From fd56a6f76c65edd63b10d36061acc53bc2be65d7 Mon Sep 17 00:00:00 2001 From: offtools Date: Fri, 25 Nov 2016 18:39:44 +0100 Subject: [PATCH 001/333] started OSC module --- lisp/modules/action_cues/osc_cue.py | 51 ++++++++++++++++++++++++++ lisp/modules/osc/__init__.py | 5 +++ lisp/modules/osc/osc.py | 32 ++++++++++++++++ lisp/modules/osc/osc_common.py | 57 +++++++++++++++++++++++++++++ lisp/modules/osc/osc_settings.py | 44 ++++++++++++++++++++++ 5 files changed, 189 insertions(+) create mode 100644 lisp/modules/action_cues/osc_cue.py create mode 100644 lisp/modules/osc/__init__.py create mode 100644 lisp/modules/osc/osc.py create mode 100644 lisp/modules/osc/osc_common.py create mode 100644 lisp/modules/osc/osc_settings.py diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py new file mode 100644 index 000000000..8ea2e0ffc --- /dev/null +++ b/lisp/modules/action_cues/osc_cue.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + + +from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP + + +from lisp.cues.cue import Cue, CueState +from lisp.ui.settings.cue_settings import CueSettingsRegistry,\ + SettingsPage + + +class OscCue(Cue): + Name = QT_TRANSLATE_NOOP('CueName', 'OSC Cue') + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + @Cue.state.getter + def state(self): + return CueState.Stop + + def __start__(self): + pass + + +class OscCueSettings(SettingsPage): + Name = QT_TRANSLATE_NOOP('CueName', 'OSC Settings') + + def __init__(self, **kwargs): + super().__init__(**kwargs) + pass + + +CueSettingsRegistry().add_item(OscCueSettings, OscCue) diff --git a/lisp/modules/osc/__init__.py b/lisp/modules/osc/__init__.py new file mode 100644 index 000000000..0f64693f9 --- /dev/null +++ b/lisp/modules/osc/__init__.py @@ -0,0 +1,5 @@ +try: + from lisp.utils import elogging + from .osc import Osc +except ImportError as error: + elogging.error(e, 'pyliblo not found', dialog=False) \ No newline at end of file diff --git a/lisp/modules/osc/osc.py b/lisp/modules/osc/osc.py new file mode 100644 index 000000000..a8c105779 --- /dev/null +++ b/lisp/modules/osc/osc.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + + +from lisp.core.module import Module +from lisp.ui.settings.app_settings import AppSettings +from lisp.modules.osc.osc_settings import OscSettings + + +class Osc(Module): + """Provide OSC I/O functionality""" + + def __init__(self): + # Register the settings widget + AppSettings.register_settings_widget(OscSettings) + diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py new file mode 100644 index 000000000..4dd3f3cfc --- /dev/null +++ b/lisp/modules/osc/osc_common.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + + +from lisp.core.singleton import ABCSingleton +from liblo import ServerThread, Address, ServerError + +from lisp.utils.configuration import config +from lisp.utils import elogging + + +class OscCommon(metaclass=ABCSingleton): + def __init__(self): + self.__srv = None + self.__listening = False + self.__callbacks = [] + + def start(self): + self.stop() + try: + self.__srv = ServerThread(config['OSC']['port']) + for cb in self.__callbacks: + self.__srv.add_method(cb[0], cb[1], cb[2]) + self.__srv.start() + self.__listening = True + except ServerError as e: + elogging.error(e, dialog=False) + + def stop(self): + if self.__listening: + self.__srv.stop() + self.__listening = False + self.__srv.free() + + def send(self, path, *args): + if self.__listening: + target = Address(config['OSC']['hostname'], config['OSC']['port']) + self.__srv.send(target, path, *args) + + def register_callback(self, path, typespec, func): + self.__callbacks.append([path, typespec, func]) diff --git a/lisp/modules/osc/osc_settings.py b/lisp/modules/osc/osc_settings.py new file mode 100644 index 000000000..4fd2558b9 --- /dev/null +++ b/lisp/modules/osc/osc_settings.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + + +from PyQt5.QtCore import QTime, QT_TRANSLATE_NOOP + + +from lisp.ui.settings.settings_page import SettingsPage + + +class OscSettings(SettingsPage): + Name = QT_TRANSLATE_NOOP('SettingsPageName', 'OSC settings') + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def get_settings(self): + conf = { + 'inport': 9001, + 'outport': 9002, + 'hostname': 'localhost', + 'feedback': True + } + + return {'OSC': conf} + + def load_settings(self, settings): + pass \ No newline at end of file From 57269d760c955e50540545996951d01d43ffb578 Mon Sep 17 00:00:00 2001 From: offtools Date: Sun, 20 Nov 2016 03:49:24 +0100 Subject: [PATCH 002/333] implemented OSC settings --- lisp/default.cfg | 11 ++++- lisp/modules/osc/osc.py | 4 ++ lisp/modules/osc/osc_settings.py | 73 ++++++++++++++++++++++++++++---- 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/lisp/default.cfg b/lisp/default.cfg index fa245a345..ffdcce9be 100644 --- a/lisp/default.cfg +++ b/lisp/default.cfg @@ -53,4 +53,13 @@ GoKey = Space [DbMeter] dBMax = 0 dbMin = -60 -dbClip = 0 \ No newline at end of file +dbClip = 0 +======= +dbClip = 0 + +[OSC] +enable = False +inport = 9000 +outport = 9001 +hostname = localhost +feedback = False diff --git a/lisp/modules/osc/osc.py b/lisp/modules/osc/osc.py index a8c105779..080bae82d 100644 --- a/lisp/modules/osc/osc.py +++ b/lisp/modules/osc/osc.py @@ -19,8 +19,10 @@ from lisp.core.module import Module +from lisp.utils.configuration import config from lisp.ui.settings.app_settings import AppSettings from lisp.modules.osc.osc_settings import OscSettings +from lisp.modules.osc.osc_common import OscCommon class Osc(Module): @@ -30,3 +32,5 @@ def __init__(self): # Register the settings widget AppSettings.register_settings_widget(OscSettings) + # if config['OSC']['enabled']: + # OscCommon().start() \ No newline at end of file diff --git a/lisp/modules/osc/osc_settings.py b/lisp/modules/osc/osc_settings.py index 4fd2558b9..335dfaf1e 100644 --- a/lisp/modules/osc/osc_settings.py +++ b/lisp/modules/osc/osc_settings.py @@ -18,9 +18,12 @@ # along with Linux Show Player. If not, see . -from PyQt5.QtCore import QTime, QT_TRANSLATE_NOOP - +from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt +from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel,\ + QCheckBox, QComboBox, QHBoxLayout, QMessageBox, QSpinBox,\ + QLineEdit +from lisp.ui.ui_utils import translate from lisp.ui.settings.settings_page import SettingsPage @@ -29,16 +32,70 @@ class OscSettings(SettingsPage): def __init__(self, **kwargs): super().__init__(**kwargs) + self.setLayout(QVBoxLayout()) + self.layout().setAlignment(Qt.AlignTop) + + self.groupBox = QGroupBox(self) + self.groupBox.setLayout(QVBoxLayout()) + self.groupBox.setTitle( + translate('OscSettings', 'OSC Settings')) + self.layout().addWidget(self.groupBox) + + self.enableModule = QCheckBox(self.groupBox) + self.enableModule.setText( + translate('OscSettings', 'enable OSC')) + self.groupBox.layout().addWidget(self.enableModule) + + self.enableFeedback = QCheckBox(self.groupBox) + self.enableFeedback.setText( + translate('OscSettings', 'enable Feedback')) + self.groupBox.layout().addWidget(self.enableFeedback) + + hbox = QHBoxLayout() + self.inportBox = QSpinBox(self) + self.inportBox.setMinimum(1000) + self.inportBox.setMaximum(99999) + hbox.layout().addWidget(self.inportBox) + label = QLabel( + translate('OscSettings', 'Input Port')) + hbox.layout().addWidget(label) + self.groupBox.layout().addLayout(hbox) + + hbox = QHBoxLayout() + self.outportBox = QSpinBox(self) + self.outportBox.setMinimum(1000) + self.outportBox.setMaximum(99999) + hbox.layout().addWidget(self.outportBox) + label = QLabel( + translate('OscSettings', 'Input Port')) + hbox.layout().addWidget(label) + self.groupBox.layout().addLayout(hbox) + + hbox = QHBoxLayout() + self.hostnameEdit = QLineEdit() + self.hostnameEdit.setText('localhost') + self.hostnameEdit.setMaximumWidth(200) + hbox.layout().addWidget(self.hostnameEdit) + label = QLabel( + translate('OscSettings', 'Hostname')) + hbox.layout().addWidget(label) + self.groupBox.layout().addLayout(hbox) def get_settings(self): conf = { - 'inport': 9001, - 'outport': 9002, - 'hostname': 'localhost', - 'feedback': True + 'enabled': str(self.enableCheck.isChecked()), + 'inport': str(self.inportBox.value()), + 'outport': str(self.outportBox.value()), + 'hostname': str(self.hostnameEdit.text()), + 'feedback': str(self.enableFeedback.isChecked()) } - return {'OSC': conf} def load_settings(self, settings): - pass \ No newline at end of file + settings = settings.get('OSC', {}) + + self.enableModule.setChecked(settings.get('enabled') == 'True') + self.enableFeedback.setChecked(settings.get('feedback') == 'True') + self.inportBox.setValue(int(settings.get('inport'))) + self.outportBox.setValue(int(settings.get('outport'))) + self.hostnameEdit.setText(settings.get('hostname')) \ No newline at end of file From a5277352cd3502b477c14da2966e774cdf96bea8 Mon Sep 17 00:00:00 2001 From: offtools Date: Sun, 20 Nov 2016 17:24:02 +0100 Subject: [PATCH 003/333] OSC implemented go, restart_all, pause_all, stop_all, settings applied to server --- lisp/modules/osc/osc.py | 4 +- lisp/modules/osc/osc_common.py | 63 ++++++++++++++++++++++++++++---- lisp/modules/osc/osc_settings.py | 52 ++++++++++++++++++++++---- 3 files changed, 102 insertions(+), 17 deletions(-) diff --git a/lisp/modules/osc/osc.py b/lisp/modules/osc/osc.py index 080bae82d..4f77155fd 100644 --- a/lisp/modules/osc/osc.py +++ b/lisp/modules/osc/osc.py @@ -32,5 +32,5 @@ def __init__(self): # Register the settings widget AppSettings.register_settings_widget(OscSettings) - # if config['OSC']['enabled']: - # OscCommon().start() \ No newline at end of file + if config['OSC']['enabled']: + OscCommon().start() \ No newline at end of file diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index 4dd3f3cfc..3eb972c6c 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -22,36 +22,83 @@ from liblo import ServerThread, Address, ServerError from lisp.utils.configuration import config +from lisp.layouts.list_layout.layout import ListLayout from lisp.utils import elogging +from lisp.ui.mainwindow import MainWindow +from lisp.application import Application +from lisp.cues.cue import Cue, CueState + + +def _go(path, args, types, src): + go = int(args[0]) + if isinstance(MainWindow().layout, ListLayout): + if go >= 1: + MainWindow().layout.go() + + +def _pause_all(path, args, types, src): + pause = int(args[0]) + for cue in Application().cue_model: + if cue.state == CueState.Running and pause >= 1: + cue.start() + + +def _restart_all(path, args, types, src): + restart = int(args[0]) + for cue in Application().cue_model: + if cue.state == CueState.Pause and restart >= 1: + cue.start() + + +def _stop_all(path, args, types, src): + stop = int(args[0]) + if stop >= 1: + for cue in Application().cue_model: + cue.stop() class OscCommon(metaclass=ABCSingleton): def __init__(self): self.__srv = None self.__listening = False - self.__callbacks = [] + + # TODO: static paths and callbacks, find smarter way + self.__callbacks = [ + ['/lisp/go', 'i', _go], + ['/lisp/pause', 'i', _pause_all], + ['/lisp/start', 'i', _restart_all], + ['/lisp/stop', 'i', _stop_all], + ] def start(self): - self.stop() + if self.__listening: + return + try: - self.__srv = ServerThread(config['OSC']['port']) + self.__srv = ServerThread(int(config['OSC']['inport'])) for cb in self.__callbacks: self.__srv.add_method(cb[0], cb[1], cb[2]) self.__srv.start() self.__listening = True + elogging.info('OSC: Server started ' + self.__srv.url, dialog=False) except ServerError as e: elogging.error(e, dialog=False) def stop(self): - if self.__listening: - self.__srv.stop() - self.__listening = False - self.__srv.free() + if self.__srv: + if self.__listening: + self.__srv.stop() + self.__listening = False + self.__srv.free() + elogging.info('OSC: Server stopped', dialog=False) def send(self, path, *args): if self.__listening: - target = Address(config['OSC']['hostname'], config['OSC']['port']) + target = Address(config['OSC']['hostname'], int(config['OSC']['port'])) self.__srv.send(target, path, *args) def register_callback(self, path, typespec, func): self.__callbacks.append([path, typespec, func]) + + def activate_feedback(self, feedback): + pass \ No newline at end of file diff --git a/lisp/modules/osc/osc_settings.py b/lisp/modules/osc/osc_settings.py index 335dfaf1e..1faf6e9d1 100644 --- a/lisp/modules/osc/osc_settings.py +++ b/lisp/modules/osc/osc_settings.py @@ -24,7 +24,9 @@ QLineEdit from lisp.ui.ui_utils import translate +from lisp.utils.configuration import config from lisp.ui.settings.settings_page import SettingsPage +from lisp.modules.osc.osc_common import OscCommon class OscSettings(SettingsPage): @@ -55,35 +57,71 @@ def __init__(self, **kwargs): self.inportBox = QSpinBox(self) self.inportBox.setMinimum(1000) self.inportBox.setMaximum(99999) - hbox.layout().addWidget(self.inportBox) label = QLabel( - translate('OscSettings', 'Input Port')) + translate('OscSettings', 'Input Port:')) hbox.layout().addWidget(label) + hbox.layout().addWidget(self.inportBox) self.groupBox.layout().addLayout(hbox) hbox = QHBoxLayout() self.outportBox = QSpinBox(self) self.outportBox.setMinimum(1000) self.outportBox.setMaximum(99999) - hbox.layout().addWidget(self.outportBox) label = QLabel( - translate('OscSettings', 'Input Port')) + translate('OscSettings', 'Input Port:')) hbox.layout().addWidget(label) + hbox.layout().addWidget(self.outportBox) self.groupBox.layout().addLayout(hbox) hbox = QHBoxLayout() self.hostnameEdit = QLineEdit() self.hostnameEdit.setText('localhost') self.hostnameEdit.setMaximumWidth(200) - hbox.layout().addWidget(self.hostnameEdit) label = QLabel( - translate('OscSettings', 'Hostname')) + translate('OscSettings', 'Hostname:')) hbox.layout().addWidget(label) + hbox.layout().addWidget(self.hostnameEdit) self.groupBox.layout().addLayout(hbox) + self.enableModule.stateChanged.connect(self.activate_module, Qt.QueuedConnection) + self.enableFeedback.stateChanged.connect(self.activate_feedback, Qt.QueuedConnection) + self.inportBox.valueChanged.connect(self.change_inport, Qt.QueuedConnection) + self.outportBox.valueChanged.connect(self.change_outport, Qt.QueuedConnection) + self.hostnameEdit.textChanged.connect(self.change_hostname, Qt.QueuedConnection) + + def activate_module(self): + if self.enableModule.isChecked(): + # start osc server + OscCommon().start() + # enable OSC Module in Settings + config.set('OSC', 'enabled', 'True') + else: + # stop osc server + OscCommon().stop() + # disable OSC Module in Settings + config.set('OSC', 'enabled', 'False') + + def activate_feedback(self): + OscCommon().activate_feedback(self.enableFeedback.isChecked()) + + def change_inport(self): + port = self.inportBox.value() + if str(port) != config.get('OSC', 'inport'): + config.set('OSC', 'inport', str(port)) + if self.enableModule.isChecked(): + self.enableModule.setChecked(False) + + def change_outport(self): + port = self.outportBox.value() + config.set('OSC', 'outport', str(port)) + + def change_hostname(self): + hostname = self.hostnameEdit.text() + config.set('OSC', 'hostname', hostname) + def get_settings(self): conf = { - 'enabled': str(self.enableCheck.isChecked()), + 'enabled': str(self.enableModule.isChecked()), 'inport': str(self.inportBox.value()), 'outport': str(self.outportBox.value()), 'hostname': str(self.hostnameEdit.text()), From 76d19f7217b7b96f8c45113145614d1a217f1b89 Mon Sep 17 00:00:00 2001 From: offtools Date: Sun, 20 Nov 2016 17:36:36 +0100 Subject: [PATCH 004/333] OscMessage type Enum for OscCue --- lisp/modules/action_cues/osc_cue.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 8ea2e0ffc..1ee9363f3 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -17,15 +17,24 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +from enum import Enum from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP - +from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, QLabel, \ + QComboBox, QSpinBox, QFrame from lisp.cues.cue import Cue, CueState from lisp.ui.settings.cue_settings import CueSettingsRegistry,\ SettingsPage +class OscMessageType(Enum): + Int = 'Integer', + Float = 'Float', + Bool = 'Bool', + String = 'String' + + class OscCue(Cue): Name = QT_TRANSLATE_NOOP('CueName', 'OSC Cue') @@ -45,7 +54,22 @@ class OscCueSettings(SettingsPage): def __init__(self, **kwargs): super().__init__(**kwargs) + self.setLayout(QVBoxLayout()) + self.layout().setAlignment(Qt.AlignTop) + + self.msgGroup = QGroupBox(self) + self.msgGroup.setLayout(QGridLayout()) + self.layout().addWidget(self.msgGroup) + + self.retranslateUi() + + def retranslateUi(self): + self.msgGroup.setTitle(translate('OscCue', 'OSC Message')) + + def get_settings(self): pass + def load_settings(self, settings): + pass CueSettingsRegistry().add_item(OscCueSettings, OscCue) From 08a0272ee0fea38691db01bc3bffb98dfcf43fa4 Mon Sep 17 00:00:00 2001 From: offtools Date: Tue, 22 Nov 2016 01:19:49 +0100 Subject: [PATCH 005/333] added OscCue UI elements --- lisp/modules/action_cues/osc_cue.py | 72 ++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 1ee9363f3..25743e9f1 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -20,12 +20,15 @@ from enum import Enum from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, QLabel, \ - QComboBox, QSpinBox, QFrame +from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, \ + QTableView, QTableWidget, QHeaderView, QPushButton +from lisp.ui.qmodels import SimpleTableModel +from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate from lisp.cues.cue import Cue, CueState from lisp.ui.settings.cue_settings import CueSettingsRegistry,\ SettingsPage +from lisp.ui.ui_utils import translate class OscMessageType(Enum): @@ -57,14 +60,37 @@ def __init__(self, **kwargs): self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) - self.msgGroup = QGroupBox(self) - self.msgGroup.setLayout(QGridLayout()) - self.layout().addWidget(self.msgGroup) + self.oscGroup = QGroupBox(self) + self.oscGroup.setLayout(QGridLayout()) + self.layout().addWidget(self.oscGroup) + + self.oscModel = SimpleTableModel([ + translate('ControllerMidiSettings', 'Type'), + translate('ControllerMidiSettings', 'Argument')]) + + self.oscView = OscView(parent=self.oscGroup) + self.oscView.setModel(self.oscModel) + self.oscGroup.layout().addWidget(self.oscView, 0, 0, 1, 2) + + self.addButton = QPushButton(self.oscGroup) + self.addButton.clicked.connect(self.__new_message) + self.oscGroup.layout().addWidget(self.addButton, 1, 0) + + self.removeButton = QPushButton(self.oscGroup) + self.removeButton.clicked.connect(self.__remove_message) + self.oscGroup.layout().addWidget(self.removeButton, 1, 1) + + self.oscCapture = QPushButton(self.oscGroup) + self.oscCapture.clicked.connect(self.capture_message) + self.oscGroup.layout().addWidget(self.oscCapture, 2, 0) self.retranslateUi() def retranslateUi(self): - self.msgGroup.setTitle(translate('OscCue', 'OSC Message')) + self.oscGroup.setTitle(translate('OscCue', 'OSC Message')) + self.addButton.setText(translate('OscCue', 'Add')) + self.removeButton.setText(translate('OscCue', 'Remove')) + self.oscCapture.setText(translate('OscCue', 'Capture')) def get_settings(self): pass @@ -72,4 +98,38 @@ def get_settings(self): def load_settings(self, settings): pass + def __new_message(self): + pass + + def __remove_message(self): + pass + + def capture_message(self): + pass + + +class OscView(QTableView): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self.delegates = [ComboBoxDelegate(options=OscMessageType, + tr_context='OscMessageType'), + LineEditDelegate()] + + self.setSelectionBehavior(QTableWidget.SelectRows) + self.setSelectionMode(QTableView.SingleSelection) + + self.setShowGrid(False) + self.setAlternatingRowColors(True) + + self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + self.horizontalHeader().setHighlightSections(False) + + self.verticalHeader().sectionResizeMode(QHeaderView.Fixed) + self.verticalHeader().setDefaultSectionSize(24) + self.verticalHeader().setHighlightSections(False) + + for column, delegate in enumerate(self.delegates): + self.setItemDelegateForColumn(column, delegate) + CueSettingsRegistry().add_item(OscCueSettings, OscCue) From 0b9d16a07aba82d82ec1f8e9db1ddbdbc7a129df Mon Sep 17 00:00:00 2001 From: offtools Date: Wed, 23 Nov 2016 21:25:03 +0100 Subject: [PATCH 006/333] implmented OSC Cue --- lisp/modules/action_cues/osc_cue.py | 92 ++++++++++++++++++++--------- lisp/modules/osc/osc_common.py | 2 +- lisp/modules/osc/osc_settings.py | 2 +- 3 files changed, 66 insertions(+), 30 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 25743e9f1..548471d3f 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -17,30 +17,38 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +# TODO: proper Test for Format, add Error Dialog, Test Cue Button in Settings + from enum import Enum from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, \ - QTableView, QTableWidget, QHeaderView, QPushButton + QTableView, QTableWidget, QHeaderView, QPushButton, QLabel, \ + QLineEdit from lisp.ui.qmodels import SimpleTableModel +from lisp.core.has_properties import Property from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate from lisp.cues.cue import Cue, CueState from lisp.ui.settings.cue_settings import CueSettingsRegistry,\ SettingsPage +from lisp.modules.osc.osc_common import OscCommon from lisp.ui.ui_utils import translate class OscMessageType(Enum): - Int = 'Integer', - Float = 'Float', - Bool = 'Bool', + Int = 'Integer' + Float = 'Float' + Bool = 'Bool' String = 'String' class OscCue(Cue): Name = QT_TRANSLATE_NOOP('CueName', 'OSC Cue') + path = Property(default='') + args = Property(default=[]) + def __init__(self, **kwargs): super().__init__(**kwargs) @@ -49,11 +57,31 @@ def state(self): return CueState.Stop def __start__(self): - pass + arg_list = [] + for arg in self.args: + conv_arg = self.__convert_arg(arg[0], arg[1]) + if conv_arg is not None: + arg_list.append(conv_arg) + OscCommon().send(self.path, *arg_list) + + def __convert_arg(self, t, arg): + try: + if t == OscMessageType.Int.value: + return int(arg) + elif t == OscMessageType.Float.value: + return float(arg) + elif t == OscMessageType.Bool.value: + return bool(arg) + elif t == OscMessageType.String.value: + return str(arg) + else: + return None + except ValueError: + return None class OscCueSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('CueName', 'OSC Settings') + Name = QT_TRANSLATE_NOOP('Cue Name', 'OSC Settings') def __init__(self, **kwargs): super().__init__(**kwargs) @@ -64,25 +92,27 @@ def __init__(self, **kwargs): self.oscGroup.setLayout(QGridLayout()) self.layout().addWidget(self.oscGroup) + self.pathLabel = QLabel() + self.oscGroup.layout().addWidget(self.pathLabel, 0, 0) + + self.pathEdit = QLineEdit() + self.oscGroup.layout().addWidget(self.pathEdit, 1, 0, 1, 2) + self.oscModel = SimpleTableModel([ - translate('ControllerMidiSettings', 'Type'), - translate('ControllerMidiSettings', 'Argument')]) + translate('Osc Cue', 'Type'), + translate('Osc Cue', 'Argument')]) self.oscView = OscView(parent=self.oscGroup) self.oscView.setModel(self.oscModel) - self.oscGroup.layout().addWidget(self.oscView, 0, 0, 1, 2) + self.oscGroup.layout().addWidget(self.oscView, 2, 0, 1, 2) self.addButton = QPushButton(self.oscGroup) - self.addButton.clicked.connect(self.__new_message) - self.oscGroup.layout().addWidget(self.addButton, 1, 0) + self.addButton.clicked.connect(self.__new_argument) + self.oscGroup.layout().addWidget(self.addButton, 3, 0) self.removeButton = QPushButton(self.oscGroup) - self.removeButton.clicked.connect(self.__remove_message) - self.oscGroup.layout().addWidget(self.removeButton, 1, 1) - - self.oscCapture = QPushButton(self.oscGroup) - self.oscCapture.clicked.connect(self.capture_message) - self.oscGroup.layout().addWidget(self.oscCapture, 2, 0) + self.removeButton.clicked.connect(self.__remove_argument) + self.oscGroup.layout().addWidget(self.removeButton, 3, 1) self.retranslateUi() @@ -90,29 +120,35 @@ def retranslateUi(self): self.oscGroup.setTitle(translate('OscCue', 'OSC Message')) self.addButton.setText(translate('OscCue', 'Add')) self.removeButton.setText(translate('OscCue', 'Remove')) - self.oscCapture.setText(translate('OscCue', 'Capture')) + self.pathLabel.setText(translate('OscCue', 'OSC Path: (example: "/path/to/something")')) def get_settings(self): - pass + oscmsg = {'path': self.pathEdit.text(), + 'args': [row for row in self.oscModel.rows]} + return oscmsg def load_settings(self, settings): - pass - - def __new_message(self): - pass + if 'path' in settings: + path = settings.get('path', '') + self.pathEdit.setText(path) + if 'args' in settings: + args = settings.get('args', '') + for row in args: + self.oscModel.appendRow(row[0], row[1]) - def __remove_message(self): - pass + def __new_argument(self): + self.oscModel.appendRow(OscMessageType.Int.value, 'argument') - def capture_message(self): - pass + def __remove_argument(self): + if self.oscModel.rowCount(): + self.oscModel.removeRow(self.oscView.currentIndex().row()) class OscView(QTableView): def __init__(self, **kwargs): super().__init__(**kwargs) - self.delegates = [ComboBoxDelegate(options=OscMessageType, + self.delegates = [ComboBoxDelegate(options=[i.value for i in OscMessageType], tr_context='OscMessageType'), LineEditDelegate()] diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index 3eb972c6c..3fd198302 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -94,7 +94,7 @@ def stop(self): def send(self, path, *args): if self.__listening: - target = Address(config['OSC']['hostname'], int(config['OSC']['port'])) + target = Address(config['OSC']['hostname'], int(config['OSC']['outport'])) self.__srv.send(target, path, *args) def register_callback(self, path, typespec, func): diff --git a/lisp/modules/osc/osc_settings.py b/lisp/modules/osc/osc_settings.py index 1faf6e9d1..e3ce721ef 100644 --- a/lisp/modules/osc/osc_settings.py +++ b/lisp/modules/osc/osc_settings.py @@ -68,7 +68,7 @@ def __init__(self, **kwargs): self.outportBox.setMinimum(1000) self.outportBox.setMaximum(99999) label = QLabel( - translate('OscSettings', 'Input Port:')) + translate('OscSettings', 'Output Port:')) hbox.layout().addWidget(label) hbox.layout().addWidget(self.outportBox) self.groupBox.layout().addLayout(hbox) From 96ee3fb49fc5c57e39671b4ea80300703df5e33a Mon Sep 17 00:00:00 2001 From: offtools Date: Fri, 25 Nov 2016 17:36:17 +0100 Subject: [PATCH 007/333] updated TODO --- lisp/modules/action_cues/osc_cue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 548471d3f..e8f29835b 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -# TODO: proper Test for Format, add Error Dialog, Test Cue Button in Settings +# TODO: proper Test for Format, add Error Dialog, Cue Test-Button in Settings from enum import Enum From 5eb283d0bca531f47991213daed83b729e06b5c2 Mon Sep 17 00:00:00 2001 From: offtools Date: Fri, 25 Nov 2016 17:36:48 +0100 Subject: [PATCH 008/333] implemented OSC Protocol --- lisp/modules/osc/osc_common.py | 71 ++++++--- lisp/plugins/controller/protocols/osc.py | 185 +++++++++++++++++++++++ 2 files changed, 237 insertions(+), 19 deletions(-) create mode 100644 lisp/plugins/controller/protocols/osc.py diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index 3fd198302..0846c0d0f 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +from collections import deque from lisp.core.singleton import ABCSingleton from liblo import ServerThread, Address, ServerError @@ -26,50 +27,67 @@ from lisp.utils import elogging from lisp.ui.mainwindow import MainWindow from lisp.application import Application -from lisp.cues.cue import Cue, CueState +from lisp.cues.cue import CueState +from lisp.core.signal import Signal +# decorator for OSC callback (pushing messages to log, emits message_event) +def osc_handler(func): + def func_wrapper(path, args, types, src): + func(path, args, types, src) + OscCommon().push_log(path, args, types, src) + OscCommon().new_message.emit(path, args, types, src) + return func_wrapper + + +@osc_handler def _go(path, args, types, src): - go = int(args[0]) if isinstance(MainWindow().layout, ListLayout): - if go >= 1: - MainWindow().layout.go() + MainWindow().layout.go() +@osc_handler def _pause_all(path, args, types, src): - pause = int(args[0]) for cue in Application().cue_model: - if cue.state == CueState.Running and pause >= 1: - cue.start() + if cue.state == CueState.Running: + cue.pause() +@osc_handler def _restart_all(path, args, types, src): - restart = int(args[0]) for cue in Application().cue_model: - if cue.state == CueState.Pause and restart >= 1: + if cue.state == CueState.Pause: cue.start() +@osc_handler def _stop_all(path, args, types, src): - stop = int(args[0]) - if stop >= 1: - for cue in Application().cue_model: - cue.stop() + for cue in Application().cue_model: + cue.stop() class OscCommon(metaclass=ABCSingleton): def __init__(self): self.__srv = None self.__listening = False + self.__log = deque([], 10) + self.new_message = Signal() # TODO: static paths and callbacks, find smarter way self.__callbacks = [ - ['/lisp/go', 'i', _go], - ['/lisp/pause', 'i', _pause_all], - ['/lisp/start', 'i', _restart_all], - ['/lisp/stop', 'i', _stop_all], + ['/lisp/go', '', _go], + ['/lisp/pause', '', _pause_all], + ['/lisp/start', '', _restart_all], + ['/lisp/stop', '', _stop_all], + [None, None, self.__new_message] ] + def push_log(self, path, args, types, src, success=True): + self.__log.append([path, args, types, src, success]) + + def get_log(self): + return self.__log + def start(self): if self.__listening: return @@ -92,13 +110,28 @@ def stop(self): self.__srv.free() elogging.info('OSC: Server stopped', dialog=False) + @property + def listening(self): + return self.__listening + def send(self, path, *args): if self.__listening: target = Address(config['OSC']['hostname'], int(config['OSC']['outport'])) self.__srv.send(target, path, *args) - def register_callback(self, path, typespec, func): - self.__callbacks.append([path, typespec, func]) + def __new_message(self, path, args, types, src): + self.push_log(path, args, types, src, False) + self.new_message.emit(path, args, types, src) + # elogging.warning('OscCommon: unknown message received: {0} {1} {2}'.format(path, types, args), dialog=False) + + # def register_callback(self, path, typespec, func): + # # insert into callback list before callback, otherwise its ignored + # self.__callbacks.insert(-1, [path, typespec, func]) + # + # # fallback needs to be the registered callback + # self.__srv.del_method(None, None) + # self.__srv.add_method(path, typespec, func) + # self.__srv.add_method(None, None, self.__new_message) def activate_feedback(self, feedback): pass \ No newline at end of file diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py new file mode 100644 index 000000000..47f4bc197 --- /dev/null +++ b/lisp/plugins/controller/protocols/osc.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP +from PyQt5.QtWidgets import QGroupBox, QPushButton, QComboBox, QVBoxLayout, \ + QMessageBox, QTableView, QTableWidget, QHeaderView, QGridLayout, QLabel, \ + QDialog, QDialogButtonBox + +from lisp.modules import check_module +from lisp.application import MainWindow +from lisp.modules.osc.osc_common import OscCommon +from lisp.plugins.controller.protocols.protocol import Protocol +from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate +from lisp.ui.qmodels import SimpleTableModel +from lisp.ui.settings.settings_page import CueSettingsPage +from lisp.ui.ui_utils import translate + + +class Osc(Protocol): + def __init__(self): + super().__init__() + + if check_module('osc') and OscCommon().listening: + OscCommon().new_message.connect(self.__new_message) + + def __new_message(self, path, args, types, src): + self.protocol_event.emit(Osc.key_from_message(path, types)) + + @staticmethod + def key_from_message(path, types): + if len(types): + return '{0} {1}'.format(path, types) + else: + return path + + @staticmethod + def from_key(message_str): + m = message_str.split(' ') + return m[0], '' if len(m) < 2 else m[1] + + +class OscSettings(CueSettingsPage): + Name = QT_TRANSLATE_NOOP('SettingsPageName', 'OSC Controls') + + def __init__(self, cue_class, **kwargs): + super().__init__(cue_class, **kwargs) + self.setLayout(QVBoxLayout()) + self.layout().setAlignment(Qt.AlignTop) + + self.oscGroup = QGroupBox(self) + self.oscGroup.setTitle(translate('ControllerOscSettings', 'OSC')) + self.oscGroup.setLayout(QGridLayout()) + self.layout().addWidget(self.oscGroup) + + self.oscModel = SimpleTableModel([ + translate('ControllerOscSettings', 'Path'), + translate('ControllerOscSettings', 'Types'), + translate('ControllerOscSettings', 'Actions')]) + + self.OscView = OscView(cue_class, parent=self.oscGroup) + self.OscView.setModel(self.oscModel) + self.oscGroup.layout().addWidget(self.OscView, 0, 0, 1, 2) + + self.addButton = QPushButton(self.oscGroup) + self.addButton.clicked.connect(self.__new_message) + self.oscGroup.layout().addWidget(self.addButton, 1, 0) + + self.removeButton = QPushButton(self.oscGroup) + self.removeButton.clicked.connect(self.__remove_message) + self.oscGroup.layout().addWidget(self.removeButton, 1, 1) + + self.oscCapture = QPushButton(self.oscGroup) + self.oscCapture.clicked.connect(self.capture_message) + self.oscGroup.layout().addWidget(self.oscCapture, 2, 0) + + self.captureDialog = QDialog() + self.captureDialog.setWindowTitle(translate('ControllerOscSettings', + 'OSC Capture')) + self.captureDialog.setModal(True) + self.captureLabel = QLabel('...') + self.captureDialog.setLayout(QVBoxLayout()) + self.captureDialog.layout().addWidget(self.captureLabel) + + self.buttonBox = QDialogButtonBox( + QDialogButtonBox.Ok | QDialogButtonBox.Cancel, + Qt.Horizontal, self.captureDialog) + self.buttonBox.accepted.connect(self.captureDialog.accept) + self.buttonBox.rejected.connect(self.captureDialog.reject) + self.captureDialog.layout().addWidget(self.buttonBox) + + self.capturedMessage = {'path': None, 'types': None} + + self.retranslateUi() + + self._default_action = self._cue_class.CueActions[0].name + + def retranslateUi(self): + self.addButton.setText(translate('ControllerOscSettings', 'Add')) + self.removeButton.setText(translate('ControllerOscSettings', 'Remove')) + self.oscCapture.setText(translate('ControllerOscSettings', 'Capture')) + + def enable_check(self, enabled): + self.oscGroup.setCheckable(enabled) + self.oscGroup.setChecked(False) + + def get_settings(self): + settings = {} + + messages = [] + + for row in self.oscModel.rows: + message = Osc.key_from_message(row[0], row[1]) + messages.append((message, row[-1])) + + if messages: + settings['osc'] = messages + + return settings + + def load_settings(self, settings): + if 'osc' in settings: + for options in settings['osc']: + path, types = Osc.from_key(options[0]) + self.oscModel.appendRow(path, types, options[1]) + + def capture_message(self): + OscCommon().new_message.connect(self.__show_message) + result = self.captureDialog.exec() + if result == QDialog.Accepted: + self.oscModel.appendRow(self.capturedMessage['path'], self.capturedMessage['types'], self._default_action) + OscCommon().new_message.disconnect(self.__show_message) + + def __show_message(self, path, args, types, src): + self.capturedMessage['path'] = path + self.capturedMessage['types'] = types + self.captureLabel.setText('{0} "{1}" {2}'.format(path, types, args)) + + def __new_message(self): + self.oscModel.appendRow('', '', self._default_action) + + def __remove_message(self): + self.oscModel.removeRow(self.OscView.currentIndex().row()) + + +class OscView(QTableView): + def __init__(self, cue_class, **kwargs): + super().__init__(**kwargs) + + cue_actions = [action.name for action in cue_class.CueActions] + self.delegates = [LineEditDelegate(), + LineEditDelegate(), + ComboBoxDelegate(options=cue_actions, + tr_context='CueAction')] + + self.setSelectionBehavior(QTableWidget.SelectRows) + self.setSelectionMode(QTableView.SingleSelection) + + self.setShowGrid(False) + self.setAlternatingRowColors(True) + + self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + self.horizontalHeader().setHighlightSections(False) + + self.verticalHeader().sectionResizeMode(QHeaderView.Fixed) + self.verticalHeader().setDefaultSectionSize(24) + self.verticalHeader().setHighlightSections(False) + + for column, delegate in enumerate(self.delegates): + self.setItemDelegateForColumn(column, delegate) From 0ad77c040d07dbff8425f59e68a34ab17ac591d7 Mon Sep 17 00:00:00 2001 From: offtools Date: Fri, 25 Nov 2016 18:22:01 +0100 Subject: [PATCH 009/333] OSC import cleanups --- lisp/modules/osc/osc_settings.py | 3 +-- lisp/plugins/controller/protocols/osc.py | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lisp/modules/osc/osc_settings.py b/lisp/modules/osc/osc_settings.py index e3ce721ef..e3a0dc717 100644 --- a/lisp/modules/osc/osc_settings.py +++ b/lisp/modules/osc/osc_settings.py @@ -20,8 +20,7 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel,\ - QCheckBox, QComboBox, QHBoxLayout, QMessageBox, QSpinBox,\ - QLineEdit + QCheckBox, QHBoxLayout, QSpinBox, QLineEdit from lisp.ui.ui_utils import translate from lisp.utils.configuration import config diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 47f4bc197..b8ab99ae0 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -18,12 +18,11 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QPushButton, QComboBox, QVBoxLayout, \ - QMessageBox, QTableView, QTableWidget, QHeaderView, QGridLayout, QLabel, \ +from PyQt5.QtWidgets import QGroupBox, QPushButton, QVBoxLayout, \ + QTableView, QTableWidget, QHeaderView, QGridLayout, QLabel, \ QDialog, QDialogButtonBox from lisp.modules import check_module -from lisp.application import MainWindow from lisp.modules.osc.osc_common import OscCommon from lisp.plugins.controller.protocols.protocol import Protocol from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate From 08366b216d22f700c46aac714a8980bb727a8df9 Mon Sep 17 00:00:00 2001 From: offtools Date: Sun, 20 Nov 2016 03:49:24 +0100 Subject: [PATCH 010/333] implemented OSC settings --- lisp/default.cfg | 4 +++ lisp/modules/osc/osc.py | 4 +-- lisp/modules/osc/osc_settings.py | 56 ++++++-------------------------- 3 files changed, 16 insertions(+), 48 deletions(-) diff --git a/lisp/default.cfg b/lisp/default.cfg index ffdcce9be..5999cac35 100644 --- a/lisp/default.cfg +++ b/lisp/default.cfg @@ -63,3 +63,7 @@ inport = 9000 outport = 9001 hostname = localhost feedback = False + +[Timecode] +enabled = False +format = FILM diff --git a/lisp/modules/osc/osc.py b/lisp/modules/osc/osc.py index 4f77155fd..9af008ad2 100644 --- a/lisp/modules/osc/osc.py +++ b/lisp/modules/osc/osc.py @@ -32,5 +32,5 @@ def __init__(self): # Register the settings widget AppSettings.register_settings_widget(OscSettings) - if config['OSC']['enabled']: - OscCommon().start() \ No newline at end of file + # if config['OSC']['enabled']: + # OscCommon().start() diff --git a/lisp/modules/osc/osc_settings.py b/lisp/modules/osc/osc_settings.py index e3a0dc717..0ce2673dc 100644 --- a/lisp/modules/osc/osc_settings.py +++ b/lisp/modules/osc/osc_settings.py @@ -20,10 +20,10 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel,\ - QCheckBox, QHBoxLayout, QSpinBox, QLineEdit + QCheckBox, QComboBox, QHBoxLayout, QMessageBox, QSpinBox,\ + QLineEdit from lisp.ui.ui_utils import translate -from lisp.utils.configuration import config from lisp.ui.settings.settings_page import SettingsPage from lisp.modules.osc.osc_common import OscCommon @@ -56,71 +56,35 @@ def __init__(self, **kwargs): self.inportBox = QSpinBox(self) self.inportBox.setMinimum(1000) self.inportBox.setMaximum(99999) + hbox.layout().addWidget(self.inportBox) label = QLabel( - translate('OscSettings', 'Input Port:')) + translate('OscSettings', 'Input Port')) hbox.layout().addWidget(label) - hbox.layout().addWidget(self.inportBox) self.groupBox.layout().addLayout(hbox) hbox = QHBoxLayout() self.outportBox = QSpinBox(self) self.outportBox.setMinimum(1000) self.outportBox.setMaximum(99999) + hbox.layout().addWidget(self.outportBox) label = QLabel( - translate('OscSettings', 'Output Port:')) + translate('OscSettings', 'Input Port')) hbox.layout().addWidget(label) - hbox.layout().addWidget(self.outportBox) self.groupBox.layout().addLayout(hbox) hbox = QHBoxLayout() self.hostnameEdit = QLineEdit() self.hostnameEdit.setText('localhost') self.hostnameEdit.setMaximumWidth(200) + hbox.layout().addWidget(self.hostnameEdit) label = QLabel( - translate('OscSettings', 'Hostname:')) + translate('OscSettings', 'Hostname')) hbox.layout().addWidget(label) - hbox.layout().addWidget(self.hostnameEdit) self.groupBox.layout().addLayout(hbox) - self.enableModule.stateChanged.connect(self.activate_module, Qt.QueuedConnection) - self.enableFeedback.stateChanged.connect(self.activate_feedback, Qt.QueuedConnection) - self.inportBox.valueChanged.connect(self.change_inport, Qt.QueuedConnection) - self.outportBox.valueChanged.connect(self.change_outport, Qt.QueuedConnection) - self.hostnameEdit.textChanged.connect(self.change_hostname, Qt.QueuedConnection) - - def activate_module(self): - if self.enableModule.isChecked(): - # start osc server - OscCommon().start() - # enable OSC Module in Settings - config.set('OSC', 'enabled', 'True') - else: - # stop osc server - OscCommon().stop() - # disable OSC Module in Settings - config.set('OSC', 'enabled', 'False') - - def activate_feedback(self): - OscCommon().activate_feedback(self.enableFeedback.isChecked()) - - def change_inport(self): - port = self.inportBox.value() - if str(port) != config.get('OSC', 'inport'): - config.set('OSC', 'inport', str(port)) - if self.enableModule.isChecked(): - self.enableModule.setChecked(False) - - def change_outport(self): - port = self.outportBox.value() - config.set('OSC', 'outport', str(port)) - - def change_hostname(self): - hostname = self.hostnameEdit.text() - config.set('OSC', 'hostname', hostname) - def get_settings(self): conf = { - 'enabled': str(self.enableModule.isChecked()), + 'enabled': str(self.enableCheck.isChecked()), 'inport': str(self.inportBox.value()), 'outport': str(self.outportBox.value()), 'hostname': str(self.hostnameEdit.text()), @@ -135,4 +99,4 @@ def load_settings(self, settings): self.enableFeedback.setChecked(settings.get('feedback') == 'True') self.inportBox.setValue(int(settings.get('inport'))) self.outportBox.setValue(int(settings.get('outport'))) - self.hostnameEdit.setText(settings.get('hostname')) \ No newline at end of file + self.hostnameEdit.setText(settings.get('hostname')) From 4831aa440cf797a12dc25ee9761f991bba05cb15 Mon Sep 17 00:00:00 2001 From: offtools Date: Sun, 20 Nov 2016 17:24:02 +0100 Subject: [PATCH 011/333] OSC implemented go, restart_all, pause_all, stop_all, settings applied to server --- lisp/modules/osc/osc.py | 4 +- lisp/modules/osc/osc_common.py | 75 +++++++++----------------------- lisp/modules/osc/osc_settings.py | 51 +++++++++++++++++++--- 3 files changed, 67 insertions(+), 63 deletions(-) diff --git a/lisp/modules/osc/osc.py b/lisp/modules/osc/osc.py index 9af008ad2..5af26598b 100644 --- a/lisp/modules/osc/osc.py +++ b/lisp/modules/osc/osc.py @@ -32,5 +32,5 @@ def __init__(self): # Register the settings widget AppSettings.register_settings_widget(OscSettings) - # if config['OSC']['enabled']: - # OscCommon().start() + if config['OSC']['enabled']: + OscCommon().start() diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index 0846c0d0f..82989fb61 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -17,7 +17,6 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from collections import deque from lisp.core.singleton import ABCSingleton from liblo import ServerThread, Address, ServerError @@ -27,67 +26,50 @@ from lisp.utils import elogging from lisp.ui.mainwindow import MainWindow from lisp.application import Application -from lisp.cues.cue import CueState -from lisp.core.signal import Signal +from lisp.cues.cue import Cue, CueState -# decorator for OSC callback (pushing messages to log, emits message_event) -def osc_handler(func): - def func_wrapper(path, args, types, src): - func(path, args, types, src) - OscCommon().push_log(path, args, types, src) - OscCommon().new_message.emit(path, args, types, src) - return func_wrapper - - -@osc_handler def _go(path, args, types, src): + go = int(args[0]) if isinstance(MainWindow().layout, ListLayout): - MainWindow().layout.go() + if go >= 1: + MainWindow().layout.go() -@osc_handler def _pause_all(path, args, types, src): + pause = int(args[0]) for cue in Application().cue_model: - if cue.state == CueState.Running: - cue.pause() + if cue.state == CueState.Running and pause >= 1: + cue.start() -@osc_handler def _restart_all(path, args, types, src): + restart = int(args[0]) for cue in Application().cue_model: - if cue.state == CueState.Pause: + if cue.state == CueState.Pause and restart >= 1: cue.start() -@osc_handler def _stop_all(path, args, types, src): - for cue in Application().cue_model: - cue.stop() + stop = int(args[0]) + if stop >= 1: + for cue in Application().cue_model: + cue.stop() class OscCommon(metaclass=ABCSingleton): def __init__(self): self.__srv = None self.__listening = False - self.__log = deque([], 10) - self.new_message = Signal() # TODO: static paths and callbacks, find smarter way self.__callbacks = [ - ['/lisp/go', '', _go], - ['/lisp/pause', '', _pause_all], - ['/lisp/start', '', _restart_all], - ['/lisp/stop', '', _stop_all], - [None, None, self.__new_message] + ['/lisp/go', 'i', _go], + ['/lisp/pause', 'i', _pause_all], + ['/lisp/start', 'i', _restart_all], + ['/lisp/stop', 'i', _stop_all], ] - def push_log(self, path, args, types, src, success=True): - self.__log.append([path, args, types, src, success]) - - def get_log(self): - return self.__log - def start(self): if self.__listening: return @@ -110,28 +92,13 @@ def stop(self): self.__srv.free() elogging.info('OSC: Server stopped', dialog=False) - @property - def listening(self): - return self.__listening - def send(self, path, *args): if self.__listening: - target = Address(config['OSC']['hostname'], int(config['OSC']['outport'])) + target = Address(config['OSC']['hostname'], int(config['OSC']['port'])) self.__srv.send(target, path, *args) - def __new_message(self, path, args, types, src): - self.push_log(path, args, types, src, False) - self.new_message.emit(path, args, types, src) - # elogging.warning('OscCommon: unknown message received: {0} {1} {2}'.format(path, types, args), dialog=False) - - # def register_callback(self, path, typespec, func): - # # insert into callback list before callback, otherwise its ignored - # self.__callbacks.insert(-1, [path, typespec, func]) - # - # # fallback needs to be the registered callback - # self.__srv.del_method(None, None) - # self.__srv.add_method(path, typespec, func) - # self.__srv.add_method(None, None, self.__new_message) + def register_callback(self, path, typespec, func): + self.__callbacks.append([path, typespec, func]) def activate_feedback(self, feedback): - pass \ No newline at end of file + pass diff --git a/lisp/modules/osc/osc_settings.py b/lisp/modules/osc/osc_settings.py index 0ce2673dc..261053a78 100644 --- a/lisp/modules/osc/osc_settings.py +++ b/lisp/modules/osc/osc_settings.py @@ -24,6 +24,7 @@ QLineEdit from lisp.ui.ui_utils import translate +from lisp.utils.configuration import config from lisp.ui.settings.settings_page import SettingsPage from lisp.modules.osc.osc_common import OscCommon @@ -56,35 +57,71 @@ def __init__(self, **kwargs): self.inportBox = QSpinBox(self) self.inportBox.setMinimum(1000) self.inportBox.setMaximum(99999) - hbox.layout().addWidget(self.inportBox) label = QLabel( - translate('OscSettings', 'Input Port')) + translate('OscSettings', 'Input Port:')) hbox.layout().addWidget(label) + hbox.layout().addWidget(self.inportBox) self.groupBox.layout().addLayout(hbox) hbox = QHBoxLayout() self.outportBox = QSpinBox(self) self.outportBox.setMinimum(1000) self.outportBox.setMaximum(99999) - hbox.layout().addWidget(self.outportBox) label = QLabel( - translate('OscSettings', 'Input Port')) + translate('OscSettings', 'Input Port:')) hbox.layout().addWidget(label) + hbox.layout().addWidget(self.outportBox) self.groupBox.layout().addLayout(hbox) hbox = QHBoxLayout() self.hostnameEdit = QLineEdit() self.hostnameEdit.setText('localhost') self.hostnameEdit.setMaximumWidth(200) - hbox.layout().addWidget(self.hostnameEdit) label = QLabel( - translate('OscSettings', 'Hostname')) + translate('OscSettings', 'Hostname:')) hbox.layout().addWidget(label) + hbox.layout().addWidget(self.hostnameEdit) self.groupBox.layout().addLayout(hbox) + self.enableModule.stateChanged.connect(self.activate_module, Qt.QueuedConnection) + self.enableFeedback.stateChanged.connect(self.activate_feedback, Qt.QueuedConnection) + self.inportBox.valueChanged.connect(self.change_inport, Qt.QueuedConnection) + self.outportBox.valueChanged.connect(self.change_outport, Qt.QueuedConnection) + self.hostnameEdit.textChanged.connect(self.change_hostname, Qt.QueuedConnection) + + def activate_module(self): + if self.enableModule.isChecked(): + # start osc server + OscCommon().start() + # enable OSC Module in Settings + config.set('OSC', 'enabled', 'True') + else: + # stop osc server + OscCommon().stop() + # disable OSC Module in Settings + config.set('OSC', 'enabled', 'False') + + def activate_feedback(self): + OscCommon().activate_feedback(self.enableFeedback.isChecked()) + + def change_inport(self): + port = self.inportBox.value() + if str(port) != config.get('OSC', 'inport'): + config.set('OSC', 'inport', str(port)) + if self.enableModule.isChecked(): + self.enableModule.setChecked(False) + + def change_outport(self): + port = self.outportBox.value() + config.set('OSC', 'outport', str(port)) + + def change_hostname(self): + hostname = self.hostnameEdit.text() + config.set('OSC', 'hostname', hostname) + def get_settings(self): conf = { - 'enabled': str(self.enableCheck.isChecked()), + 'enabled': str(self.enableModule.isChecked()), 'inport': str(self.inportBox.value()), 'outport': str(self.outportBox.value()), 'hostname': str(self.hostnameEdit.text()), From eba593c5ee9e12b942be68e03a3419681661414c Mon Sep 17 00:00:00 2001 From: offtools Date: Sun, 20 Nov 2016 17:36:36 +0100 Subject: [PATCH 012/333] OscMessage type Enum for OscCue --- lisp/modules/action_cues/osc_cue.py | 122 +++------------------------- 1 file changed, 13 insertions(+), 109 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index e8f29835b..1ee9363f3 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -17,38 +17,27 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -# TODO: proper Test for Format, add Error Dialog, Cue Test-Button in Settings - from enum import Enum from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, \ - QTableView, QTableWidget, QHeaderView, QPushButton, QLabel, \ - QLineEdit +from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, QLabel, \ + QComboBox, QSpinBox, QFrame -from lisp.ui.qmodels import SimpleTableModel -from lisp.core.has_properties import Property -from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate from lisp.cues.cue import Cue, CueState from lisp.ui.settings.cue_settings import CueSettingsRegistry,\ SettingsPage -from lisp.modules.osc.osc_common import OscCommon -from lisp.ui.ui_utils import translate class OscMessageType(Enum): - Int = 'Integer' - Float = 'Float' - Bool = 'Bool' + Int = 'Integer', + Float = 'Float', + Bool = 'Bool', String = 'String' class OscCue(Cue): Name = QT_TRANSLATE_NOOP('CueName', 'OSC Cue') - path = Property(default='') - args = Property(default=[]) - def __init__(self, **kwargs): super().__init__(**kwargs) @@ -57,115 +46,30 @@ def state(self): return CueState.Stop def __start__(self): - arg_list = [] - for arg in self.args: - conv_arg = self.__convert_arg(arg[0], arg[1]) - if conv_arg is not None: - arg_list.append(conv_arg) - OscCommon().send(self.path, *arg_list) - - def __convert_arg(self, t, arg): - try: - if t == OscMessageType.Int.value: - return int(arg) - elif t == OscMessageType.Float.value: - return float(arg) - elif t == OscMessageType.Bool.value: - return bool(arg) - elif t == OscMessageType.String.value: - return str(arg) - else: - return None - except ValueError: - return None + pass class OscCueSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('Cue Name', 'OSC Settings') + Name = QT_TRANSLATE_NOOP('CueName', 'OSC Settings') def __init__(self, **kwargs): super().__init__(**kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) - self.oscGroup = QGroupBox(self) - self.oscGroup.setLayout(QGridLayout()) - self.layout().addWidget(self.oscGroup) - - self.pathLabel = QLabel() - self.oscGroup.layout().addWidget(self.pathLabel, 0, 0) - - self.pathEdit = QLineEdit() - self.oscGroup.layout().addWidget(self.pathEdit, 1, 0, 1, 2) - - self.oscModel = SimpleTableModel([ - translate('Osc Cue', 'Type'), - translate('Osc Cue', 'Argument')]) - - self.oscView = OscView(parent=self.oscGroup) - self.oscView.setModel(self.oscModel) - self.oscGroup.layout().addWidget(self.oscView, 2, 0, 1, 2) - - self.addButton = QPushButton(self.oscGroup) - self.addButton.clicked.connect(self.__new_argument) - self.oscGroup.layout().addWidget(self.addButton, 3, 0) - - self.removeButton = QPushButton(self.oscGroup) - self.removeButton.clicked.connect(self.__remove_argument) - self.oscGroup.layout().addWidget(self.removeButton, 3, 1) + self.msgGroup = QGroupBox(self) + self.msgGroup.setLayout(QGridLayout()) + self.layout().addWidget(self.msgGroup) self.retranslateUi() def retranslateUi(self): - self.oscGroup.setTitle(translate('OscCue', 'OSC Message')) - self.addButton.setText(translate('OscCue', 'Add')) - self.removeButton.setText(translate('OscCue', 'Remove')) - self.pathLabel.setText(translate('OscCue', 'OSC Path: (example: "/path/to/something")')) + self.msgGroup.setTitle(translate('OscCue', 'OSC Message')) def get_settings(self): - oscmsg = {'path': self.pathEdit.text(), - 'args': [row for row in self.oscModel.rows]} - return oscmsg + pass def load_settings(self, settings): - if 'path' in settings: - path = settings.get('path', '') - self.pathEdit.setText(path) - if 'args' in settings: - args = settings.get('args', '') - for row in args: - self.oscModel.appendRow(row[0], row[1]) - - def __new_argument(self): - self.oscModel.appendRow(OscMessageType.Int.value, 'argument') - - def __remove_argument(self): - if self.oscModel.rowCount(): - self.oscModel.removeRow(self.oscView.currentIndex().row()) - - -class OscView(QTableView): - def __init__(self, **kwargs): - super().__init__(**kwargs) - - self.delegates = [ComboBoxDelegate(options=[i.value for i in OscMessageType], - tr_context='OscMessageType'), - LineEditDelegate()] - - self.setSelectionBehavior(QTableWidget.SelectRows) - self.setSelectionMode(QTableView.SingleSelection) - - self.setShowGrid(False) - self.setAlternatingRowColors(True) - - self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) - self.horizontalHeader().setHighlightSections(False) - - self.verticalHeader().sectionResizeMode(QHeaderView.Fixed) - self.verticalHeader().setDefaultSectionSize(24) - self.verticalHeader().setHighlightSections(False) - - for column, delegate in enumerate(self.delegates): - self.setItemDelegateForColumn(column, delegate) + pass CueSettingsRegistry().add_item(OscCueSettings, OscCue) From 13638e5c5eb9a6014ffe9641dd646516cf33d7b1 Mon Sep 17 00:00:00 2001 From: offtools Date: Tue, 22 Nov 2016 01:19:49 +0100 Subject: [PATCH 013/333] added OscCue UI elements --- lisp/default.cfg | 4 -- lisp/modules/action_cues/osc_cue.py | 72 ++++++++++++++++++++++++++--- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/lisp/default.cfg b/lisp/default.cfg index 5999cac35..ffdcce9be 100644 --- a/lisp/default.cfg +++ b/lisp/default.cfg @@ -63,7 +63,3 @@ inport = 9000 outport = 9001 hostname = localhost feedback = False - -[Timecode] -enabled = False -format = FILM diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 1ee9363f3..25743e9f1 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -20,12 +20,15 @@ from enum import Enum from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, QLabel, \ - QComboBox, QSpinBox, QFrame +from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, \ + QTableView, QTableWidget, QHeaderView, QPushButton +from lisp.ui.qmodels import SimpleTableModel +from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate from lisp.cues.cue import Cue, CueState from lisp.ui.settings.cue_settings import CueSettingsRegistry,\ SettingsPage +from lisp.ui.ui_utils import translate class OscMessageType(Enum): @@ -57,14 +60,37 @@ def __init__(self, **kwargs): self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) - self.msgGroup = QGroupBox(self) - self.msgGroup.setLayout(QGridLayout()) - self.layout().addWidget(self.msgGroup) + self.oscGroup = QGroupBox(self) + self.oscGroup.setLayout(QGridLayout()) + self.layout().addWidget(self.oscGroup) + + self.oscModel = SimpleTableModel([ + translate('ControllerMidiSettings', 'Type'), + translate('ControllerMidiSettings', 'Argument')]) + + self.oscView = OscView(parent=self.oscGroup) + self.oscView.setModel(self.oscModel) + self.oscGroup.layout().addWidget(self.oscView, 0, 0, 1, 2) + + self.addButton = QPushButton(self.oscGroup) + self.addButton.clicked.connect(self.__new_message) + self.oscGroup.layout().addWidget(self.addButton, 1, 0) + + self.removeButton = QPushButton(self.oscGroup) + self.removeButton.clicked.connect(self.__remove_message) + self.oscGroup.layout().addWidget(self.removeButton, 1, 1) + + self.oscCapture = QPushButton(self.oscGroup) + self.oscCapture.clicked.connect(self.capture_message) + self.oscGroup.layout().addWidget(self.oscCapture, 2, 0) self.retranslateUi() def retranslateUi(self): - self.msgGroup.setTitle(translate('OscCue', 'OSC Message')) + self.oscGroup.setTitle(translate('OscCue', 'OSC Message')) + self.addButton.setText(translate('OscCue', 'Add')) + self.removeButton.setText(translate('OscCue', 'Remove')) + self.oscCapture.setText(translate('OscCue', 'Capture')) def get_settings(self): pass @@ -72,4 +98,38 @@ def get_settings(self): def load_settings(self, settings): pass + def __new_message(self): + pass + + def __remove_message(self): + pass + + def capture_message(self): + pass + + +class OscView(QTableView): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self.delegates = [ComboBoxDelegate(options=OscMessageType, + tr_context='OscMessageType'), + LineEditDelegate()] + + self.setSelectionBehavior(QTableWidget.SelectRows) + self.setSelectionMode(QTableView.SingleSelection) + + self.setShowGrid(False) + self.setAlternatingRowColors(True) + + self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + self.horizontalHeader().setHighlightSections(False) + + self.verticalHeader().sectionResizeMode(QHeaderView.Fixed) + self.verticalHeader().setDefaultSectionSize(24) + self.verticalHeader().setHighlightSections(False) + + for column, delegate in enumerate(self.delegates): + self.setItemDelegateForColumn(column, delegate) + CueSettingsRegistry().add_item(OscCueSettings, OscCue) From db966a92771ac86694bbc5feddd92b9486afd227 Mon Sep 17 00:00:00 2001 From: offtools Date: Wed, 23 Nov 2016 21:25:03 +0100 Subject: [PATCH 014/333] implmented OSC Cue --- lisp/modules/action_cues/osc_cue.py | 92 ++++++++++++++++++++--------- lisp/modules/osc/osc_common.py | 2 +- lisp/modules/osc/osc_settings.py | 2 +- 3 files changed, 66 insertions(+), 30 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 25743e9f1..548471d3f 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -17,30 +17,38 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +# TODO: proper Test for Format, add Error Dialog, Test Cue Button in Settings + from enum import Enum from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, \ - QTableView, QTableWidget, QHeaderView, QPushButton + QTableView, QTableWidget, QHeaderView, QPushButton, QLabel, \ + QLineEdit from lisp.ui.qmodels import SimpleTableModel +from lisp.core.has_properties import Property from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate from lisp.cues.cue import Cue, CueState from lisp.ui.settings.cue_settings import CueSettingsRegistry,\ SettingsPage +from lisp.modules.osc.osc_common import OscCommon from lisp.ui.ui_utils import translate class OscMessageType(Enum): - Int = 'Integer', - Float = 'Float', - Bool = 'Bool', + Int = 'Integer' + Float = 'Float' + Bool = 'Bool' String = 'String' class OscCue(Cue): Name = QT_TRANSLATE_NOOP('CueName', 'OSC Cue') + path = Property(default='') + args = Property(default=[]) + def __init__(self, **kwargs): super().__init__(**kwargs) @@ -49,11 +57,31 @@ def state(self): return CueState.Stop def __start__(self): - pass + arg_list = [] + for arg in self.args: + conv_arg = self.__convert_arg(arg[0], arg[1]) + if conv_arg is not None: + arg_list.append(conv_arg) + OscCommon().send(self.path, *arg_list) + + def __convert_arg(self, t, arg): + try: + if t == OscMessageType.Int.value: + return int(arg) + elif t == OscMessageType.Float.value: + return float(arg) + elif t == OscMessageType.Bool.value: + return bool(arg) + elif t == OscMessageType.String.value: + return str(arg) + else: + return None + except ValueError: + return None class OscCueSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('CueName', 'OSC Settings') + Name = QT_TRANSLATE_NOOP('Cue Name', 'OSC Settings') def __init__(self, **kwargs): super().__init__(**kwargs) @@ -64,25 +92,27 @@ def __init__(self, **kwargs): self.oscGroup.setLayout(QGridLayout()) self.layout().addWidget(self.oscGroup) + self.pathLabel = QLabel() + self.oscGroup.layout().addWidget(self.pathLabel, 0, 0) + + self.pathEdit = QLineEdit() + self.oscGroup.layout().addWidget(self.pathEdit, 1, 0, 1, 2) + self.oscModel = SimpleTableModel([ - translate('ControllerMidiSettings', 'Type'), - translate('ControllerMidiSettings', 'Argument')]) + translate('Osc Cue', 'Type'), + translate('Osc Cue', 'Argument')]) self.oscView = OscView(parent=self.oscGroup) self.oscView.setModel(self.oscModel) - self.oscGroup.layout().addWidget(self.oscView, 0, 0, 1, 2) + self.oscGroup.layout().addWidget(self.oscView, 2, 0, 1, 2) self.addButton = QPushButton(self.oscGroup) - self.addButton.clicked.connect(self.__new_message) - self.oscGroup.layout().addWidget(self.addButton, 1, 0) + self.addButton.clicked.connect(self.__new_argument) + self.oscGroup.layout().addWidget(self.addButton, 3, 0) self.removeButton = QPushButton(self.oscGroup) - self.removeButton.clicked.connect(self.__remove_message) - self.oscGroup.layout().addWidget(self.removeButton, 1, 1) - - self.oscCapture = QPushButton(self.oscGroup) - self.oscCapture.clicked.connect(self.capture_message) - self.oscGroup.layout().addWidget(self.oscCapture, 2, 0) + self.removeButton.clicked.connect(self.__remove_argument) + self.oscGroup.layout().addWidget(self.removeButton, 3, 1) self.retranslateUi() @@ -90,29 +120,35 @@ def retranslateUi(self): self.oscGroup.setTitle(translate('OscCue', 'OSC Message')) self.addButton.setText(translate('OscCue', 'Add')) self.removeButton.setText(translate('OscCue', 'Remove')) - self.oscCapture.setText(translate('OscCue', 'Capture')) + self.pathLabel.setText(translate('OscCue', 'OSC Path: (example: "/path/to/something")')) def get_settings(self): - pass + oscmsg = {'path': self.pathEdit.text(), + 'args': [row for row in self.oscModel.rows]} + return oscmsg def load_settings(self, settings): - pass - - def __new_message(self): - pass + if 'path' in settings: + path = settings.get('path', '') + self.pathEdit.setText(path) + if 'args' in settings: + args = settings.get('args', '') + for row in args: + self.oscModel.appendRow(row[0], row[1]) - def __remove_message(self): - pass + def __new_argument(self): + self.oscModel.appendRow(OscMessageType.Int.value, 'argument') - def capture_message(self): - pass + def __remove_argument(self): + if self.oscModel.rowCount(): + self.oscModel.removeRow(self.oscView.currentIndex().row()) class OscView(QTableView): def __init__(self, **kwargs): super().__init__(**kwargs) - self.delegates = [ComboBoxDelegate(options=OscMessageType, + self.delegates = [ComboBoxDelegate(options=[i.value for i in OscMessageType], tr_context='OscMessageType'), LineEditDelegate()] diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index 82989fb61..234c1a78f 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -94,7 +94,7 @@ def stop(self): def send(self, path, *args): if self.__listening: - target = Address(config['OSC']['hostname'], int(config['OSC']['port'])) + target = Address(config['OSC']['hostname'], int(config['OSC']['outport'])) self.__srv.send(target, path, *args) def register_callback(self, path, typespec, func): diff --git a/lisp/modules/osc/osc_settings.py b/lisp/modules/osc/osc_settings.py index 261053a78..0f6db135a 100644 --- a/lisp/modules/osc/osc_settings.py +++ b/lisp/modules/osc/osc_settings.py @@ -68,7 +68,7 @@ def __init__(self, **kwargs): self.outportBox.setMinimum(1000) self.outportBox.setMaximum(99999) label = QLabel( - translate('OscSettings', 'Input Port:')) + translate('OscSettings', 'Output Port:')) hbox.layout().addWidget(label) hbox.layout().addWidget(self.outportBox) self.groupBox.layout().addLayout(hbox) From 7d7f68aca628550af21522d5fc8dd936aa050fc5 Mon Sep 17 00:00:00 2001 From: offtools Date: Fri, 25 Nov 2016 17:36:17 +0100 Subject: [PATCH 015/333] updated TODO --- lisp/modules/action_cues/osc_cue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 548471d3f..e8f29835b 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -# TODO: proper Test for Format, add Error Dialog, Test Cue Button in Settings +# TODO: proper Test for Format, add Error Dialog, Cue Test-Button in Settings from enum import Enum From f6d7d9114dc6a9949d8e743d614fedc19e96073e Mon Sep 17 00:00:00 2001 From: offtools Date: Fri, 25 Nov 2016 17:36:48 +0100 Subject: [PATCH 016/333] implemented OSC Protocol --- lisp/modules/osc/osc_common.py | 71 +++++++++++++++++------- lisp/plugins/controller/protocols/osc.py | 9 +++ 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index 234c1a78f..90cee93a0 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +from collections import deque from lisp.core.singleton import ABCSingleton from liblo import ServerThread, Address, ServerError @@ -26,50 +27,67 @@ from lisp.utils import elogging from lisp.ui.mainwindow import MainWindow from lisp.application import Application -from lisp.cues.cue import Cue, CueState +from lisp.cues.cue import CueState +from lisp.core.signal import Signal +# decorator for OSC callback (pushing messages to log, emits message_event) +def osc_handler(func): + def func_wrapper(path, args, types, src): + func(path, args, types, src) + OscCommon().push_log(path, args, types, src) + OscCommon().new_message.emit(path, args, types, src) + return func_wrapper + + +@osc_handler def _go(path, args, types, src): - go = int(args[0]) if isinstance(MainWindow().layout, ListLayout): - if go >= 1: - MainWindow().layout.go() + MainWindow().layout.go() +@osc_handler def _pause_all(path, args, types, src): - pause = int(args[0]) for cue in Application().cue_model: - if cue.state == CueState.Running and pause >= 1: - cue.start() + if cue.state == CueState.Running: + cue.pause() +@osc_handler def _restart_all(path, args, types, src): - restart = int(args[0]) for cue in Application().cue_model: - if cue.state == CueState.Pause and restart >= 1: + if cue.state == CueState.Pause: cue.start() +@osc_handler def _stop_all(path, args, types, src): - stop = int(args[0]) - if stop >= 1: - for cue in Application().cue_model: - cue.stop() + for cue in Application().cue_model: + cue.stop() class OscCommon(metaclass=ABCSingleton): def __init__(self): self.__srv = None self.__listening = False + self.__log = deque([], 10) + self.new_message = Signal() # TODO: static paths and callbacks, find smarter way self.__callbacks = [ - ['/lisp/go', 'i', _go], - ['/lisp/pause', 'i', _pause_all], - ['/lisp/start', 'i', _restart_all], - ['/lisp/stop', 'i', _stop_all], + ['/lisp/go', '', _go], + ['/lisp/pause', '', _pause_all], + ['/lisp/start', '', _restart_all], + ['/lisp/stop', '', _stop_all], + [None, None, self.__new_message] ] + def push_log(self, path, args, types, src, success=True): + self.__log.append([path, args, types, src, success]) + + def get_log(self): + return self.__log + def start(self): if self.__listening: return @@ -92,13 +110,28 @@ def stop(self): self.__srv.free() elogging.info('OSC: Server stopped', dialog=False) + @property + def listening(self): + return self.__listening + def send(self, path, *args): if self.__listening: target = Address(config['OSC']['hostname'], int(config['OSC']['outport'])) self.__srv.send(target, path, *args) - def register_callback(self, path, typespec, func): - self.__callbacks.append([path, typespec, func]) + def __new_message(self, path, args, types, src): + self.push_log(path, args, types, src, False) + self.new_message.emit(path, args, types, src) + # elogging.warning('OscCommon: unknown message received: {0} {1} {2}'.format(path, types, args), dialog=False) + + # def register_callback(self, path, typespec, func): + # # insert into callback list before callback, otherwise its ignored + # self.__callbacks.insert(-1, [path, typespec, func]) + # + # # fallback needs to be the registered callback + # self.__srv.del_method(None, None) + # self.__srv.add_method(path, typespec, func) + # self.__srv.add_method(None, None, self.__new_message) def activate_feedback(self, feedback): pass diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index b8ab99ae0..7afcf4ea6 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -18,11 +18,20 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP +<<<<<<< HEAD from PyQt5.QtWidgets import QGroupBox, QPushButton, QVBoxLayout, \ QTableView, QTableWidget, QHeaderView, QGridLayout, QLabel, \ QDialog, QDialogButtonBox from lisp.modules import check_module +======= +from PyQt5.QtWidgets import QGroupBox, QPushButton, QComboBox, QVBoxLayout, \ + QMessageBox, QTableView, QTableWidget, QHeaderView, QGridLayout, QLabel, \ + QDialog, QDialogButtonBox + +from lisp.modules import check_module +from lisp.application import MainWindow +>>>>>>> 6f32977... implemented OSC Protocol from lisp.modules.osc.osc_common import OscCommon from lisp.plugins.controller.protocols.protocol import Protocol from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate From 3230bc7e74ccf0c4693ceb3fead87252d9fbd0f4 Mon Sep 17 00:00:00 2001 From: offtools Date: Fri, 25 Nov 2016 18:22:01 +0100 Subject: [PATCH 017/333] OSC import cleanups --- lisp/modules/osc/osc_settings.py | 3 +-- lisp/plugins/controller/protocols/osc.py | 9 --------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/lisp/modules/osc/osc_settings.py b/lisp/modules/osc/osc_settings.py index 0f6db135a..e967cd5be 100644 --- a/lisp/modules/osc/osc_settings.py +++ b/lisp/modules/osc/osc_settings.py @@ -20,8 +20,7 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel,\ - QCheckBox, QComboBox, QHBoxLayout, QMessageBox, QSpinBox,\ - QLineEdit + QCheckBox, QHBoxLayout, QSpinBox, QLineEdit from lisp.ui.ui_utils import translate from lisp.utils.configuration import config diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 7afcf4ea6..b8ab99ae0 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -18,20 +18,11 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -<<<<<<< HEAD from PyQt5.QtWidgets import QGroupBox, QPushButton, QVBoxLayout, \ QTableView, QTableWidget, QHeaderView, QGridLayout, QLabel, \ QDialog, QDialogButtonBox from lisp.modules import check_module -======= -from PyQt5.QtWidgets import QGroupBox, QPushButton, QComboBox, QVBoxLayout, \ - QMessageBox, QTableView, QTableWidget, QHeaderView, QGridLayout, QLabel, \ - QDialog, QDialogButtonBox - -from lisp.modules import check_module -from lisp.application import MainWindow ->>>>>>> 6f32977... implemented OSC Protocol from lisp.modules.osc.osc_common import OscCommon from lisp.plugins.controller.protocols.protocol import Protocol from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate From b200f3ed7af15b83b080e66c9ef65b18ff7f95b2 Mon Sep 17 00:00:00 2001 From: offtools Date: Wed, 30 Nov 2016 04:01:16 +0100 Subject: [PATCH 018/333] cleaned default.cfg --- lisp/default.cfg | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lisp/default.cfg b/lisp/default.cfg index c7397de60..ac2399df8 100644 --- a/lisp/default.cfg +++ b/lisp/default.cfg @@ -54,11 +54,10 @@ GoKey = Space dBMax = 0 dbMin = -60 dbClip = 0 -======= dbClip = 0 [OSC] -enable = False +enable = True inport = 9000 outport = 9001 hostname = localhost From e5a40aa0e11cd3d9423e402f4975ec1936312501 Mon Sep 17 00:00:00 2001 From: offtools Date: Wed, 30 Nov 2016 04:19:50 +0100 Subject: [PATCH 019/333] updated to upstream changes --- lisp/default.cfg | 3 +-- lisp/modules/action_cues/osc_cue.py | 2 +- lisp/modules/osc/__init__.py | 6 +----- lisp/modules/osc/osc.py | 4 ++-- lisp/modules/osc/osc_common.py | 4 ++-- lisp/modules/osc/osc_settings.py | 2 +- 6 files changed, 8 insertions(+), 13 deletions(-) diff --git a/lisp/default.cfg b/lisp/default.cfg index ac2399df8..2fb3561e2 100644 --- a/lisp/default.cfg +++ b/lisp/default.cfg @@ -54,10 +54,9 @@ GoKey = Space dBMax = 0 dbMin = -60 dbClip = 0 -dbClip = 0 [OSC] -enable = True +enable = False inport = 9000 outport = 9001 hostname = localhost diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index e8f29835b..3bff519fc 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -56,7 +56,7 @@ def __init__(self, **kwargs): def state(self): return CueState.Stop - def __start__(self): + def __start__(self, fade=False): arg_list = [] for arg in self.args: conv_arg = self.__convert_arg(arg[0], arg[1]) diff --git a/lisp/modules/osc/__init__.py b/lisp/modules/osc/__init__.py index 0f64693f9..69bcd841b 100644 --- a/lisp/modules/osc/__init__.py +++ b/lisp/modules/osc/__init__.py @@ -1,5 +1 @@ -try: - from lisp.utils import elogging - from .osc import Osc -except ImportError as error: - elogging.error(e, 'pyliblo not found', dialog=False) \ No newline at end of file +from .osc import Osc \ No newline at end of file diff --git a/lisp/modules/osc/osc.py b/lisp/modules/osc/osc.py index 5af26598b..2bb52904f 100644 --- a/lisp/modules/osc/osc.py +++ b/lisp/modules/osc/osc.py @@ -19,7 +19,7 @@ from lisp.core.module import Module -from lisp.utils.configuration import config +from lisp.core.configuration import config from lisp.ui.settings.app_settings import AppSettings from lisp.modules.osc.osc_settings import OscSettings from lisp.modules.osc.osc_common import OscCommon @@ -32,5 +32,5 @@ def __init__(self): # Register the settings widget AppSettings.register_settings_widget(OscSettings) - if config['OSC']['enabled']: + if config['OSC']['enable']: OscCommon().start() diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index 90cee93a0..ed5633c8d 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -22,9 +22,9 @@ from lisp.core.singleton import ABCSingleton from liblo import ServerThread, Address, ServerError -from lisp.utils.configuration import config +from lisp.core.configuration import config from lisp.layouts.list_layout.layout import ListLayout -from lisp.utils import elogging +from lisp.ui import elogging from lisp.ui.mainwindow import MainWindow from lisp.application import Application from lisp.cues.cue import CueState diff --git a/lisp/modules/osc/osc_settings.py b/lisp/modules/osc/osc_settings.py index e967cd5be..45851ed51 100644 --- a/lisp/modules/osc/osc_settings.py +++ b/lisp/modules/osc/osc_settings.py @@ -23,7 +23,7 @@ QCheckBox, QHBoxLayout, QSpinBox, QLineEdit from lisp.ui.ui_utils import translate -from lisp.utils.configuration import config +from lisp.core.configuration import config from lisp.ui.settings.settings_page import SettingsPage from lisp.modules.osc.osc_common import OscCommon From aedfdf2f45cd8650d3132c68307a19ad58b7ba79 Mon Sep 17 00:00:00 2001 From: offtools Date: Fri, 2 Dec 2016 01:41:55 +0100 Subject: [PATCH 020/333] handle input errors of OSC messages --- lisp/modules/action_cues/osc_cue.py | 110 +++++++++++++++++++++++----- 1 file changed, 91 insertions(+), 19 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 3bff519fc..fce6d9d31 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -26,6 +26,7 @@ QTableView, QTableWidget, QHeaderView, QPushButton, QLabel, \ QLineEdit +from lisp.ui import elogging from lisp.ui.qmodels import SimpleTableModel from lisp.core.has_properties import Property from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate @@ -43,6 +44,46 @@ class OscMessageType(Enum): String = 'String' +def string_to_value(t, sarg): + """converts string to requested value for given type""" + if t == OscMessageType.Int.value: + return int(sarg) + elif t == OscMessageType.Float.value: + return float(sarg) + elif t == OscMessageType.Bool.value: + if sarg.lower() == 'true': + return True + elif sarg.lower() == 'false': + return False + else: + return bool(int(sarg)) + elif t == OscMessageType.String.value: + return str(sarg) + else: + raise ValueError + + +def format_string(t, sarg): + """checks if string can be converted and formats it""" + if len(sarg) == 0: + return '' + elif t == OscMessageType.Int.value: + return "{0:d}".format(int(sarg)) + elif t == OscMessageType.Float.value: + return "{0:.2f}".format(int(sarg)) + elif t == OscMessageType.Bool.value: + if sarg.lower() == 'true': + return 'True' + elif sarg.lower() == 'false': + return 'False' + else: + return "{0}".format(bool(int(sarg))) + elif t == OscMessageType.String.value: + return "{0}".format(sarg) + else: + raise ValueError + + class OscCue(Cue): Name = QT_TRANSLATE_NOOP('CueName', 'OSC Cue') @@ -58,26 +99,16 @@ def state(self): def __start__(self, fade=False): arg_list = [] - for arg in self.args: - conv_arg = self.__convert_arg(arg[0], arg[1]) - if conv_arg is not None: - arg_list.append(conv_arg) - OscCommon().send(self.path, *arg_list) - - def __convert_arg(self, t, arg): + if len(self.path) < 2 or self.path[0] != '/': + elogging.warning("OSC: no valid path for OSC message - nothing sent", dialog=False) + return try: - if t == OscMessageType.Int.value: - return int(arg) - elif t == OscMessageType.Float.value: - return float(arg) - elif t == OscMessageType.Bool.value: - return bool(arg) - elif t == OscMessageType.String.value: - return str(arg) - else: - return None + for arg in self.args: + conv_arg = string_to_value(arg[0], arg[1]) + arg_list.append(conv_arg) + OscCommon().send(self.path, *arg_list) except ValueError: - return None + elogging.warning("OSC: Error on parsing argument list - nothing sent", dialog=False) class OscCueSettings(SettingsPage): @@ -102,6 +133,8 @@ def __init__(self, **kwargs): translate('Osc Cue', 'Type'), translate('Osc Cue', 'Argument')]) + self.oscModel.dataChanged.connect(self.__argument_changed) + self.oscView = OscView(parent=self.oscGroup) self.oscView.setModel(self.oscModel) self.oscGroup.layout().addWidget(self.oscView, 2, 0, 1, 2) @@ -114,12 +147,17 @@ def __init__(self, **kwargs): self.removeButton.clicked.connect(self.__remove_argument) self.oscGroup.layout().addWidget(self.removeButton, 3, 1) + self.testButton = QPushButton(self.oscGroup) + self.testButton.clicked.connect(self.__test_message) + self.oscGroup.layout().addWidget(self.testButton, 4, 0) + self.retranslateUi() def retranslateUi(self): self.oscGroup.setTitle(translate('OscCue', 'OSC Message')) self.addButton.setText(translate('OscCue', 'Add')) self.removeButton.setText(translate('OscCue', 'Remove')) + self.testButton.setText(translate('OscCue', 'Test')) self.pathLabel.setText(translate('OscCue', 'OSC Path: (example: "/path/to/something")')) def get_settings(self): @@ -137,12 +175,46 @@ def load_settings(self, settings): self.oscModel.appendRow(row[0], row[1]) def __new_argument(self): - self.oscModel.appendRow(OscMessageType.Int.value, 'argument') + self.oscModel.appendRow(OscMessageType.Int.value, '') def __remove_argument(self): if self.oscModel.rowCount(): self.oscModel.removeRow(self.oscView.currentIndex().row()) + def __test_message(self): + oscmsg = {'path': self.pathEdit.text(), + 'args': [row for row in self.oscModel.rows]} + arg_list = [] + if len(oscmsg['path']) < 2: + elogging.warning("OSC: no valid path for OSC message - nothing sent", + details="Path too short.", + dialog=True) + return + + if oscmsg['path'][0] != '/': + elogging.warning("OSC: no valid path for OSC message - nothing", + details="Path should start with '/'.", + dialog=True) + return + + try: + for arg in oscmsg['args']: + conv_arg = string_to_value(arg[0], arg[1]) + arg_list.append(conv_arg) + OscCommon().send(oscmsg['path'], *arg_list) + except ValueError: + elogging.warning("OSC: Error on parsing argument list - nothing sent") + + def __argument_changed(self, index_topleft, index_bottomright, roles): + model = index_bottomright.model() + osctype = model.rows[index_bottomright.row()][0] + argument = model.rows[index_bottomright.row()][1] + try: + model.rows[index_bottomright.row()][1] = format_string(osctype, argument) + except ValueError: + model.rows[index_bottomright.row()][1] = '' + elogging.warning("OSC Argument Error", details="{0} not a {1}".format(argument, osctype), dialog=True) + class OscView(QTableView): def __init__(self, **kwargs): From 18f7a8b7fb43eb3412bd9316e53a05654e89868e Mon Sep 17 00:00:00 2001 From: offtools Date: Sun, 4 Dec 2016 14:10:42 +0100 Subject: [PATCH 021/333] removed state getter, __start__ returns False --- lisp/modules/action_cues/osc_cue.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index fce6d9d31..2cc5a8165 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -93,10 +93,6 @@ class OscCue(Cue): def __init__(self, **kwargs): super().__init__(**kwargs) - @Cue.state.getter - def state(self): - return CueState.Stop - def __start__(self, fade=False): arg_list = [] if len(self.path) < 2 or self.path[0] != '/': @@ -110,6 +106,8 @@ def __start__(self, fade=False): except ValueError: elogging.warning("OSC: Error on parsing argument list - nothing sent", dialog=False) + return False + class OscCueSettings(SettingsPage): Name = QT_TRANSLATE_NOOP('Cue Name', 'OSC Settings') From 36432a841672581164042404b3cf6fd62e7cfcdb Mon Sep 17 00:00:00 2001 From: offtools Date: Sun, 4 Dec 2016 18:15:46 +0100 Subject: [PATCH 022/333] started fades for osc cues --- lisp/modules/action_cues/osc_cue.py | 142 +++++++++++++++++++++++----- lisp/ui/qdelegates.py | 23 ++++- 2 files changed, 140 insertions(+), 25 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 2cc5a8165..9ec7a14a8 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -20,22 +20,32 @@ # TODO: proper Test for Format, add Error Dialog, Cue Test-Button in Settings from enum import Enum +from time import sleep from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, \ QTableView, QTableWidget, QHeaderView, QPushButton, QLabel, \ QLineEdit +from lisp.core.decorators import async, locked_method +from lisp.core.fade_functions import ntime, FadeInType, FadeOutType +from lisp.core.fader import Fader +from lisp.cues.cue import Cue, CueState, CueAction from lisp.ui import elogging from lisp.ui.qmodels import SimpleTableModel from lisp.core.has_properties import Property -from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate +from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate,\ + CheckBoxDelegate from lisp.cues.cue import Cue, CueState from lisp.ui.settings.cue_settings import CueSettingsRegistry,\ SettingsPage from lisp.modules.osc.osc_common import OscCommon from lisp.ui.ui_utils import translate +COL_TYPE = 0 +COL_START_VAL = 1 +COL_END_VAL = 2 +COL_DO_FADE = 3 class OscMessageType(Enum): Int = 'Integer' @@ -84,29 +94,98 @@ def format_string(t, sarg): raise ValueError +def can_fade(t): + if t == OscMessageType.Int.value: + return True + elif t == OscMessageType.Float.value: + return True + else: + return False + + class OscCue(Cue): Name = QT_TRANSLATE_NOOP('CueName', 'OSC Cue') + CueActions = (CueAction.Default, CueAction.Start, CueAction.FadeInStart, + CueAction.Stop, CueAction.FadeOutStop, CueAction.Pause, + CueAction.FadeOutPause) + path = Property(default='') args = Property(default=[]) + # one global fader used for all faded arguments + position = Property(default=0.0) + def __init__(self, **kwargs): super().__init__(**kwargs) + self.__in_fadein = False + self.__in_fadeout = False + self.__fader = Fader(self, 'position') + self.changed('position').connect(self.__send_fade) + + def __send_fade(self): + print("OSC fade position:", self.position) + def __start__(self, fade=False): - arg_list = [] - if len(self.path) < 2 or self.path[0] != '/': - elogging.warning("OSC: no valid path for OSC message - nothing sent", dialog=False) - return - try: - for arg in self.args: - conv_arg = string_to_value(arg[0], arg[1]) - arg_list.append(conv_arg) - OscCommon().send(self.path, *arg_list) - except ValueError: - elogging.warning("OSC: Error on parsing argument list - nothing sent", dialog=False) + if fade and self._can_fadein(): + # Proceed fadein + self._fadein() + return True + else: + # Single Shot + value_list = [] + + if len(self.path) < 2 or self.path[0] != '/': + elogging.warning("OSC: no valid path for OSC message - nothing sent", dialog=False) + return + try: + for arg in self.args: + value = string_to_value(arg[COL_TYPE], arg[COL_START_VAL]) + value_list.append(value) + OscCommon().send(self.path, *value_list) + except ValueError: + elogging.warning("OSC: Error on parsing argument list - nothing sent", dialog=False) - return False + return False + + def __stop__(self, fade=False): + return True + + def __pause__(self, fade=False): + return True + + def _can_fade(self): + has_fade = False + for arg in self.args: + if arg[COL_DO_FADE]: + has_fade = True + break + return has_fade + + def _can_fadein(self): + return self._can_fade() and self.fadein_duration > 0 + + def _can_fadeout(self): + return self._can_fade() and self.fadeout_duration > 0 + + @async + def _fadein(self): + if self._can_fadein(): + self.__in_fadein = True + self.fadein_start.emit() + try: + self.__fader.prepare() + self.__fader.fade(self.fadein_duration, + 1.0, + FadeInType[self.fadein_type]) + finally: + self.__in_fadein = False + self.fadein_end.emit() + + def _fadeout(self): + ended = True + return ended class OscCueSettings(SettingsPage): @@ -129,7 +208,9 @@ def __init__(self, **kwargs): self.oscModel = SimpleTableModel([ translate('Osc Cue', 'Type'), - translate('Osc Cue', 'Argument')]) + translate('Osc Cue', 'Argument'), + translate('Osc Cue', 'FadeTo'), + translate('Osc Cue', 'Fade')]) self.oscModel.dataChanged.connect(self.__argument_changed) @@ -170,10 +251,10 @@ def load_settings(self, settings): if 'args' in settings: args = settings.get('args', '') for row in args: - self.oscModel.appendRow(row[0], row[1]) + self.oscModel.appendRow(row[0], row[1], row[2], row[3]) def __new_argument(self): - self.oscModel.appendRow(OscMessageType.Int.value, '') + self.oscModel.appendRow(OscMessageType.Int.value, '', '', False) def __remove_argument(self): if self.oscModel.rowCount(): @@ -182,7 +263,7 @@ def __remove_argument(self): def __test_message(self): oscmsg = {'path': self.pathEdit.text(), 'args': [row for row in self.oscModel.rows]} - arg_list = [] + value_list = [] if len(oscmsg['path']) < 2: elogging.warning("OSC: no valid path for OSC message - nothing sent", details="Path too short.", @@ -197,21 +278,32 @@ def __test_message(self): try: for arg in oscmsg['args']: - conv_arg = string_to_value(arg[0], arg[1]) - arg_list.append(conv_arg) - OscCommon().send(oscmsg['path'], *arg_list) + value = string_to_value(arg[0], arg[1]) + value_list.append(value) + OscCommon().send(oscmsg['path'], *value_list) except ValueError: elogging.warning("OSC: Error on parsing argument list - nothing sent") def __argument_changed(self, index_topleft, index_bottomright, roles): model = index_bottomright.model() osctype = model.rows[index_bottomright.row()][0] - argument = model.rows[index_bottomright.row()][1] + start = model.rows[index_bottomright.row()][1] + end = model.rows[index_bottomright.row()][2] + try: - model.rows[index_bottomright.row()][1] = format_string(osctype, argument) + model.rows[index_bottomright.row()][1] = format_string(osctype, start) except ValueError: model.rows[index_bottomright.row()][1] = '' - elogging.warning("OSC Argument Error", details="{0} not a {1}".format(argument, osctype), dialog=True) + elogging.warning("OSC Argument Error", details="{0} not a {1}".format(start, osctype), dialog=True) + + try: + model.rows[index_bottomright.row()][2] = format_string(osctype, end) + except ValueError: + model.rows[index_bottomright.row()][2] = '' + elogging.warning("OSC Argument Error", details="{0} not a {1}".format(end, osctype), dialog=True) + + if not can_fade(osctype): + model.rows[index_bottomright.row()][3] = False class OscView(QTableView): @@ -220,7 +312,9 @@ def __init__(self, **kwargs): self.delegates = [ComboBoxDelegate(options=[i.value for i in OscMessageType], tr_context='OscMessageType'), - LineEditDelegate()] + LineEditDelegate(), + LineEditDelegate(), + CheckBoxDelegate()] self.setSelectionBehavior(QTableWidget.SelectRows) self.setSelectionMode(QTableView.SingleSelection) diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index 969066e41..082ef7b13 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -19,7 +19,7 @@ from PyQt5.QtCore import Qt, QEvent from PyQt5.QtWidgets import QStyledItemDelegate, QComboBox, QSpinBox, \ - QLineEdit, QStyle, QDialog + QLineEdit, QStyle, QDialog, QCheckBox from lisp.application import Application from lisp.cues.cue import CueAction @@ -114,6 +114,27 @@ def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) +class CheckBoxDelegate(QStyledItemDelegate): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def createEditor(self, parent, option, index): + editor = QCheckBox(parent) + + return editor + + def setEditorData(self, checkBox, index): + value = index.model().data(index, Qt.EditRole) + if isinstance(value, bool): + checkBox.setChecked(value) + + def setModelData(self, checkBox, model, index): + model.setData(index, checkBox.isChecked(), Qt.EditRole) + + def updateEditorGeometry(self, editor, option, index): + editor.setGeometry(option.rect) + + class LineEditDelegate(QStyledItemDelegate): def __init__(self, max_length=-1, validator=None, **kwargs): super().__init__(**kwargs) From e9ea0bdd6e15e97917b20849003e22fd98895e3d Mon Sep 17 00:00:00 2001 From: offtools Date: Thu, 8 Dec 2016 12:07:24 +0100 Subject: [PATCH 023/333] osc cue cleanup, fixed format_string --- lisp/modules/action_cues/osc_cue.py | 86 +++++++++++++++++------------ 1 file changed, 50 insertions(+), 36 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 9ec7a14a8..ec79eec94 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -47,6 +47,7 @@ COL_END_VAL = 2 COL_DO_FADE = 3 + class OscMessageType(Enum): Int = 'Integer' Float = 'Float' @@ -80,7 +81,7 @@ def format_string(t, sarg): elif t == OscMessageType.Int.value: return "{0:d}".format(int(sarg)) elif t == OscMessageType.Float.value: - return "{0:.2f}".format(int(sarg)) + return "{0:.2f}".format(float(sarg)) elif t == OscMessageType.Bool.value: if sarg.lower() == 'true': return 'True' @@ -94,7 +95,7 @@ def format_string(t, sarg): raise ValueError -def can_fade(t): +def type_can_fade(t): if t == OscMessageType.Int.value: return True elif t == OscMessageType.Float.value: @@ -124,30 +125,42 @@ def __init__(self, **kwargs): self.__fader = Fader(self, 'position') self.changed('position').connect(self.__send_fade) - def __send_fade(self): - print("OSC fade position:", self.position) + def __filter_fade_args(self): + value_list = [] + for arg in self.args: + if arg[COL_DO_FADE]: + value_list.append(arg) + return value_list def __start__(self, fade=False): - if fade and self._can_fadein(): + faded_args = self.__filter_fade_args() + print("FADED ARGS: ", faded_args) + if fade and faded_args: # Proceed fadein - self._fadein() - return True + # self._fadein() + # return True + return self.__send_single_shot() else: - # Single Shot - value_list = [] + return self.__send_single_shot() - if len(self.path) < 2 or self.path[0] != '/': - elogging.warning("OSC: no valid path for OSC message - nothing sent", dialog=False) - return - try: - for arg in self.args: - value = string_to_value(arg[COL_TYPE], arg[COL_START_VAL]) - value_list.append(value) - OscCommon().send(self.path, *value_list) - except ValueError: - elogging.warning("OSC: Error on parsing argument list - nothing sent", dialog=False) + def __send_single_shot(self): + value_list = [] - return False + if len(self.path) < 2 or self.path[0] != '/': + elogging.warning("OSC: no valid path for OSC message - nothing sent", dialog=False) + return + try: + for arg in self.args: + value = string_to_value(arg[COL_TYPE], arg[COL_START_VAL]) + value_list.append(value) + OscCommon().send(self.path, *value_list) + except ValueError: + elogging.warning("OSC: Error on parsing argument list - nothing sent", dialog=False) + + return False + + def __send_fade(self): + print("OSC fade position:", self.position) def __stop__(self, fade=False): return True @@ -155,14 +168,6 @@ def __stop__(self, fade=False): def __pause__(self, fade=False): return True - def _can_fade(self): - has_fade = False - for arg in self.args: - if arg[COL_DO_FADE]: - has_fade = True - break - return has_fade - def _can_fadein(self): return self._can_fade() and self.fadein_duration > 0 @@ -286,23 +291,32 @@ def __test_message(self): def __argument_changed(self, index_topleft, index_bottomright, roles): model = index_bottomright.model() - osctype = model.rows[index_bottomright.row()][0] - start = model.rows[index_bottomright.row()][1] - end = model.rows[index_bottomright.row()][2] + osctype = model.rows[index_bottomright.row()][COL_TYPE] + start = model.rows[index_bottomright.row()][COL_START_VAL] + end = model.rows[index_bottomright.row()][COL_END_VAL] + # test Start value for correct format try: - model.rows[index_bottomright.row()][1] = format_string(osctype, start) + model.rows[index_bottomright.row()][COL_START_VAL] = format_string(osctype, start) except ValueError: - model.rows[index_bottomright.row()][1] = '' + model.rows[index_bottomright.row()][COL_START_VAL] = '' elogging.warning("OSC Argument Error", details="{0} not a {1}".format(start, osctype), dialog=True) + # test End value for correct format try: - model.rows[index_bottomright.row()][2] = format_string(osctype, end) + model.rows[index_bottomright.row()][COL_END_VAL] = format_string(osctype, end) except ValueError: - model.rows[index_bottomright.row()][2] = '' + model.rows[index_bottomright.row()][COL_END_VAL] = '' elogging.warning("OSC Argument Error", details="{0} not a {1}".format(end, osctype), dialog=True) - if not can_fade(osctype): + # Fade only enabled for Int and Float + if not type_can_fade(osctype): + model.rows[index_bottomright.row()][COL_DO_FADE] = False + + # for Fade, test if end value is provided + if not model.rows[index_bottomright.row()][COL_END_VAL]and \ + model.rows[index_bottomright.row()][COL_DO_FADE]: + elogging.warning("OSC Argument Error", details="FadeOut value is missing", dialog=True) model.rows[index_bottomright.row()][3] = False From 9b03000e64579f3dd2900f219615ba88661fd77b Mon Sep 17 00:00:00 2001 From: offtools Date: Fri, 9 Dec 2016 19:51:31 +0100 Subject: [PATCH 024/333] osc message to reset List layout (stop all, index to 0) --- lisp/modules/osc/osc_common.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index ed5633c8d..b9add62a6 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -46,6 +46,14 @@ def _go(path, args, types, src): MainWindow().layout.go() +@osc_handler +def _reset_list(path, args, types, src): + if isinstance(MainWindow().layout, ListLayout): + for cue in Application().cue_model: + cue.stop() + MainWindow().layout.set_current_index(0) + + @osc_handler def _pause_all(path, args, types, src): for cue in Application().cue_model: @@ -76,6 +84,7 @@ def __init__(self): # TODO: static paths and callbacks, find smarter way self.__callbacks = [ ['/lisp/go', '', _go], + ['/lisp/reset', '', _reset_list], ['/lisp/pause', '', _pause_all], ['/lisp/start', '', _restart_all], ['/lisp/stop', '', _stop_all], From 9565943db0f94b897236635b3e67ff5c4bd21847 Mon Sep 17 00:00:00 2001 From: offtools Date: Fri, 9 Dec 2016 22:57:00 +0100 Subject: [PATCH 025/333] added doc string to osc callback, list layout related osc messages have own pat /lisp/list/... --- lisp/modules/osc/osc_common.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index b9add62a6..8b3837d6b 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -42,12 +42,14 @@ def func_wrapper(path, args, types, src): @osc_handler def _go(path, args, types, src): + """triggers GO in ListLayout""" if isinstance(MainWindow().layout, ListLayout): MainWindow().layout.go() @osc_handler def _reset_list(path, args, types, src): + """reset, stops all cues, sets cursor to Cue index 0 in ListLayout""" if isinstance(MainWindow().layout, ListLayout): for cue in Application().cue_model: cue.stop() @@ -56,6 +58,7 @@ def _reset_list(path, args, types, src): @osc_handler def _pause_all(path, args, types, src): + """triggers global pause all media""" for cue in Application().cue_model: if cue.state == CueState.Running: cue.pause() @@ -63,6 +66,7 @@ def _pause_all(path, args, types, src): @osc_handler def _restart_all(path, args, types, src): + """triggers global play, if pausing""" for cue in Application().cue_model: if cue.state == CueState.Pause: cue.start() @@ -70,6 +74,7 @@ def _restart_all(path, args, types, src): @osc_handler def _stop_all(path, args, types, src): + """triggers global stop, stops all media cues""" for cue in Application().cue_model: cue.stop() @@ -83,8 +88,8 @@ def __init__(self): # TODO: static paths and callbacks, find smarter way self.__callbacks = [ - ['/lisp/go', '', _go], - ['/lisp/reset', '', _reset_list], + ['/lisp/list/go', '', _go], + ['/lisp/list/reset', '', _reset_list], ['/lisp/pause', '', _pause_all], ['/lisp/start', '', _restart_all], ['/lisp/stop', '', _stop_all], From cdc7917fc46accf520c3bc68e573fec45553a820 Mon Sep 17 00:00:00 2001 From: offtools Date: Sat, 10 Dec 2016 15:21:41 +0100 Subject: [PATCH 026/333] added fades to OscCue based on VolumeControl --- lisp/modules/action_cues/osc_cue.py | 187 +++++++++++++++++++++------- 1 file changed, 140 insertions(+), 47 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index ec79eec94..e59e1a7ad 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -22,10 +22,13 @@ from enum import Enum from time import sleep +from PyQt5 import QtCore from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, \ QTableView, QTableWidget, QHeaderView, QPushButton, QLabel, \ - QLineEdit + QLineEdit, QDoubleSpinBox + +from lisp.ui.widgets import FadeComboBox from lisp.core.decorators import async, locked_method from lisp.core.fade_functions import ntime, FadeInType, FadeOutType @@ -47,6 +50,10 @@ COL_END_VAL = 2 COL_DO_FADE = 3 +COL_BASE_VAL = 1 +COL_DIFF_VAL = 2 +COL_FUNCTOR = 3 + class OscMessageType(Enum): Int = 'Integer' @@ -74,6 +81,20 @@ def string_to_value(t, sarg): raise ValueError +def convert_value(t, value): + """converts value to requested value for given type""" + if t == OscMessageType.Int.value: + return round(int(value)) + elif t == OscMessageType.Float.value: + return float(value) + elif t == OscMessageType.Bool.value: + return bool(value) + elif t == OscMessageType.String.value: + return str(value) + else: + raise ValueError + + def format_string(t, sarg): """checks if string can be converted and formats it""" if len(sarg) == 0: @@ -107,48 +128,70 @@ def type_can_fade(t): class OscCue(Cue): Name = QT_TRANSLATE_NOOP('CueName', 'OSC Cue') - CueActions = (CueAction.Default, CueAction.Start, CueAction.FadeInStart, - CueAction.Stop, CueAction.FadeOutStop, CueAction.Pause, - CueAction.FadeOutPause) + CueActions = (CueAction.Default, CueAction.Start, CueAction.Stop, + CueAction.Pause) path = Property(default='') args = Property(default=[]) + fade_type = Property(default=FadeInType.Linear.name) # one global fader used for all faded arguments position = Property(default=0.0) def __init__(self, **kwargs): super().__init__(**kwargs) - - self.__in_fadein = False - self.__in_fadeout = False - self.__fader = Fader(self, 'position') - self.changed('position').connect(self.__send_fade) - - def __filter_fade_args(self): + self.name = translate('CueName', self.Name) + + self.__fade_args = None + self.__time = 0 + self.__stop = False + self.__pause = False + + def __prepare_fade(self): + """ returns list of arguments, that will be faded + each item of the list contains: + message type tag, start value, diff value, fade functor + start and end value are converted from strings to the + type to the correct type (message type tag) + """ value_list = [] for arg in self.args: - if arg[COL_DO_FADE]: - value_list.append(arg) + if not arg[COL_END_VAL] or not arg[COL_DO_FADE]: + value_list.append([arg[COL_TYPE], + string_to_value(arg[COL_TYPE], arg[COL_BASE_VAL]), + 0, + None]) + else: + base_value = string_to_value(arg[COL_TYPE], arg[COL_START_VAL]) + diff_value = string_to_value(arg[COL_TYPE], arg[COL_END_VAL]) - base_value + if arg[COL_DO_FADE] and abs(diff_value): + fade_arg = [] + fade_arg.append(arg[COL_TYPE]) + fade_arg.append(base_value) + fade_arg.append(diff_value) + if diff_value > 0: + fade_arg.append(FadeInType[self.fade_type].value) + else: + fade_arg.append(FadeOutType[self.fade_type].value) + value_list.append(fade_arg) return value_list def __start__(self, fade=False): - faded_args = self.__filter_fade_args() - print("FADED ARGS: ", faded_args) - if fade and faded_args: - # Proceed fadein - # self._fadein() - # return True - return self.__send_single_shot() + self.__fade_args = self.__prepare_fade() + if self.__can_fade(): + self.__send_fade() + return True else: - return self.__send_single_shot() + self.__send_single_shot() + + return False def __send_single_shot(self): value_list = [] if len(self.path) < 2 or self.path[0] != '/': elogging.warning("OSC: no valid path for OSC message - nothing sent", dialog=False) - return + return False try: for arg in self.args: value = string_to_value(arg[COL_TYPE], arg[COL_START_VAL]) @@ -159,39 +202,62 @@ def __send_single_shot(self): return False + @async + @locked_method def __send_fade(self): - print("OSC fade position:", self.position) + self.__stop = False + self.__pause = False + + try: + begin = self.__time + duration = (self.duration // 10) + + while (not (self.__stop or self.__pause) and + self.__time <= duration): + time = ntime(self.__time, begin, duration) + + value_list = [] + for arg in self.__fade_args: + if arg[COL_DIFF_VAL]: + functor = arg[COL_FUNCTOR] + current = functor(time, arg[COL_DIFF_VAL], arg[COL_BASE_VAL]) + value_list.append(convert_value(arg[COL_TYPE], current)) + else: + value_list.append(convert_value(arg[COL_TYPE], arg[COL_BASE_VAL])) + + OscCommon().send(self.path, *value_list) + + self.__time += 1 + sleep(0.01) + + if not self.__pause: + # to avoid approximation problems + # self.__send_single_shot() # but with end value + if not self.__stop: + self._ended() + + except Exception as e: + self._error( + translate('OscCue', 'Error during cue execution'), + str(e) + ) + finally: + if not self.__pause: + self.__time = 0 def __stop__(self, fade=False): + self.__stop = True return True def __pause__(self, fade=False): + self.__pause = True return True - def _can_fadein(self): - return self._can_fade() and self.fadein_duration > 0 - - def _can_fadeout(self): - return self._can_fade() and self.fadeout_duration > 0 - - @async - def _fadein(self): - if self._can_fadein(): - self.__in_fadein = True - self.fadein_start.emit() - try: - self.__fader.prepare() - self.__fader.fade(self.fadein_duration, - 1.0, - FadeInType[self.fadein_type]) - finally: - self.__in_fadein = False - self.fadein_end.emit() - - def _fadeout(self): - ended = True - return ended + def __can_fade(self): + return self.__fade_args and self.duration > 0 + def current_time(self): + return self.__time * 10 class OscCueSettings(SettingsPage): Name = QT_TRANSLATE_NOOP('Cue Name', 'OSC Settings') @@ -235,6 +301,26 @@ def __init__(self, **kwargs): self.testButton.clicked.connect(self.__test_message) self.oscGroup.layout().addWidget(self.testButton, 4, 0) + # Fade + self.fadeGroup = QGroupBox(self) + self.fadeGroup.setLayout(QGridLayout()) + self.layout().addWidget(self.fadeGroup) + + self.fadeSpin = QDoubleSpinBox(self.fadeGroup) + self.fadeSpin.setMaximum(3600) + self.fadeGroup.layout().addWidget(self.fadeSpin, 0, 0) + + self.fadeLabel = QLabel(self.fadeGroup) + self.fadeLabel.setAlignment(QtCore.Qt.AlignCenter) + self.fadeGroup.layout().addWidget(self.fadeLabel, 0, 1) + + self.fadeCurveCombo = FadeComboBox(parent=self.fadeGroup) + self.fadeGroup.layout().addWidget(self.fadeCurveCombo, 1, 0) + + self.fadeCurveLabel = QLabel(self.fadeGroup) + self.fadeCurveLabel.setAlignment(QtCore.Qt.AlignCenter) + self.fadeGroup.layout().addWidget(self.fadeCurveLabel, 1, 1) + self.retranslateUi() def retranslateUi(self): @@ -243,10 +329,15 @@ def retranslateUi(self): self.removeButton.setText(translate('OscCue', 'Remove')) self.testButton.setText(translate('OscCue', 'Test')) self.pathLabel.setText(translate('OscCue', 'OSC Path: (example: "/path/to/something")')) + self.fadeGroup.setTitle(translate('OscCue', 'Fade')) + self.fadeLabel.setText(translate('OscCue', 'Time (sec)')) + self.fadeCurveLabel.setText(translate('OscCue', 'Curve')) def get_settings(self): oscmsg = {'path': self.pathEdit.text(), - 'args': [row for row in self.oscModel.rows]} + 'args': [row for row in self.oscModel.rows], + 'duration': self.fadeSpin.value() * 1000, + 'fade_type': self.fadeCurveCombo.currentType()} return oscmsg def load_settings(self, settings): @@ -257,6 +348,8 @@ def load_settings(self, settings): args = settings.get('args', '') for row in args: self.oscModel.appendRow(row[0], row[1], row[2], row[3]) + self.fadeSpin.setValue(settings.get('duration', 0) / 1000) + self.fadeCurveCombo.setCurrentType(settings.get('fade_type', '')) def __new_argument(self): self.oscModel.appendRow(OscMessageType.Int.value, '', '', False) From 7db93b42689d4952d3f193ada8604594ec553d21 Mon Sep 17 00:00:00 2001 From: offtools Date: Sat, 10 Dec 2016 16:17:11 +0100 Subject: [PATCH 027/333] OscCues added checkable option for presets --- lisp/modules/action_cues/osc_cue.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index e59e1a7ad..7c8a585ae 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -333,12 +333,24 @@ def retranslateUi(self): self.fadeLabel.setText(translate('OscCue', 'Time (sec)')) self.fadeCurveLabel.setText(translate('OscCue', 'Curve')) + def enable_check(self, enabled): + self.oscGroup.setCheckable(enabled) + self.oscGroup.setChecked(False) + + self.fadeGroup.setCheckable(enabled) + self.fadeGroup.setChecked(False) + def get_settings(self): - oscmsg = {'path': self.pathEdit.text(), - 'args': [row for row in self.oscModel.rows], - 'duration': self.fadeSpin.value() * 1000, - 'fade_type': self.fadeCurveCombo.currentType()} - return oscmsg + conf = {} + checkable = self.oscGroup.isCheckable() + + if not (checkable and not self.oscGroup.isChecked()): + conf['path'] = self.pathEdit.text() + conf['args'] = [row for row in self.oscModel.rows] + if not (checkable and not self.fadeGroup.isCheckable()): + conf['duration'] = self.fadeSpin.value() * 1000 + conf['fade_type'] = self.fadeCurveCombo.currentType() + return conf def load_settings(self, settings): if 'path' in settings: From 7301b11be5079cb5de9bc8f86c3b04f3794749db Mon Sep 17 00:00:00 2001 From: offtools Date: Sat, 10 Dec 2016 16:20:10 +0100 Subject: [PATCH 028/333] added copyright --- lisp/modules/action_cues/osc_cue.py | 1 + lisp/modules/osc/__init__.py | 21 +++++++++++++++++++++ lisp/modules/osc/osc.py | 1 + lisp/modules/osc/osc_common.py | 1 + lisp/modules/osc/osc_settings.py | 1 + 5 files changed, 25 insertions(+) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 7c8a585ae..12008213a 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -3,6 +3,7 @@ # This file is part of Linux Show Player # # Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/modules/osc/__init__.py b/lisp/modules/osc/__init__.py index 69bcd841b..3b5f38acc 100644 --- a/lisp/modules/osc/__init__.py +++ b/lisp/modules/osc/__init__.py @@ -1 +1,22 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2016 Thomas Achtner +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + + from .osc import Osc \ No newline at end of file diff --git a/lisp/modules/osc/osc.py b/lisp/modules/osc/osc.py index 2bb52904f..3a8d40385 100644 --- a/lisp/modules/osc/osc.py +++ b/lisp/modules/osc/osc.py @@ -3,6 +3,7 @@ # This file is part of Linux Show Player # # Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index 8b3837d6b..ef608bfee 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -3,6 +3,7 @@ # This file is part of Linux Show Player # # Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/modules/osc/osc_settings.py b/lisp/modules/osc/osc_settings.py index 45851ed51..64a582fa3 100644 --- a/lisp/modules/osc/osc_settings.py +++ b/lisp/modules/osc/osc_settings.py @@ -3,6 +3,7 @@ # This file is part of Linux Show Player # # Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From 35675d19262cac231f70c7a94a320d51dcc27f11 Mon Sep 17 00:00:00 2001 From: offtools Date: Tue, 27 Dec 2016 12:10:45 +0100 Subject: [PATCH 029/333] removed double import --- lisp/modules/action_cues/osc_cue.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 12008213a..9ab29f117 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -40,7 +40,6 @@ from lisp.core.has_properties import Property from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate,\ CheckBoxDelegate -from lisp.cues.cue import Cue, CueState from lisp.ui.settings.cue_settings import CueSettingsRegistry,\ SettingsPage from lisp.modules.osc.osc_common import OscCommon From 4c3e351ed8ce396b42229d9e8e8be818402896a1 Mon Sep 17 00:00:00 2001 From: offtools Date: Fri, 30 Dec 2016 21:16:56 +0100 Subject: [PATCH 030/333] started of using Fader for OscCue fades --- lisp/modules/action_cues/osc_cue.py | 163 +++++++++------------------- 1 file changed, 53 insertions(+), 110 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 9ab29f117..ce90412d1 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -18,7 +18,6 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -# TODO: proper Test for Format, add Error Dialog, Cue Test-Button in Settings from enum import Enum from time import sleep @@ -32,19 +31,20 @@ from lisp.ui.widgets import FadeComboBox from lisp.core.decorators import async, locked_method -from lisp.core.fade_functions import ntime, FadeInType, FadeOutType from lisp.core.fader import Fader -from lisp.cues.cue import Cue, CueState, CueAction +from lisp.core.fade_functions import ntime, FadeInType, FadeOutType +from lisp.cues.cue import Cue, CueAction from lisp.ui import elogging from lisp.ui.qmodels import SimpleTableModel from lisp.core.has_properties import Property -from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate,\ +from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate, \ CheckBoxDelegate -from lisp.ui.settings.cue_settings import CueSettingsRegistry,\ +from lisp.ui.settings.cue_settings import CueSettingsRegistry, \ SettingsPage from lisp.modules.osc.osc_common import OscCommon from lisp.ui.ui_utils import translate + COL_TYPE = 0 COL_START_VAL = 1 COL_END_VAL = 2 @@ -52,7 +52,6 @@ COL_BASE_VAL = 1 COL_DIFF_VAL = 2 -COL_FUNCTOR = 3 class OscMessageType(Enum): @@ -135,129 +134,71 @@ class OscCue(Cue): args = Property(default=[]) fade_type = Property(default=FadeInType.Linear.name) - # one global fader used for all faded arguments - position = Property(default=0.0) - def __init__(self, **kwargs): super().__init__(**kwargs) self.name = translate('CueName', self.Name) - self.__fade_args = None - self.__time = 0 - self.__stop = False - self.__pause = False - - def __prepare_fade(self): - """ returns list of arguments, that will be faded - each item of the list contains: - message type tag, start value, diff value, fade functor - start and end value are converted from strings to the - type to the correct type (message type tag) - """ - value_list = [] - for arg in self.args: - if not arg[COL_END_VAL] or not arg[COL_DO_FADE]: - value_list.append([arg[COL_TYPE], - string_to_value(arg[COL_TYPE], arg[COL_BASE_VAL]), - 0, - None]) - else: - base_value = string_to_value(arg[COL_TYPE], arg[COL_START_VAL]) - diff_value = string_to_value(arg[COL_TYPE], arg[COL_END_VAL]) - base_value - if arg[COL_DO_FADE] and abs(diff_value): - fade_arg = [] - fade_arg.append(arg[COL_TYPE]) - fade_arg.append(base_value) - fade_arg.append(diff_value) - if diff_value > 0: - fade_arg.append(FadeInType[self.fade_type].value) - else: - fade_arg.append(FadeOutType[self.fade_type].value) - value_list.append(fade_arg) - return value_list + self.__fader = Fader(self, 'position') + self.value = 1 + self.current_value = 0 + + self.__arg_list = [] + self.__has_fade = False + + def __init_fader(self): + self.__fader.target = self + return True def __start__(self, fade=False): - self.__fade_args = self.__prepare_fade() - if self.__can_fade(): - self.__send_fade() - return True - else: - self.__send_single_shot() + if self.__init_fader(): + if self.__fader.is_paused(): + self.__fader.restart() + return True + + if self.duration > 0: + if self.__fader.target.current_value > self.value: + self.__fade(FadeOutType[self.fade_type]) + return True + elif self.__fader.target.current_value < self.value: + self.__fade(FadeInType[self.fade_type]) + return True + else: + # single shot + self.__fader.target.current_value = self.value return False - def __send_single_shot(self): - value_list = [] + def __stop__(self, fade=False): + self.__fader.stop() + return True - if len(self.path) < 2 or self.path[0] != '/': - elogging.warning("OSC: no valid path for OSC message - nothing sent", dialog=False) - return False - try: - for arg in self.args: - value = string_to_value(arg[COL_TYPE], arg[COL_START_VAL]) - value_list.append(value) - OscCommon().send(self.path, *value_list) - except ValueError: - elogging.warning("OSC: Error on parsing argument list - nothing sent", dialog=False) + def __pause__(self, fade=False): + self.__fader.pause() + return True - return False + __interrupt__ = __stop__ @async - @locked_method - def __send_fade(self): - self.__stop = False - self.__pause = False - + def __fade(self, fade_type): try: - begin = self.__time - duration = (self.duration // 10) - - while (not (self.__stop or self.__pause) and - self.__time <= duration): - time = ntime(self.__time, begin, duration) - - value_list = [] - for arg in self.__fade_args: - if arg[COL_DIFF_VAL]: - functor = arg[COL_FUNCTOR] - current = functor(time, arg[COL_DIFF_VAL], arg[COL_BASE_VAL]) - value_list.append(convert_value(arg[COL_TYPE], current)) - else: - value_list.append(convert_value(arg[COL_TYPE], arg[COL_BASE_VAL])) - - OscCommon().send(self.path, *value_list) - - self.__time += 1 - sleep(0.01) - - if not self.__pause: - # to avoid approximation problems - # self.__send_single_shot() # but with end value - if not self.__stop: - self._ended() - + self.__fader.prepare() + ended = self.__fader.fade(round(self.duration / 1000, 2), + self.value, + fade_type) + + # to avoid approximation problems + self.__fader.target.current_value = self.value + if ended: + self._ended() except Exception as e: self._error( translate('OscCue', 'Error during cue execution'), str(e) ) - finally: - if not self.__pause: - self.__time = 0 - - def __stop__(self, fade=False): - self.__stop = True - return True - - def __pause__(self, fade=False): - self.__pause = True - return True - - def __can_fade(self): - return self.__fade_args and self.duration > 0 def current_time(self): - return self.__time * 10 + return self.__fader.current_time() + class OscCueSettings(SettingsPage): Name = QT_TRANSLATE_NOOP('Cue Name', 'OSC Settings') @@ -394,7 +335,8 @@ def __test_message(self): except ValueError: elogging.warning("OSC: Error on parsing argument list - nothing sent") - def __argument_changed(self, index_topleft, index_bottomright, roles): + @staticmethod + def __argument_changed(index_topleft, index_bottomright, roles): model = index_bottomright.model() osctype = model.rows[index_bottomright.row()][COL_TYPE] start = model.rows[index_bottomright.row()][COL_START_VAL] @@ -419,7 +361,7 @@ def __argument_changed(self, index_topleft, index_bottomright, roles): model.rows[index_bottomright.row()][COL_DO_FADE] = False # for Fade, test if end value is provided - if not model.rows[index_bottomright.row()][COL_END_VAL]and \ + if not model.rows[index_bottomright.row()][COL_END_VAL] and \ model.rows[index_bottomright.row()][COL_DO_FADE]: elogging.warning("OSC Argument Error", details="FadeOut value is missing", dialog=True) model.rows[index_bottomright.row()][3] = False @@ -451,4 +393,5 @@ def __init__(self, **kwargs): for column, delegate in enumerate(self.delegates): self.setItemDelegateForColumn(column, delegate) + CueSettingsRegistry().add_item(OscCueSettings, OscCue) From e1a594ddc8dd9536b30074108904bf765cc47c17 Mon Sep 17 00:00:00 2001 From: offtools Date: Sat, 31 Dec 2016 03:20:55 +0100 Subject: [PATCH 031/333] using Fader class for fades in OscCue --- lisp/modules/action_cues/osc_cue.py | 78 +++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index ce90412d1..452bc84b3 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -44,14 +44,11 @@ from lisp.modules.osc.osc_common import OscCommon from lisp.ui.ui_utils import translate - COL_TYPE = 0 COL_START_VAL = 1 COL_END_VAL = 2 -COL_DO_FADE = 3 - -COL_BASE_VAL = 1 COL_DIFF_VAL = 2 +COL_DO_FADE = 3 class OscMessageType(Enum): @@ -138,33 +135,79 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.name = translate('CueName', self.Name) - self.__fader = Fader(self, 'position') - self.value = 1 - self.current_value = 0 + self.__fader = Fader(self, '_position') + self.__value = 0 + self.__is_fadein = True self.__arg_list = [] self.__has_fade = False + def __get_position(self): + return self.__value + + def __set_position(self, value): + self.__value = value + arguments = [row[COL_START_VAL] + row[COL_DIFF_VAL] * self.__value for row in self.__arg_list] + OscCommon().send(self.path, *arguments) + + def __get_fadein(self): + return self.__is_fadein + + def __set_fadein(self, fadein): + if fadein: + self.__is_fadein = True + else: + self.__is_fadein = False + + _position = property(__get_position, __set_position) + is_fadein = property(__get_fadein, __set_fadein) + def __init_fader(self): - self.__fader.target = self + self.__arg_list = [] + for arg in self.args: + if not arg[COL_END_VAL] or not arg[COL_DO_FADE]: + self.__arg_list.append([arg[COL_TYPE], + string_to_value(arg[COL_TYPE], arg[COL_START_VAL]), + 0]) + else: + self.__has_fade = True + base_value = string_to_value(arg[COL_TYPE], arg[COL_START_VAL]) + diff_value = string_to_value(arg[COL_TYPE], arg[COL_END_VAL]) - base_value + self.__arg_list.append([arg[COL_TYPE], base_value, diff_value]) + + for row in self.__arg_list: + if row[COL_DIFF_VAL] > 0: + self.is_fadein = True + break + elif row[COL_DIFF_VAL] < 0: + self.is_fadein = False + break + else: + continue + + # we always fade from 0 to 1, reset value before start or restart + self.__value = 0 + return True + def has_fade(self): + return self.duration > 0 and self.__has_fade + def __start__(self, fade=False): if self.__init_fader(): if self.__fader.is_paused(): self.__fader.restart() return True - if self.duration > 0: - if self.__fader.target.current_value > self.value: + if self.has_fade(): + if not self.is_fadein: self.__fade(FadeOutType[self.fade_type]) return True - elif self.__fader.target.current_value < self.value: + else: self.__fade(FadeInType[self.fade_type]) return True else: - # single shot - self.__fader.target.current_value = self.value + self._position = 1 return False @@ -183,11 +226,11 @@ def __fade(self, fade_type): try: self.__fader.prepare() ended = self.__fader.fade(round(self.duration / 1000, 2), - self.value, + 1, fade_type) # to avoid approximation problems - self.__fader.target.current_value = self.value + self._position = 1 if ended: self._ended() except Exception as e: @@ -366,6 +409,11 @@ def __argument_changed(index_topleft, index_bottomright, roles): elogging.warning("OSC Argument Error", details="FadeOut value is missing", dialog=True) model.rows[index_bottomright.row()][3] = False + # test if start != end value + if model.rows[index_bottomright.row()][COL_END_VAL] == model.rows[index_bottomright.row()][COL_START_VAL]: + model.rows[index_bottomright.row()][COL_DO_FADE] = False + elogging.warning("OSC Argument Error", details="FadeIn equals FadeOut - no fade", dialog=True) + class OscView(QTableView): def __init__(self, **kwargs): From 0597ade1981f22d2cee5e6c857e630e081cc4045 Mon Sep 17 00:00:00 2001 From: offtools Date: Sat, 31 Dec 2016 03:33:10 +0100 Subject: [PATCH 032/333] notes --- lisp/modules/action_cues/osc_cue.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 452bc84b3..1ed441a73 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -163,6 +163,8 @@ def __set_fadein(self, fadein): is_fadein = property(__get_fadein, __set_fadein) def __init_fader(self): + # arguments from the gui are converted and stored in a new list + # list: [ type, base_value, diff_value ] self.__arg_list = [] for arg in self.args: if not arg[COL_END_VAL] or not arg[COL_DO_FADE]: @@ -175,6 +177,14 @@ def __init_fader(self): diff_value = string_to_value(arg[COL_TYPE], arg[COL_END_VAL]) - base_value self.__arg_list.append([arg[COL_TYPE], base_value, diff_value]) + # as only one fader for all arguments is used, + # we decide at the first faded argument + # if we use fadein or fadeout, + # following arguments containing fades are ignored for that test. + # in and outfades on different arguments are possible, + # but handled internal either as in or outfade together, + # can cause problems with quadric curve (add this to the wikki), + # which can start reverse, when mixing in and out fade values in one message for row in self.__arg_list: if row[COL_DIFF_VAL] > 0: self.is_fadein = True @@ -185,7 +195,7 @@ def __init_fader(self): else: continue - # we always fade from 0 to 1, reset value before start or restart + # always fade from 0 to 1, reset value before start or restart fade self.__value = 0 return True From 34c0b7195a6b036c9d8dad6c0d39bd76158829ab Mon Sep 17 00:00:00 2001 From: offtools Date: Sun, 1 Jan 2017 02:17:25 +0100 Subject: [PATCH 033/333] simplified is_fadein --- lisp/modules/action_cues/osc_cue.py | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 1ed441a73..d664deff0 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -20,7 +20,6 @@ from enum import Enum -from time import sleep from PyQt5 import QtCore from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP @@ -30,9 +29,9 @@ from lisp.ui.widgets import FadeComboBox -from lisp.core.decorators import async, locked_method +from lisp.core.decorators import async from lisp.core.fader import Fader -from lisp.core.fade_functions import ntime, FadeInType, FadeOutType +from lisp.core.fade_functions import FadeInType, FadeOutType from lisp.cues.cue import Cue, CueAction from lisp.ui import elogging from lisp.ui.qmodels import SimpleTableModel @@ -137,7 +136,7 @@ def __init__(self, **kwargs): self.__fader = Fader(self, '_position') self.__value = 0 - self.__is_fadein = True + self.__fadein = True self.__arg_list = [] self.__has_fade = False @@ -150,17 +149,7 @@ def __set_position(self, value): arguments = [row[COL_START_VAL] + row[COL_DIFF_VAL] * self.__value for row in self.__arg_list] OscCommon().send(self.path, *arguments) - def __get_fadein(self): - return self.__is_fadein - - def __set_fadein(self, fadein): - if fadein: - self.__is_fadein = True - else: - self.__is_fadein = False - _position = property(__get_position, __set_position) - is_fadein = property(__get_fadein, __set_fadein) def __init_fader(self): # arguments from the gui are converted and stored in a new list @@ -187,10 +176,10 @@ def __init_fader(self): # which can start reverse, when mixing in and out fade values in one message for row in self.__arg_list: if row[COL_DIFF_VAL] > 0: - self.is_fadein = True + self.__fadein = True break elif row[COL_DIFF_VAL] < 0: - self.is_fadein = False + self.__fadein = False break else: continue @@ -210,7 +199,7 @@ def __start__(self, fade=False): return True if self.has_fade(): - if not self.is_fadein: + if not self.__fadein: self.__fade(FadeOutType[self.fade_type]) return True else: From 5df5b8442c540cc3f9057896caff28f872b9cb42 Mon Sep 17 00:00:00 2001 From: offtools Date: Sun, 1 Jan 2017 02:35:54 +0100 Subject: [PATCH 034/333] cleanup, removed feedback code --- lisp/modules/osc/osc_common.py | 13 ------------- lisp/modules/osc/osc_settings.py | 11 ----------- 2 files changed, 24 deletions(-) diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index ef608bfee..f06e18d05 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -137,16 +137,3 @@ def send(self, path, *args): def __new_message(self, path, args, types, src): self.push_log(path, args, types, src, False) self.new_message.emit(path, args, types, src) - # elogging.warning('OscCommon: unknown message received: {0} {1} {2}'.format(path, types, args), dialog=False) - - # def register_callback(self, path, typespec, func): - # # insert into callback list before callback, otherwise its ignored - # self.__callbacks.insert(-1, [path, typespec, func]) - # - # # fallback needs to be the registered callback - # self.__srv.del_method(None, None) - # self.__srv.add_method(path, typespec, func) - # self.__srv.add_method(None, None, self.__new_message) - - def activate_feedback(self, feedback): - pass diff --git a/lisp/modules/osc/osc_settings.py b/lisp/modules/osc/osc_settings.py index 64a582fa3..2a64d7352 100644 --- a/lisp/modules/osc/osc_settings.py +++ b/lisp/modules/osc/osc_settings.py @@ -48,11 +48,6 @@ def __init__(self, **kwargs): translate('OscSettings', 'enable OSC')) self.groupBox.layout().addWidget(self.enableModule) - self.enableFeedback = QCheckBox(self.groupBox) - self.enableFeedback.setText( - translate('OscSettings', 'enable Feedback')) - self.groupBox.layout().addWidget(self.enableFeedback) - hbox = QHBoxLayout() self.inportBox = QSpinBox(self) self.inportBox.setMinimum(1000) @@ -84,7 +79,6 @@ def __init__(self, **kwargs): self.groupBox.layout().addLayout(hbox) self.enableModule.stateChanged.connect(self.activate_module, Qt.QueuedConnection) - self.enableFeedback.stateChanged.connect(self.activate_feedback, Qt.QueuedConnection) self.inportBox.valueChanged.connect(self.change_inport, Qt.QueuedConnection) self.outportBox.valueChanged.connect(self.change_outport, Qt.QueuedConnection) self.hostnameEdit.textChanged.connect(self.change_hostname, Qt.QueuedConnection) @@ -101,9 +95,6 @@ def activate_module(self): # disable OSC Module in Settings config.set('OSC', 'enabled', 'False') - def activate_feedback(self): - OscCommon().activate_feedback(self.enableFeedback.isChecked()) - def change_inport(self): port = self.inportBox.value() if str(port) != config.get('OSC', 'inport'): @@ -125,7 +116,6 @@ def get_settings(self): 'inport': str(self.inportBox.value()), 'outport': str(self.outportBox.value()), 'hostname': str(self.hostnameEdit.text()), - 'feedback': str(self.enableFeedback.isChecked()) } return {'OSC': conf} @@ -133,7 +123,6 @@ def load_settings(self, settings): settings = settings.get('OSC', {}) self.enableModule.setChecked(settings.get('enabled') == 'True') - self.enableFeedback.setChecked(settings.get('feedback') == 'True') self.inportBox.setValue(int(settings.get('inport'))) self.outportBox.setValue(int(settings.get('outport'))) self.hostnameEdit.setText(settings.get('hostname')) From 417ae5a589f01d3482ec4d3a8717df1ffb118c30 Mon Sep 17 00:00:00 2001 From: offtools Date: Wed, 4 Jan 2017 20:05:07 +0100 Subject: [PATCH 035/333] osc cue values of argument list, stored as values in a dict, not list of strings --- lisp/modules/action_cues/osc_cue.py | 212 +++++++++++++++++----------- 1 file changed, 133 insertions(+), 79 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index d664deff0..e48ba4f99 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -18,6 +18,10 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +# TODO: work with values in config['args'] not strings +# only gui operates with strings, config holds real values +# config['args'] is a dict (keys: type, start, end (optional), fade) +# all conversions are done in settings not the cue from enum import Enum @@ -46,9 +50,11 @@ COL_TYPE = 0 COL_START_VAL = 1 COL_END_VAL = 2 -COL_DIFF_VAL = 2 COL_DO_FADE = 3 +COL_BASE_VAL = 1 +COL_DIFF_VAL = 2 + class OscMessageType(Enum): Int = 'Integer' @@ -57,6 +63,17 @@ class OscMessageType(Enum): String = 'String' +def test_path(path): + if isinstance(path, str): + if len(path) > 1 and path[0] is '/': + return True + return False + +# TODO: test argument list +def test_arguments(args): + print(args) + + def string_to_value(t, sarg): """converts string to requested value for given type""" if t == OscMessageType.Int.value: @@ -92,9 +109,9 @@ def convert_value(t, value): def format_string(t, sarg): """checks if string can be converted and formats it""" - if len(sarg) == 0: - return '' - elif t == OscMessageType.Int.value: + # if len(sarg) == 0: + # return '' + if t == OscMessageType.Int.value: return "{0:d}".format(int(sarg)) elif t == OscMessageType.Float.value: return "{0:.2f}".format(float(sarg)) @@ -146,43 +163,49 @@ def __get_position(self): def __set_position(self, value): self.__value = value - arguments = [row[COL_START_VAL] + row[COL_DIFF_VAL] * self.__value for row in self.__arg_list] - OscCommon().send(self.path, *arguments) + args = [] + for row in self.__arg_list: + if row[COL_DIFF_VAL] > 0: + args.append(row[COL_BASE_VAL] + row[COL_DIFF_VAL] * self.__value) + else: + args.append(row[COL_BASE_VAL]) + OscCommon().send(self.path, *args) _position = property(__get_position, __set_position) - def __init_fader(self): - # arguments from the gui are converted and stored in a new list + def __init_arguments(self): + # check path + if not test_path(self.path): + return False + + # arguments from the cue settings are converted and stored in a new list # list: [ type, base_value, diff_value ] self.__arg_list = [] - for arg in self.args: - if not arg[COL_END_VAL] or not arg[COL_DO_FADE]: - self.__arg_list.append([arg[COL_TYPE], - string_to_value(arg[COL_TYPE], arg[COL_START_VAL]), - 0]) - else: - self.__has_fade = True - base_value = string_to_value(arg[COL_TYPE], arg[COL_START_VAL]) - diff_value = string_to_value(arg[COL_TYPE], arg[COL_END_VAL]) - base_value - self.__arg_list.append([arg[COL_TYPE], base_value, diff_value]) - - # as only one fader for all arguments is used, - # we decide at the first faded argument - # if we use fadein or fadeout, - # following arguments containing fades are ignored for that test. - # in and outfades on different arguments are possible, - # but handled internal either as in or outfade together, - # can cause problems with quadric curve (add this to the wikki), - # which can start reverse, when mixing in and out fade values in one message - for row in self.__arg_list: - if row[COL_DIFF_VAL] > 0: - self.__fadein = True - break - elif row[COL_DIFF_VAL] < 0: - self.__fadein = False - break - else: - continue + try: + for arg in self.args: + if 'end' in arg and arg['fade']: + self.__has_fade = True + diff_value = arg['end'] - arg['start'] + self.__arg_list.append([arg['type'], arg['start'], diff_value]) + else: + self.__arg_list.append([arg['type'], + arg['start'], + 0]) + except: + elogging.error("OSC: could not parse argument list, nothing sent") + return False + + # set fade type, based on the first argument, which will have a fade + if self.__has_fade: + for row in self.__arg_list: + if row[COL_DIFF_VAL] > 0: + self.__fadein = True + break + elif row[COL_DIFF_VAL] < 0: + self.__fadein = False + break + else: + continue # always fade from 0 to 1, reset value before start or restart fade self.__value = 0 @@ -193,7 +216,7 @@ def has_fade(self): return self.duration > 0 and self.__has_fade def __start__(self, fade=False): - if self.__init_fader(): + if self.__init_arguments(): if self.__fader.is_paused(): self.__fader.restart() return True @@ -207,6 +230,8 @@ def __start__(self, fade=False): return True else: self._position = 1 + else: + elogging.error("OSC: Error on parsing argument list - nothing sent", dialog=False) return False @@ -259,6 +284,7 @@ def __init__(self, **kwargs): self.pathEdit = QLineEdit() self.oscGroup.layout().addWidget(self.pathEdit, 1, 0, 1, 2) + self.pathEdit.editingFinished.connect(self.__path_changed) self.oscModel = SimpleTableModel([ translate('Osc Cue', 'Type'), @@ -327,9 +353,25 @@ def get_settings(self): conf = {} checkable = self.oscGroup.isCheckable() + # TODO: test paths and argument + # if not (checkable and not self.oscGroup.isChecked()): + # if test_path(): + # return test_arguments([row for row in self.oscModel.rows]) + if not (checkable and not self.oscGroup.isChecked()): conf['path'] = self.pathEdit.text() - conf['args'] = [row for row in self.oscModel.rows] + args_list = [] + for row in self.oscModel.rows: + arg = {'type': row[COL_TYPE], + 'start': string_to_value(row[COL_TYPE], row[COL_START_VAL])} + + if row[COL_END_VAL]: + arg['end'] = string_to_value(row[COL_TYPE], row[COL_END_VAL]) + + arg['fade'] = row[COL_DO_FADE] + args_list.append(arg) + + conf['args'] = args_list if not (checkable and not self.fadeGroup.isCheckable()): conf['duration'] = self.fadeSpin.value() * 1000 conf['fade_type'] = self.fadeCurveCombo.currentType() @@ -340,45 +382,60 @@ def load_settings(self, settings): path = settings.get('path', '') self.pathEdit.setText(path) if 'args' in settings: + args = settings.get('args', '') - for row in args: - self.oscModel.appendRow(row[0], row[1], row[2], row[3]) + for arg in args: + self.oscModel.appendRow(arg['type'], + str(arg['start']), + str(arg['end']) if 'end' in arg else '', + arg['fade']) + self.fadeSpin.setValue(settings.get('duration', 0) / 1000) self.fadeCurveCombo.setCurrentType(settings.get('fade_type', '')) def __new_argument(self): - self.oscModel.appendRow(OscMessageType.Int.value, '', '', False) + self.oscModel.appendRow(OscMessageType.Int.value, '1', '', False) def __remove_argument(self): if self.oscModel.rowCount(): self.oscModel.removeRow(self.oscView.currentIndex().row()) def __test_message(self): - oscmsg = {'path': self.pathEdit.text(), - 'args': [row for row in self.oscModel.rows]} - value_list = [] - if len(oscmsg['path']) < 2: - elogging.warning("OSC: no valid path for OSC message - nothing sent", - details="Path too short.", - dialog=True) - return + # TODO: check arguments for error + + path = self.pathEdit.text() - if oscmsg['path'][0] != '/': - elogging.warning("OSC: no valid path for OSC message - nothing", - details="Path should start with '/'.", + if not test_path(path): + elogging.warning("OSC: no valid path for OSC message - nothing sent", + details="Path should start with a '/' followed by a name.", dialog=True) return try: - for arg in oscmsg['args']: - value = string_to_value(arg[0], arg[1]) - value_list.append(value) - OscCommon().send(oscmsg['path'], *value_list) + args = [] + for row in self.oscModel.rows: + if row[COL_END_VAL] and row[COL_DO_FADE]: + args.append(string_to_value(row[COL_TYPE], row[COL_END_VAL])) + else: + args.append(string_to_value(row[COL_TYPE], row[COL_START_VAL])) + + OscCommon().send(path, *args) except ValueError: - elogging.warning("OSC: Error on parsing argument list - nothing sent") + elogging.error("OSC: Error on parsing argument list - nothing sent") + + def __path_changed(self): + if not test_path(self.pathEdit.text()): + elogging.warning("OSC: no valid path for OSC message", + details="Path should start with a '/' followed by a name.", + dialog=True) @staticmethod def __argument_changed(index_topleft, index_bottomright, roles): + """helper function, no error handling: + *formats input + *disable fade for non fade types + *checks if start and end value is provided + """ model = index_bottomright.model() osctype = model.rows[index_bottomright.row()][COL_TYPE] start = model.rows[index_bottomright.row()][COL_START_VAL] @@ -388,30 +445,27 @@ def __argument_changed(index_topleft, index_bottomright, roles): try: model.rows[index_bottomright.row()][COL_START_VAL] = format_string(osctype, start) except ValueError: - model.rows[index_bottomright.row()][COL_START_VAL] = '' + model.rows[index_bottomright.row()][COL_START_VAL] = '1' elogging.warning("OSC Argument Error", details="{0} not a {1}".format(start, osctype), dialog=True) - # test End value for correct format - try: - model.rows[index_bottomright.row()][COL_END_VAL] = format_string(osctype, end) - except ValueError: - model.rows[index_bottomright.row()][COL_END_VAL] = '' - elogging.warning("OSC Argument Error", details="{0} not a {1}".format(end, osctype), dialog=True) - - # Fade only enabled for Int and Float - if not type_can_fade(osctype): - model.rows[index_bottomright.row()][COL_DO_FADE] = False - - # for Fade, test if end value is provided - if not model.rows[index_bottomright.row()][COL_END_VAL] and \ - model.rows[index_bottomright.row()][COL_DO_FADE]: - elogging.warning("OSC Argument Error", details="FadeOut value is missing", dialog=True) - model.rows[index_bottomright.row()][3] = False - - # test if start != end value - if model.rows[index_bottomright.row()][COL_END_VAL] == model.rows[index_bottomright.row()][COL_START_VAL]: - model.rows[index_bottomright.row()][COL_DO_FADE] = False - elogging.warning("OSC Argument Error", details="FadeIn equals FadeOut - no fade", dialog=True) + # test fades + if model.rows[index_bottomright.row()][COL_DO_FADE]: + if type_can_fade(osctype): + try: + model.rows[index_bottomright.row()][COL_END_VAL] = format_string(osctype, end) + except ValueError: + elogging.warning("OSC Argument Error", details="{0} not a {1}".format(end, osctype), dialog=True) + model.rows[index_bottomright.row()][COL_END_VAL] = '' + model.rows[index_bottomright.row()][3] = False + # end_value equals start_value + if model.rows[index_bottomright.row()][COL_END_VAL] == model.rows[index_bottomright.row()][COL_START_VAL]: + model.rows[index_bottomright.row()][COL_END_VAL] = '' + model.rows[index_bottomright.row()][COL_DO_FADE] = False + elogging.warning("OSC Argument Error", details="FadeIn equals FadeOut - no fade", dialog=True) + else: + # disable fade for non fade types + model.rows[index_bottomright.row()][COL_DO_FADE] = False + model.rows[index_bottomright.row()][COL_END_VAL] = '' class OscView(QTableView): From 291723700c2cf0b47ab90b2f58529c948f4e2ad7 Mon Sep 17 00:00:00 2001 From: offtools Date: Thu, 5 Jan 2017 18:24:04 +0100 Subject: [PATCH 036/333] removed unused osc log, @osc_handler is obsolete by now, log will be introduced later --- lisp/modules/action_cues/osc_cue.py | 7 +-- lisp/modules/osc/osc_common.py | 58 ++++++++++++------------ lisp/plugins/controller/protocols/osc.py | 4 +- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index e48ba4f99..8906ef7d8 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -354,9 +354,10 @@ def get_settings(self): checkable = self.oscGroup.isCheckable() # TODO: test paths and argument - # if not (checkable and not self.oscGroup.isChecked()): - # if test_path(): - # return test_arguments([row for row in self.oscModel.rows]) + if not (checkable and not self.oscGroup.isChecked()): + if not test_path(self.pathEdit.text()) or not test_arguments([row for row in self.oscModel.rows]): + elogging.error("OSC: Error parsing message elements, remove message") + return conf if not (checkable and not self.oscGroup.isChecked()): conf['path'] = self.pathEdit.text() diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index f06e18d05..1810b2391 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -33,23 +33,23 @@ # decorator for OSC callback (pushing messages to log, emits message_event) -def osc_handler(func): - def func_wrapper(path, args, types, src): - func(path, args, types, src) - OscCommon().push_log(path, args, types, src) - OscCommon().new_message.emit(path, args, types, src) - return func_wrapper +# def __osc_handler(func): +# def func_wrapper(path, args, types): +# func() +# # OscCommon().push_log(path, args, types, src) +# OscCommon().new_message.emit(path, args, types) +# return func_wrapper -@osc_handler -def _go(path, args, types, src): +# @__osc_handler +def _go(): """triggers GO in ListLayout""" if isinstance(MainWindow().layout, ListLayout): MainWindow().layout.go() -@osc_handler -def _reset_list(path, args, types, src): +# @__osc_handler +def _reset_list(): """reset, stops all cues, sets cursor to Cue index 0 in ListLayout""" if isinstance(MainWindow().layout, ListLayout): for cue in Application().cue_model: @@ -57,24 +57,24 @@ def _reset_list(path, args, types, src): MainWindow().layout.set_current_index(0) -@osc_handler -def _pause_all(path, args, types, src): +# @__osc_handler +def _pause_all(): """triggers global pause all media""" for cue in Application().cue_model: if cue.state == CueState.Running: cue.pause() -@osc_handler -def _restart_all(path, args, types, src): +# @__osc_handler +def _restart_all(): """triggers global play, if pausing""" for cue in Application().cue_model: if cue.state == CueState.Pause: cue.start() -@osc_handler -def _stop_all(path, args, types, src): +# @__osc_handler +def _stop_all(): """triggers global stop, stops all media cues""" for cue in Application().cue_model: cue.stop() @@ -89,19 +89,19 @@ def __init__(self): # TODO: static paths and callbacks, find smarter way self.__callbacks = [ - ['/lisp/list/go', '', _go], - ['/lisp/list/reset', '', _reset_list], - ['/lisp/pause', '', _pause_all], - ['/lisp/start', '', _restart_all], - ['/lisp/stop', '', _stop_all], + ['/lisp/list/go', None, _go], + ['/lisp/list/reset', None, _reset_list], + ['/lisp/pause', None, _pause_all], + ['/lisp/start', None, _restart_all], + ['/lisp/stop', None, _stop_all], [None, None, self.__new_message] ] - def push_log(self, path, args, types, src, success=True): - self.__log.append([path, args, types, src, success]) - - def get_log(self): - return self.__log + # def push_log(self, path, args, types, src, success=True): + # self.__log.append([path, args, types, src, success]) + # + # def get_log(self): + # return self.__log def start(self): if self.__listening: @@ -134,6 +134,6 @@ def send(self, path, *args): target = Address(config['OSC']['hostname'], int(config['OSC']['outport'])) self.__srv.send(target, path, *args) - def __new_message(self, path, args, types, src): - self.push_log(path, args, types, src, False) - self.new_message.emit(path, args, types, src) + def __new_message(self, path, args, types): + # self.push_log(path, args, types, src, False) + self.new_message.emit(path, args, types) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index b8ab99ae0..5554b28ac 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -38,7 +38,7 @@ def __init__(self): if check_module('osc') and OscCommon().listening: OscCommon().new_message.connect(self.__new_message) - def __new_message(self, path, args, types, src): + def __new_message(self, path, args, types): self.protocol_event.emit(Osc.key_from_message(path, types)) @staticmethod @@ -145,7 +145,7 @@ def capture_message(self): self.oscModel.appendRow(self.capturedMessage['path'], self.capturedMessage['types'], self._default_action) OscCommon().new_message.disconnect(self.__show_message) - def __show_message(self, path, args, types, src): + def __show_message(self, path, args, types): self.capturedMessage['path'] = path self.capturedMessage['types'] = types self.captureLabel.setText('{0} "{1}" {2}'.format(path, types, args)) From e699130276d716fcdad4f1e06d9dacb62ceed1e2 Mon Sep 17 00:00:00 2001 From: offtools Date: Fri, 6 Jan 2017 21:57:41 +0100 Subject: [PATCH 037/333] osc protocol now supports arguments, /mysong/play i 0 can be used for pause, /mysong/play 1 for start --- lisp/plugins/controller/protocols/osc.py | 41 ++++++++++++++++-------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 5554b28ac..95e712093 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -39,19 +39,28 @@ def __init__(self): OscCommon().new_message.connect(self.__new_message) def __new_message(self, path, args, types): - self.protocol_event.emit(Osc.key_from_message(path, types)) + key = Osc.key_from_message(path, args, types) + self.protocol_event.emit(key) @staticmethod - def key_from_message(path, types): + def key_from_message(path, args, types): if len(types): - return '{0} {1}'.format(path, types) + args = ' '.join([str(i) for i in args]) + return 'OSC\u001f{0}\u001f{1}\u001f{2}'.format(path, args, types) else: - return path + return 'OSC\u001f{0}'.format(path) + + @staticmethod + def key_from_settings(path, args, types): + if len(types): + return 'OSC\u001f{0}\u001f{1}\u001f{2}'.format(path, args, types) + else: + return 'OSC\u001f{0}'.format(path) @staticmethod def from_key(message_str): - m = message_str.split(' ') - return m[0], '' if len(m) < 2 else m[1] + m = message_str.split('\u001f')[1:] + return (m[0], m[1], m[2]) if len(m) > 2 else (m[0], '', '') class OscSettings(CueSettingsPage): @@ -69,6 +78,7 @@ def __init__(self, cue_class, **kwargs): self.oscModel = SimpleTableModel([ translate('ControllerOscSettings', 'Path'), + translate('ControllerOscSettings', 'Arguments'), translate('ControllerOscSettings', 'Types'), translate('ControllerOscSettings', 'Actions')]) @@ -103,7 +113,7 @@ def __init__(self, cue_class, **kwargs): self.buttonBox.rejected.connect(self.captureDialog.reject) self.captureDialog.layout().addWidget(self.buttonBox) - self.capturedMessage = {'path': None, 'types': None} + self.capturedMessage = {'path': None, 'args': None, 'types': None} self.retranslateUi() @@ -124,7 +134,7 @@ def get_settings(self): messages = [] for row in self.oscModel.rows: - message = Osc.key_from_message(row[0], row[1]) + message = Osc.key_from_settings(row[0], row[1], row[2]) messages.append((message, row[-1])) if messages: @@ -135,23 +145,27 @@ def get_settings(self): def load_settings(self, settings): if 'osc' in settings: for options in settings['osc']: - path, types = Osc.from_key(options[0]) - self.oscModel.appendRow(path, types, options[1]) + path, args, types = Osc.from_key(options[0]) + self.oscModel.appendRow(path, args, types, options[1]) def capture_message(self): OscCommon().new_message.connect(self.__show_message) result = self.captureDialog.exec() if result == QDialog.Accepted: - self.oscModel.appendRow(self.capturedMessage['path'], self.capturedMessage['types'], self._default_action) + self.oscModel.appendRow(self.capturedMessage['path'], + self.capturedMessage['args'], + self.capturedMessage['types'], + self._default_action) OscCommon().new_message.disconnect(self.__show_message) def __show_message(self, path, args, types): self.capturedMessage['path'] = path + self.capturedMessage['args'] = ' '.join([str(i) for i in args]) self.capturedMessage['types'] = types self.captureLabel.setText('{0} "{1}" {2}'.format(path, types, args)) def __new_message(self): - self.oscModel.appendRow('', '', self._default_action) + self.oscModel.appendRow('', '', '', self._default_action) def __remove_message(self): self.oscModel.removeRow(self.OscView.currentIndex().row()) @@ -163,6 +177,7 @@ def __init__(self, cue_class, **kwargs): cue_actions = [action.name for action in cue_class.CueActions] self.delegates = [LineEditDelegate(), + LineEditDelegate(), LineEditDelegate(), ComboBoxDelegate(options=cue_actions, tr_context='CueAction')] @@ -181,4 +196,4 @@ def __init__(self, cue_class, **kwargs): self.verticalHeader().setHighlightSections(False) for column, delegate in enumerate(self.delegates): - self.setItemDelegateForColumn(column, delegate) + self.setItemDelegateForColumn(column, delegate) \ No newline at end of file From ff7801f45e735537d1d8f10e6b124e635d5eccce Mon Sep 17 00:00:00 2001 From: offtools Date: Sat, 7 Jan 2017 20:31:29 +0100 Subject: [PATCH 038/333] better protocol osc keys, no separator using ast.literal_eval to parse keys (see conversion on gitter) --- lisp/plugins/controller/protocols/osc.py | 68 ++++++++++++++---------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 95e712093..4fc95cd2b 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -17,6 +17,11 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +# TODO: LineEdit text orientation +# TODO: error handling for manual edited messages + +import ast + from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QGroupBox, QPushButton, QVBoxLayout, \ QTableView, QTableWidget, QHeaderView, QGridLayout, QLabel, \ @@ -39,28 +44,25 @@ def __init__(self): OscCommon().new_message.connect(self.__new_message) def __new_message(self, path, args, types): - key = Osc.key_from_message(path, args, types) + key = Osc.key_from_message(path, types, args) self.protocol_event.emit(key) @staticmethod - def key_from_message(path, args, types): - if len(types): - args = ' '.join([str(i) for i in args]) - return 'OSC\u001f{0}\u001f{1}\u001f{2}'.format(path, args, types) - else: - return 'OSC\u001f{0}'.format(path) + def key_from_message(path, types, args): + key = [path, types, *args] + return 'OSC{}'.format(key) @staticmethod - def key_from_settings(path, args, types): - if len(types): - return 'OSC\u001f{0}\u001f{1}\u001f{2}'.format(path, args, types) + def key_from_settings(path, types, args): + if not len(types): + return "OSC['{0}', '{1}']".format(path, types) else: - return 'OSC\u001f{0}'.format(path) + return "OSC['{0}', '{1}', {2}]".format(path, types, args) @staticmethod - def from_key(message_str): - m = message_str.split('\u001f')[1:] - return (m[0], m[1], m[2]) if len(m) > 2 else (m[0], '', '') + def message_from_key(key): + key = ast.literal_eval(key[3:]) + return key class OscSettings(CueSettingsPage): @@ -78,8 +80,8 @@ def __init__(self, cue_class, **kwargs): self.oscModel = SimpleTableModel([ translate('ControllerOscSettings', 'Path'), - translate('ControllerOscSettings', 'Arguments'), translate('ControllerOscSettings', 'Types'), + translate('ControllerOscSettings', 'Arguments'), translate('ControllerOscSettings', 'Actions')]) self.OscView = OscView(cue_class, parent=self.oscGroup) @@ -98,11 +100,10 @@ def __init__(self, cue_class, **kwargs): self.oscCapture.clicked.connect(self.capture_message) self.oscGroup.layout().addWidget(self.oscCapture, 2, 0) - self.captureDialog = QDialog() - self.captureDialog.setWindowTitle(translate('ControllerOscSettings', - 'OSC Capture')) + self.captureDialog = QDialog(self, flags=Qt.Dialog) + self.captureDialog.setWindowTitle(translate('ControllerOscSettings', 'OSC Capture')) self.captureDialog.setModal(True) - self.captureLabel = QLabel('...') + self.captureLabel = QLabel('Waiting for message:') self.captureDialog.setLayout(QVBoxLayout()) self.captureDialog.layout().addWidget(self.captureLabel) @@ -113,7 +114,7 @@ def __init__(self, cue_class, **kwargs): self.buttonBox.rejected.connect(self.captureDialog.reject) self.captureDialog.layout().addWidget(self.buttonBox) - self.capturedMessage = {'path': None, 'args': None, 'types': None} + self.capturedMessage = {'path': None, 'types': None, 'args': None} self.retranslateUi() @@ -134,8 +135,8 @@ def get_settings(self): messages = [] for row in self.oscModel.rows: - message = Osc.key_from_settings(row[0], row[1], row[2]) - messages.append((message, row[-1])) + key = Osc.key_from_settings(row[0], row[1], row[2]) + messages.append((key, row[-1])) if messages: settings['osc'] = messages @@ -145,24 +146,33 @@ def get_settings(self): def load_settings(self, settings): if 'osc' in settings: for options in settings['osc']: - path, args, types = Osc.from_key(options[0]) - self.oscModel.appendRow(path, args, types, options[1]) + key = Osc.message_from_key(options[0]) + self.oscModel.appendRow(key[0], + key[1], + '{}'.format(key[2:])[1:-1], + options[1]) def capture_message(self): OscCommon().new_message.connect(self.__show_message) result = self.captureDialog.exec() - if result == QDialog.Accepted: + if result == QDialog.Accepted and self.capturedMessage['path']: + args = '{}'.format(self.capturedMessage['args'])[1:-1] self.oscModel.appendRow(self.capturedMessage['path'], - self.capturedMessage['args'], self.capturedMessage['types'], + args, self._default_action) OscCommon().new_message.disconnect(self.__show_message) + self.captureLabel.setText('Waiting for messsage:') def __show_message(self, path, args, types): + key = Osc.key_from_message(path, types, args) + self.capturedMessage['path'] = path - self.capturedMessage['args'] = ' '.join([str(i) for i in args]) self.capturedMessage['types'] = types - self.captureLabel.setText('{0} "{1}" {2}'.format(path, types, args)) + self.capturedMessage['args'] = args + self.captureLabel.setText('OSC: "{0}" "{1}" {2}'.format(self.capturedMessage['path'], + self.capturedMessage['types'], + self.capturedMessage['args'])) def __new_message(self): self.oscModel.appendRow('', '', '', self._default_action) @@ -196,4 +206,4 @@ def __init__(self, cue_class, **kwargs): self.verticalHeader().setHighlightSections(False) for column, delegate in enumerate(self.delegates): - self.setItemDelegateForColumn(column, delegate) \ No newline at end of file + self.setItemDelegateForColumn(column, delegate) From e0363477ad0840dd3ae1d55a01fd638a633b7bce Mon Sep 17 00:00:00 2001 From: offtools Date: Sat, 7 Jan 2017 20:57:48 +0100 Subject: [PATCH 039/333] removed unused key --- lisp/plugins/controller/protocols/osc.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 4fc95cd2b..b13ba4443 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -165,8 +165,6 @@ def capture_message(self): self.captureLabel.setText('Waiting for messsage:') def __show_message(self, path, args, types): - key = Osc.key_from_message(path, types, args) - self.capturedMessage['path'] = path self.capturedMessage['types'] = types self.capturedMessage['args'] = args From f4040201fc56a16121adc2a8c2c47eb4b6e704d7 Mon Sep 17 00:00:00 2001 From: offtools Date: Sun, 8 Jan 2017 17:27:43 +0100 Subject: [PATCH 040/333] TODO updated, capture dialog has fixed size, text is centered --- lisp/plugins/controller/protocols/osc.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index b13ba4443..a533ee0d0 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -19,6 +19,16 @@ # TODO: LineEdit text orientation # TODO: error handling for manual edited messages +# error check get_settings: +# 1. Path +# 1.1 path not set +# 1.2 path wrong (leading / and len) +# 2. Types +# 2.1 types are set, values None +# 2.2 types not set, values not None +# 2.3 len(types) != len(values) +# 3 Semantic test on argument list +# 3.2 type for type in types == convert_str_to_type(arg for arg in args) import ast @@ -101,9 +111,13 @@ def __init__(self, cue_class, **kwargs): self.oscGroup.layout().addWidget(self.oscCapture, 2, 0) self.captureDialog = QDialog(self, flags=Qt.Dialog) + self.captureDialog.resize(300, 150) + self.captureDialog.setMaximumSize(self.captureDialog.size()) + self.captureDialog.setMinimumSize(self.captureDialog.size()) self.captureDialog.setWindowTitle(translate('ControllerOscSettings', 'OSC Capture')) self.captureDialog.setModal(True) self.captureLabel = QLabel('Waiting for message:') + self.captureLabel.setAlignment(Qt.AlignCenter) self.captureDialog.setLayout(QVBoxLayout()) self.captureDialog.layout().addWidget(self.captureLabel) @@ -162,15 +176,15 @@ def capture_message(self): args, self._default_action) OscCommon().new_message.disconnect(self.__show_message) - self.captureLabel.setText('Waiting for messsage:') + self.captureLabel.setText('Waiting for message:') def __show_message(self, path, args, types): self.capturedMessage['path'] = path self.capturedMessage['types'] = types self.capturedMessage['args'] = args self.captureLabel.setText('OSC: "{0}" "{1}" {2}'.format(self.capturedMessage['path'], - self.capturedMessage['types'], - self.capturedMessage['args'])) + self.capturedMessage['types'], + self.capturedMessage['args'])) def __new_message(self): self.oscModel.appendRow('', '', '', self._default_action) From 92398135d013be5a417e02e9ff9b7ec0876d37a3 Mon Sep 17 00:00:00 2001 From: offtools Date: Sun, 8 Jan 2017 17:33:57 +0100 Subject: [PATCH 041/333] added KeyError to exception --- lisp/modules/action_cues/osc_cue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 8906ef7d8..1ca0af2e3 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -191,7 +191,7 @@ def __init_arguments(self): self.__arg_list.append([arg['type'], arg['start'], 0]) - except: + except KeyError: elogging.error("OSC: could not parse argument list, nothing sent") return False From 9e5626d8e7faf33da910d98e14459a17fcd79bdf Mon Sep 17 00:00:00 2001 From: offtools Date: Thu, 12 Jan 2017 23:43:28 +0100 Subject: [PATCH 042/333] better formating and error handling of agument setting --- lisp/modules/action_cues/osc_cue.py | 210 ++++++++++++++++++---------- 1 file changed, 137 insertions(+), 73 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 1ca0af2e3..95a2d0bce 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -18,10 +18,6 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -# TODO: work with values in config['args'] not strings -# only gui operates with strings, config holds real values -# config['args'] is a dict (keys: type, start, end (optional), fade) -# all conversions are done in settings not the cue from enum import Enum @@ -29,7 +25,7 @@ from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, \ QTableView, QTableWidget, QHeaderView, QPushButton, QLabel, \ - QLineEdit, QDoubleSpinBox + QLineEdit, QDoubleSpinBox, QStyledItemDelegate, QCheckbox, QSpinBox from lisp.ui.widgets import FadeComboBox @@ -69,10 +65,6 @@ def test_path(path): return True return False -# TODO: test argument list -def test_arguments(args): - print(args) - def string_to_value(t, sarg): """converts string to requested value for given type""" @@ -107,6 +99,34 @@ def convert_value(t, value): raise ValueError +def guess_end_value(t, value): + """guess end value for fades""" + if t == OscMessageType.Int.value: + start = round(int(value)) + if start < 0: + return "{0:d}".format(0) + elif start == 0: + return "{0:d}".format(255) + else: + for i in range(4): + if start < 0xff << (i * 8): + return "{0:d}".format(0xff << (i * 8)) + return "{0:d}".format(0xff << (3 * 8)) + + elif t == OscMessageType.Float.value: + start = float(value) + if start == 0: + return "{0:.2f}".format(1) + elif start < 0: + return "{0:.2f}".format(0) + elif start < 1: + return "{0:.2f}".format(1) + else: + return "{0:.2f}".format(1000) + else: + return '' + + def format_string(t, sarg): """checks if string can be converted and formats it""" # if len(sarg) == 0: @@ -120,8 +140,10 @@ def format_string(t, sarg): return 'True' elif sarg.lower() == 'false': return 'False' - else: + if sarg.isdigit(): return "{0}".format(bool(int(sarg))) + else: + return "{0}".format(bool(False)) elif t == OscMessageType.String.value: return "{0}".format(sarg) else: @@ -284,7 +306,6 @@ def __init__(self, **kwargs): self.pathEdit = QLineEdit() self.oscGroup.layout().addWidget(self.pathEdit, 1, 0, 1, 2) - self.pathEdit.editingFinished.connect(self.__path_changed) self.oscModel = SimpleTableModel([ translate('Osc Cue', 'Type'), @@ -353,26 +374,30 @@ def get_settings(self): conf = {} checkable = self.oscGroup.isCheckable() - # TODO: test paths and argument if not (checkable and not self.oscGroup.isChecked()): - if not test_path(self.pathEdit.text()) or not test_arguments([row for row in self.oscModel.rows]): - elogging.error("OSC: Error parsing message elements, remove message") + if not test_path(self.pathEdit.text()): + elogging.error("OSC: Error parsing osc path, removing message") return conf if not (checkable and not self.oscGroup.isChecked()): - conf['path'] = self.pathEdit.text() - args_list = [] - for row in self.oscModel.rows: - arg = {'type': row[COL_TYPE], - 'start': string_to_value(row[COL_TYPE], row[COL_START_VAL])} + try: + conf['path'] = self.pathEdit.text() + args_list = [] + for row in self.oscModel.rows: + arg = {'type': row[COL_TYPE], + 'start': string_to_value(row[COL_TYPE], row[COL_START_VAL])} + + if row[COL_END_VAL]: + arg['end'] = string_to_value(row[COL_TYPE], row[COL_END_VAL]) - if row[COL_END_VAL]: - arg['end'] = string_to_value(row[COL_TYPE], row[COL_END_VAL]) + arg['fade'] = row[COL_DO_FADE] + args_list.append(arg) - arg['fade'] = row[COL_DO_FADE] - args_list.append(arg) + conf['args'] = args_list + except ValueError: + elogging.error("OSC: Error parsing osc arguments, removing message") + return {} - conf['args'] = args_list if not (checkable and not self.fadeGroup.isCheckable()): conf['duration'] = self.fadeSpin.value() * 1000 conf['fade_type'] = self.fadeCurveCombo.currentType() @@ -382,20 +407,21 @@ def load_settings(self, settings): if 'path' in settings: path = settings.get('path', '') self.pathEdit.setText(path) - if 'args' in settings: - args = settings.get('args', '') - for arg in args: - self.oscModel.appendRow(arg['type'], - str(arg['start']), - str(arg['end']) if 'end' in arg else '', - arg['fade']) + if 'args' in settings: + + args = settings.get('args', '') + for arg in args: + self.oscModel.appendRow(arg['type'], + str(arg['start']), + str(arg['end']) if 'end' in arg else '', + arg['fade']) - self.fadeSpin.setValue(settings.get('duration', 0) / 1000) - self.fadeCurveCombo.setCurrentType(settings.get('fade_type', '')) + self.fadeSpin.setValue(settings.get('duration', 0) / 1000) + self.fadeCurveCombo.setCurrentType(settings.get('fade_type', '')) def __new_argument(self): - self.oscModel.appendRow(OscMessageType.Int.value, '1', '', False) + self.oscModel.appendRow(OscMessageType.Int.value, '0', '', False) def __remove_argument(self): if self.oscModel.rowCount(): @@ -424,49 +450,87 @@ def __test_message(self): except ValueError: elogging.error("OSC: Error on parsing argument list - nothing sent") - def __path_changed(self): - if not test_path(self.pathEdit.text()): - elogging.warning("OSC: no valid path for OSC message", - details="Path should start with a '/' followed by a name.", - dialog=True) - @staticmethod def __argument_changed(index_topleft, index_bottomright, roles): - """helper function, no error handling: - *formats input - *disable fade for non fade types - *checks if start and end value is provided - """ - model = index_bottomright.model() - osctype = model.rows[index_bottomright.row()][COL_TYPE] - start = model.rows[index_bottomright.row()][COL_START_VAL] - end = model.rows[index_bottomright.row()][COL_END_VAL] + if not (Qt.EditRole in roles): + return - # test Start value for correct format - try: - model.rows[index_bottomright.row()][COL_START_VAL] = format_string(osctype, start) - except ValueError: - model.rows[index_bottomright.row()][COL_START_VAL] = '1' - elogging.warning("OSC Argument Error", details="{0} not a {1}".format(start, osctype), dialog=True) - - # test fades - if model.rows[index_bottomright.row()][COL_DO_FADE]: - if type_can_fade(osctype): - try: - model.rows[index_bottomright.row()][COL_END_VAL] = format_string(osctype, end) - except ValueError: - elogging.warning("OSC Argument Error", details="{0} not a {1}".format(end, osctype), dialog=True) - model.rows[index_bottomright.row()][COL_END_VAL] = '' - model.rows[index_bottomright.row()][3] = False - # end_value equals start_value - if model.rows[index_bottomright.row()][COL_END_VAL] == model.rows[index_bottomright.row()][COL_START_VAL]: - model.rows[index_bottomright.row()][COL_END_VAL] = '' - model.rows[index_bottomright.row()][COL_DO_FADE] = False - elogging.warning("OSC Argument Error", details="FadeIn equals FadeOut - no fade", dialog=True) + model = index_bottomright.model() + curr_row = index_topleft.row() + model_row = model.rows[curr_row] + curr_col = index_bottomright.column() + + osctype = model_row[COL_TYPE] + start = model_row[COL_START_VAL] + end = model_row[COL_END_VAL] + + # check message + if curr_col == COL_TYPE: + # format start value + try: + model_row[COL_START_VAL] = format_string(osctype, start) + except ValueError: + model_row[COL_START_VAL] = format_string(osctype, 0) + elogging.warning("OSC Argument Error", details="start value {0} is not a {1}".format(start, osctype), + dialog=True) + # if end value: format end value or remove if nonfade type + if not type_can_fade(osctype): + model_row[COL_END_VAL] = '' + model_row[COL_DO_FADE] = False + else: + if model_row[COL_END_VAL]: + try: + model_row[COL_END_VAL] = format_string(osctype, end) + except ValueError: + model_row[COL_END_VAL] = '' + elogging.warning("OSC Argument Error", + details="end value {0} is not a {1}".format(end, osctype), dialog=True) + + elif curr_col == COL_START_VAL: + # format start value + try: + model_row[COL_START_VAL] = format_string(osctype, start) + except ValueError: + model_row[COL_START_VAL] = format_string(osctype, '0') + elogging.warning("OSC Argument Error", details="{0} not a {1}".format(start, osctype), dialog=True) + + elif curr_col == COL_END_VAL: + # check if type can fade + if not type_can_fade(osctype): + model_row[COL_END_VAL] = '' + elogging.warning("OSC Argument Error", details="cannot fade {0}".format(osctype), dialog=True) + else: + # format end value + if model_row[COL_DO_FADE]: + try: + model_row[COL_END_VAL] = format_string(osctype, end) + except ValueError: + model_row[COL_END_VAL] = guess_end_value(osctype, model_row[COL_START_VAL]) + elogging.warning("OSC Argument Error", details="{0} not a {1}".format(end, osctype), dialog=True) + else: + if model_row[COL_END_VAL]: + try: + model_row[COL_END_VAL] = format_string(osctype, end) + except ValueError: + model_row[COL_END_VAL] = guess_end_value(osctype, + model_row[COL_START_VAL]) + elogging.warning("OSC Argument Error", details="{0} not a {1}".format(end, osctype), + dialog=True) + + elif curr_col == COL_DO_FADE: + # fade is True + if model_row[COL_DO_FADE] is True: + if not type_can_fade(osctype): + elogging.warning("OSC Argument Error", details="cannot fade {0}".format(osctype), dialog=True) + model_row[COL_DO_FADE] = False + else: + if not model_row[COL_END_VAL]: + model_row[COL_END_VAL] = guess_end_value(osctype, model_row[COL_START_VAL]) + elogging.warning("OSC Argument Error", details="fades need an end value", dialog=True) else: - # disable fade for non fade types - model.rows[index_bottomright.row()][COL_DO_FADE] = False - model.rows[index_bottomright.row()][COL_END_VAL] = '' + model_row[COL_END_VAL] = '' + else: + raise RuntimeError("OSC: ModelIndex Error") class OscView(QTableView): From 025ab6afb43f1d61429d44ecafa3a3dbf13e674a Mon Sep 17 00:00:00 2001 From: offtools Date: Mon, 16 Jan 2017 20:27:39 +0100 Subject: [PATCH 043/333] gui changes for osc cue, introducing OscArgumentDelegate delegate, which can handle multiple types, reduces parsing and converting between string and real values --- lisp/modules/action_cues/osc_cue.py | 230 ++++++---------------------- lisp/ui/qdelegates.py | 72 ++++++++- 2 files changed, 121 insertions(+), 181 deletions(-) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 95a2d0bce..3664a1ff6 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -19,13 +19,11 @@ # along with Linux Show Player. If not, see . -from enum import Enum - from PyQt5 import QtCore from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, \ QTableView, QTableWidget, QHeaderView, QPushButton, QLabel, \ - QLineEdit, QDoubleSpinBox, QStyledItemDelegate, QCheckbox, QSpinBox + QLineEdit, QDoubleSpinBox from lisp.ui.widgets import FadeComboBox @@ -36,12 +34,14 @@ from lisp.ui import elogging from lisp.ui.qmodels import SimpleTableModel from lisp.core.has_properties import Property -from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate, \ +from lisp.ui.qdelegates import ComboBoxDelegate, OscArgumentDelegate, \ CheckBoxDelegate from lisp.ui.settings.cue_settings import CueSettingsRegistry, \ SettingsPage from lisp.modules.osc.osc_common import OscCommon from lisp.ui.ui_utils import translate +from lisp.modules.osc.osc_common import OscMessageType + COL_TYPE = 0 COL_START_VAL = 1 @@ -52,13 +52,6 @@ COL_DIFF_VAL = 2 -class OscMessageType(Enum): - Int = 'Integer' - Float = 'Float' - Bool = 'Bool' - String = 'String' - - def test_path(path): if isinstance(path, str): if len(path) > 1 and path[0] is '/': @@ -66,90 +59,6 @@ def test_path(path): return False -def string_to_value(t, sarg): - """converts string to requested value for given type""" - if t == OscMessageType.Int.value: - return int(sarg) - elif t == OscMessageType.Float.value: - return float(sarg) - elif t == OscMessageType.Bool.value: - if sarg.lower() == 'true': - return True - elif sarg.lower() == 'false': - return False - else: - return bool(int(sarg)) - elif t == OscMessageType.String.value: - return str(sarg) - else: - raise ValueError - - -def convert_value(t, value): - """converts value to requested value for given type""" - if t == OscMessageType.Int.value: - return round(int(value)) - elif t == OscMessageType.Float.value: - return float(value) - elif t == OscMessageType.Bool.value: - return bool(value) - elif t == OscMessageType.String.value: - return str(value) - else: - raise ValueError - - -def guess_end_value(t, value): - """guess end value for fades""" - if t == OscMessageType.Int.value: - start = round(int(value)) - if start < 0: - return "{0:d}".format(0) - elif start == 0: - return "{0:d}".format(255) - else: - for i in range(4): - if start < 0xff << (i * 8): - return "{0:d}".format(0xff << (i * 8)) - return "{0:d}".format(0xff << (3 * 8)) - - elif t == OscMessageType.Float.value: - start = float(value) - if start == 0: - return "{0:.2f}".format(1) - elif start < 0: - return "{0:.2f}".format(0) - elif start < 1: - return "{0:.2f}".format(1) - else: - return "{0:.2f}".format(1000) - else: - return '' - - -def format_string(t, sarg): - """checks if string can be converted and formats it""" - # if len(sarg) == 0: - # return '' - if t == OscMessageType.Int.value: - return "{0:d}".format(int(sarg)) - elif t == OscMessageType.Float.value: - return "{0:.2f}".format(float(sarg)) - elif t == OscMessageType.Bool.value: - if sarg.lower() == 'true': - return 'True' - elif sarg.lower() == 'false': - return 'False' - if sarg.isdigit(): - return "{0}".format(bool(int(sarg))) - else: - return "{0}".format(bool(False)) - elif t == OscMessageType.String.value: - return "{0}".format(sarg) - else: - raise ValueError - - def type_can_fade(t): if t == OscMessageType.Int.value: return True @@ -376,8 +285,7 @@ def get_settings(self): if not (checkable and not self.oscGroup.isChecked()): if not test_path(self.pathEdit.text()): - elogging.error("OSC: Error parsing osc path, removing message") - return conf + elogging.error("OSC: Error parsing osc path, message will be unable to send") if not (checkable and not self.oscGroup.isChecked()): try: @@ -385,18 +293,17 @@ def get_settings(self): args_list = [] for row in self.oscModel.rows: arg = {'type': row[COL_TYPE], - 'start': string_to_value(row[COL_TYPE], row[COL_START_VAL])} + 'start': row[COL_START_VAL]} - if row[COL_END_VAL]: - arg['end'] = string_to_value(row[COL_TYPE], row[COL_END_VAL]) + if row[COL_END_VAL] and row[COL_DO_FADE] is True: + arg['end'] = row[COL_END_VAL] arg['fade'] = row[COL_DO_FADE] args_list.append(arg) conf['args'] = args_list except ValueError: - elogging.error("OSC: Error parsing osc arguments, removing message") - return {} + elogging.error("OSC: Error parsing osc arguments, message will be unable to send") if not (checkable and not self.fadeGroup.isCheckable()): conf['duration'] = self.fadeSpin.value() * 1000 @@ -413,15 +320,15 @@ def load_settings(self, settings): args = settings.get('args', '') for arg in args: self.oscModel.appendRow(arg['type'], - str(arg['start']), - str(arg['end']) if 'end' in arg else '', + arg['start'], + arg['end'] if 'end' in arg else None, arg['fade']) self.fadeSpin.setValue(settings.get('duration', 0) / 1000) self.fadeCurveCombo.setCurrentType(settings.get('fade_type', '')) def __new_argument(self): - self.oscModel.appendRow(OscMessageType.Int.value, '0', '', False) + self.oscModel.appendRow(OscMessageType.Int.value, 0, '', False) def __remove_argument(self): if self.oscModel.rowCount(): @@ -442,16 +349,15 @@ def __test_message(self): args = [] for row in self.oscModel.rows: if row[COL_END_VAL] and row[COL_DO_FADE]: - args.append(string_to_value(row[COL_TYPE], row[COL_END_VAL])) + args.append(row[COL_END_VAL]) else: - args.append(string_to_value(row[COL_TYPE], row[COL_START_VAL])) + args.append(row[COL_START_VAL]) OscCommon().send(path, *args) except ValueError: elogging.error("OSC: Error on parsing argument list - nothing sent") - @staticmethod - def __argument_changed(index_topleft, index_bottomright, roles): + def __argument_changed(self, index_topleft, index_bottomright, roles): if not (Qt.EditRole in roles): return @@ -459,78 +365,42 @@ def __argument_changed(index_topleft, index_bottomright, roles): curr_row = index_topleft.row() model_row = model.rows[curr_row] curr_col = index_bottomright.column() + osc_type = model_row[COL_TYPE] - osctype = model_row[COL_TYPE] - start = model_row[COL_START_VAL] - end = model_row[COL_END_VAL] - - # check message + # Type changed if curr_col == COL_TYPE: - # format start value - try: - model_row[COL_START_VAL] = format_string(osctype, start) - except ValueError: - model_row[COL_START_VAL] = format_string(osctype, 0) - elogging.warning("OSC Argument Error", details="start value {0} is not a {1}".format(start, osctype), - dialog=True) - # if end value: format end value or remove if nonfade type - if not type_can_fade(osctype): - model_row[COL_END_VAL] = '' - model_row[COL_DO_FADE] = False - else: - if model_row[COL_END_VAL]: - try: - model_row[COL_END_VAL] = format_string(osctype, end) - except ValueError: - model_row[COL_END_VAL] = '' - elogging.warning("OSC Argument Error", - details="end value {0} is not a {1}".format(end, osctype), dialog=True) - - elif curr_col == COL_START_VAL: - # format start value - try: - model_row[COL_START_VAL] = format_string(osctype, start) - except ValueError: - model_row[COL_START_VAL] = format_string(osctype, '0') - elogging.warning("OSC Argument Error", details="{0} not a {1}".format(start, osctype), dialog=True) - - elif curr_col == COL_END_VAL: - # check if type can fade - if not type_can_fade(osctype): - model_row[COL_END_VAL] = '' - elogging.warning("OSC Argument Error", details="cannot fade {0}".format(osctype), dialog=True) + index_start = model.createIndex(curr_row, COL_START_VAL) + index_end = model.createIndex(curr_row, COL_END_VAL) + delegate_start = self.oscView.itemDelegate(index_start) + delegate_end = self.oscView.itemDelegate(index_end) + + if osc_type == 'Integer': + delegate_start.updateEditor(OscMessageType.Int) + delegate_end.updateEditor(OscMessageType.Int) + model_row[COL_START_VAL] = 0 + elif osc_type == 'Float': + delegate_start.updateEditor(OscMessageType.Float) + delegate_end.updateEditor(OscMessageType.Float) + model_row[COL_START_VAL] = 0 + elif osc_type == 'Bool': + delegate_start.updateEditor(OscMessageType.Bool) + delegate_end.updateEditor() + model_row[COL_START_VAL] = True else: - # format end value - if model_row[COL_DO_FADE]: - try: - model_row[COL_END_VAL] = format_string(osctype, end) - except ValueError: - model_row[COL_END_VAL] = guess_end_value(osctype, model_row[COL_START_VAL]) - elogging.warning("OSC Argument Error", details="{0} not a {1}".format(end, osctype), dialog=True) - else: - if model_row[COL_END_VAL]: - try: - model_row[COL_END_VAL] = format_string(osctype, end) - except ValueError: - model_row[COL_END_VAL] = guess_end_value(osctype, - model_row[COL_START_VAL]) - elogging.warning("OSC Argument Error", details="{0} not a {1}".format(end, osctype), - dialog=True) - - elif curr_col == COL_DO_FADE: - # fade is True - if model_row[COL_DO_FADE] is True: - if not type_can_fade(osctype): - elogging.warning("OSC Argument Error", details="cannot fade {0}".format(osctype), dialog=True) - model_row[COL_DO_FADE] = False - else: - if not model_row[COL_END_VAL]: - model_row[COL_END_VAL] = guess_end_value(osctype, model_row[COL_START_VAL]) - elogging.warning("OSC Argument Error", details="fades need an end value", dialog=True) - else: - model_row[COL_END_VAL] = '' - else: - raise RuntimeError("OSC: ModelIndex Error") + delegate_start.updateEditor(OscMessageType.String) + delegate_end.updateEditor() + model_row[COL_START_VAL] = 'None' + + model_row[COL_END_VAL] = None + model_row[COL_DO_FADE] = False + + if not type_can_fade(model_row[COL_TYPE]): + model_row[COL_END_VAL] = None + model_row[COL_DO_FADE] = False + + if curr_col == COL_DO_FADE: + if model_row[COL_DO_FADE] is False: + model_row[COL_END_VAL] = None class OscView(QTableView): @@ -539,8 +409,8 @@ def __init__(self, **kwargs): self.delegates = [ComboBoxDelegate(options=[i.value for i in OscMessageType], tr_context='OscMessageType'), - LineEditDelegate(), - LineEditDelegate(), + OscArgumentDelegate(), + OscArgumentDelegate(), CheckBoxDelegate()] self.setSelectionBehavior(QTableWidget.SelectRows) diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index 082ef7b13..c24d86a99 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -19,13 +19,14 @@ from PyQt5.QtCore import Qt, QEvent from PyQt5.QtWidgets import QStyledItemDelegate, QComboBox, QSpinBox, \ - QLineEdit, QStyle, QDialog, QCheckBox + QLineEdit, QStyle, QDialog, QCheckBox, QDoubleSpinBox, QLabel from lisp.application import Application from lisp.cues.cue import CueAction from lisp.ui.qmodels import CueClassRole from lisp.ui.ui_utils import translate from lisp.ui.widgets import CueActionComboBox +from lisp.modules.osc.osc_common import OscMessageType class LabelDelegate(QStyledItemDelegate): @@ -163,6 +164,75 @@ def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) +class OscArgumentDelegate(QStyledItemDelegate): + MIN = -1000000 + MAX = 1000000 + DECIMALS = 4 + + def __init__(self, tag=OscMessageType.Int, **kwargs): + super().__init__(**kwargs) + self.tag = tag + + def createEditor(self, parent, option, index): + if self.tag is OscMessageType.Int: + editor = QSpinBox(parent) + editor.setRange(self.MIN, self.MAX) + editor.setSingleStep(1) + elif self.tag is OscMessageType.Float: + editor = QDoubleSpinBox(parent) + editor.setRange(self.MIN, self.MAX) + editor.setDecimals(3) + editor.setSingleStep(0.01) + elif self.tag is OscMessageType.Bool: + editor = QCheckBox(parent) + elif self.tag is OscMessageType.String: + editor = QLineEdit(parent) + editor.setFrame(False) + else: + editor = None + + return editor + + def setEditorData(self, widget, index): + if self.tag is OscMessageType.Int: + value = index.model().data(index, Qt.EditRole) + if isinstance(value, int): + widget.setValue(value) + elif self.tag is OscMessageType.Float: + value = index.model().data(index, Qt.EditRole) + if isinstance(value, float): + widget.setValue(value) + elif self.tag is OscMessageType.Bool: + value = index.model().data(index, Qt.EditRole) + if isinstance(value, bool): + widget.setChecked(value) + else: + value = index.model().data(index, Qt.EditRole) + widget.setText(str(value)) + + def setModelData(self, widget, model, index): + if self.tag is OscMessageType.Int: + widget.interpretText() + print("widget value: ", widget.value(), type(widget.value())) + model.setData(index, widget.value(), Qt.EditRole) + elif self.tag is OscMessageType.Float: + widget.interpretText() + model.setData(index, widget.value(), Qt.EditRole) + elif self.tag is OscMessageType.Bool: + model.setData(index, widget.isChecked(), Qt.EditRole) + else: + model.setData(index, widget.text(), Qt.EditRole) + + def updateEditorGeometry(self, editor, option, index): + editor.setGeometry(option.rect) + + def updateEditor(self, tag=None): + if isinstance(tag, OscMessageType): + self.tag = tag + else: + self.tag = None + + class CueActionDelegate(LabelDelegate): Mode = CueActionComboBox.Mode From d94f6a4c942ca9a61b4c445d6f71888d7616396e Mon Sep 17 00:00:00 2001 From: offtools Date: Tue, 17 Jan 2017 00:43:36 +0100 Subject: [PATCH 044/333] OscMessageType moved to osc_common, new osc message /lisp/list/index --- lisp/modules/osc/osc_common.py | 35 ++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index 1810b2391..cd0004de5 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -19,6 +19,7 @@ # along with Linux Show Player. If not, see . from collections import deque +from enum import Enum from lisp.core.singleton import ABCSingleton from liblo import ServerThread, Address, ServerError @@ -31,6 +32,14 @@ from lisp.cues.cue import CueState from lisp.core.signal import Signal +# TODO: add osc log as queue and dialog to show it + + +class OscMessageType(Enum): + Int = 'Integer' + Float = 'Float' + Bool = 'Bool' + String = 'String' # decorator for OSC callback (pushing messages to log, emits message_event) # def __osc_handler(func): @@ -49,7 +58,7 @@ def _go(): # @__osc_handler -def _reset_list(): +def _list_reset(): """reset, stops all cues, sets cursor to Cue index 0 in ListLayout""" if isinstance(MainWindow().layout, ListLayout): for cue in Application().cue_model: @@ -57,12 +66,24 @@ def _reset_list(): MainWindow().layout.set_current_index(0) +# @__osc_handler +def _list_index(path, args, types): + """sets cursor to given Cue index in ListLayout""" + if not isinstance(MainWindow().layout, ListLayout): + MainWindow().layout.set_current_index(0) + return + + if path == '/lisp/list/index' and types == 'i': + index = args[0] + MainWindow().layout.set_current_index(index) + + # @__osc_handler def _pause_all(): """triggers global pause all media""" for cue in Application().cue_model: if cue.state == CueState.Running: - cue.pause() + cue.pause(True) # @__osc_handler @@ -70,14 +91,15 @@ def _restart_all(): """triggers global play, if pausing""" for cue in Application().cue_model: if cue.state == CueState.Pause: - cue.start() + cue.start(True) +# TODO: add fade as option to message? # @__osc_handler def _stop_all(): """triggers global stop, stops all media cues""" for cue in Application().cue_model: - cue.stop() + cue.stop(True) class OscCommon(metaclass=ABCSingleton): @@ -87,10 +109,11 @@ def __init__(self): self.__log = deque([], 10) self.new_message = Signal() - # TODO: static paths and callbacks, find smarter way + # TODO: static paths and callbacks, make it editable through settings dialog self.__callbacks = [ ['/lisp/list/go', None, _go], - ['/lisp/list/reset', None, _reset_list], + ['/lisp/list/reset', None, _list_reset], + ['/lisp/list/index', 'i', _list_index], ['/lisp/pause', None, _pause_all], ['/lisp/start', None, _restart_all], ['/lisp/stop', None, _stop_all], From 85d3f8c4cb6249b796647cb08c1df5557f6bc8a6 Mon Sep 17 00:00:00 2001 From: offtools Date: Tue, 17 Jan 2017 16:50:37 +0100 Subject: [PATCH 045/333] minor changes, some osc paths changed, cleanup --- lisp/modules/osc/osc_common.py | 10 +++++----- lisp/ui/qdelegates.py | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index cd0004de5..39dab094d 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -67,13 +67,13 @@ def _list_reset(): # @__osc_handler -def _list_index(path, args, types): +def _list_cursor(path, args, types): """sets cursor to given Cue index in ListLayout""" if not isinstance(MainWindow().layout, ListLayout): MainWindow().layout.set_current_index(0) return - if path == '/lisp/list/index' and types == 'i': + if path == '/lisp/list/cursor' and types == 'i': index = args[0] MainWindow().layout.set_current_index(index) @@ -87,7 +87,7 @@ def _pause_all(): # @__osc_handler -def _restart_all(): +def _play_all(): """triggers global play, if pausing""" for cue in Application().cue_model: if cue.state == CueState.Pause: @@ -113,9 +113,9 @@ def __init__(self): self.__callbacks = [ ['/lisp/list/go', None, _go], ['/lisp/list/reset', None, _list_reset], - ['/lisp/list/index', 'i', _list_index], + ['/lisp/list/cursor', 'i', _list_cursor], ['/lisp/pause', None, _pause_all], - ['/lisp/start', None, _restart_all], + ['/lisp/play', None, _play_all], ['/lisp/stop', None, _stop_all], [None, None, self.__new_message] ] diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index c24d86a99..3067c1f3c 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -213,7 +213,6 @@ def setEditorData(self, widget, index): def setModelData(self, widget, model, index): if self.tag is OscMessageType.Int: widget.interpretText() - print("widget value: ", widget.value(), type(widget.value())) model.setData(index, widget.value(), Qt.EditRole) elif self.tag is OscMessageType.Float: widget.interpretText() From c71dfa7bdd075e3ee9d0873faf21a2cd7c28f099 Mon Sep 17 00:00:00 2001 From: offtools Date: Sat, 21 Jan 2017 00:16:21 +0100 Subject: [PATCH 046/333] fixed osc module always enabled on startup, default.cfg cleanup --- lisp/default.cfg | 3 +-- lisp/modules/osc/osc.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lisp/default.cfg b/lisp/default.cfg index bc87299c7..ec1e1b692 100644 --- a/lisp/default.cfg +++ b/lisp/default.cfg @@ -70,11 +70,10 @@ dbMin = -60 dbClip = 0 [OSC] -enable = False +enabled = False inport = 9000 outport = 9001 hostname = localhost -feedback = False [Timecode] Enabled = True diff --git a/lisp/modules/osc/osc.py b/lisp/modules/osc/osc.py index 3a8d40385..4726ea0b2 100644 --- a/lisp/modules/osc/osc.py +++ b/lisp/modules/osc/osc.py @@ -33,5 +33,5 @@ def __init__(self): # Register the settings widget AppSettings.register_settings_widget(OscSettings) - if config['OSC']['enable']: + if config['OSC'].getboolean('enabled') is True: OscCommon().start() From 12b207464e24371f61f00d324c64586ec1a057e9 Mon Sep 17 00:00:00 2001 From: offtools Date: Sun, 22 Jan 2017 18:04:56 +0100 Subject: [PATCH 047/333] OSC protocol, proper dialog for editing input OSC messages added --- lisp/plugins/controller/protocols/osc.py | 153 ++++++++++++++++++++--- 1 file changed, 138 insertions(+), 15 deletions(-) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index a533ee0d0..bf291d7d9 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -17,33 +17,24 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -# TODO: LineEdit text orientation -# TODO: error handling for manual edited messages -# error check get_settings: -# 1. Path -# 1.1 path not set -# 1.2 path wrong (leading / and len) -# 2. Types -# 2.1 types are set, values None -# 2.2 types not set, values not None -# 2.3 len(types) != len(values) -# 3 Semantic test on argument list -# 3.2 type for type in types == convert_str_to_type(arg for arg in args) import ast from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QGroupBox, QPushButton, QVBoxLayout, \ QTableView, QTableWidget, QHeaderView, QGridLayout, QLabel, \ - QDialog, QDialogButtonBox + QDialog, QDialogButtonBox, QLineEdit from lisp.modules import check_module from lisp.modules.osc.osc_common import OscCommon +from lisp.ui.qdelegates import OscArgumentDelegate +from lisp.modules.osc.osc_common import OscMessageType from lisp.plugins.controller.protocols.protocol import Protocol from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate from lisp.ui.qmodels import SimpleTableModel from lisp.ui.settings.settings_page import CueSettingsPage from lisp.ui.ui_utils import translate +from lisp.ui import elogging class Osc(Protocol): @@ -75,6 +66,112 @@ def message_from_key(key): return key +class OscMessageDialog(QDialog): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.setMaximumSize(500, 300) + self.setMinimumSize(500, 300) + self.resize(500, 300) + + self.setLayout(QVBoxLayout()) + self.layout().setAlignment(Qt.AlignTop) + + self.groupBox = QGroupBox(self) + self.groupBox.setLayout(QGridLayout()) + self.layout().addWidget(self.groupBox) + + self.pathLabel = QLabel() + self.groupBox.layout().addWidget(self.pathLabel, 0, 0, 1, 2) + + self.pathEdit = QLineEdit() + self.groupBox.layout().addWidget(self.pathEdit, 1, 0, 1, 2) + + self.model = SimpleTableModel([ + translate('Osc Cue', 'Type'), + translate('Osc Cue', 'Argument')]) + + self.model.dataChanged.connect(self.__argument_changed) + + self.view = OscArgumentView(parent=self.groupBox) + self.view.setModel(self.model) + self.groupBox.layout().addWidget(self.view, 2, 0, 1, 2) + + self.buttons = QDialogButtonBox(self) + self.buttons.addButton(QDialogButtonBox.Cancel) + self.buttons.addButton(QDialogButtonBox.Ok) + self.layout().addWidget(self.buttons) + + self.addButton = QPushButton(self.groupBox) + self.addButton.clicked.connect(self.__add_argument) + self.groupBox.layout().addWidget(self.addButton, 3, 0) + + self.removeButton = QPushButton(self.groupBox) + self.removeButton.clicked.connect(self.__remove_argument) + self.groupBox.layout().addWidget(self.removeButton, 3, 1) + + self.buttons.accepted.connect(self.accept) + self.buttons.rejected.connect(self.reject) + + self.retranslateUi() + + def __add_argument(self): + self.model.appendRow(OscMessageType.Int.value, 0) + + def __remove_argument(self): + if self.model.rowCount() and self.view.currentIndex().row(): + self.model.removeRow(self.view.currentIndex().row()) + + def __argument_changed(self, index_topleft, index_bottomright, roles): + model = index_bottomright.model() + curr_row = index_topleft.row() + model_row = model.rows[curr_row] + curr_col = index_bottomright.column() + osc_type = model_row[0] + + if curr_col == 0: + index = model.createIndex(curr_row, 1) + delegate = self.view.itemDelegate(index) + delegate.updateEditor(OscMessageType[osc_type]) + + if osc_type == 'Integer' or osc_type == 'Float': + model_row[1] = 0 + elif osc_type == 'Bool': + model_row[1] = True + else: + model_row[1] = '' + + def retranslateUi(self): + self.groupBox.setTitle(translate('ControllerOscSettings', 'OSC Message')) + self.pathLabel.setText(translate('ControllerOscSettings', 'OSC Path: (example: "/path/to/something")')) + self.addButton.setText(translate('OscCue', 'Add')) + self.removeButton.setText(translate('OscCue', 'Remove')) + + +class OscArgumentView(QTableView): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self.delegates = [ComboBoxDelegate(options=[i.value for i in OscMessageType], + tr_context='OscMessageType'), + OscArgumentDelegate()] + + self.setSelectionBehavior(QTableWidget.SelectRows) + self.setSelectionMode(QTableView.SingleSelection) + + self.setShowGrid(False) + self.setAlternatingRowColors(True) + + self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + self.horizontalHeader().setHighlightSections(False) + + self.verticalHeader().sectionResizeMode(QHeaderView.Fixed) + self.verticalHeader().setDefaultSectionSize(24) + self.verticalHeader().setHighlightSections(False) + + for column, delegate in enumerate(self.delegates): + self.setItemDelegateForColumn(column, delegate) + + class OscSettings(CueSettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'OSC Controls') @@ -187,10 +284,36 @@ def __show_message(self, path, args, types): self.capturedMessage['args'])) def __new_message(self): - self.oscModel.appendRow('', '', '', self._default_action) + dialog = OscMessageDialog(parent=self) + if dialog.exec_() == dialog.Accepted: + path = dialog.pathEdit.text() + if len(path) < 2 or path[0] is not '/': + elogging.warning('OSC: Osc path seems not valid,\ndo not forget to edit the path later.', + dialog=True) + types = '' + arguments = [] + for row in dialog.model.rows: + if row[0] == 'Bool': + if row[1] is True: + types += 'T' + if row[1] is True: + types += 'F' + else: + if row[0] == 'Integer': + types += 'i' + elif row[0] == 'Float': + types += 'f' + elif row[0] == 'String': + types += 's' + else: + raise TypeError('Unsupported Osc Type') + arguments.append(row[1]) + + self.oscModel.appendRow(path, types, '{}'.format(arguments)[1:-1], self._default_action) def __remove_message(self): - self.oscModel.removeRow(self.OscView.currentIndex().row()) + if self.oscModel.rowCount() and self.OscView.currentIndex().row(): + self.oscModel.removeRow(self.OscView.currentIndex().row()) class OscView(QTableView): From 297f1fbd6555948dd1bfa00d0d8c1b70dee3755e Mon Sep 17 00:00:00 2001 From: offtools Date: Mon, 23 Jan 2017 17:09:57 +0100 Subject: [PATCH 048/333] removed unused import --- lisp/ui/qdelegates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index 3067c1f3c..a8112778b 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -19,7 +19,7 @@ from PyQt5.QtCore import Qt, QEvent from PyQt5.QtWidgets import QStyledItemDelegate, QComboBox, QSpinBox, \ - QLineEdit, QStyle, QDialog, QCheckBox, QDoubleSpinBox, QLabel + QLineEdit, QStyle, QDialog, QCheckBox, QDoubleSpinBox from lisp.application import Application from lisp.cues.cue import CueAction From 11a0d8a1eba05239693a5e4007fdd9aa7a87320d Mon Sep 17 00:00:00 2001 From: offtools Date: Mon, 23 Jan 2017 17:10:43 +0100 Subject: [PATCH 049/333] check for edit role --- lisp/plugins/controller/protocols/osc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index bf291d7d9..61073aa81 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -122,6 +122,9 @@ def __remove_argument(self): self.model.removeRow(self.view.currentIndex().row()) def __argument_changed(self, index_topleft, index_bottomright, roles): + if not (Qt.EditRole in roles): + return + model = index_bottomright.model() curr_row = index_topleft.row() model_row = model.rows[curr_row] From 715d3e75b4ed37f9c939821b777e883542db8602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Cibrario?= Date: Sat, 7 Jan 2017 16:17:04 +0100 Subject: [PATCH 050/333] Regex rename basic module structure Basic structure in place. Only modify the first cue with only Upper and Lower Case. Nothing else --- lisp/modules/rename_cues/__init__.py | 1 + lisp/modules/rename_cues/rename_cues.py | 75 +++++++++++++++ lisp/modules/rename_cues/rename_ui.py | 120 ++++++++++++++++++++++++ 3 files changed, 196 insertions(+) create mode 100644 lisp/modules/rename_cues/__init__.py create mode 100644 lisp/modules/rename_cues/rename_cues.py create mode 100644 lisp/modules/rename_cues/rename_ui.py diff --git a/lisp/modules/rename_cues/__init__.py b/lisp/modules/rename_cues/__init__.py new file mode 100644 index 000000000..af50fdb6c --- /dev/null +++ b/lisp/modules/rename_cues/__init__.py @@ -0,0 +1 @@ +from .rename_cues import RenameCues \ No newline at end of file diff --git a/lisp/modules/rename_cues/rename_cues.py b/lisp/modules/rename_cues/rename_cues.py new file mode 100644 index 000000000..79b9ec5b7 --- /dev/null +++ b/lisp/modules/rename_cues/rename_cues.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import logging +from concurrent.futures import ThreadPoolExecutor +from concurrent.futures import as_completed as futures_completed +from math import pow +from threading import Thread, Lock + +import gi + +from lisp.ui.ui_utils import translate + +gi.require_version('Gst', '1.0') +from gi.repository import Gst +from PyQt5.QtWidgets import QMenu, QAction, QDialog, QMessageBox + +from lisp.application import Application +from lisp.core.action import Action +from lisp.core.actions_handler import MainActionsHandler +from lisp.core.module import Module +from lisp.core.signal import Signal, Connection +from lisp.cues.media_cue import MediaCue +from lisp.ui.mainwindow import MainWindow +from .rename_ui import RenameUi + + +class RenameCues(Module): + Name = 'RenameCues' + + def __init__(self): + self._gain_thread = None + + # Entry in mainWindow menu + self.menuAction = QAction(translate('RenameCues', + 'Rename Cues'), MainWindow()) + self.menuAction.triggered.connect(self.rename) + + MainWindow().menuTools.addAction(self.menuAction) + + def rename(self): + + # Warning if no cue is selected + if Application().layout.get_selected_cues() == []: + msg = QMessageBox() + msg.setIcon(QMessageBox.Information) + msg.setText('You have to select some cues to rename them') + msg.exec_() + else: + renameUi = RenameUi(MainWindow()) + renameUi.exec_() + + if renameUi.result() == QDialog.Accepted: + cues = Application().layout.get_selected_cues() + cues[0].name = renameUi.previewEdit.text() + + def terminate(self): + MainWindow().menuTools.removeAction(self.menuAction) + diff --git a/lisp/modules/rename_cues/rename_ui.py b/lisp/modules/rename_cues/rename_ui.py new file mode 100644 index 000000000..dc1642623 --- /dev/null +++ b/lisp/modules/rename_cues/rename_ui.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from os import cpu_count + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QRadioButton, QSpinBox, \ + QCheckBox, QFrame, QLabel, QGridLayout, QButtonGroup, QProgressDialog, QLineEdit + +from lisp.ui.ui_utils import translate +from lisp.application import Application + + +class RenameUi(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + + self.setWindowModality(Qt.ApplicationModal) + self.setMaximumSize(380, 210) + self.setMinimumSize(380, 210) + self.resize(380, 210) + + self.setLayout(QGridLayout()) + + + # Preview label + self.previewLabel = QLabel(self) + self.previewLabel.setText('Preview :') + self.layout().addWidget(self.previewLabel, 0, 0) + self.previewEdit = QLineEdit(self) + self.previewEdit.setReadOnly(True) + self.layout().addWidget(self.previewEdit, 0, 1) + + # Options checkbox + self.capitalizeBox = QCheckBox(self) + self.capitalizeBox.toggled.connect(self.capitalize_cue_name) + self.layout().addWidget(self.capitalizeBox, 1, 0) + self.capitalizeLabel = QLabel(self) + self.capitalizeBox.setText('Capitalize') + self.layout().addWidget(self.capitalizeLabel, 1, 1) + + self.lowerBox = QCheckBox(self) + self.lowerBox.toggled.connect(self.lower_cue_name) + self.layout().addWidget(self.lowerBox, 2, 0) + self.lowerLabel = QLabel(self) + self.lowerLabel.setText('Lowercase') + self.layout().addWidget(self.lowerLabel, 2, 1) + + # OK / Cancel buttons + self.dialogButtons = QDialogButtonBox(self) + self.dialogButtons.setStandardButtons(QDialogButtonBox.Ok | + QDialogButtonBox.Cancel) + self.layout().addWidget(self.dialogButtons, 5, 0, 1, 2) + + self.dialogButtons.accepted.connect(self.accept) + self.dialogButtons.rejected.connect(self.reject) + + + self.retranslateUi() + + self.get_cues_name() + + + def retranslateUi(self): + #TODO : translation file & Co + self.setWindowTitle( + translate('RenameCues', 'Rename cues')) + + def get_cues_name(self): + cues = Application().layout.get_selected_cues() + if cues != []: + # FIXME : Récupère juste le premier pour le test + self.previewEdit.setText('{}'.format(cues[0].name)) + + def capitalize_cue_name(self): + if self.capitalizeBox.isChecked(): + self.previewEdit.setText(self.previewEdit.text().upper()) + + def lower_cue_name(self): + if self.lowerBox.isChecked(): + self.previewEdit.setText(self.previewEdit.text().lower()) + + +# class GainProgressDialog(QProgressDialog): +# def __init__(self, maximum, parent=None): +# super().__init__(parent) +# +# self.setWindowModality(Qt.ApplicationModal) +# self.setWindowTitle(translate('ReplayGain', 'Processing files ...')) +# self.setMaximumSize(320, 110) +# self.setMinimumSize(320, 110) +# self.resize(320, 110) +# +# self.setMaximum(maximum) +# self.setLabelText('0 / {0}'.format(maximum)) +# +# def on_progress(self, value): +# if value == -1: +# # Hide the progress dialog +# self.setValue(self.maximum()) +# self.deleteLater() +# else: +# self.setValue(self.value() + value) +# self.setLabelText('{0} / {1}'.format(self.value(), self.maximum())) From dde985ce7af91452d7feacabeaab2a1b387f656d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Cibrario?= Date: Tue, 14 Feb 2017 08:21:23 +0100 Subject: [PATCH 051/333] Regex rename module Regex rename implementation working and some buttons. No actual modification. --- lisp/modules/rename_cues/rename_cues.py | 6 +- lisp/modules/rename_cues/rename_ui.py | 207 ++++++++++++++++-------- 2 files changed, 140 insertions(+), 73 deletions(-) diff --git a/lisp/modules/rename_cues/rename_cues.py b/lisp/modules/rename_cues/rename_cues.py index 79b9ec5b7..d215c3b74 100644 --- a/lisp/modules/rename_cues/rename_cues.py +++ b/lisp/modules/rename_cues/rename_cues.py @@ -45,7 +45,7 @@ class RenameCues(Module): Name = 'RenameCues' def __init__(self): - self._gain_thread = None + #self._gain_thread = None # Entry in mainWindow menu self.menuAction = QAction(translate('RenameCues', @@ -67,8 +67,8 @@ def rename(self): renameUi.exec_() if renameUi.result() == QDialog.Accepted: - cues = Application().layout.get_selected_cues() - cues[0].name = renameUi.previewEdit.text() + print('Actually modification of the cues') + def terminate(self): MainWindow().menuTools.removeAction(self.menuAction) diff --git a/lisp/modules/rename_cues/rename_ui.py b/lisp/modules/rename_cues/rename_ui.py index dc1642623..1ab4455ee 100644 --- a/lisp/modules/rename_cues/rename_ui.py +++ b/lisp/modules/rename_cues/rename_ui.py @@ -17,104 +17,171 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from os import cpu_count +import re +from copy import copy +import logging from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QRadioButton, QSpinBox, \ - QCheckBox, QFrame, QLabel, QGridLayout, QButtonGroup, QProgressDialog, QLineEdit +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QGridLayout, QLineEdit, \ + QTreeWidget, QTreeWidgetItem, QPushButton -from lisp.ui.ui_utils import translate from lisp.application import Application +from lisp.ui.ui_utils import translate + class RenameUi(QDialog): def __init__(self, parent=None): super().__init__(parent) + self.__cue_names = [] + self.__cue_names_preview = [] + # Here are stored regex result in the same order as the cue names + self.__cue_names_regex_groups = [] + self.setWindowModality(Qt.ApplicationModal) - self.setMaximumSize(380, 210) - self.setMinimumSize(380, 210) - self.resize(380, 210) + self.setMaximumSize(600, 400) + self.setMinimumSize(600, 210) + self.resize(600, 300) self.setLayout(QGridLayout()) - - # Preview label - self.previewLabel = QLabel(self) - self.previewLabel.setText('Preview :') - self.layout().addWidget(self.previewLabel, 0, 0) - self.previewEdit = QLineEdit(self) - self.previewEdit.setReadOnly(True) - self.layout().addWidget(self.previewEdit, 0, 1) - - # Options checkbox - self.capitalizeBox = QCheckBox(self) - self.capitalizeBox.toggled.connect(self.capitalize_cue_name) - self.layout().addWidget(self.capitalizeBox, 1, 0) - self.capitalizeLabel = QLabel(self) - self.capitalizeBox.setText('Capitalize') - self.layout().addWidget(self.capitalizeLabel, 1, 1) - - self.lowerBox = QCheckBox(self) - self.lowerBox.toggled.connect(self.lower_cue_name) - self.layout().addWidget(self.lowerBox, 2, 0) - self.lowerLabel = QLabel(self) - self.lowerLabel.setText('Lowercase') - self.layout().addWidget(self.lowerLabel, 2, 1) + # Preview List + self.previewList = QTreeWidget() + self.previewList.setColumnCount(2) + self.previewList.setHeaderLabels( + ['Actuel', 'Preview']) + self.previewList.resizeColumnToContents(0) + self.previewList.resizeColumnToContents(1) + self.layout().addWidget(self.previewList, 0, 0, 3, 3) + + # Options buttons + + self.capitalizeButton = QPushButton() + self.capitalizeButton.setText('Capitalize') + self.capitalizeButton.clicked.connect(self.onCapitalizeButtonClicked) + self.layout().addWidget(self.capitalizeButton, 3, 0) + + self.lowerButton = QPushButton() + self.lowerButton.setText('Lowercase') + self.lowerButton.clicked.connect(self.onLowerButtonClicked) + self.layout().addWidget(self.lowerButton, 4, 0) + + self.upperButton = QPushButton() + self.upperButton.setText('Uppercase') + self.upperButton.clicked.connect(self.onUpperButtonClicked) + self.layout().addWidget(self.upperButton, 5, 0) + + self.removeNumButton = QPushButton() + self.removeNumButton.setText('Remove Numbers') + self.removeNumButton.clicked.connect(self.onRemoveNumButtonClicked) + self.layout().addWidget(self.removeNumButton, 3, 1) + + # Modif line + self.regexLine = QLineEdit() + self.regexLine.setPlaceholderText('Type your regex here :') + self.regexLine.textChanged.connect(self.onRegexLineChanged) + self.layout().addWidget(self.regexLine, 4, 2) + + self.outRegexLine = QLineEdit() + self.outRegexLine.setPlaceholderText('Output, display catched () with $1, $2, etc..."') + self.outRegexLine.textChanged.connect(self.onOutRegexChanged) + self.layout().addWidget(self.outRegexLine, 5, 2) # OK / Cancel buttons - self.dialogButtons = QDialogButtonBox(self) + self.dialogButtons = QDialogButtonBox() self.dialogButtons.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) - self.layout().addWidget(self.dialogButtons, 5, 0, 1, 2) + self.layout().addWidget(self.dialogButtons, 6, 2) self.dialogButtons.accepted.connect(self.accept) self.dialogButtons.rejected.connect(self.reject) - + # i18n self.retranslateUi() + # Populate the preview list self.get_cues_name() - def retranslateUi(self): - #TODO : translation file & Co + # TODO : translation file & Co self.setWindowTitle( translate('RenameCues', 'Rename cues')) def get_cues_name(self): - cues = Application().layout.get_selected_cues() - if cues != []: - # FIXME : Récupère juste le premier pour le test - self.previewEdit.setText('{}'.format(cues[0].name)) - - def capitalize_cue_name(self): - if self.capitalizeBox.isChecked(): - self.previewEdit.setText(self.previewEdit.text().upper()) - - def lower_cue_name(self): - if self.lowerBox.isChecked(): - self.previewEdit.setText(self.previewEdit.text().lower()) - - -# class GainProgressDialog(QProgressDialog): -# def __init__(self, maximum, parent=None): -# super().__init__(parent) -# -# self.setWindowModality(Qt.ApplicationModal) -# self.setWindowTitle(translate('ReplayGain', 'Processing files ...')) -# self.setMaximumSize(320, 110) -# self.setMinimumSize(320, 110) -# self.resize(320, 110) -# -# self.setMaximum(maximum) -# self.setLabelText('0 / {0}'.format(maximum)) -# -# def on_progress(self, value): -# if value == -1: -# # Hide the progress dialog -# self.setValue(self.maximum()) -# self.deleteLater() -# else: -# self.setValue(self.value() + value) -# self.setLabelText('{0} / {1}'.format(self.value(), self.maximum())) + for cue in Application().layout.get_selected_cues(): + self.__cue_names.append(cue.name) + self.__cue_names_preview = copy(self.__cue_names) + # Initialization for regex matches + self.__cue_names_regex_groups = [() for i in self.__cue_names] + + self.update_preview_list() + + self.previewList.setColumnWidth(0, 300) + + def update_preview_list(self): + self.previewList.clear() + + if self.__cue_names != []: + for i, cue in enumerate(self.__cue_names): + a = QTreeWidgetItem(self.previewList) + a.setText(0, cue) + a.setText(1, self.__cue_names_preview[i]) + + def onCapitalizeButtonClicked(self): + self.__cue_names_preview = [ + x.capitalize() for x in self.__cue_names_preview] + self.update_preview_list() + + def onLowerButtonClicked(self): + self.__cue_names_preview = [ + x.lower() for x in self.__cue_names_preview] + self.update_preview_list() + + def onUpperButtonClicked(self): + self.__cue_names_preview = [ + x.upper() for x in self.__cue_names_preview] + self.update_preview_list() + + def onRemoveNumButtonClicked(self): + def remove_numb(input): + #TODO : compile this fucking regex !! + match = re.search('^[^a-zA-Z]+(.+)', input) + if match is not None: + return match.group(1) + else: + return input + + self.__cue_names_preview = [ + remove_numb(x) for x in self.__cue_names_preview] + self.update_preview_list() + + def onRegexLineChanged(self): + pattern = self.regexLine.text() + try: + regex = re.compile(pattern) + except re.error: + logging.info("Regex error : not a valid pattern") + else: + for i, cue_name in enumerate(self.__cue_names): + result = regex.search(cue_name) + if result: + self.__cue_names_regex_groups[i] = result.groups() + + self.onOutRegexChanged() + + def onOutRegexChanged(self): + out_pattern = self.outRegexLine.text() + rep_variables = re.findall('\$[0-9]+', out_pattern) + + for i, cue_name in enumerate(self.__cue_names): + out_string = out_pattern + for n in range(len(rep_variables)): + pattern = '\${}'.format(n) + try: + out_string = re.sub(pattern, self.__cue_names_regex_groups[i][n], out_string) + except IndexError: + logging.info("Regex error : Catch with () before display with $n") + self.__cue_names_preview[i] = out_string + + self.update_preview_list() From ea309ed15d44c0901b72b45a95fa7d44ead1b840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Cibrario?= Date: Thu, 16 Feb 2017 09:07:51 +0100 Subject: [PATCH 052/333] Regex rename module Ui ok, regex implementation ok. No actual modification --- lisp/modules/rename_cues/rename_cues.py | 1 + lisp/modules/rename_cues/rename_ui.py | 60 +++++++++++++++++-------- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/lisp/modules/rename_cues/rename_cues.py b/lisp/modules/rename_cues/rename_cues.py index d215c3b74..7ab7c08b2 100644 --- a/lisp/modules/rename_cues/rename_cues.py +++ b/lisp/modules/rename_cues/rename_cues.py @@ -64,6 +64,7 @@ def rename(self): msg.exec_() else: renameUi = RenameUi(MainWindow()) + renameUi.get_cues_name() renameUi.exec_() if renameUi.result() == QDialog.Accepted: diff --git a/lisp/modules/rename_cues/rename_ui.py b/lisp/modules/rename_cues/rename_ui.py index 1ab4455ee..6ad1fe5f1 100644 --- a/lisp/modules/rename_cues/rename_ui.py +++ b/lisp/modules/rename_cues/rename_ui.py @@ -23,7 +23,7 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QGridLayout, QLineEdit, \ - QTreeWidget, QTreeWidgetItem, QPushButton + QTreeWidget, QTreeWidgetItem, QPushButton, QSpacerItem from lisp.application import Application from lisp.ui.ui_utils import translate @@ -36,13 +36,11 @@ def __init__(self, parent=None): self.__cue_names = [] self.__cue_names_preview = [] - # Here are stored regex result in the same order as the cue names + # Here will be stored regex result in the same order as the cue names above self.__cue_names_regex_groups = [] self.setWindowModality(Qt.ApplicationModal) - self.setMaximumSize(600, 400) - self.setMinimumSize(600, 210) - self.resize(600, 300) + self.resize(650, 350) self.setLayout(QGridLayout()) @@ -53,10 +51,9 @@ def __init__(self, parent=None): ['Actuel', 'Preview']) self.previewList.resizeColumnToContents(0) self.previewList.resizeColumnToContents(1) - self.layout().addWidget(self.previewList, 0, 0, 3, 3) + self.layout().addWidget(self.previewList, 0, 0, 3, 4) # Options buttons - self.capitalizeButton = QPushButton() self.capitalizeButton.setText('Capitalize') self.capitalizeButton.clicked.connect(self.onCapitalizeButtonClicked) @@ -77,22 +74,30 @@ def __init__(self, parent=None): self.removeNumButton.clicked.connect(self.onRemoveNumButtonClicked) self.layout().addWidget(self.removeNumButton, 3, 1) - # Modif line - self.regexLine = QLineEdit() - self.regexLine.setPlaceholderText('Type your regex here :') - self.regexLine.textChanged.connect(self.onRegexLineChanged) - self.layout().addWidget(self.regexLine, 4, 2) + self.addNumberingButton = QPushButton() + self.addNumberingButton.setText('Add numbering') + self.addNumberingButton.clicked.connect(self.onAddNumberingButtonClicked) + self.layout().addWidget(self.addNumberingButton, 4, 1) + + # Spacer + self.layout().addItem(QSpacerItem(30, 0), 3, 2) + # Regex lines self.outRegexLine = QLineEdit() - self.outRegexLine.setPlaceholderText('Output, display catched () with $1, $2, etc..."') + self.outRegexLine.setPlaceholderText('Rename all cue. () in regex above usable with $0, $1 ...') self.outRegexLine.textChanged.connect(self.onOutRegexChanged) - self.layout().addWidget(self.outRegexLine, 5, 2) + self.layout().addWidget(self.outRegexLine, 3, 3) + + self.regexLine = QLineEdit() + self.regexLine.setPlaceholderText('Type your regex here :') + self.regexLine.textChanged.connect(self.onRegexLineChanged) + self.layout().addWidget(self.regexLine, 4, 3) # OK / Cancel buttons self.dialogButtons = QDialogButtonBox() self.dialogButtons.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) - self.layout().addWidget(self.dialogButtons, 6, 2) + self.layout().addWidget(self.dialogButtons, 6, 3) self.dialogButtons.accepted.connect(self.accept) self.dialogButtons.rejected.connect(self.reject) @@ -100,9 +105,6 @@ def __init__(self, parent=None): # i18n self.retranslateUi() - # Populate the preview list - self.get_cues_name() - def retranslateUi(self): # TODO : translation file & Co self.setWindowTitle( @@ -130,7 +132,7 @@ def update_preview_list(self): def onCapitalizeButtonClicked(self): self.__cue_names_preview = [ - x.capitalize() for x in self.__cue_names_preview] + x.title() for x in self.__cue_names_preview] self.update_preview_list() def onLowerButtonClicked(self): @@ -156,6 +158,15 @@ def remove_numb(input): remove_numb(x) for x in self.__cue_names_preview] self.update_preview_list() + def onAddNumberingButtonClicked(self): + cues_nbr = len(self.__cue_names) + digit_nbr = len(str(cues_nbr)) + self.__cue_names_preview = [ + f"{i:0{digit_nbr}.0f} - {cue_name}" for i, cue_name in enumerate(self.__cue_names_preview) + ] + + self.update_preview_list() + def onRegexLineChanged(self): pattern = self.regexLine.text() try: @@ -185,3 +196,14 @@ def onOutRegexChanged(self): self.__cue_names_preview[i] = out_string self.update_preview_list() + + +if __name__ == "__main__": + # To test Ui quickly + from PyQt5.QtWidgets import QApplication + import sys + + gui_test_app = QApplication(sys.argv) + rename_ui = RenameUi() + rename_ui.show() + sys.exit(gui_test_app.exec()) From 87e8b15a9785dcf7d6e81401374b0253c883f91f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Cibrario?= Date: Thu, 16 Feb 2017 10:07:56 +0100 Subject: [PATCH 053/333] Regex Rename Module working, beta version. No i18n --- lisp/modules/rename_cues/rename_cues.py | 26 +++------------ lisp/modules/rename_cues/rename_ui.py | 44 +++++++++++++++---------- 2 files changed, 31 insertions(+), 39 deletions(-) diff --git a/lisp/modules/rename_cues/rename_cues.py b/lisp/modules/rename_cues/rename_cues.py index 7ab7c08b2..e3fed1d31 100644 --- a/lisp/modules/rename_cues/rename_cues.py +++ b/lisp/modules/rename_cues/rename_cues.py @@ -17,36 +17,22 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import logging -from concurrent.futures import ThreadPoolExecutor -from concurrent.futures import as_completed as futures_completed -from math import pow -from threading import Thread, Lock - import gi - -from lisp.ui.ui_utils import translate - -gi.require_version('Gst', '1.0') -from gi.repository import Gst -from PyQt5.QtWidgets import QMenu, QAction, QDialog, QMessageBox +from PyQt5.QtWidgets import QAction, QDialog, QMessageBox from lisp.application import Application -from lisp.core.action import Action -from lisp.core.actions_handler import MainActionsHandler from lisp.core.module import Module -from lisp.core.signal import Signal, Connection -from lisp.cues.media_cue import MediaCue from lisp.ui.mainwindow import MainWindow +from lisp.ui.ui_utils import translate from .rename_ui import RenameUi +gi.require_version('Gst', '1.0') + class RenameCues(Module): Name = 'RenameCues' def __init__(self): - #self._gain_thread = None - # Entry in mainWindow menu self.menuAction = QAction(translate('RenameCues', 'Rename Cues'), MainWindow()) @@ -55,7 +41,6 @@ def __init__(self): MainWindow().menuTools.addAction(self.menuAction) def rename(self): - # Warning if no cue is selected if Application().layout.get_selected_cues() == []: msg = QMessageBox() @@ -68,8 +53,7 @@ def rename(self): renameUi.exec_() if renameUi.result() == QDialog.Accepted: - print('Actually modification of the cues') - + renameUi.record_cues_name() def terminate(self): MainWindow().menuTools.removeAction(self.menuAction) diff --git a/lisp/modules/rename_cues/rename_ui.py b/lisp/modules/rename_cues/rename_ui.py index 6ad1fe5f1..25e3eb7f8 100644 --- a/lisp/modules/rename_cues/rename_ui.py +++ b/lisp/modules/rename_cues/rename_ui.py @@ -17,9 +17,9 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging import re from copy import copy -import logging from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QGridLayout, QLineEdit, \ @@ -29,7 +29,6 @@ from lisp.ui.ui_utils import translate - class RenameUi(QDialog): def __init__(self, parent=None): super().__init__(parent) @@ -51,6 +50,7 @@ def __init__(self, parent=None): ['Actuel', 'Preview']) self.previewList.resizeColumnToContents(0) self.previewList.resizeColumnToContents(1) + self.previewList.setColumnWidth(0, 300) self.layout().addWidget(self.previewList, 0, 0, 3, 4) # Options buttons @@ -119,8 +119,6 @@ def get_cues_name(self): self.update_preview_list() - self.previewList.setColumnWidth(0, 300) - def update_preview_list(self): self.previewList.clear() @@ -146,9 +144,10 @@ def onUpperButtonClicked(self): self.update_preview_list() def onRemoveNumButtonClicked(self): + regex = re.compile('^[^a-zA-Z]+(.+)') + def remove_numb(input): - #TODO : compile this fucking regex !! - match = re.search('^[^a-zA-Z]+(.+)', input) + match = regex.search(input) if match is not None: return match.group(1) else: @@ -156,13 +155,15 @@ def remove_numb(input): self.__cue_names_preview = [ remove_numb(x) for x in self.__cue_names_preview] + self.update_preview_list() def onAddNumberingButtonClicked(self): cues_nbr = len(self.__cue_names) digit_nbr = len(str(cues_nbr)) self.__cue_names_preview = [ - f"{i:0{digit_nbr}.0f} - {cue_name}" for i, cue_name in enumerate(self.__cue_names_preview) + f"{i+1:0{digit_nbr}.0f} - {cue_name}" + for i, cue_name in enumerate(self.__cue_names_preview) ] self.update_preview_list() @@ -183,20 +184,27 @@ def onRegexLineChanged(self): def onOutRegexChanged(self): out_pattern = self.outRegexLine.text() - rep_variables = re.findall('\$[0-9]+', out_pattern) - - for i, cue_name in enumerate(self.__cue_names): - out_string = out_pattern - for n in range(len(rep_variables)): - pattern = '\${}'.format(n) - try: - out_string = re.sub(pattern, self.__cue_names_regex_groups[i][n], out_string) - except IndexError: - logging.info("Regex error : Catch with () before display with $n") - self.__cue_names_preview[i] = out_string + + if out_pattern == '': + self.__cue_names_preview = copy(self.__cue_names) + else: + for i, cue_name in enumerate(self.__cue_names): + out_string = out_pattern + for n in range(len(self.__cue_names_regex_groups[0])): + pattern = '\${}'.format(n) + try: + out_string = re.sub(pattern, + self.__cue_names_regex_groups[i][n], out_string) + except IndexError: + logging.info("Regex error : Catch with () before display with $n") + self.__cue_names_preview[i] = out_string self.update_preview_list() + def record_cues_name(self): + for i, cue_name in enumerate(self.__cue_names_preview): + old_cue = Application().layout.get_selected_cues()[i] + old_cue.name = cue_name if __name__ == "__main__": # To test Ui quickly From b284f42777e9a05e4b01d58b9feed7f38feb7abd Mon Sep 17 00:00:00 2001 From: offtools Date: Thu, 16 Feb 2017 12:53:18 +0100 Subject: [PATCH 054/333] *refractor key_from_setting -> key_from_values --- lisp/plugins/controller/protocols/osc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 61073aa81..0667392b2 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -54,7 +54,7 @@ def key_from_message(path, types, args): return 'OSC{}'.format(key) @staticmethod - def key_from_settings(path, types, args): + def key_from_values(path, types, args): if not len(types): return "OSC['{0}', '{1}']".format(path, types) else: @@ -249,7 +249,7 @@ def get_settings(self): messages = [] for row in self.oscModel.rows: - key = Osc.key_from_settings(row[0], row[1], row[2]) + key = Osc.key_from_values(row[0], row[1], row[2]) messages.append((key, row[-1])) if messages: From 4a2fe6c89fd5c18d9b199bc568881b8e151e9e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Cibrario?= Date: Mon, 20 Feb 2017 00:12:26 +0100 Subject: [PATCH 055/333] Update Regex Rename Module i18n, undo/redo, reset button --- lisp/modules/rename_cues/rename_action.py | 42 ++++++++++++++++++++ lisp/modules/rename_cues/rename_cues.py | 1 - lisp/modules/rename_cues/rename_ui.py | 48 +++++++++++++---------- 3 files changed, 70 insertions(+), 21 deletions(-) create mode 100644 lisp/modules/rename_cues/rename_action.py diff --git a/lisp/modules/rename_cues/rename_action.py b/lisp/modules/rename_cues/rename_action.py new file mode 100644 index 000000000..fd9c5ac10 --- /dev/null +++ b/lisp/modules/rename_cues/rename_action.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from lisp.application import Application +from lisp.core.action import Action + +class RenameCueAction(Action): + + # Store names for undo/redo in a dict like that : {'id': name} + names = {} + + def __init__(self, new_names): + """Get and store names""" + for i, new_cue_name in enumerate(new_names): + cue = Application().layout.get_selected_cues()[i] + self.names[cue.id] = new_cue_name + + def do(self): + """Use stored name and exchange with current names""" + for id in self.names: + cue = Application().cue_model.get(id) + cue.name, self.names[id] = self.names[id], cue.name + + def undo(self): + """Restore previous names and save current for redo""" + self.do() diff --git a/lisp/modules/rename_cues/rename_cues.py b/lisp/modules/rename_cues/rename_cues.py index e3fed1d31..2876fef8b 100644 --- a/lisp/modules/rename_cues/rename_cues.py +++ b/lisp/modules/rename_cues/rename_cues.py @@ -57,4 +57,3 @@ def rename(self): def terminate(self): MainWindow().menuTools.removeAction(self.menuAction) - diff --git a/lisp/modules/rename_cues/rename_ui.py b/lisp/modules/rename_cues/rename_ui.py index 25e3eb7f8..b762a7dcf 100644 --- a/lisp/modules/rename_cues/rename_ui.py +++ b/lisp/modules/rename_cues/rename_ui.py @@ -26,6 +26,8 @@ QTreeWidget, QTreeWidgetItem, QPushButton, QSpacerItem from lisp.application import Application +from lisp.application import MainActionsHandler +from lisp.modules.rename_cues.rename_action import RenameCueAction from lisp.ui.ui_utils import translate @@ -38,6 +40,7 @@ def __init__(self, parent=None): # Here will be stored regex result in the same order as the cue names above self.__cue_names_regex_groups = [] + self.setWindowTitle(translate('RenameCues', 'Rename cues')) self.setWindowModality(Qt.ApplicationModal) self.resize(650, 350) @@ -46,8 +49,10 @@ def __init__(self, parent=None): # Preview List self.previewList = QTreeWidget() self.previewList.setColumnCount(2) - self.previewList.setHeaderLabels( - ['Actuel', 'Preview']) + self.previewList.setHeaderLabels([ + translate('RenameCues', 'Current'), + translate('RenameCues', 'Preview') + ]) self.previewList.resizeColumnToContents(0) self.previewList.resizeColumnToContents(1) self.previewList.setColumnWidth(0, 300) @@ -55,41 +60,47 @@ def __init__(self, parent=None): # Options buttons self.capitalizeButton = QPushButton() - self.capitalizeButton.setText('Capitalize') + self.capitalizeButton.setText(translate('RenameCues', 'Capitalize')) self.capitalizeButton.clicked.connect(self.onCapitalizeButtonClicked) self.layout().addWidget(self.capitalizeButton, 3, 0) self.lowerButton = QPushButton() - self.lowerButton.setText('Lowercase') + self.lowerButton.setText(translate('RenameCues', 'Lowercase')) self.lowerButton.clicked.connect(self.onLowerButtonClicked) self.layout().addWidget(self.lowerButton, 4, 0) self.upperButton = QPushButton() - self.upperButton.setText('Uppercase') + self.upperButton.setText(translate('RenameCues', 'Uppercase')) self.upperButton.clicked.connect(self.onUpperButtonClicked) self.layout().addWidget(self.upperButton, 5, 0) self.removeNumButton = QPushButton() - self.removeNumButton.setText('Remove Numbers') + self.removeNumButton.setText(translate('RenameCues', 'Remove Numbers')) self.removeNumButton.clicked.connect(self.onRemoveNumButtonClicked) self.layout().addWidget(self.removeNumButton, 3, 1) self.addNumberingButton = QPushButton() - self.addNumberingButton.setText('Add numbering') + self.addNumberingButton.setText(translate('RenameCues', 'Add numbering')) self.addNumberingButton.clicked.connect(self.onAddNumberingButtonClicked) self.layout().addWidget(self.addNumberingButton, 4, 1) + self.resetButton = QPushButton() + self.resetButton.setText(translate('RenameCues', 'Reset')) + self.resetButton.clicked.connect(self.onResetButtonClicked) + self.layout().addWidget(self.resetButton, 5, 1) + # Spacer self.layout().addItem(QSpacerItem(30, 0), 3, 2) # Regex lines self.outRegexLine = QLineEdit() - self.outRegexLine.setPlaceholderText('Rename all cue. () in regex above usable with $0, $1 ...') + self.outRegexLine.setPlaceholderText( + translate('RenameCues', 'Rename all cue. () in regex below usable with $0, $1 ...')) self.outRegexLine.textChanged.connect(self.onOutRegexChanged) self.layout().addWidget(self.outRegexLine, 3, 3) self.regexLine = QLineEdit() - self.regexLine.setPlaceholderText('Type your regex here :') + self.regexLine.setPlaceholderText(translate('RenameCues', 'Type your regex here :')) self.regexLine.textChanged.connect(self.onRegexLineChanged) self.layout().addWidget(self.regexLine, 4, 3) @@ -102,14 +113,6 @@ def __init__(self, parent=None): self.dialogButtons.accepted.connect(self.accept) self.dialogButtons.rejected.connect(self.reject) - # i18n - self.retranslateUi() - - def retranslateUi(self): - # TODO : translation file & Co - self.setWindowTitle( - translate('RenameCues', 'Rename cues')) - def get_cues_name(self): for cue in Application().layout.get_selected_cues(): self.__cue_names.append(cue.name) @@ -168,6 +171,11 @@ def onAddNumberingButtonClicked(self): self.update_preview_list() + def onResetButtonClicked(self): + self.__cue_names_preview = copy(self.__cue_names) + + self.update_preview_list() + def onRegexLineChanged(self): pattern = self.regexLine.text() try: @@ -202,9 +210,9 @@ def onOutRegexChanged(self): self.update_preview_list() def record_cues_name(self): - for i, cue_name in enumerate(self.__cue_names_preview): - old_cue = Application().layout.get_selected_cues()[i] - old_cue.name = cue_name + MainActionsHandler.do_action( + RenameCueAction(self.__cue_names_preview) + ) if __name__ == "__main__": # To test Ui quickly From 230d9e597bdeefd707afe5dad770537be48b69b2 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 23 Feb 2017 11:38:51 +0100 Subject: [PATCH 056/333] Updates in the Property API --- lisp/backend/media.py | 3 +- lisp/backend/media_element.py | 7 - lisp/core/has_properties.py | 234 +++++++----------- lisp/cues/cue.py | 3 +- lisp/cues/media_cue.py | 4 +- lisp/modules/action_cues/collection_cue.py | 2 +- lisp/modules/action_cues/command_cue.py | 4 +- lisp/modules/action_cues/index_action_cue.py | 2 +- lisp/modules/action_cues/midi_cue.py | 2 +- lisp/modules/action_cues/seek_cue.py | 4 +- lisp/modules/action_cues/stop_all.py | 2 +- lisp/modules/action_cues/volume_control.py | 7 +- .../modules/gst_backend/elements/jack_sink.py | 7 +- .../gst_backend/elements/preset_src.py | 2 +- lisp/modules/gst_backend/elements/speed.py | 2 +- .../modules/gst_backend/elements/uri_input.py | 2 +- .../gst_backend/elements/user_element.py | 2 +- lisp/modules/gst_backend/elements/volume.py | 10 +- lisp/modules/gst_backend/gst_element.py | 14 +- lisp/modules/gst_backend/gst_media.py | 2 +- lisp/plugins/controller/controller.py | 4 +- lisp/plugins/timecode/timecode.py | 4 +- lisp/plugins/triggers/triggers.py | 4 +- 23 files changed, 140 insertions(+), 187 deletions(-) diff --git a/lisp/backend/media.py b/lisp/backend/media.py index 5c4977114..6077e9e56 100644 --- a/lisp/backend/media.py +++ b/lisp/backend/media.py @@ -20,7 +20,8 @@ from abc import abstractmethod from enum import Enum -from lisp.core.has_properties import HasProperties, Property +from lisp.core.has_properties import HasProperties +from lisp.core.properties import Property from lisp.core.signal import Signal diff --git a/lisp/backend/media_element.py b/lisp/backend/media_element.py index 14954b914..dc899d9b5 100644 --- a/lisp/backend/media_element.py +++ b/lisp/backend/media_element.py @@ -40,13 +40,6 @@ class MediaElement(HasProperties): """Base media-element class A MediaElement object control specific media's parameters (e.g. volume). - Every MediaElement provides two kind of properties: - 1) The one defined via class:`HasProperties`; - 2) runtime only properties, those are reset to the previous value at - playback end. - - Runtime properties are defined using the following naming convention: - runtime_. """ ElementType = None diff --git a/lisp/core/has_properties.py b/lisp/core/has_properties.py index 35c11ae00..3a49aa5c0 100644 --- a/lisp/core/has_properties.py +++ b/lisp/core/has_properties.py @@ -18,120 +18,16 @@ # along with Linux Show Player. If not, see . from abc import ABCMeta -from copy import deepcopy +from lisp.core.properties import Property, LiveProperty from lisp.core.signal import Signal from lisp.core.util import subclasses -class Property: - """Descriptor to be used in HasProperties subclasses to define properties. - - .. warning:: - To be able to save properties into a session, the stored value - MUST be JSON-serializable. - - .. warning:: - If extended any subclass *MUST*: - 1) if the __get__ method receive a None instance return self; - 2) if the __get__ is called while the value is not set, set it with a - safe copy of 'default' and return. - 3) After the value is changed call the __changed__ method. - """ - - def __init__(self, default=None): - self.name = 'unnamed_property' - self.default = default - - def __get__(self, instance, owner=None): - if instance is None: - return self - elif self.name not in instance.__dict__: - instance.__dict__[self.name] = deepcopy(self.default) - - return instance.__dict__.get(self.name) - - def __set__(self, instance, value): - if instance is not None: - # Only change the value if different - if value != instance.__dict__.get(self.name, self.default): - instance.__dict__[self.name] = value - self.__changed__(instance, value) - - def changed(self, instance): - if instance is not None: - value = self.__get__(instance) - return value != self.default, value - - return False, self.default - - def __changed__(self, instance, value): - instance.property_changed.emit(instance, self.name, value) - # Get the related signal - property_signal = instance.changed_signals.get(self.name) - if property_signal is not None: - property_signal.emit(value) - - -class WriteOnceProperty(Property): - """Property that can be modified only once. - - Obviously this is not really "write-once", but if used as normal attribute - will ignore any change when the stored value is different from default. - """ - - def __set__(self, instance, value): - if self.__get__(instance) == self.default: - super().__set__(instance, value) - - -class NestedProperties(Property): - """Simplify retrieving the properties of nested HasProperties objects. - - The goal is to avoid the reimplementation of HasProperties.properties() - and HasProperties.update_properties(). - - ..note:: - When getting or setting a single property of the nested object is better - to access it directly instead that using the nested-property. - """ - - def __init__(self, provider_name, default=None): - super().__init__(default=default) - self.provider_name = provider_name - - def __get__(self, instance, owner=None): - if instance is None: - return self - else: - provider = instance.__dict__.get(self.provider_name) - if isinstance(provider, HasProperties): - return provider.properties() - - def __set__(self, instance, value): - if instance is not None: - provider = instance.__dict__.get(self.provider_name) - if isinstance(provider, HasProperties): - provider.update_properties(value) - self.__changed__(instance, value) - - def changed(self, instance): - if instance is not None: - provider = instance.__dict__.get(self.provider_name) - if isinstance(provider, HasProperties): - properties = provider.properties(only_changed=True) - # If no properties is changed (empty dict) return false - return bool(properties), properties - - return False, {} - - class HasPropertiesMeta(ABCMeta): """Metaclass for HasProperties classes. - This metaclass manage the 'propagation' of the properties in all subclasses, - this process involves overwriting __properties__ with a set containing all - the properties names. + This metaclass manage the "propagation" of the properties in all subclasses. ..note:: This metaclass is derived form :class:`abc.ABCMeta`, so abstract @@ -140,18 +36,48 @@ class HasPropertiesMeta(ABCMeta): def __init__(cls, *args, **kwargs): super().__init__(*args, **kwargs) - - # Use a set for avoiding repetitions cls.__properties__ = set() + cls.__live_properties__ = set() - # Populate with all the properties + # Populate with the existing properties for name, attribute in vars(cls).items(): if isinstance(attribute, Property): cls.__properties__.add(name) attribute.name = name + elif isinstance(attribute, LiveProperty): + cls.__live_properties__.add(name) + attribute.name = name for base in cls.__bases__: - cls.__properties__.update(getattr(base, '__properties__', ())) + if isinstance(base, HasPropertiesMeta): + cls.__properties__.update(base.__properties__) + cls.__live_properties__.update(base.__live_properties__) + + def __setattr__(cls, name, value): + super().__setattr__(name, value) + + if isinstance(value, Property): + cls.__properties__.add(name) + value.name = name + + for subclass in subclasses(cls): + subclass.__properties__.add(name) + elif isinstance(value, LiveProperty): + cls.__live_properties__.add(name) + value.name = name + + for subclass in subclasses(cls): + subclass.__live_properties__.add(name) + + def __delattr__(cls, name): + super().__delattr__(name) + + cls.__properties__.discard(name) + cls.__live_properties__.discard(name) + + for subclass in subclasses(cls): + subclass.__properties__.discard(name) + subclass.__live_properties__.discard(name) class HasProperties(metaclass=HasPropertiesMeta): @@ -161,53 +87,70 @@ class HasProperties(metaclass=HasPropertiesMeta): properties, that can be easily retrieved and updated via :func:`properties` and :func:`update_properties`. + When using LiveProperty those should be named as the "non-live" counterpart + prefixed by "live_". + .. Usage:: class MyClass(HasProperties): prop1 = Property(default=100) prop2 = Property() + + live_prop1 = ConcreteLiveProperty() """ __properties__ = set() + __live_properties__ = set() def __init__(self): + self._changed_signals = {} + # Contains signals that are emitted after the associated property is + # changed, the signal are create only when requested the first time. + self.property_changed = Signal() - #: Emitted after property change (self, name, value) + # Emitted after property change (self, name, value) - self.changed_signals = {} - """Contains signals that are emitted after the associated property is - changed, the signal are create only when requested the first time. - """ + def __setattr__(self, name, value): + super().__setattr__(name, value) + + if name in self.properties_names(): + self.property_changed.emit(self, name, value) + self.__emit_changed(name, value) + elif name in self.live_properties_names(): + self.__emit_changed(name, value) + + def __emit_changed(self, name, value): + try: + self._changed_signals[name].emit(value) + except KeyError: + pass @classmethod def register_property(cls, name, prop): """Register a new property with the given name. - :param str name: Property name - :param BaseProperty prop: The property - """ - if name not in cls.__properties__: - prop.name = name - setattr(cls, name, prop) - cls.__properties__.add(name) + :param name: Property name + :param prop: The property - for subclass in subclasses(cls): - subclass.__properties__.add(name) + _Deprecated: use normal attribute assignment for the class_ + """ + setattr(cls, name, prop) - def changed(self, property_name): + def changed(self, name): """ - :param property_name: The property name + :param name: The property name :return: The property change-signal :rtype: Signal """ - if property_name not in self.__class__.__properties__: - raise ValueError('no property "{0}" found'.format(property_name)) + if (name not in self.properties_names() and + name not in self.live_properties_names()): + raise ValueError('no property "{}" found'.format(name)) - signal = self.changed_signals.get(property_name, None) + signal = self._changed_signals.get(name) if signal is None: signal = Signal() - self.changed_signals[property_name] = signal + self._changed_signals[name] = signal return signal @@ -221,14 +164,24 @@ def properties(self, only_changed=False): """ if only_changed: properties = {} - for name in self.__properties__: + for name in self.properties_names(): changed, value = getattr(self.__class__, name).changed(self) if changed: properties[name] = value return properties - return {name: getattr(self, name) for name in self.__properties__} + return {name: getattr(self, name) for name in self.properties_names()} + + def update_properties(self, properties): + """Set the given properties. + + :param properties: The element properties + :type properties: dict + """ + for name, value in properties.items(): + if name in self.properties_names(): + setattr(self, name, value) @classmethod def properties_defaults(cls): @@ -238,12 +191,15 @@ def properties_defaults(cls): """ return {name: getattr(cls, name).default for name in cls.__properties__} - def update_properties(self, properties): - """Set the given properties. + @classmethod + def properties_names(cls): + """Retrieve properties names from the class - :param properties: The element properties - :type properties: dict + :return: A set containing the properties names + :rtype: set[str] """ - for name, value in properties.items(): - if name in self.__properties__: - setattr(self, name, value) + return cls.__properties__ + + @classmethod + def live_properties_names(cls): + return cls.__live_properties__ diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 8b2418d4c..47b4189c7 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -23,7 +23,8 @@ from lisp.core.configuration import config from lisp.core.decorators import async from lisp.core.fade_functions import FadeInType, FadeOutType -from lisp.core.has_properties import HasProperties, Property, WriteOnceProperty +from lisp.core.has_properties import HasProperties +from lisp.core.properties import Property, WriteOnceProperty from lisp.core.rwait import RWait from lisp.core.signal import Signal from lisp.core.util import EqEnum diff --git a/lisp/cues/media_cue.py b/lisp/cues/media_cue.py index 1422669ce..508031f44 100644 --- a/lisp/cues/media_cue.py +++ b/lisp/cues/media_cue.py @@ -25,7 +25,7 @@ from lisp.core.decorators import async from lisp.core.fade_functions import FadeInType, FadeOutType from lisp.core.fader import Fader -from lisp.core.has_properties import NestedProperties +from lisp.core.properties import NestedProperties from lisp.cues.cue import Cue, CueAction, CueState @@ -54,7 +54,7 @@ def __init__(self, media, id=None): self.__in_fadeout = False self.__volume = self.media.element('Volume') - self.__fader = Fader(self.__volume, 'current_volume') + self.__fader = Fader(self.__volume, 'live_volume') self.__fade_lock = Lock() def __elements_changed(self): diff --git a/lisp/modules/action_cues/collection_cue.py b/lisp/modules/action_cues/collection_cue.py index d6ce2c06c..02e2d5e68 100644 --- a/lisp/modules/action_cues/collection_cue.py +++ b/lisp/modules/action_cues/collection_cue.py @@ -22,7 +22,7 @@ QDialog, QAbstractItemView, QHeaderView, QTableView from lisp.application import Application -from lisp.core.has_properties import Property +from lisp.core.properties import Property from lisp.cues.cue import Cue, CueAction from lisp.ui.cuelistdialog import CueSelectDialog from lisp.ui.qdelegates import CueActionDelegate, CueSelectionDelegate diff --git a/lisp/modules/action_cues/command_cue.py b/lisp/modules/action_cues/command_cue.py index f443142ba..088b43d9d 100644 --- a/lisp/modules/action_cues/command_cue.py +++ b/lisp/modules/action_cues/command_cue.py @@ -23,8 +23,8 @@ from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLineEdit, QCheckBox from lisp.core.decorators import async -from lisp.core.has_properties import Property -from lisp.cues.cue import Cue, CueState, CueAction +from lisp.core.properties import Property +from lisp.cues.cue import Cue, CueAction from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/modules/action_cues/index_action_cue.py b/lisp/modules/action_cues/index_action_cue.py index 57a8301c1..2eac917ca 100644 --- a/lisp/modules/action_cues/index_action_cue.py +++ b/lisp/modules/action_cues/index_action_cue.py @@ -22,7 +22,7 @@ QGridLayout, QVBoxLayout from lisp.application import Application -from lisp.core.has_properties import Property +from lisp.core.properties import Property from lisp.cues.cue import Cue, CueAction from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.settings_page import SettingsPage diff --git a/lisp/modules/action_cues/midi_cue.py b/lisp/modules/action_cues/midi_cue.py index 5ec66c8d7..db94bd697 100644 --- a/lisp/modules/action_cues/midi_cue.py +++ b/lisp/modules/action_cues/midi_cue.py @@ -21,7 +21,7 @@ from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, QLabel, \ QComboBox, QSpinBox, QFrame -from lisp.core.has_properties import Property +from lisp.core.properties import Property from lisp.cues.cue import Cue from lisp.modules.midi.midi_output import MIDIOutput from lisp.modules.midi.midi_utils import str_msg_to_dict, dict_msg_to_str diff --git a/lisp/modules/action_cues/seek_cue.py b/lisp/modules/action_cues/seek_cue.py index c2ac6e883..7971c7905 100644 --- a/lisp/modules/action_cues/seek_cue.py +++ b/lisp/modules/action_cues/seek_cue.py @@ -23,8 +23,8 @@ QHBoxLayout, QTimeEdit from lisp.application import Application -from lisp.core.has_properties import Property -from lisp.cues.cue import Cue, CueState +from lisp.core.properties import Property +from lisp.cues.cue import Cue from lisp.cues.media_cue import MediaCue from lisp.ui.cuelistdialog import CueSelectDialog from lisp.ui.settings.cue_settings import CueSettingsRegistry diff --git a/lisp/modules/action_cues/stop_all.py b/lisp/modules/action_cues/stop_all.py index 4b61b515e..a25e863c7 100644 --- a/lisp/modules/action_cues/stop_all.py +++ b/lisp/modules/action_cues/stop_all.py @@ -22,7 +22,7 @@ from PyQt5.QtWidgets import QVBoxLayout, QGroupBox from lisp.application import Application -from lisp.core.has_properties import Property +from lisp.core.properties import Property from lisp.cues.cue import Cue, CueAction from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.settings_page import SettingsPage diff --git a/lisp/modules/action_cues/volume_control.py b/lisp/modules/action_cues/volume_control.py index 3d29bdf64..92da5e39f 100644 --- a/lisp/modules/action_cues/volume_control.py +++ b/lisp/modules/action_cues/volume_control.py @@ -20,7 +20,7 @@ from PyQt5 import QtCore from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QVBoxLayout, QLabel, QHBoxLayout, QGroupBox, \ - QPushButton, QDoubleSpinBox, QGridLayout + QPushButton, QDoubleSpinBox from lisp.application import Application from lisp.backend.audio_utils import MIN_VOLUME_DB, MAX_VOLUME_DB, \ @@ -28,14 +28,13 @@ from lisp.core.decorators import async from lisp.core.fade_functions import FadeInType, FadeOutType from lisp.core.fader import Fader -from lisp.core.has_properties import Property +from lisp.core.properties import Property from lisp.cues.cue import Cue, CueAction from lisp.cues.media_cue import MediaCue from lisp.ui.cuelistdialog import CueSelectDialog from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate -from lisp.ui.widgets import FadeComboBox from lisp.ui.widgets.fade_edit import FadeEdit @@ -53,7 +52,7 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.name = translate('CueName', self.Name) - self.__fader = Fader(None, 'current_volume') + self.__fader = Fader(None, 'live_volume') self.__init_fader() def __init_fader(self): diff --git a/lisp/modules/gst_backend/elements/jack_sink.py b/lisp/modules/gst_backend/elements/jack_sink.py index c9cfe6730..b8ef8c564 100644 --- a/lisp/modules/gst_backend/elements/jack_sink.py +++ b/lisp/modules/gst_backend/elements/jack_sink.py @@ -17,14 +17,15 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import jack import logging + +import jack from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.media_element import ElementType, MediaType -from lisp.core.has_properties import Property +from lisp.core.properties import Property from lisp.modules.gst_backend.gi_repository import Gst -from lisp.modules.gst_backend.gst_element import GstMediaElement, GstProperty +from lisp.modules.gst_backend.gst_element import GstMediaElement class JackSink(GstMediaElement): diff --git a/lisp/modules/gst_backend/elements/preset_src.py b/lisp/modules/gst_backend/elements/preset_src.py index 3c2914af1..a26da97a7 100644 --- a/lisp/modules/gst_backend/elements/preset_src.py +++ b/lisp/modules/gst_backend/elements/preset_src.py @@ -22,7 +22,7 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.media_element import MediaType -from lisp.core.has_properties import Property +from lisp.core.properties import Property from lisp.modules.gst_backend.gi_repository import Gst, GstApp from lisp.modules.gst_backend.gst_element import GstSrcElement diff --git a/lisp/modules/gst_backend/elements/speed.py b/lisp/modules/gst_backend/elements/speed.py index 5690597f0..ef08ff703 100644 --- a/lisp/modules/gst_backend/elements/speed.py +++ b/lisp/modules/gst_backend/elements/speed.py @@ -20,7 +20,7 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.media_element import ElementType, MediaType -from lisp.core.has_properties import Property +from lisp.core.properties import Property from lisp.modules.gst_backend.gi_repository import Gst from lisp.modules.gst_backend.gst_element import GstMediaElement diff --git a/lisp/modules/gst_backend/elements/uri_input.py b/lisp/modules/gst_backend/elements/uri_input.py index c4e8b7aa1..0adbdc3d6 100644 --- a/lisp/modules/gst_backend/elements/uri_input.py +++ b/lisp/modules/gst_backend/elements/uri_input.py @@ -24,7 +24,7 @@ from lisp.backend.media_element import MediaType from lisp.core.decorators import async_in_pool -from lisp.core.has_properties import Property +from lisp.core.properties import Property from lisp.modules.gst_backend.gi_repository import Gst from lisp.modules.gst_backend.gst_element import GstProperty, \ GstSrcElement diff --git a/lisp/modules/gst_backend/elements/user_element.py b/lisp/modules/gst_backend/elements/user_element.py index 9df24bdb7..4566e1aec 100644 --- a/lisp/modules/gst_backend/elements/user_element.py +++ b/lisp/modules/gst_backend/elements/user_element.py @@ -20,7 +20,7 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.media_element import ElementType, MediaType -from lisp.core.has_properties import Property +from lisp.core.properties import Property from lisp.modules.gst_backend.gi_repository import Gst from lisp.modules.gst_backend.gst_element import GstMediaElement diff --git a/lisp/modules/gst_backend/elements/volume.py b/lisp/modules/gst_backend/elements/volume.py index 6fa79d369..819f9a278 100644 --- a/lisp/modules/gst_backend/elements/volume.py +++ b/lisp/modules/gst_backend/elements/volume.py @@ -22,7 +22,7 @@ from lisp.backend.media_element import ElementType, MediaType from lisp.modules.gst_backend.gi_repository import Gst from lisp.modules.gst_backend.gst_element import GstMediaElement, GstProperty, \ - GstRuntimeProperty + GstLiveProperty class Volume(GstMediaElement): @@ -35,8 +35,8 @@ class Volume(GstMediaElement): normal_volume = GstProperty('gst_normal_volume', default=1.0, gst_name='volume') - current_mute = GstRuntimeProperty('gst_volume', 'mute') - current_volume = GstRuntimeProperty('gst_volume', 'volume') + live_volume = GstLiveProperty('gst_volume', 'volume', type=float, + range=(0, 10)) def __init__(self, pipe): super().__init__() @@ -59,8 +59,8 @@ def src(self): return self.audio_convert def stop(self): - self.current_mute = self.mute - self.current_volume = self.volume + self.live_mute = self.mute + self.live_volume = self.volume def interrupt(self): self.stop() diff --git a/lisp/modules/gst_backend/gst_element.py b/lisp/modules/gst_backend/gst_element.py index cdb7871e3..267130fc5 100644 --- a/lisp/modules/gst_backend/gst_element.py +++ b/lisp/modules/gst_backend/gst_element.py @@ -18,13 +18,14 @@ # along with Linux Show Player. If not, see . from lisp.backend.media_element import MediaElement, ElementType -from lisp.core.has_properties import Property +from lisp.core.properties import Property, LiveProperty class GstProperty(Property): - def __init__(self, element_name, default=None, gst_name=None, adapter=None): - super().__init__(default=default) + def __init__(self, element_name, default=None, gst_name=None, adapter=None, + **meta): + super().__init__(default=default, **meta) self.element_name = element_name self.gst_name = gst_name @@ -41,14 +42,15 @@ def __set__(self, instance, value): getattr(instance, self.element_name).set_property(name, value) -class GstRuntimeProperty: +class GstLiveProperty(LiveProperty): - def __init__(self, element_name, property_name, adapter=None): + def __init__(self, element_name, property_name, adapter=None, **meta): + super().__init__(**meta) self.element_name = element_name self.property_name = property_name self.adapter = adapter - def __get__(self, instance, cls): + def __get__(self, instance, owner=None): if instance is None: return self else: diff --git a/lisp/modules/gst_backend/gst_media.py b/lisp/modules/gst_backend/gst_media.py index a3aa7e594..7514a995b 100644 --- a/lisp/modules/gst_backend/gst_media.py +++ b/lisp/modules/gst_backend/gst_media.py @@ -20,7 +20,7 @@ import weakref from lisp.backend.media import Media, MediaState -from lisp.core.has_properties import Property +from lisp.core.properties import Property from lisp.modules.gst_backend import elements from lisp.modules.gst_backend.gi_repository import Gst diff --git a/lisp/plugins/controller/controller.py b/lisp/plugins/controller/controller.py index f0a6b9ac3..b655e39f0 100644 --- a/lisp/plugins/controller/controller.py +++ b/lisp/plugins/controller/controller.py @@ -18,8 +18,8 @@ # along with Linux Show Player. If not, see . from lisp.application import Application -from lisp.core.has_properties import Property from lisp.core.plugin import Plugin +from lisp.core.properties import Property from lisp.cues.cue import Cue, CueAction from lisp.plugins.controller import protocols from lisp.plugins.controller.controller_settings import ControllerSettings @@ -37,7 +37,7 @@ def __init__(self): self.__protocols = {} # Register a new Cue property to store settings - Cue.register_property('controller', Property(default={})) + Cue.controller = Property(default={}) # Listen cue_model changes Application().cue_model.item_added.connect(self.__cue_added) diff --git a/lisp/plugins/timecode/timecode.py b/lisp/plugins/timecode/timecode.py index 1c463924b..f0b86e1c3 100644 --- a/lisp/plugins/timecode/timecode.py +++ b/lisp/plugins/timecode/timecode.py @@ -26,8 +26,8 @@ from lisp.application import Application from lisp.core.clock import Clock from lisp.core.configuration import config -from lisp.core.has_properties import Property from lisp.core.plugin import Plugin +from lisp.core.properties import Property from lisp.core.signal import Connection from lisp.core.util import time_tuple from lisp.cues.cue import Cue @@ -160,7 +160,7 @@ def __init__(self): self.__cues = set() # Register a new Cue property to store settings - Cue.register_property('timecode', Property(default={})) + Cue.timecode = Property(default={}) # Register cue-settings-page CueSettingsRegistry().add_item(TimecodeCueSettings, MediaCue) diff --git a/lisp/plugins/triggers/triggers.py b/lisp/plugins/triggers/triggers.py index 0c9e0ecae..64d9ebcb3 100644 --- a/lisp/plugins/triggers/triggers.py +++ b/lisp/plugins/triggers/triggers.py @@ -18,8 +18,8 @@ # along with Linux Show Player. If not, see . from lisp.application import Application -from lisp.core.has_properties import Property from lisp.core.plugin import Plugin +from lisp.core.properties import Property from lisp.cues.cue import Cue from lisp.plugins.triggers.triggers_handler import CueHandler from lisp.plugins.triggers.triggers_settings import TriggersSettings @@ -34,7 +34,7 @@ def __init__(self): self.__handlers = {} # Register a Cue property to store settings - Cue.register_property('triggers', Property({})) + Cue.triggers = Property({}) # Cue.triggers -> {trigger: [(target_id, action), ...]} # Register SettingsPage From 85f211c17abe7375d2bfdce08af5220cb7860b88 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 23 Feb 2017 11:39:09 +0100 Subject: [PATCH 057/333] Updates in the Property API --- lisp/core/properties.py | 130 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 lisp/core/properties.py diff --git a/lisp/core/properties.py b/lisp/core/properties.py new file mode 100644 index 000000000..62bfc7865 --- /dev/null +++ b/lisp/core/properties.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2017 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from copy import deepcopy + + +class LiveProperty: + """Descriptor used to define live-properties. + + Live-properties allow to manipulate parameters in a live context, without + touching the values stored inside `Property` descriptors. + + This class doesn't implement any behavior, but it must be extended in order + to correctly register live-properties. + """ + def __init__(self, **meta): + self.name = '_' + self.meta = meta + + def __get__(self, instance, owner=None): + pass + + def __set__(self, instance, value): + pass + + +class Property: + """Descriptor used to define properties. + + Properties allow to define "special" attributes for objects, to provide + custom behaviors in a simple and reusable manner. + + .. warning:: + To be able to save properties into a session, the stored value + MUST be JSON-serializable. + """ + + def __init__(self, default=None, **meta): + self.name = '_' + self.default = default + self.meta = meta + + def __get__(self, instance, owner=None): + if instance is None: + return self + elif self.name not in instance.__dict__: + instance.__dict__[self.name] = deepcopy(self.default) + + return instance.__dict__.get(self.name) + + def __set__(self, instance, value): + if instance is not None: + # Only change the value if different + if value != instance.__dict__.get(self.name, self.default): + instance.__dict__[self.name] = value + + def changed(self, instance): + if instance is not None: + value = self.__get__(instance) + return value != self.default, value + + return False, self.default + + +class WriteOnceProperty(Property): + """Property that can be modified only once. + + Obviously this is not really "write-once", but if used as normal attribute + will ignore any change when the stored value is different from default. + """ + + def __set__(self, instance, value): + if self.__get__(instance) == self.default: + super().__set__(instance, value) + + +class NestedProperties(Property): + """Simplify retrieving the properties of nested objects. + + The goal is to avoid the reimplementation of `properties()` and + `update_properties()` functions. + + ..note:: + When getting or setting a single property of the nested object is better + to access it directly instead that using the nested-property. + + Because of this is suggested to use a "special" name for the + nested-property, for example use "_media_" instead of "media". + """ + + def __init__(self, provider_name, default=None, **meta): + super().__init__(default=default, **meta) + self.provider_name = provider_name + + def __get__(self, instance, owner=None): + if instance is None: + return self + else: + return self.provider(instance).properties() + + def __set__(self, instance, value): + if instance is not None: + self.provider(instance).update_properties(value) + + def changed(self, instance): + if instance is not None: + properties = self.provider(instance).properties(only_changed=True) + # If no properties is changed (empty dict) return false + return bool(properties), properties + + return False, {} + + def provider(self, instance): + return instance.__dict__.get(self.provider_name) \ No newline at end of file From 0a7ac5ebb47cce9e02799fb7ce20e4467ec4a8ea Mon Sep 17 00:00:00 2001 From: offtools Date: Sat, 25 Feb 2017 19:56:06 +0100 Subject: [PATCH 058/333] *fixes wrong delegate type in OscArgumentView --- lisp/plugins/controller/protocols/osc.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 0667392b2..4fe715894 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -93,6 +93,7 @@ def __init__(self, **kwargs): self.model.dataChanged.connect(self.__argument_changed) self.view = OscArgumentView(parent=self.groupBox) + self.view.doubleClicked.connect(self.__update_editor) self.view.setModel(self.model) self.groupBox.layout().addWidget(self.view, 2, 0, 1, 2) @@ -121,21 +122,28 @@ def __remove_argument(self): if self.model.rowCount() and self.view.currentIndex().row(): self.model.removeRow(self.view.currentIndex().row()) + def __update_editor(self, model_index): + print("QModelIndex: ", model_index.row(), model_index.column()) + column = model_index.column() + row = model_index.row() + if column == 1: + model = model_index.model() + osc_type = model.rows[row][0] + print(osc_type) + delegate = self.view.itemDelegate(model_index) + delegate.updateEditor(OscMessageType(osc_type)) + def __argument_changed(self, index_topleft, index_bottomright, roles): if not (Qt.EditRole in roles): return - model = index_bottomright.model() + model = self.model curr_row = index_topleft.row() model_row = model.rows[curr_row] curr_col = index_bottomright.column() osc_type = model_row[0] if curr_col == 0: - index = model.createIndex(curr_row, 1) - delegate = self.view.itemDelegate(index) - delegate.updateEditor(OscMessageType[osc_type]) - if osc_type == 'Integer' or osc_type == 'Float': model_row[1] = 0 elif osc_type == 'Bool': From 95687b4000cb889f58e6b7fbcee5df6fb7969ec0 Mon Sep 17 00:00:00 2001 From: offtools Date: Sat, 25 Feb 2017 19:57:14 +0100 Subject: [PATCH 059/333] cleanup print --- lisp/plugins/controller/protocols/osc.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 4fe715894..32057ae43 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -123,13 +123,11 @@ def __remove_argument(self): self.model.removeRow(self.view.currentIndex().row()) def __update_editor(self, model_index): - print("QModelIndex: ", model_index.row(), model_index.column()) column = model_index.column() row = model_index.row() if column == 1: model = model_index.model() osc_type = model.rows[row][0] - print(osc_type) delegate = self.view.itemDelegate(model_index) delegate.updateEditor(OscMessageType(osc_type)) From 952c48535d4f4b55f3a680aafdc16d61f77edbbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Cibrario?= Date: Mon, 27 Feb 2017 21:51:02 +0100 Subject: [PATCH 060/333] Checkboxes for Regex Rename Module Added checkboxes in preview list. Changed internal implementation to allow selection "on the fly" --- lisp/modules/rename_cues/rename_action.py | 9 +- lisp/modules/rename_cues/rename_cues.py | 19 ++- lisp/modules/rename_cues/rename_ui.py | 158 ++++++++++++++++------ 3 files changed, 132 insertions(+), 54 deletions(-) diff --git a/lisp/modules/rename_cues/rename_action.py b/lisp/modules/rename_cues/rename_action.py index fd9c5ac10..03cd074b1 100644 --- a/lisp/modules/rename_cues/rename_action.py +++ b/lisp/modules/rename_cues/rename_action.py @@ -25,11 +25,10 @@ class RenameCueAction(Action): # Store names for undo/redo in a dict like that : {'id': name} names = {} - def __init__(self, new_names): - """Get and store names""" - for i, new_cue_name in enumerate(new_names): - cue = Application().layout.get_selected_cues()[i] - self.names[cue.id] = new_cue_name + def __init__(self, new_cue_list): + """Store new names with id""" + for renamed_cue in new_cue_list: + self.names[renamed_cue['id']] = renamed_cue['cue_preview'] def do(self): """Use stored name and exchange with current names""" diff --git a/lisp/modules/rename_cues/rename_cues.py b/lisp/modules/rename_cues/rename_cues.py index 2876fef8b..f6da1659c 100644 --- a/lisp/modules/rename_cues/rename_cues.py +++ b/lisp/modules/rename_cues/rename_cues.py @@ -41,19 +41,24 @@ def __init__(self): MainWindow().menuTools.addAction(self.menuAction) def rename(self): - # Warning if no cue is selected + # Initiate rename windows + renameUi = RenameUi(MainWindow()) + # Different behaviour if no cues are selected if Application().layout.get_selected_cues() == []: msg = QMessageBox() msg.setIcon(QMessageBox.Information) - msg.setText('You have to select some cues to rename them') + msg.setText(translate('RenameCues', + "No cues are selected. Rename tool will display all your cues.\n")) msg.exec_() + + renameUi.get_all_cues() else: - renameUi = RenameUi(MainWindow()) - renameUi.get_cues_name() - renameUi.exec_() + renameUi.get_selected_cues() + + renameUi.exec_() - if renameUi.result() == QDialog.Accepted: - renameUi.record_cues_name() + if renameUi.result() == QDialog.Accepted: + renameUi.record_cues_name() def terminate(self): MainWindow().menuTools.removeAction(self.menuAction) diff --git a/lisp/modules/rename_cues/rename_ui.py b/lisp/modules/rename_cues/rename_ui.py index b762a7dcf..505ee520c 100644 --- a/lisp/modules/rename_cues/rename_ui.py +++ b/lisp/modules/rename_cues/rename_ui.py @@ -19,11 +19,10 @@ import logging import re -from copy import copy from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QGridLayout, QLineEdit, \ - QTreeWidget, QTreeWidgetItem, QPushButton, QSpacerItem + QTreeWidget, QAbstractItemView, QTreeWidgetItem, QPushButton, QSpacerItem from lisp.application import Application from lisp.application import MainActionsHandler @@ -35,10 +34,8 @@ class RenameUi(QDialog): def __init__(self, parent=None): super().__init__(parent) - self.__cue_names = [] - self.__cue_names_preview = [] - # Here will be stored regex result in the same order as the cue names above - self.__cue_names_regex_groups = [] + # This list store infos on cues with dict (see self.get_selected_cues) + self._cues_list = [] self.setWindowTitle(translate('RenameCues', 'Rename cues')) self.setWindowModality(Qt.ApplicationModal) @@ -56,6 +53,8 @@ def __init__(self, parent=None): self.previewList.resizeColumnToContents(0) self.previewList.resizeColumnToContents(1) self.previewList.setColumnWidth(0, 300) + self.previewList.setSelectionMode(QAbstractItemView.NoSelection) + self.previewList.itemPressed.connect(self.onPreviewListItemPressed) self.layout().addWidget(self.previewList, 0, 0, 3, 4) # Options buttons @@ -96,11 +95,21 @@ def __init__(self, parent=None): self.outRegexLine = QLineEdit() self.outRegexLine.setPlaceholderText( translate('RenameCues', 'Rename all cue. () in regex below usable with $0, $1 ...')) + self.outRegexLine.setToolTip( + translate('RenameCues', 'What you type here is used to rename all cues.\n' + 'The regex line below is used to match patterns, and to ' + 'capture parts of the text with parenthesis.\n' + 'You can use the content of first () here with $0, the second with $1, etc..')) self.outRegexLine.textChanged.connect(self.onOutRegexChanged) self.layout().addWidget(self.outRegexLine, 3, 3) self.regexLine = QLineEdit() self.regexLine.setPlaceholderText(translate('RenameCues', 'Type your regex here :')) + self.regexLine.setToolTip(translate('RenameCues', 'Match patterns with standard python regex.\n' + 'Exemple : [a-z]([0-9]+) will find a lower case character [a-z] ' + 'followed by one or more number.\n' + 'Only the numbers will be captured for use with $0 above.\n' + 'For more information, consult python documentation')) self.regexLine.textChanged.connect(self.onRegexLineChanged) self.layout().addWidget(self.regexLine, 4, 3) @@ -113,37 +122,81 @@ def __init__(self, parent=None): self.dialogButtons.accepted.connect(self.accept) self.dialogButtons.rejected.connect(self.reject) - def get_cues_name(self): + def get_selected_cues(self): for cue in Application().layout.get_selected_cues(): - self.__cue_names.append(cue.name) - self.__cue_names_preview = copy(self.__cue_names) - # Initialization for regex matches - self.__cue_names_regex_groups = [() for i in self.__cue_names] + self._cues_list.append({ + 'cue_name': cue.name, + 'cue_preview': cue.name, + 'selected': True, + 'regex_groups': [], + 'id': cue.id + }) self.update_preview_list() - def update_preview_list(self): - self.previewList.clear() + def get_all_cues(self): + for cue in Application().cue_model.filter(): + self._cues_list.append({ + 'cue_name': cue.name, + 'cue_preview': cue.name, + 'selected': True, + 'regex_groups': [], + 'id': cue.id + }) + + self.update_preview_list() - if self.__cue_names != []: - for i, cue in enumerate(self.__cue_names): - a = QTreeWidgetItem(self.previewList) - a.setText(0, cue) - a.setText(1, self.__cue_names_preview[i]) + def update_preview_list(self): + if self.previewList.topLevelItemCount() == 0: + self.populate_preview_list() + else: + for i in range(self.previewList.topLevelItemCount()): + item = self.previewList.topLevelItem(i) + item.setText(0, self._cues_list[i]['cue_name']) + item.setText(1, self._cues_list[i]['cue_preview']) + if self._cues_list[i]['selected']: + item.setCheckState(0, Qt.Checked) + else: + item.setCheckState(0, Qt.Unchecked) + + def populate_preview_list(self): + if self._cues_list != []: + for cue_to_rename in self._cues_list: + item = QTreeWidgetItem(self.previewList) + item.setText(0, cue_to_rename['cue_name']) + item.setText(1, cue_to_rename['cue_preview']) + if cue_to_rename['selected']: + item.setCheckState(0, Qt.Checked) + else: + item.setCheckState(0, Qt.Unchecked) + + def onPreviewListItemPressed(self, item, event): + # Toggle checkbox on first column + # and change 'selected' in self._cues_list + i = self.previewList.indexOfTopLevelItem(item) + if item.checkState(0) == Qt.Checked: + item.setCheckState(0, Qt.Unchecked) + self._cues_list[i]['selected'] = False + else: + item.setCheckState(0, Qt.Checked) + self._cues_list[i]['selected'] = True def onCapitalizeButtonClicked(self): - self.__cue_names_preview = [ - x.title() for x in self.__cue_names_preview] + for cue in self._cues_list: + if cue['selected']: + cue['cue_preview'] = cue['cue_preview'].title() self.update_preview_list() def onLowerButtonClicked(self): - self.__cue_names_preview = [ - x.lower() for x in self.__cue_names_preview] + for cue in self._cues_list: + if cue['selected']: + cue['cue_preview'] = cue['cue_preview'].lower() self.update_preview_list() def onUpperButtonClicked(self): - self.__cue_names_preview = [ - x.upper() for x in self.__cue_names_preview] + for cue in self._cues_list: + if cue['selected']: + cue['cue_preview'] = cue['cue_preview'].upper() self.update_preview_list() def onRemoveNumButtonClicked(self): @@ -156,23 +209,30 @@ def remove_numb(input): else: return input - self.__cue_names_preview = [ - remove_numb(x) for x in self.__cue_names_preview] + for cue in self._cues_list: + if cue['selected']: + cue['cue_preview'] = remove_numb(cue['cue_preview']) self.update_preview_list() def onAddNumberingButtonClicked(self): - cues_nbr = len(self.__cue_names) + # Extract selected rows + cues_to_modify = [cue for cue in self._cues_list if cue['selected']] + # Count selected rows + cues_nbr = len(cues_to_modify) + # Calculate number of digits in order to add appropriate number of 0's digit_nbr = len(str(cues_nbr)) - self.__cue_names_preview = [ - f"{i+1:0{digit_nbr}.0f} - {cue_name}" - for i, cue_name in enumerate(self.__cue_names_preview) - ] + + for i, cue in enumerate(cues_to_modify): + if cue['selected']: + cue['cue_preview'] = f"{i+1:0{digit_nbr}.0f} - {cue['cue_preview']}" self.update_preview_list() def onResetButtonClicked(self): - self.__cue_names_preview = copy(self.__cue_names) + for cue in self._cues_list: + cue['cue_preview'] = cue['cue_name'] + cue['selected'] = True self.update_preview_list() @@ -183,10 +243,10 @@ def onRegexLineChanged(self): except re.error: logging.info("Regex error : not a valid pattern") else: - for i, cue_name in enumerate(self.__cue_names): - result = regex.search(cue_name) + for cue in self._cues_list: + result = regex.search(cue['cue_name']) if result: - self.__cue_names_regex_groups[i] = result.groups() + cue['regex_groups'] = result.groups() self.onOutRegexChanged() @@ -194,24 +254,27 @@ def onOutRegexChanged(self): out_pattern = self.outRegexLine.text() if out_pattern == '': - self.__cue_names_preview = copy(self.__cue_names) + for cue in self._cues_list: + if cue['selected']: + cue['cue_preview'] = cue['cue_name'] else: - for i, cue_name in enumerate(self.__cue_names): + for cue in self._cues_list: out_string = out_pattern - for n in range(len(self.__cue_names_regex_groups[0])): - pattern = '\${}'.format(n) + for n in range(len(cue['regex_groups'])): + pattern = f"\${n}" try: out_string = re.sub(pattern, - self.__cue_names_regex_groups[i][n], out_string) + cue['regex_groups'][n], out_string) except IndexError: logging.info("Regex error : Catch with () before display with $n") - self.__cue_names_preview[i] = out_string + if cue['selected']: + cue['cue_preview'] = out_string self.update_preview_list() def record_cues_name(self): MainActionsHandler.do_action( - RenameCueAction(self.__cue_names_preview) + RenameCueAction(self._cues_list) ) if __name__ == "__main__": @@ -221,5 +284,16 @@ def record_cues_name(self): gui_test_app = QApplication(sys.argv) rename_ui = RenameUi() + + # Put a few names for test + rename_ui._cues_list.append( + {'cue_name': 'Cue Name', 'cue_preview': 'Cue Preview', 'selected': True, 'regex_groups': []}) + rename_ui._cues_list.append( + {'cue_name': 'Other Cue Name', 'cue_preview': 'Cue Preview', 'selected': True, 'regex_groups': []}) + rename_ui._cues_list.append( + {'cue_name': 'Third Cue Name', 'cue_preview': 'Cue Preview', 'selected': True, 'regex_groups': []}) + rename_ui.populate_preview_list() + rename_ui.show() + sys.exit(gui_test_app.exec()) From e659079698c8ef5d68e204cbc2d69e94421bdeca Mon Sep 17 00:00:00 2001 From: offtools Date: Thu, 2 Mar 2017 21:32:54 +0100 Subject: [PATCH 061/333] fixed remove first argument in __remove_argument --- lisp/plugins/controller/protocols/osc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 32057ae43..8a9e446c3 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -119,7 +119,7 @@ def __add_argument(self): self.model.appendRow(OscMessageType.Int.value, 0) def __remove_argument(self): - if self.model.rowCount() and self.view.currentIndex().row(): + if self.model.rowCount() and self.view.currentIndex().row() >= 0: self.model.removeRow(self.view.currentIndex().row()) def __update_editor(self, model_index): From 2b9ec6423a628e28dd94bbfb3c7ffd726aec2441 Mon Sep 17 00:00:00 2001 From: offtools Date: Thu, 2 Mar 2017 21:32:54 +0100 Subject: [PATCH 062/333] fixed remove first argument in __remove_argument and __remove_message --- lisp/plugins/controller/protocols/osc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 32057ae43..8a9e446c3 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -119,7 +119,7 @@ def __add_argument(self): self.model.appendRow(OscMessageType.Int.value, 0) def __remove_argument(self): - if self.model.rowCount() and self.view.currentIndex().row(): + if self.model.rowCount() and self.view.currentIndex().row() >= 0: self.model.removeRow(self.view.currentIndex().row()) def __update_editor(self, model_index): From 6020e50f0b4e77950363222ccb8502d25feb93c4 Mon Sep 17 00:00:00 2001 From: offtools Date: Thu, 2 Mar 2017 21:41:38 +0100 Subject: [PATCH 063/333] fixed remove first argument in __remove_message and __remove_argument --- lisp/plugins/controller/protocols/osc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 8a9e446c3..c7b9d1d4d 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -119,7 +119,7 @@ def __add_argument(self): self.model.appendRow(OscMessageType.Int.value, 0) def __remove_argument(self): - if self.model.rowCount() and self.view.currentIndex().row() >= 0: + if self.model.rowCount() and self.view.currentIndex().row() > -1: self.model.removeRow(self.view.currentIndex().row()) def __update_editor(self, model_index): @@ -321,7 +321,7 @@ def __new_message(self): self.oscModel.appendRow(path, types, '{}'.format(arguments)[1:-1], self._default_action) def __remove_message(self): - if self.oscModel.rowCount() and self.OscView.currentIndex().row(): + if self.oscModel.rowCount() and self.OscView.currentIndex().row() > -1: self.oscModel.removeRow(self.OscView.currentIndex().row()) From cebd23e93839377a8d4e8eedf99f811e00e9f9f2 Mon Sep 17 00:00:00 2001 From: offtools Date: Fri, 3 Mar 2017 16:37:10 +0100 Subject: [PATCH 064/333] *osc server started in protocols.osc.init, stopped (and callback cleanup) on reset *additional callbacks for ListLayout commands only added when in Listlayout --- lisp/modules/osc/osc.py | 5 +- lisp/modules/osc/osc_common.py | 125 +++++++++++------------ lisp/plugins/controller/protocols/osc.py | 7 ++ 3 files changed, 68 insertions(+), 69 deletions(-) diff --git a/lisp/modules/osc/osc.py b/lisp/modules/osc/osc.py index 4726ea0b2..fd1a33a9e 100644 --- a/lisp/modules/osc/osc.py +++ b/lisp/modules/osc/osc.py @@ -31,7 +31,4 @@ class Osc(Module): def __init__(self): # Register the settings widget - AppSettings.register_settings_widget(OscSettings) - - if config['OSC'].getboolean('enabled') is True: - OscCommon().start() + AppSettings.register_settings_widget(OscSettings) \ No newline at end of file diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index 39dab094d..227c10b27 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -27,13 +27,9 @@ from lisp.core.configuration import config from lisp.layouts.list_layout.layout import ListLayout from lisp.ui import elogging -from lisp.ui.mainwindow import MainWindow from lisp.application import Application -from lisp.cues.cue import CueState from lisp.core.signal import Signal -# TODO: add osc log as queue and dialog to show it - class OscMessageType(Enum): Int = 'Integer' @@ -41,65 +37,62 @@ class OscMessageType(Enum): Bool = 'Bool' String = 'String' -# decorator for OSC callback (pushing messages to log, emits message_event) -# def __osc_handler(func): -# def func_wrapper(path, args, types): -# func() -# # OscCommon().push_log(path, args, types, src) -# OscCommon().new_message.emit(path, args, types) -# return func_wrapper +def callback_go(_, args, types): + if not isinstance(Application().layout, ListLayout): + return + + if (types == 'i' and args[0] == 1) or types == '': + Application().layout.go() + + +def callback_reset(_, args, types): + if not isinstance(Application().layout, ListLayout): + return + + if (types == 'i' and args[0] == 1) or types == '': + Application().layout.interrupt_all() + Application().layout.set_current_index(0) + + +def callback_restart(_, args, types): + if not isinstance(Application().layout, ListLayout): + return + + if (types == 'i' and args[0] == 1) or types == '': + Application().layout.restart_all() -# @__osc_handler -def _go(): - """triggers GO in ListLayout""" - if isinstance(MainWindow().layout, ListLayout): - MainWindow().layout.go() +def callback_pause(_, args, types): + if not isinstance(Application().layout, ListLayout): + return -# @__osc_handler -def _list_reset(): - """reset, stops all cues, sets cursor to Cue index 0 in ListLayout""" - if isinstance(MainWindow().layout, ListLayout): - for cue in Application().cue_model: - cue.stop() - MainWindow().layout.set_current_index(0) + if (types == 'i' and args[0] == 1) or types == '': + Application().layout.pause_all() -# @__osc_handler -def _list_cursor(path, args, types): - """sets cursor to given Cue index in ListLayout""" - if not isinstance(MainWindow().layout, ListLayout): - MainWindow().layout.set_current_index(0) +def callback_stop(_, args, types): + if not isinstance(Application().layout, ListLayout): return - if path == '/lisp/list/cursor' and types == 'i': - index = args[0] - MainWindow().layout.set_current_index(index) + if (types == 'i' and args[0] == 1) or types == '': + Application().layout.stop_all() -# @__osc_handler -def _pause_all(): - """triggers global pause all media""" - for cue in Application().cue_model: - if cue.state == CueState.Running: - cue.pause(True) +def callback_select(_, args, types): + if not isinstance(Application().layout, ListLayout): + return + if types == 'i' and args[0] > -1: + Application().layout.set_current_index(args[0]) -# @__osc_handler -def _play_all(): - """triggers global play, if pausing""" - for cue in Application().cue_model: - if cue.state == CueState.Pause: - cue.start(True) +def callback_interrupt(_, args, types): + if not isinstance(Application().layout, ListLayout): + return -# TODO: add fade as option to message? -# @__osc_handler -def _stop_all(): - """triggers global stop, stops all media cues""" - for cue in Application().cue_model: - cue.stop(True) + if (types == 'i' and args[0] == 1) or types == '': + Application().layout.interrupt_all() class OscCommon(metaclass=ABCSingleton): @@ -109,31 +102,29 @@ def __init__(self): self.__log = deque([], 10) self.new_message = Signal() - # TODO: static paths and callbacks, make it editable through settings dialog self.__callbacks = [ - ['/lisp/list/go', None, _go], - ['/lisp/list/reset', None, _list_reset], - ['/lisp/list/cursor', 'i', _list_cursor], - ['/lisp/pause', None, _pause_all], - ['/lisp/play', None, _play_all], - ['/lisp/stop', None, _stop_all], - [None, None, self.__new_message] + ['/lisp/list/go', None, callback_go], + ['/lisp/list/reset', None, callback_reset], + ['/lisp/list/select', 'i', callback_select], + ['/lisp/list/pause', None, callback_pause], + ['/lisp/list/restart', None, callback_restart], + ['/lisp/list/stop', None, callback_stop], + ['/lisp/list/interrupt', None, callback_interrupt] ] - # def push_log(self, path, args, types, src, success=True): - # self.__log.append([path, args, types, src, success]) - # - # def get_log(self): - # return self.__log - def start(self): if self.__listening: return try: self.__srv = ServerThread(int(config['OSC']['inport'])) - for cb in self.__callbacks: - self.__srv.add_method(cb[0], cb[1], cb[2]) + + if isinstance(Application().layout, ListLayout): + for cb in self.__callbacks: + self.__srv.add_method(cb[0], cb[1], cb[2]) + + self.__srv.add_method(None, None, self.__new_message) + self.__srv.start() self.__listening = True elogging.info('OSC: Server started ' + self.__srv.url, dialog=False) @@ -148,6 +139,10 @@ def stop(self): self.__srv.free() elogging.info('OSC: Server stopped', dialog=False) + @property + def enabled(self): + return config['OSC']['enabled'] == 'True' + @property def listening(self): return self.__listening diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index c7b9d1d4d..420e932c9 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -44,6 +44,13 @@ def __init__(self): if check_module('osc') and OscCommon().listening: OscCommon().new_message.connect(self.__new_message) + def init(self): + if OscCommon().enabled: + OscCommon().start() + + def reset(self): + OscCommon().stop() + def __new_message(self, path, args, types): key = Osc.key_from_message(path, types, args) self.protocol_event.emit(key) From f70577f59d1c1f6d377c8761ca9528c11995a940 Mon Sep 17 00:00:00 2001 From: offtools Date: Tue, 7 Mar 2017 13:22:32 +0100 Subject: [PATCH 065/333] Timecode Output module (midi and artnet) (#56) Extend timecode plugin with MIDI and add the possibility to add new backends --- .gitignore | 1 + lisp/default.cfg | 5 +- lisp/plugins/timecode/__init__.py | 9 +- lisp/plugins/timecode/protocols/__init__.py | 59 +++++++ lisp/plugins/timecode/protocols/artnet.py | 87 +++++++++++ lisp/plugins/timecode/protocols/midi.py | 122 +++++++++++++++ lisp/plugins/timecode/timecode.py | 152 ++---------------- lisp/plugins/timecode/timecode_common.py | 127 +++++++++++++++ lisp/plugins/timecode/timecode_protocol.py | 41 +++++ lisp/plugins/timecode/timecode_settings.py | 162 ++++++++++---------- 10 files changed, 534 insertions(+), 231 deletions(-) create mode 100644 lisp/plugins/timecode/protocols/__init__.py create mode 100644 lisp/plugins/timecode/protocols/artnet.py create mode 100644 lisp/plugins/timecode/protocols/midi.py create mode 100644 lisp/plugins/timecode/timecode_common.py create mode 100644 lisp/plugins/timecode/timecode_protocol.py diff --git a/.gitignore b/.gitignore index 7ad858049..5ca7f7e99 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ eggs/ # QT project files, generated only for pylupdate (qt i18n) *.pro +/doc/ diff --git a/lisp/default.cfg b/lisp/default.cfg index db5e33b9f..3b629df48 100644 --- a/lisp/default.cfg +++ b/lisp/default.cfg @@ -70,6 +70,5 @@ dbMin = -60 dbClip = 0 [Timecode] -Enabled = True -Format = FILM -HRes = True +Format = SMPTE +Protocol = MIDI \ No newline at end of file diff --git a/lisp/plugins/timecode/__init__.py b/lisp/plugins/timecode/__init__.py index 0921c8dd7..28e5d0b1c 100644 --- a/lisp/plugins/timecode/__init__.py +++ b/lisp/plugins/timecode/__init__.py @@ -18,11 +18,4 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -try: - import ola -except ImportError: - ola = False - raise ImportError('OLA module not found, plugin not loaded.') - -if ola: - from .timecode import Timecode +from .timecode import Timecode diff --git a/lisp/plugins/timecode/protocols/__init__.py b/lisp/plugins/timecode/protocols/__init__.py new file mode 100644 index 000000000..cde93d58f --- /dev/null +++ b/lisp/plugins/timecode/protocols/__init__.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from os.path import dirname +from lisp.core.loading import load_classes + +try: + import ola +except ImportError: + ola = False + +__PROTOCOLS = {} + + +def load_protocols(): + if not ola: + exclude = 'artnet' + else: + exclude = '' + + for name, protocol_class in load_classes(__package__, dirname(__file__), exclude=exclude): + __PROTOCOLS[protocol_class.Name] = protocol_class + + +def get_protocol(name): + """ + :param name: protocol name + :type name: str + :return: instance of a TimecodeProtocol + :rtype: TimecodeProtocol + """ + if name in __PROTOCOLS and callable(__PROTOCOLS[name]): + return __PROTOCOLS[name]() + else: + raise AttributeError("Timecode Protocol not found", name) + + +def list_protocols(): + """ + :return: list of protocol names + :rtype: list + """ + return __PROTOCOLS diff --git a/lisp/plugins/timecode/protocols/artnet.py b/lisp/plugins/timecode/protocols/artnet.py new file mode 100644 index 000000000..438099b4f --- /dev/null +++ b/lisp/plugins/timecode/protocols/artnet.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + + +from ola.OlaClient import OlaClient, OLADNotRunningException + +from lisp.ui.ui_utils import translate +from lisp.core.util import time_tuple +from lisp.plugins.timecode.timecode_common import TcFormat +from lisp.plugins.timecode.timecode_protocol import TimecodeProtocol +from lisp.ui import elogging + + +class Artnet(TimecodeProtocol): + Name = 'ArtNet' + + __format__ = {TcFormat.FILM: OlaClient.TIMECODE_FILM, + TcFormat.EBU: OlaClient.TIMECODE_EBU, + TcFormat.SMPTE: OlaClient.TIMECODE_SMPTE} + + def __init__(self): + super().__init__() + try: + self.__client = OlaClient() + except OLADNotRunningException as e: + self.__client = None + elogging.error(translate('Timecode', 'TIMECODE: Could not create OlaClient'), + details="{0}".format(e), dialog=False) + + self.__last_time = -1 + + def status(self): + return bool(self.__client) + + def send(self, fmt, time, track=-1): + if self.__last_time + fmt.value >= time: + return + + tt = time_tuple(time) + + hours = tt[0] + if track > -1: + hours = track + minutes = tt[1] + seconds = tt[2] + frames = int(tt[3] / fmt.value) + + try: + self.__client.SendTimeCode(self.__format__[fmt], + hours, + minutes, + seconds, + frames) + + except OLADNotRunningException: + elogging.error( + translate('Timecode', 'Cannot send timecode.'), + details=translate('Timecode', 'OLA has stopped.')) + return False + except Exception as e: + elogging.exception('Cannot send timecode.', e, dialog=False) + return False + + self.__last_time = time + return True + + def stop(self, rclient=False): + self.__last_time = -1 + + if rclient: + self.__client = None diff --git a/lisp/plugins/timecode/protocols/midi.py b/lisp/plugins/timecode/protocols/midi.py new file mode 100644 index 000000000..ddaed457b --- /dev/null +++ b/lisp/plugins/timecode/protocols/midi.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from mido import Message + +from lisp.core.util import time_tuple +from lisp.modules.midi.midi_output import MIDIOutput +from lisp.plugins.timecode.timecode_common import TcFormat +from lisp.plugins.timecode.timecode_protocol import TimecodeProtocol +from lisp.ui import elogging + + +class Midi(TimecodeProtocol): + Name = 'MIDI' + + __format__ = { + TcFormat.FILM: 0, + TcFormat.EBU: 1, + TcFormat.SMPTE: 3 + } + + __table__ = [ + lambda h, m, s, f, r: f & 15, + lambda h, m, s, f, r: (f >> 4) & 1, + lambda h, m, s, f, r: s & 15, + lambda h, m, s, f, r: (s >> 4) & 3, + lambda h, m, s, f, r: m & 15, + lambda h, m, s, f, r: (m >> 4) & 3, + lambda h, m, s, f, r: h & 15, + lambda h, m, s, f, r: (h >> 4 & 1) + (r << 1) + ] + + def __init__(self): + super().__init__() + self.__last_time = -1 + self.__last_frame = -1 + if not MIDIOutput().is_open(): + MIDIOutput().open() + + def status(self): + return MIDIOutput().is_open() + + def __send_fullframe(self, fmt, hours, minutes, seconds, frames): + """sends fullframe timecode message, in case timecode is non continous (repositioning, rewind)""" + fmt = self.__format__[fmt] + hh = (fmt << 5) + hours + msg = Message('sysex', data=[0x7f, 0x7f, 0x01, 0x01, hh, minutes, seconds, frames]) + MIDIOutput().send(msg) + return True + + def __send_quarterframe(self, frame_type, fmt, hours, minutes, seconds, frames): + """send quarterframe midi message""" + rate = self.__format__[fmt] + msg = Message('quarter_frame') + msg.frame_type = frame_type + msg.frame_value = self.__table__[frame_type](hours, minutes, seconds, frames, rate) + MIDIOutput().send(msg) + + def __next_frame(self, fmt, time): + return time - self.__last_time > (fmt.value / 4) + + def send(self, fmt, time, track=-1): + tick = fmt.value / 4 + + if self.__last_time < 0 or time - self.__last_time > tick * 8 or time - self.__last_time < 0: + tt = time_tuple(time) + self.__send_fullframe(fmt, + int(tt[0]) if track < 0 else track, + int(tt[1]), + int(tt[2]), + int(tt[3]/fmt.value)) + if int(tt[3]/fmt.value) % 2: + self.__last_frame = 3 + else: + self.__last_frame = -1 + self.__last_time = time + return True + if not self.__next_frame(fmt, time): + return True + + frame_type = self.__last_frame + + while self.__last_time + tick < time: + self.__last_time += tick + tt = time_tuple(self.__last_time) + if frame_type < len(self.__table__) - 1: + frame_type += 1 + else: + frame_type = 0 + self.__send_quarterframe(frame_type, + fmt, + tt[0] if track < 0 else track, + int(tt[1]), + int(tt[2]), + int(tt[3]/fmt.value)) + + self.__last_frame = frame_type + + return True + + def stop(self, rclient=False): + self.__last_time = -1 + self.__last_frame = -1 + + if rclient: + elogging.debug("TIMECODE midi backend should be closed by midi module. Doing nothing", dialog=False) diff --git a/lisp/plugins/timecode/timecode.py b/lisp/plugins/timecode/timecode.py index 1c463924b..637bbfcc9 100644 --- a/lisp/plugins/timecode/timecode.py +++ b/lisp/plugins/timecode/timecode.py @@ -18,137 +18,17 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import logging -from collections import namedtuple - -from ola.OlaClient import OLADNotRunningException, OlaClient from lisp.application import Application -from lisp.core.clock import Clock -from lisp.core.configuration import config from lisp.core.has_properties import Property from lisp.core.plugin import Plugin from lisp.core.signal import Connection -from lisp.core.util import time_tuple from lisp.cues.cue import Cue -from lisp.cues.cue_time import CueTime from lisp.cues.media_cue import MediaCue -from lisp.plugins.timecode.timecode_settings import TimecodeCueSettings, \ - TimecodeSettings -from lisp.ui import elogging -from lisp.ui.settings.app_settings import AppSettings from lisp.ui.settings.cue_settings import CueSettingsRegistry -from lisp.ui.ui_utils import translate - -TcFormatDef = namedtuple('TimecodeDef', ['format', 'millis']) -TcFormat = { - 'FILM': TcFormatDef(format=OlaClient.TIMECODE_FILM, millis=1000 / 24), - 'EBU': TcFormatDef(format=OlaClient.TIMECODE_EBU, millis=1000 / 25), - 'SMPTE': TcFormatDef(format=OlaClient.TIMECODE_SMPTE, millis=1000 / 30) -} - - -class HRCueTime(CueTime): - _Clock = Clock(30) # 1000 /30 = 33.3333 milliseconds - - -class OlaTimecode: - def __init__(self): - try: - self.__client = OlaClient() - except OLADNotRunningException: - self.__client = None - - self.__cue = None - self.__cue_time = None - - self.__track = 0 - self.__hres = config['Timecode'].getboolean('hres') - self.__format = TcFormat[config['Timecode']['format']].format - self.__millis = TcFormat[config['Timecode']['format']].millis - self.__replace_hours = False - self.__last_frame = -1 - - @property - def cue(self): - return self.__cue - - def status(self): - return bool(self.__client) - - def start_timecode(self, cue): - """Start the timecode, using the given cue.""" - # Test and create client, if needed, return on fail - if not self.__client: - try: - self.__client = OlaClient() - except OLADNotRunningException: - logging.debug('TIMECODE: Cannot track cue, OLA not running.') - return - - # Load cue settings, if enabled, otherwise return - if not cue.timecode['enabled']: - return - - # Stop the currently "running" timecode - self.stop_timecode() - - # Reload format settings - self.__hres = config['Timecode'].getboolean('hres') - self.__format = TcFormat[config['Timecode']['format']].format - self.__millis = TcFormat[config['Timecode']['format']].millis - - # Setup new cue and options - self.__cue = cue - self.__cue_time = HRCueTime(cue) if self.__hres else CueTime(cue) - self.__replace_hours = cue.timecode['replace_hours'] - self.__track = cue.timecode['track'] - - # Start watching the new cue - self.__cue_time.notify.connect( - self.__send_timecode, Connection.QtQueued) - - def stop_timecode(self, rclient=False, rcue=False): - """Stop the timecode - - :param rclient: Reset the client - :param rcue: Reset the cues - """ - if self.__cue_time is not None: - self.__cue_time.notify.disconnect(self.__send_timecode) - - self.__last_frame = -1 - if rclient: - self.__client = None - if rcue: - self.__cue = None - self.__cue_time = None - - def __send_timecode(self, time): - tt = time_tuple(time) - frame = int(tt[3] / self.__millis) - - if self.__hres: - if self.__last_frame == frame: - return - self.__last_frame = frame - - try: - if not self.__replace_hours: - track = tt[0] - else: - track = self.__track - - self.__client.SendTimeCode(self.__format, - track, tt[1], tt[2], frame) - except OLADNotRunningException: - self.stop_timecode(rclient=True, rcue=True) - elogging.error( - translate('Timecode', 'Cannot send timecode.'), - details=translate('Timecode', 'OLA has stopped.')) - except Exception as e: - self.stop_timecode(rclient=True, rcue=True) - elogging.exception('Cannot send timecode.', e, dialog=False) +from lisp.ui.settings.app_settings import AppSettings +from lisp.plugins.timecode.timecode_common import TimecodeCommon +from lisp.plugins.timecode.timecode_settings import TimecodeAppSettings, TimecodeSettings class Timecode(Plugin): @@ -156,30 +36,27 @@ class Timecode(Plugin): def __init__(self): super().__init__() - self.__client = OlaTimecode() self.__cues = set() # Register a new Cue property to store settings Cue.register_property('timecode', Property(default={})) # Register cue-settings-page - CueSettingsRegistry().add_item(TimecodeCueSettings, MediaCue) - # Register pref-settings-page - AppSettings.register_settings_widget(TimecodeSettings) + CueSettingsRegistry().add_item(TimecodeSettings, MediaCue) + + # Register the settings widget + AppSettings.register_settings_widget(TimecodeAppSettings) # Watch cue-model changes Application().cue_model.item_added.connect(self.__cue_added) Application().cue_model.item_removed.connect(self.__cue_removed) def init(self): - if not config['Timecode'].getboolean('enabled'): - logging.info('TIMECODE: disabled by application settings') - elif not self.__client.status(): - logging.info('TIMECODE: disabled, OLA not running') + TimecodeCommon().init() def reset(self): self.__cues.clear() - self.__client.stop_timecode(rclient=True, rcue=True) + TimecodeCommon().stop(rclient=True, rcue=True) def __cue_changed(self, cue, property_name, value): if property_name == 'timecode': @@ -197,17 +74,12 @@ def __cue_added(self, cue): def __cue_removed(self, cue): try: self.__cues.remove(cue.id) - if self.__client.cue is cue: - self.__client.stop_timecode(rcue=True) - + if TimecodeCommon().cue is cue: + TimecodeCommon().stop(rcue=True) cue.started.disconnect(self.__cue_started) cue.property_changed.disconnect(self.__cue_changed) except KeyError: pass def __cue_started(self, cue): - if config['Timecode'].getboolean('enabled'): - if cue is not self.__client.cue: - self.__client.start_timecode(cue) - elif cue is self.__client.cue: - self.__client.stop_timecode(rcue=True) + TimecodeCommon().start(cue) diff --git a/lisp/plugins/timecode/timecode_common.py b/lisp/plugins/timecode/timecode_common.py new file mode 100644 index 000000000..41796b736 --- /dev/null +++ b/lisp/plugins/timecode/timecode_common.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from enum import Enum + +from lisp.core.clock import Clock +from lisp.core.configuration import config +from lisp.core.signal import Connection +from lisp.core.singleton import ABCSingleton +from lisp.cues.cue_time import CueTime +from lisp.plugins.timecode import protocols +from lisp.ui import elogging + + +class TcFormat(Enum): + + FILM = 1000 / 24 + EBU = 1000 / 25 + SMPTE = 1000 / 30 + + +class HRCueTime(CueTime): + _Clock = Clock(30) # 1000 /30 = 33.3333 milliseconds + + +class TimecodeCommon(metaclass=ABCSingleton): + def __init__(self): + self.__protocol_name = config['Timecode']['protocol'] + self.__protocol = None + + self.__cue = None + self.__cue_time = None + + self.__track = 0 + self.__format = TcFormat[config['Timecode']['format']] + self.__replace_hours = False + + # load timecode protocol components + protocols.load_protocols() + + @property + def cue(self): + """current cue, which timecode is send""" + return self.__cue + + @property + def status(self): + """returns the status of the protocol""" + return self.__protocol and self.__protocol.status() + + @property + def protocol(self): + return self.__protocol.Name + + def init(self): + """set timecode protocol""" + self.__protocol = protocols.get_protocol(self.__protocol_name) + + if self.__protocol.status(): + elogging.debug("TIMECODE: protocol created - {0}".format(self.__protocol.Name)) + else: + elogging.error("TIMECODE: error creating protocol - {0}".format(self.__protocol.Name), dialog=False) + + def change_protocol(self, protocol_name): + if protocol_name in protocols.list_protocols(): + self.__protocol_name = protocol_name + self.init() + + def start(self, cue): + """initialize timecode for new cue, stop old""" + if not self.status: + return + + # Stop the currently "running" timecode + self.stop(rcue=self.__cue) + + # Reload format settings + self.__format = TcFormat[config['Timecode']['format']] + + # Setup new cue and options + self.__cue = cue + self.__cue_time = HRCueTime(cue) + self.__replace_hours = cue.timecode['replace_hours'] + if self.__replace_hours: + self.__track = cue.timecode['track'] + else: + self.__track = -1 + + self.send(self.__cue.current_time()) + + # Start watching the new cue + self.__cue_time.notify.connect( + self.send, Connection.QtQueued) + + def stop(self, rclient=False, rcue=False): + """stops sending timecode""" + if self.__cue_time is not None: + self.__cue_time.notify.disconnect(self.send) + + if rcue: + self.__cue = None + self.__cue_time = None + + if self.status: + self.__protocol.stop(rclient) + + def send(self, time): + """sends timecode""" + if not self.__protocol.send(self.__format, time, self.__track): + elogging.error('TIMECODE: could not send timecode, stopping timecode', dialog=False) + self.stop() diff --git a/lisp/plugins/timecode/timecode_protocol.py b/lisp/plugins/timecode/timecode_protocol.py new file mode 100644 index 000000000..1693ad540 --- /dev/null +++ b/lisp/plugins/timecode/timecode_protocol.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from abc import abstractmethod +from abc import ABCMeta + + +class TimecodeProtocol(metaclass=ABCMeta): + """base class for timecode protocol""" + Name = 'None' + + def __init__(self): + """constructor of timecode backend""" + + @abstractmethod + def status(self): + """returns status of backend, True if ready""" + + @abstractmethod + def send(self, fmt, time, track=-1): + """send timecode, returs success for error handling""" + + @abstractmethod + def stop(self, rclient=False): + """cleanup after client has stopped""" diff --git a/lisp/plugins/timecode/timecode_settings.py b/lisp/plugins/timecode/timecode_settings.py index e52cd7b6a..fe3fcd4e2 100644 --- a/lisp/plugins/timecode/timecode_settings.py +++ b/lisp/plugins/timecode/timecode_settings.py @@ -18,88 +18,23 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . + from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt from PyQt5.QtWidgets import QGridLayout -from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel,\ - QCheckBox, QComboBox, QHBoxLayout, QSpinBox -from ola.OlaClient import OLADNotRunningException, OlaClient + QCheckBox, QSpinBox, QComboBox -from lisp.ui.mainwindow import MainWindow -from lisp.ui.settings.settings_page import CueSettingsPage,\ - SettingsPage +from lisp.ui import elogging from lisp.ui.ui_utils import translate +from lisp.core.configuration import config +from lisp.plugins.timecode.timecode_common import TcFormat +from lisp.plugins.timecode import protocols +from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.settings_page import CueSettingsPage +from lisp.plugins.timecode.timecode_common import TimecodeCommon -class TimecodeSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Timecode Settings') - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.setLayout(QVBoxLayout()) - self.layout().setAlignment(Qt.AlignTop) - - self.groupBox = QGroupBox(self) - self.groupBox.setLayout(QGridLayout()) - self.layout().addWidget(self.groupBox) - - self.activateBox = QCheckBox(self.groupBox) - self.groupBox.layout().addWidget(self.activateBox, 0, 0) - - self.hresBox = QCheckBox(self.groupBox) - self.groupBox.layout().addWidget(self.hresBox, 1, 0) - - self.formatLabel = QLabel(self.groupBox) - self.groupBox.layout().addWidget(self.formatLabel, 2, 0) - - self.formatBox = QComboBox(self.groupBox) - self.formatBox.addItem('FILM') - self.formatBox.addItem('EBU') - self.formatBox.addItem('SMPTE') - self.groupBox.layout().addWidget(self.formatBox, 2, 1) - - self.retranslateUi() - - def retranslateUi(self): - self.groupBox.setTitle( - translate('TimecodeSettings', 'OLA Timecode Settings')) - self.activateBox.setText(translate('TimecodeSettings', 'Enable Plugin')) - self.hresBox.setText( - translate('TimecodeSettings', 'High-Resolution Timecode')) - self.formatLabel.setText( - translate('TimecodeSettings', 'Timecode Format:')) - - def testOla(self): - if self.activateBox.isChecked(): - try: - client = OlaClient() - del client - except OLADNotRunningException: - QMessageBox.warning( - MainWindow(), - translate('TimecodeSettings', 'OLA status'), - translate('TimecodeSettings', - 'OLA is not running - start the OLA daemon.') - ) - - def get_settings(self): - return {'Timecode': { - 'enabled': str(self.activateBox.isChecked()), - 'hres': str(self.hresBox.isChecked()), - 'format': self.formatBox.currentText() - }} - - def load_settings(self, settings): - settings = settings.get('Timecode', {}) - - self.activateBox.setChecked(settings.get('enabled') == 'True') - self.hresBox.setChecked(settings.get('hres') == 'True') - self.formatBox.setCurrentText(settings.get('format', '')) - - self.activateBox.stateChanged.connect(self.testOla) - - -class TimecodeCueSettings(CueSettingsPage): +class TimecodeSettings(CueSettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Timecode') def __init__(self, cue_class, **kwargs): @@ -146,13 +81,15 @@ def retranslateUi(self): translate('TimecodeSettings', 'Replace HOURS by a static track number')) self.enableCheck.setText( - translate('TimecodeSettings', 'Enable ArtNet Timecode')) + translate('TimecodeSettings', 'Enable Timecode')) self.trackLabel.setText( translate('TimecodeSettings', 'Track number')) - self.warnLabel.setText( - translate('TimecodeSettings', - 'To send ArtNet Timecode you need to setup a running OLA' - ' session!')) + if 'artnet' in config['Timecode']['protocol'].lower(): + self.warnLabel.setText( + translate('TimecodeSettings' + '', + 'To send ArtNet Timecode you need to setup a running OLA' + ' session!')) def get_settings(self): settings = { @@ -168,3 +105,68 @@ def load_settings(self, settings): self.enableCheck.setChecked(settings.get('enabled', False)) self.useHoursCheck.setChecked(settings.get('replace_hours', False)) self.trackSpin.setValue(settings.get('track', 0)) + + +class TimecodeAppSettings(SettingsPage): + Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Timecode Settings') + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.setLayout(QVBoxLayout()) + self.layout().setAlignment(Qt.AlignTop) + + self.groupBox = QGroupBox(self) + self.groupBox.setLayout(QGridLayout()) + self.layout().addWidget(self.groupBox) + + self.formatLabel = QLabel(self.groupBox) + self.groupBox.layout().addWidget(self.formatLabel, 0, 0) + + self.formatBox = QComboBox(self.groupBox) + for fmt in TcFormat: + self.formatBox.addItem(fmt.name) + self.groupBox.layout().addWidget(self.formatBox, 0, 1) + + self.protocolLabel = QLabel(self.groupBox) + self.groupBox.layout().addWidget(self.protocolLabel, 1, 0) + + self.protocolCombo = QComboBox(self.groupBox) + for protocol in protocols.list_protocols(): + self.protocolCombo.addItem(protocol) + self.groupBox.layout().addWidget(self.protocolCombo, 1, 1) + + self.retranslateUi() + + def retranslateUi(self): + self.groupBox.setTitle( + translate('TimecodeSettings', 'Timecode Settings')) + self.formatLabel.setText( + translate('TimecodeSettings', 'Timecode Format:')) + self.protocolLabel.setText( + translate('TimecodeSettings', 'Timecode Protocol:')) + + def __protocol_changed(self, protocol): + # check for restart + TimecodeCommon().change_protocol(protocol) + + print("__protocol_changed", protocol, TimecodeCommon().status) + if not TimecodeCommon().status: + elogging.error( + translate('TimecodeSettings', 'Error on setting Timecode Protocol to {}.\n' + 'Timecode cannot be sent.\n' + 'Change the Protocol Entry.').format(protocol)) + + def get_settings(self): + conf = {} + + conf['format'] = self.formatBox.currentText() + conf['protocol'] = self.protocolCombo.currentText() + + return {'Timecode': conf} + + def load_settings(self, settings): + settings = settings.get('Timecode', {}) + + self.formatBox.setCurrentText(settings.get('format', '')) + self.protocolCombo.setCurrentText(settings.get('protocol', '')) + self.protocolCombo.currentTextChanged.connect(self.__protocol_changed) From e00c52df7000d91f1fc4ae90f6ca5f7606402350 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 7 Mar 2017 17:18:19 +0100 Subject: [PATCH 066/333] Refactored Timecode pluging. Update: MIDI module now provide a virtual IO port and use them by default --- lisp/default.cfg | 7 +- lisp/modules/midi/midi.py | 24 +++- lisp/plugins/timecode/protocols/artnet.py | 40 +++---- lisp/plugins/timecode/protocols/midi.py | 133 ++++++++++----------- lisp/plugins/timecode/timecode.py | 19 +-- lisp/plugins/timecode/timecode_common.py | 43 ++++--- lisp/plugins/timecode/timecode_protocol.py | 14 +-- lisp/plugins/timecode/timecode_settings.py | 42 +++---- 8 files changed, 161 insertions(+), 161 deletions(-) diff --git a/lisp/default.cfg b/lisp/default.cfg index 3b629df48..789aaea82 100644 --- a/lisp/default.cfg +++ b/lisp/default.cfg @@ -2,7 +2,7 @@ [Version] #Don't change this section values -Number = 19 +Number = 20 [Cue] FadeActionDuration = 3 @@ -24,9 +24,10 @@ Pipeline = Volume, Equalizer10, DbMeter, AutoSink Default = NoDefault [MIDI] -InputDevice = SysDefault -OutputDevice = SysDefault Backend = mido.backends.rtmidi +AppPortName = LiSP +InputDevice = LiSP +OutputDevice = LiSP [Remote] BindIp = 0.0.0.0 diff --git a/lisp/modules/midi/midi.py b/lisp/modules/midi/midi.py index a5feeada0..c4dfb84c2 100644 --- a/lisp/modules/midi/midi.py +++ b/lisp/modules/midi/midi.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -28,13 +28,27 @@ class Midi(Module): """Provide MIDI I/O functionality""" + AppPort = None + def __init__(self): # Register the settings widget AppSettings.register_settings_widget(MIDISettings) - bk_name = config['MIDI']['Backend'] + backend = config['MIDI']['Backend'] try: - # Load the backend and set as current mido backend - mido.set_backend(mido.Backend(bk_name, load=True)) + # Load the backend and set it as current mido backend + mido.set_backend(mido.Backend(backend, load=True)) except Exception: - raise RuntimeError('Backend loading failed: {0}'.format(bk_name)) + raise RuntimeError('Backend loading failed: {0}'.format(backend)) + + # Create LiSP MIDI I/O port + try: + Midi.AppPort = mido.backend.open_ioport( + config['MIDI']['AppPortName'], virtual=True) + except IOError: + import logging + logging.error('MIDI: cannot open application virtual-port.') + + def terminate(self): + if Midi.AppPort is not None: + Midi.AppPort.close() diff --git a/lisp/plugins/timecode/protocols/artnet.py b/lisp/plugins/timecode/protocols/artnet.py index 438099b4f..5642812d9 100644 --- a/lisp/plugins/timecode/protocols/artnet.py +++ b/lisp/plugins/timecode/protocols/artnet.py @@ -2,7 +2,8 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2017 Francesco Ceruti +# Copyright 2016-2017 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,31 +18,35 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging from ola.OlaClient import OlaClient, OLADNotRunningException -from lisp.ui.ui_utils import translate from lisp.core.util import time_tuple from lisp.plugins.timecode.timecode_common import TcFormat from lisp.plugins.timecode.timecode_protocol import TimecodeProtocol from lisp.ui import elogging +from lisp.ui.ui_utils import translate + +ARTNET_FORMATS = { + TcFormat.FILM: OlaClient.TIMECODE_FILM, + TcFormat.EBU: OlaClient.TIMECODE_EBU, + TcFormat.SMPTE: OlaClient.TIMECODE_SMPTE +} class Artnet(TimecodeProtocol): Name = 'ArtNet' - __format__ = {TcFormat.FILM: OlaClient.TIMECODE_FILM, - TcFormat.EBU: OlaClient.TIMECODE_EBU, - TcFormat.SMPTE: OlaClient.TIMECODE_SMPTE} - def __init__(self): super().__init__() + try: self.__client = OlaClient() except OLADNotRunningException as e: self.__client = None - elogging.error(translate('Timecode', 'TIMECODE: Could not create OlaClient'), - details="{0}".format(e), dialog=False) + logging.error('TIMECODE: cannot create OlaClient') + logging.debug('TIMECODE: {}'.format(e)) self.__last_time = -1 @@ -52,29 +57,22 @@ def send(self, fmt, time, track=-1): if self.__last_time + fmt.value >= time: return - tt = time_tuple(time) - - hours = tt[0] + hours, minutes, seconds, millis = time_tuple(time) + frame = int(millis / fmt.value) if track > -1: hours = track - minutes = tt[1] - seconds = tt[2] - frames = int(tt[3] / fmt.value) try: - self.__client.SendTimeCode(self.__format__[fmt], - hours, - minutes, - seconds, - frames) - + self.__client.SendTimeCode( + ARTNET_FORMATS[fmt], hours, minutes, seconds, frame) except OLADNotRunningException: elogging.error( translate('Timecode', 'Cannot send timecode.'), details=translate('Timecode', 'OLA has stopped.')) return False except Exception as e: - elogging.exception('Cannot send timecode.', e, dialog=False) + logging.error('TIMECODE: Cannot send timecode') + logging.debug('TIMECODE: {}'.format(e)) return False self.__last_time = time diff --git a/lisp/plugins/timecode/protocols/midi.py b/lisp/plugins/timecode/protocols/midi.py index ddaed457b..324d54cc8 100644 --- a/lisp/plugins/timecode/protocols/midi.py +++ b/lisp/plugins/timecode/protocols/midi.py @@ -2,7 +2,8 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2017 Francesco Ceruti +# Copyright 2016-2017 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -23,29 +24,30 @@ from lisp.modules.midi.midi_output import MIDIOutput from lisp.plugins.timecode.timecode_common import TcFormat from lisp.plugins.timecode.timecode_protocol import TimecodeProtocol -from lisp.ui import elogging + + +MIDI_FORMATS = { + TcFormat.FILM: 0, + TcFormat.EBU: 1, + TcFormat.SMPTE: 3 +} + + +FRAME_VALUES = [ + lambda h, m, s, f, r: f & 15, + lambda h, m, s, f, r: (f >> 4) & 1, + lambda h, m, s, f, r: s & 15, + lambda h, m, s, f, r: (s >> 4) & 3, + lambda h, m, s, f, r: m & 15, + lambda h, m, s, f, r: (m >> 4) & 3, + lambda h, m, s, f, r: h & 15, + lambda h, m, s, f, r: (h >> 4 & 1) + (r << 1) +] class Midi(TimecodeProtocol): Name = 'MIDI' - __format__ = { - TcFormat.FILM: 0, - TcFormat.EBU: 1, - TcFormat.SMPTE: 3 - } - - __table__ = [ - lambda h, m, s, f, r: f & 15, - lambda h, m, s, f, r: (f >> 4) & 1, - lambda h, m, s, f, r: s & 15, - lambda h, m, s, f, r: (s >> 4) & 3, - lambda h, m, s, f, r: m & 15, - lambda h, m, s, f, r: (m >> 4) & 3, - lambda h, m, s, f, r: h & 15, - lambda h, m, s, f, r: (h >> 4 & 1) + (r << 1) - ] - def __init__(self): super().__init__() self.__last_time = -1 @@ -56,67 +58,58 @@ def __init__(self): def status(self): return MIDIOutput().is_open() - def __send_fullframe(self, fmt, hours, minutes, seconds, frames): - """sends fullframe timecode message, in case timecode is non continous (repositioning, rewind)""" - fmt = self.__format__[fmt] - hh = (fmt << 5) + hours - msg = Message('sysex', data=[0x7f, 0x7f, 0x01, 0x01, hh, minutes, seconds, frames]) + def __send_full(self, fmt, hours, minutes, seconds, frame): + """Sends fullframe timecode message. + + Used in case timecode is non continuous (repositioning, rewind). + """ + hh = (MIDI_FORMATS[fmt] << 5) + hours + msg = Message('sysex', data=[0x7f, 0x7f, 0x01, 0x01, hh, minutes, + seconds, frame]) MIDIOutput().send(msg) - return True - def __send_quarterframe(self, frame_type, fmt, hours, minutes, seconds, frames): - """send quarterframe midi message""" - rate = self.__format__[fmt] + def __send_quarter(self, frame_type, fmt, hours, minutes, seconds, frame): + """Send quarterframe midi message.""" msg = Message('quarter_frame') msg.frame_type = frame_type - msg.frame_value = self.__table__[frame_type](hours, minutes, seconds, frames, rate) + msg.frame_value = FRAME_VALUES[frame_type]( + hours, minutes, seconds, frame, MIDI_FORMATS[fmt]) MIDIOutput().send(msg) - def __next_frame(self, fmt, time): - return time - self.__last_time > (fmt.value / 4) - - def send(self, fmt, time, track=-1): - tick = fmt.value / 4 - - if self.__last_time < 0 or time - self.__last_time > tick * 8 or time - self.__last_time < 0: - tt = time_tuple(time) - self.__send_fullframe(fmt, - int(tt[0]) if track < 0 else track, - int(tt[1]), - int(tt[2]), - int(tt[3]/fmt.value)) - if int(tt[3]/fmt.value) % 2: - self.__last_frame = 3 - else: - self.__last_frame = -1 + def send(self, format, time, track=-1): + # Split the time in its components + hours, minutes, seconds, millis = time_tuple(time) + frame = int(millis / format.value) + if track > -1: + hours = track + + tick = format.value / 4 + t_diff = time - self.__last_time + + if self.__last_time < 0 or t_diff > tick * 8 or t_diff < 0: + # First frame or time jumped forward/backward + self.__send_full(format, hours, minutes, seconds, frame) + + self.__last_frame = 3 if frame % 2 else -1 self.__last_time = time - return True - if not self.__next_frame(fmt, time): - return True - - frame_type = self.__last_frame - - while self.__last_time + tick < time: - self.__last_time += tick - tt = time_tuple(self.__last_time) - if frame_type < len(self.__table__) - 1: - frame_type += 1 - else: - frame_type = 0 - self.__send_quarterframe(frame_type, - fmt, - tt[0] if track < 0 else track, - int(tt[1]), - int(tt[2]), - int(tt[3]/fmt.value)) - - self.__last_frame = frame_type + elif t_diff > tick: + # Send quarter-frames + frame_type = self.__last_frame + while self.__last_time + tick < time: + self.__last_time += tick + + if frame_type < len(FRAME_VALUES) - 1: + frame_type += 1 + else: + frame_type = 0 + + self.__send_quarter( + frame_type, format, hours, minutes, seconds, frame) + + self.__last_frame = frame_type return True def stop(self, rclient=False): self.__last_time = -1 self.__last_frame = -1 - - if rclient: - elogging.debug("TIMECODE midi backend should be closed by midi module. Doing nothing", dialog=False) diff --git a/lisp/plugins/timecode/timecode.py b/lisp/plugins/timecode/timecode.py index 637bbfcc9..3cb161469 100644 --- a/lisp/plugins/timecode/timecode.py +++ b/lisp/plugins/timecode/timecode.py @@ -2,8 +2,8 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti -# Copyright 2016 Thomas Achtner +# Copyright 2012-2017 Francesco Ceruti +# Copyright 2016-2017 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -40,10 +40,8 @@ def __init__(self): # Register a new Cue property to store settings Cue.register_property('timecode', Property(default={})) - # Register cue-settings-page CueSettingsRegistry().add_item(TimecodeSettings, MediaCue) - # Register the settings widget AppSettings.register_settings_widget(TimecodeAppSettings) @@ -63,7 +61,8 @@ def __cue_changed(self, cue, property_name, value): if value.get('enabled', False): if cue.id not in self.__cues: self.__cues.add(cue.id) - cue.started.connect(self.__cue_started, Connection.QtQueued) + cue.started.connect(TimecodeCommon().start, + Connection.QtQueued) else: self.__cue_removed(cue) @@ -73,13 +72,15 @@ def __cue_added(self, cue): def __cue_removed(self, cue): try: + # Try removing the cue self.__cues.remove(cue.id) + + # If the cue is tracked, stop the tracking if TimecodeCommon().cue is cue: TimecodeCommon().stop(rcue=True) - cue.started.disconnect(self.__cue_started) + + # Disconnect signals + cue.started.disconnect(TimecodeCommon().start) cue.property_changed.disconnect(self.__cue_changed) except KeyError: pass - - def __cue_started(self, cue): - TimecodeCommon().start(cue) diff --git a/lisp/plugins/timecode/timecode_common.py b/lisp/plugins/timecode/timecode_common.py index 41796b736..fa6b87e9f 100644 --- a/lisp/plugins/timecode/timecode_common.py +++ b/lisp/plugins/timecode/timecode_common.py @@ -2,7 +2,8 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2017 Francesco Ceruti +# Copyright 2016-2017 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -19,24 +20,24 @@ from enum import Enum +import logging + from lisp.core.clock import Clock from lisp.core.configuration import config from lisp.core.signal import Connection from lisp.core.singleton import ABCSingleton from lisp.cues.cue_time import CueTime from lisp.plugins.timecode import protocols -from lisp.ui import elogging class TcFormat(Enum): - FILM = 1000 / 24 EBU = 1000 / 25 SMPTE = 1000 / 30 - + class HRCueTime(CueTime): - _Clock = Clock(30) # 1000 /30 = 33.3333 milliseconds + _Clock = Clock(30) # 1000 / 30 = 33.3333 milliseconds class TimecodeCommon(metaclass=ABCSingleton): @@ -51,31 +52,33 @@ def __init__(self): self.__format = TcFormat[config['Timecode']['format']] self.__replace_hours = False - # load timecode protocol components + # Load timecode protocol components protocols.load_protocols() @property def cue(self): - """current cue, which timecode is send""" + """Current cue, which timecode is send""" return self.__cue @property def status(self): - """returns the status of the protocol""" - return self.__protocol and self.__protocol.status() + """Returns the status of the protocol""" + return self.__protocol is not None and self.__protocol.status() @property def protocol(self): return self.__protocol.Name def init(self): - """set timecode protocol""" + """Set timecode protocol""" self.__protocol = protocols.get_protocol(self.__protocol_name) if self.__protocol.status(): - elogging.debug("TIMECODE: protocol created - {0}".format(self.__protocol.Name)) + logging.info('TIMECODE: init with "{0}" protocol'.format( + self.__protocol.Name)) else: - elogging.error("TIMECODE: error creating protocol - {0}".format(self.__protocol.Name), dialog=False) + logging.error('TIMECODE: failed init with "{0}" protocol'.format( + self.__protocol.Name)) def change_protocol(self, protocol_name): if protocol_name in protocols.list_protocols(): @@ -83,7 +86,7 @@ def change_protocol(self, protocol_name): self.init() def start(self, cue): - """initialize timecode for new cue, stop old""" + """Initialize timecode for new cue, stop old""" if not self.status: return @@ -97,19 +100,15 @@ def start(self, cue): self.__cue = cue self.__cue_time = HRCueTime(cue) self.__replace_hours = cue.timecode['replace_hours'] - if self.__replace_hours: - self.__track = cue.timecode['track'] - else: - self.__track = -1 + self.__track = cue.timecode['track'] if self.__replace_hours else -1 self.send(self.__cue.current_time()) # Start watching the new cue - self.__cue_time.notify.connect( - self.send, Connection.QtQueued) + self.__cue_time.notify.connect(self.send, Connection.QtQueued) def stop(self, rclient=False, rcue=False): - """stops sending timecode""" + """Stops sending timecode""" if self.__cue_time is not None: self.__cue_time.notify.disconnect(self.send) @@ -121,7 +120,7 @@ def stop(self, rclient=False, rcue=False): self.__protocol.stop(rclient) def send(self, time): - """sends timecode""" + """Send timecode""" if not self.__protocol.send(self.__format, time, self.__track): - elogging.error('TIMECODE: could not send timecode, stopping timecode', dialog=False) + logging.error('TIMECODE: cannot send timecode, stopping') self.stop() diff --git a/lisp/plugins/timecode/timecode_protocol.py b/lisp/plugins/timecode/timecode_protocol.py index 1693ad540..575fe3d6e 100644 --- a/lisp/plugins/timecode/timecode_protocol.py +++ b/lisp/plugins/timecode/timecode_protocol.py @@ -2,7 +2,8 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2017 Francesco Ceruti +# Copyright 2016-2017 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,20 +23,17 @@ class TimecodeProtocol(metaclass=ABCMeta): - """base class for timecode protocol""" + """Base class for timecode protocols""" Name = 'None' - def __init__(self): - """constructor of timecode backend""" - @abstractmethod def status(self): - """returns status of backend, True if ready""" + """Return the status of the backend, True if ready""" @abstractmethod def send(self, fmt, time, track=-1): - """send timecode, returs success for error handling""" + """Send timecode, returns success for error handling""" @abstractmethod def stop(self, rclient=False): - """cleanup after client has stopped""" + """Cleanup after client has stopped""" diff --git a/lisp/plugins/timecode/timecode_settings.py b/lisp/plugins/timecode/timecode_settings.py index fe3fcd4e2..1a5c3a015 100644 --- a/lisp/plugins/timecode/timecode_settings.py +++ b/lisp/plugins/timecode/timecode_settings.py @@ -2,8 +2,8 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti -# Copyright 2016 Thomas Achtner +# Copyright 2012-2017 Francesco Ceruti +# Copyright 2016-2017 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,10 +21,10 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt from PyQt5.QtWidgets import QGridLayout -from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel,\ +from PyQt5.QtWidgets import QMessageBox +from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel, \ QCheckBox, QSpinBox, QComboBox -from lisp.ui import elogging from lisp.ui.ui_utils import translate from lisp.core.configuration import config from lisp.plugins.timecode.timecode_common import TcFormat @@ -84,24 +84,23 @@ def retranslateUi(self): translate('TimecodeSettings', 'Enable Timecode')) self.trackLabel.setText( translate('TimecodeSettings', 'Track number')) + if 'artnet' in config['Timecode']['protocol'].lower(): self.warnLabel.setText( - translate('TimecodeSettings' - '', - 'To send ArtNet Timecode you need to setup a running OLA' - ' session!')) + translate('TimecodeSettings', + 'To send ArtNet Timecode you need to setup a running' + 'OLA session!')) def get_settings(self): - settings = { + return {'timecode': { 'enabled': self.enableCheck.isChecked(), 'replace_hours': self.useHoursCheck.isChecked(), 'track': self.trackSpin.value() - } - - return {'timecode': settings} + }} def load_settings(self, settings): settings = settings.get('timecode', {}) + self.enableCheck.setChecked(settings.get('enabled', False)) self.useHoursCheck.setChecked(settings.get('replace_hours', False)) self.trackSpin.setValue(settings.get('track', 0)) @@ -149,20 +148,17 @@ def __protocol_changed(self, protocol): # check for restart TimecodeCommon().change_protocol(protocol) - print("__protocol_changed", protocol, TimecodeCommon().status) if not TimecodeCommon().status: - elogging.error( - translate('TimecodeSettings', 'Error on setting Timecode Protocol to {}.\n' - 'Timecode cannot be sent.\n' - 'Change the Protocol Entry.').format(protocol)) + QMessageBox.critical( + self, translate('TimecodeSettings', 'Error'), + translate('TimecodeSettings', + 'Cannot enable "{}" protocol').format(protocol)) def get_settings(self): - conf = {} - - conf['format'] = self.formatBox.currentText() - conf['protocol'] = self.protocolCombo.currentText() - - return {'Timecode': conf} + return {'Timecode': { + 'format': self.formatBox.currentText(), + 'protocol': self.protocolCombo.currentText() + }} def load_settings(self, settings): settings = settings.get('Timecode', {}) From 4b55f2add418865ff6697c7a046bd67a7e5f7b15 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 7 Mar 2017 17:37:33 +0100 Subject: [PATCH 067/333] Minor changes in the timcode plugin --- lisp/plugins/timecode/protocols/__init__.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/lisp/plugins/timecode/protocols/__init__.py b/lisp/plugins/timecode/protocols/__init__.py index cde93d58f..07044e13c 100644 --- a/lisp/plugins/timecode/protocols/__init__.py +++ b/lisp/plugins/timecode/protocols/__init__.py @@ -18,23 +18,14 @@ # along with Linux Show Player. If not, see . from os.path import dirname -from lisp.core.loading import load_classes -try: - import ola -except ImportError: - ola = False +from lisp.core.loading import load_classes __PROTOCOLS = {} def load_protocols(): - if not ola: - exclude = 'artnet' - else: - exclude = '' - - for name, protocol_class in load_classes(__package__, dirname(__file__), exclude=exclude): + for name, protocol_class in load_classes(__package__, dirname(__file__)): __PROTOCOLS[protocol_class.Name] = protocol_class @@ -48,7 +39,7 @@ def get_protocol(name): if name in __PROTOCOLS and callable(__PROTOCOLS[name]): return __PROTOCOLS[name]() else: - raise AttributeError("Timecode Protocol not found", name) + raise AttributeError('Timecode-Protocol not found', name) def list_protocols(): From 90933cff3df12476c4736996891c632effda8bbe Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 29 Mar 2017 14:21:09 +0200 Subject: [PATCH 068/333] Increase config version --- lisp/default.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/default.cfg b/lisp/default.cfg index fed252b8f..e64fb9411 100644 --- a/lisp/default.cfg +++ b/lisp/default.cfg @@ -2,7 +2,7 @@ [Version] #Don't change this section values -Number = 20 +Number = 21 [Cue] FadeActionDuration = 3 From 7dc95e3acc853ba9a3d08d42d6cd9764ce74d826 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 30 Mar 2017 12:01:42 +0200 Subject: [PATCH 069/333] Remove application MIDI ports --- lisp/default.cfg | 5 ++--- lisp/modules/midi/midi.py | 14 -------------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/lisp/default.cfg b/lisp/default.cfg index e64fb9411..9ff2c1cff 100644 --- a/lisp/default.cfg +++ b/lisp/default.cfg @@ -25,9 +25,8 @@ Default = NoDefault [MIDI] Backend = mido.backends.rtmidi -AppPortName = LiSP -InputDevice = LiSP -OutputDevice = LiSP +InputDevice = SysDefault +OutputDevice = SysDefault [Remote] BindIp = 0.0.0.0 diff --git a/lisp/modules/midi/midi.py b/lisp/modules/midi/midi.py index c4dfb84c2..a457a4ac4 100644 --- a/lisp/modules/midi/midi.py +++ b/lisp/modules/midi/midi.py @@ -28,8 +28,6 @@ class Midi(Module): """Provide MIDI I/O functionality""" - AppPort = None - def __init__(self): # Register the settings widget AppSettings.register_settings_widget(MIDISettings) @@ -40,15 +38,3 @@ def __init__(self): mido.set_backend(mido.Backend(backend, load=True)) except Exception: raise RuntimeError('Backend loading failed: {0}'.format(backend)) - - # Create LiSP MIDI I/O port - try: - Midi.AppPort = mido.backend.open_ioport( - config['MIDI']['AppPortName'], virtual=True) - except IOError: - import logging - logging.error('MIDI: cannot open application virtual-port.') - - def terminate(self): - if Midi.AppPort is not None: - Midi.AppPort.close() From 92a91fa6a6709e5f688d9ded18b39e10db8e6506 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 30 Mar 2017 14:29:43 +0200 Subject: [PATCH 070/333] Updates to OSC module --- lisp/default.cfg | 4 +- lisp/modules/action_cues/osc_cue.py | 26 +++++----- lisp/modules/osc/__init__.py | 23 +-------- lisp/modules/osc/osc.py | 12 +++-- lisp/modules/osc/osc_common.py | 50 ++++++++++--------- lisp/modules/osc/osc_settings.py | 61 +++++++++++------------- lisp/plugins/controller/protocols/osc.py | 3 -- lisp/ui/qdelegates.py | 15 ++---- 8 files changed, 81 insertions(+), 113 deletions(-) diff --git a/lisp/default.cfg b/lisp/default.cfg index 986df8990..0f3dcdeac 100644 --- a/lisp/default.cfg +++ b/lisp/default.cfg @@ -2,7 +2,7 @@ [Version] #Don't change this section values -Number = 21 +Number = 22 [Cue] FadeActionDuration = 3 @@ -70,7 +70,7 @@ dbMin = -60 dbClip = 0 [OSC] -enabled = False +enabled = True inport = 9000 outport = 9001 hostname = localhost diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index 3664a1ff6..ec9a04543 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -18,30 +18,26 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . - +import logging from PyQt5 import QtCore from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, \ - QTableView, QTableWidget, QHeaderView, QPushButton, QLabel, \ - QLineEdit, QDoubleSpinBox - -from lisp.ui.widgets import FadeComboBox +from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, QLineEdit, \ + QTableView, QTableWidget, QHeaderView, QPushButton, QLabel, QDoubleSpinBox from lisp.core.decorators import async -from lisp.core.fader import Fader from lisp.core.fade_functions import FadeInType, FadeOutType +from lisp.core.fader import Fader +from lisp.core.has_properties import Property from lisp.cues.cue import Cue, CueAction +from lisp.modules.osc.osc_common import OscCommon +from lisp.modules.osc.osc_common import OscMessageType from lisp.ui import elogging -from lisp.ui.qmodels import SimpleTableModel -from lisp.core.has_properties import Property from lisp.ui.qdelegates import ComboBoxDelegate, OscArgumentDelegate, \ CheckBoxDelegate -from lisp.ui.settings.cue_settings import CueSettingsRegistry, \ - SettingsPage -from lisp.modules.osc.osc_common import OscCommon +from lisp.ui.qmodels import SimpleTableModel +from lisp.ui.settings.cue_settings import CueSettingsRegistry, SettingsPage from lisp.ui.ui_utils import translate -from lisp.modules.osc.osc_common import OscMessageType - +from lisp.ui.widgets import FadeComboBox COL_TYPE = 0 COL_START_VAL = 1 @@ -162,7 +158,7 @@ def __start__(self, fade=False): else: self._position = 1 else: - elogging.error("OSC: Error on parsing argument list - nothing sent", dialog=False) + logging.error("OSC: Error while parsing arguments, nothing sent") return False diff --git a/lisp/modules/osc/__init__.py b/lisp/modules/osc/__init__.py index 3b5f38acc..1ca9154a1 100644 --- a/lisp/modules/osc/__init__.py +++ b/lisp/modules/osc/__init__.py @@ -1,22 +1 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# Copyright 2012-2016 Thomas Achtner -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - - -from .osc import Osc \ No newline at end of file +from .osc import Osc diff --git a/lisp/modules/osc/osc.py b/lisp/modules/osc/osc.py index fd1a33a9e..c618edea6 100644 --- a/lisp/modules/osc/osc.py +++ b/lisp/modules/osc/osc.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2017 Francesco Ceruti # Copyright 2012-2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify @@ -20,10 +20,9 @@ from lisp.core.module import Module -from lisp.core.configuration import config -from lisp.ui.settings.app_settings import AppSettings -from lisp.modules.osc.osc_settings import OscSettings from lisp.modules.osc.osc_common import OscCommon +from lisp.modules.osc.osc_settings import OscSettings +from lisp.ui.settings.app_settings import AppSettings class Osc(Module): @@ -31,4 +30,7 @@ class Osc(Module): def __init__(self): # Register the settings widget - AppSettings.register_settings_widget(OscSettings) \ No newline at end of file + AppSettings.register_settings_widget(OscSettings) + + def terminate(self): + OscCommon().stop() \ No newline at end of file diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index 227c10b27..44f32f70b 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2017 Francesco Ceruti # Copyright 2012-2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify @@ -18,17 +18,17 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging +import traceback from collections import deque from enum import Enum - -from lisp.core.singleton import ABCSingleton from liblo import ServerThread, Address, ServerError -from lisp.core.configuration import config -from lisp.layouts.list_layout.layout import ListLayout -from lisp.ui import elogging from lisp.application import Application +from lisp.core.configuration import config from lisp.core.signal import Signal +from lisp.core.singleton import ABCSingleton +from lisp.layouts.list_layout.layout import ListLayout class OscMessageType(Enum): @@ -95,22 +95,24 @@ def callback_interrupt(_, args, types): Application().layout.interrupt_all() +GLOBAL_CALLBACKS = [ + ['/lisp/list/go', None, callback_go], + ['/lisp/list/reset', None, callback_reset], + ['/lisp/list/select', 'i', callback_select], + ['/lisp/list/pause', None, callback_pause], + ['/lisp/list/restart', None, callback_restart], + ['/lisp/list/stop', None, callback_stop], + ['/lisp/list/interrupt', None, callback_interrupt] +] + + class OscCommon(metaclass=ABCSingleton): def __init__(self): self.__srv = None self.__listening = False self.__log = deque([], 10) - self.new_message = Signal() - self.__callbacks = [ - ['/lisp/list/go', None, callback_go], - ['/lisp/list/reset', None, callback_reset], - ['/lisp/list/select', 'i', callback_select], - ['/lisp/list/pause', None, callback_pause], - ['/lisp/list/restart', None, callback_restart], - ['/lisp/list/stop', None, callback_stop], - ['/lisp/list/interrupt', None, callback_interrupt] - ] + self.new_message = Signal() def start(self): if self.__listening: @@ -120,24 +122,25 @@ def start(self): self.__srv = ServerThread(int(config['OSC']['inport'])) if isinstance(Application().layout, ListLayout): - for cb in self.__callbacks: + for cb in GLOBAL_CALLBACKS: self.__srv.add_method(cb[0], cb[1], cb[2]) self.__srv.add_method(None, None, self.__new_message) self.__srv.start() self.__listening = True - elogging.info('OSC: Server started ' + self.__srv.url, dialog=False) - except ServerError as e: - elogging.error(e, dialog=False) + logging.info('OSC: Server started at {}'.format(self.__srv.url)) + except ServerError: + logging.error('OSC: Cannot start sever') + logging.debug(traceback.format_exc()) def stop(self): - if self.__srv: + if self.__srv is not None: if self.__listening: self.__srv.stop() self.__listening = False self.__srv.free() - elogging.info('OSC: Server stopped', dialog=False) + logging.info('OSC: Server stopped') @property def enabled(self): @@ -149,7 +152,8 @@ def listening(self): def send(self, path, *args): if self.__listening: - target = Address(config['OSC']['hostname'], int(config['OSC']['outport'])) + target = Address(config['OSC']['hostname'], + int(config['OSC']['outport'])) self.__srv.send(target, path, *args) def __new_message(self, path, args, types): diff --git a/lisp/modules/osc/osc_settings.py b/lisp/modules/osc/osc_settings.py index 2a64d7352..ed704c215 100644 --- a/lisp/modules/osc/osc_settings.py +++ b/lisp/modules/osc/osc_settings.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2017 Francesco Ceruti # Copyright 2012-2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify @@ -18,15 +18,14 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . - from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt -from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel,\ +from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel, \ QCheckBox, QHBoxLayout, QSpinBox, QLineEdit -from lisp.ui.ui_utils import translate from lisp.core.configuration import config -from lisp.ui.settings.settings_page import SettingsPage from lisp.modules.osc.osc_common import OscCommon +from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.ui_utils import translate class OscSettings(SettingsPage): @@ -39,21 +38,18 @@ def __init__(self, **kwargs): self.groupBox = QGroupBox(self) self.groupBox.setLayout(QVBoxLayout()) - self.groupBox.setTitle( - translate('OscSettings', 'OSC Settings')) + self.groupBox.setTitle(translate('OscSettings', 'OSC Settings')) self.layout().addWidget(self.groupBox) self.enableModule = QCheckBox(self.groupBox) - self.enableModule.setText( - translate('OscSettings', 'enable OSC')) + self.enableModule.setText(translate('OscSettings', 'enable OSC')) self.groupBox.layout().addWidget(self.enableModule) hbox = QHBoxLayout() self.inportBox = QSpinBox(self) self.inportBox.setMinimum(1000) self.inportBox.setMaximum(99999) - label = QLabel( - translate('OscSettings', 'Input Port:')) + label = QLabel(translate('OscSettings', 'Input Port:')) hbox.layout().addWidget(label) hbox.layout().addWidget(self.inportBox) self.groupBox.layout().addLayout(hbox) @@ -62,8 +58,7 @@ def __init__(self, **kwargs): self.outportBox = QSpinBox(self) self.outportBox.setMinimum(1000) self.outportBox.setMaximum(99999) - label = QLabel( - translate('OscSettings', 'Output Port:')) + label = QLabel(translate('OscSettings', 'Output Port:')) hbox.layout().addWidget(label) hbox.layout().addWidget(self.outportBox) self.groupBox.layout().addLayout(hbox) @@ -72,39 +67,41 @@ def __init__(self, **kwargs): self.hostnameEdit = QLineEdit() self.hostnameEdit.setText('localhost') self.hostnameEdit.setMaximumWidth(200) - label = QLabel( - translate('OscSettings', 'Hostname:')) + label = QLabel(translate('OscSettings', 'Hostname:')) hbox.layout().addWidget(label) hbox.layout().addWidget(self.hostnameEdit) self.groupBox.layout().addLayout(hbox) - self.enableModule.stateChanged.connect(self.activate_module, Qt.QueuedConnection) - self.inportBox.valueChanged.connect(self.change_inport, Qt.QueuedConnection) - self.outportBox.valueChanged.connect(self.change_outport, Qt.QueuedConnection) - self.hostnameEdit.textChanged.connect(self.change_hostname, Qt.QueuedConnection) + self.enableModule.stateChanged.connect( + self.activate_module,Qt.QueuedConnection) + self.inportBox.valueChanged.connect( + self.change_inport, Qt.QueuedConnection) + self.outportBox.valueChanged.connect( + self.change_outport, Qt.QueuedConnection) + self.hostnameEdit.textChanged.connect( + self.change_hostname, Qt.QueuedConnection) def activate_module(self): if self.enableModule.isChecked(): - # start osc server - OscCommon().start() # enable OSC Module in Settings config.set('OSC', 'enabled', 'True') + # start osc server + OscCommon().start() else: - # stop osc server - OscCommon().stop() # disable OSC Module in Settings config.set('OSC', 'enabled', 'False') + # stop osc server + OscCommon().stop() def change_inport(self): - port = self.inportBox.value() - if str(port) != config.get('OSC', 'inport'): - config.set('OSC', 'inport', str(port)) + port = str(self.inportBox.value()) + if port != config.get('OSC', 'inport'): + config.set('OSC', 'inport', port) if self.enableModule.isChecked(): self.enableModule.setChecked(False) def change_outport(self): - port = self.outportBox.value() - config.set('OSC', 'outport', str(port)) + config.set('OSC', 'outport', str(self.outportBox.value())) def change_hostname(self): hostname = self.hostnameEdit.text() @@ -115,7 +112,7 @@ def get_settings(self): 'enabled': str(self.enableModule.isChecked()), 'inport': str(self.inportBox.value()), 'outport': str(self.outportBox.value()), - 'hostname': str(self.hostnameEdit.text()), + 'hostname': self.hostnameEdit.text(), } return {'OSC': conf} @@ -123,6 +120,6 @@ def load_settings(self, settings): settings = settings.get('OSC', {}) self.enableModule.setChecked(settings.get('enabled') == 'True') - self.inportBox.setValue(int(settings.get('inport'))) - self.outportBox.setValue(int(settings.get('outport'))) - self.hostnameEdit.setText(settings.get('hostname')) + self.inportBox.setValue(int(settings.get('inport', 9000))) + self.outportBox.setValue(int(settings.get('outport', 9001))) + self.hostnameEdit.setText(settings.get('hostname', 'localhost')) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 420e932c9..d74169da2 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -48,9 +48,6 @@ def init(self): if OscCommon().enabled: OscCommon().start() - def reset(self): - OscCommon().stop() - def __new_message(self, path, args, types): key = Osc.key_from_message(path, types, args) self.protocol_event.emit(key) diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index a8112778b..9012460c7 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -120,9 +120,7 @@ def __init__(self, **kwargs): super().__init__(**kwargs) def createEditor(self, parent, option, index): - editor = QCheckBox(parent) - - return editor + return QCheckBox(parent) def setEditorData(self, checkBox, index): value = index.model().data(index, Qt.EditRole) @@ -194,27 +192,22 @@ def createEditor(self, parent, option, index): return editor def setEditorData(self, widget, index): + value = index.model().data(index, Qt.EditRole) + if self.tag is OscMessageType.Int: - value = index.model().data(index, Qt.EditRole) if isinstance(value, int): widget.setValue(value) elif self.tag is OscMessageType.Float: - value = index.model().data(index, Qt.EditRole) if isinstance(value, float): widget.setValue(value) elif self.tag is OscMessageType.Bool: - value = index.model().data(index, Qt.EditRole) if isinstance(value, bool): widget.setChecked(value) else: - value = index.model().data(index, Qt.EditRole) widget.setText(str(value)) def setModelData(self, widget, model, index): - if self.tag is OscMessageType.Int: - widget.interpretText() - model.setData(index, widget.value(), Qt.EditRole) - elif self.tag is OscMessageType.Float: + if self.tag is OscMessageType.Int or self.tag is OscMessageType.Float: widget.interpretText() model.setData(index, widget.value(), Qt.EditRole) elif self.tag is OscMessageType.Bool: From e98136004aaabbd02ea5d6726afcfad238dc97fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Cibrario?= Date: Mon, 3 Apr 2017 11:49:26 +0200 Subject: [PATCH 071/333] Minor enhancement Add tooltip dialog, removed unused imports... --- lisp/modules/rename_cues/rename_cues.py | 9 ----- lisp/modules/rename_cues/rename_ui.py | 45 ++++++++++++++++++------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/lisp/modules/rename_cues/rename_cues.py b/lisp/modules/rename_cues/rename_cues.py index f6da1659c..2da675121 100644 --- a/lisp/modules/rename_cues/rename_cues.py +++ b/lisp/modules/rename_cues/rename_cues.py @@ -17,7 +17,6 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import gi from PyQt5.QtWidgets import QAction, QDialog, QMessageBox from lisp.application import Application @@ -26,8 +25,6 @@ from lisp.ui.ui_utils import translate from .rename_ui import RenameUi -gi.require_version('Gst', '1.0') - class RenameCues(Module): Name = 'RenameCues' @@ -45,12 +42,6 @@ def rename(self): renameUi = RenameUi(MainWindow()) # Different behaviour if no cues are selected if Application().layout.get_selected_cues() == []: - msg = QMessageBox() - msg.setIcon(QMessageBox.Information) - msg.setText(translate('RenameCues', - "No cues are selected. Rename tool will display all your cues.\n")) - msg.exec_() - renameUi.get_all_cues() else: renameUi.get_selected_cues() diff --git a/lisp/modules/rename_cues/rename_ui.py b/lisp/modules/rename_cues/rename_ui.py index 505ee520c..8b0c2698c 100644 --- a/lisp/modules/rename_cues/rename_ui.py +++ b/lisp/modules/rename_cues/rename_ui.py @@ -20,9 +20,11 @@ import logging import re -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QSize +from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QGridLayout, QLineEdit, \ - QTreeWidget, QAbstractItemView, QTreeWidgetItem, QPushButton, QSpacerItem + QTreeWidget, QAbstractItemView, QTreeWidgetItem, QPushButton, QSpacerItem, \ + QMessageBox from lisp.application import Application from lisp.application import MainActionsHandler @@ -55,7 +57,7 @@ def __init__(self, parent=None): self.previewList.setColumnWidth(0, 300) self.previewList.setSelectionMode(QAbstractItemView.NoSelection) self.previewList.itemPressed.connect(self.onPreviewListItemPressed) - self.layout().addWidget(self.previewList, 0, 0, 3, 4) + self.layout().addWidget(self.previewList, 0, 0, 3, 5) # Options buttons self.capitalizeButton = QPushButton() @@ -95,24 +97,21 @@ def __init__(self, parent=None): self.outRegexLine = QLineEdit() self.outRegexLine.setPlaceholderText( translate('RenameCues', 'Rename all cue. () in regex below usable with $0, $1 ...')) - self.outRegexLine.setToolTip( - translate('RenameCues', 'What you type here is used to rename all cues.\n' - 'The regex line below is used to match patterns, and to ' - 'capture parts of the text with parenthesis.\n' - 'You can use the content of first () here with $0, the second with $1, etc..')) self.outRegexLine.textChanged.connect(self.onOutRegexChanged) self.layout().addWidget(self.outRegexLine, 3, 3) self.regexLine = QLineEdit() self.regexLine.setPlaceholderText(translate('RenameCues', 'Type your regex here :')) - self.regexLine.setToolTip(translate('RenameCues', 'Match patterns with standard python regex.\n' - 'Exemple : [a-z]([0-9]+) will find a lower case character [a-z] ' - 'followed by one or more number.\n' - 'Only the numbers will be captured for use with $0 above.\n' - 'For more information, consult python documentation')) self.regexLine.textChanged.connect(self.onRegexLineChanged) self.layout().addWidget(self.regexLine, 4, 3) + # Help button + self.helpButton = QPushButton() + self.helpButton.setIcon(QIcon.fromTheme('help-info')) + self.helpButton.setIconSize(QSize(32, 32)) + self.layout().addWidget(self.helpButton, 3, 4, 2, 1) + self.helpButton.clicked.connect(self.onHelpButtonClicked) + # OK / Cancel buttons self.dialogButtons = QDialogButtonBox() self.dialogButtons.setStandardButtons(QDialogButtonBox.Ok | @@ -236,6 +235,26 @@ def onResetButtonClicked(self): self.update_preview_list() + def onHelpButtonClicked(self): + + msg = QMessageBox() + msg.setIcon(QMessageBox.Information) + msg.setWindowTitle(translate('RenameCues', 'Regex help')) + msg.setText(translate('RenameCues', + "You can use Regexes to rename your cues.\n\n" + "Insert expressions captured with regexes in the " + "line below with $0 for the first parenthesis, $1 for" + "the second, etc...\n" + "In the second line, you can use standard Python Regexes " + "to match expressions in the original cues names. Use " + "parenthesis to capture parts of the matched expression.\n\n" + "Exemple : \n^[a-z]([0-9]+) will find a lower case character ([a-z]), " + "followed by one or more number.\n" + "Only the numbers are between parenthesis and will be usable with " + "$0 in the first line.\n\n" + "For more information about Regexes, consult python documentation")) + msg.exec_() + def onRegexLineChanged(self): pattern = self.regexLine.text() try: From d52e1b182b6d76f430195dfb065fc7db5c00ac50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Cibrario?= Date: Tue, 4 Apr 2017 20:09:15 +0200 Subject: [PATCH 072/333] Default list selection and code enhancement Changed checkbox for standard list selection Various minor code enhancement --- lisp/modules/rename_cues/rename_cues.py | 17 ++-- lisp/modules/rename_cues/rename_ui.py | 116 +++++++++--------------- 2 files changed, 54 insertions(+), 79 deletions(-) diff --git a/lisp/modules/rename_cues/rename_cues.py b/lisp/modules/rename_cues/rename_cues.py index 2da675121..cdaf0db8b 100644 --- a/lisp/modules/rename_cues/rename_cues.py +++ b/lisp/modules/rename_cues/rename_cues.py @@ -38,13 +38,18 @@ def __init__(self): MainWindow().menuTools.addAction(self.menuAction) def rename(self): - # Initiate rename windows - renameUi = RenameUi(MainWindow()) - # Different behaviour if no cues are selected - if Application().layout.get_selected_cues() == []: - renameUi.get_all_cues() + # Test if some cues are selected, else select all cues + if Application().layout.get_selected_cues(): + selected_cues = Application().layout.get_selected_cues() else: - renameUi.get_selected_cues() + #TODO : implement dialog box if/when QSettings is implemented + # the dialog should inform the user that rename_module load only selected cues if needed + # but it will bother more than being useful if we can't provide a "Don't show again" + # Could be provided by QErrorMessage if QSettings is supported + selected_cues = Application().cue_model + + # Initiate rename windows + renameUi = RenameUi(MainWindow(), selected_cues) renameUi.exec_() diff --git a/lisp/modules/rename_cues/rename_ui.py b/lisp/modules/rename_cues/rename_ui.py index 8b0c2698c..e7fb15c76 100644 --- a/lisp/modules/rename_cues/rename_ui.py +++ b/lisp/modules/rename_cues/rename_ui.py @@ -26,19 +26,15 @@ QTreeWidget, QAbstractItemView, QTreeWidgetItem, QPushButton, QSpacerItem, \ QMessageBox -from lisp.application import Application from lisp.application import MainActionsHandler from lisp.modules.rename_cues.rename_action import RenameCueAction from lisp.ui.ui_utils import translate class RenameUi(QDialog): - def __init__(self, parent=None): + def __init__(self, parent=None, selected_cues=[]): super().__init__(parent) - # This list store infos on cues with dict (see self.get_selected_cues) - self._cues_list = [] - self.setWindowTitle(translate('RenameCues', 'Rename cues')) self.setWindowModality(Qt.ApplicationModal) self.resize(650, 350) @@ -55,8 +51,8 @@ def __init__(self, parent=None): self.previewList.resizeColumnToContents(0) self.previewList.resizeColumnToContents(1) self.previewList.setColumnWidth(0, 300) - self.previewList.setSelectionMode(QAbstractItemView.NoSelection) - self.previewList.itemPressed.connect(self.onPreviewListItemPressed) + self.previewList.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.previewList.itemSelectionChanged.connect(self.onPreviewListItemSelectionChanged) self.layout().addWidget(self.previewList, 0, 0, 3, 5) # Options buttons @@ -121,20 +117,10 @@ def __init__(self, parent=None): self.dialogButtons.accepted.connect(self.accept) self.dialogButtons.rejected.connect(self.reject) - def get_selected_cues(self): - for cue in Application().layout.get_selected_cues(): - self._cues_list.append({ - 'cue_name': cue.name, - 'cue_preview': cue.name, - 'selected': True, - 'regex_groups': [], - 'id': cue.id - }) - self.update_preview_list() - - def get_all_cues(self): - for cue in Application().cue_model.filter(): + # This list store infos on cues with dicts + self._cues_list = [] + for cue in selected_cues: self._cues_list.append({ 'cue_name': cue.name, 'cue_preview': cue.name, @@ -143,42 +129,25 @@ def get_all_cues(self): 'id': cue.id }) - self.update_preview_list() + # Populate Preview list + for cue_to_rename in self._cues_list: + item = QTreeWidgetItem(self.previewList) + item.setText(0, cue_to_rename['cue_name']) + item.setText(1, cue_to_rename['cue_preview']) + self.previewList.selectAll() def update_preview_list(self): - if self.previewList.topLevelItemCount() == 0: - self.populate_preview_list() - else: - for i in range(self.previewList.topLevelItemCount()): - item = self.previewList.topLevelItem(i) - item.setText(0, self._cues_list[i]['cue_name']) - item.setText(1, self._cues_list[i]['cue_preview']) - if self._cues_list[i]['selected']: - item.setCheckState(0, Qt.Checked) - else: - item.setCheckState(0, Qt.Unchecked) - - def populate_preview_list(self): - if self._cues_list != []: - for cue_to_rename in self._cues_list: - item = QTreeWidgetItem(self.previewList) - item.setText(0, cue_to_rename['cue_name']) - item.setText(1, cue_to_rename['cue_preview']) - if cue_to_rename['selected']: - item.setCheckState(0, Qt.Checked) - else: - item.setCheckState(0, Qt.Unchecked) - - def onPreviewListItemPressed(self, item, event): - # Toggle checkbox on first column - # and change 'selected' in self._cues_list - i = self.previewList.indexOfTopLevelItem(item) - if item.checkState(0) == Qt.Checked: - item.setCheckState(0, Qt.Unchecked) - self._cues_list[i]['selected'] = False - else: - item.setCheckState(0, Qt.Checked) - self._cues_list[i]['selected'] = True + for i in range(self.previewList.topLevelItemCount()): + item = self.previewList.topLevelItem(i) + item.setText(1, self._cues_list[i]['cue_preview']) + + def onPreviewListItemSelectionChanged(self): + for i in range(self.previewList.topLevelItemCount()): + item = self.previewList.topLevelItem(i) + if item.isSelected(): + self._cues_list[i]['selected'] = True + else: + self._cues_list[i]['selected'] = False def onCapitalizeButtonClicked(self): for cue in self._cues_list: @@ -201,16 +170,11 @@ def onUpperButtonClicked(self): def onRemoveNumButtonClicked(self): regex = re.compile('^[^a-zA-Z]+(.+)') - def remove_numb(input): - match = regex.search(input) - if match is not None: - return match.group(1) - else: - return input - for cue in self._cues_list: if cue['selected']: - cue['cue_preview'] = remove_numb(cue['cue_preview']) + match = regex.search(cue['cue_preview']) + if match is not None: + cue['cue_preview'] = match.group(1) self.update_preview_list() @@ -234,6 +198,7 @@ def onResetButtonClicked(self): cue['selected'] = True self.update_preview_list() + self.previewList.selectAll() def onHelpButtonClicked(self): @@ -260,7 +225,7 @@ def onRegexLineChanged(self): try: regex = re.compile(pattern) except re.error: - logging.info("Regex error : not a valid pattern") + logging.debug("Regex error : not a valid pattern") else: for cue in self._cues_list: result = regex.search(cue['cue_name']) @@ -285,7 +250,7 @@ def onOutRegexChanged(self): out_string = re.sub(pattern, cue['regex_groups'][n], out_string) except IndexError: - logging.info("Regex error : Catch with () before display with $n") + logging.debug("Regex error : Catch with () before display with $n") if cue['selected']: cue['cue_preview'] = out_string @@ -301,17 +266,22 @@ def record_cues_name(self): from PyQt5.QtWidgets import QApplication import sys + # Put a few names for test + class FakeCue: + def __init__(self, name, id): + self.name = name + self.id = id + + cue1 = FakeCue('Cue Name', '3829434920') + cue2 = FakeCue('Other Name', '4934893213') + cue3 = FakeCue('Foo Bar foo bar', '4985943859') + cue4 = FakeCue('blablablabla', '9938492384') + + fake_cue_list = [cue1, cue2, cue3, cue4] + gui_test_app = QApplication(sys.argv) - rename_ui = RenameUi() + rename_ui = RenameUi(None, fake_cue_list) - # Put a few names for test - rename_ui._cues_list.append( - {'cue_name': 'Cue Name', 'cue_preview': 'Cue Preview', 'selected': True, 'regex_groups': []}) - rename_ui._cues_list.append( - {'cue_name': 'Other Cue Name', 'cue_preview': 'Cue Preview', 'selected': True, 'regex_groups': []}) - rename_ui._cues_list.append( - {'cue_name': 'Third Cue Name', 'cue_preview': 'Cue Preview', 'selected': True, 'regex_groups': []}) - rename_ui.populate_preview_list() rename_ui.show() From 06484629064671c4feaf5b4b3f2899d20f41da7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Cibrario?= Date: Wed, 5 Apr 2017 12:44:20 +0200 Subject: [PATCH 073/333] Add copyright --- lisp/modules/rename_cues/rename_action.py | 3 ++- lisp/modules/rename_cues/rename_cues.py | 1 + lisp/modules/rename_cues/rename_ui.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lisp/modules/rename_cues/rename_action.py b/lisp/modules/rename_cues/rename_action.py index 03cd074b1..2551cd1fc 100644 --- a/lisp/modules/rename_cues/rename_action.py +++ b/lisp/modules/rename_cues/rename_action.py @@ -2,7 +2,8 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016-2017 Aurelien Cibrario +# Copyright 2012-2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/modules/rename_cues/rename_cues.py b/lisp/modules/rename_cues/rename_cues.py index cdaf0db8b..d25545814 100644 --- a/lisp/modules/rename_cues/rename_cues.py +++ b/lisp/modules/rename_cues/rename_cues.py @@ -2,6 +2,7 @@ # # This file is part of Linux Show Player # +# Copyright 2016-2017 Aurelien Cibrario # Copyright 2012-2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify diff --git a/lisp/modules/rename_cues/rename_ui.py b/lisp/modules/rename_cues/rename_ui.py index e7fb15c76..7c3a0abce 100644 --- a/lisp/modules/rename_cues/rename_ui.py +++ b/lisp/modules/rename_cues/rename_ui.py @@ -2,6 +2,7 @@ # # This file is part of Linux Show Player # +# Copyright 2016-2017 Aurelien Cibrario # Copyright 2012-2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify From ed9b6c664e41a0ff9c3dbb4f11b851b3c550e3c0 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 2 Jul 2017 20:23:23 +0200 Subject: [PATCH 074/333] Implementation of a proper "session" and relative paths --- lisp/application.py | 152 +++++++++--------- lisp/core/memento_model.py | 6 +- lisp/layouts/__init__.py | 13 +- lisp/layouts/cart_layout/layout.py | 10 -- lisp/layouts/cue_layout.py | 7 +- lisp/layouts/list_layout/layout.py | 11 +- lisp/main.py | 2 + .../modules/gst_backend/elements/uri_input.py | 29 +++- .../modules/gst_backend/settings/uri_input.py | 9 +- lisp/modules/media_cue_menus.py | 19 +-- lisp/plugins/__init__.py | 25 --- lisp/session.py | 84 ++++++++++ lisp/ui/mainwindow.py | 58 +++---- setup.py | 1 + 14 files changed, 239 insertions(+), 187 deletions(-) create mode 100644 lisp/session.py diff --git a/lisp/application.py b/lisp/application.py index e0b891cda..b8e4cf0d2 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -23,82 +23,94 @@ from PyQt5.QtWidgets import QDialog, qApp from lisp import layouts -from lisp import modules from lisp import plugins -from lisp.core import configuration as cfg from lisp.core.actions_handler import MainActionsHandler -from lisp.core.memento_model import AdapterMementoModel +from lisp.core.configuration import config from lisp.core.singleton import Singleton +from lisp.cues.cue import Cue from lisp.cues.cue_factory import CueFactory from lisp.cues.cue_model import CueModel +from lisp.cues.media_cue import MediaCue +from lisp.session import new_session from lisp.ui import elogging from lisp.ui.layoutselect import LayoutSelect from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.app_settings import AppSettings +from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.pages.app_general import AppGeneral from lisp.ui.settings.pages.cue_app_settings import CueAppSettings +from lisp.ui.settings.pages.cue_appearance import Appearance +from lisp.ui.settings.pages.cue_general import CueGeneralSettings +from lisp.ui.settings.pages.media_cue_settings import MediaCueSettings class Application(metaclass=Singleton): def __init__(self): - self._mainWindow = MainWindow() - self._app_conf = {} - self._layout = None - self._memento_model = None - self._cue_model = CueModel() - - # Connect mainWindow actions - self._mainWindow.new_session.connect(self.new_session_dialog) - self._mainWindow.save_session.connect(self._save_to_file) - self._mainWindow.open_session.connect(self._load_from_file) + self.__main_window = MainWindow() + self.__cue_model = CueModel() + self.__session = None # Register general settings widget AppSettings.register_settings_widget(AppGeneral) AppSettings.register_settings_widget(CueAppSettings) + # Register common cue-settings widgets + CueSettingsRegistry().add_item(CueGeneralSettings, Cue) + CueSettingsRegistry().add_item(MediaCueSettings, MediaCue) + CueSettingsRegistry().add_item(Appearance) + # Connect mainWindow actions + self.__main_window.new_session.connect(self._new_session_dialog) + self.__main_window.save_session.connect(self._save_to_file) + self.__main_window.open_session.connect(self._load_from_file) # Show the mainWindow maximized - self._mainWindow.showMaximized() + self.__main_window.showMaximized() + + @property + def session(self): + """:rtype: lisp.session.BaseSession""" + return self.__session @property def layout(self): - """:rtype: lisp.layouts.cue_layout.CueLayout""" - return self._layout + return self.session.layout @property def cue_model(self): """:rtype: lisp.cues.cue_model.CueModel""" - return self._cue_model + return self.__cue_model def start(self, session_file=''): if exists(session_file): self._load_from_file(session_file) - elif cfg.config['Layout']['Default'].lower() != 'nodefault': - layout = layouts.get_layout(cfg.config['Layout']['Default']) - self._new_session(layout) + elif config['Layout']['Default'].lower() != 'nodefault': + self._new_session(layouts.get_layout(config['Layout']['Default'])) else: - self.new_session_dialog() + self._new_session_dialog() + + def finalize(self): + self._delete_session() + + self.__main_window = None + self.__cue_model = None - def new_session_dialog(self): + def _new_session_dialog(self): """Show the layout-selection dialog""" try: # Prompt the user for a new layout dialog = LayoutSelect() - if dialog.exec_() != QDialog.Accepted: - if self._layout is None: + if dialog.exec_() == QDialog.Accepted: + # If a valid file is selected load it, otherwise load the layout + if exists(dialog.filepath): + self._load_from_file(dialog.filepath) + else: + self._new_session(dialog.selected()) + else: + if self.__session is None: # If the user close the dialog, and no layout exists # the application is closed self.finalize() qApp.quit() - exit() - else: - return - - # If a valid file is selected load it, otherwise load the layout - if exists(dialog.filepath): - self._load_from_file(dialog.filepath) - else: - self._new_session(dialog.selected()) - + exit(0) except Exception as e: elogging.exception('Startup error', e) qApp.quit() @@ -107,84 +119,68 @@ def new_session_dialog(self): def _new_session(self, layout): self._delete_session() - self._layout = layout(self._cue_model) - self._memento_model = AdapterMementoModel(self.layout.model_adapter) - self._mainWindow.set_layout(self._layout) - self._app_conf['layout'] = layout.NAME + self.__session = new_session(layout(self.__cue_model)) + self.__main_window.set_session(self.__session) plugins.init_plugins() def _delete_session(self): - if self._layout is not None: + if self.__session is not None: MainActionsHandler.clear() plugins.reset_plugins() - self._app_conf.clear() - self._cue_model.reset() - - self._layout.finalize() - self._layout = None - self._memento_model = None - self._cue_model.reset() - - def finalize(self): - modules.terminate_modules() - - self._delete_session() - self._mainWindow.deleteLater() + self.__session.destroy() + self.__session = None def _save_to_file(self, session_file): """Save the current session into a file.""" - session = {"cues": [], "plugins": {}, "application": []} + self.session.session_file = session_file # Add the cues - for cue in self._cue_model: - session['cues'].append(cue.properties(only_changed=True)) + session_dict = {"cues": []} + + for cue in self.__cue_model: + session_dict['cues'].append(cue.properties(only_changed=True)) # Sort cues by index, allow sorted-models to load properly - session['cues'].sort(key=lambda cue: cue['index']) + session_dict['cues'].sort(key=lambda cue: cue['index']) - session['plugins'] = plugins.get_plugin_settings() - session['application'] = self._app_conf + # Get session settings + session_dict['session'] = self.__session.properties() + # Remove the 'session_file' property (not needed in the session file) + session_dict['session'].pop('session_file', None) # Write to a file the json-encoded dictionary with open(session_file, mode='w', encoding='utf-8') as file: - file.write(json.dumps(session, sort_keys=True, indent=4)) + file.write(json.dumps(session_dict, sort_keys=True, indent=4)) MainActionsHandler.set_saved() - self._mainWindow.update_window_title() + self.__main_window.update_window_title() def _load_from_file(self, session_file): """ Load a saved session from file """ try: with open(session_file, mode='r', encoding='utf-8') as file: - session = json.load(file) + session_dict = json.load(file) # New session self._new_session( - layouts.get_layout(session['application']['layout'])) - # Get the application settings - self._app_conf = session['application'] + layouts.get_layout(session_dict['session']['layout_type'])) + self.__session.update_properties(session_dict['session']) + self.__session.session_file = session_file # Load cues - for cue_conf in session['cues']: - cue_type = cue_conf.pop('_type_', 'Undefined') - cue_id = cue_conf.pop('id') + for cues_dict in session_dict.get('cues', {}): + cue_type = cues_dict.pop('_type_', 'Undefined') + cue_id = cues_dict.pop('id') try: cue = CueFactory.create_cue(cue_type, cue_id=cue_id) - cue.update_properties(cue_conf) - self._cue_model.add(cue) + cue.update_properties(cues_dict) + self.__cue_model.add(cue) except Exception as e: elogging.exception('Unable to create the cue', e) MainActionsHandler.set_saved() - self._mainWindow.update_window_title() - - # Load plugins settings - plugins.set_plugins_settings(session['plugins']) - - # Update the main-window - self._mainWindow.filename = session_file - self._mainWindow.update() + self.__main_window.update_window_title() except Exception as e: elogging.exception('Error during file reading', e) - self.new_session_dialog() + self._new_session_dialog() diff --git a/lisp/core/memento_model.py b/lisp/core/memento_model.py index 73237d841..4a73a424f 100644 --- a/lisp/core/memento_model.py +++ b/lisp/core/memento_model.py @@ -31,8 +31,8 @@ class MementoModel(ReadOnlyProxyModel): If no handler is specified the MainActionHandler is used. ..note:: - The methods, locks and unlock, allow to avoid reentering when an action - is undone/redone. + The methods, `locks` and `unlock`, avoid reentering when an action is + undone/redone. """ def __init__(self, model, handler=None): @@ -62,7 +62,7 @@ def _model_reset(self): """Reset cannot be reverted""" -class AdapterMementoModel(MementoModel): +class MementoModelAdapter(MementoModel): """Extension of the MementoModel that use a ModelAdapter as a base-model""" def __init__(self, model_adapter, handler=None): diff --git a/lisp/layouts/__init__.py b/lisp/layouts/__init__.py index 0c06d1320..69be652c0 100644 --- a/lisp/layouts/__init__.py +++ b/lisp/layouts/__init__.py @@ -2,14 +2,15 @@ from lisp.layouts.list_layout.layout import ListLayout -__LAYOUTS__ = [CartLayout, ListLayout] +__LAYOUTS__ = { + CartLayout.__name__: CartLayout, + ListLayout.__name__: ListLayout +} def get_layouts(): - return __LAYOUTS__ + return sorted(__LAYOUTS__.values()) -def get_layout(name): - for layout in __LAYOUTS__: - if layout.NAME == name: - return layout +def get_layout(class_name): + return __LAYOUTS__[class_name] diff --git a/lisp/layouts/cart_layout/layout.py b/lisp/layouts/cart_layout/layout.py index f8d6d360d..97807ff5e 100644 --- a/lisp/layouts/cart_layout/layout.py +++ b/lisp/layouts/cart_layout/layout.py @@ -33,10 +33,6 @@ from lisp.layouts.cue_layout import CueLayout from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.app_settings import AppSettings -from lisp.ui.settings.cue_settings import CueSettingsRegistry -from lisp.ui.settings.pages.cue_appearance import Appearance -from lisp.ui.settings.pages.cue_general import CueGeneralSettings -from lisp.ui.settings.pages.media_cue_settings import MediaCueSettings from lisp.ui.ui_utils import translate AppSettings.register_settings_widget(CartLayoutSettings) @@ -122,12 +118,6 @@ def __init__(self, cue_model, **kwargs): layoutMenu.addAction(self.show_volume_action) layoutMenu.addAction(self.show_accurate_action) - # TODO: maybe can be moved outside the layout - # Add cue preferences widgets - CueSettingsRegistry().add_item(CueGeneralSettings, Cue) - CueSettingsRegistry().add_item(MediaCueSettings, MediaCue) - CueSettingsRegistry().add_item(Appearance) - # Cue(s) context-menu actions self.edit_action = QAction(self) self.edit_action.triggered.connect(self._edit_cue_action) diff --git a/lisp/layouts/cue_layout.py b/lisp/layouts/cue_layout.py index 8217adf62..5c504a546 100644 --- a/lisp/layouts/cue_layout.py +++ b/lisp/layouts/cue_layout.py @@ -92,10 +92,6 @@ def go(self, action=CueAction.Default, advance=1): :rtype: lisp.cues.cue.Cue """ - @abstractmethod - def finalize(self): - """Destroy all the layout elements""" - def edit_cue(self, cue): edit_ui = CueSettings(cue, parent=MainWindow()) @@ -178,3 +174,6 @@ def show_cue_context_menu(self, position): menu.move(menu.x(), menu.y() - menu.height()) if menu_rect.right() > desktop.right(): menu.move(menu.x() - menu.width(), menu.y()) + + def finalize(self): + """Destroy all the layout elements""" diff --git a/lisp/layouts/list_layout/layout.py b/lisp/layouts/list_layout/layout.py index 146937e51..ff9576c4e 100644 --- a/lisp/layouts/list_layout/layout.py +++ b/lisp/layouts/list_layout/layout.py @@ -38,10 +38,6 @@ from lisp.layouts.list_layout.playing_list_widget import RunningCuesListWidget from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.app_settings import AppSettings -from lisp.ui.settings.cue_settings import CueSettingsRegistry -from lisp.ui.settings.pages.cue_appearance import Appearance -from lisp.ui.settings.pages.cue_general import CueGeneralSettings -from lisp.ui.settings.pages.media_cue_settings import MediaCueSettings from lisp.ui.ui_utils import translate AppSettings.register_settings_widget(ListLayoutSettings) @@ -53,6 +49,7 @@ class EndListBehavior(Enum): class ListLayout(QWidget, CueLayout): + ID=1 NAME = 'List Layout' DESCRIPTION = QT_TRANSLATE_NOOP('LayoutDescription', 'Organize the cues in a list') @@ -171,12 +168,6 @@ def __init__(self, cue_model, **kwargs): self.set_playing_visible(self._show_playing) - # TODO: maybe can be moved outside the layout - # Add cue preferences widgets - CueSettingsRegistry().add_item(CueGeneralSettings, Cue) - CueSettingsRegistry().add_item(MediaCueSettings, MediaCue) - CueSettingsRegistry().add_item(Appearance) - # Context menu actions self.edit_action = QAction(self) self.edit_action.triggered.connect(self.edit_context_cue) diff --git a/lisp/main.py b/lisp/main.py index 8178f4809..2c817ab24 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -120,6 +120,8 @@ def main(): # Start Qt Application (block until exit) exit_code = qt_app.exec_() + # Terminate the modules + modules.terminate_modules() # Finalize the application lisp_app.finalize() # Exit diff --git a/lisp/modules/gst_backend/elements/uri_input.py b/lisp/modules/gst_backend/elements/uri_input.py index c4e8b7aa1..ee71d4245 100644 --- a/lisp/modules/gst_backend/elements/uri_input.py +++ b/lisp/modules/gst_backend/elements/uri_input.py @@ -22,6 +22,7 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP +from lisp.application import Application from lisp.backend.media_element import MediaType from lisp.core.decorators import async_in_pool from lisp.core.has_properties import Property @@ -31,11 +32,19 @@ from lisp.modules.gst_backend.gst_utils import gst_uri_duration +def abs_uri(uri): + protocol, path_ = uri.split('://') + if protocol == 'file': + return 'file://' + Application().session.abs_path(path_) + else: + return uri + + class UriInput(GstSrcElement): MediaType = MediaType.Audio Name = QT_TRANSLATE_NOOP('MediaElementName', 'URI Input') - uri = GstProperty('decoder', default='') + uri = GstProperty('decoder', default='', adapter=abs_uri) download = GstProperty('decoder', default=False) buffer_size = GstProperty('decoder', default=-1, gst_name='buffer-size') use_buffering = GstProperty('decoder', default=False, @@ -53,6 +62,8 @@ def __init__(self, pipe): pipe.add(self.audio_convert) self.changed('uri').connect(self.__uri_changed) + Application().session.changed('session_file').connect( + self.__session_moved) def input_uri(self): return self.uri @@ -66,13 +77,14 @@ def src(self): def __on_pad_added(self, *args): self.decoder.link(self.audio_convert) - def __uri_changed(self, value): + def __uri_changed(self, uri): # Save the current mtime (file flag for last-change time) mtime = self._mtime # If the uri is a file, then update the current mtime - if value.split('://')[0] == 'file': - if path.exists(value.split('//')[1]): - self._mtime = path.getmtime(value.split('//')[1]) + protocol, path_ = abs_uri(uri).split('://') + if protocol == 'file': + if path.exists(path_): + self._mtime = path.getmtime(path_) else: mtime = None @@ -82,4 +94,9 @@ def __uri_changed(self, value): @async_in_pool(pool=ThreadPoolExecutor(1)) def __duration(self): - self.duration = gst_uri_duration(self.uri) + self.duration = gst_uri_duration(abs_uri(self.uri)) + + def __session_moved(self, _): + protocol, old_path = self.decoder.get_property('uri').split('://') + if protocol == 'file': + self.uri = 'file://' + Application().session.rel_path(old_path) diff --git a/lisp/modules/gst_backend/settings/uri_input.py b/lisp/modules/gst_backend/settings/uri_input.py index 149e54e1f..61fbba83a 100644 --- a/lisp/modules/gst_backend/settings/uri_input.py +++ b/lisp/modules/gst_backend/settings/uri_input.py @@ -102,12 +102,9 @@ def enable_check(self, enable): def select_file(self): path = QStandardPaths.writableLocation(QStandardPaths.MusicLocation) - file, ok = QFileDialog.getOpenFileName(self, - translate('UriInputSettings', - 'Choose file'), - path, - translate('UriInputSettings', - 'All files') + ' (*)') + file, ok = QFileDialog.getOpenFileName( + self, translate('UriInputSettings', 'Choose file'), + path, translate('UriInputSettings', 'All files') + ' (*)') if ok: self.filePath.setText('file://' + file) diff --git a/lisp/modules/media_cue_menus.py b/lisp/modules/media_cue_menus.py index 369133203..85b8a122f 100644 --- a/lisp/modules/media_cue_menus.py +++ b/lisp/modules/media_cue_menus.py @@ -44,27 +44,22 @@ def __init__(self): @staticmethod def add_uri_audio_media_cue(): """Add audio MediaCue(s) form user-selected files""" - if get_backend() is None: QMessageBox.critical(MainWindow(), 'Error', 'Backend not loaded') return - # Default path to system "music" folder - path = QStandardPaths.writableLocation(QStandardPaths.MusicLocation) - - # Get the backend extensions and create a filter for the Qt file-dialog - extensions = get_backend().supported_extensions() - filters = qfile_filters(extensions, anyfile=False) - # Display a file-dialog for the user to choose the media-files - files, _ = QFileDialog.getOpenFileNames(MainWindow(), - translate('MediaCueMenus', - 'Select media files'), - path, filters) + files, _ = QFileDialog.getOpenFileNames( + MainWindow(), + translate('MediaCueMenus', 'Select media files'), + Application().session.path(), + qfile_filters(get_backend().supported_extensions(), anyfile=False) + ) QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) # Create media cues, and add them to the Application cue_model for file in files: + file = os.path.relpath(file, start=Application().session.path()) cue = CueFactory.create_cue('URIAudioCue', uri='file://' + file) # Use the filename without extension as cue name cue.name = os.path.splitext(os.path.basename(file))[0] diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index 557226504..4bccd5aa6 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -66,28 +66,3 @@ def reset_plugins(): elogging.debug('PLUGINS: Reset "{0}"'.format(plugin)) except Exception as e: elogging.exception('PLUGINS: Failed "{0}" reset'.format(plugin), e) - - -def set_plugins_settings(settings): - for plugin in __PLUGINS.values(): - if plugin.Name in settings: - try: - plugin.load_settings(settings[plugin.Name]) - except Exception as e: - elogging.exception('PLUGINS: Failed "{0}" settings load' - .format(plugin.Name), e) - - -def get_plugin_settings(): - plugins_settings = {} - - for plugin in __PLUGINS.values(): - try: - settings = plugin.settings() - if settings is not None and len(settings) > 0: - plugins_settings[plugin.Name] = settings - except Exception as e: - elogging.exception('PLUGINS: Failed "{0}" settings retrieve' - .format(plugin.Name), e) - - return plugins_settings diff --git a/lisp/session.py b/lisp/session.py new file mode 100644 index 000000000..d618b416a --- /dev/null +++ b/lisp/session.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2017 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import os + +from lisp.core.signal import Signal +from lisp.core.has_properties import HasProperties, Property +from lisp.core.memento_model import MementoModelAdapter + + +def new_session(layout): + return type('Session', (BaseSession, ), {})(layout) + + +class BaseSession(HasProperties): + layout_type = Property(default='') + session_file = Property(default='') + + def __init__(self, layout): + super().__init__() + self.destroyed = Signal() + + self.layout_type = layout.__class__.__name__ + + self.__layout = layout + self.__cue_model = layout.cue_model + self.__memento_model = MementoModelAdapter(layout.model_adapter) + + @property + def cue_model(self): + """ + :rtype: lisp.cues.cue_model.CueModel + """ + return self.__cue_model + + @property + def layout(self): + """ + :rtype: lisp.layouts.cue_layout.CueLayout + """ + return self.__layout + + def name(self): + if self.session_file: + return os.path.splitext(os.path.basename(self.session_file))[0] + else: + return 'Untitled' + + def path(self): + if self.session_file: + return os.path.dirname(self.session_file) + else: + return os.path.expanduser('~') + + def abs_path(self, rel_path): + if not os.path.isabs(rel_path): + return os.path.normpath(os.path.join(self.path(), rel_path)) + + return rel_path + + def rel_path(self, abs_path): + return os.path.relpath(abs_path, start=self.path()) + + def destroy(self): + self.__layout.finalize() + self.__cue_model.reset() + + self.destroyed.emit() diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 321718d0e..502259181 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -39,6 +39,8 @@ class MainWindow(QMainWindow, metaclass=QSingleton): save_session = pyqtSignal(str) open_session = pyqtSignal(str) + TITLE = 'Linux Show Player' + def __init__(self): super().__init__() self.setMinimumSize(500, 400) @@ -47,7 +49,7 @@ def __init__(self): self.centralWidget().layout().setContentsMargins(5, 5, 5, 5) self._cue_add_menu = {} - self.layout = None + self.session = None # Status Bar self.statusBar = QStatusBar(self) @@ -143,7 +145,7 @@ def __init__(self): self.filename = '' def retranslateUi(self): - self.setWindowTitle('Linux Show Player') + self.setWindowTitle(MainWindow.TITLE) # menuFile self.menuFile.setTitle(translate('MainWindow', '&File')) self.newSessionAction.setText(translate('MainWindow', 'New session')) @@ -186,10 +188,10 @@ def retranslateUi(self): self.actionAbout.setText(translate('MainWindow', 'About')) self.actionAbout_Qt.setText(translate('MainWindow', 'About Qt')) - def set_layout(self, layout): - if self.layout is not None: - self.layout.hide() - self.centralWidget().layout().removeWidget(self.layout) + def set_session(self, session): + if self.session is not None: + layout = self.session.layout + self.centralWidget().layout().removeWidget(layout) self.multiEdit.triggered.disconnect() self.selectAll.triggered.disconnect() @@ -197,16 +199,18 @@ def set_layout(self, layout): self.deselectAll.triggered.disconnect() self.invertSelection.triggered.disconnect() - self.layout = layout - self.centralWidget().layout().addWidget(self.layout) - self.layout.show() + self.session = session + layout = self.session.layout + + self.centralWidget().layout().addWidget(layout) + layout.show() - self.multiEdit.triggered.connect(self.layout.edit_selected_cues) - self.selectAll.triggered.connect(lambda: self.layout.select_all()) + self.multiEdit.triggered.connect(layout.edit_selected_cues) + self.selectAll.triggered.connect(lambda: layout.select_all()) + self.invertSelection.triggered.connect(layout.invert_selection) + self.deselectAll.triggered.connect(lambda: layout.deselect_all()) self.selectAllMedia.triggered.connect( - lambda: self.layout.select_all(MediaCue)) - self.deselectAll.triggered.connect(lambda: self.layout.deselect_all()) - self.invertSelection.triggered.connect(self.layout.invert_selection) + lambda: layout.select_all(MediaCue)) def closeEvent(self, event): self._exit() @@ -238,11 +242,11 @@ def register_cue_menu_action(self, name, function, category='', self.menuEdit.insertAction(self.cueSeparator, action) def update_window_title(self): - saved = MainActionsHandler.is_saved() - if not saved and not self.windowTitle()[0] == '*': - self.setWindowTitle('*' + self.windowTitle()) - elif saved and self.windowTitle()[0] == '*': - self.setWindowTitle(self.windowTitle()[1:]) + tile = MainWindow.TITLE + ' - ' + self.session.name() + if not MainActionsHandler.is_saved(): + tile = '*' + tile + + self.setWindowTitle(tile) def _action_done(self, action): self.statusBar.showMessage(action.log()) @@ -259,20 +263,20 @@ def _action_redone(self, action): self.update_window_title() def _save(self): - if self.filename == '': + if self.session.session_file == '': self._save_with_name() else: - self.save_session.emit(self.filename) + self.save_session.emit(self.session.session_file) def _save_with_name(self): - filename, _ = QFileDialog.getSaveFileName(parent=self, - filter='*.lsp', - directory=os.getenv('HOME')) - if filename != '': + filename, ok = QFileDialog.getSaveFileName( + parent=self, filter='*.lsp', directory=self.session.path()) + + if ok: if not filename.endswith('.lsp'): filename += '.lsp' - self.filename = filename - self._save() + + self.save_session.emit(filename) def _show_preferences(self): prefUi = AppSettings(configuration.config_to_dict(), parent=self) diff --git a/setup.py b/setup.py index 177e779bc..e1555ab00 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ def find_files(directory, regex='.*', rel=''): 'mido', 'python-rtmidi', 'JACK-Client', + 'pyliblo', 'scandir;python_version<"3.5"' ], packages=find_packages(), From 2436b48e4ac208c2d17bbf06e60f8c14f6fa5a1f Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 4 Jul 2017 11:44:28 +0200 Subject: [PATCH 075/333] UI/widgets improvements Add: CueNextActions widgets Update: Moved all "fade" realted widgets under the same module Update: Modified CueAction widgets for addded flexability --- lisp/core/fader.py | 2 +- lisp/core/util.py | 3 + lisp/layouts/__init__.py | 8 +- lisp/modules/action_cues/index_action_cue.py | 4 +- lisp/modules/action_cues/volume_control.py | 3 +- lisp/ui/qdelegates.py | 2 +- lisp/ui/settings/pages/cue_app_settings.py | 2 +- lisp/ui/settings/pages/cue_general.py | 60 +++++++------ lisp/ui/widgets/__init__.py | 5 +- lisp/ui/widgets/cue_actions.py | 86 +++++++++++++++++++ lisp/ui/widgets/cue_next_actions.py | 63 ++++++++++++++ lisp/ui/widgets/cueaction_combobox.py | 69 --------------- lisp/ui/widgets/fade_edit.py | 64 -------------- .../ui/widgets/{fade_combobox.py => fades.py} | 45 +++++++++- 14 files changed, 241 insertions(+), 175 deletions(-) create mode 100644 lisp/ui/widgets/cue_actions.py create mode 100644 lisp/ui/widgets/cue_next_actions.py delete mode 100644 lisp/ui/widgets/cueaction_combobox.py delete mode 100644 lisp/ui/widgets/fade_edit.py rename lisp/ui/widgets/{fade_combobox.py => fades.py} (57%) diff --git a/lisp/core/fader.py b/lisp/core/fader.py index 9244431ce..45b71ce2c 100644 --- a/lisp/core/fader.py +++ b/lisp/core/fader.py @@ -99,7 +99,7 @@ def fade(self, duration, to_value, fade_type): if not isinstance(fade_type, (FadeInType, FadeOutType)): raise AttributeError( - 'fade_type must be one of FadeInType or FadeOutType member,' + 'fade_type must be one of FadeInType or FadeOutType members,' 'not {}'.format(fade_type.__class__.__name__)) try: diff --git a/lisp/core/util.py b/lisp/core/util.py index 87003c35a..3349025b6 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -214,6 +214,7 @@ class E2(EqEnum): E.A is 10 # False E.A == E.A2 # False + E.A == E.A2.value # True """ def __eq__(self, other): @@ -222,6 +223,8 @@ def __eq__(self, other): return super().__eq__(other) + __hash__ = Enum.__hash__ + class FunctionProxy: """Allow to mask a function as an Object. diff --git a/lisp/layouts/__init__.py b/lisp/layouts/__init__.py index 69be652c0..b644bc998 100644 --- a/lisp/layouts/__init__.py +++ b/lisp/layouts/__init__.py @@ -1,15 +1,17 @@ +from collections import OrderedDict + from lisp.layouts.cart_layout.layout import CartLayout from lisp.layouts.list_layout.layout import ListLayout -__LAYOUTS__ = { +__LAYOUTS__ = OrderedDict({ CartLayout.__name__: CartLayout, ListLayout.__name__: ListLayout -} +}) def get_layouts(): - return sorted(__LAYOUTS__.values()) + return list(__LAYOUTS__.values()) def get_layout(class_name): diff --git a/lisp/modules/action_cues/index_action_cue.py b/lisp/modules/action_cues/index_action_cue.py index 531edf1b0..83418d53c 100644 --- a/lisp/modules/action_cues/index_action_cue.py +++ b/lisp/modules/action_cues/index_action_cue.py @@ -85,7 +85,7 @@ def __init__(self, **kwargs): self.actionGroup.setLayout(QVBoxLayout(self.actionGroup)) self.layout().addWidget(self.actionGroup) - self.actionCombo = CueActionComboBox(self._target_class, + self.actionCombo = CueActionComboBox(self._target_class.CueActions, mode=CueActionComboBox.Mode.Value, parent=self.actionGroup) self.actionGroup.layout().addWidget(self.actionCombo) @@ -146,7 +146,7 @@ def _update_action_combo(self): self.actionCombo.deleteLater() self.actionCombo = CueActionComboBox( - self._target_class, + self._target_class.CueActions, mode=CueActionComboBox.Mode.Value, parent=self.actionGroup) self.actionGroup.layout().addWidget(self.actionCombo) diff --git a/lisp/modules/action_cues/volume_control.py b/lisp/modules/action_cues/volume_control.py index 3d29bdf64..da21e4cf8 100644 --- a/lisp/modules/action_cues/volume_control.py +++ b/lisp/modules/action_cues/volume_control.py @@ -35,8 +35,7 @@ from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate -from lisp.ui.widgets import FadeComboBox -from lisp.ui.widgets.fade_edit import FadeEdit +from lisp.ui.widgets import FadeComboBox, FadeEdit class VolumeControl(Cue): diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index 9012460c7..b2b864417 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -252,7 +252,7 @@ def createEditor(self, parent, option, index): if self.cue_class is None: self.cue_class = index.data(CueClassRole) - editor = CueActionComboBox(self.cue_class, + editor = CueActionComboBox(self.cue_class.CueActions, mode=self.mode, parent=parent) editor.setFrame(False) diff --git a/lisp/ui/settings/pages/cue_app_settings.py b/lisp/ui/settings/pages/cue_app_settings.py index c17b78bcc..d0584a979 100644 --- a/lisp/ui/settings/pages/cue_app_settings.py +++ b/lisp/ui/settings/pages/cue_app_settings.py @@ -22,7 +22,7 @@ from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate -from lisp.ui.widgets.fade_edit import FadeEdit +from lisp.ui.widgets import FadeEdit class CueAppSettings(SettingsPage): diff --git a/lisp/ui/settings/pages/cue_general.py b/lisp/ui/settings/pages/cue_general.py index a9fefa73b..11f6fe850 100644 --- a/lisp/ui/settings/pages/cue_general.py +++ b/lisp/ui/settings/pages/cue_general.py @@ -18,14 +18,14 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QLabel, QComboBox,\ - QVBoxLayout, QDoubleSpinBox, QTabWidget, QWidget +from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QLabel, QVBoxLayout, \ + QDoubleSpinBox, QTabWidget, QWidget -from lisp.cues.cue import CueNextAction, CueAction +from lisp.cues.cue import CueAction from lisp.ui.settings.settings_page import CueSettingsPage from lisp.ui.ui_utils import translate -from lisp.ui.widgets import FadeComboBox -from lisp.ui.widgets.fade_edit import FadeEdit +from lisp.ui.widgets import FadeComboBox, CueActionComboBox,\ + CueNextActionComboBox, FadeEdit class CueGeneralSettings(CueSettingsPage): @@ -48,11 +48,14 @@ def __init__(self, cue_class, **kwargs): self.startActionGroup.setLayout(QHBoxLayout()) self.tab_1.layout().addWidget(self.startActionGroup) - self.startActionCombo = QComboBox(self.startActionGroup) - for action in [CueAction.Start, CueAction.FadeInStart]: - if action in cue_class.CueActions: - self.startActionCombo.addItem( - translate('CueAction', action.name), action.value) + self.startActionCombo = CueActionComboBox( + { + CueAction.Start, + CueAction.FadeInStart + }.intersection(cue_class.CueActions), + mode=CueActionComboBox.Mode.Value, + parent=self.startActionGroup + ) self.startActionCombo.setEnabled(self.startActionCombo.count() > 1) self.startActionGroup.layout().addWidget(self.startActionCombo) @@ -65,12 +68,16 @@ def __init__(self, cue_class, **kwargs): self.stopActionGroup.setLayout(QHBoxLayout()) self.tab_1.layout().addWidget(self.stopActionGroup) - self.stopActionCombo = QComboBox(self.stopActionGroup) - for action in [CueAction.Stop, CueAction.Pause, - CueAction.FadeOutStop, CueAction.FadeOutPause]: - if action in cue_class.CueActions: - self.stopActionCombo.addItem( - translate('CueAction', action.name), action.value) + self.stopActionCombo = CueActionComboBox( + { + CueAction.Stop, + CueAction.Pause, + CueAction.FadeOutStop, + CueAction.FadeOutPause + }.intersection(cue_class.CueActions), + mode=CueActionComboBox.Mode.Value, + parent=self.stopActionGroup + ) self.stopActionCombo.setEnabled(self.stopActionCombo.count() > 1) self.stopActionGroup.layout().addWidget(self.stopActionCombo) @@ -118,10 +125,8 @@ def __init__(self, cue_class, **kwargs): self.nextActionGroup.setLayout(QHBoxLayout()) self.tab_2.layout().addWidget(self.nextActionGroup) - self.nextActionCombo = QComboBox(self.nextActionGroup) - for action in CueNextAction: - self.nextActionCombo.addItem( - translate('CueNextAction', action.name), action.value) + self.nextActionCombo = CueNextActionComboBox( + parent=self.nextActionGroup) self.nextActionGroup.layout().addWidget(self.nextActionCombo) # TAB 3 (Fade In/Out) @@ -189,15 +194,14 @@ def retranslateUi(self): self.fadeOutGroup.setTitle(translate('FadeSettings', 'Fade Out')) def load_settings(self, settings): - self.startActionCombo.setCurrentText( - translate('CueAction', settings.get('default_start_action', ''))) - self.stopActionCombo.setCurrentText( - translate('CueAction', settings.get('default_stop_action', ''))) + self.startActionCombo.setCurrentAction( + settings.get('default_start_action', '')) + self.stopActionCombo.setCurrentAction( + settings.get('default_stop_action', '')) self.preWaitSpin.setValue(settings.get('pre_wait', 0)) self.postWaitSpin.setValue(settings.get('post_wait', 0)) - self.nextActionCombo.setCurrentText( - translate('CueNextAction', settings.get('next_action', ''))) + self.nextActionCombo.setCurrentAction(settings.get('next_action', '')) self.fadeInEdit.setFadeType(settings.get('fadein_type', '')) self.fadeInEdit.setDuration(settings.get('fadein_duration', 0)) @@ -228,10 +232,10 @@ def get_settings(self): if not (checkable and not self.startActionGroup.isChecked()): if self.startActionCombo.isEnabled(): - conf['default_start_action'] = self.startActionCombo.currentData() + conf['default_start_action'] = self.startActionCombo.currentAction() if not (checkable and not self.stopActionGroup.isChecked()): if self.stopActionCombo.isEnabled(): - conf['default_stop_action'] = self.stopActionCombo.currentData() + conf['default_stop_action'] = self.stopActionCombo.currentAction() if not (checkable and not self.preWaitGroup.isChecked()): conf['pre_wait'] = self.preWaitSpin.value() diff --git a/lisp/ui/widgets/__init__.py b/lisp/ui/widgets/__init__.py index af3589fce..f96cb81a1 100644 --- a/lisp/ui/widgets/__init__.py +++ b/lisp/ui/widgets/__init__.py @@ -1,5 +1,6 @@ -from .cueaction_combobox import CueActionComboBox -from .fade_combobox import FadeComboBox +from .cue_actions import CueActionComboBox +from .cue_next_actions import CueNextActionComboBox +from .fades import FadeComboBox, FadeEdit from .qclicklabel import QClickLabel from .qclickslider import QClickSlider from .qcolorbutton import QColorButton diff --git a/lisp/ui/widgets/cue_actions.py b/lisp/ui/widgets/cue_actions.py new file mode 100644 index 000000000..448fd9310 --- /dev/null +++ b/lisp/ui/widgets/cue_actions.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from enum import Enum + +from PyQt5.QtCore import QT_TRANSLATE_NOOP +from PyQt5.QtWidgets import QComboBox + +from lisp.cues.cue import CueAction +from lisp.ui.ui_utils import translate + + +CueActionsStrings = { + CueAction.Default: QT_TRANSLATE_NOOP('CueAction', 'Default'), + CueAction.FadeInStart: QT_TRANSLATE_NOOP('CueAction', 'Faded Start'), + CueAction.FadeOutStop: QT_TRANSLATE_NOOP('CueAction', 'Faded Stop'), + CueAction.FadeOutPause: QT_TRANSLATE_NOOP('CueAction', 'Faded Pause'), + CueAction.FadeOutInterrupt: QT_TRANSLATE_NOOP( + 'CueAction', 'Faded Interrupt'), + CueAction.Start: QT_TRANSLATE_NOOP('CueAction', 'Start'), + CueAction.Stop: QT_TRANSLATE_NOOP('CueAction', 'Stop'), + CueAction.Pause: QT_TRANSLATE_NOOP('CueAction', 'Pause'), +} + + +def tr_action(action): + """ + :param action: CueAction to "translate" + :type action: CueAction + + :return: translated UI friendly string to indicate the action + :rtype: str + """ + return translate( + 'CueAction', CueActionsStrings.get(action, action.name)) + + +class CueActionComboBox(QComboBox): + class Mode(Enum): + Action = 0 + Value = 1 + Name = 2 + + def __init__(self, actions, mode=Mode.Action, **kwargs): + super().__init__(**kwargs) + self.mode = mode + + for action in actions: + if mode is CueActionComboBox.Mode.Value: + value = action.value + elif mode is CueActionComboBox.Mode.Name: + value = action.name + else: + value = action + + self.addItem(tr_action(action), value) + + def currentAction(self): + return self.currentData() + + def setCurrentAction(self, action): + try: + if self.mode is CueActionComboBox.Mode.Value: + action = CueAction(action) + elif self.mode is CueActionComboBox.Mode.Name: + action = CueAction[action] + + self.setCurrentText(tr_action(action)) + except(ValueError, KeyError): + pass diff --git a/lisp/ui/widgets/cue_next_actions.py b/lisp/ui/widgets/cue_next_actions.py new file mode 100644 index 000000000..f3c38f8e4 --- /dev/null +++ b/lisp/ui/widgets/cue_next_actions.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import QT_TRANSLATE_NOOP +from PyQt5.QtWidgets import QComboBox + +from lisp.cues.cue import CueNextAction +from lisp.ui.ui_utils import translate + + +CueNextActionsStrings = { + CueNextAction.DoNothing: QT_TRANSLATE_NOOP( + 'CueNextAction', 'Do Nothing'), + CueNextAction.AutoFollow: QT_TRANSLATE_NOOP( + 'CueNextAction', 'Auto Follow'), + CueNextAction.AutoNext: QT_TRANSLATE_NOOP('CueNextAction', 'Auto Next') +} + + +def tr_next_action(action): + """ + :param action: CueNextAction to "translate" + :type action: CueNextAction + + :return: translated UI friendly string to indicate the action + :rtype: str + """ + return translate( + 'CueNextAction', CueNextActionsStrings.get(action, action.name)) + + +class CueNextActionComboBox(QComboBox): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + for action in CueNextAction: + self.addItem(tr_next_action(action), action.value) + + def currentAction(self): + return self.currentData() + + def setCurrentAction(self, value): + try: + value = CueNextAction(value) + self.setCurrentText(tr_next_action(value)) + except ValueError: + pass diff --git a/lisp/ui/widgets/cueaction_combobox.py b/lisp/ui/widgets/cueaction_combobox.py deleted file mode 100644 index a9afa3efe..000000000 --- a/lisp/ui/widgets/cueaction_combobox.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from enum import Enum - -from PyQt5.QtCore import QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QComboBox - -from lisp.cues.cue import Cue, CueAction -from lisp.ui.ui_utils import translate - -QT_TRANSLATE_NOOP('CueAction', 'Default') -QT_TRANSLATE_NOOP('CueAction', 'FadeInStart') -QT_TRANSLATE_NOOP('CueAction', 'FadeOutStop') -QT_TRANSLATE_NOOP('CueAction', 'FadeOutPause') -QT_TRANSLATE_NOOP('CueAction', 'Start') -QT_TRANSLATE_NOOP('CueAction', 'Stop') -QT_TRANSLATE_NOOP('CueAction', 'Pause') - - -class CueActionComboBox(QComboBox): - class Mode(Enum): - Action = 0 - Value = 1 - Name = 2 - - def __init__(self, cue_class, mode=Mode.Action, **kwargs): - super().__init__(**kwargs) - self.mode = mode - - if issubclass(cue_class, Cue): - for action in cue_class.CueActions: - if mode is CueActionComboBox.Mode.Value: - value = action.value - elif mode is CueActionComboBox.Mode.Name: - value = action.name - else: - value = action - - self.addItem(translate('CueAction', action.name), value) - - def currentAction(self): - return self.currentData() - - def setCurrentAction(self, action): - if self.mode is CueActionComboBox.Mode.Value: - name = CueAction(action).name - elif self.mode is CueActionComboBox.Mode.Action: - name = action.name - else: - name = action - - self.setCurrentText(translate('CueAction', name)) diff --git a/lisp/ui/widgets/fade_edit.py b/lisp/ui/widgets/fade_edit.py deleted file mode 100644 index 82a5f544e..000000000 --- a/lisp/ui/widgets/fade_edit.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2017 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QDoubleSpinBox, QGridLayout, QLabel, QWidget - -from lisp.ui.ui_utils import translate -from lisp.ui.widgets import FadeComboBox - - -class FadeEdit(QWidget): - - def __init__(self, *args, mode=FadeComboBox.Mode.FadeOut, **kwargs): - super().__init__(*args, **kwargs) - self.setLayout(QGridLayout()) - - self.fadeDurationSpin = QDoubleSpinBox(self) - self.fadeDurationSpin.setRange(0, 3600) - self.layout().addWidget(self.fadeDurationSpin, 0, 0) - - self.fadeDurationLabel = QLabel(self) - self.fadeDurationLabel.setAlignment(Qt.AlignCenter) - self.layout().addWidget(self.fadeDurationLabel, 0, 1) - - self.fadeTypeCombo = FadeComboBox(self, mode=mode) - self.layout().addWidget(self.fadeTypeCombo, 1, 0) - - self.fadeTypeLabel = QLabel(self) - self.fadeTypeLabel.setAlignment(Qt.AlignCenter) - self.layout().addWidget(self.fadeTypeLabel, 1, 1) - - self.retranslateUi() - - def retranslateUi(self): - self.fadeDurationLabel.setText(translate('FadeEdit', 'Duration (sec)')) - self.fadeTypeLabel.setText(translate('FadeEdit', 'Curve')) - - def duration(self): - return self.fadeDurationSpin.value() - - def setDuration(self, value): - self.fadeDurationSpin.setValue(value) - - def fadeType(self): - return self.fadeTypeCombo.currentType() - - def setFadeType(self, fade_type): - self.fadeTypeCombo.setCurrentType(fade_type) diff --git a/lisp/ui/widgets/fade_combobox.py b/lisp/ui/widgets/fades.py similarity index 57% rename from lisp/ui/widgets/fade_combobox.py rename to lisp/ui/widgets/fades.py index 27d91a280..3fed0c8e9 100644 --- a/lisp/ui/widgets/fade_combobox.py +++ b/lisp/ui/widgets/fades.py @@ -19,9 +19,10 @@ from enum import Enum -from PyQt5.QtCore import QT_TRANSLATE_NOOP +from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt from PyQt5.QtGui import QIcon -from PyQt5.QtWidgets import QComboBox, QStyledItemDelegate +from PyQt5.QtWidgets import QComboBox, QStyledItemDelegate, QWidget, \ + QGridLayout, QDoubleSpinBox, QLabel from lisp.ui.ui_utils import translate @@ -64,3 +65,43 @@ def setCurrentType(self, type): def currentType(self): return self.currentData() + + +class FadeEdit(QWidget): + + def __init__(self, *args, mode=FadeComboBox.Mode.FadeOut, **kwargs): + super().__init__(*args, **kwargs) + self.setLayout(QGridLayout()) + + self.fadeDurationSpin = QDoubleSpinBox(self) + self.fadeDurationSpin.setRange(0, 3600) + self.layout().addWidget(self.fadeDurationSpin, 0, 0) + + self.fadeDurationLabel = QLabel(self) + self.fadeDurationLabel.setAlignment(Qt.AlignCenter) + self.layout().addWidget(self.fadeDurationLabel, 0, 1) + + self.fadeTypeCombo = FadeComboBox(self, mode=mode) + self.layout().addWidget(self.fadeTypeCombo, 1, 0) + + self.fadeTypeLabel = QLabel(self) + self.fadeTypeLabel.setAlignment(Qt.AlignCenter) + self.layout().addWidget(self.fadeTypeLabel, 1, 1) + + self.retranslateUi() + + def retranslateUi(self): + self.fadeDurationLabel.setText(translate('FadeEdit', 'Duration (sec)')) + self.fadeTypeLabel.setText(translate('FadeEdit', 'Curve')) + + def duration(self): + return self.fadeDurationSpin.value() + + def setDuration(self, value): + self.fadeDurationSpin.setValue(value) + + def fadeType(self): + return self.fadeTypeCombo.currentType() + + def setFadeType(self, fade_type): + self.fadeTypeCombo.setCurrentType(fade_type) \ No newline at end of file From 60db4f44c39eaadaac23b758f65733dc1514ccd8 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 1 Oct 2017 17:30:13 +0200 Subject: [PATCH 076/333] API changes Update: Renamed all "restart" to "resume" when needed, for clarity Update: Moved start_all/stop_all/... etc functions to CueLayout, so that all layout can inherit them --- lisp/application.py | 4 +-- lisp/core/fader.py | 3 +- lisp/cues/cue.py | 3 +- lisp/default.cfg | 10 +++--- lisp/layouts/cue_layout.py | 29 +++++++++++++++++ lisp/layouts/list_layout/layout.py | 32 +------------------ .../list_layout/playing_mediawidget.py | 2 +- lisp/modules/action_cues/osc_cue.py | 2 +- lisp/modules/action_cues/volume_control.py | 2 +- lisp/modules/gst_backend/gst_media.py | 1 - lisp/modules/osc/osc_common.py | 2 +- lisp/session.py | 6 ++-- 12 files changed, 46 insertions(+), 50 deletions(-) diff --git a/lisp/application.py b/lisp/application.py index b8e4cf0d2..1d6649d9c 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -129,7 +129,7 @@ def _delete_session(self): MainActionsHandler.clear() plugins.reset_plugins() - self.__session.destroy() + self.__session.finalize() self.__session = None def _save_to_file(self, session_file): diff --git a/lisp/core/fader.py b/lisp/core/fader.py index 45b71ce2c..39e7b40a5 100644 --- a/lisp/core/fader.py +++ b/lisp/core/fader.py @@ -141,8 +141,7 @@ def pause(self): if self.is_running(): self._pause.clear() - def restart(self): - # TODO: change to resume + def resume(self): self._pause.set() def is_paused(self): diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index ab8a401b4..2ca07e4bb 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -267,9 +267,8 @@ def start(self, fade=False): finally: self._st_lock.release() - def restart(self, fade=False): + def resume(self, fade=False): """Restart the cue if paused.""" - # TODO: change to resume if self._state & CueState.IsPaused: self.start(fade) diff --git a/lisp/default.cfg b/lisp/default.cfg index 0f3dcdeac..e62cd4dcf 100644 --- a/lisp/default.cfg +++ b/lisp/default.cfg @@ -2,7 +2,7 @@ [Version] #Don't change this section values -Number = 22 +Number = 23 [Cue] FadeActionDuration = 3 @@ -22,6 +22,10 @@ Pipeline = Volume, Equalizer10, DbMeter, AutoSink [Layout] Default = NoDefault +StopAllFade = False +PauseAllFade = False +ResumeAllFade = False +InterruptAllFade = True [MIDI] Backend = mido.backends.rtmidi @@ -59,10 +63,6 @@ StopCueFade = True PauseCueFade = True ResumeCueFade = True InterruptCueFade = True -StopAllFade = False -PauseAllFade = False -ResumeAllFade = False -InterruptAllFade = True [DbMeter] dBMax = 0 diff --git a/lisp/layouts/cue_layout.py b/lisp/layouts/cue_layout.py index 5c504a546..29f5f3999 100644 --- a/lisp/layouts/cue_layout.py +++ b/lisp/layouts/cue_layout.py @@ -22,6 +22,7 @@ from PyQt5.QtWidgets import QAction, QMenu, qApp from lisp.core.actions_handler import MainActionsHandler +from lisp.core.configuration import config from lisp.core.signal import Signal from lisp.core.util import greatest_common_superclass from lisp.cues.cue import Cue, CueAction @@ -92,6 +93,34 @@ def go(self, action=CueAction.Default, advance=1): :rtype: lisp.cues.cue.Cue """ + def stop_all(self): + fade = config['Layout'].getboolean('StopAllFade') + for cue in self.model_adapter: + cue.stop(fade=fade) + + def interrupt_all(self): + fade = config['Layout'].getboolean('InterruptAllFade') + for cue in self.model_adapter: + cue.interrupt(fade=fade) + + def pause_all(self): + fade = config['Layout'].getboolean('PauseAllFade') + for cue in self.model_adapter: + cue.pause(fade=fade) + + def resume_all(self): + fade = config['Layout'].getboolean('ResumeAllFade') + for cue in self.model_adapter: + cue.resume(fade=fade) + + def fadein_all(self): + for cue in self.model_adapter: + cue.execute(CueAction.FadeIn) + + def fadeout_all(self): + for cue in self.model_adapter: + cue.execute(CueAction.FadeOut) + def edit_cue(self, cue): edit_ui = CueSettings(cue, parent=MainWindow()) diff --git a/lisp/layouts/list_layout/layout.py b/lisp/layouts/list_layout/layout.py index ff9576c4e..a6ec63dc0 100644 --- a/lisp/layouts/list_layout/layout.py +++ b/lisp/layouts/list_layout/layout.py @@ -49,7 +49,6 @@ class EndListBehavior(Enum): class ListLayout(QWidget, CueLayout): - ID=1 NAME = 'List Layout' DESCRIPTION = QT_TRANSLATE_NOOP('LayoutDescription', 'Organize the cues in a list') @@ -144,7 +143,7 @@ def __init__(self, cue_model, **kwargs): self.controlButtons.pauseButton.clicked.connect(self.pause_all) self.controlButtons.fadeInButton.clicked.connect(self.fadein_all) self.controlButtons.fadeOutButton.clicked.connect(self.fadeout_all) - self.controlButtons.resumeButton.clicked.connect(self.restart_all) + self.controlButtons.resumeButton.clicked.connect(self.resume_all) self.controlButtons.interruptButton.clicked.connect(self.interrupt_all) self.layout().addWidget(self.controlButtons, 0, 2) @@ -306,35 +305,6 @@ def remove_context_cue(self): def edit_context_cue(self): self.edit_cue(self.get_context_cue()) - def stop_all(self): - fade = config['ListLayout'].getboolean('StopAllFade') - for cue in self._model_adapter: - cue.stop(fade=fade) - - def interrupt_all(self): - fade = config['ListLayout'].getboolean('InterruptAllFade') - for cue in self._model_adapter: - cue.interrupt(fade=fade) - - def pause_all(self): - fade = config['ListLayout'].getboolean('PauseAllFade') - for cue in self._model_adapter: - cue.pause(fade=fade) - - def restart_all(self): - # TODO: change to resume - fade = config['ListLayout'].getboolean('ResumeAllFade') - for cue in self._model_adapter: - cue.restart(fade=fade) - - def fadein_all(self): - for cue in self._model_adapter: - cue.execute(CueAction.FadeIn) - - def fadeout_all(self): - for cue in self._model_adapter: - cue.execute(CueAction.FadeOut) - def get_selected_cues(self, cue_class=Cue): cues = [] for index in range(self.listView.topLevelItemCount()): diff --git a/lisp/layouts/list_layout/playing_mediawidget.py b/lisp/layouts/list_layout/playing_mediawidget.py index a5fcd6a0d..3d8e58d87 100644 --- a/lisp/layouts/list_layout/playing_mediawidget.py +++ b/lisp/layouts/list_layout/playing_mediawidget.py @@ -150,7 +150,7 @@ def _pause(self): self.cue.pause(fade=config['ListLayout'].getboolean('PauseCueFade')) def _resume(self): - self.cue.restart(fade=config['ListLayout'].getboolean('ResumeCueFade')) + self.cue.resume(fade=config['ListLayout'].getboolean('ResumeCueFade')) def _stop(self): self.cue.stop(fade=config['ListLayout'].getboolean('StopCueFade')) diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/modules/action_cues/osc_cue.py index ec9a04543..8d123086b 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/modules/action_cues/osc_cue.py @@ -145,7 +145,7 @@ def has_fade(self): def __start__(self, fade=False): if self.__init_arguments(): if self.__fader.is_paused(): - self.__fader.restart() + self.__fader.resume() return True if self.has_fade(): diff --git a/lisp/modules/action_cues/volume_control.py b/lisp/modules/action_cues/volume_control.py index da21e4cf8..ed5722522 100644 --- a/lisp/modules/action_cues/volume_control.py +++ b/lisp/modules/action_cues/volume_control.py @@ -70,7 +70,7 @@ def __init_fader(self): def __start__(self, fade=False): if self.__init_fader(): if self.__fader.is_paused(): - self.__fader.restart() + self.__fader.resume() return True if self.duration > 0: diff --git a/lisp/modules/gst_backend/gst_media.py b/lisp/modules/gst_backend/gst_media.py index a3aa7e594..c1ee04c9b 100644 --- a/lisp/modules/gst_backend/gst_media.py +++ b/lisp/modules/gst_backend/gst_media.py @@ -149,7 +149,6 @@ def stop(self): self.stopped.emit(self) def __seek(self, position): - # FIXME: not working when in pause (fix or disallow) if self.state == MediaState.Playing or self.state == MediaState.Paused: max_position = self.duration if 0 < self.stop_time < self.duration: diff --git a/lisp/modules/osc/osc_common.py b/lisp/modules/osc/osc_common.py index 44f32f70b..6ceef8398 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/modules/osc/osc_common.py @@ -60,7 +60,7 @@ def callback_restart(_, args, types): return if (types == 'i' and args[0] == 1) or types == '': - Application().layout.restart_all() + Application().layout.resume_all() def callback_pause(_, args, types): diff --git a/lisp/session.py b/lisp/session.py index d618b416a..b543db55b 100644 --- a/lisp/session.py +++ b/lisp/session.py @@ -34,7 +34,7 @@ class BaseSession(HasProperties): def __init__(self, layout): super().__init__() - self.destroyed = Signal() + self.finalized = Signal() self.layout_type = layout.__class__.__name__ @@ -77,8 +77,8 @@ def abs_path(self, rel_path): def rel_path(self, abs_path): return os.path.relpath(abs_path, start=self.path()) - def destroy(self): + def finalize(self): self.__layout.finalize() self.__cue_model.reset() - self.destroyed.emit() + self.finalized.emit() From a3c5dda648c998c5e929bd8e6ccdc3749d769536 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 1 Oct 2017 18:03:04 +0200 Subject: [PATCH 077/333] Initial experimental implementation for a RESTful remote module --- lisp/modules/remote2/__init__.py | 1 + lisp/modules/remote2/api/__init__.py | 19 ++++++ lisp/modules/remote2/api/api.py | 30 ++++++++++ lisp/modules/remote2/api/cues.py | 86 ++++++++++++++++++++++++++++ lisp/modules/remote2/api/layout.py | 53 +++++++++++++++++ lisp/modules/remote2/remote.py | 53 +++++++++++++++++ setup.py | 4 +- 7 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 lisp/modules/remote2/__init__.py create mode 100644 lisp/modules/remote2/api/__init__.py create mode 100644 lisp/modules/remote2/api/api.py create mode 100644 lisp/modules/remote2/api/cues.py create mode 100644 lisp/modules/remote2/api/layout.py create mode 100644 lisp/modules/remote2/remote.py diff --git a/lisp/modules/remote2/__init__.py b/lisp/modules/remote2/__init__.py new file mode 100644 index 000000000..9cc0c494e --- /dev/null +++ b/lisp/modules/remote2/__init__.py @@ -0,0 +1 @@ +from .remote import Remote as Remote2 \ No newline at end of file diff --git a/lisp/modules/remote2/api/__init__.py b/lisp/modules/remote2/api/__init__.py new file mode 100644 index 000000000..48288fc6a --- /dev/null +++ b/lisp/modules/remote2/api/__init__.py @@ -0,0 +1,19 @@ +import logging + +from .api import API + + +def load_api(app): + from . import cues + from . import layout + + load_module(cues, app) + load_module(layout, app) + + +def load_module(mod, app): + for cls in mod.API_EXPORT: + cls.route_to_app(app) + + logging.debug( + 'REMOTE2: Routed {} at {}'.format(cls.__name__, cls.UriTemplate)) diff --git a/lisp/modules/remote2/api/api.py b/lisp/modules/remote2/api/api.py new file mode 100644 index 000000000..34484a1d6 --- /dev/null +++ b/lisp/modules/remote2/api/api.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2017 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + + +class API: + UriTemplate = '' + + @classmethod + def route_to_app(cls, app): + """ + :param app: The app to add the route to + :type app: falcon.API + """ + app.add_route(cls.UriTemplate, cls()) diff --git a/lisp/modules/remote2/api/cues.py b/lisp/modules/remote2/api/cues.py new file mode 100644 index 000000000..fd4ed03f7 --- /dev/null +++ b/lisp/modules/remote2/api/cues.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2017 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import json + +import falcon + +from lisp.application import Application +from lisp.cues.cue import CueAction +from .api import API + + +def resolve_cue(req, resp, resource, params): + cue_id = params.pop('cue_id', None) + cue = Application().cue_model.get(cue_id) + + if cue is None: + raise falcon.HTTPNotFound() + + params['cue'] = cue + + +class API_CuesList(API): + UriTemplate = '/cues' + + def on_get(self, req, resp): + resp.status = falcon.HTTP_OK + resp.body = json.dumps({ + 'cues': tuple(Application().cue_model.keys()) + }) + + +@falcon.before(resolve_cue) +class API_Cue(API): + UriTemplate = '/cues/{cue_id}' + + def on_get(self, req, resp, cue): + resp.status = falcon.HTTP_OK + resp.body = json.dumps(cue.properties()) + + +@falcon.before(resolve_cue) +class API_CueAction(API): + UriTemplate = '/cues/{cue_id}/action' + + def on_post(self, req, resp, cue): + try: + data = json.load(req.stream) + action = CueAction(data['action']) + + cue.execute(action=action) + resp.status = falcon.HTTP_CREATED + except(KeyError, json.JSONDecodeError): + resp.status = falcon.HTTP_BAD_REQUEST + + +@falcon.before(resolve_cue) +class API_CueState(API): + UriTemplate = '/cues/{cue_id}/state' + + def on_get(self, req, resp, cue): + resp.status = falcon.HTTP_OK + resp.body = json.dumps({ + 'state': cue.state, + 'current_time': cue.current_time(), + 'prewait_time': cue.prewait_time(), + 'postwait_time': cue.postwait_time() + }) + +API_EXPORT = (API_Cue, API_CueAction, API_CuesList, API_CueState) diff --git a/lisp/modules/remote2/api/layout.py b/lisp/modules/remote2/api/layout.py new file mode 100644 index 000000000..519b371c9 --- /dev/null +++ b/lisp/modules/remote2/api/layout.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2017 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import json + +import falcon + +from lisp.application import Application +from .api import API + + +class API_LayoutAction(API): + UriTemplate = '/layout/action' + + def on_post(self, req, resp): + try: + data = json.load(req.stream) + action = data['action'] + + resp.status = falcon.HTTP_CREATED + + if action == 'GO': + Application().layout.go() + elif action == 'STOP_ALL': + Application().layout.stop_all() + elif action == 'PAUSE_ALL': + Application().layout.pause_all() + elif action == 'RESUME_ALL': + Application().layout.resume_all() + else: + resp.status = falcon.HTTP_BAD_REQUEST + + except(KeyError, json.JSONDecodeError): + resp.status = falcon.HTTP_BAD_REQUEST + + +API_EXPORT = (API_LayoutAction, ) \ No newline at end of file diff --git a/lisp/modules/remote2/remote.py b/lisp/modules/remote2/remote.py new file mode 100644 index 000000000..43f38d455 --- /dev/null +++ b/lisp/modules/remote2/remote.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2017 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import logging +from wsgiref.simple_server import make_server + +import falcon + +from lisp.core.decorators import async +from lisp.core.module import Module +from .api import load_api + + +class Remote(Module): + def __init__(self): + self.server = None + self.app = falcon.API() + # for development environment + self.app.resp_options.secure_cookies_by_default = False + + load_api(self.app) + + self.start() + + @async + def start(self): + with make_server('', 9090, self.app) as self.server: + logging.info('REMOTE2: Server started at {}'.format( + self.server.server_address)) + + self.server.serve_forever() + + logging.info('REMOTE2: Server stopped') + + def terminate(self): + if self.server is not None: + self.server.server_close() diff --git a/setup.py b/setup.py index e1555ab00..819963ac0 100644 --- a/setup.py +++ b/setup.py @@ -30,12 +30,12 @@ def find_files(directory, regex='.*', rel=''): url=lisp.__email__, description='Cue player for live shows', install_requires=[ + 'scandir;python_version<"3.5"', 'sortedcontainers', - 'mido', 'python-rtmidi', 'JACK-Client', 'pyliblo', - 'scandir;python_version<"3.5"' + 'mido' ], packages=find_packages(), package_data={ From d7575d3dac09deea8ecb738ab1ed1a36e0cbd869 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 1 Oct 2017 19:55:47 +0200 Subject: [PATCH 078/333] Changes for IndexActionCue Fix: fixed bugged settings panel Add: an auto-generated suggestion for the cue name, based on selected action and target --- lisp/modules/action_cues/index_action_cue.py | 85 ++++++++++++++------ lisp/ui/widgets/cue_actions.py | 8 +- 2 files changed, 66 insertions(+), 27 deletions(-) diff --git a/lisp/modules/action_cues/index_action_cue.py b/lisp/modules/action_cues/index_action_cue.py index 83418d53c..638332bd8 100644 --- a/lisp/modules/action_cues/index_action_cue.py +++ b/lisp/modules/action_cues/index_action_cue.py @@ -18,8 +18,8 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QCheckBox, QComboBox, QGroupBox, QLabel, QSpinBox,\ - QGridLayout, QVBoxLayout +from PyQt5.QtWidgets import QCheckBox, QGroupBox, QLabel, QSpinBox, \ + QGridLayout, QVBoxLayout, QLineEdit from lisp.application import Application from lisp.core.has_properties import Property @@ -58,6 +58,8 @@ def __start__(self, fade=False): class IndexActionCueSettings(SettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Action Settings') + DEFAULT_SUGGESTION = translate('IndexActionCue', 'No suggestion') + def __init__(self, **kwargs): super().__init__(**kwargs) self.setLayout(QVBoxLayout()) @@ -75,21 +77,36 @@ def __init__(self, **kwargs): self.indexGroup.layout().addWidget(self.relativeCheck, 0, 0, 1, 2) self.targetIndexSpin = QSpinBox(self) - self.targetIndexSpin.editingFinished.connect(self._update_action_combo) + self.targetIndexSpin.valueChanged.connect(self._target_changed) + self.targetIndexSpin.setRange(0, len(Application().cue_model) - 1) self.indexGroup.layout().addWidget(self.targetIndexSpin, 1, 0) self.targetIndexLabel = QLabel(self) + self.targetIndexLabel.setAlignment(Qt.AlignCenter) self.indexGroup.layout().addWidget(self.targetIndexLabel, 1, 1) self.actionGroup = QGroupBox(self) self.actionGroup.setLayout(QVBoxLayout(self.actionGroup)) self.layout().addWidget(self.actionGroup) - self.actionCombo = CueActionComboBox(self._target_class.CueActions, - mode=CueActionComboBox.Mode.Value, - parent=self.actionGroup) + self.actionCombo = CueActionComboBox( + self._target_class.CueActions, + mode=CueActionComboBox.Mode.Value, + parent=self.actionGroup) self.actionGroup.layout().addWidget(self.actionCombo) + self.suggestionGroup = QGroupBox(self) + self.suggestionGroup.setLayout(QVBoxLayout()) + self.layout().addWidget(self.suggestionGroup) + + self.suggestionPreview = QLineEdit(self.suggestionGroup) + self.suggestionPreview.setAlignment(Qt.AlignCenter) + self.suggestionPreview.setText( + IndexActionCueSettings.DEFAULT_SUGGESTION) + self.suggestionGroup.layout().addWidget(self.suggestionPreview) + + self.actionCombo.currentTextChanged.connect(self._update_suggestion) + self.retranslateUi() def retranslateUi(self): @@ -100,6 +117,9 @@ def retranslateUi(self): translate('IndexActionCue', 'Target index')) self.actionGroup.setTitle(translate('IndexActionCue', 'Action')) + self.suggestionGroup.setTitle( + translate('IndexActionCue', 'Suggested cue name')) + def enable_check(self, enabled): self.indexGroup.setChecked(enabled) self.indexGroup.setChecked(False) @@ -124,32 +144,32 @@ def load_settings(self, settings): self.relativeCheck.setChecked(settings.get('relative', True)) self.targetIndexSpin.setValue(settings.get('target_index', 0)) - self.actionCombo.setCurrentText( - translate('CueAction', settings.get('action', ''))) + self._target_changed() # Ensure that the correct options are displayed + self.actionCombo.setCurrentAction(settings.get('action', '')) - def _update_action_combo(self): - if self.relativeCheck.isChecked(): - index = self._cue_index + self.targetIndexSpin.value() - else: - index = self.targetIndexSpin.value() + def _target_changed(self): + target = self._current_target() - try: - target = Application().layout.model_adapter.item(index) + if target is not None: target_class = target.__class__ - except IndexError: + else: target_class = Cue if target_class is not self._target_class: self._target_class = target_class + self.actionCombo.rebuild(self._target_class.CueActions) - self.actionGroup.layout().removeWidget(self.actionCombo) - self.actionCombo.deleteLater() + self._update_suggestion() - self.actionCombo = CueActionComboBox( - self._target_class.CueActions, - mode=CueActionComboBox.Mode.Value, - parent=self.actionGroup) - self.actionGroup.layout().addWidget(self.actionCombo) + def _update_suggestion(self): + target = self._current_target() + + if target is not None: + suggestion = self.actionCombo.currentText() + ' ➜ ' + target.name + else: + suggestion = IndexActionCueSettings.DEFAULT_SUGGESTION + + self.suggestionPreview.setText(suggestion) def _relative_changed(self): max_index = len(Application().cue_model) - 1 @@ -158,10 +178,25 @@ def _relative_changed(self): self.targetIndexSpin.setRange(0, max_index) else: if self._cue_index >= 0: - self.targetIndexSpin.setRange(-self._cue_index, - max_index - self._cue_index) + self.targetIndexSpin.setRange( + -self._cue_index, + max_index - self._cue_index + ) else: self.targetIndexSpin.setRange(-max_index, max_index) + self._target_changed() + + def _current_target(self): + index = self.targetIndexSpin.value() + + if self.relativeCheck.isChecked(): + index += self._cue_index + + try: + return Application().layout.model_adapter.item(index) + except IndexError: + return None + CueSettingsRegistry().add_item(IndexActionCueSettings, IndexActionCue) diff --git a/lisp/ui/widgets/cue_actions.py b/lisp/ui/widgets/cue_actions.py index 448fd9310..98b6fa471 100644 --- a/lisp/ui/widgets/cue_actions.py +++ b/lisp/ui/widgets/cue_actions.py @@ -60,11 +60,15 @@ class Mode(Enum): def __init__(self, actions, mode=Mode.Action, **kwargs): super().__init__(**kwargs) self.mode = mode + self.rebuild(actions) + + def rebuild(self, actions): + self.clear() for action in actions: - if mode is CueActionComboBox.Mode.Value: + if self.mode is CueActionComboBox.Mode.Value: value = action.value - elif mode is CueActionComboBox.Mode.Name: + elif self.mode is CueActionComboBox.Mode.Name: value = action.name else: value = action From 4bcb2cbb6237a4f5f91483bead85e0f3098b1035 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 12 Oct 2017 17:40:28 +0200 Subject: [PATCH 079/333] Minor changes --- lisp/application.py | 5 ++-- lisp/core/loading.py | 30 ++++++++++---------- lisp/core/plugin.py | 4 +-- lisp/{ => core}/session.py | 0 lisp/default.cfg | 2 +- lisp/modules/action_cues/index_action_cue.py | 1 + lisp/modules/midi/__init__.py | 2 +- 7 files changed, 22 insertions(+), 22 deletions(-) rename lisp/{ => core}/session.py (100%) diff --git a/lisp/application.py b/lisp/application.py index 1d6649d9c..124373943 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -22,16 +22,15 @@ from PyQt5.QtWidgets import QDialog, qApp -from lisp import layouts -from lisp import plugins +from lisp import layouts, plugins from lisp.core.actions_handler import MainActionsHandler from lisp.core.configuration import config +from lisp.core.session import new_session from lisp.core.singleton import Singleton from lisp.cues.cue import Cue from lisp.cues.cue_factory import CueFactory from lisp.cues.cue_model import CueModel from lisp.cues.media_cue import MediaCue -from lisp.session import new_session from lisp.ui import elogging from lisp.ui.layoutselect import LayoutSelect from lisp.ui.mainwindow import MainWindow diff --git a/lisp/core/loading.py b/lisp/core/loading.py index e25047124..bb8b5153b 100644 --- a/lisp/core/loading.py +++ b/lisp/core/loading.py @@ -93,7 +93,7 @@ def load(self): # Load class from imported module for prefix, suffix in zip(self.prefixes, self.suffixes): - name = self._class_name(mod_name, prefix, suffix) + name = module_to_class_name(mod_name, prefix, suffix) if hasattr(module, name): cls = getattr(module, name) yield (name, cls) @@ -102,23 +102,23 @@ def load(self): logging.warning('Cannot load module: {0}'.format(mod_name)) logging.debug(traceback.format_exc()) - @staticmethod - def _class_name(mod_name, pre='', suf=''): - """Return the supposed class name from loaded module. - Substitutions: - * Remove `underscores` - * First letters to uppercase version +def module_to_class_name(mod_name, pre='', suf=''): + """Return the supposed class name from loaded module. - For example: - * For "module", the result will be "Module" - * For "module_name", the result will be "ModuleName" - """ + Substitutions: + * Remove `underscores` + * First letters to uppercase version + + For example: + * For "module", the result will be "Module" + * For "module_name", the result will be "ModuleName" + """ - # Capitalize the first letter of each word - base_name = ''.join(word.title() for word in mod_name.split('_')) - # Add prefix and suffix to the base name - return pre + base_name + suf + # Capitalize the first letter of each word + base_name = ''.join(word.title() for word in mod_name.split('_')) + # Add prefix and suffix to the base name + return pre + base_name + suf def import_module(module_path): diff --git a/lisp/core/plugin.py b/lisp/core/plugin.py index c19206eb8..f013fd53c 100644 --- a/lisp/core/plugin.py +++ b/lisp/core/plugin.py @@ -21,8 +21,8 @@ class Plugin: """Interface for plugins. - The "init" and "reset" functions are called respectively after the session - layout is created (init) and before (reset). + The "reset" and "init" functions are called respectively before the session + layout is created (reset) and after (init). """ Name = 'Plugin' diff --git a/lisp/session.py b/lisp/core/session.py similarity index 100% rename from lisp/session.py rename to lisp/core/session.py diff --git a/lisp/default.cfg b/lisp/default.cfg index e62cd4dcf..3716cb34d 100644 --- a/lisp/default.cfg +++ b/lisp/default.cfg @@ -1,7 +1,7 @@ #LiSP (Linux Show Player) configuration [Version] -#Don't change this section values +# Settings file version, users should not change this value Number = 23 [Cue] diff --git a/lisp/modules/action_cues/index_action_cue.py b/lisp/modules/action_cues/index_action_cue.py index 638332bd8..747df7750 100644 --- a/lisp/modules/action_cues/index_action_cue.py +++ b/lisp/modules/action_cues/index_action_cue.py @@ -165,6 +165,7 @@ def _update_suggestion(self): target = self._current_target() if target is not None: + # This require unicode support by the used font, but hey, it's 2017 suggestion = self.actionCombo.currentText() + ' ➜ ' + target.name else: suggestion = IndexActionCueSettings.DEFAULT_SUGGESTION diff --git a/lisp/modules/midi/__init__.py b/lisp/modules/midi/__init__.py index 541f500b2..4b38fbddb 100644 --- a/lisp/modules/midi/__init__.py +++ b/lisp/modules/midi/__init__.py @@ -1 +1 @@ -from .midi import Midi \ No newline at end of file +from .midi import Midi \ No newline at end of file From 537f2702a321265e126ea9d34ac72d2b9cc699db Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 6 Jan 2018 19:25:07 +0100 Subject: [PATCH 080/333] Heavy refactoring of plugins and configurations Merged modules and plugins; Plugins can now declare dependencies; Plugins now have their own configuration; Plugins now have multiple metadata; Rewritten configuration API, is now reusable; Configurations are now stored in JSON format; Reduced the number of singletons, cleaning up the architecture; Many changes related to the above ones. --- i18n_update.py | 8 +- lisp/__init__.py | 31 ++- lisp/application.py | 35 +++- lisp/backend/backend.py | 6 +- lisp/core/actions_handler.py | 12 +- lisp/core/configuration.py | 149 ++++++++++----- lisp/core/decorators.py | 1 + lisp/core/loading.py | 22 ++- lisp/core/module.py | 27 --- lisp/core/plugin.py | 46 ++--- lisp/core/signal.py | 14 +- lisp/core/singleton.py | 44 +++-- lisp/core/util.py | 1 + lisp/cues/cue.py | 18 +- lisp/cues/media_cue.py | 7 +- lisp/default.json | 54 ++++++ .../cart_layout/cart_layout_settings.py | 41 ++-- lisp/layouts/cart_layout/layout.py | 20 +- lisp/layouts/cue_layout.py | 10 +- lisp/layouts/list_layout/layout.py | 21 +- .../list_layout/list_layout_settings.py | 89 +++++---- .../list_layout/playing_mediawidget.py | 10 +- lisp/main.py | 55 ++---- lisp/modules/__init__.py | 56 ------ lisp/modules/gst_backend/__init__.py | 4 - lisp/modules/midi/midi.py | 40 ---- lisp/modules/uri_changer/__init__.py | 1 - .../uri_changer/i18n/uri_changer_cs.qm | Bin 456 -> 0 bytes .../uri_changer/i18n/uri_changer_cs.ts | 35 ---- .../uri_changer/i18n/uri_changer_en.qm | Bin 432 -> 0 bytes .../uri_changer/i18n/uri_changer_en.ts | 35 ---- .../uri_changer/i18n/uri_changer_es.qm | Bin 468 -> 0 bytes .../uri_changer/i18n/uri_changer_es.ts | 35 ---- .../uri_changer/i18n/uri_changer_fr.qm | Bin 480 -> 0 bytes .../uri_changer/i18n/uri_changer_fr.ts | 35 ---- .../uri_changer/i18n/uri_changer_it.qm | Bin 468 -> 0 bytes .../uri_changer/i18n/uri_changer_it.ts | 35 ---- .../uri_changer/i18n/uri_changer_sl_SI.qm | Bin 447 -> 0 bytes .../uri_changer/i18n/uri_changer_sl_SI.ts | 35 ---- lisp/modules/uri_changer/json_utils.py | 56 ------ lisp/modules/uri_changer/session.py | 63 ------ lisp/modules/uri_changer/uri_changer.py | 43 ----- .../modules/uri_changer/uri_changer_dialog.py | 158 ---------------- lisp/plugins/__init__.py | 145 ++++++++++---- .../action_cues/__init__.py | 27 +-- .../action_cues/collection_cue.py | 2 +- .../action_cues/command_cue.py | 2 +- .../action_cues/i18n/action_cues_cs.qm | Bin .../action_cues/i18n/action_cues_cs.ts | 0 .../action_cues/i18n/action_cues_en.qm | Bin .../action_cues/i18n/action_cues_en.ts | 0 .../action_cues/i18n/action_cues_es.qm | Bin .../action_cues/i18n/action_cues_es.ts | 0 .../action_cues/i18n/action_cues_fr.qm | Bin .../action_cues/i18n/action_cues_fr.ts | 0 .../action_cues/i18n/action_cues_it.qm | Bin .../action_cues/i18n/action_cues_it.ts | 0 .../action_cues/i18n/action_cues_sl_SI.qm | Bin .../action_cues/i18n/action_cues_sl_SI.ts | 0 .../action_cues/index_action_cue.py | 0 .../action_cues/midi_cue.py | 11 +- .../action_cues/osc_cue.py | 17 +- .../action_cues/seek_cue.py | 2 +- .../action_cues/stop_all.py | 3 +- .../action_cues/volume_control.py | 4 +- lisp/plugins/controller/controller.py | 23 ++- lisp/plugins/controller/protocols/keyboard.py | 2 +- lisp/plugins/controller/protocols/midi.py | 12 +- lisp/plugins/controller/protocols/osc.py | 103 ++++++---- lisp/plugins/controller/protocols/protocol.py | 4 +- lisp/plugins/default.cfg | 7 + lisp/plugins/default.json | 4 + lisp/plugins/gst_backend/__init__.py | 4 + lisp/plugins/gst_backend/default.json | 5 + .../gst_backend/elements/__init__.py | 1 - .../gst_backend/elements/alsa_sink.py | 4 +- .../gst_backend/elements/audio_dynamic.py | 4 +- .../gst_backend/elements/audio_pan.py | 4 +- .../gst_backend/elements/auto_sink.py | 4 +- .../gst_backend/elements/auto_src.py | 4 +- .../gst_backend/elements/db_meter.py | 4 +- .../gst_backend/elements/equalizer10.py | 4 +- .../gst_backend/elements/jack_sink.py | 7 +- .../gst_backend/elements/pitch.py | 4 +- .../gst_backend/elements/preset_src.py | 4 +- .../gst_backend/elements/pulse_sink.py | 4 +- .../gst_backend/elements/speed.py | 4 +- .../gst_backend/elements/uri_input.py | 6 +- .../gst_backend/elements/user_element.py | 4 +- .../gst_backend/elements/volume.py | 4 +- .../gst_backend/gi_repository.py | 0 .../gst_backend/gst_backend.py | 44 +++-- .../gst_backend/gst_cue_factories.py | 43 +++-- .../gst_backend/gst_element.py | 0 .../gst_backend/gst_media.py | 4 +- .../gst_backend/gst_media_settings.py | 4 +- .../gst_backend/gst_pipe_edit.py | 2 +- .../gst_backend/gst_settings.py | 7 +- .../gst_backend/gst_utils.py | 2 +- .../gst_backend/i18n/gst_backend_cs.qm | Bin .../gst_backend/i18n/gst_backend_cs.ts | 0 .../gst_backend/i18n/gst_backend_en.qm | Bin .../gst_backend/i18n/gst_backend_en.ts | 0 .../gst_backend/i18n/gst_backend_es.qm | Bin .../gst_backend/i18n/gst_backend_es.ts | 0 .../gst_backend/i18n/gst_backend_fr.qm | Bin .../gst_backend/i18n/gst_backend_fr.ts | 0 .../gst_backend/i18n/gst_backend_it.qm | Bin .../gst_backend/i18n/gst_backend_it.ts | 0 .../gst_backend/i18n/gst_backend_sl_SI.qm | Bin .../gst_backend/i18n/gst_backend_sl_SI.ts | 0 .../gst_backend/settings/__init__.py | 0 .../gst_backend/settings/alsa_sink.py | 2 +- .../gst_backend/settings/audio_dynamic.py | 2 +- .../gst_backend/settings/audio_pan.py | 2 +- .../gst_backend/settings/db_meter.py | 4 +- .../gst_backend/settings/equalizer10.py | 2 +- .../gst_backend/settings/jack_sink.py | 4 +- .../gst_backend/settings/pitch.py | 2 +- .../gst_backend/settings/preset_src.py | 2 +- .../gst_backend/settings/speed.py | 2 +- .../gst_backend/settings/uri_input.py | 2 +- .../gst_backend/settings/user_element.py | 2 +- .../gst_backend/settings/volume.py | 4 +- lisp/{modules => plugins}/media_cue_menus.py | 31 +-- .../media_info/__init__.py | 0 .../media_info/i18n/media_info_cs.qm | Bin .../media_info/i18n/media_info_cs.ts | 0 .../media_info/i18n/media_info_en.qm | Bin .../media_info/i18n/media_info_en.ts | 0 .../media_info/i18n/media_info_es.qm | Bin .../media_info/i18n/media_info_es.ts | 0 .../media_info/i18n/media_info_fr.qm | Bin .../media_info/i18n/media_info_fr.ts | 0 .../media_info/i18n/media_info_it.qm | Bin .../media_info/i18n/media_info_it.ts | 0 .../media_info/i18n/media_info_sl_SI.qm | Bin .../media_info/i18n/media_info_sl_SI.ts | 0 .../media_info/media_info.py | 26 +-- lisp/{modules => plugins}/midi/__init__.py | 0 lisp/plugins/midi/default.json | 7 + .../{modules => plugins}/midi/i18n/midi_cs.qm | Bin .../{modules => plugins}/midi/i18n/midi_cs.ts | 0 .../{modules => plugins}/midi/i18n/midi_en.qm | Bin .../{modules => plugins}/midi/i18n/midi_en.ts | 0 .../{modules => plugins}/midi/i18n/midi_es.qm | Bin .../{modules => plugins}/midi/i18n/midi_es.ts | 0 .../{modules => plugins}/midi/i18n/midi_fr.qm | Bin .../{modules => plugins}/midi/i18n/midi_fr.ts | 0 .../{modules => plugins}/midi/i18n/midi_it.qm | Bin .../{modules => plugins}/midi/i18n/midi_it.ts | 0 .../midi/i18n/midi_sl_SI.qm | Bin .../midi/i18n/midi_sl_SI.ts | 0 lisp/plugins/midi/midi.py | 83 ++++++++ lisp/{modules => plugins}/midi/midi_common.py | 14 +- lisp/{modules => plugins}/midi/midi_input.py | 11 +- lisp/{modules => plugins}/midi/midi_output.py | 9 +- .../midi/midi_settings.py | 52 ++--- lisp/{modules => plugins}/midi/midi_utils.py | 24 +-- lisp/{modules => plugins}/osc/__init__.py | 0 lisp/plugins/osc/default.json | 7 + lisp/{modules => plugins}/osc/osc.py | 32 +++- .../osc/osc_server.py} | 58 ++---- lisp/{modules => plugins}/osc/osc_settings.py | 63 +----- lisp/{modules => plugins}/presets/__init__.py | 0 .../presets/i18n/presets_cs.qm | Bin .../presets/i18n/presets_cs.ts | 0 .../presets/i18n/presets_en.qm | Bin .../presets/i18n/presets_en.ts | 0 .../presets/i18n/presets_es.qm | Bin .../presets/i18n/presets_es.ts | 0 .../presets/i18n/presets_fr.qm | Bin .../presets/i18n/presets_fr.ts | 0 .../presets/i18n/presets_it.qm | Bin .../presets/i18n/presets_it.ts | 0 .../presets/i18n/presets_sl_SI.qm | Bin .../presets/i18n/presets_sl_SI.ts | 0 lisp/{modules => plugins}/presets/lib.py | 4 +- lisp/{modules => plugins}/presets/presets.py | 43 ++--- .../presets/presets_ui.py | 26 ++- lisp/{modules => plugins}/remote/__init__.py | 0 .../{modules => plugins}/remote/controller.py | 29 ++- lisp/plugins/remote/default.json | 10 + lisp/{modules => plugins}/remote/discovery.py | 30 +-- .../{modules => plugins}/remote/dispatcher.py | 10 +- lisp/{modules => plugins}/remote/remote.py | 34 ++-- .../rename_cues/__init__.py | 0 .../rename_cues/rename_action.py | 7 +- .../rename_cues/rename_cues.py | 38 ++-- .../rename_cues/rename_ui.py | 66 ++----- .../replay_gain/__init__.py | 0 .../replay_gain/gain_ui.py | 0 .../replay_gain/i18n/replay_gain_cs.qm | Bin .../replay_gain/i18n/replay_gain_cs.ts | 0 .../replay_gain/i18n/replay_gain_en.qm | Bin .../replay_gain/i18n/replay_gain_en.ts | 0 .../replay_gain/i18n/replay_gain_es.qm | Bin .../replay_gain/i18n/replay_gain_es.ts | 0 .../replay_gain/i18n/replay_gain_fr.qm | Bin .../replay_gain/i18n/replay_gain_fr.ts | 0 .../replay_gain/i18n/replay_gain_it.qm | Bin .../replay_gain/i18n/replay_gain_it.ts | 0 .../replay_gain/i18n/replay_gain_sl_SI.qm | Bin .../replay_gain/i18n/replay_gain_sl_SI.ts | 0 .../replay_gain/replay_gain.py | 179 +++++++++--------- lisp/plugins/synchronizer/peers_dialog.py | 6 +- .../synchronizer/peers_discovery_dialog.py | 2 +- lisp/plugins/synchronizer/synchronizer.py | 36 ++-- .../{timecode_common.py => cue_tracker.py} | 112 ++++++----- lisp/plugins/timecode/default.json | 6 + .../{timecode_protocol.py => protocol.py} | 14 +- lisp/plugins/timecode/protocols/__init__.py | 9 +- lisp/plugins/timecode/protocols/artnet.py | 17 +- lisp/plugins/timecode/protocols/midi.py | 41 ++-- .../{timecode_settings.py => settings.py} | 46 ++--- lisp/plugins/timecode/timecode.py | 96 ++++++---- lisp/plugins/triggers/triggers.py | 20 +- lisp/plugins/triggers/triggers_handler.py | 6 +- lisp/ui/mainwindow.py | 24 +-- lisp/ui/qdelegates.py | 2 +- lisp/ui/settings/app_settings.py | 34 ++-- lisp/ui/settings/pages/app_general.py | 21 +- lisp/ui/settings/pages/cue_app_settings.py | 20 +- lisp/ui/settings/pages/cue_appearance.py | 3 +- lisp/ui/settings/pages/plugins_settings.py | 80 ++++++++ lisp/ui/ui_utils.py | 18 ++ lisp/ui/widgets/qdbmeter.py | 57 +++--- plugins_utils.py | 84 ++++++++ 228 files changed, 1724 insertions(+), 1854 deletions(-) delete mode 100644 lisp/core/module.py create mode 100644 lisp/default.json delete mode 100644 lisp/modules/__init__.py delete mode 100644 lisp/modules/gst_backend/__init__.py delete mode 100644 lisp/modules/midi/midi.py delete mode 100644 lisp/modules/uri_changer/__init__.py delete mode 100644 lisp/modules/uri_changer/i18n/uri_changer_cs.qm delete mode 100644 lisp/modules/uri_changer/i18n/uri_changer_cs.ts delete mode 100644 lisp/modules/uri_changer/i18n/uri_changer_en.qm delete mode 100644 lisp/modules/uri_changer/i18n/uri_changer_en.ts delete mode 100644 lisp/modules/uri_changer/i18n/uri_changer_es.qm delete mode 100644 lisp/modules/uri_changer/i18n/uri_changer_es.ts delete mode 100644 lisp/modules/uri_changer/i18n/uri_changer_fr.qm delete mode 100644 lisp/modules/uri_changer/i18n/uri_changer_fr.ts delete mode 100644 lisp/modules/uri_changer/i18n/uri_changer_it.qm delete mode 100644 lisp/modules/uri_changer/i18n/uri_changer_it.ts delete mode 100644 lisp/modules/uri_changer/i18n/uri_changer_sl_SI.qm delete mode 100644 lisp/modules/uri_changer/i18n/uri_changer_sl_SI.ts delete mode 100644 lisp/modules/uri_changer/json_utils.py delete mode 100644 lisp/modules/uri_changer/session.py delete mode 100644 lisp/modules/uri_changer/uri_changer.py delete mode 100644 lisp/modules/uri_changer/uri_changer_dialog.py rename lisp/{modules => plugins}/action_cues/__init__.py (76%) rename lisp/{modules => plugins}/action_cues/collection_cue.py (99%) rename lisp/{modules => plugins}/action_cues/command_cue.py (99%) rename lisp/{modules => plugins}/action_cues/i18n/action_cues_cs.qm (100%) rename lisp/{modules => plugins}/action_cues/i18n/action_cues_cs.ts (100%) rename lisp/{modules => plugins}/action_cues/i18n/action_cues_en.qm (100%) rename lisp/{modules => plugins}/action_cues/i18n/action_cues_en.ts (100%) rename lisp/{modules => plugins}/action_cues/i18n/action_cues_es.qm (100%) rename lisp/{modules => plugins}/action_cues/i18n/action_cues_es.ts (100%) rename lisp/{modules => plugins}/action_cues/i18n/action_cues_fr.qm (100%) rename lisp/{modules => plugins}/action_cues/i18n/action_cues_fr.ts (100%) rename lisp/{modules => plugins}/action_cues/i18n/action_cues_it.qm (100%) rename lisp/{modules => plugins}/action_cues/i18n/action_cues_it.ts (100%) rename lisp/{modules => plugins}/action_cues/i18n/action_cues_sl_SI.qm (100%) rename lisp/{modules => plugins}/action_cues/i18n/action_cues_sl_SI.ts (100%) rename lisp/{modules => plugins}/action_cues/index_action_cue.py (100%) rename lisp/{modules => plugins}/action_cues/midi_cue.py (95%) rename lisp/{modules => plugins}/action_cues/osc_cue.py (97%) rename lisp/{modules => plugins}/action_cues/seek_cue.py (99%) rename lisp/{modules => plugins}/action_cues/stop_all.py (97%) rename lisp/{modules => plugins}/action_cues/volume_control.py (98%) create mode 100644 lisp/plugins/default.cfg create mode 100644 lisp/plugins/default.json create mode 100644 lisp/plugins/gst_backend/__init__.py create mode 100644 lisp/plugins/gst_backend/default.json rename lisp/{modules => plugins}/gst_backend/elements/__init__.py (99%) rename lisp/{modules => plugins}/gst_backend/elements/alsa_sink.py (91%) rename lisp/{modules => plugins}/gst_backend/elements/audio_dynamic.py (94%) rename lisp/{modules => plugins}/gst_backend/elements/audio_pan.py (92%) rename lisp/{modules => plugins}/gst_backend/elements/auto_sink.py (91%) rename lisp/{modules => plugins}/gst_backend/elements/auto_src.py (91%) rename lisp/{modules => plugins}/gst_backend/elements/db_meter.py (95%) rename lisp/{modules => plugins}/gst_backend/elements/equalizer10.py (94%) rename lisp/{modules => plugins}/gst_backend/elements/jack_sink.py (97%) rename lisp/{modules => plugins}/gst_backend/elements/pitch.py (92%) rename lisp/{modules => plugins}/gst_backend/elements/preset_src.py (96%) rename lisp/{modules => plugins}/gst_backend/elements/pulse_sink.py (91%) rename lisp/{modules => plugins}/gst_backend/elements/speed.py (96%) rename lisp/{modules => plugins}/gst_backend/elements/uri_input.py (94%) rename lisp/{modules => plugins}/gst_backend/elements/user_element.py (96%) rename lisp/{modules => plugins}/gst_backend/elements/volume.py (94%) rename lisp/{modules => plugins}/gst_backend/gi_repository.py (100%) rename lisp/{modules => plugins}/gst_backend/gst_backend.py (60%) rename lisp/{modules => plugins}/gst_backend/gst_cue_factories.py (51%) rename lisp/{modules => plugins}/gst_backend/gst_element.py (100%) rename lisp/{modules => plugins}/gst_backend/gst_media.py (99%) rename lisp/{modules => plugins}/gst_backend/gst_media_settings.py (97%) rename lisp/{modules => plugins}/gst_backend/gst_pipe_edit.py (99%) rename lisp/{modules => plugins}/gst_backend/gst_settings.py (86%) rename lisp/{modules => plugins}/gst_backend/gst_utils.py (97%) rename lisp/{modules => plugins}/gst_backend/i18n/gst_backend_cs.qm (100%) rename lisp/{modules => plugins}/gst_backend/i18n/gst_backend_cs.ts (100%) rename lisp/{modules => plugins}/gst_backend/i18n/gst_backend_en.qm (100%) rename lisp/{modules => plugins}/gst_backend/i18n/gst_backend_en.ts (100%) rename lisp/{modules => plugins}/gst_backend/i18n/gst_backend_es.qm (100%) rename lisp/{modules => plugins}/gst_backend/i18n/gst_backend_es.ts (100%) rename lisp/{modules => plugins}/gst_backend/i18n/gst_backend_fr.qm (100%) rename lisp/{modules => plugins}/gst_backend/i18n/gst_backend_fr.ts (100%) rename lisp/{modules => plugins}/gst_backend/i18n/gst_backend_it.qm (100%) rename lisp/{modules => plugins}/gst_backend/i18n/gst_backend_it.ts (100%) rename lisp/{modules => plugins}/gst_backend/i18n/gst_backend_sl_SI.qm (100%) rename lisp/{modules => plugins}/gst_backend/i18n/gst_backend_sl_SI.ts (100%) rename lisp/{modules => plugins}/gst_backend/settings/__init__.py (100%) rename lisp/{modules => plugins}/gst_backend/settings/alsa_sink.py (98%) rename lisp/{modules => plugins}/gst_backend/settings/audio_dynamic.py (98%) rename lisp/{modules => plugins}/gst_backend/settings/audio_pan.py (97%) rename lisp/{modules => plugins}/gst_backend/settings/db_meter.py (97%) rename lisp/{modules => plugins}/gst_backend/settings/equalizer10.py (98%) rename lisp/{modules => plugins}/gst_backend/settings/jack_sink.py (98%) rename lisp/{modules => plugins}/gst_backend/settings/pitch.py (98%) rename lisp/{modules => plugins}/gst_backend/settings/preset_src.py (97%) rename lisp/{modules => plugins}/gst_backend/settings/speed.py (97%) rename lisp/{modules => plugins}/gst_backend/settings/uri_input.py (98%) rename lisp/{modules => plugins}/gst_backend/settings/user_element.py (97%) rename lisp/{modules => plugins}/gst_backend/settings/volume.py (98%) rename lisp/{modules => plugins}/media_cue_menus.py (78%) rename lisp/{modules => plugins}/media_info/__init__.py (100%) rename lisp/{modules => plugins}/media_info/i18n/media_info_cs.qm (100%) rename lisp/{modules => plugins}/media_info/i18n/media_info_cs.ts (100%) rename lisp/{modules => plugins}/media_info/i18n/media_info_en.qm (100%) rename lisp/{modules => plugins}/media_info/i18n/media_info_en.ts (100%) rename lisp/{modules => plugins}/media_info/i18n/media_info_es.qm (100%) rename lisp/{modules => plugins}/media_info/i18n/media_info_es.ts (100%) rename lisp/{modules => plugins}/media_info/i18n/media_info_fr.qm (100%) rename lisp/{modules => plugins}/media_info/i18n/media_info_fr.ts (100%) rename lisp/{modules => plugins}/media_info/i18n/media_info_it.qm (100%) rename lisp/{modules => plugins}/media_info/i18n/media_info_it.ts (100%) rename lisp/{modules => plugins}/media_info/i18n/media_info_sl_SI.qm (100%) rename lisp/{modules => plugins}/media_info/i18n/media_info_sl_SI.ts (100%) rename lisp/{modules => plugins}/media_info/media_info.py (90%) rename lisp/{modules => plugins}/midi/__init__.py (100%) create mode 100644 lisp/plugins/midi/default.json rename lisp/{modules => plugins}/midi/i18n/midi_cs.qm (100%) rename lisp/{modules => plugins}/midi/i18n/midi_cs.ts (100%) rename lisp/{modules => plugins}/midi/i18n/midi_en.qm (100%) rename lisp/{modules => plugins}/midi/i18n/midi_en.ts (100%) rename lisp/{modules => plugins}/midi/i18n/midi_es.qm (100%) rename lisp/{modules => plugins}/midi/i18n/midi_es.ts (100%) rename lisp/{modules => plugins}/midi/i18n/midi_fr.qm (100%) rename lisp/{modules => plugins}/midi/i18n/midi_fr.ts (100%) rename lisp/{modules => plugins}/midi/i18n/midi_it.qm (100%) rename lisp/{modules => plugins}/midi/i18n/midi_it.ts (100%) rename lisp/{modules => plugins}/midi/i18n/midi_sl_SI.qm (100%) rename lisp/{modules => plugins}/midi/i18n/midi_sl_SI.ts (100%) create mode 100644 lisp/plugins/midi/midi.py rename lisp/{modules => plugins}/midi/midi_common.py (78%) rename lisp/{modules => plugins}/midi/midi_input.py (76%) rename lisp/{modules => plugins}/midi/midi_output.py (78%) rename lisp/{modules => plugins}/midi/midi_settings.py (68%) rename lisp/{modules => plugins}/midi/midi_utils.py (63%) rename lisp/{modules => plugins}/osc/__init__.py (100%) create mode 100644 lisp/plugins/osc/default.json rename lisp/{modules => plugins}/osc/osc.py (57%) rename lisp/{modules/osc/osc_common.py => plugins/osc/osc_server.py} (78%) rename lisp/{modules => plugins}/osc/osc_settings.py (54%) rename lisp/{modules => plugins}/presets/__init__.py (100%) rename lisp/{modules => plugins}/presets/i18n/presets_cs.qm (100%) rename lisp/{modules => plugins}/presets/i18n/presets_cs.ts (100%) rename lisp/{modules => plugins}/presets/i18n/presets_en.qm (100%) rename lisp/{modules => plugins}/presets/i18n/presets_en.ts (100%) rename lisp/{modules => plugins}/presets/i18n/presets_es.qm (100%) rename lisp/{modules => plugins}/presets/i18n/presets_es.ts (100%) rename lisp/{modules => plugins}/presets/i18n/presets_fr.qm (100%) rename lisp/{modules => plugins}/presets/i18n/presets_fr.ts (100%) rename lisp/{modules => plugins}/presets/i18n/presets_it.qm (100%) rename lisp/{modules => plugins}/presets/i18n/presets_it.ts (100%) rename lisp/{modules => plugins}/presets/i18n/presets_sl_SI.qm (100%) rename lisp/{modules => plugins}/presets/i18n/presets_sl_SI.ts (100%) rename lisp/{modules => plugins}/presets/lib.py (98%) rename lisp/{modules => plugins}/presets/presets.py (71%) rename lisp/{modules => plugins}/presets/presets_ui.py (96%) rename lisp/{modules => plugins}/remote/__init__.py (100%) rename lisp/{modules => plugins}/remote/controller.py (71%) create mode 100644 lisp/plugins/remote/default.json rename lisp/{modules => plugins}/remote/discovery.py (83%) rename lisp/{modules => plugins}/remote/dispatcher.py (84%) rename lisp/{modules => plugins}/remote/remote.py (56%) rename lisp/{modules => plugins}/rename_cues/__init__.py (100%) rename lisp/{modules => plugins}/rename_cues/rename_action.py (91%) rename lisp/{modules => plugins}/rename_cues/rename_cues.py (60%) rename lisp/{modules => plugins}/rename_cues/rename_ui.py (86%) rename lisp/{modules => plugins}/replay_gain/__init__.py (100%) rename lisp/{modules => plugins}/replay_gain/gain_ui.py (100%) rename lisp/{modules => plugins}/replay_gain/i18n/replay_gain_cs.qm (100%) rename lisp/{modules => plugins}/replay_gain/i18n/replay_gain_cs.ts (100%) rename lisp/{modules => plugins}/replay_gain/i18n/replay_gain_en.qm (100%) rename lisp/{modules => plugins}/replay_gain/i18n/replay_gain_en.ts (100%) rename lisp/{modules => plugins}/replay_gain/i18n/replay_gain_es.qm (100%) rename lisp/{modules => plugins}/replay_gain/i18n/replay_gain_es.ts (100%) rename lisp/{modules => plugins}/replay_gain/i18n/replay_gain_fr.qm (100%) rename lisp/{modules => plugins}/replay_gain/i18n/replay_gain_fr.ts (100%) rename lisp/{modules => plugins}/replay_gain/i18n/replay_gain_it.qm (100%) rename lisp/{modules => plugins}/replay_gain/i18n/replay_gain_it.ts (100%) rename lisp/{modules => plugins}/replay_gain/i18n/replay_gain_sl_SI.qm (100%) rename lisp/{modules => plugins}/replay_gain/i18n/replay_gain_sl_SI.ts (100%) rename lisp/{modules => plugins}/replay_gain/replay_gain.py (90%) rename lisp/plugins/timecode/{timecode_common.py => cue_tracker.py} (50%) create mode 100644 lisp/plugins/timecode/default.json rename lisp/plugins/timecode/{timecode_protocol.py => protocol.py} (79%) rename lisp/plugins/timecode/{timecode_settings.py => settings.py} (76%) create mode 100644 lisp/ui/settings/pages/plugins_settings.py create mode 100755 plugins_utils.py diff --git a/i18n_update.py b/i18n_update.py index 695058415..d582cab48 100755 --- a/i18n_update.py +++ b/i18n_update.py @@ -25,7 +25,7 @@ import sys try: - from os import scandir + from os import scandir, getcwd except ImportError: from scandir import scandir @@ -90,7 +90,6 @@ def create_pro_file(root, exclude=(), extensions=('py',)): def generate_for_submodules(path, qm=False): - # Here "modules" is used generically modules = [entry.path for entry in scandir(path) if entry.is_dir()] for module in modules: if os.path.exists(os.path.join(module, 'i18n')): @@ -107,7 +106,7 @@ def generate_for_submodules(path, qm=False): print('>>> UPDATE TRANSLATIONS FOR APPLICATION') -create_pro_file('lisp', exclude=('lisp/modules/', 'lisp/plugins/')) +create_pro_file('lisp', exclude=('lisp/plugins/', )) if args.qm: subprocess.run(['lrelease', 'lisp/lisp.pro'], stdout=sys.stdout, @@ -117,8 +116,5 @@ def generate_for_submodules(path, qm=False): stdout=sys.stdout, stderr=sys.stderr) -print('>>> UPDATE TRANSLATIONS FOR MODULES') -generate_for_submodules('lisp/modules', qm=args.qm) - print('>>> UPDATE TRANSLATIONS FOR PLUGINS') generate_for_submodules('lisp/plugins', qm=args.qm) diff --git a/lisp/__init__.py b/lisp/__init__.py index ce867a9de..d9f00d8ae 100644 --- a/lisp/__init__.py +++ b/lisp/__init__.py @@ -1,12 +1,33 @@ # -*- coding: utf-8 -*- -""" -Linux Show Player +# +# This file is part of Linux Show Player +# +# Copyright 2012-2017 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . -Cue player for live shows -""" +from os import path __author__ = 'Francesco Ceruti' __email__ = 'ceppofrancy@gmail.com' __url__ = 'https://github.com/FrancescoCeruti/linux-show-player' __license__ = 'GPLv3' -__version__ = '0.5' +__version__ = '0.6dev' + +# Application wide "constants" + +USER_DIR = path.join(path.expanduser("~"), '.linux_show_player') + +DEFAULT_APP_CONFIG = path.join(path.dirname(__file__), 'default.json') +USER_APP_CONFIG = path.join(USER_DIR, 'lisp.json') diff --git a/lisp/application.py b/lisp/application.py index 124373943..42837737f 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -22,9 +22,11 @@ from PyQt5.QtWidgets import QDialog, qApp -from lisp import layouts, plugins +from lisp.core.configuration import AppConfig +from lisp.core.signal import Signal + +from lisp import layouts from lisp.core.actions_handler import MainActionsHandler -from lisp.core.configuration import config from lisp.core.session import new_session from lisp.core.singleton import Singleton from lisp.cues.cue import Cue @@ -41,17 +43,23 @@ from lisp.ui.settings.pages.cue_appearance import Appearance from lisp.ui.settings.pages.cue_general import CueGeneralSettings from lisp.ui.settings.pages.media_cue_settings import MediaCueSettings +from lisp.ui.settings.pages.plugins_settings import PluginsSettings class Application(metaclass=Singleton): def __init__(self): + self.session_created = Signal() + self.session_before_finalize = Signal() + self.__main_window = MainWindow() self.__cue_model = CueModel() self.__session = None # Register general settings widget - AppSettings.register_settings_widget(AppGeneral) - AppSettings.register_settings_widget(CueAppSettings) + AppSettings.register_settings_widget(AppGeneral, AppConfig()) + AppSettings.register_settings_widget(CueAppSettings, AppConfig()) + AppSettings.register_settings_widget(PluginsSettings, AppConfig()) + # Register common cue-settings widgets CueSettingsRegistry().add_item(CueGeneralSettings, Cue) CueSettingsRegistry().add_item(MediaCueSettings, MediaCue) @@ -69,6 +77,11 @@ def session(self): """:rtype: lisp.session.BaseSession""" return self.__session + @property + def window(self): + """:rtype: lisp.ui.mainwindow.MainWindow""" + return self.__main_window + @property def layout(self): return self.session.layout @@ -81,10 +94,13 @@ def cue_model(self): def start(self, session_file=''): if exists(session_file): self._load_from_file(session_file) - elif config['Layout']['Default'].lower() != 'nodefault': - self._new_session(layouts.get_layout(config['Layout']['Default'])) else: - self._new_session_dialog() + layout = AppConfig().get('Layout', 'Default', default='nodefault') + + if layout.lower() != 'nodefault': + self._new_session(layouts.get_layout(layout)) + else: + self._new_session_dialog() def finalize(self): self._delete_session() @@ -121,12 +137,13 @@ def _new_session(self, layout): self.__session = new_session(layout(self.__cue_model)) self.__main_window.set_session(self.__session) - plugins.init_plugins() + self.session_created.emit(self.__session) def _delete_session(self): if self.__session is not None: MainActionsHandler.clear() - plugins.reset_plugins() + + self.session_before_finalize.emit(self.session) self.__session.finalize() self.__session = None diff --git a/lisp/backend/backend.py b/lisp/backend/backend.py index 36b71372b..170ab2836 100644 --- a/lisp/backend/backend.py +++ b/lisp/backend/backend.py @@ -17,12 +17,10 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from abc import abstractmethod +from abc import abstractmethod, ABCMeta -from lisp.core.singleton import ABCSingleton - -class Backend(metaclass=ABCSingleton): +class Backend(metaclass=ABCMeta): """Common interface that any backend must provide. An __init__() method can be defined if the backend require initialization. diff --git a/lisp/core/actions_handler.py b/lisp/core/actions_handler.py index f03fc73f0..d470a1490 100644 --- a/lisp/core/actions_handler.py +++ b/lisp/core/actions_handler.py @@ -20,7 +20,6 @@ import logging from collections import deque -from lisp.core import configuration as cfg from lisp.core.action import Action from lisp.core.signal import Signal @@ -28,9 +27,7 @@ class ActionsHandler: """Provide a classic undo/redo mechanism based on stacks.""" - MaxStackSize = int(cfg.config['Actions']['MaxStackSize']) - - def __init__(self): + def __init__(self, stack_size=-1): super().__init__() self.action_done = Signal() @@ -39,9 +36,10 @@ def __init__(self): self._undo = deque() self._redo = deque() - if self.MaxStackSize > 0: - self._undo.maxlen = self.MaxStackSize - self._redo.maxlen = self.MaxStackSize + + if stack_size > 0: + self._undo.maxlen = stack_size + self._redo.maxlen = stack_size self._saved_action = None diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index 8d6e147cc..f62175f74 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -17,70 +17,129 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import os -from configparser import ConfigParser +from collections import Mapping +from os import path from shutil import copyfile -DEFAULT_CFG_PATH = os.path.join(os.path.dirname(__file__), '../default.cfg') -CFG_DIR = os.path.expanduser("~") + '/.linux_show_player' -CFG_PATH = CFG_DIR + '/config.cfg' +import json -config = ConfigParser() +from lisp.core.util import deep_update +from lisp import USER_APP_CONFIG, DEFAULT_APP_CONFIG +from lisp.core.singleton import Singleton -def load_config(): - # Check if the current user configuration is up-to-date - check_user_config() - # Read the user configuration - config.read(CFG_PATH) +# Used to indicate the default behaviour when a specific option is not found to +# raise an exception. Created to enable `None' as a valid fallback value. +_UNSET = object() -def write_config(): - with open(CFG_PATH, 'w') as f: - config.write(f) +class Configuration: + """Allow to read/write json based configuration files. + Two path must be provided on creation, user-path and default-path, + the first one is used to read/write, the second is copied over when the + first is not available or the version value is different. -def check_user_config(): - update = True + While the default-path can (and should) be read-only, the user-path must + be writeable by the application. - if not os.path.exists(CFG_DIR): - os.makedirs(CFG_DIR) - elif os.path.exists(CFG_PATH): - default = ConfigParser() - default.read(DEFAULT_CFG_PATH) + The version value should be located at "_version_", the value can be + anything. If the default and user value are different or missing the default + configuration is copied over. + """ - current = ConfigParser() - current.read(CFG_PATH) + def __init__(self, user_path, default_path, read=True): + self.__config = {} - current_version = current['Version']['Number'] - update = current_version != default['Version']['Number'] + self.user_path = user_path + self.default_path = default_path - if update: - copyfile(CFG_PATH, CFG_PATH + '.old') - print('Configuration file backup: {}.old'.format(CFG_PATH)) + if read: + self.read() - if update: - copyfile(DEFAULT_CFG_PATH, CFG_PATH) - print('Create new configuration file: {}'.format(CFG_PATH)) + def read(self): + self.check() + self.__config = self._read_json(self.user_path) + def write(self): + with open(self.user_path, 'w') as f: + json.dump(self.__config, f, indent=True) -def config_to_dict(): - conf_dict = {} + def check(self): + if path.exists(self.user_path): + # Read default configuration + default = self._read_json(self.default_path) + default = default.get('_version_', object()) - for section in config.keys(): - conf_dict[section] = {} - for option in config[section].keys(): - conf_dict[section][option] = config[section][option] + # Read user configuration + user = self._read_json(self.user_path) + user = user.get('_version_', object()) - return conf_dict + # if the user and default version aren't the same + if user != default: + # Copy the new configuration + copyfile(self.default_path, self.user_path) + else: + copyfile(self.default_path, self.user_path) + @staticmethod + def _read_json(path): + with open(path, 'r') as f: + return json.load(f) -def update_config_from_dict(conf): - for section in conf.keys(): - for option in conf[section].keys(): - config[section][option] = conf[section][option] + def get(self, *path, default=_UNSET): + value = self.__config + for key in path: + if isinstance(value, Mapping): + try: + value = value[key] + except KeyError: + if default is _UNSET: + raise + return default + else: + break - write_config() + return value -# Load configuration -load_config() \ No newline at end of file + def copy(self): + return self.__config.copy() + + def update(self, update_dict): + deep_update(self.__config, update_dict) + + def __getitem__(self, item): + return self.__config.__getitem__(item) + + def __setitem__(self, key, value): + return self.__config.__setitem__(key, value) + + def __contains__(self, key): + return self.__config.__contains__(key) + + +class DummyConfiguration(Configuration): + """Configuration without read/write capabilities. + + Can be used for uninitialized component. + """ + + def __init__(self): + super().__init__('', '', False) + + def read(self): + pass + + def write(self): + pass + + def check(self): + pass + + +# TODO: move into Application? +class AppConfig(Configuration, metaclass=Singleton): + """Provide access to the application configuration (singleton)""" + + def __init__(self): + super().__init__(USER_APP_CONFIG, DEFAULT_APP_CONFIG) diff --git a/lisp/core/decorators.py b/lisp/core/decorators.py index 5faac58fa..c6a44123b 100644 --- a/lisp/core/decorators.py +++ b/lisp/core/decorators.py @@ -23,6 +23,7 @@ from threading import Thread, Lock, RLock +# TODO: rename to ensure compatibility with Python 3.7 def async(target): """Decorator. Make a function asynchronous. diff --git a/lisp/core/loading.py b/lisp/core/loading.py index bb8b5153b..b2725eb3c 100644 --- a/lisp/core/loading.py +++ b/lisp/core/loading.py @@ -34,13 +34,25 @@ class load_classes: The class name must be the same as the module name, optionally suffixes and prefixes lists can be provided. + Using a package __init__ module is possible to create sub-packages + .. highlight:: - modules + package + | ├── module1.py + | | | └──Module1 - └── module_extra.py - └── ModuleExtra + | + ├──── module_extra.py + | | + | └── ModuleExtra + | + └──── sub_package + | + └── __init__.py + | + └──SubPackage """ @@ -81,7 +93,7 @@ def load(self): if not re.match('.py[cod]?', ext): continue - # Exclude excluded ¯\_(ツ)_/¯ + # Exclude excluded ¯\_(°^°)_/¯ if mod_name in self.excluded: continue @@ -98,7 +110,7 @@ def load(self): cls = getattr(module, name) yield (name, cls) - except ImportError: + except(ImportError, Exception): logging.warning('Cannot load module: {0}'.format(mod_name)) logging.debug(traceback.format_exc()) diff --git a/lisp/core/module.py b/lisp/core/module.py deleted file mode 100644 index 094624024..000000000 --- a/lisp/core/module.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from lisp.core.singleton import ABCSingleton - - -class Module(metaclass=ABCSingleton): - """Interface for modules.""" - - def terminate(self): - """Finalize the module (e.g. free resources).""" diff --git a/lisp/core/plugin.py b/lisp/core/plugin.py index f013fd53c..422974f06 100644 --- a/lisp/core/plugin.py +++ b/lisp/core/plugin.py @@ -17,32 +17,28 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +from lisp.core.configuration import DummyConfiguration -class Plugin: - """Interface for plugins. - The "reset" and "init" functions are called respectively before the session - layout is created (reset) and after (init). - """ +# TODO: add possible additional metadata (Icon, Version, ...) +# TODO: implement some kind of plugin status +class Plugin: + """Base class for plugins.""" Name = 'Plugin' - - def init(self): - """Initialize the session-related components of the plugin.""" - - def reset(self): - """Reset the session-related components of plugin.""" - - def settings(self): - """Return the plugin settings. - - :rtype: dict - """ - return {} - - def load_settings(self, settings): - """Load the plugin settings. - - :param settings: the settings to be loaded - :type settings: dict - """ + Depends = () + OptDepends = () + Authors = ('None',) + Description = 'No Description' + Config = DummyConfiguration() + + def __init__(self, app): + self.__app = app + + @property + def app(self): + """:rtype: lisp.application.Application""" + return self.__app + + def finalize(self): + """Called when the application is getting closed.""" diff --git a/lisp/core/signal.py b/lisp/core/signal.py index 61e31d622..44e99ba3c 100644 --- a/lisp/core/signal.py +++ b/lisp/core/signal.py @@ -178,11 +178,15 @@ def connect(self, slot_callable, mode=Connection.Direct): raise ValueError('invalid mode value: {0}'.format(mode)) with self.__lock: - # Create a new Slot object, use a weakref for the callback - # to avoid cyclic references. - callback = weak_call_proxy(weakref.WeakMethod(self.__remove_slot)) - self.__slots[slot_id(slot_callable)] = mode.new_slot(slot_callable, - callback) + sid = slot_id(slot_callable) + # If already connected do nothing + if sid not in self.__slots: + # Create a new Slot object, use a weakref for the callback + # to avoid cyclic references. + self.__slots[sid] = mode.new_slot( + slot_callable, + weak_call_proxy(weakref.WeakMethod(self.__remove_slot)) + ) def disconnect(self, slot=None): """Disconnect the given slot, or all if no slot is specified. diff --git a/lisp/core/singleton.py b/lisp/core/singleton.py index b26a578a6..1a09cc2b8 100644 --- a/lisp/core/singleton.py +++ b/lisp/core/singleton.py @@ -25,40 +25,60 @@ class Singleton(type): - def __call__(cls, *args, **kwargs): try: return cls.__instance except AttributeError: - cls.__instance = super(Singleton, cls).__call__(*args, **kwargs) - return cls.__instance + pass + cls.__instance = super(Singleton, cls).__call__(*args, **kwargs) + return cls.__instance + + @property + def instance(cls): + return cls.__instance -class ABCSingleton(ABCMeta): +class ABCSingleton(ABCMeta): def __call__(cls, *args, **kwargs): try: return cls.__instance except AttributeError: - cls.__instance = super(ABCSingleton, cls).__call__(*args, **kwargs) - return cls.__instance + pass + cls.__instance = super(ABCSingleton, cls).__call__(*args, **kwargs) + return cls.__instance + + @property + def instance(cls): + return cls.__instance -class QSingleton(type(QObject)): +class QSingleton(type(QObject)): def __call__(cls, *args, **kwargs): try: return cls.__instance except AttributeError: - cls.__instance = super(QSingleton, cls).__call__(*args, **kwargs) - return cls.__instance + pass + cls.__instance = super(QSingleton, cls).__call__(*args, **kwargs) + return cls.__instance + + @property + def instance(cls): + return cls.__instance -class QABCSingleton(QABCMeta): +class QABCSingleton(QABCMeta): def __call__(cls, *args, **kwargs): try: return cls.__instance except AttributeError: - cls.__instance = super(QABCSingleton, cls).__call__(*args, **kwargs) - return cls.__instance + pass + + cls.__instance = super(QABCSingleton, cls).__call__(*args, **kwargs) + return cls.__instance + + @property + def instance(cls): + return cls.__instance diff --git a/lisp/core/util.py b/lisp/core/util.py index 3349025b6..9d34a4d58 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -106,6 +106,7 @@ def get_lan_ip(): ip = '127.0.0.1' finally: s.close() + return ip diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 2ca07e4bb..42a9c6d27 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -20,7 +20,7 @@ from threading import Lock from uuid import uuid4 -from lisp.core.configuration import config +from lisp.core.configuration import AppConfig from lisp.core.decorators import async from lisp.core.fade_functions import FadeInType, FadeOutType from lisp.core.has_properties import HasProperties, Property, WriteOnceProperty @@ -198,13 +198,17 @@ def execute(self, action=CueAction.Default): elif action == CueAction.FadeOutPause: self.pause(fade=self.fadeout_duration > 0) elif action == CueAction.FadeOut: - duration = config['Cue'].getfloat('FadeActionDuration') - fade_type = FadeOutType[config['Cue'].get('FadeActionType')] - self.fadeout(duration, fade_type) + duration = AppConfig().getfloat('Cue', 'FadeActionDuration', 0) + fade = AppConfig().get( + 'Cue', 'FadeActionType', FadeOutType.Linear.name) + + self.fadeout(duration, FadeOutType[fade]) elif action == CueAction.FadeIn: - duration = config['Cue'].getfloat('FadeActionDuration') - fade_type = FadeInType[config['Cue'].get('FadeActionType')] - self.fadein(duration, fade_type) + duration = AppConfig().getfloat('Cue', 'FadeActionDuration', 0) + fade = AppConfig().get( + 'Cue', 'FadeActionType', FadeInType.Linear.name) + + self.fadein(duration, FadeInType[fade]) @async def start(self, fade=False): diff --git a/lisp/cues/media_cue.py b/lisp/cues/media_cue.py index 1422669ce..2a398d96e 100644 --- a/lisp/cues/media_cue.py +++ b/lisp/cues/media_cue.py @@ -21,7 +21,7 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP -from lisp.core.configuration import config +from lisp.core.configuration import AppConfig from lisp.core.decorators import async from lisp.core.fade_functions import FadeInType, FadeOutType from lisp.core.fader import Fader @@ -207,8 +207,9 @@ def _on_start_fade(self): def _on_stop_fade(self, interrupt=False): if interrupt: - duration = config['Cue'].getfloat('InterruptFade') - fade_type = config['Cue'].get('InterruptFadeType') + duration = AppConfig().getfloat('Cue', 'InterruptFade', 0) + fade_type = AppConfig().get( + 'Cue', 'InterruptFadeType', FadeOutType.Linear.name) else: duration = self.fadeout_duration fade_type = self.fadeout_type diff --git a/lisp/default.json b/lisp/default.json new file mode 100644 index 000000000..257ed3ab5 --- /dev/null +++ b/lisp/default.json @@ -0,0 +1,54 @@ +{ + "_version_": "0.6dev.1", + "Cue": { + "FadeActionDuration": 3, + "FadeActionType": "Linear", + "InterruptFade": 3, + "InterruptFadeType": "Linear" + }, + "Theme": { + "Theme": "Dark", + "Icons": "numix" + }, + "Backend": { + "Default": "gst" + }, + "Layout": { + "Default": "NoDefault", + "StopAllFade": false, + "PauseAllFade": false, + "ResumeAllFade": false, + "InterruptAllFade": true + }, + "Actions": { + "MaxStackSize": 0 + }, + "CartLayout": { + "GridColumns": 7, + "GridRows": 4, + "ShowSeek": false, + "ShowDbMeters": false, + "ShowAccurate": false, + "ShowVolume": false, + "Countdown": true, + "AutoAddPage": true + }, + "ListLayout": { + "ShowDbMeters": true, + "ShowSeek": true, + "ShowAccurate": false, + "ShowPlaying": true, + "AutoContinue": true, + "EndList": "Stop", + "GoKey": "Space", + "StopCueFade": true, + "PauseCueFade": true, + "ResumeCueFade": true, + "InterruptCueFade": true + }, + "DbMeter": { + "dBMax": 0, + "dbMin": -60, + "dbClip": 0 + } +} diff --git a/lisp/layouts/cart_layout/cart_layout_settings.py b/lisp/layouts/cart_layout/cart_layout_settings.py index a2cc4c170..2957dd07d 100644 --- a/lisp/layouts/cart_layout/cart_layout_settings.py +++ b/lisp/layouts/cart_layout/cart_layout_settings.py @@ -95,33 +95,26 @@ def retranslateUi(self): def get_settings(self): conf = { - 'gridcolumns': str(self.columnsSpin.value()), - 'gridrows': str(self.rowsSpin.value()), - 'showdbmeters': str(self.showDbMeters.isChecked()), - 'showseek': str(self.showSeek.isChecked()), - 'showaccurate': str(self.showAccurate.isChecked()), - 'showvolume': str(self.showVolume.isChecked()), - 'countdown': str(self.countdownMode.isChecked()), - 'autoaddpage': str(self.autoAddPage.isChecked()) + 'GridColumns': self.columnsSpin.value(), + 'GridRows': self.rowsSpin.value(), + 'ShowDbMeters': self.showDbMeters.isChecked(), + 'ShowSeek': self.showSeek.isChecked(), + 'ShowAccurate': self.showAccurate.isChecked(), + 'ShowVolume': self.showVolume.isChecked(), + 'Countdown': self.countdownMode.isChecked(), + 'AutoAddPage': self.autoAddPage.isChecked() } return {'CartLayout': conf} def load_settings(self, settings): settings = settings.get('CartLayout', {}) - if 'gridcolumns' in settings: - self.columnsSpin.setValue(int(settings['gridcolumns'])) - if 'gridrows' in settings: - self.rowsSpin.setValue(int(settings['gridrows'])) - if 'showseek' in settings: - self.showSeek.setChecked(settings['showseek'] == 'True') - if 'showdbmeters' in settings: - self.showDbMeters.setChecked(settings['showdbmeters'] == 'True') - if 'showaccurate' in settings: - self.showAccurate.setChecked(settings['showaccurate'] == 'True') - if 'showvolume' in settings: - self.showVolume.setChecked(settings['showvolume'] == 'True') - if 'countdown' in settings: - self.countdownMode.setChecked(settings['countdown'] == 'True') - if 'autoaddpage' in settings: - self.autoAddPage.setChecked(settings['autoaddpage'] == 'True') + + self.columnsSpin.setValue(settings['GridColumns']) + self.rowsSpin.setValue(settings['GridRows']) + self.showSeek.setChecked(settings['ShowSeek']) + self.showDbMeters.setChecked(settings['ShowDbMeters']) + self.showAccurate.setChecked(settings['ShowAccurate']) + self.showVolume.setChecked(settings['ShowVolume']) + self.countdownMode.setChecked(settings['Countdown']) + self.autoAddPage.setChecked(settings['AutoAddPage']) diff --git a/lisp/layouts/cart_layout/layout.py b/lisp/layouts/cart_layout/layout.py index 97807ff5e..281668516 100644 --- a/lisp/layouts/cart_layout/layout.py +++ b/lisp/layouts/cart_layout/layout.py @@ -21,7 +21,7 @@ from PyQt5.QtWidgets import QTabWidget, QAction, QInputDialog, qApp, \ QMessageBox -from lisp.core.configuration import config +from lisp.core.configuration import AppConfig from lisp.core.signal import Connection from lisp.cues.cue import Cue from lisp.cues.cue_factory import CueFactory @@ -35,7 +35,7 @@ from lisp.ui.settings.app_settings import AppSettings from lisp.ui.ui_utils import translate -AppSettings.register_settings_widget(CartLayoutSettings) +AppSettings.register_settings_widget(CartLayoutSettings, AppConfig()) class CartLayout(QTabWidget, CueLayout): @@ -56,17 +56,17 @@ def __init__(self, cue_model, **kwargs): super().__init__(cue_model=cue_model, **kwargs) self.tabBar().setObjectName('CartTabBar') - self.__columns = int(config['CartLayout']['GridColumns']) - self.__rows = int(config['CartLayout']['GridRows']) + self.__columns = AppConfig()['CartLayout']['GridColumns'] + self.__rows = AppConfig()['CartLayout']['GridRows'] self.__pages = [] self.__context_widget = None - self._show_seek = config['CartLayout'].getboolean('ShowSeek') - self._show_dbmeter = config['CartLayout'].getboolean('ShowDbMeters') - self._show_volume = config['CartLayout'].getboolean('ShowVolume') - self._accurate_timing = config['CartLayout'].getboolean('ShowAccurate') - self._countdown_mode = config['CartLayout'].getboolean('CountDown') - self._auto_add_page = config['CartLayout'].getboolean('AutoAddPage') + self._show_seek = AppConfig()['CartLayout']['ShowSeek'] + self._show_dbmeter = AppConfig()['CartLayout']['ShowDbMeters'] + self._show_volume = AppConfig()['CartLayout']['ShowVolume'] + self._accurate_timing = AppConfig()['CartLayout']['ShowAccurate'] + self._countdown_mode = AppConfig()['CartLayout']['Countdown'] + self._auto_add_page = AppConfig()['CartLayout']['AutoAddPage'] self._model_adapter = CueCartModel(cue_model, self.__rows, self.__columns) self._model_adapter.item_added.connect(self.__cue_added, Connection.QtQueued) diff --git a/lisp/layouts/cue_layout.py b/lisp/layouts/cue_layout.py index 29f5f3999..0ef65c6e0 100644 --- a/lisp/layouts/cue_layout.py +++ b/lisp/layouts/cue_layout.py @@ -22,7 +22,7 @@ from PyQt5.QtWidgets import QAction, QMenu, qApp from lisp.core.actions_handler import MainActionsHandler -from lisp.core.configuration import config +from lisp.core.configuration import AppConfig from lisp.core.signal import Signal from lisp.core.util import greatest_common_superclass from lisp.cues.cue import Cue, CueAction @@ -94,22 +94,22 @@ def go(self, action=CueAction.Default, advance=1): """ def stop_all(self): - fade = config['Layout'].getboolean('StopAllFade') + fade = AppConfig().getbool('Layout', 'StopAllFade') for cue in self.model_adapter: cue.stop(fade=fade) def interrupt_all(self): - fade = config['Layout'].getboolean('InterruptAllFade') + fade = AppConfig().getbool('Layout','InterruptAllFade') for cue in self.model_adapter: cue.interrupt(fade=fade) def pause_all(self): - fade = config['Layout'].getboolean('PauseAllFade') + fade = AppConfig().getbool('Layout','PauseAllFade') for cue in self.model_adapter: cue.pause(fade=fade) def resume_all(self): - fade = config['Layout'].getboolean('ResumeAllFade') + fade = AppConfig().getbool('Layout','ResumeAllFade') for cue in self.model_adapter: cue.resume(fade=fade) diff --git a/lisp/layouts/list_layout/layout.py b/lisp/layouts/list_layout/layout.py index a6ec63dc0..cf870d124 100644 --- a/lisp/layouts/list_layout/layout.py +++ b/lisp/layouts/list_layout/layout.py @@ -24,7 +24,7 @@ from PyQt5.QtWidgets import QWidget, QAction, qApp, QGridLayout, \ QPushButton, QSizePolicy -from lisp.core.configuration import config +from lisp.core.configuration import AppConfig from lisp.core.signal import Connection from lisp.cues.cue import Cue, CueAction from lisp.cues.media_cue import MediaCue @@ -40,7 +40,7 @@ from lisp.ui.settings.app_settings import AppSettings from lisp.ui.ui_utils import translate -AppSettings.register_settings_widget(ListLayoutSettings) +AppSettings.register_settings_widget(ListLayoutSettings, AppConfig()) class EndListBehavior(Enum): @@ -75,19 +75,16 @@ def __init__(self, cue_model, **kwargs): self._context_item = None self._next_cue_index = 0 - self._show_dbmeter = config['ListLayout'].getboolean('ShowDbMeters') - self._seek_visible = config['ListLayout'].getboolean('ShowSeek') - self._accurate_time = config['ListLayout'].getboolean('ShowAccurate') - self._auto_continue = config['ListLayout'].getboolean('AutoContinue') - self._show_playing = config['ListLayout'].getboolean('ShowPlaying') - self._go_key = config['ListLayout']['GoKey'] + self._show_dbmeter = AppConfig()['ListLayout']['ShowDbMeters'] + self._seek_visible = AppConfig()['ListLayout']['ShowSeek'] + self._accurate_time = AppConfig()['ListLayout']['ShowAccurate'] + self._auto_continue = AppConfig()['ListLayout']['AutoContinue'] + self._show_playing = AppConfig()['ListLayout']['ShowPlaying'] + self._go_key = AppConfig()['ListLayout']['GoKey'] self._go_key_sequence = QKeySequence(self._go_key, QKeySequence.NativeText) - try: - self._end_list = EndListBehavior(config['ListLayout']['EndList']) - except ValueError: - self._end_list = EndListBehavior.Stop + self._end_list = EndListBehavior(AppConfig()['ListLayout']['EndList']) # Add layout-specific menus self.showPlayingAction = QAction(self) diff --git a/lisp/layouts/list_layout/list_layout_settings.py b/lisp/layouts/list_layout/list_layout_settings.py index eabf1f79e..0aff95810 100644 --- a/lisp/layouts/list_layout/list_layout_settings.py +++ b/lisp/layouts/list_layout/list_layout_settings.py @@ -91,14 +91,14 @@ def __init__(self, **kwargs): self.useFadeGroup.layout().addWidget(self.interruptCueFade, 3, 0) # all - self.stopAllFade = QCheckBox(self.useFadeGroup) - self.useFadeGroup.layout().addWidget(self.stopAllFade, 0, 1) - self.pauseAllFade = QCheckBox(self.useFadeGroup) - self.useFadeGroup.layout().addWidget(self.pauseAllFade, 1, 1) - self.resumeAllFade = QCheckBox(self.useFadeGroup) - self.useFadeGroup.layout().addWidget(self.resumeAllFade, 2, 1) - self.interruptAllFade = QCheckBox(self.useFadeGroup) - self.useFadeGroup.layout().addWidget(self.interruptAllFade, 3, 1) + #self.stopAllFade = QCheckBox(self.useFadeGroup) + #self.useFadeGroup.layout().addWidget(self.stopAllFade, 0, 1) + #self.pauseAllFade = QCheckBox(self.useFadeGroup) + #self.useFadeGroup.layout().addWidget(self.pauseAllFade, 1, 1) + #self.resumeAllFade = QCheckBox(self.useFadeGroup) + #self.useFadeGroup.layout().addWidget(self.resumeAllFade, 2, 1) + #self.interruptAllFade = QCheckBox(self.useFadeGroup) + #self.useFadeGroup.layout().addWidget(self.interruptAllFade, 3, 1) self.retranslateUi() @@ -118,29 +118,30 @@ def retranslateUi(self): self.pauseCueFade.setText(translate('ListLayout', 'Pause Cue')) self.resumeCueFade.setText(translate('ListLayout', 'Resume Cue')) self.interruptCueFade.setText(translate('ListLayout', 'Interrupt Cue')) - self.stopAllFade.setText(translate('ListLayout', 'Stop All')) - self.pauseAllFade.setText(translate('ListLayout', 'Pause All')) - self.resumeAllFade.setText(translate('ListLayout', 'Resume All')) - self.interruptAllFade.setText(translate('ListLayout', 'Interrupt All')) + + #self.stopAllFade.setText(translate('ListLayout', 'Stop All')) + #self.pauseAllFade.setText(translate('ListLayout', 'Pause All')) + #self.resumeAllFade.setText(translate('ListLayout', 'Resume All')) + #self.interruptAllFade.setText(translate('ListLayout', 'Interrupt All')) def get_settings(self): settings = { - 'showplaying': str(self.showPlaying.isChecked()), - 'showdbmeters': str(self.showDbMeters.isChecked()), - 'showseek': str(self.showSeek.isChecked()), - 'showaccurate': str(self.showAccurate.isChecked()), - 'autocontinue': str(self.autoNext.isChecked()), - 'endlist': str(self.endListBehavior.currentData()), - 'gokey': self.goKeyEdit.keySequence().toString( + 'ShowPlaying': self.showPlaying.isChecked(), + 'ShowDbMeters': self.showDbMeters.isChecked(), + 'ShowSeek': self.showSeek.isChecked(), + 'ShowAccurate': self.showAccurate.isChecked(), + 'AutoContinue': self.autoNext.isChecked(), + 'EndList': self.endListBehavior.currentData(), + 'GoKey': self.goKeyEdit.keySequence().toString( QKeySequence.NativeText), - 'stopcuefade': str(self.stopCueFade.isChecked()), - 'pausecuefade': str(self.pauseCueFade.isChecked()), - 'resumecuefade': str(self.resumeCueFade.isChecked()), - 'interruptcuefade': str(self.interruptCueFade.isChecked()), - 'stopallfade': str(self.stopAllFade.isChecked()), - 'pauseallfade': str(self.pauseAllFade.isChecked()), - 'resumeallfade': str(self.resumeAllFade.isChecked()), - 'interruptallfade': str(self.interruptAllFade.isChecked()), + 'StopCueFade': self.stopCueFade.isChecked(), + 'PauseCueFade': self.pauseCueFade.isChecked(), + 'ResumeCueFade': self.resumeCueFade.isChecked(), + 'InterruptCueFade': self.interruptCueFade.isChecked(), + #'StopAllFade': self.stopAllFade.isChecked(), + #'PauseAllFade': self.pauseAllFade.isChecked(), + #'ResumeAllFade': self.resumeAllFade.isChecked(), + #'InterruptAllFade': self.interruptAllFade.isChecked(), } return {'ListLayout': settings} @@ -148,25 +149,23 @@ def get_settings(self): def load_settings(self, settings): settings = settings.get('ListLayout', {}) - self.showPlaying.setChecked(settings.get('showplaying') == 'True') - self.showDbMeters.setChecked(settings.get('showdbmeters') == 'True') - self.showAccurate.setChecked(settings.get('showaccurate') == 'True') - self.showSeek.setChecked(settings.get('showseek') == 'True') - self.autoNext.setChecked(settings.get('autocontinue') == 'True') + self.showPlaying.setChecked(settings['ShowPlaying']) + self.showDbMeters.setChecked(settings['ShowDbMeters']) + self.showAccurate.setChecked(settings['ShowAccurate']) + self.showSeek.setChecked(settings['ShowSeek']) + self.autoNext.setChecked(settings['AutoContinue']) self.endListBehavior.setCurrentText( - translate('ListLayout', settings.get('endlist', ''))) + translate('ListLayout', settings.get('EndList', ''))) self.goKeyEdit.setKeySequence( - QKeySequence(settings.get('gokey', 'Space'), + QKeySequence(settings.get('GoKey', 'Space'), QKeySequence.NativeText)) - self.stopCueFade.setChecked(settings.get('stopcuefade') == 'True') - self.pauseCueFade.setChecked(settings.get('pausecuefade') == 'True') - self.resumeCueFade.setChecked(settings.get('resumecuefade') == 'True') - self.interruptCueFade.setChecked( - settings.get('interruptcuefade') == 'True') - - self.stopAllFade.setChecked(settings.get('stopallfade') == 'True') - self.pauseAllFade.setChecked(settings.get('pauseallfade') == 'True') - self.resumeAllFade.setChecked(settings.get('resumeallfade') == 'True') - self.interruptAllFade.setChecked( - settings.get('interruptallfade') == 'True') \ No newline at end of file + self.stopCueFade.setChecked(settings['StopCueFade']) + self.pauseCueFade.setChecked(settings['PauseCueFade']) + self.resumeCueFade.setChecked(settings['ResumeCueFade']) + self.interruptCueFade.setChecked(settings['InterruptCueFade']) + + #self.stopAllFade.setChecked(settings['StopAllFade']) + #self.pauseAllFade.setChecked(settings['PauseAllFade']) + #self.resumeAllFade.setChecked(settings['ResumeAllFade']) + #self.interruptAllFade.setChecked(settings['InterruptAllFade']) \ No newline at end of file diff --git a/lisp/layouts/list_layout/playing_mediawidget.py b/lisp/layouts/list_layout/playing_mediawidget.py index 3d8e58d87..1bbe02631 100644 --- a/lisp/layouts/list_layout/playing_mediawidget.py +++ b/lisp/layouts/list_layout/playing_mediawidget.py @@ -23,7 +23,7 @@ QLCDNumber, QHBoxLayout from lisp.cues.cue import CueAction -from lisp.core.configuration import config +from lisp.core.configuration import AppConfig from lisp.core.signal import Connection from lisp.core.util import strtime from lisp.cues.cue_time import CueTime @@ -147,17 +147,17 @@ def _update_timers(self, time): accurate=self._accurate_time)) def _pause(self): - self.cue.pause(fade=config['ListLayout'].getboolean('PauseCueFade')) + self.cue.pause(fade=AppConfig().getbool('ListLayout', 'PauseCueFade')) def _resume(self): - self.cue.resume(fade=config['ListLayout'].getboolean('ResumeCueFade')) + self.cue.resume(fade=AppConfig().getbool('ListLayout', 'ResumeCueFade')) def _stop(self): - self.cue.stop(fade=config['ListLayout'].getboolean('StopCueFade')) + self.cue.stop(fade=AppConfig().getbool('ListLayout', 'StopCueFade')) def _interrupt(self): self.cue.interrupt( - fade=config['ListLayout'].getboolean('InterruptCueFade')) + fade=AppConfig().getbool('ListLayout', 'InterruptCueFade')) def _fadeout(self): self.cue.execute(CueAction.FadeOut) diff --git a/lisp/main.py b/lisp/main.py index 2c817ab24..b4d946ff0 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -20,18 +20,17 @@ import argparse import logging import sys -from itertools import chain -from os import path +import os -from PyQt5.QtCore import QTranslator, QLocale, QLibraryInfo +from PyQt5.QtCore import QLocale, QLibraryInfo from PyQt5.QtGui import QFont, QIcon from PyQt5.QtWidgets import QApplication -from lisp import modules -from lisp import plugins +from lisp import plugins, USER_DIR from lisp.application import Application -from lisp.core.configuration import config +from lisp.core.configuration import AppConfig from lisp.ui.styles import styles +from lisp.ui.ui_utils import install_translation def main(): @@ -63,6 +62,9 @@ def main(): level=log ) + # Create (if not present) user directory + os.makedirs(os.path.dirname(USER_DIR), exist_ok=True) + # Create the QApplication qt_app = QApplication(sys.argv) qt_app.setApplicationName('Linux Show Player') @@ -74,8 +76,8 @@ def main(): qt_app.setFont(appFont) # Set icons and theme from the application configuration QIcon.setThemeSearchPaths(styles.IconsThemePaths) - QIcon.setThemeName(config['Theme']['icons']) - styles.apply_style(config['Theme']['theme']) + QIcon.setThemeName(AppConfig()['Theme']['Icons']) + styles.apply_style(AppConfig()['Theme']['Theme']) # Get/Set the locale locale = args.locale @@ -84,44 +86,25 @@ def main(): logging.info('Using {} locale'.format(QLocale().name())) - # Main app translations - translator = QTranslator() - translator.load(QLocale(), 'lisp', '_', - path.join(path.dirname(path.realpath(__file__)), 'i18n')) - - qt_app.installTranslator(translator) - ui_translators = [translator] - # Qt platform translation - translator = QTranslator() - translator.load(QLocale(), 'qt', '_', - QLibraryInfo.location(QLibraryInfo.TranslationsPath)) - - qt_app.installTranslator(translator) - ui_translators.append(translator) - - # Modules and plugins translations - for tr_file in chain(modules.translations(), - plugins.translations()): - translator = QTranslator() - translator.load(QLocale(), tr_file, '_') - - qt_app.installTranslator(translator) - ui_translators.append(translator) + install_translation('qt', tr_path=QLibraryInfo.location( + QLibraryInfo.TranslationsPath)) + # Main app translations + install_translation('lisp', tr_path=os.path.join(os.path.dirname( + os.path.realpath(__file__)), 'i18n')) # Create the application lisp_app = Application() - # Load modules and plugins - modules.load_modules() - plugins.load_plugins() + # Load plugins + plugins.load_plugins(lisp_app) # Start/Initialize LiSP Application lisp_app.start(session_file=args.file) # Start Qt Application (block until exit) exit_code = qt_app.exec_() - # Terminate the modules - modules.terminate_modules() + # Finalize plugins + plugins.finalize_plugins() # Finalize the application lisp_app.finalize() # Exit diff --git a/lisp/modules/__init__.py b/lisp/modules/__init__.py deleted file mode 100644 index 8f550eec6..000000000 --- a/lisp/modules/__init__.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -import os - -from lisp.core.loading import load_classes -from lisp.ui import elogging - -__MODULES = {} - - -def load_modules(): - for name, module in load_classes(__package__, os.path.dirname(__file__)): - try: - __MODULES[name] = module() - elogging.debug('MODULES: Loaded "{0}"'.format(name)) - except Exception as e: - elogging.exception('Failed "{0}" lading'.format(name), e) - - -def translations(): - base_path = os.path.dirname(os.path.realpath(__file__)) - for module in next(os.walk(base_path))[1]: - i18n_dir = os.path.join(base_path, module, 'i18n') - if os.path.exists(i18n_dir): - yield os.path.join(i18n_dir, module) - - -def terminate_modules(): - for module_name in __MODULES: - try: - __MODULES[module_name].terminate() - elogging.debug('MODULES: Terminated "{0}"'.format(module_name)) - except Exception as e: - elogging.exception('Failed "{0}" termination'.format(module_name), - e) - - -def check_module(modname): - return modname.lower() in [mod.lower() for mod in __MODULES] diff --git a/lisp/modules/gst_backend/__init__.py b/lisp/modules/gst_backend/__init__.py deleted file mode 100644 index 42e20118c..000000000 --- a/lisp/modules/gst_backend/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from lisp.core.configuration import config - -if config['Backend']['Default'].lower() == 'gst': - from .gst_backend import GstBackend diff --git a/lisp/modules/midi/midi.py b/lisp/modules/midi/midi.py deleted file mode 100644 index a457a4ac4..000000000 --- a/lisp/modules/midi/midi.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2017 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -import mido - -from lisp.core.configuration import config -from lisp.core.module import Module -from lisp.modules.midi.midi_settings import MIDISettings -from lisp.ui.settings.app_settings import AppSettings - - -class Midi(Module): - """Provide MIDI I/O functionality""" - - def __init__(self): - # Register the settings widget - AppSettings.register_settings_widget(MIDISettings) - - backend = config['MIDI']['Backend'] - try: - # Load the backend and set it as current mido backend - mido.set_backend(mido.Backend(backend, load=True)) - except Exception: - raise RuntimeError('Backend loading failed: {0}'.format(backend)) diff --git a/lisp/modules/uri_changer/__init__.py b/lisp/modules/uri_changer/__init__.py deleted file mode 100644 index 031256aa9..000000000 --- a/lisp/modules/uri_changer/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .uri_changer import UriChanger \ No newline at end of file diff --git a/lisp/modules/uri_changer/i18n/uri_changer_cs.qm b/lisp/modules/uri_changer/i18n/uri_changer_cs.qm deleted file mode 100644 index 8e212ffcc4293c5b54bc7e8ce2a5699ee5ef1359..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 456 zcmcE7ks@*G{hX<16=n7(EZlq7iGhK^fWc>G5|B1woRy}^z`&@++GAP*nrA0-lc_r*X zHdkm-rgKJOUV3T~BS;SygEK=0P*)N|B3wPIYf({t5jNElKpPT)>Wdg2GL$eVFjO(* zG2}CpF_gmfvIV8)G}G%)xM72Pk+M8UM2|u>b%!KwcsM diff --git a/lisp/modules/uri_changer/i18n/uri_changer_cs.ts b/lisp/modules/uri_changer/i18n/uri_changer_cs.ts deleted file mode 100644 index 47144c6c1..000000000 --- a/lisp/modules/uri_changer/i18n/uri_changer_cs.ts +++ /dev/null @@ -1,35 +0,0 @@ - - - UriChanger - - - Session URI change - Změna URI sezení - - - - Current - Nynější - - - - Replace - Nahradit - - - - Session file - Soubor se sezením - - - - Reload - Nahrát znovu - - - - Error - Chyba - - - \ No newline at end of file diff --git a/lisp/modules/uri_changer/i18n/uri_changer_en.qm b/lisp/modules/uri_changer/i18n/uri_changer_en.qm deleted file mode 100644 index 5f15aa37cabef11244974b01b6e97bff74341695..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 432 zcmcE7ks@*G{hX<16=n7(EZlq7iGhK^fWc>G5|B1woRy}^z`!WO+GAP*Sgdr)dYPGWK@Ha#i~!9YujfiM$jQ67T=LnzP`PavJlkO4F& zk0Bk&B%$Ec;^NHwJcZC8PlaT#&#)OPfnjJG(D)oAgLq&DrDf(|xT^ygR7{MF0EbLh Ah5!Hn diff --git a/lisp/modules/uri_changer/i18n/uri_changer_en.ts b/lisp/modules/uri_changer/i18n/uri_changer_en.ts deleted file mode 100644 index 83fe232bb..000000000 --- a/lisp/modules/uri_changer/i18n/uri_changer_en.ts +++ /dev/null @@ -1,35 +0,0 @@ - - - UriChanger - - - Session URI change - Session URI change - - - - Current - Current - - - - Replace - Replace - - - - Session file - Session file - - - - Reload - Reload - - - - Error - Error - - - \ No newline at end of file diff --git a/lisp/modules/uri_changer/i18n/uri_changer_es.qm b/lisp/modules/uri_changer/i18n/uri_changer_es.qm deleted file mode 100644 index 25583cc7148319526d64a052c475fb5ef89f3c00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 468 zcmcE7ks@*G{hX<16=n7(EZlq7iGhK^fWc>G5|B1xoRy}^z`$t0+GAP*Lwx z6H~Bh5&>&UWyobHV8{V#szTAo9+X;;lbD=}O{W2aGf-_VLlV%3AS)FZLV;#^0_hZ> zGZet4DKHcR*~LH=pBeJt&JhYuEiTT?&r=8u@>EC$`xl!98o=->0{SNdsJ#pr280df Tff<~ZnS - - UriChanger - - - Session URI change - Cambiar URI de la sesión - - - - Current - Actual - - - - Replace - Reemplazar - - - - Session file - Archivo de la sesión - - - - Reload - Recargar - - - - Error - Error - - - \ No newline at end of file diff --git a/lisp/modules/uri_changer/i18n/uri_changer_fr.qm b/lisp/modules/uri_changer/i18n/uri_changer_fr.qm deleted file mode 100644 index 82c94ccb1eac0bdb8cae1b11d46c290299c636a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 480 zcmcE7ks@*G{hX<16=n7(EZlq7iGhK^fWc>G5|B1xoRy}^z`$t5+GAP*=ujIzuSX8J<9P z3Q!Crk_aS=f$U-+%mnJlgL_3NIJLMqGe1uuG{{pS8SHax7HKiK0X2gB1+#{*(L6At Q(=u}~T;Bl - - UriChanger - - - Session URI change - Modifier l'URI de la session - - - - Current - Actuel - - - - Replace - Remplacer - - - - Session file - Fichier de la session - - - - Reload - Recharger - - - - Error - Erreur - - - \ No newline at end of file diff --git a/lisp/modules/uri_changer/i18n/uri_changer_it.qm b/lisp/modules/uri_changer/i18n/uri_changer_it.qm deleted file mode 100644 index eecf61cdcb1b5be4a41299bb602e5789be12fa36..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 468 zcmcE7ks@*G{hX<16=n7(EZlq7iGhK^fWc>G5|B1yoRy}^z`$t1+GAP*m0|`j{@o0Tqk=(fHXrUP*F28kRia}%#hDe1ca#!c?=~CsT?3_HXvqqE-flb%`0IC zvbjQwGMzIL^U_m`7(u#t7+k@6KspgRSY3;X@{6#k7hnhiTA9p{2viTI;kK{^rRL-( zreM<~#t;m2S~1X3nLu0$B#VJYWWu$w2c;I|Bqpa~(`&%s3)Gte)RP9WkU@bV6zB*~ zAPo{#0GgEw#Kk~%KG5R`PY4C478hsc=P85+c`77>{fx~51qL^u)*PTAgbd_?8JL!t OgW=W=U|=#aG6Dbv=U(&x diff --git a/lisp/modules/uri_changer/i18n/uri_changer_it.ts b/lisp/modules/uri_changer/i18n/uri_changer_it.ts deleted file mode 100644 index b1f73fce9..000000000 --- a/lisp/modules/uri_changer/i18n/uri_changer_it.ts +++ /dev/null @@ -1,35 +0,0 @@ - - - UriChanger - - - Session URI change - Modifica URI di sessione - - - - Current - Corrente - - - - Replace - Sostituisci - - - - Session file - File di sessione - - - - Reload - Ricarica - - - - Error - Errore - - - \ No newline at end of file diff --git a/lisp/modules/uri_changer/i18n/uri_changer_sl_SI.qm b/lisp/modules/uri_changer/i18n/uri_changer_sl_SI.qm deleted file mode 100644 index 18dad4eee30bde5deeb42e9d4796ddfc06938c74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 447 zcmcE7ks@*G{hX<16=n7(EZlq7iGhK^fWc>G5|B1yoRy}^z`&@)+GAP* zJb`pEP%Mif6>gGHaB6XJW`3SRXppBuGT3+643%PV0UA@nkPpPEApc@9iU(#?T4oN0 T%Q}Do#;M5oUy - - UriChanger - - - Session URI change - Sprememba URI seje - - - - Current - Trenutno - - - - Replace - Zamenjaj - - - - Session file - Datoteka seje - - - - Reload - Osveži - - - - Error - Napaka - - - \ No newline at end of file diff --git a/lisp/modules/uri_changer/json_utils.py b/lisp/modules/uri_changer/json_utils.py deleted file mode 100644 index 8c02eb090..000000000 --- a/lisp/modules/uri_changer/json_utils.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - - -def json_deep_search(iterable, field): - """Takes a JSON like data-structure and search for the occurrences - of the given field/key. - """ - fields_found = [] - - if isinstance(iterable, dict): - for key, value in iterable.items(): - if key == field: - fields_found.append(value) - - elif isinstance(value, (dict, list)): - fields_found.extend(json_deep_search(value, field)) - - elif isinstance(iterable, list): - for item in iterable: - fields_found.extend(json_deep_search(item, field)) - - return fields_found - - -def json_deep_replace(iterable, field, replace_function): - """Takes a JSON like data-structure and replace the value for all the - occurrences of the given field/key with the given replace-function. - """ - if isinstance(iterable, dict): - for key, value in iterable.items(): - if key == field: - iterable[key] = replace_function(value) - - elif isinstance(value, (dict, list)): - json_deep_replace(value, field, replace_function) - - elif isinstance(iterable, list): - for item in iterable: - json_deep_replace(item, field, replace_function) diff --git a/lisp/modules/uri_changer/session.py b/lisp/modules/uri_changer/session.py deleted file mode 100644 index d8d4d00d1..000000000 --- a/lisp/modules/uri_changer/session.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -import json - -from lisp.modules.uri_changer.json_utils import json_deep_search, \ - json_deep_replace - - -class Session: - - def __init__(self, file): - self.file = file - self.session = {} - self.prefixes = set() - - self.load() - - def load(self): - # Read the file content - with open(self.file, mode='r', encoding='utf-8') as file: - self.session = json.load(file) - - def analyze(self): - self.prefixes = set() - - for uri in json_deep_search(self.session, 'uri'): - prefix = '' - - # The uri should be like "protocol://some/url/here" - for token in uri[uri.find('://')+4:].split('/')[:-1]: - prefix += '/' + token - self.prefixes.add(prefix) - - return self.prefixes - - def replace(self, old, new): - def replace(value): - if isinstance(value, str): - return value.replace(old, new) - return value - - json_deep_replace(self.session, 'uri', replace) - - def save(self, file): - with open(file, mode='w', encoding='utf-8') as new_file: - new_file.write(json.dumps(self.session, sort_keys=True, indent=4)) diff --git a/lisp/modules/uri_changer/uri_changer.py b/lisp/modules/uri_changer/uri_changer.py deleted file mode 100644 index b8e0a8d4a..000000000 --- a/lisp/modules/uri_changer/uri_changer.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from PyQt5.QtWidgets import QAction - -from lisp.core.module import Module -from lisp.ui.mainwindow import MainWindow -from lisp.ui.ui_utils import translate -from .uri_changer_dialog import UriChangerDialog - - -class UriChanger(Module): - Name = 'URI Changer' - - def __init__(self): - self.menuAction = QAction(translate('UriChanger', 'Session URI change'), - MainWindow()) - self.menuAction.triggered.connect(self.show_dialog) - - MainWindow().menuTools.addAction(self.menuAction) - - def show_dialog(self): - dialog = UriChangerDialog(parent=MainWindow()) - dialog.exec_() - - def terminate(self): - MainWindow().menuTools.removeAction(self.menuAction) diff --git a/lisp/modules/uri_changer/uri_changer_dialog.py b/lisp/modules/uri_changer/uri_changer_dialog.py deleted file mode 100644 index 71e3f8a62..000000000 --- a/lisp/modules/uri_changer/uri_changer_dialog.py +++ /dev/null @@ -1,158 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -import os - -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QIcon -from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLineEdit, \ - QPushButton, QGridLayout, QLabel, QListWidget, QDialogButtonBox, \ - QFileDialog, QMessageBox - -from lisp.ui.ui_utils import translate -from .session import Session - - -class UriChangerDialog(QDialog): - def __init__(self, **kwargs): - super().__init__(**kwargs) - - self.resize(600, 300) - self.setLayout(QVBoxLayout()) - - # FILE SELECT - self.sessionLayout = QHBoxLayout() - self.layout().addLayout(self.sessionLayout) - - self.sessionFileEdit = QLineEdit(self) - self.sessionFileEdit.editingFinished.connect(self.session_init) - self.sessionLayout.addWidget(self.sessionFileEdit) - - self.sessionFileSelect = QPushButton(self) - self.sessionFileSelect.clicked.connect(self.session_select) - self.sessionLayout.addWidget(self.sessionFileSelect) - - self.sessionReload = QPushButton(self) - self.sessionReload.setIcon(QIcon.fromTheme('view-refresh')) - self.sessionReload.clicked.connect(self.session_init) - self.sessionLayout.addWidget(self.sessionReload) - - # REPLACE - self.replaceLayout = QGridLayout() - self.layout().addLayout(self.replaceLayout) - - self.currentLabel = QLabel(self) - self.currentLabel.setAlignment(Qt.AlignCenter) - self.replaceLayout.addWidget(self.currentLabel, 0, 0) - - self.replaceLabel = QLabel(self) - self.replaceLabel.setAlignment(Qt.AlignCenter) - self.replaceLayout.addWidget(self.replaceLabel, 0, 1) - - self.prefixList = QListWidget(self) - self.prefixList.currentTextChanged.connect(self.prefix_changed) - self.replaceLayout.addWidget(self.prefixList, 1, 0, 2, 1) - - self.newPrefixLayout = QVBoxLayout() - self.replaceLayout.addLayout(self.newPrefixLayout, 1, 1) - - self.prefixEdit = QLineEdit(self) - self.newPrefixLayout.addWidget(self.prefixEdit) - self.replaceLayout.setAlignment(self.prefixEdit, Qt.AlignTop) - - self.replaceApply = QPushButton(self) - self.replaceApply.clicked.connect(self.session_replace) - self.newPrefixLayout.addWidget(self.replaceApply) - self.newPrefixLayout.setAlignment(self.replaceApply, Qt.AlignRight) - - self.buttons = QDialogButtonBox(self) - self.buttons.setStandardButtons(QDialogButtonBox.Cancel) - self.buttons.rejected.connect(self.reject) - self.layout().addWidget(self.buttons) - - self.saveButton = self.buttons.addButton(QDialogButtonBox.Save) - self.saveButton.clicked.connect(self.session_save) - - self.retranslateUi() - - self.session = None - - def retranslateUi(self): - self.currentLabel.setText(translate('UriChanger', 'Current')) - self.replaceLabel.setText(translate('UriChanger', 'Replace')) - self.sessionFileSelect.setText(translate('UriChanger', 'Session file')) - self.sessionReload.setToolTip(translate('UriChanger', 'Reload')) - self.replaceApply.setText(translate('UriChanger', 'Replace')) - - def prefix_changed(self, prefix): - if self.prefixEdit.text() == '': - self.prefixEdit.setText(prefix) - else: - # Search if the prefixEdit text is one of the listed prefixes - lower = 0 - upper = self.prefixList.count() - 1 - search = self.prefixEdit.text() - - while lower <= upper: - middle = (lower + upper) // 2 - item = self.prefixList.item(middle).text() - - if item < search: - lower = middle + 1 - elif item > search: - upper = middle - 1 - else: - self.prefixEdit.setText(prefix) - break - - def session_select(self): - file, _ = QFileDialog.getOpenFileName(self, filter='*.lsp', - directory=os.getenv('HOME')) - if file != '': - self.sessionFileEdit.setText(file) - self.session_init() - - def session_analyze(self): - self.prefixList.clear() - self.prefixList.addItems(self.session.analyze()) - self.prefixList.sortItems() - - def session_init(self): - file = self.sessionFileEdit.text() - - if os.path.exists(file): - self.session = Session(file) - self.session_analyze() - elif file.strip() != '': - QMessageBox.critical(self, translate('UriChanger', 'Error'), - translate('UriChanger', - 'Session file "{}" not found'.format( - file))) - - def session_replace(self): - self.session.replace(self.prefixList.currentItem().text(), - self.prefixEdit.text()) - self.session_analyze() - - def session_save(self): - file, ok = QFileDialog.getSaveFileName(self, filter='*.lsp', - directory=os.getenv('HOME')) - if ok: - self.session.save(file) - self.accept() diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index 4bccd5aa6..0aa25a56c 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -17,52 +17,133 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import os +import inspect +from os import path +from lisp import USER_DIR +from lisp.core.configuration import Configuration from lisp.core.loading import load_classes from lisp.ui import elogging +from lisp.ui.ui_utils import install_translation -__PLUGINS = {} +PLUGINS = {} +LOADED = {} +FALLBACK_CONFIG_PATH = path.join(path.dirname(__file__), 'default.json') -def load_plugins(): - """Load available plugins.""" - for name, plugin in load_classes(__package__, os.path.dirname(__file__)): + +def load_plugins(application): + """Load and instantiate available plugins.""" + for name, plugin in load_classes(__package__, path.dirname(__file__)): try: - __PLUGINS[name] = plugin() - elogging.debug('PLUGINS: Loaded "{0}"'.format(name)) - except Exception as e: - elogging.exception('PLUGINS: Failed "{0}" load'.format(name), e) + PLUGINS[name] = plugin + mod_path = path.dirname(inspect.getfile(plugin)) + mod_name = plugin.__module__.split('.')[-1] -def translations(): - base_path = os.path.dirname(os.path.realpath(__file__)) - for module in next(os.walk(base_path))[1]: - i18n_dir = os.path.join(base_path, module, 'i18n') - if os.path.exists(i18n_dir): - yield os.path.join(i18n_dir, module) + if path.exists(path.join(mod_path, 'default.json')): + default_config_path = path.join(mod_path, 'default.json') + else: + default_config_path = FALLBACK_CONFIG_PATH + # Load plugin configuration + config = Configuration( + path.join(USER_DIR, mod_name + '.json'), + default_config_path + ) + plugin.Config = config -def init_plugins(): - """Initialize all the plugins.""" - failed = [] - for plugin in __PLUGINS: - try: - __PLUGINS[plugin].init() - elogging.debug('PLUGINS: Initialized "{0}"'.format(plugin)) + # Load plugin translations + install_translation(mod_name) + install_translation(mod_name, tr_path=path.join(mod_path, 'i18n')) except Exception as e: - failed.append(plugin) - elogging.exception('PLUGINS: Failed "{0}" init'.format(plugin), e) + elogging.exception('PLUGINS: Failed "{}" load'.format(name), e) + + __init_plugins(application) + + +def __init_plugins(application): + pending = PLUGINS.copy() + + # Run until all the pending plugins are loaded (unless interrupted) + while pending: + # Load all plugins with satisfied dependencies + unresolved = not __load_plugins(pending, application) + + # If no plugin with satisfied dependencies is found try again + # ignoring optional-dependencies + if unresolved: + unresolved = not __load_plugins(pending, application, False) + + if unresolved: + # We've go through all the not loaded plugins and weren't able + # to resolve their dependencies, which means there are cyclic or + # missing/disabled dependencies + elogging.warning( + 'PLUGINS: Cyclic or missing/disabled dependencies detected.', + dialog=False + ) + elogging.debug('PLUGINS: Skip: {}'.format(list(pending.keys()))) + + return - for plugin in failed: - __PLUGINS.pop(plugin) +def __load_plugins(plugins, application, optionals=True): + """ + Go through each plugin and check if it's dependencies are already loaded, + otherwise leave it in the pending dict. + If all of it's dependencies are satisfied then try to load it. -def reset_plugins(): - """Resets all the plugins.""" - for plugin in __PLUGINS: + :type plugins: typing.MutableMapping[str, Type[lisp.core.plugin.Plugin]] + :type application: lisp.application.Application + :type optionals: bool + :rtype: bool + """ + resolved = False + + for name, plugin in list(plugins.items()): + dependencies = plugin.Depends + if optionals: + dependencies += plugin.OptDepends + + for dep in dependencies: + if dep not in LOADED: + break + else: + plugins.pop(name) + resolved = True + + # Try to load the plugin, if enabled + try: + if plugin.Config.get('_enabled_', False): + # Create an instance of the plugin and save it + LOADED[name] = plugin(application) + elogging.debug('PLUGINS: Loaded "{}"'.format(name)) + else: + elogging.debug( + 'PLUGINS: Skip "{}", disabled in configuration' + .format(name) + ) + except Exception as e: + elogging.exception( + 'PLUGINS: Failed "{}" load'.format(name), e) + + return resolved + + +def finalize_plugins(): + """Finalize all the plugins.""" + for plugin in LOADED: try: - __PLUGINS[plugin].reset() - elogging.debug('PLUGINS: Reset "{0}"'.format(plugin)) + LOADED[plugin].finalize() + elogging.debug('PLUGINS: Reset "{}"'.format(plugin)) except Exception as e: - elogging.exception('PLUGINS: Failed "{0}" reset'.format(plugin), e) + elogging.exception('PLUGINS: Failed "{}" reset'.format(plugin), e) + + +def is_loaded(plugin_name): + return plugin_name in LOADED + + +def get_plugin(plugin_name): + return LOADED[plugin_name] diff --git a/lisp/modules/action_cues/__init__.py b/lisp/plugins/action_cues/__init__.py similarity index 76% rename from lisp/modules/action_cues/__init__.py rename to lisp/plugins/action_cues/__init__.py index ff373b469..6d3ebf3f8 100644 --- a/lisp/modules/action_cues/__init__.py +++ b/lisp/plugins/action_cues/__init__.py @@ -22,36 +22,41 @@ from lisp.application import Application from lisp.core.loading import load_classes -from lisp.core.module import Module +from lisp.core.plugin import Plugin from lisp.cues.cue_factory import CueFactory -from lisp.ui.mainwindow import MainWindow from lisp.ui.ui_utils import translate -class ActionCues(Module): +class ActionCues(Plugin): + + Name = 'Action Cues' + Authors = ('Francesco Ceruti', ) + Description = 'Provide a collection of cues for different purposes' + + def __init__(self, app): + super().__init__(app) - def __init__(self): for name, cue in load_classes(__package__, path.dirname(__file__)): # Register the action-cue in the cue-factory CueFactory.register_factory(cue.__name__, cue) + # Register the menu action for adding the action-cue - add_function = ActionCues.create_add_action_cue_method(cue) - MainWindow().register_cue_menu_action( + add_function = self.create_add_action_cue_function(cue) + self.app.window.register_cue_menu_action( translate('CueName', cue.Name), add_function, 'Action cues') logging.debug('ACTION-CUES: Loaded "' + name + '"') - @staticmethod - def create_add_action_cue_method(cue_class): - def method(): + def create_add_action_cue_function(self, cue_class): + def function(): try: cue = CueFactory.create_cue(cue_class.__name__) - Application().cue_model.add(cue) + self.app.cue_model.add(cue) except Exception: # TODO: Display a message to the user import logging, traceback logging.error('Cannot create cue {}', cue_class.__name__) logging.debug(traceback.format_exc()) - return method + return function diff --git a/lisp/modules/action_cues/collection_cue.py b/lisp/plugins/action_cues/collection_cue.py similarity index 99% rename from lisp/modules/action_cues/collection_cue.py rename to lisp/plugins/action_cues/collection_cue.py index d6ce2c06c..7bffa6813 100644 --- a/lisp/modules/action_cues/collection_cue.py +++ b/lisp/plugins/action_cues/collection_cue.py @@ -18,7 +18,7 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QVBoxLayout, QSizePolicy, QDialogButtonBox,\ +from PyQt5.QtWidgets import QVBoxLayout, QSizePolicy, QDialogButtonBox, \ QDialog, QAbstractItemView, QHeaderView, QTableView from lisp.application import Application diff --git a/lisp/modules/action_cues/command_cue.py b/lisp/plugins/action_cues/command_cue.py similarity index 99% rename from lisp/modules/action_cues/command_cue.py rename to lisp/plugins/action_cues/command_cue.py index f443142ba..7ce724841 100644 --- a/lisp/modules/action_cues/command_cue.py +++ b/lisp/plugins/action_cues/command_cue.py @@ -24,7 +24,7 @@ from lisp.core.decorators import async from lisp.core.has_properties import Property -from lisp.cues.cue import Cue, CueState, CueAction +from lisp.cues.cue import Cue, CueAction from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/modules/action_cues/i18n/action_cues_cs.qm b/lisp/plugins/action_cues/i18n/action_cues_cs.qm similarity index 100% rename from lisp/modules/action_cues/i18n/action_cues_cs.qm rename to lisp/plugins/action_cues/i18n/action_cues_cs.qm diff --git a/lisp/modules/action_cues/i18n/action_cues_cs.ts b/lisp/plugins/action_cues/i18n/action_cues_cs.ts similarity index 100% rename from lisp/modules/action_cues/i18n/action_cues_cs.ts rename to lisp/plugins/action_cues/i18n/action_cues_cs.ts diff --git a/lisp/modules/action_cues/i18n/action_cues_en.qm b/lisp/plugins/action_cues/i18n/action_cues_en.qm similarity index 100% rename from lisp/modules/action_cues/i18n/action_cues_en.qm rename to lisp/plugins/action_cues/i18n/action_cues_en.qm diff --git a/lisp/modules/action_cues/i18n/action_cues_en.ts b/lisp/plugins/action_cues/i18n/action_cues_en.ts similarity index 100% rename from lisp/modules/action_cues/i18n/action_cues_en.ts rename to lisp/plugins/action_cues/i18n/action_cues_en.ts diff --git a/lisp/modules/action_cues/i18n/action_cues_es.qm b/lisp/plugins/action_cues/i18n/action_cues_es.qm similarity index 100% rename from lisp/modules/action_cues/i18n/action_cues_es.qm rename to lisp/plugins/action_cues/i18n/action_cues_es.qm diff --git a/lisp/modules/action_cues/i18n/action_cues_es.ts b/lisp/plugins/action_cues/i18n/action_cues_es.ts similarity index 100% rename from lisp/modules/action_cues/i18n/action_cues_es.ts rename to lisp/plugins/action_cues/i18n/action_cues_es.ts diff --git a/lisp/modules/action_cues/i18n/action_cues_fr.qm b/lisp/plugins/action_cues/i18n/action_cues_fr.qm similarity index 100% rename from lisp/modules/action_cues/i18n/action_cues_fr.qm rename to lisp/plugins/action_cues/i18n/action_cues_fr.qm diff --git a/lisp/modules/action_cues/i18n/action_cues_fr.ts b/lisp/plugins/action_cues/i18n/action_cues_fr.ts similarity index 100% rename from lisp/modules/action_cues/i18n/action_cues_fr.ts rename to lisp/plugins/action_cues/i18n/action_cues_fr.ts diff --git a/lisp/modules/action_cues/i18n/action_cues_it.qm b/lisp/plugins/action_cues/i18n/action_cues_it.qm similarity index 100% rename from lisp/modules/action_cues/i18n/action_cues_it.qm rename to lisp/plugins/action_cues/i18n/action_cues_it.qm diff --git a/lisp/modules/action_cues/i18n/action_cues_it.ts b/lisp/plugins/action_cues/i18n/action_cues_it.ts similarity index 100% rename from lisp/modules/action_cues/i18n/action_cues_it.ts rename to lisp/plugins/action_cues/i18n/action_cues_it.ts diff --git a/lisp/modules/action_cues/i18n/action_cues_sl_SI.qm b/lisp/plugins/action_cues/i18n/action_cues_sl_SI.qm similarity index 100% rename from lisp/modules/action_cues/i18n/action_cues_sl_SI.qm rename to lisp/plugins/action_cues/i18n/action_cues_sl_SI.qm diff --git a/lisp/modules/action_cues/i18n/action_cues_sl_SI.ts b/lisp/plugins/action_cues/i18n/action_cues_sl_SI.ts similarity index 100% rename from lisp/modules/action_cues/i18n/action_cues_sl_SI.ts rename to lisp/plugins/action_cues/i18n/action_cues_sl_SI.ts diff --git a/lisp/modules/action_cues/index_action_cue.py b/lisp/plugins/action_cues/index_action_cue.py similarity index 100% rename from lisp/modules/action_cues/index_action_cue.py rename to lisp/plugins/action_cues/index_action_cue.py diff --git a/lisp/modules/action_cues/midi_cue.py b/lisp/plugins/action_cues/midi_cue.py similarity index 95% rename from lisp/modules/action_cues/midi_cue.py rename to lisp/plugins/action_cues/midi_cue.py index 5ec66c8d7..80f4aa8ea 100644 --- a/lisp/modules/action_cues/midi_cue.py +++ b/lisp/plugins/action_cues/midi_cue.py @@ -23,8 +23,8 @@ from lisp.core.has_properties import Property from lisp.cues.cue import Cue -from lisp.modules.midi.midi_output import MIDIOutput -from lisp.modules.midi.midi_utils import str_msg_to_dict, dict_msg_to_str +from lisp.plugins import get_plugin +from lisp.plugins.midi.midi_utils import str_msg_to_dict, dict_msg_to_str from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate @@ -38,14 +38,11 @@ class MidiCue(Cue): def __init__(self, **kwargs): super().__init__(**kwargs) self.name = translate('CueName', self.Name) - - midi_out = MIDIOutput() - if not midi_out.is_open(): - midi_out.open() + self._output = get_plugin('Midi').output def __start__(self, fade=False): if self.message: - MIDIOutput().send_from_str(self.message) + self._output.send_from_str(self.message) return False diff --git a/lisp/modules/action_cues/osc_cue.py b/lisp/plugins/action_cues/osc_cue.py similarity index 97% rename from lisp/modules/action_cues/osc_cue.py rename to lisp/plugins/action_cues/osc_cue.py index 8d123086b..8076506c6 100644 --- a/lisp/modules/action_cues/osc_cue.py +++ b/lisp/plugins/action_cues/osc_cue.py @@ -19,6 +19,7 @@ # along with Linux Show Player. If not, see . import logging + from PyQt5 import QtCore from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, QLineEdit, \ @@ -29,8 +30,8 @@ from lisp.core.fader import Fader from lisp.core.has_properties import Property from lisp.cues.cue import Cue, CueAction -from lisp.modules.osc.osc_common import OscCommon -from lisp.modules.osc.osc_common import OscMessageType +from lisp.plugins import get_plugin +from lisp.plugins.osc.osc_server import OscMessageType from lisp.ui import elogging from lisp.ui.qdelegates import ComboBoxDelegate, OscArgumentDelegate, \ CheckBoxDelegate @@ -91,12 +92,17 @@ def __get_position(self): def __set_position(self, value): self.__value = value args = [] + for row in self.__arg_list: if row[COL_DIFF_VAL] > 0: args.append(row[COL_BASE_VAL] + row[COL_DIFF_VAL] * self.__value) else: args.append(row[COL_BASE_VAL]) - OscCommon().send(self.path, *args) + + try: + get_plugin('Osc').server.send(self.path, *args) + except KeyError: + pass _position = property(__get_position, __set_position) @@ -349,7 +355,10 @@ def __test_message(self): else: args.append(row[COL_START_VAL]) - OscCommon().send(path, *args) + try: + get_plugin('Osc').server.send(self.path, *args) + except KeyError: + pass except ValueError: elogging.error("OSC: Error on parsing argument list - nothing sent") diff --git a/lisp/modules/action_cues/seek_cue.py b/lisp/plugins/action_cues/seek_cue.py similarity index 99% rename from lisp/modules/action_cues/seek_cue.py rename to lisp/plugins/action_cues/seek_cue.py index c2ac6e883..9a0a725d6 100644 --- a/lisp/modules/action_cues/seek_cue.py +++ b/lisp/plugins/action_cues/seek_cue.py @@ -24,7 +24,7 @@ from lisp.application import Application from lisp.core.has_properties import Property -from lisp.cues.cue import Cue, CueState +from lisp.cues.cue import Cue from lisp.cues.media_cue import MediaCue from lisp.ui.cuelistdialog import CueSelectDialog from lisp.ui.settings.cue_settings import CueSettingsRegistry diff --git a/lisp/modules/action_cues/stop_all.py b/lisp/plugins/action_cues/stop_all.py similarity index 97% rename from lisp/modules/action_cues/stop_all.py rename to lisp/plugins/action_cues/stop_all.py index 4b61b515e..d44484961 100644 --- a/lisp/modules/action_cues/stop_all.py +++ b/lisp/plugins/action_cues/stop_all.py @@ -18,8 +18,7 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QComboBox -from PyQt5.QtWidgets import QVBoxLayout, QGroupBox +from PyQt5.QtWidgets import QComboBox, QVBoxLayout, QGroupBox from lisp.application import Application from lisp.core.has_properties import Property diff --git a/lisp/modules/action_cues/volume_control.py b/lisp/plugins/action_cues/volume_control.py similarity index 98% rename from lisp/modules/action_cues/volume_control.py rename to lisp/plugins/action_cues/volume_control.py index ed5722522..1c2307d2b 100644 --- a/lisp/modules/action_cues/volume_control.py +++ b/lisp/plugins/action_cues/volume_control.py @@ -20,7 +20,7 @@ from PyQt5 import QtCore from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QVBoxLayout, QLabel, QHBoxLayout, QGroupBox, \ - QPushButton, QDoubleSpinBox, QGridLayout + QPushButton, QDoubleSpinBox from lisp.application import Application from lisp.backend.audio_utils import MIN_VOLUME_DB, MAX_VOLUME_DB, \ @@ -35,7 +35,7 @@ from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate -from lisp.ui.widgets import FadeComboBox, FadeEdit +from lisp.ui.widgets import FadeEdit class VolumeControl(Cue): diff --git a/lisp/plugins/controller/controller.py b/lisp/plugins/controller/controller.py index f0a6b9ac3..0636935cf 100644 --- a/lisp/plugins/controller/controller.py +++ b/lisp/plugins/controller/controller.py @@ -17,7 +17,6 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from lisp.application import Application from lisp.core.has_properties import Property from lisp.core.plugin import Plugin from lisp.cues.cue import Cue, CueAction @@ -29,9 +28,14 @@ class Controller(Plugin): Name = 'Controller' + Authors = ('Francesco Ceruti', 'Thomas Achtner') + Depends = ('Midi', 'Osc') + Description = 'Allow to control cues via external commands with multiple ' \ + 'protocols' + + def __init__(self, app): + super().__init__(app) - def __init__(self): - super().__init__() self.__map = {} self.__actions_map = {} self.__protocols = {} @@ -39,20 +43,25 @@ def __init__(self): # Register a new Cue property to store settings Cue.register_property('controller', Property(default={})) + # On session created/destroy + self.app.session_created.connect(self.session_init) + self.app.session_before_finalize.connect(self.session_reset) + # Listen cue_model changes - Application().cue_model.item_added.connect(self.__cue_added) - Application().cue_model.item_removed.connect(self.__cue_removed) + self.app.cue_model.item_added.connect(self.__cue_added) + self.app.cue_model.item_removed.connect(self.__cue_removed) # Register settings-page CueSettingsRegistry().add_item(ControllerSettings) + # Load available protocols self.__load_protocols() - def init(self): + def session_init(self): for protocol in self.__protocols.values(): protocol.init() - def reset(self): + def session_reset(self): self.__map.clear() self.__actions_map.clear() diff --git a/lisp/plugins/controller/protocols/keyboard.py b/lisp/plugins/controller/protocols/keyboard.py index e71b7c18b..40f73c8a7 100644 --- a/lisp/plugins/controller/protocols/keyboard.py +++ b/lisp/plugins/controller/protocols/keyboard.py @@ -23,7 +23,7 @@ from lisp.application import Application from lisp.plugins.controller.protocols.protocol import Protocol -from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate, \ +from lisp.ui.qdelegates import LineEditDelegate, \ CueActionDelegate from lisp.ui.qmodels import SimpleTableModel from lisp.ui.settings.settings_page import CueSettingsPage diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index 48c8a61da..985d92c03 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -21,8 +21,7 @@ from PyQt5.QtWidgets import QGroupBox, QPushButton, QComboBox, QVBoxLayout, \ QMessageBox, QTableView, QTableWidget, QHeaderView, QGridLayout -from lisp.modules import check_module -from lisp.modules.midi.midi_input import MIDIInput +from lisp.plugins import get_plugin from lisp.plugins.controller.protocols.protocol import Protocol from lisp.ui.qdelegates import ComboBoxDelegate, SpinBoxDelegate, \ CueActionDelegate @@ -35,11 +34,8 @@ class Midi(Protocol): def __init__(self): super().__init__() - if check_module('midi'): - midi_input = MIDIInput() - if not midi_input.is_open(): - midi_input.open() - MIDIInput().new_message.connect(self.__new_message) + # Install callback for new MIDI messages + get_plugin('Midi').input.new_message.connect(self.__new_message) def __new_message(self, message): if message.type == 'note_on' or message.type == 'note_off': @@ -140,7 +136,7 @@ def load_settings(self, settings): self.midiModel.appendRow(m_type, channel+1, note, options[1]) def capture_message(self): - handler = MIDIInput() + handler = get_plugin('Midi').input handler.alternate_mode = True handler.new_message_alt.connect(self.__add_message) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index d74169da2..9b64beb4b 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -2,7 +2,8 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2017 Francesco Ceruti +# Copyright 2012-2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -25,31 +26,30 @@ QTableView, QTableWidget, QHeaderView, QGridLayout, QLabel, \ QDialog, QDialogButtonBox, QLineEdit -from lisp.modules import check_module -from lisp.modules.osc.osc_common import OscCommon -from lisp.ui.qdelegates import OscArgumentDelegate -from lisp.modules.osc.osc_common import OscMessageType +from lisp.plugins import get_plugin from lisp.plugins.controller.protocols.protocol import Protocol -from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate +from lisp.plugins.osc.osc_server import OscMessageType +from lisp.ui import elogging +from lisp.ui.qdelegates import OscArgumentDelegate, ComboBoxDelegate, \ + LineEditDelegate from lisp.ui.qmodels import SimpleTableModel from lisp.ui.settings.settings_page import CueSettingsPage from lisp.ui.ui_utils import translate -from lisp.ui import elogging class Osc(Protocol): def __init__(self): super().__init__() - if check_module('osc') and OscCommon().listening: - OscCommon().new_message.connect(self.__new_message) + try: + osc = get_plugin('Osc') + except KeyError: + raise RuntimeError('Osc plugin is not available.') - def init(self): - if OscCommon().enabled: - OscCommon().start() + osc.server.new_message.connect(self.__new_message) def __new_message(self, path, args, types): - key = Osc.key_from_message(path, types, args) + key = self.key_from_message(path, types, args) self.protocol_event.emit(key) @staticmethod @@ -154,8 +154,11 @@ def __argument_changed(self, index_topleft, index_bottomright, roles): model_row[1] = '' def retranslateUi(self): - self.groupBox.setTitle(translate('ControllerOscSettings', 'OSC Message')) - self.pathLabel.setText(translate('ControllerOscSettings', 'OSC Path: (example: "/path/to/something")')) + self.groupBox.setTitle( + translate('ControllerOscSettings', 'OSC Message')) + self.pathLabel.setText( + translate('ControllerOscSettings', + 'OSC Path: (example: "/path/to/something")')) self.addButton.setText(translate('OscCue', 'Add')) self.removeButton.setText(translate('OscCue', 'Remove')) @@ -164,9 +167,12 @@ class OscArgumentView(QTableView): def __init__(self, **kwargs): super().__init__(**kwargs) - self.delegates = [ComboBoxDelegate(options=[i.value for i in OscMessageType], - tr_context='OscMessageType'), - OscArgumentDelegate()] + self.delegates = [ + ComboBoxDelegate( + options=[i.value for i in OscMessageType], + tr_context='OscMessageType'), + OscArgumentDelegate() + ] self.setSelectionBehavior(QTableWidget.SelectRows) self.setSelectionMode(QTableView.SingleSelection) @@ -224,7 +230,8 @@ def __init__(self, cue_class, **kwargs): self.captureDialog.resize(300, 150) self.captureDialog.setMaximumSize(self.captureDialog.size()) self.captureDialog.setMinimumSize(self.captureDialog.size()) - self.captureDialog.setWindowTitle(translate('ControllerOscSettings', 'OSC Capture')) + self.captureDialog.setWindowTitle( + translate('ControllerOscSettings', 'OSC Capture')) self.captureDialog.setModal(True) self.captureLabel = QLabel('Waiting for message:') self.captureLabel.setAlignment(Qt.AlignCenter) @@ -271,38 +278,59 @@ def load_settings(self, settings): if 'osc' in settings: for options in settings['osc']: key = Osc.message_from_key(options[0]) - self.oscModel.appendRow(key[0], - key[1], - '{}'.format(key[2:])[1:-1], - options[1]) + self.oscModel.appendRow( + key[0], + key[1], + '{}'.format(key[2:])[1:-1], + options[1] + ) def capture_message(self): - OscCommon().new_message.connect(self.__show_message) + try: + osc = get_plugin('Osc') + except KeyError: + # TODO: non silent failure / disable option + return + + osc.server.new_message.connect(self.__show_message) + result = self.captureDialog.exec() if result == QDialog.Accepted and self.capturedMessage['path']: args = '{}'.format(self.capturedMessage['args'])[1:-1] - self.oscModel.appendRow(self.capturedMessage['path'], - self.capturedMessage['types'], - args, - self._default_action) - OscCommon().new_message.disconnect(self.__show_message) + self.oscModel.appendRow( + self.capturedMessage['path'], + self.capturedMessage['types'], + args, + self._default_action + ) + + osc.server.new_message.disconnect(self.__show_message) + self.captureLabel.setText('Waiting for message:') def __show_message(self, path, args, types): self.capturedMessage['path'] = path self.capturedMessage['types'] = types self.capturedMessage['args'] = args - self.captureLabel.setText('OSC: "{0}" "{1}" {2}'.format(self.capturedMessage['path'], - self.capturedMessage['types'], - self.capturedMessage['args'])) + self.captureLabel.setText( + 'OSC: "{0}" "{1}" {2}'.format( + self.capturedMessage['path'], + self.capturedMessage['types'], + self.capturedMessage['args'] + ) + ) def __new_message(self): dialog = OscMessageDialog(parent=self) if dialog.exec_() == dialog.Accepted: path = dialog.pathEdit.text() if len(path) < 2 or path[0] is not '/': - elogging.warning('OSC: Osc path seems not valid,\ndo not forget to edit the path later.', - dialog=True) + elogging.warning( + 'OSC: Osc path seems not valid,' + '\ndo not forget to edit the path later.', + dialog=True + ) + types = '' arguments = [] for row in dialog.model.rows: @@ -322,7 +350,12 @@ def __new_message(self): raise TypeError('Unsupported Osc Type') arguments.append(row[1]) - self.oscModel.appendRow(path, types, '{}'.format(arguments)[1:-1], self._default_action) + self.oscModel.appendRow( + path, + types, + '{}'.format(arguments)[1:-1], + self._default_action + ) def __remove_message(self): if self.oscModel.rowCount() and self.OscView.currentIndex().row() > -1: diff --git a/lisp/plugins/controller/protocols/protocol.py b/lisp/plugins/controller/protocols/protocol.py index 745922fff..7b7426d7c 100644 --- a/lisp/plugins/controller/protocols/protocol.py +++ b/lisp/plugins/controller/protocols/protocol.py @@ -23,8 +23,8 @@ class Protocol: """Base interface for protocols. - The init() and reset() functions are called when the respective functions - of the main-plugin are called. + The init() and reset() functions are called when a session is created + and deleted. When an event that can trigger a cue is "detected", the protocol_event signal should be emitted with the event representation. diff --git a/lisp/plugins/default.cfg b/lisp/plugins/default.cfg new file mode 100644 index 000000000..7c677ce29 --- /dev/null +++ b/lisp/plugins/default.cfg @@ -0,0 +1,7 @@ +# Default plugin configuration + +[Version] +Number = 0 + +[Base] +Enabled = True \ No newline at end of file diff --git a/lisp/plugins/default.json b/lisp/plugins/default.json new file mode 100644 index 000000000..245058514 --- /dev/null +++ b/lisp/plugins/default.json @@ -0,0 +1,4 @@ +{ + "_version_": "0", + "_enabled_": true +} \ No newline at end of file diff --git a/lisp/plugins/gst_backend/__init__.py b/lisp/plugins/gst_backend/__init__.py new file mode 100644 index 000000000..348756eee --- /dev/null +++ b/lisp/plugins/gst_backend/__init__.py @@ -0,0 +1,4 @@ +from lisp.core.configuration import AppConfig + +if AppConfig()['Backend']['Default'].lower() == 'gst': + from .gst_backend import GstBackend diff --git a/lisp/plugins/gst_backend/default.json b/lisp/plugins/gst_backend/default.json new file mode 100644 index 000000000..3ccf18105 --- /dev/null +++ b/lisp/plugins/gst_backend/default.json @@ -0,0 +1,5 @@ +{ + "_version_": "0.1", + "_enabled_": true, + "Pipeline": ["Volume", "Equalizer10", "DbMeter", "AutoSink"] +} \ No newline at end of file diff --git a/lisp/modules/gst_backend/elements/__init__.py b/lisp/plugins/gst_backend/elements/__init__.py similarity index 99% rename from lisp/modules/gst_backend/elements/__init__.py rename to lisp/plugins/gst_backend/elements/__init__.py index 4ac6ccc72..943c9f24b 100644 --- a/lisp/modules/gst_backend/elements/__init__.py +++ b/lisp/plugins/gst_backend/elements/__init__.py @@ -22,7 +22,6 @@ from lisp.backend.media_element import ElementType from lisp.core.loading import load_classes - __INPUTS = {} __OUTPUTS = {} __PLUGINS = {} diff --git a/lisp/modules/gst_backend/elements/alsa_sink.py b/lisp/plugins/gst_backend/elements/alsa_sink.py similarity index 91% rename from lisp/modules/gst_backend/elements/alsa_sink.py rename to lisp/plugins/gst_backend/elements/alsa_sink.py index ceaf5fbfd..47a545528 100644 --- a/lisp/modules/gst_backend/elements/alsa_sink.py +++ b/lisp/plugins/gst_backend/elements/alsa_sink.py @@ -20,8 +20,8 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.media_element import ElementType, MediaType -from lisp.modules.gst_backend.gi_repository import Gst -from lisp.modules.gst_backend.gst_element import GstMediaElement, GstProperty +from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gst_element import GstMediaElement, GstProperty class AlsaSink(GstMediaElement): diff --git a/lisp/modules/gst_backend/elements/audio_dynamic.py b/lisp/plugins/gst_backend/elements/audio_dynamic.py similarity index 94% rename from lisp/modules/gst_backend/elements/audio_dynamic.py rename to lisp/plugins/gst_backend/elements/audio_dynamic.py index 28c72e2b8..7de38e9e7 100644 --- a/lisp/modules/gst_backend/elements/audio_dynamic.py +++ b/lisp/plugins/gst_backend/elements/audio_dynamic.py @@ -22,8 +22,8 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.media_element import ElementType, MediaType -from lisp.modules.gst_backend.gi_repository import Gst -from lisp.modules.gst_backend.gst_element import GstMediaElement, GstProperty +from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gst_element import GstMediaElement, GstProperty class AudioDynamic(GstMediaElement): diff --git a/lisp/modules/gst_backend/elements/audio_pan.py b/lisp/plugins/gst_backend/elements/audio_pan.py similarity index 92% rename from lisp/modules/gst_backend/elements/audio_pan.py rename to lisp/plugins/gst_backend/elements/audio_pan.py index e12bad54d..596305543 100644 --- a/lisp/modules/gst_backend/elements/audio_pan.py +++ b/lisp/plugins/gst_backend/elements/audio_pan.py @@ -20,8 +20,8 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.media_element import ElementType, MediaType -from lisp.modules.gst_backend.gi_repository import Gst -from lisp.modules.gst_backend.gst_element import GstMediaElement, GstProperty +from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gst_element import GstMediaElement, GstProperty class AudioPan(GstMediaElement): diff --git a/lisp/modules/gst_backend/elements/auto_sink.py b/lisp/plugins/gst_backend/elements/auto_sink.py similarity index 91% rename from lisp/modules/gst_backend/elements/auto_sink.py rename to lisp/plugins/gst_backend/elements/auto_sink.py index 8a778dee7..079a62fa8 100644 --- a/lisp/modules/gst_backend/elements/auto_sink.py +++ b/lisp/plugins/gst_backend/elements/auto_sink.py @@ -20,8 +20,8 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.media_element import ElementType, MediaType -from lisp.modules.gst_backend.gi_repository import Gst -from lisp.modules.gst_backend.gst_element import GstMediaElement +from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gst_element import GstMediaElement class AutoSink(GstMediaElement): diff --git a/lisp/modules/gst_backend/elements/auto_src.py b/lisp/plugins/gst_backend/elements/auto_src.py similarity index 91% rename from lisp/modules/gst_backend/elements/auto_src.py rename to lisp/plugins/gst_backend/elements/auto_src.py index 0ac4ea5fb..476aca347 100644 --- a/lisp/modules/gst_backend/elements/auto_src.py +++ b/lisp/plugins/gst_backend/elements/auto_src.py @@ -20,8 +20,8 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.media_element import MediaType -from lisp.modules.gst_backend.gi_repository import Gst -from lisp.modules.gst_backend.gst_element import GstSrcElement +from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gst_element import GstSrcElement class AutoSrc(GstSrcElement): diff --git a/lisp/modules/gst_backend/elements/db_meter.py b/lisp/plugins/gst_backend/elements/db_meter.py similarity index 95% rename from lisp/modules/gst_backend/elements/db_meter.py rename to lisp/plugins/gst_backend/elements/db_meter.py index 44b90061a..9c3ab1931 100644 --- a/lisp/modules/gst_backend/elements/db_meter.py +++ b/lisp/plugins/gst_backend/elements/db_meter.py @@ -21,8 +21,8 @@ from lisp.backend.media_element import ElementType, MediaType from lisp.core.signal import Signal -from lisp.modules.gst_backend.gi_repository import Gst -from lisp.modules.gst_backend.gst_element import GstMediaElement, GstProperty +from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gst_element import GstMediaElement, GstProperty class DbMeter(GstMediaElement): diff --git a/lisp/modules/gst_backend/elements/equalizer10.py b/lisp/plugins/gst_backend/elements/equalizer10.py similarity index 94% rename from lisp/modules/gst_backend/elements/equalizer10.py rename to lisp/plugins/gst_backend/elements/equalizer10.py index b604c157f..3da4a9662 100644 --- a/lisp/modules/gst_backend/elements/equalizer10.py +++ b/lisp/plugins/gst_backend/elements/equalizer10.py @@ -20,8 +20,8 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.media_element import ElementType, MediaType -from lisp.modules.gst_backend.gi_repository import Gst -from lisp.modules.gst_backend.gst_element import GstMediaElement, GstProperty +from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gst_element import GstMediaElement, GstProperty class Equalizer10(GstMediaElement): diff --git a/lisp/modules/gst_backend/elements/jack_sink.py b/lisp/plugins/gst_backend/elements/jack_sink.py similarity index 97% rename from lisp/modules/gst_backend/elements/jack_sink.py rename to lisp/plugins/gst_backend/elements/jack_sink.py index c9cfe6730..08caea0b8 100644 --- a/lisp/modules/gst_backend/elements/jack_sink.py +++ b/lisp/plugins/gst_backend/elements/jack_sink.py @@ -17,14 +17,15 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import jack import logging + +import jack from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.media_element import ElementType, MediaType from lisp.core.has_properties import Property -from lisp.modules.gst_backend.gi_repository import Gst -from lisp.modules.gst_backend.gst_element import GstMediaElement, GstProperty +from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gst_element import GstMediaElement class JackSink(GstMediaElement): diff --git a/lisp/modules/gst_backend/elements/pitch.py b/lisp/plugins/gst_backend/elements/pitch.py similarity index 92% rename from lisp/modules/gst_backend/elements/pitch.py rename to lisp/plugins/gst_backend/elements/pitch.py index 35c775b3e..cca69fdd0 100644 --- a/lisp/modules/gst_backend/elements/pitch.py +++ b/lisp/plugins/gst_backend/elements/pitch.py @@ -20,8 +20,8 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.media_element import ElementType, MediaType -from lisp.modules.gst_backend.gi_repository import Gst -from lisp.modules.gst_backend.gst_element import GstMediaElement, GstProperty +from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gst_element import GstMediaElement, GstProperty class Pitch(GstMediaElement): diff --git a/lisp/modules/gst_backend/elements/preset_src.py b/lisp/plugins/gst_backend/elements/preset_src.py similarity index 96% rename from lisp/modules/gst_backend/elements/preset_src.py rename to lisp/plugins/gst_backend/elements/preset_src.py index 3c2914af1..e27938aa5 100644 --- a/lisp/modules/gst_backend/elements/preset_src.py +++ b/lisp/plugins/gst_backend/elements/preset_src.py @@ -23,8 +23,8 @@ from lisp.backend.media_element import MediaType from lisp.core.has_properties import Property -from lisp.modules.gst_backend.gi_repository import Gst, GstApp -from lisp.modules.gst_backend.gst_element import GstSrcElement +from lisp.plugins.gst_backend.gi_repository import Gst, GstApp +from lisp.plugins.gst_backend.gst_element import GstSrcElement class PresetSrc(GstSrcElement): diff --git a/lisp/modules/gst_backend/elements/pulse_sink.py b/lisp/plugins/gst_backend/elements/pulse_sink.py similarity index 91% rename from lisp/modules/gst_backend/elements/pulse_sink.py rename to lisp/plugins/gst_backend/elements/pulse_sink.py index 352b10caa..37849efa8 100644 --- a/lisp/modules/gst_backend/elements/pulse_sink.py +++ b/lisp/plugins/gst_backend/elements/pulse_sink.py @@ -20,8 +20,8 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.media_element import ElementType, MediaType -from lisp.modules.gst_backend.gi_repository import Gst -from lisp.modules.gst_backend.gst_element import GstMediaElement +from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gst_element import GstMediaElement class PulseSink(GstMediaElement): diff --git a/lisp/modules/gst_backend/elements/speed.py b/lisp/plugins/gst_backend/elements/speed.py similarity index 96% rename from lisp/modules/gst_backend/elements/speed.py rename to lisp/plugins/gst_backend/elements/speed.py index 5690597f0..d5059bc0c 100644 --- a/lisp/modules/gst_backend/elements/speed.py +++ b/lisp/plugins/gst_backend/elements/speed.py @@ -21,8 +21,8 @@ from lisp.backend.media_element import ElementType, MediaType from lisp.core.has_properties import Property -from lisp.modules.gst_backend.gi_repository import Gst -from lisp.modules.gst_backend.gst_element import GstMediaElement +from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gst_element import GstMediaElement class Speed(GstMediaElement): diff --git a/lisp/modules/gst_backend/elements/uri_input.py b/lisp/plugins/gst_backend/elements/uri_input.py similarity index 94% rename from lisp/modules/gst_backend/elements/uri_input.py rename to lisp/plugins/gst_backend/elements/uri_input.py index ee71d4245..36dac7ea5 100644 --- a/lisp/modules/gst_backend/elements/uri_input.py +++ b/lisp/plugins/gst_backend/elements/uri_input.py @@ -26,10 +26,10 @@ from lisp.backend.media_element import MediaType from lisp.core.decorators import async_in_pool from lisp.core.has_properties import Property -from lisp.modules.gst_backend.gi_repository import Gst -from lisp.modules.gst_backend.gst_element import GstProperty, \ +from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gst_element import GstProperty, \ GstSrcElement -from lisp.modules.gst_backend.gst_utils import gst_uri_duration +from lisp.plugins.gst_backend.gst_utils import gst_uri_duration def abs_uri(uri): diff --git a/lisp/modules/gst_backend/elements/user_element.py b/lisp/plugins/gst_backend/elements/user_element.py similarity index 96% rename from lisp/modules/gst_backend/elements/user_element.py rename to lisp/plugins/gst_backend/elements/user_element.py index 9df24bdb7..eebd6797b 100644 --- a/lisp/modules/gst_backend/elements/user_element.py +++ b/lisp/plugins/gst_backend/elements/user_element.py @@ -21,8 +21,8 @@ from lisp.backend.media_element import ElementType, MediaType from lisp.core.has_properties import Property -from lisp.modules.gst_backend.gi_repository import Gst -from lisp.modules.gst_backend.gst_element import GstMediaElement +from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gst_element import GstMediaElement class UserElement(GstMediaElement): diff --git a/lisp/modules/gst_backend/elements/volume.py b/lisp/plugins/gst_backend/elements/volume.py similarity index 94% rename from lisp/modules/gst_backend/elements/volume.py rename to lisp/plugins/gst_backend/elements/volume.py index 6fa79d369..8a76e384e 100644 --- a/lisp/modules/gst_backend/elements/volume.py +++ b/lisp/plugins/gst_backend/elements/volume.py @@ -20,8 +20,8 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.media_element import ElementType, MediaType -from lisp.modules.gst_backend.gi_repository import Gst -from lisp.modules.gst_backend.gst_element import GstMediaElement, GstProperty, \ +from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gst_element import GstMediaElement, GstProperty, \ GstRuntimeProperty diff --git a/lisp/modules/gst_backend/gi_repository.py b/lisp/plugins/gst_backend/gi_repository.py similarity index 100% rename from lisp/modules/gst_backend/gi_repository.py rename to lisp/plugins/gst_backend/gi_repository.py diff --git a/lisp/modules/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py similarity index 60% rename from lisp/modules/gst_backend/gst_backend.py rename to lisp/plugins/gst_backend/gst_backend.py index 889a5c4bb..6649aaf8d 100644 --- a/lisp/modules/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -16,35 +16,51 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +from lisp.cues.cue_factory import CueFactory from lisp import backend from lisp.backend.backend import Backend as BaseBackend from lisp.core.decorators import memoize -from lisp.core.module import Module +from lisp.core.plugin import Plugin from lisp.cues.media_cue import MediaCue -from lisp.modules.gst_backend.gst_utils import gst_parse_tags_list -from lisp.modules.gst_backend.gst_utils import gst_uri_metadata, gst_mime_types, \ - gst_uri_duration +from lisp.plugins.gst_backend import elements, settings +from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gst_cue_factories import gst_media_cue_factory,\ + UriAudioCueFactory, CaptureAudioCueFactory +from lisp.plugins.gst_backend.gst_media_settings import GstMediaSettings +from lisp.plugins.gst_backend.gst_settings import GstSettings +from lisp.plugins.gst_backend.gst_utils import gst_parse_tags_list, \ + gst_uri_metadata, gst_mime_types, gst_uri_duration from lisp.ui.settings.app_settings import AppSettings from lisp.ui.settings.cue_settings import CueSettingsRegistry -from lisp.modules.gst_backend import elements, settings -from lisp.modules.gst_backend.gi_repository import Gst -from lisp.modules.gst_backend.gst_cue_factories import register_factories -from lisp.modules.gst_backend.gst_media_settings import GstMediaSettings -from lisp.modules.gst_backend.gst_settings import GstSettings -class GstBackend(Module, BaseBackend): - def __init__(self): +class GstBackend(Plugin, BaseBackend): + + Name = 'GStreamer Backend' + Authors = ('Francesco Ceruti', ) + Description = 'Provide audio playback capabilities via the GStreamer ' \ + 'framework' + + def __init__(self, app): + super().__init__(app) + # Initialize GStreamer Gst.init(None) # Register GStreamer settings widgets - AppSettings.register_settings_widget(GstSettings) + AppSettings.register_settings_widget(GstSettings, GstBackend.Config) # Add MediaCue settings widget to the CueLayout CueSettingsRegistry().add_item(GstMediaSettings, MediaCue) - # Register the GstMedia cue builder - register_factories() + + # Register the GstMediaCue factories + base_pipeline = GstBackend.Config['Pipeline'] + + CueFactory.register_factory('MediaCue', gst_media_cue_factory) + CueFactory.register_factory( + 'URIAudioCue', UriAudioCueFactory(base_pipeline)) + CueFactory.register_factory( + 'CaptureAudioCue', CaptureAudioCueFactory(base_pipeline)) elements.load() settings.load() diff --git a/lisp/modules/gst_backend/gst_cue_factories.py b/lisp/plugins/gst_backend/gst_cue_factories.py similarity index 51% rename from lisp/modules/gst_backend/gst_cue_factories.py rename to lisp/plugins/gst_backend/gst_cue_factories.py index 07ba7217a..e6f9d6143 100644 --- a/lisp/modules/gst_backend/gst_cue_factories.py +++ b/lisp/plugins/gst_backend/gst_cue_factories.py @@ -17,13 +17,11 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from lisp.core.configuration import config -from lisp.cues.cue_factory import CueFactory from lisp.cues.media_cue import MediaCue -from lisp.modules.gst_backend.gst_media import GstMedia +from lisp.plugins.gst_backend.gst_media import GstMedia -def gst_media(id=None, pipeline=None): +def gst_media_cue_factory(id=None, pipeline=None): media = GstMedia() if pipeline is not None: @@ -32,25 +30,34 @@ def gst_media(id=None, pipeline=None): return MediaCue(media, id=id) -def uri_audio(id=None, uri=None): - cue = gst_media(id=id, pipeline=compose_pipeline('UriInput')) +class GstCueFactory: - if uri is not None: - cue.media.element('UriInput').uri = uri + def __init__(self, base_pipeline): + self.base_pipeline = base_pipeline + self.input = '' - return cue + def __call__(self, id=None): + return gst_media_cue_factory(id=id, pipeline=self.pipeline()) + def pipeline(self): + return [self.input] + self.base_pipeline -def capture_audio(id=None): - return gst_media(id=id, pipeline=compose_pipeline('AutoSrc')) +class UriAudioCueFactory(GstCueFactory): + def __init__(self, base_pipeline): + super().__init__(base_pipeline) + self.input = 'UriInput' -def compose_pipeline(input_element): - return (input_element,) +\ - tuple(config['Gst']['Pipeline'].replace(' ', '').split(',')) + def __call__(self, id=None, uri=None): + cue = super().__call__(id=id) + if uri is not None: + cue.media.element('UriInput').uri = uri -def register_factories(): - CueFactory.register_factory('MediaCue', gst_media) - CueFactory.register_factory('URIAudioCue', uri_audio) - CueFactory.register_factory('CaptureAudioCue', capture_audio) + return cue + + +class CaptureAudioCueFactory(GstCueFactory): + def __init__(self, base_pipeline): + super().__init__(base_pipeline) + self.input = 'AutoSrc' diff --git a/lisp/modules/gst_backend/gst_element.py b/lisp/plugins/gst_backend/gst_element.py similarity index 100% rename from lisp/modules/gst_backend/gst_element.py rename to lisp/plugins/gst_backend/gst_element.py diff --git a/lisp/modules/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py similarity index 99% rename from lisp/modules/gst_backend/gst_media.py rename to lisp/plugins/gst_backend/gst_media.py index c1ee04c9b..87d490b2b 100644 --- a/lisp/modules/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -21,8 +21,8 @@ from lisp.backend.media import Media, MediaState from lisp.core.has_properties import Property -from lisp.modules.gst_backend import elements -from lisp.modules.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend import elements +from lisp.plugins.gst_backend.gi_repository import Gst def validate_pipeline(pipe, rebuild=False): diff --git a/lisp/modules/gst_backend/gst_media_settings.py b/lisp/plugins/gst_backend/gst_media_settings.py similarity index 97% rename from lisp/modules/gst_backend/gst_media_settings.py rename to lisp/plugins/gst_backend/gst_media_settings.py index a95eaf834..a95a67376 100644 --- a/lisp/modules/gst_backend/gst_media_settings.py +++ b/lisp/plugins/gst_backend/gst_media_settings.py @@ -23,8 +23,8 @@ from PyQt5.QtWidgets import QGridLayout, QListWidget, QPushButton, \ QListWidgetItem -from lisp.modules.gst_backend.gst_pipe_edit import GstPipeEditDialog -from lisp.modules.gst_backend.settings import pages_by_element +from lisp.plugins.gst_backend.gst_pipe_edit import GstPipeEditDialog +from lisp.plugins.gst_backend.settings import pages_by_element from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/modules/gst_backend/gst_pipe_edit.py b/lisp/plugins/gst_backend/gst_pipe_edit.py similarity index 99% rename from lisp/modules/gst_backend/gst_pipe_edit.py rename to lisp/plugins/gst_backend/gst_pipe_edit.py index e5b73c413..bf8b9f727 100644 --- a/lisp/modules/gst_backend/gst_pipe_edit.py +++ b/lisp/plugins/gst_backend/gst_pipe_edit.py @@ -23,7 +23,7 @@ QAbstractItemView, QVBoxLayout, QPushButton, QDialogButtonBox, QWidget, \ QListWidgetItem -from lisp.modules.gst_backend import elements +from lisp.plugins.gst_backend import elements from lisp.ui.ui_utils import translate diff --git a/lisp/modules/gst_backend/gst_settings.py b/lisp/plugins/gst_backend/gst_settings.py similarity index 86% rename from lisp/modules/gst_backend/gst_settings.py rename to lisp/plugins/gst_backend/gst_settings.py index 155485432..ef23af1f1 100644 --- a/lisp/modules/gst_backend/gst_settings.py +++ b/lisp/plugins/gst_backend/gst_settings.py @@ -20,7 +20,7 @@ from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QVBoxLayout, QGroupBox -from lisp.modules.gst_backend.gst_pipe_edit import GstPipeEdit +from lisp.plugins.gst_backend.gst_pipe_edit import GstPipeEdit from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate @@ -46,8 +46,7 @@ def retranslateUi(self): self.pipeGroup.setTitle(translate('GstSettings', 'Pipeline')) def get_settings(self): - return {'Gst': {'pipeline': ', '.join(self.pipeEdit.get_pipe())}} + return {'Pipeline': list(self.pipeEdit.get_pipe())} def load_settings(self, settings): - pipe = tuple(settings['Gst']['pipeline'].replace(' ', '').split(',')) - self.pipeEdit.set_pipe(pipe) + self.pipeEdit.set_pipe(settings['Pipeline']) diff --git a/lisp/modules/gst_backend/gst_utils.py b/lisp/plugins/gst_backend/gst_utils.py similarity index 97% rename from lisp/modules/gst_backend/gst_utils.py rename to lisp/plugins/gst_backend/gst_utils.py index 51cef7c55..a42744f5d 100644 --- a/lisp/modules/gst_backend/gst_utils.py +++ b/lisp/plugins/gst_backend/gst_utils.py @@ -20,7 +20,7 @@ from urllib.parse import unquote, quote from lisp.backend.audio_utils import uri_duration -from lisp.modules.gst_backend.gi_repository import Gst, GstPbutils +from lisp.plugins.gst_backend.gi_repository import Gst, GstPbutils def gst_uri_duration(uri): diff --git a/lisp/modules/gst_backend/i18n/gst_backend_cs.qm b/lisp/plugins/gst_backend/i18n/gst_backend_cs.qm similarity index 100% rename from lisp/modules/gst_backend/i18n/gst_backend_cs.qm rename to lisp/plugins/gst_backend/i18n/gst_backend_cs.qm diff --git a/lisp/modules/gst_backend/i18n/gst_backend_cs.ts b/lisp/plugins/gst_backend/i18n/gst_backend_cs.ts similarity index 100% rename from lisp/modules/gst_backend/i18n/gst_backend_cs.ts rename to lisp/plugins/gst_backend/i18n/gst_backend_cs.ts diff --git a/lisp/modules/gst_backend/i18n/gst_backend_en.qm b/lisp/plugins/gst_backend/i18n/gst_backend_en.qm similarity index 100% rename from lisp/modules/gst_backend/i18n/gst_backend_en.qm rename to lisp/plugins/gst_backend/i18n/gst_backend_en.qm diff --git a/lisp/modules/gst_backend/i18n/gst_backend_en.ts b/lisp/plugins/gst_backend/i18n/gst_backend_en.ts similarity index 100% rename from lisp/modules/gst_backend/i18n/gst_backend_en.ts rename to lisp/plugins/gst_backend/i18n/gst_backend_en.ts diff --git a/lisp/modules/gst_backend/i18n/gst_backend_es.qm b/lisp/plugins/gst_backend/i18n/gst_backend_es.qm similarity index 100% rename from lisp/modules/gst_backend/i18n/gst_backend_es.qm rename to lisp/plugins/gst_backend/i18n/gst_backend_es.qm diff --git a/lisp/modules/gst_backend/i18n/gst_backend_es.ts b/lisp/plugins/gst_backend/i18n/gst_backend_es.ts similarity index 100% rename from lisp/modules/gst_backend/i18n/gst_backend_es.ts rename to lisp/plugins/gst_backend/i18n/gst_backend_es.ts diff --git a/lisp/modules/gst_backend/i18n/gst_backend_fr.qm b/lisp/plugins/gst_backend/i18n/gst_backend_fr.qm similarity index 100% rename from lisp/modules/gst_backend/i18n/gst_backend_fr.qm rename to lisp/plugins/gst_backend/i18n/gst_backend_fr.qm diff --git a/lisp/modules/gst_backend/i18n/gst_backend_fr.ts b/lisp/plugins/gst_backend/i18n/gst_backend_fr.ts similarity index 100% rename from lisp/modules/gst_backend/i18n/gst_backend_fr.ts rename to lisp/plugins/gst_backend/i18n/gst_backend_fr.ts diff --git a/lisp/modules/gst_backend/i18n/gst_backend_it.qm b/lisp/plugins/gst_backend/i18n/gst_backend_it.qm similarity index 100% rename from lisp/modules/gst_backend/i18n/gst_backend_it.qm rename to lisp/plugins/gst_backend/i18n/gst_backend_it.qm diff --git a/lisp/modules/gst_backend/i18n/gst_backend_it.ts b/lisp/plugins/gst_backend/i18n/gst_backend_it.ts similarity index 100% rename from lisp/modules/gst_backend/i18n/gst_backend_it.ts rename to lisp/plugins/gst_backend/i18n/gst_backend_it.ts diff --git a/lisp/modules/gst_backend/i18n/gst_backend_sl_SI.qm b/lisp/plugins/gst_backend/i18n/gst_backend_sl_SI.qm similarity index 100% rename from lisp/modules/gst_backend/i18n/gst_backend_sl_SI.qm rename to lisp/plugins/gst_backend/i18n/gst_backend_sl_SI.qm diff --git a/lisp/modules/gst_backend/i18n/gst_backend_sl_SI.ts b/lisp/plugins/gst_backend/i18n/gst_backend_sl_SI.ts similarity index 100% rename from lisp/modules/gst_backend/i18n/gst_backend_sl_SI.ts rename to lisp/plugins/gst_backend/i18n/gst_backend_sl_SI.ts diff --git a/lisp/modules/gst_backend/settings/__init__.py b/lisp/plugins/gst_backend/settings/__init__.py similarity index 100% rename from lisp/modules/gst_backend/settings/__init__.py rename to lisp/plugins/gst_backend/settings/__init__.py diff --git a/lisp/modules/gst_backend/settings/alsa_sink.py b/lisp/plugins/gst_backend/settings/alsa_sink.py similarity index 98% rename from lisp/modules/gst_backend/settings/alsa_sink.py rename to lisp/plugins/gst_backend/settings/alsa_sink.py index 5d96afc1e..edb2fb8a3 100644 --- a/lisp/modules/gst_backend/settings/alsa_sink.py +++ b/lisp/plugins/gst_backend/settings/alsa_sink.py @@ -22,7 +22,7 @@ from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QComboBox, QLabel, \ QVBoxLayout -from lisp.modules.gst_backend.elements.alsa_sink import AlsaSink +from lisp.plugins.gst_backend.elements.alsa_sink import AlsaSink from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/modules/gst_backend/settings/audio_dynamic.py b/lisp/plugins/gst_backend/settings/audio_dynamic.py similarity index 98% rename from lisp/modules/gst_backend/settings/audio_dynamic.py rename to lisp/plugins/gst_backend/settings/audio_dynamic.py index 91b225927..61579fd2a 100644 --- a/lisp/modules/gst_backend/settings/audio_dynamic.py +++ b/lisp/plugins/gst_backend/settings/audio_dynamic.py @@ -24,7 +24,7 @@ from PyQt5.QtWidgets import QGroupBox, QGridLayout, QComboBox, QDoubleSpinBox, \ QLabel, QVBoxLayout -from lisp.modules.gst_backend.elements.audio_dynamic import AudioDynamic +from lisp.plugins.gst_backend.elements.audio_dynamic import AudioDynamic from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/modules/gst_backend/settings/audio_pan.py b/lisp/plugins/gst_backend/settings/audio_pan.py similarity index 97% rename from lisp/modules/gst_backend/settings/audio_pan.py rename to lisp/plugins/gst_backend/settings/audio_pan.py index 269afa2f2..d9c7ffd11 100644 --- a/lisp/modules/gst_backend/settings/audio_pan.py +++ b/lisp/plugins/gst_backend/settings/audio_pan.py @@ -20,7 +20,7 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QSlider, QLabel, QVBoxLayout -from lisp.modules.gst_backend.elements.audio_pan import AudioPan +from lisp.plugins.gst_backend.elements.audio_pan import AudioPan from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/modules/gst_backend/settings/db_meter.py b/lisp/plugins/gst_backend/settings/db_meter.py similarity index 97% rename from lisp/modules/gst_backend/settings/db_meter.py rename to lisp/plugins/gst_backend/settings/db_meter.py index 8906aa28b..46732000d 100644 --- a/lisp/modules/gst_backend/settings/db_meter.py +++ b/lisp/plugins/gst_backend/settings/db_meter.py @@ -22,8 +22,8 @@ from PyQt5.QtWidgets import QGroupBox, QGridLayout, QSpinBox, QLabel, \ QVBoxLayout -from lisp.modules.gst_backend.elements.db_meter import DbMeter -from lisp.modules.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.elements.db_meter import DbMeter +from lisp.plugins.gst_backend.gi_repository import Gst from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/modules/gst_backend/settings/equalizer10.py b/lisp/plugins/gst_backend/settings/equalizer10.py similarity index 98% rename from lisp/modules/gst_backend/settings/equalizer10.py rename to lisp/plugins/gst_backend/settings/equalizer10.py index 716ccdf81..3f73e7346 100644 --- a/lisp/modules/gst_backend/settings/equalizer10.py +++ b/lisp/plugins/gst_backend/settings/equalizer10.py @@ -22,7 +22,7 @@ from PyQt5.QtGui import QFontMetrics from PyQt5.QtWidgets import QGroupBox, QGridLayout, QLabel, QSlider, QVBoxLayout -from lisp.modules.gst_backend.elements.equalizer10 import Equalizer10 +from lisp.plugins.gst_backend.elements.equalizer10 import Equalizer10 from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/modules/gst_backend/settings/jack_sink.py b/lisp/plugins/gst_backend/settings/jack_sink.py similarity index 98% rename from lisp/modules/gst_backend/settings/jack_sink.py rename to lisp/plugins/gst_backend/settings/jack_sink.py index ebe3e5d8f..e4991e798 100644 --- a/lisp/modules/gst_backend/settings/jack_sink.py +++ b/lisp/plugins/gst_backend/settings/jack_sink.py @@ -20,11 +20,11 @@ import jack from PyQt5.QtCore import Qt from PyQt5.QtGui import QPainter, QPolygon, QPainterPath -from PyQt5.QtWidgets import QGroupBox, QLineEdit, QLabel, QWidget, \ +from PyQt5.QtWidgets import QGroupBox, QWidget, \ QHBoxLayout, QTreeWidget, QTreeWidgetItem, QGridLayout, QDialog, \ QDialogButtonBox, QPushButton, QVBoxLayout -from lisp.modules.gst_backend.elements.jack_sink import JackSink +from lisp.plugins.gst_backend.elements.jack_sink import JackSink from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/modules/gst_backend/settings/pitch.py b/lisp/plugins/gst_backend/settings/pitch.py similarity index 98% rename from lisp/modules/gst_backend/settings/pitch.py rename to lisp/plugins/gst_backend/settings/pitch.py index 646bad45a..3172e2def 100644 --- a/lisp/modules/gst_backend/settings/pitch.py +++ b/lisp/plugins/gst_backend/settings/pitch.py @@ -23,7 +23,7 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QSlider, QLabel, QVBoxLayout -from lisp.modules.gst_backend.elements.pitch import Pitch +from lisp.plugins.gst_backend.elements.pitch import Pitch from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/modules/gst_backend/settings/preset_src.py b/lisp/plugins/gst_backend/settings/preset_src.py similarity index 97% rename from lisp/modules/gst_backend/settings/preset_src.py rename to lisp/plugins/gst_backend/settings/preset_src.py index ff8e8485d..7d3070571 100644 --- a/lisp/modules/gst_backend/settings/preset_src.py +++ b/lisp/plugins/gst_backend/settings/preset_src.py @@ -20,7 +20,7 @@ from PyQt5.QtCore import Qt, QTime from PyQt5.QtWidgets import QGroupBox, QComboBox, QVBoxLayout, QTimeEdit -from lisp.modules.gst_backend.elements.preset_src import PresetSrc +from lisp.plugins.gst_backend.elements.preset_src import PresetSrc from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/modules/gst_backend/settings/speed.py b/lisp/plugins/gst_backend/settings/speed.py similarity index 97% rename from lisp/modules/gst_backend/settings/speed.py rename to lisp/plugins/gst_backend/settings/speed.py index 7dba38f52..cb793152f 100644 --- a/lisp/modules/gst_backend/settings/speed.py +++ b/lisp/plugins/gst_backend/settings/speed.py @@ -21,7 +21,7 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QSlider, QLabel, QVBoxLayout -from lisp.modules.gst_backend.elements.speed import Speed +from lisp.plugins.gst_backend.elements.speed import Speed from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/modules/gst_backend/settings/uri_input.py b/lisp/plugins/gst_backend/settings/uri_input.py similarity index 98% rename from lisp/modules/gst_backend/settings/uri_input.py rename to lisp/plugins/gst_backend/settings/uri_input.py index 61fbba83a..3aa197619 100644 --- a/lisp/modules/gst_backend/settings/uri_input.py +++ b/lisp/plugins/gst_backend/settings/uri_input.py @@ -21,7 +21,7 @@ from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QPushButton, QLineEdit, \ QGridLayout, QCheckBox, QSpinBox, QLabel, QFileDialog, QVBoxLayout -from lisp.modules.gst_backend.elements.uri_input import UriInput +from lisp.plugins.gst_backend.elements.uri_input import UriInput from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/modules/gst_backend/settings/user_element.py b/lisp/plugins/gst_backend/settings/user_element.py similarity index 97% rename from lisp/modules/gst_backend/settings/user_element.py rename to lisp/plugins/gst_backend/settings/user_element.py index 5cb8b06c3..3da6baff3 100644 --- a/lisp/modules/gst_backend/settings/user_element.py +++ b/lisp/plugins/gst_backend/settings/user_element.py @@ -21,7 +21,7 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QPlainTextEdit, QLabel -from lisp.modules.gst_backend.elements.user_element import UserElement +from lisp.plugins.gst_backend.elements.user_element import UserElement from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/modules/gst_backend/settings/volume.py b/lisp/plugins/gst_backend/settings/volume.py similarity index 98% rename from lisp/modules/gst_backend/settings/volume.py rename to lisp/plugins/gst_backend/settings/volume.py index 1a6b106c4..deec1ec8d 100644 --- a/lisp/modules/gst_backend/settings/volume.py +++ b/lisp/plugins/gst_backend/settings/volume.py @@ -22,10 +22,10 @@ QVBoxLayout from lisp.backend.audio_utils import db_to_linear, linear_to_db -from lisp.modules.gst_backend.elements.volume import Volume +from lisp.plugins.gst_backend.elements.volume import Volume from lisp.ui.settings.settings_page import SettingsPage -from lisp.ui.widgets import QMuteButton from lisp.ui.ui_utils import translate +from lisp.ui.widgets import QMuteButton class VolumeSettings(SettingsPage): diff --git a/lisp/modules/media_cue_menus.py b/lisp/plugins/media_cue_menus.py similarity index 78% rename from lisp/modules/media_cue_menus.py rename to lisp/plugins/media_cue_menus.py index 85b8a122f..d989c8eb0 100644 --- a/lisp/modules/media_cue_menus.py +++ b/lisp/plugins/media_cue_menus.py @@ -19,30 +19,35 @@ import os -from PyQt5.QtCore import QStandardPaths, Qt +from PyQt5.QtCore import Qt from PyQt5.QtGui import QCursor from PyQt5.QtWidgets import QFileDialog, QApplication, QMessageBox from lisp.backend import get_backend -from lisp.application import Application -from lisp.core.module import Module +from lisp.core.plugin import Plugin from lisp.cues.cue_factory import CueFactory from lisp.ui.mainwindow import MainWindow from lisp.ui.ui_utils import qfile_filters, translate -class MediaCueMenus(Module): +class MediaCueMenus(Plugin): """Register menus to add MediaCue to layouts""" - def __init__(self): - MainWindow().register_cue_menu_action( + Name = 'Media cue menus' + Authors = ('Francesco Ceruti', ) + Description = 'Register menu entries to add MediaCue(s) to the layout' + + def __init__(self, app): + super().__init__(app) + + self.app.window.register_cue_menu_action( translate('MediaCueMenus', 'Audio cue (from file)'), self.add_uri_audio_media_cue, category='Media cues', - shortcut='CTRL+M') + shortcut='CTRL+M' + ) - @staticmethod - def add_uri_audio_media_cue(): + def add_uri_audio_media_cue(self): """Add audio MediaCue(s) form user-selected files""" if get_backend() is None: QMessageBox.critical(MainWindow(), 'Error', 'Backend not loaded') @@ -51,18 +56,18 @@ def add_uri_audio_media_cue(): files, _ = QFileDialog.getOpenFileNames( MainWindow(), translate('MediaCueMenus', 'Select media files'), - Application().session.path(), - qfile_filters(get_backend().supported_extensions(), anyfile=False) + self.app.session.path(), + qfile_filters(get_backend().supported_extensions(), anyfile=True) ) QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) # Create media cues, and add them to the Application cue_model for file in files: - file = os.path.relpath(file, start=Application().session.path()) + file = os.path.relpath(file, start=self.app.session.path()) cue = CueFactory.create_cue('URIAudioCue', uri='file://' + file) # Use the filename without extension as cue name cue.name = os.path.splitext(os.path.basename(file))[0] - Application().cue_model.add(cue) + self.app.cue_model.add(cue) QApplication.restoreOverrideCursor() diff --git a/lisp/modules/media_info/__init__.py b/lisp/plugins/media_info/__init__.py similarity index 100% rename from lisp/modules/media_info/__init__.py rename to lisp/plugins/media_info/__init__.py diff --git a/lisp/modules/media_info/i18n/media_info_cs.qm b/lisp/plugins/media_info/i18n/media_info_cs.qm similarity index 100% rename from lisp/modules/media_info/i18n/media_info_cs.qm rename to lisp/plugins/media_info/i18n/media_info_cs.qm diff --git a/lisp/modules/media_info/i18n/media_info_cs.ts b/lisp/plugins/media_info/i18n/media_info_cs.ts similarity index 100% rename from lisp/modules/media_info/i18n/media_info_cs.ts rename to lisp/plugins/media_info/i18n/media_info_cs.ts diff --git a/lisp/modules/media_info/i18n/media_info_en.qm b/lisp/plugins/media_info/i18n/media_info_en.qm similarity index 100% rename from lisp/modules/media_info/i18n/media_info_en.qm rename to lisp/plugins/media_info/i18n/media_info_en.qm diff --git a/lisp/modules/media_info/i18n/media_info_en.ts b/lisp/plugins/media_info/i18n/media_info_en.ts similarity index 100% rename from lisp/modules/media_info/i18n/media_info_en.ts rename to lisp/plugins/media_info/i18n/media_info_en.ts diff --git a/lisp/modules/media_info/i18n/media_info_es.qm b/lisp/plugins/media_info/i18n/media_info_es.qm similarity index 100% rename from lisp/modules/media_info/i18n/media_info_es.qm rename to lisp/plugins/media_info/i18n/media_info_es.qm diff --git a/lisp/modules/media_info/i18n/media_info_es.ts b/lisp/plugins/media_info/i18n/media_info_es.ts similarity index 100% rename from lisp/modules/media_info/i18n/media_info_es.ts rename to lisp/plugins/media_info/i18n/media_info_es.ts diff --git a/lisp/modules/media_info/i18n/media_info_fr.qm b/lisp/plugins/media_info/i18n/media_info_fr.qm similarity index 100% rename from lisp/modules/media_info/i18n/media_info_fr.qm rename to lisp/plugins/media_info/i18n/media_info_fr.qm diff --git a/lisp/modules/media_info/i18n/media_info_fr.ts b/lisp/plugins/media_info/i18n/media_info_fr.ts similarity index 100% rename from lisp/modules/media_info/i18n/media_info_fr.ts rename to lisp/plugins/media_info/i18n/media_info_fr.ts diff --git a/lisp/modules/media_info/i18n/media_info_it.qm b/lisp/plugins/media_info/i18n/media_info_it.qm similarity index 100% rename from lisp/modules/media_info/i18n/media_info_it.qm rename to lisp/plugins/media_info/i18n/media_info_it.qm diff --git a/lisp/modules/media_info/i18n/media_info_it.ts b/lisp/plugins/media_info/i18n/media_info_it.ts similarity index 100% rename from lisp/modules/media_info/i18n/media_info_it.ts rename to lisp/plugins/media_info/i18n/media_info_it.ts diff --git a/lisp/modules/media_info/i18n/media_info_sl_SI.qm b/lisp/plugins/media_info/i18n/media_info_sl_SI.qm similarity index 100% rename from lisp/modules/media_info/i18n/media_info_sl_SI.qm rename to lisp/plugins/media_info/i18n/media_info_sl_SI.qm diff --git a/lisp/modules/media_info/i18n/media_info_sl_SI.ts b/lisp/plugins/media_info/i18n/media_info_sl_SI.ts similarity index 100% rename from lisp/modules/media_info/i18n/media_info_sl_SI.ts rename to lisp/plugins/media_info/i18n/media_info_sl_SI.ts diff --git a/lisp/modules/media_info/media_info.py b/lisp/plugins/media_info/media_info.py similarity index 90% rename from lisp/modules/media_info/media_info.py rename to lisp/plugins/media_info/media_info.py index 5816c085c..da0041bcf 100644 --- a/lisp/modules/media_info/media_info.py +++ b/lisp/plugins/media_info/media_info.py @@ -21,23 +21,27 @@ from PyQt5 import QtCore from PyQt5.QtWidgets import QAction, QMessageBox, QDialog, QVBoxLayout, \ - QTreeWidget, QAbstractItemView, QDialogButtonBox, QTreeWidgetItem -from PyQt5.QtWidgets import QHeaderView + QTreeWidget, QAbstractItemView, QDialogButtonBox, QTreeWidgetItem, \ + QHeaderView -from lisp.application import Application -from lisp.modules.gst_backend.gst_utils import gst_uri_metadata, \ - gst_parse_tags_list -from lisp.core.module import Module +from lisp.core.plugin import Plugin from lisp.cues.media_cue import MediaCue from lisp.layouts.cue_layout import CueLayout +from lisp.plugins.gst_backend.gst_utils import gst_uri_metadata, \ + gst_parse_tags_list from lisp.ui.mainwindow import MainWindow from lisp.ui.ui_utils import translate -class MediaInfo(Module): - Name = 'MediaInfo' +class MediaInfo(Plugin): + + Name = 'Media-Cue information dialog' + Authors = ('Francesco Ceruti', ) + Description = 'Provide a function to show information on media-cues source' + + def __init__(self, app): + super().__init__(app) - def __init__(self): self.menuAction = QAction(None) self.menuAction.triggered.connect(self.show_info) self.menuAction.setText(translate('MediaInfo', 'Media Info')) @@ -46,7 +50,7 @@ def __init__(self): CueLayout.cm_registry.add_item(self.menuAction, MediaCue) def show_info(self, clicked): - media_uri = Application().layout.get_context_cue().media.input_uri() + media_uri = self.app.layout.get_context_cue().media.input_uri() if not media_uri: QMessageBox.critical(MainWindow(), translate('MediaInfo', 'Error'), translate('MediaInfo', 'No info to display')) @@ -89,7 +93,7 @@ def show_info(self, clicked): # Show the dialog dialog = InfoDialog(MainWindow(), info, - Application().layout.get_context_cue().name) + self.app.layout.get_context_cue().name) dialog.exec_() diff --git a/lisp/modules/midi/__init__.py b/lisp/plugins/midi/__init__.py similarity index 100% rename from lisp/modules/midi/__init__.py rename to lisp/plugins/midi/__init__.py diff --git a/lisp/plugins/midi/default.json b/lisp/plugins/midi/default.json new file mode 100644 index 000000000..836614749 --- /dev/null +++ b/lisp/plugins/midi/default.json @@ -0,0 +1,7 @@ +{ + "_version_": "0.1", + "_enabled_": true, + "Backend": "mido.backends.rtmidi", + "InputDevice": "", + "OutputDevice": "" +} \ No newline at end of file diff --git a/lisp/modules/midi/i18n/midi_cs.qm b/lisp/plugins/midi/i18n/midi_cs.qm similarity index 100% rename from lisp/modules/midi/i18n/midi_cs.qm rename to lisp/plugins/midi/i18n/midi_cs.qm diff --git a/lisp/modules/midi/i18n/midi_cs.ts b/lisp/plugins/midi/i18n/midi_cs.ts similarity index 100% rename from lisp/modules/midi/i18n/midi_cs.ts rename to lisp/plugins/midi/i18n/midi_cs.ts diff --git a/lisp/modules/midi/i18n/midi_en.qm b/lisp/plugins/midi/i18n/midi_en.qm similarity index 100% rename from lisp/modules/midi/i18n/midi_en.qm rename to lisp/plugins/midi/i18n/midi_en.qm diff --git a/lisp/modules/midi/i18n/midi_en.ts b/lisp/plugins/midi/i18n/midi_en.ts similarity index 100% rename from lisp/modules/midi/i18n/midi_en.ts rename to lisp/plugins/midi/i18n/midi_en.ts diff --git a/lisp/modules/midi/i18n/midi_es.qm b/lisp/plugins/midi/i18n/midi_es.qm similarity index 100% rename from lisp/modules/midi/i18n/midi_es.qm rename to lisp/plugins/midi/i18n/midi_es.qm diff --git a/lisp/modules/midi/i18n/midi_es.ts b/lisp/plugins/midi/i18n/midi_es.ts similarity index 100% rename from lisp/modules/midi/i18n/midi_es.ts rename to lisp/plugins/midi/i18n/midi_es.ts diff --git a/lisp/modules/midi/i18n/midi_fr.qm b/lisp/plugins/midi/i18n/midi_fr.qm similarity index 100% rename from lisp/modules/midi/i18n/midi_fr.qm rename to lisp/plugins/midi/i18n/midi_fr.qm diff --git a/lisp/modules/midi/i18n/midi_fr.ts b/lisp/plugins/midi/i18n/midi_fr.ts similarity index 100% rename from lisp/modules/midi/i18n/midi_fr.ts rename to lisp/plugins/midi/i18n/midi_fr.ts diff --git a/lisp/modules/midi/i18n/midi_it.qm b/lisp/plugins/midi/i18n/midi_it.qm similarity index 100% rename from lisp/modules/midi/i18n/midi_it.qm rename to lisp/plugins/midi/i18n/midi_it.qm diff --git a/lisp/modules/midi/i18n/midi_it.ts b/lisp/plugins/midi/i18n/midi_it.ts similarity index 100% rename from lisp/modules/midi/i18n/midi_it.ts rename to lisp/plugins/midi/i18n/midi_it.ts diff --git a/lisp/modules/midi/i18n/midi_sl_SI.qm b/lisp/plugins/midi/i18n/midi_sl_SI.qm similarity index 100% rename from lisp/modules/midi/i18n/midi_sl_SI.qm rename to lisp/plugins/midi/i18n/midi_sl_SI.qm diff --git a/lisp/modules/midi/i18n/midi_sl_SI.ts b/lisp/plugins/midi/i18n/midi_sl_SI.ts similarity index 100% rename from lisp/modules/midi/i18n/midi_sl_SI.ts rename to lisp/plugins/midi/i18n/midi_sl_SI.ts diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py new file mode 100644 index 000000000..8d7e393d7 --- /dev/null +++ b/lisp/plugins/midi/midi.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2017 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import mido + +from lisp.core.plugin import Plugin +from lisp.plugins.midi.midi_input import MIDIInput +from lisp.plugins.midi.midi_output import MIDIOutput +from lisp.plugins.midi.midi_settings import MIDISettings +from lisp.ui.settings.app_settings import AppSettings + + +class Midi(Plugin): + """Provide MIDI I/O functionality""" + + Name = 'MIDI' + Authors = ('Francesco Ceruti', ) + Description = 'Provide MIDI I/O functionality' + + def __init__(self, app): + super().__init__(app) + + # Register the settings widget + AppSettings.register_settings_widget(MIDISettings, Midi.Config) + + # Load the backend and set it as current mido backend + self.backend = mido.Backend(Midi.Config['Backend'], load=True) + mido.set_backend(self.backend) + + # Create default I/O and open the ports/devices + self.__input = MIDIInput( + self._input_name(Midi.Config['InputDevice'])) + self.__input.open() + + self.__output = MIDIOutput( + self._output_name(Midi.Config['OutputDevice'])) + self.__output.open() + + def _input_name(self, port_name): + """Check if port_name exists as an input port for the current backend. + + :param port_name: The input port name to check + :type port_name: str + + :returns The input port name if exists, None otherwise + """ + if port_name != "" and port_name in self.backend.get_input_names(): + return port_name + + def _output_name(self, port_name): + """Check if port_name exists as an output port for the current backend. + + :param port_name: The output port name to check + :type port_name: str + + :returns The output port name if exists, None otherwise + """ + if port_name != "" and port_name in self.backend.get_output_names(): + return port_name + + @property + def input(self): + return self.__input + + @property + def output(self): + return self.__output diff --git a/lisp/modules/midi/midi_common.py b/lisp/plugins/midi/midi_common.py similarity index 78% rename from lisp/modules/midi/midi_common.py rename to lisp/plugins/midi/midi_common.py index cf75865c7..5e6c0bb7c 100644 --- a/lisp/modules/midi/midi_common.py +++ b/lisp/plugins/midi/midi_common.py @@ -19,21 +19,13 @@ from abc import abstractmethod -from lisp.core.singleton import ABCSingleton - -class MIDICommon(metaclass=ABCSingleton): - - def __init__(self, port_name='AppDefault'): +class MIDICommon(): + def __init__(self, port_name=None): """ :param port_name: the port name - - The port name can be: - * SysDefault - the system default port - * AppDefault - the app default port defined in the config file - * - the port name """ - self._bakend = None + self._backend = None self._port_name = port_name self._port = None diff --git a/lisp/modules/midi/midi_input.py b/lisp/plugins/midi/midi_input.py similarity index 76% rename from lisp/modules/midi/midi_input.py rename to lisp/plugins/midi/midi_input.py index b87301514..4f96e5bab 100644 --- a/lisp/modules/midi/midi_input.py +++ b/lisp/plugins/midi/midi_input.py @@ -18,12 +18,12 @@ # along with Linux Show Player. If not, see . from lisp.core.signal import Signal -from lisp.modules.midi.midi_common import MIDICommon -from lisp.modules.midi.midi_utils import mido_backend, mido_port_name +from lisp.plugins.midi.midi_common import MIDICommon +from lisp.plugins.midi.midi_utils import mido_backend class MIDIInput(MIDICommon): - def __init__(self, port_name='AppDefault'): + def __init__(self, port_name=None): super().__init__(port_name=port_name) self.alternate_mode = False @@ -31,9 +31,8 @@ def __init__(self, port_name='AppDefault'): self.new_message_alt = Signal() def open(self): - port_name = mido_port_name(self._port_name, 'I') - self._port = mido_backend().open_input(name=port_name, - callback=self.__new_message) + self._port = mido_backend().open_input( + name=self._port_name, callback=self.__new_message) def __new_message(self, message): if self.alternate_mode: diff --git a/lisp/modules/midi/midi_output.py b/lisp/plugins/midi/midi_output.py similarity index 78% rename from lisp/modules/midi/midi_output.py rename to lisp/plugins/midi/midi_output.py index d5d0ab08f..43278fc87 100644 --- a/lisp/modules/midi/midi_output.py +++ b/lisp/plugins/midi/midi_output.py @@ -19,12 +19,12 @@ import mido -from lisp.modules.midi.midi_common import MIDICommon -from lisp.modules.midi.midi_utils import mido_backend, mido_port_name +from lisp.plugins.midi.midi_common import MIDICommon +from lisp.plugins.midi.midi_utils import mido_backend class MIDIOutput(MIDICommon): - def __init__(self, port_name='AppDefault'): + def __init__(self, port_name=None): super().__init__(port_name=port_name) def send_from_str(self, str_message): @@ -34,5 +34,4 @@ def send(self, message): self._port.send(message) def open(self): - port_name = mido_port_name(self._port_name, 'O') - self._port = mido_backend().open_output(port_name) + self._port = mido_backend().open_output(self._port_name) diff --git a/lisp/modules/midi/midi_settings.py b/lisp/plugins/midi/midi_settings.py similarity index 68% rename from lisp/modules/midi/midi_settings.py rename to lisp/plugins/midi/midi_settings.py index 978891c7b..3d316d388 100644 --- a/lisp/modules/midi/midi_settings.py +++ b/lisp/plugins/midi/midi_settings.py @@ -18,12 +18,12 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QComboBox, QGridLayout, QLabel +from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QComboBox, QGridLayout, \ + QLabel -from lisp.modules import check_module -from lisp.modules.midi.midi_input import MIDIInput -from lisp.modules.midi.midi_output import MIDIOutput -from lisp.modules.midi.midi_utils import mido_backend +from lisp.plugins.midi.midi_input import MIDIInput +from lisp.plugins.midi.midi_output import MIDIOutput +from lisp.plugins.midi.midi_utils import mido_backend from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate @@ -57,42 +57,42 @@ def __init__(self, **kwargs): self.midiGroup.layout().setColumnStretch(0, 2) self.midiGroup.layout().setColumnStretch(1, 3) - if check_module('Midi'): - try: - self._load_devices() - except Exception: - self.setEnabled(False) - else: + try: + self._load_devices() + except Exception: self.setEnabled(False) def get_settings(self): conf = {} if self.isEnabled(): - conf['inputdevice'] = self.inputCombo.currentText() - MIDIInput().change_port(conf['inputdevice']) - if self.isEnabled(): - conf['outputdevice'] = self.outputCombo.currentText() - MIDIOutput().change_port(conf['outputdevice']) + input = self.inputCombo.currentText() + if input == 'Default': + conf['InputDevice'] = "" + else: + conf['InputDevice'] = input - return {'MIDI': conf} + output = self.outputCombo.currentText() + if output == 'Default': + conf['OutputDevice'] = "" + else: + conf['OutputDevice'] = output - def load_settings(self, settings): - if 'inputdevice' in settings['MIDI']: - self.inputCombo.setCurrentText('AppDefault') - self.inputCombo.setCurrentText(settings['MIDI']['inputdevice']) + return conf - if 'outputdevice' in settings['MIDI']: - self.outputCombo.setCurrentText('AppDefaut') - self.outputCombo.setCurrentText(settings['MIDI']['outputdevice']) + def load_settings(self, settings): + self.inputCombo.setCurrentText('Default') + self.inputCombo.setCurrentText(settings['InputDevice']) + self.outputCombo.setCurrentText('Default') + self.outputCombo.setCurrentText(settings['OutputDevice']) def _load_devices(self): backend = mido_backend() self.inputCombo.clear() - self.inputCombo.addItems(['AppDefault', 'SysDefault']) + self.inputCombo.addItems(['Default']) self.inputCombo.addItems(backend.get_input_names()) self.outputCombo.clear() - self.outputCombo.addItems(['AppDefault', 'SysDefault']) + self.outputCombo.addItems(['Default']) self.outputCombo.addItems(backend.get_output_names()) diff --git a/lisp/modules/midi/midi_utils.py b/lisp/plugins/midi/midi_utils.py similarity index 63% rename from lisp/modules/midi/midi_utils.py rename to lisp/plugins/midi/midi_utils.py index f20810fcf..a60145eb6 100644 --- a/lisp/modules/midi/midi_utils.py +++ b/lisp/plugins/midi/midi_utils.py @@ -19,7 +19,7 @@ import mido -from lisp.core.configuration import config +from lisp.core.configuration import AppConfig def str_msg_to_dict(str_message): @@ -51,25 +51,3 @@ def mido_backend(): return backend - -def mido_port_name(port_name, mode): - """Transform application default-port to appropriate values for mido - - :param port_name: port-name to be checked - :param mode: port I/O mode ['I'=input, 'O'=output] - :return: the port-name to give to mido to open the right port - """ - port_name = port_name - - if port_name == 'AppDefault': - if mode.upper() == 'I': - port_name = config['MIDI']['InputDevice'] - elif mode.upper() == 'O': - port_name = config['MIDI']['OutputDevice'] - - if port_name == 'SysDefault': - return None - elif mode.upper() == 'I' and port_name in mido_backend().get_input_names(): - return port_name - elif mode.upper() == 'O' and port_name in mido_backend().get_output_names(): - return port_name diff --git a/lisp/modules/osc/__init__.py b/lisp/plugins/osc/__init__.py similarity index 100% rename from lisp/modules/osc/__init__.py rename to lisp/plugins/osc/__init__.py diff --git a/lisp/plugins/osc/default.json b/lisp/plugins/osc/default.json new file mode 100644 index 000000000..92f8e0cd1 --- /dev/null +++ b/lisp/plugins/osc/default.json @@ -0,0 +1,7 @@ +{ + "_version_": "1", + "_enabled_": true, + "InPort": 9000, + "OutPort": 9001, + "Hostname": "localhost" +} \ No newline at end of file diff --git a/lisp/modules/osc/osc.py b/lisp/plugins/osc/osc.py similarity index 57% rename from lisp/modules/osc/osc.py rename to lisp/plugins/osc/osc.py index c618edea6..e78393003 100644 --- a/lisp/modules/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -19,18 +19,36 @@ # along with Linux Show Player. If not, see . -from lisp.core.module import Module -from lisp.modules.osc.osc_common import OscCommon -from lisp.modules.osc.osc_settings import OscSettings +from lisp.core.plugin import Plugin +from lisp.plugins.osc.osc_server import OscServer +from lisp.plugins.osc.osc_settings import OscSettings from lisp.ui.settings.app_settings import AppSettings -class Osc(Module): +# TODO: layout-controls in external plugin (now disabled, see osc_server.py) +class Osc(Plugin): """Provide OSC I/O functionality""" - def __init__(self): + Name = 'OSC' + Authors = ('Thomas Achtner', ) + Description = 'Provide OSC I/O functionality' + + def __init__(self, app): + super().__init__(app) + # Register the settings widget - AppSettings.register_settings_widget(OscSettings) + AppSettings.register_settings_widget(OscSettings, Osc.Config) + + # Create a server instance + self.__server = OscServer( + Osc.Config['Hostname'], + Osc.Config['InPort'], + Osc.Config['OutPort'] + ) + + @property + def server(self): + return self.__server def terminate(self): - OscCommon().stop() \ No newline at end of file + self.__server.stop() diff --git a/lisp/modules/osc/osc_common.py b/lisp/plugins/osc/osc_server.py similarity index 78% rename from lisp/modules/osc/osc_common.py rename to lisp/plugins/osc/osc_server.py index 6ceef8398..e8b53e01e 100644 --- a/lisp/modules/osc/osc_common.py +++ b/lisp/plugins/osc/osc_server.py @@ -20,25 +20,12 @@ import logging import traceback -from collections import deque from enum import Enum from liblo import ServerThread, Address, ServerError -from lisp.application import Application -from lisp.core.configuration import config from lisp.core.signal import Signal -from lisp.core.singleton import ABCSingleton -from lisp.layouts.list_layout.layout import ListLayout - -class OscMessageType(Enum): - Int = 'Integer' - Float = 'Float' - Bool = 'Bool' - String = 'String' - - -def callback_go(_, args, types): +'''def callback_go(_, args, types): if not isinstance(Application().layout, ListLayout): return @@ -103,14 +90,24 @@ def callback_interrupt(_, args, types): ['/lisp/list/restart', None, callback_restart], ['/lisp/list/stop', None, callback_stop], ['/lisp/list/interrupt', None, callback_interrupt] -] +]''' -class OscCommon(metaclass=ABCSingleton): - def __init__(self): +class OscMessageType(Enum): + Int = 'Integer' + Float = 'Float' + Bool = 'Bool' + String = 'String' + + +class OscServer: + def __init__(self, hostname, iport, oport): + self.__hostname = hostname + self.__iport = iport + self.__oport = oport + self.__srv = None self.__listening = False - self.__log = deque([], 10) self.new_message = Signal() @@ -119,16 +116,12 @@ def start(self): return try: - self.__srv = ServerThread(int(config['OSC']['inport'])) - - if isinstance(Application().layout, ListLayout): - for cb in GLOBAL_CALLBACKS: - self.__srv.add_method(cb[0], cb[1], cb[2]) - - self.__srv.add_method(None, None, self.__new_message) - + self.__srv = ServerThread(self.__iport) + self.__srv.add_method(None, None, self.new_message.emit) self.__srv.start() + self.__listening = True + logging.info('OSC: Server started at {}'.format(self.__srv.url)) except ServerError: logging.error('OSC: Cannot start sever') @@ -139,23 +132,14 @@ def stop(self): if self.__listening: self.__srv.stop() self.__listening = False + self.__srv.free() logging.info('OSC: Server stopped') - @property - def enabled(self): - return config['OSC']['enabled'] == 'True' - @property def listening(self): return self.__listening def send(self, path, *args): if self.__listening: - target = Address(config['OSC']['hostname'], - int(config['OSC']['outport'])) - self.__srv.send(target, path, *args) - - def __new_message(self, path, args, types): - # self.push_log(path, args, types, src, False) - self.new_message.emit(path, args, types) + self.__srv.send(Address(self.__hostname, self.__oport), path, *args) diff --git a/lisp/modules/osc/osc_settings.py b/lisp/plugins/osc/osc_settings.py similarity index 54% rename from lisp/modules/osc/osc_settings.py rename to lisp/plugins/osc/osc_settings.py index ed704c215..b26c4a52b 100644 --- a/lisp/modules/osc/osc_settings.py +++ b/lisp/plugins/osc/osc_settings.py @@ -20,14 +20,13 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel, \ - QCheckBox, QHBoxLayout, QSpinBox, QLineEdit + QHBoxLayout, QSpinBox, QLineEdit -from lisp.core.configuration import config -from lisp.modules.osc.osc_common import OscCommon from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate +# FIXME class OscSettings(SettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'OSC settings') @@ -41,10 +40,6 @@ def __init__(self, **kwargs): self.groupBox.setTitle(translate('OscSettings', 'OSC Settings')) self.layout().addWidget(self.groupBox) - self.enableModule = QCheckBox(self.groupBox) - self.enableModule.setText(translate('OscSettings', 'enable OSC')) - self.groupBox.layout().addWidget(self.enableModule) - hbox = QHBoxLayout() self.inportBox = QSpinBox(self) self.inportBox.setMinimum(1000) @@ -72,54 +67,14 @@ def __init__(self, **kwargs): hbox.layout().addWidget(self.hostnameEdit) self.groupBox.layout().addLayout(hbox) - self.enableModule.stateChanged.connect( - self.activate_module,Qt.QueuedConnection) - self.inportBox.valueChanged.connect( - self.change_inport, Qt.QueuedConnection) - self.outportBox.valueChanged.connect( - self.change_outport, Qt.QueuedConnection) - self.hostnameEdit.textChanged.connect( - self.change_hostname, Qt.QueuedConnection) - - def activate_module(self): - if self.enableModule.isChecked(): - # enable OSC Module in Settings - config.set('OSC', 'enabled', 'True') - # start osc server - OscCommon().start() - else: - # disable OSC Module in Settings - config.set('OSC', 'enabled', 'False') - # stop osc server - OscCommon().stop() - - def change_inport(self): - port = str(self.inportBox.value()) - if port != config.get('OSC', 'inport'): - config.set('OSC', 'inport', port) - if self.enableModule.isChecked(): - self.enableModule.setChecked(False) - - def change_outport(self): - config.set('OSC', 'outport', str(self.outportBox.value())) - - def change_hostname(self): - hostname = self.hostnameEdit.text() - config.set('OSC', 'hostname', hostname) - def get_settings(self): - conf = { - 'enabled': str(self.enableModule.isChecked()), - 'inport': str(self.inportBox.value()), - 'outport': str(self.outportBox.value()), - 'hostname': self.hostnameEdit.text(), + return { + 'InPort': self.inportBox.value(), + 'OutPort': self.outportBox.value(), + 'Hostname': self.hostnameEdit.text(), } - return {'OSC': conf} def load_settings(self, settings): - settings = settings.get('OSC', {}) - - self.enableModule.setChecked(settings.get('enabled') == 'True') - self.inportBox.setValue(int(settings.get('inport', 9000))) - self.outportBox.setValue(int(settings.get('outport', 9001))) - self.hostnameEdit.setText(settings.get('hostname', 'localhost')) + self.inportBox.setValue(settings['InPort']) + self.outportBox.setValue(settings['OutPort']) + self.hostnameEdit.setText(settings['Hostname']) diff --git a/lisp/modules/presets/__init__.py b/lisp/plugins/presets/__init__.py similarity index 100% rename from lisp/modules/presets/__init__.py rename to lisp/plugins/presets/__init__.py diff --git a/lisp/modules/presets/i18n/presets_cs.qm b/lisp/plugins/presets/i18n/presets_cs.qm similarity index 100% rename from lisp/modules/presets/i18n/presets_cs.qm rename to lisp/plugins/presets/i18n/presets_cs.qm diff --git a/lisp/modules/presets/i18n/presets_cs.ts b/lisp/plugins/presets/i18n/presets_cs.ts similarity index 100% rename from lisp/modules/presets/i18n/presets_cs.ts rename to lisp/plugins/presets/i18n/presets_cs.ts diff --git a/lisp/modules/presets/i18n/presets_en.qm b/lisp/plugins/presets/i18n/presets_en.qm similarity index 100% rename from lisp/modules/presets/i18n/presets_en.qm rename to lisp/plugins/presets/i18n/presets_en.qm diff --git a/lisp/modules/presets/i18n/presets_en.ts b/lisp/plugins/presets/i18n/presets_en.ts similarity index 100% rename from lisp/modules/presets/i18n/presets_en.ts rename to lisp/plugins/presets/i18n/presets_en.ts diff --git a/lisp/modules/presets/i18n/presets_es.qm b/lisp/plugins/presets/i18n/presets_es.qm similarity index 100% rename from lisp/modules/presets/i18n/presets_es.qm rename to lisp/plugins/presets/i18n/presets_es.qm diff --git a/lisp/modules/presets/i18n/presets_es.ts b/lisp/plugins/presets/i18n/presets_es.ts similarity index 100% rename from lisp/modules/presets/i18n/presets_es.ts rename to lisp/plugins/presets/i18n/presets_es.ts diff --git a/lisp/modules/presets/i18n/presets_fr.qm b/lisp/plugins/presets/i18n/presets_fr.qm similarity index 100% rename from lisp/modules/presets/i18n/presets_fr.qm rename to lisp/plugins/presets/i18n/presets_fr.qm diff --git a/lisp/modules/presets/i18n/presets_fr.ts b/lisp/plugins/presets/i18n/presets_fr.ts similarity index 100% rename from lisp/modules/presets/i18n/presets_fr.ts rename to lisp/plugins/presets/i18n/presets_fr.ts diff --git a/lisp/modules/presets/i18n/presets_it.qm b/lisp/plugins/presets/i18n/presets_it.qm similarity index 100% rename from lisp/modules/presets/i18n/presets_it.qm rename to lisp/plugins/presets/i18n/presets_it.qm diff --git a/lisp/modules/presets/i18n/presets_it.ts b/lisp/plugins/presets/i18n/presets_it.ts similarity index 100% rename from lisp/modules/presets/i18n/presets_it.ts rename to lisp/plugins/presets/i18n/presets_it.ts diff --git a/lisp/modules/presets/i18n/presets_sl_SI.qm b/lisp/plugins/presets/i18n/presets_sl_SI.qm similarity index 100% rename from lisp/modules/presets/i18n/presets_sl_SI.qm rename to lisp/plugins/presets/i18n/presets_sl_SI.qm diff --git a/lisp/modules/presets/i18n/presets_sl_SI.ts b/lisp/plugins/presets/i18n/presets_sl_SI.ts similarity index 100% rename from lisp/modules/presets/i18n/presets_sl_SI.ts rename to lisp/plugins/presets/i18n/presets_sl_SI.ts diff --git a/lisp/modules/presets/lib.py b/lisp/plugins/presets/lib.py similarity index 98% rename from lisp/modules/presets/lib.py rename to lisp/plugins/presets/lib.py index 33869f8b8..635559b83 100644 --- a/lisp/modules/presets/lib.py +++ b/lisp/plugins/presets/lib.py @@ -21,7 +21,7 @@ import os from zipfile import ZipFile, BadZipFile -from lisp.core import configuration +from lisp import USER_DIR from lisp.core.actions_handler import MainActionsHandler from lisp.cues.cue_actions import UpdateCueAction, UpdateCuesAction @@ -30,7 +30,7 @@ except ImportError: from scandir import scandir -PRESETS_DIR = os.path.join(configuration.CFG_DIR, 'presets') +PRESETS_DIR = os.path.join(USER_DIR, 'presets') def preset_path(name): diff --git a/lisp/modules/presets/presets.py b/lisp/plugins/presets/presets.py similarity index 71% rename from lisp/modules/presets/presets.py rename to lisp/plugins/presets/presets.py index d1fdd20ea..b71ba4891 100644 --- a/lisp/modules/presets/presets.py +++ b/lisp/plugins/presets/presets.py @@ -19,33 +19,35 @@ import os -from PyQt5.QtWidgets import QAction, QMenu +from PyQt5.QtWidgets import QAction -from lisp.application import Application -from lisp.core.module import Module +from lisp.core.plugin import Plugin from lisp.layouts.cue_layout import CueLayout -from lisp.modules.presets.lib import PRESETS_DIR, load_on_cue, preset_exists -from lisp.modules.presets.presets_ui import select_preset_dialog, \ +from lisp.plugins.presets.lib import PRESETS_DIR, load_on_cue, preset_exists +from lisp.plugins.presets.presets_ui import select_preset_dialog, \ PresetsDialog, save_preset_dialog, check_override_dialog, write_preset, \ load_preset_error, write_preset_error -from lisp.ui.mainwindow import MainWindow from lisp.ui.ui_utils import translate -class Presets(Module): +class Presets(Plugin): - def __init__(self): - super().__init__() + Name = 'Preset' + Authors = ('Francesco Ceruti', ) + Description = 'Allow to save, edit, import and export cue presets' + + def __init__(self, app): + super().__init__(app) if not os.path.exists(PRESETS_DIR): os.makedirs(PRESETS_DIR, exist_ok=True) # Entry in mainWindow menu - self.manageAction = QAction(MainWindow()) + self.manageAction = QAction(self.app.window) self.manageAction.triggered.connect(self.__edit_presets) self.manageAction.setText(translate('Presets', 'Presets')) - self.menu_action = MainWindow().menuTools.addAction(self.manageAction) + self.menu_action = self.app.window.menuTools.addAction(self.manageAction) self.loadOnCueAction = QAction(None) self.loadOnCueAction.triggered.connect(self.__load_on_cue) @@ -60,23 +62,20 @@ def __init__(self): CueLayout.cm_registry.add_item(self.createFromCueAction) CueLayout.cm_registry.add_separator() - @staticmethod - def __edit_presets(): - ui = PresetsDialog(parent=MainWindow()) + def __edit_presets(self): + ui = PresetsDialog(self.app, parent=self.app.window) ui.show() - @staticmethod - def __load_on_cue(): + def __load_on_cue(self): preset_name = select_preset_dialog() if preset_name is not None: try: - load_on_cue(preset_name, Application().layout.get_context_cue()) + load_on_cue(preset_name, self.app.layout.get_context_cue()) except OSError as e: - load_preset_error(e, preset_name, parent=MainWindow()) + load_preset_error(e, preset_name, parent=self.app.window) - @staticmethod - def __create_from_cue(): - cue = Application().layout.get_context_cue() + def __create_from_cue(self): + cue = self.app.layout.get_context_cue() name = save_preset_dialog(cue.name) if name is not None: @@ -90,4 +89,4 @@ def __create_from_cue(): try: write_preset(name, preset) except OSError as e: - write_preset_error(e, name, parent=MainWindow()) + write_preset_error(e, name, parent=self.app.window) diff --git a/lisp/modules/presets/presets_ui.py b/lisp/plugins/presets/presets_ui.py similarity index 96% rename from lisp/modules/presets/presets_ui.py rename to lisp/plugins/presets/presets_ui.py index eb7d4d420..589dbeed2 100644 --- a/lisp/modules/presets/presets_ui.py +++ b/lisp/plugins/presets/presets_ui.py @@ -18,20 +18,16 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QComboBox -from PyQt5.QtWidgets import QDialog, QInputDialog, QMessageBox, QListWidget, \ - QPushButton, QVBoxLayout, QGridLayout, QDialogButtonBox, QWidget, QLabel, \ - QLineEdit -from PyQt5.QtWidgets import QFileDialog -from PyQt5.QtWidgets import QHBoxLayout - -from lisp.application import Application +from PyQt5.QtWidgets import QComboBox, QDialog, QInputDialog, QMessageBox, \ + QListWidget, QPushButton, QVBoxLayout, QGridLayout, QDialogButtonBox, \ + QWidget, QLabel, QLineEdit, QFileDialog, QHBoxLayout + from lisp.core.util import natural_keys from lisp.cues.cue import Cue from lisp.cues.cue_factory import CueFactory -from lisp.modules.presets.lib import preset_exists, export_presets, \ - import_presets, import_has_conflicts, PresetExportError,\ - PresetImportError, scan_presets, delete_preset, write_preset,\ +from lisp.plugins.presets.lib import preset_exists, export_presets, \ + import_presets, import_has_conflicts, PresetExportError, \ + PresetImportError, scan_presets, delete_preset, write_preset, \ rename_preset, load_preset, load_on_cues from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.cue_settings import CueSettings, CueSettingsRegistry @@ -129,8 +125,10 @@ def save_preset_dialog(base_name=''): class PresetsDialog(QDialog): - def __init__(self, **kwargs): + def __init__(self, app, **kwargs): super().__init__(**kwargs) + self.app = app + self.resize(500, 400) self.setMaximumSize(self.size()) self.setMinimumSize(self.size()) @@ -296,7 +294,7 @@ def __cue_from_preset(self, preset_name): cue = CueFactory.create_cue(preset['_type_']) cue.update_properties(preset) - Application().cue_model.add(cue) + self.app.cue_model.add(cue) else: QMessageBox.warning( self, @@ -316,7 +314,7 @@ def __load_on_selected(self): if item is not None: preset_name = item.text() try: - cues = Application().layout.get_selected_cues() + cues = self.app.layout.get_selected_cues() if cues: load_on_cues(preset_name, cues) except OSError as e: diff --git a/lisp/modules/remote/__init__.py b/lisp/plugins/remote/__init__.py similarity index 100% rename from lisp/modules/remote/__init__.py rename to lisp/plugins/remote/__init__.py diff --git a/lisp/modules/remote/controller.py b/lisp/plugins/remote/controller.py similarity index 71% rename from lisp/modules/remote/controller.py rename to lisp/plugins/remote/controller.py index b6ee5dea0..ee537af5b 100644 --- a/lisp/modules/remote/controller.py +++ b/lisp/plugins/remote/controller.py @@ -24,9 +24,7 @@ from xmlrpc.server import SimpleXMLRPCServer from lisp.core.decorators import async -from lisp.core.singleton import Singleton -from lisp.modules.remote.discovery import Announcer -from lisp.modules.remote.dispatcher import RemoteDispatcher +from lisp.plugins.remote.dispatcher import RemoteDispatcher class TimeoutTransport(Transport): @@ -39,16 +37,13 @@ def make_connection(self, host): return HTTPConnection(host, timeout=self.timeout) -class RemoteController(metaclass=Singleton): - """ - Provide control over a SimpleXMLRPCServer. - """ +class RemoteController: + """Provide control over a SimpleXMLRPCServer.""" - def __init__(self, ip='localhost', port=8070): + def __init__(self, app, ip, port): try: - self.server = SimpleXMLRPCServer((ip, port), allow_none=True, - logRequests=False) - self._announcer = Announcer() + self.server = SimpleXMLRPCServer( + (ip, port), allow_none=True, logRequests=False) except OSError as error: # If address already in use if error.errno == 98: @@ -58,16 +53,15 @@ def __init__(self, ip='localhost', port=8070): raise error self.server.register_introspection_functions() - self.server.register_instance(RemoteDispatcher()) + self.server.register_instance(RemoteDispatcher(app)) @async def start(self): - logging.info('REMOTE: Session started at ' + - str(self.server.server_address)) + logging.info( + 'REMOTE: Session started at ' + str(self.server.server_address)) - self._announcer.start() - self.server.serve_forever() # Blocking - self._announcer.stop() + # Blocking call + self.server.serve_forever() logging.info('REMOTE: Session ended') @@ -88,5 +82,4 @@ def connect_to(uri): # Not connected, socket error mean that the service is unreachable. raise OSError('Session at ' + uri + ' is unreachable') - # Just in case the method is registered in the XmlRPC server return proxy diff --git a/lisp/plugins/remote/default.json b/lisp/plugins/remote/default.json new file mode 100644 index 000000000..c53cc83b1 --- /dev/null +++ b/lisp/plugins/remote/default.json @@ -0,0 +1,10 @@ +{ + "_version_": "1", + "_enabled_": true, + "IP": "0.0.0.0", + "Port": 8070, + "Discovery": { + "Port": 50000, + "Magic": "L1SPR3m0t3" + } +} \ No newline at end of file diff --git a/lisp/modules/remote/discovery.py b/lisp/plugins/remote/discovery.py similarity index 83% rename from lisp/modules/remote/discovery.py rename to lisp/plugins/remote/discovery.py index 949b6fe0c..ba5c30fc8 100644 --- a/lisp/modules/remote/discovery.py +++ b/lisp/plugins/remote/discovery.py @@ -20,23 +20,20 @@ import socket from threading import Thread -from lisp.core.configuration import config from lisp.core.signal import Signal -IP = config['Remote']['BindIp'] -PORT = int(config['Remote']['DiscoverPort']) -# To avoid conflicts with other applications -MAGIC = config['Remote']['DiscoverMagic'] - class Announcer(Thread): - def __init__(self): + def __init__(self, ip, port, magic): super().__init__(daemon=True) + self.magic = magic + self.address = (ip, port) + # Create UDP socket self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True) - self._socket.bind((IP, PORT)) + self._socket.bind(self.address) self._running = True @@ -45,7 +42,7 @@ def run(self): data, addr = self._socket.recvfrom(1024) if data is not None and addr is not None: data = str(data, 'utf-8') - if data == MAGIC: + if data == self.magic: data += socket.gethostname() self._socket.sendto(bytes(data, 'utf-8'), addr) @@ -61,8 +58,11 @@ def stop(self): class Discoverer(Thread): - def __init__(self): - super().__init__() + def __init__(self, ip, port, magic): + super().__init__(daemon=True) + self.magic = magic + self.address = (ip, port) + self.discovered = Signal() # Create UDP socket @@ -73,7 +73,9 @@ def __init__(self): self._cache = [] def run(self): - self._socket.sendto(bytes(MAGIC, 'utf-8'), ('', PORT)) + self._socket.sendto( + bytes(self.magic, 'utf-8'), self.address + ) while self._running: data, addr = self._socket.recvfrom(1024) @@ -82,9 +84,9 @@ def run(self): # Take only the IP, discard the port addr = addr[0] # Check if valid and new announcement - if data.startswith(MAGIC) and addr not in self._cache: + if data.startswith(self.magic) and addr not in self._cache: # Get the host name - host = data[len(MAGIC):] + host = data[len(self.magic):] # Append the adders to the cache self._cache.append(addr) # Emit diff --git a/lisp/modules/remote/dispatcher.py b/lisp/plugins/remote/dispatcher.py similarity index 84% rename from lisp/modules/remote/dispatcher.py rename to lisp/plugins/remote/dispatcher.py index 8896194d6..f4623387b 100644 --- a/lisp/modules/remote/dispatcher.py +++ b/lisp/plugins/remote/dispatcher.py @@ -17,26 +17,28 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from lisp.application import Application from lisp.cues.cue import Cue class RemoteDispatcher: # Layout functions + def __init__(self, app): + self.app = app + def get_cue_at(self, index): - cue = Application().layout.model_adapter.item(index) + cue = self.app.layout.model_adapter.item(index) if cue is not None: return cue.properties() return {} def get_cues(self, cue_class=Cue): - cues = Application().cue_model.filter(cue_class) + cues = self.app.cue_model.filter(cue_class) return [cue.properties() for cue in cues] # Cue function def execute(self, index): - cue = Application().layout.model_adapter.item(index) + cue = self.app.layout.model_adapter.item(index) if cue is not None: cue.execute() diff --git a/lisp/modules/remote/remote.py b/lisp/plugins/remote/remote.py similarity index 56% rename from lisp/modules/remote/remote.py rename to lisp/plugins/remote/remote.py index e52ba06c4..19d17f67f 100644 --- a/lisp/modules/remote/remote.py +++ b/lisp/plugins/remote/remote.py @@ -17,25 +17,35 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import socket +from lisp.core.plugin import Plugin +from lisp.plugins.remote.controller import RemoteController +from lisp.plugins.remote.discovery import Announcer -from lisp.core.configuration import config -from lisp.core.module import Module -from lisp.modules.remote.controller import RemoteController - -class Remote(Module): +class Remote(Plugin): """Provide remote control over cues via RPCs calls.""" - def __init__(self): - ip = config['Remote']['BindIp'] - port = int(config['Remote']['BindPort']) + Name = 'Remote' + Authors = ('Francesco Ceruti', ) + Description = 'Provide remote control of cues over network' + + def __init__(self, app): + super().__init__(app) + + self._controller = RemoteController( + app, Remote.Config['IP'], Remote.Config['Port']) + self._controller.start() - RemoteController(ip=ip, port=port) - RemoteController().start() + self._announcer = Announcer( + Remote.Config['IP'], + Remote.Config['Discovery']['Port'], + Remote.Config['Discovery']['Magic'] + ) + self._announcer.start() def terminate(self): - RemoteController().stop() + self._controller.stop() + self._announcer.stop() def compose_uri(url, port, directory='/'): diff --git a/lisp/modules/rename_cues/__init__.py b/lisp/plugins/rename_cues/__init__.py similarity index 100% rename from lisp/modules/rename_cues/__init__.py rename to lisp/plugins/rename_cues/__init__.py diff --git a/lisp/modules/rename_cues/rename_action.py b/lisp/plugins/rename_cues/rename_action.py similarity index 91% rename from lisp/modules/rename_cues/rename_action.py rename to lisp/plugins/rename_cues/rename_action.py index 2551cd1fc..c294d53d1 100644 --- a/lisp/modules/rename_cues/rename_action.py +++ b/lisp/plugins/rename_cues/rename_action.py @@ -18,7 +18,6 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from lisp.application import Application from lisp.core.action import Action class RenameCueAction(Action): @@ -26,15 +25,17 @@ class RenameCueAction(Action): # Store names for undo/redo in a dict like that : {'id': name} names = {} - def __init__(self, new_cue_list): + def __init__(self, app, new_cue_list): """Store new names with id""" + self.app = app + for renamed_cue in new_cue_list: self.names[renamed_cue['id']] = renamed_cue['cue_preview'] def do(self): """Use stored name and exchange with current names""" for id in self.names: - cue = Application().cue_model.get(id) + cue = self.app.cue_model.get(id) cue.name, self.names[id] = self.names[id], cue.name def undo(self): diff --git a/lisp/modules/rename_cues/rename_cues.py b/lisp/plugins/rename_cues/rename_cues.py similarity index 60% rename from lisp/modules/rename_cues/rename_cues.py rename to lisp/plugins/rename_cues/rename_cues.py index d25545814..40c1f8251 100644 --- a/lisp/modules/rename_cues/rename_cues.py +++ b/lisp/plugins/rename_cues/rename_cues.py @@ -18,44 +18,50 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtWidgets import QAction, QDialog, QMessageBox +from PyQt5.QtWidgets import QAction, QDialog +from lisp.core.actions_handler import MainActionsHandler -from lisp.application import Application -from lisp.core.module import Module -from lisp.ui.mainwindow import MainWindow +from lisp.core.plugin import Plugin +from lisp.plugins.rename_cues.rename_action import RenameCueAction from lisp.ui.ui_utils import translate from .rename_ui import RenameUi -class RenameCues(Module): +class RenameCues(Plugin): Name = 'RenameCues' + Authors = ('Aurelien Cibrario',) + Description = 'Provide a dialog for batch renaming of cues' + + def __init__(self, app): + super().__init__(app) - def __init__(self): # Entry in mainWindow menu - self.menuAction = QAction(translate('RenameCues', - 'Rename Cues'), MainWindow()) + self.menuAction = QAction( + translate('RenameCues', 'Rename Cues'), self.app.window) self.menuAction.triggered.connect(self.rename) - MainWindow().menuTools.addAction(self.menuAction) + self.app.window.menuTools.addAction(self.menuAction) def rename(self): # Test if some cues are selected, else select all cues - if Application().layout.get_selected_cues(): - selected_cues = Application().layout.get_selected_cues() + if self.app.layout.get_selected_cues(): + selected_cues = self.app.layout.get_selected_cues() else: - #TODO : implement dialog box if/when QSettings is implemented + # TODO : implement dialog box if/when QSettings is implemented # the dialog should inform the user that rename_module load only selected cues if needed # but it will bother more than being useful if we can't provide a "Don't show again" # Could be provided by QErrorMessage if QSettings is supported - selected_cues = Application().cue_model + selected_cues = self.app.cue_model # Initiate rename windows - renameUi = RenameUi(MainWindow(), selected_cues) + renameUi = RenameUi(self.app.window, selected_cues) renameUi.exec_() if renameUi.result() == QDialog.Accepted: - renameUi.record_cues_name() + MainActionsHandler.do_action( + RenameCueAction(self.app, renameUi.cues_list) + ) def terminate(self): - MainWindow().menuTools.removeAction(self.menuAction) + self.app.window.menuTools.removeAction(self.menuAction) diff --git a/lisp/modules/rename_cues/rename_ui.py b/lisp/plugins/rename_cues/rename_ui.py similarity index 86% rename from lisp/modules/rename_cues/rename_ui.py rename to lisp/plugins/rename_cues/rename_ui.py index 7c3a0abce..210dbc878 100644 --- a/lisp/modules/rename_cues/rename_ui.py +++ b/lisp/plugins/rename_cues/rename_ui.py @@ -28,12 +28,12 @@ QMessageBox from lisp.application import MainActionsHandler -from lisp.modules.rename_cues.rename_action import RenameCueAction +from lisp.plugins.rename_cues.rename_action import RenameCueAction from lisp.ui.ui_utils import translate class RenameUi(QDialog): - def __init__(self, parent=None, selected_cues=[]): + def __init__(self, parent=None, selected_cues=()): super().__init__(parent) self.setWindowTitle(translate('RenameCues', 'Rename cues')) @@ -120,9 +120,9 @@ def __init__(self, parent=None, selected_cues=[]): # This list store infos on cues with dicts - self._cues_list = [] + self.cues_list = [] for cue in selected_cues: - self._cues_list.append({ + self.cues_list.append({ 'cue_name': cue.name, 'cue_preview': cue.name, 'selected': True, @@ -131,7 +131,7 @@ def __init__(self, parent=None, selected_cues=[]): }) # Populate Preview list - for cue_to_rename in self._cues_list: + for cue_to_rename in self.cues_list: item = QTreeWidgetItem(self.previewList) item.setText(0, cue_to_rename['cue_name']) item.setText(1, cue_to_rename['cue_preview']) @@ -140,30 +140,30 @@ def __init__(self, parent=None, selected_cues=[]): def update_preview_list(self): for i in range(self.previewList.topLevelItemCount()): item = self.previewList.topLevelItem(i) - item.setText(1, self._cues_list[i]['cue_preview']) + item.setText(1, self.cues_list[i]['cue_preview']) def onPreviewListItemSelectionChanged(self): for i in range(self.previewList.topLevelItemCount()): item = self.previewList.topLevelItem(i) if item.isSelected(): - self._cues_list[i]['selected'] = True + self.cues_list[i]['selected'] = True else: - self._cues_list[i]['selected'] = False + self.cues_list[i]['selected'] = False def onCapitalizeButtonClicked(self): - for cue in self._cues_list: + for cue in self.cues_list: if cue['selected']: cue['cue_preview'] = cue['cue_preview'].title() self.update_preview_list() def onLowerButtonClicked(self): - for cue in self._cues_list: + for cue in self.cues_list: if cue['selected']: cue['cue_preview'] = cue['cue_preview'].lower() self.update_preview_list() def onUpperButtonClicked(self): - for cue in self._cues_list: + for cue in self.cues_list: if cue['selected']: cue['cue_preview'] = cue['cue_preview'].upper() self.update_preview_list() @@ -171,7 +171,7 @@ def onUpperButtonClicked(self): def onRemoveNumButtonClicked(self): regex = re.compile('^[^a-zA-Z]+(.+)') - for cue in self._cues_list: + for cue in self.cues_list: if cue['selected']: match = regex.search(cue['cue_preview']) if match is not None: @@ -181,7 +181,7 @@ def onRemoveNumButtonClicked(self): def onAddNumberingButtonClicked(self): # Extract selected rows - cues_to_modify = [cue for cue in self._cues_list if cue['selected']] + cues_to_modify = [cue for cue in self.cues_list if cue['selected']] # Count selected rows cues_nbr = len(cues_to_modify) # Calculate number of digits in order to add appropriate number of 0's @@ -194,7 +194,7 @@ def onAddNumberingButtonClicked(self): self.update_preview_list() def onResetButtonClicked(self): - for cue in self._cues_list: + for cue in self.cues_list: cue['cue_preview'] = cue['cue_name'] cue['selected'] = True @@ -202,7 +202,6 @@ def onResetButtonClicked(self): self.previewList.selectAll() def onHelpButtonClicked(self): - msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setWindowTitle(translate('RenameCues', 'Regex help')) @@ -228,7 +227,7 @@ def onRegexLineChanged(self): except re.error: logging.debug("Regex error : not a valid pattern") else: - for cue in self._cues_list: + for cue in self.cues_list: result = regex.search(cue['cue_name']) if result: cue['regex_groups'] = result.groups() @@ -239,11 +238,11 @@ def onOutRegexChanged(self): out_pattern = self.outRegexLine.text() if out_pattern == '': - for cue in self._cues_list: + for cue in self.cues_list: if cue['selected']: cue['cue_preview'] = cue['cue_name'] else: - for cue in self._cues_list: + for cue in self.cues_list: out_string = out_pattern for n in range(len(cue['regex_groups'])): pattern = f"\${n}" @@ -256,34 +255,3 @@ def onOutRegexChanged(self): cue['cue_preview'] = out_string self.update_preview_list() - - def record_cues_name(self): - MainActionsHandler.do_action( - RenameCueAction(self._cues_list) - ) - -if __name__ == "__main__": - # To test Ui quickly - from PyQt5.QtWidgets import QApplication - import sys - - # Put a few names for test - class FakeCue: - def __init__(self, name, id): - self.name = name - self.id = id - - cue1 = FakeCue('Cue Name', '3829434920') - cue2 = FakeCue('Other Name', '4934893213') - cue3 = FakeCue('Foo Bar foo bar', '4985943859') - cue4 = FakeCue('blablablabla', '9938492384') - - fake_cue_list = [cue1, cue2, cue3, cue4] - - gui_test_app = QApplication(sys.argv) - rename_ui = RenameUi(None, fake_cue_list) - - - rename_ui.show() - - sys.exit(gui_test_app.exec()) diff --git a/lisp/modules/replay_gain/__init__.py b/lisp/plugins/replay_gain/__init__.py similarity index 100% rename from lisp/modules/replay_gain/__init__.py rename to lisp/plugins/replay_gain/__init__.py diff --git a/lisp/modules/replay_gain/gain_ui.py b/lisp/plugins/replay_gain/gain_ui.py similarity index 100% rename from lisp/modules/replay_gain/gain_ui.py rename to lisp/plugins/replay_gain/gain_ui.py diff --git a/lisp/modules/replay_gain/i18n/replay_gain_cs.qm b/lisp/plugins/replay_gain/i18n/replay_gain_cs.qm similarity index 100% rename from lisp/modules/replay_gain/i18n/replay_gain_cs.qm rename to lisp/plugins/replay_gain/i18n/replay_gain_cs.qm diff --git a/lisp/modules/replay_gain/i18n/replay_gain_cs.ts b/lisp/plugins/replay_gain/i18n/replay_gain_cs.ts similarity index 100% rename from lisp/modules/replay_gain/i18n/replay_gain_cs.ts rename to lisp/plugins/replay_gain/i18n/replay_gain_cs.ts diff --git a/lisp/modules/replay_gain/i18n/replay_gain_en.qm b/lisp/plugins/replay_gain/i18n/replay_gain_en.qm similarity index 100% rename from lisp/modules/replay_gain/i18n/replay_gain_en.qm rename to lisp/plugins/replay_gain/i18n/replay_gain_en.qm diff --git a/lisp/modules/replay_gain/i18n/replay_gain_en.ts b/lisp/plugins/replay_gain/i18n/replay_gain_en.ts similarity index 100% rename from lisp/modules/replay_gain/i18n/replay_gain_en.ts rename to lisp/plugins/replay_gain/i18n/replay_gain_en.ts diff --git a/lisp/modules/replay_gain/i18n/replay_gain_es.qm b/lisp/plugins/replay_gain/i18n/replay_gain_es.qm similarity index 100% rename from lisp/modules/replay_gain/i18n/replay_gain_es.qm rename to lisp/plugins/replay_gain/i18n/replay_gain_es.qm diff --git a/lisp/modules/replay_gain/i18n/replay_gain_es.ts b/lisp/plugins/replay_gain/i18n/replay_gain_es.ts similarity index 100% rename from lisp/modules/replay_gain/i18n/replay_gain_es.ts rename to lisp/plugins/replay_gain/i18n/replay_gain_es.ts diff --git a/lisp/modules/replay_gain/i18n/replay_gain_fr.qm b/lisp/plugins/replay_gain/i18n/replay_gain_fr.qm similarity index 100% rename from lisp/modules/replay_gain/i18n/replay_gain_fr.qm rename to lisp/plugins/replay_gain/i18n/replay_gain_fr.qm diff --git a/lisp/modules/replay_gain/i18n/replay_gain_fr.ts b/lisp/plugins/replay_gain/i18n/replay_gain_fr.ts similarity index 100% rename from lisp/modules/replay_gain/i18n/replay_gain_fr.ts rename to lisp/plugins/replay_gain/i18n/replay_gain_fr.ts diff --git a/lisp/modules/replay_gain/i18n/replay_gain_it.qm b/lisp/plugins/replay_gain/i18n/replay_gain_it.qm similarity index 100% rename from lisp/modules/replay_gain/i18n/replay_gain_it.qm rename to lisp/plugins/replay_gain/i18n/replay_gain_it.qm diff --git a/lisp/modules/replay_gain/i18n/replay_gain_it.ts b/lisp/plugins/replay_gain/i18n/replay_gain_it.ts similarity index 100% rename from lisp/modules/replay_gain/i18n/replay_gain_it.ts rename to lisp/plugins/replay_gain/i18n/replay_gain_it.ts diff --git a/lisp/modules/replay_gain/i18n/replay_gain_sl_SI.qm b/lisp/plugins/replay_gain/i18n/replay_gain_sl_SI.qm similarity index 100% rename from lisp/modules/replay_gain/i18n/replay_gain_sl_SI.qm rename to lisp/plugins/replay_gain/i18n/replay_gain_sl_SI.qm diff --git a/lisp/modules/replay_gain/i18n/replay_gain_sl_SI.ts b/lisp/plugins/replay_gain/i18n/replay_gain_sl_SI.ts similarity index 100% rename from lisp/modules/replay_gain/i18n/replay_gain_sl_SI.ts rename to lisp/plugins/replay_gain/i18n/replay_gain_sl_SI.ts diff --git a/lisp/modules/replay_gain/replay_gain.py b/lisp/plugins/replay_gain/replay_gain.py similarity index 90% rename from lisp/modules/replay_gain/replay_gain.py rename to lisp/plugins/replay_gain/replay_gain.py index 111335d0b..f8e281917 100644 --- a/lisp/modules/replay_gain/replay_gain.py +++ b/lisp/plugins/replay_gain/replay_gain.py @@ -18,8 +18,8 @@ # along with Linux Show Player. If not, see . import logging -from concurrent.futures import ThreadPoolExecutor -from concurrent.futures import as_completed as futures_completed +from concurrent.futures import ThreadPoolExecutor, \ + as_completed as futures_completed from math import pow from threading import Thread, Lock @@ -31,16 +31,102 @@ from gi.repository import Gst from PyQt5.QtWidgets import QMenu, QAction, QDialog -from lisp.application import Application from lisp.core.action import Action from lisp.core.actions_handler import MainActionsHandler -from lisp.core.module import Module +from lisp.core.plugin import Plugin from lisp.core.signal import Signal, Connection from lisp.cues.media_cue import MediaCue -from lisp.ui.mainwindow import MainWindow from .gain_ui import GainUi, GainProgressDialog +class ReplayGain(Plugin): + + Name = 'ReplayGain / Normalization' + Authors = ('Francesco Ceruti', ) + Description = 'Allow to normalize cues volume' + + def __init__(self, app): + super().__init__(app) + + self._gain_thread = None + + # Entry in mainWindow menu + self.menu = QMenu(translate('ReplayGain', + 'ReplayGain / Normalization')) + self.menu_action = self.app.window.menuTools.addMenu(self.menu) + + self.actionGain = QAction(self.app.window) + self.actionGain.triggered.connect(self.gain) + self.actionGain.setText(translate('ReplayGain', 'Calculate')) + self.menu.addAction(self.actionGain) + + self.actionReset = QAction(self.app.window) + self.actionReset.triggered.connect(self._reset_all) + self.actionReset.setText(translate('ReplayGain', 'Reset all')) + self.menu.addAction(self.actionReset) + + self.actionResetSelected = QAction(self.app.window) + self.actionResetSelected.triggered.connect(self._reset_selected) + self.actionResetSelected.setText(translate('ReplayGain', + 'Reset selected')) + self.menu.addAction(self.actionResetSelected) + + def gain(self): + gainUi = GainUi(self.app.window) + gainUi.exec_() + + if gainUi.result() == QDialog.Accepted: + + files = {} + if gainUi.only_selected(): + cues = self.app.layout.get_selected_cues(MediaCue) + else: + cues = self.app.cue_model.filter(MediaCue) + + for cue in cues: + media = cue.media + uri = media.input_uri() + if uri is not None: + if uri not in files: + files[uri] = [media] + else: + files[uri].append(media) + + # Gain (main) thread + self._gain_thread = GainMainThread(files, gainUi.threads(), + gainUi.mode(), + gainUi.ref_level(), + gainUi.norm_level()) + + # Progress dialog + self._progress = GainProgressDialog(len(files)) + self._gain_thread.on_progress.connect(self._progress.on_progress, + mode=Connection.QtQueued) + + self._progress.show() + self._gain_thread.start() + + def stop(self): + if self._gain_thread is not None: + self._gain_thread.stop() + + def terminate(self): + self.stop() + self.app.window.menuTools.removeAction(self.menu_action) + + def _reset_all(self): + self._reset(self.app.cue_model.filter(MediaCue)) + + def _reset_selected(self): + self._reset(self.app.cue_model.filter(MediaCue)) + + def _reset(self, cues): + action = GainAction() + for cue in cues: + action.add_media(cue.media, 1.0) + MainActionsHandler.do_action(action) + + class GainAction(Action): __slots__ = ('__media_list', '__new_volumes', '__old_volumes') @@ -147,89 +233,6 @@ def _post_process(self, gained, gain, peak, uri): self.on_progress.emit(1) -class ReplayGain(Module): - Name = 'ReplayGain' - - def __init__(self): - self._gain_thread = None - - # Entry in mainWindow menu - self.menu = QMenu(translate('ReplayGain', - 'ReplayGain / Normalization')) - self.menu_action = MainWindow().menuTools.addMenu(self.menu) - - self.actionGain = QAction(MainWindow()) - self.actionGain.triggered.connect(self.gain) - self.actionGain.setText(translate('ReplayGain', 'Calculate')) - self.menu.addAction(self.actionGain) - - self.actionReset = QAction(MainWindow()) - self.actionReset.triggered.connect(self._reset_all) - self.actionReset.setText(translate('ReplayGain', 'Reset all')) - self.menu.addAction(self.actionReset) - - self.actionResetSelected = QAction(MainWindow()) - self.actionResetSelected.triggered.connect(self._reset_selected) - self.actionResetSelected.setText(translate('ReplayGain', - 'Reset selected')) - self.menu.addAction(self.actionResetSelected) - - def gain(self): - gainUi = GainUi(MainWindow()) - gainUi.exec_() - - if gainUi.result() == QDialog.Accepted: - - files = {} - if gainUi.only_selected(): - cues = Application().layout.get_selected_cues(MediaCue) - else: - cues = Application().cue_model.filter(MediaCue) - - for cue in cues: - media = cue.media - uri = media.input_uri() - if uri is not None: - if uri not in files: - files[uri] = [media] - else: - files[uri].append(media) - - # Gain (main) thread - self._gain_thread = GainMainThread(files, gainUi.threads(), - gainUi.mode(), - gainUi.ref_level(), - gainUi.norm_level()) - - # Progress dialog - self._progress = GainProgressDialog(len(files)) - self._gain_thread.on_progress.connect(self._progress.on_progress, - mode=Connection.QtQueued) - - self._progress.show() - self._gain_thread.start() - - def stop(self): - if self._gain_thread is not None: - self._gain_thread.stop() - - def terminate(self): - self.stop() - MainWindow().menuTools.removeAction(self.menu_action) - - def _reset_all(self): - self._reset(Application().cue_model.filter(MediaCue)) - - def _reset_selected(self): - self._reset(Application().cue_model.filter(MediaCue)) - - def _reset(self, cues): - action = GainAction() - for cue in cues: - action.add_media(cue.media, 1.0) - MainActionsHandler.do_action(action) - - class GstGain: def __init__(self, uri, ref_level): self.__lock = Lock() diff --git a/lisp/plugins/synchronizer/peers_dialog.py b/lisp/plugins/synchronizer/peers_dialog.py index 6b90df813..616d9098d 100644 --- a/lisp/plugins/synchronizer/peers_dialog.py +++ b/lisp/plugins/synchronizer/peers_dialog.py @@ -21,9 +21,9 @@ from PyQt5.QtWidgets import QDialog, QHBoxLayout, QListWidget, QVBoxLayout, \ QPushButton, QDialogButtonBox, QInputDialog, QMessageBox -from lisp.core.configuration import config +from lisp.core.configuration import AppConfig from lisp.core.util import compose_http_url -from lisp.modules.remote.remote import RemoteController +from lisp.plugins.remote.remote import RemoteController from lisp.ui import elogging from lisp.ui.ui_utils import translate from .peers_discovery_dialog import PeersDiscoveryDialog @@ -97,7 +97,7 @@ def add_peer(self): self._add_peer(ip) def _add_peer(self, ip): - port = config['Remote']['BindPort'] + port = AppConfig()['Remote']['BindPort'] uri = compose_http_url(ip, port) for peer in self.peers: diff --git a/lisp/plugins/synchronizer/peers_discovery_dialog.py b/lisp/plugins/synchronizer/peers_discovery_dialog.py index 7827018e2..5211deae7 100644 --- a/lisp/plugins/synchronizer/peers_discovery_dialog.py +++ b/lisp/plugins/synchronizer/peers_discovery_dialog.py @@ -22,7 +22,7 @@ QListWidgetItem, QDialog from lisp.core.signal import Connection -from lisp.modules.remote.discovery import Discoverer +from lisp.plugins.remote.discovery import Discoverer from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/synchronizer/synchronizer.py b/lisp/plugins/synchronizer/synchronizer.py index 5301f3d17..a5c93925c 100644 --- a/lisp/plugins/synchronizer/synchronizer.py +++ b/lisp/plugins/synchronizer/synchronizer.py @@ -18,7 +18,6 @@ # along with Linux Show Player. If not, see . import logging -import socket import traceback from PyQt5.QtWidgets import QMenu, QAction, QMessageBox @@ -33,40 +32,49 @@ class Synchronizer(Plugin): + Name = 'Synchronizer' + Authors = ('Francesco Ceruti', ) + Depends = ('Remote', ) + Description = 'Keep multiple sessions, on a network, synchronized' + + def __init__(self, app): + super().__init__(app) - def __init__(self): self.syncMenu = QMenu(translate('Synchronizer', 'Synchronization')) - self.menu_action = MainWindow().menuTools.addMenu(self.syncMenu) + self.menu_action = self.app.window.menuTools.addMenu(self.syncMenu) self.addPeerAction = QAction( - translate('Synchronizer', 'Manage connected peers'), MainWindow()) + translate('Synchronizer', 'Manage connected peers'), self.app.window) self.addPeerAction.triggered.connect(self.manage_peers) self.syncMenu.addAction(self.addPeerAction) self.showIpAction = QAction( - translate('Synchronizer', 'Show your IP'), MainWindow()) + translate('Synchronizer', 'Show your IP'), self.app.window) self.showIpAction.triggered.connect(self.show_ip) self.syncMenu.addAction(self.showIpAction) self.peers = [] self.cue_media = {} - def init(self): - Application().layout.cue_executed.connect(self.remote_execute, - mode=Connection.Async) + self.app.session_created.connect(self.session_init) + self.app.session_before_finalize.connect(self.session_reset) + + def session_init(self): + self.app.layout.cue_executed.connect( + self.remote_execute, mode=Connection.Async) + + def session_reset(self): + self.peers.clear() + self.cue_media.clear() def manage_peers(self): - manager = PeersDialog(self.peers, parent=MainWindow()) + manager = PeersDialog(self.peers, parent=self.app.window) manager.exec_() def show_ip(self): ip = translate('Synchronizer', 'Your IP is:') + ' ' + str(get_lan_ip()) - QMessageBox.information(MainWindow(), ' ', ip) - - def reset(self): - self.peers.clear() - self.cue_media.clear() + QMessageBox.information(self.app.window, ' ', ip) def remote_execute(self, cue): for peer in self.peers: diff --git a/lisp/plugins/timecode/timecode_common.py b/lisp/plugins/timecode/cue_tracker.py similarity index 50% rename from lisp/plugins/timecode/timecode_common.py rename to lisp/plugins/timecode/cue_tracker.py index fa6b87e9f..45484c912 100644 --- a/lisp/plugins/timecode/timecode_common.py +++ b/lisp/plugins/timecode/cue_tracker.py @@ -18,16 +18,13 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from enum import Enum - import logging +from enum import Enum +from threading import Lock from lisp.core.clock import Clock -from lisp.core.configuration import config from lisp.core.signal import Connection -from lisp.core.singleton import ABCSingleton from lisp.cues.cue_time import CueTime -from lisp.plugins.timecode import protocols class TcFormat(Enum): @@ -40,61 +37,57 @@ class HRCueTime(CueTime): _Clock = Clock(30) # 1000 / 30 = 33.3333 milliseconds -class TimecodeCommon(metaclass=ABCSingleton): - def __init__(self): - self.__protocol_name = config['Timecode']['protocol'] - self.__protocol = None +class TimecodeCueTracker: + def __init__(self, protocol, tc_format): + """ + :param protocol: The protocol to be use to send timecode + :type protocol: lisp.plugins.timecode.protocol.TimecodeProtocol + :param tc_format: The format in which to send timecode + :type tc_format: TcFormat + """ + + self.format = tc_format + self.__protocol = protocol self.__cue = None self.__cue_time = None self.__track = 0 - self.__format = TcFormat[config['Timecode']['format']] self.__replace_hours = False - # Load timecode protocol components - protocols.load_protocols() + self.__lock = Lock() @property def cue(self): - """Current cue, which timecode is send""" + """ + :rtype: lisp.cues.cue.Cue + """ return self.__cue - @property - def status(self): - """Returns the status of the protocol""" - return self.__protocol is not None and self.__protocol.status() - @property def protocol(self): - return self.__protocol.Name - - def init(self): - """Set timecode protocol""" - self.__protocol = protocols.get_protocol(self.__protocol_name) - - if self.__protocol.status(): - logging.info('TIMECODE: init with "{0}" protocol'.format( - self.__protocol.Name)) - else: - logging.error('TIMECODE: failed init with "{0}" protocol'.format( - self.__protocol.Name)) - - def change_protocol(self, protocol_name): - if protocol_name in protocols.list_protocols(): - self.__protocol_name = protocol_name - self.init() - - def start(self, cue): - """Initialize timecode for new cue, stop old""" - if not self.status: + """ + :rtype: lisp.plugins.timecode.protocol.TimecodeProtocol + """ + return self.__protocol + + @protocol.setter + def protocol(self, protocol): + """ + :type protocol: lisp.plugins.timecode.protocol.TimecodeProtocol + """ + with self.__lock: + self.__protocol.finalize() + self.__protocol = protocol + + def track(self, cue): + """Start tracking a new cue, untrack the current, if any""" + + if cue is self.__cue: return - # Stop the currently "running" timecode - self.stop(rcue=self.__cue) - - # Reload format settings - self.__format = TcFormat[config['Timecode']['format']] + # Stop tracking + self.untrack() # Setup new cue and options self.__cue = cue @@ -102,25 +95,30 @@ def start(self, cue): self.__replace_hours = cue.timecode['replace_hours'] self.__track = cue.timecode['track'] if self.__replace_hours else -1 + # Send a "starting" time self.send(self.__cue.current_time()) - # Start watching the new cue self.__cue_time.notify.connect(self.send, Connection.QtQueued) - def stop(self, rclient=False, rcue=False): - """Stops sending timecode""" - if self.__cue_time is not None: + def untrack(self): + """Stop tracking the current cue""" + if self.__cue is not None: self.__cue_time.notify.disconnect(self.send) - if rcue: - self.__cue = None self.__cue_time = None - - if self.status: - self.__protocol.stop(rclient) + self.__cue = None def send(self, time): - """Send timecode""" - if not self.__protocol.send(self.__format, time, self.__track): - logging.error('TIMECODE: cannot send timecode, stopping') - self.stop() + """Send time as timecode""" + if self.__lock.acquire(blocking=False): + try: + if not self.__protocol.send(self.format, time, self.__track): + logging.error( + 'TIMECODE: cannot send timecode, untracking cue') + self.untrack() + except Exception: + self.__lock.release() + + def finalize(self): + self.untrack() + self.protocol = None diff --git a/lisp/plugins/timecode/default.json b/lisp/plugins/timecode/default.json new file mode 100644 index 000000000..8f725c103 --- /dev/null +++ b/lisp/plugins/timecode/default.json @@ -0,0 +1,6 @@ +{ + "_version_": "1", + "_enabled_": true, + "Format": "SMPTE", + "Protocol": "MIDI" +} \ No newline at end of file diff --git a/lisp/plugins/timecode/timecode_protocol.py b/lisp/plugins/timecode/protocol.py similarity index 79% rename from lisp/plugins/timecode/timecode_protocol.py rename to lisp/plugins/timecode/protocol.py index 575fe3d6e..af6c3b347 100644 --- a/lisp/plugins/timecode/timecode_protocol.py +++ b/lisp/plugins/timecode/protocol.py @@ -18,22 +18,14 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from abc import abstractmethod -from abc import ABCMeta - -class TimecodeProtocol(metaclass=ABCMeta): +class TimecodeProtocol: """Base class for timecode protocols""" Name = 'None' - @abstractmethod - def status(self): - """Return the status of the backend, True if ready""" - - @abstractmethod def send(self, fmt, time, track=-1): """Send timecode, returns success for error handling""" + return True - @abstractmethod - def stop(self, rclient=False): + def finalize(self): """Cleanup after client has stopped""" diff --git a/lisp/plugins/timecode/protocols/__init__.py b/lisp/plugins/timecode/protocols/__init__.py index 07044e13c..efb097224 100644 --- a/lisp/plugins/timecode/protocols/__init__.py +++ b/lisp/plugins/timecode/protocols/__init__.py @@ -33,11 +33,10 @@ def get_protocol(name): """ :param name: protocol name :type name: str - :return: instance of a TimecodeProtocol - :rtype: TimecodeProtocol + :rtype: type[TimecodeProtocol] """ - if name in __PROTOCOLS and callable(__PROTOCOLS[name]): - return __PROTOCOLS[name]() + if name in __PROTOCOLS: + return __PROTOCOLS[name] else: raise AttributeError('Timecode-Protocol not found', name) @@ -47,4 +46,4 @@ def list_protocols(): :return: list of protocol names :rtype: list """ - return __PROTOCOLS + return list(__PROTOCOLS.keys()) diff --git a/lisp/plugins/timecode/protocols/artnet.py b/lisp/plugins/timecode/protocols/artnet.py index 5642812d9..44be63219 100644 --- a/lisp/plugins/timecode/protocols/artnet.py +++ b/lisp/plugins/timecode/protocols/artnet.py @@ -23,8 +23,8 @@ from ola.OlaClient import OlaClient, OLADNotRunningException from lisp.core.util import time_tuple -from lisp.plugins.timecode.timecode_common import TcFormat -from lisp.plugins.timecode.timecode_protocol import TimecodeProtocol +from lisp.plugins.timecode.cue_tracker import TcFormat +from lisp.plugins.timecode.protocol import TimecodeProtocol from lisp.ui import elogging from lisp.ui.ui_utils import translate @@ -44,15 +44,10 @@ def __init__(self): try: self.__client = OlaClient() except OLADNotRunningException as e: - self.__client = None - logging.error('TIMECODE: cannot create OlaClient') - logging.debug('TIMECODE: {}'.format(e)) + raise e self.__last_time = -1 - def status(self): - return bool(self.__client) - def send(self, fmt, time, track=-1): if self.__last_time + fmt.value >= time: return @@ -78,8 +73,6 @@ def send(self, fmt, time, track=-1): self.__last_time = time return True - def stop(self, rclient=False): + def finalize(self): self.__last_time = -1 - - if rclient: - self.__client = None + self.__client = None diff --git a/lisp/plugins/timecode/protocols/midi.py b/lisp/plugins/timecode/protocols/midi.py index 324d54cc8..05901179b 100644 --- a/lisp/plugins/timecode/protocols/midi.py +++ b/lisp/plugins/timecode/protocols/midi.py @@ -21,10 +21,9 @@ from mido import Message from lisp.core.util import time_tuple -from lisp.modules.midi.midi_output import MIDIOutput -from lisp.plugins.timecode.timecode_common import TcFormat -from lisp.plugins.timecode.timecode_protocol import TimecodeProtocol - +from lisp.plugins import get_plugin +from lisp.plugins.timecode.cue_tracker import TcFormat +from lisp.plugins.timecode.protocol import TimecodeProtocol MIDI_FORMATS = { TcFormat.FILM: 0, @@ -52,29 +51,37 @@ def __init__(self): super().__init__() self.__last_time = -1 self.__last_frame = -1 - if not MIDIOutput().is_open(): - MIDIOutput().open() + self.__midi = None - def status(self): - return MIDIOutput().is_open() + try: + self.__midi = get_plugin('Midi') + except Exception: + raise RuntimeError('Midi plugin is not available') def __send_full(self, fmt, hours, minutes, seconds, frame): """Sends fullframe timecode message. Used in case timecode is non continuous (repositioning, rewind). """ - hh = (MIDI_FORMATS[fmt] << 5) + hours - msg = Message('sysex', data=[0x7f, 0x7f, 0x01, 0x01, hh, minutes, - seconds, frame]) - MIDIOutput().send(msg) + message = Message( + 'sysex', data=[ + 0x7f, 0x7f, 0x01, 0x01, + (MIDI_FORMATS[fmt] << 5) + hours, minutes, seconds, frame + ] + ) + + if self.__midi is not None: + self.__midi.output.send(message) def __send_quarter(self, frame_type, fmt, hours, minutes, seconds, frame): """Send quarterframe midi message.""" - msg = Message('quarter_frame') - msg.frame_type = frame_type - msg.frame_value = FRAME_VALUES[frame_type]( + messsage = Message('quarter_frame') + messsage.frame_type = frame_type + messsage.frame_value = FRAME_VALUES[frame_type]( hours, minutes, seconds, frame, MIDI_FORMATS[fmt]) - MIDIOutput().send(msg) + + if self.__midi is not None: + self.__midi.send(messsage) def send(self, format, time, track=-1): # Split the time in its components @@ -110,6 +117,6 @@ def send(self, format, time, track=-1): return True - def stop(self, rclient=False): + def finalize(self): self.__last_time = -1 self.__last_frame = -1 diff --git a/lisp/plugins/timecode/timecode_settings.py b/lisp/plugins/timecode/settings.py similarity index 76% rename from lisp/plugins/timecode/timecode_settings.py rename to lisp/plugins/timecode/settings.py index 1a5c3a015..9f4fcde89 100644 --- a/lisp/plugins/timecode/timecode_settings.py +++ b/lisp/plugins/timecode/settings.py @@ -20,18 +20,13 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt -from PyQt5.QtWidgets import QGridLayout -from PyQt5.QtWidgets import QMessageBox -from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel, \ - QCheckBox, QSpinBox, QComboBox +from PyQt5.QtWidgets import QGridLayout, QVBoxLayout, QGroupBox, \ + QLabel, QCheckBox, QSpinBox, QComboBox -from lisp.ui.ui_utils import translate -from lisp.core.configuration import config -from lisp.plugins.timecode.timecode_common import TcFormat from lisp.plugins.timecode import protocols -from lisp.ui.settings.settings_page import SettingsPage -from lisp.ui.settings.settings_page import CueSettingsPage -from lisp.plugins.timecode.timecode_common import TimecodeCommon +from lisp.plugins.timecode.cue_tracker import TcFormat +from lisp.ui.settings.settings_page import SettingsPage, CueSettingsPage +from lisp.ui.ui_utils import translate class TimecodeSettings(CueSettingsPage): @@ -85,12 +80,6 @@ def retranslateUi(self): self.trackLabel.setText( translate('TimecodeSettings', 'Track number')) - if 'artnet' in config['Timecode']['protocol'].lower(): - self.warnLabel.setText( - translate('TimecodeSettings', - 'To send ArtNet Timecode you need to setup a running' - 'OLA session!')) - def get_settings(self): return {'timecode': { 'enabled': self.enableCheck.isChecked(), @@ -144,25 +133,12 @@ def retranslateUi(self): self.protocolLabel.setText( translate('TimecodeSettings', 'Timecode Protocol:')) - def __protocol_changed(self, protocol): - # check for restart - TimecodeCommon().change_protocol(protocol) - - if not TimecodeCommon().status: - QMessageBox.critical( - self, translate('TimecodeSettings', 'Error'), - translate('TimecodeSettings', - 'Cannot enable "{}" protocol').format(protocol)) - def get_settings(self): - return {'Timecode': { - 'format': self.formatBox.currentText(), - 'protocol': self.protocolCombo.currentText() - }} + return { + 'Format': self.formatBox.currentText(), + 'Protocol': self.protocolCombo.currentText() + } def load_settings(self, settings): - settings = settings.get('Timecode', {}) - - self.formatBox.setCurrentText(settings.get('format', '')) - self.protocolCombo.setCurrentText(settings.get('protocol', '')) - self.protocolCombo.currentTextChanged.connect(self.__protocol_changed) + self.formatBox.setCurrentText(settings.get('Format', '')) + self.protocolCombo.setCurrentText(settings.get('Protocol', '')) diff --git a/lisp/plugins/timecode/timecode.py b/lisp/plugins/timecode/timecode.py index 3cb161469..01bae63c5 100644 --- a/lisp/plugins/timecode/timecode.py +++ b/lisp/plugins/timecode/timecode.py @@ -19,68 +19,94 @@ # along with Linux Show Player. If not, see . -from lisp.application import Application from lisp.core.has_properties import Property from lisp.core.plugin import Plugin from lisp.core.signal import Connection from lisp.cues.cue import Cue from lisp.cues.media_cue import MediaCue -from lisp.ui.settings.cue_settings import CueSettingsRegistry +from lisp.plugins.timecode import protocols +from lisp.plugins.timecode.cue_tracker import TimecodeCueTracker, TcFormat +from lisp.plugins.timecode.protocol import TimecodeProtocol +from lisp.plugins.timecode.settings import TimecodeAppSettings, \ + TimecodeSettings from lisp.ui.settings.app_settings import AppSettings -from lisp.plugins.timecode.timecode_common import TimecodeCommon -from lisp.plugins.timecode.timecode_settings import TimecodeAppSettings, TimecodeSettings +from lisp.ui.settings.cue_settings import CueSettingsRegistry class Timecode(Plugin): + Name = 'Timecode' + Authors = ('Thomas Achtner', ) + OptDepends = ('Midi', ) + Description = 'Provide timecode via multiple protocols' - def __init__(self): - super().__init__() - self.__cues = set() + TC_PROPERTY = 'timecode' + + def __init__(self, app): + super().__init__(app) # Register a new Cue property to store settings - Cue.register_property('timecode', Property(default={})) + Cue.register_property(Timecode.TC_PROPERTY, Property(default={})) # Register cue-settings-page CueSettingsRegistry().add_item(TimecodeSettings, MediaCue) # Register the settings widget - AppSettings.register_settings_widget(TimecodeAppSettings) + AppSettings.register_settings_widget( + TimecodeAppSettings, Timecode.Config) + + # Load available protocols + protocols.load_protocols() + + try: + protocol = protocols.get_protocol(Timecode.Config['Protocol'])() + except Exception: + # TODO: warn the user + # Use a dummy protocol in case of failure + protocol = TimecodeProtocol() + + # Create the cue tracker object + self.__cue_tracker = TimecodeCueTracker( + protocol, + TcFormat[Timecode.Config['Format']] + ) + + # Cues with timecode-tracking enabled + self.__cues = set() + + # Watch for session finalization(s) + self.app.session_before_finalize.connect(self.__session_finalize) # Watch cue-model changes - Application().cue_model.item_added.connect(self.__cue_added) - Application().cue_model.item_removed.connect(self.__cue_removed) + self.app.cue_model.item_added.connect(self.__cue_added) + self.app.cue_model.item_removed.connect(self.__cue_removed) - def init(self): - TimecodeCommon().init() + def finalize(self): + self.__cue_tracker.finalize() - def reset(self): + def __session_finalize(self): + self.__cue_tracker.untrack() self.__cues.clear() - TimecodeCommon().stop(rclient=True, rcue=True) def __cue_changed(self, cue, property_name, value): - if property_name == 'timecode': + if property_name == Timecode.TC_PROPERTY: if value.get('enabled', False): - if cue.id not in self.__cues: - self.__cues.add(cue.id) - cue.started.connect(TimecodeCommon().start, - Connection.QtQueued) + cue.started.connect( + self.__cue_tracker.track, Connection.QtQueued) else: - self.__cue_removed(cue) + self.__disable_on_cue(cue) def __cue_added(self, cue): cue.property_changed.connect(self.__cue_changed) - self.__cue_changed(cue, 'timecode', cue.timecode) + # Check for current cue settings + self.__cue_changed(cue, Timecode.TC_PROPERTY, cue.timecode) def __cue_removed(self, cue): - try: - # Try removing the cue - self.__cues.remove(cue.id) - - # If the cue is tracked, stop the tracking - if TimecodeCommon().cue is cue: - TimecodeCommon().stop(rcue=True) - - # Disconnect signals - cue.started.disconnect(TimecodeCommon().start) - cue.property_changed.disconnect(self.__cue_changed) - except KeyError: - pass + cue.property_changed.disconnect(self.__cue_changed) + self.__disable_on_cue(cue) + + def __disable_on_cue(self, cue): + # If it's not connected this does nothing + cue.started.disconnect(self.__cue_tracker.track) + + # If the cue is tracked, stop the tracking + if self.__cue_tracker.cue is cue: + self.__cue_tracker.untrack() diff --git a/lisp/plugins/triggers/triggers.py b/lisp/plugins/triggers/triggers.py index 0c9e0ecae..4538a5046 100644 --- a/lisp/plugins/triggers/triggers.py +++ b/lisp/plugins/triggers/triggers.py @@ -17,7 +17,6 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from lisp.application import Application from lisp.core.has_properties import Property from lisp.core.plugin import Plugin from lisp.cues.cue import Cue @@ -27,10 +26,14 @@ class Triggers(Plugin): + Name = 'Triggers' + Authors = ('Francesco Ceruti', ) + Description = 'Allow cues to react to other-cues state changes' + + def __init__(self, app): + super().__init__(app) - def __init__(self): - super().__init__() self.__handlers = {} # Register a Cue property to store settings @@ -40,10 +43,13 @@ def __init__(self): # Register SettingsPage CueSettingsRegistry().add_item(TriggersSettings) - Application().cue_model.item_added.connect(self.__cue_added) - Application().cue_model.item_removed.connect(self.__cue_removed) + # On session destroy + self.app.session_before_finalize.connect(self.session_reset) + + self.app.cue_model.item_added.connect(self.__cue_added) + self.app.cue_model.item_removed.connect(self.__cue_removed) - def reset(self): + def session_reset(self): self.__handlers.clear() def __cue_changed(self, cue, property_name, value): @@ -51,7 +57,7 @@ def __cue_changed(self, cue, property_name, value): if cue.id in self.__handlers: self.__handlers[cue.id].triggers = cue.triggers else: - self.__handlers[cue.id] = CueHandler(cue, cue.triggers) + self.__handlers[cue.id] = CueHandler(self.app, cue, cue.triggers) def __cue_added(self, cue): cue.property_changed.connect(self.__cue_changed) diff --git a/lisp/plugins/triggers/triggers_handler.py b/lisp/plugins/triggers/triggers_handler.py index 481cf12b8..b27964111 100644 --- a/lisp/plugins/triggers/triggers_handler.py +++ b/lisp/plugins/triggers/triggers_handler.py @@ -21,7 +21,6 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP -from lisp.application import Application from lisp.core.signal import Connection from lisp.cues.cue import CueAction @@ -34,7 +33,8 @@ class CueTriggers(Enum): class CueHandler: - def __init__(self, cue, triggers): + def __init__(self, app, cue, triggers): + self.app = app self.triggers = triggers self.cue = cue @@ -57,7 +57,7 @@ def __ended(self): def __execute(self, trigger): for target_id, action in self.triggers.get(trigger, []): - target = Application().cue_model.get(target_id) + target = self.app.cue_model.get(target_id) if target is not None: target.execute(CueAction(action)) diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 502259181..92be54788 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -25,8 +25,8 @@ from PyQt5.QtWidgets import QMainWindow, QStatusBar, QMenuBar, QMenu, QAction, \ qApp, QFileDialog, QDialog, QMessageBox, QVBoxLayout, QWidget -from lisp.core import configuration from lisp.core.actions_handler import MainActionsHandler +from lisp.core.configuration import AppConfig from lisp.core.singleton import QSingleton from lisp.cues.media_cue import MediaCue from lisp.ui import about @@ -39,9 +39,7 @@ class MainWindow(QMainWindow, metaclass=QSingleton): save_session = pyqtSignal(str) open_session = pyqtSignal(str) - TITLE = 'Linux Show Player' - - def __init__(self): + def __init__(self, title='Linux Show Player'): super().__init__() self.setMinimumSize(500, 400) self.setCentralWidget(QWidget()) @@ -49,6 +47,7 @@ def __init__(self): self.centralWidget().layout().setContentsMargins(5, 5, 5, 5) self._cue_add_menu = {} + self._title = title self.session = None # Status Bar @@ -141,11 +140,9 @@ def __init__(self): # Set component text self.retranslateUi() - # The save file name - self.filename = '' def retranslateUi(self): - self.setWindowTitle(MainWindow.TITLE) + self.setWindowTitle(self._title) # menuFile self.menuFile.setTitle(translate('MainWindow', '&File')) self.newSessionAction.setText(translate('MainWindow', 'New session')) @@ -218,13 +215,14 @@ def closeEvent(self, event): def register_cue_menu_action(self, name, function, category='', shortcut=''): - '''Register a new-cue choice for the edit-menu + """Register a new-cue choice for the edit-menu param name: The name for the MenuAction param function: The function that add the new cue(s) param category: The optional menu where insert the MenuAction param shortcut: An optional shortcut for the MenuAction - ''' + """ + action = QAction(self) action.setText(translate('MainWindow', name)) action.triggered.connect(function) @@ -242,7 +240,7 @@ def register_cue_menu_action(self, name, function, category='', self.menuEdit.insertAction(self.cueSeparator, action) def update_window_title(self): - tile = MainWindow.TITLE + ' - ' + self.session.name() + tile = self._title + ' - ' + self.session.name() if not MainActionsHandler.is_saved(): tile = '*' + tile @@ -279,12 +277,9 @@ def _save_with_name(self): self.save_session.emit(filename) def _show_preferences(self): - prefUi = AppSettings(configuration.config_to_dict(), parent=self) + prefUi = AppSettings(parent=self) prefUi.exec_() - if prefUi.result() == QDialog.Accepted: - configuration.update_config_from_dict(prefUi.get_configuraton()) - def _load_from_file(self): if self._check_saved(): path, _ = QFileDialog.getOpenFileName(self, filter='*.lsp', @@ -292,7 +287,6 @@ def _load_from_file(self): if os.path.exists(path): self.open_session.emit(path) - self.filename = path def _new_session(self): if self._check_saved(): diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index b2b864417..c593a3e50 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -23,10 +23,10 @@ from lisp.application import Application from lisp.cues.cue import CueAction +from lisp.plugins.osc.osc_server import OscMessageType from lisp.ui.qmodels import CueClassRole from lisp.ui.ui_utils import translate from lisp.ui.widgets import CueActionComboBox -from lisp.modules.osc.osc_common import OscMessageType class LabelDelegate(QStyledItemDelegate): diff --git a/lisp/ui/settings/app_settings.py b/lisp/ui/settings/app_settings.py index 45827f9c8..d4db0cb79 100644 --- a/lisp/ui/settings/app_settings.py +++ b/lisp/ui/settings/app_settings.py @@ -21,18 +21,15 @@ from PyQt5.QtWidgets import QDialog, QListWidget, QStackedWidget, \ QDialogButtonBox -from lisp.core.util import deep_update from lisp.ui.ui_utils import translate class AppSettings(QDialog): - SettingsWidgets = [] + SettingsWidgets = {} - def __init__(self, conf, **kwargs): + def __init__(self, **kwargs): super().__init__(**kwargs) - - self.conf = conf self.setWindowTitle(translate('AppSettings', 'LiSP preferences')) self.setWindowModality(QtCore.Qt.ApplicationModal) @@ -46,10 +43,10 @@ def __init__(self, conf, **kwargs): self.sections = QStackedWidget(self) self.sections.setGeometry(QtCore.QRect(200, 10, 430, 470)) - for widget in self.SettingsWidgets: + for widget, config in self.SettingsWidgets.items(): widget = widget(parent=self) widget.resize(430, 465) - widget.load_settings(self.conf) + widget.load_settings(config.copy()) self.listWidget.addItem(translate('SettingsPageName', widget.Name)) self.sections.addWidget(widget) @@ -67,25 +64,28 @@ def __init__(self, conf, **kwargs): self.dialogButtons.rejected.connect(self.reject) self.dialogButtons.accepted.connect(self.accept) - def get_configuraton(self): - conf = {} - + def accept(self): for n in range(self.sections.count()): widget = self.sections.widget(n) - newconf = widget.get_settings() - deep_update(conf, newconf) - return conf + config = AppSettings.SettingsWidgets[widget.__class__] + config.update(widget.get_settings()) + config.write() + + return super().accept() @classmethod - def register_settings_widget(cls, widget): + def register_settings_widget(cls, widget, configuration): + """ + :type widget: Type[lisp.ui.settings.settings_page.SettingsPage] + :type configuration: lisp.core.configuration.Configuration + """ if widget not in cls.SettingsWidgets: - cls.SettingsWidgets.append(widget) + cls.SettingsWidgets[widget] = configuration @classmethod def unregister_settings_widget(cls, widget): - if widget in cls.SettingsWidgets: - cls.SettingsWidgets.remove(widget) + cls.SettingsWidgets.pop(widget, None) def _change_page(self, current, previous): if not current: diff --git a/lisp/ui/settings/pages/app_general.py b/lisp/ui/settings/pages/app_general.py index 5c23bf60c..5b616d33b 100644 --- a/lisp/ui/settings/pages/app_general.py +++ b/lisp/ui/settings/pages/app_general.py @@ -68,21 +68,20 @@ def get_settings(self): conf = {'Layout': {}, 'Theme': {}} if self.startupDialogCheck.isChecked(): - conf['Layout']['default'] = 'NoDefault' + conf['Layout']['Default'] = 'NoDefault' else: - conf['Layout']['default'] = self.layoutCombo.currentText() + conf['Layout']['Default'] = self.layoutCombo.currentText() - conf['Theme']['theme'] = self.themeCombo.currentText() + conf['Theme']['Theme'] = self.themeCombo.currentText() styles.apply_style(self.themeCombo.currentText()) return conf def load_settings(self, settings): - if 'default' in settings['Layout']: - if settings['Layout']['default'].lower() == 'nodefault': - self.startupDialogCheck.setChecked(True) - self.layoutCombo.setEnabled(False) - else: - self.layoutCombo.setCurrentText(settings['Layout']['default']) - if 'theme' in settings['Theme']: - self.themeCombo.setCurrentText(settings['Theme']['theme']) + if settings['Layout']['Default'].lower() == 'nodefault': + self.startupDialogCheck.setChecked(True) + self.layoutCombo.setEnabled(False) + else: + self.layoutCombo.setCurrentText(settings['Layout']['Default']) + + self.themeCombo.setCurrentText(settings['Theme']['Theme']) diff --git a/lisp/ui/settings/pages/cue_app_settings.py b/lisp/ui/settings/pages/cue_app_settings.py index d0584a979..52d8b9a2a 100644 --- a/lisp/ui/settings/pages/cue_app_settings.py +++ b/lisp/ui/settings/pages/cue_app_settings.py @@ -57,21 +57,17 @@ def retranslateUi(self): def load_settings(self, settings): # Interrupt - self.interruptFadeEdit.setDuration( - float(settings['Cue'].get('interruptfade', 0))) - self.interruptFadeEdit.setFadeType( - settings['Cue'].get('interruptfadetype', '')) + self.interruptFadeEdit.setDuration(settings['Cue']['InterruptFade']) + self.interruptFadeEdit.setFadeType(settings['Cue']['InterruptFadeType']) # FadeAction - self.fadeActionEdit.setDuration( - float(settings['Cue'].get('fadeactionduration', 0))) - self.fadeActionEdit.setFadeType( - settings['Cue'].get('fadeactiontype', '')) + self.fadeActionEdit.setDuration(settings['Cue']['FadeActionDuration']) + self.fadeActionEdit.setFadeType(settings['Cue']['FadeActionType']) def get_settings(self): return {'Cue': { - 'interruptfade': str(self.interruptFadeEdit.duration()), - 'interruptfadetype': self.interruptFadeEdit.fadeType(), - 'fadeactionduration': str(self.fadeActionEdit.duration()), - 'fadeactiontype': self.fadeActionEdit.fadeType() + 'InterruptFade': self.interruptFadeEdit.duration(), + 'InterruptFadeType': self.interruptFadeEdit.fadeType(), + 'FadeActionDuration': self.fadeActionEdit.duration(), + 'FadeActionType': self.fadeActionEdit.fadeType() }} diff --git a/lisp/ui/settings/pages/cue_appearance.py b/lisp/ui/settings/pages/cue_appearance.py index 923a8ead1..cbfd2782e 100644 --- a/lisp/ui/settings/pages/cue_appearance.py +++ b/lisp/ui/settings/pages/cue_appearance.py @@ -82,8 +82,7 @@ def __init__(self, **kwargs): def retranslateUi(self): self.cueNameGroup.setTitle( translate('CueAppearanceSettings', 'Cue name')) - self.cueNameEdit.setText(translate('CueAppearanceSettings', - 'NoName')) + self.cueNameEdit.setText(translate('CueAppearanceSettings', 'NoName')) self.cueDescriptionGroup.setTitle( translate('CueAppearanceSettings', 'Description/Note')) self.fontSizeGroup.setTitle( diff --git a/lisp/ui/settings/pages/plugins_settings.py b/lisp/ui/settings/pages/plugins_settings.py new file mode 100644 index 000000000..d0cfcbc65 --- /dev/null +++ b/lisp/ui/settings/pages/plugins_settings.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2017 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt, QSize +from PyQt5.QtGui import QIcon +from PyQt5.QtWidgets import QListWidget, QListWidgetItem, \ + QTextBrowser, QVBoxLayout + +from lisp import plugins +from lisp.ui.settings.settings_page import SettingsPage + + +# TODO: just a proof-of concept +class PluginsSettings(SettingsPage): + Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Plugins') + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.setLayout(QVBoxLayout()) + self.layout().setAlignment(Qt.AlignTop) + + self.pluginsList = QListWidget(self) + self.pluginsList.setIconSize(QSize(12, 12)) + self.pluginsList.setAlternatingRowColors(True) + self.pluginsList.setSortingEnabled(True) + self.pluginsList.setSelectionMode(QListWidget.SingleSelection) + self.pluginsList.itemSelectionChanged.connect(self.__selection_changed) + self.layout().addWidget(self.pluginsList) + + for name, plugin in plugins.PLUGINS.items(): + item = QListWidgetItem(plugin.Name) + if plugins.is_loaded(name): + item.setIcon(QIcon().fromTheme('led-running')) + elif not plugin.Config['_enabled_']: + item.setIcon(QIcon().fromTheme('led-pause')) + else: + item.setIcon(QIcon().fromTheme('led-error')) + + item.setData(Qt.UserRole, plugin) + + self.pluginsList.addItem(item) + + # Plugin description + self.pluginDescription = QTextBrowser(self) + self.layout().addWidget(self.pluginDescription) + + self.layout().setStretch(0, 3) + self.layout().setStretch(1, 1) + + self.__selection_changed() + + def __selection_changed(self): + item = self.pluginsList.currentItem() + + if item is not None: + plugin = item.data(Qt.UserRole) + html = 'Description: {}'.format(plugin.Description) + html += '

' + html += 'Authors: {}'.format(', '.join(plugin.Authors)) + + self.pluginDescription.setHtml(html) + else: + self.pluginDescription.setHtml( + 'Description:

Authors: ') diff --git a/lisp/ui/ui_utils.py b/lisp/ui/ui_utils.py index c718b339a..88462dfab 100644 --- a/lisp/ui/ui_utils.py +++ b/lisp/ui/ui_utils.py @@ -16,13 +16,19 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging from itertools import chain +from os import path +from PyQt5.QtCore import QTranslator, QLocale from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QApplication +import lisp from lisp.core.decorators import memoize +DEFAULT_I18N_PATH = path.join(path.dirname(lisp.__file__), 'i18n') +TRANSLATORS = [] @memoize def load_icon(icon_name): @@ -79,6 +85,18 @@ def qfile_filters(extensions, allexts=True, anyfile=True): return ';;'.join(filters) +def install_translation(name, tr_path=DEFAULT_I18N_PATH): + tr_file = path.join(tr_path, name) + + translator = QTranslator() + translator.load(QLocale(), tr_file, '_') + + TRANSLATORS.append(translator) + + if QApplication.installTranslator(translator): + logging.debug('Installed translation: {}'.format(tr_file)) + + def translate(context, text, disambiguation=None, n=-1): return QApplication.translate(context, text, disambiguation, n) diff --git a/lisp/ui/widgets/qdbmeter.py b/lisp/ui/widgets/qdbmeter.py index ae814d652..f84cfd199 100644 --- a/lisp/ui/widgets/qdbmeter.py +++ b/lisp/ui/widgets/qdbmeter.py @@ -21,21 +21,20 @@ from PyQt5.QtGui import QLinearGradient, QColor, QPainter from PyQt5.QtWidgets import QWidget -from lisp.core.configuration import config from lisp.core.decorators import suppress_exceptions class QDbMeter(QWidget): - DB_MIN = int(config["DbMeter"]["dbMin"]) - DB_MAX = int(config["DbMeter"]["dbMax"]) - DB_CLIP = int(config["DbMeter"]["dbClip"]) - def __init__(self, parent): + def __init__(self, parent, min=-60, max=0, clip=0): super().__init__(parent) + self.db_min = min + self.db_max = max + self.db_clip = clip - db_range = abs(self.DB_MIN - self.DB_MAX) - yellow = abs(self.DB_MIN + 20) / db_range # -20 db - red = abs(self.DB_MIN) / db_range # 0 db + db_range = abs(self.db_min - self.db_max) + yellow = abs(self.db_min + 20) / db_range # -20 db + red = abs(self.db_min) / db_range # 0 db self.grad = QLinearGradient() self.grad.setColorAt(0, QColor(0, 255, 0)) # Green @@ -45,9 +44,9 @@ def __init__(self, parent): self.reset() def reset(self): - self.peaks = [self.DB_MIN, self.DB_MIN] - self.rmss = [self.DB_MIN, self.DB_MIN] - self.decPeak = [self.DB_MIN, self.DB_MIN] + self.peaks = [self.db_min, self.db_min] + self.rmss = [self.db_min, self.db_min] + self.decPeak = [self.db_min, self.db_min] self.clipping = {} self.repaint() @@ -63,37 +62,37 @@ def paintEvent(self, e): if not self.visibleRegion().isEmpty(): # Stretch factor mul = (self.height() - 4) - mul /= (self.DB_MAX - self.DB_MIN) + mul /= (self.db_max - self.db_min) peaks = [] for n, peak in enumerate(self.peaks): - if peak > self.DB_CLIP: + if peak > self.db_clip: self.clipping[n] = True - if peak < self.DB_MIN: - peak = self.DB_MIN - elif peak > self.DB_MAX: - peak = self.DB_MAX + if peak < self.db_min: + peak = self.db_min + elif peak > self.db_max: + peak = self.db_max - peaks.append(round((peak - self.DB_MIN) * mul)) + peaks.append(round((peak - self.db_min) * mul)) rmss = [] for n, rms in enumerate(self.rmss): - if rms < self.DB_MIN: - rms = self.DB_MIN - elif rms > self.DB_MAX: - rms = self.DB_MAX + if rms < self.db_min: + rms = self.db_min + elif rms > self.db_max: + rms = self.db_max - rmss.append(round((rms - self.DB_MIN) * mul)) + rmss.append(round((rms - self.db_min) * mul)) dPeaks = [] for dPeak in self.decPeak: - if dPeak < self.DB_MIN: - dPeak = self.DB_MIN - elif dPeak > self.DB_MAX: - dPeak = self.DB_MAX + if dPeak < self.db_min: + dPeak = self.db_min + elif dPeak > self.db_max: + dPeak = self.db_max - dPeaks.append(round((dPeak - self.DB_MIN) * mul)) + dPeaks.append(round((dPeak - self.db_min) * mul)) qp = QPainter() qp.begin(self) @@ -103,7 +102,7 @@ def paintEvent(self, e): xdim = self.width() / len(peaks) for n, (peak, rms, dPeak) in enumerate(zip(peaks, rmss, dPeaks)): - # Maximum "peak-rect" size + # Maximum 'peak-rect' size maxRect = QtCore.QRect(xpos, self.height() - 2, xdim - 2, 2 - self.height()) diff --git a/plugins_utils.py b/plugins_utils.py new file mode 100755 index 000000000..12a82c51b --- /dev/null +++ b/plugins_utils.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2017 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import argparse +import os +import sys +from shutil import copyfile + +from lisp.core.loading import module_to_class_name + +parser = argparse.ArgumentParser() +parser.add_argument('name', help='Name of the new plugin') + +args = parser.parse_args() + +PLUGINS_DIRECTORY = 'lisp/plugins' + + +def create_plugin(name: str): + print('>>> CREATE NEW PLUGIN: {}'.format(name)) + + if not name.isidentifier(): + print('Invalid plugin name!', file=sys.stderr) + sys.exit(-1) + + plugin_path = os.path.join(PLUGINS_DIRECTORY, name) + class_name = module_to_class_name(name) + + os.makedirs(plugin_path) + print('>>> DIRECTORY {} CREATED'.format(plugin_path)) + + print('>>> CREATE DEFAULT SETTINGS FILE') + copyfile(os.path.join(PLUGINS_DIRECTORY, 'default.json'), + os.path.join(plugin_path, name + '.json')) + + print('>>> CREATE DEFAULT INIT FILE') + with open(os.path.join(plugin_path, '__init__.py'), 'w') as init_file: + init_file.write('\n'.join([ + '# Auto-generated __init__.py for plugin', + '', + 'from .{} import {}'.format(name, class_name), + '' + ])) + + print('>>> CREATE DEFAULT MAIN PLUGIN FILE') + with open(os.path.join(plugin_path, name + '.py'), 'w') as main_plugin: + main_plugin.write('\n'.join([ + '# Auto-generated plugin', + '', + 'from lisp.core.plugin import Plugin', + '', + '', + 'class {}(Plugin):'.format(class_name), + '', + ' Name = "{}"'.format(class_name), + ' Authors = ("Nobody", )', + ' Description = "No Description"', + '', + ])) + + print('>>> DONE') + + +try: + create_plugin(args.name) +except OSError as e: + print(str(e), file=sys.stderr) From 7105a297c395d613f3157c02f9feeeb7d42b61cc Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 6 Jan 2018 20:09:35 +0100 Subject: [PATCH 081/333] Add Codacy settings various fixes --- .codacy.yml | 5 +++++ lisp/core/configuration.py | 2 +- lisp/cues/cue.py | 4 ++-- lisp/cues/media_cue.py | 2 +- lisp/layouts/cue_layout.py | 8 ++++---- lisp/layouts/list_layout/playing_mediawidget.py | 8 ++++---- 6 files changed, 17 insertions(+), 12 deletions(-) create mode 100644 .codacy.yml diff --git a/.codacy.yml b/.codacy.yml new file mode 100644 index 000000000..110a4989f --- /dev/null +++ b/.codacy.yml @@ -0,0 +1,5 @@ +--- +engines: + pylint: + enabled: true + python_version: 3 \ No newline at end of file diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index f62175f74..c8283ab10 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -137,7 +137,7 @@ def check(self): pass -# TODO: move into Application? +# TODO: we should remove this in favor of a non-singleton class AppConfig(Configuration, metaclass=Singleton): """Provide access to the application configuration (singleton)""" diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 42a9c6d27..41edd9760 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -198,13 +198,13 @@ def execute(self, action=CueAction.Default): elif action == CueAction.FadeOutPause: self.pause(fade=self.fadeout_duration > 0) elif action == CueAction.FadeOut: - duration = AppConfig().getfloat('Cue', 'FadeActionDuration', 0) + duration = AppConfig().get('Cue', 'FadeActionDuration', 0) fade = AppConfig().get( 'Cue', 'FadeActionType', FadeOutType.Linear.name) self.fadeout(duration, FadeOutType[fade]) elif action == CueAction.FadeIn: - duration = AppConfig().getfloat('Cue', 'FadeActionDuration', 0) + duration = AppConfig().get('Cue', 'FadeActionDuration', 0) fade = AppConfig().get( 'Cue', 'FadeActionType', FadeInType.Linear.name) diff --git a/lisp/cues/media_cue.py b/lisp/cues/media_cue.py index 2a398d96e..e14f2e8cd 100644 --- a/lisp/cues/media_cue.py +++ b/lisp/cues/media_cue.py @@ -207,7 +207,7 @@ def _on_start_fade(self): def _on_stop_fade(self, interrupt=False): if interrupt: - duration = AppConfig().getfloat('Cue', 'InterruptFade', 0) + duration = AppConfig().get('Cue', 'InterruptFade', 0) fade_type = AppConfig().get( 'Cue', 'InterruptFadeType', FadeOutType.Linear.name) else: diff --git a/lisp/layouts/cue_layout.py b/lisp/layouts/cue_layout.py index 0ef65c6e0..5ac17e73e 100644 --- a/lisp/layouts/cue_layout.py +++ b/lisp/layouts/cue_layout.py @@ -94,22 +94,22 @@ def go(self, action=CueAction.Default, advance=1): """ def stop_all(self): - fade = AppConfig().getbool('Layout', 'StopAllFade') + fade = AppConfig().get('Layout', 'StopAllFade') for cue in self.model_adapter: cue.stop(fade=fade) def interrupt_all(self): - fade = AppConfig().getbool('Layout','InterruptAllFade') + fade = AppConfig().get('Layout','InterruptAllFade') for cue in self.model_adapter: cue.interrupt(fade=fade) def pause_all(self): - fade = AppConfig().getbool('Layout','PauseAllFade') + fade = AppConfig().get('Layout','PauseAllFade') for cue in self.model_adapter: cue.pause(fade=fade) def resume_all(self): - fade = AppConfig().getbool('Layout','ResumeAllFade') + fade = AppConfig().get('Layout','ResumeAllFade') for cue in self.model_adapter: cue.resume(fade=fade) diff --git a/lisp/layouts/list_layout/playing_mediawidget.py b/lisp/layouts/list_layout/playing_mediawidget.py index 1bbe02631..b3ba6dc68 100644 --- a/lisp/layouts/list_layout/playing_mediawidget.py +++ b/lisp/layouts/list_layout/playing_mediawidget.py @@ -147,17 +147,17 @@ def _update_timers(self, time): accurate=self._accurate_time)) def _pause(self): - self.cue.pause(fade=AppConfig().getbool('ListLayout', 'PauseCueFade')) + self.cue.pause(fade=AppConfig().get('ListLayout', 'PauseCueFade')) def _resume(self): - self.cue.resume(fade=AppConfig().getbool('ListLayout', 'ResumeCueFade')) + self.cue.resume(fade=AppConfig().get('ListLayout', 'ResumeCueFade')) def _stop(self): - self.cue.stop(fade=AppConfig().getbool('ListLayout', 'StopCueFade')) + self.cue.stop(fade=AppConfig().get('ListLayout', 'StopCueFade')) def _interrupt(self): self.cue.interrupt( - fade=AppConfig().getbool('ListLayout', 'InterruptCueFade')) + fade=AppConfig().get('ListLayout', 'InterruptCueFade')) def _fadeout(self): self.cue.execute(CueAction.FadeOut) From a2dc139d476f9c0945c2e4736262c93b5b0f7bf7 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 9 Jan 2018 18:58:24 +0100 Subject: [PATCH 082/333] Minor changes in naming --- lisp/core/has_properties.py | 2 +- lisp/plugins/gst_backend/settings/alsa_sink.py | 2 +- lisp/plugins/gst_backend/settings/audio_dynamic.py | 2 +- lisp/plugins/gst_backend/settings/audio_pan.py | 2 +- lisp/plugins/gst_backend/settings/equalizer10.py | 2 +- lisp/plugins/gst_backend/settings/jack_sink.py | 2 +- lisp/plugins/gst_backend/settings/pitch.py | 2 +- lisp/plugins/gst_backend/settings/speed.py | 2 +- lisp/plugins/gst_backend/settings/uri_input.py | 2 +- lisp/plugins/gst_backend/settings/user_element.py | 2 +- lisp/plugins/gst_backend/settings/volume.py | 2 +- lisp/ui/settings/pages/cue_appearance.py | 2 +- lisp/ui/settings/pages/cue_general.py | 2 +- lisp/ui/settings/pages/media_cue_settings.py | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lisp/core/has_properties.py b/lisp/core/has_properties.py index 35c11ae00..8fa59d748 100644 --- a/lisp/core/has_properties.py +++ b/lisp/core/has_properties.py @@ -40,7 +40,7 @@ class Property: """ def __init__(self, default=None): - self.name = 'unnamed_property' + self.name = '_' self.default = default def __get__(self, instance, owner=None): diff --git a/lisp/plugins/gst_backend/settings/alsa_sink.py b/lisp/plugins/gst_backend/settings/alsa_sink.py index edb2fb8a3..1dfdaa1d8 100644 --- a/lisp/plugins/gst_backend/settings/alsa_sink.py +++ b/lisp/plugins/gst_backend/settings/alsa_sink.py @@ -58,7 +58,7 @@ def __init__(self, **kwargs): self.label.setAlignment(QtCore.Qt.AlignCenter) self.deviceGroup.layout().addWidget(self.label) - def enable_check(self, enable): + def enable_check(self, enabled): self.deviceGroup.setCheckable(enable) self.deviceGroup.setChecked(False) diff --git a/lisp/plugins/gst_backend/settings/audio_dynamic.py b/lisp/plugins/gst_backend/settings/audio_dynamic.py index 61579fd2a..b0635f2f0 100644 --- a/lisp/plugins/gst_backend/settings/audio_dynamic.py +++ b/lisp/plugins/gst_backend/settings/audio_dynamic.py @@ -99,7 +99,7 @@ def retranslateUi(self): self.thresholdLabel.setText( translate('AudioDynamicSettings', 'Threshold (dB)')) - def enable_check(self, enable): + def enable_check(self, enabled): self.groupBox.setCheckable(enable) self.groupBox.setChecked(False) diff --git a/lisp/plugins/gst_backend/settings/audio_pan.py b/lisp/plugins/gst_backend/settings/audio_pan.py index d9c7ffd11..4a6354849 100644 --- a/lisp/plugins/gst_backend/settings/audio_pan.py +++ b/lisp/plugins/gst_backend/settings/audio_pan.py @@ -60,7 +60,7 @@ def retransaleUi(self): self.panBox.setTitle(translate('AudioPanSettings', 'Audio Pan')) self.panLabel.setText(translate('AudioPanSettings', 'Center')) - def enable_check(self, enable): + def enable_check(self, enabled): self.panBox.setCheckable(enable) self.panBox.setChecked(False) diff --git a/lisp/plugins/gst_backend/settings/equalizer10.py b/lisp/plugins/gst_backend/settings/equalizer10.py index 3f73e7346..91799064b 100644 --- a/lisp/plugins/gst_backend/settings/equalizer10.py +++ b/lisp/plugins/gst_backend/settings/equalizer10.py @@ -72,7 +72,7 @@ def __init__(self, **kwargs): fLabel.setText(self.FREQ[n]) self.groupBox.layout().addWidget(fLabel, 2, n) - def enable_check(self, enable): + def enable_check(self, enabled): self.groupBox.setCheckable(enable) self.groupBox.setChecked(False) diff --git a/lisp/plugins/gst_backend/settings/jack_sink.py b/lisp/plugins/gst_backend/settings/jack_sink.py index e4991e798..f75507045 100644 --- a/lisp/plugins/gst_backend/settings/jack_sink.py +++ b/lisp/plugins/gst_backend/settings/jack_sink.py @@ -72,7 +72,7 @@ def get_settings(self): def load_settings(self, settings): self.connections = settings.get('connections', self.connections).copy() - def enable_check(self, enable): + def enable_check(self, enabled): self.jackGroup.setCheckable(enable) self.jackGroup.setChecked(False) diff --git a/lisp/plugins/gst_backend/settings/pitch.py b/lisp/plugins/gst_backend/settings/pitch.py index 3172e2def..38483e954 100644 --- a/lisp/plugins/gst_backend/settings/pitch.py +++ b/lisp/plugins/gst_backend/settings/pitch.py @@ -66,7 +66,7 @@ def retranslateUi(self): self.groupBox.setTitle(translate('PitchSettings', 'Pitch')) self.pitch_changed(0) - def enable_check(self, enable): + def enable_check(self, enabled): self.groupBox.setCheckable(enable) self.groupBox.setChecked(False) diff --git a/lisp/plugins/gst_backend/settings/speed.py b/lisp/plugins/gst_backend/settings/speed.py index cb793152f..d2d55f520 100644 --- a/lisp/plugins/gst_backend/settings/speed.py +++ b/lisp/plugins/gst_backend/settings/speed.py @@ -64,7 +64,7 @@ def retranslateUi(self): self.groupBox.setTitle(translate('SpeedSettings', 'Speed')) self.speedLabel.setText('1.0') - def enable_check(self, enable): + def enable_check(self, enabled): self.groupBox.setCheckable(enable) self.groupBox.setChecked(False) diff --git a/lisp/plugins/gst_backend/settings/uri_input.py b/lisp/plugins/gst_backend/settings/uri_input.py index 3aa197619..360184bc1 100644 --- a/lisp/plugins/gst_backend/settings/uri_input.py +++ b/lisp/plugins/gst_backend/settings/uri_input.py @@ -96,7 +96,7 @@ def get_settings(self): def load_settings(self, settings): self.filePath.setText(settings.get('uri', '')) - def enable_check(self, enable): + def enable_check(self, enabled): self.fileGroup.setCheckable(enable) self.fileGroup.setChecked(False) diff --git a/lisp/plugins/gst_backend/settings/user_element.py b/lisp/plugins/gst_backend/settings/user_element.py index 3da6baff3..c8163d77b 100644 --- a/lisp/plugins/gst_backend/settings/user_element.py +++ b/lisp/plugins/gst_backend/settings/user_element.py @@ -56,7 +56,7 @@ def retranslateUi(self): self.warning.setText( translate('UserElementSettings', 'Only for advanced user!')) - def enable_check(self, enable): + def enable_check(self, enabled): self.groupBox.setCheckable(enable) self.groupBox.setChecked(False) diff --git a/lisp/plugins/gst_backend/settings/volume.py b/lisp/plugins/gst_backend/settings/volume.py index deec1ec8d..24188d094 100644 --- a/lisp/plugins/gst_backend/settings/volume.py +++ b/lisp/plugins/gst_backend/settings/volume.py @@ -83,7 +83,7 @@ def retranslateUi(self): self.normalLabel.setText('0.0 dB') self.normalReset.setText(translate('VolumeSettings', 'Reset')) - def enable_check(self, enable): + def enable_check(self, enabled): for box in [self.normalBox, self.volumeBox]: box.setCheckable(enable) box.setChecked(False) diff --git a/lisp/ui/settings/pages/cue_appearance.py b/lisp/ui/settings/pages/cue_appearance.py index cbfd2782e..4c344df54 100644 --- a/lisp/ui/settings/pages/cue_appearance.py +++ b/lisp/ui/settings/pages/cue_appearance.py @@ -93,7 +93,7 @@ def retranslateUi(self): self.colorFButton.setText( translate('CueAppearanceSettings', 'Select font color')) - def enable_check(self, enable): + def enable_check(self, enabled): self.cueNameGroup.setCheckable(enable) self.cueNameGroup.setChecked(False) diff --git a/lisp/ui/settings/pages/cue_general.py b/lisp/ui/settings/pages/cue_general.py index 11f6fe850..1486880ba 100644 --- a/lisp/ui/settings/pages/cue_general.py +++ b/lisp/ui/settings/pages/cue_general.py @@ -208,7 +208,7 @@ def load_settings(self, settings): self.fadeOutEdit.setFadeType(settings.get('fadeout_type', '')) self.fadeOutEdit.setDuration(settings.get('fadeout_duration', 0)) - def enable_check(self, enable): + def enable_check(self, enabled): self.startActionGroup.setCheckable(enable) self.startActionGroup.setChecked(False) self.stopActionGroup.setCheckable(enable) diff --git a/lisp/ui/settings/pages/media_cue_settings.py b/lisp/ui/settings/pages/media_cue_settings.py index d0c002d1a..b7fdb259a 100644 --- a/lisp/ui/settings/pages/media_cue_settings.py +++ b/lisp/ui/settings/pages/media_cue_settings.py @@ -100,7 +100,7 @@ def get_settings(self): return conf - def enable_check(self, enable): + def enable_check(self, enabled): self.startGroup.setCheckable(enable) self.startGroup.setChecked(False) From f7b1a52da75224f73444a153bde343dda38081a8 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 27 Jan 2018 19:38:15 +0100 Subject: [PATCH 083/333] Stupid IDE --- lisp/application.py | 6 +- lisp/backend/media.py | 47 ++--- lisp/backend/media_element.py | 2 +- lisp/core/configuration.py | 1 + lisp/core/has_properties.py | 61 ++++--- lisp/core/properties.py | 49 +----- lisp/core/util.py | 4 +- lisp/cues/cue.py | 2 +- lisp/cues/media_cue.py | 6 +- lisp/layouts/cart_layout/cue_widget.py | 4 +- lisp/plugins/action_cues/collection_cue.py | 2 +- lisp/plugins/action_cues/command_cue.py | 2 +- lisp/plugins/action_cues/index_action_cue.py | 2 +- lisp/plugins/action_cues/midi_cue.py | 2 +- lisp/plugins/action_cues/seek_cue.py | 2 +- lisp/plugins/action_cues/stop_all.py | 2 +- lisp/plugins/action_cues/volume_control.py | 2 +- lisp/plugins/controller/controller.py | 4 +- lisp/plugins/gst_backend/elements/__init__.py | 17 +- .../plugins/gst_backend/elements/jack_sink.py | 2 +- .../gst_backend/elements/preset_src.py | 2 +- lisp/plugins/gst_backend/elements/speed.py | 2 +- .../plugins/gst_backend/elements/uri_input.py | 2 +- .../gst_backend/elements/user_element.py | 2 +- lisp/plugins/gst_backend/elements/volume.py | 10 +- lisp/plugins/gst_backend/gst_backend.py | 56 ++++-- lisp/plugins/gst_backend/gst_element.py | 8 +- lisp/plugins/gst_backend/gst_media.py | 166 ++++++++++-------- lisp/plugins/gst_backend/gst_media_cue.py | 5 +- .../plugins/gst_backend/gst_media_settings.py | 10 +- .../plugins/gst_backend/settings/alsa_sink.py | 8 +- .../gst_backend/settings/audio_dynamic.py | 4 +- .../plugins/gst_backend/settings/audio_pan.py | 4 +- .../gst_backend/settings/equalizer10.py | 4 +- .../plugins/gst_backend/settings/jack_sink.py | 4 +- lisp/plugins/gst_backend/settings/pitch.py | 4 +- lisp/plugins/gst_backend/settings/speed.py | 4 +- .../plugins/gst_backend/settings/uri_input.py | 4 +- .../gst_backend/settings/user_element.py | 4 +- lisp/plugins/gst_backend/settings/volume.py | 4 +- lisp/plugins/media_info/media_info.py | 65 ++++--- lisp/plugins/presets/presets.py | 4 +- lisp/plugins/timecode/timecode.py | 2 +- lisp/plugins/triggers/triggers.py | 2 +- lisp/ui/settings/pages/cue_appearance.py | 8 +- lisp/ui/settings/pages/cue_general.py | 14 +- lisp/ui/settings/pages/media_cue_settings.py | 6 +- 47 files changed, 320 insertions(+), 307 deletions(-) diff --git a/lisp/application.py b/lisp/application.py index 42837737f..79f881efa 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -74,7 +74,7 @@ def __init__(self): @property def session(self): - """:rtype: lisp.session.BaseSession""" + """:rtype: lisp.core.session.BaseSession""" return self.__session @property @@ -156,7 +156,7 @@ def _save_to_file(self, session_file): session_dict = {"cues": []} for cue in self.__cue_model: - session_dict['cues'].append(cue.properties(only_changed=True)) + session_dict['cues'].append(cue.properties(defaults=False)) # Sort cues by index, allow sorted-models to load properly session_dict['cues'].sort(key=lambda cue: cue['index']) diff --git a/lisp/backend/media.py b/lisp/backend/media.py index 6077e9e56..0644da38b 100644 --- a/lisp/backend/media.py +++ b/lisp/backend/media.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -48,35 +48,36 @@ class Media(HasProperties): duration = Property(default=0) start_time = Property(default=0) stop_time = Property(default=0) + elements = Property(default={}) def __init__(self): super().__init__() self.paused = Signal() - """Emitted when paused (self)""" + # Emitted when paused (self) self.played = Signal() - """Emitted when played (self)""" + # Emitted when played (self) self.stopped = Signal() - """Emitted when stopped (self)""" + # Emitted when stopped (self) self.interrupted = Signal() - """Emitted after interruption (self)""" + # Emitted after interruption (self) self.eos = Signal() - """End-of-Stream (self)""" + # End-of-Stream (self) self.on_play = Signal() - """Emitted before play (self)""" + # Emitted before play (self) self.on_stop = Signal() - """Emitted before stop (self)""" + # Emitted before stop (self) self.on_pause = Signal() - """Emitted before pause (self)""" + # Emitted before pause (self) self.sought = Signal() - """Emitted after a seek (self, position)""" + # Emitted after a seek (self, position) self.error = Signal() - """Emitted when an error occurs (self, error, details)""" + # Emitted when an error occurs (self, error, details) self.elements_changed = Signal() - """Emitted when one or more elements are added/removed (self)""" + # Emitted when one or more elements are added/removed (self) @property @abstractmethod @@ -103,20 +104,6 @@ def element(self, class_name): :rtype: lisp.core.base.media_element.MediaElement """ - @abstractmethod - def elements(self): - """ - :return: All the MediaElement(s) of the media - :rtype: list - """ - - @abstractmethod - def elements_properties(self): - """ - :return: Media elements configurations - :rtype: dict - """ - @abstractmethod def input_uri(self): """ @@ -147,11 +134,3 @@ def seek(self, position): @abstractmethod def stop(self): """The media go in STOPPED state and stop the playback.""" - - @abstractmethod - def update_elements(self, settings): - """Update the elements configuration. - - :param settings: Media-elements settings - :type settings: dict - """ \ No newline at end of file diff --git a/lisp/backend/media_element.py b/lisp/backend/media_element.py index dc899d9b5..77c31ae63 100644 --- a/lisp/backend/media_element.py +++ b/lisp/backend/media_element.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index c8283ab10..be5740e95 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -33,6 +33,7 @@ _UNSET = object() +# TODO: HasProperties? class Configuration: """Allow to read/write json based configuration files. diff --git a/lisp/core/has_properties.py b/lisp/core/has_properties.py index 3a49aa5c0..afd7e777f 100644 --- a/lisp/core/has_properties.py +++ b/lisp/core/has_properties.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -113,10 +113,10 @@ def __init__(self): def __setattr__(self, name, value): super().__setattr__(name, value) - if name in self.properties_names(): + if name in self.__class__.__properties__: self.property_changed.emit(self, name, value) self.__emit_changed(name, value) - elif name in self.live_properties_names(): + elif name in self.__class__.__live_properties__: self.__emit_changed(name, value) def __emit_changed(self, name, value): @@ -131,19 +131,27 @@ def register_property(cls, name, prop): :param name: Property name :param prop: The property - - _Deprecated: use normal attribute assignment for the class_ """ setattr(cls, name, prop) + @classmethod + def remove_property(cls, name): + """Remove the property with the given name. + + :param name: Property name + """ + delattr(cls, name) + def changed(self, name): """ :param name: The property name - :return: The property change-signal + :return: A signal that notify the given property changes :rtype: Signal + + The signals returned by this method are created lazily and cached. """ - if (name not in self.properties_names() and - name not in self.live_properties_names()): + if (name not in self.__class__.__properties__ and + name not in self.__class__.__live_properties__): raise ValueError('no property "{}" found'.format(name)) signal = self._changed_signals.get(name) @@ -154,24 +162,27 @@ def changed(self, name): return signal - def properties(self, only_changed=False): + def properties(self, defaults=True): """ - :param only_changed: when True only "changed" properties are collected - :type only_changed: bool + :param defaults: include/exclude properties equals to their default + :type defaults: bool :return: The properties as a dictionary {name: value} :rtype: dict """ - if only_changed: - properties = {} - for name in self.properties_names(): - changed, value = getattr(self.__class__, name).changed(self) - if changed: - properties[name] = value + properties = {} - return properties + for name in self.__class__.__properties__: + value = getattr(self, name) + + if isinstance(value, HasProperties): + value = value.properties(defaults=defaults) + if defaults or value: + properties[name] = value + elif defaults or value != getattr(self.__class__, name).default: + properties[name] = value - return {name: getattr(self, name) for name in self.properties_names()} + return properties def update_properties(self, properties): """Set the given properties. @@ -180,8 +191,12 @@ def update_properties(self, properties): :type properties: dict """ for name, value in properties.items(): - if name in self.properties_names(): - setattr(self, name, value) + if name in self.__class__.__properties__: + current = getattr(self, name) + if isinstance(current, HasProperties): + current.update_properties(value) + else: + setattr(self, name, value) @classmethod def properties_defaults(cls): @@ -198,8 +213,8 @@ def properties_names(cls): :return: A set containing the properties names :rtype: set[str] """ - return cls.__properties__ + return cls.__properties__.copy() @classmethod def live_properties_names(cls): - return cls.__live_properties__ + return cls.__live_properties__.copy() diff --git a/lisp/core/properties.py b/lisp/core/properties.py index 62bfc7865..136d7efe7 100644 --- a/lisp/core/properties.py +++ b/lisp/core/properties.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -70,13 +70,6 @@ def __set__(self, instance, value): if value != instance.__dict__.get(self.name, self.default): instance.__dict__[self.name] = value - def changed(self, instance): - if instance is not None: - value = self.__get__(instance) - return value != self.default, value - - return False, self.default - class WriteOnceProperty(Property): """Property that can be modified only once. @@ -88,43 +81,3 @@ class WriteOnceProperty(Property): def __set__(self, instance, value): if self.__get__(instance) == self.default: super().__set__(instance, value) - - -class NestedProperties(Property): - """Simplify retrieving the properties of nested objects. - - The goal is to avoid the reimplementation of `properties()` and - `update_properties()` functions. - - ..note:: - When getting or setting a single property of the nested object is better - to access it directly instead that using the nested-property. - - Because of this is suggested to use a "special" name for the - nested-property, for example use "_media_" instead of "media". - """ - - def __init__(self, provider_name, default=None, **meta): - super().__init__(default=default, **meta) - self.provider_name = provider_name - - def __get__(self, instance, owner=None): - if instance is None: - return self - else: - return self.provider(instance).properties() - - def __set__(self, instance, value): - if instance is not None: - self.provider(instance).update_properties(value) - - def changed(self, instance): - if instance is not None: - properties = self.provider(instance).properties(only_changed=True) - # If no properties is changed (empty dict) return false - return bool(properties), properties - - return False, {} - - def provider(self, instance): - return instance.__dict__.get(self.provider_name) \ No newline at end of file diff --git a/lisp/core/util.py b/lisp/core/util.py index 9d34a4d58..40c156b56 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -137,7 +137,7 @@ def natural_keys(text): l.sort(key=natural_keys) # sorts in human order ['something1', 'something4', 'something17'] """ - return [int(c) if c.isdigit() else c for c in re.split('(\d+)', text)] + return [int(c) if c.isdigit() else c for c in re.split('([0-9]+)', text)] def rhasattr(obj, attr): diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 5fcf50af4..52fab63da 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/cues/media_cue.py b/lisp/cues/media_cue.py index 792cd8a2d..f095c82b4 100644 --- a/lisp/cues/media_cue.py +++ b/lisp/cues/media_cue.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -25,14 +25,14 @@ from lisp.core.decorators import async from lisp.core.fade_functions import FadeInType, FadeOutType from lisp.core.fader import Fader -from lisp.core.properties import NestedProperties +from lisp.core.properties import Property from lisp.cues.cue import Cue, CueAction, CueState class MediaCue(Cue): Name = QT_TRANSLATE_NOOP('CueName', 'Media Cue') - _media_ = NestedProperties('media', default={}) + media = Property() CueActions = (CueAction.Default, CueAction.Start, CueAction.FadeInStart, CueAction.Stop, CueAction.FadeOutStop, CueAction.Pause, diff --git a/lisp/layouts/cart_layout/cue_widget.py b/lisp/layouts/cart_layout/cue_widget.py index 1d92c64c8..ccf805a1d 100644 --- a/lisp/layouts/cart_layout/cue_widget.py +++ b/lisp/layouts/cart_layout/cue_widget.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -274,7 +274,7 @@ def _update_description(self, description): self.nameButton.setToolTip(description) def _change_volume(self, new_volume): - self._volume_element.current_volume = slider_to_fader( + self._volume_element.live_volume = slider_to_fader( new_volume / CueWidget.SLIDER_RANGE) def _clicked(self, event): diff --git a/lisp/plugins/action_cues/collection_cue.py b/lisp/plugins/action_cues/collection_cue.py index e5760c1e4..6d26832e3 100644 --- a/lisp/plugins/action_cues/collection_cue.py +++ b/lisp/plugins/action_cues/collection_cue.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/action_cues/command_cue.py b/lisp/plugins/action_cues/command_cue.py index 088b43d9d..9ce8e3fc4 100644 --- a/lisp/plugins/action_cues/command_cue.py +++ b/lisp/plugins/action_cues/command_cue.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/action_cues/index_action_cue.py b/lisp/plugins/action_cues/index_action_cue.py index 3c59c63de..2fd155f73 100644 --- a/lisp/plugins/action_cues/index_action_cue.py +++ b/lisp/plugins/action_cues/index_action_cue.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/action_cues/midi_cue.py b/lisp/plugins/action_cues/midi_cue.py index 0b1061819..5cc19ab81 100644 --- a/lisp/plugins/action_cues/midi_cue.py +++ b/lisp/plugins/action_cues/midi_cue.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/action_cues/seek_cue.py b/lisp/plugins/action_cues/seek_cue.py index 7971c7905..e729ab8e7 100644 --- a/lisp/plugins/action_cues/seek_cue.py +++ b/lisp/plugins/action_cues/seek_cue.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/action_cues/stop_all.py b/lisp/plugins/action_cues/stop_all.py index 5a9171777..ef3ce0daf 100644 --- a/lisp/plugins/action_cues/stop_all.py +++ b/lisp/plugins/action_cues/stop_all.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/action_cues/volume_control.py b/lisp/plugins/action_cues/volume_control.py index 63dd2b828..4fe069119 100644 --- a/lisp/plugins/action_cues/volume_control.py +++ b/lisp/plugins/action_cues/volume_control.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/controller/controller.py b/lisp/plugins/controller/controller.py index 93720ee03..a615c29f0 100644 --- a/lisp/plugins/controller/controller.py +++ b/lisp/plugins/controller/controller.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -29,7 +29,7 @@ class Controller(Plugin): Name = 'Controller' Authors = ('Francesco Ceruti', 'Thomas Achtner') - Depends = ('Midi', 'Osc') + OptDepends = ('Midi', 'Osc') Description = 'Allow to control cues via external commands with multiple ' \ 'protocols' diff --git a/lisp/plugins/gst_backend/elements/__init__.py b/lisp/plugins/gst_backend/elements/__init__.py index 943c9f24b..e2747cf2d 100644 --- a/lisp/plugins/gst_backend/elements/__init__.py +++ b/lisp/plugins/gst_backend/elements/__init__.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -39,7 +39,7 @@ def load(): # Getter functions def inputs(): - return __INPUTS + return __INPUTS.copy() def input_name(class_name): @@ -47,7 +47,7 @@ def input_name(class_name): def outputs(): - return __OUTPUTS + return __OUTPUTS.copy() def output_name(class_name): @@ -55,8 +55,15 @@ def output_name(class_name): def plugins(): - return __PLUGINS + return __PLUGINS.copy() def plugin_name(class_name): - return __PLUGINS[class_name].Name \ No newline at end of file + return __PLUGINS[class_name].Name + + +def all_elements(): + elements = inputs() + elements.update(plugins()) + elements.update(outputs()) + return elements diff --git a/lisp/plugins/gst_backend/elements/jack_sink.py b/lisp/plugins/gst_backend/elements/jack_sink.py index 845d923c0..b5806e03f 100644 --- a/lisp/plugins/gst_backend/elements/jack_sink.py +++ b/lisp/plugins/gst_backend/elements/jack_sink.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/preset_src.py b/lisp/plugins/gst_backend/elements/preset_src.py index ad6f58912..e7d0c6d60 100644 --- a/lisp/plugins/gst_backend/elements/preset_src.py +++ b/lisp/plugins/gst_backend/elements/preset_src.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/speed.py b/lisp/plugins/gst_backend/elements/speed.py index 408cfebaa..f34f0f21d 100644 --- a/lisp/plugins/gst_backend/elements/speed.py +++ b/lisp/plugins/gst_backend/elements/speed.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/uri_input.py b/lisp/plugins/gst_backend/elements/uri_input.py index 4e26fdf86..707edfdb3 100644 --- a/lisp/plugins/gst_backend/elements/uri_input.py +++ b/lisp/plugins/gst_backend/elements/uri_input.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/user_element.py b/lisp/plugins/gst_backend/elements/user_element.py index 1188e6b3c..670d6a6ef 100644 --- a/lisp/plugins/gst_backend/elements/user_element.py +++ b/lisp/plugins/gst_backend/elements/user_element.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/volume.py b/lisp/plugins/gst_backend/elements/volume.py index 5d512b944..d230eb4df 100644 --- a/lisp/plugins/gst_backend/elements/volume.py +++ b/lisp/plugins/gst_backend/elements/volume.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -32,11 +32,11 @@ class Volume(GstMediaElement): mute = GstProperty('gst_volume', default=False) volume = GstProperty('gst_volume', default=1.0) - normal_volume = GstProperty('gst_normal_volume', default=1.0, - gst_name='volume') + normal_volume = GstProperty( + 'gst_normal_volume', default=1.0, gst_name='volume') - live_volume = GstLiveProperty('gst_volume', 'volume', type=float, - range=(0, 10)) + live_volume = GstLiveProperty( + 'gst_volume', 'volume', type=float, range=(0, 10)) def __init__(self, pipe): super().__init__() diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index 6649aaf8d..ac3c65082 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,17 +16,24 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from lisp.cues.cue_factory import CueFactory + +import os.path + +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QCursor +from PyQt5.QtWidgets import QFileDialog, QApplication +from lisp.ui.ui_utils import translate, qfile_filters from lisp import backend from lisp.backend.backend import Backend as BaseBackend from lisp.core.decorators import memoize from lisp.core.plugin import Plugin +from lisp.cues.cue_factory import CueFactory from lisp.cues.media_cue import MediaCue from lisp.plugins.gst_backend import elements, settings from lisp.plugins.gst_backend.gi_repository import Gst -from lisp.plugins.gst_backend.gst_cue_factories import gst_media_cue_factory,\ - UriAudioCueFactory, CaptureAudioCueFactory +from lisp.plugins.gst_backend.gst_media_cue import GstCueFactory, \ + UriAudioCueFactory from lisp.plugins.gst_backend.gst_media_settings import GstMediaSettings from lisp.plugins.gst_backend.gst_settings import GstSettings from lisp.plugins.gst_backend.gst_utils import gst_parse_tags_list, \ @@ -53,15 +60,18 @@ def __init__(self, app): # Add MediaCue settings widget to the CueLayout CueSettingsRegistry().add_item(GstMediaSettings, MediaCue) - # Register the GstMediaCue factories - base_pipeline = GstBackend.Config['Pipeline'] + # Register GstMediaCue factory + CueFactory.register_factory('GstMediaCue', GstCueFactory(tuple())) - CueFactory.register_factory('MediaCue', gst_media_cue_factory) - CueFactory.register_factory( - 'URIAudioCue', UriAudioCueFactory(base_pipeline)) - CueFactory.register_factory( - 'CaptureAudioCue', CaptureAudioCueFactory(base_pipeline)) + # Add Menu entry + self.app.window.register_cue_menu_action( + translate('GstBackend', 'Audio cue (from file)'), + self._add_uri_audio_cue, + category='Media cues', + shortcut='CTRL+M' + ) + # Load elements and their settings-widgets elements.load() settings.load() @@ -87,3 +97,27 @@ def supported_extensions(self): extensions[mime].extend(gst_extensions) return extensions + + def _add_uri_audio_cue(self): + """Add audio MediaCue(s) form user-selected files""" + + files, _ = QFileDialog.getOpenFileNames( + self.app.window, + translate('GstBackend', 'Select media files'), + self.app.session.path(), + qfile_filters(self.supported_extensions(), anyfile=True) + ) + + QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + + # Create media cues, and add them to the Application cue_model + factory = UriAudioCueFactory(GstBackend.Config['Pipeline']) + + for file in files: + file = self.app.session.rel_path(file) + cue = factory(uri='file://' + file) + # Use the filename without extension as cue name + cue.name = os.path.splitext(os.path.basename(file))[0] + self.app.cue_model.add(cue) + + QApplication.restoreOverrideCursor() \ No newline at end of file diff --git a/lisp/plugins/gst_backend/gst_element.py b/lisp/plugins/gst_backend/gst_element.py index 267130fc5..9aba6193c 100644 --- a/lisp/plugins/gst_backend/gst_element.py +++ b/lisp/plugins/gst_backend/gst_element.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -60,10 +60,12 @@ def __get__(self, instance, owner=None): def __set__(self, instance, value): if self.adapter is not None: value = self.adapter(value) - getattr(instance, self.element_name).set_property(self.property_name, - value) + getattr(instance, self.element_name).set_property( + self.property_name, value) + +# TODO: base provide base implementation of __init__ class GstMediaElement(MediaElement): """All the subclass must take the pipeline as first __init__ argument""" diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index ffcbd8568..b7018e5d0 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,14 +20,15 @@ import weakref from lisp.backend.media import Media, MediaState -from lisp.core.properties import Property -from lisp.plugins.gst_backend import elements +from lisp.core.has_properties import HasProperties +from lisp.core.properties import Property, WriteOnceProperty +from lisp.plugins.gst_backend import elements as gst_elements from lisp.plugins.gst_backend.gi_repository import Gst def validate_pipeline(pipe, rebuild=False): # The first must be an input element - if pipe[0] not in elements.inputs().keys(): + if pipe[0] not in gst_elements.inputs().keys(): return False # The middle elements must be plugins elements @@ -36,13 +37,13 @@ def validate_pipeline(pipe, rebuild=False): pipe = list(pipe) pipe[1:-1] = set(pipe[1:-1]).intersection( - set(elements.plugins().keys())) + set(gst_elements.plugins().keys())) else: - if len(set(pipe[1:-1]) - set(elements.plugins().keys())) != 0: + if len(set(pipe[1:-1]) - set(gst_elements.plugins().keys())) != 0: return False # The last must be an output element - if pipe[-1] not in elements.outputs().keys(): + if pipe[-1] not in gst_elements.outputs().keys(): return False return pipe if rebuild else True @@ -52,15 +53,16 @@ class GstMedia(Media): """Media implementation based on the GStreamer framework.""" pipe = Property(default=()) + elements = Property(default=None) def __init__(self): super().__init__() self._state = MediaState.Null - self._elements = [] self._old_pipe = '' self._loop_count = 0 + self.elements = GstMediaElements() self._gst_pipe = Gst.Pipeline() self._gst_state = Gst.State.NULL self._time_query = Gst.Query.new_position(Gst.Format.TIME) @@ -73,7 +75,7 @@ def __init__(self): on_message = weakref.WeakMethod(self.__on_message) handler = bus.connect('message', lambda *args: on_message()(*args)) weakref.finalize(self, self.__finalizer, self._gst_pipe, handler, - self._elements) + self.elements) self.changed('loop').connect(self.__prepare_loops) self.changed('pipe').connect(self.__prepare_pipe) @@ -95,13 +97,13 @@ def __prepare_pipe(self, pipe): raise ValueError('Invalid pipeline "{0}"'.format(pipe)) # Build the pipeline - elements_properties = self.elements_properties() + ep_copy = self.elements.properties() self.__build_pipeline() - self.update_elements(elements_properties) + self.elements.update_properties(ep_copy) - self._elements[0].changed('duration').connect( + self.elements[0].changed('duration').connect( self.__duration_changed) - self.__duration_changed(self._elements[0].duration) + self.__duration_changed(self.elements[0].duration) def current_time(self): ok, position = self._gst_pipe.query_position(Gst.Format.TIME) @@ -111,7 +113,7 @@ def play(self): if self.state == MediaState.Stopped or self.state == MediaState.Paused: self.on_play.emit(self) - for element in self._elements: + for element in self.elements: element.play() self._state = MediaState.Playing @@ -127,7 +129,7 @@ def pause(self): if self.state == MediaState.Playing: self.on_pause.emit(self) - for element in self._elements: + for element in self.elements: element.pause() self._state = MediaState.Paused @@ -142,7 +144,7 @@ def stop(self): if self.state == MediaState.Playing or self.state == MediaState.Paused: self.on_stop.emit(self) - for element in self._elements: + for element in self.elements: element.stop() self.interrupt(emit=False) @@ -184,31 +186,16 @@ def seek(self, position): self.sought.emit(self, position) def element(self, class_name): - for element in self._elements: - if type(element).__name__ == class_name: - return element - - def elements(self): - return self._elements.copy() - - def elements_properties(self, only_changed=False): - properties = {} - - for element in self._elements: - e_properties = element.properties(only_changed) - if e_properties: - properties[type(element).__name__] = e_properties - - return properties + return getattr(self.elements, class_name, None) def input_uri(self): try: - return self._elements[0].input_uri() + return self.elements[0].input_uri() except Exception: pass def interrupt(self, dispose=False, emit=True): - for element in self._elements: + for element in self.elements: element.interrupt() state = self._state @@ -226,62 +213,32 @@ def interrupt(self, dispose=False, emit=True): state == MediaState.Paused): self.interrupted.emit(self) - def properties(self, only_changed=False): - properties = super().properties(only_changed).copy() - properties['elements'] = self.elements_properties(only_changed) - return properties - - def update_elements(self, properties): - for element in self._elements: - if type(element).__name__ in properties: - element.update_properties(properties[type(element).__name__]) - def update_properties(self, properties): - elements_properties = properties.pop('elements', {}) - super().update_properties(properties) + # In order to update the other properties we need the pipeline + pipe = properties.pop('pipe', None) + if pipe: + self.pipe = pipe - self.update_elements(elements_properties) + super().update_properties(properties) if self.state == MediaState.Null or self.state == MediaState.Error: self._state = MediaState.Stopped - @staticmethod - def _pipe_elements(): - tmp = {} - tmp.update(elements.inputs()) - tmp.update(elements.outputs()) - tmp.update(elements.plugins()) - return tmp - - def __append_element(self, element): - if self._elements: - self._elements[-1].link(element) - - self._elements.append(element) - - def __remove_element(self, index): - if index > 0: - self._elements[index - 1].unlink(self._elements[index]) - if index < len(self._elements) - 1: - self._elements[index - 1].link(self._elements[index + 1]) - self._elements[index].unlink(self._elements[index]) - - self._elements.pop(index).dispose() - def __build_pipeline(self): # Set to NULL the pipeline self.interrupt(dispose=True) + # Remove all pipeline children for __ in range(self._gst_pipe.get_children_count()): self._gst_pipe.remove(self._gst_pipe.get_child_by_index(0)) + # Remove all the elements - for __ in range(len(self._elements)): - self.__remove_element(len(self._elements) - 1) + self.elements.clear() # Create all the new elements - pipe_elements = self._pipe_elements() + all_elements = gst_elements.all_elements() for element in self.pipe: - self.__append_element(pipe_elements[element](self._gst_pipe)) + self.elements.append(all_elements[element](self._gst_pipe)) # Set to Stopped/READY the pipeline self._state = MediaState.Stopped @@ -326,5 +283,62 @@ def __finalizer(pipeline, connection_handler, media_elements): bus.remove_signal_watch() bus.disconnect(connection_handler) - for element in media_elements: - element.dispose() + media_elements.clear() + + +def GstMediaElements(): + return type('GstMediaElements', (_GstMediaElements, ), {})() + + +class _GstMediaElements(HasProperties): + + def __init__(self): + super().__init__() + self.elements = [] + + def __getitem__(self, index): + return self.elements[index] + + def __len__(self): + return len(self.elements) + + def __contains__(self, item): + return item in self.elements + + def __iter__(self): + return iter(self.elements) + + def append(self, element): + """ + :type element: lisp.backend.media_element.MediaElement + """ + if self.elements: + self.elements[-1].link(element) + self.elements.append(element) + + # Add a property for the new added element + self.register_property( + element.__class__.__name__, + WriteOnceProperty(default=None) + ) + setattr(self, element.__class__.__name__, element) + + def remove(self, element): + self.pop(self.elements.index(element)) + + def pop(self, index): + if index > 0: + self.elements[index - 1].unlink(self.elements[index]) + if index < len(self.elements) - 1: + self.elements[index].unlink(self.elements[index + 1]) + self.elements[index - 1].link(self.elements[index + 1]) + + element = self.elements.pop(index) + element.dispose() + + # Remove the element corresponding property + self.remove_property(element.__class__.__name__) + + def clear(self): + while self.elements: + self.pop(len(self.elements) - 1) diff --git a/lisp/plugins/gst_backend/gst_media_cue.py b/lisp/plugins/gst_backend/gst_media_cue.py index f2cc182d2..c24f8404a 100644 --- a/lisp/plugins/gst_backend/gst_media_cue.py +++ b/lisp/plugins/gst_backend/gst_media_cue.py @@ -40,7 +40,8 @@ def __call__(self, id=None): return GstMediaCue(GstMedia(), id=id, pipeline=self.pipeline()) def pipeline(self): - return [self.input] + self.base_pipeline + if self.base_pipeline and self.input: + return [self.input] + self.base_pipeline class UriAudioCueFactory(GstCueFactory): @@ -50,7 +51,7 @@ def __init__(self, base_pipeline): self.input = 'UriInput' def __call__(self, id=None, uri=None): - cue = super()(id=id) + cue = super().__call__(id=id) if uri is not None: try: diff --git a/lisp/plugins/gst_backend/gst_media_settings.py b/lisp/plugins/gst_backend/gst_media_settings.py index a95a67376..a8c5f364d 100644 --- a/lisp/plugins/gst_backend/gst_media_settings.py +++ b/lisp/plugins/gst_backend/gst_media_settings.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -55,7 +55,7 @@ def __init__(self, **kwargs): self.pipeButton.clicked.connect(self.__edit_pipe) def load_settings(self, settings): - settings = settings.get('_media_', {}) + settings = settings.get('media', {}) # Create a local copy of the configuration self._settings = deepcopy(settings) @@ -90,7 +90,7 @@ def get_settings(self): if not self._check: settings['pipe'] = self._settings['pipe'] - return {'_media_': settings} + return {'media': settings} def enable_check(self, enabled): self._check = enabled @@ -111,7 +111,7 @@ def __change_page(self, current, previous): def __edit_pipe(self): # Backup the settings - self._settings = self.get_settings()['_media_'] + self._settings = self.get_settings()['media'] # Show the dialog dialog = GstPipeEditDialog(self._settings.get('pipe', ()), parent=self) @@ -128,5 +128,5 @@ def __edit_pipe(self): # Reload with the new pipeline self._settings['pipe'] = dialog.get_pipe() - self.load_settings({'_media_': self._settings}) + self.load_settings({'media': self._settings}) self.enable_check(self._check) diff --git a/lisp/plugins/gst_backend/settings/alsa_sink.py b/lisp/plugins/gst_backend/settings/alsa_sink.py index 1dfdaa1d8..8f0fe83c3 100644 --- a/lisp/plugins/gst_backend/settings/alsa_sink.py +++ b/lisp/plugins/gst_backend/settings/alsa_sink.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -59,7 +59,7 @@ def __init__(self, **kwargs): self.deviceGroup.layout().addWidget(self.label) def enable_check(self, enabled): - self.deviceGroup.setCheckable(enable) + self.deviceGroup.setCheckable(enabled) self.deviceGroup.setChecked(False) def load_settings(self, settings): @@ -71,8 +71,8 @@ def load_settings(self, settings): break def get_settings(self): - if not ( - self.deviceGroup.isCheckable() and not self.deviceGroup.isChecked()): + if not (self.deviceGroup.isCheckable() and + not self.deviceGroup.isChecked()): return {'device': self.devices[self.device.currentText()]} return {} diff --git a/lisp/plugins/gst_backend/settings/audio_dynamic.py b/lisp/plugins/gst_backend/settings/audio_dynamic.py index b0635f2f0..ab4cb9df9 100644 --- a/lisp/plugins/gst_backend/settings/audio_dynamic.py +++ b/lisp/plugins/gst_backend/settings/audio_dynamic.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -100,7 +100,7 @@ def retranslateUi(self): translate('AudioDynamicSettings', 'Threshold (dB)')) def enable_check(self, enabled): - self.groupBox.setCheckable(enable) + self.groupBox.setCheckable(enabled) self.groupBox.setChecked(False) def get_settings(self): diff --git a/lisp/plugins/gst_backend/settings/audio_pan.py b/lisp/plugins/gst_backend/settings/audio_pan.py index 4a6354849..b05877514 100644 --- a/lisp/plugins/gst_backend/settings/audio_pan.py +++ b/lisp/plugins/gst_backend/settings/audio_pan.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -61,7 +61,7 @@ def retransaleUi(self): self.panLabel.setText(translate('AudioPanSettings', 'Center')) def enable_check(self, enabled): - self.panBox.setCheckable(enable) + self.panBox.setCheckable(enabled) self.panBox.setChecked(False) def get_settings(self): diff --git a/lisp/plugins/gst_backend/settings/equalizer10.py b/lisp/plugins/gst_backend/settings/equalizer10.py index 91799064b..5f8b4a84f 100644 --- a/lisp/plugins/gst_backend/settings/equalizer10.py +++ b/lisp/plugins/gst_backend/settings/equalizer10.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -73,7 +73,7 @@ def __init__(self, **kwargs): self.groupBox.layout().addWidget(fLabel, 2, n) def enable_check(self, enabled): - self.groupBox.setCheckable(enable) + self.groupBox.setCheckable(enabled) self.groupBox.setChecked(False) def get_settings(self): diff --git a/lisp/plugins/gst_backend/settings/jack_sink.py b/lisp/plugins/gst_backend/settings/jack_sink.py index f75507045..049b11d8d 100644 --- a/lisp/plugins/gst_backend/settings/jack_sink.py +++ b/lisp/plugins/gst_backend/settings/jack_sink.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -73,7 +73,7 @@ def load_settings(self, settings): self.connections = settings.get('connections', self.connections).copy() def enable_check(self, enabled): - self.jackGroup.setCheckable(enable) + self.jackGroup.setCheckable(enabled) self.jackGroup.setChecked(False) def __edit_connections(self): diff --git a/lisp/plugins/gst_backend/settings/pitch.py b/lisp/plugins/gst_backend/settings/pitch.py index 38483e954..ec231543f 100644 --- a/lisp/plugins/gst_backend/settings/pitch.py +++ b/lisp/plugins/gst_backend/settings/pitch.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -67,7 +67,7 @@ def retranslateUi(self): self.pitch_changed(0) def enable_check(self, enabled): - self.groupBox.setCheckable(enable) + self.groupBox.setCheckable(enabled) self.groupBox.setChecked(False) def get_settings(self): diff --git a/lisp/plugins/gst_backend/settings/speed.py b/lisp/plugins/gst_backend/settings/speed.py index d2d55f520..60c580140 100644 --- a/lisp/plugins/gst_backend/settings/speed.py +++ b/lisp/plugins/gst_backend/settings/speed.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -65,7 +65,7 @@ def retranslateUi(self): self.speedLabel.setText('1.0') def enable_check(self, enabled): - self.groupBox.setCheckable(enable) + self.groupBox.setCheckable(enabled) self.groupBox.setChecked(False) def get_settings(self): diff --git a/lisp/plugins/gst_backend/settings/uri_input.py b/lisp/plugins/gst_backend/settings/uri_input.py index 360184bc1..9e673128d 100644 --- a/lisp/plugins/gst_backend/settings/uri_input.py +++ b/lisp/plugins/gst_backend/settings/uri_input.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -97,7 +97,7 @@ def load_settings(self, settings): self.filePath.setText(settings.get('uri', '')) def enable_check(self, enabled): - self.fileGroup.setCheckable(enable) + self.fileGroup.setCheckable(enabled) self.fileGroup.setChecked(False) def select_file(self): diff --git a/lisp/plugins/gst_backend/settings/user_element.py b/lisp/plugins/gst_backend/settings/user_element.py index c8163d77b..838b8e59c 100644 --- a/lisp/plugins/gst_backend/settings/user_element.py +++ b/lisp/plugins/gst_backend/settings/user_element.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -57,7 +57,7 @@ def retranslateUi(self): translate('UserElementSettings', 'Only for advanced user!')) def enable_check(self, enabled): - self.groupBox.setCheckable(enable) + self.groupBox.setCheckable(enabled) self.groupBox.setChecked(False) def load_settings(self, settings): diff --git a/lisp/plugins/gst_backend/settings/volume.py b/lisp/plugins/gst_backend/settings/volume.py index 24188d094..f4b9c539b 100644 --- a/lisp/plugins/gst_backend/settings/volume.py +++ b/lisp/plugins/gst_backend/settings/volume.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -85,7 +85,7 @@ def retranslateUi(self): def enable_check(self, enabled): for box in [self.normalBox, self.volumeBox]: - box.setCheckable(enable) + box.setCheckable(enabled) box.setChecked(False) def get_settings(self): diff --git a/lisp/plugins/media_info/media_info.py b/lisp/plugins/media_info/media_info.py index da0041bcf..aa677b101 100644 --- a/lisp/plugins/media_info/media_info.py +++ b/lisp/plugins/media_info/media_info.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -49,12 +49,19 @@ def __init__(self, app): CueLayout.cm_registry.add_separator(MediaCue) CueLayout.cm_registry.add_item(self.menuAction, MediaCue) - def show_info(self, clicked): + def show_info(self): media_uri = self.app.layout.get_context_cue().media.input_uri() - if not media_uri: - QMessageBox.critical(MainWindow(), translate('MediaInfo', 'Error'), - translate('MediaInfo', 'No info to display')) + + if media_uri is None: + QMessageBox.warning( + self.app.window, + translate('MediaInfo', 'Warning'), + translate('MediaInfo', 'No info to display') + ) else: + if media_uri.startswith('file://'): + media_uri = 'file://' + self.app.session.abs_path(media_uri[7:]) + gst_info = gst_uri_metadata(media_uri) info = {'URI': unquote(gst_info.get_uri())} @@ -78,22 +85,24 @@ def show_info(self, clicked): stream.get_framerate_denom())) } - # Media tags - info['Tags'] = {} + # Tags + gst_tags = gst_info.get_tags() + tags = {} + + if gst_tags is not None: + for name, value in gst_parse_tags_list(gst_tags).items(): + tag_txt = str(value) - tags = gst_info.get_tags() - if tags is not None: - tags = gst_parse_tags_list(tags) - for tag in tags: - if type(tags[tag]).__str__ is not object.__str__: - info['Tags'][tag.capitalize()] = str(tags[tag]) + # Include the value only if it's representation make sense + if tag_txt != object.__str__(value): + tags[name.capitalize()] = tag_txt - if not info['Tags']: - info.pop('Tags') + if tags: + info['Tags'] = tags # Show the dialog - dialog = InfoDialog(MainWindow(), info, - self.app.layout.get_context_cue().name) + dialog = InfoDialog( + self.app.window, info, self.app.layout.get_context_cue().name) dialog.exec_() @@ -106,35 +115,33 @@ def __init__(self, parent, info, title): self.setWindowModality(QtCore.Qt.ApplicationModal) self.setMinimumSize(550, 300) self.resize(550, 500) - - self.vLayout = QVBoxLayout(self) + self.setLayout(QVBoxLayout(self)) self.infoTree = QTreeWidget(self) self.infoTree.setColumnCount(2) - self.infoTree.setHeaderLabels([translate('MediaInfo', 'Info'), - translate('MediaInfo', 'Value')]) + self.infoTree.setHeaderLabels( + [translate('MediaInfo', 'Info'), translate('MediaInfo', 'Value')]) self.infoTree.setAlternatingRowColors(True) self.infoTree.setSelectionMode(QAbstractItemView.NoSelection) self.infoTree.setEditTriggers(QAbstractItemView.NoEditTriggers) self.infoTree.header().setStretchLastSection(False) self.infoTree.header().setSectionResizeMode( QHeaderView.ResizeToContents) - self.vLayout.addWidget(self.infoTree) - - self.__generate_items(info) - self.infoTree.expandAll() + self.layout().addWidget(self.infoTree) self.buttonBox = QDialogButtonBox(self) self.buttonBox.setStandardButtons(QDialogButtonBox.Close) - self.vLayout.addWidget(self.buttonBox) - self.buttonBox.rejected.connect(self.close) + self.layout().addWidget(self.buttonBox) + + self.populateTree(info) + self.infoTree.expandAll() - def __generate_items(self, info, parent=None): + def populateTree(self, info, parent=None): for key in sorted(info.keys()): if isinstance(info[key], dict): widget = QTreeWidgetItem([key]) - self.__generate_items(info[key], widget) + self.populateTree(info[key], widget) else: widget = QTreeWidgetItem([key, info[key]]) diff --git a/lisp/plugins/presets/presets.py b/lisp/plugins/presets/presets.py index b71ba4891..246700231 100644 --- a/lisp/plugins/presets/presets.py +++ b/lisp/plugins/presets/presets.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -80,7 +80,7 @@ def __create_from_cue(self): if name is not None: if not (preset_exists(name) and not check_override_dialog(name)): - preset = cue.properties(only_changed=True) + preset = cue.properties(defaults=False) # Discard id and index preset.pop('id') diff --git a/lisp/plugins/timecode/timecode.py b/lisp/plugins/timecode/timecode.py index 9aa13a4af..3dbb68db3 100644 --- a/lisp/plugins/timecode/timecode.py +++ b/lisp/plugins/timecode/timecode.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # Copyright 2016-2017 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify diff --git a/lisp/plugins/triggers/triggers.py b/lisp/plugins/triggers/triggers.py index 63035347d..11b0095f1 100644 --- a/lisp/plugins/triggers/triggers.py +++ b/lisp/plugins/triggers/triggers.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/settings/pages/cue_appearance.py b/lisp/ui/settings/pages/cue_appearance.py index 4c344df54..433e13c21 100644 --- a/lisp/ui/settings/pages/cue_appearance.py +++ b/lisp/ui/settings/pages/cue_appearance.py @@ -94,16 +94,16 @@ def retranslateUi(self): translate('CueAppearanceSettings', 'Select font color')) def enable_check(self, enabled): - self.cueNameGroup.setCheckable(enable) + self.cueNameGroup.setCheckable(enabled) self.cueNameGroup.setChecked(False) - self.cueDescriptionGroup.setChecked(enable) + self.cueDescriptionGroup.setChecked(enabled) self.cueDescriptionGroup.setChecked(False) - self.fontSizeGroup.setCheckable(enable) + self.fontSizeGroup.setCheckable(enabled) self.fontSizeGroup.setChecked(False) - self.colorGroup.setCheckable(enable) + self.colorGroup.setCheckable(enabled) self.colorGroup.setChecked(False) def get_settings(self): diff --git a/lisp/ui/settings/pages/cue_general.py b/lisp/ui/settings/pages/cue_general.py index 1486880ba..439a7196f 100644 --- a/lisp/ui/settings/pages/cue_general.py +++ b/lisp/ui/settings/pages/cue_general.py @@ -209,21 +209,21 @@ def load_settings(self, settings): self.fadeOutEdit.setDuration(settings.get('fadeout_duration', 0)) def enable_check(self, enabled): - self.startActionGroup.setCheckable(enable) + self.startActionGroup.setCheckable(enabled) self.startActionGroup.setChecked(False) - self.stopActionGroup.setCheckable(enable) + self.stopActionGroup.setCheckable(enabled) self.stopActionGroup.setChecked(False) - self.preWaitGroup.setCheckable(enable) + self.preWaitGroup.setCheckable(enabled) self.preWaitGroup.setChecked(False) - self.postWaitGroup.setCheckable(enable) + self.postWaitGroup.setCheckable(enabled) self.postWaitGroup.setChecked(False) - self.nextActionGroup.setCheckable(enable) + self.nextActionGroup.setCheckable(enabled) self.nextActionGroup.setChecked(False) - self.fadeInGroup.setCheckable(enable) + self.fadeInGroup.setCheckable(enabled) self.fadeInGroup.setChecked(False) - self.fadeOutGroup.setCheckable(enable) + self.fadeOutGroup.setCheckable(enabled) self.fadeOutGroup.setChecked(False) def get_settings(self): diff --git a/lisp/ui/settings/pages/media_cue_settings.py b/lisp/ui/settings/pages/media_cue_settings.py index b7fdb259a..26390790f 100644 --- a/lisp/ui/settings/pages/media_cue_settings.py +++ b/lisp/ui/settings/pages/media_cue_settings.py @@ -101,13 +101,13 @@ def get_settings(self): return conf def enable_check(self, enabled): - self.startGroup.setCheckable(enable) + self.startGroup.setCheckable(enabled) self.startGroup.setChecked(False) - self.stopGroup.setCheckable(enable) + self.stopGroup.setCheckable(enabled) self.stopGroup.setChecked(False) - self.loopGroup.setCheckable(enable) + self.loopGroup.setCheckable(enabled) self.loopGroup.setChecked(False) def load_settings(self, settings): From adf3bfed11dc66b92c669e254e18bc1ddbca8b22 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 6 Feb 2018 18:05:52 +0100 Subject: [PATCH 084/333] Add "DoNothing" action for cues --- lisp/core/util.py | 13 +++++++++++ lisp/cues/cue.py | 31 ++++++++++++++++----------- lisp/ui/settings/pages/cue_general.py | 8 +++++-- lisp/ui/widgets/cue_actions.py | 1 + 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/lisp/core/util.py b/lisp/core/util.py index 40c156b56..8e6109ea6 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -25,6 +25,7 @@ from os.path import isdir, exists, join import functools +from uuid import uuid4 def deep_update(d1, d2): @@ -237,3 +238,15 @@ def __init__(self, function): def __call__(self, *args, **kwargs): return self.function(*args, **kwargs) + + +class InstanceOfSubclassMeta(type): + """Some horrible black magic here""" + _MAGIC = str(uuid4()) + + def __call__(cls, *args, **kwargs): + if kwargs.pop(cls._MAGIC, False): + return super().__call__(*args, **kwargs) + + kwargs.update({cls._MAGIC: True}) + return type(cls.__name__, (cls, ), {})(*args, **kwargs) \ No newline at end of file diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 52fab63da..98a744d80 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -60,6 +60,7 @@ class CueAction(EqEnum): Start = 'Start' Stop = 'Stop' Pause = 'Pause' + DoNothing = 'DoNothing' class CueNextAction(EqEnum): @@ -99,6 +100,8 @@ class Cue(HasProperties): A cue should declare CueAction.Default as supported only if CueAction.Start and CueAction.Stop are both supported. If CueAction.Stop is supported, CueAction.Interrupt should be supported. + CueAction.DoNothing doesn't need to be declared, it should always be + considered as supported. .. Note:: If 'next_action' is AutoFollow or DoNothing, the postwait is not @@ -181,30 +184,32 @@ def execute(self, action=CueAction.Default): else: action = CueAction(self.default_start_action) - if action is CueAction.Interrupt: - self.interrupt() - elif action is CueAction.FadeOutInterrupt: - self.interrupt(fade=True) - elif action in self.CueActions: - if action == CueAction.Start: + if action is CueAction.DoNothing: + return + if action in self.CueActions: + if action is CueAction.Interrupt: + self.interrupt() + elif action is CueAction.FadeOutInterrupt: + self.interrupt(fade=True) + if action is CueAction.Start: self.start() - elif action == CueAction.FadeInStart: + elif action is CueAction.FadeInStart: self.start(fade=self.fadein_duration > 0) - elif action == CueAction.Stop: + elif action is CueAction.Stop: self.stop() - elif action == CueAction.FadeOutStop: + elif action is CueAction.FadeOutStop: self.stop(fade=self.fadeout_duration > 0) - elif action == CueAction.Pause: + elif action is CueAction.Pause: self.pause() - elif action == CueAction.FadeOutPause: + elif action is CueAction.FadeOutPause: self.pause(fade=self.fadeout_duration > 0) - elif action == CueAction.FadeOut: + elif action is CueAction.FadeOut: duration = AppConfig().get('Cue', 'FadeActionDuration', 0) fade = AppConfig().get( 'Cue', 'FadeActionType', FadeOutType.Linear.name) self.fadeout(duration, FadeOutType[fade]) - elif action == CueAction.FadeIn: + elif action is CueAction.FadeIn: duration = AppConfig().get('Cue', 'FadeActionDuration', 0) fade = AppConfig().get( 'Cue', 'FadeActionType', FadeInType.Linear.name) diff --git a/lisp/ui/settings/pages/cue_general.py b/lisp/ui/settings/pages/cue_general.py index 439a7196f..efefc4ad0 100644 --- a/lisp/ui/settings/pages/cue_general.py +++ b/lisp/ui/settings/pages/cue_general.py @@ -52,7 +52,9 @@ def __init__(self, cue_class, **kwargs): { CueAction.Start, CueAction.FadeInStart - }.intersection(cue_class.CueActions), + }.intersection(cue_class.CueActions).union({ + CueAction.DoNothing + }), mode=CueActionComboBox.Mode.Value, parent=self.startActionGroup ) @@ -74,7 +76,9 @@ def __init__(self, cue_class, **kwargs): CueAction.Pause, CueAction.FadeOutStop, CueAction.FadeOutPause - }.intersection(cue_class.CueActions), + }.intersection(cue_class.CueActions).union({ + CueAction.DoNothing + }), mode=CueActionComboBox.Mode.Value, parent=self.stopActionGroup ) diff --git a/lisp/ui/widgets/cue_actions.py b/lisp/ui/widgets/cue_actions.py index 98b6fa471..0ba91063e 100644 --- a/lisp/ui/widgets/cue_actions.py +++ b/lisp/ui/widgets/cue_actions.py @@ -36,6 +36,7 @@ CueAction.Start: QT_TRANSLATE_NOOP('CueAction', 'Start'), CueAction.Stop: QT_TRANSLATE_NOOP('CueAction', 'Stop'), CueAction.Pause: QT_TRANSLATE_NOOP('CueAction', 'Pause'), + CueAction.DoNothing: QT_TRANSLATE_NOOP('CueAction', 'Do Nothing'), } From b77f037e7fca933903e6e7767e2a7ec738a0a453 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 8 Feb 2018 01:03:37 +0100 Subject: [PATCH 085/333] Rewrite configuration classes, again Update: all configuration keys are now camelCase Update: configuration keys are now in dotted notation ('a.b') Update: configuration objects now provides a "changed" signal Minor changes/fixes --- lisp/application.py | 2 +- lisp/core/configuration.py | 174 +++++++++++------- lisp/core/util.py | 15 +- lisp/cues/cue.py | 8 +- lisp/cues/media_cue.py | 4 +- lisp/default.json | 85 ++++----- .../cart_layout/cart_layout_settings.py | 38 ++-- lisp/layouts/cart_layout/layout.py | 16 +- lisp/layouts/cue_layout.py | 8 +- lisp/layouts/list_layout/layout.py | 37 ++-- .../list_layout/list_layout_settings.py | 48 ++--- .../list_layout/playing_mediawidget.py | 15 +- lisp/main.py | 4 +- lisp/plugins/__init__.py | 4 +- lisp/plugins/gst_backend/__init__.py | 5 +- lisp/plugins/gst_backend/default.json | 4 +- lisp/plugins/gst_backend/gst_backend.py | 2 +- lisp/plugins/gst_backend/gst_settings.py | 4 +- lisp/plugins/midi/default.json | 8 +- lisp/plugins/midi/midi.py | 6 +- lisp/plugins/midi/midi_settings.py | 22 +-- lisp/plugins/osc/default.json | 8 +- lisp/plugins/osc/osc.py | 6 +- lisp/plugins/osc/osc_settings.py | 12 +- lisp/plugins/remote/default.json | 12 +- lisp/plugins/remote/remote.py | 12 +- lisp/plugins/synchronizer/default.json | 10 + lisp/plugins/synchronizer/peers_dialog.py | 30 +-- .../synchronizer/peers_discovery_dialog.py | 6 +- lisp/plugins/synchronizer/synchronizer.py | 3 +- lisp/plugins/timecode/default.json | 6 +- lisp/plugins/timecode/settings.py | 8 +- lisp/plugins/timecode/timecode.py | 2 +- lisp/ui/settings/pages/app_general.py | 14 +- lisp/ui/settings/pages/cue_app_settings.py | 18 +- 35 files changed, 354 insertions(+), 302 deletions(-) create mode 100644 lisp/plugins/synchronizer/default.json diff --git a/lisp/application.py b/lisp/application.py index 79f881efa..0ef355b20 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -95,7 +95,7 @@ def start(self, session_file=''): if exists(session_file): self._load_from_file(session_file) else: - layout = AppConfig().get('Layout', 'Default', default='nodefault') + layout = AppConfig().get('layout.default', 'nodefault') if layout.lower() != 'nodefault': self._new_session(layouts.get_layout(layout)) diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index be5740e95..997b12966 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,25 +17,115 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from collections import Mapping -from os import path -from shutil import copyfile +# Used to indicate the default behaviour when a specific option is not found to +# raise an exception. Created to enable `None' as a valid fallback value. import json +from abc import ABCMeta, abstractmethod +from copy import deepcopy +from os import path +from shutil import copyfile -from lisp.core.util import deep_update - -from lisp import USER_APP_CONFIG, DEFAULT_APP_CONFIG -from lisp.core.singleton import Singleton +from lisp import DEFAULT_APP_CONFIG, USER_APP_CONFIG +from lisp.core.signal import Signal # Used to indicate the default behaviour when a specific option is not found to # raise an exception. Created to enable `None' as a valid fallback value. +from lisp.core.singleton import ABCSingleton + _UNSET = object() -# TODO: HasProperties? -class Configuration: - """Allow to read/write json based configuration files. +class Configuration(metaclass=ABCMeta): + """ABC for a configuration object. + + Subclasses need to implement `read`, `write` methods. + + Keep in mind that the `set` and `update` methods ignores non-existing keys. + """ + + def __init__(self): + self._root = {} + self.changed = Signal() + + @abstractmethod + def read(self): + pass + + @abstractmethod + def write(self): + pass + + def get(self, path, default=_UNSET): + try: + node, key = self.__traverse(path.split('.'), self._root) + return node[key] + except (KeyError, TypeError) as e: + if default is not _UNSET: + return default + + raise e + + def set(self, path, value): + try: + node, key = self.__traverse(path.split('.'), self._root) + old, node[key] = node[key], value + + self.changed.emit(path, old, value) + except (KeyError, TypeError): + pass + + def __traverse(self, keys, root): + next_step = keys.pop(0) + + if keys: + return self.__traverse(keys, root[next_step]) + + return root, next_step + + def update(self, new_conf): + self.__update(self._root, new_conf) + + def __update(self, root, new_conf, _path=''): + for key, value in new_conf.items(): + if key in root: + _path += '.' + key + + if isinstance(root[key], dict): + self.__update(root[key], value, _path) + else: + old, root[key] = root[key], value + self.changed.emit(_path[1:], old, value) + + def copy(self): + return deepcopy(self._root) + + def __getitem__(self, path): + return self.get(path) + + def __setitem__(self, path, value): + self.set(path, value) + + def __contains__(self, path): + try: + node, key = self.__traverse(path.split('.'), self._root) + return key in node + except (KeyError, TypeError): + return False + + +class DummyConfiguration(Configuration): + """Configuration without read/write capabilities.""" + + def read(self): + pass + + def write(self): + pass + + +class JSONFileConfiguration(Configuration): + """Read/Write configurations from/to a JSON file. Two path must be provided on creation, user-path and default-path, the first one is used to read/write, the second is copied over when the @@ -50,7 +140,7 @@ class Configuration: """ def __init__(self, user_path, default_path, read=True): - self.__config = {} + super().__init__() self.user_path = user_path self.default_path = default_path @@ -59,14 +149,15 @@ def __init__(self, user_path, default_path, read=True): self.read() def read(self): - self.check() - self.__config = self._read_json(self.user_path) + self._check() + self._root = self._read_json(self.user_path) def write(self): with open(self.user_path, 'w') as f: - json.dump(self.__config, f, indent=True) + json.dump(self._root, f, indent=True) - def check(self): + def _check(self): + """Ensure the last configuration is present at the user-path position""" if path.exists(self.user_path): # Read default configuration default = self._read_json(self.default_path) @@ -88,58 +179,9 @@ def _read_json(path): with open(path, 'r') as f: return json.load(f) - def get(self, *path, default=_UNSET): - value = self.__config - for key in path: - if isinstance(value, Mapping): - try: - value = value[key] - except KeyError: - if default is _UNSET: - raise - return default - else: - break - - return value - - def copy(self): - return self.__config.copy() - - def update(self, update_dict): - deep_update(self.__config, update_dict) - - def __getitem__(self, item): - return self.__config.__getitem__(item) - - def __setitem__(self, key, value): - return self.__config.__setitem__(key, value) - - def __contains__(self, key): - return self.__config.__contains__(key) - - -class DummyConfiguration(Configuration): - """Configuration without read/write capabilities. - - Can be used for uninitialized component. - """ - - def __init__(self): - super().__init__('', '', False) - - def read(self): - pass - - def write(self): - pass - - def check(self): - pass - # TODO: we should remove this in favor of a non-singleton -class AppConfig(Configuration, metaclass=Singleton): +class AppConfig(JSONFileConfiguration, metaclass=ABCSingleton): """Provide access to the application configuration (singleton)""" def __init__(self): diff --git a/lisp/core/util.py b/lisp/core/util.py index 8e6109ea6..79711437a 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -81,9 +81,12 @@ def strtime(time, accurate=False): return '{:02}:{:02}.00'.format(*time[1:3]) -def compose_http_url(url, port, directory='/'): +def compose_http_url(address, port, path='/'): """Compose an http URL.""" - return 'http://' + url + ':' + str(port) + directory + if not path.startswith('/'): + path = '/' + path + + return 'http://{}:{}{}'.format(address, port, path) def greatest_common_superclass(instances): @@ -101,7 +104,7 @@ def get_lan_ip(): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: # Doesn't have to be reachable - s.connect(('10.255.255.255', 0)) + s.connect(('10.10.10.10', 0)) ip = s.getsockname()[0] except: ip = '127.0.0.1' @@ -241,7 +244,11 @@ def __call__(self, *args, **kwargs): class InstanceOfSubclassMeta(type): - """Some horrible black magic here""" + """Some horrible black magic here + + When creating an object from a class using this metaclass, + an instance of a subclass created on-the-fly will be returned. + """ _MAGIC = str(uuid4()) def __call__(cls, *args, **kwargs): diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 98a744d80..8ff55e0de 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -204,15 +204,15 @@ def execute(self, action=CueAction.Default): elif action is CueAction.FadeOutPause: self.pause(fade=self.fadeout_duration > 0) elif action is CueAction.FadeOut: - duration = AppConfig().get('Cue', 'FadeActionDuration', 0) + duration = AppConfig().get('cue.fadeActionDuration', 0) fade = AppConfig().get( - 'Cue', 'FadeActionType', FadeOutType.Linear.name) + 'cue.fadeActionType', FadeOutType.Linear.name) self.fadeout(duration, FadeOutType[fade]) elif action is CueAction.FadeIn: - duration = AppConfig().get('Cue', 'FadeActionDuration', 0) + duration = AppConfig().get('cue.fadeActionDuration', 0) fade = AppConfig().get( - 'Cue', 'FadeActionType', FadeInType.Linear.name) + 'cue.fadeActionType', FadeInType.Linear.name) self.fadein(duration, FadeInType[fade]) diff --git a/lisp/cues/media_cue.py b/lisp/cues/media_cue.py index f095c82b4..8403e0bed 100644 --- a/lisp/cues/media_cue.py +++ b/lisp/cues/media_cue.py @@ -207,9 +207,9 @@ def _on_start_fade(self): def _on_stop_fade(self, interrupt=False): if interrupt: - duration = AppConfig().get('Cue', 'InterruptFade', 0) + duration = AppConfig().get('cue.interruptFade', 0) fade_type = AppConfig().get( - 'Cue', 'InterruptFadeType', FadeOutType.Linear.name) + 'cue.interruptFadeType', FadeOutType.Linear.name) else: duration = self.fadeout_duration fade_type = self.fadeout_type diff --git a/lisp/default.json b/lisp/default.json index 257ed3ab5..4b5fd673d 100644 --- a/lisp/default.json +++ b/lisp/default.json @@ -1,54 +1,49 @@ { - "_version_": "0.6dev.1", - "Cue": { - "FadeActionDuration": 3, - "FadeActionType": "Linear", - "InterruptFade": 3, - "InterruptFadeType": "Linear" + "_version_": "0.6dev.3", + "cue": { + "fadeActionDuration": 3, + "fadeActionType": "Linear", + "interruptFade": 3, + "interruptFadeType": "Linear" }, - "Theme": { - "Theme": "Dark", - "Icons": "numix" + "theme": { + "theme": "Dark", + "icons": "numix" }, - "Backend": { - "Default": "gst" + "backend": { + "default": "gst" }, - "Layout": { - "Default": "NoDefault", - "StopAllFade": false, - "PauseAllFade": false, - "ResumeAllFade": false, - "InterruptAllFade": true + "layout": { + "default": "NoDefault", + "stopAllFade": false, + "pauseAllFade": false, + "resumeAllFade": false, + "interruptAllFade": true }, - "Actions": { - "MaxStackSize": 0 + "actions": { + "maxStackSize": 0 }, - "CartLayout": { - "GridColumns": 7, - "GridRows": 4, - "ShowSeek": false, - "ShowDbMeters": false, - "ShowAccurate": false, - "ShowVolume": false, - "Countdown": true, - "AutoAddPage": true + "cartLayout": { + "gridColumns": 7, + "gridRows": 4, + "showSeek": false, + "showDbMeters": false, + "showAccurate": false, + "showVolume": false, + "countdown": true, + "autoAddPage": true }, - "ListLayout": { - "ShowDbMeters": true, - "ShowSeek": true, - "ShowAccurate": false, - "ShowPlaying": true, - "AutoContinue": true, - "EndList": "Stop", - "GoKey": "Space", - "StopCueFade": true, - "PauseCueFade": true, - "ResumeCueFade": true, - "InterruptCueFade": true - }, - "DbMeter": { - "dBMax": 0, - "dbMin": -60, - "dbClip": 0 + "listLayout": { + "showDbMeters": true, + "showSeek": true, + "showAccurate": false, + "showPlaying": true, + "autoContinue": true, + "endList": "Stop", + "goKey": "Space", + "stopCueFade": true, + "pauseCueFade": true, + "resumeCueFade": true, + "interruptCueFade": true } } diff --git a/lisp/layouts/cart_layout/cart_layout_settings.py b/lisp/layouts/cart_layout/cart_layout_settings.py index 2957dd07d..0dae0b6b3 100644 --- a/lisp/layouts/cart_layout/cart_layout_settings.py +++ b/lisp/layouts/cart_layout/cart_layout_settings.py @@ -95,26 +95,26 @@ def retranslateUi(self): def get_settings(self): conf = { - 'GridColumns': self.columnsSpin.value(), - 'GridRows': self.rowsSpin.value(), - 'ShowDbMeters': self.showDbMeters.isChecked(), - 'ShowSeek': self.showSeek.isChecked(), - 'ShowAccurate': self.showAccurate.isChecked(), - 'ShowVolume': self.showVolume.isChecked(), - 'Countdown': self.countdownMode.isChecked(), - 'AutoAddPage': self.autoAddPage.isChecked() + 'gridColumns': self.columnsSpin.value(), + 'gridRows': self.rowsSpin.value(), + 'showDbMeters': self.showDbMeters.isChecked(), + 'showSeek': self.showSeek.isChecked(), + 'showAccurate': self.showAccurate.isChecked(), + 'showVolume': self.showVolume.isChecked(), + 'countdown': self.countdownMode.isChecked(), + 'autoAddPage': self.autoAddPage.isChecked() } - return {'CartLayout': conf} + return {'cartLayout': conf} def load_settings(self, settings): - settings = settings.get('CartLayout', {}) - - self.columnsSpin.setValue(settings['GridColumns']) - self.rowsSpin.setValue(settings['GridRows']) - self.showSeek.setChecked(settings['ShowSeek']) - self.showDbMeters.setChecked(settings['ShowDbMeters']) - self.showAccurate.setChecked(settings['ShowAccurate']) - self.showVolume.setChecked(settings['ShowVolume']) - self.countdownMode.setChecked(settings['Countdown']) - self.autoAddPage.setChecked(settings['AutoAddPage']) + settings = settings.get('cartLayout', {}) + + self.columnsSpin.setValue(settings['gridColumns']) + self.rowsSpin.setValue(settings['gridRows']) + self.showSeek.setChecked(settings['showSeek']) + self.showDbMeters.setChecked(settings['showDbMeters']) + self.showAccurate.setChecked(settings['showAccurate']) + self.showVolume.setChecked(settings['showVolume']) + self.countdownMode.setChecked(settings['countdown']) + self.autoAddPage.setChecked(settings['autoAddPage']) diff --git a/lisp/layouts/cart_layout/layout.py b/lisp/layouts/cart_layout/layout.py index 281668516..6f2a3c541 100644 --- a/lisp/layouts/cart_layout/layout.py +++ b/lisp/layouts/cart_layout/layout.py @@ -56,17 +56,17 @@ def __init__(self, cue_model, **kwargs): super().__init__(cue_model=cue_model, **kwargs) self.tabBar().setObjectName('CartTabBar') - self.__columns = AppConfig()['CartLayout']['GridColumns'] - self.__rows = AppConfig()['CartLayout']['GridRows'] + self.__columns = AppConfig()['cartLayout.gridColumns'] + self.__rows = AppConfig()['cartLayout.gridRows'] self.__pages = [] self.__context_widget = None - self._show_seek = AppConfig()['CartLayout']['ShowSeek'] - self._show_dbmeter = AppConfig()['CartLayout']['ShowDbMeters'] - self._show_volume = AppConfig()['CartLayout']['ShowVolume'] - self._accurate_timing = AppConfig()['CartLayout']['ShowAccurate'] - self._countdown_mode = AppConfig()['CartLayout']['Countdown'] - self._auto_add_page = AppConfig()['CartLayout']['AutoAddPage'] + self._show_seek = AppConfig()['cartLayout.showSeek'] + self._show_dbmeter = AppConfig()['cartLayout.showDbMeters'] + self._show_volume = AppConfig()['cartLayout.showVolume'] + self._accurate_timing = AppConfig()['cartLayout.showAccurate'] + self._countdown_mode = AppConfig()['cartLayout.countdown'] + self._auto_add_page = AppConfig()['cartLayout.autoAddPage'] self._model_adapter = CueCartModel(cue_model, self.__rows, self.__columns) self._model_adapter.item_added.connect(self.__cue_added, Connection.QtQueued) diff --git a/lisp/layouts/cue_layout.py b/lisp/layouts/cue_layout.py index 5ac17e73e..7f04f1b8a 100644 --- a/lisp/layouts/cue_layout.py +++ b/lisp/layouts/cue_layout.py @@ -94,22 +94,22 @@ def go(self, action=CueAction.Default, advance=1): """ def stop_all(self): - fade = AppConfig().get('Layout', 'StopAllFade') + fade = AppConfig().get('layout.stopAllFade', False) for cue in self.model_adapter: cue.stop(fade=fade) def interrupt_all(self): - fade = AppConfig().get('Layout','InterruptAllFade') + fade = AppConfig().get('layout.interruptAllFade', False) for cue in self.model_adapter: cue.interrupt(fade=fade) def pause_all(self): - fade = AppConfig().get('Layout','PauseAllFade') + fade = AppConfig().get('layout.pauseAllFade', False) for cue in self.model_adapter: cue.pause(fade=fade) def resume_all(self): - fade = AppConfig().get('Layout','ResumeAllFade') + fade = AppConfig().get('layout.resumeAllFade', True) for cue in self.model_adapter: cue.resume(fade=fade) diff --git a/lisp/layouts/list_layout/layout.py b/lisp/layouts/list_layout/layout.py index cf870d124..fcf480a73 100644 --- a/lisp/layouts/list_layout/layout.py +++ b/lisp/layouts/list_layout/layout.py @@ -50,16 +50,17 @@ class EndListBehavior(Enum): class ListLayout(QWidget, CueLayout): NAME = 'List Layout' - DESCRIPTION = QT_TRANSLATE_NOOP('LayoutDescription', - 'Organize the cues in a list') + DESCRIPTION = QT_TRANSLATE_NOOP( + 'LayoutDescription', 'Organize the cues in a list') DETAILS = [ - QT_TRANSLATE_NOOP('LayoutDetails', - 'SHIFT + Space or Double-Click to edit a cue'), - QT_TRANSLATE_NOOP('LayoutDetails', - 'CTRL + Left Click to select cues'), - QT_TRANSLATE_NOOP('LayoutDetails', - 'To copy cues drag them while pressing CTRL'), - QT_TRANSLATE_NOOP('LayoutDetails', 'To move cues drag them') + QT_TRANSLATE_NOOP( + 'LayoutDetails', 'SHIFT + Space or Double-Click to edit a cue'), + QT_TRANSLATE_NOOP( + 'LayoutDetails', 'CTRL + Left Click to select cues'), + QT_TRANSLATE_NOOP( + 'LayoutDetails', 'To copy cues drag them while pressing CTRL'), + QT_TRANSLATE_NOOP( + 'LayoutDetails', 'To move cues drag them') ] def __init__(self, cue_model, **kwargs): @@ -75,16 +76,16 @@ def __init__(self, cue_model, **kwargs): self._context_item = None self._next_cue_index = 0 - self._show_dbmeter = AppConfig()['ListLayout']['ShowDbMeters'] - self._seek_visible = AppConfig()['ListLayout']['ShowSeek'] - self._accurate_time = AppConfig()['ListLayout']['ShowAccurate'] - self._auto_continue = AppConfig()['ListLayout']['AutoContinue'] - self._show_playing = AppConfig()['ListLayout']['ShowPlaying'] - self._go_key = AppConfig()['ListLayout']['GoKey'] - self._go_key_sequence = QKeySequence(self._go_key, - QKeySequence.NativeText) + self._show_dbmeter = AppConfig()['listLayout.showDbMeters'] + self._seek_visible = AppConfig()['listLayout.showSeek'] + self._accurate_time = AppConfig()['listLayout.showAccurate'] + self._auto_continue = AppConfig()['listLayout.autoContinue'] + self._show_playing = AppConfig()['listLayout.showPlaying'] + self._go_key = AppConfig()['listLayout.goKey'] + self._go_key_sequence = QKeySequence( + self._go_key, QKeySequence.NativeText) - self._end_list = EndListBehavior(AppConfig()['ListLayout']['EndList']) + self._end_list = EndListBehavior(AppConfig()['listLayout.endList']) # Add layout-specific menus self.showPlayingAction = QAction(self) diff --git a/lisp/layouts/list_layout/list_layout_settings.py b/lisp/layouts/list_layout/list_layout_settings.py index 0aff95810..097a0eb6e 100644 --- a/lisp/layouts/list_layout/list_layout_settings.py +++ b/lisp/layouts/list_layout/list_layout_settings.py @@ -126,44 +126,44 @@ def retranslateUi(self): def get_settings(self): settings = { - 'ShowPlaying': self.showPlaying.isChecked(), - 'ShowDbMeters': self.showDbMeters.isChecked(), - 'ShowSeek': self.showSeek.isChecked(), - 'ShowAccurate': self.showAccurate.isChecked(), - 'AutoContinue': self.autoNext.isChecked(), - 'EndList': self.endListBehavior.currentData(), - 'GoKey': self.goKeyEdit.keySequence().toString( + 'showPlaying': self.showPlaying.isChecked(), + 'showDbMeters': self.showDbMeters.isChecked(), + 'showSeek': self.showSeek.isChecked(), + 'showAccurate': self.showAccurate.isChecked(), + 'autoContinue': self.autoNext.isChecked(), + 'endList': self.endListBehavior.currentData(), + 'goKey': self.goKeyEdit.keySequence().toString( QKeySequence.NativeText), - 'StopCueFade': self.stopCueFade.isChecked(), - 'PauseCueFade': self.pauseCueFade.isChecked(), - 'ResumeCueFade': self.resumeCueFade.isChecked(), - 'InterruptCueFade': self.interruptCueFade.isChecked(), + 'stopCueFade': self.stopCueFade.isChecked(), + 'pauseCueFade': self.pauseCueFade.isChecked(), + 'resumeCueFade': self.resumeCueFade.isChecked(), + 'interruptCueFade': self.interruptCueFade.isChecked(), #'StopAllFade': self.stopAllFade.isChecked(), #'PauseAllFade': self.pauseAllFade.isChecked(), #'ResumeAllFade': self.resumeAllFade.isChecked(), #'InterruptAllFade': self.interruptAllFade.isChecked(), } - return {'ListLayout': settings} + return {'listLayout': settings} def load_settings(self, settings): - settings = settings.get('ListLayout', {}) + settings = settings.get('listLayout', {}) - self.showPlaying.setChecked(settings['ShowPlaying']) - self.showDbMeters.setChecked(settings['ShowDbMeters']) - self.showAccurate.setChecked(settings['ShowAccurate']) - self.showSeek.setChecked(settings['ShowSeek']) - self.autoNext.setChecked(settings['AutoContinue']) + self.showPlaying.setChecked(settings['showPlaying']) + self.showDbMeters.setChecked(settings['showDbMeters']) + self.showAccurate.setChecked(settings['showAccurate']) + self.showSeek.setChecked(settings['showSeek']) + self.autoNext.setChecked(settings['autoContinue']) self.endListBehavior.setCurrentText( - translate('ListLayout', settings.get('EndList', ''))) + translate('ListLayout', settings.get('endList', ''))) self.goKeyEdit.setKeySequence( - QKeySequence(settings.get('GoKey', 'Space'), + QKeySequence(settings.get('goKey', 'Space'), QKeySequence.NativeText)) - self.stopCueFade.setChecked(settings['StopCueFade']) - self.pauseCueFade.setChecked(settings['PauseCueFade']) - self.resumeCueFade.setChecked(settings['ResumeCueFade']) - self.interruptCueFade.setChecked(settings['InterruptCueFade']) + self.stopCueFade.setChecked(settings['stopCueFade']) + self.pauseCueFade.setChecked(settings['pauseCueFade']) + self.resumeCueFade.setChecked(settings['resumeCueFade']) + self.interruptCueFade.setChecked(settings['interruptCueFade']) #self.stopAllFade.setChecked(settings['StopAllFade']) #self.pauseAllFade.setChecked(settings['PauseAllFade']) diff --git a/lisp/layouts/list_layout/playing_mediawidget.py b/lisp/layouts/list_layout/playing_mediawidget.py index b3ba6dc68..aaa0295e4 100644 --- a/lisp/layouts/list_layout/playing_mediawidget.py +++ b/lisp/layouts/list_layout/playing_mediawidget.py @@ -143,21 +143,24 @@ def _time_updated(self, time): self._update_timers(time) def _update_timers(self, time): - self.timeDisplay.display(strtime(self.cue.duration - time, - accurate=self._accurate_time)) + self.timeDisplay.display( + strtime(self.cue.duration - time, accurate=self._accurate_time)) def _pause(self): - self.cue.pause(fade=AppConfig().get('ListLayout', 'PauseCueFade')) + self.cue.pause( + fade=AppConfig().get('listLayout.pauseCueFade', True)) def _resume(self): - self.cue.resume(fade=AppConfig().get('ListLayout', 'ResumeCueFade')) + self.cue.resume( + fade=AppConfig().get('listLayout.resumeCueFade', True)) def _stop(self): - self.cue.stop(fade=AppConfig().get('ListLayout', 'StopCueFade')) + self.cue.stop( + fade=AppConfig().get('listLayout.stopCueFade', True)) def _interrupt(self): self.cue.interrupt( - fade=AppConfig().get('ListLayout', 'InterruptCueFade')) + fade=AppConfig().get('listLayout.interruptCueFade', True)) def _fadeout(self): self.cue.execute(CueAction.FadeOut) diff --git a/lisp/main.py b/lisp/main.py index b4d946ff0..ba77eeeca 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -76,8 +76,8 @@ def main(): qt_app.setFont(appFont) # Set icons and theme from the application configuration QIcon.setThemeSearchPaths(styles.IconsThemePaths) - QIcon.setThemeName(AppConfig()['Theme']['Icons']) - styles.apply_style(AppConfig()['Theme']['Theme']) + QIcon.setThemeName(AppConfig()['theme.icons']) + styles.apply_style(AppConfig()['theme.theme']) # Get/Set the locale locale = args.locale diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index 0aa25a56c..70572e61d 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -21,7 +21,7 @@ from os import path from lisp import USER_DIR -from lisp.core.configuration import Configuration +from lisp.core.configuration import JSONFileConfiguration from lisp.core.loading import load_classes from lisp.ui import elogging from lisp.ui.ui_utils import install_translation @@ -47,7 +47,7 @@ def load_plugins(application): default_config_path = FALLBACK_CONFIG_PATH # Load plugin configuration - config = Configuration( + config = JSONFileConfiguration( path.join(USER_DIR, mod_name + '.json'), default_config_path ) diff --git a/lisp/plugins/gst_backend/__init__.py b/lisp/plugins/gst_backend/__init__.py index 348756eee..54de010ad 100644 --- a/lisp/plugins/gst_backend/__init__.py +++ b/lisp/plugins/gst_backend/__init__.py @@ -1,4 +1 @@ -from lisp.core.configuration import AppConfig - -if AppConfig()['Backend']['Default'].lower() == 'gst': - from .gst_backend import GstBackend +from .gst_backend import GstBackend diff --git a/lisp/plugins/gst_backend/default.json b/lisp/plugins/gst_backend/default.json index 3ccf18105..11765c58e 100644 --- a/lisp/plugins/gst_backend/default.json +++ b/lisp/plugins/gst_backend/default.json @@ -1,5 +1,5 @@ { - "_version_": "0.1", + "_version_": "2", "_enabled_": true, - "Pipeline": ["Volume", "Equalizer10", "DbMeter", "AutoSink"] + "pipeline": ["Volume", "Equalizer10", "DbMeter", "AutoSink"] } \ No newline at end of file diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index ac3c65082..72c42f8ad 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -111,7 +111,7 @@ def _add_uri_audio_cue(self): QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) # Create media cues, and add them to the Application cue_model - factory = UriAudioCueFactory(GstBackend.Config['Pipeline']) + factory = UriAudioCueFactory(GstBackend.Config['pipeline']) for file in files: file = self.app.session.rel_path(file) diff --git a/lisp/plugins/gst_backend/gst_settings.py b/lisp/plugins/gst_backend/gst_settings.py index ef23af1f1..cdfb9337f 100644 --- a/lisp/plugins/gst_backend/gst_settings.py +++ b/lisp/plugins/gst_backend/gst_settings.py @@ -46,7 +46,7 @@ def retranslateUi(self): self.pipeGroup.setTitle(translate('GstSettings', 'Pipeline')) def get_settings(self): - return {'Pipeline': list(self.pipeEdit.get_pipe())} + return {'pipeline': list(self.pipeEdit.get_pipe())} def load_settings(self, settings): - self.pipeEdit.set_pipe(settings['Pipeline']) + self.pipeEdit.set_pipe(settings['pipeline']) diff --git a/lisp/plugins/midi/default.json b/lisp/plugins/midi/default.json index 836614749..d1ead3531 100644 --- a/lisp/plugins/midi/default.json +++ b/lisp/plugins/midi/default.json @@ -1,7 +1,7 @@ { - "_version_": "0.1", + "_version_": "2", "_enabled_": true, - "Backend": "mido.backends.rtmidi", - "InputDevice": "", - "OutputDevice": "" + "backend": "mido.backends.rtmidi", + "inputDevice": "", + "outputDevice": "" } \ No newline at end of file diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index 8d7e393d7..7c82cceaf 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -40,16 +40,16 @@ def __init__(self, app): AppSettings.register_settings_widget(MIDISettings, Midi.Config) # Load the backend and set it as current mido backend - self.backend = mido.Backend(Midi.Config['Backend'], load=True) + self.backend = mido.Backend(Midi.Config['backend'], load=True) mido.set_backend(self.backend) # Create default I/O and open the ports/devices self.__input = MIDIInput( - self._input_name(Midi.Config['InputDevice'])) + self._input_name(Midi.Config['inputDevice'])) self.__input.open() self.__output = MIDIOutput( - self._output_name(Midi.Config['OutputDevice'])) + self._output_name(Midi.Config['outputDevice'])) self.__output.open() def _input_name(self, port_name): diff --git a/lisp/plugins/midi/midi_settings.py b/lisp/plugins/midi/midi_settings.py index 3d316d388..4dc85fb98 100644 --- a/lisp/plugins/midi/midi_settings.py +++ b/lisp/plugins/midi/midi_settings.py @@ -21,8 +21,6 @@ from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QComboBox, QGridLayout, \ QLabel -from lisp.plugins.midi.midi_input import MIDIInput -from lisp.plugins.midi.midi_output import MIDIOutput from lisp.plugins.midi.midi_utils import mido_backend from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.ui_utils import translate @@ -42,14 +40,14 @@ def __init__(self, **kwargs): self.midiGroup.setLayout(QGridLayout()) self.layout().addWidget(self.midiGroup) - self.inputLabel = QLabel(translate('MIDISettings', 'Input'), - self.midiGroup) + self.inputLabel = QLabel( + translate('MIDISettings', 'Input'), self.midiGroup) self.midiGroup.layout().addWidget(self.inputLabel, 0, 0) self.inputCombo = QComboBox(self.midiGroup) self.midiGroup.layout().addWidget(self.inputCombo, 0, 1) - self.outputLabel = QLabel(translate('MIDISettings', 'Output'), - self.midiGroup) + self.outputLabel = QLabel( + translate('MIDISettings', 'Output'), self.midiGroup) self.midiGroup.layout().addWidget(self.outputLabel, 1, 0) self.outputCombo = QComboBox(self.midiGroup) self.midiGroup.layout().addWidget(self.outputCombo, 1, 1) @@ -68,23 +66,23 @@ def get_settings(self): if self.isEnabled(): input = self.inputCombo.currentText() if input == 'Default': - conf['InputDevice'] = "" + conf['inputDevice'] = '' else: - conf['InputDevice'] = input + conf['inputDevice'] = input output = self.outputCombo.currentText() if output == 'Default': - conf['OutputDevice'] = "" + conf['outputDevice'] = '' else: - conf['OutputDevice'] = output + conf['outputDevice'] = output return conf def load_settings(self, settings): self.inputCombo.setCurrentText('Default') - self.inputCombo.setCurrentText(settings['InputDevice']) + self.inputCombo.setCurrentText(settings['inputDevice']) self.outputCombo.setCurrentText('Default') - self.outputCombo.setCurrentText(settings['OutputDevice']) + self.outputCombo.setCurrentText(settings['outputDevice']) def _load_devices(self): backend = mido_backend() diff --git a/lisp/plugins/osc/default.json b/lisp/plugins/osc/default.json index 92f8e0cd1..1d407a097 100644 --- a/lisp/plugins/osc/default.json +++ b/lisp/plugins/osc/default.json @@ -1,7 +1,7 @@ { - "_version_": "1", + "_version_": "2", "_enabled_": true, - "InPort": 9000, - "OutPort": 9001, - "Hostname": "localhost" + "inPort": 9000, + "outPort": 9001, + "hostname": "localhost" } \ No newline at end of file diff --git a/lisp/plugins/osc/osc.py b/lisp/plugins/osc/osc.py index e78393003..ffdb5165f 100644 --- a/lisp/plugins/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -41,9 +41,9 @@ def __init__(self, app): # Create a server instance self.__server = OscServer( - Osc.Config['Hostname'], - Osc.Config['InPort'], - Osc.Config['OutPort'] + Osc.Config['hostname'], + Osc.Config['inPort'], + Osc.Config['outPort'] ) @property diff --git a/lisp/plugins/osc/osc_settings.py b/lisp/plugins/osc/osc_settings.py index b26c4a52b..c8fbf1924 100644 --- a/lisp/plugins/osc/osc_settings.py +++ b/lisp/plugins/osc/osc_settings.py @@ -69,12 +69,12 @@ def __init__(self, **kwargs): def get_settings(self): return { - 'InPort': self.inportBox.value(), - 'OutPort': self.outportBox.value(), - 'Hostname': self.hostnameEdit.text(), + 'inPort': self.inportBox.value(), + 'outPort': self.outportBox.value(), + 'hostname': self.hostnameEdit.text(), } def load_settings(self, settings): - self.inportBox.setValue(settings['InPort']) - self.outportBox.setValue(settings['OutPort']) - self.hostnameEdit.setText(settings['Hostname']) + self.inportBox.setValue(settings['inPort']) + self.outportBox.setValue(settings['outPort']) + self.hostnameEdit.setText(settings['hostname']) diff --git a/lisp/plugins/remote/default.json b/lisp/plugins/remote/default.json index c53cc83b1..dc14669ee 100644 --- a/lisp/plugins/remote/default.json +++ b/lisp/plugins/remote/default.json @@ -1,10 +1,10 @@ { - "_version_": "1", + "_version_": "2", "_enabled_": true, - "IP": "0.0.0.0", - "Port": 8070, - "Discovery": { - "Port": 50000, - "Magic": "L1SPR3m0t3" + "ip": "0.0.0.0", + "port": 8070, + "discovery": { + "port": 50000, + "magic": "L1SPR3m0t3" } } \ No newline at end of file diff --git a/lisp/plugins/remote/remote.py b/lisp/plugins/remote/remote.py index 19d17f67f..10c6d8013 100644 --- a/lisp/plugins/remote/remote.py +++ b/lisp/plugins/remote/remote.py @@ -33,20 +33,16 @@ def __init__(self, app): super().__init__(app) self._controller = RemoteController( - app, Remote.Config['IP'], Remote.Config['Port']) + app, Remote.Config['ip'], Remote.Config['port']) self._controller.start() self._announcer = Announcer( - Remote.Config['IP'], - Remote.Config['Discovery']['Port'], - Remote.Config['Discovery']['Magic'] + Remote.Config['ip'], + Remote.Config['discovery.port'], + Remote.Config['discovery.magic'] ) self._announcer.start() def terminate(self): self._controller.stop() self._announcer.stop() - - -def compose_uri(url, port, directory='/'): - return 'http://' + url + ':' + str(port) + directory \ No newline at end of file diff --git a/lisp/plugins/synchronizer/default.json b/lisp/plugins/synchronizer/default.json new file mode 100644 index 000000000..dc14669ee --- /dev/null +++ b/lisp/plugins/synchronizer/default.json @@ -0,0 +1,10 @@ +{ + "_version_": "2", + "_enabled_": true, + "ip": "0.0.0.0", + "port": 8070, + "discovery": { + "port": 50000, + "magic": "L1SPR3m0t3" + } +} \ No newline at end of file diff --git a/lisp/plugins/synchronizer/peers_dialog.py b/lisp/plugins/synchronizer/peers_dialog.py index 616d9098d..9a9a289a2 100644 --- a/lisp/plugins/synchronizer/peers_dialog.py +++ b/lisp/plugins/synchronizer/peers_dialog.py @@ -21,7 +21,6 @@ from PyQt5.QtWidgets import QDialog, QHBoxLayout, QListWidget, QVBoxLayout, \ QPushButton, QDialogButtonBox, QInputDialog, QMessageBox -from lisp.core.configuration import AppConfig from lisp.core.util import compose_http_url from lisp.plugins.remote.remote import RemoteController from lisp.ui import elogging @@ -30,9 +29,10 @@ class PeersDialog(QDialog): - def __init__(self, peers, **kwargs): + def __init__(self, peers, config, **kwargs): super().__init__(**kwargs) self.peers = peers + self.config = config self.setWindowModality(Qt.ApplicationModal) self.setMaximumSize(500, 200) @@ -90,22 +90,22 @@ def retranslateUi(self): translate('SyncPeerDialog', 'Remove all peers')) def add_peer(self): - ip, ok = QInputDialog.getText(self, - translate('SyncPeerDialog', 'Address'), - translate('SyncPeerDialog', 'Peer IP')) + ip, ok = QInputDialog.getText( + self, + translate('SyncPeerDialog', 'Address'), + translate('SyncPeerDialog', 'Peer IP')) if ok: self._add_peer(ip) def _add_peer(self, ip): - port = AppConfig()['Remote']['BindPort'] - uri = compose_http_url(ip, port) + uri = compose_http_url(ip, self.config['port']) for peer in self.peers: if peer['uri'] == uri: - QMessageBox.critical(self, - translate('SyncPeerDialog', 'Error'), - translate('SyncPeerDialog', - 'Already connected')) + QMessageBox.critical( + self, + translate('SyncPeerDialog', 'Error'), + translate('SyncPeerDialog', 'Already connected')) return try: @@ -113,17 +113,17 @@ def _add_peer(self, ip): self.peers.append(peer) self.listWidget.addItem(peer['uri']) except Exception as e: - elogging.exception(translate('SyncPeerDialog', 'Cannot add peer'), - str(e)) + elogging.exception( + translate('SyncPeerDialog', 'Cannot add peer'), str(e)) def discover_peers(self): - dialog = PeersDiscoveryDialog(parent=self) + dialog = PeersDiscoveryDialog(self.config, parent=self) if dialog.exec_() == dialog.Accepted: for peer in dialog.get_peers(): self._add_peer(peer) def remove_peer(self): - if len(self.listWidget.selectedIndexes()) != 0: + if self.listWidget.selectedIndexes(): self.peers.pop(self.current_index()) self.listWidget.takeItem(self.current_index()) diff --git a/lisp/plugins/synchronizer/peers_discovery_dialog.py b/lisp/plugins/synchronizer/peers_discovery_dialog.py index 5211deae7..fc2d0bef4 100644 --- a/lisp/plugins/synchronizer/peers_discovery_dialog.py +++ b/lisp/plugins/synchronizer/peers_discovery_dialog.py @@ -27,7 +27,7 @@ class PeersDiscoveryDialog(QDialog): - def __init__(self, **kwargs): + def __init__(self, config, **kwargs): super().__init__(**kwargs) self.setWindowModality(Qt.ApplicationModal) @@ -48,7 +48,9 @@ def __init__(self, **kwargs): self.retranslateUi() - self._discoverer = Discoverer() + self._discoverer = Discoverer( + config['ip'], config['discovery.port'], config['discovery.magic'] + ) self._discoverer.discovered.connect(self._new_peer, Connection.QtQueued) def retranslateUi(self): diff --git a/lisp/plugins/synchronizer/synchronizer.py b/lisp/plugins/synchronizer/synchronizer.py index a5c93925c..b9c1d0c0d 100644 --- a/lisp/plugins/synchronizer/synchronizer.py +++ b/lisp/plugins/synchronizer/synchronizer.py @@ -69,7 +69,8 @@ def session_reset(self): self.cue_media.clear() def manage_peers(self): - manager = PeersDialog(self.peers, parent=self.app.window) + manager = PeersDialog( + self.peers, Synchronizer.Config, parent=self.app.window) manager.exec_() def show_ip(self): diff --git a/lisp/plugins/timecode/default.json b/lisp/plugins/timecode/default.json index 8f725c103..b020c2d3d 100644 --- a/lisp/plugins/timecode/default.json +++ b/lisp/plugins/timecode/default.json @@ -1,6 +1,6 @@ { - "_version_": "1", + "_version_": "2", "_enabled_": true, - "Format": "SMPTE", - "Protocol": "MIDI" + "format": "SMPTE", + "protocol": "MIDI" } \ No newline at end of file diff --git a/lisp/plugins/timecode/settings.py b/lisp/plugins/timecode/settings.py index 9f4fcde89..08b34c8e7 100644 --- a/lisp/plugins/timecode/settings.py +++ b/lisp/plugins/timecode/settings.py @@ -135,10 +135,10 @@ def retranslateUi(self): def get_settings(self): return { - 'Format': self.formatBox.currentText(), - 'Protocol': self.protocolCombo.currentText() + 'format': self.formatBox.currentText(), + 'protocol': self.protocolCombo.currentText() } def load_settings(self, settings): - self.formatBox.setCurrentText(settings.get('Format', '')) - self.protocolCombo.setCurrentText(settings.get('Protocol', '')) + self.formatBox.setCurrentText(settings.get('format', '')) + self.protocolCombo.setCurrentText(settings.get('protocol', '')) diff --git a/lisp/plugins/timecode/timecode.py b/lisp/plugins/timecode/timecode.py index 3dbb68db3..23dc74896 100644 --- a/lisp/plugins/timecode/timecode.py +++ b/lisp/plugins/timecode/timecode.py @@ -65,7 +65,7 @@ def __init__(self, app): # Create the cue tracker object self.__cue_tracker = TimecodeCueTracker( protocol, - TcFormat[Timecode.Config['Format']] + TcFormat[Timecode.Config['format']] ) # Cues with timecode-tracking enabled diff --git a/lisp/ui/settings/pages/app_general.py b/lisp/ui/settings/pages/app_general.py index 5b616d33b..888d237d2 100644 --- a/lisp/ui/settings/pages/app_general.py +++ b/lisp/ui/settings/pages/app_general.py @@ -65,23 +65,23 @@ def __init__(self, **kwargs): self.themeGroup.layout().addWidget(self.themeCombo) def get_settings(self): - conf = {'Layout': {}, 'Theme': {}} + conf = {'layout': {}, 'theme': {}} if self.startupDialogCheck.isChecked(): - conf['Layout']['Default'] = 'NoDefault' + conf['layout']['default'] = 'NoDefault' else: - conf['Layout']['Default'] = self.layoutCombo.currentText() + conf['layout']['default'] = self.layoutCombo.currentText() - conf['Theme']['Theme'] = self.themeCombo.currentText() + conf['theme']['theme'] = self.themeCombo.currentText() styles.apply_style(self.themeCombo.currentText()) return conf def load_settings(self, settings): - if settings['Layout']['Default'].lower() == 'nodefault': + if settings['layout']['default'].lower() == 'nodefault': self.startupDialogCheck.setChecked(True) self.layoutCombo.setEnabled(False) else: - self.layoutCombo.setCurrentText(settings['Layout']['Default']) + self.layoutCombo.setCurrentText(settings['layout']['default']) - self.themeCombo.setCurrentText(settings['Theme']['Theme']) + self.themeCombo.setCurrentText(settings['theme']['theme']) diff --git a/lisp/ui/settings/pages/cue_app_settings.py b/lisp/ui/settings/pages/cue_app_settings.py index 52d8b9a2a..cfae2bde6 100644 --- a/lisp/ui/settings/pages/cue_app_settings.py +++ b/lisp/ui/settings/pages/cue_app_settings.py @@ -57,17 +57,17 @@ def retranslateUi(self): def load_settings(self, settings): # Interrupt - self.interruptFadeEdit.setDuration(settings['Cue']['InterruptFade']) - self.interruptFadeEdit.setFadeType(settings['Cue']['InterruptFadeType']) + self.interruptFadeEdit.setDuration(settings['cue']['interruptFade']) + self.interruptFadeEdit.setFadeType(settings['cue']['interruptFadeType']) # FadeAction - self.fadeActionEdit.setDuration(settings['Cue']['FadeActionDuration']) - self.fadeActionEdit.setFadeType(settings['Cue']['FadeActionType']) + self.fadeActionEdit.setDuration(settings['cue']['fadeActionDuration']) + self.fadeActionEdit.setFadeType(settings['cue']['fadeActionType']) def get_settings(self): - return {'Cue': { - 'InterruptFade': self.interruptFadeEdit.duration(), - 'InterruptFadeType': self.interruptFadeEdit.fadeType(), - 'FadeActionDuration': self.fadeActionEdit.duration(), - 'FadeActionType': self.fadeActionEdit.fadeType() + return {'cue': { + 'interruptFade': self.interruptFadeEdit.duration(), + 'interruptFadeType': self.interruptFadeEdit.fadeType(), + 'fadeActionDuration': self.fadeActionEdit.duration(), + 'fadeActionType': self.fadeActionEdit.fadeType() }} From 07f940fd692607514d51de6f707ff18395006b46 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 20 Feb 2018 15:38:58 +0100 Subject: [PATCH 086/333] Refactored application sytilng code Add: colored icon theme Update: changed how custom icons are handled Update: removed AppConfig usage in a few places --- lisp/__init__.py | 7 +- lisp/application.py | 20 +- lisp/core/configuration.py | 52 +++-- lisp/default.json | 4 +- lisp/layouts/cart_layout/cue_widget.py | 12 +- lisp/layouts/cart_layout/layout.py | 41 ++-- lisp/layouts/cue_layout.py | 22 +- lisp/layouts/list_layout/control_buttons.py | 31 ++- lisp/layouts/list_layout/cue_list_item.py | 7 +- lisp/layouts/list_layout/layout.py | 27 ++- lisp/layouts/list_layout/listwidgets.py | 16 +- lisp/main.py | 53 +++-- lisp/plugins/gst_backend/gst_pipe_edit.py | 6 +- lisp/plugins/midi/midi_utils.py | 2 - lisp/plugins/rename_cues/rename_ui.py | 2 +- lisp/ui/about.py | 4 +- lisp/ui/mainwindow.py | 8 +- lisp/ui/settings/pages/app_general.py | 28 ++- lisp/ui/settings/pages/plugins_settings.py | 12 +- lisp/ui/styles/dark/__init__.py | 0 lisp/ui/styles/icons/LICENSE | 16 -- lisp/ui/styles/icons/lisp/index.theme | 15 -- .../icons/lisp/scalable/fadein-generic.svg | 204 ------------------ .../icons/lisp/scalable/fadeout-generic.svg | 204 ------------------ lisp/ui/styles/styles.py | 91 -------- lisp/ui/themes/__init__.py | 11 + lisp/ui/{styles => themes/dark}/__init__.py | 0 lisp/ui/{styles => themes}/dark/assetes.py | 0 lisp/ui/{styles => themes}/dark/assetes.rcc | 0 .../dark/assets/Hmovetoolbar.png | Bin .../dark/assets/Hsepartoolbar.png | Bin .../dark/assets/Vmovetoolbar.png | Bin .../dark/assets/Vsepartoolbar.png | Bin .../dark/assets/branch-closed.png | Bin .../dark/assets/branch-end.png | Bin .../dark/assets/branch-more.png | Bin .../dark/assets/branch-open.png | Bin .../dark/assets/checkbox-checked-disabled.png | Bin .../dark/assets/checkbox-checked.png | Bin .../dark/assets/checkbox-mixed-disabled.png | Bin .../dark/assets/checkbox-mixed.png | Bin .../assets/checkbox-unchecked-disabled.png | Bin .../dark/assets/checkbox-unchecked.png | Bin .../{styles => themes}/dark/assets/close.png | Bin .../dark/assets/down-arrow-disabled.png | Bin .../dark/assets/down-arrow.png | Bin .../dark/assets/radio-checked-disabled.png | Bin .../dark/assets/radio-checked.png | Bin .../dark/assets/radio-mixed-disabled.png | Bin .../dark/assets/radio-mixed.png | Bin .../dark/assets/radio-unchecked-disabled.png | Bin .../dark/assets/radio-unchecked.png | Bin .../dark/assets/sizegrip.png | Bin .../dark/assets/slider-handle-disabled.png | Bin .../dark/assets/slider-handle.png | Bin .../dark/assets/spin-down-disabled.png | Bin .../dark/assets/spin-down.png | Bin .../dark/assets/spin-up-disabled.png | Bin .../dark/assets/spin-up.png | Bin .../dark/assets/transparent.png | Bin .../{styles => themes}/dark/assets/undock.png | Bin .../dark/assets/up-arrow-disabled.png | Bin .../dark/assets/up-arrow.png | Bin .../{styles => themes}/dark/assets/vline.png | Bin .../dark/style.py => themes/dark/theme.py} | 28 ++- .../dark/style.qss => themes/dark/theme.qss} | 0 .../icons/lisp}/fadein-linear.png | Bin .../icons/lisp}/fadein-quadratic.png | Bin .../icons/lisp}/fadein-quadratic2.png | Bin .../icons/lisp}/fadeout-linear.png | Bin .../icons/lisp}/fadeout-quadratic.png | Bin .../icons/lisp}/fadeout-quadratic2.png | Bin .../icons/lisp}/led-error.svg | 0 .../icons/lisp}/led-off.svg | 0 .../icons/lisp}/led-pause.svg | 0 .../icons/lisp}/led-running.svg | 0 .../icons/lisp}/linux-show-player.png | Bin .../icons/lisp}/mixer-handle.svg | 0 lisp/ui/themes/icons/numix/cue-interrupt.svg | 7 + lisp/ui/themes/icons/numix/cue-pause.svg | 6 + lisp/ui/themes/icons/numix/cue-start.svg | 3 + lisp/ui/themes/icons/numix/cue-stop.svg | 3 + lisp/ui/themes/icons/numix/fadein-generic.svg | 69 ++++++ .../icons/numix/fadeout-generic.svg} | 51 +++-- lisp/ui/themes/icons/numix/media-eject.svg | 6 + lisp/ui/themes/icons/numix/media-record.svg | 3 + .../icons/numix/media-seek-backward.svg | 7 + .../themes/icons/numix/media-seek-forward.svg | 7 + .../icons/numix/media-skip-backward.svg | 10 + .../themes/icons/numix/media-skip-forward.svg | 10 + .../icons/symbolic}/auto-follow.svg | 0 .../icons/symbolic}/auto-next.svg | 0 .../themes/icons/symbolic/cue-interrupt.svg | 3 + lisp/ui/themes/icons/symbolic/cue-pause.svg | 4 + lisp/ui/themes/icons/symbolic/cue-start.svg | 3 + lisp/ui/themes/icons/symbolic/cue-stop.svg | 3 + .../themes/icons/symbolic/fadein-generic.svg | 74 +++++++ .../themes/icons/symbolic/fadeout-generic.svg | 74 +++++++ lisp/ui/themes/icons/symbolic/media-eject.svg | 3 + .../ui/themes/icons/symbolic/media-record.svg | 3 + .../icons/symbolic/media-seek-backward.svg | 4 + .../icons/symbolic/media-seek-forward.svg | 4 + .../icons/symbolic/media-skip-backward.svg | 3 + .../icons/symbolic/media-skip-forward.svg | 3 + lisp/ui/themes/theme.py | 76 +++++++ lisp/ui/ui_utils.py | 45 ++-- lisp/ui/widgets/fades.py | 16 +- lisp/ui/widgets/qmutebutton.py | 7 +- 108 files changed, 678 insertions(+), 752 deletions(-) delete mode 100644 lisp/ui/styles/dark/__init__.py delete mode 100644 lisp/ui/styles/icons/LICENSE delete mode 100644 lisp/ui/styles/icons/lisp/index.theme delete mode 100644 lisp/ui/styles/icons/lisp/scalable/fadein-generic.svg delete mode 100644 lisp/ui/styles/icons/lisp/scalable/fadeout-generic.svg delete mode 100644 lisp/ui/styles/styles.py create mode 100644 lisp/ui/themes/__init__.py rename lisp/ui/{styles => themes/dark}/__init__.py (100%) rename lisp/ui/{styles => themes}/dark/assetes.py (100%) rename lisp/ui/{styles => themes}/dark/assetes.rcc (100%) rename lisp/ui/{styles => themes}/dark/assets/Hmovetoolbar.png (100%) rename lisp/ui/{styles => themes}/dark/assets/Hsepartoolbar.png (100%) rename lisp/ui/{styles => themes}/dark/assets/Vmovetoolbar.png (100%) rename lisp/ui/{styles => themes}/dark/assets/Vsepartoolbar.png (100%) rename lisp/ui/{styles => themes}/dark/assets/branch-closed.png (100%) rename lisp/ui/{styles => themes}/dark/assets/branch-end.png (100%) rename lisp/ui/{styles => themes}/dark/assets/branch-more.png (100%) rename lisp/ui/{styles => themes}/dark/assets/branch-open.png (100%) rename lisp/ui/{styles => themes}/dark/assets/checkbox-checked-disabled.png (100%) rename lisp/ui/{styles => themes}/dark/assets/checkbox-checked.png (100%) rename lisp/ui/{styles => themes}/dark/assets/checkbox-mixed-disabled.png (100%) rename lisp/ui/{styles => themes}/dark/assets/checkbox-mixed.png (100%) rename lisp/ui/{styles => themes}/dark/assets/checkbox-unchecked-disabled.png (100%) rename lisp/ui/{styles => themes}/dark/assets/checkbox-unchecked.png (100%) rename lisp/ui/{styles => themes}/dark/assets/close.png (100%) rename lisp/ui/{styles => themes}/dark/assets/down-arrow-disabled.png (100%) rename lisp/ui/{styles => themes}/dark/assets/down-arrow.png (100%) rename lisp/ui/{styles => themes}/dark/assets/radio-checked-disabled.png (100%) rename lisp/ui/{styles => themes}/dark/assets/radio-checked.png (100%) rename lisp/ui/{styles => themes}/dark/assets/radio-mixed-disabled.png (100%) rename lisp/ui/{styles => themes}/dark/assets/radio-mixed.png (100%) rename lisp/ui/{styles => themes}/dark/assets/radio-unchecked-disabled.png (100%) rename lisp/ui/{styles => themes}/dark/assets/radio-unchecked.png (100%) rename lisp/ui/{styles => themes}/dark/assets/sizegrip.png (100%) rename lisp/ui/{styles => themes}/dark/assets/slider-handle-disabled.png (100%) rename lisp/ui/{styles => themes}/dark/assets/slider-handle.png (100%) rename lisp/ui/{styles => themes}/dark/assets/spin-down-disabled.png (100%) rename lisp/ui/{styles => themes}/dark/assets/spin-down.png (100%) rename lisp/ui/{styles => themes}/dark/assets/spin-up-disabled.png (100%) rename lisp/ui/{styles => themes}/dark/assets/spin-up.png (100%) rename lisp/ui/{styles => themes}/dark/assets/transparent.png (100%) rename lisp/ui/{styles => themes}/dark/assets/undock.png (100%) rename lisp/ui/{styles => themes}/dark/assets/up-arrow-disabled.png (100%) rename lisp/ui/{styles => themes}/dark/assets/up-arrow.png (100%) rename lisp/ui/{styles => themes}/dark/assets/vline.png (100%) rename lisp/ui/{styles/dark/style.py => themes/dark/theme.py} (58%) rename lisp/ui/{styles/dark/style.qss => themes/dark/theme.qss} (100%) rename lisp/ui/{styles/icons/lisp/16 => themes/icons/lisp}/fadein-linear.png (100%) rename lisp/ui/{styles/icons/lisp/16 => themes/icons/lisp}/fadein-quadratic.png (100%) rename lisp/ui/{styles/icons/lisp/16 => themes/icons/lisp}/fadein-quadratic2.png (100%) rename lisp/ui/{styles/icons/lisp/16 => themes/icons/lisp}/fadeout-linear.png (100%) rename lisp/ui/{styles/icons/lisp/16 => themes/icons/lisp}/fadeout-quadratic.png (100%) rename lisp/ui/{styles/icons/lisp/16 => themes/icons/lisp}/fadeout-quadratic2.png (100%) rename lisp/ui/{styles/icons/lisp/scalable => themes/icons/lisp}/led-error.svg (100%) rename lisp/ui/{styles/icons/lisp/scalable => themes/icons/lisp}/led-off.svg (100%) rename lisp/ui/{styles/icons/lisp/scalable => themes/icons/lisp}/led-pause.svg (100%) rename lisp/ui/{styles/icons/lisp/scalable => themes/icons/lisp}/led-running.svg (100%) rename lisp/ui/{styles/icons/lisp/512 => themes/icons/lisp}/linux-show-player.png (100%) rename lisp/ui/{styles/icons/lisp/scalable => themes/icons/lisp}/mixer-handle.svg (100%) create mode 100644 lisp/ui/themes/icons/numix/cue-interrupt.svg create mode 100644 lisp/ui/themes/icons/numix/cue-pause.svg create mode 100644 lisp/ui/themes/icons/numix/cue-start.svg create mode 100644 lisp/ui/themes/icons/numix/cue-stop.svg create mode 100644 lisp/ui/themes/icons/numix/fadein-generic.svg rename lisp/ui/{styles/icons/numix/scalable/devices/drive-removable-media-usb.svg => themes/icons/numix/fadeout-generic.svg} (50%) create mode 100644 lisp/ui/themes/icons/numix/media-eject.svg create mode 100644 lisp/ui/themes/icons/numix/media-record.svg create mode 100644 lisp/ui/themes/icons/numix/media-seek-backward.svg create mode 100644 lisp/ui/themes/icons/numix/media-seek-forward.svg create mode 100644 lisp/ui/themes/icons/numix/media-skip-backward.svg create mode 100644 lisp/ui/themes/icons/numix/media-skip-forward.svg rename lisp/ui/{styles/icons/lisp/scalable => themes/icons/symbolic}/auto-follow.svg (100%) rename lisp/ui/{styles/icons/lisp/scalable => themes/icons/symbolic}/auto-next.svg (100%) create mode 100644 lisp/ui/themes/icons/symbolic/cue-interrupt.svg create mode 100644 lisp/ui/themes/icons/symbolic/cue-pause.svg create mode 100644 lisp/ui/themes/icons/symbolic/cue-start.svg create mode 100644 lisp/ui/themes/icons/symbolic/cue-stop.svg create mode 100644 lisp/ui/themes/icons/symbolic/fadein-generic.svg create mode 100644 lisp/ui/themes/icons/symbolic/fadeout-generic.svg create mode 100644 lisp/ui/themes/icons/symbolic/media-eject.svg create mode 100644 lisp/ui/themes/icons/symbolic/media-record.svg create mode 100644 lisp/ui/themes/icons/symbolic/media-seek-backward.svg create mode 100644 lisp/ui/themes/icons/symbolic/media-seek-forward.svg create mode 100644 lisp/ui/themes/icons/symbolic/media-skip-backward.svg create mode 100644 lisp/ui/themes/icons/symbolic/media-skip-forward.svg create mode 100644 lisp/ui/themes/theme.py diff --git a/lisp/__init__.py b/lisp/__init__.py index d9f00d8ae..f7020ba71 100644 --- a/lisp/__init__.py +++ b/lisp/__init__.py @@ -26,8 +26,13 @@ __version__ = '0.6dev' # Application wide "constants" +APP_DIR = path.dirname(__file__) USER_DIR = path.join(path.expanduser("~"), '.linux_show_player') -DEFAULT_APP_CONFIG = path.join(path.dirname(__file__), 'default.json') +DEFAULT_APP_CONFIG = path.join(APP_DIR, 'default.json') USER_APP_CONFIG = path.join(USER_DIR, 'lisp.json') + +I18N_PATH = path.join(APP_DIR, 'i18n') + +ICON_THEMES_DIR = path.join(APP_DIR, 'ui', 'themes', 'icons') diff --git a/lisp/application.py b/lisp/application.py index 0ef355b20..a9f49a4ee 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -22,12 +22,10 @@ from PyQt5.QtWidgets import QDialog, qApp -from lisp.core.configuration import AppConfig -from lisp.core.signal import Signal - from lisp import layouts from lisp.core.actions_handler import MainActionsHandler from lisp.core.session import new_session +from lisp.core.signal import Signal from lisp.core.singleton import Singleton from lisp.cues.cue import Cue from lisp.cues.cue_factory import CueFactory @@ -47,7 +45,9 @@ class Application(metaclass=Singleton): - def __init__(self): + def __init__(self, app_conf): + self.conf = app_conf + self.session_created = Signal() self.session_before_finalize = Signal() @@ -56,9 +56,9 @@ def __init__(self): self.__session = None # Register general settings widget - AppSettings.register_settings_widget(AppGeneral, AppConfig()) - AppSettings.register_settings_widget(CueAppSettings, AppConfig()) - AppSettings.register_settings_widget(PluginsSettings, AppConfig()) + AppSettings.register_settings_widget(AppGeneral, self.conf) + AppSettings.register_settings_widget(CueAppSettings, self.conf) + AppSettings.register_settings_widget(PluginsSettings, self.conf) # Register common cue-settings widgets CueSettingsRegistry().add_item(CueGeneralSettings, Cue) @@ -95,7 +95,7 @@ def start(self, session_file=''): if exists(session_file): self._load_from_file(session_file) else: - layout = AppConfig().get('layout.default', 'nodefault') + layout = self.conf.get('layout.default', 'nodefault') if layout.lower() != 'nodefault': self._new_session(layouts.get_layout(layout)) @@ -134,7 +134,7 @@ def _new_session_dialog(self): def _new_session(self, layout): self._delete_session() - self.__session = new_session(layout(self.__cue_model)) + self.__session = new_session(layout(application=self)) self.__main_window.set_session(self.__session) self.session_created.emit(self.__session) @@ -153,7 +153,7 @@ def _save_to_file(self, session_file): self.session.session_file = session_file # Add the cues - session_dict = {"cues": []} + session_dict = {'cues': []} for cue in self.__cue_model: session_dict['cues'].append(cue.properties(defaults=False)) diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index 997b12966..ac596794f 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -21,6 +21,7 @@ # raise an exception. Created to enable `None' as a valid fallback value. import json +import logging from abc import ABCMeta, abstractmethod from copy import deepcopy from os import path @@ -30,17 +31,16 @@ from lisp.core.signal import Signal # Used to indicate the default behaviour when a specific option is not found to -# raise an exception. Created to enable `None' as a valid fallback value. +# raise an exception. Created to enable `None` as a valid fallback value. from lisp.core.singleton import ABCSingleton _UNSET = object() class Configuration(metaclass=ABCMeta): - """ABC for a configuration object. - - Subclasses need to implement `read`, `write` methods. + """ABC for a dictionary-based configuration object. + Subclasses need to implement `read` and `write` methods. Keep in mind that the `set` and `update` methods ignores non-existing keys. """ @@ -62,6 +62,10 @@ def get(self, path, default=_UNSET): return node[key] except (KeyError, TypeError) as e: if default is not _UNSET: + logging.warning( + 'CONFIG: invalid key "{}" in get operation, default used.' + .format(path) + ) return default raise e @@ -73,7 +77,10 @@ def set(self, path, value): self.changed.emit(path, old, value) except (KeyError, TypeError): - pass + logging.warning( + 'CONFIG: invalid key "{}" in set operation, ignored.' + .format(path) + ) def __traverse(self, keys, root): next_step = keys.pop(0) @@ -84,12 +91,18 @@ def __traverse(self, keys, root): return root, next_step def update(self, new_conf): + """Update the current configuration using the given dictionary. + + :param new_conf: the new configuration (can be partial) + :type new_conf: dict + """ self.__update(self._root, new_conf) def __update(self, root, new_conf, _path=''): + """Recursively update the current configuration.""" for key, value in new_conf.items(): if key in root: - _path += '.' + key + _path = self.jp(_path, key) if isinstance(root[key], dict): self.__update(root[key], value, _path) @@ -98,8 +111,16 @@ def __update(self, root, new_conf, _path=''): self.changed.emit(_path[1:], old, value) def copy(self): + """Return a deep-copy of the internal dictionary. + + :rtype: dict + """ return deepcopy(self._root) + @staticmethod + def jp(*paths): + return '.'.join(*paths) + def __getitem__(self, path): return self.get(path) @@ -149,14 +170,14 @@ def __init__(self, user_path, default_path, read=True): self.read() def read(self): - self._check() + self._check_file() self._root = self._read_json(self.user_path) def write(self): with open(self.user_path, 'w') as f: json.dump(self._root, f, indent=True) - def _check(self): + def _check_file(self): """Ensure the last configuration is present at the user-path position""" if path.exists(self.user_path): # Read default configuration @@ -167,12 +188,15 @@ def _check(self): user = self._read_json(self.user_path) user = user.get('_version_', object()) - # if the user and default version aren't the same - if user != default: - # Copy the new configuration - copyfile(self.default_path, self.user_path) - else: - copyfile(self.default_path, self.user_path) + # if the user and default version are the same we are good + if user == default: + return + + # Copy the new configuration + copyfile(self.default_path, self.user_path) + logging.info('CONFIG: new configuration installed at {}'.format( + self.user_path + )) @staticmethod def _read_json(path): diff --git a/lisp/default.json b/lisp/default.json index 4b5fd673d..d59307066 100644 --- a/lisp/default.json +++ b/lisp/default.json @@ -1,5 +1,5 @@ { - "_version_": "0.6dev.3", + "_version_": "0.6dev.5", "cue": { "fadeActionDuration": 3, "fadeActionType": "Linear", @@ -8,7 +8,7 @@ }, "theme": { "theme": "Dark", - "icons": "numix" + "icons": "Symbolic" }, "backend": { "default": "gst" diff --git a/lisp/layouts/cart_layout/cue_widget.py b/lisp/layouts/cart_layout/cue_widget.py index ccf805a1d..935def4c8 100644 --- a/lisp/layouts/cart_layout/cue_widget.py +++ b/lisp/layouts/cart_layout/cue_widget.py @@ -29,7 +29,7 @@ from lisp.cues.cue_time import CueTime from lisp.cues.media_cue import MediaCue from lisp.layouts.cart_layout.page_widget import PageWidget -from lisp.ui.ui_utils import pixmap_from_icon +from lisp.ui.themes.theme import IconTheme from lisp.ui.widgets import QClickLabel, QClickSlider, QDbMeter,\ QDetailedMessageBox @@ -76,7 +76,7 @@ def __init__(self, cue, **kwargs): self.statusIcon = QLabel(self.nameButton) self.statusIcon.setStyleSheet('background-color: transparent') self.statusIcon.setPixmap( - pixmap_from_icon('led-off', CueWidget.ICON_SIZE)) + IconTheme.get('led-off').pixmap(CueWidget.ICON_SIZE)) self.seekSlider = QClickSlider(self.nameButton) self.seekSlider.setOrientation(Qt.Horizontal) @@ -308,24 +308,24 @@ def _exit_fade(self): def _status_stopped(self): self.statusIcon.setPixmap( - pixmap_from_icon('led-off', CueWidget.ICON_SIZE)) + IconTheme.get('led-off').pixmap(CueWidget.ICON_SIZE)) self.volumeSlider.setEnabled(False) self._update_time(0, True) self.reset_volume() def _status_playing(self): self.statusIcon.setPixmap( - pixmap_from_icon('led-running', CueWidget.ICON_SIZE)) + IconTheme.get('led-running').pixmap(CueWidget.ICON_SIZE)) self.volumeSlider.setEnabled(True) def _status_paused(self): self.statusIcon.setPixmap( - pixmap_from_icon('led-pause', CueWidget.ICON_SIZE)) + IconTheme.get('led-pause').pixmap(CueWidget.ICON_SIZE)) self.volumeSlider.setEnabled(False) def _status_error(self, cue, error, details): self.statusIcon.setPixmap( - pixmap_from_icon('led-error', CueWidget.ICON_SIZE)) + IconTheme.get('led-off').pixmap(CueWidget.ICON_SIZE)) self.volumeSlider.setEnabled(False) self.reset_volume() diff --git a/lisp/layouts/cart_layout/layout.py b/lisp/layouts/cart_layout/layout.py index 6f2a3c541..75a07fd3f 100644 --- a/lisp/layouts/cart_layout/layout.py +++ b/lisp/layouts/cart_layout/layout.py @@ -35,8 +35,6 @@ from lisp.ui.settings.app_settings import AppSettings from lisp.ui.ui_utils import translate -AppSettings.register_settings_widget(CartLayoutSettings, AppConfig()) - class CartLayout(QTabWidget, CueLayout): NAME = 'Cart Layout' @@ -52,27 +50,34 @@ class CartLayout(QTabWidget, CueLayout): 'To copy cues drag them while pressing SHIFT') ] - def __init__(self, cue_model, **kwargs): - super().__init__(cue_model=cue_model, **kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) + AppSettings.register_settings_widget(CartLayoutSettings, self.app.conf) + self.tabBar().setObjectName('CartTabBar') - self.__columns = AppConfig()['cartLayout.gridColumns'] - self.__rows = AppConfig()['cartLayout.gridRows'] + self.__columns = self.app.conf['cartLayout.gridColumns'] + self.__rows = self.app.conf['cartLayout.gridRows'] self.__pages = [] self.__context_widget = None - self._show_seek = AppConfig()['cartLayout.showSeek'] - self._show_dbmeter = AppConfig()['cartLayout.showDbMeters'] - self._show_volume = AppConfig()['cartLayout.showVolume'] - self._accurate_timing = AppConfig()['cartLayout.showAccurate'] - self._countdown_mode = AppConfig()['cartLayout.countdown'] - self._auto_add_page = AppConfig()['cartLayout.autoAddPage'] - - self._model_adapter = CueCartModel(cue_model, self.__rows, self.__columns) - self._model_adapter.item_added.connect(self.__cue_added, Connection.QtQueued) - self._model_adapter.item_removed.connect(self.__cue_removed, Connection.QtQueued) - self._model_adapter.item_moved.connect(self.__cue_moved, Connection.QtQueued) - self._model_adapter.model_reset.connect(self.__model_reset) + self._show_seek = self.app.conf['cartLayout.showSeek'] + self._show_dbmeter = self.app.conf['cartLayout.showDbMeters'] + self._show_volume = self.app.conf['cartLayout.showVolume'] + self._accurate_timing = self.app.conf['cartLayout.showAccurate'] + self._countdown_mode = self.app.conf['cartLayout.countdown'] + self._auto_add_page = self.app.conf['cartLayout.autoAddPage'] + + self._model_adapter = CueCartModel( + self.cue_model, self.__rows, self.__columns) + self._model_adapter.item_added.connect( + self.__cue_added, Connection.QtQueued) + self._model_adapter.item_removed.connect( + self.__cue_removed, Connection.QtQueued) + self._model_adapter.item_moved.connect( + self.__cue_moved, Connection.QtQueued) + self._model_adapter.model_reset.connect( + self.__model_reset) # Add layout-specific menus self.new_page_action = QAction(self) diff --git a/lisp/layouts/cue_layout.py b/lisp/layouts/cue_layout.py index 7f04f1b8a..b62921b69 100644 --- a/lisp/layouts/cue_layout.py +++ b/lisp/layouts/cue_layout.py @@ -22,17 +22,16 @@ from PyQt5.QtWidgets import QAction, QMenu, qApp from lisp.core.actions_handler import MainActionsHandler -from lisp.core.configuration import AppConfig from lisp.core.signal import Signal from lisp.core.util import greatest_common_superclass from lisp.cues.cue import Cue, CueAction -from lisp.cues.cue_actions import UpdateCueAction, \ - UpdateCuesAction +from lisp.cues.cue_actions import UpdateCueAction, UpdateCuesAction from lisp.layouts.cue_menu_registry import CueMenuRegistry from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.cue_settings import CueSettings +# TODO: split widget and "controller" class CueLayout: # Layout name NAME = 'Base' @@ -43,8 +42,11 @@ class CueLayout: cm_registry = CueMenuRegistry() - def __init__(self, cue_model): - self._cue_model = cue_model + def __init__(self, application=None): + """ + :type application: lisp.application.Application + """ + self.app = application self.cue_executed = Signal() # After a cue is executed self.focus_changed = Signal() # After the focused cue is changed @@ -53,7 +55,7 @@ def __init__(self, cue_model): @property def cue_model(self): """:rtype: lisp.core.cue_model.CueModel""" - return self._cue_model + return self.app.cue_model @property @abstractmethod @@ -94,22 +96,22 @@ def go(self, action=CueAction.Default, advance=1): """ def stop_all(self): - fade = AppConfig().get('layout.stopAllFade', False) + fade = self.app.conf.get('layout.stopAllFade', False) for cue in self.model_adapter: cue.stop(fade=fade) def interrupt_all(self): - fade = AppConfig().get('layout.interruptAllFade', False) + fade = self.app.conf.get('layout.interruptAllFade', False) for cue in self.model_adapter: cue.interrupt(fade=fade) def pause_all(self): - fade = AppConfig().get('layout.pauseAllFade', False) + fade = self.app.conf.get('layout.pauseAllFade', False) for cue in self.model_adapter: cue.pause(fade=fade) def resume_all(self): - fade = AppConfig().get('layout.resumeAllFade', True) + fade = self.app.conf.get('layout.resumeAllFade', True) for cue in self.model_adapter: cue.resume(fade=fade) diff --git a/lisp/layouts/list_layout/control_buttons.py b/lisp/layouts/list_layout/control_buttons.py index 54f9093e8..f8f2c1957 100644 --- a/lisp/layouts/list_layout/control_buttons.py +++ b/lisp/layouts/list_layout/control_buttons.py @@ -18,9 +18,9 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QSize -from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QWidget, QGridLayout, QSizePolicy +from lisp.ui.themes.theme import IconTheme from lisp.ui.ui_utils import translate from lisp.ui.widgets.qiconpushbutton import QIconPushButton @@ -33,25 +33,23 @@ def __init__(self, **kwargs): self.layout().setSpacing(5) # Row 0 - self.pauseButton = self.newButton( - QIcon.fromTheme('media-playback-pause')) + self.pauseButton = self.newButton(IconTheme.get('cue-pause')) self.layout().addWidget(self.pauseButton, 0, 0) - self.stopButton = self.newButton(QIcon.fromTheme('media-playback-stop')) + self.stopButton = self.newButton(IconTheme.get('cue-stop')) self.layout().addWidget(self.stopButton, 0, 1) - self.interruptButton = self.newButton(QIcon.fromTheme('window-close')) + self.interruptButton = self.newButton(IconTheme.get('cue-interrupt')) self.layout().addWidget(self.interruptButton, 0, 2) # Row 1 - self.resumeButton = self.newButton( - QIcon.fromTheme('media-playback-start')) + self.resumeButton = self.newButton(IconTheme.get('cue-start')) self.layout().addWidget(self.resumeButton, 1, 0) - self.fadeOutButton = self.newButton(QIcon.fromTheme('fadeout-generic')) + self.fadeOutButton = self.newButton(IconTheme.get('fadeout-generic')) self.layout().addWidget(self.fadeOutButton, 1, 1) - self.fadeInButton = self.newButton(QIcon.fromTheme('fadein-generic')) + self.fadeInButton = self.newButton(IconTheme.get('fadein-generic')) self.layout().addWidget(self.fadeInButton, 1, 2) self.retranslateUi() @@ -84,27 +82,24 @@ def __init__(self, **kwargs): self.layout().setSpacing(2) # Start/Pause - self.pauseButton = self.newButton( - QIcon.fromTheme('media-playback-pause')) + self.pauseButton = self.newButton(IconTheme.get('cue-pause')) self.layout().addWidget(self.pauseButton, 0, 0, 2, 1) - self.startButton = self.newButton( - QIcon.fromTheme('media-playback-start')) + self.startButton = self.newButton(IconTheme.get('cue-start')) self.startButton.hide() # Row 0 - self.stopButton = self.newButton( - QIcon.fromTheme('media-playback-stop')) + self.stopButton = self.newButton(IconTheme.get('cue-stop')) self.layout().addWidget(self.stopButton, 0, 1) - self.interruptButton = self.newButton(QIcon.fromTheme('window-close')) + self.interruptButton = self.newButton(IconTheme.get('cue-interrupt')) self.layout().addWidget(self.interruptButton, 0, 2) # Row 1 - self.fadeOutButton = self.newButton(QIcon.fromTheme('fadeout-generic')) + self.fadeOutButton = self.newButton(IconTheme.get('fadeout-generic')) self.layout().addWidget(self.fadeOutButton, 1, 1) - self.fadeInButton = self.newButton(QIcon.fromTheme('fadein-generic')) + self.fadeInButton = self.newButton(IconTheme.get('fadein-generic')) self.layout().addWidget(self.fadeInButton, 1, 2) self.layout().setColumnStretch(0, 3) diff --git a/lisp/layouts/list_layout/cue_list_item.py b/lisp/layouts/list_layout/cue_list_item.py index e8aa09348..7900e6dd7 100644 --- a/lisp/layouts/list_layout/cue_list_item.py +++ b/lisp/layouts/list_layout/cue_list_item.py @@ -21,7 +21,7 @@ from PyQt5.QtWidgets import QTreeWidgetItem from lisp.core.signal import Connection -from lisp.ui.ui_utils import load_icon +from lisp.ui.themes.theme import IconTheme class CueListItem(QTreeWidgetItem): @@ -35,8 +35,7 @@ def __init__(self, cue): self._selected = False - self.cue.changed('name').connect( - self._update_name, Connection.QtQueued) + self.cue.changed('name').connect(self._update_name, Connection.QtQueued) self.cue.changed('index').connect( self._update_index, Connection.QtQueued) @@ -52,7 +51,7 @@ def selected(self): @selected.setter def selected(self, value): self._selected = value - self.setIcon(0, load_icon('mark-location' if value else '')) + self.setIcon(0, IconTheme.get('mark-location' if value else '')) def _update_index(self, index): self.setText(self.num_column, str(index)) diff --git a/lisp/layouts/list_layout/layout.py b/lisp/layouts/list_layout/layout.py index fcf480a73..1a0eb723d 100644 --- a/lisp/layouts/list_layout/layout.py +++ b/lisp/layouts/list_layout/layout.py @@ -24,7 +24,6 @@ from PyQt5.QtWidgets import QWidget, QAction, qApp, QGridLayout, \ QPushButton, QSizePolicy -from lisp.core.configuration import AppConfig from lisp.core.signal import Connection from lisp.cues.cue import Cue, CueAction from lisp.cues.media_cue import MediaCue @@ -40,8 +39,6 @@ from lisp.ui.settings.app_settings import AppSettings from lisp.ui.ui_utils import translate -AppSettings.register_settings_widget(ListLayoutSettings, AppConfig()) - class EndListBehavior(Enum): Stop = 'Stop' @@ -63,29 +60,31 @@ class ListLayout(QWidget, CueLayout): 'LayoutDetails', 'To move cues drag them') ] - def __init__(self, cue_model, **kwargs): - super().__init__(cue_model=cue_model, **kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) + AppSettings.register_settings_widget(ListLayoutSettings, self.app.conf) + self.setLayout(QGridLayout()) self.layout().setContentsMargins(0, 0, 0, 0) - self._model_adapter = CueListModel(self._cue_model) + self._model_adapter = CueListModel(self.cue_model) self._model_adapter.item_added.connect(self.__cue_added) self._model_adapter.item_removed.connect(self.__cue_removed) - self._playing_model = RunningCueModel(self._cue_model) + self._playing_model = RunningCueModel(self.cue_model) self._context_item = None self._next_cue_index = 0 - self._show_dbmeter = AppConfig()['listLayout.showDbMeters'] - self._seek_visible = AppConfig()['listLayout.showSeek'] - self._accurate_time = AppConfig()['listLayout.showAccurate'] - self._auto_continue = AppConfig()['listLayout.autoContinue'] - self._show_playing = AppConfig()['listLayout.showPlaying'] - self._go_key = AppConfig()['listLayout.goKey'] + self._show_dbmeter = self.app.conf['listLayout.showDbMeters'] + self._seek_visible = self.app.conf['listLayout.showSeek'] + self._accurate_time = self.app.conf['listLayout.showAccurate'] + self._auto_continue = self.app.conf['listLayout.autoContinue'] + self._show_playing = self.app.conf['listLayout.showPlaying'] + self._go_key = self.app.conf['listLayout.goKey'] self._go_key_sequence = QKeySequence( self._go_key, QKeySequence.NativeText) - self._end_list = EndListBehavior(AppConfig()['listLayout.endList']) + self._end_list = EndListBehavior(self.app.conf['listLayout.endList']) # Add layout-specific menus self.showPlayingAction = QAction(self) diff --git a/lisp/layouts/list_layout/listwidgets.py b/lisp/layouts/list_layout/listwidgets.py index cad22d6c4..4935a3c41 100644 --- a/lisp/layouts/list_layout/listwidgets.py +++ b/lisp/layouts/list_layout/listwidgets.py @@ -24,7 +24,7 @@ from lisp.core.util import strtime from lisp.cues.cue import CueNextAction, CueState from lisp.cues.cue_time import CueTime, CueWaitTime -from lisp.ui.ui_utils import pixmap_from_icon +from lisp.ui.themes.theme import IconTheme class CueStatusIcon(QLabel): @@ -44,16 +44,16 @@ def __init__(self, cue, *args): self.cue.end.connect(self._stop, Connection.QtQueued) def _start(self): - self.setPixmap(pixmap_from_icon('led-running', self.SIZE)) + self.setPixmap(IconTheme.get('led-running').pixmap(self.SIZE)) def _pause(self): - self.setPixmap(pixmap_from_icon('led-pause', self.SIZE)) + self.setPixmap(IconTheme.get('led-pause',).pixmap(self.SIZE)) def _error(self, *args): - self.setPixmap(pixmap_from_icon('led-error', self.SIZE)) + self.setPixmap(IconTheme.get('led-error').pixmap(self.SIZE)) def _stop(self): - self.setPixmap(pixmap_from_icon('', self.SIZE)) + self.setPixmap(IconTheme.get('').pixmap(self.SIZE)) def sizeHint(self): return QSize(self.SIZE, self.SIZE) @@ -75,13 +75,13 @@ def __init__(self, cue, *args): def _update_icon(self, next_action): next_action = CueNextAction(next_action) - pixmap = pixmap_from_icon('', self.SIZE) + pixmap = IconTheme.get('').pixmap(self.SIZE) if next_action == CueNextAction.AutoNext: - pixmap = pixmap_from_icon('auto-next', self.SIZE) + pixmap = IconTheme.get('auto-next').pixmap(self.SIZE) self.setToolTip(CueNextAction.AutoNext.value) elif next_action == CueNextAction.AutoFollow: - pixmap = pixmap_from_icon('auto-follow', self.SIZE) + pixmap = IconTheme.get('auto-follow').pixmap(self.SIZE) self.setToolTip(CueNextAction.AutoFollow.value) else: self.setToolTip('') diff --git a/lisp/main.py b/lisp/main.py index ba77eeeca..b180d4b6a 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -19,17 +19,18 @@ import argparse import logging -import sys import os +import sys +import traceback from PyQt5.QtCore import QLocale, QLibraryInfo -from PyQt5.QtGui import QFont, QIcon from PyQt5.QtWidgets import QApplication -from lisp import plugins, USER_DIR +from lisp import USER_DIR, DEFAULT_APP_CONFIG, USER_APP_CONFIG, plugins, \ + I18N_PATH from lisp.application import Application -from lisp.core.configuration import AppConfig -from lisp.ui.styles import styles +from lisp.core.configuration import JSONFileConfiguration +from lisp.ui import themes from lisp.ui.ui_utils import install_translation @@ -59,26 +60,20 @@ def main(): logging.basicConfig( format='%(asctime)s.%(msecs)03d %(levelname)s:: %(message)s', datefmt='%H:%M:%S', - level=log + level=log, ) # Create (if not present) user directory os.makedirs(os.path.dirname(USER_DIR), exist_ok=True) + # Load application configuration + app_conf = JSONFileConfiguration(USER_APP_CONFIG, DEFAULT_APP_CONFIG) + # Create the QApplication qt_app = QApplication(sys.argv) qt_app.setApplicationName('Linux Show Player') qt_app.setQuitOnLastWindowClosed(True) - # Force light font, for environment with "bad" QT support. - appFont = qt_app.font() - appFont.setWeight(QFont.Light) - qt_app.setFont(appFont) - # Set icons and theme from the application configuration - QIcon.setThemeSearchPaths(styles.IconsThemePaths) - QIcon.setThemeName(AppConfig()['theme.icons']) - styles.apply_style(AppConfig()['theme.theme']) - # Get/Set the locale locale = args.locale if locale: @@ -87,14 +82,32 @@ def main(): logging.info('Using {} locale'.format(QLocale().name())) # Qt platform translation - install_translation('qt', tr_path=QLibraryInfo.location( - QLibraryInfo.TranslationsPath)) + install_translation( + 'qt', tr_path=QLibraryInfo.location(QLibraryInfo.TranslationsPath)) # Main app translations - install_translation('lisp', tr_path=os.path.join(os.path.dirname( - os.path.realpath(__file__)), 'i18n')) + install_translation('lisp', tr_path=I18N_PATH) + + # Set UI theme + try: + theme = app_conf['theme.theme'] + themes.THEMES[theme].apply(qt_app) + logging.info('Using "{}" theme'.format(theme)) + except Exception: + logging.error('Unable to load theme') + logging.debug(traceback.format_exc()) + + # Set the global IconTheme + try: + icon_theme_name = app_conf['theme.icons'] + icon_theme = themes.ICON_THEMES[icon_theme_name] + icon_theme.set_theme(icon_theme) + logging.info('Using "{}" icon theme'.format(icon_theme_name)) + except Exception: + logging.error('Unable to load icon theme') + logging.debug(traceback.format_exc()) # Create the application - lisp_app = Application() + lisp_app = Application(app_conf) # Load plugins plugins.load_plugins(lisp_app) diff --git a/lisp/plugins/gst_backend/gst_pipe_edit.py b/lisp/plugins/gst_backend/gst_pipe_edit.py index bf8b9f727..da6c74d78 100644 --- a/lisp/plugins/gst_backend/gst_pipe_edit.py +++ b/lisp/plugins/gst_backend/gst_pipe_edit.py @@ -18,12 +18,12 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt -from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QDialog, QGridLayout, QComboBox, QListWidget, \ QAbstractItemView, QVBoxLayout, QPushButton, QDialogButtonBox, QWidget, \ QListWidgetItem from lisp.plugins.gst_backend import elements +from lisp.ui.themes.theme import IconTheme from lisp.ui.ui_utils import translate @@ -61,13 +61,13 @@ def __init__(self, pipe, app_mode=False, **kwargs): self.layout().setAlignment(self.buttonsLayout, Qt.AlignHCenter) self.addButton = QPushButton(self) - self.addButton.setIcon(QIcon.fromTheme('go-previous')) + self.addButton.setIcon(IconTheme.get('go-previous')) self.addButton.clicked.connect(self.__add_plugin) self.buttonsLayout.addWidget(self.addButton) self.buttonsLayout.setAlignment(self.addButton, Qt.AlignHCenter) self.delButton = QPushButton(self) - self.delButton.setIcon(QIcon.fromTheme('go-next')) + self.delButton.setIcon(IconTheme.get('go-next')) self.delButton.clicked.connect(self.__remove_plugin) self.buttonsLayout.addWidget(self.delButton) self.buttonsLayout.setAlignment(self.delButton, Qt.AlignHCenter) diff --git a/lisp/plugins/midi/midi_utils.py b/lisp/plugins/midi/midi_utils.py index a60145eb6..c3351db14 100644 --- a/lisp/plugins/midi/midi_utils.py +++ b/lisp/plugins/midi/midi_utils.py @@ -19,8 +19,6 @@ import mido -from lisp.core.configuration import AppConfig - def str_msg_to_dict(str_message): message = mido.parse_string(str_message) diff --git a/lisp/plugins/rename_cues/rename_ui.py b/lisp/plugins/rename_cues/rename_ui.py index 210dbc878..cd2fe31e2 100644 --- a/lisp/plugins/rename_cues/rename_ui.py +++ b/lisp/plugins/rename_cues/rename_ui.py @@ -104,7 +104,7 @@ def __init__(self, parent=None, selected_cues=()): # Help button self.helpButton = QPushButton() - self.helpButton.setIcon(QIcon.fromTheme('help-info')) + self.helpButton.setIcon(IconTheme.get('help-info')) self.helpButton.setIconSize(QSize(32, 32)) self.layout().addWidget(self.helpButton, 3, 4, 2, 1) self.helpButton.clicked.connect(self.onHelpButtonClicked) diff --git a/lisp/ui/about.py b/lisp/ui/about.py index 69f2394a3..5c85de93b 100644 --- a/lisp/ui/about.py +++ b/lisp/ui/about.py @@ -21,11 +21,11 @@ from PyQt5 import QtCore from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QWidget, QTabWidget, \ QTextBrowser, QDialogButtonBox import lisp +from lisp.ui.themes.theme import IconTheme from lisp.ui.ui_utils import translate @@ -81,7 +81,7 @@ def __init__(self, *args, **kwargs): self.iconLabel = QLabel(self) self.iconLabel.setPixmap( - QIcon.fromTheme('linux-show-player').pixmap(100, 100)) + IconTheme.get('linux-show-player').pixmap(100, 100)) self.layout().addWidget(self.iconLabel, 0, 0) self.shortInfo = QLabel(self) diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 92be54788..4f711161d 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -26,10 +26,9 @@ qApp, QFileDialog, QDialog, QMessageBox, QVBoxLayout, QWidget from lisp.core.actions_handler import MainActionsHandler -from lisp.core.configuration import AppConfig from lisp.core.singleton import QSingleton from lisp.cues.media_cue import MediaCue -from lisp.ui import about +from lisp.ui.about import About from lisp.ui.settings.app_settings import AppSettings from lisp.ui.ui_utils import translate @@ -129,7 +128,7 @@ def __init__(self, title='Linux Show Player'): # menuAbout self.actionAbout = QAction(self) - self.actionAbout.triggered.connect(about.About(self).show) + self.actionAbout.triggered.connect(self._show_about) self.actionAbout_Qt = QAction(self) self.actionAbout_Qt.triggered.connect(qApp.aboutQt) @@ -319,6 +318,9 @@ def _fullscreen(self, enable): else: self.showMaximized() + def _show_about(self): + About(self).show() + def _exit(self): if self._check_saved(): qApp.quit() diff --git a/lisp/ui/settings/pages/app_general.py b/lisp/ui/settings/pages/app_general.py index 888d237d2..030a4ea50 100644 --- a/lisp/ui/settings/pages/app_general.py +++ b/lisp/ui/settings/pages/app_general.py @@ -18,11 +18,12 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QComboBox +from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QComboBox, \ + QGridLayout, QLabel from lisp import layouts -from lisp.ui.styles import styles from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.themes import THEMES, ICON_THEMES from lisp.ui.ui_utils import translate @@ -57,12 +58,26 @@ def __init__(self, **kwargs): self.themeGroup = QGroupBox(self) self.themeGroup.setTitle( translate('AppGeneralSettings', 'Application theme')) - self.themeGroup.setLayout(QVBoxLayout()) + self.themeGroup.setLayout(QGridLayout()) self.layout().addWidget(self.themeGroup) + self.themeLabel = QLabel(self.themeGroup) + self.themeLabel.setText( + translate('AppGeneralSettings', 'Theme')) + self.themeGroup.layout().addWidget(self.themeLabel, 0, 0) + self.themeCombo = QComboBox(self.themeGroup) - self.themeCombo.addItems(styles.styles()) - self.themeGroup.layout().addWidget(self.themeCombo) + self.themeCombo.addItems(THEMES.keys()) + self.themeGroup.layout().addWidget(self.themeCombo, 0, 1) + + self.iconsLabel = QLabel(self.themeGroup) + self.iconsLabel.setText( + translate('AppGeneralSettings', 'Icons')) + self.themeGroup.layout().addWidget(self.iconsLabel, 1, 0) + + self.iconsCombo = QComboBox(self.themeGroup) + self.iconsCombo.addItems(ICON_THEMES.keys()) + self.themeGroup.layout().addWidget(self.iconsCombo, 1, 1) def get_settings(self): conf = {'layout': {}, 'theme': {}} @@ -73,7 +88,7 @@ def get_settings(self): conf['layout']['default'] = self.layoutCombo.currentText() conf['theme']['theme'] = self.themeCombo.currentText() - styles.apply_style(self.themeCombo.currentText()) + conf['theme']['icons'] = self.iconsCombo.currentText() return conf @@ -85,3 +100,4 @@ def load_settings(self, settings): self.layoutCombo.setCurrentText(settings['layout']['default']) self.themeCombo.setCurrentText(settings['theme']['theme']) + self.iconsCombo.setCurrentText(settings['theme']['icons']) diff --git a/lisp/ui/settings/pages/plugins_settings.py b/lisp/ui/settings/pages/plugins_settings.py index d0cfcbc65..9bbb9faff 100644 --- a/lisp/ui/settings/pages/plugins_settings.py +++ b/lisp/ui/settings/pages/plugins_settings.py @@ -18,12 +18,12 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt, QSize -from PyQt5.QtGui import QIcon -from PyQt5.QtWidgets import QListWidget, QListWidgetItem, \ - QTextBrowser, QVBoxLayout +from PyQt5.QtWidgets import QListWidget, QListWidgetItem, QTextBrowser, \ + QVBoxLayout from lisp import plugins from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.themes.theme import IconTheme # TODO: just a proof-of concept @@ -46,11 +46,11 @@ def __init__(self, **kwargs): for name, plugin in plugins.PLUGINS.items(): item = QListWidgetItem(plugin.Name) if plugins.is_loaded(name): - item.setIcon(QIcon().fromTheme('led-running')) + item.setIcon(IconTheme.get('led-running')) elif not plugin.Config['_enabled_']: - item.setIcon(QIcon().fromTheme('led-pause')) + item.setIcon(IconTheme.get('led-pause')) else: - item.setIcon(QIcon().fromTheme('led-error')) + item.setIcon(IconTheme.get('led-error')) item.setData(Qt.UserRole, plugin) diff --git a/lisp/ui/styles/dark/__init__.py b/lisp/ui/styles/dark/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/lisp/ui/styles/icons/LICENSE b/lisp/ui/styles/icons/LICENSE deleted file mode 100644 index 47aaabfd5..000000000 --- a/lisp/ui/styles/icons/LICENSE +++ /dev/null @@ -1,16 +0,0 @@ -* Fade functions icons from Qtractor - http://qtractor.sourceforge.net/qtractor-index.html - * icons/lisp/16/fadein-linear.png - * icons/lisp/16/fadein-quadratic.png - * icons/lisp/16/fadein-quadratic2.png - * icons/lisp/16/fadeout-linear.png - * icons/lisp/16/fadeout-quadratic.png - * icons/lisp/16/fadeout-quadratic2.png - -* Numix Project icons - https://numixproject.org - https://github.com/numixproject - * /icons/numix/ - -* Based on Numix Project icons: - * /icons/lisp/scalable/auto-follow.svg - * /icons/lisp/scalable/auto-next.svg - -* All the not previously specified icons are part of Linux Show Player diff --git a/lisp/ui/styles/icons/lisp/index.theme b/lisp/ui/styles/icons/lisp/index.theme deleted file mode 100644 index 0d922d642..000000000 --- a/lisp/ui/styles/icons/lisp/index.theme +++ /dev/null @@ -1,15 +0,0 @@ -[Icon Theme] -Name=lisp -Directories=16, scalable - -[16] -Size=16 -Type=fixed - -[512] -Size=512 -Type=fixed - -[scalable] -Size=96 -Type=scalable diff --git a/lisp/ui/styles/icons/lisp/scalable/fadein-generic.svg b/lisp/ui/styles/icons/lisp/scalable/fadein-generic.svg deleted file mode 100644 index eb2199562..000000000 --- a/lisp/ui/styles/icons/lisp/scalable/fadein-generic.svg +++ /dev/null @@ -1,204 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/lisp/scalable/fadeout-generic.svg b/lisp/ui/styles/icons/lisp/scalable/fadeout-generic.svg deleted file mode 100644 index 0e71914b2..000000000 --- a/lisp/ui/styles/icons/lisp/scalable/fadeout-generic.svg +++ /dev/null @@ -1,204 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/lisp/ui/styles/styles.py b/lisp/ui/styles/styles.py deleted file mode 100644 index e1ce64a06..000000000 --- a/lisp/ui/styles/styles.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -import sys -import os.path -from collections import namedtuple - -from PyQt5.QtGui import QPalette, QColor -from PyQt5.QtWidgets import QStyleFactory, qApp - -try: - from os import scandir -except ImportError: - from scandir import scandir - -StylesPath = os.path.abspath(os.path.dirname(__file__)) -IconsThemePaths = [os.path.join(StylesPath, 'icons')] - -LiSPStyles = {} -Style = namedtuple('Style', ['path', 'has_qss', 'has_py']) - - -def styles(): - """Returns all available styles (Qt and installed).""" - return QStyleFactory.keys() + list(LiSPStyles.keys()) - - -def scan_styles(): - """Scan for "installed" styles.""" - LiSPStyles.clear() - - for entry in scandir(StylesPath): - if entry.is_dir(): - has_qss = os.path.exists(os.path.join(entry.path, 'style.qss')) - has_py = os.path.exists(os.path.join(entry.path, 'style.py')) - - if has_qss or has_py: - LiSPStyles[entry.name.title()] = Style( - path=entry.path, - has_qss=has_qss, - has_py=has_py - ) - - -def __load_qss_style(path): - """Read and load the stylesheet file.""" - with open(path, mode='r', encoding='utf-8') as f: - style = f.read() - - qApp.setStyleSheet(style) - - -def __load_py_style(module): - """Load the python style module.""" - __import__(module, globals(), locals(), ['*']) - - -def apply_style(name): - """Load a style given its name.""" - style = LiSPStyles.get(name.title()) - - if isinstance(style, Style): - if style.has_py: - module = __package__ + '.' + os.path.basename(style.path) + '.style' - __load_py_style(module) - - if style.has_qss: - __load_qss_style(os.path.join(style.path, 'style.qss')) - else: - qApp.setStyleSheet('') - qApp.setStyle(QStyleFactory.create(name)) - - -# Search for styles -scan_styles() diff --git a/lisp/ui/themes/__init__.py b/lisp/ui/themes/__init__.py new file mode 100644 index 000000000..d455bd9ca --- /dev/null +++ b/lisp/ui/themes/__init__.py @@ -0,0 +1,11 @@ +from lisp.ui.themes.dark.theme import DarkTheme +from lisp.ui.themes.theme import IconTheme + +THEMES = { + DarkTheme.Name: DarkTheme(), +} + +ICON_THEMES = { + 'Symbolic': IconTheme('symbolic', 'lisp'), + 'Numix': IconTheme('numix', 'lisp') +} diff --git a/lisp/ui/styles/__init__.py b/lisp/ui/themes/dark/__init__.py similarity index 100% rename from lisp/ui/styles/__init__.py rename to lisp/ui/themes/dark/__init__.py diff --git a/lisp/ui/styles/dark/assetes.py b/lisp/ui/themes/dark/assetes.py similarity index 100% rename from lisp/ui/styles/dark/assetes.py rename to lisp/ui/themes/dark/assetes.py diff --git a/lisp/ui/styles/dark/assetes.rcc b/lisp/ui/themes/dark/assetes.rcc similarity index 100% rename from lisp/ui/styles/dark/assetes.rcc rename to lisp/ui/themes/dark/assetes.rcc diff --git a/lisp/ui/styles/dark/assets/Hmovetoolbar.png b/lisp/ui/themes/dark/assets/Hmovetoolbar.png similarity index 100% rename from lisp/ui/styles/dark/assets/Hmovetoolbar.png rename to lisp/ui/themes/dark/assets/Hmovetoolbar.png diff --git a/lisp/ui/styles/dark/assets/Hsepartoolbar.png b/lisp/ui/themes/dark/assets/Hsepartoolbar.png similarity index 100% rename from lisp/ui/styles/dark/assets/Hsepartoolbar.png rename to lisp/ui/themes/dark/assets/Hsepartoolbar.png diff --git a/lisp/ui/styles/dark/assets/Vmovetoolbar.png b/lisp/ui/themes/dark/assets/Vmovetoolbar.png similarity index 100% rename from lisp/ui/styles/dark/assets/Vmovetoolbar.png rename to lisp/ui/themes/dark/assets/Vmovetoolbar.png diff --git a/lisp/ui/styles/dark/assets/Vsepartoolbar.png b/lisp/ui/themes/dark/assets/Vsepartoolbar.png similarity index 100% rename from lisp/ui/styles/dark/assets/Vsepartoolbar.png rename to lisp/ui/themes/dark/assets/Vsepartoolbar.png diff --git a/lisp/ui/styles/dark/assets/branch-closed.png b/lisp/ui/themes/dark/assets/branch-closed.png similarity index 100% rename from lisp/ui/styles/dark/assets/branch-closed.png rename to lisp/ui/themes/dark/assets/branch-closed.png diff --git a/lisp/ui/styles/dark/assets/branch-end.png b/lisp/ui/themes/dark/assets/branch-end.png similarity index 100% rename from lisp/ui/styles/dark/assets/branch-end.png rename to lisp/ui/themes/dark/assets/branch-end.png diff --git a/lisp/ui/styles/dark/assets/branch-more.png b/lisp/ui/themes/dark/assets/branch-more.png similarity index 100% rename from lisp/ui/styles/dark/assets/branch-more.png rename to lisp/ui/themes/dark/assets/branch-more.png diff --git a/lisp/ui/styles/dark/assets/branch-open.png b/lisp/ui/themes/dark/assets/branch-open.png similarity index 100% rename from lisp/ui/styles/dark/assets/branch-open.png rename to lisp/ui/themes/dark/assets/branch-open.png diff --git a/lisp/ui/styles/dark/assets/checkbox-checked-disabled.png b/lisp/ui/themes/dark/assets/checkbox-checked-disabled.png similarity index 100% rename from lisp/ui/styles/dark/assets/checkbox-checked-disabled.png rename to lisp/ui/themes/dark/assets/checkbox-checked-disabled.png diff --git a/lisp/ui/styles/dark/assets/checkbox-checked.png b/lisp/ui/themes/dark/assets/checkbox-checked.png similarity index 100% rename from lisp/ui/styles/dark/assets/checkbox-checked.png rename to lisp/ui/themes/dark/assets/checkbox-checked.png diff --git a/lisp/ui/styles/dark/assets/checkbox-mixed-disabled.png b/lisp/ui/themes/dark/assets/checkbox-mixed-disabled.png similarity index 100% rename from lisp/ui/styles/dark/assets/checkbox-mixed-disabled.png rename to lisp/ui/themes/dark/assets/checkbox-mixed-disabled.png diff --git a/lisp/ui/styles/dark/assets/checkbox-mixed.png b/lisp/ui/themes/dark/assets/checkbox-mixed.png similarity index 100% rename from lisp/ui/styles/dark/assets/checkbox-mixed.png rename to lisp/ui/themes/dark/assets/checkbox-mixed.png diff --git a/lisp/ui/styles/dark/assets/checkbox-unchecked-disabled.png b/lisp/ui/themes/dark/assets/checkbox-unchecked-disabled.png similarity index 100% rename from lisp/ui/styles/dark/assets/checkbox-unchecked-disabled.png rename to lisp/ui/themes/dark/assets/checkbox-unchecked-disabled.png diff --git a/lisp/ui/styles/dark/assets/checkbox-unchecked.png b/lisp/ui/themes/dark/assets/checkbox-unchecked.png similarity index 100% rename from lisp/ui/styles/dark/assets/checkbox-unchecked.png rename to lisp/ui/themes/dark/assets/checkbox-unchecked.png diff --git a/lisp/ui/styles/dark/assets/close.png b/lisp/ui/themes/dark/assets/close.png similarity index 100% rename from lisp/ui/styles/dark/assets/close.png rename to lisp/ui/themes/dark/assets/close.png diff --git a/lisp/ui/styles/dark/assets/down-arrow-disabled.png b/lisp/ui/themes/dark/assets/down-arrow-disabled.png similarity index 100% rename from lisp/ui/styles/dark/assets/down-arrow-disabled.png rename to lisp/ui/themes/dark/assets/down-arrow-disabled.png diff --git a/lisp/ui/styles/dark/assets/down-arrow.png b/lisp/ui/themes/dark/assets/down-arrow.png similarity index 100% rename from lisp/ui/styles/dark/assets/down-arrow.png rename to lisp/ui/themes/dark/assets/down-arrow.png diff --git a/lisp/ui/styles/dark/assets/radio-checked-disabled.png b/lisp/ui/themes/dark/assets/radio-checked-disabled.png similarity index 100% rename from lisp/ui/styles/dark/assets/radio-checked-disabled.png rename to lisp/ui/themes/dark/assets/radio-checked-disabled.png diff --git a/lisp/ui/styles/dark/assets/radio-checked.png b/lisp/ui/themes/dark/assets/radio-checked.png similarity index 100% rename from lisp/ui/styles/dark/assets/radio-checked.png rename to lisp/ui/themes/dark/assets/radio-checked.png diff --git a/lisp/ui/styles/dark/assets/radio-mixed-disabled.png b/lisp/ui/themes/dark/assets/radio-mixed-disabled.png similarity index 100% rename from lisp/ui/styles/dark/assets/radio-mixed-disabled.png rename to lisp/ui/themes/dark/assets/radio-mixed-disabled.png diff --git a/lisp/ui/styles/dark/assets/radio-mixed.png b/lisp/ui/themes/dark/assets/radio-mixed.png similarity index 100% rename from lisp/ui/styles/dark/assets/radio-mixed.png rename to lisp/ui/themes/dark/assets/radio-mixed.png diff --git a/lisp/ui/styles/dark/assets/radio-unchecked-disabled.png b/lisp/ui/themes/dark/assets/radio-unchecked-disabled.png similarity index 100% rename from lisp/ui/styles/dark/assets/radio-unchecked-disabled.png rename to lisp/ui/themes/dark/assets/radio-unchecked-disabled.png diff --git a/lisp/ui/styles/dark/assets/radio-unchecked.png b/lisp/ui/themes/dark/assets/radio-unchecked.png similarity index 100% rename from lisp/ui/styles/dark/assets/radio-unchecked.png rename to lisp/ui/themes/dark/assets/radio-unchecked.png diff --git a/lisp/ui/styles/dark/assets/sizegrip.png b/lisp/ui/themes/dark/assets/sizegrip.png similarity index 100% rename from lisp/ui/styles/dark/assets/sizegrip.png rename to lisp/ui/themes/dark/assets/sizegrip.png diff --git a/lisp/ui/styles/dark/assets/slider-handle-disabled.png b/lisp/ui/themes/dark/assets/slider-handle-disabled.png similarity index 100% rename from lisp/ui/styles/dark/assets/slider-handle-disabled.png rename to lisp/ui/themes/dark/assets/slider-handle-disabled.png diff --git a/lisp/ui/styles/dark/assets/slider-handle.png b/lisp/ui/themes/dark/assets/slider-handle.png similarity index 100% rename from lisp/ui/styles/dark/assets/slider-handle.png rename to lisp/ui/themes/dark/assets/slider-handle.png diff --git a/lisp/ui/styles/dark/assets/spin-down-disabled.png b/lisp/ui/themes/dark/assets/spin-down-disabled.png similarity index 100% rename from lisp/ui/styles/dark/assets/spin-down-disabled.png rename to lisp/ui/themes/dark/assets/spin-down-disabled.png diff --git a/lisp/ui/styles/dark/assets/spin-down.png b/lisp/ui/themes/dark/assets/spin-down.png similarity index 100% rename from lisp/ui/styles/dark/assets/spin-down.png rename to lisp/ui/themes/dark/assets/spin-down.png diff --git a/lisp/ui/styles/dark/assets/spin-up-disabled.png b/lisp/ui/themes/dark/assets/spin-up-disabled.png similarity index 100% rename from lisp/ui/styles/dark/assets/spin-up-disabled.png rename to lisp/ui/themes/dark/assets/spin-up-disabled.png diff --git a/lisp/ui/styles/dark/assets/spin-up.png b/lisp/ui/themes/dark/assets/spin-up.png similarity index 100% rename from lisp/ui/styles/dark/assets/spin-up.png rename to lisp/ui/themes/dark/assets/spin-up.png diff --git a/lisp/ui/styles/dark/assets/transparent.png b/lisp/ui/themes/dark/assets/transparent.png similarity index 100% rename from lisp/ui/styles/dark/assets/transparent.png rename to lisp/ui/themes/dark/assets/transparent.png diff --git a/lisp/ui/styles/dark/assets/undock.png b/lisp/ui/themes/dark/assets/undock.png similarity index 100% rename from lisp/ui/styles/dark/assets/undock.png rename to lisp/ui/themes/dark/assets/undock.png diff --git a/lisp/ui/styles/dark/assets/up-arrow-disabled.png b/lisp/ui/themes/dark/assets/up-arrow-disabled.png similarity index 100% rename from lisp/ui/styles/dark/assets/up-arrow-disabled.png rename to lisp/ui/themes/dark/assets/up-arrow-disabled.png diff --git a/lisp/ui/styles/dark/assets/up-arrow.png b/lisp/ui/themes/dark/assets/up-arrow.png similarity index 100% rename from lisp/ui/styles/dark/assets/up-arrow.png rename to lisp/ui/themes/dark/assets/up-arrow.png diff --git a/lisp/ui/styles/dark/assets/vline.png b/lisp/ui/themes/dark/assets/vline.png similarity index 100% rename from lisp/ui/styles/dark/assets/vline.png rename to lisp/ui/themes/dark/assets/vline.png diff --git a/lisp/ui/styles/dark/style.py b/lisp/ui/themes/dark/theme.py similarity index 58% rename from lisp/ui/styles/dark/style.py rename to lisp/ui/themes/dark/theme.py index 2d306073b..59aea8419 100644 --- a/lisp/ui/styles/dark/style.py +++ b/lisp/ui/themes/dark/theme.py @@ -17,16 +17,26 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtGui import QColor -from PyQt5.QtGui import QPalette -from PyQt5.QtWidgets import qApp +import os +from PyQt5.QtGui import QColor, QPalette + +from lisp.ui.themes.theme import Theme # Import resources # noinspection PyUnresolvedReferences -from lisp.ui.styles.dark import assetes +from . import assetes + + +class DarkTheme(Theme): + QssPath = os.path.join(os.path.dirname(__file__), 'theme.qss') + Name = 'Dark' + + def apply(self, qt_app): + with open(DarkTheme.QssPath, mode='r', encoding='utf-8') as f: + qt_app.setStyleSheet(f.read()) -# Change link color -palette = qApp.palette() -palette.setColor(QPalette.Link, QColor(65, 155, 230)) -palette.setColor(QPalette.LinkVisited, QColor(43, 103, 153)) -qApp.setPalette(palette) + # Change link color + palette = qt_app.palette() + palette.setColor(QPalette.Link, QColor(65, 155, 230)) + palette.setColor(QPalette.LinkVisited, QColor(43, 103, 153)) + qt_app.setPalette(palette) diff --git a/lisp/ui/styles/dark/style.qss b/lisp/ui/themes/dark/theme.qss similarity index 100% rename from lisp/ui/styles/dark/style.qss rename to lisp/ui/themes/dark/theme.qss diff --git a/lisp/ui/styles/icons/lisp/16/fadein-linear.png b/lisp/ui/themes/icons/lisp/fadein-linear.png similarity index 100% rename from lisp/ui/styles/icons/lisp/16/fadein-linear.png rename to lisp/ui/themes/icons/lisp/fadein-linear.png diff --git a/lisp/ui/styles/icons/lisp/16/fadein-quadratic.png b/lisp/ui/themes/icons/lisp/fadein-quadratic.png similarity index 100% rename from lisp/ui/styles/icons/lisp/16/fadein-quadratic.png rename to lisp/ui/themes/icons/lisp/fadein-quadratic.png diff --git a/lisp/ui/styles/icons/lisp/16/fadein-quadratic2.png b/lisp/ui/themes/icons/lisp/fadein-quadratic2.png similarity index 100% rename from lisp/ui/styles/icons/lisp/16/fadein-quadratic2.png rename to lisp/ui/themes/icons/lisp/fadein-quadratic2.png diff --git a/lisp/ui/styles/icons/lisp/16/fadeout-linear.png b/lisp/ui/themes/icons/lisp/fadeout-linear.png similarity index 100% rename from lisp/ui/styles/icons/lisp/16/fadeout-linear.png rename to lisp/ui/themes/icons/lisp/fadeout-linear.png diff --git a/lisp/ui/styles/icons/lisp/16/fadeout-quadratic.png b/lisp/ui/themes/icons/lisp/fadeout-quadratic.png similarity index 100% rename from lisp/ui/styles/icons/lisp/16/fadeout-quadratic.png rename to lisp/ui/themes/icons/lisp/fadeout-quadratic.png diff --git a/lisp/ui/styles/icons/lisp/16/fadeout-quadratic2.png b/lisp/ui/themes/icons/lisp/fadeout-quadratic2.png similarity index 100% rename from lisp/ui/styles/icons/lisp/16/fadeout-quadratic2.png rename to lisp/ui/themes/icons/lisp/fadeout-quadratic2.png diff --git a/lisp/ui/styles/icons/lisp/scalable/led-error.svg b/lisp/ui/themes/icons/lisp/led-error.svg similarity index 100% rename from lisp/ui/styles/icons/lisp/scalable/led-error.svg rename to lisp/ui/themes/icons/lisp/led-error.svg diff --git a/lisp/ui/styles/icons/lisp/scalable/led-off.svg b/lisp/ui/themes/icons/lisp/led-off.svg similarity index 100% rename from lisp/ui/styles/icons/lisp/scalable/led-off.svg rename to lisp/ui/themes/icons/lisp/led-off.svg diff --git a/lisp/ui/styles/icons/lisp/scalable/led-pause.svg b/lisp/ui/themes/icons/lisp/led-pause.svg similarity index 100% rename from lisp/ui/styles/icons/lisp/scalable/led-pause.svg rename to lisp/ui/themes/icons/lisp/led-pause.svg diff --git a/lisp/ui/styles/icons/lisp/scalable/led-running.svg b/lisp/ui/themes/icons/lisp/led-running.svg similarity index 100% rename from lisp/ui/styles/icons/lisp/scalable/led-running.svg rename to lisp/ui/themes/icons/lisp/led-running.svg diff --git a/lisp/ui/styles/icons/lisp/512/linux-show-player.png b/lisp/ui/themes/icons/lisp/linux-show-player.png similarity index 100% rename from lisp/ui/styles/icons/lisp/512/linux-show-player.png rename to lisp/ui/themes/icons/lisp/linux-show-player.png diff --git a/lisp/ui/styles/icons/lisp/scalable/mixer-handle.svg b/lisp/ui/themes/icons/lisp/mixer-handle.svg similarity index 100% rename from lisp/ui/styles/icons/lisp/scalable/mixer-handle.svg rename to lisp/ui/themes/icons/lisp/mixer-handle.svg diff --git a/lisp/ui/themes/icons/numix/cue-interrupt.svg b/lisp/ui/themes/icons/numix/cue-interrupt.svg new file mode 100644 index 000000000..2c6cfebdc --- /dev/null +++ b/lisp/ui/themes/icons/numix/cue-interrupt.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/themes/icons/numix/cue-pause.svg b/lisp/ui/themes/icons/numix/cue-pause.svg new file mode 100644 index 000000000..39a356a5c --- /dev/null +++ b/lisp/ui/themes/icons/numix/cue-pause.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/themes/icons/numix/cue-start.svg b/lisp/ui/themes/icons/numix/cue-start.svg new file mode 100644 index 000000000..07df6ef16 --- /dev/null +++ b/lisp/ui/themes/icons/numix/cue-start.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/numix/cue-stop.svg b/lisp/ui/themes/icons/numix/cue-stop.svg new file mode 100644 index 000000000..b397553dd --- /dev/null +++ b/lisp/ui/themes/icons/numix/cue-stop.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/numix/fadein-generic.svg b/lisp/ui/themes/icons/numix/fadein-generic.svg new file mode 100644 index 000000000..f03aba150 --- /dev/null +++ b/lisp/ui/themes/icons/numix/fadein-generic.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/lisp/ui/styles/icons/numix/scalable/devices/drive-removable-media-usb.svg b/lisp/ui/themes/icons/numix/fadeout-generic.svg similarity index 50% rename from lisp/ui/styles/icons/numix/scalable/devices/drive-removable-media-usb.svg rename to lisp/ui/themes/icons/numix/fadeout-generic.svg index 569f89eec..4d49e99fc 100644 --- a/lisp/ui/styles/icons/numix/scalable/devices/drive-removable-media-usb.svg +++ b/lisp/ui/themes/icons/numix/fadeout-generic.svg @@ -7,13 +7,12 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - viewBox="0 0 16 16" - id="svg2" + width="16" + height="16" version="1.1" - inkscape:version="0.48.4 r9939" - width="100%" - height="100%" - sodipodi:docname="drive-removable-media-usb-symbolic.svg"> + id="svg4" + sodipodi:docname="fadeout-generic.svg" + inkscape:version="0.92.2 5c3e80d, 2017-08-06"> @@ -22,6 +21,7 @@ image/svg+xml + @@ -37,24 +37,33 @@ inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1366" - inkscape:window-height="704" + inkscape:window-height="713" id="namedview6" - showgrid="true" + showgrid="false" inkscape:zoom="29.5" - inkscape:cx="1.6271186" - inkscape:cy="8" + inkscape:cx="6.4237288" + inkscape:cy="7.5979727" inkscape:window-x="0" inkscape:window-y="27" inkscape:window-maximized="1" - inkscape:current-layer="svg2"> - - - + inkscape:current-layer="svg4" /> + + + + + diff --git a/lisp/ui/themes/icons/numix/media-eject.svg b/lisp/ui/themes/icons/numix/media-eject.svg new file mode 100644 index 000000000..22d746140 --- /dev/null +++ b/lisp/ui/themes/icons/numix/media-eject.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/themes/icons/numix/media-record.svg b/lisp/ui/themes/icons/numix/media-record.svg new file mode 100644 index 000000000..25b172d93 --- /dev/null +++ b/lisp/ui/themes/icons/numix/media-record.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/numix/media-seek-backward.svg b/lisp/ui/themes/icons/numix/media-seek-backward.svg new file mode 100644 index 000000000..6d810ad18 --- /dev/null +++ b/lisp/ui/themes/icons/numix/media-seek-backward.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/themes/icons/numix/media-seek-forward.svg b/lisp/ui/themes/icons/numix/media-seek-forward.svg new file mode 100644 index 000000000..862e3f64b --- /dev/null +++ b/lisp/ui/themes/icons/numix/media-seek-forward.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/themes/icons/numix/media-skip-backward.svg b/lisp/ui/themes/icons/numix/media-skip-backward.svg new file mode 100644 index 000000000..078849d90 --- /dev/null +++ b/lisp/ui/themes/icons/numix/media-skip-backward.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/themes/icons/numix/media-skip-forward.svg b/lisp/ui/themes/icons/numix/media-skip-forward.svg new file mode 100644 index 000000000..613fafe49 --- /dev/null +++ b/lisp/ui/themes/icons/numix/media-skip-forward.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/styles/icons/lisp/scalable/auto-follow.svg b/lisp/ui/themes/icons/symbolic/auto-follow.svg similarity index 100% rename from lisp/ui/styles/icons/lisp/scalable/auto-follow.svg rename to lisp/ui/themes/icons/symbolic/auto-follow.svg diff --git a/lisp/ui/styles/icons/lisp/scalable/auto-next.svg b/lisp/ui/themes/icons/symbolic/auto-next.svg similarity index 100% rename from lisp/ui/styles/icons/lisp/scalable/auto-next.svg rename to lisp/ui/themes/icons/symbolic/auto-next.svg diff --git a/lisp/ui/themes/icons/symbolic/cue-interrupt.svg b/lisp/ui/themes/icons/symbolic/cue-interrupt.svg new file mode 100644 index 000000000..223335ada --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/cue-interrupt.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/symbolic/cue-pause.svg b/lisp/ui/themes/icons/symbolic/cue-pause.svg new file mode 100644 index 000000000..a4b3ae0b5 --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/cue-pause.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/themes/icons/symbolic/cue-start.svg b/lisp/ui/themes/icons/symbolic/cue-start.svg new file mode 100644 index 000000000..891f6ad60 --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/cue-start.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/symbolic/cue-stop.svg b/lisp/ui/themes/icons/symbolic/cue-stop.svg new file mode 100644 index 000000000..4c899eb29 --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/cue-stop.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/symbolic/fadein-generic.svg b/lisp/ui/themes/icons/symbolic/fadein-generic.svg new file mode 100644 index 000000000..dcb4c625f --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/fadein-generic.svg @@ -0,0 +1,74 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/lisp/ui/themes/icons/symbolic/fadeout-generic.svg b/lisp/ui/themes/icons/symbolic/fadeout-generic.svg new file mode 100644 index 000000000..bd33f5717 --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/fadeout-generic.svg @@ -0,0 +1,74 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/lisp/ui/themes/icons/symbolic/media-eject.svg b/lisp/ui/themes/icons/symbolic/media-eject.svg new file mode 100644 index 000000000..4cd574e67 --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/media-eject.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/symbolic/media-record.svg b/lisp/ui/themes/icons/symbolic/media-record.svg new file mode 100644 index 000000000..2031186ac --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/media-record.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/symbolic/media-seek-backward.svg b/lisp/ui/themes/icons/symbolic/media-seek-backward.svg new file mode 100644 index 000000000..2b629317e --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/media-seek-backward.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/themes/icons/symbolic/media-seek-forward.svg b/lisp/ui/themes/icons/symbolic/media-seek-forward.svg new file mode 100644 index 000000000..be9a09a9f --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/media-seek-forward.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/themes/icons/symbolic/media-skip-backward.svg b/lisp/ui/themes/icons/symbolic/media-skip-backward.svg new file mode 100644 index 000000000..64d17f65f --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/media-skip-backward.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/symbolic/media-skip-forward.svg b/lisp/ui/themes/icons/symbolic/media-skip-forward.svg new file mode 100644 index 000000000..82b864de9 --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/media-skip-forward.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/theme.py b/lisp/ui/themes/theme.py new file mode 100644 index 000000000..dbcefc5e4 --- /dev/null +++ b/lisp/ui/themes/theme.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import glob +import os + +from PyQt5.QtGui import QIcon + +from lisp import ICON_THEMES_DIR + + +class Theme: + Name = 'Theme' + + def apply(self, qt_app): + """ + :type qt_app: PyQt5.QtWidgets.QApplication + """ + + +class IconTheme: + BLANK = QIcon() + + _GlobalCache = {} + _GlobalTheme = None + + def __init__(self, *dirs): + self._lookup_dirs = [ + os.path.join(ICON_THEMES_DIR, d) for d in dirs + ] + + def __iter__(self): + yield from self._lookup_dirs + + @staticmethod + def get(icon_name): + icon = IconTheme._GlobalCache.get(icon_name, None) + + if icon is None: + icon = IconTheme.BLANK + for dir_ in IconTheme.theme(): + for icon in glob.iglob(os.path.join(dir_, icon_name) + '.*'): + icon = QIcon(icon) + + IconTheme._GlobalCache[icon_name] = icon + + return icon + + @staticmethod + def theme(): + return IconTheme._GlobalTheme + + @staticmethod + def set_theme(theme): + IconTheme.clean_cache() + IconTheme._GlobalTheme = theme + + @staticmethod + def clean_cache(): + IconTheme._GlobalCache.clear() diff --git a/lisp/ui/ui_utils.py b/lisp/ui/ui_utils.py index 88462dfab..82b5f68e1 100644 --- a/lisp/ui/ui_utils.py +++ b/lisp/ui/ui_utils.py @@ -16,42 +16,15 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . + import logging from itertools import chain from os import path from PyQt5.QtCore import QTranslator, QLocale -from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QApplication -import lisp -from lisp.core.decorators import memoize - -DEFAULT_I18N_PATH = path.join(path.dirname(lisp.__file__), 'i18n') -TRANSLATORS = [] - -@memoize -def load_icon(icon_name): - """Return a QIcon from the icon theme. - - The loaded icons are cached. - - .. warning: - QIcons should be loaded only from the QT main loop. - """ - return QIcon.fromTheme(icon_name) - - -@memoize -def pixmap_from_icon(icon_name, size): - """Return a QPixmap of "size x size" pixels from the icon theme. - - The returned pixmaps are cached. - - .. warning: - QPixmap should be created only from the QT main loop. - """ - return load_icon(icon_name).pixmap(size, size) +from lisp import I18N_PATH def qfile_filters(extensions, allexts=True, anyfile=True): @@ -85,16 +58,22 @@ def qfile_filters(extensions, allexts=True, anyfile=True): return ';;'.join(filters) -def install_translation(name, tr_path=DEFAULT_I18N_PATH): +# Keep a reference of translators objects +_TRANSLATORS = [] + + +def install_translation(name, tr_path=I18N_PATH): tr_file = path.join(tr_path, name) translator = QTranslator() translator.load(QLocale(), tr_file, '_') - TRANSLATORS.append(translator) - if QApplication.installTranslator(translator): + # Keep a reference, QApplication does not + _TRANSLATORS.append(translator) logging.debug('Installed translation: {}'.format(tr_file)) + else: + logging.debug('No translation for: {}'.format(tr_file)) def translate(context, text, disambiguation=None, n=-1): @@ -119,4 +98,4 @@ def tr_key(item): def tr_key(item): translate(context, item) - return sorted(iterable, key=tr_key, reverse=reverse)\ + return sorted(iterable, key=tr_key, reverse=reverse) diff --git a/lisp/ui/widgets/fades.py b/lisp/ui/widgets/fades.py index 3fed0c8e9..561a57e74 100644 --- a/lisp/ui/widgets/fades.py +++ b/lisp/ui/widgets/fades.py @@ -20,10 +20,10 @@ from enum import Enum from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt -from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QComboBox, QStyledItemDelegate, QWidget, \ QGridLayout, QDoubleSpinBox, QLabel +from lisp.ui.themes.theme import IconTheme from lisp.ui.ui_utils import translate QT_TRANSLATE_NOOP('Fade', 'Linear') @@ -33,15 +33,15 @@ class FadeComboBox(QComboBox): FadeOutIcons = { - 'Linear': QIcon.fromTheme('fadeout-linear'), - 'Quadratic': QIcon.fromTheme('fadeout-quadratic'), - 'Quadratic2': QIcon.fromTheme('fadeout-quadratic2') + 'Linear': 'fadeout-linear', + 'Quadratic': 'fadeout-quadratic', + 'Quadratic2': 'fadeout-quadratic2' } FadeInIcons = { - 'Linear': QIcon.fromTheme('fadein-linear'), - 'Quadratic': QIcon.fromTheme('fadein-quadratic'), - 'Quadratic2': QIcon.fromTheme('fadein-quadratic2') + 'Linear': 'fadein-linear', + 'Quadratic': 'fadein-quadratic', + 'Quadratic2': 'fadein-quadratic2' } class Mode(Enum): @@ -58,7 +58,7 @@ def __init__(self, *args, mode=Mode.FadeOut, **kwargs): items = self.FadeOutIcons for key in sorted(items.keys()): - self.addItem(items[key], translate('Fade', key), key) + self.addItem(IconTheme.get(items[key]), translate('Fade', key), key) def setCurrentType(self, type): self.setCurrentText(translate('Fade', type)) diff --git a/lisp/ui/widgets/qmutebutton.py b/lisp/ui/widgets/qmutebutton.py index 1abc5de3f..c19c727ac 100644 --- a/lisp/ui/widgets/qmutebutton.py +++ b/lisp/ui/widgets/qmutebutton.py @@ -17,9 +17,10 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QPushButton +from lisp.ui.themes.theme import IconTheme + class QMuteButton(QPushButton): def __init__(self, *args): @@ -39,6 +40,6 @@ def __init__(self, *args): def onToggle(self): if self.isChecked(): - self.setIcon(QIcon.fromTheme('audio-volume-muted')) + self.setIcon(IconTheme.get('audio-volume-muted')) else: - self.setIcon(QIcon.fromTheme('audio-volume-high')) + self.setIcon(IconTheme.get('audio-volume-high')) From 5e0a754ee56ec997246b34c8cbcc9d23e3281229 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 21 Feb 2018 19:45:23 +0100 Subject: [PATCH 087/333] Remove unused icons --- lisp/ui/styles/icons/numix/index.theme | 61 --- .../scalable/actions/action-unavailable.svg | 6 - .../numix/scalable/actions/bookmark-add.svg | 6 - .../numix/scalable/actions/bookmark-new.svg | 5 - .../scalable/actions/browser-download.svg | 6 - .../icons/numix/scalable/actions/call-end.svg | 6 - .../numix/scalable/actions/call-start.svg | 5 - .../numix/scalable/actions/contact-new.svg | 3 - .../scalable/actions/content-loading.svg | 7 - .../scalable/actions/document-export.svg | 3 - .../scalable/actions/document-import.svg | 7 - .../numix/scalable/actions/document-new.svg | 6 - .../scalable/actions/document-open-recent.svg | 3 - .../numix/scalable/actions/document-open.svg | 6 - .../scalable/actions/document-page-setup.svg | 6 - .../scalable/actions/document-properties.svg | 6 - .../scalable/actions/document-save-as.svg | 8 - .../numix/scalable/actions/document-save.svg | 8 - .../numix/scalable/actions/document-send.svg | 8 - .../scalable/actions/drive-multidisk.svg | 10 - .../numix/scalable/actions/edit-clear-all.svg | 7 - .../numix/scalable/actions/edit-clear-rtl.svg | 5 - .../numix/scalable/actions/edit-clear.svg | 5 - .../numix/scalable/actions/edit-copy.svg | 6 - .../icons/numix/scalable/actions/edit-cut.svg | 9 - .../numix/scalable/actions/edit-delete.svg | 5 - .../numix/scalable/actions/edit-find.svg | 15 - .../numix/scalable/actions/edit-paste.svg | 7 - .../numix/scalable/actions/edit-redo-rtl.svg | 6 - .../numix/scalable/actions/edit-redo.svg | 6 - .../scalable/actions/edit-select-all.svg | 5 - .../numix/scalable/actions/edit-select.svg | 6 - .../numix/scalable/actions/edit-undo-rtl.svg | 6 - .../numix/scalable/actions/edit-undo.svg | 6 - .../numix/scalable/actions/find-location.svg | 12 - .../actions/format-indent-less-rtl.svg | 8 - .../scalable/actions/format-indent-less.svg | 8 - .../actions/format-indent-more-rtl.svg | 8 - .../scalable/actions/format-indent-more.svg | 8 - .../actions/format-justify-center.svg | 7 - .../scalable/actions/format-justify-fill.svg | 7 - .../scalable/actions/format-justify-left.svg | 7 - .../scalable/actions/format-justify-right.svg | 7 - .../scalable/actions/format-text-bold.svg | 6 - .../actions/format-text-direction-ltr.svg | 7 - .../actions/format-text-direction-rtl.svg | 7 - .../scalable/actions/format-text-italic.svg | 5 - .../scalable/actions/format-text-larger.svg | 3 - .../scalable/actions/format-text-smaller.svg | 3 - .../actions/format-text-strikethrough.svg | 6 - .../actions/format-text-underline.svg | 6 - .../numix/scalable/actions/go-bottom.svg | 6 - .../icons/numix/scalable/actions/go-down.svg | 6 - .../numix/scalable/actions/go-first-rtl.svg | 6 - .../icons/numix/scalable/actions/go-first.svg | 6 - .../icons/numix/scalable/actions/go-home.svg | 5 - .../numix/scalable/actions/go-jump-rtl.svg | 6 - .../icons/numix/scalable/actions/go-jump.svg | 6 - .../numix/scalable/actions/go-last-rtl.svg | 6 - .../icons/numix/scalable/actions/go-last.svg | 6 - .../numix/scalable/actions/go-next-rtl.svg | 5 - .../icons/numix/scalable/actions/go-next.svg | 6 - .../scalable/actions/go-previous-rtl.svg | 5 - .../numix/scalable/actions/go-previous.svg | 6 - .../icons/numix/scalable/actions/go-top.svg | 6 - .../icons/numix/scalable/actions/go-up.svg | 6 - .../numix/scalable/actions/help-info.svg | 3 - .../numix/scalable/actions/image-crop.svg | 3 - .../numix/scalable/actions/image-red-eye.svg | 12 - .../numix/scalable/actions/insert-image.svg | 9 - .../numix/scalable/actions/insert-link.svg | 10 - .../numix/scalable/actions/insert-object.svg | 6 - .../numix/scalable/actions/insert-text.svg | 6 - .../icons/numix/scalable/actions/list-add.svg | 5 - .../scalable/actions/list-remove-all.svg | 13 - .../numix/scalable/actions/list-remove.svg | 5 - .../numix/scalable/actions/mail-forward.svg | 3 - .../scalable/actions/mail-mark-important.svg | 9 - .../scalable/actions/mail-reply-sender.svg | 3 - .../scalable/actions/mail-send-receive.svg | 8 - .../numix/scalable/actions/mail-send.svg | 6 - .../numix/scalable/actions/mark-location.svg | 5 - .../numix/scalable/actions/media-eject.svg | 6 - .../icons/numix/scalable/actions/media-eq.svg | 3 - .../scalable/actions/media-playback-pause.svg | 4 - .../actions/media-playback-start-rtl.svg | 5 - .../scalable/actions/media-playback-start.svg | 5 - .../scalable/actions/media-playback-stop.svg | 3 - .../numix/scalable/actions/media-record.svg | 5 - .../actions/media-seek-backward-rtl.svg | 4 - .../scalable/actions/media-seek-backward.svg | 4 - .../actions/media-seek-forward-rtl.svg | 4 - .../scalable/actions/media-seek-forward.svg | 4 - .../actions/media-skip-backward-rtl.svg | 7 - .../scalable/actions/media-skip-backward.svg | 7 - .../actions/media-skip-forward-rtl.svg | 5 - .../scalable/actions/media-skip-forward.svg | 5 - .../actions/object-flip-horizontal.svg | 9 - .../scalable/actions/object-flip-vertical.svg | 7 - .../numix/scalable/actions/object-inverse.svg | 58 --- .../scalable/actions/object-rotate-left.svg | 6 - .../scalable/actions/object-rotate-right.svg | 6 - .../numix/scalable/actions/object-select.svg | 5 - .../numix/scalable/actions/pane-hide.svg | 6 - .../numix/scalable/actions/pane-show.svg | 3 - .../numix/scalable/actions/process-stop.svg | 6 - .../icons/numix/scalable/actions/send-to.svg | 6 - .../numix/scalable/actions/system-devices.svg | 3 - .../numix/scalable/actions/system-run.svg | 8 - .../scalable/actions/system-shutdown.svg | 7 - .../icons/numix/scalable/actions/tab-new.svg | 7 - .../scalable/actions/view-calendar-day.svg | 327 ------------- .../scalable/actions/view-calendar-list.svg | 317 ------------ .../scalable/actions/view-calendar-month.svg | 352 -------------- .../scalable/actions/view-calendar-week.svg | 448 ----------------- .../actions/view-calendar-workweek.svg | 455 ------------------ .../numix/scalable/actions/view-column.svg | 3 - .../scalable/actions/view-continuous.svg | 9 - .../numix/scalable/actions/view-coverflow.svg | 19 - .../numix/scalable/actions/view-dual.svg | 9 - .../numix/scalable/actions/view-filter.svg | 3 - .../scalable/actions/view-fullscreen.svg | 85 ---- .../numix/scalable/actions/view-grid.svg | 13 - .../scalable/actions/view-list-compact.svg | 10 - .../scalable/actions/view-list-images.svg | 3 - .../scalable/actions/view-list-video.svg | 3 - .../numix/scalable/actions/view-list.svg | 14 - .../numix/scalable/actions/view-more.svg | 7 - .../numix/scalable/actions/view-paged.svg | 10 - .../numix/scalable/actions/view-refresh.svg | 6 - .../numix/scalable/actions/view-restore.svg | 17 - .../numix/scalable/actions/view-sidebar.svg | 7 - .../numix/scalable/actions/window-close.svg | 5 - .../scalable/actions/window-maximize.svg | 5 - .../scalable/actions/window-minimize.svg | 5 - .../numix/scalable/actions/window-restore.svg | 5 - .../numix/scalable/actions/zoom-fit-best.svg | 5 - .../icons/numix/scalable/actions/zoom-in.svg | 5 - .../numix/scalable/actions/zoom-original.svg | 5 - .../icons/numix/scalable/actions/zoom-out.svg | 5 - .../numix/scalable/categories/bluetooth.svg | 65 --- .../scalable/categories/preferences-other.svg | 106 ---- .../categories/preferences-system-old.svg | 10 - .../categories/preferences-system.svg | 114 ----- .../scalable/categories/system-users.svg | 11 - .../scalable/devices/audio-headphones.svg | 5 - .../numix/scalable/devices/audio-headset.svg | 11 - .../numix/scalable/devices/audio-speakers.svg | 20 - .../numix/scalable/devices/camera-photo.svg | 58 --- .../numix/scalable/devices/camera-web.svg | 5 - .../numix/scalable/devices/drive-harddisk.svg | 75 --- .../numix/scalable/devices/drive-optical.svg | 6 - .../devices/drive-removable-media-usb-1.svg | 128 ----- .../devices/drive-removable-media-usb-old.svg | 3 - .../numix/scalable/devices/headphones.svg | 5 - .../numix/scalable/devices/input-gaming.svg | 5 - .../scalable/devices/input-keyboard-1.svg | 69 --- .../scalable/devices/input-keyboard-old.svg | 12 - .../numix/scalable/devices/input-keyboard.svg | 64 --- .../numix/scalable/devices/input-mouse.svg | 5 - .../numix/scalable/devices/media-floppy.svg | 8 - .../scalable/devices/media-memory-sd.svg | 66 --- .../devices/media-optical-cd-audio.svg | 6 - .../numix/scalable/devices/media-optical.svg | 5 - .../multimedia-player-apple-ipod-touch.svg | 60 --- .../scalable/devices/multimedia-player.svg | 6 - .../icons/numix/scalable/devices/phone.svg | 6 - .../icons/numix/scalable/devices/printer.svg | 5 - .../icons/numix/scalable/devices/scanner.svg | 7 - .../numix/scalable/devices/video-display.svg | 65 --- .../numix/scalable/emblems/emblem-default.svg | 3 - .../scalable/emblems/emblem-documents.svg | 5 - .../scalable/emblems/emblem-favorite.svg | 7 - .../scalable/emblems/emblem-important.svg | 3 - .../numix/scalable/emblems/emblem-music.svg | 5 - .../numix/scalable/emblems/emblem-ok.svg | 5 - .../numix/scalable/emblems/emblem-photos.svg | 16 - .../numix/scalable/emblems/emblem-shared.svg | 5 - .../scalable/emblems/emblem-synchronizing.svg | 3 - .../numix/scalable/emblems/emblem-system.svg | 7 - .../numix/scalable/emblems/emblem-videos.svg | 5 - .../scalable/places/folder-documents.svg | 93 ---- .../numix/scalable/places/folder-download.svg | 103 ---- .../numix/scalable/places/folder-images.svg | 89 ---- .../numix/scalable/places/folder-music.svg | 110 ----- .../numix/scalable/places/folder-pictures.svg | 89 ---- .../scalable/places/folder-templates.svg | 5 - .../numix/scalable/places/folder-videos.svg | 68 --- .../icons/numix/scalable/places/folder.svg | 67 --- .../numix/scalable/places/network-server.svg | 103 ---- .../scalable/places/network-workgroup.svg | 67 --- .../numix/scalable/places/user-desktop.svg | 71 --- .../icons/numix/scalable/places/user-home.svg | 72 --- .../numix/scalable/places/user-images.svg | 89 ---- .../numix/scalable/places/user-pictures.svg | 89 ---- .../numix/scalable/places/user-trash-full.svg | 75 --- .../numix/scalable/places/user-trash.svg | 71 --- .../status/audio-input-microphone.svg | 6 - .../scalable/status/audio-volume-high.svg | 20 - .../scalable/status/audio-volume-low.svg | 18 - .../scalable/status/audio-volume-medium.svg | 18 - .../scalable/status/audio-volume-muted.svg | 7 - .../scalable/status/avatar-default-old.svg | 56 --- .../numix/scalable/status/avatar-default.svg | 60 --- .../status/battery-caution-charging.svg | 63 --- .../numix/scalable/status/battery-caution.svg | 61 --- .../status/battery-empty-charging.svg | 56 --- .../numix/scalable/status/battery-empty.svg | 56 --- .../scalable/status/battery-full-charged.svg | 54 --- .../scalable/status/battery-full-charging.svg | 3 - .../numix/scalable/status/battery-full.svg | 3 - .../scalable/status/battery-good-charging.svg | 4 - .../numix/scalable/status/battery-good.svg | 4 - .../scalable/status/battery-low-charging.svg | 4 - .../numix/scalable/status/battery-low.svg | 4 - .../numix/scalable/status/battery-missing.svg | 3 - .../scalable/status/bluetooth-active.svg | 5 - .../scalable/status/bluetooth-disabled.svg | 5 - .../numix/scalable/status/changes-prevent.svg | 4 - .../numix/scalable/status/changes-secure.svg | 4 - .../numix/scalable/status/dialog-error.svg | 3 - .../scalable/status/dialog-information.svg | 3 - .../numix/scalable/status/dialog-password.svg | 3 - .../numix/scalable/status/dialog-question.svg | 3 - .../numix/scalable/status/dialog-warning.svg | 3 - .../scalable/status/display-brightness.svg | 24 - .../scalable/status/indicator-cpufreq-100.svg | 81 ---- .../scalable/status/indicator-cpufreq-25.svg | 78 --- .../scalable/status/indicator-cpufreq-50.svg | 78 --- .../scalable/status/indicator-cpufreq-75.svg | 78 --- .../scalable/status/indicator-cpufreq.svg | 81 ---- .../numix/scalable/status/network-wired.svg | 3 - .../status/network-wireless-acquiring.svg | 10 - .../status/network-wireless-connected.svg | 5 - .../status/network-wireless-encrypted.svg | 5 - .../network-wireless-signal-excellent.svg | 5 - .../status/network-wireless-signal-good.svg | 8 - .../status/network-wireless-signal-none.svg | 8 - .../status/network-wireless-signal-ok.svg | 8 - .../status/network-wireless-signal-weak.svg | 8 - .../numix/scalable/status/user-available.svg | 5 - .../icons/numix/scalable/status/user-away.svg | 5 - .../icons/numix/scalable/status/user-busy.svg | 5 - .../icons/numix/scalable/status/user-idle.svg | 5 - .../numix/scalable/status/user-invisible.svg | 5 - .../numix/scalable/status/user-offline.svg | 5 - .../numix/scalable/status/user-status.svg | 8 - .../scalable/status/weather-clear-night.svg | 4 - .../numix/scalable/status/weather-clear.svg | 23 - .../status/weather-few-clouds-night.svg | 95 ---- .../scalable/status/weather-few-clouds.svg | 95 ---- .../numix/scalable/status/weather-fog.svg | 4 - .../scalable/status/weather-overcast.svg | 95 ---- .../scalable/status/weather-severe-alert.svg | 78 --- .../status/weather-showers-scattered.svg | 84 ---- .../numix/scalable/status/weather-showers.svg | 77 --- .../numix/scalable/status/weather-snow.svg | 77 --- .../numix/scalable/status/weather-storm.svg | 86 ---- .../scalable/status/xn-emblem-system.svg | 8 - .../numix/scalable/status/xn-playlist.svg | 10 - 260 files changed, 7066 deletions(-) delete mode 100644 lisp/ui/styles/icons/numix/index.theme delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/action-unavailable.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/bookmark-add.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/bookmark-new.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/browser-download.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/call-end.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/call-start.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/contact-new.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/content-loading.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/document-export.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/document-import.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/document-new.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/document-open-recent.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/document-open.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/document-page-setup.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/document-properties.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/document-save-as.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/document-save.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/document-send.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/drive-multidisk.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/edit-clear-all.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/edit-clear-rtl.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/edit-clear.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/edit-copy.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/edit-cut.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/edit-delete.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/edit-find.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/edit-paste.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/edit-redo-rtl.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/edit-redo.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/edit-select-all.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/edit-select.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/edit-undo-rtl.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/edit-undo.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/find-location.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/format-indent-less-rtl.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/format-indent-less.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/format-indent-more-rtl.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/format-indent-more.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/format-justify-center.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/format-justify-fill.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/format-justify-left.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/format-justify-right.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/format-text-bold.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/format-text-direction-ltr.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/format-text-direction-rtl.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/format-text-italic.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/format-text-larger.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/format-text-smaller.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/format-text-strikethrough.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/format-text-underline.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/go-bottom.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/go-down.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/go-first-rtl.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/go-first.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/go-home.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/go-jump-rtl.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/go-jump.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/go-last-rtl.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/go-last.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/go-next-rtl.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/go-next.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/go-previous-rtl.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/go-previous.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/go-top.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/go-up.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/help-info.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/image-crop.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/image-red-eye.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/insert-image.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/insert-link.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/insert-object.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/insert-text.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/list-add.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/list-remove-all.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/list-remove.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/mail-forward.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/mail-mark-important.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/mail-reply-sender.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/mail-send-receive.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/mail-send.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/mark-location.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/media-eject.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/media-eq.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/media-playback-pause.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/media-playback-start-rtl.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/media-playback-start.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/media-playback-stop.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/media-record.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/media-seek-backward-rtl.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/media-seek-backward.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/media-seek-forward-rtl.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/media-seek-forward.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/media-skip-backward-rtl.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/media-skip-backward.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/media-skip-forward-rtl.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/media-skip-forward.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/object-flip-horizontal.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/object-flip-vertical.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/object-inverse.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/object-rotate-left.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/object-rotate-right.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/object-select.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/pane-hide.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/pane-show.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/process-stop.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/send-to.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/system-devices.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/system-run.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/system-shutdown.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/tab-new.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-calendar-day.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-calendar-list.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-calendar-month.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-calendar-week.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-calendar-workweek.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-column.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-continuous.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-coverflow.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-dual.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-filter.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-fullscreen.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-grid.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-list-compact.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-list-images.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-list-video.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-list.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-more.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-paged.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-refresh.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-restore.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/view-sidebar.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/window-close.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/window-maximize.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/window-minimize.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/window-restore.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/zoom-fit-best.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/zoom-in.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/zoom-original.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/actions/zoom-out.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/categories/bluetooth.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/categories/preferences-other.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/categories/preferences-system-old.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/categories/preferences-system.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/categories/system-users.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/audio-headphones.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/audio-headset.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/audio-speakers.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/camera-photo.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/camera-web.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/drive-harddisk.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/drive-optical.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/drive-removable-media-usb-1.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/drive-removable-media-usb-old.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/headphones.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/input-gaming.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/input-keyboard-1.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/input-keyboard-old.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/input-keyboard.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/input-mouse.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/media-floppy.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/media-memory-sd.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/media-optical-cd-audio.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/media-optical.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/multimedia-player-apple-ipod-touch.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/multimedia-player.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/phone.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/printer.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/scanner.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/devices/video-display.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/emblems/emblem-default.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/emblems/emblem-documents.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/emblems/emblem-favorite.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/emblems/emblem-important.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/emblems/emblem-music.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/emblems/emblem-ok.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/emblems/emblem-photos.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/emblems/emblem-shared.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/emblems/emblem-synchronizing.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/emblems/emblem-system.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/emblems/emblem-videos.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/places/folder-documents.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/places/folder-download.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/places/folder-images.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/places/folder-music.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/places/folder-pictures.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/places/folder-templates.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/places/folder-videos.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/places/folder.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/places/network-server.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/places/network-workgroup.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/places/user-desktop.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/places/user-home.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/places/user-images.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/places/user-pictures.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/places/user-trash-full.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/places/user-trash.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/audio-input-microphone.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/audio-volume-high.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/audio-volume-low.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/audio-volume-medium.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/audio-volume-muted.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/avatar-default-old.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/avatar-default.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/battery-caution-charging.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/battery-caution.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/battery-empty-charging.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/battery-empty.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/battery-full-charged.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/battery-full-charging.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/battery-full.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/battery-good-charging.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/battery-good.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/battery-low-charging.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/battery-low.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/battery-missing.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/bluetooth-active.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/bluetooth-disabled.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/changes-prevent.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/changes-secure.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/dialog-error.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/dialog-information.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/dialog-password.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/dialog-question.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/dialog-warning.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/display-brightness.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq-100.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq-25.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq-50.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq-75.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/network-wired.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/network-wireless-acquiring.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/network-wireless-connected.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/network-wireless-encrypted.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-excellent.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-good.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-none.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-ok.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-weak.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/user-available.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/user-away.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/user-busy.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/user-idle.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/user-invisible.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/user-offline.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/user-status.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/weather-clear-night.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/weather-clear.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/weather-few-clouds-night.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/weather-few-clouds.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/weather-fog.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/weather-overcast.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/weather-severe-alert.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/weather-showers-scattered.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/weather-showers.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/weather-snow.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/weather-storm.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/xn-emblem-system.svg delete mode 100644 lisp/ui/styles/icons/numix/scalable/status/xn-playlist.svg diff --git a/lisp/ui/styles/icons/numix/index.theme b/lisp/ui/styles/icons/numix/index.theme deleted file mode 100644 index fb3f89735..000000000 --- a/lisp/ui/styles/icons/numix/index.theme +++ /dev/null @@ -1,61 +0,0 @@ -[Icon Theme] -Name=Numix -Comment=Icon theme from Numix Project -Inherits=lisp,hicolor -Directories=scalable/actions,scalable/apps,scalable/categories,scalable/devices,scalable/emblems,scalable/mimetypes,scalable/places,scalable/status - -[scalable/actions] -Size=16 -MinSize=16 -MaxSize=256 -Context=Actions -Type=Scalable - -[scalable/apps] -Size=16 -MinSize=16 -MaxSize=256 -Context=Applications -Type=Scalable - -[scalable/categories] -Size=16 -MinSize=16 -MaxSize=256 -Context=Categories -Type=Scalable - -[scalable/devices] -Size=16 -MinSize=16 -MaxSize=256 -Context=Devices -Type=Scalable - -[scalable/emblems] -Size=16 -MinSize=16 -MaxSize=256 -Context=Emblems -Type=Scalable - -[scalable/mimetypes] -Size=16 -MinSize=16 -MaxSize=256 -Context=MimeTypes -Type=Scalable - -[scalable/places] -Size=16 -MinSize=16 -MaxSize=256 -Context=Places -Type=Scalable - -[scalable/status] -Size=16 -MinSize=16 -MaxSize=256 -Context=Status -Type=Scalable diff --git a/lisp/ui/styles/icons/numix/scalable/actions/action-unavailable.svg b/lisp/ui/styles/icons/numix/scalable/actions/action-unavailable.svg deleted file mode 100644 index a6d358bce..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/action-unavailable.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/bookmark-add.svg b/lisp/ui/styles/icons/numix/scalable/actions/bookmark-add.svg deleted file mode 100644 index a17fd3936..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/bookmark-add.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/bookmark-new.svg b/lisp/ui/styles/icons/numix/scalable/actions/bookmark-new.svg deleted file mode 100644 index 537faff7b..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/bookmark-new.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/browser-download.svg b/lisp/ui/styles/icons/numix/scalable/actions/browser-download.svg deleted file mode 100644 index 1565c8471..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/browser-download.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/call-end.svg b/lisp/ui/styles/icons/numix/scalable/actions/call-end.svg deleted file mode 100644 index d1f205136..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/call-end.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/call-start.svg b/lisp/ui/styles/icons/numix/scalable/actions/call-start.svg deleted file mode 100644 index 48f5207db..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/call-start.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/contact-new.svg b/lisp/ui/styles/icons/numix/scalable/actions/contact-new.svg deleted file mode 100644 index 4fce2eff4..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/contact-new.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/content-loading.svg b/lisp/ui/styles/icons/numix/scalable/actions/content-loading.svg deleted file mode 100644 index bd0fcae00..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/content-loading.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/document-export.svg b/lisp/ui/styles/icons/numix/scalable/actions/document-export.svg deleted file mode 100644 index 778c00f6c..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/document-export.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/document-import.svg b/lisp/ui/styles/icons/numix/scalable/actions/document-import.svg deleted file mode 100644 index 800afb82e..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/document-import.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/document-new.svg b/lisp/ui/styles/icons/numix/scalable/actions/document-new.svg deleted file mode 100644 index 08b1e16f7..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/document-new.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/document-open-recent.svg b/lisp/ui/styles/icons/numix/scalable/actions/document-open-recent.svg deleted file mode 100644 index 5acbe4ee1..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/document-open-recent.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/document-open.svg b/lisp/ui/styles/icons/numix/scalable/actions/document-open.svg deleted file mode 100644 index b01e2509e..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/document-open.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/document-page-setup.svg b/lisp/ui/styles/icons/numix/scalable/actions/document-page-setup.svg deleted file mode 100644 index a5cd6541b..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/document-page-setup.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/document-properties.svg b/lisp/ui/styles/icons/numix/scalable/actions/document-properties.svg deleted file mode 100644 index 290fc5275..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/document-properties.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/document-save-as.svg b/lisp/ui/styles/icons/numix/scalable/actions/document-save-as.svg deleted file mode 100644 index 19f3d0278..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/document-save-as.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/document-save.svg b/lisp/ui/styles/icons/numix/scalable/actions/document-save.svg deleted file mode 100644 index b61c17a48..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/document-save.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/document-send.svg b/lisp/ui/styles/icons/numix/scalable/actions/document-send.svg deleted file mode 100644 index fae307dd4..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/document-send.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/drive-multidisk.svg b/lisp/ui/styles/icons/numix/scalable/actions/drive-multidisk.svg deleted file mode 100644 index 5a4507f8c..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/drive-multidisk.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/edit-clear-all.svg b/lisp/ui/styles/icons/numix/scalable/actions/edit-clear-all.svg deleted file mode 100644 index 9ab16312a..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/edit-clear-all.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/edit-clear-rtl.svg b/lisp/ui/styles/icons/numix/scalable/actions/edit-clear-rtl.svg deleted file mode 100644 index b461ae870..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/edit-clear-rtl.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/edit-clear.svg b/lisp/ui/styles/icons/numix/scalable/actions/edit-clear.svg deleted file mode 100644 index a06f2ce72..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/edit-clear.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/edit-copy.svg b/lisp/ui/styles/icons/numix/scalable/actions/edit-copy.svg deleted file mode 100644 index 6c5a046e2..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/edit-copy.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/edit-cut.svg b/lisp/ui/styles/icons/numix/scalable/actions/edit-cut.svg deleted file mode 100644 index f4219f99b..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/edit-cut.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/edit-delete.svg b/lisp/ui/styles/icons/numix/scalable/actions/edit-delete.svg deleted file mode 100644 index 37b43d22b..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/edit-delete.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/edit-find.svg b/lisp/ui/styles/icons/numix/scalable/actions/edit-find.svg deleted file mode 100644 index 9e45e8a46..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/edit-find.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/edit-paste.svg b/lisp/ui/styles/icons/numix/scalable/actions/edit-paste.svg deleted file mode 100644 index 4b5f68336..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/edit-paste.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/edit-redo-rtl.svg b/lisp/ui/styles/icons/numix/scalable/actions/edit-redo-rtl.svg deleted file mode 100644 index 99310db5b..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/edit-redo-rtl.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/edit-redo.svg b/lisp/ui/styles/icons/numix/scalable/actions/edit-redo.svg deleted file mode 100644 index 5de630ec7..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/edit-redo.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/edit-select-all.svg b/lisp/ui/styles/icons/numix/scalable/actions/edit-select-all.svg deleted file mode 100644 index f57d6ff1c..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/edit-select-all.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/edit-select.svg b/lisp/ui/styles/icons/numix/scalable/actions/edit-select.svg deleted file mode 100644 index 241281db1..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/edit-select.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/edit-undo-rtl.svg b/lisp/ui/styles/icons/numix/scalable/actions/edit-undo-rtl.svg deleted file mode 100644 index 62c3e6546..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/edit-undo-rtl.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/edit-undo.svg b/lisp/ui/styles/icons/numix/scalable/actions/edit-undo.svg deleted file mode 100644 index cde1c1019..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/edit-undo.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/find-location.svg b/lisp/ui/styles/icons/numix/scalable/actions/find-location.svg deleted file mode 100644 index 6faf1686a..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/find-location.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/format-indent-less-rtl.svg b/lisp/ui/styles/icons/numix/scalable/actions/format-indent-less-rtl.svg deleted file mode 100644 index 1f7d6f935..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/format-indent-less-rtl.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/format-indent-less.svg b/lisp/ui/styles/icons/numix/scalable/actions/format-indent-less.svg deleted file mode 100644 index 1689696ef..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/format-indent-less.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/format-indent-more-rtl.svg b/lisp/ui/styles/icons/numix/scalable/actions/format-indent-more-rtl.svg deleted file mode 100644 index 04983f87d..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/format-indent-more-rtl.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/format-indent-more.svg b/lisp/ui/styles/icons/numix/scalable/actions/format-indent-more.svg deleted file mode 100644 index 247cbbc7a..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/format-indent-more.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/format-justify-center.svg b/lisp/ui/styles/icons/numix/scalable/actions/format-justify-center.svg deleted file mode 100644 index a181af9ef..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/format-justify-center.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/format-justify-fill.svg b/lisp/ui/styles/icons/numix/scalable/actions/format-justify-fill.svg deleted file mode 100644 index 757cb2685..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/format-justify-fill.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/format-justify-left.svg b/lisp/ui/styles/icons/numix/scalable/actions/format-justify-left.svg deleted file mode 100644 index b803fb3de..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/format-justify-left.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/format-justify-right.svg b/lisp/ui/styles/icons/numix/scalable/actions/format-justify-right.svg deleted file mode 100644 index 1b56e8c56..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/format-justify-right.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/format-text-bold.svg b/lisp/ui/styles/icons/numix/scalable/actions/format-text-bold.svg deleted file mode 100644 index 8cca1ef08..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/format-text-bold.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/format-text-direction-ltr.svg b/lisp/ui/styles/icons/numix/scalable/actions/format-text-direction-ltr.svg deleted file mode 100644 index dc9ccb9bf..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/format-text-direction-ltr.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/format-text-direction-rtl.svg b/lisp/ui/styles/icons/numix/scalable/actions/format-text-direction-rtl.svg deleted file mode 100644 index df59c5eca..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/format-text-direction-rtl.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/format-text-italic.svg b/lisp/ui/styles/icons/numix/scalable/actions/format-text-italic.svg deleted file mode 100644 index f4b49bfe2..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/format-text-italic.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/format-text-larger.svg b/lisp/ui/styles/icons/numix/scalable/actions/format-text-larger.svg deleted file mode 100644 index 490217218..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/format-text-larger.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/format-text-smaller.svg b/lisp/ui/styles/icons/numix/scalable/actions/format-text-smaller.svg deleted file mode 100644 index 76096e2d2..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/format-text-smaller.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/format-text-strikethrough.svg b/lisp/ui/styles/icons/numix/scalable/actions/format-text-strikethrough.svg deleted file mode 100644 index 8c034a819..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/format-text-strikethrough.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/format-text-underline.svg b/lisp/ui/styles/icons/numix/scalable/actions/format-text-underline.svg deleted file mode 100644 index 0e88faeac..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/format-text-underline.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/go-bottom.svg b/lisp/ui/styles/icons/numix/scalable/actions/go-bottom.svg deleted file mode 100644 index 3580f9383..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/go-bottom.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/go-down.svg b/lisp/ui/styles/icons/numix/scalable/actions/go-down.svg deleted file mode 100644 index c1bbcc31d..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/go-down.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/go-first-rtl.svg b/lisp/ui/styles/icons/numix/scalable/actions/go-first-rtl.svg deleted file mode 100644 index 7563c2f09..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/go-first-rtl.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/go-first.svg b/lisp/ui/styles/icons/numix/scalable/actions/go-first.svg deleted file mode 100644 index b7851e11c..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/go-first.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/go-home.svg b/lisp/ui/styles/icons/numix/scalable/actions/go-home.svg deleted file mode 100644 index 47b0614d2..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/go-home.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/go-jump-rtl.svg b/lisp/ui/styles/icons/numix/scalable/actions/go-jump-rtl.svg deleted file mode 100644 index a1b234e42..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/go-jump-rtl.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/go-jump.svg b/lisp/ui/styles/icons/numix/scalable/actions/go-jump.svg deleted file mode 100644 index c665762ec..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/go-jump.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/go-last-rtl.svg b/lisp/ui/styles/icons/numix/scalable/actions/go-last-rtl.svg deleted file mode 100644 index cf0460977..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/go-last-rtl.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/go-last.svg b/lisp/ui/styles/icons/numix/scalable/actions/go-last.svg deleted file mode 100644 index 68478bbed..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/go-last.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/go-next-rtl.svg b/lisp/ui/styles/icons/numix/scalable/actions/go-next-rtl.svg deleted file mode 100644 index d6de532b5..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/go-next-rtl.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/go-next.svg b/lisp/ui/styles/icons/numix/scalable/actions/go-next.svg deleted file mode 100644 index 8249306bc..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/go-next.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/go-previous-rtl.svg b/lisp/ui/styles/icons/numix/scalable/actions/go-previous-rtl.svg deleted file mode 100644 index 895537cac..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/go-previous-rtl.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/go-previous.svg b/lisp/ui/styles/icons/numix/scalable/actions/go-previous.svg deleted file mode 100644 index 031f63e2f..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/go-previous.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/go-top.svg b/lisp/ui/styles/icons/numix/scalable/actions/go-top.svg deleted file mode 100644 index f5ec3b786..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/go-top.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/go-up.svg b/lisp/ui/styles/icons/numix/scalable/actions/go-up.svg deleted file mode 100644 index 64a3dc60c..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/go-up.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/help-info.svg b/lisp/ui/styles/icons/numix/scalable/actions/help-info.svg deleted file mode 100644 index 0889383cd..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/help-info.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/image-crop.svg b/lisp/ui/styles/icons/numix/scalable/actions/image-crop.svg deleted file mode 100644 index dbc105e36..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/image-crop.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/image-red-eye.svg b/lisp/ui/styles/icons/numix/scalable/actions/image-red-eye.svg deleted file mode 100644 index 86289095c..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/image-red-eye.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/insert-image.svg b/lisp/ui/styles/icons/numix/scalable/actions/insert-image.svg deleted file mode 100644 index dbba81178..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/insert-image.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/insert-link.svg b/lisp/ui/styles/icons/numix/scalable/actions/insert-link.svg deleted file mode 100644 index 6e26972a0..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/insert-link.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/insert-object.svg b/lisp/ui/styles/icons/numix/scalable/actions/insert-object.svg deleted file mode 100644 index 0a75f6a18..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/insert-object.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/insert-text.svg b/lisp/ui/styles/icons/numix/scalable/actions/insert-text.svg deleted file mode 100644 index f37187126..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/insert-text.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/list-add.svg b/lisp/ui/styles/icons/numix/scalable/actions/list-add.svg deleted file mode 100644 index 6b6a09c8d..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/list-add.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/list-remove-all.svg b/lisp/ui/styles/icons/numix/scalable/actions/list-remove-all.svg deleted file mode 100644 index f168230dd..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/list-remove-all.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/list-remove.svg b/lisp/ui/styles/icons/numix/scalable/actions/list-remove.svg deleted file mode 100644 index 0e809822b..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/list-remove.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/mail-forward.svg b/lisp/ui/styles/icons/numix/scalable/actions/mail-forward.svg deleted file mode 100644 index 4d7698e10..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/mail-forward.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/mail-mark-important.svg b/lisp/ui/styles/icons/numix/scalable/actions/mail-mark-important.svg deleted file mode 100644 index 6f3668ad1..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/mail-mark-important.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/mail-reply-sender.svg b/lisp/ui/styles/icons/numix/scalable/actions/mail-reply-sender.svg deleted file mode 100644 index ecc8ef29d..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/mail-reply-sender.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/mail-send-receive.svg b/lisp/ui/styles/icons/numix/scalable/actions/mail-send-receive.svg deleted file mode 100644 index eeece102e..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/mail-send-receive.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/mail-send.svg b/lisp/ui/styles/icons/numix/scalable/actions/mail-send.svg deleted file mode 100644 index 39f17a42d..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/mail-send.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/mark-location.svg b/lisp/ui/styles/icons/numix/scalable/actions/mark-location.svg deleted file mode 100644 index 77368eb99..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/mark-location.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/media-eject.svg b/lisp/ui/styles/icons/numix/scalable/actions/media-eject.svg deleted file mode 100644 index b8a4bb04c..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/media-eject.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/media-eq.svg b/lisp/ui/styles/icons/numix/scalable/actions/media-eq.svg deleted file mode 100644 index 72628c95b..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/media-eq.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/media-playback-pause.svg b/lisp/ui/styles/icons/numix/scalable/actions/media-playback-pause.svg deleted file mode 100644 index 2a560ee6b..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/media-playback-pause.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/media-playback-start-rtl.svg b/lisp/ui/styles/icons/numix/scalable/actions/media-playback-start-rtl.svg deleted file mode 100644 index 82121cf47..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/media-playback-start-rtl.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/media-playback-start.svg b/lisp/ui/styles/icons/numix/scalable/actions/media-playback-start.svg deleted file mode 100644 index 0057499a2..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/media-playback-start.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/media-playback-stop.svg b/lisp/ui/styles/icons/numix/scalable/actions/media-playback-stop.svg deleted file mode 100644 index cdaaaa2cb..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/media-playback-stop.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/media-record.svg b/lisp/ui/styles/icons/numix/scalable/actions/media-record.svg deleted file mode 100644 index 5e112d1c1..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/media-record.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/media-seek-backward-rtl.svg b/lisp/ui/styles/icons/numix/scalable/actions/media-seek-backward-rtl.svg deleted file mode 100644 index 54bbc015f..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/media-seek-backward-rtl.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/media-seek-backward.svg b/lisp/ui/styles/icons/numix/scalable/actions/media-seek-backward.svg deleted file mode 100644 index 25d225a56..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/media-seek-backward.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/media-seek-forward-rtl.svg b/lisp/ui/styles/icons/numix/scalable/actions/media-seek-forward-rtl.svg deleted file mode 100644 index 25d225a56..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/media-seek-forward-rtl.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/media-seek-forward.svg b/lisp/ui/styles/icons/numix/scalable/actions/media-seek-forward.svg deleted file mode 100644 index 54bbc015f..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/media-seek-forward.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/media-skip-backward-rtl.svg b/lisp/ui/styles/icons/numix/scalable/actions/media-skip-backward-rtl.svg deleted file mode 100644 index ce4f91ef7..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/media-skip-backward-rtl.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/media-skip-backward.svg b/lisp/ui/styles/icons/numix/scalable/actions/media-skip-backward.svg deleted file mode 100644 index da2741761..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/media-skip-backward.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/media-skip-forward-rtl.svg b/lisp/ui/styles/icons/numix/scalable/actions/media-skip-forward-rtl.svg deleted file mode 100644 index 8da9454f2..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/media-skip-forward-rtl.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/media-skip-forward.svg b/lisp/ui/styles/icons/numix/scalable/actions/media-skip-forward.svg deleted file mode 100644 index 47f0a56b9..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/media-skip-forward.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/object-flip-horizontal.svg b/lisp/ui/styles/icons/numix/scalable/actions/object-flip-horizontal.svg deleted file mode 100644 index 9a30e68ca..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/object-flip-horizontal.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/object-flip-vertical.svg b/lisp/ui/styles/icons/numix/scalable/actions/object-flip-vertical.svg deleted file mode 100644 index 2391985c0..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/object-flip-vertical.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/object-inverse.svg b/lisp/ui/styles/icons/numix/scalable/actions/object-inverse.svg deleted file mode 100644 index c6e08c122..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/object-inverse.svg +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/object-rotate-left.svg b/lisp/ui/styles/icons/numix/scalable/actions/object-rotate-left.svg deleted file mode 100644 index 2b08d55bc..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/object-rotate-left.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/object-rotate-right.svg b/lisp/ui/styles/icons/numix/scalable/actions/object-rotate-right.svg deleted file mode 100644 index e007702db..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/object-rotate-right.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/object-select.svg b/lisp/ui/styles/icons/numix/scalable/actions/object-select.svg deleted file mode 100644 index ac405a917..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/object-select.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/pane-hide.svg b/lisp/ui/styles/icons/numix/scalable/actions/pane-hide.svg deleted file mode 100644 index f817c3034..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/pane-hide.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/pane-show.svg b/lisp/ui/styles/icons/numix/scalable/actions/pane-show.svg deleted file mode 100644 index 1b2c02c3a..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/pane-show.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/process-stop.svg b/lisp/ui/styles/icons/numix/scalable/actions/process-stop.svg deleted file mode 100644 index ee18507c5..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/process-stop.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/send-to.svg b/lisp/ui/styles/icons/numix/scalable/actions/send-to.svg deleted file mode 100644 index e18d69efb..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/send-to.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/system-devices.svg b/lisp/ui/styles/icons/numix/scalable/actions/system-devices.svg deleted file mode 100644 index 1b9fad5d1..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/system-devices.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/system-run.svg b/lisp/ui/styles/icons/numix/scalable/actions/system-run.svg deleted file mode 100644 index 6a144be56..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/system-run.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/system-shutdown.svg b/lisp/ui/styles/icons/numix/scalable/actions/system-shutdown.svg deleted file mode 100644 index b84249003..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/system-shutdown.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/tab-new.svg b/lisp/ui/styles/icons/numix/scalable/actions/tab-new.svg deleted file mode 100644 index 865d99b59..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/tab-new.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-calendar-day.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-calendar-day.svg deleted file mode 100644 index 5c7f0aeeb..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-calendar-day.svg +++ /dev/null @@ -1,327 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-calendar-list.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-calendar-list.svg deleted file mode 100644 index 40e999686..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-calendar-list.svg +++ /dev/null @@ -1,317 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-calendar-month.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-calendar-month.svg deleted file mode 100644 index 2f1c113e3..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-calendar-month.svg +++ /dev/null @@ -1,352 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-calendar-week.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-calendar-week.svg deleted file mode 100644 index 3dce8ab52..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-calendar-week.svg +++ /dev/null @@ -1,448 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-calendar-workweek.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-calendar-workweek.svg deleted file mode 100644 index 0b5e4951e..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-calendar-workweek.svg +++ /dev/null @@ -1,455 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-column.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-column.svg deleted file mode 100644 index 8d0cbc1ab..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-column.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-continuous.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-continuous.svg deleted file mode 100644 index 75541dc32..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-continuous.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-coverflow.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-coverflow.svg deleted file mode 100644 index 469005593..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-coverflow.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-dual.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-dual.svg deleted file mode 100644 index b709061a8..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-dual.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-filter.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-filter.svg deleted file mode 100644 index 4d43c640d..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-filter.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-fullscreen.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-fullscreen.svg deleted file mode 100644 index 45a1e0326..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-fullscreen.svg +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-grid.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-grid.svg deleted file mode 100644 index 8fd37b876..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-grid.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-list-compact.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-list-compact.svg deleted file mode 100644 index 4822529c3..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-list-compact.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-list-images.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-list-images.svg deleted file mode 100644 index ad38733bd..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-list-images.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-list-video.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-list-video.svg deleted file mode 100644 index 75dc6721a..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-list-video.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-list.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-list.svg deleted file mode 100644 index 62bad65de..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-list.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-more.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-more.svg deleted file mode 100644 index 66716a09a..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-more.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-paged.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-paged.svg deleted file mode 100644 index 05afdc530..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-paged.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-refresh.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-refresh.svg deleted file mode 100644 index 8de167dd7..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-refresh.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-restore.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-restore.svg deleted file mode 100644 index 299910d4c..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-restore.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/view-sidebar.svg b/lisp/ui/styles/icons/numix/scalable/actions/view-sidebar.svg deleted file mode 100644 index 8a8b76288..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/view-sidebar.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/window-close.svg b/lisp/ui/styles/icons/numix/scalable/actions/window-close.svg deleted file mode 100644 index 25e92b66c..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/window-close.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/window-maximize.svg b/lisp/ui/styles/icons/numix/scalable/actions/window-maximize.svg deleted file mode 100644 index 92702a299..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/window-maximize.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/window-minimize.svg b/lisp/ui/styles/icons/numix/scalable/actions/window-minimize.svg deleted file mode 100644 index 48fce8a76..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/window-minimize.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/window-restore.svg b/lisp/ui/styles/icons/numix/scalable/actions/window-restore.svg deleted file mode 100644 index 20a7f1ff2..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/window-restore.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/zoom-fit-best.svg b/lisp/ui/styles/icons/numix/scalable/actions/zoom-fit-best.svg deleted file mode 100644 index e8cc7e8d2..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/zoom-fit-best.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/zoom-in.svg b/lisp/ui/styles/icons/numix/scalable/actions/zoom-in.svg deleted file mode 100644 index dd80260f2..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/zoom-in.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/zoom-original.svg b/lisp/ui/styles/icons/numix/scalable/actions/zoom-original.svg deleted file mode 100644 index c5dbe4e80..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/zoom-original.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/actions/zoom-out.svg b/lisp/ui/styles/icons/numix/scalable/actions/zoom-out.svg deleted file mode 100644 index 0a90544c9..000000000 --- a/lisp/ui/styles/icons/numix/scalable/actions/zoom-out.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/categories/bluetooth.svg b/lisp/ui/styles/icons/numix/scalable/categories/bluetooth.svg deleted file mode 100644 index eb94a5a8d..000000000 --- a/lisp/ui/styles/icons/numix/scalable/categories/bluetooth.svg +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/categories/preferences-other.svg b/lisp/ui/styles/icons/numix/scalable/categories/preferences-other.svg deleted file mode 100644 index 096534be2..000000000 --- a/lisp/ui/styles/icons/numix/scalable/categories/preferences-other.svg +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/categories/preferences-system-old.svg b/lisp/ui/styles/icons/numix/scalable/categories/preferences-system-old.svg deleted file mode 100644 index 14e61b1fb..000000000 --- a/lisp/ui/styles/icons/numix/scalable/categories/preferences-system-old.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/categories/preferences-system.svg b/lisp/ui/styles/icons/numix/scalable/categories/preferences-system.svg deleted file mode 100644 index 55b4404e1..000000000 --- a/lisp/ui/styles/icons/numix/scalable/categories/preferences-system.svg +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/categories/system-users.svg b/lisp/ui/styles/icons/numix/scalable/categories/system-users.svg deleted file mode 100644 index 8b01a40e3..000000000 --- a/lisp/ui/styles/icons/numix/scalable/categories/system-users.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/audio-headphones.svg b/lisp/ui/styles/icons/numix/scalable/devices/audio-headphones.svg deleted file mode 100644 index 019f3e7fc..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/audio-headphones.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/audio-headset.svg b/lisp/ui/styles/icons/numix/scalable/devices/audio-headset.svg deleted file mode 100644 index ab7075b68..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/audio-headset.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/audio-speakers.svg b/lisp/ui/styles/icons/numix/scalable/devices/audio-speakers.svg deleted file mode 100644 index b539bdb7c..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/audio-speakers.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/camera-photo.svg b/lisp/ui/styles/icons/numix/scalable/devices/camera-photo.svg deleted file mode 100644 index 02a638b6d..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/camera-photo.svg +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/camera-web.svg b/lisp/ui/styles/icons/numix/scalable/devices/camera-web.svg deleted file mode 100644 index 72ae2f65b..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/camera-web.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/drive-harddisk.svg b/lisp/ui/styles/icons/numix/scalable/devices/drive-harddisk.svg deleted file mode 100644 index e6a312527..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/drive-harddisk.svg +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/drive-optical.svg b/lisp/ui/styles/icons/numix/scalable/devices/drive-optical.svg deleted file mode 100644 index 3f4c84e4c..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/drive-optical.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/drive-removable-media-usb-1.svg b/lisp/ui/styles/icons/numix/scalable/devices/drive-removable-media-usb-1.svg deleted file mode 100644 index 370de490e..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/drive-removable-media-usb-1.svg +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/drive-removable-media-usb-old.svg b/lisp/ui/styles/icons/numix/scalable/devices/drive-removable-media-usb-old.svg deleted file mode 100644 index 61d52c8a5..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/drive-removable-media-usb-old.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/headphones.svg b/lisp/ui/styles/icons/numix/scalable/devices/headphones.svg deleted file mode 100644 index 019f3e7fc..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/headphones.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/input-gaming.svg b/lisp/ui/styles/icons/numix/scalable/devices/input-gaming.svg deleted file mode 100644 index 29589da67..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/input-gaming.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/input-keyboard-1.svg b/lisp/ui/styles/icons/numix/scalable/devices/input-keyboard-1.svg deleted file mode 100644 index 5932b29d6..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/input-keyboard-1.svg +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/input-keyboard-old.svg b/lisp/ui/styles/icons/numix/scalable/devices/input-keyboard-old.svg deleted file mode 100644 index 3859be8c8..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/input-keyboard-old.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/input-keyboard.svg b/lisp/ui/styles/icons/numix/scalable/devices/input-keyboard.svg deleted file mode 100644 index 9671e3fba..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/input-keyboard.svg +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/input-mouse.svg b/lisp/ui/styles/icons/numix/scalable/devices/input-mouse.svg deleted file mode 100644 index c13daa4a5..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/input-mouse.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/media-floppy.svg b/lisp/ui/styles/icons/numix/scalable/devices/media-floppy.svg deleted file mode 100644 index 7cdcddd94..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/media-floppy.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/media-memory-sd.svg b/lisp/ui/styles/icons/numix/scalable/devices/media-memory-sd.svg deleted file mode 100644 index 4c63b40d5..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/media-memory-sd.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/media-optical-cd-audio.svg b/lisp/ui/styles/icons/numix/scalable/devices/media-optical-cd-audio.svg deleted file mode 100644 index b87892240..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/media-optical-cd-audio.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/media-optical.svg b/lisp/ui/styles/icons/numix/scalable/devices/media-optical.svg deleted file mode 100644 index 1940ead75..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/media-optical.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/multimedia-player-apple-ipod-touch.svg b/lisp/ui/styles/icons/numix/scalable/devices/multimedia-player-apple-ipod-touch.svg deleted file mode 100644 index 3dfba4df3..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/multimedia-player-apple-ipod-touch.svg +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/multimedia-player.svg b/lisp/ui/styles/icons/numix/scalable/devices/multimedia-player.svg deleted file mode 100644 index 8d7e3ba02..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/multimedia-player.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/phone.svg b/lisp/ui/styles/icons/numix/scalable/devices/phone.svg deleted file mode 100644 index e1269b5fa..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/phone.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/printer.svg b/lisp/ui/styles/icons/numix/scalable/devices/printer.svg deleted file mode 100644 index 48c7c6360..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/printer.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/scanner.svg b/lisp/ui/styles/icons/numix/scalable/devices/scanner.svg deleted file mode 100644 index cc1bd7305..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/scanner.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/devices/video-display.svg b/lisp/ui/styles/icons/numix/scalable/devices/video-display.svg deleted file mode 100644 index 0327dec6e..000000000 --- a/lisp/ui/styles/icons/numix/scalable/devices/video-display.svg +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-default.svg b/lisp/ui/styles/icons/numix/scalable/emblems/emblem-default.svg deleted file mode 100644 index 4733cd8c4..000000000 --- a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-default.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-documents.svg b/lisp/ui/styles/icons/numix/scalable/emblems/emblem-documents.svg deleted file mode 100644 index 7bb5efcc4..000000000 --- a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-documents.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-favorite.svg b/lisp/ui/styles/icons/numix/scalable/emblems/emblem-favorite.svg deleted file mode 100644 index f715554a1..000000000 --- a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-favorite.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-important.svg b/lisp/ui/styles/icons/numix/scalable/emblems/emblem-important.svg deleted file mode 100644 index 362ebd497..000000000 --- a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-important.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-music.svg b/lisp/ui/styles/icons/numix/scalable/emblems/emblem-music.svg deleted file mode 100644 index 3441f2838..000000000 --- a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-music.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-ok.svg b/lisp/ui/styles/icons/numix/scalable/emblems/emblem-ok.svg deleted file mode 100644 index c49c46e6a..000000000 --- a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-ok.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-photos.svg b/lisp/ui/styles/icons/numix/scalable/emblems/emblem-photos.svg deleted file mode 100644 index af5cdd85a..000000000 --- a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-photos.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-shared.svg b/lisp/ui/styles/icons/numix/scalable/emblems/emblem-shared.svg deleted file mode 100644 index 62fb71f08..000000000 --- a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-shared.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-synchronizing.svg b/lisp/ui/styles/icons/numix/scalable/emblems/emblem-synchronizing.svg deleted file mode 100644 index 0831be81b..000000000 --- a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-synchronizing.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-system.svg b/lisp/ui/styles/icons/numix/scalable/emblems/emblem-system.svg deleted file mode 100644 index 1b56f04b8..000000000 --- a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-system.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-videos.svg b/lisp/ui/styles/icons/numix/scalable/emblems/emblem-videos.svg deleted file mode 100644 index 0215a3772..000000000 --- a/lisp/ui/styles/icons/numix/scalable/emblems/emblem-videos.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/places/folder-documents.svg b/lisp/ui/styles/icons/numix/scalable/places/folder-documents.svg deleted file mode 100644 index 70451dda8..000000000 --- a/lisp/ui/styles/icons/numix/scalable/places/folder-documents.svg +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/places/folder-download.svg b/lisp/ui/styles/icons/numix/scalable/places/folder-download.svg deleted file mode 100644 index 388716693..000000000 --- a/lisp/ui/styles/icons/numix/scalable/places/folder-download.svg +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/places/folder-images.svg b/lisp/ui/styles/icons/numix/scalable/places/folder-images.svg deleted file mode 100644 index ae89ae2a1..000000000 --- a/lisp/ui/styles/icons/numix/scalable/places/folder-images.svg +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/places/folder-music.svg b/lisp/ui/styles/icons/numix/scalable/places/folder-music.svg deleted file mode 100644 index b63d12f28..000000000 --- a/lisp/ui/styles/icons/numix/scalable/places/folder-music.svg +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/places/folder-pictures.svg b/lisp/ui/styles/icons/numix/scalable/places/folder-pictures.svg deleted file mode 100644 index ae89ae2a1..000000000 --- a/lisp/ui/styles/icons/numix/scalable/places/folder-pictures.svg +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/places/folder-templates.svg b/lisp/ui/styles/icons/numix/scalable/places/folder-templates.svg deleted file mode 100644 index cb7f1fcf9..000000000 --- a/lisp/ui/styles/icons/numix/scalable/places/folder-templates.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/places/folder-videos.svg b/lisp/ui/styles/icons/numix/scalable/places/folder-videos.svg deleted file mode 100644 index b08e88851..000000000 --- a/lisp/ui/styles/icons/numix/scalable/places/folder-videos.svg +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/places/folder.svg b/lisp/ui/styles/icons/numix/scalable/places/folder.svg deleted file mode 100644 index e0bc0bc2d..000000000 --- a/lisp/ui/styles/icons/numix/scalable/places/folder.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/places/network-server.svg b/lisp/ui/styles/icons/numix/scalable/places/network-server.svg deleted file mode 100644 index 0230094af..000000000 --- a/lisp/ui/styles/icons/numix/scalable/places/network-server.svg +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - Gnome Symbolic Icon Theme - - - - Gnome Symbolic Icon Theme - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/places/network-workgroup.svg b/lisp/ui/styles/icons/numix/scalable/places/network-workgroup.svg deleted file mode 100644 index 837170dd0..000000000 --- a/lisp/ui/styles/icons/numix/scalable/places/network-workgroup.svg +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/places/user-desktop.svg b/lisp/ui/styles/icons/numix/scalable/places/user-desktop.svg deleted file mode 100644 index 70d2581a3..000000000 --- a/lisp/ui/styles/icons/numix/scalable/places/user-desktop.svg +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/places/user-home.svg b/lisp/ui/styles/icons/numix/scalable/places/user-home.svg deleted file mode 100644 index 9e66324d3..000000000 --- a/lisp/ui/styles/icons/numix/scalable/places/user-home.svg +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/places/user-images.svg b/lisp/ui/styles/icons/numix/scalable/places/user-images.svg deleted file mode 100644 index ae89ae2a1..000000000 --- a/lisp/ui/styles/icons/numix/scalable/places/user-images.svg +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/places/user-pictures.svg b/lisp/ui/styles/icons/numix/scalable/places/user-pictures.svg deleted file mode 100644 index ae89ae2a1..000000000 --- a/lisp/ui/styles/icons/numix/scalable/places/user-pictures.svg +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/places/user-trash-full.svg b/lisp/ui/styles/icons/numix/scalable/places/user-trash-full.svg deleted file mode 100644 index 7815e1327..000000000 --- a/lisp/ui/styles/icons/numix/scalable/places/user-trash-full.svg +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/places/user-trash.svg b/lisp/ui/styles/icons/numix/scalable/places/user-trash.svg deleted file mode 100644 index 48ed1aaef..000000000 --- a/lisp/ui/styles/icons/numix/scalable/places/user-trash.svg +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/audio-input-microphone.svg b/lisp/ui/styles/icons/numix/scalable/status/audio-input-microphone.svg deleted file mode 100644 index ebd7db4b1..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/audio-input-microphone.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/audio-volume-high.svg b/lisp/ui/styles/icons/numix/scalable/status/audio-volume-high.svg deleted file mode 100644 index b539bdb7c..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/audio-volume-high.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/audio-volume-low.svg b/lisp/ui/styles/icons/numix/scalable/status/audio-volume-low.svg deleted file mode 100644 index 6906fd945..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/audio-volume-low.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/audio-volume-medium.svg b/lisp/ui/styles/icons/numix/scalable/status/audio-volume-medium.svg deleted file mode 100644 index d6b7e67db..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/audio-volume-medium.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/audio-volume-muted.svg b/lisp/ui/styles/icons/numix/scalable/status/audio-volume-muted.svg deleted file mode 100644 index 9e6f8e0fb..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/audio-volume-muted.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/avatar-default-old.svg b/lisp/ui/styles/icons/numix/scalable/status/avatar-default-old.svg deleted file mode 100644 index a5b6c8a31..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/avatar-default-old.svg +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/avatar-default.svg b/lisp/ui/styles/icons/numix/scalable/status/avatar-default.svg deleted file mode 100644 index 5de997a5c..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/avatar-default.svg +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/battery-caution-charging.svg b/lisp/ui/styles/icons/numix/scalable/status/battery-caution-charging.svg deleted file mode 100644 index 9466da543..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/battery-caution-charging.svg +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/battery-caution.svg b/lisp/ui/styles/icons/numix/scalable/status/battery-caution.svg deleted file mode 100644 index c1d15f110..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/battery-caution.svg +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/battery-empty-charging.svg b/lisp/ui/styles/icons/numix/scalable/status/battery-empty-charging.svg deleted file mode 100644 index 34d161ee2..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/battery-empty-charging.svg +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/battery-empty.svg b/lisp/ui/styles/icons/numix/scalable/status/battery-empty.svg deleted file mode 100644 index 91771b8b8..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/battery-empty.svg +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/battery-full-charged.svg b/lisp/ui/styles/icons/numix/scalable/status/battery-full-charged.svg deleted file mode 100644 index 485504c97..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/battery-full-charged.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/battery-full-charging.svg b/lisp/ui/styles/icons/numix/scalable/status/battery-full-charging.svg deleted file mode 100644 index b6ce5beb0..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/battery-full-charging.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/battery-full.svg b/lisp/ui/styles/icons/numix/scalable/status/battery-full.svg deleted file mode 100644 index e52ea2054..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/battery-full.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/battery-good-charging.svg b/lisp/ui/styles/icons/numix/scalable/status/battery-good-charging.svg deleted file mode 100644 index 78953e2cd..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/battery-good-charging.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/battery-good.svg b/lisp/ui/styles/icons/numix/scalable/status/battery-good.svg deleted file mode 100644 index da00e865d..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/battery-good.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/battery-low-charging.svg b/lisp/ui/styles/icons/numix/scalable/status/battery-low-charging.svg deleted file mode 100644 index e3b094c66..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/battery-low-charging.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/battery-low.svg b/lisp/ui/styles/icons/numix/scalable/status/battery-low.svg deleted file mode 100644 index 2a5246218..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/battery-low.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/battery-missing.svg b/lisp/ui/styles/icons/numix/scalable/status/battery-missing.svg deleted file mode 100644 index d02159fd4..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/battery-missing.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/bluetooth-active.svg b/lisp/ui/styles/icons/numix/scalable/status/bluetooth-active.svg deleted file mode 100644 index 9468591f7..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/bluetooth-active.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/bluetooth-disabled.svg b/lisp/ui/styles/icons/numix/scalable/status/bluetooth-disabled.svg deleted file mode 100644 index 4b654ed6d..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/bluetooth-disabled.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/changes-prevent.svg b/lisp/ui/styles/icons/numix/scalable/status/changes-prevent.svg deleted file mode 100644 index eef82f8c2..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/changes-prevent.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/changes-secure.svg b/lisp/ui/styles/icons/numix/scalable/status/changes-secure.svg deleted file mode 100644 index eef82f8c2..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/changes-secure.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/dialog-error.svg b/lisp/ui/styles/icons/numix/scalable/status/dialog-error.svg deleted file mode 100644 index 0e3916944..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/dialog-error.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/dialog-information.svg b/lisp/ui/styles/icons/numix/scalable/status/dialog-information.svg deleted file mode 100644 index bceacf8b8..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/dialog-information.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/dialog-password.svg b/lisp/ui/styles/icons/numix/scalable/status/dialog-password.svg deleted file mode 100644 index d0ac063a7..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/dialog-password.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/dialog-question.svg b/lisp/ui/styles/icons/numix/scalable/status/dialog-question.svg deleted file mode 100644 index 4d615ba9d..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/dialog-question.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/dialog-warning.svg b/lisp/ui/styles/icons/numix/scalable/status/dialog-warning.svg deleted file mode 100644 index 7d154863e..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/dialog-warning.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/display-brightness.svg b/lisp/ui/styles/icons/numix/scalable/status/display-brightness.svg deleted file mode 100644 index 10f628c3d..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/display-brightness.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq-100.svg b/lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq-100.svg deleted file mode 100644 index 2e343c874..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq-100.svg +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq-25.svg b/lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq-25.svg deleted file mode 100644 index c68e0fb4c..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq-25.svg +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq-50.svg b/lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq-50.svg deleted file mode 100644 index 0cab669d1..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq-50.svg +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq-75.svg b/lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq-75.svg deleted file mode 100644 index 4793c2046..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq-75.svg +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq.svg b/lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq.svg deleted file mode 100644 index 2e343c874..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/indicator-cpufreq.svg +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/network-wired.svg b/lisp/ui/styles/icons/numix/scalable/status/network-wired.svg deleted file mode 100644 index e65bf9789..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/network-wired.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/network-wireless-acquiring.svg b/lisp/ui/styles/icons/numix/scalable/status/network-wireless-acquiring.svg deleted file mode 100644 index b37e931c3..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/network-wireless-acquiring.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/network-wireless-connected.svg b/lisp/ui/styles/icons/numix/scalable/status/network-wireless-connected.svg deleted file mode 100644 index 40dec5075..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/network-wireless-connected.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/network-wireless-encrypted.svg b/lisp/ui/styles/icons/numix/scalable/status/network-wireless-encrypted.svg deleted file mode 100644 index 74e35f2d5..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/network-wireless-encrypted.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-excellent.svg b/lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-excellent.svg deleted file mode 100644 index 40dec5075..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-excellent.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-good.svg b/lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-good.svg deleted file mode 100644 index 63bc0b6d8..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-good.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-none.svg b/lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-none.svg deleted file mode 100644 index 1eb249899..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-none.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-ok.svg b/lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-ok.svg deleted file mode 100644 index 3a5f69eb7..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-ok.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-weak.svg b/lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-weak.svg deleted file mode 100644 index 3d9aac01f..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/network-wireless-signal-weak.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/user-available.svg b/lisp/ui/styles/icons/numix/scalable/status/user-available.svg deleted file mode 100644 index 97a10423f..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/user-available.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/user-away.svg b/lisp/ui/styles/icons/numix/scalable/status/user-away.svg deleted file mode 100644 index 74e3330e8..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/user-away.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/user-busy.svg b/lisp/ui/styles/icons/numix/scalable/status/user-busy.svg deleted file mode 100644 index 0a2f610b5..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/user-busy.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/user-idle.svg b/lisp/ui/styles/icons/numix/scalable/status/user-idle.svg deleted file mode 100644 index cb58ba8ba..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/user-idle.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/user-invisible.svg b/lisp/ui/styles/icons/numix/scalable/status/user-invisible.svg deleted file mode 100644 index 3a723e5f7..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/user-invisible.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/user-offline.svg b/lisp/ui/styles/icons/numix/scalable/status/user-offline.svg deleted file mode 100644 index c2e80b2f1..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/user-offline.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/user-status.svg b/lisp/ui/styles/icons/numix/scalable/status/user-status.svg deleted file mode 100644 index 9240c3948..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/user-status.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/weather-clear-night.svg b/lisp/ui/styles/icons/numix/scalable/status/weather-clear-night.svg deleted file mode 100644 index 4fdf4fe75..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/weather-clear-night.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/weather-clear.svg b/lisp/ui/styles/icons/numix/scalable/status/weather-clear.svg deleted file mode 100644 index b66e089ec..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/weather-clear.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/weather-few-clouds-night.svg b/lisp/ui/styles/icons/numix/scalable/status/weather-few-clouds-night.svg deleted file mode 100644 index ef691229e..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/weather-few-clouds-night.svg +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/weather-few-clouds.svg b/lisp/ui/styles/icons/numix/scalable/status/weather-few-clouds.svg deleted file mode 100644 index 3c68da89d..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/weather-few-clouds.svg +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/weather-fog.svg b/lisp/ui/styles/icons/numix/scalable/status/weather-fog.svg deleted file mode 100644 index 6bbb42e08..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/weather-fog.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/weather-overcast.svg b/lisp/ui/styles/icons/numix/scalable/status/weather-overcast.svg deleted file mode 100644 index 0624a47c6..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/weather-overcast.svg +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/weather-severe-alert.svg b/lisp/ui/styles/icons/numix/scalable/status/weather-severe-alert.svg deleted file mode 100644 index b56d6d48e..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/weather-severe-alert.svg +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/weather-showers-scattered.svg b/lisp/ui/styles/icons/numix/scalable/status/weather-showers-scattered.svg deleted file mode 100644 index 1fa012670..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/weather-showers-scattered.svg +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/weather-showers.svg b/lisp/ui/styles/icons/numix/scalable/status/weather-showers.svg deleted file mode 100644 index 55b380c79..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/weather-showers.svg +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/weather-snow.svg b/lisp/ui/styles/icons/numix/scalable/status/weather-snow.svg deleted file mode 100644 index c32ce8973..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/weather-snow.svg +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/weather-storm.svg b/lisp/ui/styles/icons/numix/scalable/status/weather-storm.svg deleted file mode 100644 index ad4402d2c..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/weather-storm.svg +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/xn-emblem-system.svg b/lisp/ui/styles/icons/numix/scalable/status/xn-emblem-system.svg deleted file mode 100644 index e1d776b9d..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/xn-emblem-system.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/styles/icons/numix/scalable/status/xn-playlist.svg b/lisp/ui/styles/icons/numix/scalable/status/xn-playlist.svg deleted file mode 100644 index ad35bd5ee..000000000 --- a/lisp/ui/styles/icons/numix/scalable/status/xn-playlist.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - From e67b8ce2f5edacdec5d4683b84c1e3fb039ec860 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 24 Feb 2018 13:38:53 +0100 Subject: [PATCH 088/333] Remove unused class --- lisp/core/util.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/lisp/core/util.py b/lisp/core/util.py index 79711437a..e4d8be8c1 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -241,19 +241,3 @@ def __init__(self, function): def __call__(self, *args, **kwargs): return self.function(*args, **kwargs) - - -class InstanceOfSubclassMeta(type): - """Some horrible black magic here - - When creating an object from a class using this metaclass, - an instance of a subclass created on-the-fly will be returned. - """ - _MAGIC = str(uuid4()) - - def __call__(cls, *args, **kwargs): - if kwargs.pop(cls._MAGIC, False): - return super().__call__(*args, **kwargs) - - kwargs.update({cls._MAGIC: True}) - return type(cls.__name__, (cls, ), {})(*args, **kwargs) \ No newline at end of file From 27c8d8c3407b362cd91574b9f9b8d62df23391ab Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 26 Feb 2018 13:47:48 +0100 Subject: [PATCH 089/333] Refactoring of Property and HasProperties to allow per-instance properties. Add: new InstanceProperty and HasInstance properties classes Add: volume icons Update: changes to use InstanceProperties when required Fix: some problem with references kept alive by lambdas --- lisp/application.py | 5 +- lisp/core/configuration.py | 2 +- lisp/core/has_properties.py | 267 ++++++++++-------- lisp/core/properties.py | 68 +++-- lisp/core/session.py | 16 +- .../plugins/gst_backend/elements/alsa_sink.py | 2 +- .../gst_backend/elements/audio_dynamic.py | 17 +- .../plugins/gst_backend/elements/audio_pan.py | 2 +- lisp/plugins/gst_backend/elements/db_meter.py | 15 +- .../gst_backend/elements/equalizer10.py | 20 +- .../plugins/gst_backend/elements/jack_sink.py | 5 +- lisp/plugins/gst_backend/elements/pitch.py | 2 +- lisp/plugins/gst_backend/elements/speed.py | 18 +- .../plugins/gst_backend/elements/uri_input.py | 45 +-- lisp/plugins/gst_backend/elements/volume.py | 7 +- lisp/plugins/gst_backend/gst_element.py | 12 +- lisp/plugins/gst_backend/gst_media.py | 20 +- .../plugins/gst_backend/gst_media_settings.py | 2 +- lisp/ui/mainwindow.py | 26 +- lisp/ui/themes/icons/numix/NOTE | 1 + .../themes/icons/numix/audio-volume-high.svg | 9 + .../themes/icons/numix/audio-volume-low.svg | 7 + .../icons/numix/audio-volume-medium.svg | 7 + .../themes/icons/numix/audio-volume-muted.svg | 7 + .../icons/symbolic/audio-volume-high.svg | 3 + .../icons/symbolic/audio-volume-low.svg | 4 + .../icons/symbolic/audio-volume-medium.svg | 4 + .../icons/symbolic/audio-volume-muted.svg | 3 + 28 files changed, 352 insertions(+), 244 deletions(-) create mode 100644 lisp/ui/themes/icons/numix/NOTE create mode 100644 lisp/ui/themes/icons/numix/audio-volume-high.svg create mode 100644 lisp/ui/themes/icons/numix/audio-volume-low.svg create mode 100644 lisp/ui/themes/icons/numix/audio-volume-medium.svg create mode 100644 lisp/ui/themes/icons/numix/audio-volume-muted.svg create mode 100644 lisp/ui/themes/icons/symbolic/audio-volume-high.svg create mode 100644 lisp/ui/themes/icons/symbolic/audio-volume-low.svg create mode 100644 lisp/ui/themes/icons/symbolic/audio-volume-medium.svg create mode 100644 lisp/ui/themes/icons/symbolic/audio-volume-muted.svg diff --git a/lisp/application.py b/lisp/application.py index a9f49a4ee..8d431111d 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -20,11 +20,12 @@ import json from os.path import exists +import objgraph as objgraph from PyQt5.QtWidgets import QDialog, qApp from lisp import layouts from lisp.core.actions_handler import MainActionsHandler -from lisp.core.session import new_session +from lisp.core.session import Session from lisp.core.signal import Signal from lisp.core.singleton import Singleton from lisp.cues.cue import Cue @@ -134,7 +135,7 @@ def _new_session_dialog(self): def _new_session(self, layout): self._delete_session() - self.__session = new_session(layout(application=self)) + self.__session = Session(layout(application=self)) self.__main_window.set_session(self.__session) self.session_created.emit(self.__session) diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index ac596794f..97050f8c2 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -119,7 +119,7 @@ def copy(self): @staticmethod def jp(*paths): - return '.'.join(*paths) + return '.'.join(paths) def __getitem__(self, path): return self.get(path) diff --git a/lisp/core/has_properties.py b/lisp/core/has_properties.py index afd7e777f..161220588 100644 --- a/lisp/core/has_properties.py +++ b/lisp/core/has_properties.py @@ -19,148 +19,123 @@ from abc import ABCMeta -from lisp.core.properties import Property, LiveProperty +from lisp.core.properties import Property, InstanceProperty from lisp.core.signal import Signal -from lisp.core.util import subclasses class HasPropertiesMeta(ABCMeta): - """Metaclass for HasProperties classes. + """Metaclass for defining HasProperties classes. - This metaclass manage the "propagation" of the properties in all subclasses. + Use this metaclass to create an HasProperties. This metaclass takes care + of keeping an update register of all the properties in the class and + to propagate the changes in all the hierarchy. - ..note:: + On it's own this metaclass it's not very useful, look at the HasProperty + class for a comprehensive implementation. + + Note: This metaclass is derived form :class:`abc.ABCMeta`, so abstract classes can be created without using an intermediate metaclass """ - def __init__(cls, *args, **kwargs): - super().__init__(*args, **kwargs) - cls.__properties__ = set() - cls.__live_properties__ = set() - - # Populate with the existing properties - for name, attribute in vars(cls).items(): - if isinstance(attribute, Property): - cls.__properties__.add(name) - attribute.name = name - elif isinstance(attribute, LiveProperty): - cls.__live_properties__.add(name) - attribute.name = name - - for base in cls.__bases__: + def __new__(mcls, name, bases, namespace, **kwargs): + cls = super().__new__(mcls, name, bases, namespace, **kwargs) + + # Compute the set of property names + cls.__pro__ = { + name + for name, value in namespace.items() + if isinstance(value, Property) + } + + # Update the set from "proper" base classes + for base in bases: if isinstance(base, HasPropertiesMeta): - cls.__properties__.update(base.__properties__) - cls.__live_properties__.update(base.__live_properties__) + cls.__pro__.update(base.__pro__) + + return cls def __setattr__(cls, name, value): super().__setattr__(name, value) if isinstance(value, Property): - cls.__properties__.add(name) - value.name = name - - for subclass in subclasses(cls): - subclass.__properties__.add(name) - elif isinstance(value, LiveProperty): - cls.__live_properties__.add(name) - value.name = name - - for subclass in subclasses(cls): - subclass.__live_properties__.add(name) + cls._add_property(name) def __delattr__(cls, name): super().__delattr__(name) - cls.__properties__.discard(name) - cls.__live_properties__.discard(name) - - for subclass in subclasses(cls): - subclass.__properties__.discard(name) - subclass.__live_properties__.discard(name) + if name in cls.__pro__: + cls._del_property(name) + def _add_property(cls, name): + cls.__pro__.add(name) + for subclass in cls.__subclasses__(): + subclass._add_property(name) -class HasProperties(metaclass=HasPropertiesMeta): - """Base class providing a simple way to mange object properties. + def _del_property(cls, name): + cls.__pro__.discard(name) + for subclass in cls.__subclasses__(): + subclass._del_property(name) - Using the Property descriptor, subclasses, can specify a set of - properties, that can be easily retrieved and updated via :func:`properties` - and :func:`update_properties`. - When using LiveProperty those should be named as the "non-live" counterpart - prefixed by "live_". +class HasProperties(metaclass=HasPropertiesMeta): + """Base class which allow to use Property and InstanceProperty. - .. Usage:: + Using the Property descriptor subclasses can specify a set of properties + that can be easily retrieved and updated via a series of provided functions. - class MyClass(HasProperties): - prop1 = Property(default=100) - prop2 = Property() + HasProperties objects can be nested, using a property that keep an + HasProperties object as value. - live_prop1 = ConcreteLiveProperty() - """ + Usage: - __properties__ = set() - __live_properties__ = set() + class DeepThought(HasProperties): + the_answer = Property(default=42) + nested = Property(default=AnotherThought.class_defaults()) + """ def __init__(self): - self._changed_signals = {} + self.__changed_signals = {} # Contains signals that are emitted after the associated property is # changed, the signal are create only when requested the first time. self.property_changed = Signal() # Emitted after property change (self, name, value) - def __setattr__(self, name, value): - super().__setattr__(name, value) - - if name in self.__class__.__properties__: - self.property_changed.emit(self, name, value) - self.__emit_changed(name, value) - elif name in self.__class__.__live_properties__: - self.__emit_changed(name, value) - - def __emit_changed(self, name, value): - try: - self._changed_signals[name].emit(value) - except KeyError: - pass - - @classmethod - def register_property(cls, name, prop): - """Register a new property with the given name. + def properties_names(self): + return self.__class__.__pro__ - :param name: Property name - :param prop: The property - """ - setattr(cls, name, prop) + def properties_defaults(self): + """Instance properties defaults. - @classmethod - def remove_property(cls, name): - """Remove the property with the given name. + Differently from `class_defaults` this works on instances, and it might + give different results with some subclass. - :param name: Property name + :return: The default properties as a dictionary {name: default_value} + :rtype: dict """ - delattr(cls, name) + defaults = {} - def changed(self, name): - """ - :param name: The property name - :return: A signal that notify the given property changes - :rtype: Signal + for name in self.properties_names(): + value = self._pro(name).default + if isinstance(value, HasProperties): + value = value.properties_defaults() - The signals returned by this method are created lazily and cached. - """ - if (name not in self.__class__.__properties__ and - name not in self.__class__.__live_properties__): - raise ValueError('no property "{}" found'.format(name)) + defaults[name] = value - signal = self._changed_signals.get(name) + return defaults - if signal is None: - signal = Signal() - self._changed_signals[name] = signal + @classmethod + def class_defaults(cls): + """Class properties defaults. - return signal + :return: The default properties as a dictionary {name: default_value} + :rtype: dict + """ + return { + name: getattr(cls, name).default + for name in cls.__pro__ + } def properties(self, defaults=True): """ @@ -172,49 +147,109 @@ def properties(self, defaults=True): """ properties = {} - for name in self.__class__.__properties__: + for name in self.properties_names(): value = getattr(self, name) if isinstance(value, HasProperties): value = value.properties(defaults=defaults) if defaults or value: properties[name] = value - elif defaults or value != getattr(self.__class__, name).default: + elif defaults or value != self._pro(name).default: properties[name] = value return properties def update_properties(self, properties): - """Set the given properties. + """Update the current properties using the given dict. :param properties: The element properties :type properties: dict """ for name, value in properties.items(): - if name in self.__class__.__properties__: + if name in self.properties_names(): current = getattr(self, name) if isinstance(current, HasProperties): current.update_properties(value) else: setattr(self, name, value) - @classmethod - def properties_defaults(cls): + def changed(self, name): """ - :return: The default properties as a dictionary {name: default_value} - :rtype: dict + :param name: The property name + :return: A signal that notify the given property changes + :rtype: Signal + + The signals returned by this method are created lazily and cached. """ - return {name: getattr(cls, name).default for name in cls.__properties__} + if name not in self.properties_names(): + raise ValueError('no property "{}" found'.format(name)) - @classmethod - def properties_names(cls): - """Retrieve properties names from the class + signal = self.__changed_signals.get(name) + if signal is None: + signal = Signal() + self.__changed_signals[name] = signal - :return: A set containing the properties names - :rtype: set[str] - """ - return cls.__properties__.copy() + return signal - @classmethod - def live_properties_names(cls): - return cls.__live_properties__.copy() + def __setattr__(self, name, value): + super().__setattr__(name, value) + if name in self.properties_names(): + self._emit_changed(name, value) + + def _emit_changed(self, name, value): + self.property_changed.emit(self, name, value) + try: + self.__changed_signals[name].emit(value) + except KeyError: + pass + + def _pro(self, name): + if name in self.__class__.__pro__: + return getattr(self.__class__, name) + + # TODO: PropertyError ?? + raise AttributeError( + "'{}' object has no property '{}'".format( + type(self).__name__, name) + ) + + +class HasInstanceProperties(HasProperties): + # Fallback __init__ + __ipro__ = set() + + def __init__(self): + super().__init__() + self.__ipro__ = set() + # Registry to keep track of instance-properties + + def properties_names(self): + return super().properties_names().union(self.__ipro__) + + def __getattribute__(self, name): + attribute = super().__getattribute__(name) + if isinstance(attribute, InstanceProperty): + return attribute.__pget__() + + return attribute + + def __setattr__(self, name, value): + if isinstance(value, InstanceProperty): + super().__setattr__(name, value) + self.__ipro__.add(name) + elif name in self.__ipro__: + property = super().__getattribute__(name) + property.__pset__(value) + self._emit_changed(name, value) + else: + super().__setattr__(name, value) + + def __delattr__(self, name): + super().__delattr__(name) + self.__ipro__.discard(name) + + def _pro(self, name): + if name in self.__ipro__: + return self._getattribute(name) + + return super()._pro(name) diff --git a/lisp/core/properties.py b/lisp/core/properties.py index 136d7efe7..eb162b06a 100644 --- a/lisp/core/properties.py +++ b/lisp/core/properties.py @@ -18,26 +18,7 @@ # along with Linux Show Player. If not, see . from copy import deepcopy - - -class LiveProperty: - """Descriptor used to define live-properties. - - Live-properties allow to manipulate parameters in a live context, without - touching the values stored inside `Property` descriptors. - - This class doesn't implement any behavior, but it must be extended in order - to correctly register live-properties. - """ - def __init__(self, **meta): - self.name = '_' - self.meta = meta - - def __get__(self, instance, owner=None): - pass - - def __set__(self, instance, value): - pass +from weakref import WeakKeyDictionary class Property: @@ -46,38 +27,65 @@ class Property: Properties allow to define "special" attributes for objects, to provide custom behaviors in a simple and reusable manner. - .. warning:: + Should be used in combination with an HasProperties object. + + As default value list, dict and likely can be used, since the default value + is (deep)copied. + + Warning: To be able to save properties into a session, the stored value MUST be JSON-serializable. + + Note: + Internally a WeakKeyDictionary is used, to avoid keeping objects + references alive """ def __init__(self, default=None, **meta): - self.name = '_' + self._values = WeakKeyDictionary() + self.default = default self.meta = meta def __get__(self, instance, owner=None): if instance is None: return self - elif self.name not in instance.__dict__: - instance.__dict__[self.name] = deepcopy(self.default) + elif instance not in self._values: + self._values[instance] = deepcopy(self.default) - return instance.__dict__.get(self.name) + return self._values[instance] def __set__(self, instance, value): if instance is not None: - # Only change the value if different - if value != instance.__dict__.get(self.name, self.default): - instance.__dict__[self.name] = value + self._values[instance] = value class WriteOnceProperty(Property): """Property that can be modified only once. - Obviously this is not really "write-once", but if used as normal attribute - will ignore any change when the stored value is different from default. + Obviously this is not really "write-once", but when used as normal attribute + will ignore any change when the stored value is different than the default. """ def __set__(self, instance, value): if self.__get__(instance) == self.default: super().__set__(instance, value) + + +class InstanceProperty: + """Per-instance property, not a descriptor. + + To be of any use an InstanceProperty should be used in combination + of an HasInstanceProperties object. + """ + __slots__ = ('value', 'default') + + def __init__(self, default=None): + self.value = default + self.default = default + + def __pget__(self): + return self.value + + def __pset__(self, value): + self.value = value \ No newline at end of file diff --git a/lisp/core/session.py b/lisp/core/session.py index b543db55b..50362b3ea 100644 --- a/lisp/core/session.py +++ b/lisp/core/session.py @@ -19,16 +19,12 @@ import os -from lisp.core.signal import Signal -from lisp.core.has_properties import HasProperties, Property +from lisp.core.has_properties import Property, HasInstanceProperties from lisp.core.memento_model import MementoModelAdapter +from lisp.core.signal import Signal -def new_session(layout): - return type('Session', (BaseSession, ), {})(layout) - - -class BaseSession(HasProperties): +class Session(HasInstanceProperties): layout_type = Property(default='') session_file = Property(default='') @@ -36,7 +32,7 @@ def __init__(self, layout): super().__init__() self.finalized = Signal() - self.layout_type = layout.__class__.__name__ + self.layout_type = type(layout).__name__ self.__layout = layout self.__cue_model = layout.cue_model @@ -57,24 +53,28 @@ def layout(self): return self.__layout def name(self): + """Return the name of session, depending on the session-file.""" if self.session_file: return os.path.splitext(os.path.basename(self.session_file))[0] else: return 'Untitled' def path(self): + """Return the current session-file path.""" if self.session_file: return os.path.dirname(self.session_file) else: return os.path.expanduser('~') def abs_path(self, rel_path): + """Return an absolute version of the given path.""" if not os.path.isabs(rel_path): return os.path.normpath(os.path.join(self.path(), rel_path)) return rel_path def rel_path(self, abs_path): + """Return a relative (to the session-file) version of the given path.""" return os.path.relpath(abs_path, start=self.path()) def finalize(self): diff --git a/lisp/plugins/gst_backend/elements/alsa_sink.py b/lisp/plugins/gst_backend/elements/alsa_sink.py index 47a545528..0e23e1a65 100644 --- a/lisp/plugins/gst_backend/elements/alsa_sink.py +++ b/lisp/plugins/gst_backend/elements/alsa_sink.py @@ -29,7 +29,7 @@ class AlsaSink(GstMediaElement): MediaType = MediaType.Audio Name = QT_TRANSLATE_NOOP('MediaElementName', 'ALSA Out') - device = GstProperty('alsa_sink', default='') + device = GstProperty('alsa_sink', 'device', default='') def __init__(self, pipe): super().__init__() diff --git a/lisp/plugins/gst_backend/elements/audio_dynamic.py b/lisp/plugins/gst_backend/elements/audio_dynamic.py index 7de38e9e7..4fd36607d 100644 --- a/lisp/plugins/gst_backend/elements/audio_dynamic.py +++ b/lisp/plugins/gst_backend/elements/audio_dynamic.py @@ -35,16 +35,19 @@ class Mode(Enum): Compressor = 'compressor' Expander = 'expander' - class Characteristics(Enum): HardKnee = 'hard-knee' SoftKnee = 'soft-knee' - mode = GstProperty('audio_dynamic', default=Mode.Compressor.value) - characteristics = GstProperty('audio_dynamic', - default=Characteristics.HardKnee.value) - ratio = GstProperty('audio_dynamic', default=1) - threshold = GstProperty('audio_dynamic', default=0) + mode = GstProperty( + 'audio_dynamic', 'mode', default=Mode.Compressor.value) + ratio = GstProperty('audio_dynamic', 'ratio', default=1) + threshold = GstProperty('audio_dynamic', 'threshold', default=0) + characteristics = GstProperty( + 'audio_dynamic', + 'characteristics', + default=Characteristics.HardKnee.value + ) def __init__(self, pipe): super().__init__() @@ -63,4 +66,4 @@ def sink(self): return self.audio_dynamic def src(self): - return self.audio_converter \ No newline at end of file + return self.audio_converter diff --git a/lisp/plugins/gst_backend/elements/audio_pan.py b/lisp/plugins/gst_backend/elements/audio_pan.py index 596305543..a03c94247 100644 --- a/lisp/plugins/gst_backend/elements/audio_pan.py +++ b/lisp/plugins/gst_backend/elements/audio_pan.py @@ -29,7 +29,7 @@ class AudioPan(GstMediaElement): MediaType = MediaType.Audio Name = QT_TRANSLATE_NOOP('MediaElementName', 'Audio Pan') - pan = GstProperty('panorama', default=.0, gst_name='panorama') + pan = GstProperty('panorama', 'panorama', default=.0) def __init__(self, pipe): super().__init__() diff --git a/lisp/plugins/gst_backend/elements/db_meter.py b/lisp/plugins/gst_backend/elements/db_meter.py index 9c3ab1931..5582f050f 100644 --- a/lisp/plugins/gst_backend/elements/db_meter.py +++ b/lisp/plugins/gst_backend/elements/db_meter.py @@ -30,13 +30,12 @@ class DbMeter(GstMediaElement): MediaType = MediaType.Audio Name = QT_TRANSLATE_NOOP('MediaElementName', 'dB Meter') - interval = GstProperty('level', default=50 * Gst.MSECOND) - peak_ttl = GstProperty('level', default=Gst.SECOND, gst_name='peak-ttl') - peak_falloff = GstProperty('level', default=20, gst_name='peak-falloff') + interval = GstProperty('level', 'interval', default=50 * Gst.MSECOND) + peak_ttl = GstProperty('level', 'peak-ttl', default=Gst.SECOND) + peak_falloff = GstProperty('level', 'peak-falloff', default=20) def __init__(self, pipeline): super().__init__() - self.level_ready = Signal() self.pipeline = pipeline @@ -71,6 +70,8 @@ def __on_message(self, bus, message): if message.src == self.level: structure = message.get_structure() if structure is not None and structure.has_name('level'): - self.level_ready.emit(structure.get_value('peak'), - structure.get_value('rms'), - structure.get_value('decay')) + self.level_ready.emit( + structure.get_value('peak'), + structure.get_value('rms'), + structure.get_value('decay') + ) diff --git a/lisp/plugins/gst_backend/elements/equalizer10.py b/lisp/plugins/gst_backend/elements/equalizer10.py index 3da4a9662..c62939a8b 100644 --- a/lisp/plugins/gst_backend/elements/equalizer10.py +++ b/lisp/plugins/gst_backend/elements/equalizer10.py @@ -29,16 +29,16 @@ class Equalizer10(GstMediaElement): MediaType = MediaType.Audio Name = QT_TRANSLATE_NOOP('MediaElementName', '10 Bands Equalizer') - band0 = GstProperty('equalizer', default=0) - band1 = GstProperty('equalizer', default=0) - band2 = GstProperty('equalizer', default=0) - band3 = GstProperty('equalizer', default=0) - band4 = GstProperty('equalizer', default=0) - band5 = GstProperty('equalizer', default=0) - band6 = GstProperty('equalizer', default=0) - band7 = GstProperty('equalizer', default=0) - band8 = GstProperty('equalizer', default=0) - band9 = GstProperty('equalizer', default=0) + band0 = GstProperty('equalizer', 'band0', default=0) + band1 = GstProperty('equalizer', 'band1', default=0) + band2 = GstProperty('equalizer', 'band2', default=0) + band3 = GstProperty('equalizer', 'band3', default=0) + band4 = GstProperty('equalizer', 'band4', default=0) + band5 = GstProperty('equalizer', 'band5', default=0) + band6 = GstProperty('equalizer', 'band6', default=0) + band7 = GstProperty('equalizer', 'band7', default=0) + band8 = GstProperty('equalizer', 'band8', default=0) + band9 = GstProperty('equalizer', 'band9', default=0) def __init__(self, pipe): super().__init__() diff --git a/lisp/plugins/gst_backend/elements/jack_sink.py b/lisp/plugins/gst_backend/elements/jack_sink.py index b5806e03f..97d2f4e66 100644 --- a/lisp/plugins/gst_backend/elements/jack_sink.py +++ b/lisp/plugins/gst_backend/elements/jack_sink.py @@ -140,10 +140,9 @@ def __jack_connect(self): def __on_message(self, bus, message): if message.src == self.jack_sink: if message.type == Gst.MessageType.STATE_CHANGED: - change = message.parse_state_changed() + change = tuple(message.parse_state_changed())[0:2] # The jack ports are available when the the jackaudiosink # change from READY to PAUSED state - if (change[0] == Gst.State.READY and - change[1] == Gst.State.PAUSED): + if change == (Gst.State.READY, Gst.State.PAUSED): self.__jack_connect() diff --git a/lisp/plugins/gst_backend/elements/pitch.py b/lisp/plugins/gst_backend/elements/pitch.py index cca69fdd0..f64be3eba 100644 --- a/lisp/plugins/gst_backend/elements/pitch.py +++ b/lisp/plugins/gst_backend/elements/pitch.py @@ -29,7 +29,7 @@ class Pitch(GstMediaElement): MediaType = MediaType.Audio Name = QT_TRANSLATE_NOOP('MediaElementName', 'Pitch') - pitch = GstProperty('gst_pitch', gst_name='pitch', default=1.0) + pitch = GstProperty('gst_pitch', 'pitch', default=1.0) def __init__(self, pipe): super().__init__() diff --git a/lisp/plugins/gst_backend/elements/speed.py b/lisp/plugins/gst_backend/elements/speed.py index f34f0f21d..b21886544 100644 --- a/lisp/plugins/gst_backend/elements/speed.py +++ b/lisp/plugins/gst_backend/elements/speed.py @@ -76,12 +76,12 @@ def __on_message(self, bus, message): self.__change_speed() def __change_speed(self): - current_position = self.scale_tempo.query_position(Gst.Format.TIME)[1] - - self.scale_tempo.seek(self.speed, - Gst.Format.TIME, - Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE, - Gst.SeekType.SET, - current_position, - Gst.SeekType.NONE, - 0) + self.scale_tempo.seek( + self.speed, + Gst.Format.TIME, + Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE, + Gst.SeekType.SET, + self.scale_tempo.query_position(Gst.Format.TIME)[1], + Gst.SeekType.NONE, + 0 + ) diff --git a/lisp/plugins/gst_backend/elements/uri_input.py b/lisp/plugins/gst_backend/elements/uri_input.py index 707edfdb3..4088af25b 100644 --- a/lisp/plugins/gst_backend/elements/uri_input.py +++ b/lisp/plugins/gst_backend/elements/uri_input.py @@ -19,6 +19,7 @@ from concurrent.futures import ThreadPoolExecutor from os import path +from urllib.parse import quote as urlquote, unquote as urlunquote from PyQt5.QtCore import QT_TRANSLATE_NOOP @@ -32,24 +33,28 @@ from lisp.plugins.gst_backend.gst_utils import gst_uri_duration -def abs_uri(uri): - protocol, path_ = uri.split('://') - if protocol == 'file': - return 'file://' + Application().session.abs_path(path_) - else: - return uri +def abs_path(path_): + return Application().session.abs_path(path_) + + +def uri_split(uri): + return uri.split('://') + + +def uri_adapter(uri): + scheme, path = uri_split(uri) + return scheme + '://' + urlquote(abs_path(path)) class UriInput(GstSrcElement): MediaType = MediaType.Audio Name = QT_TRANSLATE_NOOP('MediaElementName', 'URI Input') - uri = GstProperty('decoder', default='', adapter=abs_uri) - download = GstProperty('decoder', default=False) - buffer_size = GstProperty('decoder', default=-1, gst_name='buffer-size') - use_buffering = GstProperty('decoder', default=False, - gst_name='use-buffering') _mtime = Property(default=-1) + uri = GstProperty('decoder', 'uri', default='', adapter=uri_adapter) + download = GstProperty('decoder', 'download', default=False) + buffer_size = GstProperty('decoder', 'buffer-size', default=-1) + use_buffering = GstProperty('decoder', 'use-buffering', default=False) def __init__(self, pipe): super().__init__() @@ -80,9 +85,11 @@ def __on_pad_added(self, *args): def __uri_changed(self, uri): # Save the current mtime (file flag for last-change time) mtime = self._mtime - # If the uri is a file, then update the current mtime - protocol, path_ = abs_uri(uri).split('://') - if protocol == 'file': + + # If the uri is a file update the current mtime + scheme, path_ = uri_split(uri) + if scheme == 'file': + path_ = abs_path(path_) if path.exists(path_): self._mtime = path.getmtime(path_) else: @@ -94,9 +101,11 @@ def __uri_changed(self, uri): @async_in_pool(pool=ThreadPoolExecutor(1)) def __duration(self): - self.duration = gst_uri_duration(abs_uri(self.uri)) + scheme, path = uri_split(self.uri) + self.duration = gst_uri_duration(scheme + '://' + abs_path(path)) def __session_moved(self, _): - protocol, old_path = self.decoder.get_property('uri').split('://') - if protocol == 'file': - self.uri = 'file://' + Application().session.rel_path(old_path) + scheme, path_ = uri_split(self.decoder.get_property('uri')) + if scheme == 'file': + path_ = urlunquote(path_) + self.uri = 'file://' + Application().session.rel_path(path_) diff --git a/lisp/plugins/gst_backend/elements/volume.py b/lisp/plugins/gst_backend/elements/volume.py index d230eb4df..22c79e7b2 100644 --- a/lisp/plugins/gst_backend/elements/volume.py +++ b/lisp/plugins/gst_backend/elements/volume.py @@ -30,10 +30,9 @@ class Volume(GstMediaElement): MediaType = MediaType.Audio Name = QT_TRANSLATE_NOOP('MediaElementName', 'Volume') - mute = GstProperty('gst_volume', default=False) - volume = GstProperty('gst_volume', default=1.0) - normal_volume = GstProperty( - 'gst_normal_volume', default=1.0, gst_name='volume') + mute = GstProperty('gst_volume', 'mute', default=False) + volume = GstProperty('gst_volume', 'volume', default=1.0) + normal_volume = GstProperty('gst_normal_volume', 'volume', default=1.0) live_volume = GstLiveProperty( 'gst_volume', 'volume', type=float, range=(0, 10)) diff --git a/lisp/plugins/gst_backend/gst_element.py b/lisp/plugins/gst_backend/gst_element.py index 9aba6193c..c4afc7d46 100644 --- a/lisp/plugins/gst_backend/gst_element.py +++ b/lisp/plugins/gst_backend/gst_element.py @@ -18,17 +18,17 @@ # along with Linux Show Player. If not, see . from lisp.backend.media_element import MediaElement, ElementType -from lisp.core.properties import Property, LiveProperty +from lisp.core.properties import Property class GstProperty(Property): - def __init__(self, element_name, default=None, gst_name=None, adapter=None, + def __init__(self, element_name, property_name, default=None, adapter=None, **meta): super().__init__(default=default, **meta) self.element_name = element_name - self.gst_name = gst_name + self.property_name = property_name self.adapter = adapter def __set__(self, instance, value): @@ -38,11 +38,11 @@ def __set__(self, instance, value): if self.adapter is not None: value = self.adapter(value) - name = self.gst_name if self.gst_name is not None else self.name - getattr(instance, self.element_name).set_property(name, value) + getattr(instance, self.element_name).set_property( + self.property_name, value) -class GstLiveProperty(LiveProperty): +class GstLiveProperty(Property): def __init__(self, element_name, property_name, adapter=None, **meta): super().__init__(**meta) diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index b7018e5d0..6a85dd14c 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -20,8 +20,8 @@ import weakref from lisp.backend.media import Media, MediaState -from lisp.core.has_properties import HasProperties -from lisp.core.properties import Property, WriteOnceProperty +from lisp.core.has_properties import HasInstanceProperties +from lisp.core.properties import Property, InstanceProperty from lisp.plugins.gst_backend import elements as gst_elements from lisp.plugins.gst_backend.gi_repository import Gst @@ -286,11 +286,7 @@ def __finalizer(pipeline, connection_handler, media_elements): media_elements.clear() -def GstMediaElements(): - return type('GstMediaElements', (_GstMediaElements, ), {})() - - -class _GstMediaElements(HasProperties): +class GstMediaElements(HasInstanceProperties): def __init__(self): super().__init__() @@ -317,11 +313,9 @@ def append(self, element): self.elements.append(element) # Add a property for the new added element - self.register_property( - element.__class__.__name__, - WriteOnceProperty(default=None) - ) - setattr(self, element.__class__.__name__, element) + element_name = element.__class__.__name__ + setattr(self, element_name, InstanceProperty(default=None)) + setattr(self, element_name, element) def remove(self, element): self.pop(self.elements.index(element)) @@ -337,7 +331,7 @@ def pop(self, index): element.dispose() # Remove the element corresponding property - self.remove_property(element.__class__.__name__) + delattr(self, element.__class__.__name__) def clear(self): while self.elements: diff --git a/lisp/plugins/gst_backend/gst_media_settings.py b/lisp/plugins/gst_backend/gst_media_settings.py index a8c5f364d..9d8161deb 100644 --- a/lisp/plugins/gst_backend/gst_media_settings.py +++ b/lisp/plugins/gst_backend/gst_media_settings.py @@ -68,7 +68,7 @@ def load_settings(self, settings): page = page(parent=self) page.load_settings( settings.get('elements', {}) - .get(element, page.ELEMENT.properties_defaults())) + .get(element, page.ELEMENT.class_defaults())) page.setVisible(False) self._pages.append(page) diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 4f711161d..79e68f96b 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -201,12 +201,11 @@ def set_session(self, session): self.centralWidget().layout().addWidget(layout) layout.show() - self.multiEdit.triggered.connect(layout.edit_selected_cues) - self.selectAll.triggered.connect(lambda: layout.select_all()) - self.invertSelection.triggered.connect(layout.invert_selection) - self.deselectAll.triggered.connect(lambda: layout.deselect_all()) - self.selectAllMedia.triggered.connect( - lambda: layout.select_all(MediaCue)) + self.multiEdit.triggered.connect(self._edit_selected_cue) + self.selectAll.triggered.connect(self._select_all) + self.invertSelection.triggered.connect(self._invert_selection) + self.deselectAll.triggered.connect(self._deselect_all) + self.selectAllMedia.triggered.connect(self._select_all_media) def closeEvent(self, event): self._exit() @@ -324,3 +323,18 @@ def _show_about(self): def _exit(self): if self._check_saved(): qApp.quit() + + def _edit_selected_cue(self): + self.session.layout.edit_selected_cues() + + def _select_all(self): + self.session.layout.select_all() + + def _invert_selection(self): + self.session.layout.invert_selection() + + def _deselect_all(self): + self.session.layout.deselect_all() + + def _select_all_media(self): + self.session.layout.select_all(cue_class=MediaCue) \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/NOTE b/lisp/ui/themes/icons/numix/NOTE new file mode 100644 index 000000000..5c2b65999 --- /dev/null +++ b/lisp/ui/themes/icons/numix/NOTE @@ -0,0 +1 @@ +Icons from "Paprius Icon Theme" \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/audio-volume-high.svg b/lisp/ui/themes/icons/numix/audio-volume-high.svg new file mode 100644 index 000000000..65ee60f73 --- /dev/null +++ b/lisp/ui/themes/icons/numix/audio-volume-high.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/themes/icons/numix/audio-volume-low.svg b/lisp/ui/themes/icons/numix/audio-volume-low.svg new file mode 100644 index 000000000..d9e694cce --- /dev/null +++ b/lisp/ui/themes/icons/numix/audio-volume-low.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/themes/icons/numix/audio-volume-medium.svg b/lisp/ui/themes/icons/numix/audio-volume-medium.svg new file mode 100644 index 000000000..4286fc8f8 --- /dev/null +++ b/lisp/ui/themes/icons/numix/audio-volume-medium.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/themes/icons/numix/audio-volume-muted.svg b/lisp/ui/themes/icons/numix/audio-volume-muted.svg new file mode 100644 index 000000000..d19c73603 --- /dev/null +++ b/lisp/ui/themes/icons/numix/audio-volume-muted.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/themes/icons/symbolic/audio-volume-high.svg b/lisp/ui/themes/icons/symbolic/audio-volume-high.svg new file mode 100644 index 000000000..25a7d7a1b --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/audio-volume-high.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/symbolic/audio-volume-low.svg b/lisp/ui/themes/icons/symbolic/audio-volume-low.svg new file mode 100644 index 000000000..84de0effb --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/audio-volume-low.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/themes/icons/symbolic/audio-volume-medium.svg b/lisp/ui/themes/icons/symbolic/audio-volume-medium.svg new file mode 100644 index 000000000..9adb0a84e --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/audio-volume-medium.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/themes/icons/symbolic/audio-volume-muted.svg b/lisp/ui/themes/icons/symbolic/audio-volume-muted.svg new file mode 100644 index 000000000..9a97be2a3 --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/audio-volume-muted.svg @@ -0,0 +1,3 @@ + + + From a74c42316ee8856bf38432952fa0a0eb0a3ef911 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 7 Mar 2018 14:52:05 +0100 Subject: [PATCH 090/333] Overhaul of the logging system Add: missing icons Update: use the colored icons by default Update: new icons for generic-fade Fix: the ReplayGain plugin now works with the new relative paths --- Pipfile | 21 ++ Pipfile.lock | 187 +++++++++++++++ lisp/application.py | 28 ++- lisp/core/action.py | 10 +- lisp/core/actions_handler.py | 30 ++- lisp/core/configuration.py | 15 +- lisp/core/decorators.py | 2 +- lisp/core/loading.py | 10 +- lisp/core/memento_model.py | 12 +- lisp/core/memento_model_actions.py | 26 +-- lisp/core/session.py | 3 +- lisp/core/signal.py | 4 +- lisp/cues/cue_memento_model.py | 48 ++++ lisp/cues/cue_model.py | 1 + lisp/default.json | 27 ++- lisp/layouts/list_layout/cue_list_model.py | 1 - lisp/layouts/list_layout/listwidgets.py | 4 + lisp/main.py | 17 +- lisp/plugins/__init__.py | 36 ++- lisp/plugins/action_cues/__init__.py | 26 +-- lisp/plugins/action_cues/osc_cue.py | 47 ++-- lisp/plugins/controller/controller.py | 16 +- lisp/plugins/controller/protocols/osc.py | 27 +-- .../plugins/gst_backend/elements/jack_sink.py | 14 +- lisp/plugins/osc/osc_delegate.py | 87 +++++++ lisp/plugins/osc/osc_server.py | 12 +- lisp/plugins/osc/osc_settings.py | 3 +- lisp/plugins/remote/controller.py | 26 +-- lisp/plugins/rename_cues/rename_ui.py | 21 +- lisp/plugins/replay_gain/replay_gain.py | 139 +++++++----- lisp/plugins/synchronizer/peers_dialog.py | 10 +- lisp/plugins/synchronizer/synchronizer.py | 13 +- lisp/plugins/timecode/cue_tracker.py | 5 +- lisp/plugins/timecode/protocols/artnet.py | 16 +- lisp/ui/cuelistdialog.py | 10 +- lisp/ui/elogging.py | 76 ------- lisp/ui/logging/__init__.py | 0 lisp/ui/logging/common.py | 57 +++++ lisp/ui/logging/dialog.py | 140 ++++++++++++ lisp/ui/logging/handler.py | 67 ++++++ lisp/ui/logging/models.py | 165 ++++++++++++++ lisp/ui/logging/status.py | 85 +++++++ lisp/ui/logging/viewer.py | 140 ++++++++++++ lisp/ui/mainwindow.py | 43 +++- lisp/ui/qdelegates.py | 68 +----- lisp/ui/themes/dark/theme.qss | 4 + lisp/ui/themes/icons/lisp/led-error.svg | 132 +---------- lisp/ui/themes/icons/lisp/led-off.svg | 132 +---------- lisp/ui/themes/icons/lisp/led-pause.svg | 132 +---------- lisp/ui/themes/icons/lisp/led-running.svg | 132 +---------- lisp/ui/themes/icons/lisp/mixer-handle.svg | 213 +----------------- lisp/ui/themes/icons/numix/NOTE | 1 - .../themes/icons/numix/audio-volume-high.svg | 10 +- .../themes/icons/numix/audio-volume-low.svg | 8 +- .../icons/numix/audio-volume-medium.svg | 8 +- .../themes/icons/numix/audio-volume-muted.svg | 8 +- lisp/ui/themes/icons/numix/cue-interrupt.svg | 8 +- lisp/ui/themes/icons/numix/cue-pause.svg | 7 +- lisp/ui/themes/icons/numix/cue-start.svg | 4 +- lisp/ui/themes/icons/numix/cue-stop.svg | 4 +- lisp/ui/themes/icons/numix/dialog-error.svg | 3 + .../themes/icons/numix/dialog-information.svg | 3 + .../ui/themes/icons/numix/dialog-question.svg | 3 + lisp/ui/themes/icons/numix/dialog-warning.svg | 1 + lisp/ui/themes/icons/numix/fadein-generic.svg | 70 +----- .../ui/themes/icons/numix/fadeout-generic.svg | 70 +----- lisp/ui/themes/icons/numix/go-next.svg | 4 + lisp/ui/themes/icons/numix/go-previous.svg | 6 + lisp/ui/themes/icons/numix/help-info.svg | 9 + lisp/ui/themes/icons/numix/media-eject.svg | 7 +- lisp/ui/themes/icons/numix/media-record.svg | 4 +- .../icons/numix/media-seek-backward.svg | 8 +- .../themes/icons/numix/media-seek-forward.svg | 8 +- .../icons/numix/media-skip-backward.svg | 11 +- .../themes/icons/numix/media-skip-forward.svg | 11 +- .../icons/symbolic/audio-volume-high.svg | 4 +- .../icons/symbolic/audio-volume-low.svg | 5 +- .../icons/symbolic/audio-volume-medium.svg | 5 +- .../icons/symbolic/audio-volume-muted.svg | 4 +- lisp/ui/themes/icons/symbolic/auto-follow.svg | 91 +------- lisp/ui/themes/icons/symbolic/auto-next.svg | 74 +----- .../themes/icons/symbolic/cue-interrupt.svg | 4 +- lisp/ui/themes/icons/symbolic/cue-pause.svg | 5 +- lisp/ui/themes/icons/symbolic/cue-start.svg | 4 +- lisp/ui/themes/icons/symbolic/cue-stop.svg | 4 +- .../ui/themes/icons/symbolic/dialog-error.svg | 11 + .../icons/symbolic/dialog-information.svg | 11 + .../themes/icons/symbolic/dialog-question.svg | 8 + .../themes/icons/symbolic/dialog-warning.svg | 8 + .../themes/icons/symbolic/fadein-generic.svg | 75 +----- .../themes/icons/symbolic/fadeout-generic.svg | 75 +----- lisp/ui/themes/icons/symbolic/go-next.svg | 3 + lisp/ui/themes/icons/symbolic/go-previous.svg | 3 + lisp/ui/themes/icons/symbolic/help-info.svg | 3 + lisp/ui/themes/icons/symbolic/media-eject.svg | 4 +- .../ui/themes/icons/symbolic/media-record.svg | 4 +- .../icons/symbolic/media-seek-backward.svg | 5 +- .../icons/symbolic/media-seek-forward.svg | 5 +- .../icons/symbolic/media-skip-backward.svg | 4 +- .../icons/symbolic/media-skip-forward.svg | 4 +- lisp/ui/ui_utils.py | 6 +- lisp/ui/widgets/qdbmeter.py | 16 +- setup.py | 3 + 103 files changed, 1511 insertions(+), 1753 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 lisp/cues/cue_memento_model.py create mode 100644 lisp/plugins/osc/osc_delegate.py delete mode 100644 lisp/ui/elogging.py create mode 100644 lisp/ui/logging/__init__.py create mode 100644 lisp/ui/logging/common.py create mode 100644 lisp/ui/logging/dialog.py create mode 100644 lisp/ui/logging/handler.py create mode 100644 lisp/ui/logging/models.py create mode 100644 lisp/ui/logging/status.py create mode 100644 lisp/ui/logging/viewer.py delete mode 100644 lisp/ui/themes/icons/numix/NOTE create mode 100644 lisp/ui/themes/icons/numix/dialog-error.svg create mode 100644 lisp/ui/themes/icons/numix/dialog-information.svg create mode 100644 lisp/ui/themes/icons/numix/dialog-question.svg create mode 100644 lisp/ui/themes/icons/numix/dialog-warning.svg create mode 100644 lisp/ui/themes/icons/numix/go-next.svg create mode 100644 lisp/ui/themes/icons/numix/go-previous.svg create mode 100644 lisp/ui/themes/icons/numix/help-info.svg create mode 100644 lisp/ui/themes/icons/symbolic/dialog-error.svg create mode 100644 lisp/ui/themes/icons/symbolic/dialog-information.svg create mode 100644 lisp/ui/themes/icons/symbolic/dialog-question.svg create mode 100644 lisp/ui/themes/icons/symbolic/dialog-warning.svg create mode 100644 lisp/ui/themes/icons/symbolic/go-next.svg create mode 100644 lisp/ui/themes/icons/symbolic/go-previous.svg create mode 100644 lisp/ui/themes/icons/symbolic/help-info.svg diff --git a/Pipfile b/Pipfile new file mode 100644 index 000000000..876dc8d86 --- /dev/null +++ b/Pipfile @@ -0,0 +1,21 @@ +[[source]] + +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + + +[packages] + +sortedcontainers = "*" +mido = "*" +python-rtmidi = "*" +jack-client = "*" +pyliblo = "*" +"pyqt5" = "*" +pygobject = "*" + + +[dev-packages] + +cython = "*" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 000000000..26eb3b57f --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,187 @@ +{ + "_meta": { + "hash": { + "sha256": "7758b232e7b383ea49c6f98a167e2d857ab7d1fa6c95efaed803bc014482cc7f" + }, + "host-environment-markers": { + "implementation_name": "cpython", + "implementation_version": "3.6.4", + "os_name": "posix", + "platform_machine": "x86_64", + "platform_python_implementation": "CPython", + "platform_release": "4.15.5-1-ARCH", + "platform_system": "Linux", + "platform_version": "#1 SMP PREEMPT Thu Feb 22 22:15:20 UTC 2018", + "python_full_version": "3.6.4", + "python_version": "3.6", + "sys_platform": "linux" + }, + "pipfile-spec": 6, + "requires": {}, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "cffi": { + "hashes": [ + "sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50", + "sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596", + "sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef", + "sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743", + "sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f", + "sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31", + "sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04", + "sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6", + "sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3", + "sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6", + "sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b", + "sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca", + "sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e", + "sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb", + "sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd", + "sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1", + "sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917", + "sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359", + "sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f", + "sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95", + "sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801", + "sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257", + "sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184", + "sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc", + "sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085", + "sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93", + "sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4" + ], + "version": "==1.11.5" + }, + "jack-client": { + "hashes": [ + "sha256:46998c8065bcfa1b6c9faa0005786be2175f21bdced3a5105462489656b1b063", + "sha256:58eb7004e0cbfd769b4ad352dca1faeb5f25a764f55c173363d09f08c7d7b64d" + ], + "version": "==0.4.4" + }, + "mido": { + "hashes": [ + "sha256:64b9d1595da8f319bff2eb866f9181257d3670a7803f7e38415f22c03a577560", + "sha256:35142874d4521dc5fcebcdc3a645df87cb0ecad129dd031cbca391e2d052313f" + ], + "version": "==1.2.8" + }, + "pycairo": { + "hashes": [ + "sha256:5bb321e5d4f8b3a51f56fc6a35c143f1b72ce0d748b43d8b623596e8215f01f7" + ], + "version": "==1.16.3" + }, + "pycparser": { + "hashes": [ + "sha256:99a8ca03e29851d96616ad0404b4aad7d9ee16f25c9f9708a11faf2810f7b226" + ], + "version": "==2.18" + }, + "pygobject": { + "hashes": [ + "sha256:d5ff822fc06d5a6cba81b7f1bb1192c2f64715e847da26e907040b0eb43ee068" + ], + "version": "==3.27.4" + }, + "pyliblo": { + "hashes": [ + "sha256:fc67f1950b827272b00f9f0dc4ed7113c0ccef0c1c09e9976dead40ebbf1798f" + ], + "version": "==0.10.0" + }, + "pyqt5": { + "hashes": [ + "sha256:9c17ab3974c1fc7bbb04cc1c9dae780522c0ebc158613f3025fccae82227b5f7", + "sha256:1e652910bd1ffd23a3a48c510ecad23a57a853ed26b782cd54b16658e6f271ac", + "sha256:f6035baa009acf45e5f460cf88f73580ad5dc0e72330029acd99e477f20a5d61", + "sha256:4db7113f464c733a99fcb66c4c093a47cf7204ad3f8b3bda502efcc0839ac14b" + ], + "version": "==5.10.1" + }, + "python-rtmidi": { + "hashes": [ + "sha256:887fd11551f77b27d566235916723b065c30d1e3959c9d665131a7919c012df2", + "sha256:dfba865b1e7c7793c7d222b1922ac5f901f5545af71bf70e06d88a77482aa20c", + "sha256:9d0fddaf38c4408e6f8466d6579ce3cf7b1da9a878f26454bc62fdf66228ea65", + "sha256:5e1bb542ceebfd44290ecafa6367ade1a712cc9a73b61e843b2fa802105a2759", + "sha256:659fb40e69cbc57cc7d970cd7bcd85e97ee2eb18992e147ed7bb690a83a7bd3f", + "sha256:c0f54d1e6dd1c04958130611d0fe6a821194175847d9f696824d011dffce2092", + "sha256:7c82bed1b0cb5e11495bc3a779500c3d5cde6d84f68416f276622b0952174d25", + "sha256:1187b2a3703d4ede2b02c0fccd395648a3a468d3cd57d7932f445ef1bfa9fc5d", + "sha256:80c6d31508ec8435b4dc1d4a979aa6046edb83413a837b790f33f65f176ad018", + "sha256:8685ffc012e007f10ddb9ed04a3cf1648d26a8ef95bd261bfeca0b90d11c971f", + "sha256:d82eb82e0b270f75375e3d5f9f45cb75950485700e6a3862192d0c121c802b0e" + ], + "version": "==1.1.0" + }, + "sip": { + "hashes": [ + "sha256:d9023422127b94d11c1a84bfa94933e959c484f2c79553c1ef23c69fe00d25f8", + "sha256:e72955e12f4fccf27aa421be383453d697b8a44bde2cc26b08d876fd492d0174", + "sha256:52074f7cb5488e8b75b52f34ec2230bc75d22986c7fe5cd3f2d266c23f3349a7", + "sha256:5ff887a33839de8fc77d7f69aed0259b67a384dc91a1dc7588e328b0b980bde2", + "sha256:09f9a4e6c28afd0bafedb26ffba43375b97fe7207bd1a0d3513f79b7d168b331", + "sha256:cf98150a99e43fda7ae22abe655b6f202e491d6291486548daa56cb15a2fcf85", + "sha256:74da4ddd20c5b35c19cda753ce1e8e1f71616931391caeac2de7a1715945c679", + "sha256:265ddf69235dd70571b7d4da20849303b436192e875ce7226be7144ca702a45c", + "sha256:97bb93ee0ef01ba90f57be2b606e08002660affd5bc380776dd8b0fcaa9e093a", + "sha256:7d69e9cf4f8253a3c0dfc5ba6bb9ac8087b8239851f22998e98cb35cfe497b68", + "sha256:1bb10aac55bd5ab0e2ee74b3047aa2016cfa7932077c73f602a6f6541af8cd51", + "sha256:105edaaa1c8aa486662226360bd3999b4b89dd56de3e314d82b83ed0587d8783" + ], + "version": "==4.19.8" + }, + "sortedcontainers": { + "hashes": [ + "sha256:fb9e22cd6ee4b459f0d7b9b4189b19631031c72ac05715563139162014c13672", + "sha256:844daced0f20d75c02ce53f373d048ea2e401ad8a7b3a4c43b2aa544b569efb3" + ], + "version": "==1.5.9" + } + }, + "develop": { + "cython": { + "hashes": [ + "sha256:aba08b4db9163c52a1106efa8b19c5a4e519e5f45644551bb1358ec189a14505", + "sha256:d17a6a689bb3e6c0f280620dfddb1b91ffb8fcdae3b8707bdf6285df4100d011", + "sha256:82bbbeadbc579b6f2a7365db2d3ea21043699e424cfcca434b3baa11309260ae", + "sha256:cefa9356770d71176c87ab60f6fdb186ec9cbc6399a6c2de1017852505d44963", + "sha256:83237d27b6dec0f6b76406de5be1cf249cffb4377079acc3f3dfa72de1e307cf", + "sha256:7fbd49b1bd5240feb69ec4c67f4c76fc9dfdfc472d642833668ecaad0e14673d", + "sha256:abfb2426266d806a7d794a8ecbd0fbb385b48bddf2db6ac9a2e95d48e2c603bd", + "sha256:d8e40814fefc300bc64251f5d6bd8988e49442d7ded0e5702b5366323a035afc", + "sha256:8a4733edf3ca1c7dd9c7e1289dd5974657f1147642ef8ded1e57a1266922e32a", + "sha256:fb5b8978b937677a32b5aecc72231560d29243622a298a5c31183b156f9f42e3", + "sha256:e30fd7a32cbe7fe6c186deefef03670bf75d76ead23418f8c91519899be5518e", + "sha256:b9bc1d7e681c5a7560280a5aa8000f736eb0e47efacb7b6206275ed004595a92", + "sha256:251bb50e36044ad26ff45960a60cb75868757fd46900eaf00129a4af3a0d5fcc", + "sha256:e86984b90a0176bafb5890f1f14a2962317eee9e95e0a917e6b06a5b0c764e25", + "sha256:4c958bd9e50fcd316830561f0725b3ccf893de328566c960b7bb38424d3b983d", + "sha256:a51c3a1c948664874f91636ba6169bca4dc68f5fac7b980fcb9c769a8d1f9ebc", + "sha256:28c938bc970d2b5fe296460cf6ed19d0328776a6295f68a79057060e60bfc82f", + "sha256:38a9e5afc5e096f26bfe5c4b458541e455032028cb5356a734ba235868b19823", + "sha256:3914ae1268334208e33b952e9f447e0d9f43decd562f939d7d07e8589d0473ba", + "sha256:f441347d0d07b4cb9ca36bda70c3aa98a7484a262b8f44d0f82b70dbb5e11b47", + "sha256:f01e79f4123205d0f99b97199b53345cb6378974e738792c620957c6223fded9", + "sha256:56a9f99b42ad6ad1fdb7705c16aa69b2e1a4ac34f760286a6de64c5719499da7", + "sha256:9f8bbd876c4d68cb12b9de9dc6734c17ca295e4759e684417fd7f3dd96d3ed2c", + "sha256:7bc07c32703e4e63f2f60c729b0ae99acbccbfd3ae4c64a322a75482d915d4af", + "sha256:8afdc3751c6269ef46879ca6ff32a1362f298c77f2cf0723c66ae65c35be5db0", + "sha256:264a68823751fe2a18d0b14dd44b614aecd508bd01453902921c23fd5be13d4c", + "sha256:ca81d4b6f511f1e0d2a1c76ad5b3a4a3a0edf683f27600d4a86da55cb134372a", + "sha256:26e24164d59e92280fa759a8a361c68490763ec230307e21990020bdeb87de50", + "sha256:5e0006c3a2849b7a36d2cb23bcf14753b6ffc3eafac48fa29fafad942cfa4627", + "sha256:6a00512de1f2e3ce66ba35c5420babaef1fe2d9c43a8faab4080b0dbcc26bc64" + ], + "version": "==0.27.3" + } + } +} diff --git a/lisp/application.py b/lisp/application.py index 8d431111d..6eec758eb 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -18,9 +18,9 @@ # along with Linux Show Player. If not, see . import json +import logging from os.path import exists -import objgraph as objgraph from PyQt5.QtWidgets import QDialog, qApp from lisp import layouts @@ -32,7 +32,6 @@ from lisp.cues.cue_factory import CueFactory from lisp.cues.cue_model import CueModel from lisp.cues.media_cue import MediaCue -from lisp.ui import elogging from lisp.ui.layoutselect import LayoutSelect from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.app_settings import AppSettings @@ -44,6 +43,8 @@ from lisp.ui.settings.pages.media_cue_settings import MediaCueSettings from lisp.ui.settings.pages.plugins_settings import PluginsSettings +logger = logging.getLogger(__name__) + class Application(metaclass=Singleton): def __init__(self, app_conf): @@ -52,7 +53,7 @@ def __init__(self, app_conf): self.session_created = Signal() self.session_before_finalize = Signal() - self.__main_window = MainWindow() + self.__main_window = MainWindow(self.conf) self.__cue_model = CueModel() self.__session = None @@ -70,8 +71,6 @@ def __init__(self, app_conf): self.__main_window.new_session.connect(self._new_session_dialog) self.__main_window.save_session.connect(self._save_to_file) self.__main_window.open_session.connect(self._load_from_file) - # Show the mainWindow maximized - self.__main_window.showMaximized() @property def session(self): @@ -93,6 +92,9 @@ def cue_model(self): return self.__cue_model def start(self, session_file=''): + # Show the mainWindow maximized + self.__main_window.showMaximized() + if exists(session_file): self._load_from_file(session_file) else: @@ -127,8 +129,8 @@ def _new_session_dialog(self): self.finalize() qApp.quit() exit(0) - except Exception as e: - elogging.exception('Startup error', e) + except Exception: + logger.critical('Startup error', exc_info=True) qApp.quit() exit(-1) @@ -193,11 +195,15 @@ def _load_from_file(self, session_file): cue = CueFactory.create_cue(cue_type, cue_id=cue_id) cue.update_properties(cues_dict) self.__cue_model.add(cue) - except Exception as e: - elogging.exception('Unable to create the cue', e) + except Exception: + name = cues_dict.get('name', 'No name') + logging.exception( + 'Unable to create the cue "{}"'.format(name)) MainActionsHandler.set_saved() self.__main_window.update_window_title() - except Exception as e: - elogging.exception('Error during file reading', e) + except Exception: + logging.exception( + 'Error while reading the session file "{}"' + .format(session_file)) self._new_session_dialog() diff --git a/lisp/core/action.py b/lisp/core/action.py index e858c5b12..7aa6f35bd 100644 --- a/lisp/core/action.py +++ b/lisp/core/action.py @@ -28,7 +28,7 @@ class Action: An action could provide, via the "log" function, a simple log message. .. warning:: - Actions may reference external objects, preventing gc. + Actions may keep reference to external objects. """ __slots__ = () @@ -49,9 +49,13 @@ def redo(self): self.do() def log(self): - """Used for logging + """Return a short message to describe what the action does. + + The method should return a message that can be used for do/undo/redo + generically, an handler will care about adding context to the message. + + The log message should be user-friendly and localized. - :return: A log message :rtype: str """ return '' diff --git a/lisp/core/actions_handler.py b/lisp/core/actions_handler.py index d470a1490..975dd6c26 100644 --- a/lisp/core/actions_handler.py +++ b/lisp/core/actions_handler.py @@ -22,10 +22,16 @@ from lisp.core.action import Action from lisp.core.signal import Signal +from lisp.ui.ui_utils import translate + +logger = logging.getLogger(__name__) class ActionsHandler: """Provide a classic undo/redo mechanism based on stacks.""" + DO_ACTION_STR = '{}' + UNDO_ACTION_STR = translate('Actions', 'Undo: {}') + REDO_ACTION_STR = translate('Actions', 'Redo: {}') def __init__(self, stack_size=-1): super().__init__() @@ -44,7 +50,7 @@ def __init__(self, stack_size=-1): self._saved_action = None def clear(self): - """Clear the `undo` and `redo` stacks.""" + """Clear the `undo`, `redo` stacks and the save status.""" self._undo.clear() self._redo.clear() self._saved_action = None @@ -53,6 +59,7 @@ def do_action(self, action: Action): """Execute the action, and add it the `undo` stack. When an action is executed: + * the `do()` method is called * is logged * is appended to the `undo` stack * the `redo` stack is cleared to maintain consistency @@ -60,7 +67,7 @@ def do_action(self, action: Action): """ action.do() - self._logging(action, 'Last action: ') + self._logging(action, ActionsHandler.DO_ACTION_STR) self._undo.append(action) # Clean the redo stack for maintain consistency self._redo.clear() @@ -72,6 +79,7 @@ def undo_action(self): When an action is undone: * is removed from the `undo` stack + * the `undo` method is called * is logged * is appended to the `redo` stack * the signal `action_undone` is emitted @@ -80,7 +88,7 @@ def undo_action(self): action = self._undo.pop() action.undo() - self._logging(action, 'Undo: ') + self._logging(action, ActionsHandler.UNDO_ACTION_STR) self._redo.append(action) self.action_undone.emit(action) @@ -90,6 +98,7 @@ def redo_action(self): When an action is redone: * is remove from the `redo` stack + * the `redo` method is called * is logged * is added to the `undo` stack * the `action_redone` signal is emitted @@ -98,7 +107,7 @@ def redo_action(self): action = self._redo.pop() action.redo() - self._logging(action, 'Redo: ') + self._logging(action, ActionsHandler.REDO_ACTION_STR) self._undo.append(action) self.action_redone.emit(action) @@ -114,16 +123,15 @@ def is_saved(self) -> bool: """ if self._undo: return self._undo[-1] is self._saved_action - else: - return True + + return True @staticmethod - def _logging(action: Action, pre: str): + def _logging(action: Action, action_str: str): message = action.log() - if message.strip() == '': - message = type(action).__name__ - - logging.info(pre + message) + if message: + logger.info(action_str.format(message)) +# TODO: remove this MainActionsHandler = ActionsHandler() # "global" action-handler diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index 97050f8c2..e9aa125cf 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -36,6 +36,8 @@ _UNSET = object() +logger = logging.getLogger(__name__) + class Configuration(metaclass=ABCMeta): """ABC for a dictionary-based configuration object. @@ -62,8 +64,8 @@ def get(self, path, default=_UNSET): return node[key] except (KeyError, TypeError) as e: if default is not _UNSET: - logging.warning( - 'CONFIG: invalid key "{}" in get operation, default used.' + logger.warning( + 'Invalid key "{}" in get operation, default used.' .format(path) ) return default @@ -77,8 +79,8 @@ def set(self, path, value): self.changed.emit(path, old, value) except (KeyError, TypeError): - logging.warning( - 'CONFIG: invalid key "{}" in set operation, ignored.' + logger.warning( + 'Invalid key "{}" in set operation, ignored.' .format(path) ) @@ -194,9 +196,8 @@ def _check_file(self): # Copy the new configuration copyfile(self.default_path, self.user_path) - logging.info('CONFIG: new configuration installed at {}'.format( - self.user_path - )) + logger.info( + 'New configuration installed at {}'.format(self.user_path)) @staticmethod def _read_json(path): diff --git a/lisp/core/decorators.py b/lisp/core/decorators.py index c6a44123b..048341882 100644 --- a/lisp/core/decorators.py +++ b/lisp/core/decorators.py @@ -156,7 +156,7 @@ def wrapped(*args, **kwargs): try: return target(*args, **kwargs) except Exception: - logging.warning('Exception suppressed:\n' + traceback.format_exc()) + logging.warning('Exception suppressed.', exc_info=True) return wrapped diff --git a/lisp/core/loading.py b/lisp/core/loading.py index b2725eb3c..58dbd22da 100644 --- a/lisp/core/loading.py +++ b/lisp/core/loading.py @@ -20,13 +20,14 @@ import logging import os.path import re -import traceback try: from os import scandir except ImportError: from scandir import scandir +logger = logging.getLogger(__name__) + class load_classes: """Generator for iterating over classes in a package. @@ -109,10 +110,9 @@ def load(self): if hasattr(module, name): cls = getattr(module, name) yield (name, cls) - - except(ImportError, Exception): - logging.warning('Cannot load module: {0}'.format(mod_name)) - logging.debug(traceback.format_exc()) + except Exception: + logger.warning( + 'Cannot load module: {0}'.format(mod_name), exc_info=True) def module_to_class_name(mod_name, pre='', suf=''): diff --git a/lisp/core/memento_model.py b/lisp/core/memento_model.py index 4a73a424f..c941487ce 100644 --- a/lisp/core/memento_model.py +++ b/lisp/core/memento_model.py @@ -37,6 +37,8 @@ class MementoModel(ReadOnlyProxyModel): def __init__(self, model, handler=None): super().__init__(model) + self._add_action = AddItemAction + self._remove_action = RemoveItemAction if handler is None: handler = MainActionsHandler @@ -46,11 +48,11 @@ def __init__(self, model, handler=None): def _item_added(self, item): if not self._locked: - self._handler.do_action(AddItemAction(self, self.model, item)) + self._handler.do_action(self._add_action(self, self.model, item)) def _item_removed(self, item): if not self._locked: - self._handler.do_action(RemoveItemAction(self, self.model, item)) + self._handler.do_action(self._remove_action(self, self.model, item)) def lock(self): self._locked = True @@ -67,9 +69,11 @@ class MementoModelAdapter(MementoModel): def __init__(self, model_adapter, handler=None): super().__init__(model_adapter, handler) + self._move_action = MoveItemAction + self.model.item_moved.connect(self._item_moved) def _item_moved(self, old_index, new_index): if not self._locked: - self._handler.do_action(MoveItemAction(self, self.model, old_index, - new_index)) + self._handler.do_action( + self._move_action(self, self.model, old_index, new_index)) diff --git a/lisp/core/memento_model_actions.py b/lisp/core/memento_model_actions.py index da62ec60b..7de28b8d9 100644 --- a/lisp/core/memento_model_actions.py +++ b/lisp/core/memento_model_actions.py @@ -60,45 +60,45 @@ def __redo__(self): class AddItemAction(MementoAction): - __slots__ = '__item' + __slots__ = '_item' def __init__(self, m_model, model, item): super().__init__(m_model, model) - self.__item = item + self._item = item def __undo__(self): - self._model.remove(self.__item) + self._model.remove(self._item) def __redo__(self): - self._model.add(self.__item) + self._model.add(self._item) class RemoveItemAction(MementoAction): - __slots__ = '__item' + __slots__ = '_item' def __init__(self, m_model, model, item): super().__init__(m_model, model) - self.__item = item + self._item = item def __undo__(self): - self._model.add(self.__item) + self._model.add(self._item) def __redo__(self): - self._model.remove(self.__item) + self._model.remove(self._item) class MoveItemAction(MementoAction): - __slots__ = ('__old_index', '__new_index') + __slots__ = ('_old_index', '_new_index') def __init__(self, m_model, model_adapter, old_index, new_index): super().__init__(m_model, model_adapter) - self.__old_index = old_index - self.__new_index = new_index + self._old_index = old_index + self._new_index = new_index def __undo__(self): - self._model.move(self.__new_index, self.__old_index) + self._model.move(self._new_index, self._old_index) def __redo__(self): - self._model.move(self.__old_index, self.__new_index) + self._model.move(self._old_index, self._new_index) diff --git a/lisp/core/session.py b/lisp/core/session.py index 50362b3ea..53e411a15 100644 --- a/lisp/core/session.py +++ b/lisp/core/session.py @@ -22,6 +22,7 @@ from lisp.core.has_properties import Property, HasInstanceProperties from lisp.core.memento_model import MementoModelAdapter from lisp.core.signal import Signal +from lisp.cues.cue_memento_model import CueMementoAdapter class Session(HasInstanceProperties): @@ -36,7 +37,7 @@ def __init__(self, layout): self.__layout = layout self.__cue_model = layout.cue_model - self.__memento_model = MementoModelAdapter(layout.model_adapter) + self.__memento_model = CueMementoAdapter(layout.model_adapter) @property def cue_model(self): diff --git a/lisp/core/signal.py b/lisp/core/signal.py index 44e99ba3c..605783f19 100644 --- a/lisp/core/signal.py +++ b/lisp/core/signal.py @@ -33,6 +33,8 @@ __all__ = ['Signal', 'Connection'] +logger = logging.getLogger(__name__) + def slot_id(slot_callable): """Return the id of the given slot_callable. @@ -72,7 +74,7 @@ def call(self, *args, **kwargs): else: self._reference()(*args, **kwargs) except Exception: - logging.error(traceback.format_exc()) + logger.warning('An error occurred.', exc_info=True) def is_alive(self): return self._reference() is not None diff --git a/lisp/cues/cue_memento_model.py b/lisp/cues/cue_memento_model.py new file mode 100644 index 000000000..0585e6b1d --- /dev/null +++ b/lisp/cues/cue_memento_model.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from lisp.core.memento_model import MementoModelAdapter +from lisp.core.memento_model_actions import AddItemAction, MoveItemAction, \ + RemoveItemAction + + +class CueMementoAdapter(MementoModelAdapter): + def __init__(self, model_adapter, handler=None): + super().__init__(model_adapter, handler) + self._add_action = CueAddAction + self._remove_action = CueRemoveAction + self._move_action = CueMoveAction + + +# TODO: keep only the cue-properties for the cue-removed/added action ?? + +class CueAddAction(AddItemAction): + def log(self): + return 'Add cue "{}"'.format(self._item.name) + + +class CueRemoveAction(RemoveItemAction): + def log(self): + return 'Remove cue "{}"'.format(self._item.name) + + +class CueMoveAction(MoveItemAction): + def log(self): + return 'Move cue from "{}" to "{}"'.format( + self._old_index, self._new_index) diff --git a/lisp/cues/cue_model.py b/lisp/cues/cue_model.py index 9712d6333..0bb32153b 100644 --- a/lisp/cues/cue_model.py +++ b/lisp/cues/cue_model.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . + from lisp.core.model import Model from lisp.cues.cue import Cue diff --git a/lisp/default.json b/lisp/default.json index d59307066..f5265c64e 100644 --- a/lisp/default.json +++ b/lisp/default.json @@ -1,5 +1,5 @@ { - "_version_": "0.6dev.5", + "_version_": "0.6dev.8", "cue": { "fadeActionDuration": 3, "fadeActionType": "Linear", @@ -8,7 +8,30 @@ }, "theme": { "theme": "Dark", - "icons": "Symbolic" + "icons": "Numix" + }, + "logging": { + "limit": 10000, + "msg_format": "%(asctime)s.%(msecs)03d\t%(name)s\t%(levelname)s\t%(message)s", + "date_format": "%H:%M:%S", + "backgrounds": { + "DEBUG": [230, 230, 230, 200], + "INFO": [80, 255, 65, 200], + "WARNING": [255, 200, 0, 200], + "ERROR": [255, 65, 0, 200], + "CRITICAL": [255, 0, 0, 200] + }, + "foregrounds": { + "DEBUG": [0, 0, 0], + "INFO": [0, 0, 0], + "WARNING": [0, 0, 0], + "ERROR": [255, 255, 255], + "CRITICAL": [255, 255, 255] + }, + "viewer": { + "visibleLevels": ["WARNING", "ERROR", "CRITICAL"], + "visibleColumns": ["asctime", "name", "message"] + } }, "backend": { "default": "gst" diff --git a/lisp/layouts/list_layout/cue_list_model.py b/lisp/layouts/list_layout/cue_list_model.py index fe269ebdd..4e516bfe7 100644 --- a/lisp/layouts/list_layout/cue_list_model.py +++ b/lisp/layouts/list_layout/cue_list_model.py @@ -19,7 +19,6 @@ from lisp.core.model_adapter import ModelAdapter from lisp.core.proxy_model import ReadOnlyProxyModel -from lisp.cues.media_cue import MediaCue class CueListModel(ModelAdapter): diff --git a/lisp/layouts/list_layout/listwidgets.py b/lisp/layouts/list_layout/listwidgets.py index 4935a3c41..5d5dc2ffb 100644 --- a/lisp/layouts/list_layout/listwidgets.py +++ b/lisp/layouts/list_layout/listwidgets.py @@ -18,6 +18,7 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import QSize +from PyQt5.QtGui import QFont from PyQt5.QtWidgets import QLabel, QProgressBar from lisp.core.signal import Connection @@ -99,6 +100,9 @@ def __init__(self, cue, *args): self.setObjectName('ListTimeWidget') self.setValue(0) self.setTextVisible(True) + font = QFont('Monospace') + font.setStyleHint(QFont.Monospace) + self.setFont(font) self.show_zero_duration = False self.accurate_time = True diff --git a/lisp/main.py b/lisp/main.py index b180d4b6a..32860faf6 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -58,10 +58,11 @@ def main(): log = logging.WARNING logging.basicConfig( - format='%(asctime)s.%(msecs)03d %(levelname)s:: %(message)s', + format='%(asctime)s.%(msecs)03d\t%(name)s\t%(levelname)s\t%(message)s', datefmt='%H:%M:%S', level=log, ) + logger = logging.getLogger(__name__) # Create (if not present) user directory os.makedirs(os.path.dirname(USER_DIR), exist_ok=True) @@ -79,7 +80,7 @@ def main(): if locale: QLocale().setDefault(QLocale(locale)) - logging.info('Using {} locale'.format(QLocale().name())) + logger.info('Using {} locale'.format(QLocale().name())) # Qt platform translation install_translation( @@ -91,20 +92,18 @@ def main(): try: theme = app_conf['theme.theme'] themes.THEMES[theme].apply(qt_app) - logging.info('Using "{}" theme'.format(theme)) + logger.info('Using "{}" theme'.format(theme)) except Exception: - logging.error('Unable to load theme') - logging.debug(traceback.format_exc()) + logger.exception('Unable to load theme.') # Set the global IconTheme try: icon_theme_name = app_conf['theme.icons'] icon_theme = themes.ICON_THEMES[icon_theme_name] icon_theme.set_theme(icon_theme) - logging.info('Using "{}" icon theme'.format(icon_theme_name)) + logger.info('Using "{}" icon theme'.format(icon_theme_name)) except Exception: - logging.error('Unable to load icon theme') - logging.debug(traceback.format_exc()) + logger.exception('Unable to load icon theme.') # Create the application lisp_app = Application(app_conf) diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index 70572e61d..bc0e8a050 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,12 +18,12 @@ # along with Linux Show Player. If not, see . import inspect +import logging from os import path from lisp import USER_DIR from lisp.core.configuration import JSONFileConfiguration from lisp.core.loading import load_classes -from lisp.ui import elogging from lisp.ui.ui_utils import install_translation PLUGINS = {} @@ -31,6 +31,8 @@ FALLBACK_CONFIG_PATH = path.join(path.dirname(__file__), 'default.json') +logger = logging.getLogger(__name__) + def load_plugins(application): """Load and instantiate available plugins.""" @@ -56,8 +58,8 @@ def load_plugins(application): # Load plugin translations install_translation(mod_name) install_translation(mod_name, tr_path=path.join(mod_path, 'i18n')) - except Exception as e: - elogging.exception('PLUGINS: Failed "{}" load'.format(name), e) + except Exception: + logger.exception('PLUGINS: Failed "{}" load'.format(name)) __init_plugins(application) @@ -79,11 +81,10 @@ def __init_plugins(application): # We've go through all the not loaded plugins and weren't able # to resolve their dependencies, which means there are cyclic or # missing/disabled dependencies - elogging.warning( - 'PLUGINS: Cyclic or missing/disabled dependencies detected.', - dialog=False + logger.warning( + 'Cannot satisfy some plugin dependencies: {}'.format( + ', '.join(pending)) ) - elogging.debug('PLUGINS: Skip: {}'.format(list(pending.keys()))) return @@ -118,15 +119,12 @@ def __load_plugins(plugins, application, optionals=True): if plugin.Config.get('_enabled_', False): # Create an instance of the plugin and save it LOADED[name] = plugin(application) - elogging.debug('PLUGINS: Loaded "{}"'.format(name)) + logger.info('Plugin loaded: "{}"'.format(name)) else: - elogging.debug( - 'PLUGINS: Skip "{}", disabled in configuration' - .format(name) - ) - except Exception as e: - elogging.exception( - 'PLUGINS: Failed "{}" load'.format(name), e) + logger.debug( + 'Plugin disabled in configuration: "{}"'.format(name)) + except Exception: + logger.exception('Failed to load plugin: "{}"'.format(name)) return resolved @@ -136,9 +134,9 @@ def finalize_plugins(): for plugin in LOADED: try: LOADED[plugin].finalize() - elogging.debug('PLUGINS: Reset "{}"'.format(plugin)) - except Exception as e: - elogging.exception('PLUGINS: Failed "{}" reset'.format(plugin), e) + logger.info('Plugin terminated: "{}"'.format(plugin)) + except Exception: + logger.exception('Failed to terminate plugin: "{}"'.format(plugin)) def is_loaded(plugin_name): diff --git a/lisp/plugins/action_cues/__init__.py b/lisp/plugins/action_cues/__init__.py index 6d3ebf3f8..9b61cbd5c 100644 --- a/lisp/plugins/action_cues/__init__.py +++ b/lisp/plugins/action_cues/__init__.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -26,11 +26,12 @@ from lisp.cues.cue_factory import CueFactory from lisp.ui.ui_utils import translate +logger = logging.getLogger(__name__) -class ActionCues(Plugin): +class ActionCues(Plugin): Name = 'Action Cues' - Authors = ('Francesco Ceruti', ) + Authors = ('Francesco Ceruti',) Description = 'Provide a collection of cues for different purposes' def __init__(self, app): @@ -41,22 +42,21 @@ def __init__(self, app): CueFactory.register_factory(cue.__name__, cue) # Register the menu action for adding the action-cue - add_function = self.create_add_action_cue_function(cue) self.app.window.register_cue_menu_action( translate('CueName', cue.Name), - add_function, 'Action cues') + self._new_cue_factory(cue), + 'Action cues' + ) - logging.debug('ACTION-CUES: Loaded "' + name + '"') + logger.debug('Loaded "' + name + '"') - def create_add_action_cue_function(self, cue_class): - def function(): + def _new_cue_factory(self, cue_class): + def cue_factory(): try: cue = CueFactory.create_cue(cue_class.__name__) self.app.cue_model.add(cue) except Exception: - # TODO: Display a message to the user - import logging, traceback - logging.error('Cannot create cue {}', cue_class.__name__) - logging.debug(traceback.format_exc()) + logger.exception( + 'Cannot create cue {}'.format(cue_class.__name__)) - return function + return cue_factory diff --git a/lisp/plugins/action_cues/osc_cue.py b/lisp/plugins/action_cues/osc_cue.py index 8076506c6..84788b7b4 100644 --- a/lisp/plugins/action_cues/osc_cue.py +++ b/lisp/plugins/action_cues/osc_cue.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # Copyright 2012-2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify @@ -23,7 +23,7 @@ from PyQt5 import QtCore from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, QLineEdit, \ - QTableView, QTableWidget, QHeaderView, QPushButton, QLabel, QDoubleSpinBox + QTableView, QTableWidget, QHeaderView, QPushButton, QLabel, QDoubleSpinBox, QMessageBox from lisp.core.decorators import async from lisp.core.fade_functions import FadeInType, FadeOutType @@ -32,13 +32,16 @@ from lisp.cues.cue import Cue, CueAction from lisp.plugins import get_plugin from lisp.plugins.osc.osc_server import OscMessageType -from lisp.ui import elogging -from lisp.ui.qdelegates import ComboBoxDelegate, OscArgumentDelegate, \ - CheckBoxDelegate +from lisp.ui.qdelegates import ComboBoxDelegate, CheckBoxDelegate +from lisp.plugins.osc.osc_delegate import OscArgumentDelegate from lisp.ui.qmodels import SimpleTableModel from lisp.ui.settings.cue_settings import CueSettingsRegistry, SettingsPage from lisp.ui.ui_utils import translate -from lisp.ui.widgets import FadeComboBox +from lisp.ui.widgets import FadeComboBox, QDetailedMessageBox + +# TODO: a lot of refactoring + +logger = logging.getLogger(__name__) COL_TYPE = 0 COL_START_VAL = 1 @@ -125,7 +128,7 @@ def __init_arguments(self): arg['start'], 0]) except KeyError: - elogging.error("OSC: could not parse argument list, nothing sent") + logger.error('Could not parse argument list, nothing sent') return False # set fade type, based on the first argument, which will have a fade @@ -164,7 +167,7 @@ def __start__(self, fade=False): else: self._position = 1 else: - logging.error("OSC: Error while parsing arguments, nothing sent") + logger.error('Error while parsing arguments, nothing sent') return False @@ -287,7 +290,7 @@ def get_settings(self): if not (checkable and not self.oscGroup.isChecked()): if not test_path(self.pathEdit.text()): - elogging.error("OSC: Error parsing osc path, message will be unable to send") + logger.error('Error parsing OSC path, message will be unable to send') if not (checkable and not self.oscGroup.isChecked()): try: @@ -305,7 +308,7 @@ def get_settings(self): conf['args'] = args_list except ValueError: - elogging.error("OSC: Error parsing osc arguments, message will be unable to send") + logger.error('Error parsing OSC arguments, message will be unable to send') if not (checkable and not self.fadeGroup.isCheckable()): conf['duration'] = self.fadeSpin.value() * 1000 @@ -342,9 +345,11 @@ def __test_message(self): path = self.pathEdit.text() if not test_path(path): - elogging.warning("OSC: no valid path for OSC message - nothing sent", - details="Path should start with a '/' followed by a name.", - dialog=True) + QDetailedMessageBox.dwarning( + 'Warning', + 'No valid path for OSC message - nothing sent', + "Path should start with a '/' followed by a name." + ) return try: @@ -360,7 +365,8 @@ def __test_message(self): except KeyError: pass except ValueError: - elogging.error("OSC: Error on parsing argument list - nothing sent") + QMessageBox.critical( + None, 'Error', 'Error on parsing argument list - nothing sent') def __argument_changed(self, index_topleft, index_bottomright, roles): if not (Qt.EditRole in roles): @@ -412,11 +418,14 @@ class OscView(QTableView): def __init__(self, **kwargs): super().__init__(**kwargs) - self.delegates = [ComboBoxDelegate(options=[i.value for i in OscMessageType], - tr_context='OscMessageType'), - OscArgumentDelegate(), - OscArgumentDelegate(), - CheckBoxDelegate()] + self.delegates = [ + ComboBoxDelegate( + options=[i.value for i in OscMessageType], + tr_context='OscMessageType'), + OscArgumentDelegate(), + OscArgumentDelegate(), + CheckBoxDelegate() + ] self.setSelectionBehavior(QTableWidget.SelectRows) self.setSelectionMode(QTableView.SingleSelection) diff --git a/lisp/plugins/controller/controller.py b/lisp/plugins/controller/controller.py index a615c29f0..a40816506 100644 --- a/lisp/plugins/controller/controller.py +++ b/lisp/plugins/controller/controller.py @@ -17,6 +17,8 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging + from lisp.core.plugin import Plugin from lisp.core.properties import Property from lisp.cues.cue import Cue, CueAction @@ -24,9 +26,10 @@ from lisp.plugins.controller.controller_settings import ControllerSettings from lisp.ui.settings.cue_settings import CueSettingsRegistry +logger = logging.getLogger(__name__) -class Controller(Plugin): +class Controller(Plugin): Name = 'Controller' Authors = ('Francesco Ceruti', 'Thomas Achtner') OptDepends = ('Midi', 'Osc') @@ -106,10 +109,7 @@ def __load_protocols(self): protocol.protocol_event.connect(self.perform_action) self.__protocols[protocol_class.__name__.lower()] = protocol - except Exception as e: - import logging - import traceback - - logging.error('CONTROLLER: cannot setup protocol "{}"'.format( - protocol_class.__name__)) - logging.debug(traceback.format_exc()) + except Exception: + logger.exception( + 'Cannot setup controller protocol "{}"'.format( + protocol_class.__name__)) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 9b64beb4b..2cbbc8b1a 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # Copyright 2012-2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify @@ -24,13 +24,13 @@ from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QGroupBox, QPushButton, QVBoxLayout, \ QTableView, QTableWidget, QHeaderView, QGridLayout, QLabel, \ - QDialog, QDialogButtonBox, QLineEdit + QDialog, QDialogButtonBox, QLineEdit, QMessageBox from lisp.plugins import get_plugin from lisp.plugins.controller.protocols.protocol import Protocol +from lisp.plugins.osc.osc_delegate import OscArgumentDelegate from lisp.plugins.osc.osc_server import OscMessageType -from lisp.ui import elogging -from lisp.ui.qdelegates import OscArgumentDelegate, ComboBoxDelegate, \ +from lisp.ui.qdelegates import ComboBoxDelegate, \ LineEditDelegate from lisp.ui.qmodels import SimpleTableModel from lisp.ui.settings.settings_page import CueSettingsPage @@ -325,10 +325,10 @@ def __new_message(self): if dialog.exec_() == dialog.Accepted: path = dialog.pathEdit.text() if len(path) < 2 or path[0] is not '/': - elogging.warning( - 'OSC: Osc path seems not valid,' - '\ndo not forget to edit the path later.', - dialog=True + QMessageBox.warning( + None, 'Warning', + 'Osc path seems not valid, \ndo not forget to edit the ' + 'path later.', ) types = '' @@ -367,11 +367,12 @@ def __init__(self, cue_class, **kwargs): super().__init__(**kwargs) cue_actions = [action.name for action in cue_class.CueActions] - self.delegates = [LineEditDelegate(), - LineEditDelegate(), - LineEditDelegate(), - ComboBoxDelegate(options=cue_actions, - tr_context='CueAction')] + self.delegates = [ + LineEditDelegate(), + LineEditDelegate(), + LineEditDelegate(), + ComboBoxDelegate(options=cue_actions, tr_context='CueAction') + ] self.setSelectionBehavior(QTableWidget.SelectRows) self.setSelectionMode(QTableView.SingleSelection) diff --git a/lisp/plugins/gst_backend/elements/jack_sink.py b/lisp/plugins/gst_backend/elements/jack_sink.py index 97d2f4e66..3385bb06c 100644 --- a/lisp/plugins/gst_backend/elements/jack_sink.py +++ b/lisp/plugins/gst_backend/elements/jack_sink.py @@ -27,6 +27,8 @@ from lisp.plugins.gst_backend.gi_repository import Gst from lisp.plugins.gst_backend.gst_element import GstMediaElement +logger = logging.getLogger(__name__) + class JackSink(GstMediaElement): ElementType = ElementType.Output @@ -99,7 +101,7 @@ def default_connections(cls, client): def __prepare_connections(self, value): if (self.pipeline.current_state == Gst.State.PLAYING or - self.pipeline.current_state == Gst.State.PAUSED): + self.pipeline.current_state == Gst.State.PAUSED): self.__jack_connect() @classmethod @@ -123,8 +125,9 @@ def __jack_connect(self): for conn_port in JackSink._ControlClient.get_all_connections(port): try: JackSink._ControlClient.disconnect(port, conn_port) - except jack.JackError as e: - logging.error('GST: JACK-SINK: {}'.format(e)) + except jack.JackError: + logger.exception( + 'An error occurred while disconnection Jack ports') for output, in_ports in enumerate(self.connections): for input_name in in_ports: @@ -132,8 +135,9 @@ def __jack_connect(self): try: JackSink._ControlClient.connect(out_ports[output], input_name) - except jack.JackError as e: - logging.error('GST: JACK-SINK: {}'.format(e)) + except jack.JackError: + logger.exception( + 'An error occurred while connecting Jack ports') else: break diff --git a/lisp/plugins/osc/osc_delegate.py b/lisp/plugins/osc/osc_delegate.py new file mode 100644 index 000000000..0608d0dcd --- /dev/null +++ b/lisp/plugins/osc/osc_delegate.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QStyledItemDelegate, QSpinBox, QDoubleSpinBox, \ + QCheckBox, QLineEdit + +from lisp.plugins.osc.osc_server import OscMessageType + + +class OscArgumentDelegate(QStyledItemDelegate): + MIN = -1000000 + MAX = 1000000 + DECIMALS = 4 + + def __init__(self, tag=OscMessageType.Int, **kwargs): + super().__init__(**kwargs) + self.tag = tag + + def createEditor(self, parent, option, index): + if self.tag is OscMessageType.Int: + editor = QSpinBox(parent) + editor.setRange(self.MIN, self.MAX) + editor.setSingleStep(1) + elif self.tag is OscMessageType.Float: + editor = QDoubleSpinBox(parent) + editor.setRange(self.MIN, self.MAX) + editor.setDecimals(3) + editor.setSingleStep(0.01) + elif self.tag is OscMessageType.Bool: + editor = QCheckBox(parent) + elif self.tag is OscMessageType.String: + editor = QLineEdit(parent) + editor.setFrame(False) + else: + editor = None + + return editor + + def setEditorData(self, widget, index): + value = index.model().data(index, Qt.EditRole) + + if self.tag is OscMessageType.Int: + if isinstance(value, int): + widget.setValue(value) + elif self.tag is OscMessageType.Float: + if isinstance(value, float): + widget.setValue(value) + elif self.tag is OscMessageType.Bool: + if isinstance(value, bool): + widget.setChecked(value) + else: + widget.setText(str(value)) + + def setModelData(self, widget, model, index): + if self.tag is OscMessageType.Int or self.tag is OscMessageType.Float: + widget.interpretText() + model.setData(index, widget.value(), Qt.EditRole) + elif self.tag is OscMessageType.Bool: + model.setData(index, widget.isChecked(), Qt.EditRole) + else: + model.setData(index, widget.text(), Qt.EditRole) + + def updateEditorGeometry(self, editor, option, index): + editor.setGeometry(option.rect) + + def updateEditor(self, tag=None): + if isinstance(tag, OscMessageType): + self.tag = tag + else: + self.tag = None diff --git a/lisp/plugins/osc/osc_server.py b/lisp/plugins/osc/osc_server.py index e8b53e01e..bb3c4b002 100644 --- a/lisp/plugins/osc/osc_server.py +++ b/lisp/plugins/osc/osc_server.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # Copyright 2012-2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify @@ -92,6 +92,8 @@ def callback_interrupt(_, args, types): ['/lisp/list/interrupt', None, callback_interrupt] ]''' +logger = logging.getLogger(__name__) + class OscMessageType(Enum): Int = 'Integer' @@ -122,10 +124,10 @@ def start(self): self.__listening = True - logging.info('OSC: Server started at {}'.format(self.__srv.url)) + logger.info('Server started at {}'.format(self.__srv.url)) except ServerError: - logging.error('OSC: Cannot start sever') - logging.debug(traceback.format_exc()) + logger.error('Cannot start sever') + logger.debug(traceback.format_exc()) def stop(self): if self.__srv is not None: @@ -134,7 +136,7 @@ def stop(self): self.__listening = False self.__srv.free() - logging.info('OSC: Server stopped') + logger.info('Server stopped') @property def listening(self): diff --git a/lisp/plugins/osc/osc_settings.py b/lisp/plugins/osc/osc_settings.py index c8fbf1924..620709ed7 100644 --- a/lisp/plugins/osc/osc_settings.py +++ b/lisp/plugins/osc/osc_settings.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # Copyright 2012-2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify @@ -26,7 +26,6 @@ from lisp.ui.ui_utils import translate -# FIXME class OscSettings(SettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'OSC settings') diff --git a/lisp/plugins/remote/controller.py b/lisp/plugins/remote/controller.py index ee537af5b..04101c451 100644 --- a/lisp/plugins/remote/controller.py +++ b/lisp/plugins/remote/controller.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -26,6 +26,8 @@ from lisp.core.decorators import async from lisp.plugins.remote.dispatcher import RemoteDispatcher +logger = logging.getLogger(__name__) + class TimeoutTransport(Transport): timeout = 2.0 @@ -41,29 +43,25 @@ class RemoteController: """Provide control over a SimpleXMLRPCServer.""" def __init__(self, app, ip, port): - try: - self.server = SimpleXMLRPCServer( - (ip, port), allow_none=True, logRequests=False) - except OSError as error: - # If address already in use - if error.errno == 98: - raise Exception( - "Only one application instance can use this module") - else: - raise error + self.server = SimpleXMLRPCServer( + (ip, port), allow_none=True, logRequests=False) self.server.register_introspection_functions() self.server.register_instance(RemoteDispatcher(app)) @async def start(self): - logging.info( - 'REMOTE: Session started at ' + str(self.server.server_address)) + logger.info( + 'Remote network session started: IP="{}" Port="{}"'.format( + self.server.server_address[0], + self.server.server_address[1] + ) + ) # Blocking call self.server.serve_forever() - logging.info('REMOTE: Session ended') + logger.info('Remote network session ended.') def stop(self): self.server.shutdown() diff --git a/lisp/plugins/rename_cues/rename_ui.py b/lisp/plugins/rename_cues/rename_ui.py index cd2fe31e2..b88454eca 100644 --- a/lisp/plugins/rename_cues/rename_ui.py +++ b/lisp/plugins/rename_cues/rename_ui.py @@ -3,7 +3,7 @@ # This file is part of Linux Show Player # # Copyright 2016-2017 Aurelien Cibrario -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,15 +22,15 @@ import re from PyQt5.QtCore import Qt, QSize -from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QGridLayout, QLineEdit, \ QTreeWidget, QAbstractItemView, QTreeWidgetItem, QPushButton, QSpacerItem, \ QMessageBox -from lisp.application import MainActionsHandler -from lisp.plugins.rename_cues.rename_action import RenameCueAction +from lisp.ui.themes import IconTheme from lisp.ui.ui_utils import translate +logger = logging.getLogger(__name__) + class RenameUi(QDialog): def __init__(self, parent=None, selected_cues=()): @@ -118,7 +118,6 @@ def __init__(self, parent=None, selected_cues=()): self.dialogButtons.accepted.connect(self.accept) self.dialogButtons.rejected.connect(self.reject) - # This list store infos on cues with dicts self.cues_list = [] for cue in selected_cues: @@ -225,7 +224,7 @@ def onRegexLineChanged(self): try: regex = re.compile(pattern) except re.error: - logging.debug("Regex error : not a valid pattern") + logger.debug("Regex error: invalid pattern") else: for cue in self.cues_list: result = regex.search(cue['cue_name']) @@ -247,10 +246,14 @@ def onOutRegexChanged(self): for n in range(len(cue['regex_groups'])): pattern = f"\${n}" try: - out_string = re.sub(pattern, - cue['regex_groups'][n], out_string) + out_string = re.sub( + pattern, + cue['regex_groups'][n], + out_string + ) except IndexError: - logging.debug("Regex error : Catch with () before display with $n") + logger.debug( + "Regex error: catch with () before display with $n") if cue['selected']: cue['cue_preview'] = out_string diff --git a/lisp/plugins/replay_gain/replay_gain.py b/lisp/plugins/replay_gain/replay_gain.py index f8e281917..a0e957048 100644 --- a/lisp/plugins/replay_gain/replay_gain.py +++ b/lisp/plugins/replay_gain/replay_gain.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,15 +18,12 @@ # along with Linux Show Player. If not, see . import logging -from concurrent.futures import ThreadPoolExecutor, \ - as_completed as futures_completed +from concurrent.futures import ThreadPoolExecutor, as_completed from math import pow from threading import Thread, Lock import gi -from lisp.ui.ui_utils import translate - gi.require_version('Gst', '1.0') from gi.repository import Gst from PyQt5.QtWidgets import QMenu, QAction, QDialog @@ -36,23 +33,26 @@ from lisp.core.plugin import Plugin from lisp.core.signal import Signal, Connection from lisp.cues.media_cue import MediaCue +from lisp.ui.ui_utils import translate from .gain_ui import GainUi, GainProgressDialog +logger = logging.getLogger(__name__) -class ReplayGain(Plugin): +class ReplayGain(Plugin): Name = 'ReplayGain / Normalization' - Authors = ('Francesco Ceruti', ) + Authors = ('Francesco Ceruti',) Description = 'Allow to normalize cues volume' + RESET_VALUE = 1.0 + def __init__(self, app): super().__init__(app) - self._gain_thread = None # Entry in mainWindow menu - self.menu = QMenu(translate('ReplayGain', - 'ReplayGain / Normalization')) + self.menu = QMenu( + translate('ReplayGain', 'ReplayGain / Normalization')) self.menu_action = self.app.window.menuTools.addMenu(self.menu) self.actionGain = QAction(self.app.window) @@ -76,32 +76,40 @@ def gain(self): gainUi.exec_() if gainUi.result() == QDialog.Accepted: - - files = {} if gainUi.only_selected(): cues = self.app.layout.get_selected_cues(MediaCue) else: cues = self.app.cue_model.filter(MediaCue) + # Extract single uri(s), this way if a uri is used in more than + # one place, the gain is only calculated once. + files = {} for cue in cues: media = cue.media uri = media.input_uri() + if uri is not None: + if uri[:7] == 'file://': + uri = 'file://' + self.app.session.abs_path(uri[7:]) + if uri not in files: files[uri] = [media] else: files[uri].append(media) # Gain (main) thread - self._gain_thread = GainMainThread(files, gainUi.threads(), - gainUi.mode(), - gainUi.ref_level(), - gainUi.norm_level()) + self._gain_thread = GainMainThread( + files, + gainUi.threads(), + gainUi.mode(), + gainUi.ref_level(), + gainUi.norm_level() + ) # Progress dialog self._progress = GainProgressDialog(len(files)) - self._gain_thread.on_progress.connect(self._progress.on_progress, - mode=Connection.QtQueued) + self._gain_thread.on_progress.connect( + self._progress.on_progress, mode=Connection.QtQueued) self._progress.show() self._gain_thread.start() @@ -118,12 +126,13 @@ def _reset_all(self): self._reset(self.app.cue_model.filter(MediaCue)) def _reset_selected(self): - self._reset(self.app.cue_model.filter(MediaCue)) + self._reset(self.app.layout.get_selected_cues(MediaCue)) def _reset(self, cues): action = GainAction() for cue in cues: - action.add_media(cue.media, 1.0) + action.add_media(cue.media, ReplayGain.RESET_VALUE) + MainActionsHandler.do_action(action) @@ -158,12 +167,10 @@ def redo(self): self.do() def log(self): - return 'Replay gain volume adjusted' + return 'Replay gain volume adjusted.' class GainMainThread(Thread): - MAX_GAIN = 20 # dB - def __init__(self, files, threads, mode, ref_level, norm_level): super().__init__() self.setDaemon(True) @@ -195,65 +202,74 @@ def run(self): gain = GstGain(file, self.ref_level) self._futures[executor.submit(gain.gain)] = gain - for future in futures_completed(self._futures): + for future in as_completed(self._futures): if self._running: try: - self._post_process(*future.result()) + self._post_process(future.result()) except Exception: - # Call with the value stored in the GstGain object - self._post_process(*self._futures[future].result) + logger.exception( + 'An error occurred while processing gain results.') + finally: + self.on_progress.emit(1) else: break if self._running: MainActionsHandler.do_action(self._action) else: - logging.info('REPLY-GAIN:: Stopped by user') + logger.info('Gain processing stopped by user.') self.on_progress.emit(-1) self.on_progress.disconnect() - def _post_process(self, gained, gain, peak, uri): - if gained: - if gain > self.MAX_GAIN: - gain = self.MAX_GAIN - + def _post_process(self, gain): + """ + :type gain: GstGain + """ + if gain.completed: if self.mode == 0: - volume = min(1 / peak, pow(10, gain / 20)) + volume = pow(10, gain.gain_value / 20) else: # (self.mode == 1) - volume = 1 / peak * pow(10, self.norm_level / 20) + volume = 1 / gain.peak_value * pow(10, self.norm_level / 20) - for media in self.files[uri]: + for media in self.files[gain.uri]: self._action.add_media(media, volume) - logging.info('REPLY-GAIN:: completed ' + uri) + logger.debug('Applied gain for: {}'.format(gain.uri)) else: - logging.error('REPLY-GAIN:: failed ' + uri) - - self.on_progress.emit(1) + logger.debug('Discarded gain for: {}'.format(gain.uri)) class GstGain: + PIPELINE = 'uridecodebin uri="{0}" ! audioconvert ! rganalysis ' \ + 'reference-level={1} ! fakesink' + def __init__(self, uri, ref_level): self.__lock = Lock() self.uri = uri self.ref_level = ref_level - self.result = (False, 0, 0, uri) self.gain_pipe = None + # Result attributes + self.gain_value = 0 + self.peak_value = 0 + self.completed = False + # Create a pipeline with a fake audio output and get the gain levels def gain(self): - pipe = 'uridecodebin uri="{0}" ! audioconvert ! rganalysis \ - reference-level={1} ! fakesink'.format(self.uri, self.ref_level) - self.gain_pipe = Gst.parse_launch(pipe) + self.gain_pipe = Gst.parse_launch( + GstGain.PIPELINE.format(self.uri, self.ref_level)) gain_bus = self.gain_pipe.get_bus() gain_bus.add_signal_watch() - gain_bus.connect('message', self._on_message) + # Connect only the messages we want + gain_bus.connect('message::eos', self._on_message) + gain_bus.connect('message::tag', self._on_message) + gain_bus.connect('message::error', self._on_message) - logging.info('REPLY-GAIN:: started ' + str(self.uri)) self.gain_pipe.set_state(Gst.State.PLAYING) + logger.info('Started gain calculation for: {}'.format(self.uri)) # Block here until EOS self.__lock.acquire(False) @@ -263,7 +279,7 @@ def gain(self): self.gain_pipe = None # Return the computation result - return self.result + return self def stop(self): if self.gain_pipe is not None: @@ -271,8 +287,8 @@ def stop(self): def _on_message(self, bus, message): try: - if message.type == Gst.MessageType.EOS and self.__lock.locked(): - self.__lock.release() + if message.type == Gst.MessageType.EOS: + self.__release() elif message.type == Gst.MessageType.TAG: tags = message.parse_tag() tag = tags.get_double(Gst.TAG_TRACK_GAIN) @@ -280,17 +296,24 @@ def _on_message(self, bus, message): if tag[0] and peak[0]: self.gain_pipe.set_state(Gst.State.NULL) - self.result = (True, tag[1], peak[1], self.uri) + # Save the gain results + self.gain_value = tag[1] + self.peak_value = peak[1] - if self.__lock.locked(): - self.__lock.release() + logger.info('Gain calculated for: {}'.format(self.uri)) + self.completed = True + self.__release() elif message.type == Gst.MessageType.ERROR: - logging.debug('REPLY-GAIN:: ' + str(message.parse_error())) - + error, _ = message.parse_error() self.gain_pipe.set_state(Gst.State.NULL) - if self.__lock.locked(): - self.__lock.release() + logger.error( + 'GStreamer: {}'.format(error.message), exc_info=error) + self.__release() except Exception: - if self.__lock.locked(): - self.__lock.release() + logger.exception('An error occurred during gain calculation.') + self.__release() + + def __release(self): + if self.__lock.locked(): + self.__lock.release() diff --git a/lisp/plugins/synchronizer/peers_dialog.py b/lisp/plugins/synchronizer/peers_dialog.py index 9a9a289a2..600e48600 100644 --- a/lisp/plugins/synchronizer/peers_dialog.py +++ b/lisp/plugins/synchronizer/peers_dialog.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,13 +17,14 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging + from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QDialog, QHBoxLayout, QListWidget, QVBoxLayout, \ QPushButton, QDialogButtonBox, QInputDialog, QMessageBox from lisp.core.util import compose_http_url from lisp.plugins.remote.remote import RemoteController -from lisp.ui import elogging from lisp.ui.ui_utils import translate from .peers_discovery_dialog import PeersDiscoveryDialog @@ -112,9 +113,8 @@ def _add_peer(self, ip): peer = {'proxy': RemoteController.connect_to(uri), 'uri': uri} self.peers.append(peer) self.listWidget.addItem(peer['uri']) - except Exception as e: - elogging.exception( - translate('SyncPeerDialog', 'Cannot add peer'), str(e)) + except Exception: + logging.exception(translate('SyncPeerDialog', 'Cannot add peer.')) def discover_peers(self): dialog = PeersDiscoveryDialog(self.config, parent=self) diff --git a/lisp/plugins/synchronizer/synchronizer.py b/lisp/plugins/synchronizer/synchronizer.py index b9c1d0c0d..710cb9a4f 100644 --- a/lisp/plugins/synchronizer/synchronizer.py +++ b/lisp/plugins/synchronizer/synchronizer.py @@ -18,24 +18,22 @@ # along with Linux Show Player. If not, see . import logging -import traceback from PyQt5.QtWidgets import QMenu, QAction, QMessageBox -from lisp.application import Application from lisp.core.plugin import Plugin from lisp.core.signal import Connection from lisp.core.util import get_lan_ip -from lisp.ui.mainwindow import MainWindow from lisp.ui.ui_utils import translate from .peers_dialog import PeersDialog +logger = logging.getLogger(__name__) -class Synchronizer(Plugin): +class Synchronizer(Plugin): Name = 'Synchronizer' - Authors = ('Francesco Ceruti', ) - Depends = ('Remote', ) + Authors = ('Francesco Ceruti',) + Depends = ('Remote',) Description = 'Keep multiple sessions, on a network, synchronized' def __init__(self, app): @@ -82,5 +80,4 @@ def remote_execute(self, cue): try: peer['proxy'].execute(cue.index) except Exception: - logging.error('REMOTE: remote control failed') - logging.debug('REMOTE: ' + traceback.format_exc()) + logger.warning('Remote control failed.', exc_info=True) diff --git a/lisp/plugins/timecode/cue_tracker.py b/lisp/plugins/timecode/cue_tracker.py index 45484c912..8d13ca50c 100644 --- a/lisp/plugins/timecode/cue_tracker.py +++ b/lisp/plugins/timecode/cue_tracker.py @@ -26,6 +26,8 @@ from lisp.core.signal import Connection from lisp.cues.cue_time import CueTime +logger = logging.getLogger(__name__) + class TcFormat(Enum): FILM = 1000 / 24 @@ -113,8 +115,7 @@ def send(self, time): if self.__lock.acquire(blocking=False): try: if not self.__protocol.send(self.format, time, self.__track): - logging.error( - 'TIMECODE: cannot send timecode, untracking cue') + logger.warning('Cannot send timecode, untracking cue') self.untrack() except Exception: self.__lock.release() diff --git a/lisp/plugins/timecode/protocols/artnet.py b/lisp/plugins/timecode/protocols/artnet.py index 44be63219..10bcd3e6e 100644 --- a/lisp/plugins/timecode/protocols/artnet.py +++ b/lisp/plugins/timecode/protocols/artnet.py @@ -25,7 +25,6 @@ from lisp.core.util import time_tuple from lisp.plugins.timecode.cue_tracker import TcFormat from lisp.plugins.timecode.protocol import TimecodeProtocol -from lisp.ui import elogging from lisp.ui.ui_utils import translate ARTNET_FORMATS = { @@ -34,6 +33,8 @@ TcFormat.SMPTE: OlaClient.TIMECODE_SMPTE } +logger = logging.getLogger(__name__) + class Artnet(TimecodeProtocol): Name = 'ArtNet' @@ -44,7 +45,7 @@ def __init__(self): try: self.__client = OlaClient() except OLADNotRunningException as e: - raise e + raise e self.__last_time = -1 @@ -61,13 +62,12 @@ def send(self, fmt, time, track=-1): self.__client.SendTimeCode( ARTNET_FORMATS[fmt], hours, minutes, seconds, frame) except OLADNotRunningException: - elogging.error( - translate('Timecode', 'Cannot send timecode.'), - details=translate('Timecode', 'OLA has stopped.')) + logger.error( + translate( + 'Timecode', 'Cannot send timecode. \nOLA has stopped.')) return False - except Exception as e: - logging.error('TIMECODE: Cannot send timecode') - logging.debug('TIMECODE: {}'.format(e)) + except Exception: + logger.exception(translate('Timecode', 'Cannot send timecode.')) return False self.__last_time = time diff --git a/lisp/ui/cuelistdialog.py b/lisp/ui/cuelistdialog.py index 2d47df400..3d98d8798 100644 --- a/lisp/ui/cuelistdialog.py +++ b/lisp/ui/cuelistdialog.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,8 +21,6 @@ from PyQt5.QtWidgets import QDialog, QTreeWidget, QHeaderView, QVBoxLayout, \ QDialogButtonBox, QTreeWidgetItem -from lisp.ui import elogging - class CueSelectDialog(QDialog): def __init__(self, cues=None, properties=('index', 'name'), @@ -65,11 +63,7 @@ def add_cue(self, cue): item.setTextAlignment(0, Qt.AlignCenter) for n, prop in enumerate(self._properties): - try: - item.setData(n, Qt.DisplayRole, getattr(cue, prop, 'Undefined')) - except Exception as e: - elogging.exception('Cannot display {0} property'.format(prop), e, - dialog=False) + item.setData(n, Qt.DisplayRole, getattr(cue, prop, 'Undefined')) self._cues[cue] = item item.setData(0, Qt.UserRole, cue) diff --git a/lisp/ui/elogging.py b/lisp/ui/elogging.py deleted file mode 100644 index bf71effb7..000000000 --- a/lisp/ui/elogging.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -""" - Module providing a simple proxy over python-logging default module - and optionally showing a dialog to the user. -""" - -import logging -import traceback - -from lisp.ui.widgets import QDetailedMessageBox -from lisp.ui.ui_utils import translate - - -def info(msg, details='', dialog=False): - logging.info(_log_msg(msg, details)) - if dialog: - _dialog(translate('Logging', 'Information'), msg, details, - QDetailedMessageBox.Information) - - -def debug(msg, details='', dialog=False): - logging.debug(_log_msg(msg, details)) - if dialog: - _dialog(translate('Logging', 'Debug'), msg, details, - QDetailedMessageBox.Information) - - -def warning(msg, details='', dialog=True): - logging.warning(_log_msg(msg, details)) - if dialog: - _dialog(translate('Logging', 'Warning'), msg, details, - QDetailedMessageBox.Warning) - - -def error(msg, details='', dialog=True): - logging.error(_log_msg(msg, details)) - if dialog: - _dialog(translate('Logging', 'Error'), msg, details, - QDetailedMessageBox.Critical) - - -def exception(msg, exception, dialog=True): - logging.error(_log_msg(msg, traceback.format_exc())) - if dialog: - _dialog(translate('Logging', 'Error'), msg, str(exception), - QDetailedMessageBox.Critical) - - -def _log_msg(msg, details): - if details.strip() != '': - details = translate('Logging', 'Details:') + ' ' + details - return msg + '\n' + details - - return msg - - -def _dialog(title, msg, details, type_): - QDetailedMessageBox.dgeneric(title, msg, details, type_) diff --git a/lisp/ui/logging/__init__.py b/lisp/ui/logging/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lisp/ui/logging/common.py b/lisp/ui/logging/common.py new file mode 100644 index 000000000..c55a3dcf0 --- /dev/null +++ b/lisp/ui/logging/common.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP + +LOG_LEVELS = { + 'DEBUG': QT_TRANSLATE_NOOP('Logging', 'Debug'), + 'INFO': QT_TRANSLATE_NOOP('Logging', 'Info'), + 'WARNING': QT_TRANSLATE_NOOP('Logging', 'Warning'), + 'ERROR': QT_TRANSLATE_NOOP('Logging', 'Error'), + 'CRITICAL': QT_TRANSLATE_NOOP('Logging', 'Critical') +} + +LOG_ICONS_NAMES = { + 'DEBUG': 'dialog-question', + 'INFO': 'dialog-information', + 'WARNING': 'dialog-warning', + 'ERROR': 'dialog-error', + 'CRITICAL': 'dialog-error' +} + +LOG_ATTRIBUTES = { + 'asctime': QT_TRANSLATE_NOOP('Logging', 'Time'), + 'msecs': QT_TRANSLATE_NOOP('Logging', 'Milliseconds'), + 'name': QT_TRANSLATE_NOOP('Logging', 'Logger name'), + 'levelname': QT_TRANSLATE_NOOP('Logging', 'Level'), + 'message': QT_TRANSLATE_NOOP('Logging', 'Message'), + #'exc_info': QT_TRANSLATE_NOOP('Logging', 'Traceback'), + 'funcName': QT_TRANSLATE_NOOP('Logging', 'Function'), + 'pathname': QT_TRANSLATE_NOOP('Logging', 'Path name'), + 'filename': QT_TRANSLATE_NOOP('Logging', 'File name'), + 'lineno': QT_TRANSLATE_NOOP('Logging', 'Line no.'), + 'module': QT_TRANSLATE_NOOP('Logging', 'Module'), + 'process': QT_TRANSLATE_NOOP('Logging', 'Process ID'), + 'processName': QT_TRANSLATE_NOOP('Logging', 'Process name'), + 'thread': QT_TRANSLATE_NOOP('Logging', 'Thread ID'), + 'threadName': QT_TRANSLATE_NOOP('Logging', 'Thread name'), +} + +LogRecordRole = Qt.UserRole +LogAttributeRole = Qt.UserRole + 1 diff --git a/lisp/ui/logging/dialog.py b/lisp/ui/logging/dialog.py new file mode 100644 index 000000000..9caa67263 --- /dev/null +++ b/lisp/ui/logging/dialog.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import logging + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QDialogButtonBox, \ + QSizePolicy, QPushButton, QTextEdit + +from lisp.ui.logging.common import LOG_LEVELS, LOG_ICONS_NAMES +from lisp.ui.themes import IconTheme +from lisp.ui.ui_utils import translate + + +class LogDialogs: + def __init__(self, log_model, level=logging.ERROR): + """ + :type log_model: lisp.ui.logging.models.LogRecordModel + """ + self._level = level + self._log_model = log_model + self._log_model.rowsInserted.connect(self._new_rows) + + self._mmbox = MultiMessagesBox() + + def _new_rows(self, parent, start, end): + for n in range(start, end + 1): + record = self._log_model.record(n) + if record.levelno >= self._level: + self._mmbox.addRecord(record) + + +class MultiMessagesBox(QDialog): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.setLayout(QGridLayout()) + self.setWindowModality(Qt.ApplicationModal) + self.layout().setSizeConstraint(QGridLayout.SetFixedSize) + + self._formatter = logging.Formatter() + self._records = [] + + self.iconLabel = QLabel(self) + self.iconLabel.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) + self.layout().addWidget(self.iconLabel, 0, 0) + + self.messageLabel = QLabel(self) + self.layout().addWidget(self.messageLabel, 0, 1) + + self.buttonBox = QDialogButtonBox(self) + self.layout().addWidget(self.buttonBox, 1, 0, 1, 2) + + self.okButton = self.buttonBox.addButton(QDialogButtonBox.Ok) + self.okButton.clicked.connect(self.nextRecord) + + self.dismissButton = QPushButton(self) + self.dismissButton.setText(translate('Logging', 'Dismiss all')) + self.dismissButton.clicked.connect(self.dismissAll) + self.dismissButton.hide() + self.buttonBox.addButton(self.dismissButton, QDialogButtonBox.ResetRole) + + self.detailsButton = QPushButton(self) + self.detailsButton.setCheckable(True) + self.detailsButton.setText(translate('Logging', 'Show details')) + self.buttonBox.addButton( + self.detailsButton, QDialogButtonBox.ActionRole) + + self.detailsText = QTextEdit(self) + self.detailsText.setReadOnly(True) + self.detailsText.setLineWrapMode(QTextEdit.NoWrap) + self.detailsText.hide() + self.layout().addWidget(self.detailsText, 2, 0, 1, 2) + + self.detailsButton.toggled.connect(self.detailsText.setVisible) + + def addRecord(self, record): + self._records.append(record) + self.updateDisplayed() + + def updateDisplayed(self): + if self._records: + record = self._records[-1] + + self.messageLabel.setText(record.message) + self.iconLabel.setPixmap( + IconTheme.get(LOG_ICONS_NAMES[record.levelname]).pixmap(32)) + self.setWindowTitle( + '{} ({})'.format( + translate('Logging', LOG_LEVELS[record.levelname]), + len(self._records) + ) + ) + + # Get record details (exception traceback) + details = '' + if record.exc_info is not None: + details = self._formatter.formatException(record.exc_info) + + self.detailsText.setText(details) + self.detailsButton.setVisible(bool(details)) + # If no details, than hide the widget, otherwise keep it's current + # visibility unchanged + if not details: + self.detailsText.hide() + + # Show the dismiss-all button if there's more than one record + self.dismissButton.setVisible(len(self._records) > 1) + + self.show() + self.setMinimumSize(self.size()) + self.setMaximumSize(self.size()) + else: + self.hide() + + def nextRecord(self): + self._records.pop() + self.updateDisplayed() + + def dismissAll(self): + self._records.clear() + self.updateDisplayed() + + def reject(self): + self.dismissAll() diff --git a/lisp/ui/logging/handler.py b/lisp/ui/logging/handler.py new file mode 100644 index 000000000..e26ad113d --- /dev/null +++ b/lisp/ui/logging/handler.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import logging + +from lisp.core.signal import Signal, Connection + + +class QLoggingHandler(logging.Handler): + """Base class to implement Qt-safe logging handlers. + + Instead of `emit` subclass must implement the `_emit` method. + """ + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + # We need to append the new records while in the GUI thread, since + # logging is asynchronous we need to use this "workaround" + self.new_record = Signal() + self.new_record.connect(self._emit, Connection.QtQueued) + + def emit(self, record): + try: + # The call will be processed in the Qt main-thread, + # since the slot is queued, the call order is preserved + self.new_record.emit(record) + except Exception: + self.handleError(record) + + def _emit(self, record): + """Should implement the actual handling. + + This function is called in the Qt main-thread + """ + pass + + +class LogModelHandler(QLoggingHandler): + """Simple QLoggingHandler to log into a LogRecordModel""" + + def __init__(self, log_model, **kwargs): + """ + :param log_model: the model to send the records to + :type log_model: lisp.ui.logging.models.LogRecordModel + """ + super().__init__(**kwargs) + self._log_model = log_model + + def _emit(self, record): + self._log_model.append(record) diff --git a/lisp/ui/logging/models.py b/lisp/ui/logging/models.py new file mode 100644 index 000000000..ddf23fc1a --- /dev/null +++ b/lisp/ui/logging/models.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from collections import deque + +from PyQt5.QtCore import QModelIndex, Qt, QSortFilterProxyModel, \ + QAbstractListModel, QAbstractTableModel +from PyQt5.QtGui import QFont, QColor +from logging import Formatter + +from lisp.ui.logging.common import LogRecordRole, LOG_ATTRIBUTES, LogAttributeRole + + +class LogRecordModel(QAbstractTableModel): + Font = QFont('Monospace') + Font.setStyleHint(QFont.Monospace) + + def __init__(self, columns, bg, fg, limit=0, **kwargs): + """ + :param columns: column to be display + :type columns: dict[str, str] + :type bg: dict[str, QColor] + :type fg: dict[str, QColor] + :param limit: maximum number of stored records, 0 means no limit + :type limit: int + """ + super().__init__(**kwargs) + + self._columns = list(columns.keys()) + self._columns_names = [columns[key] for key in self._columns] + self._records = deque() + self._limit = limit + + self._backgrounds = bg + self._foregrounds = fg + + def columnCount(self, parent=QModelIndex()): + if parent.isValid(): + return 0 + + return len(self._columns) + + def rowCount(self, parent=QModelIndex()): + if parent.isValid(): + return 0 + + return len(self._records) + + def data(self, index, role=Qt.DisplayRole): + if index.isValid(): + record = self._records[index.row()] + + if role == Qt.DisplayRole: + try: + return getattr(record, self._columns[index.column()]) + except Exception: + pass + elif role == Qt.BackgroundColorRole: + return self._backgrounds.get(record.levelname) + elif role == Qt.ForegroundRole: + return self._foregrounds.get(record.levelname) + elif role == Qt.FontRole: + return LogRecordModel.Font + elif role == LogRecordRole: + return record + + def headerData(self, index, orientation, role=Qt.DisplayRole): + if orientation == Qt.Horizontal and index < len(self._columns_names): + if role == LogAttributeRole: + return self._columns[index] + elif role == Qt.DisplayRole: + return self._columns_names[index] + + def append(self, record): + pos = len(self._records) + + if self._limit and pos >= self._limit: + self.beginRemoveRows(QModelIndex(), 0, 0) + self._records.popleft() + self.endRemoveRows() + pos -= 1 + + self.beginInsertRows(QModelIndex(), pos, pos) + self._records.append(record) + self.endInsertRows() + + def clear(self): + self._records.clear() + self.reset() + + def record(self, pos): + return self._records[pos] + + +class LogRecordFilterModel(QSortFilterProxyModel): + def __init__(self, levels, **kwargs): + super().__init__(**kwargs) + self._levels = set(levels) + + def showLevel(self, name): + self._levels.add(name) + self.invalidateFilter() + + def hideLevel(self, name): + self._levels.discard(name) + self.invalidateFilter() + + def setSourceModel(self, source_model): + """ + :type source_model: LogRecordModel + """ + if isinstance(source_model, LogRecordModel): + super().setSourceModel(source_model) + else: + raise TypeError( + 'LogRecordFilterModel source must be LogRecordModel, not {}' + .format(type(source_model).__name__) + ) + + def filterAcceptsRow(self, source_row, source_parent): + if self.sourceModel() is not None: + record = self.sourceModel().record(source_row) + return record.levelname in self._levels + + return False + + +def log_model_factory(config): + # Get logging configuration + limit = config.get('logging.limit', 0) + msg_format = config.get('logging.msg_format', None) + date_format = config.get('logging.date_format', None) + levels_background = { + level: QColor(*color) + for level, color + in config.get('logging.backgrounds', {}).items() + } + levels_foreground = { + level: QColor(*color) + for level, color + in config.get('logging.foregrounds', {}).items() + } + + # Formatter to display log messages + #formatter = Formatter(fmt=msg_format, datefmt=date_format) + + # Return a new model to store logging records + return LogRecordModel( + LOG_ATTRIBUTES, levels_background, levels_foreground, limit=limit) diff --git a/lisp/ui/logging/status.py b/lisp/ui/logging/status.py new file mode 100644 index 000000000..d6fc2691f --- /dev/null +++ b/lisp/ui/logging/status.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import logging + +from PyQt5.QtCore import pyqtSignal +from PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout, QFrame + +from lisp.ui.themes import IconTheme + + +class LogStatusView(QWidget): + double_clicked = pyqtSignal() + + def __init__(self, log_model, icons_size=16, **kwargs): + """ + :type log_model: lisp.ui.logging.models.LogRecordModel + """ + super().__init__(**kwargs) + self.setLayout(QHBoxLayout()) + self.layout().setSpacing(10) + self.layout().setContentsMargins(5, 5, 5, 5) + + self._log_model = log_model + self._log_model.rowsInserted.connect(self._new_rows) + + self._icons_size = icons_size + self._errors = 0 + self._warnings = 0 + + self.messageLabel = QLabel(self) + self.messageLabel.setMaximumWidth(300) + self.layout().addWidget(self.messageLabel) + + self.line = QFrame() + self.line.setFrameShape(QFrame.VLine) + self.line.setFrameShadow(QFrame.Sunken) + self.layout().addWidget(self.line) + + self.iconWidget = QWidget(self) + self.iconWidget.setToolTip('Errors/Warnings') + self.iconWidget.setLayout(QHBoxLayout()) + self.iconWidget.layout().setContentsMargins(0, 0, 0, 0) + self.iconWidget.layout().setSpacing(5) + self.layout().addWidget(self.iconWidget) + + self.errorsCount = QLabel('0', self.iconWidget) + self.iconWidget.layout().addWidget(self.errorsCount) + + self.errorIcon = QLabel(self.iconWidget) + self.errorIcon.setPixmap( + IconTheme.get('dialog-error').pixmap(icons_size)) + self.iconWidget.layout().addWidget(self.errorIcon) + + def mouseDoubleClickEvent(self, e): + self.double_clicked.emit() + + def _new_rows(self, parent, start, end): + last_record = self._log_model.record(self._log_model.rowCount() - 1) + + if last_record.levelno >= logging.INFO: + self.messageLabel.setText(last_record.message) + + for n in range(start, end + 1): + level = self._log_model.record(n).levelno + if level >= logging.WARNING: + self._errors += 1 + + self.errorsCount.setText(str(self._errors)) diff --git a/lisp/ui/logging/viewer.py b/lisp/ui/logging/viewer.py new file mode 100644 index 000000000..b97d5d206 --- /dev/null +++ b/lisp/ui/logging/viewer.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QAction, QToolBar, \ + QMainWindow, QStatusBar, QLabel, QTableView, QToolButton + +from lisp.ui.logging.common import LOG_LEVELS, LogAttributeRole, LOG_ATTRIBUTES +from lisp.ui.logging.models import LogRecordFilterModel +from lisp.ui.ui_utils import translate + + +class LogViewer(QMainWindow): + """Provide a custom window to display a simple list of logging records. + + Displayed records can be filtered based on their level. + """ + + def __init__(self, log_model, config, **kwargs): + """ + :type log_model: lisp.ui.logging.models.LogRecordModel + :type config: lisp.core.configuration.Configuration + """ + super().__init__(**kwargs) + self.resize(700, 500) + self.setWindowTitle( + translate('Logging', 'Linux Show Player - Log Viewer')) + self.setStatusBar(QStatusBar(self)) + + # Add a permanent label to the toolbar to display shown/filter records + self.statusLabel = QLabel() + self.statusBar().addPermanentWidget(self.statusLabel) + + # ToolBar + self.optionsToolBar = QToolBar(self) + self.addToolBar(self.optionsToolBar) + # Create level-toggle actions + self.levelsActions = self._create_actions( + self.optionsToolBar, LOG_LEVELS, self._toggle_level) + + self.columnsMenu = QToolButton() + self.columnsMenu.setText('Columns') + self.columnsMenu.setPopupMode(QToolButton.InstantPopup) + # Create column-toggle actions + self.columnsActions = self._create_actions( + self.columnsMenu, LOG_ATTRIBUTES, self._toggle_column) + + self.optionsToolBar.addSeparator() + self.optionsToolBar.addWidget(self.columnsMenu) + + # Setup level filters and columns + visible_levels = config.get('logging.viewer.visibleLevels', ()) + visible_columns = config.get('logging.viewer.visibleColumns', ()) + for level in visible_levels: + self.levelsActions[level].setChecked(True) + + # Filter model to show/hide records based on their level + self.filterModel = LogRecordFilterModel(visible_levels) + self.filterModel.setSourceModel(log_model) + + # List view to display the messages stored in the model + self.logView = QTableView(self) + self.logView.setModel(self.filterModel) + self.logView.setSelectionMode(QTableView.NoSelection) + self.logView.setFocusPolicy(Qt.NoFocus) + self.setCentralWidget(self.logView) + + # Setup visible columns + for n in range(self.filterModel.columnCount()): + column = self.filterModel.headerData( + n, Qt.Horizontal, LogAttributeRole) + visible = column in visible_columns + + self.columnsActions[column].setChecked(visible) + self.logView.setColumnHidden(n, not visible) + + # When the filter model change, update the status-label + self.filterModel.rowsInserted.connect(self._rows_changed) + self.filterModel.rowsRemoved.connect(self._rows_changed) + self.filterModel.modelReset.connect(self._rows_changed) + + def _rows_changed(self, *args): + self.statusLabel.setText( + 'Showing {} of {} records'.format( + self.filterModel.rowCount(), + self.filterModel.sourceModel().rowCount()) + ) + self.logView.resizeColumnsToContents() + self.logView.scrollToBottom() + + def _create_actions(self, menu, actions, trigger_slot): + menu_actions = {} + for key, name in actions.items(): + action = QAction(translate('Logging', name)) + action.setCheckable(True) + action.triggered.connect(self._action_slot(trigger_slot, key)) + + menu.addAction(action) + menu_actions[key] = action + + return menu_actions + + @staticmethod + def _action_slot(target, attr): + def slot(checked): + target(attr, checked) + + return slot + + def _toggle_level(self, level, toggle): + if toggle: + self.filterModel.showLevel(level) + else: + self.filterModel.hideLevel(level) + + def _toggle_column(self, column, toggle): + for n in range(self.filterModel.columnCount()): + column_n = self.filterModel.headerData( + n, Qt.Horizontal, LogAttributeRole) + + if column_n == column: + self.logView.setColumnHidden(n, not toggle) + self.logView.resizeColumnsToContents() + return diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 79e68f96b..1c2e987ff 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,18 +17,24 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging import os from PyQt5 import QtCore from PyQt5.QtCore import pyqtSignal from PyQt5.QtGui import QKeySequence from PyQt5.QtWidgets import QMainWindow, QStatusBar, QMenuBar, QMenu, QAction, \ - qApp, QFileDialog, QDialog, QMessageBox, QVBoxLayout, QWidget + qApp, QFileDialog, QMessageBox, QVBoxLayout, QWidget from lisp.core.actions_handler import MainActionsHandler from lisp.core.singleton import QSingleton from lisp.cues.media_cue import MediaCue from lisp.ui.about import About +from lisp.ui.logging.dialog import LogDialogs +from lisp.ui.logging.handler import LogModelHandler +from lisp.ui.logging.models import log_model_factory +from lisp.ui.logging.status import LogStatusView +from lisp.ui.logging.viewer import LogViewer from lisp.ui.settings.app_settings import AppSettings from lisp.ui.ui_utils import translate @@ -38,8 +44,8 @@ class MainWindow(QMainWindow, metaclass=QSingleton): save_session = pyqtSignal(str) open_session = pyqtSignal(str) - def __init__(self, title='Linux Show Player'): - super().__init__() + def __init__(self, conf, title='Linux Show Player', **kwargs): + super().__init__(**kwargs) self.setMinimumSize(500, 400) self.setCentralWidget(QWidget()) self.centralWidget().setLayout(QVBoxLayout()) @@ -47,11 +53,14 @@ def __init__(self, title='Linux Show Player'): self._cue_add_menu = {} self._title = title + + self.conf = conf self.session = None # Status Bar - self.statusBar = QStatusBar(self) - self.setStatusBar(self.statusBar) + self.setStatusBar(QStatusBar(self)) + + # Changes MainActionsHandler.action_done.connect(self._action_done) MainActionsHandler.action_undone.connect(self._action_undone) MainActionsHandler.action_redone.connect(self._action_redone) @@ -137,6 +146,23 @@ def __init__(self, title='Linux Show Player'): self.menuAbout.addSeparator() self.menuAbout.addAction(self.actionAbout_Qt) + # Logging model + self.logModel = log_model_factory(self.conf) + # Handler to populate the model + self.logHandler = LogModelHandler(self.logModel) + logging.getLogger().addHandler(self.logHandler) + + # Logging + self.logViewer = LogViewer(self.logModel, self.conf) + + # Logging status widget + self.logStatus = LogStatusView(self.logModel) + self.logStatus.double_clicked.connect(self.logViewer.show) + self.statusBar().addPermanentWidget(self.logStatus) + + # Logging dialogs for errors + self.logDialogs = LogDialogs(self.logModel, logging.ERROR) + # Set component text self.retranslateUi() @@ -245,17 +271,12 @@ def update_window_title(self): self.setWindowTitle(tile) def _action_done(self, action): - self.statusBar.showMessage(action.log()) self.update_window_title() def _action_undone(self, action): - self.statusBar.showMessage( - translate('MainWindow', 'Undone: ') + action.log()) self.update_window_title() def _action_redone(self, action): - self.statusBar.showMessage( - translate('MainWindow', 'Redone: ') + action.log()) self.update_window_title() def _save(self): diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index c593a3e50..74eeb2489 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -19,11 +19,10 @@ from PyQt5.QtCore import Qt, QEvent from PyQt5.QtWidgets import QStyledItemDelegate, QComboBox, QSpinBox, \ - QLineEdit, QStyle, QDialog, QCheckBox, QDoubleSpinBox + QLineEdit, QStyle, QDialog, QCheckBox from lisp.application import Application from lisp.cues.cue import CueAction -from lisp.plugins.osc.osc_server import OscMessageType from lisp.ui.qmodels import CueClassRole from lisp.ui.ui_utils import translate from lisp.ui.widgets import CueActionComboBox @@ -162,69 +161,6 @@ def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) -class OscArgumentDelegate(QStyledItemDelegate): - MIN = -1000000 - MAX = 1000000 - DECIMALS = 4 - - def __init__(self, tag=OscMessageType.Int, **kwargs): - super().__init__(**kwargs) - self.tag = tag - - def createEditor(self, parent, option, index): - if self.tag is OscMessageType.Int: - editor = QSpinBox(parent) - editor.setRange(self.MIN, self.MAX) - editor.setSingleStep(1) - elif self.tag is OscMessageType.Float: - editor = QDoubleSpinBox(parent) - editor.setRange(self.MIN, self.MAX) - editor.setDecimals(3) - editor.setSingleStep(0.01) - elif self.tag is OscMessageType.Bool: - editor = QCheckBox(parent) - elif self.tag is OscMessageType.String: - editor = QLineEdit(parent) - editor.setFrame(False) - else: - editor = None - - return editor - - def setEditorData(self, widget, index): - value = index.model().data(index, Qt.EditRole) - - if self.tag is OscMessageType.Int: - if isinstance(value, int): - widget.setValue(value) - elif self.tag is OscMessageType.Float: - if isinstance(value, float): - widget.setValue(value) - elif self.tag is OscMessageType.Bool: - if isinstance(value, bool): - widget.setChecked(value) - else: - widget.setText(str(value)) - - def setModelData(self, widget, model, index): - if self.tag is OscMessageType.Int or self.tag is OscMessageType.Float: - widget.interpretText() - model.setData(index, widget.value(), Qt.EditRole) - elif self.tag is OscMessageType.Bool: - model.setData(index, widget.isChecked(), Qt.EditRole) - else: - model.setData(index, widget.text(), Qt.EditRole) - - def updateEditorGeometry(self, editor, option, index): - editor.setGeometry(option.rect) - - def updateEditor(self, tag=None): - if isinstance(tag, OscMessageType): - self.tag = tag - else: - self.tag = None - - class CueActionDelegate(LabelDelegate): Mode = CueActionComboBox.Mode diff --git a/lisp/ui/themes/dark/theme.qss b/lisp/ui/themes/dark/theme.qss index 1a28fb2c0..cd98c8cd2 100644 --- a/lisp/ui/themes/dark/theme.qss +++ b/lisp/ui/themes/dark/theme.qss @@ -569,6 +569,10 @@ QToolButton:hover { background-color: #3A3A3A; } +QToolButton::menu-indicator { + image: none; +} + QCheckBox, QRadioButton { background-color: transparent; } diff --git a/lisp/ui/themes/icons/lisp/led-error.svg b/lisp/ui/themes/icons/lisp/led-error.svg index 002addf0c..43fff65ad 100644 --- a/lisp/ui/themes/icons/lisp/led-error.svg +++ b/lisp/ui/themes/icons/lisp/led-error.svg @@ -1,131 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/lisp/led-off.svg b/lisp/ui/themes/icons/lisp/led-off.svg index c6f5b8732..e9d97d34c 100644 --- a/lisp/ui/themes/icons/lisp/led-off.svg +++ b/lisp/ui/themes/icons/lisp/led-off.svg @@ -1,131 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/lisp/led-pause.svg b/lisp/ui/themes/icons/lisp/led-pause.svg index b97d90f04..c181410da 100644 --- a/lisp/ui/themes/icons/lisp/led-pause.svg +++ b/lisp/ui/themes/icons/lisp/led-pause.svg @@ -1,131 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/lisp/led-running.svg b/lisp/ui/themes/icons/lisp/led-running.svg index 70748e0d3..d56e3e11c 100644 --- a/lisp/ui/themes/icons/lisp/led-running.svg +++ b/lisp/ui/themes/icons/lisp/led-running.svg @@ -1,131 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/lisp/mixer-handle.svg b/lisp/ui/themes/icons/lisp/mixer-handle.svg index d76208264..c3a532a72 100644 --- a/lisp/ui/themes/icons/lisp/mixer-handle.svg +++ b/lisp/ui/themes/icons/lisp/mixer-handle.svg @@ -1,212 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/NOTE b/lisp/ui/themes/icons/numix/NOTE deleted file mode 100644 index 5c2b65999..000000000 --- a/lisp/ui/themes/icons/numix/NOTE +++ /dev/null @@ -1 +0,0 @@ -Icons from "Paprius Icon Theme" \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/audio-volume-high.svg b/lisp/ui/themes/icons/numix/audio-volume-high.svg index 65ee60f73..1f5044916 100644 --- a/lisp/ui/themes/icons/numix/audio-volume-high.svg +++ b/lisp/ui/themes/icons/numix/audio-volume-high.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/audio-volume-low.svg b/lisp/ui/themes/icons/numix/audio-volume-low.svg index d9e694cce..648b4cf36 100644 --- a/lisp/ui/themes/icons/numix/audio-volume-low.svg +++ b/lisp/ui/themes/icons/numix/audio-volume-low.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/audio-volume-medium.svg b/lisp/ui/themes/icons/numix/audio-volume-medium.svg index 4286fc8f8..699d468d5 100644 --- a/lisp/ui/themes/icons/numix/audio-volume-medium.svg +++ b/lisp/ui/themes/icons/numix/audio-volume-medium.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/audio-volume-muted.svg b/lisp/ui/themes/icons/numix/audio-volume-muted.svg index d19c73603..895e94b6e 100644 --- a/lisp/ui/themes/icons/numix/audio-volume-muted.svg +++ b/lisp/ui/themes/icons/numix/audio-volume-muted.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/cue-interrupt.svg b/lisp/ui/themes/icons/numix/cue-interrupt.svg index 2c6cfebdc..b07dbf044 100644 --- a/lisp/ui/themes/icons/numix/cue-interrupt.svg +++ b/lisp/ui/themes/icons/numix/cue-interrupt.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/cue-pause.svg b/lisp/ui/themes/icons/numix/cue-pause.svg index 39a356a5c..3625f1a60 100644 --- a/lisp/ui/themes/icons/numix/cue-pause.svg +++ b/lisp/ui/themes/icons/numix/cue-pause.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/cue-start.svg b/lisp/ui/themes/icons/numix/cue-start.svg index 07df6ef16..870e84567 100644 --- a/lisp/ui/themes/icons/numix/cue-start.svg +++ b/lisp/ui/themes/icons/numix/cue-start.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/cue-stop.svg b/lisp/ui/themes/icons/numix/cue-stop.svg index b397553dd..e84173d62 100644 --- a/lisp/ui/themes/icons/numix/cue-stop.svg +++ b/lisp/ui/themes/icons/numix/cue-stop.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/dialog-error.svg b/lisp/ui/themes/icons/numix/dialog-error.svg new file mode 100644 index 000000000..fb595800a --- /dev/null +++ b/lisp/ui/themes/icons/numix/dialog-error.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/numix/dialog-information.svg b/lisp/ui/themes/icons/numix/dialog-information.svg new file mode 100644 index 000000000..dd8cc7360 --- /dev/null +++ b/lisp/ui/themes/icons/numix/dialog-information.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/numix/dialog-question.svg b/lisp/ui/themes/icons/numix/dialog-question.svg new file mode 100644 index 000000000..11524af1b --- /dev/null +++ b/lisp/ui/themes/icons/numix/dialog-question.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/numix/dialog-warning.svg b/lisp/ui/themes/icons/numix/dialog-warning.svg new file mode 100644 index 000000000..d55713da1 --- /dev/null +++ b/lisp/ui/themes/icons/numix/dialog-warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/fadein-generic.svg b/lisp/ui/themes/icons/numix/fadein-generic.svg index f03aba150..8f118732b 100644 --- a/lisp/ui/themes/icons/numix/fadein-generic.svg +++ b/lisp/ui/themes/icons/numix/fadein-generic.svg @@ -1,69 +1 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/fadeout-generic.svg b/lisp/ui/themes/icons/numix/fadeout-generic.svg index 4d49e99fc..2a43dfd2f 100644 --- a/lisp/ui/themes/icons/numix/fadeout-generic.svg +++ b/lisp/ui/themes/icons/numix/fadeout-generic.svg @@ -1,69 +1 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/go-next.svg b/lisp/ui/themes/icons/numix/go-next.svg new file mode 100644 index 000000000..b690c9635 --- /dev/null +++ b/lisp/ui/themes/icons/numix/go-next.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/themes/icons/numix/go-previous.svg b/lisp/ui/themes/icons/numix/go-previous.svg new file mode 100644 index 000000000..649611d5d --- /dev/null +++ b/lisp/ui/themes/icons/numix/go-previous.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/themes/icons/numix/help-info.svg b/lisp/ui/themes/icons/numix/help-info.svg new file mode 100644 index 000000000..7fe4e9298 --- /dev/null +++ b/lisp/ui/themes/icons/numix/help-info.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/themes/icons/numix/media-eject.svg b/lisp/ui/themes/icons/numix/media-eject.svg index 22d746140..c146bdb8a 100644 --- a/lisp/ui/themes/icons/numix/media-eject.svg +++ b/lisp/ui/themes/icons/numix/media-eject.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/media-record.svg b/lisp/ui/themes/icons/numix/media-record.svg index 25b172d93..a47192241 100644 --- a/lisp/ui/themes/icons/numix/media-record.svg +++ b/lisp/ui/themes/icons/numix/media-record.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/media-seek-backward.svg b/lisp/ui/themes/icons/numix/media-seek-backward.svg index 6d810ad18..1909982fb 100644 --- a/lisp/ui/themes/icons/numix/media-seek-backward.svg +++ b/lisp/ui/themes/icons/numix/media-seek-backward.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/media-seek-forward.svg b/lisp/ui/themes/icons/numix/media-seek-forward.svg index 862e3f64b..d8698f7b9 100644 --- a/lisp/ui/themes/icons/numix/media-seek-forward.svg +++ b/lisp/ui/themes/icons/numix/media-seek-forward.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/media-skip-backward.svg b/lisp/ui/themes/icons/numix/media-skip-backward.svg index 078849d90..433298616 100644 --- a/lisp/ui/themes/icons/numix/media-skip-backward.svg +++ b/lisp/ui/themes/icons/numix/media-skip-backward.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/media-skip-forward.svg b/lisp/ui/themes/icons/numix/media-skip-forward.svg index 613fafe49..d138185b0 100644 --- a/lisp/ui/themes/icons/numix/media-skip-forward.svg +++ b/lisp/ui/themes/icons/numix/media-skip-forward.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/audio-volume-high.svg b/lisp/ui/themes/icons/symbolic/audio-volume-high.svg index 25a7d7a1b..c63c39ea5 100644 --- a/lisp/ui/themes/icons/symbolic/audio-volume-high.svg +++ b/lisp/ui/themes/icons/symbolic/audio-volume-high.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/audio-volume-low.svg b/lisp/ui/themes/icons/symbolic/audio-volume-low.svg index 84de0effb..612bfbb1a 100644 --- a/lisp/ui/themes/icons/symbolic/audio-volume-low.svg +++ b/lisp/ui/themes/icons/symbolic/audio-volume-low.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/audio-volume-medium.svg b/lisp/ui/themes/icons/symbolic/audio-volume-medium.svg index 9adb0a84e..801b0692d 100644 --- a/lisp/ui/themes/icons/symbolic/audio-volume-medium.svg +++ b/lisp/ui/themes/icons/symbolic/audio-volume-medium.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/audio-volume-muted.svg b/lisp/ui/themes/icons/symbolic/audio-volume-muted.svg index 9a97be2a3..124d42d6c 100644 --- a/lisp/ui/themes/icons/symbolic/audio-volume-muted.svg +++ b/lisp/ui/themes/icons/symbolic/audio-volume-muted.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/auto-follow.svg b/lisp/ui/themes/icons/symbolic/auto-follow.svg index ca63712dc..feb54a422 100644 --- a/lisp/ui/themes/icons/symbolic/auto-follow.svg +++ b/lisp/ui/themes/icons/symbolic/auto-follow.svg @@ -1,90 +1 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/auto-next.svg b/lisp/ui/themes/icons/symbolic/auto-next.svg index 7648d432d..a1de92a56 100644 --- a/lisp/ui/themes/icons/symbolic/auto-next.svg +++ b/lisp/ui/themes/icons/symbolic/auto-next.svg @@ -1,73 +1 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/cue-interrupt.svg b/lisp/ui/themes/icons/symbolic/cue-interrupt.svg index 223335ada..c35b008b8 100644 --- a/lisp/ui/themes/icons/symbolic/cue-interrupt.svg +++ b/lisp/ui/themes/icons/symbolic/cue-interrupt.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/cue-pause.svg b/lisp/ui/themes/icons/symbolic/cue-pause.svg index a4b3ae0b5..59f205c2b 100644 --- a/lisp/ui/themes/icons/symbolic/cue-pause.svg +++ b/lisp/ui/themes/icons/symbolic/cue-pause.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/cue-start.svg b/lisp/ui/themes/icons/symbolic/cue-start.svg index 891f6ad60..e72965424 100644 --- a/lisp/ui/themes/icons/symbolic/cue-start.svg +++ b/lisp/ui/themes/icons/symbolic/cue-start.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/cue-stop.svg b/lisp/ui/themes/icons/symbolic/cue-stop.svg index 4c899eb29..35948a37b 100644 --- a/lisp/ui/themes/icons/symbolic/cue-stop.svg +++ b/lisp/ui/themes/icons/symbolic/cue-stop.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/dialog-error.svg b/lisp/ui/themes/icons/symbolic/dialog-error.svg new file mode 100644 index 000000000..cbc6e287d --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/dialog-error.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/themes/icons/symbolic/dialog-information.svg b/lisp/ui/themes/icons/symbolic/dialog-information.svg new file mode 100644 index 000000000..23ab0eaf3 --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/dialog-information.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/themes/icons/symbolic/dialog-question.svg b/lisp/ui/themes/icons/symbolic/dialog-question.svg new file mode 100644 index 000000000..749085d7a --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/dialog-question.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/themes/icons/symbolic/dialog-warning.svg b/lisp/ui/themes/icons/symbolic/dialog-warning.svg new file mode 100644 index 000000000..eaade11ce --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/dialog-warning.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/themes/icons/symbolic/fadein-generic.svg b/lisp/ui/themes/icons/symbolic/fadein-generic.svg index dcb4c625f..731868850 100644 --- a/lisp/ui/themes/icons/symbolic/fadein-generic.svg +++ b/lisp/ui/themes/icons/symbolic/fadein-generic.svg @@ -1,74 +1 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/fadeout-generic.svg b/lisp/ui/themes/icons/symbolic/fadeout-generic.svg index bd33f5717..5083f8359 100644 --- a/lisp/ui/themes/icons/symbolic/fadeout-generic.svg +++ b/lisp/ui/themes/icons/symbolic/fadeout-generic.svg @@ -1,74 +1 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/go-next.svg b/lisp/ui/themes/icons/symbolic/go-next.svg new file mode 100644 index 000000000..b47909357 --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/go-next.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/symbolic/go-previous.svg b/lisp/ui/themes/icons/symbolic/go-previous.svg new file mode 100644 index 000000000..16ef162a4 --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/go-previous.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/symbolic/help-info.svg b/lisp/ui/themes/icons/symbolic/help-info.svg new file mode 100644 index 000000000..a70617e77 --- /dev/null +++ b/lisp/ui/themes/icons/symbolic/help-info.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/symbolic/media-eject.svg b/lisp/ui/themes/icons/symbolic/media-eject.svg index 4cd574e67..5e1a1359e 100644 --- a/lisp/ui/themes/icons/symbolic/media-eject.svg +++ b/lisp/ui/themes/icons/symbolic/media-eject.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/media-record.svg b/lisp/ui/themes/icons/symbolic/media-record.svg index 2031186ac..9da42dcfa 100644 --- a/lisp/ui/themes/icons/symbolic/media-record.svg +++ b/lisp/ui/themes/icons/symbolic/media-record.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/media-seek-backward.svg b/lisp/ui/themes/icons/symbolic/media-seek-backward.svg index 2b629317e..dceba05b9 100644 --- a/lisp/ui/themes/icons/symbolic/media-seek-backward.svg +++ b/lisp/ui/themes/icons/symbolic/media-seek-backward.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/media-seek-forward.svg b/lisp/ui/themes/icons/symbolic/media-seek-forward.svg index be9a09a9f..630fa1653 100644 --- a/lisp/ui/themes/icons/symbolic/media-seek-forward.svg +++ b/lisp/ui/themes/icons/symbolic/media-seek-forward.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/media-skip-backward.svg b/lisp/ui/themes/icons/symbolic/media-skip-backward.svg index 64d17f65f..9326f7204 100644 --- a/lisp/ui/themes/icons/symbolic/media-skip-backward.svg +++ b/lisp/ui/themes/icons/symbolic/media-skip-backward.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/media-skip-forward.svg b/lisp/ui/themes/icons/symbolic/media-skip-forward.svg index 82b864de9..7c6210d3b 100644 --- a/lisp/ui/themes/icons/symbolic/media-skip-forward.svg +++ b/lisp/ui/themes/icons/symbolic/media-skip-forward.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/ui_utils.py b/lisp/ui/ui_utils.py index 82b5f68e1..f01b29363 100644 --- a/lisp/ui/ui_utils.py +++ b/lisp/ui/ui_utils.py @@ -26,6 +26,8 @@ from lisp import I18N_PATH +logger = logging.getLogger(__name__) + def qfile_filters(extensions, allexts=True, anyfile=True): """Create a filter-string for a FileChooser. @@ -71,9 +73,9 @@ def install_translation(name, tr_path=I18N_PATH): if QApplication.installTranslator(translator): # Keep a reference, QApplication does not _TRANSLATORS.append(translator) - logging.debug('Installed translation: {}'.format(tr_file)) + logger.debug('Installed translation: {}'.format(tr_file)) else: - logging.debug('No translation for: {}'.format(tr_file)) + logger.debug('No translation for: {}'.format(tr_file)) def translate(context, text, disambiguation=None, n=-1): diff --git a/lisp/ui/widgets/qdbmeter.py b/lisp/ui/widgets/qdbmeter.py index f84cfd199..43d8ca80f 100644 --- a/lisp/ui/widgets/qdbmeter.py +++ b/lisp/ui/widgets/qdbmeter.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,8 +21,6 @@ from PyQt5.QtGui import QLinearGradient, QColor, QPainter from PyQt5.QtWidgets import QWidget -from lisp.core.decorators import suppress_exceptions - class QDbMeter(QWidget): @@ -57,7 +55,6 @@ def plot(self, peaks, rms, decPeak): self.repaint() - @suppress_exceptions def paintEvent(self, e): if not self.visibleRegion().isEmpty(): # Stretch factor @@ -69,7 +66,8 @@ def paintEvent(self, e): if peak > self.db_clip: self.clipping[n] = True - if peak < self.db_min: + # Checks also for NaN values + if peak < self.db_min or peak != peak: peak = self.db_min elif peak > self.db_max: peak = self.db_max @@ -77,8 +75,9 @@ def paintEvent(self, e): peaks.append(round((peak - self.db_min) * mul)) rmss = [] - for n, rms in enumerate(self.rmss): - if rms < self.db_min: + for rms in self.rmss: + # Checks also for NaN values + if rms < self.db_min or rms != rms: rms = self.db_min elif rms > self.db_max: rms = self.db_max @@ -87,7 +86,8 @@ def paintEvent(self, e): dPeaks = [] for dPeak in self.decPeak: - if dPeak < self.db_min: + # Checks also for NaN values + if dPeak < self.db_min or dPeak != dPeak: dPeak = self.db_min elif dPeak > self.db_max: dPeak = self.db_max diff --git a/setup.py b/setup.py index e1555ab00..58dca74ca 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,7 @@ def find_files(directory, regex='.*', rel=''): paths.append(file_path[len(rel):]) return paths + # List the directories with icons to be installed lisp_icons = find_files('lisp/ui/styles/icons', rel='lisp/ui/styles/') @@ -30,6 +31,8 @@ def find_files(directory, regex='.*', rel=''): url=lisp.__email__, description='Cue player for live shows', install_requires=[ + 'PyQt5', + 'PyGobject', 'sortedcontainers', 'mido', 'python-rtmidi', From 6b2b96fbb0f4677c4f84fde1054456b002b4030c Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 7 Mar 2018 15:02:12 +0100 Subject: [PATCH 091/333] Minor cleanup --- lisp/ui/logging/common.py | 1 - lisp/ui/logging/models.py | 10 ++-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/lisp/ui/logging/common.py b/lisp/ui/logging/common.py index c55a3dcf0..40c215df6 100644 --- a/lisp/ui/logging/common.py +++ b/lisp/ui/logging/common.py @@ -41,7 +41,6 @@ 'name': QT_TRANSLATE_NOOP('Logging', 'Logger name'), 'levelname': QT_TRANSLATE_NOOP('Logging', 'Level'), 'message': QT_TRANSLATE_NOOP('Logging', 'Message'), - #'exc_info': QT_TRANSLATE_NOOP('Logging', 'Traceback'), 'funcName': QT_TRANSLATE_NOOP('Logging', 'Function'), 'pathname': QT_TRANSLATE_NOOP('Logging', 'Path name'), 'filename': QT_TRANSLATE_NOOP('Logging', 'File name'), diff --git a/lisp/ui/logging/models.py b/lisp/ui/logging/models.py index ddf23fc1a..0e2bd32e9 100644 --- a/lisp/ui/logging/models.py +++ b/lisp/ui/logging/models.py @@ -20,9 +20,8 @@ from collections import deque from PyQt5.QtCore import QModelIndex, Qt, QSortFilterProxyModel, \ - QAbstractListModel, QAbstractTableModel + QAbstractTableModel from PyQt5.QtGui import QFont, QColor -from logging import Formatter from lisp.ui.logging.common import LogRecordRole, LOG_ATTRIBUTES, LogAttributeRole @@ -69,7 +68,7 @@ def data(self, index, role=Qt.DisplayRole): if role == Qt.DisplayRole: try: return getattr(record, self._columns[index.column()]) - except Exception: + except AttributeError: pass elif role == Qt.BackgroundColorRole: return self._backgrounds.get(record.levelname) @@ -144,8 +143,6 @@ def filterAcceptsRow(self, source_row, source_parent): def log_model_factory(config): # Get logging configuration limit = config.get('logging.limit', 0) - msg_format = config.get('logging.msg_format', None) - date_format = config.get('logging.date_format', None) levels_background = { level: QColor(*color) for level, color @@ -157,9 +154,6 @@ def log_model_factory(config): in config.get('logging.foregrounds', {}).items() } - # Formatter to display log messages - #formatter = Formatter(fmt=msg_format, datefmt=date_format) - # Return a new model to store logging records return LogRecordModel( LOG_ATTRIBUTES, levels_background, levels_foreground, limit=limit) From e21012a74e8b2f07cae0bd3e28f389889ccf3059 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 8 Mar 2018 15:22:40 +0100 Subject: [PATCH 092/333] Logging improvment, new icons Add: added standard icons to the numix and symbolic theme Update: some change in how the icon-themes works Update: app themes are now discovered at startup Update: removed arguments from Cue and Media `error` signals --- .landscape.yml | 2 - Pipfile.lock | 141 +++++++------- lisp/__init__.py | 5 +- lisp/backend/media.py | 2 +- lisp/core/signal.py | 2 +- lisp/cues/cue.py | 6 +- lisp/cues/media_cue.py | 6 +- lisp/default.json | 4 +- lisp/layouts/cart_layout/cue_widget.py | 8 +- lisp/layouts/list_layout/control_buttons.py | 2 +- lisp/layouts/list_layout/cue_list_item.py | 2 +- lisp/layouts/list_layout/cue_list_model.py | 2 +- lisp/layouts/list_layout/listwidgets.py | 4 +- lisp/main.py | 78 ++++---- lisp/plugins/action_cues/osc_cue.py | 13 +- lisp/plugins/action_cues/volume_control.py | 16 +- lisp/plugins/gst_backend/gst_media.py | 10 +- lisp/plugins/gst_backend/gst_pipe_edit.py | 2 +- lisp/plugins/rename_cues/rename_ui.py | 6 +- lisp/ui/about.py | 2 +- lisp/ui/icons/__init__.py | 71 +++++++ .../{themes => }/icons/lisp/fadein-linear.png | Bin .../icons/lisp/fadein-quadratic.png | Bin .../icons/lisp/fadein-quadratic2.png | Bin .../icons/lisp/fadeout-linear.png | Bin .../icons/lisp/fadeout-quadratic.png | Bin .../icons/lisp/fadeout-quadratic2.png | Bin lisp/ui/{themes => }/icons/lisp/led-error.svg | 0 lisp/ui/{themes => }/icons/lisp/led-off.svg | 0 lisp/ui/{themes => }/icons/lisp/led-pause.svg | 0 .../{themes => }/icons/lisp/led-running.svg | 0 .../icons/lisp/linux-show-player.png | Bin .../{themes => }/icons/lisp/mixer-handle.svg | 0 .../numix/custom}/cue-interrupt.svg | 0 .../numix/custom}/cue-pause.svg | 0 .../numix/custom}/cue-start.svg | 0 .../numix => icons/numix/custom}/cue-stop.svg | 0 .../numix/custom}/fadein-generic.svg | 0 .../numix/custom}/fadeout-generic.svg | 0 .../standard/actions/address-book-new.svg | 7 + .../standard/actions/application-exit.svg | 11 ++ .../standard/actions/appointment-new.svg | 8 + .../numix/standard/actions/call-start.svg | 5 + .../numix/standard/actions/call-stop.svg | 6 + .../numix/standard/actions/contact-new.svg | 4 + .../numix/standard/actions/document-new.svg | 4 + .../standard/actions/document-open-recent.svg | 15 ++ .../numix/standard/actions/document-open.svg | 11 ++ .../standard/actions/document-page-setup.svg | 9 + .../actions/document-print-preview.svg | 14 ++ .../numix/standard/actions/document-print.svg | 9 + .../standard/actions/document-properties.svg | 7 + .../standard/actions/document-revert.svg | 4 + .../standard/actions/document-save-as.svg | 9 + .../numix/standard/actions/document-save.svg | 9 + .../numix/standard/actions/document-send.svg | 11 ++ .../numix/standard/actions/edit-clear.svg | 4 + .../numix/standard/actions/edit-copy.svg | 11 ++ .../icons/numix/standard/actions/edit-cut.svg | 8 + .../numix/standard/actions/edit-delete.svg | 9 + .../standard/actions/edit-find-replace.svg | 14 ++ .../numix/standard/actions/edit-find.svg | 7 + .../numix/standard/actions/edit-paste.svg | 9 + .../numix/standard/actions/edit-redo.svg | 3 + .../standard/actions/edit-select-all.svg | 12 ++ .../numix/standard/actions/edit-undo.svg | 3 + .../numix/standard/actions/folder-new.svg | 11 ++ .../standard/actions/format-indent-less.svg | 9 + .../standard/actions/format-indent-more.svg | 9 + .../actions/format-justify-center.svg | 8 + .../standard/actions/format-justify-fill.svg | 8 + .../standard/actions/format-justify-left.svg | 8 + .../standard/actions/format-justify-right.svg | 8 + .../standard/actions/format-text-bold.svg | 3 + .../actions/format-text-direction-ltr.svg | 7 + .../actions/format-text-direction-rtl.svg | 7 + .../standard/actions/format-text-italic.svg | 5 + .../actions/format-text-strikethrough.svg | 6 + .../actions/format-text-underline.svg | 6 + .../numix/standard/actions/go-bottom.svg | 7 + .../icons/numix/standard/actions/go-down.svg | 6 + .../icons/numix/standard/actions/go-first.svg | 12 ++ .../icons/numix/standard/actions/go-home.svg | 6 + .../icons/numix/standard/actions/go-jump.svg | 8 + .../icons/numix/standard/actions/go-last.svg | 11 ++ .../numix/standard/actions}/go-next.svg | 0 .../numix/standard/actions}/go-previous.svg | 0 .../icons/numix/standard/actions/go-top.svg | 11 ++ .../ui/icons/numix/standard/actions/go-up.svg | 6 + .../numix/standard/actions/help-about.svg} | 0 .../numix/standard/actions/help-contents.svg | 12 ++ .../icons/numix/standard/actions/help-faq.svg | 9 + .../numix/standard/actions/insert-image.svg | 5 + .../numix/standard/actions/insert-link.svg | 12 ++ .../numix/standard/actions/insert-object.svg | 9 + .../numix/standard/actions/insert-text.svg | 8 + .../icons/numix/standard/actions/list-add.svg | 3 + .../numix/standard/actions/list-remove.svg | 3 + .../numix/standard/actions/mail-forward.svg | 3 + .../standard/actions/mail-mark-important.svg | 18 ++ .../numix/standard/actions/mail-mark-junk.svg | 14 ++ .../standard/actions/mail-mark-notjunk.svg | 15 ++ .../numix/standard/actions/mail-mark-read.svg | 11 ++ .../standard/actions/mail-mark-unread.svg | 13 ++ .../standard/actions/mail-message-new.svg | 12 ++ .../numix/standard/actions/mail-reply-all.svg | 7 + .../standard/actions/mail-reply-sender.svg | 3 + .../standard/actions/mail-send-receive.svg | 4 + .../numix/standard/actions/mail-send.svg | 6 + .../numix/standard/actions/media-eject.svg | 6 + .../standard/actions/media-playback-pause.svg | 6 + .../standard/actions/media-playback-start.svg | 3 + .../standard/actions/media-playback-stop.svg | 3 + .../numix/standard/actions/media-record.svg | 3 + .../standard/actions/media-seek-backward.svg | 5 + .../standard/actions/media-seek-forward.svg | 7 + .../standard/actions/media-skip-backward.svg | 10 + .../standard/actions/media-skip-forward.svg | 8 + .../actions/object-flip-horizontal.svg | 10 + .../standard/actions/object-flip-vertical.svg | 12 ++ .../standard/actions/object-rotate-left.svg | 3 + .../standard/actions/object-rotate-right.svg | 3 + .../numix/standard/actions/process-stop.svg | 6 + .../standard/actions/system-lock-screen.svg | 4 + .../numix/standard/actions/system-log-out.svg | 13 ++ .../numix/standard/actions/system-reboot.svg | 3 + .../numix/standard/actions/system-run.svg | 9 + .../numix/standard/actions/system-search.svg | 7 + .../standard/actions/system-shutdown.svg | 11 ++ .../standard/actions/tools-check-spelling.svg | 8 + .../standard/actions/view-fullscreen.svg | 11 ++ .../numix/standard/actions/view-refresh.svg | 7 + .../numix/standard/actions/view-restore.svg | 11 ++ .../standard/actions/view-sort-ascending.svg | 10 + .../standard/actions/view-sort-descending.svg | 8 + .../numix/standard/actions/window-close.svg | 3 + .../numix/standard/actions/window-new.svg | 7 + .../numix/standard/actions/zoom-fit-best.svg | 10 + .../icons/numix/standard/actions/zoom-in.svg | 7 + .../numix/standard/actions/zoom-original.svg | 7 + .../icons/numix/standard/actions/zoom-out.svg | 7 + .../categories/applications-accessories.svg | 10 + .../categories/applications-development.svg | 6 + .../categories/applications-engineering.svg | 54 ++++++ .../categories/applications-games.svg | 3 + .../categories/applications-graphics.svg | 7 + .../categories/applications-internet.svg | 13 ++ .../categories/applications-multimedia.svg | 5 + .../categories/applications-office.svg | 3 + .../categories/applications-other.svg | 7 + .../categories/applications-science.svg | 8 + .../categories/applications-system.svg | 3 + .../categories/applications-utilities.svg | 6 + .../preferences-desktop-peripherals.svg | 15 ++ .../preferences-desktop-personal.svg | 4 + .../categories/preferences-desktop.svg | 4 + .../standard/categories/preferences-other.svg | 7 + .../categories/preferences-system-network.svg | 6 + .../categories/preferences-system.svg | 4 + .../numix/standard/categories/system-help.svg | 3 + .../numix/standard/devices/audio-card.svg | 12 ++ .../devices/audio-input-microphone.svg | 14 ++ .../icons/numix/standard/devices/battery.svg | 13 ++ .../numix/standard/devices/camera-photo.svg | 19 ++ .../numix/standard/devices/camera-web.svg | 9 + .../icons/numix/standard/devices/computer.svg | 5 + .../numix/standard/devices/drive-harddisk.svg | 10 + .../numix/standard/devices/drive-optical.svg | 13 ++ .../devices/drive-removable-media.svg | 10 + .../numix/standard/devices/input-gaming.svg | 12 ++ .../numix/standard/devices/input-keyboard.svg | 10 + .../numix/standard/devices/input-mouse.svg | 9 + .../numix/standard/devices/input-tablet.svg | 11 ++ .../numix/standard/devices/media-flash.svg | 11 ++ .../numix/standard/devices/media-floppy.svg | 9 + .../numix/standard/devices/media-optical.svg | 10 + .../numix/standard/devices/media-tape.svg | 11 ++ .../standard/devices/multimedia-player.svg | 8 + .../numix/standard/devices/network-wired.svg | 8 + .../standard/devices/network-wireless.svg | 9 + lisp/ui/icons/numix/standard/devices/pda.svg | 9 + .../ui/icons/numix/standard/devices/phone.svg | 12 ++ .../icons/numix/standard/devices/printer.svg | 9 + .../icons/numix/standard/devices/scanner.svg | 11 ++ .../numix/standard/devices/video-display.svg | 9 + .../numix/standard/emblems/emblem-default.svg | 4 + .../standard/emblems/emblem-documents.svg | 6 + .../standard/emblems/emblem-downloads.svg | 7 + .../standard/emblems/emblem-favorite.svg | 4 + .../standard/emblems/emblem-important.svg | 4 + .../numix/standard/emblems/emblem-mail.svg | 9 + .../numix/standard/emblems/emblem-photos.svg | 4 + .../standard/emblems/emblem-readonly.svg | 6 + .../numix/standard/emblems/emblem-shared.svg | 4 + .../standard/emblems/emblem-symbolic-link.svg | 6 + .../numix/standard/emblems/emblem-system.svg | 6 + .../standard/emblems/emblem-unreadable.svg | 4 + .../numix/standard/places/folder-remote.svg | 5 + .../ui/icons/numix/standard/places/folder.svg | 10 + .../numix/standard/places/network-server.svg | 180 ++++++++++++++++++ .../standard/places/network-workgroup.svg | 5 + .../numix/standard/places/start-here.svg | 8 + .../numix/standard/places/user-bookmarks.svg | 6 + .../numix/standard/places/user-desktop.svg | 9 + .../icons/numix/standard/places/user-home.svg | 7 + .../numix/standard/places/user-trash.svg | 148 ++++++++++++++ .../standard/status/appointment-missed.svg | 9 + .../standard/status/appointment-soon.svg | 10 + .../standard/status/audio-volume-high.svg | 7 + .../standard/status/audio-volume-low.svg | 7 + .../standard/status/audio-volume-medium.svg | 7 + .../standard/status/audio-volume-muted.svg | 5 + .../numix/standard/status/battery-caution.svg | 4 + .../numix/standard/status/battery-low.svg | 4 + .../numix/standard/status/dialog-error.svg | 3 + .../standard/status/dialog-information.svg | 3 + .../numix/standard/status/dialog-password.svg | 7 + .../numix/standard/status/dialog-question.svg | 3 + .../numix/standard/status/dialog-warning.svg | 3 + .../numix/standard/status/image-missing.svg | 4 + .../numix/standard/status/network-error.svg | 4 + .../numix/standard/status/network-idle.svg | 4 + .../numix/standard/status/network-offline.svg | 4 + .../numix/standard/status/network-receive.svg | 4 + .../status/network-transmit-receive.svg | 4 + .../standard/status/network-transmit.svg | 4 + .../standard/status/printer-printing.svg | 11 ++ .../numix/standard/status/security-high.svg | 7 + .../numix/standard/status/security-low.svg | 7 + .../numix/standard/status/security-medium.svg | 5 + .../icons/numix/standard/status/task-due.svg | 7 + .../numix/standard/status/task-past-due.svg | 5 + .../numix/standard/status/user-available.svg | 4 + .../icons/numix/standard/status/user-away.svg | 4 + .../icons/numix/standard/status/user-idle.svg | 4 + .../numix/standard/status/user-offline.svg | 4 + .../custom}/cue-interrupt.svg | 0 .../papirus-symbolic/custom}/cue-pause.svg | 0 .../papirus-symbolic/custom}/cue-start.svg | 0 .../papirus-symbolic/custom}/cue-stop.svg | 0 .../custom}/fadein-generic.svg | 0 .../custom}/fadeout-generic.svg | 0 .../standard/actions/address-book-new.svg | 3 + .../standard/actions/application-exit.svg | 4 + .../standard/actions/appointment-new.svg | 4 + .../standard/actions/call-start.svg | 3 + .../standard/actions/call-stop.svg | 4 + .../standard/actions/contact-new.svg | 3 + .../standard/actions/document-new.svg | 3 + .../standard/actions/document-open-recent.svg | 4 + .../standard/actions/document-open.svg | 3 + .../standard/actions/document-page-setup.svg | 4 + .../actions/document-print-preview.svg | 7 + .../standard/actions/document-print.svg | 5 + .../standard/actions/document-properties.svg | 6 + .../standard/actions/document-revert.svg | 4 + .../standard/actions/document-save-as.svg | 4 + .../standard/actions/document-save.svg | 3 + .../standard/actions/document-send.svg | 3 + .../standard/actions/edit-clear.svg | 3 + .../standard/actions/edit-copy.svg | 3 + .../standard/actions/edit-cut.svg | 3 + .../standard/actions/edit-delete.svg | 4 + .../standard/actions/edit-find-replace.svg | 4 + .../standard/actions/edit-find.svg | 3 + .../standard/actions/edit-paste.svg | 3 + .../standard/actions/edit-redo.svg | 3 + .../standard/actions/edit-select-all.svg | 3 + .../standard/actions/edit-undo.svg | 3 + .../standard/actions/folder-new.svg | 3 + .../standard/actions/format-indent-less.svg | 4 + .../standard/actions/format-indent-more.svg | 4 + .../actions/format-justify-center.svg | 3 + .../standard/actions/format-justify-fill.svg | 3 + .../standard/actions/format-justify-left.svg | 3 + .../standard/actions/format-justify-right.svg | 3 + .../standard/actions/format-text-bold.svg | 3 + .../actions/format-text-direction-ltr.svg | 5 + .../actions/format-text-direction-rtl.svg | 5 + .../standard/actions/format-text-italic.svg | 3 + .../actions/format-text-strikethrough.svg | 4 + .../actions/format-text-underline.svg | 4 + .../standard/actions/go-bottom.svg | 4 + .../standard/actions/go-down.svg | 3 + .../standard/actions/go-first.svg | 4 + .../standard/actions/go-home.svg | 3 + .../standard/actions/go-jump.svg | 3 + .../standard/actions/go-last.svg | 4 + .../standard/actions}/go-next.svg | 0 .../standard/actions}/go-previous.svg | 0 .../standard/actions/go-top.svg | 4 + .../standard/actions/go-up.svg | 3 + .../standard/actions/help-about.svg} | 0 .../standard/actions/insert-image.svg | 4 + .../standard/actions/insert-link.svg | 3 + .../standard/actions/insert-object.svg | 4 + .../standard/actions/insert-text.svg | 5 + .../standard/actions/list-add.svg | 3 + .../standard/actions/list-remove.svg | 3 + .../standard/actions/mail-forward.svg | 3 + .../standard/actions/mail-mark-important.svg | 3 + .../standard/actions/mail-mark-junk.svg | 3 + .../standard/actions/mail-mark-notjunk.svg | 3 + .../standard/actions/mail-mark-read.svg | 3 + .../standard/actions/mail-mark-unread.svg | 3 + .../standard/actions/mail-message-new.svg | 3 + .../standard/actions/mail-reply-all.svg | 4 + .../standard/actions/mail-reply-sender.svg | 3 + .../standard/actions/mail-send-receive.svg | 6 + .../standard/actions/mail-send.svg | 3 + .../standard/actions/media-eject.svg | 3 + .../standard/actions/media-playback-pause.svg | 4 + .../standard/actions/media-playback-start.svg | 3 + .../standard/actions/media-playback-stop.svg | 3 + .../standard/actions/media-record.svg | 3 + .../standard/actions/media-seek-backward.svg | 4 + .../standard/actions/media-seek-forward.svg | 4 + .../standard/actions/media-skip-backward.svg | 3 + .../standard/actions/media-skip-forward.svg | 3 + .../actions/object-flip-horizontal.svg | 3 + .../standard/actions/object-flip-vertical.svg | 3 + .../standard/actions/object-rotate-left.svg | 3 + .../standard/actions/object-rotate-right.svg | 3 + .../standard/actions/process-stop.svg | 3 + .../standard/actions/system-lock-screen.svg | 3 + .../standard/actions/system-log-out.svg | 4 + .../standard/actions/system-run.svg | 4 + .../standard/actions/system-search.svg | 3 + .../standard/actions/system-shutdown.svg | 4 + .../standard/actions/tools-check-spelling.svg | 5 + .../standard/actions/view-fullscreen.svg | 3 + .../standard/actions/view-refresh.svg | 4 + .../standard/actions/view-restore.svg | 3 + .../standard/actions/view-sort-ascending.svg | 6 + .../standard/actions/view-sort-descending.svg | 6 + .../standard/actions/window-close.svg | 3 + .../standard/actions/zoom-fit-best.svg | 3 + .../standard/actions/zoom-in.svg | 3 + .../standard/actions/zoom-original.svg | 3 + .../standard/actions/zoom-out.svg | 3 + .../categories/applications-engineering.svg | 4 + .../categories/applications-games.svg | 3 + .../categories/applications-graphics.svg | 5 + .../categories/applications-multimedia.svg | 4 + .../categories/applications-science.svg | 5 + .../categories/applications-system.svg | 3 + .../categories/applications-utilities.svg | 5 + .../standard/categories/preferences-other.svg | 6 + .../categories/preferences-system.svg | 3 + .../standard/categories/system-help.svg | 3 + .../standard/devices/audio-card.svg | 3 + .../devices/audio-input-microphone.svg | 3 + .../standard/devices/battery.svg | 3 + .../standard/devices/camera-photo.svg | 3 + .../standard/devices/camera-video.svg | 3 + .../standard/devices/camera-web.svg | 3 + .../standard/devices/computer.svg | 3 + .../standard/devices/drive-harddisk.svg | 3 + .../standard/devices/drive-optical.svg | 3 + .../devices/drive-removable-media.svg | 3 + .../standard/devices/input-gaming.svg | 3 + .../standard/devices/input-keyboard.svg | 3 + .../standard/devices/input-mouse.svg | 3 + .../standard/devices/input-tablet.svg | 4 + .../standard/devices/media-flash.svg | 3 + .../standard/devices/media-floppy.svg | 3 + .../standard/devices/media-tape.svg | 3 + .../standard/devices/multimedia-player.svg | 3 + .../standard/devices/network-wired.svg | 3 + .../standard/devices/network-wireless.svg | 3 + .../standard/devices/phone.svg | 3 + .../standard/devices/printer.svg | 3 + .../standard/devices/scanner.svg | 3 + .../standard/devices/video-display.svg | 3 + .../standard/emblems/emblem-default.svg | 3 + .../standard/emblems/emblem-documents.svg | 4 + .../standard/emblems/emblem-favorite.svg | 3 + .../standard/emblems/emblem-important.svg | 3 + .../standard/emblems/emblem-photos.svg | 3 + .../standard/emblems/emblem-shared.svg | 3 + .../standard/emblems/emblem-system.svg | 3 + .../standard/places/folder-remote.svg | 3 + .../standard/places/folder.svg | 3 + .../standard/places/network-workgroup.svg | 3 + .../standard/places/start-here.svg | 11 ++ .../standard/places/user-bookmarks.svg | 3 + .../standard/places/user-desktop.svg | 3 + .../standard/places/user-home.svg | 3 + .../standard/places/user-trash.svg | 4 + .../standard/status/appointment-missed.svg | 5 + .../standard/status/appointment-soon.svg | 4 + .../standard/status/audio-volume-high.svg | 3 + .../standard/status/audio-volume-low.svg | 4 + .../standard/status/audio-volume-medium.svg | 4 + .../standard/status/audio-volume-muted.svg | 3 + .../standard/status/battery-caution.svg | 4 + .../standard/status/battery-low.svg | 4 + .../standard/status/dialog-error.svg | 11 ++ .../standard/status/dialog-information.svg | 11 ++ .../standard/status/dialog-password.svg | 11 ++ .../standard/status/dialog-question.svg | 8 + .../standard/status/dialog-warning.svg | 11 ++ .../standard/status/folder-drag-accept.svg | 4 + .../standard/status/folder-open.svg | 4 + .../standard/status/folder-visiting.svg | 4 + .../standard/status/image-loading.svg | 6 + .../standard/status/mail-attachment.svg | 3 + .../standard/status/mail-read.svg | 3 + .../standard/status/mail-replied.svg | 3 + .../standard/status/mail-unread.svg | 3 + .../standard/status/media-playlist-repeat.svg | 3 + .../status/media-playlist-shuffle.svg | 6 + .../standard/status/network-error.svg | 5 + .../standard/status/network-idle.svg | 4 + .../standard/status/network-offline.svg | 5 + .../standard/status/network-receive.svg | 4 + .../status/network-transmit-receive.svg | 4 + .../standard/status/network-transmit.svg | 4 + .../standard/status/printer-error.svg | 4 + .../standard/status/printer-printing.svg | 6 + .../standard/status/security-high.svg | 3 + .../standard/status/security-low.svg | 3 + .../standard/status/security-medium.svg | 3 + .../status/software-update-available.svg | 3 + .../status/software-update-urgent.svg | 3 + .../standard/status/task-due.svg | 4 + .../standard/status/task-past-due.svg | 4 + .../standard/status/user-available.svg | 3 + .../standard/status/user-away.svg | 4 + .../standard/status/user-idle.svg | 3 + .../standard/status/user-offline.svg | 3 + .../standard/status/user-trash-full.svg | 4 + lisp/ui/logging/dialog.py | 2 +- lisp/ui/logging/status.py | 10 +- lisp/ui/mainwindow.py | 3 +- lisp/ui/settings/pages/app_general.py | 7 +- lisp/ui/settings/pages/plugins_settings.py | 2 +- lisp/ui/themes/__init__.py | 31 ++- lisp/ui/themes/dark/__init__.py | 1 + lisp/ui/themes/dark/{theme.py => dark.py} | 5 +- .../themes/icons/numix/audio-volume-high.svg | 1 - .../themes/icons/numix/audio-volume-low.svg | 1 - .../icons/numix/audio-volume-medium.svg | 1 - .../themes/icons/numix/audio-volume-muted.svg | 1 - lisp/ui/themes/icons/numix/dialog-error.svg | 3 - .../themes/icons/numix/dialog-information.svg | 3 - .../ui/themes/icons/numix/dialog-question.svg | 3 - lisp/ui/themes/icons/numix/dialog-warning.svg | 1 - lisp/ui/themes/icons/numix/media-eject.svg | 1 - lisp/ui/themes/icons/numix/media-record.svg | 1 - .../icons/numix/media-seek-backward.svg | 1 - .../themes/icons/numix/media-seek-forward.svg | 1 - .../icons/numix/media-skip-backward.svg | 1 - .../themes/icons/numix/media-skip-forward.svg | 1 - .../icons/symbolic/audio-volume-high.svg | 1 - .../icons/symbolic/audio-volume-low.svg | 1 - .../icons/symbolic/audio-volume-medium.svg | 1 - .../icons/symbolic/audio-volume-muted.svg | 1 - lisp/ui/themes/icons/symbolic/auto-follow.svg | 1 - lisp/ui/themes/icons/symbolic/auto-next.svg | 1 - .../ui/themes/icons/symbolic/dialog-error.svg | 11 -- .../icons/symbolic/dialog-information.svg | 11 -- .../themes/icons/symbolic/dialog-question.svg | 8 - .../themes/icons/symbolic/dialog-warning.svg | 8 - lisp/ui/themes/icons/symbolic/media-eject.svg | 1 - .../ui/themes/icons/symbolic/media-record.svg | 1 - .../icons/symbolic/media-seek-backward.svg | 1 - .../icons/symbolic/media-seek-forward.svg | 1 - .../icons/symbolic/media-skip-backward.svg | 1 - .../icons/symbolic/media-skip-forward.svg | 1 - lisp/ui/themes/theme.py | 51 ----- lisp/ui/widgets/fades.py | 2 +- lisp/ui/widgets/qmutebutton.py | 2 +- 473 files changed, 2784 insertions(+), 302 deletions(-) delete mode 100644 .landscape.yml create mode 100644 lisp/ui/icons/__init__.py rename lisp/ui/{themes => }/icons/lisp/fadein-linear.png (100%) rename lisp/ui/{themes => }/icons/lisp/fadein-quadratic.png (100%) rename lisp/ui/{themes => }/icons/lisp/fadein-quadratic2.png (100%) rename lisp/ui/{themes => }/icons/lisp/fadeout-linear.png (100%) rename lisp/ui/{themes => }/icons/lisp/fadeout-quadratic.png (100%) rename lisp/ui/{themes => }/icons/lisp/fadeout-quadratic2.png (100%) rename lisp/ui/{themes => }/icons/lisp/led-error.svg (100%) rename lisp/ui/{themes => }/icons/lisp/led-off.svg (100%) rename lisp/ui/{themes => }/icons/lisp/led-pause.svg (100%) rename lisp/ui/{themes => }/icons/lisp/led-running.svg (100%) rename lisp/ui/{themes => }/icons/lisp/linux-show-player.png (100%) rename lisp/ui/{themes => }/icons/lisp/mixer-handle.svg (100%) rename lisp/ui/{themes/icons/numix => icons/numix/custom}/cue-interrupt.svg (100%) rename lisp/ui/{themes/icons/numix => icons/numix/custom}/cue-pause.svg (100%) rename lisp/ui/{themes/icons/numix => icons/numix/custom}/cue-start.svg (100%) rename lisp/ui/{themes/icons/numix => icons/numix/custom}/cue-stop.svg (100%) rename lisp/ui/{themes/icons/numix => icons/numix/custom}/fadein-generic.svg (100%) rename lisp/ui/{themes/icons/numix => icons/numix/custom}/fadeout-generic.svg (100%) create mode 100644 lisp/ui/icons/numix/standard/actions/address-book-new.svg create mode 100644 lisp/ui/icons/numix/standard/actions/application-exit.svg create mode 100644 lisp/ui/icons/numix/standard/actions/appointment-new.svg create mode 100644 lisp/ui/icons/numix/standard/actions/call-start.svg create mode 100644 lisp/ui/icons/numix/standard/actions/call-stop.svg create mode 100644 lisp/ui/icons/numix/standard/actions/contact-new.svg create mode 100644 lisp/ui/icons/numix/standard/actions/document-new.svg create mode 100644 lisp/ui/icons/numix/standard/actions/document-open-recent.svg create mode 100644 lisp/ui/icons/numix/standard/actions/document-open.svg create mode 100644 lisp/ui/icons/numix/standard/actions/document-page-setup.svg create mode 100644 lisp/ui/icons/numix/standard/actions/document-print-preview.svg create mode 100644 lisp/ui/icons/numix/standard/actions/document-print.svg create mode 100644 lisp/ui/icons/numix/standard/actions/document-properties.svg create mode 100644 lisp/ui/icons/numix/standard/actions/document-revert.svg create mode 100644 lisp/ui/icons/numix/standard/actions/document-save-as.svg create mode 100644 lisp/ui/icons/numix/standard/actions/document-save.svg create mode 100644 lisp/ui/icons/numix/standard/actions/document-send.svg create mode 100644 lisp/ui/icons/numix/standard/actions/edit-clear.svg create mode 100644 lisp/ui/icons/numix/standard/actions/edit-copy.svg create mode 100644 lisp/ui/icons/numix/standard/actions/edit-cut.svg create mode 100644 lisp/ui/icons/numix/standard/actions/edit-delete.svg create mode 100644 lisp/ui/icons/numix/standard/actions/edit-find-replace.svg create mode 100644 lisp/ui/icons/numix/standard/actions/edit-find.svg create mode 100644 lisp/ui/icons/numix/standard/actions/edit-paste.svg create mode 100644 lisp/ui/icons/numix/standard/actions/edit-redo.svg create mode 100644 lisp/ui/icons/numix/standard/actions/edit-select-all.svg create mode 100644 lisp/ui/icons/numix/standard/actions/edit-undo.svg create mode 100644 lisp/ui/icons/numix/standard/actions/folder-new.svg create mode 100644 lisp/ui/icons/numix/standard/actions/format-indent-less.svg create mode 100644 lisp/ui/icons/numix/standard/actions/format-indent-more.svg create mode 100644 lisp/ui/icons/numix/standard/actions/format-justify-center.svg create mode 100644 lisp/ui/icons/numix/standard/actions/format-justify-fill.svg create mode 100644 lisp/ui/icons/numix/standard/actions/format-justify-left.svg create mode 100644 lisp/ui/icons/numix/standard/actions/format-justify-right.svg create mode 100644 lisp/ui/icons/numix/standard/actions/format-text-bold.svg create mode 100644 lisp/ui/icons/numix/standard/actions/format-text-direction-ltr.svg create mode 100644 lisp/ui/icons/numix/standard/actions/format-text-direction-rtl.svg create mode 100644 lisp/ui/icons/numix/standard/actions/format-text-italic.svg create mode 100644 lisp/ui/icons/numix/standard/actions/format-text-strikethrough.svg create mode 100644 lisp/ui/icons/numix/standard/actions/format-text-underline.svg create mode 100644 lisp/ui/icons/numix/standard/actions/go-bottom.svg create mode 100644 lisp/ui/icons/numix/standard/actions/go-down.svg create mode 100644 lisp/ui/icons/numix/standard/actions/go-first.svg create mode 100644 lisp/ui/icons/numix/standard/actions/go-home.svg create mode 100644 lisp/ui/icons/numix/standard/actions/go-jump.svg create mode 100644 lisp/ui/icons/numix/standard/actions/go-last.svg rename lisp/ui/{themes/icons/numix => icons/numix/standard/actions}/go-next.svg (100%) rename lisp/ui/{themes/icons/numix => icons/numix/standard/actions}/go-previous.svg (100%) create mode 100644 lisp/ui/icons/numix/standard/actions/go-top.svg create mode 100644 lisp/ui/icons/numix/standard/actions/go-up.svg rename lisp/ui/{themes/icons/numix/help-info.svg => icons/numix/standard/actions/help-about.svg} (100%) create mode 100644 lisp/ui/icons/numix/standard/actions/help-contents.svg create mode 100644 lisp/ui/icons/numix/standard/actions/help-faq.svg create mode 100644 lisp/ui/icons/numix/standard/actions/insert-image.svg create mode 100644 lisp/ui/icons/numix/standard/actions/insert-link.svg create mode 100644 lisp/ui/icons/numix/standard/actions/insert-object.svg create mode 100644 lisp/ui/icons/numix/standard/actions/insert-text.svg create mode 100644 lisp/ui/icons/numix/standard/actions/list-add.svg create mode 100644 lisp/ui/icons/numix/standard/actions/list-remove.svg create mode 100644 lisp/ui/icons/numix/standard/actions/mail-forward.svg create mode 100644 lisp/ui/icons/numix/standard/actions/mail-mark-important.svg create mode 100644 lisp/ui/icons/numix/standard/actions/mail-mark-junk.svg create mode 100644 lisp/ui/icons/numix/standard/actions/mail-mark-notjunk.svg create mode 100644 lisp/ui/icons/numix/standard/actions/mail-mark-read.svg create mode 100644 lisp/ui/icons/numix/standard/actions/mail-mark-unread.svg create mode 100644 lisp/ui/icons/numix/standard/actions/mail-message-new.svg create mode 100644 lisp/ui/icons/numix/standard/actions/mail-reply-all.svg create mode 100644 lisp/ui/icons/numix/standard/actions/mail-reply-sender.svg create mode 100644 lisp/ui/icons/numix/standard/actions/mail-send-receive.svg create mode 100644 lisp/ui/icons/numix/standard/actions/mail-send.svg create mode 100644 lisp/ui/icons/numix/standard/actions/media-eject.svg create mode 100644 lisp/ui/icons/numix/standard/actions/media-playback-pause.svg create mode 100644 lisp/ui/icons/numix/standard/actions/media-playback-start.svg create mode 100644 lisp/ui/icons/numix/standard/actions/media-playback-stop.svg create mode 100644 lisp/ui/icons/numix/standard/actions/media-record.svg create mode 100644 lisp/ui/icons/numix/standard/actions/media-seek-backward.svg create mode 100644 lisp/ui/icons/numix/standard/actions/media-seek-forward.svg create mode 100644 lisp/ui/icons/numix/standard/actions/media-skip-backward.svg create mode 100644 lisp/ui/icons/numix/standard/actions/media-skip-forward.svg create mode 100644 lisp/ui/icons/numix/standard/actions/object-flip-horizontal.svg create mode 100644 lisp/ui/icons/numix/standard/actions/object-flip-vertical.svg create mode 100644 lisp/ui/icons/numix/standard/actions/object-rotate-left.svg create mode 100644 lisp/ui/icons/numix/standard/actions/object-rotate-right.svg create mode 100644 lisp/ui/icons/numix/standard/actions/process-stop.svg create mode 100644 lisp/ui/icons/numix/standard/actions/system-lock-screen.svg create mode 100644 lisp/ui/icons/numix/standard/actions/system-log-out.svg create mode 100644 lisp/ui/icons/numix/standard/actions/system-reboot.svg create mode 100644 lisp/ui/icons/numix/standard/actions/system-run.svg create mode 100644 lisp/ui/icons/numix/standard/actions/system-search.svg create mode 100644 lisp/ui/icons/numix/standard/actions/system-shutdown.svg create mode 100644 lisp/ui/icons/numix/standard/actions/tools-check-spelling.svg create mode 100644 lisp/ui/icons/numix/standard/actions/view-fullscreen.svg create mode 100644 lisp/ui/icons/numix/standard/actions/view-refresh.svg create mode 100644 lisp/ui/icons/numix/standard/actions/view-restore.svg create mode 100644 lisp/ui/icons/numix/standard/actions/view-sort-ascending.svg create mode 100644 lisp/ui/icons/numix/standard/actions/view-sort-descending.svg create mode 100644 lisp/ui/icons/numix/standard/actions/window-close.svg create mode 100644 lisp/ui/icons/numix/standard/actions/window-new.svg create mode 100644 lisp/ui/icons/numix/standard/actions/zoom-fit-best.svg create mode 100644 lisp/ui/icons/numix/standard/actions/zoom-in.svg create mode 100644 lisp/ui/icons/numix/standard/actions/zoom-original.svg create mode 100644 lisp/ui/icons/numix/standard/actions/zoom-out.svg create mode 100644 lisp/ui/icons/numix/standard/categories/applications-accessories.svg create mode 100644 lisp/ui/icons/numix/standard/categories/applications-development.svg create mode 100644 lisp/ui/icons/numix/standard/categories/applications-engineering.svg create mode 100644 lisp/ui/icons/numix/standard/categories/applications-games.svg create mode 100644 lisp/ui/icons/numix/standard/categories/applications-graphics.svg create mode 100644 lisp/ui/icons/numix/standard/categories/applications-internet.svg create mode 100644 lisp/ui/icons/numix/standard/categories/applications-multimedia.svg create mode 100644 lisp/ui/icons/numix/standard/categories/applications-office.svg create mode 100644 lisp/ui/icons/numix/standard/categories/applications-other.svg create mode 100644 lisp/ui/icons/numix/standard/categories/applications-science.svg create mode 100644 lisp/ui/icons/numix/standard/categories/applications-system.svg create mode 100644 lisp/ui/icons/numix/standard/categories/applications-utilities.svg create mode 100644 lisp/ui/icons/numix/standard/categories/preferences-desktop-peripherals.svg create mode 100644 lisp/ui/icons/numix/standard/categories/preferences-desktop-personal.svg create mode 100644 lisp/ui/icons/numix/standard/categories/preferences-desktop.svg create mode 100644 lisp/ui/icons/numix/standard/categories/preferences-other.svg create mode 100644 lisp/ui/icons/numix/standard/categories/preferences-system-network.svg create mode 100644 lisp/ui/icons/numix/standard/categories/preferences-system.svg create mode 100644 lisp/ui/icons/numix/standard/categories/system-help.svg create mode 100644 lisp/ui/icons/numix/standard/devices/audio-card.svg create mode 100644 lisp/ui/icons/numix/standard/devices/audio-input-microphone.svg create mode 100644 lisp/ui/icons/numix/standard/devices/battery.svg create mode 100644 lisp/ui/icons/numix/standard/devices/camera-photo.svg create mode 100644 lisp/ui/icons/numix/standard/devices/camera-web.svg create mode 100644 lisp/ui/icons/numix/standard/devices/computer.svg create mode 100644 lisp/ui/icons/numix/standard/devices/drive-harddisk.svg create mode 100644 lisp/ui/icons/numix/standard/devices/drive-optical.svg create mode 100644 lisp/ui/icons/numix/standard/devices/drive-removable-media.svg create mode 100644 lisp/ui/icons/numix/standard/devices/input-gaming.svg create mode 100644 lisp/ui/icons/numix/standard/devices/input-keyboard.svg create mode 100644 lisp/ui/icons/numix/standard/devices/input-mouse.svg create mode 100644 lisp/ui/icons/numix/standard/devices/input-tablet.svg create mode 100644 lisp/ui/icons/numix/standard/devices/media-flash.svg create mode 100644 lisp/ui/icons/numix/standard/devices/media-floppy.svg create mode 100644 lisp/ui/icons/numix/standard/devices/media-optical.svg create mode 100644 lisp/ui/icons/numix/standard/devices/media-tape.svg create mode 100644 lisp/ui/icons/numix/standard/devices/multimedia-player.svg create mode 100644 lisp/ui/icons/numix/standard/devices/network-wired.svg create mode 100644 lisp/ui/icons/numix/standard/devices/network-wireless.svg create mode 100644 lisp/ui/icons/numix/standard/devices/pda.svg create mode 100644 lisp/ui/icons/numix/standard/devices/phone.svg create mode 100644 lisp/ui/icons/numix/standard/devices/printer.svg create mode 100644 lisp/ui/icons/numix/standard/devices/scanner.svg create mode 100644 lisp/ui/icons/numix/standard/devices/video-display.svg create mode 100644 lisp/ui/icons/numix/standard/emblems/emblem-default.svg create mode 100644 lisp/ui/icons/numix/standard/emblems/emblem-documents.svg create mode 100644 lisp/ui/icons/numix/standard/emblems/emblem-downloads.svg create mode 100644 lisp/ui/icons/numix/standard/emblems/emblem-favorite.svg create mode 100644 lisp/ui/icons/numix/standard/emblems/emblem-important.svg create mode 100644 lisp/ui/icons/numix/standard/emblems/emblem-mail.svg create mode 100644 lisp/ui/icons/numix/standard/emblems/emblem-photos.svg create mode 100644 lisp/ui/icons/numix/standard/emblems/emblem-readonly.svg create mode 100644 lisp/ui/icons/numix/standard/emblems/emblem-shared.svg create mode 100644 lisp/ui/icons/numix/standard/emblems/emblem-symbolic-link.svg create mode 100644 lisp/ui/icons/numix/standard/emblems/emblem-system.svg create mode 100644 lisp/ui/icons/numix/standard/emblems/emblem-unreadable.svg create mode 100644 lisp/ui/icons/numix/standard/places/folder-remote.svg create mode 100644 lisp/ui/icons/numix/standard/places/folder.svg create mode 100644 lisp/ui/icons/numix/standard/places/network-server.svg create mode 100644 lisp/ui/icons/numix/standard/places/network-workgroup.svg create mode 100644 lisp/ui/icons/numix/standard/places/start-here.svg create mode 100644 lisp/ui/icons/numix/standard/places/user-bookmarks.svg create mode 100644 lisp/ui/icons/numix/standard/places/user-desktop.svg create mode 100644 lisp/ui/icons/numix/standard/places/user-home.svg create mode 100644 lisp/ui/icons/numix/standard/places/user-trash.svg create mode 100644 lisp/ui/icons/numix/standard/status/appointment-missed.svg create mode 100644 lisp/ui/icons/numix/standard/status/appointment-soon.svg create mode 100644 lisp/ui/icons/numix/standard/status/audio-volume-high.svg create mode 100644 lisp/ui/icons/numix/standard/status/audio-volume-low.svg create mode 100644 lisp/ui/icons/numix/standard/status/audio-volume-medium.svg create mode 100644 lisp/ui/icons/numix/standard/status/audio-volume-muted.svg create mode 100644 lisp/ui/icons/numix/standard/status/battery-caution.svg create mode 100644 lisp/ui/icons/numix/standard/status/battery-low.svg create mode 100644 lisp/ui/icons/numix/standard/status/dialog-error.svg create mode 100644 lisp/ui/icons/numix/standard/status/dialog-information.svg create mode 100644 lisp/ui/icons/numix/standard/status/dialog-password.svg create mode 100644 lisp/ui/icons/numix/standard/status/dialog-question.svg create mode 100644 lisp/ui/icons/numix/standard/status/dialog-warning.svg create mode 100644 lisp/ui/icons/numix/standard/status/image-missing.svg create mode 100644 lisp/ui/icons/numix/standard/status/network-error.svg create mode 100644 lisp/ui/icons/numix/standard/status/network-idle.svg create mode 100644 lisp/ui/icons/numix/standard/status/network-offline.svg create mode 100644 lisp/ui/icons/numix/standard/status/network-receive.svg create mode 100644 lisp/ui/icons/numix/standard/status/network-transmit-receive.svg create mode 100644 lisp/ui/icons/numix/standard/status/network-transmit.svg create mode 100644 lisp/ui/icons/numix/standard/status/printer-printing.svg create mode 100644 lisp/ui/icons/numix/standard/status/security-high.svg create mode 100644 lisp/ui/icons/numix/standard/status/security-low.svg create mode 100644 lisp/ui/icons/numix/standard/status/security-medium.svg create mode 100644 lisp/ui/icons/numix/standard/status/task-due.svg create mode 100644 lisp/ui/icons/numix/standard/status/task-past-due.svg create mode 100644 lisp/ui/icons/numix/standard/status/user-available.svg create mode 100644 lisp/ui/icons/numix/standard/status/user-away.svg create mode 100644 lisp/ui/icons/numix/standard/status/user-idle.svg create mode 100644 lisp/ui/icons/numix/standard/status/user-offline.svg rename lisp/ui/{themes/icons/symbolic => icons/papirus-symbolic/custom}/cue-interrupt.svg (100%) rename lisp/ui/{themes/icons/symbolic => icons/papirus-symbolic/custom}/cue-pause.svg (100%) rename lisp/ui/{themes/icons/symbolic => icons/papirus-symbolic/custom}/cue-start.svg (100%) rename lisp/ui/{themes/icons/symbolic => icons/papirus-symbolic/custom}/cue-stop.svg (100%) rename lisp/ui/{themes/icons/symbolic => icons/papirus-symbolic/custom}/fadein-generic.svg (100%) rename lisp/ui/{themes/icons/symbolic => icons/papirus-symbolic/custom}/fadeout-generic.svg (100%) create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/address-book-new.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/application-exit.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/appointment-new.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/call-start.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/call-stop.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/contact-new.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/document-new.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/document-open-recent.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/document-open.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/document-page-setup.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/document-print-preview.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/document-print.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/document-properties.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/document-revert.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/document-save-as.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/document-save.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/document-send.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/edit-clear.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/edit-copy.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/edit-cut.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/edit-delete.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/edit-find-replace.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/edit-find.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/edit-paste.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/edit-redo.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/edit-select-all.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/edit-undo.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/folder-new.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-less.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-more.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-center.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-fill.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-left.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-right.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/format-text-bold.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-ltr.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-rtl.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/format-text-italic.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/format-text-strikethrough.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/format-text-underline.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/go-bottom.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/go-down.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/go-first.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/go-home.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/go-jump.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/go-last.svg rename lisp/ui/{themes/icons/symbolic => icons/papirus-symbolic/standard/actions}/go-next.svg (100%) rename lisp/ui/{themes/icons/symbolic => icons/papirus-symbolic/standard/actions}/go-previous.svg (100%) create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/go-top.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/go-up.svg rename lisp/ui/{themes/icons/symbolic/help-info.svg => icons/papirus-symbolic/standard/actions/help-about.svg} (100%) create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/insert-image.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/insert-link.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/insert-object.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/insert-text.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/list-add.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/list-remove.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/mail-forward.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-important.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-junk.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-notjunk.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-read.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-unread.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/mail-message-new.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-all.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-sender.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/mail-send-receive.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/mail-send.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/media-eject.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-pause.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-start.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-stop.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/media-record.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-backward.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-forward.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-backward.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-forward.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-horizontal.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-vertical.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-left.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-right.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/process-stop.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/system-lock-screen.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/system-log-out.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/system-run.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/system-search.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/system-shutdown.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/tools-check-spelling.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/view-fullscreen.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/view-refresh.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/view-restore.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-ascending.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-descending.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/window-close.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/zoom-fit-best.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/zoom-in.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/zoom-original.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/actions/zoom-out.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/categories/applications-engineering.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/categories/applications-games.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/categories/applications-graphics.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/categories/applications-multimedia.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/categories/applications-science.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/categories/applications-system.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/categories/applications-utilities.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/categories/preferences-other.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/categories/preferences-system.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/categories/system-help.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/audio-card.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/audio-input-microphone.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/battery.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/camera-photo.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/camera-video.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/camera-web.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/computer.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/drive-harddisk.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/drive-optical.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/drive-removable-media.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/input-gaming.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/input-keyboard.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/input-mouse.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/input-tablet.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/media-flash.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/media-floppy.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/media-tape.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/multimedia-player.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/network-wired.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/network-wireless.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/phone.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/printer.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/scanner.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/devices/video-display.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-default.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-documents.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-favorite.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-important.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-photos.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-shared.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-system.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/places/folder-remote.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/places/folder.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/places/network-workgroup.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/places/start-here.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/places/user-bookmarks.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/places/user-desktop.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/places/user-home.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/places/user-trash.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/appointment-missed.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/appointment-soon.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-high.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-low.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-medium.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-muted.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/battery-caution.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/battery-low.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/dialog-error.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/dialog-information.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/dialog-password.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/dialog-question.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/dialog-warning.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/folder-drag-accept.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/folder-open.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/folder-visiting.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/image-loading.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/mail-attachment.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/mail-read.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/mail-replied.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/mail-unread.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-repeat.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-shuffle.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/network-error.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/network-idle.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/network-offline.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/network-receive.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/network-transmit-receive.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/network-transmit.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/printer-error.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/printer-printing.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/security-high.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/security-low.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/security-medium.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/software-update-available.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/software-update-urgent.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/task-due.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/task-past-due.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/user-available.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/user-away.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/user-idle.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/user-offline.svg create mode 100644 lisp/ui/icons/papirus-symbolic/standard/status/user-trash-full.svg rename lisp/ui/themes/dark/{theme.py => dark.py} (92%) delete mode 100644 lisp/ui/themes/icons/numix/audio-volume-high.svg delete mode 100644 lisp/ui/themes/icons/numix/audio-volume-low.svg delete mode 100644 lisp/ui/themes/icons/numix/audio-volume-medium.svg delete mode 100644 lisp/ui/themes/icons/numix/audio-volume-muted.svg delete mode 100644 lisp/ui/themes/icons/numix/dialog-error.svg delete mode 100644 lisp/ui/themes/icons/numix/dialog-information.svg delete mode 100644 lisp/ui/themes/icons/numix/dialog-question.svg delete mode 100644 lisp/ui/themes/icons/numix/dialog-warning.svg delete mode 100644 lisp/ui/themes/icons/numix/media-eject.svg delete mode 100644 lisp/ui/themes/icons/numix/media-record.svg delete mode 100644 lisp/ui/themes/icons/numix/media-seek-backward.svg delete mode 100644 lisp/ui/themes/icons/numix/media-seek-forward.svg delete mode 100644 lisp/ui/themes/icons/numix/media-skip-backward.svg delete mode 100644 lisp/ui/themes/icons/numix/media-skip-forward.svg delete mode 100644 lisp/ui/themes/icons/symbolic/audio-volume-high.svg delete mode 100644 lisp/ui/themes/icons/symbolic/audio-volume-low.svg delete mode 100644 lisp/ui/themes/icons/symbolic/audio-volume-medium.svg delete mode 100644 lisp/ui/themes/icons/symbolic/audio-volume-muted.svg delete mode 100644 lisp/ui/themes/icons/symbolic/auto-follow.svg delete mode 100644 lisp/ui/themes/icons/symbolic/auto-next.svg delete mode 100644 lisp/ui/themes/icons/symbolic/dialog-error.svg delete mode 100644 lisp/ui/themes/icons/symbolic/dialog-information.svg delete mode 100644 lisp/ui/themes/icons/symbolic/dialog-question.svg delete mode 100644 lisp/ui/themes/icons/symbolic/dialog-warning.svg delete mode 100644 lisp/ui/themes/icons/symbolic/media-eject.svg delete mode 100644 lisp/ui/themes/icons/symbolic/media-record.svg delete mode 100644 lisp/ui/themes/icons/symbolic/media-seek-backward.svg delete mode 100644 lisp/ui/themes/icons/symbolic/media-seek-forward.svg delete mode 100644 lisp/ui/themes/icons/symbolic/media-skip-backward.svg delete mode 100644 lisp/ui/themes/icons/symbolic/media-skip-forward.svg diff --git a/.landscape.yml b/.landscape.yml deleted file mode 100644 index e906e20f0..000000000 --- a/.landscape.yml +++ /dev/null @@ -1,2 +0,0 @@ -python-targets: - - 3 diff --git a/Pipfile.lock b/Pipfile.lock index 26eb3b57f..9677d8946 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -3,19 +3,6 @@ "hash": { "sha256": "7758b232e7b383ea49c6f98a167e2d857ab7d1fa6c95efaed803bc014482cc7f" }, - "host-environment-markers": { - "implementation_name": "cpython", - "implementation_version": "3.6.4", - "os_name": "posix", - "platform_machine": "x86_64", - "platform_python_implementation": "CPython", - "platform_release": "4.15.5-1-ARCH", - "platform_system": "Linux", - "platform_version": "#1 SMP PREEMPT Thu Feb 22 22:15:20 UTC 2018", - "python_full_version": "3.6.4", - "python_version": "3.6", - "sys_platform": "linux" - }, "pipfile-spec": 6, "requires": {}, "sources": [ @@ -29,33 +16,33 @@ "default": { "cffi": { "hashes": [ - "sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50", - "sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596", - "sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef", "sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743", - "sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f", - "sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31", - "sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04", - "sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6", + "sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef", + "sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50", + "sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f", + "sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93", + "sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257", "sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3", + "sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc", + "sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04", "sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6", + "sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359", + "sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596", "sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b", - "sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca", - "sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e", - "sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb", "sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd", - "sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1", - "sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917", - "sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359", - "sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f", "sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95", + "sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e", + "sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6", + "sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca", + "sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31", + "sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1", + "sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085", "sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801", - "sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257", + "sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4", "sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184", - "sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc", - "sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085", - "sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93", - "sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4" + "sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917", + "sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f", + "sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb" ], "version": "==1.11.5" }, @@ -68,8 +55,8 @@ }, "mido": { "hashes": [ - "sha256:64b9d1595da8f319bff2eb866f9181257d3670a7803f7e38415f22c03a577560", - "sha256:35142874d4521dc5fcebcdc3a645df87cb0ecad129dd031cbca391e2d052313f" + "sha256:35142874d4521dc5fcebcdc3a645df87cb0ecad129dd031cbca391e2d052313f", + "sha256:64b9d1595da8f319bff2eb866f9181257d3670a7803f7e38415f22c03a577560" ], "version": "==1.2.8" }, @@ -87,9 +74,9 @@ }, "pygobject": { "hashes": [ - "sha256:d5ff822fc06d5a6cba81b7f1bb1192c2f64715e847da26e907040b0eb43ee068" + "sha256:cb1db68b843df9c1c84a76c6b0c5f3566e6baf805bd218072a759269f8a9ae6b" ], - "version": "==3.27.4" + "version": "==3.27.5" }, "pyliblo": { "hashes": [ @@ -99,50 +86,50 @@ }, "pyqt5": { "hashes": [ - "sha256:9c17ab3974c1fc7bbb04cc1c9dae780522c0ebc158613f3025fccae82227b5f7", "sha256:1e652910bd1ffd23a3a48c510ecad23a57a853ed26b782cd54b16658e6f271ac", - "sha256:f6035baa009acf45e5f460cf88f73580ad5dc0e72330029acd99e477f20a5d61", - "sha256:4db7113f464c733a99fcb66c4c093a47cf7204ad3f8b3bda502efcc0839ac14b" + "sha256:4db7113f464c733a99fcb66c4c093a47cf7204ad3f8b3bda502efcc0839ac14b", + "sha256:9c17ab3974c1fc7bbb04cc1c9dae780522c0ebc158613f3025fccae82227b5f7", + "sha256:f6035baa009acf45e5f460cf88f73580ad5dc0e72330029acd99e477f20a5d61" ], "version": "==5.10.1" }, "python-rtmidi": { "hashes": [ - "sha256:887fd11551f77b27d566235916723b065c30d1e3959c9d665131a7919c012df2", - "sha256:dfba865b1e7c7793c7d222b1922ac5f901f5545af71bf70e06d88a77482aa20c", - "sha256:9d0fddaf38c4408e6f8466d6579ce3cf7b1da9a878f26454bc62fdf66228ea65", + "sha256:1187b2a3703d4ede2b02c0fccd395648a3a468d3cd57d7932f445ef1bfa9fc5d", "sha256:5e1bb542ceebfd44290ecafa6367ade1a712cc9a73b61e843b2fa802105a2759", "sha256:659fb40e69cbc57cc7d970cd7bcd85e97ee2eb18992e147ed7bb690a83a7bd3f", - "sha256:c0f54d1e6dd1c04958130611d0fe6a821194175847d9f696824d011dffce2092", "sha256:7c82bed1b0cb5e11495bc3a779500c3d5cde6d84f68416f276622b0952174d25", - "sha256:1187b2a3703d4ede2b02c0fccd395648a3a468d3cd57d7932f445ef1bfa9fc5d", "sha256:80c6d31508ec8435b4dc1d4a979aa6046edb83413a837b790f33f65f176ad018", "sha256:8685ffc012e007f10ddb9ed04a3cf1648d26a8ef95bd261bfeca0b90d11c971f", - "sha256:d82eb82e0b270f75375e3d5f9f45cb75950485700e6a3862192d0c121c802b0e" + "sha256:887fd11551f77b27d566235916723b065c30d1e3959c9d665131a7919c012df2", + "sha256:9d0fddaf38c4408e6f8466d6579ce3cf7b1da9a878f26454bc62fdf66228ea65", + "sha256:c0f54d1e6dd1c04958130611d0fe6a821194175847d9f696824d011dffce2092", + "sha256:d82eb82e0b270f75375e3d5f9f45cb75950485700e6a3862192d0c121c802b0e", + "sha256:dfba865b1e7c7793c7d222b1922ac5f901f5545af71bf70e06d88a77482aa20c" ], "version": "==1.1.0" }, "sip": { "hashes": [ - "sha256:d9023422127b94d11c1a84bfa94933e959c484f2c79553c1ef23c69fe00d25f8", - "sha256:e72955e12f4fccf27aa421be383453d697b8a44bde2cc26b08d876fd492d0174", + "sha256:09f9a4e6c28afd0bafedb26ffba43375b97fe7207bd1a0d3513f79b7d168b331", + "sha256:105edaaa1c8aa486662226360bd3999b4b89dd56de3e314d82b83ed0587d8783", + "sha256:1bb10aac55bd5ab0e2ee74b3047aa2016cfa7932077c73f602a6f6541af8cd51", + "sha256:265ddf69235dd70571b7d4da20849303b436192e875ce7226be7144ca702a45c", "sha256:52074f7cb5488e8b75b52f34ec2230bc75d22986c7fe5cd3f2d266c23f3349a7", "sha256:5ff887a33839de8fc77d7f69aed0259b67a384dc91a1dc7588e328b0b980bde2", - "sha256:09f9a4e6c28afd0bafedb26ffba43375b97fe7207bd1a0d3513f79b7d168b331", - "sha256:cf98150a99e43fda7ae22abe655b6f202e491d6291486548daa56cb15a2fcf85", "sha256:74da4ddd20c5b35c19cda753ce1e8e1f71616931391caeac2de7a1715945c679", - "sha256:265ddf69235dd70571b7d4da20849303b436192e875ce7226be7144ca702a45c", - "sha256:97bb93ee0ef01ba90f57be2b606e08002660affd5bc380776dd8b0fcaa9e093a", "sha256:7d69e9cf4f8253a3c0dfc5ba6bb9ac8087b8239851f22998e98cb35cfe497b68", - "sha256:1bb10aac55bd5ab0e2ee74b3047aa2016cfa7932077c73f602a6f6541af8cd51", - "sha256:105edaaa1c8aa486662226360bd3999b4b89dd56de3e314d82b83ed0587d8783" + "sha256:97bb93ee0ef01ba90f57be2b606e08002660affd5bc380776dd8b0fcaa9e093a", + "sha256:cf98150a99e43fda7ae22abe655b6f202e491d6291486548daa56cb15a2fcf85", + "sha256:d9023422127b94d11c1a84bfa94933e959c484f2c79553c1ef23c69fe00d25f8", + "sha256:e72955e12f4fccf27aa421be383453d697b8a44bde2cc26b08d876fd492d0174" ], "version": "==4.19.8" }, "sortedcontainers": { "hashes": [ - "sha256:fb9e22cd6ee4b459f0d7b9b4189b19631031c72ac05715563139162014c13672", - "sha256:844daced0f20d75c02ce53f373d048ea2e401ad8a7b3a4c43b2aa544b569efb3" + "sha256:844daced0f20d75c02ce53f373d048ea2e401ad8a7b3a4c43b2aa544b569efb3", + "sha256:fb9e22cd6ee4b459f0d7b9b4189b19631031c72ac05715563139162014c13672" ], "version": "==1.5.9" } @@ -150,36 +137,36 @@ "develop": { "cython": { "hashes": [ - "sha256:aba08b4db9163c52a1106efa8b19c5a4e519e5f45644551bb1358ec189a14505", - "sha256:d17a6a689bb3e6c0f280620dfddb1b91ffb8fcdae3b8707bdf6285df4100d011", - "sha256:82bbbeadbc579b6f2a7365db2d3ea21043699e424cfcca434b3baa11309260ae", - "sha256:cefa9356770d71176c87ab60f6fdb186ec9cbc6399a6c2de1017852505d44963", - "sha256:83237d27b6dec0f6b76406de5be1cf249cffb4377079acc3f3dfa72de1e307cf", - "sha256:7fbd49b1bd5240feb69ec4c67f4c76fc9dfdfc472d642833668ecaad0e14673d", - "sha256:abfb2426266d806a7d794a8ecbd0fbb385b48bddf2db6ac9a2e95d48e2c603bd", - "sha256:d8e40814fefc300bc64251f5d6bd8988e49442d7ded0e5702b5366323a035afc", - "sha256:8a4733edf3ca1c7dd9c7e1289dd5974657f1147642ef8ded1e57a1266922e32a", - "sha256:fb5b8978b937677a32b5aecc72231560d29243622a298a5c31183b156f9f42e3", - "sha256:e30fd7a32cbe7fe6c186deefef03670bf75d76ead23418f8c91519899be5518e", - "sha256:b9bc1d7e681c5a7560280a5aa8000f736eb0e47efacb7b6206275ed004595a92", "sha256:251bb50e36044ad26ff45960a60cb75868757fd46900eaf00129a4af3a0d5fcc", - "sha256:e86984b90a0176bafb5890f1f14a2962317eee9e95e0a917e6b06a5b0c764e25", - "sha256:4c958bd9e50fcd316830561f0725b3ccf893de328566c960b7bb38424d3b983d", - "sha256:a51c3a1c948664874f91636ba6169bca4dc68f5fac7b980fcb9c769a8d1f9ebc", + "sha256:264a68823751fe2a18d0b14dd44b614aecd508bd01453902921c23fd5be13d4c", + "sha256:26e24164d59e92280fa759a8a361c68490763ec230307e21990020bdeb87de50", "sha256:28c938bc970d2b5fe296460cf6ed19d0328776a6295f68a79057060e60bfc82f", "sha256:38a9e5afc5e096f26bfe5c4b458541e455032028cb5356a734ba235868b19823", "sha256:3914ae1268334208e33b952e9f447e0d9f43decd562f939d7d07e8589d0473ba", - "sha256:f441347d0d07b4cb9ca36bda70c3aa98a7484a262b8f44d0f82b70dbb5e11b47", - "sha256:f01e79f4123205d0f99b97199b53345cb6378974e738792c620957c6223fded9", + "sha256:4c958bd9e50fcd316830561f0725b3ccf893de328566c960b7bb38424d3b983d", "sha256:56a9f99b42ad6ad1fdb7705c16aa69b2e1a4ac34f760286a6de64c5719499da7", - "sha256:9f8bbd876c4d68cb12b9de9dc6734c17ca295e4759e684417fd7f3dd96d3ed2c", + "sha256:5e0006c3a2849b7a36d2cb23bcf14753b6ffc3eafac48fa29fafad942cfa4627", + "sha256:6a00512de1f2e3ce66ba35c5420babaef1fe2d9c43a8faab4080b0dbcc26bc64", "sha256:7bc07c32703e4e63f2f60c729b0ae99acbccbfd3ae4c64a322a75482d915d4af", + "sha256:7fbd49b1bd5240feb69ec4c67f4c76fc9dfdfc472d642833668ecaad0e14673d", + "sha256:82bbbeadbc579b6f2a7365db2d3ea21043699e424cfcca434b3baa11309260ae", + "sha256:83237d27b6dec0f6b76406de5be1cf249cffb4377079acc3f3dfa72de1e307cf", + "sha256:8a4733edf3ca1c7dd9c7e1289dd5974657f1147642ef8ded1e57a1266922e32a", "sha256:8afdc3751c6269ef46879ca6ff32a1362f298c77f2cf0723c66ae65c35be5db0", - "sha256:264a68823751fe2a18d0b14dd44b614aecd508bd01453902921c23fd5be13d4c", + "sha256:9f8bbd876c4d68cb12b9de9dc6734c17ca295e4759e684417fd7f3dd96d3ed2c", + "sha256:a51c3a1c948664874f91636ba6169bca4dc68f5fac7b980fcb9c769a8d1f9ebc", + "sha256:aba08b4db9163c52a1106efa8b19c5a4e519e5f45644551bb1358ec189a14505", + "sha256:abfb2426266d806a7d794a8ecbd0fbb385b48bddf2db6ac9a2e95d48e2c603bd", + "sha256:b9bc1d7e681c5a7560280a5aa8000f736eb0e47efacb7b6206275ed004595a92", "sha256:ca81d4b6f511f1e0d2a1c76ad5b3a4a3a0edf683f27600d4a86da55cb134372a", - "sha256:26e24164d59e92280fa759a8a361c68490763ec230307e21990020bdeb87de50", - "sha256:5e0006c3a2849b7a36d2cb23bcf14753b6ffc3eafac48fa29fafad942cfa4627", - "sha256:6a00512de1f2e3ce66ba35c5420babaef1fe2d9c43a8faab4080b0dbcc26bc64" + "sha256:cefa9356770d71176c87ab60f6fdb186ec9cbc6399a6c2de1017852505d44963", + "sha256:d17a6a689bb3e6c0f280620dfddb1b91ffb8fcdae3b8707bdf6285df4100d011", + "sha256:d8e40814fefc300bc64251f5d6bd8988e49442d7ded0e5702b5366323a035afc", + "sha256:e30fd7a32cbe7fe6c186deefef03670bf75d76ead23418f8c91519899be5518e", + "sha256:e86984b90a0176bafb5890f1f14a2962317eee9e95e0a917e6b06a5b0c764e25", + "sha256:f01e79f4123205d0f99b97199b53345cb6378974e738792c620957c6223fded9", + "sha256:f441347d0d07b4cb9ca36bda70c3aa98a7484a262b8f44d0f82b70dbb5e11b47", + "sha256:fb5b8978b937677a32b5aecc72231560d29243622a298a5c31183b156f9f42e3" ], "version": "==0.27.3" } diff --git a/lisp/__init__.py b/lisp/__init__.py index f7020ba71..f69dcbdeb 100644 --- a/lisp/__init__.py +++ b/lisp/__init__.py @@ -30,9 +30,12 @@ USER_DIR = path.join(path.expanduser("~"), '.linux_show_player') +LOGS_DIR = path.join(USER_DIR, 'logs') + DEFAULT_APP_CONFIG = path.join(APP_DIR, 'default.json') USER_APP_CONFIG = path.join(USER_DIR, 'lisp.json') I18N_PATH = path.join(APP_DIR, 'i18n') -ICON_THEMES_DIR = path.join(APP_DIR, 'ui', 'themes', 'icons') +ICON_THEMES_DIR = path.join(APP_DIR, 'ui', 'icons') +ICON_THEME_COMMON = 'lisp' diff --git a/lisp/backend/media.py b/lisp/backend/media.py index 0644da38b..6e1b73aa4 100644 --- a/lisp/backend/media.py +++ b/lisp/backend/media.py @@ -74,7 +74,7 @@ def __init__(self): self.sought = Signal() # Emitted after a seek (self, position) self.error = Signal() - # Emitted when an error occurs (self, error, details) + # Emitted when an error occurs (self) self.elements_changed = Signal() # Emitted when one or more elements are added/removed (self) diff --git a/lisp/core/signal.py b/lisp/core/signal.py index 605783f19..57ba102bb 100644 --- a/lisp/core/signal.py +++ b/lisp/core/signal.py @@ -74,7 +74,7 @@ def call(self, *args, **kwargs): else: self._reference()(*args, **kwargs) except Exception: - logger.warning('An error occurred.', exc_info=True) + logger.warning('Error in callback method.', exc_info=True) def is_alive(self): return self._reference() is not None diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 8ff55e0de..34568f5fc 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -161,7 +161,7 @@ def __init__(self, id=None): self.started = Signal() # self self.stopped = Signal() # self self.paused = Signal() # self - self.error = Signal() # self, error, details + self.error = Signal() # self self.next = Signal() # self self.end = Signal() # self @@ -489,7 +489,7 @@ def _ended(self): if locked: self._st_lock.release() - def _error(self, message, details): + def _error(self): """Remove Running/Pause/Stop state and add Error state.""" locked = self._st_lock.acquire(blocking=False) @@ -499,7 +499,7 @@ def _error(self, message, details): (self._state ^ CueState.Stop) ) | CueState.Error - self.error.emit(self, message, details) + self.error.emit(self) if locked: self._st_lock.release() diff --git a/lisp/cues/media_cue.py b/lisp/cues/media_cue.py index 8403e0bed..5ba0aceb9 100644 --- a/lisp/cues/media_cue.py +++ b/lisp/cues/media_cue.py @@ -185,15 +185,15 @@ def current_time(self): def _duration_change(self, value): self.duration = value - def _on_eos(self, *args): + def _on_eos(self): with self._st_lock: self.__fader.stop() self._ended() - def _on_error(self, media, message, details): + def _on_error(self): with self._st_lock: self.__fader.stop() - self._error(message, details) + self._error() def _can_fade(self, duration): return self.__volume is not None and duration > 0 diff --git a/lisp/default.json b/lisp/default.json index f5265c64e..3897734b4 100644 --- a/lisp/default.json +++ b/lisp/default.json @@ -1,5 +1,5 @@ { - "_version_": "0.6dev.8", + "_version_": "0.6dev.9", "cue": { "fadeActionDuration": 3, "fadeActionType": "Linear", @@ -8,7 +8,7 @@ }, "theme": { "theme": "Dark", - "icons": "Numix" + "icons": "numix" }, "logging": { "limit": 10000, diff --git a/lisp/layouts/cart_layout/cue_widget.py b/lisp/layouts/cart_layout/cue_widget.py index 935def4c8..0498407dd 100644 --- a/lisp/layouts/cart_layout/cue_widget.py +++ b/lisp/layouts/cart_layout/cue_widget.py @@ -29,7 +29,7 @@ from lisp.cues.cue_time import CueTime from lisp.cues.media_cue import MediaCue from lisp.layouts.cart_layout.page_widget import PageWidget -from lisp.ui.themes.theme import IconTheme +from lisp.ui.icons import IconTheme from lisp.ui.widgets import QClickLabel, QClickSlider, QDbMeter,\ QDetailedMessageBox @@ -323,14 +323,12 @@ def _status_paused(self): IconTheme.get('led-pause').pixmap(CueWidget.ICON_SIZE)) self.volumeSlider.setEnabled(False) - def _status_error(self, cue, error, details): + def _status_error(self): self.statusIcon.setPixmap( - IconTheme.get('led-off').pixmap(CueWidget.ICON_SIZE)) + IconTheme.get('led-error').pixmap(CueWidget.ICON_SIZE)) self.volumeSlider.setEnabled(False) self.reset_volume() - QDetailedMessageBox.dcritical(self.cue.name, error, details) - def _update_duration(self, duration): # Update the maximum values of seek-slider and time progress-bar if duration > 0: diff --git a/lisp/layouts/list_layout/control_buttons.py b/lisp/layouts/list_layout/control_buttons.py index f8f2c1957..84ad2190b 100644 --- a/lisp/layouts/list_layout/control_buttons.py +++ b/lisp/layouts/list_layout/control_buttons.py @@ -20,7 +20,7 @@ from PyQt5.QtCore import Qt, QSize from PyQt5.QtWidgets import QWidget, QGridLayout, QSizePolicy -from lisp.ui.themes.theme import IconTheme +from lisp.ui.icons import IconTheme from lisp.ui.ui_utils import translate from lisp.ui.widgets.qiconpushbutton import QIconPushButton diff --git a/lisp/layouts/list_layout/cue_list_item.py b/lisp/layouts/list_layout/cue_list_item.py index 7900e6dd7..54685a0fb 100644 --- a/lisp/layouts/list_layout/cue_list_item.py +++ b/lisp/layouts/list_layout/cue_list_item.py @@ -21,7 +21,7 @@ from PyQt5.QtWidgets import QTreeWidgetItem from lisp.core.signal import Connection -from lisp.ui.themes.theme import IconTheme +from lisp.ui.icons import IconTheme class CueListItem(QTreeWidgetItem): diff --git a/lisp/layouts/list_layout/cue_list_model.py b/lisp/layouts/list_layout/cue_list_model.py index 4e516bfe7..52dad4197 100644 --- a/lisp/layouts/list_layout/cue_list_model.py +++ b/lisp/layouts/list_layout/cue_list_model.py @@ -121,7 +121,7 @@ def _add(self, cue): self.__playing.append(cue) self.item_added.emit(cue) - def _remove(self, cue, *args): + def _remove(self, cue): try: self.__playing.remove(cue) self.item_removed.emit(cue) diff --git a/lisp/layouts/list_layout/listwidgets.py b/lisp/layouts/list_layout/listwidgets.py index 5d5dc2ffb..39cedfcd1 100644 --- a/lisp/layouts/list_layout/listwidgets.py +++ b/lisp/layouts/list_layout/listwidgets.py @@ -25,7 +25,7 @@ from lisp.core.util import strtime from lisp.cues.cue import CueNextAction, CueState from lisp.cues.cue_time import CueTime, CueWaitTime -from lisp.ui.themes.theme import IconTheme +from lisp.ui.icons import IconTheme class CueStatusIcon(QLabel): @@ -50,7 +50,7 @@ def _start(self): def _pause(self): self.setPixmap(IconTheme.get('led-pause',).pixmap(self.SIZE)) - def _error(self, *args): + def _error(self): self.setPixmap(IconTheme.get('led-error').pixmap(self.SIZE)) def _stop(self): diff --git a/lisp/main.py b/lisp/main.py index 32860faf6..20ab9a184 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -21,16 +21,17 @@ import logging import os import sys -import traceback from PyQt5.QtCore import QLocale, QLibraryInfo from PyQt5.QtWidgets import QApplication +from logging.handlers import RotatingFileHandler from lisp import USER_DIR, DEFAULT_APP_CONFIG, USER_APP_CONFIG, plugins, \ - I18N_PATH + I18N_PATH, LOGS_DIR from lisp.application import Application from lisp.core.configuration import JSONFileConfiguration from lisp.ui import themes +from lisp.ui.icons import IconTheme from lisp.ui.ui_utils import install_translation @@ -45,27 +46,41 @@ def main(): args = parser.parse_args() - # Set the logging level - if args.log == 'debug': - log = logging.DEBUG + # Make sure the application user directory exists + os.makedirs(USER_DIR, exist_ok=True) + # Get logging level for the console + if args.log == 'debug': + console_log_level = logging.DEBUG # If something bad happen at low-level (e.g. segfault) print the stack import faulthandler faulthandler.enable() elif args.log == 'info': - log = logging.INFO + console_log_level = logging.INFO else: - log = logging.WARNING + console_log_level = logging.WARNING - logging.basicConfig( - format='%(asctime)s.%(msecs)03d\t%(name)s\t%(levelname)s\t%(message)s', - datefmt='%H:%M:%S', - level=log, + # Setup the root logger + default_formatter = logging.Formatter( + '%(asctime)s.%(msecs)03d\t%(name)s\t%(levelname)s\t%(message)s', + datefmt='%Y-%m-%d %H:%M:%S' ) - logger = logging.getLogger(__name__) - - # Create (if not present) user directory - os.makedirs(os.path.dirname(USER_DIR), exist_ok=True) + root_logger = logging.getLogger() + root_logger.setLevel(logging.DEBUG) + + # Create the console handler + stream_handler = logging.StreamHandler() + stream_handler.setFormatter(default_formatter) + stream_handler.setLevel(console_log_level) + root_logger.addHandler(stream_handler) + + # Make sure the logs directory exists + os.makedirs(LOGS_DIR, exist_ok=True) + # Create the file handler + file_handler = RotatingFileHandler( + os.path.join(LOGS_DIR, 'lisp.log'), maxBytes=10*(2**20), backupCount=5) + file_handler.setFormatter(default_formatter) + root_logger.addHandler(file_handler) # Load application configuration app_conf = JSONFileConfiguration(USER_APP_CONFIG, DEFAULT_APP_CONFIG) @@ -80,7 +95,7 @@ def main(): if locale: QLocale().setDefault(QLocale(locale)) - logger.info('Using {} locale'.format(QLocale().name())) + logging.info('Using {} locale'.format(QLocale().name())) # Qt platform translation install_translation( @@ -90,36 +105,31 @@ def main(): # Set UI theme try: - theme = app_conf['theme.theme'] - themes.THEMES[theme].apply(qt_app) - logger.info('Using "{}" theme'.format(theme)) + theme_name = app_conf['theme.theme'] + themes.get_theme(theme_name).apply(qt_app) + logging.info('Using "{}" theme'.format(theme_name)) except Exception: - logger.exception('Unable to load theme.') + logging.exception('Unable to load theme.') - # Set the global IconTheme + # Set LiSP icon theme (not the Qt one) try: - icon_theme_name = app_conf['theme.icons'] - icon_theme = themes.ICON_THEMES[icon_theme_name] - icon_theme.set_theme(icon_theme) - logger.info('Using "{}" icon theme'.format(icon_theme_name)) + icon_theme = app_conf['theme.icons'] + IconTheme.set_theme_name(icon_theme) + logging.info('Using "{}" icon theme'.format(icon_theme)) except Exception: - logger.exception('Unable to load icon theme.') + logging.exception('Unable to load icon theme.') - # Create the application + # Initialize the application lisp_app = Application(app_conf) - # Load plugins plugins.load_plugins(lisp_app) - # Start/Initialize LiSP Application + # Start the application lisp_app.start(session_file=args.file) - # Start Qt Application (block until exit) - exit_code = qt_app.exec_() + exit_code = qt_app.exec_() # block until exit - # Finalize plugins + # Finalize all and exit plugins.finalize_plugins() - # Finalize the application lisp_app.finalize() - # Exit sys.exit(exit_code) diff --git a/lisp/plugins/action_cues/osc_cue.py b/lisp/plugins/action_cues/osc_cue.py index 84788b7b4..fbb99db28 100644 --- a/lisp/plugins/action_cues/osc_cue.py +++ b/lisp/plugins/action_cues/osc_cue.py @@ -185,19 +185,16 @@ def __pause__(self, fade=False): def __fade(self, fade_type): try: self.__fader.prepare() - ended = self.__fader.fade(round(self.duration / 1000, 2), - 1, - fade_type) + ended = self.__fader.fade( + round(self.duration / 1000, 2), 1, fade_type) # to avoid approximation problems self._position = 1 if ended: self._ended() - except Exception as e: - self._error( - translate('OscCue', 'Error during cue execution'), - str(e) - ) + except Exception: + logger.exception(translate('OscCue', 'Error during cue execution.')) + self._error() def current_time(self): return self.__fader.current_time() diff --git a/lisp/plugins/action_cues/volume_control.py b/lisp/plugins/action_cues/volume_control.py index 4fe069119..efad7b860 100644 --- a/lisp/plugins/action_cues/volume_control.py +++ b/lisp/plugins/action_cues/volume_control.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging from PyQt5 import QtCore from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP @@ -37,6 +38,8 @@ from lisp.ui.ui_utils import translate from lisp.ui.widgets import FadeEdit +logger = logging.getLogger(__name__) + class VolumeControl(Cue): Name = QT_TRANSLATE_NOOP('CueName', 'Volume Control') @@ -99,19 +102,16 @@ def __pause__(self, fade=False): def __fade(self, fade_type): try: self.__fader.prepare() - ended = self.__fader.fade(round(self.duration / 1000, 2), - self.volume, - fade_type) + ended = self.__fader.fade( + round(self.duration / 1000, 2), self.volume, fade_type) if ended: # to avoid approximation problems self.__fader.target.current_volume = self.volume self._ended() - except Exception as e: - self._error( - translate('VolumeControl', 'Error during cue execution'), - str(e) - ) + except Exception: + logger.exception(translate('OscCue', 'Error during cue execution.')) + self._error() def current_time(self): return self.__fader.current_time() diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index 6a85dd14c..f852e181e 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging import weakref from lisp.backend.media import Media, MediaState @@ -25,6 +26,8 @@ from lisp.plugins.gst_backend import elements as gst_elements from lisp.plugins.gst_backend.gi_repository import Gst +logger = logging.getLogger(__name__) + def validate_pipeline(pipe, rebuild=False): # The first must be an input element @@ -257,10 +260,13 @@ def __on_message(self, bus, message): self._gst_pipe.set_state(Gst.State.PLAYING) if message.type == Gst.MessageType.ERROR: - err, debug = message.parse_error() + error, _ = message.parse_error() + logger.error('GStreamer: {}'.format(error.message), exc_info=error) + self._state = MediaState.Error self.interrupt(dispose=True, emit=False) - self.error.emit(self, str(err), str(debug)) + + self.error.emit(self) def __on_eos(self): if self._loop_count != 0: diff --git a/lisp/plugins/gst_backend/gst_pipe_edit.py b/lisp/plugins/gst_backend/gst_pipe_edit.py index da6c74d78..bf8890f66 100644 --- a/lisp/plugins/gst_backend/gst_pipe_edit.py +++ b/lisp/plugins/gst_backend/gst_pipe_edit.py @@ -23,7 +23,7 @@ QListWidgetItem from lisp.plugins.gst_backend import elements -from lisp.ui.themes.theme import IconTheme +from lisp.ui.icons import IconTheme from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/rename_cues/rename_ui.py b/lisp/plugins/rename_cues/rename_ui.py index b88454eca..594d9da96 100644 --- a/lisp/plugins/rename_cues/rename_ui.py +++ b/lisp/plugins/rename_cues/rename_ui.py @@ -26,7 +26,7 @@ QTreeWidget, QAbstractItemView, QTreeWidgetItem, QPushButton, QSpacerItem, \ QMessageBox -from lisp.ui.themes import IconTheme +from lisp.ui.icons import IconTheme from lisp.ui.ui_utils import translate logger = logging.getLogger(__name__) @@ -98,13 +98,13 @@ def __init__(self, parent=None, selected_cues=()): self.layout().addWidget(self.outRegexLine, 3, 3) self.regexLine = QLineEdit() - self.regexLine.setPlaceholderText(translate('RenameCues', 'Type your regex here :')) + self.regexLine.setPlaceholderText(translate('RenameCues', 'Type your regex here: ')) self.regexLine.textChanged.connect(self.onRegexLineChanged) self.layout().addWidget(self.regexLine, 4, 3) # Help button self.helpButton = QPushButton() - self.helpButton.setIcon(IconTheme.get('help-info')) + self.helpButton.setIcon(IconTheme.get('help-about')) self.helpButton.setIconSize(QSize(32, 32)) self.layout().addWidget(self.helpButton, 3, 4, 2, 1) self.helpButton.clicked.connect(self.onHelpButtonClicked) diff --git a/lisp/ui/about.py b/lisp/ui/about.py index 5c85de93b..4eb68f6b8 100644 --- a/lisp/ui/about.py +++ b/lisp/ui/about.py @@ -25,7 +25,7 @@ QTextBrowser, QDialogButtonBox import lisp -from lisp.ui.themes.theme import IconTheme +from lisp.ui.icons import IconTheme from lisp.ui.ui_utils import translate diff --git a/lisp/ui/icons/__init__.py b/lisp/ui/icons/__init__.py new file mode 100644 index 000000000..e6ee40019 --- /dev/null +++ b/lisp/ui/icons/__init__.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import glob +from os import path + +try: + from os import scandir +except ImportError: + from scandir import scandir + +from PyQt5.QtGui import QIcon + +from lisp import ICON_THEMES_DIR, ICON_THEME_COMMON + + +def icon_themes_names(): + for entry in scandir(path.dirname(__file__)): + if (entry.is_dir() and entry.name != ICON_THEME_COMMON + and entry.name[0] != '_'): + yield entry.name + + +class IconTheme: + _SEARCH_PATTERN = '{}/**/{}.*' + _BLANK_ICON = QIcon() + _GlobalCache = {} + _GlobalTheme = None + + def __init__(self, *names): + self._lookup_dirs = [path.join(ICON_THEMES_DIR, d) for d in names] + + def __iter__(self): + yield from self._lookup_dirs + + @staticmethod + def get(icon_name): + icon = IconTheme._GlobalCache.get(icon_name, None) + + if icon is None: + icon = IconTheme._BLANK_ICON + for dir_ in IconTheme._GlobalTheme: + pattern = IconTheme._SEARCH_PATTERN.format(dir_, icon_name) + for icon in glob.iglob(pattern, recursive=True): + icon = QIcon(icon) + break + + IconTheme._GlobalCache[icon_name] = icon + + return icon + + @staticmethod + def set_theme_name(theme_name): + IconTheme._GlobalCache.clear() + IconTheme._GlobalTheme = IconTheme(theme_name, ICON_THEME_COMMON) diff --git a/lisp/ui/themes/icons/lisp/fadein-linear.png b/lisp/ui/icons/lisp/fadein-linear.png similarity index 100% rename from lisp/ui/themes/icons/lisp/fadein-linear.png rename to lisp/ui/icons/lisp/fadein-linear.png diff --git a/lisp/ui/themes/icons/lisp/fadein-quadratic.png b/lisp/ui/icons/lisp/fadein-quadratic.png similarity index 100% rename from lisp/ui/themes/icons/lisp/fadein-quadratic.png rename to lisp/ui/icons/lisp/fadein-quadratic.png diff --git a/lisp/ui/themes/icons/lisp/fadein-quadratic2.png b/lisp/ui/icons/lisp/fadein-quadratic2.png similarity index 100% rename from lisp/ui/themes/icons/lisp/fadein-quadratic2.png rename to lisp/ui/icons/lisp/fadein-quadratic2.png diff --git a/lisp/ui/themes/icons/lisp/fadeout-linear.png b/lisp/ui/icons/lisp/fadeout-linear.png similarity index 100% rename from lisp/ui/themes/icons/lisp/fadeout-linear.png rename to lisp/ui/icons/lisp/fadeout-linear.png diff --git a/lisp/ui/themes/icons/lisp/fadeout-quadratic.png b/lisp/ui/icons/lisp/fadeout-quadratic.png similarity index 100% rename from lisp/ui/themes/icons/lisp/fadeout-quadratic.png rename to lisp/ui/icons/lisp/fadeout-quadratic.png diff --git a/lisp/ui/themes/icons/lisp/fadeout-quadratic2.png b/lisp/ui/icons/lisp/fadeout-quadratic2.png similarity index 100% rename from lisp/ui/themes/icons/lisp/fadeout-quadratic2.png rename to lisp/ui/icons/lisp/fadeout-quadratic2.png diff --git a/lisp/ui/themes/icons/lisp/led-error.svg b/lisp/ui/icons/lisp/led-error.svg similarity index 100% rename from lisp/ui/themes/icons/lisp/led-error.svg rename to lisp/ui/icons/lisp/led-error.svg diff --git a/lisp/ui/themes/icons/lisp/led-off.svg b/lisp/ui/icons/lisp/led-off.svg similarity index 100% rename from lisp/ui/themes/icons/lisp/led-off.svg rename to lisp/ui/icons/lisp/led-off.svg diff --git a/lisp/ui/themes/icons/lisp/led-pause.svg b/lisp/ui/icons/lisp/led-pause.svg similarity index 100% rename from lisp/ui/themes/icons/lisp/led-pause.svg rename to lisp/ui/icons/lisp/led-pause.svg diff --git a/lisp/ui/themes/icons/lisp/led-running.svg b/lisp/ui/icons/lisp/led-running.svg similarity index 100% rename from lisp/ui/themes/icons/lisp/led-running.svg rename to lisp/ui/icons/lisp/led-running.svg diff --git a/lisp/ui/themes/icons/lisp/linux-show-player.png b/lisp/ui/icons/lisp/linux-show-player.png similarity index 100% rename from lisp/ui/themes/icons/lisp/linux-show-player.png rename to lisp/ui/icons/lisp/linux-show-player.png diff --git a/lisp/ui/themes/icons/lisp/mixer-handle.svg b/lisp/ui/icons/lisp/mixer-handle.svg similarity index 100% rename from lisp/ui/themes/icons/lisp/mixer-handle.svg rename to lisp/ui/icons/lisp/mixer-handle.svg diff --git a/lisp/ui/themes/icons/numix/cue-interrupt.svg b/lisp/ui/icons/numix/custom/cue-interrupt.svg similarity index 100% rename from lisp/ui/themes/icons/numix/cue-interrupt.svg rename to lisp/ui/icons/numix/custom/cue-interrupt.svg diff --git a/lisp/ui/themes/icons/numix/cue-pause.svg b/lisp/ui/icons/numix/custom/cue-pause.svg similarity index 100% rename from lisp/ui/themes/icons/numix/cue-pause.svg rename to lisp/ui/icons/numix/custom/cue-pause.svg diff --git a/lisp/ui/themes/icons/numix/cue-start.svg b/lisp/ui/icons/numix/custom/cue-start.svg similarity index 100% rename from lisp/ui/themes/icons/numix/cue-start.svg rename to lisp/ui/icons/numix/custom/cue-start.svg diff --git a/lisp/ui/themes/icons/numix/cue-stop.svg b/lisp/ui/icons/numix/custom/cue-stop.svg similarity index 100% rename from lisp/ui/themes/icons/numix/cue-stop.svg rename to lisp/ui/icons/numix/custom/cue-stop.svg diff --git a/lisp/ui/themes/icons/numix/fadein-generic.svg b/lisp/ui/icons/numix/custom/fadein-generic.svg similarity index 100% rename from lisp/ui/themes/icons/numix/fadein-generic.svg rename to lisp/ui/icons/numix/custom/fadein-generic.svg diff --git a/lisp/ui/themes/icons/numix/fadeout-generic.svg b/lisp/ui/icons/numix/custom/fadeout-generic.svg similarity index 100% rename from lisp/ui/themes/icons/numix/fadeout-generic.svg rename to lisp/ui/icons/numix/custom/fadeout-generic.svg diff --git a/lisp/ui/icons/numix/standard/actions/address-book-new.svg b/lisp/ui/icons/numix/standard/actions/address-book-new.svg new file mode 100644 index 000000000..6256b7e64 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/address-book-new.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/application-exit.svg b/lisp/ui/icons/numix/standard/actions/application-exit.svg new file mode 100644 index 000000000..11702cf59 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/application-exit.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/appointment-new.svg b/lisp/ui/icons/numix/standard/actions/appointment-new.svg new file mode 100644 index 000000000..911520b75 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/appointment-new.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/call-start.svg b/lisp/ui/icons/numix/standard/actions/call-start.svg new file mode 100644 index 000000000..88cc876f6 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/call-start.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/call-stop.svg b/lisp/ui/icons/numix/standard/actions/call-stop.svg new file mode 100644 index 000000000..d57d79c7e --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/call-stop.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/contact-new.svg b/lisp/ui/icons/numix/standard/actions/contact-new.svg new file mode 100644 index 000000000..fb3a91c17 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/contact-new.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/actions/document-new.svg b/lisp/ui/icons/numix/standard/actions/document-new.svg new file mode 100644 index 000000000..596ca80b9 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/document-new.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/actions/document-open-recent.svg b/lisp/ui/icons/numix/standard/actions/document-open-recent.svg new file mode 100644 index 000000000..4a19b9f58 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/document-open-recent.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/document-open.svg b/lisp/ui/icons/numix/standard/actions/document-open.svg new file mode 100644 index 000000000..69d6af2c7 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/document-open.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/document-page-setup.svg b/lisp/ui/icons/numix/standard/actions/document-page-setup.svg new file mode 100644 index 000000000..becf33147 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/document-page-setup.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/document-print-preview.svg b/lisp/ui/icons/numix/standard/actions/document-print-preview.svg new file mode 100644 index 000000000..8c5947448 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/document-print-preview.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/document-print.svg b/lisp/ui/icons/numix/standard/actions/document-print.svg new file mode 100644 index 000000000..af30c1d9e --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/document-print.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/document-properties.svg b/lisp/ui/icons/numix/standard/actions/document-properties.svg new file mode 100644 index 000000000..1b7cde149 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/document-properties.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/document-revert.svg b/lisp/ui/icons/numix/standard/actions/document-revert.svg new file mode 100644 index 000000000..5f45aa940 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/document-revert.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/actions/document-save-as.svg b/lisp/ui/icons/numix/standard/actions/document-save-as.svg new file mode 100644 index 000000000..3de787a16 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/document-save-as.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/document-save.svg b/lisp/ui/icons/numix/standard/actions/document-save.svg new file mode 100644 index 000000000..0da5dca10 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/document-save.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/document-send.svg b/lisp/ui/icons/numix/standard/actions/document-send.svg new file mode 100644 index 000000000..f45e29570 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/document-send.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/edit-clear.svg b/lisp/ui/icons/numix/standard/actions/edit-clear.svg new file mode 100644 index 000000000..ec4b69d4e --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/edit-clear.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/actions/edit-copy.svg b/lisp/ui/icons/numix/standard/actions/edit-copy.svg new file mode 100644 index 000000000..a1e989989 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/edit-copy.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/edit-cut.svg b/lisp/ui/icons/numix/standard/actions/edit-cut.svg new file mode 100644 index 000000000..26f8f20d1 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/edit-cut.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/edit-delete.svg b/lisp/ui/icons/numix/standard/actions/edit-delete.svg new file mode 100644 index 000000000..f2523a11f --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/edit-delete.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/edit-find-replace.svg b/lisp/ui/icons/numix/standard/actions/edit-find-replace.svg new file mode 100644 index 000000000..ad976c680 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/edit-find-replace.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/edit-find.svg b/lisp/ui/icons/numix/standard/actions/edit-find.svg new file mode 100644 index 000000000..0a4bf4fb8 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/edit-find.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/edit-paste.svg b/lisp/ui/icons/numix/standard/actions/edit-paste.svg new file mode 100644 index 000000000..5529f5c8c --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/edit-paste.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/edit-redo.svg b/lisp/ui/icons/numix/standard/actions/edit-redo.svg new file mode 100644 index 000000000..811751811 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/edit-redo.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/actions/edit-select-all.svg b/lisp/ui/icons/numix/standard/actions/edit-select-all.svg new file mode 100644 index 000000000..bacbcc02c --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/edit-select-all.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/edit-undo.svg b/lisp/ui/icons/numix/standard/actions/edit-undo.svg new file mode 100644 index 000000000..b5c9c0714 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/edit-undo.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/actions/folder-new.svg b/lisp/ui/icons/numix/standard/actions/folder-new.svg new file mode 100644 index 000000000..ed7c3abb7 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/folder-new.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/format-indent-less.svg b/lisp/ui/icons/numix/standard/actions/format-indent-less.svg new file mode 100644 index 000000000..417f9a8e6 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/format-indent-less.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/format-indent-more.svg b/lisp/ui/icons/numix/standard/actions/format-indent-more.svg new file mode 100644 index 000000000..a8ad4f0e8 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/format-indent-more.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/format-justify-center.svg b/lisp/ui/icons/numix/standard/actions/format-justify-center.svg new file mode 100644 index 000000000..7e034f99e --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/format-justify-center.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/format-justify-fill.svg b/lisp/ui/icons/numix/standard/actions/format-justify-fill.svg new file mode 100644 index 000000000..9fa248fcc --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/format-justify-fill.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/format-justify-left.svg b/lisp/ui/icons/numix/standard/actions/format-justify-left.svg new file mode 100644 index 000000000..c920c6d21 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/format-justify-left.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/format-justify-right.svg b/lisp/ui/icons/numix/standard/actions/format-justify-right.svg new file mode 100644 index 000000000..368e3190a --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/format-justify-right.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/format-text-bold.svg b/lisp/ui/icons/numix/standard/actions/format-text-bold.svg new file mode 100644 index 000000000..4a8a5d8c4 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/format-text-bold.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/actions/format-text-direction-ltr.svg b/lisp/ui/icons/numix/standard/actions/format-text-direction-ltr.svg new file mode 100644 index 000000000..8e2c924f4 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/format-text-direction-ltr.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/format-text-direction-rtl.svg b/lisp/ui/icons/numix/standard/actions/format-text-direction-rtl.svg new file mode 100644 index 000000000..d47a3e9a4 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/format-text-direction-rtl.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/format-text-italic.svg b/lisp/ui/icons/numix/standard/actions/format-text-italic.svg new file mode 100644 index 000000000..b016d75b6 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/format-text-italic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/format-text-strikethrough.svg b/lisp/ui/icons/numix/standard/actions/format-text-strikethrough.svg new file mode 100644 index 000000000..759dcafd9 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/format-text-strikethrough.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/format-text-underline.svg b/lisp/ui/icons/numix/standard/actions/format-text-underline.svg new file mode 100644 index 000000000..4448982ab --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/format-text-underline.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/go-bottom.svg b/lisp/ui/icons/numix/standard/actions/go-bottom.svg new file mode 100644 index 000000000..ae1add864 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/go-bottom.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/go-down.svg b/lisp/ui/icons/numix/standard/actions/go-down.svg new file mode 100644 index 000000000..c5e6a3293 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/go-down.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/go-first.svg b/lisp/ui/icons/numix/standard/actions/go-first.svg new file mode 100644 index 000000000..9c91acde8 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/go-first.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/go-home.svg b/lisp/ui/icons/numix/standard/actions/go-home.svg new file mode 100644 index 000000000..421777310 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/go-home.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/go-jump.svg b/lisp/ui/icons/numix/standard/actions/go-jump.svg new file mode 100644 index 000000000..2a67036b4 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/go-jump.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/go-last.svg b/lisp/ui/icons/numix/standard/actions/go-last.svg new file mode 100644 index 000000000..b57882311 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/go-last.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/themes/icons/numix/go-next.svg b/lisp/ui/icons/numix/standard/actions/go-next.svg similarity index 100% rename from lisp/ui/themes/icons/numix/go-next.svg rename to lisp/ui/icons/numix/standard/actions/go-next.svg diff --git a/lisp/ui/themes/icons/numix/go-previous.svg b/lisp/ui/icons/numix/standard/actions/go-previous.svg similarity index 100% rename from lisp/ui/themes/icons/numix/go-previous.svg rename to lisp/ui/icons/numix/standard/actions/go-previous.svg diff --git a/lisp/ui/icons/numix/standard/actions/go-top.svg b/lisp/ui/icons/numix/standard/actions/go-top.svg new file mode 100644 index 000000000..8c4a02869 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/go-top.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/go-up.svg b/lisp/ui/icons/numix/standard/actions/go-up.svg new file mode 100644 index 000000000..e3546446d --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/go-up.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/themes/icons/numix/help-info.svg b/lisp/ui/icons/numix/standard/actions/help-about.svg similarity index 100% rename from lisp/ui/themes/icons/numix/help-info.svg rename to lisp/ui/icons/numix/standard/actions/help-about.svg diff --git a/lisp/ui/icons/numix/standard/actions/help-contents.svg b/lisp/ui/icons/numix/standard/actions/help-contents.svg new file mode 100644 index 000000000..e6e85e8c4 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/help-contents.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/help-faq.svg b/lisp/ui/icons/numix/standard/actions/help-faq.svg new file mode 100644 index 000000000..0f750ede1 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/help-faq.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/insert-image.svg b/lisp/ui/icons/numix/standard/actions/insert-image.svg new file mode 100644 index 000000000..57c927bd6 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/insert-image.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/insert-link.svg b/lisp/ui/icons/numix/standard/actions/insert-link.svg new file mode 100644 index 000000000..34debf6f8 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/insert-link.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/insert-object.svg b/lisp/ui/icons/numix/standard/actions/insert-object.svg new file mode 100644 index 000000000..b020c7e1f --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/insert-object.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/insert-text.svg b/lisp/ui/icons/numix/standard/actions/insert-text.svg new file mode 100644 index 000000000..c687c0c46 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/insert-text.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/list-add.svg b/lisp/ui/icons/numix/standard/actions/list-add.svg new file mode 100644 index 000000000..ad08e8d70 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/list-add.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/actions/list-remove.svg b/lisp/ui/icons/numix/standard/actions/list-remove.svg new file mode 100644 index 000000000..5ef07aafb --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/list-remove.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/actions/mail-forward.svg b/lisp/ui/icons/numix/standard/actions/mail-forward.svg new file mode 100644 index 000000000..01ea9bc68 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/mail-forward.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/actions/mail-mark-important.svg b/lisp/ui/icons/numix/standard/actions/mail-mark-important.svg new file mode 100644 index 000000000..103e5e179 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/mail-mark-important.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/mail-mark-junk.svg b/lisp/ui/icons/numix/standard/actions/mail-mark-junk.svg new file mode 100644 index 000000000..1b46fab98 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/mail-mark-junk.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/mail-mark-notjunk.svg b/lisp/ui/icons/numix/standard/actions/mail-mark-notjunk.svg new file mode 100644 index 000000000..97995c141 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/mail-mark-notjunk.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/mail-mark-read.svg b/lisp/ui/icons/numix/standard/actions/mail-mark-read.svg new file mode 100644 index 000000000..1219ba3f5 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/mail-mark-read.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/mail-mark-unread.svg b/lisp/ui/icons/numix/standard/actions/mail-mark-unread.svg new file mode 100644 index 000000000..99c893f98 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/mail-mark-unread.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/mail-message-new.svg b/lisp/ui/icons/numix/standard/actions/mail-message-new.svg new file mode 100644 index 000000000..82c579214 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/mail-message-new.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/mail-reply-all.svg b/lisp/ui/icons/numix/standard/actions/mail-reply-all.svg new file mode 100644 index 000000000..8167df592 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/mail-reply-all.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/mail-reply-sender.svg b/lisp/ui/icons/numix/standard/actions/mail-reply-sender.svg new file mode 100644 index 000000000..751624bed --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/mail-reply-sender.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/actions/mail-send-receive.svg b/lisp/ui/icons/numix/standard/actions/mail-send-receive.svg new file mode 100644 index 000000000..dee4a91e8 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/mail-send-receive.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/actions/mail-send.svg b/lisp/ui/icons/numix/standard/actions/mail-send.svg new file mode 100644 index 000000000..ff6842349 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/mail-send.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/media-eject.svg b/lisp/ui/icons/numix/standard/actions/media-eject.svg new file mode 100644 index 000000000..64ef44e16 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/media-eject.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/media-playback-pause.svg b/lisp/ui/icons/numix/standard/actions/media-playback-pause.svg new file mode 100644 index 000000000..969ed2208 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/media-playback-pause.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/media-playback-start.svg b/lisp/ui/icons/numix/standard/actions/media-playback-start.svg new file mode 100644 index 000000000..fd18ba6bb --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/media-playback-start.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/actions/media-playback-stop.svg b/lisp/ui/icons/numix/standard/actions/media-playback-stop.svg new file mode 100644 index 000000000..bebdea821 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/media-playback-stop.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/actions/media-record.svg b/lisp/ui/icons/numix/standard/actions/media-record.svg new file mode 100644 index 000000000..6eb0b4503 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/media-record.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/actions/media-seek-backward.svg b/lisp/ui/icons/numix/standard/actions/media-seek-backward.svg new file mode 100644 index 000000000..ddaf8d47a --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/media-seek-backward.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/media-seek-forward.svg b/lisp/ui/icons/numix/standard/actions/media-seek-forward.svg new file mode 100644 index 000000000..8cff7d5e8 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/media-seek-forward.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/media-skip-backward.svg b/lisp/ui/icons/numix/standard/actions/media-skip-backward.svg new file mode 100644 index 000000000..9f4c2adf3 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/media-skip-backward.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/media-skip-forward.svg b/lisp/ui/icons/numix/standard/actions/media-skip-forward.svg new file mode 100644 index 000000000..fb38f9ecd --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/media-skip-forward.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/object-flip-horizontal.svg b/lisp/ui/icons/numix/standard/actions/object-flip-horizontal.svg new file mode 100644 index 000000000..01b75de02 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/object-flip-horizontal.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/object-flip-vertical.svg b/lisp/ui/icons/numix/standard/actions/object-flip-vertical.svg new file mode 100644 index 000000000..8c48a6d63 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/object-flip-vertical.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/object-rotate-left.svg b/lisp/ui/icons/numix/standard/actions/object-rotate-left.svg new file mode 100644 index 000000000..486d1d72e --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/object-rotate-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/actions/object-rotate-right.svg b/lisp/ui/icons/numix/standard/actions/object-rotate-right.svg new file mode 100644 index 000000000..b15ef8ae0 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/object-rotate-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/actions/process-stop.svg b/lisp/ui/icons/numix/standard/actions/process-stop.svg new file mode 100644 index 000000000..18076fb04 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/process-stop.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/system-lock-screen.svg b/lisp/ui/icons/numix/standard/actions/system-lock-screen.svg new file mode 100644 index 000000000..e0f5fbe5e --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/system-lock-screen.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/actions/system-log-out.svg b/lisp/ui/icons/numix/standard/actions/system-log-out.svg new file mode 100644 index 000000000..4f68b02d4 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/system-log-out.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/system-reboot.svg b/lisp/ui/icons/numix/standard/actions/system-reboot.svg new file mode 100644 index 000000000..fb4e9a7d6 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/system-reboot.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/actions/system-run.svg b/lisp/ui/icons/numix/standard/actions/system-run.svg new file mode 100644 index 000000000..740f2c4d5 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/system-run.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/system-search.svg b/lisp/ui/icons/numix/standard/actions/system-search.svg new file mode 100644 index 000000000..0a4bf4fb8 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/system-search.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/system-shutdown.svg b/lisp/ui/icons/numix/standard/actions/system-shutdown.svg new file mode 100644 index 000000000..11702cf59 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/system-shutdown.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/tools-check-spelling.svg b/lisp/ui/icons/numix/standard/actions/tools-check-spelling.svg new file mode 100644 index 000000000..f2c2eda58 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/tools-check-spelling.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/view-fullscreen.svg b/lisp/ui/icons/numix/standard/actions/view-fullscreen.svg new file mode 100644 index 000000000..cc28b7e04 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/view-fullscreen.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/view-refresh.svg b/lisp/ui/icons/numix/standard/actions/view-refresh.svg new file mode 100644 index 000000000..2018e5d7e --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/view-refresh.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/view-restore.svg b/lisp/ui/icons/numix/standard/actions/view-restore.svg new file mode 100644 index 000000000..98f9063d5 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/view-restore.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/view-sort-ascending.svg b/lisp/ui/icons/numix/standard/actions/view-sort-ascending.svg new file mode 100644 index 000000000..c25c155a6 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/view-sort-ascending.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/view-sort-descending.svg b/lisp/ui/icons/numix/standard/actions/view-sort-descending.svg new file mode 100644 index 000000000..53ee7a770 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/view-sort-descending.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/window-close.svg b/lisp/ui/icons/numix/standard/actions/window-close.svg new file mode 100644 index 000000000..bae8dd284 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/window-close.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/actions/window-new.svg b/lisp/ui/icons/numix/standard/actions/window-new.svg new file mode 100644 index 000000000..282b0ebef --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/window-new.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/zoom-fit-best.svg b/lisp/ui/icons/numix/standard/actions/zoom-fit-best.svg new file mode 100644 index 000000000..091ad951a --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/zoom-fit-best.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/zoom-in.svg b/lisp/ui/icons/numix/standard/actions/zoom-in.svg new file mode 100644 index 000000000..9f5244ef7 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/zoom-in.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/zoom-original.svg b/lisp/ui/icons/numix/standard/actions/zoom-original.svg new file mode 100644 index 000000000..970050b40 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/zoom-original.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/actions/zoom-out.svg b/lisp/ui/icons/numix/standard/actions/zoom-out.svg new file mode 100644 index 000000000..b2a6884d5 --- /dev/null +++ b/lisp/ui/icons/numix/standard/actions/zoom-out.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/categories/applications-accessories.svg b/lisp/ui/icons/numix/standard/categories/applications-accessories.svg new file mode 100644 index 000000000..acdb214c0 --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/applications-accessories.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/categories/applications-development.svg b/lisp/ui/icons/numix/standard/categories/applications-development.svg new file mode 100644 index 000000000..68d61d7dc --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/applications-development.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/categories/applications-engineering.svg b/lisp/ui/icons/numix/standard/categories/applications-engineering.svg new file mode 100644 index 000000000..992ee1959 --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/applications-engineering.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/categories/applications-games.svg b/lisp/ui/icons/numix/standard/categories/applications-games.svg new file mode 100644 index 000000000..468e1f8f6 --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/applications-games.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/categories/applications-graphics.svg b/lisp/ui/icons/numix/standard/categories/applications-graphics.svg new file mode 100644 index 000000000..0bb818891 --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/applications-graphics.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/categories/applications-internet.svg b/lisp/ui/icons/numix/standard/categories/applications-internet.svg new file mode 100644 index 000000000..443150f07 --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/applications-internet.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/categories/applications-multimedia.svg b/lisp/ui/icons/numix/standard/categories/applications-multimedia.svg new file mode 100644 index 000000000..8c337d2f1 --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/applications-multimedia.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/numix/standard/categories/applications-office.svg b/lisp/ui/icons/numix/standard/categories/applications-office.svg new file mode 100644 index 000000000..e99368590 --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/applications-office.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/categories/applications-other.svg b/lisp/ui/icons/numix/standard/categories/applications-other.svg new file mode 100644 index 000000000..f3144fb8a --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/applications-other.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/categories/applications-science.svg b/lisp/ui/icons/numix/standard/categories/applications-science.svg new file mode 100644 index 000000000..af0204b0d --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/applications-science.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/categories/applications-system.svg b/lisp/ui/icons/numix/standard/categories/applications-system.svg new file mode 100644 index 000000000..e4c6f03e7 --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/applications-system.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/categories/applications-utilities.svg b/lisp/ui/icons/numix/standard/categories/applications-utilities.svg new file mode 100644 index 000000000..4ea7b8a5b --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/applications-utilities.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/categories/preferences-desktop-peripherals.svg b/lisp/ui/icons/numix/standard/categories/preferences-desktop-peripherals.svg new file mode 100644 index 000000000..00c43ec6e --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/preferences-desktop-peripherals.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/categories/preferences-desktop-personal.svg b/lisp/ui/icons/numix/standard/categories/preferences-desktop-personal.svg new file mode 100644 index 000000000..8c9956079 --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/preferences-desktop-personal.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/categories/preferences-desktop.svg b/lisp/ui/icons/numix/standard/categories/preferences-desktop.svg new file mode 100644 index 000000000..8c9956079 --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/preferences-desktop.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/categories/preferences-other.svg b/lisp/ui/icons/numix/standard/categories/preferences-other.svg new file mode 100644 index 000000000..f3144fb8a --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/preferences-other.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/categories/preferences-system-network.svg b/lisp/ui/icons/numix/standard/categories/preferences-system-network.svg new file mode 100644 index 000000000..9554c4ec9 --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/preferences-system-network.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/categories/preferences-system.svg b/lisp/ui/icons/numix/standard/categories/preferences-system.svg new file mode 100644 index 000000000..8c9956079 --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/preferences-system.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/categories/system-help.svg b/lisp/ui/icons/numix/standard/categories/system-help.svg new file mode 100644 index 000000000..0d82f82ae --- /dev/null +++ b/lisp/ui/icons/numix/standard/categories/system-help.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/devices/audio-card.svg b/lisp/ui/icons/numix/standard/devices/audio-card.svg new file mode 100644 index 000000000..51aff3373 --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/audio-card.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/audio-input-microphone.svg b/lisp/ui/icons/numix/standard/devices/audio-input-microphone.svg new file mode 100644 index 000000000..72e69b82a --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/audio-input-microphone.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/battery.svg b/lisp/ui/icons/numix/standard/devices/battery.svg new file mode 100644 index 000000000..a9b1e0146 --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/battery.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/camera-photo.svg b/lisp/ui/icons/numix/standard/devices/camera-photo.svg new file mode 100644 index 000000000..8ed47a770 --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/camera-photo.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/camera-web.svg b/lisp/ui/icons/numix/standard/devices/camera-web.svg new file mode 100644 index 000000000..a15f6d889 --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/camera-web.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/computer.svg b/lisp/ui/icons/numix/standard/devices/computer.svg new file mode 100644 index 000000000..91f7c0e51 --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/computer.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/drive-harddisk.svg b/lisp/ui/icons/numix/standard/devices/drive-harddisk.svg new file mode 100644 index 000000000..0e5627f55 --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/drive-harddisk.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/drive-optical.svg b/lisp/ui/icons/numix/standard/devices/drive-optical.svg new file mode 100644 index 000000000..bcec83269 --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/drive-optical.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/drive-removable-media.svg b/lisp/ui/icons/numix/standard/devices/drive-removable-media.svg new file mode 100644 index 000000000..8d75c5c86 --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/drive-removable-media.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/input-gaming.svg b/lisp/ui/icons/numix/standard/devices/input-gaming.svg new file mode 100644 index 000000000..9c151321d --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/input-gaming.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/input-keyboard.svg b/lisp/ui/icons/numix/standard/devices/input-keyboard.svg new file mode 100644 index 000000000..18c4f2a49 --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/input-keyboard.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/input-mouse.svg b/lisp/ui/icons/numix/standard/devices/input-mouse.svg new file mode 100644 index 000000000..ed6d25db8 --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/input-mouse.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/input-tablet.svg b/lisp/ui/icons/numix/standard/devices/input-tablet.svg new file mode 100644 index 000000000..f6c0287af --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/input-tablet.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/media-flash.svg b/lisp/ui/icons/numix/standard/devices/media-flash.svg new file mode 100644 index 000000000..efcbdf296 --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/media-flash.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/media-floppy.svg b/lisp/ui/icons/numix/standard/devices/media-floppy.svg new file mode 100644 index 000000000..acdc967b3 --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/media-floppy.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/media-optical.svg b/lisp/ui/icons/numix/standard/devices/media-optical.svg new file mode 100644 index 000000000..739752c55 --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/media-optical.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/media-tape.svg b/lisp/ui/icons/numix/standard/devices/media-tape.svg new file mode 100644 index 000000000..d69fe72ff --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/media-tape.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/multimedia-player.svg b/lisp/ui/icons/numix/standard/devices/multimedia-player.svg new file mode 100644 index 000000000..e46854b74 --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/multimedia-player.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/network-wired.svg b/lisp/ui/icons/numix/standard/devices/network-wired.svg new file mode 100644 index 000000000..7f1fa8e1a --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/network-wired.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/network-wireless.svg b/lisp/ui/icons/numix/standard/devices/network-wireless.svg new file mode 100644 index 000000000..337e86a2d --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/network-wireless.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/pda.svg b/lisp/ui/icons/numix/standard/devices/pda.svg new file mode 100644 index 000000000..a54eb83d8 --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/pda.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/phone.svg b/lisp/ui/icons/numix/standard/devices/phone.svg new file mode 100644 index 000000000..33bb6083e --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/phone.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/printer.svg b/lisp/ui/icons/numix/standard/devices/printer.svg new file mode 100644 index 000000000..1fa337ab7 --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/printer.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/scanner.svg b/lisp/ui/icons/numix/standard/devices/scanner.svg new file mode 100644 index 000000000..9727aa82e --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/scanner.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/devices/video-display.svg b/lisp/ui/icons/numix/standard/devices/video-display.svg new file mode 100644 index 000000000..572e44c6e --- /dev/null +++ b/lisp/ui/icons/numix/standard/devices/video-display.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-default.svg b/lisp/ui/icons/numix/standard/emblems/emblem-default.svg new file mode 100644 index 000000000..4c11b824b --- /dev/null +++ b/lisp/ui/icons/numix/standard/emblems/emblem-default.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-documents.svg b/lisp/ui/icons/numix/standard/emblems/emblem-documents.svg new file mode 100644 index 000000000..3ecff8c24 --- /dev/null +++ b/lisp/ui/icons/numix/standard/emblems/emblem-documents.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-downloads.svg b/lisp/ui/icons/numix/standard/emblems/emblem-downloads.svg new file mode 100644 index 000000000..45ad52395 --- /dev/null +++ b/lisp/ui/icons/numix/standard/emblems/emblem-downloads.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-favorite.svg b/lisp/ui/icons/numix/standard/emblems/emblem-favorite.svg new file mode 100644 index 000000000..b47735c9a --- /dev/null +++ b/lisp/ui/icons/numix/standard/emblems/emblem-favorite.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-important.svg b/lisp/ui/icons/numix/standard/emblems/emblem-important.svg new file mode 100644 index 000000000..108c848e6 --- /dev/null +++ b/lisp/ui/icons/numix/standard/emblems/emblem-important.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-mail.svg b/lisp/ui/icons/numix/standard/emblems/emblem-mail.svg new file mode 100644 index 000000000..1df0c2b27 --- /dev/null +++ b/lisp/ui/icons/numix/standard/emblems/emblem-mail.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-photos.svg b/lisp/ui/icons/numix/standard/emblems/emblem-photos.svg new file mode 100644 index 000000000..488fa231e --- /dev/null +++ b/lisp/ui/icons/numix/standard/emblems/emblem-photos.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-readonly.svg b/lisp/ui/icons/numix/standard/emblems/emblem-readonly.svg new file mode 100644 index 000000000..257fbbd3b --- /dev/null +++ b/lisp/ui/icons/numix/standard/emblems/emblem-readonly.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-shared.svg b/lisp/ui/icons/numix/standard/emblems/emblem-shared.svg new file mode 100644 index 000000000..2b84999ef --- /dev/null +++ b/lisp/ui/icons/numix/standard/emblems/emblem-shared.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-symbolic-link.svg b/lisp/ui/icons/numix/standard/emblems/emblem-symbolic-link.svg new file mode 100644 index 000000000..d0cafe372 --- /dev/null +++ b/lisp/ui/icons/numix/standard/emblems/emblem-symbolic-link.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-system.svg b/lisp/ui/icons/numix/standard/emblems/emblem-system.svg new file mode 100644 index 000000000..960a39e54 --- /dev/null +++ b/lisp/ui/icons/numix/standard/emblems/emblem-system.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-unreadable.svg b/lisp/ui/icons/numix/standard/emblems/emblem-unreadable.svg new file mode 100644 index 000000000..8686fa64d --- /dev/null +++ b/lisp/ui/icons/numix/standard/emblems/emblem-unreadable.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/places/folder-remote.svg b/lisp/ui/icons/numix/standard/places/folder-remote.svg new file mode 100644 index 000000000..e818bb32b --- /dev/null +++ b/lisp/ui/icons/numix/standard/places/folder-remote.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/numix/standard/places/folder.svg b/lisp/ui/icons/numix/standard/places/folder.svg new file mode 100644 index 000000000..5cab84a79 --- /dev/null +++ b/lisp/ui/icons/numix/standard/places/folder.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/places/network-server.svg b/lisp/ui/icons/numix/standard/places/network-server.svg new file mode 100644 index 000000000..56a3850be --- /dev/null +++ b/lisp/ui/icons/numix/standard/places/network-server.svg @@ -0,0 +1,180 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/places/network-workgroup.svg b/lisp/ui/icons/numix/standard/places/network-workgroup.svg new file mode 100644 index 000000000..e818bb32b --- /dev/null +++ b/lisp/ui/icons/numix/standard/places/network-workgroup.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/numix/standard/places/start-here.svg b/lisp/ui/icons/numix/standard/places/start-here.svg new file mode 100644 index 000000000..447309c02 --- /dev/null +++ b/lisp/ui/icons/numix/standard/places/start-here.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/places/user-bookmarks.svg b/lisp/ui/icons/numix/standard/places/user-bookmarks.svg new file mode 100644 index 000000000..f8fc711e1 --- /dev/null +++ b/lisp/ui/icons/numix/standard/places/user-bookmarks.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/numix/standard/places/user-desktop.svg b/lisp/ui/icons/numix/standard/places/user-desktop.svg new file mode 100644 index 000000000..1d1519e57 --- /dev/null +++ b/lisp/ui/icons/numix/standard/places/user-desktop.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/places/user-home.svg b/lisp/ui/icons/numix/standard/places/user-home.svg new file mode 100644 index 000000000..09edd2617 --- /dev/null +++ b/lisp/ui/icons/numix/standard/places/user-home.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/places/user-trash.svg b/lisp/ui/icons/numix/standard/places/user-trash.svg new file mode 100644 index 000000000..a2b35a4dd --- /dev/null +++ b/lisp/ui/icons/numix/standard/places/user-trash.svg @@ -0,0 +1,148 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/status/appointment-missed.svg b/lisp/ui/icons/numix/standard/status/appointment-missed.svg new file mode 100644 index 000000000..1caaf868b --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/appointment-missed.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/status/appointment-soon.svg b/lisp/ui/icons/numix/standard/status/appointment-soon.svg new file mode 100644 index 000000000..335f3b279 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/appointment-soon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/status/audio-volume-high.svg b/lisp/ui/icons/numix/standard/status/audio-volume-high.svg new file mode 100644 index 000000000..b031b4ddf --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/audio-volume-high.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/status/audio-volume-low.svg b/lisp/ui/icons/numix/standard/status/audio-volume-low.svg new file mode 100644 index 000000000..947969ffc --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/audio-volume-low.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/status/audio-volume-medium.svg b/lisp/ui/icons/numix/standard/status/audio-volume-medium.svg new file mode 100644 index 000000000..11d1be462 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/audio-volume-medium.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/status/audio-volume-muted.svg b/lisp/ui/icons/numix/standard/status/audio-volume-muted.svg new file mode 100644 index 000000000..e06e01840 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/audio-volume-muted.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/numix/standard/status/battery-caution.svg b/lisp/ui/icons/numix/standard/status/battery-caution.svg new file mode 100644 index 000000000..f65e9fcf8 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/battery-caution.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/status/battery-low.svg b/lisp/ui/icons/numix/standard/status/battery-low.svg new file mode 100644 index 000000000..cd5fa1f4d --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/battery-low.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/status/dialog-error.svg b/lisp/ui/icons/numix/standard/status/dialog-error.svg new file mode 100644 index 000000000..6a7b255fb --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/dialog-error.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/status/dialog-information.svg b/lisp/ui/icons/numix/standard/status/dialog-information.svg new file mode 100644 index 000000000..716b8b43f --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/dialog-information.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/status/dialog-password.svg b/lisp/ui/icons/numix/standard/status/dialog-password.svg new file mode 100644 index 000000000..f44f031fa --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/dialog-password.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/status/dialog-question.svg b/lisp/ui/icons/numix/standard/status/dialog-question.svg new file mode 100644 index 000000000..2e42fbdf7 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/dialog-question.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/status/dialog-warning.svg b/lisp/ui/icons/numix/standard/status/dialog-warning.svg new file mode 100644 index 000000000..4c5ca6de8 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/dialog-warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/numix/standard/status/image-missing.svg b/lisp/ui/icons/numix/standard/status/image-missing.svg new file mode 100644 index 000000000..633b72bd3 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/image-missing.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/status/network-error.svg b/lisp/ui/icons/numix/standard/status/network-error.svg new file mode 100644 index 000000000..6ae9e1b15 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/network-error.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/status/network-idle.svg b/lisp/ui/icons/numix/standard/status/network-idle.svg new file mode 100644 index 000000000..7563e9509 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/network-idle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/status/network-offline.svg b/lisp/ui/icons/numix/standard/status/network-offline.svg new file mode 100644 index 000000000..d1ffe3080 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/network-offline.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/status/network-receive.svg b/lisp/ui/icons/numix/standard/status/network-receive.svg new file mode 100644 index 000000000..00d215f25 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/network-receive.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/status/network-transmit-receive.svg b/lisp/ui/icons/numix/standard/status/network-transmit-receive.svg new file mode 100644 index 000000000..7563e9509 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/network-transmit-receive.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/status/network-transmit.svg b/lisp/ui/icons/numix/standard/status/network-transmit.svg new file mode 100644 index 000000000..91c51fad7 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/network-transmit.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/status/printer-printing.svg b/lisp/ui/icons/numix/standard/status/printer-printing.svg new file mode 100644 index 000000000..b9f1eaad5 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/printer-printing.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/numix/standard/status/security-high.svg b/lisp/ui/icons/numix/standard/status/security-high.svg new file mode 100644 index 000000000..01ccbbc93 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/security-high.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/status/security-low.svg b/lisp/ui/icons/numix/standard/status/security-low.svg new file mode 100644 index 000000000..9f8387051 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/security-low.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/status/security-medium.svg b/lisp/ui/icons/numix/standard/status/security-medium.svg new file mode 100644 index 000000000..88b30f2a3 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/security-medium.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/numix/standard/status/task-due.svg b/lisp/ui/icons/numix/standard/status/task-due.svg new file mode 100644 index 000000000..1d07c49f6 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/task-due.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/numix/standard/status/task-past-due.svg b/lisp/ui/icons/numix/standard/status/task-past-due.svg new file mode 100644 index 000000000..30417ef59 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/task-past-due.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/numix/standard/status/user-available.svg b/lisp/ui/icons/numix/standard/status/user-available.svg new file mode 100644 index 000000000..7fd9950e1 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/user-available.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/status/user-away.svg b/lisp/ui/icons/numix/standard/status/user-away.svg new file mode 100644 index 000000000..c347abcd1 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/user-away.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/status/user-idle.svg b/lisp/ui/icons/numix/standard/status/user-idle.svg new file mode 100644 index 000000000..c347abcd1 --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/user-idle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/numix/standard/status/user-offline.svg b/lisp/ui/icons/numix/standard/status/user-offline.svg new file mode 100644 index 000000000..83b0c7d3d --- /dev/null +++ b/lisp/ui/icons/numix/standard/status/user-offline.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/themes/icons/symbolic/cue-interrupt.svg b/lisp/ui/icons/papirus-symbolic/custom/cue-interrupt.svg similarity index 100% rename from lisp/ui/themes/icons/symbolic/cue-interrupt.svg rename to lisp/ui/icons/papirus-symbolic/custom/cue-interrupt.svg diff --git a/lisp/ui/themes/icons/symbolic/cue-pause.svg b/lisp/ui/icons/papirus-symbolic/custom/cue-pause.svg similarity index 100% rename from lisp/ui/themes/icons/symbolic/cue-pause.svg rename to lisp/ui/icons/papirus-symbolic/custom/cue-pause.svg diff --git a/lisp/ui/themes/icons/symbolic/cue-start.svg b/lisp/ui/icons/papirus-symbolic/custom/cue-start.svg similarity index 100% rename from lisp/ui/themes/icons/symbolic/cue-start.svg rename to lisp/ui/icons/papirus-symbolic/custom/cue-start.svg diff --git a/lisp/ui/themes/icons/symbolic/cue-stop.svg b/lisp/ui/icons/papirus-symbolic/custom/cue-stop.svg similarity index 100% rename from lisp/ui/themes/icons/symbolic/cue-stop.svg rename to lisp/ui/icons/papirus-symbolic/custom/cue-stop.svg diff --git a/lisp/ui/themes/icons/symbolic/fadein-generic.svg b/lisp/ui/icons/papirus-symbolic/custom/fadein-generic.svg similarity index 100% rename from lisp/ui/themes/icons/symbolic/fadein-generic.svg rename to lisp/ui/icons/papirus-symbolic/custom/fadein-generic.svg diff --git a/lisp/ui/themes/icons/symbolic/fadeout-generic.svg b/lisp/ui/icons/papirus-symbolic/custom/fadeout-generic.svg similarity index 100% rename from lisp/ui/themes/icons/symbolic/fadeout-generic.svg rename to lisp/ui/icons/papirus-symbolic/custom/fadeout-generic.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/address-book-new.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/address-book-new.svg new file mode 100644 index 000000000..d19b16ca1 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/address-book-new.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/application-exit.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/application-exit.svg new file mode 100644 index 000000000..4ce3586bf --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/application-exit.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/appointment-new.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/appointment-new.svg new file mode 100644 index 000000000..edbd1b307 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/appointment-new.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/call-start.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/call-start.svg new file mode 100644 index 000000000..55707cb19 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/call-start.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/call-stop.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/call-stop.svg new file mode 100644 index 000000000..52ddc4164 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/call-stop.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/contact-new.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/contact-new.svg new file mode 100644 index 000000000..4dd0e976b --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/contact-new.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-new.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-new.svg new file mode 100644 index 000000000..d4785033d --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-new.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-open-recent.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-open-recent.svg new file mode 100644 index 000000000..417341b97 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-open-recent.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-open.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-open.svg new file mode 100644 index 000000000..6f60a7bc0 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-open.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-page-setup.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-page-setup.svg new file mode 100644 index 000000000..7fc782c8b --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-page-setup.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-print-preview.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-print-preview.svg new file mode 100644 index 000000000..de476eb2b --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-print-preview.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-print.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-print.svg new file mode 100644 index 000000000..a256eb8df --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-print.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-properties.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-properties.svg new file mode 100644 index 000000000..c420eede6 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-properties.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-revert.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-revert.svg new file mode 100644 index 000000000..4f4b56f2d --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-revert.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-save-as.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-save-as.svg new file mode 100644 index 000000000..f16966e68 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-save-as.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-save.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-save.svg new file mode 100644 index 000000000..8627f4628 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-save.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-send.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-send.svg new file mode 100644 index 000000000..e3263dd45 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-send.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-clear.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-clear.svg new file mode 100644 index 000000000..44c585f15 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-clear.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-copy.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-copy.svg new file mode 100644 index 000000000..496df2df9 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-copy.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-cut.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-cut.svg new file mode 100644 index 000000000..3517a8f2b --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-cut.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-delete.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-delete.svg new file mode 100644 index 000000000..0e40d184e --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-delete.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-find-replace.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-find-replace.svg new file mode 100644 index 000000000..b7c16b42a --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-find-replace.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-find.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-find.svg new file mode 100644 index 000000000..d780a6dc1 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-find.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-paste.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-paste.svg new file mode 100644 index 000000000..b7dd3cb9b --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-paste.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-redo.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-redo.svg new file mode 100644 index 000000000..e3d70fa7b --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-redo.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-select-all.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-select-all.svg new file mode 100644 index 000000000..dd8ea78c1 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-select-all.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-undo.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-undo.svg new file mode 100644 index 000000000..e663517d6 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-undo.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/folder-new.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/folder-new.svg new file mode 100644 index 000000000..bdcb6042a --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/folder-new.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-less.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-less.svg new file mode 100644 index 000000000..0e9ef6d32 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-less.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-more.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-more.svg new file mode 100644 index 000000000..e81aa4c06 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-more.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-center.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-center.svg new file mode 100644 index 000000000..1e8493990 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-center.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-fill.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-fill.svg new file mode 100644 index 000000000..2ec893f17 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-left.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-left.svg new file mode 100644 index 000000000..2d49c8471 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-right.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-right.svg new file mode 100644 index 000000000..44c362619 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-bold.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-bold.svg new file mode 100644 index 000000000..f1af01a41 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-bold.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-ltr.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-ltr.svg new file mode 100644 index 000000000..bc367f7c0 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-ltr.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-rtl.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-rtl.svg new file mode 100644 index 000000000..79a23e2d4 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-rtl.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-italic.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-italic.svg new file mode 100644 index 000000000..37d7108de --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-italic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-strikethrough.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-strikethrough.svg new file mode 100644 index 000000000..ab9a9dc50 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-strikethrough.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-underline.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-underline.svg new file mode 100644 index 000000000..effa1f6a3 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-underline.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-bottom.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-bottom.svg new file mode 100644 index 000000000..4d91b1917 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-bottom.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-down.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-down.svg new file mode 100644 index 000000000..00b39aef7 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-first.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-first.svg new file mode 100644 index 000000000..4c45db77a --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-first.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-home.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-home.svg new file mode 100644 index 000000000..7bc5c2d26 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-home.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-jump.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-jump.svg new file mode 100644 index 000000000..24d8cf5f7 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-jump.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-last.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-last.svg new file mode 100644 index 000000000..7e7038d47 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-last.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/themes/icons/symbolic/go-next.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-next.svg similarity index 100% rename from lisp/ui/themes/icons/symbolic/go-next.svg rename to lisp/ui/icons/papirus-symbolic/standard/actions/go-next.svg diff --git a/lisp/ui/themes/icons/symbolic/go-previous.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-previous.svg similarity index 100% rename from lisp/ui/themes/icons/symbolic/go-previous.svg rename to lisp/ui/icons/papirus-symbolic/standard/actions/go-previous.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-top.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-top.svg new file mode 100644 index 000000000..39ce644e3 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-top.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-up.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-up.svg new file mode 100644 index 000000000..613c192f2 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/themes/icons/symbolic/help-info.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/help-about.svg similarity index 100% rename from lisp/ui/themes/icons/symbolic/help-info.svg rename to lisp/ui/icons/papirus-symbolic/standard/actions/help-about.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/insert-image.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/insert-image.svg new file mode 100644 index 000000000..e6b7d5fde --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/insert-image.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/insert-link.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/insert-link.svg new file mode 100644 index 000000000..bd7f26bae --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/insert-link.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/insert-object.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/insert-object.svg new file mode 100644 index 000000000..88632c07e --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/insert-object.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/insert-text.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/insert-text.svg new file mode 100644 index 000000000..b095d9d07 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/insert-text.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/list-add.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/list-add.svg new file mode 100644 index 000000000..85d31614e --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/list-add.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/list-remove.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/list-remove.svg new file mode 100644 index 000000000..4cf55b397 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/list-remove.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-forward.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-forward.svg new file mode 100644 index 000000000..fe0023ae6 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-forward.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-important.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-important.svg new file mode 100644 index 000000000..6272d3ea8 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-important.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-junk.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-junk.svg new file mode 100644 index 000000000..b414cc04b --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-junk.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-notjunk.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-notjunk.svg new file mode 100644 index 000000000..dfdbce898 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-notjunk.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-read.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-read.svg new file mode 100644 index 000000000..2d109a8c7 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-read.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-unread.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-unread.svg new file mode 100644 index 000000000..14ad61641 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-unread.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-message-new.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-message-new.svg new file mode 100644 index 000000000..14ad61641 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-message-new.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-all.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-all.svg new file mode 100644 index 000000000..e9fbb6720 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-all.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-sender.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-sender.svg new file mode 100644 index 000000000..ff8f1f70b --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-sender.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-send-receive.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-send-receive.svg new file mode 100644 index 000000000..c29244e02 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-send-receive.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-send.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-send.svg new file mode 100644 index 000000000..e3263dd45 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-send.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-eject.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-eject.svg new file mode 100644 index 000000000..4cd574e67 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-eject.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-pause.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-pause.svg new file mode 100644 index 000000000..a4b3ae0b5 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-pause.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-start.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-start.svg new file mode 100644 index 000000000..891f6ad60 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-start.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-stop.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-stop.svg new file mode 100644 index 000000000..4c899eb29 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-stop.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-record.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-record.svg new file mode 100644 index 000000000..2031186ac --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-record.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-backward.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-backward.svg new file mode 100644 index 000000000..2b629317e --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-backward.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-forward.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-forward.svg new file mode 100644 index 000000000..be9a09a9f --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-forward.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-backward.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-backward.svg new file mode 100644 index 000000000..64d17f65f --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-backward.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-forward.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-forward.svg new file mode 100644 index 000000000..82b864de9 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-forward.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-horizontal.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-horizontal.svg new file mode 100644 index 000000000..f78bb04e0 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-horizontal.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-vertical.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-vertical.svg new file mode 100644 index 000000000..94491b380 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-vertical.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-left.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-left.svg new file mode 100644 index 000000000..1a2ddcfa0 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-right.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-right.svg new file mode 100644 index 000000000..2cca1c8ef --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/process-stop.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/process-stop.svg new file mode 100644 index 000000000..223335ada --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/process-stop.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/system-lock-screen.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/system-lock-screen.svg new file mode 100644 index 000000000..d9cbaae8d --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/system-lock-screen.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/system-log-out.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/system-log-out.svg new file mode 100644 index 000000000..a358882ab --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/system-log-out.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/system-run.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/system-run.svg new file mode 100644 index 000000000..2dbe12a05 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/system-run.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/system-search.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/system-search.svg new file mode 100644 index 000000000..d780a6dc1 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/system-search.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/system-shutdown.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/system-shutdown.svg new file mode 100644 index 000000000..32c5192bb --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/system-shutdown.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/tools-check-spelling.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/tools-check-spelling.svg new file mode 100644 index 000000000..6f832a494 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/tools-check-spelling.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/view-fullscreen.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/view-fullscreen.svg new file mode 100644 index 000000000..06e69570f --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/view-fullscreen.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/view-refresh.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/view-refresh.svg new file mode 100644 index 000000000..d3620ab1d --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/view-refresh.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/view-restore.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/view-restore.svg new file mode 100644 index 000000000..866e56060 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/view-restore.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-ascending.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-ascending.svg new file mode 100644 index 000000000..4cfaca60b --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-ascending.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-descending.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-descending.svg new file mode 100644 index 000000000..d9fdb74e1 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-descending.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/window-close.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/window-close.svg new file mode 100644 index 000000000..bbc2fa293 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/window-close.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-fit-best.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-fit-best.svg new file mode 100644 index 000000000..0f32dbcf5 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-fit-best.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-in.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-in.svg new file mode 100644 index 000000000..3ce5a14e7 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-in.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-original.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-original.svg new file mode 100644 index 000000000..0b3d80fd4 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-original.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-out.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-out.svg new file mode 100644 index 000000000..268200ef8 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-out.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-engineering.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-engineering.svg new file mode 100644 index 000000000..3bfec67ef --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-engineering.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-games.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-games.svg new file mode 100644 index 000000000..0dd64a087 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-games.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-graphics.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-graphics.svg new file mode 100644 index 000000000..a8acef64d --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-graphics.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-multimedia.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-multimedia.svg new file mode 100644 index 000000000..789042132 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-multimedia.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-science.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-science.svg new file mode 100644 index 000000000..db38041e3 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-science.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-system.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-system.svg new file mode 100644 index 000000000..c48f4104e --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-system.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-utilities.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-utilities.svg new file mode 100644 index 000000000..0627a581c --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-utilities.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/preferences-other.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/preferences-other.svg new file mode 100644 index 000000000..ed25d073e --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/preferences-other.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/preferences-system.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/preferences-system.svg new file mode 100644 index 000000000..c48f4104e --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/preferences-system.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/system-help.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/system-help.svg new file mode 100644 index 000000000..79a2689fd --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/system-help.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/audio-card.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/audio-card.svg new file mode 100644 index 000000000..9d6daaf9c --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/audio-card.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/audio-input-microphone.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/audio-input-microphone.svg new file mode 100644 index 000000000..8892b5882 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/audio-input-microphone.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/battery.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/battery.svg new file mode 100644 index 000000000..6891d88d5 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/battery.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/camera-photo.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/camera-photo.svg new file mode 100644 index 000000000..70fd8bccb --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/camera-photo.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/camera-video.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/camera-video.svg new file mode 100644 index 000000000..0fdd47696 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/camera-video.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/camera-web.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/camera-web.svg new file mode 100644 index 000000000..8690521f2 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/camera-web.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/computer.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/computer.svg new file mode 100644 index 000000000..8b919c306 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/computer.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/drive-harddisk.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/drive-harddisk.svg new file mode 100644 index 000000000..b37309dfe --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/drive-harddisk.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/drive-optical.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/drive-optical.svg new file mode 100644 index 000000000..75037c614 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/drive-optical.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/drive-removable-media.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/drive-removable-media.svg new file mode 100644 index 000000000..53bcecf7c --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/drive-removable-media.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/input-gaming.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/input-gaming.svg new file mode 100644 index 000000000..cc6e412e3 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/input-gaming.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/input-keyboard.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/input-keyboard.svg new file mode 100644 index 000000000..3f17833bc --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/input-keyboard.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/input-mouse.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/input-mouse.svg new file mode 100644 index 000000000..2b9453fc0 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/input-mouse.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/input-tablet.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/input-tablet.svg new file mode 100644 index 000000000..756578a77 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/input-tablet.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/media-flash.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/media-flash.svg new file mode 100644 index 000000000..37ea323d0 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/media-flash.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/media-floppy.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/media-floppy.svg new file mode 100644 index 000000000..2b4c35fd9 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/media-floppy.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/media-tape.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/media-tape.svg new file mode 100644 index 000000000..997bac855 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/media-tape.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/multimedia-player.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/multimedia-player.svg new file mode 100644 index 000000000..c3f855c39 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/multimedia-player.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/network-wired.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/network-wired.svg new file mode 100644 index 000000000..8998dc3da --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/network-wired.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/network-wireless.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/network-wireless.svg new file mode 100644 index 000000000..2d50d0adf --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/network-wireless.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/phone.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/phone.svg new file mode 100644 index 000000000..ff1bc0689 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/phone.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/printer.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/printer.svg new file mode 100644 index 000000000..f43605b76 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/printer.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/scanner.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/scanner.svg new file mode 100644 index 000000000..040d3f6e1 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/scanner.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/video-display.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/video-display.svg new file mode 100644 index 000000000..3bbd5767e --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/video-display.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-default.svg b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-default.svg new file mode 100644 index 000000000..2e7628ce0 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-default.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-documents.svg b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-documents.svg new file mode 100644 index 000000000..2cda9bbe2 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-documents.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-favorite.svg b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-favorite.svg new file mode 100644 index 000000000..17cd59f94 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-favorite.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-important.svg b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-important.svg new file mode 100644 index 000000000..b68895098 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-important.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-photos.svg b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-photos.svg new file mode 100644 index 000000000..a1a3343b2 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-photos.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-shared.svg b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-shared.svg new file mode 100644 index 000000000..a9dad4e6d --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-shared.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-system.svg b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-system.svg new file mode 100644 index 000000000..c48f4104e --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-system.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/folder-remote.svg b/lisp/ui/icons/papirus-symbolic/standard/places/folder-remote.svg new file mode 100644 index 000000000..8adfb04eb --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/places/folder-remote.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/folder.svg b/lisp/ui/icons/papirus-symbolic/standard/places/folder.svg new file mode 100644 index 000000000..aa1d06f31 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/places/folder.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/network-workgroup.svg b/lisp/ui/icons/papirus-symbolic/standard/places/network-workgroup.svg new file mode 100644 index 000000000..06b1c5ac2 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/places/network-workgroup.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/start-here.svg b/lisp/ui/icons/papirus-symbolic/standard/places/start-here.svg new file mode 100644 index 000000000..d10d36ed0 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/places/start-here.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/user-bookmarks.svg b/lisp/ui/icons/papirus-symbolic/standard/places/user-bookmarks.svg new file mode 100644 index 000000000..26ebd2b34 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/places/user-bookmarks.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/user-desktop.svg b/lisp/ui/icons/papirus-symbolic/standard/places/user-desktop.svg new file mode 100644 index 000000000..86fba3cc5 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/places/user-desktop.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/user-home.svg b/lisp/ui/icons/papirus-symbolic/standard/places/user-home.svg new file mode 100644 index 000000000..7bc5c2d26 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/places/user-home.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/user-trash.svg b/lisp/ui/icons/papirus-symbolic/standard/places/user-trash.svg new file mode 100644 index 000000000..15c0b403c --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/places/user-trash.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/appointment-missed.svg b/lisp/ui/icons/papirus-symbolic/standard/status/appointment-missed.svg new file mode 100644 index 000000000..4ead036a8 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/appointment-missed.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/appointment-soon.svg b/lisp/ui/icons/papirus-symbolic/standard/status/appointment-soon.svg new file mode 100644 index 000000000..417341b97 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/appointment-soon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-high.svg b/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-high.svg new file mode 100644 index 000000000..25a7d7a1b --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-high.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-low.svg b/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-low.svg new file mode 100644 index 000000000..84de0effb --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-low.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-medium.svg b/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-medium.svg new file mode 100644 index 000000000..9adb0a84e --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-medium.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-muted.svg b/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-muted.svg new file mode 100644 index 000000000..9a97be2a3 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-muted.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/battery-caution.svg b/lisp/ui/icons/papirus-symbolic/standard/status/battery-caution.svg new file mode 100644 index 000000000..28ade726e --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/battery-caution.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/battery-low.svg b/lisp/ui/icons/papirus-symbolic/standard/status/battery-low.svg new file mode 100644 index 000000000..f3b8399a1 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/battery-low.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/dialog-error.svg b/lisp/ui/icons/papirus-symbolic/standard/status/dialog-error.svg new file mode 100644 index 000000000..c88df16af --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/dialog-error.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/dialog-information.svg b/lisp/ui/icons/papirus-symbolic/standard/status/dialog-information.svg new file mode 100644 index 000000000..b697d90bc --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/dialog-information.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/dialog-password.svg b/lisp/ui/icons/papirus-symbolic/standard/status/dialog-password.svg new file mode 100644 index 000000000..e22f4804e --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/dialog-password.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/dialog-question.svg b/lisp/ui/icons/papirus-symbolic/standard/status/dialog-question.svg new file mode 100644 index 000000000..2321a393b --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/dialog-question.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/dialog-warning.svg b/lisp/ui/icons/papirus-symbolic/standard/status/dialog-warning.svg new file mode 100644 index 000000000..cad9346aa --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/dialog-warning.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/folder-drag-accept.svg b/lisp/ui/icons/papirus-symbolic/standard/status/folder-drag-accept.svg new file mode 100644 index 000000000..94cacbf43 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/folder-drag-accept.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/folder-open.svg b/lisp/ui/icons/papirus-symbolic/standard/status/folder-open.svg new file mode 100644 index 000000000..40ececd58 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/folder-open.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/folder-visiting.svg b/lisp/ui/icons/papirus-symbolic/standard/status/folder-visiting.svg new file mode 100644 index 000000000..e728a1187 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/folder-visiting.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/image-loading.svg b/lisp/ui/icons/papirus-symbolic/standard/status/image-loading.svg new file mode 100644 index 000000000..9dab356b4 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/image-loading.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/mail-attachment.svg b/lisp/ui/icons/papirus-symbolic/standard/status/mail-attachment.svg new file mode 100644 index 000000000..184f5c6b3 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/mail-attachment.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/mail-read.svg b/lisp/ui/icons/papirus-symbolic/standard/status/mail-read.svg new file mode 100644 index 000000000..2d109a8c7 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/mail-read.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/mail-replied.svg b/lisp/ui/icons/papirus-symbolic/standard/status/mail-replied.svg new file mode 100644 index 000000000..ff8f1f70b --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/mail-replied.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/mail-unread.svg b/lisp/ui/icons/papirus-symbolic/standard/status/mail-unread.svg new file mode 100644 index 000000000..14ad61641 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/mail-unread.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-repeat.svg b/lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-repeat.svg new file mode 100644 index 000000000..01de78092 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-repeat.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-shuffle.svg b/lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-shuffle.svg new file mode 100644 index 000000000..46a5d394a --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-shuffle.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-error.svg b/lisp/ui/icons/papirus-symbolic/standard/status/network-error.svg new file mode 100644 index 000000000..25e3c8361 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/network-error.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-idle.svg b/lisp/ui/icons/papirus-symbolic/standard/status/network-idle.svg new file mode 100644 index 000000000..80f72f284 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/network-idle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-offline.svg b/lisp/ui/icons/papirus-symbolic/standard/status/network-offline.svg new file mode 100644 index 000000000..ee0cc9ac5 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/network-offline.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-receive.svg b/lisp/ui/icons/papirus-symbolic/standard/status/network-receive.svg new file mode 100644 index 000000000..a28bfae65 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/network-receive.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-transmit-receive.svg b/lisp/ui/icons/papirus-symbolic/standard/status/network-transmit-receive.svg new file mode 100644 index 000000000..ad89976eb --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/network-transmit-receive.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-transmit.svg b/lisp/ui/icons/papirus-symbolic/standard/status/network-transmit.svg new file mode 100644 index 000000000..200352e7b --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/network-transmit.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/printer-error.svg b/lisp/ui/icons/papirus-symbolic/standard/status/printer-error.svg new file mode 100644 index 000000000..2cb4e980c --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/printer-error.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/printer-printing.svg b/lisp/ui/icons/papirus-symbolic/standard/status/printer-printing.svg new file mode 100644 index 000000000..3291de3a4 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/printer-printing.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/security-high.svg b/lisp/ui/icons/papirus-symbolic/standard/status/security-high.svg new file mode 100644 index 000000000..5907e65c2 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/security-high.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/security-low.svg b/lisp/ui/icons/papirus-symbolic/standard/status/security-low.svg new file mode 100644 index 000000000..a75d544db --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/security-low.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/security-medium.svg b/lisp/ui/icons/papirus-symbolic/standard/status/security-medium.svg new file mode 100644 index 000000000..aa0ed1cee --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/security-medium.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/software-update-available.svg b/lisp/ui/icons/papirus-symbolic/standard/status/software-update-available.svg new file mode 100644 index 000000000..614160806 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/software-update-available.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/software-update-urgent.svg b/lisp/ui/icons/papirus-symbolic/standard/status/software-update-urgent.svg new file mode 100644 index 000000000..71526c1dc --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/software-update-urgent.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/task-due.svg b/lisp/ui/icons/papirus-symbolic/standard/status/task-due.svg new file mode 100644 index 000000000..76e218988 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/task-due.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/task-past-due.svg b/lisp/ui/icons/papirus-symbolic/standard/status/task-past-due.svg new file mode 100644 index 000000000..6b670fbbe --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/task-past-due.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/user-available.svg b/lisp/ui/icons/papirus-symbolic/standard/status/user-available.svg new file mode 100644 index 000000000..5b14b34cc --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/user-available.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/user-away.svg b/lisp/ui/icons/papirus-symbolic/standard/status/user-away.svg new file mode 100644 index 000000000..e68d63b6d --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/user-away.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/user-idle.svg b/lisp/ui/icons/papirus-symbolic/standard/status/user-idle.svg new file mode 100644 index 000000000..d5ecb7054 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/user-idle.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/user-offline.svg b/lisp/ui/icons/papirus-symbolic/standard/status/user-offline.svg new file mode 100644 index 000000000..d5ecb7054 --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/user-offline.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/user-trash-full.svg b/lisp/ui/icons/papirus-symbolic/standard/status/user-trash-full.svg new file mode 100644 index 000000000..b194dd77d --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/standard/status/user-trash-full.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/logging/dialog.py b/lisp/ui/logging/dialog.py index 9caa67263..b5c0d90c6 100644 --- a/lisp/ui/logging/dialog.py +++ b/lisp/ui/logging/dialog.py @@ -24,7 +24,7 @@ QSizePolicy, QPushButton, QTextEdit from lisp.ui.logging.common import LOG_LEVELS, LOG_ICONS_NAMES -from lisp.ui.themes import IconTheme +from lisp.ui.icons import IconTheme from lisp.ui.ui_utils import translate diff --git a/lisp/ui/logging/status.py b/lisp/ui/logging/status.py index d6fc2691f..2b454999e 100644 --- a/lisp/ui/logging/status.py +++ b/lisp/ui/logging/status.py @@ -22,7 +22,7 @@ from PyQt5.QtCore import pyqtSignal from PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout, QFrame -from lisp.ui.themes import IconTheme +from lisp.ui.icons import IconTheme class LogStatusView(QWidget): @@ -72,10 +72,12 @@ def mouseDoubleClickEvent(self, e): self.double_clicked.emit() def _new_rows(self, parent, start, end): - last_record = self._log_model.record(self._log_model.rowCount() - 1) + # Take the last record in the model + record = self._log_model.record(self._log_model.rowCount() - 1) - if last_record.levelno >= logging.INFO: - self.messageLabel.setText(last_record.message) + if record.levelno >= logging.INFO: + # Display only the fist line of text + self.messageLabel.setText(record.message.split('\n')[0]) for n in range(start, end + 1): level = self._log_model.record(n).levelno diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 1c2e987ff..2143c3634 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -148,12 +148,13 @@ def __init__(self, conf, title='Linux Show Player', **kwargs): # Logging model self.logModel = log_model_factory(self.conf) + # Handler to populate the model self.logHandler = LogModelHandler(self.logModel) logging.getLogger().addHandler(self.logHandler) # Logging - self.logViewer = LogViewer(self.logModel, self.conf) + self.logViewer = LogViewer(self.logModel, self.conf, parent=self) # Logging status widget self.logStatus = LogStatusView(self.logModel) diff --git a/lisp/ui/settings/pages/app_general.py b/lisp/ui/settings/pages/app_general.py index 030a4ea50..412ea2688 100644 --- a/lisp/ui/settings/pages/app_general.py +++ b/lisp/ui/settings/pages/app_general.py @@ -22,8 +22,9 @@ QGridLayout, QLabel from lisp import layouts +from lisp.ui.icons import icon_themes_names from lisp.ui.settings.settings_page import SettingsPage -from lisp.ui.themes import THEMES, ICON_THEMES +from lisp.ui.themes import themes_names from lisp.ui.ui_utils import translate @@ -67,7 +68,7 @@ def __init__(self, **kwargs): self.themeGroup.layout().addWidget(self.themeLabel, 0, 0) self.themeCombo = QComboBox(self.themeGroup) - self.themeCombo.addItems(THEMES.keys()) + self.themeCombo.addItems(themes_names()) self.themeGroup.layout().addWidget(self.themeCombo, 0, 1) self.iconsLabel = QLabel(self.themeGroup) @@ -76,7 +77,7 @@ def __init__(self, **kwargs): self.themeGroup.layout().addWidget(self.iconsLabel, 1, 0) self.iconsCombo = QComboBox(self.themeGroup) - self.iconsCombo.addItems(ICON_THEMES.keys()) + self.iconsCombo.addItems(icon_themes_names()) self.themeGroup.layout().addWidget(self.iconsCombo, 1, 1) def get_settings(self): diff --git a/lisp/ui/settings/pages/plugins_settings.py b/lisp/ui/settings/pages/plugins_settings.py index 9bbb9faff..4b824ea7b 100644 --- a/lisp/ui/settings/pages/plugins_settings.py +++ b/lisp/ui/settings/pages/plugins_settings.py @@ -23,7 +23,7 @@ from lisp import plugins from lisp.ui.settings.settings_page import SettingsPage -from lisp.ui.themes.theme import IconTheme +from lisp.ui.icons import IconTheme # TODO: just a proof-of concept diff --git a/lisp/ui/themes/__init__.py b/lisp/ui/themes/__init__.py index d455bd9ca..1bd7774ed 100644 --- a/lisp/ui/themes/__init__.py +++ b/lisp/ui/themes/__init__.py @@ -1,11 +1,24 @@ -from lisp.ui.themes.dark.theme import DarkTheme -from lisp.ui.themes.theme import IconTheme +from os import path -THEMES = { - DarkTheme.Name: DarkTheme(), -} +from lisp.core.loading import load_classes +from lisp.ui.themes.dark.dark import Dark -ICON_THEMES = { - 'Symbolic': IconTheme('symbolic', 'lisp'), - 'Numix': IconTheme('numix', 'lisp') -} +_THEMES = {} + + +def load_themes(): + if not _THEMES: + for name, theme in load_classes(__package__, + path.dirname(__file__), + exclude='theme'): + _THEMES[name] = theme() + + +def themes_names(): + load_themes() + return list(_THEMES.keys()) + + +def get_theme(theme_name): + load_themes() + return _THEMES[theme_name] diff --git a/lisp/ui/themes/dark/__init__.py b/lisp/ui/themes/dark/__init__.py index e69de29bb..6d84ea625 100644 --- a/lisp/ui/themes/dark/__init__.py +++ b/lisp/ui/themes/dark/__init__.py @@ -0,0 +1 @@ +from .dark import Dark \ No newline at end of file diff --git a/lisp/ui/themes/dark/theme.py b/lisp/ui/themes/dark/dark.py similarity index 92% rename from lisp/ui/themes/dark/theme.py rename to lisp/ui/themes/dark/dark.py index 59aea8419..ae6efa718 100644 --- a/lisp/ui/themes/dark/theme.py +++ b/lisp/ui/themes/dark/dark.py @@ -27,12 +27,11 @@ from . import assetes -class DarkTheme(Theme): +class Dark(Theme): QssPath = os.path.join(os.path.dirname(__file__), 'theme.qss') - Name = 'Dark' def apply(self, qt_app): - with open(DarkTheme.QssPath, mode='r', encoding='utf-8') as f: + with open(Dark.QssPath, mode='r', encoding='utf-8') as f: qt_app.setStyleSheet(f.read()) # Change link color diff --git a/lisp/ui/themes/icons/numix/audio-volume-high.svg b/lisp/ui/themes/icons/numix/audio-volume-high.svg deleted file mode 100644 index 1f5044916..000000000 --- a/lisp/ui/themes/icons/numix/audio-volume-high.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/audio-volume-low.svg b/lisp/ui/themes/icons/numix/audio-volume-low.svg deleted file mode 100644 index 648b4cf36..000000000 --- a/lisp/ui/themes/icons/numix/audio-volume-low.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/audio-volume-medium.svg b/lisp/ui/themes/icons/numix/audio-volume-medium.svg deleted file mode 100644 index 699d468d5..000000000 --- a/lisp/ui/themes/icons/numix/audio-volume-medium.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/audio-volume-muted.svg b/lisp/ui/themes/icons/numix/audio-volume-muted.svg deleted file mode 100644 index 895e94b6e..000000000 --- a/lisp/ui/themes/icons/numix/audio-volume-muted.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/dialog-error.svg b/lisp/ui/themes/icons/numix/dialog-error.svg deleted file mode 100644 index fb595800a..000000000 --- a/lisp/ui/themes/icons/numix/dialog-error.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/themes/icons/numix/dialog-information.svg b/lisp/ui/themes/icons/numix/dialog-information.svg deleted file mode 100644 index dd8cc7360..000000000 --- a/lisp/ui/themes/icons/numix/dialog-information.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/themes/icons/numix/dialog-question.svg b/lisp/ui/themes/icons/numix/dialog-question.svg deleted file mode 100644 index 11524af1b..000000000 --- a/lisp/ui/themes/icons/numix/dialog-question.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/themes/icons/numix/dialog-warning.svg b/lisp/ui/themes/icons/numix/dialog-warning.svg deleted file mode 100644 index d55713da1..000000000 --- a/lisp/ui/themes/icons/numix/dialog-warning.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/media-eject.svg b/lisp/ui/themes/icons/numix/media-eject.svg deleted file mode 100644 index c146bdb8a..000000000 --- a/lisp/ui/themes/icons/numix/media-eject.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/media-record.svg b/lisp/ui/themes/icons/numix/media-record.svg deleted file mode 100644 index a47192241..000000000 --- a/lisp/ui/themes/icons/numix/media-record.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/media-seek-backward.svg b/lisp/ui/themes/icons/numix/media-seek-backward.svg deleted file mode 100644 index 1909982fb..000000000 --- a/lisp/ui/themes/icons/numix/media-seek-backward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/media-seek-forward.svg b/lisp/ui/themes/icons/numix/media-seek-forward.svg deleted file mode 100644 index d8698f7b9..000000000 --- a/lisp/ui/themes/icons/numix/media-seek-forward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/media-skip-backward.svg b/lisp/ui/themes/icons/numix/media-skip-backward.svg deleted file mode 100644 index 433298616..000000000 --- a/lisp/ui/themes/icons/numix/media-skip-backward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/numix/media-skip-forward.svg b/lisp/ui/themes/icons/numix/media-skip-forward.svg deleted file mode 100644 index d138185b0..000000000 --- a/lisp/ui/themes/icons/numix/media-skip-forward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/audio-volume-high.svg b/lisp/ui/themes/icons/symbolic/audio-volume-high.svg deleted file mode 100644 index c63c39ea5..000000000 --- a/lisp/ui/themes/icons/symbolic/audio-volume-high.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/audio-volume-low.svg b/lisp/ui/themes/icons/symbolic/audio-volume-low.svg deleted file mode 100644 index 612bfbb1a..000000000 --- a/lisp/ui/themes/icons/symbolic/audio-volume-low.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/audio-volume-medium.svg b/lisp/ui/themes/icons/symbolic/audio-volume-medium.svg deleted file mode 100644 index 801b0692d..000000000 --- a/lisp/ui/themes/icons/symbolic/audio-volume-medium.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/audio-volume-muted.svg b/lisp/ui/themes/icons/symbolic/audio-volume-muted.svg deleted file mode 100644 index 124d42d6c..000000000 --- a/lisp/ui/themes/icons/symbolic/audio-volume-muted.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/auto-follow.svg b/lisp/ui/themes/icons/symbolic/auto-follow.svg deleted file mode 100644 index feb54a422..000000000 --- a/lisp/ui/themes/icons/symbolic/auto-follow.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/auto-next.svg b/lisp/ui/themes/icons/symbolic/auto-next.svg deleted file mode 100644 index a1de92a56..000000000 --- a/lisp/ui/themes/icons/symbolic/auto-next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/dialog-error.svg b/lisp/ui/themes/icons/symbolic/dialog-error.svg deleted file mode 100644 index cbc6e287d..000000000 --- a/lisp/ui/themes/icons/symbolic/dialog-error.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/lisp/ui/themes/icons/symbolic/dialog-information.svg b/lisp/ui/themes/icons/symbolic/dialog-information.svg deleted file mode 100644 index 23ab0eaf3..000000000 --- a/lisp/ui/themes/icons/symbolic/dialog-information.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/lisp/ui/themes/icons/symbolic/dialog-question.svg b/lisp/ui/themes/icons/symbolic/dialog-question.svg deleted file mode 100644 index 749085d7a..000000000 --- a/lisp/ui/themes/icons/symbolic/dialog-question.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/themes/icons/symbolic/dialog-warning.svg b/lisp/ui/themes/icons/symbolic/dialog-warning.svg deleted file mode 100644 index eaade11ce..000000000 --- a/lisp/ui/themes/icons/symbolic/dialog-warning.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/themes/icons/symbolic/media-eject.svg b/lisp/ui/themes/icons/symbolic/media-eject.svg deleted file mode 100644 index 5e1a1359e..000000000 --- a/lisp/ui/themes/icons/symbolic/media-eject.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/media-record.svg b/lisp/ui/themes/icons/symbolic/media-record.svg deleted file mode 100644 index 9da42dcfa..000000000 --- a/lisp/ui/themes/icons/symbolic/media-record.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/media-seek-backward.svg b/lisp/ui/themes/icons/symbolic/media-seek-backward.svg deleted file mode 100644 index dceba05b9..000000000 --- a/lisp/ui/themes/icons/symbolic/media-seek-backward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/media-seek-forward.svg b/lisp/ui/themes/icons/symbolic/media-seek-forward.svg deleted file mode 100644 index 630fa1653..000000000 --- a/lisp/ui/themes/icons/symbolic/media-seek-forward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/media-skip-backward.svg b/lisp/ui/themes/icons/symbolic/media-skip-backward.svg deleted file mode 100644 index 9326f7204..000000000 --- a/lisp/ui/themes/icons/symbolic/media-skip-backward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/icons/symbolic/media-skip-forward.svg b/lisp/ui/themes/icons/symbolic/media-skip-forward.svg deleted file mode 100644 index 7c6210d3b..000000000 --- a/lisp/ui/themes/icons/symbolic/media-skip-forward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/themes/theme.py b/lisp/ui/themes/theme.py index dbcefc5e4..0c4120741 100644 --- a/lisp/ui/themes/theme.py +++ b/lisp/ui/themes/theme.py @@ -17,60 +17,9 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import glob -import os - -from PyQt5.QtGui import QIcon - -from lisp import ICON_THEMES_DIR - class Theme: - Name = 'Theme' - def apply(self, qt_app): """ :type qt_app: PyQt5.QtWidgets.QApplication """ - - -class IconTheme: - BLANK = QIcon() - - _GlobalCache = {} - _GlobalTheme = None - - def __init__(self, *dirs): - self._lookup_dirs = [ - os.path.join(ICON_THEMES_DIR, d) for d in dirs - ] - - def __iter__(self): - yield from self._lookup_dirs - - @staticmethod - def get(icon_name): - icon = IconTheme._GlobalCache.get(icon_name, None) - - if icon is None: - icon = IconTheme.BLANK - for dir_ in IconTheme.theme(): - for icon in glob.iglob(os.path.join(dir_, icon_name) + '.*'): - icon = QIcon(icon) - - IconTheme._GlobalCache[icon_name] = icon - - return icon - - @staticmethod - def theme(): - return IconTheme._GlobalTheme - - @staticmethod - def set_theme(theme): - IconTheme.clean_cache() - IconTheme._GlobalTheme = theme - - @staticmethod - def clean_cache(): - IconTheme._GlobalCache.clear() diff --git a/lisp/ui/widgets/fades.py b/lisp/ui/widgets/fades.py index 561a57e74..e3933be27 100644 --- a/lisp/ui/widgets/fades.py +++ b/lisp/ui/widgets/fades.py @@ -23,7 +23,7 @@ from PyQt5.QtWidgets import QComboBox, QStyledItemDelegate, QWidget, \ QGridLayout, QDoubleSpinBox, QLabel -from lisp.ui.themes.theme import IconTheme +from lisp.ui.icons import IconTheme from lisp.ui.ui_utils import translate QT_TRANSLATE_NOOP('Fade', 'Linear') diff --git a/lisp/ui/widgets/qmutebutton.py b/lisp/ui/widgets/qmutebutton.py index c19c727ac..4194c4d48 100644 --- a/lisp/ui/widgets/qmutebutton.py +++ b/lisp/ui/widgets/qmutebutton.py @@ -19,7 +19,7 @@ from PyQt5.QtWidgets import QPushButton -from lisp.ui.themes.theme import IconTheme +from lisp.ui.icons import IconTheme class QMuteButton(QPushButton): From d011afe23668b391fb20a0e8581bb122371ea382 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 8 Mar 2018 15:25:36 +0100 Subject: [PATCH 093/333] SVG icons optimized using svg-optimizer --- .../standard/actions/address-book-new.svg | 8 +- .../standard/actions/application-exit.svg | 12 +- .../standard/actions/appointment-new.svg | 9 +- .../numix/standard/actions/call-start.svg | 6 +- .../numix/standard/actions/call-stop.svg | 7 +- .../numix/standard/actions/contact-new.svg | 5 +- .../standard/actions/document-open-recent.svg | 16 +- .../numix/standard/actions/document-open.svg | 12 +- .../standard/actions/document-page-setup.svg | 10 +- .../actions/document-print-preview.svg | 15 +- .../numix/standard/actions/document-print.svg | 10 +- .../standard/actions/document-properties.svg | 8 +- .../standard/actions/document-revert.svg | 5 +- .../standard/actions/document-save-as.svg | 10 +- .../numix/standard/actions/document-save.svg | 10 +- .../numix/standard/actions/document-send.svg | 12 +- .../icons/numix/standard/actions/edit-cut.svg | 9 +- .../standard/actions/edit-find-replace.svg | 15 +- .../numix/standard/actions/edit-find.svg | 8 +- .../numix/standard/actions/edit-paste.svg | 10 +- .../numix/standard/actions/edit-redo.svg | 4 +- .../numix/standard/actions/edit-undo.svg | 4 +- .../numix/standard/actions/folder-new.svg | 12 +- .../standard/actions/format-text-bold.svg | 4 +- .../actions/format-text-direction-ltr.svg | 8 +- .../actions/format-text-direction-rtl.svg | 8 +- .../standard/actions/format-text-italic.svg | 6 +- .../actions/format-text-strikethrough.svg | 7 +- .../actions/format-text-underline.svg | 7 +- .../numix/standard/actions/go-bottom.svg | 8 +- .../icons/numix/standard/actions/go-down.svg | 7 +- .../icons/numix/standard/actions/go-first.svg | 13 +- .../icons/numix/standard/actions/go-home.svg | 7 +- .../icons/numix/standard/actions/go-jump.svg | 9 +- .../icons/numix/standard/actions/go-last.svg | 12 +- .../icons/numix/standard/actions/go-next.svg | 5 +- .../numix/standard/actions/go-previous.svg | 7 +- .../icons/numix/standard/actions/go-top.svg | 12 +- .../ui/icons/numix/standard/actions/go-up.svg | 7 +- .../numix/standard/actions/help-about.svg | 10 +- .../numix/standard/actions/help-contents.svg | 13 +- .../icons/numix/standard/actions/help-faq.svg | 10 +- .../numix/standard/actions/insert-image.svg | 6 +- .../numix/standard/actions/insert-link.svg | 13 +- .../numix/standard/actions/insert-object.svg | 10 +- .../numix/standard/actions/insert-text.svg | 9 +- .../icons/numix/standard/actions/list-add.svg | 4 +- .../numix/standard/actions/list-remove.svg | 4 +- .../numix/standard/actions/mail-forward.svg | 4 +- .../standard/actions/mail-mark-important.svg | 19 +- .../numix/standard/actions/mail-mark-junk.svg | 15 +- .../standard/actions/mail-mark-notjunk.svg | 16 +- .../numix/standard/actions/mail-mark-read.svg | 12 +- .../standard/actions/mail-mark-unread.svg | 14 +- .../standard/actions/mail-message-new.svg | 13 +- .../numix/standard/actions/mail-reply-all.svg | 8 +- .../standard/actions/mail-reply-sender.svg | 4 +- .../standard/actions/mail-send-receive.svg | 5 +- .../numix/standard/actions/media-eject.svg | 7 +- .../standard/actions/media-playback-pause.svg | 7 +- .../standard/actions/media-playback-start.svg | 4 +- .../standard/actions/media-playback-stop.svg | 4 +- .../numix/standard/actions/media-record.svg | 4 +- .../standard/actions/media-seek-backward.svg | 6 +- .../standard/actions/media-seek-forward.svg | 8 +- .../standard/actions/media-skip-backward.svg | 11 +- .../standard/actions/media-skip-forward.svg | 9 +- .../actions/object-flip-horizontal.svg | 11 +- .../standard/actions/object-flip-vertical.svg | 13 +- .../standard/actions/object-rotate-right.svg | 4 +- .../numix/standard/actions/process-stop.svg | 7 +- .../standard/actions/system-lock-screen.svg | 5 +- .../numix/standard/actions/system-log-out.svg | 14 +- .../numix/standard/actions/system-run.svg | 10 +- .../numix/standard/actions/system-search.svg | 8 +- .../standard/actions/system-shutdown.svg | 12 +- .../standard/actions/tools-check-spelling.svg | 9 +- .../standard/actions/view-fullscreen.svg | 12 +- .../numix/standard/actions/view-restore.svg | 12 +- .../standard/actions/view-sort-ascending.svg | 11 +- .../standard/actions/view-sort-descending.svg | 9 +- .../numix/standard/actions/window-close.svg | 4 +- .../numix/standard/actions/window-new.svg | 8 +- .../numix/standard/actions/zoom-fit-best.svg | 11 +- .../icons/numix/standard/actions/zoom-in.svg | 8 +- .../numix/standard/actions/zoom-original.svg | 8 +- .../icons/numix/standard/actions/zoom-out.svg | 8 +- .../categories/applications-accessories.svg | 11 +- .../categories/applications-development.svg | 7 +- .../categories/applications-engineering.svg | 55 +----- .../categories/applications-games.svg | 4 +- .../categories/applications-graphics.svg | 8 +- .../categories/applications-internet.svg | 14 +- .../categories/applications-multimedia.svg | 6 +- .../categories/applications-office.svg | 4 +- .../categories/applications-other.svg | 8 +- .../categories/applications-science.svg | 9 +- .../categories/applications-system.svg | 4 +- .../categories/applications-utilities.svg | 7 +- .../preferences-desktop-peripherals.svg | 16 +- .../preferences-desktop-personal.svg | 5 +- .../categories/preferences-desktop.svg | 5 +- .../standard/categories/preferences-other.svg | 8 +- .../categories/preferences-system-network.svg | 7 +- .../categories/preferences-system.svg | 5 +- .../numix/standard/categories/system-help.svg | 4 +- .../numix/standard/devices/audio-card.svg | 13 +- .../devices/audio-input-microphone.svg | 15 +- .../icons/numix/standard/devices/battery.svg | 14 +- .../numix/standard/devices/camera-photo.svg | 20 +- .../numix/standard/devices/camera-web.svg | 10 +- .../icons/numix/standard/devices/computer.svg | 6 +- .../numix/standard/devices/drive-harddisk.svg | 11 +- .../numix/standard/devices/drive-optical.svg | 14 +- .../devices/drive-removable-media.svg | 11 +- .../numix/standard/devices/input-gaming.svg | 13 +- .../numix/standard/devices/input-keyboard.svg | 11 +- .../numix/standard/devices/input-mouse.svg | 10 +- .../numix/standard/devices/input-tablet.svg | 12 +- .../numix/standard/devices/media-flash.svg | 12 +- .../numix/standard/devices/media-floppy.svg | 10 +- .../numix/standard/devices/media-optical.svg | 11 +- .../numix/standard/devices/media-tape.svg | 12 +- .../standard/devices/multimedia-player.svg | 9 +- .../numix/standard/devices/network-wired.svg | 9 +- .../standard/devices/network-wireless.svg | 10 +- lisp/ui/icons/numix/standard/devices/pda.svg | 10 +- .../ui/icons/numix/standard/devices/phone.svg | 13 +- .../icons/numix/standard/devices/printer.svg | 10 +- .../icons/numix/standard/devices/scanner.svg | 12 +- .../numix/standard/devices/video-display.svg | 10 +- .../numix/standard/emblems/emblem-default.svg | 5 +- .../standard/emblems/emblem-documents.svg | 7 +- .../standard/emblems/emblem-downloads.svg | 8 +- .../standard/emblems/emblem-favorite.svg | 5 +- .../standard/emblems/emblem-important.svg | 5 +- .../numix/standard/emblems/emblem-mail.svg | 10 +- .../numix/standard/emblems/emblem-photos.svg | 5 +- .../standard/emblems/emblem-readonly.svg | 7 +- .../numix/standard/emblems/emblem-shared.svg | 5 +- .../standard/emblems/emblem-symbolic-link.svg | 7 +- .../numix/standard/emblems/emblem-system.svg | 7 +- .../standard/emblems/emblem-unreadable.svg | 5 +- .../numix/standard/places/folder-remote.svg | 6 +- .../ui/icons/numix/standard/places/folder.svg | 11 +- .../numix/standard/places/network-server.svg | 181 +----------------- .../standard/places/network-workgroup.svg | 6 +- .../numix/standard/places/start-here.svg | 9 +- .../numix/standard/places/user-bookmarks.svg | 7 +- .../numix/standard/places/user-desktop.svg | 10 +- .../icons/numix/standard/places/user-home.svg | 8 +- .../numix/standard/places/user-trash.svg | 149 +------------- .../standard/status/appointment-missed.svg | 10 +- .../standard/status/appointment-soon.svg | 11 +- .../standard/status/audio-volume-high.svg | 8 +- .../standard/status/audio-volume-low.svg | 8 +- .../standard/status/audio-volume-medium.svg | 8 +- .../standard/status/audio-volume-muted.svg | 6 +- .../numix/standard/status/battery-caution.svg | 5 +- .../numix/standard/status/battery-low.svg | 5 +- .../numix/standard/status/dialog-error.svg | 4 +- .../numix/standard/status/dialog-password.svg | 8 +- .../numix/standard/status/dialog-warning.svg | 4 +- .../numix/standard/status/network-error.svg | 5 +- .../numix/standard/status/network-idle.svg | 5 +- .../numix/standard/status/network-offline.svg | 5 +- .../numix/standard/status/network-receive.svg | 5 +- .../status/network-transmit-receive.svg | 5 +- .../standard/status/network-transmit.svg | 5 +- .../standard/status/printer-printing.svg | 12 +- .../numix/standard/status/security-high.svg | 8 +- .../numix/standard/status/security-low.svg | 8 +- .../numix/standard/status/security-medium.svg | 6 +- .../icons/numix/standard/status/task-due.svg | 8 +- .../numix/standard/status/task-past-due.svg | 6 +- .../numix/standard/status/user-available.svg | 5 +- .../icons/numix/standard/status/user-away.svg | 5 +- .../icons/numix/standard/status/user-idle.svg | 5 +- .../numix/standard/status/user-offline.svg | 5 +- .../standard/actions/application-exit.svg | 5 +- .../standard/actions/appointment-new.svg | 5 +- .../standard/actions/call-start.svg | 4 +- .../standard/actions/call-stop.svg | 5 +- .../standard/actions/contact-new.svg | 4 +- .../standard/actions/document-new.svg | 4 +- .../standard/actions/document-open-recent.svg | 5 +- .../standard/actions/document-open.svg | 4 +- .../standard/actions/document-page-setup.svg | 5 +- .../actions/document-print-preview.svg | 8 +- .../standard/actions/document-print.svg | 6 +- .../standard/actions/document-properties.svg | 7 +- .../standard/actions/document-revert.svg | 5 +- .../standard/actions/document-save-as.svg | 5 +- .../standard/actions/document-save.svg | 4 +- .../standard/actions/document-send.svg | 4 +- .../standard/actions/edit-clear.svg | 4 +- .../standard/actions/edit-copy.svg | 4 +- .../standard/actions/edit-delete.svg | 5 +- .../standard/actions/edit-find-replace.svg | 5 +- .../standard/actions/edit-find.svg | 4 +- .../standard/actions/edit-paste.svg | 4 +- .../standard/actions/edit-redo.svg | 4 +- .../standard/actions/edit-select-all.svg | 4 +- .../standard/actions/edit-undo.svg | 4 +- .../standard/actions/folder-new.svg | 4 +- .../standard/actions/format-indent-less.svg | 5 +- .../standard/actions/format-indent-more.svg | 5 +- .../actions/format-justify-center.svg | 4 +- .../standard/actions/format-justify-fill.svg | 4 +- .../standard/actions/format-justify-left.svg | 4 +- .../standard/actions/format-justify-right.svg | 4 +- .../standard/actions/format-text-bold.svg | 4 +- .../actions/format-text-direction-ltr.svg | 6 +- .../actions/format-text-direction-rtl.svg | 6 +- .../standard/actions/format-text-italic.svg | 4 +- .../actions/format-text-strikethrough.svg | 5 +- .../actions/format-text-underline.svg | 5 +- .../standard/actions/go-bottom.svg | 5 +- .../standard/actions/go-down.svg | 4 +- .../standard/actions/go-first.svg | 5 +- .../standard/actions/go-home.svg | 4 +- .../standard/actions/go-jump.svg | 4 +- .../standard/actions/go-last.svg | 5 +- .../standard/actions/go-next.svg | 4 +- .../standard/actions/go-previous.svg | 4 +- .../standard/actions/go-top.svg | 5 +- .../standard/actions/go-up.svg | 4 +- .../standard/actions/help-about.svg | 4 +- .../standard/actions/insert-image.svg | 5 +- .../standard/actions/insert-link.svg | 4 +- .../standard/actions/insert-object.svg | 5 +- .../standard/actions/insert-text.svg | 6 +- .../standard/actions/list-add.svg | 4 +- .../standard/actions/list-remove.svg | 4 +- .../standard/actions/mail-forward.svg | 4 +- .../standard/actions/mail-mark-important.svg | 4 +- .../standard/actions/mail-mark-junk.svg | 4 +- .../standard/actions/mail-mark-notjunk.svg | 4 +- .../standard/actions/mail-mark-read.svg | 4 +- .../standard/actions/mail-mark-unread.svg | 4 +- .../standard/actions/mail-message-new.svg | 4 +- .../standard/actions/mail-reply-all.svg | 5 +- .../standard/actions/mail-reply-sender.svg | 4 +- .../standard/actions/mail-send-receive.svg | 7 +- .../standard/actions/mail-send.svg | 4 +- .../standard/actions/media-eject.svg | 4 +- .../standard/actions/media-playback-pause.svg | 5 +- .../standard/actions/media-playback-start.svg | 4 +- .../standard/actions/media-playback-stop.svg | 4 +- .../standard/actions/media-record.svg | 4 +- .../standard/actions/media-seek-backward.svg | 5 +- .../standard/actions/media-seek-forward.svg | 5 +- .../standard/actions/media-skip-backward.svg | 4 +- .../standard/actions/media-skip-forward.svg | 4 +- .../actions/object-flip-horizontal.svg | 4 +- .../standard/actions/object-flip-vertical.svg | 4 +- .../standard/actions/object-rotate-left.svg | 4 +- .../standard/actions/object-rotate-right.svg | 4 +- .../standard/actions/process-stop.svg | 4 +- .../standard/actions/system-lock-screen.svg | 4 +- .../standard/actions/system-log-out.svg | 5 +- .../standard/actions/system-search.svg | 4 +- .../standard/actions/system-shutdown.svg | 5 +- .../standard/actions/tools-check-spelling.svg | 6 +- .../standard/actions/view-fullscreen.svg | 4 +- .../standard/actions/view-refresh.svg | 5 +- .../standard/actions/view-restore.svg | 4 +- .../standard/actions/view-sort-ascending.svg | 7 +- .../standard/actions/view-sort-descending.svg | 7 +- .../standard/actions/window-close.svg | 4 +- .../standard/actions/zoom-fit-best.svg | 4 +- .../standard/actions/zoom-in.svg | 4 +- .../standard/actions/zoom-original.svg | 4 +- .../standard/actions/zoom-out.svg | 4 +- .../categories/applications-engineering.svg | 5 +- .../categories/applications-graphics.svg | 6 +- .../categories/applications-multimedia.svg | 5 +- .../categories/applications-science.svg | 6 +- .../categories/applications-utilities.svg | 6 +- .../standard/categories/preferences-other.svg | 7 +- .../standard/categories/system-help.svg | 4 +- .../standard/devices/audio-card.svg | 4 +- .../devices/audio-input-microphone.svg | 4 +- .../standard/devices/battery.svg | 4 +- .../standard/devices/camera-photo.svg | 4 +- .../standard/devices/camera-video.svg | 4 +- .../standard/devices/camera-web.svg | 4 +- .../standard/devices/computer.svg | 4 +- .../standard/devices/drive-harddisk.svg | 4 +- .../standard/devices/drive-optical.svg | 4 +- .../devices/drive-removable-media.svg | 4 +- .../standard/devices/input-gaming.svg | 4 +- .../standard/devices/input-keyboard.svg | 4 +- .../standard/devices/input-mouse.svg | 4 +- .../standard/devices/input-tablet.svg | 5 +- .../standard/devices/media-flash.svg | 4 +- .../standard/devices/media-floppy.svg | 4 +- .../standard/devices/media-tape.svg | 4 +- .../standard/devices/multimedia-player.svg | 4 +- .../standard/devices/network-wired.svg | 4 +- .../standard/devices/network-wireless.svg | 4 +- .../standard/devices/phone.svg | 4 +- .../standard/devices/printer.svg | 4 +- .../standard/devices/scanner.svg | 4 +- .../standard/devices/video-display.svg | 4 +- .../standard/emblems/emblem-default.svg | 4 +- .../standard/emblems/emblem-documents.svg | 5 +- .../standard/emblems/emblem-favorite.svg | 4 +- .../standard/emblems/emblem-important.svg | 4 +- .../standard/emblems/emblem-photos.svg | 4 +- .../standard/emblems/emblem-shared.svg | 4 +- .../standard/places/folder-remote.svg | 4 +- .../standard/places/folder.svg | 4 +- .../standard/places/start-here.svg | 12 +- .../standard/places/user-bookmarks.svg | 4 +- .../standard/places/user-desktop.svg | 4 +- .../standard/places/user-home.svg | 4 +- .../standard/places/user-trash.svg | 5 +- .../standard/status/appointment-missed.svg | 6 +- .../standard/status/appointment-soon.svg | 5 +- .../standard/status/audio-volume-high.svg | 4 +- .../standard/status/audio-volume-low.svg | 5 +- .../standard/status/audio-volume-medium.svg | 5 +- .../standard/status/audio-volume-muted.svg | 4 +- .../standard/status/battery-caution.svg | 5 +- .../standard/status/battery-low.svg | 5 +- .../standard/status/dialog-error.svg | 12 +- .../standard/status/dialog-information.svg | 12 +- .../standard/status/dialog-warning.svg | 12 +- .../standard/status/folder-drag-accept.svg | 5 +- .../standard/status/folder-open.svg | 5 +- .../standard/status/folder-visiting.svg | 5 +- .../standard/status/image-loading.svg | 7 +- .../standard/status/mail-attachment.svg | 4 +- .../standard/status/mail-read.svg | 4 +- .../standard/status/mail-replied.svg | 4 +- .../standard/status/mail-unread.svg | 4 +- .../standard/status/media-playlist-repeat.svg | 4 +- .../status/media-playlist-shuffle.svg | 7 +- .../standard/status/network-error.svg | 6 +- .../standard/status/network-idle.svg | 5 +- .../standard/status/network-offline.svg | 6 +- .../standard/status/network-receive.svg | 5 +- .../status/network-transmit-receive.svg | 5 +- .../standard/status/network-transmit.svg | 5 +- .../standard/status/printer-error.svg | 5 +- .../standard/status/printer-printing.svg | 7 +- .../standard/status/security-high.svg | 4 +- .../standard/status/security-low.svg | 4 +- .../standard/status/security-medium.svg | 4 +- .../status/software-update-available.svg | 4 +- .../status/software-update-urgent.svg | 4 +- .../standard/status/task-due.svg | 5 +- .../standard/status/task-past-due.svg | 5 +- .../standard/status/user-available.svg | 4 +- .../standard/status/user-away.svg | 5 +- .../standard/status/user-idle.svg | 4 +- .../standard/status/user-offline.svg | 4 +- .../standard/status/user-trash-full.svg | 5 +- 359 files changed, 359 insertions(+), 2381 deletions(-) diff --git a/lisp/ui/icons/numix/standard/actions/address-book-new.svg b/lisp/ui/icons/numix/standard/actions/address-book-new.svg index 6256b7e64..8e06a17cc 100644 --- a/lisp/ui/icons/numix/standard/actions/address-book-new.svg +++ b/lisp/ui/icons/numix/standard/actions/address-book-new.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/application-exit.svg b/lisp/ui/icons/numix/standard/actions/application-exit.svg index 11702cf59..1dafcd22c 100644 --- a/lisp/ui/icons/numix/standard/actions/application-exit.svg +++ b/lisp/ui/icons/numix/standard/actions/application-exit.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/appointment-new.svg b/lisp/ui/icons/numix/standard/actions/appointment-new.svg index 911520b75..4b1859dfc 100644 --- a/lisp/ui/icons/numix/standard/actions/appointment-new.svg +++ b/lisp/ui/icons/numix/standard/actions/appointment-new.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/call-start.svg b/lisp/ui/icons/numix/standard/actions/call-start.svg index 88cc876f6..319dce4c6 100644 --- a/lisp/ui/icons/numix/standard/actions/call-start.svg +++ b/lisp/ui/icons/numix/standard/actions/call-start.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/call-stop.svg b/lisp/ui/icons/numix/standard/actions/call-stop.svg index d57d79c7e..c4652d69d 100644 --- a/lisp/ui/icons/numix/standard/actions/call-stop.svg +++ b/lisp/ui/icons/numix/standard/actions/call-stop.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/contact-new.svg b/lisp/ui/icons/numix/standard/actions/contact-new.svg index fb3a91c17..452759c87 100644 --- a/lisp/ui/icons/numix/standard/actions/contact-new.svg +++ b/lisp/ui/icons/numix/standard/actions/contact-new.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/document-open-recent.svg b/lisp/ui/icons/numix/standard/actions/document-open-recent.svg index 4a19b9f58..33db3be93 100644 --- a/lisp/ui/icons/numix/standard/actions/document-open-recent.svg +++ b/lisp/ui/icons/numix/standard/actions/document-open-recent.svg @@ -1,15 +1 @@ - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/document-open.svg b/lisp/ui/icons/numix/standard/actions/document-open.svg index 69d6af2c7..308e6895a 100644 --- a/lisp/ui/icons/numix/standard/actions/document-open.svg +++ b/lisp/ui/icons/numix/standard/actions/document-open.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/document-page-setup.svg b/lisp/ui/icons/numix/standard/actions/document-page-setup.svg index becf33147..e1ee2e83f 100644 --- a/lisp/ui/icons/numix/standard/actions/document-page-setup.svg +++ b/lisp/ui/icons/numix/standard/actions/document-page-setup.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/document-print-preview.svg b/lisp/ui/icons/numix/standard/actions/document-print-preview.svg index 8c5947448..56ca8ec4f 100644 --- a/lisp/ui/icons/numix/standard/actions/document-print-preview.svg +++ b/lisp/ui/icons/numix/standard/actions/document-print-preview.svg @@ -1,14 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/document-print.svg b/lisp/ui/icons/numix/standard/actions/document-print.svg index af30c1d9e..61fde1473 100644 --- a/lisp/ui/icons/numix/standard/actions/document-print.svg +++ b/lisp/ui/icons/numix/standard/actions/document-print.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/document-properties.svg b/lisp/ui/icons/numix/standard/actions/document-properties.svg index 1b7cde149..b2e3bbfa8 100644 --- a/lisp/ui/icons/numix/standard/actions/document-properties.svg +++ b/lisp/ui/icons/numix/standard/actions/document-properties.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/document-revert.svg b/lisp/ui/icons/numix/standard/actions/document-revert.svg index 5f45aa940..3570a6a32 100644 --- a/lisp/ui/icons/numix/standard/actions/document-revert.svg +++ b/lisp/ui/icons/numix/standard/actions/document-revert.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/document-save-as.svg b/lisp/ui/icons/numix/standard/actions/document-save-as.svg index 3de787a16..f8f1820ca 100644 --- a/lisp/ui/icons/numix/standard/actions/document-save-as.svg +++ b/lisp/ui/icons/numix/standard/actions/document-save-as.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/document-save.svg b/lisp/ui/icons/numix/standard/actions/document-save.svg index 0da5dca10..592fdf7bd 100644 --- a/lisp/ui/icons/numix/standard/actions/document-save.svg +++ b/lisp/ui/icons/numix/standard/actions/document-save.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/document-send.svg b/lisp/ui/icons/numix/standard/actions/document-send.svg index f45e29570..5b9eedccc 100644 --- a/lisp/ui/icons/numix/standard/actions/document-send.svg +++ b/lisp/ui/icons/numix/standard/actions/document-send.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/edit-cut.svg b/lisp/ui/icons/numix/standard/actions/edit-cut.svg index 26f8f20d1..4c2e6486d 100644 --- a/lisp/ui/icons/numix/standard/actions/edit-cut.svg +++ b/lisp/ui/icons/numix/standard/actions/edit-cut.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/edit-find-replace.svg b/lisp/ui/icons/numix/standard/actions/edit-find-replace.svg index ad976c680..d93cab944 100644 --- a/lisp/ui/icons/numix/standard/actions/edit-find-replace.svg +++ b/lisp/ui/icons/numix/standard/actions/edit-find-replace.svg @@ -1,14 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/edit-find.svg b/lisp/ui/icons/numix/standard/actions/edit-find.svg index 0a4bf4fb8..e333dab53 100644 --- a/lisp/ui/icons/numix/standard/actions/edit-find.svg +++ b/lisp/ui/icons/numix/standard/actions/edit-find.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/edit-paste.svg b/lisp/ui/icons/numix/standard/actions/edit-paste.svg index 5529f5c8c..c81728041 100644 --- a/lisp/ui/icons/numix/standard/actions/edit-paste.svg +++ b/lisp/ui/icons/numix/standard/actions/edit-paste.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/edit-redo.svg b/lisp/ui/icons/numix/standard/actions/edit-redo.svg index 811751811..b5d5bb51c 100644 --- a/lisp/ui/icons/numix/standard/actions/edit-redo.svg +++ b/lisp/ui/icons/numix/standard/actions/edit-redo.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/edit-undo.svg b/lisp/ui/icons/numix/standard/actions/edit-undo.svg index b5c9c0714..7a48060be 100644 --- a/lisp/ui/icons/numix/standard/actions/edit-undo.svg +++ b/lisp/ui/icons/numix/standard/actions/edit-undo.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/folder-new.svg b/lisp/ui/icons/numix/standard/actions/folder-new.svg index ed7c3abb7..ed879babd 100644 --- a/lisp/ui/icons/numix/standard/actions/folder-new.svg +++ b/lisp/ui/icons/numix/standard/actions/folder-new.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/format-text-bold.svg b/lisp/ui/icons/numix/standard/actions/format-text-bold.svg index 4a8a5d8c4..9bb172ec1 100644 --- a/lisp/ui/icons/numix/standard/actions/format-text-bold.svg +++ b/lisp/ui/icons/numix/standard/actions/format-text-bold.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/format-text-direction-ltr.svg b/lisp/ui/icons/numix/standard/actions/format-text-direction-ltr.svg index 8e2c924f4..650bb6d24 100644 --- a/lisp/ui/icons/numix/standard/actions/format-text-direction-ltr.svg +++ b/lisp/ui/icons/numix/standard/actions/format-text-direction-ltr.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/format-text-direction-rtl.svg b/lisp/ui/icons/numix/standard/actions/format-text-direction-rtl.svg index d47a3e9a4..514f97f1e 100644 --- a/lisp/ui/icons/numix/standard/actions/format-text-direction-rtl.svg +++ b/lisp/ui/icons/numix/standard/actions/format-text-direction-rtl.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/format-text-italic.svg b/lisp/ui/icons/numix/standard/actions/format-text-italic.svg index b016d75b6..cedb7f8b1 100644 --- a/lisp/ui/icons/numix/standard/actions/format-text-italic.svg +++ b/lisp/ui/icons/numix/standard/actions/format-text-italic.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/format-text-strikethrough.svg b/lisp/ui/icons/numix/standard/actions/format-text-strikethrough.svg index 759dcafd9..5e137df02 100644 --- a/lisp/ui/icons/numix/standard/actions/format-text-strikethrough.svg +++ b/lisp/ui/icons/numix/standard/actions/format-text-strikethrough.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/format-text-underline.svg b/lisp/ui/icons/numix/standard/actions/format-text-underline.svg index 4448982ab..c521b2274 100644 --- a/lisp/ui/icons/numix/standard/actions/format-text-underline.svg +++ b/lisp/ui/icons/numix/standard/actions/format-text-underline.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/go-bottom.svg b/lisp/ui/icons/numix/standard/actions/go-bottom.svg index ae1add864..f698ce1ec 100644 --- a/lisp/ui/icons/numix/standard/actions/go-bottom.svg +++ b/lisp/ui/icons/numix/standard/actions/go-bottom.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/go-down.svg b/lisp/ui/icons/numix/standard/actions/go-down.svg index c5e6a3293..655ffc131 100644 --- a/lisp/ui/icons/numix/standard/actions/go-down.svg +++ b/lisp/ui/icons/numix/standard/actions/go-down.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/go-first.svg b/lisp/ui/icons/numix/standard/actions/go-first.svg index 9c91acde8..0fa7f3199 100644 --- a/lisp/ui/icons/numix/standard/actions/go-first.svg +++ b/lisp/ui/icons/numix/standard/actions/go-first.svg @@ -1,12 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/go-home.svg b/lisp/ui/icons/numix/standard/actions/go-home.svg index 421777310..f72be25f9 100644 --- a/lisp/ui/icons/numix/standard/actions/go-home.svg +++ b/lisp/ui/icons/numix/standard/actions/go-home.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/go-jump.svg b/lisp/ui/icons/numix/standard/actions/go-jump.svg index 2a67036b4..d12b377f1 100644 --- a/lisp/ui/icons/numix/standard/actions/go-jump.svg +++ b/lisp/ui/icons/numix/standard/actions/go-jump.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/go-last.svg b/lisp/ui/icons/numix/standard/actions/go-last.svg index b57882311..6d84fbf06 100644 --- a/lisp/ui/icons/numix/standard/actions/go-last.svg +++ b/lisp/ui/icons/numix/standard/actions/go-last.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/go-next.svg b/lisp/ui/icons/numix/standard/actions/go-next.svg index b690c9635..139079a84 100644 --- a/lisp/ui/icons/numix/standard/actions/go-next.svg +++ b/lisp/ui/icons/numix/standard/actions/go-next.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/go-previous.svg b/lisp/ui/icons/numix/standard/actions/go-previous.svg index 649611d5d..abc21f477 100644 --- a/lisp/ui/icons/numix/standard/actions/go-previous.svg +++ b/lisp/ui/icons/numix/standard/actions/go-previous.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/go-top.svg b/lisp/ui/icons/numix/standard/actions/go-top.svg index 8c4a02869..2786269eb 100644 --- a/lisp/ui/icons/numix/standard/actions/go-top.svg +++ b/lisp/ui/icons/numix/standard/actions/go-top.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/go-up.svg b/lisp/ui/icons/numix/standard/actions/go-up.svg index e3546446d..d42860214 100644 --- a/lisp/ui/icons/numix/standard/actions/go-up.svg +++ b/lisp/ui/icons/numix/standard/actions/go-up.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/help-about.svg b/lisp/ui/icons/numix/standard/actions/help-about.svg index 7fe4e9298..af8ba627c 100644 --- a/lisp/ui/icons/numix/standard/actions/help-about.svg +++ b/lisp/ui/icons/numix/standard/actions/help-about.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/help-contents.svg b/lisp/ui/icons/numix/standard/actions/help-contents.svg index e6e85e8c4..e136f442f 100644 --- a/lisp/ui/icons/numix/standard/actions/help-contents.svg +++ b/lisp/ui/icons/numix/standard/actions/help-contents.svg @@ -1,12 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/help-faq.svg b/lisp/ui/icons/numix/standard/actions/help-faq.svg index 0f750ede1..91b58e7b4 100644 --- a/lisp/ui/icons/numix/standard/actions/help-faq.svg +++ b/lisp/ui/icons/numix/standard/actions/help-faq.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/insert-image.svg b/lisp/ui/icons/numix/standard/actions/insert-image.svg index 57c927bd6..6fad8b8c7 100644 --- a/lisp/ui/icons/numix/standard/actions/insert-image.svg +++ b/lisp/ui/icons/numix/standard/actions/insert-image.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/insert-link.svg b/lisp/ui/icons/numix/standard/actions/insert-link.svg index 34debf6f8..3ecce8ba2 100644 --- a/lisp/ui/icons/numix/standard/actions/insert-link.svg +++ b/lisp/ui/icons/numix/standard/actions/insert-link.svg @@ -1,12 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/insert-object.svg b/lisp/ui/icons/numix/standard/actions/insert-object.svg index b020c7e1f..cbe99e5ca 100644 --- a/lisp/ui/icons/numix/standard/actions/insert-object.svg +++ b/lisp/ui/icons/numix/standard/actions/insert-object.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/insert-text.svg b/lisp/ui/icons/numix/standard/actions/insert-text.svg index c687c0c46..7031e6c8c 100644 --- a/lisp/ui/icons/numix/standard/actions/insert-text.svg +++ b/lisp/ui/icons/numix/standard/actions/insert-text.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/list-add.svg b/lisp/ui/icons/numix/standard/actions/list-add.svg index ad08e8d70..0c8011d87 100644 --- a/lisp/ui/icons/numix/standard/actions/list-add.svg +++ b/lisp/ui/icons/numix/standard/actions/list-add.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/list-remove.svg b/lisp/ui/icons/numix/standard/actions/list-remove.svg index 5ef07aafb..02b865e30 100644 --- a/lisp/ui/icons/numix/standard/actions/list-remove.svg +++ b/lisp/ui/icons/numix/standard/actions/list-remove.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/mail-forward.svg b/lisp/ui/icons/numix/standard/actions/mail-forward.svg index 01ea9bc68..a067ebae8 100644 --- a/lisp/ui/icons/numix/standard/actions/mail-forward.svg +++ b/lisp/ui/icons/numix/standard/actions/mail-forward.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/mail-mark-important.svg b/lisp/ui/icons/numix/standard/actions/mail-mark-important.svg index 103e5e179..3557071ae 100644 --- a/lisp/ui/icons/numix/standard/actions/mail-mark-important.svg +++ b/lisp/ui/icons/numix/standard/actions/mail-mark-important.svg @@ -1,18 +1 @@ - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/mail-mark-junk.svg b/lisp/ui/icons/numix/standard/actions/mail-mark-junk.svg index 1b46fab98..04bb0e223 100644 --- a/lisp/ui/icons/numix/standard/actions/mail-mark-junk.svg +++ b/lisp/ui/icons/numix/standard/actions/mail-mark-junk.svg @@ -1,14 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/mail-mark-notjunk.svg b/lisp/ui/icons/numix/standard/actions/mail-mark-notjunk.svg index 97995c141..d06a7eb1a 100644 --- a/lisp/ui/icons/numix/standard/actions/mail-mark-notjunk.svg +++ b/lisp/ui/icons/numix/standard/actions/mail-mark-notjunk.svg @@ -1,15 +1 @@ - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/mail-mark-read.svg b/lisp/ui/icons/numix/standard/actions/mail-mark-read.svg index 1219ba3f5..08daa6c68 100644 --- a/lisp/ui/icons/numix/standard/actions/mail-mark-read.svg +++ b/lisp/ui/icons/numix/standard/actions/mail-mark-read.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/mail-mark-unread.svg b/lisp/ui/icons/numix/standard/actions/mail-mark-unread.svg index 99c893f98..eb5f0ed18 100644 --- a/lisp/ui/icons/numix/standard/actions/mail-mark-unread.svg +++ b/lisp/ui/icons/numix/standard/actions/mail-mark-unread.svg @@ -1,13 +1 @@ - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/mail-message-new.svg b/lisp/ui/icons/numix/standard/actions/mail-message-new.svg index 82c579214..10a59f192 100644 --- a/lisp/ui/icons/numix/standard/actions/mail-message-new.svg +++ b/lisp/ui/icons/numix/standard/actions/mail-message-new.svg @@ -1,12 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/mail-reply-all.svg b/lisp/ui/icons/numix/standard/actions/mail-reply-all.svg index 8167df592..f4c2d1d7a 100644 --- a/lisp/ui/icons/numix/standard/actions/mail-reply-all.svg +++ b/lisp/ui/icons/numix/standard/actions/mail-reply-all.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/mail-reply-sender.svg b/lisp/ui/icons/numix/standard/actions/mail-reply-sender.svg index 751624bed..c9363fa47 100644 --- a/lisp/ui/icons/numix/standard/actions/mail-reply-sender.svg +++ b/lisp/ui/icons/numix/standard/actions/mail-reply-sender.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/mail-send-receive.svg b/lisp/ui/icons/numix/standard/actions/mail-send-receive.svg index dee4a91e8..14c6ac5ec 100644 --- a/lisp/ui/icons/numix/standard/actions/mail-send-receive.svg +++ b/lisp/ui/icons/numix/standard/actions/mail-send-receive.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/media-eject.svg b/lisp/ui/icons/numix/standard/actions/media-eject.svg index 64ef44e16..533c153d5 100644 --- a/lisp/ui/icons/numix/standard/actions/media-eject.svg +++ b/lisp/ui/icons/numix/standard/actions/media-eject.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/media-playback-pause.svg b/lisp/ui/icons/numix/standard/actions/media-playback-pause.svg index 969ed2208..dd8ef7a01 100644 --- a/lisp/ui/icons/numix/standard/actions/media-playback-pause.svg +++ b/lisp/ui/icons/numix/standard/actions/media-playback-pause.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/media-playback-start.svg b/lisp/ui/icons/numix/standard/actions/media-playback-start.svg index fd18ba6bb..f254b810e 100644 --- a/lisp/ui/icons/numix/standard/actions/media-playback-start.svg +++ b/lisp/ui/icons/numix/standard/actions/media-playback-start.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/media-playback-stop.svg b/lisp/ui/icons/numix/standard/actions/media-playback-stop.svg index bebdea821..1fbd09493 100644 --- a/lisp/ui/icons/numix/standard/actions/media-playback-stop.svg +++ b/lisp/ui/icons/numix/standard/actions/media-playback-stop.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/media-record.svg b/lisp/ui/icons/numix/standard/actions/media-record.svg index 6eb0b4503..46bc5f865 100644 --- a/lisp/ui/icons/numix/standard/actions/media-record.svg +++ b/lisp/ui/icons/numix/standard/actions/media-record.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/media-seek-backward.svg b/lisp/ui/icons/numix/standard/actions/media-seek-backward.svg index ddaf8d47a..606a13a99 100644 --- a/lisp/ui/icons/numix/standard/actions/media-seek-backward.svg +++ b/lisp/ui/icons/numix/standard/actions/media-seek-backward.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/media-seek-forward.svg b/lisp/ui/icons/numix/standard/actions/media-seek-forward.svg index 8cff7d5e8..87c646a54 100644 --- a/lisp/ui/icons/numix/standard/actions/media-seek-forward.svg +++ b/lisp/ui/icons/numix/standard/actions/media-seek-forward.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/media-skip-backward.svg b/lisp/ui/icons/numix/standard/actions/media-skip-backward.svg index 9f4c2adf3..1f1eca67d 100644 --- a/lisp/ui/icons/numix/standard/actions/media-skip-backward.svg +++ b/lisp/ui/icons/numix/standard/actions/media-skip-backward.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/media-skip-forward.svg b/lisp/ui/icons/numix/standard/actions/media-skip-forward.svg index fb38f9ecd..a12a6a81f 100644 --- a/lisp/ui/icons/numix/standard/actions/media-skip-forward.svg +++ b/lisp/ui/icons/numix/standard/actions/media-skip-forward.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/object-flip-horizontal.svg b/lisp/ui/icons/numix/standard/actions/object-flip-horizontal.svg index 01b75de02..0a5d57f94 100644 --- a/lisp/ui/icons/numix/standard/actions/object-flip-horizontal.svg +++ b/lisp/ui/icons/numix/standard/actions/object-flip-horizontal.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/object-flip-vertical.svg b/lisp/ui/icons/numix/standard/actions/object-flip-vertical.svg index 8c48a6d63..9e750ea00 100644 --- a/lisp/ui/icons/numix/standard/actions/object-flip-vertical.svg +++ b/lisp/ui/icons/numix/standard/actions/object-flip-vertical.svg @@ -1,12 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/object-rotate-right.svg b/lisp/ui/icons/numix/standard/actions/object-rotate-right.svg index b15ef8ae0..6fc43f9be 100644 --- a/lisp/ui/icons/numix/standard/actions/object-rotate-right.svg +++ b/lisp/ui/icons/numix/standard/actions/object-rotate-right.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/process-stop.svg b/lisp/ui/icons/numix/standard/actions/process-stop.svg index 18076fb04..d9407c7c4 100644 --- a/lisp/ui/icons/numix/standard/actions/process-stop.svg +++ b/lisp/ui/icons/numix/standard/actions/process-stop.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/system-lock-screen.svg b/lisp/ui/icons/numix/standard/actions/system-lock-screen.svg index e0f5fbe5e..f856055e4 100644 --- a/lisp/ui/icons/numix/standard/actions/system-lock-screen.svg +++ b/lisp/ui/icons/numix/standard/actions/system-lock-screen.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/system-log-out.svg b/lisp/ui/icons/numix/standard/actions/system-log-out.svg index 4f68b02d4..4a0c976c7 100644 --- a/lisp/ui/icons/numix/standard/actions/system-log-out.svg +++ b/lisp/ui/icons/numix/standard/actions/system-log-out.svg @@ -1,13 +1 @@ - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/system-run.svg b/lisp/ui/icons/numix/standard/actions/system-run.svg index 740f2c4d5..d996c78ad 100644 --- a/lisp/ui/icons/numix/standard/actions/system-run.svg +++ b/lisp/ui/icons/numix/standard/actions/system-run.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/system-search.svg b/lisp/ui/icons/numix/standard/actions/system-search.svg index 0a4bf4fb8..e333dab53 100644 --- a/lisp/ui/icons/numix/standard/actions/system-search.svg +++ b/lisp/ui/icons/numix/standard/actions/system-search.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/system-shutdown.svg b/lisp/ui/icons/numix/standard/actions/system-shutdown.svg index 11702cf59..1dafcd22c 100644 --- a/lisp/ui/icons/numix/standard/actions/system-shutdown.svg +++ b/lisp/ui/icons/numix/standard/actions/system-shutdown.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/tools-check-spelling.svg b/lisp/ui/icons/numix/standard/actions/tools-check-spelling.svg index f2c2eda58..a8685f9d1 100644 --- a/lisp/ui/icons/numix/standard/actions/tools-check-spelling.svg +++ b/lisp/ui/icons/numix/standard/actions/tools-check-spelling.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/view-fullscreen.svg b/lisp/ui/icons/numix/standard/actions/view-fullscreen.svg index cc28b7e04..9f7771ea1 100644 --- a/lisp/ui/icons/numix/standard/actions/view-fullscreen.svg +++ b/lisp/ui/icons/numix/standard/actions/view-fullscreen.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/view-restore.svg b/lisp/ui/icons/numix/standard/actions/view-restore.svg index 98f9063d5..33320e2ec 100644 --- a/lisp/ui/icons/numix/standard/actions/view-restore.svg +++ b/lisp/ui/icons/numix/standard/actions/view-restore.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/view-sort-ascending.svg b/lisp/ui/icons/numix/standard/actions/view-sort-ascending.svg index c25c155a6..02fbbb9a4 100644 --- a/lisp/ui/icons/numix/standard/actions/view-sort-ascending.svg +++ b/lisp/ui/icons/numix/standard/actions/view-sort-ascending.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/view-sort-descending.svg b/lisp/ui/icons/numix/standard/actions/view-sort-descending.svg index 53ee7a770..9ef72a85b 100644 --- a/lisp/ui/icons/numix/standard/actions/view-sort-descending.svg +++ b/lisp/ui/icons/numix/standard/actions/view-sort-descending.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/window-close.svg b/lisp/ui/icons/numix/standard/actions/window-close.svg index bae8dd284..be0dd754c 100644 --- a/lisp/ui/icons/numix/standard/actions/window-close.svg +++ b/lisp/ui/icons/numix/standard/actions/window-close.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/window-new.svg b/lisp/ui/icons/numix/standard/actions/window-new.svg index 282b0ebef..c330dbd42 100644 --- a/lisp/ui/icons/numix/standard/actions/window-new.svg +++ b/lisp/ui/icons/numix/standard/actions/window-new.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/zoom-fit-best.svg b/lisp/ui/icons/numix/standard/actions/zoom-fit-best.svg index 091ad951a..d1a083edd 100644 --- a/lisp/ui/icons/numix/standard/actions/zoom-fit-best.svg +++ b/lisp/ui/icons/numix/standard/actions/zoom-fit-best.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/zoom-in.svg b/lisp/ui/icons/numix/standard/actions/zoom-in.svg index 9f5244ef7..87245a1f5 100644 --- a/lisp/ui/icons/numix/standard/actions/zoom-in.svg +++ b/lisp/ui/icons/numix/standard/actions/zoom-in.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/zoom-original.svg b/lisp/ui/icons/numix/standard/actions/zoom-original.svg index 970050b40..11ecd1789 100644 --- a/lisp/ui/icons/numix/standard/actions/zoom-original.svg +++ b/lisp/ui/icons/numix/standard/actions/zoom-original.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/actions/zoom-out.svg b/lisp/ui/icons/numix/standard/actions/zoom-out.svg index b2a6884d5..688614f7b 100644 --- a/lisp/ui/icons/numix/standard/actions/zoom-out.svg +++ b/lisp/ui/icons/numix/standard/actions/zoom-out.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/applications-accessories.svg b/lisp/ui/icons/numix/standard/categories/applications-accessories.svg index acdb214c0..97b46e8d9 100644 --- a/lisp/ui/icons/numix/standard/categories/applications-accessories.svg +++ b/lisp/ui/icons/numix/standard/categories/applications-accessories.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/applications-development.svg b/lisp/ui/icons/numix/standard/categories/applications-development.svg index 68d61d7dc..0bb494091 100644 --- a/lisp/ui/icons/numix/standard/categories/applications-development.svg +++ b/lisp/ui/icons/numix/standard/categories/applications-development.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/applications-engineering.svg b/lisp/ui/icons/numix/standard/categories/applications-engineering.svg index 992ee1959..8822a887a 100644 --- a/lisp/ui/icons/numix/standard/categories/applications-engineering.svg +++ b/lisp/ui/icons/numix/standard/categories/applications-engineering.svg @@ -1,54 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/applications-games.svg b/lisp/ui/icons/numix/standard/categories/applications-games.svg index 468e1f8f6..5e9b65f2f 100644 --- a/lisp/ui/icons/numix/standard/categories/applications-games.svg +++ b/lisp/ui/icons/numix/standard/categories/applications-games.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/applications-graphics.svg b/lisp/ui/icons/numix/standard/categories/applications-graphics.svg index 0bb818891..a7f96aa5e 100644 --- a/lisp/ui/icons/numix/standard/categories/applications-graphics.svg +++ b/lisp/ui/icons/numix/standard/categories/applications-graphics.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/applications-internet.svg b/lisp/ui/icons/numix/standard/categories/applications-internet.svg index 443150f07..7a0de6c9e 100644 --- a/lisp/ui/icons/numix/standard/categories/applications-internet.svg +++ b/lisp/ui/icons/numix/standard/categories/applications-internet.svg @@ -1,13 +1 @@ - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/applications-multimedia.svg b/lisp/ui/icons/numix/standard/categories/applications-multimedia.svg index 8c337d2f1..142aaa150 100644 --- a/lisp/ui/icons/numix/standard/categories/applications-multimedia.svg +++ b/lisp/ui/icons/numix/standard/categories/applications-multimedia.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/applications-office.svg b/lisp/ui/icons/numix/standard/categories/applications-office.svg index e99368590..17559f4f5 100644 --- a/lisp/ui/icons/numix/standard/categories/applications-office.svg +++ b/lisp/ui/icons/numix/standard/categories/applications-office.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/applications-other.svg b/lisp/ui/icons/numix/standard/categories/applications-other.svg index f3144fb8a..4ad4d2d32 100644 --- a/lisp/ui/icons/numix/standard/categories/applications-other.svg +++ b/lisp/ui/icons/numix/standard/categories/applications-other.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/applications-science.svg b/lisp/ui/icons/numix/standard/categories/applications-science.svg index af0204b0d..13607ad0d 100644 --- a/lisp/ui/icons/numix/standard/categories/applications-science.svg +++ b/lisp/ui/icons/numix/standard/categories/applications-science.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/applications-system.svg b/lisp/ui/icons/numix/standard/categories/applications-system.svg index e4c6f03e7..bcc275cfe 100644 --- a/lisp/ui/icons/numix/standard/categories/applications-system.svg +++ b/lisp/ui/icons/numix/standard/categories/applications-system.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/applications-utilities.svg b/lisp/ui/icons/numix/standard/categories/applications-utilities.svg index 4ea7b8a5b..64dbb7302 100644 --- a/lisp/ui/icons/numix/standard/categories/applications-utilities.svg +++ b/lisp/ui/icons/numix/standard/categories/applications-utilities.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/preferences-desktop-peripherals.svg b/lisp/ui/icons/numix/standard/categories/preferences-desktop-peripherals.svg index 00c43ec6e..81dd4d96c 100644 --- a/lisp/ui/icons/numix/standard/categories/preferences-desktop-peripherals.svg +++ b/lisp/ui/icons/numix/standard/categories/preferences-desktop-peripherals.svg @@ -1,15 +1 @@ - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/preferences-desktop-personal.svg b/lisp/ui/icons/numix/standard/categories/preferences-desktop-personal.svg index 8c9956079..5d1228ff7 100644 --- a/lisp/ui/icons/numix/standard/categories/preferences-desktop-personal.svg +++ b/lisp/ui/icons/numix/standard/categories/preferences-desktop-personal.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/preferences-desktop.svg b/lisp/ui/icons/numix/standard/categories/preferences-desktop.svg index 8c9956079..5d1228ff7 100644 --- a/lisp/ui/icons/numix/standard/categories/preferences-desktop.svg +++ b/lisp/ui/icons/numix/standard/categories/preferences-desktop.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/preferences-other.svg b/lisp/ui/icons/numix/standard/categories/preferences-other.svg index f3144fb8a..4ad4d2d32 100644 --- a/lisp/ui/icons/numix/standard/categories/preferences-other.svg +++ b/lisp/ui/icons/numix/standard/categories/preferences-other.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/preferences-system-network.svg b/lisp/ui/icons/numix/standard/categories/preferences-system-network.svg index 9554c4ec9..d9e5ce647 100644 --- a/lisp/ui/icons/numix/standard/categories/preferences-system-network.svg +++ b/lisp/ui/icons/numix/standard/categories/preferences-system-network.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/preferences-system.svg b/lisp/ui/icons/numix/standard/categories/preferences-system.svg index 8c9956079..5d1228ff7 100644 --- a/lisp/ui/icons/numix/standard/categories/preferences-system.svg +++ b/lisp/ui/icons/numix/standard/categories/preferences-system.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/categories/system-help.svg b/lisp/ui/icons/numix/standard/categories/system-help.svg index 0d82f82ae..3c43308a8 100644 --- a/lisp/ui/icons/numix/standard/categories/system-help.svg +++ b/lisp/ui/icons/numix/standard/categories/system-help.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/audio-card.svg b/lisp/ui/icons/numix/standard/devices/audio-card.svg index 51aff3373..876dbb319 100644 --- a/lisp/ui/icons/numix/standard/devices/audio-card.svg +++ b/lisp/ui/icons/numix/standard/devices/audio-card.svg @@ -1,12 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/audio-input-microphone.svg b/lisp/ui/icons/numix/standard/devices/audio-input-microphone.svg index 72e69b82a..ea9b4f657 100644 --- a/lisp/ui/icons/numix/standard/devices/audio-input-microphone.svg +++ b/lisp/ui/icons/numix/standard/devices/audio-input-microphone.svg @@ -1,14 +1 @@ - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/battery.svg b/lisp/ui/icons/numix/standard/devices/battery.svg index a9b1e0146..aa8c2aa25 100644 --- a/lisp/ui/icons/numix/standard/devices/battery.svg +++ b/lisp/ui/icons/numix/standard/devices/battery.svg @@ -1,13 +1 @@ - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/camera-photo.svg b/lisp/ui/icons/numix/standard/devices/camera-photo.svg index 8ed47a770..4226388f2 100644 --- a/lisp/ui/icons/numix/standard/devices/camera-photo.svg +++ b/lisp/ui/icons/numix/standard/devices/camera-photo.svg @@ -1,19 +1 @@ - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/camera-web.svg b/lisp/ui/icons/numix/standard/devices/camera-web.svg index a15f6d889..4d54f5a2b 100644 --- a/lisp/ui/icons/numix/standard/devices/camera-web.svg +++ b/lisp/ui/icons/numix/standard/devices/camera-web.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/computer.svg b/lisp/ui/icons/numix/standard/devices/computer.svg index 91f7c0e51..02b956c56 100644 --- a/lisp/ui/icons/numix/standard/devices/computer.svg +++ b/lisp/ui/icons/numix/standard/devices/computer.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/drive-harddisk.svg b/lisp/ui/icons/numix/standard/devices/drive-harddisk.svg index 0e5627f55..939ff085f 100644 --- a/lisp/ui/icons/numix/standard/devices/drive-harddisk.svg +++ b/lisp/ui/icons/numix/standard/devices/drive-harddisk.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/drive-optical.svg b/lisp/ui/icons/numix/standard/devices/drive-optical.svg index bcec83269..fd2c35d85 100644 --- a/lisp/ui/icons/numix/standard/devices/drive-optical.svg +++ b/lisp/ui/icons/numix/standard/devices/drive-optical.svg @@ -1,13 +1 @@ - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/drive-removable-media.svg b/lisp/ui/icons/numix/standard/devices/drive-removable-media.svg index 8d75c5c86..4b6d1c06d 100644 --- a/lisp/ui/icons/numix/standard/devices/drive-removable-media.svg +++ b/lisp/ui/icons/numix/standard/devices/drive-removable-media.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/input-gaming.svg b/lisp/ui/icons/numix/standard/devices/input-gaming.svg index 9c151321d..1dd6581a9 100644 --- a/lisp/ui/icons/numix/standard/devices/input-gaming.svg +++ b/lisp/ui/icons/numix/standard/devices/input-gaming.svg @@ -1,12 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/input-keyboard.svg b/lisp/ui/icons/numix/standard/devices/input-keyboard.svg index 18c4f2a49..115082c18 100644 --- a/lisp/ui/icons/numix/standard/devices/input-keyboard.svg +++ b/lisp/ui/icons/numix/standard/devices/input-keyboard.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/input-mouse.svg b/lisp/ui/icons/numix/standard/devices/input-mouse.svg index ed6d25db8..e92e29254 100644 --- a/lisp/ui/icons/numix/standard/devices/input-mouse.svg +++ b/lisp/ui/icons/numix/standard/devices/input-mouse.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/input-tablet.svg b/lisp/ui/icons/numix/standard/devices/input-tablet.svg index f6c0287af..c4ffef2a1 100644 --- a/lisp/ui/icons/numix/standard/devices/input-tablet.svg +++ b/lisp/ui/icons/numix/standard/devices/input-tablet.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/media-flash.svg b/lisp/ui/icons/numix/standard/devices/media-flash.svg index efcbdf296..ad7f4c7e1 100644 --- a/lisp/ui/icons/numix/standard/devices/media-flash.svg +++ b/lisp/ui/icons/numix/standard/devices/media-flash.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/media-floppy.svg b/lisp/ui/icons/numix/standard/devices/media-floppy.svg index acdc967b3..eb6dcd281 100644 --- a/lisp/ui/icons/numix/standard/devices/media-floppy.svg +++ b/lisp/ui/icons/numix/standard/devices/media-floppy.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/media-optical.svg b/lisp/ui/icons/numix/standard/devices/media-optical.svg index 739752c55..17f2b3e0b 100644 --- a/lisp/ui/icons/numix/standard/devices/media-optical.svg +++ b/lisp/ui/icons/numix/standard/devices/media-optical.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/media-tape.svg b/lisp/ui/icons/numix/standard/devices/media-tape.svg index d69fe72ff..d07590843 100644 --- a/lisp/ui/icons/numix/standard/devices/media-tape.svg +++ b/lisp/ui/icons/numix/standard/devices/media-tape.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/multimedia-player.svg b/lisp/ui/icons/numix/standard/devices/multimedia-player.svg index e46854b74..1492931f0 100644 --- a/lisp/ui/icons/numix/standard/devices/multimedia-player.svg +++ b/lisp/ui/icons/numix/standard/devices/multimedia-player.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/network-wired.svg b/lisp/ui/icons/numix/standard/devices/network-wired.svg index 7f1fa8e1a..bb488f06b 100644 --- a/lisp/ui/icons/numix/standard/devices/network-wired.svg +++ b/lisp/ui/icons/numix/standard/devices/network-wired.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/network-wireless.svg b/lisp/ui/icons/numix/standard/devices/network-wireless.svg index 337e86a2d..6c28991f9 100644 --- a/lisp/ui/icons/numix/standard/devices/network-wireless.svg +++ b/lisp/ui/icons/numix/standard/devices/network-wireless.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/pda.svg b/lisp/ui/icons/numix/standard/devices/pda.svg index a54eb83d8..88a7a26d7 100644 --- a/lisp/ui/icons/numix/standard/devices/pda.svg +++ b/lisp/ui/icons/numix/standard/devices/pda.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/phone.svg b/lisp/ui/icons/numix/standard/devices/phone.svg index 33bb6083e..e89eb9e02 100644 --- a/lisp/ui/icons/numix/standard/devices/phone.svg +++ b/lisp/ui/icons/numix/standard/devices/phone.svg @@ -1,12 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/printer.svg b/lisp/ui/icons/numix/standard/devices/printer.svg index 1fa337ab7..21e9c780c 100644 --- a/lisp/ui/icons/numix/standard/devices/printer.svg +++ b/lisp/ui/icons/numix/standard/devices/printer.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/scanner.svg b/lisp/ui/icons/numix/standard/devices/scanner.svg index 9727aa82e..8a89975d0 100644 --- a/lisp/ui/icons/numix/standard/devices/scanner.svg +++ b/lisp/ui/icons/numix/standard/devices/scanner.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/devices/video-display.svg b/lisp/ui/icons/numix/standard/devices/video-display.svg index 572e44c6e..7160ca3b9 100644 --- a/lisp/ui/icons/numix/standard/devices/video-display.svg +++ b/lisp/ui/icons/numix/standard/devices/video-display.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-default.svg b/lisp/ui/icons/numix/standard/emblems/emblem-default.svg index 4c11b824b..0c14c861e 100644 --- a/lisp/ui/icons/numix/standard/emblems/emblem-default.svg +++ b/lisp/ui/icons/numix/standard/emblems/emblem-default.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-documents.svg b/lisp/ui/icons/numix/standard/emblems/emblem-documents.svg index 3ecff8c24..d9aaa6495 100644 --- a/lisp/ui/icons/numix/standard/emblems/emblem-documents.svg +++ b/lisp/ui/icons/numix/standard/emblems/emblem-documents.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-downloads.svg b/lisp/ui/icons/numix/standard/emblems/emblem-downloads.svg index 45ad52395..d4ccc38d1 100644 --- a/lisp/ui/icons/numix/standard/emblems/emblem-downloads.svg +++ b/lisp/ui/icons/numix/standard/emblems/emblem-downloads.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-favorite.svg b/lisp/ui/icons/numix/standard/emblems/emblem-favorite.svg index b47735c9a..e5477990e 100644 --- a/lisp/ui/icons/numix/standard/emblems/emblem-favorite.svg +++ b/lisp/ui/icons/numix/standard/emblems/emblem-favorite.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-important.svg b/lisp/ui/icons/numix/standard/emblems/emblem-important.svg index 108c848e6..b5495e372 100644 --- a/lisp/ui/icons/numix/standard/emblems/emblem-important.svg +++ b/lisp/ui/icons/numix/standard/emblems/emblem-important.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-mail.svg b/lisp/ui/icons/numix/standard/emblems/emblem-mail.svg index 1df0c2b27..d49b62cfd 100644 --- a/lisp/ui/icons/numix/standard/emblems/emblem-mail.svg +++ b/lisp/ui/icons/numix/standard/emblems/emblem-mail.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-photos.svg b/lisp/ui/icons/numix/standard/emblems/emblem-photos.svg index 488fa231e..4a0382fc3 100644 --- a/lisp/ui/icons/numix/standard/emblems/emblem-photos.svg +++ b/lisp/ui/icons/numix/standard/emblems/emblem-photos.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-readonly.svg b/lisp/ui/icons/numix/standard/emblems/emblem-readonly.svg index 257fbbd3b..b2db2682a 100644 --- a/lisp/ui/icons/numix/standard/emblems/emblem-readonly.svg +++ b/lisp/ui/icons/numix/standard/emblems/emblem-readonly.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-shared.svg b/lisp/ui/icons/numix/standard/emblems/emblem-shared.svg index 2b84999ef..54ad73410 100644 --- a/lisp/ui/icons/numix/standard/emblems/emblem-shared.svg +++ b/lisp/ui/icons/numix/standard/emblems/emblem-shared.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-symbolic-link.svg b/lisp/ui/icons/numix/standard/emblems/emblem-symbolic-link.svg index d0cafe372..f79139de9 100644 --- a/lisp/ui/icons/numix/standard/emblems/emblem-symbolic-link.svg +++ b/lisp/ui/icons/numix/standard/emblems/emblem-symbolic-link.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-system.svg b/lisp/ui/icons/numix/standard/emblems/emblem-system.svg index 960a39e54..550585a4e 100644 --- a/lisp/ui/icons/numix/standard/emblems/emblem-system.svg +++ b/lisp/ui/icons/numix/standard/emblems/emblem-system.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-unreadable.svg b/lisp/ui/icons/numix/standard/emblems/emblem-unreadable.svg index 8686fa64d..bbfaf22ee 100644 --- a/lisp/ui/icons/numix/standard/emblems/emblem-unreadable.svg +++ b/lisp/ui/icons/numix/standard/emblems/emblem-unreadable.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/places/folder-remote.svg b/lisp/ui/icons/numix/standard/places/folder-remote.svg index e818bb32b..ca16aebec 100644 --- a/lisp/ui/icons/numix/standard/places/folder-remote.svg +++ b/lisp/ui/icons/numix/standard/places/folder-remote.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/places/folder.svg b/lisp/ui/icons/numix/standard/places/folder.svg index 5cab84a79..3d0f943ed 100644 --- a/lisp/ui/icons/numix/standard/places/folder.svg +++ b/lisp/ui/icons/numix/standard/places/folder.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/places/network-server.svg b/lisp/ui/icons/numix/standard/places/network-server.svg index 56a3850be..416ec6651 100644 --- a/lisp/ui/icons/numix/standard/places/network-server.svg +++ b/lisp/ui/icons/numix/standard/places/network-server.svg @@ -1,180 +1 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/places/network-workgroup.svg b/lisp/ui/icons/numix/standard/places/network-workgroup.svg index e818bb32b..ca16aebec 100644 --- a/lisp/ui/icons/numix/standard/places/network-workgroup.svg +++ b/lisp/ui/icons/numix/standard/places/network-workgroup.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/places/start-here.svg b/lisp/ui/icons/numix/standard/places/start-here.svg index 447309c02..8954fd91b 100644 --- a/lisp/ui/icons/numix/standard/places/start-here.svg +++ b/lisp/ui/icons/numix/standard/places/start-here.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/places/user-bookmarks.svg b/lisp/ui/icons/numix/standard/places/user-bookmarks.svg index f8fc711e1..55da9ae8e 100644 --- a/lisp/ui/icons/numix/standard/places/user-bookmarks.svg +++ b/lisp/ui/icons/numix/standard/places/user-bookmarks.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/places/user-desktop.svg b/lisp/ui/icons/numix/standard/places/user-desktop.svg index 1d1519e57..5368e4f82 100644 --- a/lisp/ui/icons/numix/standard/places/user-desktop.svg +++ b/lisp/ui/icons/numix/standard/places/user-desktop.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/places/user-home.svg b/lisp/ui/icons/numix/standard/places/user-home.svg index 09edd2617..78bae8522 100644 --- a/lisp/ui/icons/numix/standard/places/user-home.svg +++ b/lisp/ui/icons/numix/standard/places/user-home.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/places/user-trash.svg b/lisp/ui/icons/numix/standard/places/user-trash.svg index a2b35a4dd..ac3d1cc54 100644 --- a/lisp/ui/icons/numix/standard/places/user-trash.svg +++ b/lisp/ui/icons/numix/standard/places/user-trash.svg @@ -1,148 +1 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/appointment-missed.svg b/lisp/ui/icons/numix/standard/status/appointment-missed.svg index 1caaf868b..a33c69a0b 100644 --- a/lisp/ui/icons/numix/standard/status/appointment-missed.svg +++ b/lisp/ui/icons/numix/standard/status/appointment-missed.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/appointment-soon.svg b/lisp/ui/icons/numix/standard/status/appointment-soon.svg index 335f3b279..2ff619cbe 100644 --- a/lisp/ui/icons/numix/standard/status/appointment-soon.svg +++ b/lisp/ui/icons/numix/standard/status/appointment-soon.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/audio-volume-high.svg b/lisp/ui/icons/numix/standard/status/audio-volume-high.svg index b031b4ddf..de890ace0 100644 --- a/lisp/ui/icons/numix/standard/status/audio-volume-high.svg +++ b/lisp/ui/icons/numix/standard/status/audio-volume-high.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/audio-volume-low.svg b/lisp/ui/icons/numix/standard/status/audio-volume-low.svg index 947969ffc..852d7822a 100644 --- a/lisp/ui/icons/numix/standard/status/audio-volume-low.svg +++ b/lisp/ui/icons/numix/standard/status/audio-volume-low.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/audio-volume-medium.svg b/lisp/ui/icons/numix/standard/status/audio-volume-medium.svg index 11d1be462..e67619c11 100644 --- a/lisp/ui/icons/numix/standard/status/audio-volume-medium.svg +++ b/lisp/ui/icons/numix/standard/status/audio-volume-medium.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/audio-volume-muted.svg b/lisp/ui/icons/numix/standard/status/audio-volume-muted.svg index e06e01840..e62e7dae0 100644 --- a/lisp/ui/icons/numix/standard/status/audio-volume-muted.svg +++ b/lisp/ui/icons/numix/standard/status/audio-volume-muted.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/battery-caution.svg b/lisp/ui/icons/numix/standard/status/battery-caution.svg index f65e9fcf8..e4a958aa2 100644 --- a/lisp/ui/icons/numix/standard/status/battery-caution.svg +++ b/lisp/ui/icons/numix/standard/status/battery-caution.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/battery-low.svg b/lisp/ui/icons/numix/standard/status/battery-low.svg index cd5fa1f4d..176f09f4d 100644 --- a/lisp/ui/icons/numix/standard/status/battery-low.svg +++ b/lisp/ui/icons/numix/standard/status/battery-low.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/dialog-error.svg b/lisp/ui/icons/numix/standard/status/dialog-error.svg index 6a7b255fb..42d3465f9 100644 --- a/lisp/ui/icons/numix/standard/status/dialog-error.svg +++ b/lisp/ui/icons/numix/standard/status/dialog-error.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/dialog-password.svg b/lisp/ui/icons/numix/standard/status/dialog-password.svg index f44f031fa..32cbd1fad 100644 --- a/lisp/ui/icons/numix/standard/status/dialog-password.svg +++ b/lisp/ui/icons/numix/standard/status/dialog-password.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/dialog-warning.svg b/lisp/ui/icons/numix/standard/status/dialog-warning.svg index 4c5ca6de8..a55b8eff4 100644 --- a/lisp/ui/icons/numix/standard/status/dialog-warning.svg +++ b/lisp/ui/icons/numix/standard/status/dialog-warning.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/network-error.svg b/lisp/ui/icons/numix/standard/status/network-error.svg index 6ae9e1b15..a1beace45 100644 --- a/lisp/ui/icons/numix/standard/status/network-error.svg +++ b/lisp/ui/icons/numix/standard/status/network-error.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/network-idle.svg b/lisp/ui/icons/numix/standard/status/network-idle.svg index 7563e9509..1ca616a33 100644 --- a/lisp/ui/icons/numix/standard/status/network-idle.svg +++ b/lisp/ui/icons/numix/standard/status/network-idle.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/network-offline.svg b/lisp/ui/icons/numix/standard/status/network-offline.svg index d1ffe3080..9e3fb8d1b 100644 --- a/lisp/ui/icons/numix/standard/status/network-offline.svg +++ b/lisp/ui/icons/numix/standard/status/network-offline.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/network-receive.svg b/lisp/ui/icons/numix/standard/status/network-receive.svg index 00d215f25..f97752a03 100644 --- a/lisp/ui/icons/numix/standard/status/network-receive.svg +++ b/lisp/ui/icons/numix/standard/status/network-receive.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/network-transmit-receive.svg b/lisp/ui/icons/numix/standard/status/network-transmit-receive.svg index 7563e9509..1ca616a33 100644 --- a/lisp/ui/icons/numix/standard/status/network-transmit-receive.svg +++ b/lisp/ui/icons/numix/standard/status/network-transmit-receive.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/network-transmit.svg b/lisp/ui/icons/numix/standard/status/network-transmit.svg index 91c51fad7..5cfd3ec75 100644 --- a/lisp/ui/icons/numix/standard/status/network-transmit.svg +++ b/lisp/ui/icons/numix/standard/status/network-transmit.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/printer-printing.svg b/lisp/ui/icons/numix/standard/status/printer-printing.svg index b9f1eaad5..6b1e0f46c 100644 --- a/lisp/ui/icons/numix/standard/status/printer-printing.svg +++ b/lisp/ui/icons/numix/standard/status/printer-printing.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/security-high.svg b/lisp/ui/icons/numix/standard/status/security-high.svg index 01ccbbc93..a41eb9e54 100644 --- a/lisp/ui/icons/numix/standard/status/security-high.svg +++ b/lisp/ui/icons/numix/standard/status/security-high.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/security-low.svg b/lisp/ui/icons/numix/standard/status/security-low.svg index 9f8387051..85a523ac6 100644 --- a/lisp/ui/icons/numix/standard/status/security-low.svg +++ b/lisp/ui/icons/numix/standard/status/security-low.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/security-medium.svg b/lisp/ui/icons/numix/standard/status/security-medium.svg index 88b30f2a3..e7f083229 100644 --- a/lisp/ui/icons/numix/standard/status/security-medium.svg +++ b/lisp/ui/icons/numix/standard/status/security-medium.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/task-due.svg b/lisp/ui/icons/numix/standard/status/task-due.svg index 1d07c49f6..504b9c7b4 100644 --- a/lisp/ui/icons/numix/standard/status/task-due.svg +++ b/lisp/ui/icons/numix/standard/status/task-due.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/task-past-due.svg b/lisp/ui/icons/numix/standard/status/task-past-due.svg index 30417ef59..c94993907 100644 --- a/lisp/ui/icons/numix/standard/status/task-past-due.svg +++ b/lisp/ui/icons/numix/standard/status/task-past-due.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/user-available.svg b/lisp/ui/icons/numix/standard/status/user-available.svg index 7fd9950e1..7cc310098 100644 --- a/lisp/ui/icons/numix/standard/status/user-available.svg +++ b/lisp/ui/icons/numix/standard/status/user-available.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/user-away.svg b/lisp/ui/icons/numix/standard/status/user-away.svg index c347abcd1..f28b02e55 100644 --- a/lisp/ui/icons/numix/standard/status/user-away.svg +++ b/lisp/ui/icons/numix/standard/status/user-away.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/user-idle.svg b/lisp/ui/icons/numix/standard/status/user-idle.svg index c347abcd1..f28b02e55 100644 --- a/lisp/ui/icons/numix/standard/status/user-idle.svg +++ b/lisp/ui/icons/numix/standard/status/user-idle.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/numix/standard/status/user-offline.svg b/lisp/ui/icons/numix/standard/status/user-offline.svg index 83b0c7d3d..c3e1ab34b 100644 --- a/lisp/ui/icons/numix/standard/status/user-offline.svg +++ b/lisp/ui/icons/numix/standard/status/user-offline.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/application-exit.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/application-exit.svg index 4ce3586bf..609904248 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/application-exit.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/application-exit.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/appointment-new.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/appointment-new.svg index edbd1b307..254b2b683 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/appointment-new.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/appointment-new.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/call-start.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/call-start.svg index 55707cb19..6963d21ba 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/call-start.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/call-start.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/call-stop.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/call-stop.svg index 52ddc4164..b095d4745 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/call-stop.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/call-stop.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/contact-new.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/contact-new.svg index 4dd0e976b..30708e57e 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/contact-new.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/contact-new.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-new.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-new.svg index d4785033d..a49b1c9a0 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/document-new.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-new.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-open-recent.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-open-recent.svg index 417341b97..c5e792c55 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/document-open-recent.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-open-recent.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-open.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-open.svg index 6f60a7bc0..287220aa6 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/document-open.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-open.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-page-setup.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-page-setup.svg index 7fc782c8b..9cfc8f39a 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/document-page-setup.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-page-setup.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-print-preview.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-print-preview.svg index de476eb2b..c15d96de5 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/document-print-preview.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-print-preview.svg @@ -1,7 +1 @@ - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-print.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-print.svg index a256eb8df..7e8e588b2 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/document-print.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-print.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-properties.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-properties.svg index c420eede6..f185d91cc 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/document-properties.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-properties.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-revert.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-revert.svg index 4f4b56f2d..b08a5af6c 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/document-revert.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-revert.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-save-as.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-save-as.svg index f16966e68..98146a837 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/document-save-as.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-save-as.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-save.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-save.svg index 8627f4628..d74d3cfa1 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/document-save.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-save.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-send.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/document-send.svg index e3263dd45..ab260cbe0 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/document-send.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/document-send.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-clear.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-clear.svg index 44c585f15..b910266ad 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-clear.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-clear.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-copy.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-copy.svg index 496df2df9..50af10b03 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-copy.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-copy.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-delete.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-delete.svg index 0e40d184e..48cd5c331 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-delete.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-delete.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-find-replace.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-find-replace.svg index b7c16b42a..97fbb40fb 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-find-replace.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-find-replace.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-find.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-find.svg index d780a6dc1..38812ebe8 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-find.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-find.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-paste.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-paste.svg index b7dd3cb9b..4c9e5617a 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-paste.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-paste.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-redo.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-redo.svg index e3d70fa7b..1e4a5c0b7 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-redo.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-redo.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-select-all.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-select-all.svg index dd8ea78c1..b8eb41951 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-select-all.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-select-all.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-undo.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-undo.svg index e663517d6..35aa4206f 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-undo.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/edit-undo.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/folder-new.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/folder-new.svg index bdcb6042a..9e94f39ba 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/folder-new.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/folder-new.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-less.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-less.svg index 0e9ef6d32..a064aad15 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-less.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-less.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-more.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-more.svg index e81aa4c06..ec00f9406 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-more.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-more.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-center.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-center.svg index 1e8493990..c7dcb7fc4 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-center.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-center.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-fill.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-fill.svg index 2ec893f17..6ca7bfaa8 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-fill.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-fill.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-left.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-left.svg index 2d49c8471..d9d3c9b7d 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-left.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-left.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-right.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-right.svg index 44c362619..a50cb6e67 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-right.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-right.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-bold.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-bold.svg index f1af01a41..6c2abd4e3 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-bold.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-bold.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-ltr.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-ltr.svg index bc367f7c0..a44321c14 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-ltr.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-ltr.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-rtl.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-rtl.svg index 79a23e2d4..c8ac59893 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-rtl.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-rtl.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-italic.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-italic.svg index 37d7108de..f5bf4cb66 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-italic.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-italic.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-strikethrough.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-strikethrough.svg index ab9a9dc50..809782c17 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-strikethrough.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-strikethrough.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-underline.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-underline.svg index effa1f6a3..86c35e7d9 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-underline.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-underline.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-bottom.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-bottom.svg index 4d91b1917..3b0913235 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/go-bottom.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-bottom.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-down.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-down.svg index 00b39aef7..4af385f38 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/go-down.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-down.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-first.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-first.svg index 4c45db77a..5d78b1805 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/go-first.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-first.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-home.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-home.svg index 7bc5c2d26..ed2b2defd 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/go-home.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-home.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-jump.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-jump.svg index 24d8cf5f7..e4ac948da 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/go-jump.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-jump.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-last.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-last.svg index 7e7038d47..ebc596977 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/go-last.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-last.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-next.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-next.svg index b47909357..349a62d58 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/go-next.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-next.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-previous.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-previous.svg index 16ef162a4..e19203391 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/go-previous.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-previous.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-top.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-top.svg index 39ce644e3..f453c58e5 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/go-top.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-top.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-up.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/go-up.svg index 613c192f2..bc89e9806 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/go-up.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/go-up.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/help-about.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/help-about.svg index a70617e77..b5f4d7f40 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/help-about.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/help-about.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/insert-image.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/insert-image.svg index e6b7d5fde..5960c1143 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/insert-image.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/insert-image.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/insert-link.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/insert-link.svg index bd7f26bae..41a47a4db 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/insert-link.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/insert-link.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/insert-object.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/insert-object.svg index 88632c07e..757b3bdd3 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/insert-object.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/insert-object.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/insert-text.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/insert-text.svg index b095d9d07..9dde74e98 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/insert-text.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/insert-text.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/list-add.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/list-add.svg index 85d31614e..1fa391c7c 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/list-add.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/list-add.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/list-remove.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/list-remove.svg index 4cf55b397..ff6657208 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/list-remove.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/list-remove.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-forward.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-forward.svg index fe0023ae6..5411527bf 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-forward.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-forward.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-important.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-important.svg index 6272d3ea8..acb50ddfd 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-important.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-important.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-junk.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-junk.svg index b414cc04b..9a5456ec6 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-junk.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-junk.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-notjunk.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-notjunk.svg index dfdbce898..f98e36d9b 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-notjunk.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-notjunk.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-read.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-read.svg index 2d109a8c7..6426ef99a 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-read.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-read.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-unread.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-unread.svg index 14ad61641..90fdbaf75 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-unread.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-unread.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-message-new.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-message-new.svg index 14ad61641..90fdbaf75 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-message-new.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-message-new.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-all.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-all.svg index e9fbb6720..01234575c 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-all.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-all.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-sender.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-sender.svg index ff8f1f70b..60007b570 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-sender.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-sender.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-send-receive.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-send-receive.svg index c29244e02..6bf988e39 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-send-receive.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-send-receive.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-send.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-send.svg index e3263dd45..ab260cbe0 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-send.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/mail-send.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-eject.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-eject.svg index 4cd574e67..5e1a1359e 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/media-eject.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-eject.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-pause.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-pause.svg index a4b3ae0b5..59f205c2b 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-pause.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-pause.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-start.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-start.svg index 891f6ad60..e72965424 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-start.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-start.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-stop.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-stop.svg index 4c899eb29..35948a37b 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-stop.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-stop.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-record.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-record.svg index 2031186ac..9da42dcfa 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/media-record.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-record.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-backward.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-backward.svg index 2b629317e..dceba05b9 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-backward.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-backward.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-forward.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-forward.svg index be9a09a9f..630fa1653 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-forward.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-forward.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-backward.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-backward.svg index 64d17f65f..9326f7204 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-backward.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-backward.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-forward.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-forward.svg index 82b864de9..7c6210d3b 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-forward.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-forward.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-horizontal.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-horizontal.svg index f78bb04e0..6b2df07fe 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-horizontal.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-horizontal.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-vertical.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-vertical.svg index 94491b380..2b15fe924 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-vertical.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-vertical.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-left.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-left.svg index 1a2ddcfa0..50ce6e10e 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-left.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-left.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-right.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-right.svg index 2cca1c8ef..9e077e2a7 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-right.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-right.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/process-stop.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/process-stop.svg index 223335ada..c35b008b8 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/process-stop.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/process-stop.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/system-lock-screen.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/system-lock-screen.svg index d9cbaae8d..f53cd1700 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/system-lock-screen.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/system-lock-screen.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/system-log-out.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/system-log-out.svg index a358882ab..eb0b25a1a 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/system-log-out.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/system-log-out.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/system-search.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/system-search.svg index d780a6dc1..38812ebe8 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/system-search.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/system-search.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/system-shutdown.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/system-shutdown.svg index 32c5192bb..14e92d22e 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/system-shutdown.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/system-shutdown.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/tools-check-spelling.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/tools-check-spelling.svg index 6f832a494..17e8b6ea7 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/tools-check-spelling.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/tools-check-spelling.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/view-fullscreen.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/view-fullscreen.svg index 06e69570f..f8101065d 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/view-fullscreen.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/view-fullscreen.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/view-refresh.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/view-refresh.svg index d3620ab1d..bfa2a3e07 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/view-refresh.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/view-refresh.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/view-restore.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/view-restore.svg index 866e56060..66f1bd974 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/view-restore.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/view-restore.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-ascending.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-ascending.svg index 4cfaca60b..b8eef499e 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-ascending.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-ascending.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-descending.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-descending.svg index d9fdb74e1..92f4cedd1 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-descending.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-descending.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/window-close.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/window-close.svg index bbc2fa293..873182ad2 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/window-close.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/window-close.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-fit-best.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-fit-best.svg index 0f32dbcf5..7c3a4569b 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-fit-best.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-fit-best.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-in.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-in.svg index 3ce5a14e7..f1c6695b3 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-in.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-in.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-original.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-original.svg index 0b3d80fd4..61c65e3a2 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-original.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-original.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-out.svg b/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-out.svg index 268200ef8..e8cea04c3 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-out.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-out.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-engineering.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-engineering.svg index 3bfec67ef..b680eb12d 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-engineering.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-engineering.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-graphics.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-graphics.svg index a8acef64d..1d62dd473 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-graphics.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-graphics.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-multimedia.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-multimedia.svg index 789042132..422c2d382 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-multimedia.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-multimedia.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-science.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-science.svg index db38041e3..fdaaa5841 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-science.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-science.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-utilities.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-utilities.svg index 0627a581c..a3c1ff4d3 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-utilities.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/applications-utilities.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/preferences-other.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/preferences-other.svg index ed25d073e..8ca71f517 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/categories/preferences-other.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/preferences-other.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/system-help.svg b/lisp/ui/icons/papirus-symbolic/standard/categories/system-help.svg index 79a2689fd..5eea82cdb 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/categories/system-help.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/categories/system-help.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/audio-card.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/audio-card.svg index 9d6daaf9c..817a3cce6 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/audio-card.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/audio-card.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/audio-input-microphone.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/audio-input-microphone.svg index 8892b5882..370a47bef 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/audio-input-microphone.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/audio-input-microphone.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/battery.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/battery.svg index 6891d88d5..39b10757b 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/battery.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/battery.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/camera-photo.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/camera-photo.svg index 70fd8bccb..8e3df810a 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/camera-photo.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/camera-photo.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/camera-video.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/camera-video.svg index 0fdd47696..59ff701e6 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/camera-video.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/camera-video.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/camera-web.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/camera-web.svg index 8690521f2..eeef1ba81 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/camera-web.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/camera-web.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/computer.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/computer.svg index 8b919c306..a8c3a590c 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/computer.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/computer.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/drive-harddisk.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/drive-harddisk.svg index b37309dfe..77e2199d5 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/drive-harddisk.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/drive-harddisk.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/drive-optical.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/drive-optical.svg index 75037c614..731aed592 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/drive-optical.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/drive-optical.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/drive-removable-media.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/drive-removable-media.svg index 53bcecf7c..5a8bb45f8 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/drive-removable-media.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/drive-removable-media.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/input-gaming.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/input-gaming.svg index cc6e412e3..13a28edfc 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/input-gaming.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/input-gaming.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/input-keyboard.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/input-keyboard.svg index 3f17833bc..d7e993d3a 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/input-keyboard.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/input-keyboard.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/input-mouse.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/input-mouse.svg index 2b9453fc0..ce5ddfe42 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/input-mouse.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/input-mouse.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/input-tablet.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/input-tablet.svg index 756578a77..f0de4ade5 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/input-tablet.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/input-tablet.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/media-flash.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/media-flash.svg index 37ea323d0..eb5e2bc4a 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/media-flash.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/media-flash.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/media-floppy.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/media-floppy.svg index 2b4c35fd9..9654611f9 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/media-floppy.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/media-floppy.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/media-tape.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/media-tape.svg index 997bac855..5f40bb21c 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/media-tape.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/media-tape.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/multimedia-player.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/multimedia-player.svg index c3f855c39..4cdc61843 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/multimedia-player.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/multimedia-player.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/network-wired.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/network-wired.svg index 8998dc3da..f45e8f962 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/network-wired.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/network-wired.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/network-wireless.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/network-wireless.svg index 2d50d0adf..313a60fce 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/network-wireless.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/network-wireless.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/phone.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/phone.svg index ff1bc0689..b2932d846 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/phone.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/phone.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/printer.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/printer.svg index f43605b76..09c122678 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/printer.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/printer.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/scanner.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/scanner.svg index 040d3f6e1..3edef53ed 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/scanner.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/scanner.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/video-display.svg b/lisp/ui/icons/papirus-symbolic/standard/devices/video-display.svg index 3bbd5767e..3fde695b4 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/devices/video-display.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/devices/video-display.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-default.svg b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-default.svg index 2e7628ce0..e90e74bba 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-default.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-default.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-documents.svg b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-documents.svg index 2cda9bbe2..ea342230b 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-documents.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-documents.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-favorite.svg b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-favorite.svg index 17cd59f94..d157404f6 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-favorite.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-favorite.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-important.svg b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-important.svg index b68895098..db097bd63 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-important.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-important.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-photos.svg b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-photos.svg index a1a3343b2..b4b3248f7 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-photos.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-photos.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-shared.svg b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-shared.svg index a9dad4e6d..731cfda79 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-shared.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-shared.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/folder-remote.svg b/lisp/ui/icons/papirus-symbolic/standard/places/folder-remote.svg index 8adfb04eb..65fb87096 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/places/folder-remote.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/places/folder-remote.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/folder.svg b/lisp/ui/icons/papirus-symbolic/standard/places/folder.svg index aa1d06f31..bdaebb92b 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/places/folder.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/places/folder.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/start-here.svg b/lisp/ui/icons/papirus-symbolic/standard/places/start-here.svg index d10d36ed0..020224146 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/places/start-here.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/places/start-here.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/user-bookmarks.svg b/lisp/ui/icons/papirus-symbolic/standard/places/user-bookmarks.svg index 26ebd2b34..87637cf24 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/places/user-bookmarks.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/places/user-bookmarks.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/user-desktop.svg b/lisp/ui/icons/papirus-symbolic/standard/places/user-desktop.svg index 86fba3cc5..a63971595 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/places/user-desktop.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/places/user-desktop.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/user-home.svg b/lisp/ui/icons/papirus-symbolic/standard/places/user-home.svg index 7bc5c2d26..ed2b2defd 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/places/user-home.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/places/user-home.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/user-trash.svg b/lisp/ui/icons/papirus-symbolic/standard/places/user-trash.svg index 15c0b403c..04ca4b816 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/places/user-trash.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/places/user-trash.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/appointment-missed.svg b/lisp/ui/icons/papirus-symbolic/standard/status/appointment-missed.svg index 4ead036a8..c27251d03 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/appointment-missed.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/appointment-missed.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/appointment-soon.svg b/lisp/ui/icons/papirus-symbolic/standard/status/appointment-soon.svg index 417341b97..c5e792c55 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/appointment-soon.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/appointment-soon.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-high.svg b/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-high.svg index 25a7d7a1b..c63c39ea5 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-high.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-high.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-low.svg b/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-low.svg index 84de0effb..612bfbb1a 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-low.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-low.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-medium.svg b/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-medium.svg index 9adb0a84e..801b0692d 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-medium.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-medium.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-muted.svg b/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-muted.svg index 9a97be2a3..124d42d6c 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-muted.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-muted.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/battery-caution.svg b/lisp/ui/icons/papirus-symbolic/standard/status/battery-caution.svg index 28ade726e..2680524ef 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/battery-caution.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/battery-caution.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/battery-low.svg b/lisp/ui/icons/papirus-symbolic/standard/status/battery-low.svg index f3b8399a1..9881c65a0 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/battery-low.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/battery-low.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/dialog-error.svg b/lisp/ui/icons/papirus-symbolic/standard/status/dialog-error.svg index c88df16af..de97af4a3 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/dialog-error.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/dialog-error.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/dialog-information.svg b/lisp/ui/icons/papirus-symbolic/standard/status/dialog-information.svg index b697d90bc..b5c14fda1 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/dialog-information.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/dialog-information.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/dialog-warning.svg b/lisp/ui/icons/papirus-symbolic/standard/status/dialog-warning.svg index cad9346aa..86524985b 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/dialog-warning.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/dialog-warning.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/folder-drag-accept.svg b/lisp/ui/icons/papirus-symbolic/standard/status/folder-drag-accept.svg index 94cacbf43..ae2717b6b 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/folder-drag-accept.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/folder-drag-accept.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/folder-open.svg b/lisp/ui/icons/papirus-symbolic/standard/status/folder-open.svg index 40ececd58..3e8767829 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/folder-open.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/folder-open.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/folder-visiting.svg b/lisp/ui/icons/papirus-symbolic/standard/status/folder-visiting.svg index e728a1187..476221a9b 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/folder-visiting.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/folder-visiting.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/image-loading.svg b/lisp/ui/icons/papirus-symbolic/standard/status/image-loading.svg index 9dab356b4..8abf7988a 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/image-loading.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/image-loading.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/mail-attachment.svg b/lisp/ui/icons/papirus-symbolic/standard/status/mail-attachment.svg index 184f5c6b3..bcf71298a 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/mail-attachment.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/mail-attachment.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/mail-read.svg b/lisp/ui/icons/papirus-symbolic/standard/status/mail-read.svg index 2d109a8c7..6426ef99a 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/mail-read.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/mail-read.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/mail-replied.svg b/lisp/ui/icons/papirus-symbolic/standard/status/mail-replied.svg index ff8f1f70b..60007b570 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/mail-replied.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/mail-replied.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/mail-unread.svg b/lisp/ui/icons/papirus-symbolic/standard/status/mail-unread.svg index 14ad61641..90fdbaf75 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/mail-unread.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/mail-unread.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-repeat.svg b/lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-repeat.svg index 01de78092..79b0cf7e8 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-repeat.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-repeat.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-shuffle.svg b/lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-shuffle.svg index 46a5d394a..c3466668c 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-shuffle.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-shuffle.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-error.svg b/lisp/ui/icons/papirus-symbolic/standard/status/network-error.svg index 25e3c8361..e42e9fcdb 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/network-error.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/network-error.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-idle.svg b/lisp/ui/icons/papirus-symbolic/standard/status/network-idle.svg index 80f72f284..6841ba161 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/network-idle.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/network-idle.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-offline.svg b/lisp/ui/icons/papirus-symbolic/standard/status/network-offline.svg index ee0cc9ac5..b5b7e9204 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/network-offline.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/network-offline.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-receive.svg b/lisp/ui/icons/papirus-symbolic/standard/status/network-receive.svg index a28bfae65..5f7b2d55e 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/network-receive.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/network-receive.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-transmit-receive.svg b/lisp/ui/icons/papirus-symbolic/standard/status/network-transmit-receive.svg index ad89976eb..8fdad752e 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/network-transmit-receive.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/network-transmit-receive.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-transmit.svg b/lisp/ui/icons/papirus-symbolic/standard/status/network-transmit.svg index 200352e7b..423b3e1d3 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/network-transmit.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/network-transmit.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/printer-error.svg b/lisp/ui/icons/papirus-symbolic/standard/status/printer-error.svg index 2cb4e980c..ada96bd90 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/printer-error.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/printer-error.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/printer-printing.svg b/lisp/ui/icons/papirus-symbolic/standard/status/printer-printing.svg index 3291de3a4..63edcdb87 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/printer-printing.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/printer-printing.svg @@ -1,6 +1 @@ - - - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/security-high.svg b/lisp/ui/icons/papirus-symbolic/standard/status/security-high.svg index 5907e65c2..a907d3054 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/security-high.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/security-high.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/security-low.svg b/lisp/ui/icons/papirus-symbolic/standard/status/security-low.svg index a75d544db..23b74dd4a 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/security-low.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/security-low.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/security-medium.svg b/lisp/ui/icons/papirus-symbolic/standard/status/security-medium.svg index aa0ed1cee..9c9b99e36 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/security-medium.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/security-medium.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/software-update-available.svg b/lisp/ui/icons/papirus-symbolic/standard/status/software-update-available.svg index 614160806..c25383dcd 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/software-update-available.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/software-update-available.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/software-update-urgent.svg b/lisp/ui/icons/papirus-symbolic/standard/status/software-update-urgent.svg index 71526c1dc..c7ac5b7f3 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/software-update-urgent.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/software-update-urgent.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/task-due.svg b/lisp/ui/icons/papirus-symbolic/standard/status/task-due.svg index 76e218988..17fb8cc89 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/task-due.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/task-due.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/task-past-due.svg b/lisp/ui/icons/papirus-symbolic/standard/status/task-past-due.svg index 6b670fbbe..43da2565f 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/task-past-due.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/task-past-due.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/user-available.svg b/lisp/ui/icons/papirus-symbolic/standard/status/user-available.svg index 5b14b34cc..ece148e54 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/user-available.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/user-available.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/user-away.svg b/lisp/ui/icons/papirus-symbolic/standard/status/user-away.svg index e68d63b6d..c43b2b833 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/user-away.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/user-away.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/user-idle.svg b/lisp/ui/icons/papirus-symbolic/standard/status/user-idle.svg index d5ecb7054..4edc9a0ea 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/user-idle.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/user-idle.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/user-offline.svg b/lisp/ui/icons/papirus-symbolic/standard/status/user-offline.svg index d5ecb7054..4edc9a0ea 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/user-offline.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/user-offline.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/user-trash-full.svg b/lisp/ui/icons/papirus-symbolic/standard/status/user-trash-full.svg index b194dd77d..99cc4ef88 100644 --- a/lisp/ui/icons/papirus-symbolic/standard/status/user-trash-full.svg +++ b/lisp/ui/icons/papirus-symbolic/standard/status/user-trash-full.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file From 474801b501c45b90aa8aa95fbf06719f62b340e1 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 8 Mar 2018 15:35:38 +0100 Subject: [PATCH 094/333] Minor fix --- lisp/layouts/list_layout/listwidgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/layouts/list_layout/listwidgets.py b/lisp/layouts/list_layout/listwidgets.py index 39cedfcd1..f81925d83 100644 --- a/lisp/layouts/list_layout/listwidgets.py +++ b/lisp/layouts/list_layout/listwidgets.py @@ -48,7 +48,7 @@ def _start(self): self.setPixmap(IconTheme.get('led-running').pixmap(self.SIZE)) def _pause(self): - self.setPixmap(IconTheme.get('led-pause',).pixmap(self.SIZE)) + self.setPixmap(IconTheme.get('led-pause').pixmap(self.SIZE)) def _error(self): self.setPixmap(IconTheme.get('led-error').pixmap(self.SIZE)) From 3818e88d73b2fbd9bc21084dc30a71ddfb04c0ad Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 21 Mar 2018 09:37:54 +0100 Subject: [PATCH 095/333] Refactoring of settings/configuration UI classes Add: new "DictNode" class, can be used to create trees Add: new "TreeMultiSettingsPage" class, allow to build multilevel setting UI Update: refactored class related to the app-configuration UI Update: renamed some qt-based class methods to camelCase to keep consistency with the framework Fix: layout-selection and error-dialogs have now their parent set correctly --- lisp/application.py | 10 +- lisp/core/configuration.py | 154 ++++++++------ lisp/core/dicttree.py | 146 +++++++++++++ lisp/cues/cue.py | 4 +- lisp/default.json | 4 +- .../cart_layout/cart_layout_settings.py | 54 +++-- lisp/layouts/cart_layout/layout.py | 4 +- lisp/layouts/list_layout/layout.py | 3 +- .../list_layout/list_layout_settings.py | 92 ++++---- lisp/plugins/action_cues/collection_cue.py | 4 +- lisp/plugins/action_cues/command_cue.py | 6 +- lisp/plugins/action_cues/index_action_cue.py | 6 +- lisp/plugins/action_cues/midi_cue.py | 4 +- lisp/plugins/action_cues/osc_cue.py | 6 +- lisp/plugins/action_cues/seek_cue.py | 6 +- lisp/plugins/action_cues/stop_all.py | 6 +- lisp/plugins/action_cues/volume_control.py | 6 +- .../plugins/controller/controller_settings.py | 12 +- lisp/plugins/controller/protocols/keyboard.py | 6 +- lisp/plugins/controller/protocols/midi.py | 6 +- lisp/plugins/controller/protocols/osc.py | 6 +- lisp/plugins/gst_backend/gst_backend.py | 3 +- .../plugins/gst_backend/gst_media_settings.py | 18 +- lisp/plugins/gst_backend/gst_settings.py | 20 +- .../plugins/gst_backend/settings/alsa_sink.py | 6 +- .../gst_backend/settings/audio_dynamic.py | 6 +- .../plugins/gst_backend/settings/audio_pan.py | 6 +- lisp/plugins/gst_backend/settings/db_meter.py | 4 +- .../gst_backend/settings/equalizer10.py | 6 +- .../plugins/gst_backend/settings/jack_sink.py | 6 +- lisp/plugins/gst_backend/settings/pitch.py | 6 +- .../gst_backend/settings/preset_src.py | 6 +- lisp/plugins/gst_backend/settings/speed.py | 6 +- .../plugins/gst_backend/settings/uri_input.py | 6 +- .../gst_backend/settings/user_element.py | 6 +- lisp/plugins/gst_backend/settings/volume.py | 6 +- lisp/plugins/midi/midi.py | 3 +- lisp/plugins/midi/midi_settings.py | 44 ++-- lisp/plugins/osc/osc.py | 3 +- lisp/plugins/osc/osc_settings.py | 30 +-- lisp/plugins/timecode/settings.py | 40 ++-- lisp/plugins/timecode/timecode.py | 4 +- lisp/plugins/triggers/triggers_settings.py | 4 +- lisp/ui/layoutselect.py | 18 +- lisp/ui/logging/dialog.py | 4 +- lisp/ui/mainwindow.py | 5 +- lisp/ui/settings/app_settings.py | 131 ++++++------ lisp/ui/settings/cue_settings.py | 12 +- lisp/ui/settings/pages/app_general.py | 68 +++--- lisp/ui/settings/pages/cue_app_settings.py | 42 ++-- lisp/ui/settings/pages/cue_appearance.py | 6 +- lisp/ui/settings/pages/cue_general.py | 6 +- lisp/ui/settings/pages/media_cue_settings.py | 6 +- lisp/ui/settings/pages/plugins_settings.py | 7 +- lisp/ui/settings/pages_tree.py | 201 ++++++++++++++++++ lisp/ui/settings/settings_page.py | 117 +++++++++- 56 files changed, 946 insertions(+), 461 deletions(-) create mode 100644 lisp/core/dicttree.py create mode 100644 lisp/ui/settings/pages_tree.py diff --git a/lisp/application.py b/lisp/application.py index 6eec758eb..eab4a8359 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -58,9 +58,11 @@ def __init__(self, app_conf): self.__session = None # Register general settings widget - AppSettings.register_settings_widget(AppGeneral, self.conf) - AppSettings.register_settings_widget(CueAppSettings, self.conf) - AppSettings.register_settings_widget(PluginsSettings, self.conf) + AppSettings.registerSettingsWidget('general', AppGeneral, self.conf) + AppSettings.registerSettingsWidget( + 'general.cue', CueAppSettings, self.conf) + AppSettings.registerSettingsWidget( + 'plugins', PluginsSettings, self.conf) # Register common cue-settings widgets CueSettingsRegistry().add_item(CueGeneralSettings, Cue) @@ -115,7 +117,7 @@ def _new_session_dialog(self): """Show the layout-selection dialog""" try: # Prompt the user for a new layout - dialog = LayoutSelect() + dialog = LayoutSelect(parent=self.__main_window) if dialog.exec_() == QDialog.Accepted: # If a valid file is selected load it, otherwise load the layout if exists(dialog.filepath): diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index e9aa125cf..5637f94c3 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -29,99 +29,95 @@ from lisp import DEFAULT_APP_CONFIG, USER_APP_CONFIG from lisp.core.signal import Signal - -# Used to indicate the default behaviour when a specific option is not found to -# raise an exception. Created to enable `None` as a valid fallback value. from lisp.core.singleton import ABCSingleton +from lisp.core.util import deep_update + +logger = logging.getLogger(__name__) _UNSET = object() -logger = logging.getLogger(__name__) +class ConfDictError(Exception): + pass -class Configuration(metaclass=ABCMeta): - """ABC for a dictionary-based configuration object. - Subclasses need to implement `read` and `write` methods. - Keep in mind that the `set` and `update` methods ignores non-existing keys. - """ +class ConfDict: + """Allow to access nested-dictionaries values using "paths".""" - def __init__(self): - self._root = {} - self.changed = Signal() + def __init__(self, root=None, sep='.'): + if not sep: + raise ValueError('ConfDict separator cannot be empty') + if not isinstance(sep, str): + raise TypeError( + 'ConfDict separator must be a str, not {}'.format( + type(sep).__name__) + ) - @abstractmethod - def read(self): - pass + self._sep = sep - @abstractmethod - def write(self): - pass + if root is None: + self._root = {} + elif isinstance(root, dict): + self._root = root + else: + raise TypeError( + 'ConfDict root must be a dict, not {}'.format( + type(root).__name__) + ) def get(self, path, default=_UNSET): try: - node, key = self.__traverse(path.split('.'), self._root) + node, key = self.__traverse(self.sp(path), self._root) return node[key] - except (KeyError, TypeError) as e: + except (KeyError, TypeError): if default is not _UNSET: - logger.warning( - 'Invalid key "{}" in get operation, default used.' - .format(path) - ) + logger.debug('Invalid path, return default: {}'.format(path)) return default - raise e + raise ConfDictError('invalid path') def set(self, path, value): try: - node, key = self.__traverse(path.split('.'), self._root) - old, node[key] = node[key], value - - self.changed.emit(path, old, value) + node, key = self.__traverse(self.sp(path), self._root, set_=True) + node[key] = value except (KeyError, TypeError): - logger.warning( - 'Invalid key "{}" in set operation, ignored.' - .format(path) - ) - - def __traverse(self, keys, root): - next_step = keys.pop(0) + raise ConfDictError('invalid path') - if keys: - return self.__traverse(keys, root[next_step]) - - return root, next_step + def pop(self, path): + try: + node, key = self.__traverse(self.sp(path), self._root) + return node.pop(key) + except (KeyError, TypeError): + raise ConfDictError('invalid path') def update(self, new_conf): - """Update the current configuration using the given dictionary. + """Update the ConfDict using the given dictionary. - :param new_conf: the new configuration (can be partial) + :param new_conf: a dict containing the new values :type new_conf: dict """ - self.__update(self._root, new_conf) + deep_update(self._root, new_conf) - def __update(self, root, new_conf, _path=''): - """Recursively update the current configuration.""" - for key, value in new_conf.items(): - if key in root: - _path = self.jp(_path, key) + def deep_copy(self): + """Return a deep-copy of the internal dictionary.""" + return deepcopy(self._root) - if isinstance(root[key], dict): - self.__update(root[key], value, _path) - else: - old, root[key] = root[key], value - self.changed.emit(_path[1:], old, value) + def jp(self, *paths): + return self._sep.join(paths) - def copy(self): - """Return a deep-copy of the internal dictionary. + def sp(self, path): + return path.split(self._sep) - :rtype: dict - """ - return deepcopy(self._root) + def __traverse(self, keys, root, set_=False): + next_step = keys.pop(0) - @staticmethod - def jp(*paths): - return '.'.join(paths) + if keys: + if set_ and next_step not in root: + root[next_step] = {} + + return self.__traverse(keys, root[next_step]) + + return root, next_step def __getitem__(self, path): return self.get(path) @@ -129,14 +125,45 @@ def __getitem__(self, path): def __setitem__(self, path, value): self.set(path, value) + def __delitem__(self, path): + self.pop(path) + def __contains__(self, path): try: - node, key = self.__traverse(path.split('.'), self._root) + node, key = self.__traverse(path.split(self._sep), self._root) return key in node except (KeyError, TypeError): return False +class Configuration(ConfDict, metaclass=ABCMeta): + """ABC for configuration objects. + + Subclasses need to implement `read` and `write` methods. + """ + + def __init__(self, root=None): + super().__init__(root=root) + self.changed = Signal() + self.updated = Signal() + + @abstractmethod + def read(self): + pass + + @abstractmethod + def write(self): + pass + + def set(self, path, value): + super().set(path, value) + self.changed.emit(path, value) + + def update(self, new_conf): + super().update(new_conf) + self.updated.emit() + + class DummyConfiguration(Configuration): """Configuration without read/write capabilities.""" @@ -179,6 +206,9 @@ def write(self): with open(self.user_path, 'w') as f: json.dump(self._root, f, indent=True) + logger.debug( + 'Configuration written at {}'.format(self.user_path)) + def _check_file(self): """Ensure the last configuration is present at the user-path position""" if path.exists(self.user_path): diff --git a/lisp/core/dicttree.py b/lisp/core/dicttree.py new file mode 100644 index 000000000..6d95cd88b --- /dev/null +++ b/lisp/core/dicttree.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +# Used to indicate the default behaviour when a specific option is not found to +# raise an exception. Created to enable `None` as a valid fallback value. + +_UNSET = object() + + +class DictTreeError(Exception): + pass + + +class DictNode: + Sep = '.' + + def __init__(self, value=None, parent=None): + self.parent = parent + self.value = value + self.name = None + + self._children = {} + + @property + def children(self): + return self._children.values() + + def add_child(self, node, name): + if not isinstance(node, DictNode): + raise TypeError( + 'DictNode children must be a DictNode, not {}'.format( + type(node).__name__) + ) + if not isinstance(name, str): + raise TypeError( + 'DictNode name must be a str, not {}'.format( + type(node).__name__) + ) + if self.Sep in name: + raise DictTreeError( + 'DictNode name cannot contains the path separator') + + # Set node name and parent + node.name = name + node.parent = self + # Add the node to the children dictionary + self._children[name] = node + + def get(self, path, default=_UNSET): + if isinstance(path, str): + path = self.sp(path) + + try: + child_key = path.pop(0) + if path: + return self._children[child_key].get(path, default=default) + + return self._children[child_key].value + except (KeyError, TypeError): + if default is not _UNSET: + return default + + raise DictTreeError('invalid path') + + def set(self, path, value): + if isinstance(path, str): + path = self.sp(path) + + try: + child_key = path.pop(0) + if child_key not in self._children: + self.add_child(DictNode(), child_key) + + if path: + self._children[child_key].set(path, value) + else: + self._children[child_key].value = value + except (KeyError, TypeError): + raise DictTreeError('invalid path') + + def pop(self, path): + if isinstance(path, str): + path = self.sp(path) + + try: + child_key = path.pop(0) + if path: + self._children[child_key].pop(path) + else: + self._children.pop(child_key) + except (KeyError, TypeError): + raise DictTreeError('Invalid path') + + def path(self): + if self.parent is not None: + pp = self.parent.path() + if pp: + return self.jp(pp, self.name) + else: + return self.name + + return '' + + @classmethod + def jp(cls, *paths): + return cls.Sep.join(paths) + + @classmethod + def sp(cls, path): + return path.split(cls.Sep) + + def __getitem__(self, path): + return self.get(path) + + def __setitem__(self, path, value): + self.set(path, value) + + def __delitem__(self, path): + self.pop(path) + + def __contains__(self, path): + try: + path = self.sp(path) + child_key = path.pop(0) + if path: + return path in self._children[child_key] + + return child_key in self._children + except (KeyError, TypeError): + return False diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 34568f5fc..77de6fad5 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -204,13 +204,13 @@ def execute(self, action=CueAction.Default): elif action is CueAction.FadeOutPause: self.pause(fade=self.fadeout_duration > 0) elif action is CueAction.FadeOut: - duration = AppConfig().get('cue.fadeActionDuration', 0) + duration = AppConfig().get('cue.fadeAction', 0) fade = AppConfig().get( 'cue.fadeActionType', FadeOutType.Linear.name) self.fadeout(duration, FadeOutType[fade]) elif action is CueAction.FadeIn: - duration = AppConfig().get('cue.fadeActionDuration', 0) + duration = AppConfig().get('cue.fadeAction', 0) fade = AppConfig().get( 'cue.fadeActionType', FadeInType.Linear.name) diff --git a/lisp/default.json b/lisp/default.json index 3897734b4..876b3e8fc 100644 --- a/lisp/default.json +++ b/lisp/default.json @@ -1,7 +1,7 @@ { - "_version_": "0.6dev.9", + "_version_": "0.6dev.10", "cue": { - "fadeActionDuration": 3, + "fadeAction": 3, "fadeActionType": "Linear", "interruptFade": 3, "interruptFadeType": "Linear" diff --git a/lisp/layouts/cart_layout/cart_layout_settings.py b/lisp/layouts/cart_layout/cart_layout_settings.py index 0dae0b6b3..81804213d 100644 --- a/lisp/layouts/cart_layout/cart_layout_settings.py +++ b/lisp/layouts/cart_layout/cart_layout_settings.py @@ -21,16 +21,16 @@ from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QGridLayout, \ QSpinBox, QLabel -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.settings_page import ConfigurationPage from lisp.ui.ui_utils import translate -class CartLayoutSettings(SettingsPage): +class CartLayoutSettings(ConfigurationPage): Name = 'Cart Layout' - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, config, **kwargs): + super().__init__(config, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -79,6 +79,16 @@ def __init__(self, **kwargs): self.retranslateUi() + # Load data + self.columnsSpin.setValue(config['cartLayout.gridColumns']) + self.rowsSpin.setValue(config['cartLayout.gridRows']) + self.showSeek.setChecked(config['cartLayout.showSeek']) + self.showDbMeters.setChecked(config['cartLayout.showDbMeters']) + self.showAccurate.setChecked(config['cartLayout.showAccurate']) + self.showVolume.setChecked(config['cartLayout.showVolume']) + self.countdownMode.setChecked(config['cartLayout.countdown']) + self.autoAddPage.setChecked(config['cartLayout.autoAddPage']) + def retranslateUi(self): self.behaviorsGroup.setTitle( translate('CartLayout', 'Default behaviors')) @@ -93,28 +103,14 @@ def retranslateUi(self): self.columnsLabel.setText(translate('CartLayout', 'Number of columns')) self.rowsLabel.setText(translate('CartLayout', 'Number of rows')) - def get_settings(self): - conf = { - 'gridColumns': self.columnsSpin.value(), - 'gridRows': self.rowsSpin.value(), - 'showDbMeters': self.showDbMeters.isChecked(), - 'showSeek': self.showSeek.isChecked(), - 'showAccurate': self.showAccurate.isChecked(), - 'showVolume': self.showVolume.isChecked(), - 'countdown': self.countdownMode.isChecked(), - 'autoAddPage': self.autoAddPage.isChecked() - } - - return {'cartLayout': conf} - - def load_settings(self, settings): - settings = settings.get('cartLayout', {}) - - self.columnsSpin.setValue(settings['gridColumns']) - self.rowsSpin.setValue(settings['gridRows']) - self.showSeek.setChecked(settings['showSeek']) - self.showDbMeters.setChecked(settings['showDbMeters']) - self.showAccurate.setChecked(settings['showAccurate']) - self.showVolume.setChecked(settings['showVolume']) - self.countdownMode.setChecked(settings['countdown']) - self.autoAddPage.setChecked(settings['autoAddPage']) + def applySettings(self): + self.config['cartLayout.gridColumns'] = self.columnsSpin.value() + self.config['cartLayout.gridRows'] = self.rowsSpin.value() + self.config['cartLayout.showDbMeters'] = self.showDbMeters.isChecked() + self.config['cartLayout.showSeek'] = self.showSeek.isChecked() + self.config['cartLayout.showAccurate'] = self.showAccurate.isChecked() + self.config['cartLayout.showVolume'] = self.showVolume.isChecked() + self.config['cartLayout.countdown'] = self.countdownMode.isChecked() + self.config['cartLayout.autoAddPage'] = self.autoAddPage.isChecked() + + self.config.write() \ No newline at end of file diff --git a/lisp/layouts/cart_layout/layout.py b/lisp/layouts/cart_layout/layout.py index 75a07fd3f..2c1fd48cb 100644 --- a/lisp/layouts/cart_layout/layout.py +++ b/lisp/layouts/cart_layout/layout.py @@ -21,7 +21,6 @@ from PyQt5.QtWidgets import QTabWidget, QAction, QInputDialog, qApp, \ QMessageBox -from lisp.core.configuration import AppConfig from lisp.core.signal import Connection from lisp.cues.cue import Cue from lisp.cues.cue_factory import CueFactory @@ -52,7 +51,8 @@ class CartLayout(QTabWidget, CueLayout): def __init__(self, **kwargs): super().__init__(**kwargs) - AppSettings.register_settings_widget(CartLayoutSettings, self.app.conf) + AppSettings.registerSettingsWidget( + 'cart_layout', CartLayoutSettings, self.app.conf) self.tabBar().setObjectName('CartTabBar') diff --git a/lisp/layouts/list_layout/layout.py b/lisp/layouts/list_layout/layout.py index 1a0eb723d..5067bbeb2 100644 --- a/lisp/layouts/list_layout/layout.py +++ b/lisp/layouts/list_layout/layout.py @@ -62,7 +62,8 @@ class ListLayout(QWidget, CueLayout): def __init__(self, **kwargs): super().__init__(**kwargs) - AppSettings.register_settings_widget(ListLayoutSettings, self.app.conf) + AppSettings.registerSettingsWidget( + 'list_layout', ListLayoutSettings, self.app.conf) self.setLayout(QGridLayout()) self.layout().setContentsMargins(0, 0, 0, 0) diff --git a/lisp/layouts/list_layout/list_layout_settings.py b/lisp/layouts/list_layout/list_layout_settings.py index 097a0eb6e..d0a8baa63 100644 --- a/lisp/layouts/list_layout/list_layout_settings.py +++ b/lisp/layouts/list_layout/list_layout_settings.py @@ -19,20 +19,18 @@ from PyQt5.QtCore import Qt from PyQt5.QtGui import QKeySequence -from PyQt5.QtWidgets import QDoubleSpinBox from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QComboBox, \ QHBoxLayout, QLabel, QKeySequenceEdit, QGridLayout -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.settings_page import ConfigurationPage from lisp.ui.ui_utils import translate -from lisp.ui.widgets import FadeComboBox -class ListLayoutSettings(SettingsPage): +class ListLayoutSettings(ConfigurationPage): Name = 'List Layout' - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, config, **kwargs): + super().__init__(config, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -60,9 +58,6 @@ def __init__(self, **kwargs): self.endListLabel = QLabel(self.behaviorsGroup) self.endListLayout.addWidget(self.endListLabel) self.endListBehavior = QComboBox(self.behaviorsGroup) - self.endListBehavior.addItem(translate('ListLayout', 'Stop'), 'Stop') - self.endListBehavior.addItem(translate('ListLayout', 'Restart'), - 'Restart') self.endListLayout.addWidget(self.endListBehavior) self.endListLayout.setStretch(0, 2) self.endListLayout.setStretch(1, 5) @@ -102,6 +97,22 @@ def __init__(self, **kwargs): self.retranslateUi() + # Load configuration + self.showPlaying.setChecked(config['listLayout.showPlaying']) + self.showDbMeters.setChecked(config['listLayout.showDbMeters']) + self.showAccurate.setChecked(config['listLayout.showAccurate']) + self.showSeek.setChecked(config['listLayout.showSeek']) + self.autoNext.setChecked(config['listLayout.autoContinue']) + self.endListBehavior.setCurrentText( + translate('ListLayout', config['listLayout.endList'])) + self.goKeyEdit.setKeySequence( + QKeySequence(config['listLayout.goKey'], QKeySequence.NativeText)) + + self.stopCueFade.setChecked(config['listLayout.stopCueFade']) + self.pauseCueFade.setChecked(config['listLayout.pauseCueFade']) + self.resumeCueFade.setChecked(config['listLayout.resumeCueFade']) + self.interruptCueFade.setChecked(config['listLayout.interruptCueFade']) + def retranslateUi(self): self.behaviorsGroup.setTitle( translate('ListLayout', 'Default behaviors')) @@ -111,6 +122,9 @@ def retranslateUi(self): self.showSeek.setText(translate('ListLayout', 'Show seek-bars')) self.autoNext.setText(translate('ListLayout', 'Auto-select next cue')) self.endListLabel.setText(translate('ListLayout', 'At list end:')) + self.endListBehavior.addItem(translate('ListLayout', 'Stop'), 'Stop') + self.endListBehavior.addItem( + translate('ListLayout', 'Restart'), 'Restart') self.goKeyLabel.setText(translate('ListLayout', 'Go key:')) self.useFadeGroup.setTitle(translate('ListLayout', 'Use fade')) @@ -124,48 +138,18 @@ def retranslateUi(self): #self.resumeAllFade.setText(translate('ListLayout', 'Resume All')) #self.interruptAllFade.setText(translate('ListLayout', 'Interrupt All')) - def get_settings(self): - settings = { - 'showPlaying': self.showPlaying.isChecked(), - 'showDbMeters': self.showDbMeters.isChecked(), - 'showSeek': self.showSeek.isChecked(), - 'showAccurate': self.showAccurate.isChecked(), - 'autoContinue': self.autoNext.isChecked(), - 'endList': self.endListBehavior.currentData(), - 'goKey': self.goKeyEdit.keySequence().toString( - QKeySequence.NativeText), - 'stopCueFade': self.stopCueFade.isChecked(), - 'pauseCueFade': self.pauseCueFade.isChecked(), - 'resumeCueFade': self.resumeCueFade.isChecked(), - 'interruptCueFade': self.interruptCueFade.isChecked(), - #'StopAllFade': self.stopAllFade.isChecked(), - #'PauseAllFade': self.pauseAllFade.isChecked(), - #'ResumeAllFade': self.resumeAllFade.isChecked(), - #'InterruptAllFade': self.interruptAllFade.isChecked(), - } - - return {'listLayout': settings} - - def load_settings(self, settings): - settings = settings.get('listLayout', {}) - - self.showPlaying.setChecked(settings['showPlaying']) - self.showDbMeters.setChecked(settings['showDbMeters']) - self.showAccurate.setChecked(settings['showAccurate']) - self.showSeek.setChecked(settings['showSeek']) - self.autoNext.setChecked(settings['autoContinue']) - self.endListBehavior.setCurrentText( - translate('ListLayout', settings.get('endList', ''))) - self.goKeyEdit.setKeySequence( - QKeySequence(settings.get('goKey', 'Space'), - QKeySequence.NativeText)) - - self.stopCueFade.setChecked(settings['stopCueFade']) - self.pauseCueFade.setChecked(settings['pauseCueFade']) - self.resumeCueFade.setChecked(settings['resumeCueFade']) - self.interruptCueFade.setChecked(settings['interruptCueFade']) - - #self.stopAllFade.setChecked(settings['StopAllFade']) - #self.pauseAllFade.setChecked(settings['PauseAllFade']) - #self.resumeAllFade.setChecked(settings['ResumeAllFade']) - #self.interruptAllFade.setChecked(settings['InterruptAllFade']) \ No newline at end of file + def applySettings(self): + self.config['listLayout.showPlaying'] = self.showPlaying.isChecked() + self.config['listLayout.showDbMeters'] = self.showDbMeters.isChecked() + self.config['listLayout.showSeek'] = self.showSeek.isChecked() + self.config['listLayout.showAccurate'] = self.showAccurate.isChecked() + self.config['listLayout.autoContinue'] = self.autoNext.isChecked() + self.config['listLayout.endList'] = self.endListBehavior.currentData() + self.config['listLayout.goKey'] = self.goKeyEdit.keySequence().toString( + QKeySequence.NativeText) + self.config['listLayout.stopCueFade'] = self.stopCueFade.isChecked() + self.config['listLayout.pauseCueFade'] = self.pauseCueFade.isChecked() + self.config['listLayout.resumeCueFade'] = self.resumeCueFade.isChecked() + self.config['listLayout.interruptCueFade'] = self.interruptCueFade.isChecked() + + self.config.write() \ No newline at end of file diff --git a/lisp/plugins/action_cues/collection_cue.py b/lisp/plugins/action_cues/collection_cue.py index 6d26832e3..c2624edea 100644 --- a/lisp/plugins/action_cues/collection_cue.py +++ b/lisp/plugins/action_cues/collection_cue.py @@ -81,13 +81,13 @@ def __init__(self, **kwargs): translate('CollectionCue', 'Remove'), QDialogButtonBox.ActionRole) self.delButton.clicked.connect(self._remove_selected) - def load_settings(self, settings): + def loadSettings(self, settings): for target_id, action in settings.get('targets', []): target = Application().cue_model.get(target_id) if target is not None: self._add_cue(target, CueAction(action)) - def get_settings(self): + def getSettings(self): targets = [] for target_id, action in self.collectionModel.rows: targets.append((target_id, action.value)) diff --git a/lisp/plugins/action_cues/command_cue.py b/lisp/plugins/action_cues/command_cue.py index 9ce8e3fc4..f2ff23cd8 100644 --- a/lisp/plugins/action_cues/command_cue.py +++ b/lisp/plugins/action_cues/command_cue.py @@ -130,7 +130,7 @@ def retranslateUi(self): self.killCheckBox.setText( translate('CommandCue', 'Kill instead of terminate')) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.group.setCheckable(enabled) self.group.setChecked(False) @@ -146,13 +146,13 @@ def enable_check(self, enabled): if enabled: self.killCheckBox.setCheckState(Qt.PartiallyChecked) - def load_settings(self, settings): + def loadSettings(self, settings): self.commandLineEdit.setText(settings.get('command', '')) self.noOutputCheckBox.setChecked(settings.get('no_output', True)) self.noErrorCheckBox.setChecked(settings.get('no_error', True)) self.killCheckBox.setChecked(settings.get('kill', False)) - def get_settings(self): + def getSettings(self): settings = {} if not (self.group.isCheckable() and not self.group.isChecked()): diff --git a/lisp/plugins/action_cues/index_action_cue.py b/lisp/plugins/action_cues/index_action_cue.py index 2fd155f73..01db40561 100644 --- a/lisp/plugins/action_cues/index_action_cue.py +++ b/lisp/plugins/action_cues/index_action_cue.py @@ -120,14 +120,14 @@ def retranslateUi(self): self.suggestionGroup.setTitle( translate('IndexActionCue', 'Suggested cue name')) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.indexGroup.setChecked(enabled) self.indexGroup.setChecked(False) self.actionGroup.setCheckable(enabled) self.actionGroup.setChecked(False) - def get_settings(self): + def getSettings(self): conf = {} checkable = self.actionGroup.isCheckable() @@ -139,7 +139,7 @@ def get_settings(self): return conf - def load_settings(self, settings): + def loadSettings(self, settings): self._cue_index = settings.get('index', -1) self.relativeCheck.setChecked(settings.get('relative', True)) diff --git a/lisp/plugins/action_cues/midi_cue.py b/lisp/plugins/action_cues/midi_cue.py index 5cc19ab81..9543b02c9 100644 --- a/lisp/plugins/action_cues/midi_cue.py +++ b/lisp/plugins/action_cues/midi_cue.py @@ -127,7 +127,7 @@ def __type_changed(self, msg_type): spin.setRange( *self.ATTRIBUTES_RANGE.get(attr_name, (0, 0, 0))[0:2]) - def get_settings(self): + def getSettings(self): msg_type = self.msgTypeCombo.currentText() msg_dict = {'type': msg_type} @@ -143,7 +143,7 @@ def __attributes(self, msg_type): self.MSGS_ATTRIBUTES[msg_type]): yield label, spin, attr - def load_settings(self, settings): + def loadSettings(self, settings): str_msg = settings.get('message', '') if str_msg: dict_msg = str_msg_to_dict(str_msg) diff --git a/lisp/plugins/action_cues/osc_cue.py b/lisp/plugins/action_cues/osc_cue.py index fbb99db28..9e98948eb 100644 --- a/lisp/plugins/action_cues/osc_cue.py +++ b/lisp/plugins/action_cues/osc_cue.py @@ -274,14 +274,14 @@ def retranslateUi(self): self.fadeLabel.setText(translate('OscCue', 'Time (sec)')) self.fadeCurveLabel.setText(translate('OscCue', 'Curve')) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.oscGroup.setCheckable(enabled) self.oscGroup.setChecked(False) self.fadeGroup.setCheckable(enabled) self.fadeGroup.setChecked(False) - def get_settings(self): + def getSettings(self): conf = {} checkable = self.oscGroup.isCheckable() @@ -312,7 +312,7 @@ def get_settings(self): conf['fade_type'] = self.fadeCurveCombo.currentType() return conf - def load_settings(self, settings): + def loadSettings(self, settings): if 'path' in settings: path = settings.get('path', '') self.pathEdit.setText(path) diff --git a/lisp/plugins/action_cues/seek_cue.py b/lisp/plugins/action_cues/seek_cue.py index e729ab8e7..f487bc69f 100644 --- a/lisp/plugins/action_cues/seek_cue.py +++ b/lisp/plugins/action_cues/seek_cue.py @@ -106,18 +106,18 @@ def select_cue(self): QTime.fromMSecsSinceStartOfDay(cue.media.duration)) self.cueLabel.setText(cue.name) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.cueGroup.setCheckable(enabled) self.cueGroup.setChecked(False) self.seekGroup.setCheckable(enabled) self.seekGroup.setChecked(False) - def get_settings(self): + def getSettings(self): return {'target_id': self.cue_id, 'time': self.seekEdit.time().msecsSinceStartOfDay()} - def load_settings(self, settings): + def loadSettings(self, settings): if settings is not None: cue = Application().cue_model.get(settings.get('target_id')) if cue is not None: diff --git a/lisp/plugins/action_cues/stop_all.py b/lisp/plugins/action_cues/stop_all.py index ef3ce0daf..2fffd3f0d 100644 --- a/lisp/plugins/action_cues/stop_all.py +++ b/lisp/plugins/action_cues/stop_all.py @@ -85,11 +85,11 @@ def __init__(self, **kwargs): def retranslateUi(self): self.group.setTitle(translate('StopAll', 'Stop Action')) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.group.setCheckable(enabled) self.group.setChecked(False) - def get_settings(self): + def getSettings(self): conf = {} if not (self.group.isCheckable() and not self.group.isChecked()): @@ -97,7 +97,7 @@ def get_settings(self): return conf - def load_settings(self, settings): + def loadSettings(self, settings): self.actionCombo.setCurrentText( translate('CueAction', settings.get('action', ''))) diff --git a/lisp/plugins/action_cues/volume_control.py b/lisp/plugins/action_cues/volume_control.py index efad7b860..105cac6d5 100644 --- a/lisp/plugins/action_cues/volume_control.py +++ b/lisp/plugins/action_cues/volume_control.py @@ -192,7 +192,7 @@ def select_cue(self): self.cue_id = cue.id self.cueLabel.setText(cue.name) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.cueGroup.setCheckable(enabled) self.cueGroup.setChecked(False) @@ -202,7 +202,7 @@ def enable_check(self, enabled): self.fadeGroup.setCheckable(enabled) self.volumeGroup.setChecked(False) - def get_settings(self): + def getSettings(self): conf = {} checkable = self.cueGroup.isCheckable() @@ -216,7 +216,7 @@ def get_settings(self): return conf - def load_settings(self, settings): + def loadSettings(self, settings): cue = Application().cue_model.get(settings.get('target_id', '')) if cue is not None: self.cue_id = settings['target_id'] diff --git a/lisp/plugins/controller/controller_settings.py b/lisp/plugins/controller/controller_settings.py index 4a5a63aa9..2b6e5439b 100644 --- a/lisp/plugins/controller/controller_settings.py +++ b/lisp/plugins/controller/controller_settings.py @@ -46,19 +46,19 @@ def __init__(self, cue_class, **kwargs): self.tabWidget.setCurrentIndex(0) - def enable_check(self, enabled): + def enableCheck(self, enabled): for page in self._pages: - page.enable_check(enabled) + page.enableCheck(enabled) - def get_settings(self): + def getSettings(self): settings = {} for page in self._pages: - settings.update(page.get_settings()) + settings.update(page.getSettings()) return {'controller': settings} - def load_settings(self, settings): + def loadSettings(self, settings): settings = settings.get('controller', {}) for page in self._pages: - page.load_settings(settings) + page.loadSettings(settings) diff --git a/lisp/plugins/controller/protocols/keyboard.py b/lisp/plugins/controller/protocols/keyboard.py index 40f73c8a7..cacd64f2e 100644 --- a/lisp/plugins/controller/protocols/keyboard.py +++ b/lisp/plugins/controller/protocols/keyboard.py @@ -78,11 +78,11 @@ def retranslateUi(self): self.addButton.setText(translate('ControllerSettings', 'Add')) self.removeButton.setText(translate('ControllerSettings', 'Remove')) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.keyGroup.setCheckable(enabled) self.keyGroup.setChecked(False) - def get_settings(self): + def getSettings(self): settings = {} if not (self.keyGroup.isCheckable() and not self.keyGroup.isChecked()): @@ -90,7 +90,7 @@ def get_settings(self): return settings - def load_settings(self, settings): + def loadSettings(self, settings): for key, action in settings.get('keyboard', []): self.keyboardModel.appendRow(key, action) diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index 985d92c03..24b18a256 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -109,11 +109,11 @@ def retranslateUi(self): self.removeButton.setText(translate('ControllerSettings', 'Remove')) self.midiCapture.setText(translate('ControllerMidiSettings', 'Capture')) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.midiGroup.setCheckable(enabled) self.midiGroup.setChecked(False) - def get_settings(self): + def getSettings(self): settings = {} checkable = self.midiGroup.isCheckable() @@ -129,7 +129,7 @@ def get_settings(self): return settings - def load_settings(self, settings): + def loadSettings(self, settings): if 'midi' in settings: for options in settings['midi']: m_type, channel, note = Midi.from_string(options[0]) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 2cbbc8b1a..53d2c22c0 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -256,11 +256,11 @@ def retranslateUi(self): self.removeButton.setText(translate('ControllerOscSettings', 'Remove')) self.oscCapture.setText(translate('ControllerOscSettings', 'Capture')) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.oscGroup.setCheckable(enabled) self.oscGroup.setChecked(False) - def get_settings(self): + def getSettings(self): settings = {} messages = [] @@ -274,7 +274,7 @@ def get_settings(self): return settings - def load_settings(self, settings): + def loadSettings(self, settings): if 'osc' in settings: for options in settings['osc']: key = Osc.message_from_key(options[0]) diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index 72c42f8ad..83eb9eeb3 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -56,7 +56,8 @@ def __init__(self, app): Gst.init(None) # Register GStreamer settings widgets - AppSettings.register_settings_widget(GstSettings, GstBackend.Config) + AppSettings.registerSettingsWidget( + 'plugins.gst', GstSettings, GstBackend.Config) # Add MediaCue settings widget to the CueLayout CueSettingsRegistry().add_item(GstMediaSettings, MediaCue) diff --git a/lisp/plugins/gst_backend/gst_media_settings.py b/lisp/plugins/gst_backend/gst_media_settings.py index 9d8161deb..0314eefe7 100644 --- a/lisp/plugins/gst_backend/gst_media_settings.py +++ b/lisp/plugins/gst_backend/gst_media_settings.py @@ -54,7 +54,7 @@ def __init__(self, **kwargs): self.listWidget.currentItemChanged.connect(self.__change_page) self.pipeButton.clicked.connect(self.__edit_pipe) - def load_settings(self, settings): + def loadSettings(self, settings): settings = settings.get('media', {}) # Create a local copy of the configuration self._settings = deepcopy(settings) @@ -66,7 +66,7 @@ def load_settings(self, settings): if page is not None and issubclass(page, SettingsPage): page = page(parent=self) - page.load_settings( + page.loadSettings( settings.get('elements', {}) .get(element, page.ELEMENT.class_defaults())) page.setVisible(False) @@ -77,11 +77,11 @@ def load_settings(self, settings): self.listWidget.setCurrentRow(0) - def get_settings(self): + def getSettings(self): settings = {'elements': {}} for page in self._pages: - page_settings = page.get_settings() + page_settings = page.getSettings() if page_settings: settings['elements'][page.ELEMENT.__name__] = page_settings @@ -92,10 +92,10 @@ def get_settings(self): return {'media': settings} - def enable_check(self, enabled): + def enableCheck(self, enabled): self._check = enabled for page in self._pages: - page.enable_check(enabled) + page.enableCheck(enabled) def __change_page(self, current, previous): if current is None: @@ -111,7 +111,7 @@ def __change_page(self, current, previous): def __edit_pipe(self): # Backup the settings - self._settings = self.get_settings()['media'] + self._settings = self.getSettings()['media'] # Show the dialog dialog = GstPipeEditDialog(self._settings.get('pipe', ()), parent=self) @@ -128,5 +128,5 @@ def __edit_pipe(self): # Reload with the new pipeline self._settings['pipe'] = dialog.get_pipe() - self.load_settings({'media': self._settings}) - self.enable_check(self._check) + self.loadSettings({'media': self._settings}) + self.enableCheck(self._check) diff --git a/lisp/plugins/gst_backend/gst_settings.py b/lisp/plugins/gst_backend/gst_settings.py index cdfb9337f..d64d30b24 100644 --- a/lisp/plugins/gst_backend/gst_settings.py +++ b/lisp/plugins/gst_backend/gst_settings.py @@ -21,15 +21,15 @@ from PyQt5.QtWidgets import QVBoxLayout, QGroupBox from lisp.plugins.gst_backend.gst_pipe_edit import GstPipeEdit -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.settings_page import ConfigurationPage from lisp.ui.ui_utils import translate -class GstSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'GStreamer settings') +class GstSettings(ConfigurationPage): + Name = QT_TRANSLATE_NOOP('SettingsPageName', 'GStreamer') - def __init__(self, **kwargs): - super().__init__() + def __init__(self, config, **kwargs): + super().__init__(config, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -37,7 +37,7 @@ def __init__(self, **kwargs): self.pipeGroup.setLayout(QVBoxLayout()) self.layout().addWidget(self.pipeGroup) - self.pipeEdit = GstPipeEdit([], app_mode=True) + self.pipeEdit = GstPipeEdit(config.get('pipeline'), app_mode=True) self.pipeGroup.layout().addWidget(self.pipeEdit) self.retranslateUi() @@ -45,8 +45,6 @@ def __init__(self, **kwargs): def retranslateUi(self): self.pipeGroup.setTitle(translate('GstSettings', 'Pipeline')) - def get_settings(self): - return {'pipeline': list(self.pipeEdit.get_pipe())} - - def load_settings(self, settings): - self.pipeEdit.set_pipe(settings['pipeline']) + def applySettings(self): + self.config['pipeline'] = list(self.pipeEdit.get_pipe()) + self.config.write() \ No newline at end of file diff --git a/lisp/plugins/gst_backend/settings/alsa_sink.py b/lisp/plugins/gst_backend/settings/alsa_sink.py index 8f0fe83c3..9d444a46d 100644 --- a/lisp/plugins/gst_backend/settings/alsa_sink.py +++ b/lisp/plugins/gst_backend/settings/alsa_sink.py @@ -58,11 +58,11 @@ def __init__(self, **kwargs): self.label.setAlignment(QtCore.Qt.AlignCenter) self.deviceGroup.layout().addWidget(self.label) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.deviceGroup.setCheckable(enabled) self.deviceGroup.setChecked(False) - def load_settings(self, settings): + def loadSettings(self, settings): device = settings.get('device', 'default') for name, dev_name in self.devices.items(): @@ -70,7 +70,7 @@ def load_settings(self, settings): self.device.setCurrentText(name) break - def get_settings(self): + def getSettings(self): if not (self.deviceGroup.isCheckable() and not self.deviceGroup.isChecked()): return {'device': self.devices[self.device.currentText()]} diff --git a/lisp/plugins/gst_backend/settings/audio_dynamic.py b/lisp/plugins/gst_backend/settings/audio_dynamic.py index ab4cb9df9..5d48c2e15 100644 --- a/lisp/plugins/gst_backend/settings/audio_dynamic.py +++ b/lisp/plugins/gst_backend/settings/audio_dynamic.py @@ -99,11 +99,11 @@ def retranslateUi(self): self.thresholdLabel.setText( translate('AudioDynamicSettings', 'Threshold (dB)')) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.groupBox.setCheckable(enabled) self.groupBox.setChecked(False) - def get_settings(self): + def getSettings(self): settings = {} if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): @@ -116,7 +116,7 @@ def get_settings(self): return settings - def load_settings(self, settings): + def loadSettings(self, settings): self.modeComboBox.setCurrentText( translate('AudioDynamicSettings', settings.get('mode', 'compressor'))) diff --git a/lisp/plugins/gst_backend/settings/audio_pan.py b/lisp/plugins/gst_backend/settings/audio_pan.py index b05877514..3f4ed5273 100644 --- a/lisp/plugins/gst_backend/settings/audio_pan.py +++ b/lisp/plugins/gst_backend/settings/audio_pan.py @@ -60,17 +60,17 @@ def retransaleUi(self): self.panBox.setTitle(translate('AudioPanSettings', 'Audio Pan')) self.panLabel.setText(translate('AudioPanSettings', 'Center')) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.panBox.setCheckable(enabled) self.panBox.setChecked(False) - def get_settings(self): + def getSettings(self): if not (self.panBox.isCheckable() and not self.panBox.isChecked()): return {'pan': self.panSlider.value() / 10} return {} - def load_settings(self, settings): + def loadSettings(self, settings): self.panSlider.setValue(settings.get('pan', 0.5) * 10) def pan_changed(self, value): diff --git a/lisp/plugins/gst_backend/settings/db_meter.py b/lisp/plugins/gst_backend/settings/db_meter.py index 46732000d..c3f575d99 100644 --- a/lisp/plugins/gst_backend/settings/db_meter.py +++ b/lisp/plugins/gst_backend/settings/db_meter.py @@ -85,12 +85,12 @@ def retranslateUi(self): self.falloffLabel.setText( translate('DbMeterSettings', 'Peak falloff (dB/sec)')) - def load_settings(self, settings): + def loadSettings(self, settings): self.intervalSpin.setValue(settings.get('interval', 50) / Gst.MSECOND) self.ttlSpin.setValue(settings.get('peak_ttl', 500) / Gst.MSECOND) self.falloffSpin.setValue(settings.get('peak_falloff', 20)) - def get_settings(self): + def getSettings(self): settings = {} if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): diff --git a/lisp/plugins/gst_backend/settings/equalizer10.py b/lisp/plugins/gst_backend/settings/equalizer10.py index 5f8b4a84f..e81f1d889 100644 --- a/lisp/plugins/gst_backend/settings/equalizer10.py +++ b/lisp/plugins/gst_backend/settings/equalizer10.py @@ -72,11 +72,11 @@ def __init__(self, **kwargs): fLabel.setText(self.FREQ[n]) self.groupBox.layout().addWidget(fLabel, 2, n) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.groupBox.setCheckable(enabled) self.groupBox.setChecked(False) - def get_settings(self): + def getSettings(self): settings = {} if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): @@ -85,6 +85,6 @@ def get_settings(self): return settings - def load_settings(self, settings): + def loadSettings(self, settings): for band in self.sliders: self.sliders[band].setValue(settings.get(band, 0)) diff --git a/lisp/plugins/gst_backend/settings/jack_sink.py b/lisp/plugins/gst_backend/settings/jack_sink.py index 049b11d8d..c8c33178d 100644 --- a/lisp/plugins/gst_backend/settings/jack_sink.py +++ b/lisp/plugins/gst_backend/settings/jack_sink.py @@ -60,7 +60,7 @@ def closeEvent(self, event): self.__jack_client.close() super().closeEvent(event) - def get_settings(self): + def getSettings(self): settings = {} if not (self.jackGroup.isCheckable() and @@ -69,10 +69,10 @@ def get_settings(self): return settings - def load_settings(self, settings): + def loadSettings(self, settings): self.connections = settings.get('connections', self.connections).copy() - def enable_check(self, enabled): + def enableCheck(self, enabled): self.jackGroup.setCheckable(enabled) self.jackGroup.setChecked(False) diff --git a/lisp/plugins/gst_backend/settings/pitch.py b/lisp/plugins/gst_backend/settings/pitch.py index ec231543f..0c3844861 100644 --- a/lisp/plugins/gst_backend/settings/pitch.py +++ b/lisp/plugins/gst_backend/settings/pitch.py @@ -66,17 +66,17 @@ def retranslateUi(self): self.groupBox.setTitle(translate('PitchSettings', 'Pitch')) self.pitch_changed(0) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.groupBox.setCheckable(enabled) self.groupBox.setChecked(False) - def get_settings(self): + def getSettings(self): if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): return {'pitch': math.pow(2, self.pitchSlider.value() / 12)} return {} - def load_settings(self, settings): + def loadSettings(self, settings): self.pitchSlider.setValue( round(12 * math.log(settings.get('pitch', 1), 2))) diff --git a/lisp/plugins/gst_backend/settings/preset_src.py b/lisp/plugins/gst_backend/settings/preset_src.py index 7d3070571..d3e3c270b 100644 --- a/lisp/plugins/gst_backend/settings/preset_src.py +++ b/lisp/plugins/gst_backend/settings/preset_src.py @@ -49,11 +49,11 @@ def __init__(self, **kwargs): self.functionDuration.setDisplayFormat('HH.mm.ss.zzz') self.functionGroup.layout().addWidget(self.functionDuration) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.functionGroup.setCheckable(enabled) self.functionGroup.setChecked(False) - def get_settings(self): + def getSettings(self): if not (self.functionGroup.isCheckable() and not self.functionGroup.isChecked()): return {'preset': self.functionCombo.currentText(), 'duration': self.functionDuration.time(). @@ -61,7 +61,7 @@ def get_settings(self): return {} - def load_settings(self, settings): + def loadSettings(self, settings): self.functionCombo.setCurrentText(settings.get('preset', '')) self.functionDuration.setTime( QTime.fromMSecsSinceStartOfDay(settings.get('duration', 0))) diff --git a/lisp/plugins/gst_backend/settings/speed.py b/lisp/plugins/gst_backend/settings/speed.py index 60c580140..67dac3b61 100644 --- a/lisp/plugins/gst_backend/settings/speed.py +++ b/lisp/plugins/gst_backend/settings/speed.py @@ -64,17 +64,17 @@ def retranslateUi(self): self.groupBox.setTitle(translate('SpeedSettings', 'Speed')) self.speedLabel.setText('1.0') - def enable_check(self, enabled): + def enableCheck(self, enabled): self.groupBox.setCheckable(enabled) self.groupBox.setChecked(False) - def get_settings(self): + def getSettings(self): if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): return {'speed': self.speedSlider.value() / 100} return {} - def load_settings(self, settings): + def loadSettings(self, settings): self.speedSlider.setValue(settings.get('speed', 1) * 100) def speedChanged(self, value): diff --git a/lisp/plugins/gst_backend/settings/uri_input.py b/lisp/plugins/gst_backend/settings/uri_input.py index 9e673128d..758339a85 100644 --- a/lisp/plugins/gst_backend/settings/uri_input.py +++ b/lisp/plugins/gst_backend/settings/uri_input.py @@ -79,7 +79,7 @@ def retranslateUi(self): self.bufferSizeLabel.setText( translate('UriInputSettings', 'Buffer size (-1 default value)')) - def get_settings(self): + def getSettings(self): settings = {} checkable = self.fileGroup.isCheckable() @@ -93,10 +93,10 @@ def get_settings(self): return settings - def load_settings(self, settings): + def loadSettings(self, settings): self.filePath.setText(settings.get('uri', '')) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.fileGroup.setCheckable(enabled) self.fileGroup.setChecked(False) diff --git a/lisp/plugins/gst_backend/settings/user_element.py b/lisp/plugins/gst_backend/settings/user_element.py index 838b8e59c..bce10a84d 100644 --- a/lisp/plugins/gst_backend/settings/user_element.py +++ b/lisp/plugins/gst_backend/settings/user_element.py @@ -56,14 +56,14 @@ def retranslateUi(self): self.warning.setText( translate('UserElementSettings', 'Only for advanced user!')) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.groupBox.setCheckable(enabled) self.groupBox.setChecked(False) - def load_settings(self, settings): + def loadSettings(self, settings): self.textEdit.setPlainText(settings.get('bin', '')) - def get_settings(self): + def getSettings(self): if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): return {'bin': self.textEdit.toPlainText().strip()} diff --git a/lisp/plugins/gst_backend/settings/volume.py b/lisp/plugins/gst_backend/settings/volume.py index f4b9c539b..78b103e03 100644 --- a/lisp/plugins/gst_backend/settings/volume.py +++ b/lisp/plugins/gst_backend/settings/volume.py @@ -83,12 +83,12 @@ def retranslateUi(self): self.normalLabel.setText('0.0 dB') self.normalReset.setText(translate('VolumeSettings', 'Reset')) - def enable_check(self, enabled): + def enableCheck(self, enabled): for box in [self.normalBox, self.volumeBox]: box.setCheckable(enabled) box.setChecked(False) - def get_settings(self): + def getSettings(self): settings = {} checkable = self.volumeBox.isCheckable() @@ -105,7 +105,7 @@ def get_settings(self): return settings - def load_settings(self, settings): + def loadSettings(self, settings): self.volume.setValue(linear_to_db(settings.get('volume', 1)) * 10) self.muteButton.setMute(settings.get('mute', False)) self.normal = settings.get('normal_volume', 1) diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index 7c82cceaf..776290012 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -37,7 +37,8 @@ def __init__(self, app): super().__init__(app) # Register the settings widget - AppSettings.register_settings_widget(MIDISettings, Midi.Config) + AppSettings.registerSettingsWidget( + 'plugins.midi', MIDISettings, Midi.Config) # Load the backend and set it as current mido backend self.backend = mido.Backend(Midi.Config['backend'], load=True) diff --git a/lisp/plugins/midi/midi_settings.py b/lisp/plugins/midi/midi_settings.py index 4dc85fb98..00a5ca533 100644 --- a/lisp/plugins/midi/midi_settings.py +++ b/lisp/plugins/midi/midi_settings.py @@ -22,15 +22,15 @@ QLabel from lisp.plugins.midi.midi_utils import mido_backend -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.settings_page import ConfigurationPage from lisp.ui.ui_utils import translate -class MIDISettings(SettingsPage): +class MIDISettings(ConfigurationPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'MIDI settings') - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, conf, **kwargs): + super().__init__(conf, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -56,35 +56,33 @@ def __init__(self, **kwargs): self.midiGroup.layout().setColumnStretch(1, 3) try: - self._load_devices() + self._loadDevices() except Exception: self.setEnabled(False) - def get_settings(self): - conf = {} + # TODO: instead of forcing 'Default' add a warning label + self.inputCombo.setCurrentText('Default') + self.inputCombo.setCurrentText(conf['inputDevice']) + self.outputCombo.setCurrentText('Default') + self.outputCombo.setCurrentText(conf['outputDevice']) + def applySettings(self): if self.isEnabled(): - input = self.inputCombo.currentText() - if input == 'Default': - conf['inputDevice'] = '' + inputDevice = self.inputCombo.currentText() + if inputDevice == 'Default': + self.config['inputDevice'] = '' else: - conf['inputDevice'] = input + self.config['inputDevice'] = inputDevice - output = self.outputCombo.currentText() - if output == 'Default': - conf['outputDevice'] = '' + outputDevice = self.outputCombo.currentText() + if outputDevice == 'Default': + self.config['outputDevice'] = '' else: - conf['outputDevice'] = output - - return conf + self.config['outputDevice'] = outputDevice - def load_settings(self, settings): - self.inputCombo.setCurrentText('Default') - self.inputCombo.setCurrentText(settings['inputDevice']) - self.outputCombo.setCurrentText('Default') - self.outputCombo.setCurrentText(settings['outputDevice']) + self.config.write() - def _load_devices(self): + def _loadDevices(self): backend = mido_backend() self.inputCombo.clear() diff --git a/lisp/plugins/osc/osc.py b/lisp/plugins/osc/osc.py index ffdb5165f..ae0fedf16 100644 --- a/lisp/plugins/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -37,7 +37,8 @@ def __init__(self, app): super().__init__(app) # Register the settings widget - AppSettings.register_settings_widget(OscSettings, Osc.Config) + AppSettings.registerSettingsWidget( + 'plugins.osc', OscSettings, Osc.Config) # Create a server instance self.__server = OscServer( diff --git a/lisp/plugins/osc/osc_settings.py b/lisp/plugins/osc/osc_settings.py index 620709ed7..2d6411841 100644 --- a/lisp/plugins/osc/osc_settings.py +++ b/lisp/plugins/osc/osc_settings.py @@ -22,15 +22,15 @@ from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel, \ QHBoxLayout, QSpinBox, QLineEdit -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.settings_page import ConfigurationPage from lisp.ui.ui_utils import translate -class OscSettings(SettingsPage): +class OscSettings(ConfigurationPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'OSC settings') - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, config, **kwargs): + super().__init__(config, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -66,14 +66,16 @@ def __init__(self, **kwargs): hbox.layout().addWidget(self.hostnameEdit) self.groupBox.layout().addLayout(hbox) - def get_settings(self): - return { - 'inPort': self.inportBox.value(), - 'outPort': self.outportBox.value(), - 'hostname': self.hostnameEdit.text(), - } + self.loadConfiguration() - def load_settings(self, settings): - self.inportBox.setValue(settings['inPort']) - self.outportBox.setValue(settings['outPort']) - self.hostnameEdit.setText(settings['hostname']) + def applySettings(self): + self.config['inPort'] = self.inportBox.value() + self.config['outPort'] = self.outportBox.value() + self.config['hostname'] = self.hostnameEdit.text() + + self.config.write() + + def loadConfiguration(self): + self.inportBox.setValue(self.config['inPort']) + self.outportBox.setValue(self.config['outPort']) + self.hostnameEdit.setText(self.config['hostname']) diff --git a/lisp/plugins/timecode/settings.py b/lisp/plugins/timecode/settings.py index 08b34c8e7..7f6cdb8b8 100644 --- a/lisp/plugins/timecode/settings.py +++ b/lisp/plugins/timecode/settings.py @@ -25,7 +25,7 @@ from lisp.plugins.timecode import protocols from lisp.plugins.timecode.cue_tracker import TcFormat -from lisp.ui.settings.settings_page import SettingsPage, CueSettingsPage +from lisp.ui.settings.settings_page import SettingsPage, CueSettingsPage, ConfigurationPage from lisp.ui.ui_utils import translate @@ -42,9 +42,9 @@ def __init__(self, cue_class, **kwargs): self.layout().addWidget(self.groupBox) # enable / disable timecode - self.enableCheck = QCheckBox(self.groupBox) - self.enableCheck.setChecked(False) - self.groupBox.layout().addWidget(self.enableCheck, 0, 0) + self.enableTimecodeCheck = QCheckBox(self.groupBox) + self.enableTimecodeCheck.setChecked(False) + self.groupBox.layout().addWidget(self.enableTimecodeCheck, 0, 0) # Hours can be replaced by cue number h:m:s:frames -> CUE:m:s:frames self.useHoursCheck = QCheckBox(self.groupBox) @@ -75,31 +75,31 @@ def retranslateUi(self): self.useHoursCheck.setText( translate('TimecodeSettings', 'Replace HOURS by a static track number')) - self.enableCheck.setText( + self.enableTimecodeCheck.setText( translate('TimecodeSettings', 'Enable Timecode')) self.trackLabel.setText( translate('TimecodeSettings', 'Track number')) - def get_settings(self): + def getSettings(self): return {'timecode': { - 'enabled': self.enableCheck.isChecked(), + 'enabled': self.enableTimecodeCheck.isChecked(), 'replace_hours': self.useHoursCheck.isChecked(), 'track': self.trackSpin.value() }} - def load_settings(self, settings): + def loadSettings(self, settings): settings = settings.get('timecode', {}) - self.enableCheck.setChecked(settings.get('enabled', False)) + self.enableTimecodeCheck.setChecked(settings.get('enabled', False)) self.useHoursCheck.setChecked(settings.get('replace_hours', False)) self.trackSpin.setValue(settings.get('track', 0)) -class TimecodeAppSettings(SettingsPage): +class TimecodeAppSettings(ConfigurationPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Timecode Settings') - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, config, **kwargs): + super().__init__(config, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -124,6 +124,7 @@ def __init__(self, **kwargs): self.groupBox.layout().addWidget(self.protocolCombo, 1, 1) self.retranslateUi() + self.loadConfiguration() def retranslateUi(self): self.groupBox.setTitle( @@ -133,12 +134,11 @@ def retranslateUi(self): self.protocolLabel.setText( translate('TimecodeSettings', 'Timecode Protocol:')) - def get_settings(self): - return { - 'format': self.formatBox.currentText(), - 'protocol': self.protocolCombo.currentText() - } + def applySettings(self): + self.config['format'] = self.formatBox.currentText() + self.config['protocol'] = self.protocolCombo.currentText() + self.config.write() - def load_settings(self, settings): - self.formatBox.setCurrentText(settings.get('format', '')) - self.protocolCombo.setCurrentText(settings.get('protocol', '')) + def loadConfiguration(self): + self.formatBox.setCurrentText(self.config['format']) + self.protocolCombo.setCurrentText(self.config['protocol']) diff --git a/lisp/plugins/timecode/timecode.py b/lisp/plugins/timecode/timecode.py index 23dc74896..babcbbfc2 100644 --- a/lisp/plugins/timecode/timecode.py +++ b/lisp/plugins/timecode/timecode.py @@ -49,8 +49,8 @@ def __init__(self, app): # Register cue-settings-page CueSettingsRegistry().add_item(TimecodeSettings, MediaCue) # Register the settings widget - AppSettings.register_settings_widget( - TimecodeAppSettings, Timecode.Config) + AppSettings.registerSettingsWidget( + 'plugins.timecode', TimecodeAppSettings, Timecode.Config) # Load available protocols protocols.load_protocols() diff --git a/lisp/plugins/triggers/triggers_settings.py b/lisp/plugins/triggers/triggers_settings.py index e943fac70..bf4d0ecdc 100644 --- a/lisp/plugins/triggers/triggers_settings.py +++ b/lisp/plugins/triggers/triggers_settings.py @@ -74,7 +74,7 @@ def _add_trigger(self): def _remove_trigger(self): self.triggersModel.removeRow(self.triggersView.currentIndex().row()) - def load_settings(self, settings): + def loadSettings(self, settings): # Remove the edited cue from the list of possible targets edited_cue = Application().cue_model.get(settings.get('id')) if edited_cue: @@ -87,7 +87,7 @@ def load_settings(self, settings): self.triggersModel.appendRow(target.__class__, trigger, target.id, CueAction(action)) - def get_settings(self): + def getSettings(self): triggers = {} for trigger, target, action in self.triggersModel.rows: action = action.value diff --git a/lisp/ui/layoutselect.py b/lisp/ui/layoutselect.py index 082fbdd6a..6a3d3eace 100644 --- a/lisp/ui/layoutselect.py +++ b/lisp/ui/layoutselect.py @@ -20,7 +20,8 @@ import os from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QDialog, QComboBox, QPushButton, QFrame, QTextBrowser, QFileDialog, QGridLayout +from PyQt5.QtWidgets import QDialog, QComboBox, QPushButton, QFrame,\ + QTextBrowser, QFileDialog, QGridLayout from lisp import layouts from lisp.ui.ui_utils import translate @@ -78,7 +79,7 @@ def __init__(self, parent=None): def selected(self): return self.layoutCombo.currentData() - def show_description(self, index): + def show_description(self): layout = self.layoutCombo.currentData() details = '
    ' @@ -87,12 +88,15 @@ def show_description(self, index): details += '
' self.description.setHtml( - '

' + layout.NAME + '

' - '

' + translate('LayoutDescription', layout.DESCRIPTION) + - '

' + details) + '

{}

{}

{}'.format( + layout.NAME, + translate('LayoutDescription', layout.DESCRIPTION), + details + ) + ) def open_file(self): - path, _ = QFileDialog.getOpenFileName(self, filter='*.lsp', - directory=os.getenv('HOME')) + path, _ = QFileDialog.getOpenFileName( + self, filter='*.lsp', directory=os.getenv('HOME')) self.filepath = path self.accept() diff --git a/lisp/ui/logging/dialog.py b/lisp/ui/logging/dialog.py index b5c0d90c6..5131c7566 100644 --- a/lisp/ui/logging/dialog.py +++ b/lisp/ui/logging/dialog.py @@ -29,7 +29,7 @@ class LogDialogs: - def __init__(self, log_model, level=logging.ERROR): + def __init__(self, log_model, level=logging.ERROR, parent=None): """ :type log_model: lisp.ui.logging.models.LogRecordModel """ @@ -37,7 +37,7 @@ def __init__(self, log_model, level=logging.ERROR): self._log_model = log_model self._log_model.rowsInserted.connect(self._new_rows) - self._mmbox = MultiMessagesBox() + self._mmbox = MultiMessagesBox(parent=parent) def _new_rows(self, parent, start, end): for n in range(start, end + 1): diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 2143c3634..1e361398b 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -154,7 +154,7 @@ def __init__(self, conf, title='Linux Show Player', **kwargs): logging.getLogger().addHandler(self.logHandler) # Logging - self.logViewer = LogViewer(self.logModel, self.conf, parent=self) + self.logViewer = LogViewer(self.logModel, self.conf) # Logging status widget self.logStatus = LogStatusView(self.logModel) @@ -162,7 +162,8 @@ def __init__(self, conf, title='Linux Show Player', **kwargs): self.statusBar().addPermanentWidget(self.logStatus) # Logging dialogs for errors - self.logDialogs = LogDialogs(self.logModel, logging.ERROR) + self.logDialogs = LogDialogs( + self.logModel, level=logging.ERROR, parent=self) # Set component text self.retranslateUi() diff --git a/lisp/ui/settings/app_settings.py b/lisp/ui/settings/app_settings.py index d4db0cb79..5a349d164 100644 --- a/lisp/ui/settings/app_settings.py +++ b/lisp/ui/settings/app_settings.py @@ -17,78 +17,87 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging +from collections import namedtuple + from PyQt5 import QtCore -from PyQt5.QtWidgets import QDialog, QListWidget, QStackedWidget, \ - QDialogButtonBox +from PyQt5.QtCore import QModelIndex +from lisp.core.dicttree import DictNode +from lisp.ui.settings.pages_tree import TreeMultiSettingsPage, TreeSettingsModel +from lisp.ui.settings.settings_page import SettingsDialog, ConfigurationPage from lisp.ui.ui_utils import translate +logger = logging.getLogger(__name__) + +PageEntry = namedtuple('PageEntry', ('widget', 'config')) -class AppSettings(QDialog): - SettingsWidgets = {} +class AppSettings(SettingsDialog): + PagesRegistry = DictNode() def __init__(self, **kwargs): super().__init__(**kwargs) self.setWindowTitle(translate('AppSettings', 'LiSP preferences')) - - self.setWindowModality(QtCore.Qt.ApplicationModal) - self.setMaximumSize(635, 530) - self.setMinimumSize(635, 530) - self.resize(635, 530) - - self.listWidget = QListWidget(self) - self.listWidget.setGeometry(QtCore.QRect(5, 10, 185, 470)) - - self.sections = QStackedWidget(self) - self.sections.setGeometry(QtCore.QRect(200, 10, 430, 470)) - - for widget, config in self.SettingsWidgets.items(): - widget = widget(parent=self) - widget.resize(430, 465) - widget.load_settings(config.copy()) - - self.listWidget.addItem(translate('SettingsPageName', widget.Name)) - self.sections.addWidget(widget) - - if self.SettingsWidgets: - self.listWidget.setCurrentRow(0) - - self.listWidget.currentItemChanged.connect(self._change_page) - - self.dialogButtons = QDialogButtonBox(self) - self.dialogButtons.setGeometry(10, 495, 615, 30) - self.dialogButtons.setStandardButtons(QDialogButtonBox.Cancel | - QDialogButtonBox.Ok) - - self.dialogButtons.rejected.connect(self.reject) - self.dialogButtons.accepted.connect(self.accept) - - def accept(self): - for n in range(self.sections.count()): - widget = self.sections.widget(n) - - config = AppSettings.SettingsWidgets[widget.__class__] - config.update(widget.get_settings()) - config.write() - - return super().accept() - - @classmethod - def register_settings_widget(cls, widget, configuration): + self.setWindowModality(QtCore.Qt.WindowModal) + + def _buildMainPage(self): + self.model = TreeSettingsModel() + mainPage = TreeMultiSettingsPage(self.model) + + for r_node in AppSettings.PagesRegistry.children: + self._populateModel(QModelIndex(), r_node) + + mainPage.selectFirst() + return mainPage + + def _populateModel(self, m_parent, r_parent): + if r_parent.value is not None: + widget = r_parent.value.widget + config = r_parent.value.config + else: + widget = None + config = None + + try: + if widget is None: + # The current node have no widget, use the parent model-index + # as parent for it's children + mod_index = m_parent + elif issubclass(widget, ConfigurationPage): + mod_index = self.model.addPage(widget(config), parent=m_parent) + else: + mod_index = self.model.addPage(widget(), parent=m_parent) + except Exception: + if not isinstance(widget, type): + page_name = 'NoPage' + elif issubclass(widget, ConfigurationPage): + page_name = widget.Name + else: + page_name = widget.__name__ + + logger.warning( + 'Cannot load configuration page: "{}" ({})'.format( + page_name, r_parent.path()), exc_info=True) + else: + for r_node in r_parent.children: + self._populateModel(mod_index, r_node) + + @staticmethod + def registerSettingsWidget(path, widget, config): """ + :param path: indicate the widget "position": 'category.sub.key' + :type path: str :type widget: Type[lisp.ui.settings.settings_page.SettingsPage] - :type configuration: lisp.core.configuration.Configuration + :type config: lisp.core.configuration.Configuration """ - if widget not in cls.SettingsWidgets: - cls.SettingsWidgets[widget] = configuration + AppSettings.PagesRegistry.set( + path, PageEntry(widget=widget, config=config)) - @classmethod - def unregister_settings_widget(cls, widget): - cls.SettingsWidgets.pop(widget, None) - - def _change_page(self, current, previous): - if not current: - current = previous - - self.sections.setCurrentIndex(self.listWidget.row(current)) + @staticmethod + def unregisterSettingsWidget(path): + """ + :param path: indicate the widget "position": 'category.sub.key' + :type path: str + """ + AppSettings.PagesRegistry.pop(path) diff --git a/lisp/ui/settings/cue_settings.py b/lisp/ui/settings/cue_settings.py index 9f1c1d673..b444a1ae3 100644 --- a/lisp/ui/settings/cue_settings.py +++ b/lisp/ui/settings/cue_settings.py @@ -33,11 +33,11 @@ class CueSettingsRegistry(ClassBasedRegistry, metaclass=Singleton): def add_item(self, item, ref_class=Cue): if not issubclass(item, SettingsPage): - raise TypeError('item must be a SettingPage subclass, ' - 'not {0}'.format(item.__name__)) + raise TypeError( + 'item must be a SettingPage, not {0}'.format(item.__name__)) if not issubclass(ref_class, Cue): - raise TypeError('ref_class must be Cue or a subclass, not {0}' - .format(ref_class.__name__)) + raise TypeError( + 'ref_class must be a Cue, not {0}'.format(ref_class.__name__)) return super().add_item(item, ref_class) @@ -86,8 +86,8 @@ def sk(widget): else: settings_widget = widget() - settings_widget.load_settings(cue_properties) - settings_widget.enable_check(cue is None) + settings_widget.loadSettings(cue_properties) + settings_widget.enableCheck(cue is None) self.sections.addTab(settings_widget, translate('SettingsPageName', settings_widget.Name)) diff --git a/lisp/ui/settings/pages/app_general.py b/lisp/ui/settings/pages/app_general.py index 412ea2688..5bb4e7f40 100644 --- a/lisp/ui/settings/pages/app_general.py +++ b/lisp/ui/settings/pages/app_general.py @@ -23,48 +23,41 @@ from lisp import layouts from lisp.ui.icons import icon_themes_names -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.settings_page import ConfigurationPage from lisp.ui.themes import themes_names from lisp.ui.ui_utils import translate -class AppGeneral(SettingsPage): +class AppGeneral(ConfigurationPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'General') - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, config, **kwargs): + super().__init__(config, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) - # Startup layout + # Startup "layout" self.layoutGroup = QGroupBox(self) - self.layoutGroup.setTitle( - translate('AppGeneralSettings', 'Startup layout')) self.layoutGroup.setLayout(QVBoxLayout()) self.layout().addWidget(self.layoutGroup) self.startupDialogCheck = QCheckBox(self.layoutGroup) - self.startupDialogCheck.setText( - translate('AppGeneralSettings', 'Use startup dialog')) self.layoutGroup.layout().addWidget(self.startupDialogCheck) self.layoutCombo = QComboBox(self.layoutGroup) - self.layoutCombo.addItems([lay.NAME for lay in layouts.get_layouts()]) + for layout in layouts.get_layouts(): + self.layoutCombo.addItem(layout.NAME, layout.__name__) self.layoutGroup.layout().addWidget(self.layoutCombo) - self.startupDialogCheck.clicked.connect( - lambda check: self.layoutCombo.setEnabled(not check)) + self.startupDialogCheck.stateChanged.connect( + self.layoutCombo.setDisabled) - # Application style + # Application theme self.themeGroup = QGroupBox(self) - self.themeGroup.setTitle( - translate('AppGeneralSettings', 'Application theme')) self.themeGroup.setLayout(QGridLayout()) self.layout().addWidget(self.themeGroup) self.themeLabel = QLabel(self.themeGroup) - self.themeLabel.setText( - translate('AppGeneralSettings', 'Theme')) self.themeGroup.layout().addWidget(self.themeLabel, 0, 0) self.themeCombo = QComboBox(self.themeGroup) @@ -72,33 +65,44 @@ def __init__(self, **kwargs): self.themeGroup.layout().addWidget(self.themeCombo, 0, 1) self.iconsLabel = QLabel(self.themeGroup) - self.iconsLabel.setText( - translate('AppGeneralSettings', 'Icons')) self.themeGroup.layout().addWidget(self.iconsLabel, 1, 0) self.iconsCombo = QComboBox(self.themeGroup) self.iconsCombo.addItems(icon_themes_names()) self.themeGroup.layout().addWidget(self.iconsCombo, 1, 1) - def get_settings(self): - conf = {'layout': {}, 'theme': {}} + self.retranslateUi() + self.loadConfiguration() + + def retranslateUi(self): + self.layoutGroup.setTitle( + translate('AppGeneralSettings', 'Default layout')) + self.startupDialogCheck.setText( + translate('AppGeneralSettings', 'Enable startup layout selector')) + self.themeGroup.setTitle( + translate('AppGeneralSettings', 'Application themes')) + self.themeLabel.setText( + translate('AppGeneralSettings', 'UI theme')) + self.iconsLabel.setText( + translate('AppGeneralSettings', 'Icons theme')) + def applySettings(self): if self.startupDialogCheck.isChecked(): - conf['layout']['default'] = 'NoDefault' + self.config['layout.default'] = 'NoDefault' else: - conf['layout']['default'] = self.layoutCombo.currentText() + self.config['layout.default'] = self.layoutCombo.currentData() - conf['theme']['theme'] = self.themeCombo.currentText() - conf['theme']['icons'] = self.iconsCombo.currentText() + self.config['theme.theme'] = self.themeCombo.currentText() + self.config['theme.icons'] = self.iconsCombo.currentText() - return conf + self.config.write() - def load_settings(self, settings): - if settings['layout']['default'].lower() == 'nodefault': + def loadConfiguration(self): + layout_name = self.config.get('layout.default', 'nodefault') + if layout_name.lower() == 'nodefault': self.startupDialogCheck.setChecked(True) - self.layoutCombo.setEnabled(False) else: - self.layoutCombo.setCurrentText(settings['layout']['default']) + self.layoutCombo.setCurrentText(layout_name) - self.themeCombo.setCurrentText(settings['theme']['theme']) - self.iconsCombo.setCurrentText(settings['theme']['icons']) + self.themeCombo.setCurrentText(self.config.get('theme.theme', '')) + self.iconsCombo.setCurrentText(self.config.get('theme.icons', '')) \ No newline at end of file diff --git a/lisp/ui/settings/pages/cue_app_settings.py b/lisp/ui/settings/pages/cue_app_settings.py index cfae2bde6..987b3862a 100644 --- a/lisp/ui/settings/pages/cue_app_settings.py +++ b/lisp/ui/settings/pages/cue_app_settings.py @@ -20,16 +20,16 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt from PyQt5.QtWidgets import QVBoxLayout, QGroupBox -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.settings_page import ConfigurationPage from lisp.ui.ui_utils import translate from lisp.ui.widgets import FadeEdit -class CueAppSettings(SettingsPage): +class CueAppSettings(ConfigurationPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Cue Settings') - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, config, **kwargs): + super().__init__(config, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -50,24 +50,26 @@ def __init__(self, **kwargs): self.actionGroup.layout().addWidget(self.fadeActionEdit) self.retranslateUi() + self.loadConfiguration() def retranslateUi(self): - self.interruptGroup.setTitle(translate('CueSettings', 'Interrupt Fade')) - self.actionGroup.setTitle(translate('CueSettings', 'Fade Action')) + self.interruptGroup.setTitle(translate('CueSettings', 'Interrupt fade')) + self.actionGroup.setTitle(translate('CueSettings', 'Fade actions')) - def load_settings(self, settings): - # Interrupt - self.interruptFadeEdit.setDuration(settings['cue']['interruptFade']) - self.interruptFadeEdit.setFadeType(settings['cue']['interruptFadeType']) + def applySettings(self): + self.config['cue.interruptFade'] = self.interruptFadeEdit.duration() + self.config['cue.interruptFadeType'] = self.interruptFadeEdit.fadeType() + self.config['cue.fadeAction'] = self.fadeActionEdit.duration() + self.config['cue.fadeActionType'] = self.fadeActionEdit.fadeType() + + self.config.write() - # FadeAction - self.fadeActionEdit.setDuration(settings['cue']['fadeActionDuration']) - self.fadeActionEdit.setFadeType(settings['cue']['fadeActionType']) + def loadConfiguration(self): + self.interruptFadeEdit.setDuration( + self.config.get('cue.interruptFade', 0)) + self.interruptFadeEdit.setFadeType( + self.config.get('cue.interruptFadeType', '')) - def get_settings(self): - return {'cue': { - 'interruptFade': self.interruptFadeEdit.duration(), - 'interruptFadeType': self.interruptFadeEdit.fadeType(), - 'fadeActionDuration': self.fadeActionEdit.duration(), - 'fadeActionType': self.fadeActionEdit.fadeType() - }} + self.fadeActionEdit.setDuration(self.config.get('cue.fadeAction', 0)) + self.fadeActionEdit.setFadeType( + self.config.get('cue.fadeActionType', '')) \ No newline at end of file diff --git a/lisp/ui/settings/pages/cue_appearance.py b/lisp/ui/settings/pages/cue_appearance.py index 433e13c21..0ec39dc26 100644 --- a/lisp/ui/settings/pages/cue_appearance.py +++ b/lisp/ui/settings/pages/cue_appearance.py @@ -93,7 +93,7 @@ def retranslateUi(self): self.colorFButton.setText( translate('CueAppearanceSettings', 'Select font color')) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.cueNameGroup.setCheckable(enabled) self.cueNameGroup.setChecked(False) @@ -106,7 +106,7 @@ def enable_check(self, enabled): self.colorGroup.setCheckable(enabled) self.colorGroup.setChecked(False) - def get_settings(self): + def getSettings(self): conf = {} style = {} checkable = self.cueNameGroup.isCheckable() @@ -128,7 +128,7 @@ def get_settings(self): return conf - def load_settings(self, settings): + def loadSettings(self, settings): if 'name' in settings: self.cueNameEdit.setText(settings['name']) if 'description' in settings: diff --git a/lisp/ui/settings/pages/cue_general.py b/lisp/ui/settings/pages/cue_general.py index efefc4ad0..ece901090 100644 --- a/lisp/ui/settings/pages/cue_general.py +++ b/lisp/ui/settings/pages/cue_general.py @@ -197,7 +197,7 @@ def retranslateUi(self): self.fadeInGroup.setTitle(translate('FadeSettings', 'Fade In')) self.fadeOutGroup.setTitle(translate('FadeSettings', 'Fade Out')) - def load_settings(self, settings): + def loadSettings(self, settings): self.startActionCombo.setCurrentAction( settings.get('default_start_action', '')) self.stopActionCombo.setCurrentAction( @@ -212,7 +212,7 @@ def load_settings(self, settings): self.fadeOutEdit.setFadeType(settings.get('fadeout_type', '')) self.fadeOutEdit.setDuration(settings.get('fadeout_duration', 0)) - def enable_check(self, enabled): + def enableCheck(self, enabled): self.startActionGroup.setCheckable(enabled) self.startActionGroup.setChecked(False) self.stopActionGroup.setCheckable(enabled) @@ -230,7 +230,7 @@ def enable_check(self, enabled): self.fadeOutGroup.setCheckable(enabled) self.fadeOutGroup.setChecked(False) - def get_settings(self): + def getSettings(self): conf = {} checkable = self.preWaitGroup.isCheckable() diff --git a/lisp/ui/settings/pages/media_cue_settings.py b/lisp/ui/settings/pages/media_cue_settings.py index 26390790f..e66ddcef0 100644 --- a/lisp/ui/settings/pages/media_cue_settings.py +++ b/lisp/ui/settings/pages/media_cue_settings.py @@ -85,7 +85,7 @@ def retranslateUi(self): translate('MediaCueSettings', 'Repetition after first play ' '(-1 = infinite)')) - def get_settings(self): + def getSettings(self): conf = {'_media_': {}} checkable = self.startGroup.isCheckable() @@ -100,7 +100,7 @@ def get_settings(self): return conf - def enable_check(self, enabled): + def enableCheck(self, enabled): self.startGroup.setCheckable(enabled) self.startGroup.setChecked(False) @@ -110,7 +110,7 @@ def enable_check(self, enabled): self.loopGroup.setCheckable(enabled) self.loopGroup.setChecked(False) - def load_settings(self, settings): + def loadSettings(self, settings): if '_media_' in settings: if 'loop' in settings['_media_']: self.spinLoop.setValue(settings['_media_']['loop']) diff --git a/lisp/ui/settings/pages/plugins_settings.py b/lisp/ui/settings/pages/plugins_settings.py index 4b824ea7b..7a2b48f5f 100644 --- a/lisp/ui/settings/pages/plugins_settings.py +++ b/lisp/ui/settings/pages/plugins_settings.py @@ -22,12 +22,12 @@ QVBoxLayout from lisp import plugins -from lisp.ui.settings.settings_page import SettingsPage from lisp.ui.icons import IconTheme +from lisp.ui.settings.settings_page import ABCSettingsPage # TODO: just a proof-of concept -class PluginsSettings(SettingsPage): +class PluginsSettings(ABCSettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Plugins') def __init__(self, **kwargs): @@ -78,3 +78,6 @@ def __selection_changed(self): else: self.pluginDescription.setHtml( 'Description:

Authors: ') + + def applySettings(self): + pass diff --git a/lisp/ui/settings/pages_tree.py b/lisp/ui/settings/pages_tree.py new file mode 100644 index 000000000..3ac1b472e --- /dev/null +++ b/lisp/ui/settings/pages_tree.py @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import QAbstractItemModel, Qt, QModelIndex +from PyQt5.QtWidgets import QTreeView, QHBoxLayout, QWidget + +from lisp.ui.settings.settings_page import ABCSettingsPage + + +class TreeSettingsNode: + """ + :type parent: TreeSettingsNode + :type _children: list[TreeSettingsNode] + """ + def __init__(self, page): + self.parent = None + self.settingsPage = page + + self._children = [] + + def addChild(self, child): + self._children.append(child) + child.parent = self + + def removeChild(self, position): + if 0 < position < len(self._children): + child = self._children.pop(position) + child.parent = None + + return True + + return False + + def child(self, row): + return self._children[row] + + def childIndex(self, child): + return self._children.index(child) + + def childCount(self): + return len(self._children) + + def row(self): + if self.parent is not None: + return self.parent.childIndex(self) + + def walk(self): + for child in self._children: + yield child + yield from child.walk() + + +class TreeSettingsModel(QAbstractItemModel): + PageRole = Qt.UserRole + 1 + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._root = TreeSettingsNode(None) + + def rowCount(self, parent=QModelIndex()): + if parent.isValid(): + return parent.internalPointer().childCount() + else: + return self._root.childCount() + + def columnCount(self, parent=QModelIndex()): + return 1 + + def data(self, index, role=Qt.DisplayRole): + if index.isValid(): + node = index.internalPointer() + if role == Qt.DisplayRole: + return node.settingsPage.Name + elif role == TreeSettingsModel.PageRole: + return node.settingsPage + + def headerData(self, section, orientation, role=Qt.DisplayRole): + return None + + def flags(self, index): + return Qt.ItemIsEnabled | Qt.ItemIsSelectable + + def node(self, index): + if index.isValid(): + node = index.internalPointer() + if node is not None: + return node + + return self._root + + def parent(self, index): + parent = self.node(index).parent + if parent is self._root: + return QModelIndex() + + return self.createIndex(parent.row(), 0, parent) + + def index(self, row, column, parent=QModelIndex()): + child = self.node(parent).child(row) + if child is not None: + return self.createIndex(row, column, child) + + return QModelIndex() + + def pageIndex(self, page, parent=QModelIndex()): + parentNode = self.node(parent) + for row in range(parentNode.childCount()): + if parentNode.child(row).settingsPage is page: + return self.index(row, 0, parent) + + return QModelIndex() + + def addPage(self, page, parent=QModelIndex()): + if isinstance(page, ABCSettingsPage): + parentNode = self.node(parent) + position = parentNode.childCount() + + self.beginInsertRows(parent, position, position) + node = TreeSettingsNode(page) + parentNode.addChild(node) + self.endInsertRows() + + return self.index(position, 0, parent) + else: + raise TypeError( + 'TreeSettingsModel page must be an ABCSettingsPage, not {}' + .format(type(page).__name__) + ) + + def removePage(self, row, parent=QModelIndex()): + parentNode = self.node(parent) + + if row < parentNode.childCount(): + self.beginRemoveRows(parent, row, row) + parentNode.removeChild(row) + self.endRemoveRows() + + +class TreeMultiSettingsPage(ABCSettingsPage): + def __init__(self, navModel, **kwargs): + """ + :param navModel: The model that keeps all the pages-hierarchy + :type navModel: TreeSettingsModel + """ + super().__init__(**kwargs) + self.setLayout(QHBoxLayout()) + self.layout().setSpacing(0) + self.layout().setContentsMargins(0, 0, 0, 0) + self.navModel = navModel + + self.navWidget = QTreeView() + self.navWidget.setHeaderHidden(True) + self.navWidget.setModel(self.navModel) + self.layout().addWidget(self.navWidget) + + self._currentWidget = QWidget() + self.layout().addWidget(self._currentWidget) + + self.layout().setStretch(0, 2) + self.layout().setStretch(1, 5) + + self.navWidget.selectionModel().selectionChanged.connect( + self._changePage) + + def selectFirst(self): + self.navWidget.setCurrentIndex(self.navModel.index(0, 0, QModelIndex())) + + def currentWidget(self): + return self._currentWidget + + def applySettings(self): + root = self.navModel.node(QModelIndex()) + for node in root.walk(): + if node.settingsPage is not None: + node.settingsPage.applySettings() + + def _changePage(self, selected): + if selected.indexes(): + self.layout().removeWidget(self._currentWidget) + self._currentWidget.hide() + self._currentWidget = selected.indexes()[0].internalPointer().settingsPage + self._currentWidget.show() + self.layout().addWidget(self._currentWidget) + self.layout().setStretch(0, 2) + self.layout().setStretch(1, 5) diff --git a/lisp/ui/settings/settings_page.py b/lisp/ui/settings/settings_page.py index 3f79d26fb..f52149a81 100644 --- a/lisp/ui/settings/settings_page.py +++ b/lisp/ui/settings/settings_page.py @@ -17,20 +17,32 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtWidgets import QWidget +from abc import abstractmethod +from PyQt5.QtWidgets import QWidget, QDialog, QVBoxLayout, QDialogButtonBox, \ + QTabWidget -class SettingsPage(QWidget): - Name = 'Page' +from lisp.core.qmeta import QABCMeta +from lisp.ui.ui_utils import translate + + +class ABCSettingsPage(QWidget, metaclass=QABCMeta): + Name = 'SettingPage' + + @abstractmethod + def applySettings(self): + pass - def enable_check(self, enabled): - """Enable option check""" - def get_settings(self): +class SettingsPage(QWidget): + def enableCheck(self, enabled): + """Enable options check""" + + def getSettings(self): """Return the current settings.""" return {} - def load_settings(self, settings): + def loadSettings(self, settings): """Load the settings.""" @@ -39,5 +51,94 @@ class CueSettingsPage(SettingsPage): def __init__(self, cue_class, **kwargs): super().__init__(**kwargs) - self._cue_class = cue_class + + +class DummySettingsPage(ABCSettingsPage): + + def applySettings(self): + pass + + +class ConfigurationPage(ABCSettingsPage): + def __init__(self, config, **kwargs): + """ + :param config: Configuration object to "edit" + :type config: lisp.core.configuration.Configuration + """ + super().__init__(**kwargs) + self.config = config + + +class TabsMultiSettingsPage(QTabWidget, ABCSettingsPage): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._pages = [] + + def page(self, index): + return self._pages[index] + + def addPage(self, page): + if isinstance(page, ABCSettingsPage): + self._pages.append(page) + self.addTab(page, translate('SettingsPageName', page.Name)) + else: + raise TypeError( + 'page must be an ABCSettingsPage, not {}' + .format(type(page).__name__) + ) + + def removePage(self, index): + return self._pages.pop(index) + + def iterPages(self): + yield from self._pages + + def pageIndex(self, page): + return self._pages.index(page) + + def applySettings(self): + for page in self._pages: + page.applySettings() + + +class SettingsDialog(QDialog, ABCSettingsPage): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.setLayout(QVBoxLayout()) + self.setMaximumSize(640, 510) + self.setMinimumSize(640, 510) + self.resize(640, 510) + + self.mainPage = self._buildMainPage() + self.layout().addWidget(self.mainPage) + + self.dialogButtons = QDialogButtonBox(self) + self.dialogButtons.setStandardButtons( + QDialogButtonBox.Cancel + | QDialogButtonBox.Apply + | QDialogButtonBox.Ok + ) + self.layout().addWidget(self.dialogButtons) + + self.dialogButtons.button(QDialogButtonBox.Apply).clicked.connect( + self.applySettings) + self.dialogButtons.button(QDialogButtonBox.Ok).clicked.connect( + self.__onOK) + self.dialogButtons.button(QDialogButtonBox.Cancel).clicked.connect( + self.__onCancel) + + def applySettings(self): + self.mainPage.applySettings() + + @abstractmethod + def _buildMainPage(self): + """:rtype: ABCSettingsPage""" + pass + + def __onOK(self): + self.applySettings() + self.accept() + + def __onCancel(self): + self.reject() From fe19d8eb420f0b5936f610240afbb7efd5c54d77 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 22 Mar 2018 15:37:54 +0100 Subject: [PATCH 096/333] Refactoring of settings/configuration UI classes --- lisp/application.py | 25 +- lisp/core/class_based_registry.py | 2 +- lisp/core/configuration.py | 4 +- lisp/core/util.py | 27 +- lisp/i18n/lisp_cs.ts | 2 +- lisp/i18n/lisp_en.ts | 2 +- lisp/i18n/lisp_es.ts | 2 +- lisp/i18n/lisp_fr.ts | 2 +- lisp/i18n/lisp_it.ts | 2 +- lisp/i18n/lisp_sl_SI.ts | 2 +- .../cart_layout/cart_layout_settings.py | 2 +- lisp/layouts/cart_layout/layout.py | 4 +- lisp/layouts/cue_layout.py | 10 +- lisp/layouts/list_layout/layout.py | 4 +- .../list_layout/list_layout_settings.py | 2 +- lisp/plugins/action_cues/collection_cue.py | 2 +- lisp/plugins/action_cues/command_cue.py | 19 +- lisp/plugins/action_cues/index_action_cue.py | 2 +- lisp/plugins/action_cues/midi_cue.py | 2 +- lisp/plugins/action_cues/seek_cue.py | 2 +- lisp/plugins/action_cues/stop_all.py | 2 +- lisp/plugins/action_cues/volume_control.py | 11 +- .../plugins/controller/controller_settings.py | 39 +-- lisp/plugins/controller/protocols/__init__.py | 2 +- lisp/plugins/controller/protocols/keyboard.py | 10 +- lisp/plugins/controller/protocols/midi.py | 10 +- lisp/plugins/controller/protocols/osc.py | 10 +- lisp/plugins/gst_backend/gst_backend.py | 4 +- .../plugins/gst_backend/gst_media_settings.py | 2 +- lisp/plugins/gst_backend/gst_settings.py | 2 +- .../plugins/gst_backend/settings/alsa_sink.py | 2 +- .../gst_backend/settings/audio_dynamic.py | 2 +- .../plugins/gst_backend/settings/audio_pan.py | 2 +- lisp/plugins/gst_backend/settings/db_meter.py | 2 +- .../gst_backend/settings/equalizer10.py | 2 +- .../plugins/gst_backend/settings/jack_sink.py | 2 +- lisp/plugins/gst_backend/settings/pitch.py | 2 +- .../gst_backend/settings/preset_src.py | 2 +- lisp/plugins/gst_backend/settings/speed.py | 2 +- .../plugins/gst_backend/settings/uri_input.py | 2 +- .../gst_backend/settings/user_element.py | 2 +- lisp/plugins/gst_backend/settings/volume.py | 2 +- lisp/plugins/midi/midi.py | 4 +- lisp/plugins/midi/midi_settings.py | 2 +- lisp/plugins/osc/osc.py | 4 +- lisp/plugins/osc/osc_settings.py | 2 +- lisp/plugins/presets/presets_ui.py | 8 +- lisp/plugins/timecode/settings.py | 6 +- lisp/plugins/timecode/timecode.py | 4 +- lisp/plugins/triggers/triggers_settings.py | 2 +- lisp/ui/mainwindow.py | 4 +- lisp/ui/settings/app_configuration.py | 178 ++++++++++++ .../settings/{pages => app_pages}/__init__.py | 0 .../{pages => app_pages}/app_general.py | 2 +- .../{pages => app_pages}/cue_app_settings.py | 2 +- .../{pages => app_pages}/plugins_settings.py | 2 +- lisp/ui/settings/app_settings.py | 103 ------- lisp/ui/settings/cue_pages/__init__.py | 0 .../{pages => cue_pages}/cue_appearance.py | 14 +- .../{pages => cue_pages}/cue_general.py | 262 ++++++++++-------- .../media_cue_settings.py | 39 +-- lisp/ui/settings/cue_settings.py | 136 ++++----- lisp/ui/settings/pages.py | 159 +++++++++++ .../{pages_tree.py => pages_tree_model.py} | 77 +---- lisp/ui/settings/settings_page.py | 144 ---------- 65 files changed, 720 insertions(+), 669 deletions(-) create mode 100644 lisp/ui/settings/app_configuration.py rename lisp/ui/settings/{pages => app_pages}/__init__.py (100%) rename lisp/ui/settings/{pages => app_pages}/app_general.py (98%) rename lisp/ui/settings/{pages => app_pages}/cue_app_settings.py (97%) rename lisp/ui/settings/{pages => app_pages}/plugins_settings.py (98%) delete mode 100644 lisp/ui/settings/app_settings.py create mode 100644 lisp/ui/settings/cue_pages/__init__.py rename lisp/ui/settings/{pages => cue_pages}/cue_appearance.py (95%) rename lisp/ui/settings/{pages => cue_pages}/cue_general.py (62%) rename lisp/ui/settings/{pages => cue_pages}/media_cue_settings.py (81%) create mode 100644 lisp/ui/settings/pages.py rename lisp/ui/settings/{pages_tree.py => pages_tree_model.py} (62%) delete mode 100644 lisp/ui/settings/settings_page.py diff --git a/lisp/application.py b/lisp/application.py index eab4a8359..7c32fd554 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -34,14 +34,14 @@ from lisp.cues.media_cue import MediaCue from lisp.ui.layoutselect import LayoutSelect from lisp.ui.mainwindow import MainWindow -from lisp.ui.settings.app_settings import AppSettings +from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.settings.cue_settings import CueSettingsRegistry -from lisp.ui.settings.pages.app_general import AppGeneral -from lisp.ui.settings.pages.cue_app_settings import CueAppSettings -from lisp.ui.settings.pages.cue_appearance import Appearance -from lisp.ui.settings.pages.cue_general import CueGeneralSettings -from lisp.ui.settings.pages.media_cue_settings import MediaCueSettings -from lisp.ui.settings.pages.plugins_settings import PluginsSettings +from lisp.ui.settings.app_pages.app_general import AppGeneral +from lisp.ui.settings.app_pages.cue_app_settings import CueAppSettings +from lisp.ui.settings.cue_pages.cue_appearance import Appearance +from lisp.ui.settings.cue_pages.cue_general import CueGeneralSettings +from lisp.ui.settings.cue_pages.media_cue_settings import MediaCueSettings +from lisp.ui.settings.app_pages.plugins_settings import PluginsSettings logger = logging.getLogger(__name__) @@ -58,10 +58,11 @@ def __init__(self, app_conf): self.__session = None # Register general settings widget - AppSettings.registerSettingsWidget('general', AppGeneral, self.conf) - AppSettings.registerSettingsWidget( + AppConfigurationDialog.registerSettingsWidget( + 'general', AppGeneral, self.conf) + AppConfigurationDialog.registerSettingsWidget( 'general.cue', CueAppSettings, self.conf) - AppSettings.registerSettingsWidget( + AppConfigurationDialog.registerSettingsWidget( 'plugins', PluginsSettings, self.conf) # Register common cue-settings widgets @@ -206,6 +207,6 @@ def _load_from_file(self, session_file): self.__main_window.update_window_title() except Exception: logging.exception( - 'Error while reading the session file "{}"' - .format(session_file)) + 'Error while reading the session file "{}"'.format( + session_file)) self._new_session_dialog() diff --git a/lisp/core/class_based_registry.py b/lisp/core/class_based_registry.py index 577a6bd4d..779109c4a 100644 --- a/lisp/core/class_based_registry.py +++ b/lisp/core/class_based_registry.py @@ -58,7 +58,7 @@ def filter(self, ref_class=object): The items are sorted by ref_class name. """ - for class_ in sorted(self._registry.keys(), key=str): + for class_ in self._registry.keys(): if issubclass(ref_class, class_): yield from self._registry[class_] diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index 5637f94c3..15f08df14 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -30,7 +30,7 @@ from lisp import DEFAULT_APP_CONFIG, USER_APP_CONFIG from lisp.core.signal import Signal from lisp.core.singleton import ABCSingleton -from lisp.core.util import deep_update +from lisp.core.util import dict_merge logger = logging.getLogger(__name__) @@ -96,7 +96,7 @@ def update(self, new_conf): :param new_conf: a dict containing the new values :type new_conf: dict """ - deep_update(self._root, new_conf) + dict_merge(self._root, new_conf) def deep_copy(self): """Return a deep-copy of the internal dictionary.""" diff --git a/lisp/core/util.py b/lisp/core/util.py index e4d8be8c1..fa2e8429d 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -17,28 +17,28 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import functools import re import socket -from collections import Mapping +from collections import Mapping, MutableMapping from enum import Enum from os import listdir from os.path import isdir, exists, join -import functools -from uuid import uuid4 +def dict_merge(dct, merge_dct): + """Recursively merge the second dict into the first -def deep_update(d1, d2): - """Recursively update d1 with d2""" - for key in d2: - if key not in d1: - d1[key] = d2[key] - elif isinstance(d2[key], Mapping) and isinstance(d1[key], Mapping): - d1[key] = deep_update(d1[key], d2[key]) + :type dct: MutableMapping + :type merge_dct: Mapping + """ + for key, value in merge_dct.items(): + if (key in dct and + isinstance(dct[key], MutableMapping) and + isinstance(value, Mapping)): + dict_merge(dct[key], value) else: - d1[key] = d2[key] - - return d1 + dct[key] = value def find_packages(path='.'): @@ -236,6 +236,7 @@ class FunctionProxy: Can be useful in enum.Enum (Python enumeration) to have callable values. """ + def __init__(self, function): self.function = function diff --git a/lisp/i18n/lisp_cs.ts b/lisp/i18n/lisp_cs.ts index 9bf94f894..4230fb794 100644 --- a/lisp/i18n/lisp_cs.ts +++ b/lisp/i18n/lisp_cs.ts @@ -79,7 +79,7 @@ - AppSettings + AppConfiguration LiSP preferences diff --git a/lisp/i18n/lisp_en.ts b/lisp/i18n/lisp_en.ts index 8a59a45d0..4e9093945 100644 --- a/lisp/i18n/lisp_en.ts +++ b/lisp/i18n/lisp_en.ts @@ -79,7 +79,7 @@ - AppSettings + AppConfiguration LiSP preferences diff --git a/lisp/i18n/lisp_es.ts b/lisp/i18n/lisp_es.ts index 61de9ebab..2bc224dbc 100644 --- a/lisp/i18n/lisp_es.ts +++ b/lisp/i18n/lisp_es.ts @@ -79,7 +79,7 @@ - AppSettings + AppConfiguration LiSP preferences diff --git a/lisp/i18n/lisp_fr.ts b/lisp/i18n/lisp_fr.ts index f122e65b3..b04353aee 100644 --- a/lisp/i18n/lisp_fr.ts +++ b/lisp/i18n/lisp_fr.ts @@ -79,7 +79,7 @@ - AppSettings + AppConfiguration LiSP preferences diff --git a/lisp/i18n/lisp_it.ts b/lisp/i18n/lisp_it.ts index 6450b2f0c..d1861dd4a 100644 --- a/lisp/i18n/lisp_it.ts +++ b/lisp/i18n/lisp_it.ts @@ -79,7 +79,7 @@ - AppSettings + AppConfiguration LiSP preferences diff --git a/lisp/i18n/lisp_sl_SI.ts b/lisp/i18n/lisp_sl_SI.ts index 04808e2a6..f883d87df 100644 --- a/lisp/i18n/lisp_sl_SI.ts +++ b/lisp/i18n/lisp_sl_SI.ts @@ -79,7 +79,7 @@ - AppSettings + AppConfiguration LiSP preferences diff --git a/lisp/layouts/cart_layout/cart_layout_settings.py b/lisp/layouts/cart_layout/cart_layout_settings.py index 81804213d..f1a3ea704 100644 --- a/lisp/layouts/cart_layout/cart_layout_settings.py +++ b/lisp/layouts/cart_layout/cart_layout_settings.py @@ -21,7 +21,7 @@ from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QGridLayout, \ QSpinBox, QLabel -from lisp.ui.settings.settings_page import ConfigurationPage +from lisp.ui.settings.pages import ConfigurationPage from lisp.ui.ui_utils import translate diff --git a/lisp/layouts/cart_layout/layout.py b/lisp/layouts/cart_layout/layout.py index 2c1fd48cb..8aeeb50c1 100644 --- a/lisp/layouts/cart_layout/layout.py +++ b/lisp/layouts/cart_layout/layout.py @@ -31,7 +31,7 @@ from lisp.layouts.cart_layout.page_widget import PageWidget from lisp.layouts.cue_layout import CueLayout from lisp.ui.mainwindow import MainWindow -from lisp.ui.settings.app_settings import AppSettings +from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.ui_utils import translate @@ -51,7 +51,7 @@ class CartLayout(QTabWidget, CueLayout): def __init__(self, **kwargs): super().__init__(**kwargs) - AppSettings.registerSettingsWidget( + AppConfigurationDialog.registerSettingsWidget( 'cart_layout', CartLayoutSettings, self.app.conf) self.tabBar().setObjectName('CartTabBar') diff --git a/lisp/layouts/cue_layout.py b/lisp/layouts/cue_layout.py index b62921b69..625f46676 100644 --- a/lisp/layouts/cue_layout.py +++ b/lisp/layouts/cue_layout.py @@ -28,7 +28,7 @@ from lisp.cues.cue_actions import UpdateCueAction, UpdateCuesAction from lisp.layouts.cue_menu_registry import CueMenuRegistry from lisp.ui.mainwindow import MainWindow -from lisp.ui.settings.cue_settings import CueSettings +from lisp.ui.settings.cue_settings import CueSettingsDialog # TODO: split widget and "controller" @@ -124,13 +124,13 @@ def fadeout_all(self): cue.execute(CueAction.FadeOut) def edit_cue(self, cue): - edit_ui = CueSettings(cue, parent=MainWindow()) + edit_ui = CueSettingsDialog(cue, parent=MainWindow()) def on_apply(settings): action = UpdateCueAction(settings, cue) MainActionsHandler.do_action(action) - edit_ui.on_apply.connect(on_apply) + edit_ui.onApply.connect(on_apply) edit_ui.exec_() def edit_selected_cues(self): @@ -138,13 +138,13 @@ def edit_selected_cues(self): if cues: # Use the greatest common superclass between the selected cues - edit_ui = CueSettings(cue_class=greatest_common_superclass(cues)) + edit_ui = CueSettingsDialog(cue_class=greatest_common_superclass(cues)) def on_apply(settings): action = UpdateCuesAction(settings, cues) MainActionsHandler.do_action(action) - edit_ui.on_apply.connect(on_apply) + edit_ui.onApply.connect(on_apply) edit_ui.exec_() @abstractmethod diff --git a/lisp/layouts/list_layout/layout.py b/lisp/layouts/list_layout/layout.py index 5067bbeb2..ea4772abc 100644 --- a/lisp/layouts/list_layout/layout.py +++ b/lisp/layouts/list_layout/layout.py @@ -36,7 +36,7 @@ from lisp.layouts.list_layout.list_layout_settings import ListLayoutSettings from lisp.layouts.list_layout.playing_list_widget import RunningCuesListWidget from lisp.ui.mainwindow import MainWindow -from lisp.ui.settings.app_settings import AppSettings +from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.ui_utils import translate @@ -62,7 +62,7 @@ class ListLayout(QWidget, CueLayout): def __init__(self, **kwargs): super().__init__(**kwargs) - AppSettings.registerSettingsWidget( + AppConfigurationDialog.registerSettingsWidget( 'list_layout', ListLayoutSettings, self.app.conf) self.setLayout(QGridLayout()) diff --git a/lisp/layouts/list_layout/list_layout_settings.py b/lisp/layouts/list_layout/list_layout_settings.py index d0a8baa63..2577aadc7 100644 --- a/lisp/layouts/list_layout/list_layout_settings.py +++ b/lisp/layouts/list_layout/list_layout_settings.py @@ -22,7 +22,7 @@ from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QComboBox, \ QHBoxLayout, QLabel, QKeySequenceEdit, QGridLayout -from lisp.ui.settings.settings_page import ConfigurationPage +from lisp.ui.settings.pages import ConfigurationPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/action_cues/collection_cue.py b/lisp/plugins/action_cues/collection_cue.py index c2624edea..782de274e 100644 --- a/lisp/plugins/action_cues/collection_cue.py +++ b/lisp/plugins/action_cues/collection_cue.py @@ -28,7 +28,7 @@ from lisp.ui.qdelegates import CueActionDelegate, CueSelectionDelegate from lisp.ui.qmodels import CueClassRole, SimpleCueListModel from lisp.ui.settings.cue_settings import CueSettingsRegistry -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/action_cues/command_cue.py b/lisp/plugins/action_cues/command_cue.py index f2ff23cd8..8f40ead3b 100644 --- a/lisp/plugins/action_cues/command_cue.py +++ b/lisp/plugins/action_cues/command_cue.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging import subprocess from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP @@ -26,9 +27,11 @@ from lisp.core.properties import Property from lisp.cues.cue import Cue, CueAction from lisp.ui.settings.cue_settings import CueSettingsRegistry -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate +logger = logging.getLogger(__name__) + class CommandCue(Cue): """Cue able to execute system commands. @@ -71,14 +74,16 @@ def __exec_command(self): # If terminate normally, killed or in no-error mode if not self.__stopped: self._ended() - - self.__process = None - self.__stopped = False elif not self.no_error: # If an error occurs and not in no-error mode - self._error( - translate('CommandCue', 'Process ended with an error status.'), - translate('CommandCue', 'Exit code: ') + str(rcode)) + logger.error(translate( + 'CommandCue', + 'Command cue ended with an error status. ' + 'Exit code: {}').format(rcode)) + self._error() + + self.__process = None + self.__stopped = False def __stop__(self, fade=False): if self.__process is not None: diff --git a/lisp/plugins/action_cues/index_action_cue.py b/lisp/plugins/action_cues/index_action_cue.py index 01db40561..b433c5b3f 100644 --- a/lisp/plugins/action_cues/index_action_cue.py +++ b/lisp/plugins/action_cues/index_action_cue.py @@ -25,7 +25,7 @@ from lisp.core.properties import Property from lisp.cues.cue import Cue, CueAction from lisp.ui.settings.cue_settings import CueSettingsRegistry -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate from lisp.ui.widgets import CueActionComboBox diff --git a/lisp/plugins/action_cues/midi_cue.py b/lisp/plugins/action_cues/midi_cue.py index 9543b02c9..46c4fd32b 100644 --- a/lisp/plugins/action_cues/midi_cue.py +++ b/lisp/plugins/action_cues/midi_cue.py @@ -26,7 +26,7 @@ from lisp.plugins import get_plugin from lisp.plugins.midi.midi_utils import str_msg_to_dict, dict_msg_to_str from lisp.ui.settings.cue_settings import CueSettingsRegistry -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/action_cues/seek_cue.py b/lisp/plugins/action_cues/seek_cue.py index f487bc69f..a72ee9562 100644 --- a/lisp/plugins/action_cues/seek_cue.py +++ b/lisp/plugins/action_cues/seek_cue.py @@ -28,7 +28,7 @@ from lisp.cues.media_cue import MediaCue from lisp.ui.cuelistdialog import CueSelectDialog from lisp.ui.settings.cue_settings import CueSettingsRegistry -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/action_cues/stop_all.py b/lisp/plugins/action_cues/stop_all.py index 2fffd3f0d..8a3869bf9 100644 --- a/lisp/plugins/action_cues/stop_all.py +++ b/lisp/plugins/action_cues/stop_all.py @@ -24,7 +24,7 @@ from lisp.core.properties import Property from lisp.cues.cue import Cue, CueAction from lisp.ui.settings.cue_settings import CueSettingsRegistry -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/action_cues/volume_control.py b/lisp/plugins/action_cues/volume_control.py index 105cac6d5..7e398d794 100644 --- a/lisp/plugins/action_cues/volume_control.py +++ b/lisp/plugins/action_cues/volume_control.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . + import logging from PyQt5 import QtCore @@ -34,7 +35,7 @@ from lisp.cues.media_cue import MediaCue from lisp.ui.cuelistdialog import CueSelectDialog from lisp.ui.settings.cue_settings import CueSettingsRegistry -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate from lisp.ui.widgets import FadeEdit @@ -77,14 +78,14 @@ def __start__(self, fade=False): return True if self.duration > 0: - if self.__fader.target.current_volume > self.volume: + if self.__fader.target.live_volume > self.volume: self.__fade(FadeOutType[self.fade_type]) return True - elif self.__fader.target.current_volume < self.volume: + elif self.__fader.target.live_volume < self.volume: self.__fade(FadeInType[self.fade_type]) return True else: - self.__fader.target.current_volume = self.volume + self.__fader.target.live_volume = self.volume return False @@ -107,7 +108,7 @@ def __fade(self, fade_type): if ended: # to avoid approximation problems - self.__fader.target.current_volume = self.volume + self.__fader.target.live_volume = self.volume self._ended() except Exception: logger.exception(translate('OscCue', 'Error during cue execution.')) diff --git a/lisp/plugins/controller/controller_settings.py b/lisp/plugins/controller/controller_settings.py index 2b6e5439b..b4a1259e4 100644 --- a/lisp/plugins/controller/controller_settings.py +++ b/lisp/plugins/controller/controller_settings.py @@ -18,47 +18,22 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QHBoxLayout, QTabWidget from lisp.plugins.controller import protocols -from lisp.ui.settings.settings_page import CueSettingsPage -from lisp.ui.ui_utils import translate +from lisp.ui.settings.pages import TabsMultiSettingsPage, CuePageMixin -class ControllerSettings(CueSettingsPage): +class ControllerSettings(TabsMultiSettingsPage, CuePageMixin): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Cue Control') - def __init__(self, cue_class, **kwargs): - super().__init__(cue_class, **kwargs) - self.setLayout(QHBoxLayout()) - - self._pages = [] - - self.tabWidget = QTabWidget(self) - self.layout().addWidget(self.tabWidget) + def __init__(self, cue_type, **kwargs): + super().__init__(cue_type=cue_type, **kwargs) for page in protocols.ProtocolsSettingsPages: - page_widget = page(cue_class, parent=self) - - self.tabWidget.addTab(page_widget, - translate('SettingsPageName', page.Name)) - self._pages.append(page_widget) - - self.tabWidget.setCurrentIndex(0) - - def enableCheck(self, enabled): - for page in self._pages: - page.enableCheck(enabled) + self.addPage(page(cue_type, parent=self)) def getSettings(self): - settings = {} - for page in self._pages: - settings.update(page.getSettings()) - - return {'controller': settings} + return {'controller': super().getSettings()} def loadSettings(self, settings): - settings = settings.get('controller', {}) - - for page in self._pages: - page.loadSettings(settings) + super().loadSettings(settings.get('controller', {})) diff --git a/lisp/plugins/controller/protocols/__init__.py b/lisp/plugins/controller/protocols/__init__.py index cff9a4f6e..172148930 100644 --- a/lisp/plugins/controller/protocols/__init__.py +++ b/lisp/plugins/controller/protocols/__init__.py @@ -20,7 +20,7 @@ from os.path import dirname from lisp.core.loading import load_classes -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage Protocols = [] ProtocolsSettingsPages = [] diff --git a/lisp/plugins/controller/protocols/keyboard.py b/lisp/plugins/controller/protocols/keyboard.py index cacd64f2e..7c10cfeeb 100644 --- a/lisp/plugins/controller/protocols/keyboard.py +++ b/lisp/plugins/controller/protocols/keyboard.py @@ -26,7 +26,7 @@ from lisp.ui.qdelegates import LineEditDelegate, \ CueActionDelegate from lisp.ui.qmodels import SimpleTableModel -from lisp.ui.settings.settings_page import CueSettingsPage +from lisp.ui.settings.pages import CueSettingsPage from lisp.ui.ui_utils import translate @@ -46,8 +46,8 @@ def __key_pressed(self, key_event): class KeyboardSettings(CueSettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Keyboard Shortcuts') - def __init__(self, cue_class, **kwargs): - super().__init__(cue_class, **kwargs) + def __init__(self, cue_type, **kwargs): + super().__init__(cue_type, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -59,7 +59,7 @@ def __init__(self, cue_class, **kwargs): translate('ControllerKeySettings', 'Key'), translate('ControllerKeySettings', 'Action')]) - self.keyboardView = KeyboardView(cue_class, parent=self.keyGroup) + self.keyboardView = KeyboardView(cue_type, parent=self.keyGroup) self.keyboardView.setModel(self.keyboardModel) self.keyGroup.layout().addWidget(self.keyboardView, 0, 0, 1, 2) @@ -95,7 +95,7 @@ def loadSettings(self, settings): self.keyboardModel.appendRow(key, action) def __new_key(self): - self.keyboardModel.appendRow('', self._cue_class.CueActions[0].name) + self.keyboardModel.appendRow('', self.cue_type.CueActions[0].name) def __remove_key(self): self.keyboardModel.removeRow(self.keyboardView.currentIndex().row()) diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index 24b18a256..7024ef96e 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -26,7 +26,7 @@ from lisp.ui.qdelegates import ComboBoxDelegate, SpinBoxDelegate, \ CueActionDelegate from lisp.ui.qmodels import SimpleTableModel -from lisp.ui.settings.settings_page import CueSettingsPage +from lisp.ui.settings.pages import CueSettingsPage from lisp.ui.ui_utils import translate @@ -58,8 +58,8 @@ def from_string(message_str): class MidiSettings(CueSettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'MIDI Controls') - def __init__(self, cue_class, **kwargs): - super().__init__(cue_class, **kwargs) + def __init__(self, cue_type, **kwargs): + super().__init__(cue_type, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -75,7 +75,7 @@ def __init__(self, cue_class, **kwargs): translate('ControllerMidiSettings', 'Note'), translate('ControllerMidiSettings', 'Action')]) - self.midiView = MidiView(cue_class, parent=self.midiGroup) + self.midiView = MidiView(cue_type, parent=self.midiGroup) self.midiView.setModel(self.midiModel) self.midiGroup.layout().addWidget(self.midiView, 0, 0, 1, 2) @@ -102,7 +102,7 @@ def __init__(self, cue_class, **kwargs): self.retranslateUi() - self._default_action = self._cue_class.CueActions[0].name + self._default_action = self.cue_type.CueActions[0].name def retranslateUi(self): self.addButton.setText(translate('ControllerSettings', 'Add')) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 53d2c22c0..b3e58863f 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -33,7 +33,7 @@ from lisp.ui.qdelegates import ComboBoxDelegate, \ LineEditDelegate from lisp.ui.qmodels import SimpleTableModel -from lisp.ui.settings.settings_page import CueSettingsPage +from lisp.ui.settings.pages import CueSettingsPage from lisp.ui.ui_utils import translate @@ -194,8 +194,8 @@ def __init__(self, **kwargs): class OscSettings(CueSettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'OSC Controls') - def __init__(self, cue_class, **kwargs): - super().__init__(cue_class, **kwargs) + def __init__(self, cue_type, **kwargs): + super().__init__(cue_type, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -210,7 +210,7 @@ def __init__(self, cue_class, **kwargs): translate('ControllerOscSettings', 'Arguments'), translate('ControllerOscSettings', 'Actions')]) - self.OscView = OscView(cue_class, parent=self.oscGroup) + self.OscView = OscView(cue_type, parent=self.oscGroup) self.OscView.setModel(self.oscModel) self.oscGroup.layout().addWidget(self.OscView, 0, 0, 1, 2) @@ -249,7 +249,7 @@ def __init__(self, cue_class, **kwargs): self.retranslateUi() - self._default_action = self._cue_class.CueActions[0].name + self._default_action = self.cue_type.CueActions[0].name def retranslateUi(self): self.addButton.setText(translate('ControllerOscSettings', 'Add')) diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index 83eb9eeb3..9b7f9bb05 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -38,7 +38,7 @@ from lisp.plugins.gst_backend.gst_settings import GstSettings from lisp.plugins.gst_backend.gst_utils import gst_parse_tags_list, \ gst_uri_metadata, gst_mime_types, gst_uri_duration -from lisp.ui.settings.app_settings import AppSettings +from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.settings.cue_settings import CueSettingsRegistry @@ -56,7 +56,7 @@ def __init__(self, app): Gst.init(None) # Register GStreamer settings widgets - AppSettings.registerSettingsWidget( + AppConfigurationDialog.registerSettingsWidget( 'plugins.gst', GstSettings, GstBackend.Config) # Add MediaCue settings widget to the CueLayout CueSettingsRegistry().add_item(GstMediaSettings, MediaCue) diff --git a/lisp/plugins/gst_backend/gst_media_settings.py b/lisp/plugins/gst_backend/gst_media_settings.py index 0314eefe7..74db24441 100644 --- a/lisp/plugins/gst_backend/gst_media_settings.py +++ b/lisp/plugins/gst_backend/gst_media_settings.py @@ -25,7 +25,7 @@ from lisp.plugins.gst_backend.gst_pipe_edit import GstPipeEditDialog from lisp.plugins.gst_backend.settings import pages_by_element -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/gst_backend/gst_settings.py b/lisp/plugins/gst_backend/gst_settings.py index d64d30b24..714afa9e3 100644 --- a/lisp/plugins/gst_backend/gst_settings.py +++ b/lisp/plugins/gst_backend/gst_settings.py @@ -21,7 +21,7 @@ from PyQt5.QtWidgets import QVBoxLayout, QGroupBox from lisp.plugins.gst_backend.gst_pipe_edit import GstPipeEdit -from lisp.ui.settings.settings_page import ConfigurationPage +from lisp.ui.settings.pages import ConfigurationPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/gst_backend/settings/alsa_sink.py b/lisp/plugins/gst_backend/settings/alsa_sink.py index 9d444a46d..8a28d3dd5 100644 --- a/lisp/plugins/gst_backend/settings/alsa_sink.py +++ b/lisp/plugins/gst_backend/settings/alsa_sink.py @@ -23,7 +23,7 @@ QVBoxLayout from lisp.plugins.gst_backend.elements.alsa_sink import AlsaSink -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/gst_backend/settings/audio_dynamic.py b/lisp/plugins/gst_backend/settings/audio_dynamic.py index 5d48c2e15..2c2e88cc6 100644 --- a/lisp/plugins/gst_backend/settings/audio_dynamic.py +++ b/lisp/plugins/gst_backend/settings/audio_dynamic.py @@ -25,7 +25,7 @@ QLabel, QVBoxLayout from lisp.plugins.gst_backend.elements.audio_dynamic import AudioDynamic -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate MIN_dB = 0.000000312 # -100dB diff --git a/lisp/plugins/gst_backend/settings/audio_pan.py b/lisp/plugins/gst_backend/settings/audio_pan.py index 3f4ed5273..176fefca9 100644 --- a/lisp/plugins/gst_backend/settings/audio_pan.py +++ b/lisp/plugins/gst_backend/settings/audio_pan.py @@ -21,7 +21,7 @@ from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QSlider, QLabel, QVBoxLayout from lisp.plugins.gst_backend.elements.audio_pan import AudioPan -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/gst_backend/settings/db_meter.py b/lisp/plugins/gst_backend/settings/db_meter.py index c3f575d99..6a5a4e2eb 100644 --- a/lisp/plugins/gst_backend/settings/db_meter.py +++ b/lisp/plugins/gst_backend/settings/db_meter.py @@ -24,7 +24,7 @@ from lisp.plugins.gst_backend.elements.db_meter import DbMeter from lisp.plugins.gst_backend.gi_repository import Gst -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/gst_backend/settings/equalizer10.py b/lisp/plugins/gst_backend/settings/equalizer10.py index e81f1d889..012952d8c 100644 --- a/lisp/plugins/gst_backend/settings/equalizer10.py +++ b/lisp/plugins/gst_backend/settings/equalizer10.py @@ -23,7 +23,7 @@ from PyQt5.QtWidgets import QGroupBox, QGridLayout, QLabel, QSlider, QVBoxLayout from lisp.plugins.gst_backend.elements.equalizer10 import Equalizer10 -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/gst_backend/settings/jack_sink.py b/lisp/plugins/gst_backend/settings/jack_sink.py index c8c33178d..2d2ffa863 100644 --- a/lisp/plugins/gst_backend/settings/jack_sink.py +++ b/lisp/plugins/gst_backend/settings/jack_sink.py @@ -25,7 +25,7 @@ QDialogButtonBox, QPushButton, QVBoxLayout from lisp.plugins.gst_backend.elements.jack_sink import JackSink -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/gst_backend/settings/pitch.py b/lisp/plugins/gst_backend/settings/pitch.py index 0c3844861..88e14f7e4 100644 --- a/lisp/plugins/gst_backend/settings/pitch.py +++ b/lisp/plugins/gst_backend/settings/pitch.py @@ -24,7 +24,7 @@ from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QSlider, QLabel, QVBoxLayout from lisp.plugins.gst_backend.elements.pitch import Pitch -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/gst_backend/settings/preset_src.py b/lisp/plugins/gst_backend/settings/preset_src.py index d3e3c270b..1501698be 100644 --- a/lisp/plugins/gst_backend/settings/preset_src.py +++ b/lisp/plugins/gst_backend/settings/preset_src.py @@ -21,7 +21,7 @@ from PyQt5.QtWidgets import QGroupBox, QComboBox, QVBoxLayout, QTimeEdit from lisp.plugins.gst_backend.elements.preset_src import PresetSrc -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/gst_backend/settings/speed.py b/lisp/plugins/gst_backend/settings/speed.py index 67dac3b61..8c040ed18 100644 --- a/lisp/plugins/gst_backend/settings/speed.py +++ b/lisp/plugins/gst_backend/settings/speed.py @@ -22,7 +22,7 @@ from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QSlider, QLabel, QVBoxLayout from lisp.plugins.gst_backend.elements.speed import Speed -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/gst_backend/settings/uri_input.py b/lisp/plugins/gst_backend/settings/uri_input.py index 758339a85..5b584413f 100644 --- a/lisp/plugins/gst_backend/settings/uri_input.py +++ b/lisp/plugins/gst_backend/settings/uri_input.py @@ -22,7 +22,7 @@ QGridLayout, QCheckBox, QSpinBox, QLabel, QFileDialog, QVBoxLayout from lisp.plugins.gst_backend.elements.uri_input import UriInput -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/gst_backend/settings/user_element.py b/lisp/plugins/gst_backend/settings/user_element.py index bce10a84d..9757c963c 100644 --- a/lisp/plugins/gst_backend/settings/user_element.py +++ b/lisp/plugins/gst_backend/settings/user_element.py @@ -22,7 +22,7 @@ from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QPlainTextEdit, QLabel from lisp.plugins.gst_backend.elements.user_element import UserElement -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/gst_backend/settings/volume.py b/lisp/plugins/gst_backend/settings/volume.py index 78b103e03..c7840d399 100644 --- a/lisp/plugins/gst_backend/settings/volume.py +++ b/lisp/plugins/gst_backend/settings/volume.py @@ -23,7 +23,7 @@ from lisp.backend.audio_utils import db_to_linear, linear_to_db from lisp.plugins.gst_backend.elements.volume import Volume -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate from lisp.ui.widgets import QMuteButton diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index 776290012..a103041f8 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -23,7 +23,7 @@ from lisp.plugins.midi.midi_input import MIDIInput from lisp.plugins.midi.midi_output import MIDIOutput from lisp.plugins.midi.midi_settings import MIDISettings -from lisp.ui.settings.app_settings import AppSettings +from lisp.ui.settings.app_configuration import AppConfigurationDialog class Midi(Plugin): @@ -37,7 +37,7 @@ def __init__(self, app): super().__init__(app) # Register the settings widget - AppSettings.registerSettingsWidget( + AppConfigurationDialog.registerSettingsWidget( 'plugins.midi', MIDISettings, Midi.Config) # Load the backend and set it as current mido backend diff --git a/lisp/plugins/midi/midi_settings.py b/lisp/plugins/midi/midi_settings.py index 00a5ca533..7034776a3 100644 --- a/lisp/plugins/midi/midi_settings.py +++ b/lisp/plugins/midi/midi_settings.py @@ -22,7 +22,7 @@ QLabel from lisp.plugins.midi.midi_utils import mido_backend -from lisp.ui.settings.settings_page import ConfigurationPage +from lisp.ui.settings.pages import ConfigurationPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/osc/osc.py b/lisp/plugins/osc/osc.py index ae0fedf16..740baf998 100644 --- a/lisp/plugins/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -22,7 +22,7 @@ from lisp.core.plugin import Plugin from lisp.plugins.osc.osc_server import OscServer from lisp.plugins.osc.osc_settings import OscSettings -from lisp.ui.settings.app_settings import AppSettings +from lisp.ui.settings.app_configuration import AppConfigurationDialog # TODO: layout-controls in external plugin (now disabled, see osc_server.py) @@ -37,7 +37,7 @@ def __init__(self, app): super().__init__(app) # Register the settings widget - AppSettings.registerSettingsWidget( + AppConfigurationDialog.registerSettingsWidget( 'plugins.osc', OscSettings, Osc.Config) # Create a server instance diff --git a/lisp/plugins/osc/osc_settings.py b/lisp/plugins/osc/osc_settings.py index 2d6411841..fb42b9b5d 100644 --- a/lisp/plugins/osc/osc_settings.py +++ b/lisp/plugins/osc/osc_settings.py @@ -22,7 +22,7 @@ from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel, \ QHBoxLayout, QSpinBox, QLineEdit -from lisp.ui.settings.settings_page import ConfigurationPage +from lisp.ui.settings.pages import ConfigurationPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/presets/presets_ui.py b/lisp/plugins/presets/presets_ui.py index 589dbeed2..7aa0688bc 100644 --- a/lisp/plugins/presets/presets_ui.py +++ b/lisp/plugins/presets/presets_ui.py @@ -30,7 +30,7 @@ PresetImportError, scan_presets, delete_preset, write_preset, \ rename_preset, load_preset, load_on_cues from lisp.ui.mainwindow import MainWindow -from lisp.ui.settings.cue_settings import CueSettings, CueSettingsRegistry +from lisp.ui.settings.cue_settings import CueSettingsDialog, CueSettingsRegistry from lisp.ui.ui_utils import translate from lisp.ui.widgets import QDetailedMessageBox @@ -275,10 +275,10 @@ def __edit_preset(self): except Exception: cue_class = Cue - edit_dialog = CueSettings(cue_class=cue_class) - edit_dialog.load_settings(preset) + edit_dialog = CueSettingsDialog(cue_class=cue_class) + edit_dialog.loadSettings(preset) if edit_dialog.exec_() == edit_dialog.Accepted: - preset.update(edit_dialog.get_settings()) + preset.update(edit_dialog.getSettings()) try: write_preset(item.text(), preset) except OSError as e: diff --git a/lisp/plugins/timecode/settings.py b/lisp/plugins/timecode/settings.py index 7f6cdb8b8..5da574326 100644 --- a/lisp/plugins/timecode/settings.py +++ b/lisp/plugins/timecode/settings.py @@ -25,15 +25,15 @@ from lisp.plugins.timecode import protocols from lisp.plugins.timecode.cue_tracker import TcFormat -from lisp.ui.settings.settings_page import SettingsPage, CueSettingsPage, ConfigurationPage +from lisp.ui.settings.pages import SettingsPage, CueSettingsPage, ConfigurationPage from lisp.ui.ui_utils import translate class TimecodeSettings(CueSettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Timecode') - def __init__(self, cue_class, **kwargs): - super().__init__(cue_class, **kwargs) + def __init__(self, cue_type, **kwargs): + super().__init__(cue_type, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) diff --git a/lisp/plugins/timecode/timecode.py b/lisp/plugins/timecode/timecode.py index babcbbfc2..dabd4cd34 100644 --- a/lisp/plugins/timecode/timecode.py +++ b/lisp/plugins/timecode/timecode.py @@ -29,7 +29,7 @@ from lisp.plugins.timecode.protocol import TimecodeProtocol from lisp.plugins.timecode.settings import TimecodeAppSettings, \ TimecodeSettings -from lisp.ui.settings.app_settings import AppSettings +from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.settings.cue_settings import CueSettingsRegistry @@ -49,7 +49,7 @@ def __init__(self, app): # Register cue-settings-page CueSettingsRegistry().add_item(TimecodeSettings, MediaCue) # Register the settings widget - AppSettings.registerSettingsWidget( + AppConfigurationDialog.registerSettingsWidget( 'plugins.timecode', TimecodeAppSettings, Timecode.Config) # Load available protocols diff --git a/lisp/plugins/triggers/triggers_settings.py b/lisp/plugins/triggers/triggers_settings.py index bf4d0ecdc..956a76531 100644 --- a/lisp/plugins/triggers/triggers_settings.py +++ b/lisp/plugins/triggers/triggers_settings.py @@ -28,7 +28,7 @@ from lisp.ui.qdelegates import ComboBoxDelegate, CueActionDelegate, \ CueSelectionDelegate from lisp.ui.qmodels import CueClassRole, SimpleCueListModel -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 1e361398b..62d5f066f 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -35,7 +35,7 @@ from lisp.ui.logging.models import log_model_factory from lisp.ui.logging.status import LogStatusView from lisp.ui.logging.viewer import LogViewer -from lisp.ui.settings.app_settings import AppSettings +from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.ui_utils import translate @@ -298,7 +298,7 @@ def _save_with_name(self): self.save_session.emit(filename) def _show_preferences(self): - prefUi = AppSettings(parent=self) + prefUi = AppConfigurationDialog(parent=self) prefUi.exec_() def _load_from_file(self): diff --git a/lisp/ui/settings/app_configuration.py b/lisp/ui/settings/app_configuration.py new file mode 100644 index 000000000..b40a06a76 --- /dev/null +++ b/lisp/ui/settings/app_configuration.py @@ -0,0 +1,178 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import logging +from collections import namedtuple + +from PyQt5 import QtCore +from PyQt5.QtCore import QModelIndex +from PyQt5.QtWidgets import QVBoxLayout, QDialogButtonBox, QDialog, QTreeView, \ + QHBoxLayout, QWidget, QSizePolicy + +from lisp.core.dicttree import DictNode +from lisp.ui.settings.pages_tree_model import SettingsPagesTreeModel +from lisp.ui.settings.pages import ConfigurationPage +from lisp.ui.ui_utils import translate + +logger = logging.getLogger(__name__) + +PageEntry = namedtuple('PageEntry', ('widget', 'config')) + + +class AppConfigurationDialog(QDialog): + PagesRegistry = DictNode() + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.setWindowTitle(translate('AppConfiguration', 'LiSP preferences')) + self.setWindowModality(QtCore.Qt.WindowModal) + self.setMaximumSize(640, 510) + self.setMinimumSize(640, 510) + self.resize(640, 510) + self.setLayout(QVBoxLayout()) + + self.model = SettingsPagesTreeModel() + for r_node in AppConfigurationDialog.PagesRegistry.children: + self._populateModel(QModelIndex(), r_node) + + self.mainPage = TreeMultiSettingsWidget(self.model) + self.mainPage.selectFirst() + self.layout().addWidget(self.mainPage) + + self.dialogButtons = QDialogButtonBox(self) + self.dialogButtons.setStandardButtons( + QDialogButtonBox.Cancel | + QDialogButtonBox.Apply | + QDialogButtonBox.Ok + ) + self.layout().addWidget(self.dialogButtons) + + self.dialogButtons.button(QDialogButtonBox.Cancel).clicked.connect( + self.reject) + self.dialogButtons.button(QDialogButtonBox.Apply).clicked.connect( + self.applySettings) + self.dialogButtons.button(QDialogButtonBox.Ok).clicked.connect( + self.__onOk) + + def applySettings(self): + self.mainPage.applySettings() + + def _populateModel(self, m_parent, r_parent): + if r_parent.value is not None: + widget = r_parent.value.widget + config = r_parent.value.config + else: + widget = None + config = None + + try: + if widget is None: + # The current node have no widget, use the parent model-index + # as parent for it's children + mod_index = m_parent + elif issubclass(widget, ConfigurationPage): + mod_index = self.model.addPage(widget(config), parent=m_parent) + else: + mod_index = self.model.addPage(widget(), parent=m_parent) + except Exception: + if not isinstance(widget, type): + page_name = 'NoPage' + elif issubclass(widget, ConfigurationPage): + page_name = widget.Name + else: + page_name = widget.__name__ + + logger.warning( + 'Cannot load configuration page: "{}" ({})'.format( + page_name, r_parent.path()), exc_info=True) + else: + for r_node in r_parent.children: + self._populateModel(mod_index, r_node) + + def __onOk(self): + self.applySettings() + self.accept() + + @staticmethod + def registerSettingsWidget(path, widget, config): + """ + :param path: indicate the widget "position": 'category.sub.key' + :type path: str + :type widget: Type[lisp.ui.settings.settings_page.SettingsPage] + :type config: lisp.core.configuration.Configuration + """ + AppConfigurationDialog.PagesRegistry.set( + path, PageEntry(widget=widget, config=config)) + + @staticmethod + def unregisterSettingsWidget(path): + """ + :param path: indicate the widget "position": 'category.sub.key' + :type path: str + """ + AppConfigurationDialog.PagesRegistry.pop(path) + + +class TreeMultiSettingsWidget(QWidget): + def __init__(self, navModel, **kwargs): + """ + :param navModel: The model that keeps all the pages-hierarchy + :type navModel: SettingsPagesTreeModel + """ + super().__init__(**kwargs) + self.setLayout(QHBoxLayout()) + self.layout().setSpacing(0) + self.layout().setContentsMargins(0, 0, 0, 0) + self.navModel = navModel + + self.navWidget = QTreeView() + self.navWidget.setHeaderHidden(True) + self.navWidget.setModel(self.navModel) + self.layout().addWidget(self.navWidget) + + self._currentWidget = QWidget() + self.layout().addWidget(self._currentWidget) + + self.layout().setStretch(0, 2) + self.layout().setStretch(1, 5) + + self.navWidget.selectionModel().selectionChanged.connect( + self._changePage) + + def selectFirst(self): + self.navWidget.setCurrentIndex(self.navModel.index(0, 0, QModelIndex())) + + def currentWidget(self): + return self._currentWidget + + def applySettings(self): + root = self.navModel.node(QModelIndex()) + for node in root.walk(): + if node.widget is not None: + node.widget.applySettings() + + def _changePage(self, selected): + if selected.indexes(): + self.layout().removeWidget(self._currentWidget) + self._currentWidget.hide() + self._currentWidget = selected.indexes()[0].internalPointer().widget + self._currentWidget.show() + self.layout().addWidget(self._currentWidget) + self.layout().setStretch(0, 2) + self.layout().setStretch(1, 5) diff --git a/lisp/ui/settings/pages/__init__.py b/lisp/ui/settings/app_pages/__init__.py similarity index 100% rename from lisp/ui/settings/pages/__init__.py rename to lisp/ui/settings/app_pages/__init__.py diff --git a/lisp/ui/settings/pages/app_general.py b/lisp/ui/settings/app_pages/app_general.py similarity index 98% rename from lisp/ui/settings/pages/app_general.py rename to lisp/ui/settings/app_pages/app_general.py index 5bb4e7f40..ed649a6b4 100644 --- a/lisp/ui/settings/pages/app_general.py +++ b/lisp/ui/settings/app_pages/app_general.py @@ -23,7 +23,7 @@ from lisp import layouts from lisp.ui.icons import icon_themes_names -from lisp.ui.settings.settings_page import ConfigurationPage +from lisp.ui.settings.pages import ConfigurationPage from lisp.ui.themes import themes_names from lisp.ui.ui_utils import translate diff --git a/lisp/ui/settings/pages/cue_app_settings.py b/lisp/ui/settings/app_pages/cue_app_settings.py similarity index 97% rename from lisp/ui/settings/pages/cue_app_settings.py rename to lisp/ui/settings/app_pages/cue_app_settings.py index 987b3862a..c64b06e40 100644 --- a/lisp/ui/settings/pages/cue_app_settings.py +++ b/lisp/ui/settings/app_pages/cue_app_settings.py @@ -20,7 +20,7 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt from PyQt5.QtWidgets import QVBoxLayout, QGroupBox -from lisp.ui.settings.settings_page import ConfigurationPage +from lisp.ui.settings.pages import ConfigurationPage from lisp.ui.ui_utils import translate from lisp.ui.widgets import FadeEdit diff --git a/lisp/ui/settings/pages/plugins_settings.py b/lisp/ui/settings/app_pages/plugins_settings.py similarity index 98% rename from lisp/ui/settings/pages/plugins_settings.py rename to lisp/ui/settings/app_pages/plugins_settings.py index 7a2b48f5f..449c5f945 100644 --- a/lisp/ui/settings/pages/plugins_settings.py +++ b/lisp/ui/settings/app_pages/plugins_settings.py @@ -23,7 +23,7 @@ from lisp import plugins from lisp.ui.icons import IconTheme -from lisp.ui.settings.settings_page import ABCSettingsPage +from lisp.ui.settings.pages import ABCSettingsPage # TODO: just a proof-of concept diff --git a/lisp/ui/settings/app_settings.py b/lisp/ui/settings/app_settings.py deleted file mode 100644 index 5a349d164..000000000 --- a/lisp/ui/settings/app_settings.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -import logging -from collections import namedtuple - -from PyQt5 import QtCore -from PyQt5.QtCore import QModelIndex - -from lisp.core.dicttree import DictNode -from lisp.ui.settings.pages_tree import TreeMultiSettingsPage, TreeSettingsModel -from lisp.ui.settings.settings_page import SettingsDialog, ConfigurationPage -from lisp.ui.ui_utils import translate - -logger = logging.getLogger(__name__) - -PageEntry = namedtuple('PageEntry', ('widget', 'config')) - - -class AppSettings(SettingsDialog): - PagesRegistry = DictNode() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.setWindowTitle(translate('AppSettings', 'LiSP preferences')) - self.setWindowModality(QtCore.Qt.WindowModal) - - def _buildMainPage(self): - self.model = TreeSettingsModel() - mainPage = TreeMultiSettingsPage(self.model) - - for r_node in AppSettings.PagesRegistry.children: - self._populateModel(QModelIndex(), r_node) - - mainPage.selectFirst() - return mainPage - - def _populateModel(self, m_parent, r_parent): - if r_parent.value is not None: - widget = r_parent.value.widget - config = r_parent.value.config - else: - widget = None - config = None - - try: - if widget is None: - # The current node have no widget, use the parent model-index - # as parent for it's children - mod_index = m_parent - elif issubclass(widget, ConfigurationPage): - mod_index = self.model.addPage(widget(config), parent=m_parent) - else: - mod_index = self.model.addPage(widget(), parent=m_parent) - except Exception: - if not isinstance(widget, type): - page_name = 'NoPage' - elif issubclass(widget, ConfigurationPage): - page_name = widget.Name - else: - page_name = widget.__name__ - - logger.warning( - 'Cannot load configuration page: "{}" ({})'.format( - page_name, r_parent.path()), exc_info=True) - else: - for r_node in r_parent.children: - self._populateModel(mod_index, r_node) - - @staticmethod - def registerSettingsWidget(path, widget, config): - """ - :param path: indicate the widget "position": 'category.sub.key' - :type path: str - :type widget: Type[lisp.ui.settings.settings_page.SettingsPage] - :type config: lisp.core.configuration.Configuration - """ - AppSettings.PagesRegistry.set( - path, PageEntry(widget=widget, config=config)) - - @staticmethod - def unregisterSettingsWidget(path): - """ - :param path: indicate the widget "position": 'category.sub.key' - :type path: str - """ - AppSettings.PagesRegistry.pop(path) diff --git a/lisp/ui/settings/cue_pages/__init__.py b/lisp/ui/settings/cue_pages/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lisp/ui/settings/pages/cue_appearance.py b/lisp/ui/settings/cue_pages/cue_appearance.py similarity index 95% rename from lisp/ui/settings/pages/cue_appearance.py rename to lisp/ui/settings/cue_pages/cue_appearance.py index 0ec39dc26..f5d1dc88f 100644 --- a/lisp/ui/settings/pages/cue_appearance.py +++ b/lisp/ui/settings/cue_pages/cue_appearance.py @@ -21,7 +21,7 @@ from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QHBoxLayout, QTextEdit, \ QSpinBox, QLabel, QLineEdit -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.widgets import QColorButton from lisp.ui.ui_utils import translate @@ -107,14 +107,14 @@ def enableCheck(self, enabled): self.colorGroup.setChecked(False) def getSettings(self): - conf = {} + settings = {} style = {} checkable = self.cueNameGroup.isCheckable() if not (checkable and not self.cueNameGroup.isChecked()): - conf['name'] = self.cueNameEdit.text() + settings['name'] = self.cueNameEdit.text() if not (checkable and not self.cueDescriptionGroup.isChecked()): - conf['description'] = self.cueDescriptionEdit.toPlainText() + settings['description'] = self.cueDescriptionEdit.toPlainText() if not (checkable and not self.colorGroup.isChecked()): if self.colorBButton.color() is not None: style['background'] = self.colorBButton.color() @@ -124,9 +124,9 @@ def getSettings(self): style['font-size'] = str(self.fontSizeSpin.value()) + 'pt' if style: - conf['stylesheet'] = dict_to_css(style) + settings['stylesheet'] = dict_to_css(style) - return conf + return settings def loadSettings(self, settings): if 'name' in settings: @@ -152,7 +152,7 @@ def css_to_dict(css): try: name, value = attribute.split(':') dict[name.strip()] = value.strip() - except: + except Exception: pass return dict diff --git a/lisp/ui/settings/pages/cue_general.py b/lisp/ui/settings/cue_pages/cue_general.py similarity index 62% rename from lisp/ui/settings/pages/cue_general.py rename to lisp/ui/settings/cue_pages/cue_general.py index ece901090..19f2f70a4 100644 --- a/lisp/ui/settings/pages/cue_general.py +++ b/lisp/ui/settings/cue_pages/cue_general.py @@ -19,40 +19,42 @@ from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QLabel, QVBoxLayout, \ - QDoubleSpinBox, QTabWidget, QWidget + QDoubleSpinBox from lisp.cues.cue import CueAction -from lisp.ui.settings.settings_page import CueSettingsPage +from lisp.ui.settings.pages import CueSettingsPage, TabsMultiSettingsPage, CuePageMixin from lisp.ui.ui_utils import translate -from lisp.ui.widgets import FadeComboBox, CueActionComboBox,\ +from lisp.ui.widgets import FadeComboBox, CueActionComboBox, \ CueNextActionComboBox, FadeEdit -class CueGeneralSettings(CueSettingsPage): +class CueGeneralSettings(TabsMultiSettingsPage, CuePageMixin): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Cue') - def __init__(self, cue_class, **kwargs): - super().__init__(cue_class, **kwargs) - self.setLayout(QVBoxLayout()) + def __init__(self, cue_type, **kwargs): + super().__init__(cue_type=cue_type, **kwargs) + self.addPage(CueBehavioursPage(self.cue_type)) + self.addPage(CueWaitsPage(self.cue_type)) + self.addPage(CueFadePage(self.cue_type)) + - self.tabWidget = QTabWidget(self) - self.layout().addWidget(self.tabWidget) +class CueBehavioursPage(CueSettingsPage): + Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Behaviours') - # TAB 1 (Behaviours) - self.tab_1 = QWidget(self.tabWidget) - self.tab_1.setLayout(QVBoxLayout()) - self.tabWidget.addTab(self.tab_1, '1') + def __init__(self, cue_type, **kwargs): + super().__init__(cue_type, **kwargs) + self.setLayout(QVBoxLayout()) # Start-Action - self.startActionGroup = QGroupBox(self.tab_1) + self.startActionGroup = QGroupBox(self) self.startActionGroup.setLayout(QHBoxLayout()) - self.tab_1.layout().addWidget(self.startActionGroup) + self.layout().addWidget(self.startActionGroup) self.startActionCombo = CueActionComboBox( { CueAction.Start, CueAction.FadeInStart - }.intersection(cue_class.CueActions).union({ + }.intersection(self.cue_type.CueActions).union({ CueAction.DoNothing }), mode=CueActionComboBox.Mode.Value, @@ -66,9 +68,9 @@ def __init__(self, cue_class, **kwargs): self.startActionGroup.layout().addWidget(self.startActionLabel) # Stop-Action - self.stopActionGroup = QGroupBox(self.tab_1) + self.stopActionGroup = QGroupBox(self) self.stopActionGroup.setLayout(QHBoxLayout()) - self.tab_1.layout().addWidget(self.stopActionGroup) + self.layout().addWidget(self.stopActionGroup) self.stopActionCombo = CueActionComboBox( { @@ -76,7 +78,7 @@ def __init__(self, cue_class, **kwargs): CueAction.Pause, CueAction.FadeOutStop, CueAction.FadeOutPause - }.intersection(cue_class.CueActions).union({ + }.intersection(self.cue_type.CueActions).union({ CueAction.DoNothing }), mode=CueActionComboBox.Mode.Value, @@ -89,19 +91,64 @@ def __init__(self, cue_class, **kwargs): self.stopActionLabel.setAlignment(Qt.AlignCenter) self.stopActionGroup.layout().addWidget(self.stopActionLabel) - self.tab_1.layout().addSpacing(150) - self.tab_1.setEnabled(self.stopActionCombo.isEnabled() and - self.startActionCombo.isEnabled()) + self.layout().addSpacing(150) + self.setEnabled( + self.stopActionCombo.isEnabled() and + self.startActionCombo.isEnabled() + ) + + self.retranslateUi() + + def retranslateUi(self): + # Start-Action + self.startActionGroup.setTitle( + translate('CueSettings', 'Start action')) + self.startActionLabel.setText( + translate('CueSettings', 'Default action to start the cue')) + + # Stop-Action + self.stopActionGroup.setTitle( + translate('CueSettings', 'Stop action')) + self.stopActionLabel.setText( + translate('CueSettings', 'Default action to stop the cue')) + + def enableCheck(self, enabled): + self.startActionGroup.setCheckable(enabled) + self.startActionGroup.setChecked(False) + self.stopActionGroup.setCheckable(enabled) + self.stopActionGroup.setChecked(False) + + def getSettings(self): + settings = {} + checkable = self.startActionGroup.isCheckable() + + if ((not checkable or self.startActionGroup.isChecked()) and + self.startActionCombo.isEnabled()): + settings['default_start_action'] = self.startActionCombo.currentAction() + if ((not checkable or self.stopActionGroup.isChecked()) and + self.stopActionCombo.isEnabled()): + settings['default_stop_action'] = self.stopActionCombo.currentAction() + + return settings + + def loadSettings(self, settings): + self.startActionCombo.setCurrentAction( + settings.get('default_start_action', '')) + self.stopActionCombo.setCurrentAction( + settings.get('default_stop_action', '')) + - # TAB 2 (Pre/Post Wait) - self.tab_2 = QWidget(self.tabWidget) - self.tab_2.setLayout(QVBoxLayout()) - self.tabWidget.addTab(self.tab_2, '2') +class CueWaitsPage(CueSettingsPage): + Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Pre/Post Wait') + + def __init__(self, cue_type, **kwargs): + super().__init__(cue_type=cue_type, **kwargs) + self.setLayout(QVBoxLayout()) # Pre wait - self.preWaitGroup = QGroupBox(self.tab_2) + self.preWaitGroup = QGroupBox(self) self.preWaitGroup.setLayout(QHBoxLayout()) - self.tab_2.layout().addWidget(self.preWaitGroup) + self.layout().addWidget(self.preWaitGroup) self.preWaitSpin = QDoubleSpinBox(self.preWaitGroup) self.preWaitSpin.setMaximum(3600 * 24) @@ -112,9 +159,9 @@ def __init__(self, cue_class, **kwargs): self.preWaitGroup.layout().addWidget(self.preWaitLabel) # Post wait - self.postWaitGroup = QGroupBox(self.tab_2) + self.postWaitGroup = QGroupBox(self) self.postWaitGroup.setLayout(QHBoxLayout()) - self.tab_2.layout().addWidget(self.postWaitGroup) + self.layout().addWidget(self.postWaitGroup) self.postWaitSpin = QDoubleSpinBox(self.postWaitGroup) self.postWaitSpin.setMaximum(3600 * 24) @@ -125,134 +172,115 @@ def __init__(self, cue_class, **kwargs): self.postWaitGroup.layout().addWidget(self.postWaitLabel) # Next action - self.nextActionGroup = QGroupBox(self.tab_2) + self.nextActionGroup = QGroupBox(self) self.nextActionGroup.setLayout(QHBoxLayout()) - self.tab_2.layout().addWidget(self.nextActionGroup) + self.layout().addWidget(self.nextActionGroup) self.nextActionCombo = CueNextActionComboBox( parent=self.nextActionGroup) self.nextActionGroup.layout().addWidget(self.nextActionCombo) - # TAB 3 (Fade In/Out) - self.tab_3 = QWidget(self.tabWidget) - self.tab_3.setLayout(QVBoxLayout()) - self.tabWidget.addTab(self.tab_3, '3') + self.retranslateUi() + + def retranslateUi(self): + # PreWait + self.preWaitGroup.setTitle(translate('CueSettings', 'Pre wait')) + self.preWaitLabel.setText( + translate('CueSettings', 'Wait before cue execution')) + # PostWait + self.postWaitGroup.setTitle(translate('CueSettings', 'Post wait')) + self.postWaitLabel.setText( + translate('CueSettings', 'Wait after cue execution')) + # NextAction + self.nextActionGroup.setTitle(translate('CueSettings', 'Next action')) + + def enableCheck(self, enabled): + self.preWaitGroup.setCheckable(enabled) + self.preWaitGroup.setChecked(False) + self.postWaitGroup.setCheckable(enabled) + self.postWaitGroup.setChecked(False) + self.nextActionGroup.setCheckable(enabled) + self.nextActionGroup.setChecked(False) + + def loadSettings(self, settings): + self.preWaitSpin.setValue(settings.get('pre_wait', 0)) + self.postWaitSpin.setValue(settings.get('post_wait', 0)) + self.nextActionCombo.setCurrentAction(settings.get('next_action', '')) + + def getSettings(self): + settings = {} + checkable = self.preWaitGroup.isCheckable() + + if not checkable or self.preWaitGroup.isChecked(): + settings['pre_wait'] = self.preWaitSpin.value() + if not checkable or self.postWaitGroup.isChecked(): + settings['post_wait'] = self.postWaitSpin.value() + if not checkable or self.nextActionGroup.isChecked(): + settings['next_action'] = self.nextActionCombo.currentData() + + return settings + + +class CueFadePage(CueSettingsPage): + Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Fade In/Out') + + def __init__(self, cue_type, **kwargs): + super().__init__(cue_type, **kwargs) + self.setLayout(QVBoxLayout()) # FadeIn - self.fadeInGroup = QGroupBox(self.tab_3) + self.fadeInGroup = QGroupBox(self) self.fadeInGroup.setEnabled( - CueAction.FadeInStart in cue_class.CueActions + CueAction.FadeInStart in cue_type.CueActions ) self.fadeInGroup.setLayout(QHBoxLayout()) - self.tab_3.layout().addWidget(self.fadeInGroup) + self.layout().addWidget(self.fadeInGroup) self.fadeInEdit = FadeEdit(self.fadeInGroup, mode=FadeComboBox.Mode.FadeIn) self.fadeInGroup.layout().addWidget(self.fadeInEdit) # FadeOut - self.fadeOutGroup = QGroupBox(self.tab_3) + self.fadeOutGroup = QGroupBox(self) self.fadeOutGroup.setEnabled( - CueAction.FadeOutPause in cue_class.CueActions or - CueAction.FadeOutStop in cue_class.CueActions + CueAction.FadeOutPause in cue_type.CueActions or + CueAction.FadeOutStop in cue_type.CueActions ) self.fadeOutGroup.setLayout(QHBoxLayout()) - self.tab_3.layout().addWidget(self.fadeOutGroup) + self.layout().addWidget(self.fadeOutGroup) - self.fadeOutEdit = FadeEdit(self.fadeOutGroup, - mode=FadeComboBox.Mode.FadeOut) + self.fadeOutEdit = FadeEdit( + self.fadeOutGroup, mode=FadeComboBox.Mode.FadeOut) self.fadeOutGroup.layout().addWidget(self.fadeOutEdit) self.retranslateUi() def retranslateUi(self): - # Tabs - self.tabWidget.setTabText(0, translate('CueSettings', 'Behaviours')) - self.tabWidget.setTabText(1, translate('CueSettings', 'Pre/Post Wait')) - self.tabWidget.setTabText(2, translate('CueSettings', 'Fade In/Out')) - - # Start-Action - self.startActionGroup.setTitle( - translate('CueSettings', 'Start action')) - self.startActionLabel.setText( - translate('CueSettings', 'Default action to start the cue')) - # Stop-Action - self.stopActionGroup.setTitle( - translate('CueSettings', 'Stop action')) - self.stopActionLabel.setText( - translate('CueSettings', 'Default action to stop the cue')) - - # PreWait - self.preWaitGroup.setTitle(translate('CueSettings', 'Pre wait')) - self.preWaitLabel.setText( - translate('CueSettings', 'Wait before cue execution')) - # PostWait - self.postWaitGroup.setTitle(translate('CueSettings', 'Post wait')) - self.postWaitLabel.setText( - translate('CueSettings', 'Wait after cue execution')) - # NextAction - self.nextActionGroup.setTitle(translate('CueSettings', 'Next action')) - # FadeIn/Out self.fadeInGroup.setTitle(translate('FadeSettings', 'Fade In')) self.fadeOutGroup.setTitle(translate('FadeSettings', 'Fade Out')) def loadSettings(self, settings): - self.startActionCombo.setCurrentAction( - settings.get('default_start_action', '')) - self.stopActionCombo.setCurrentAction( - settings.get('default_stop_action', '')) - - self.preWaitSpin.setValue(settings.get('pre_wait', 0)) - self.postWaitSpin.setValue(settings.get('post_wait', 0)) - self.nextActionCombo.setCurrentAction(settings.get('next_action', '')) - self.fadeInEdit.setFadeType(settings.get('fadein_type', '')) self.fadeInEdit.setDuration(settings.get('fadein_duration', 0)) self.fadeOutEdit.setFadeType(settings.get('fadeout_type', '')) self.fadeOutEdit.setDuration(settings.get('fadeout_duration', 0)) def enableCheck(self, enabled): - self.startActionGroup.setCheckable(enabled) - self.startActionGroup.setChecked(False) - self.stopActionGroup.setCheckable(enabled) - self.stopActionGroup.setChecked(False) - - self.preWaitGroup.setCheckable(enabled) - self.preWaitGroup.setChecked(False) - self.postWaitGroup.setCheckable(enabled) - self.postWaitGroup.setChecked(False) - self.nextActionGroup.setCheckable(enabled) - self.nextActionGroup.setChecked(False) - self.fadeInGroup.setCheckable(enabled) self.fadeInGroup.setChecked(False) self.fadeOutGroup.setCheckable(enabled) self.fadeOutGroup.setChecked(False) def getSettings(self): - conf = {} - checkable = self.preWaitGroup.isCheckable() + settings = {} + checkable = self.fadeInGroup.isCheckable() + + if not checkable or self.fadeInGroup.isChecked(): + settings['fadein_type'] = self.fadeInEdit.fadeType() + settings['fadein_duration'] = self.fadeInEdit.duration() + if not checkable or self.fadeInGroup.isChecked(): + settings['fadeout_type'] = self.fadeOutEdit.fadeType() + settings['fadeout_duration'] = self.fadeOutEdit.duration() - if not (checkable and not self.startActionGroup.isChecked()): - if self.startActionCombo.isEnabled(): - conf['default_start_action'] = self.startActionCombo.currentAction() - if not (checkable and not self.stopActionGroup.isChecked()): - if self.stopActionCombo.isEnabled(): - conf['default_stop_action'] = self.stopActionCombo.currentAction() - - if not (checkable and not self.preWaitGroup.isChecked()): - conf['pre_wait'] = self.preWaitSpin.value() - if not (checkable and not self.postWaitGroup.isChecked()): - conf['post_wait'] = self.postWaitSpin.value() - if not (checkable and not self.nextActionGroup.isChecked()): - conf['next_action'] = self.nextActionCombo.currentData() - - if not (checkable and not self.fadeInGroup.isChecked()): - conf['fadein_type'] = self.fadeInEdit.fadeType() - conf['fadein_duration'] = self.fadeInEdit.duration() - if not (checkable and not self.fadeInGroup.isChecked()): - conf['fadeout_type'] = self.fadeOutEdit.fadeType() - conf['fadeout_duration'] = self.fadeOutEdit.duration() - - return conf + return settings diff --git a/lisp/ui/settings/pages/media_cue_settings.py b/lisp/ui/settings/cue_pages/media_cue_settings.py similarity index 81% rename from lisp/ui/settings/pages/media_cue_settings.py rename to lisp/ui/settings/cue_pages/media_cue_settings.py index e66ddcef0..691d64fe5 100644 --- a/lisp/ui/settings/pages/media_cue_settings.py +++ b/lisp/ui/settings/cue_pages/media_cue_settings.py @@ -21,7 +21,7 @@ from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QTimeEdit, QLabel, \ QSpinBox, QVBoxLayout -from lisp.ui.settings.settings_page import SettingsPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate @@ -86,19 +86,19 @@ def retranslateUi(self): '(-1 = infinite)')) def getSettings(self): - conf = {'_media_': {}} + settings = {} checkable = self.startGroup.isCheckable() if not (checkable and not self.startGroup.isChecked()): time = self.startEdit.time().msecsSinceStartOfDay() - conf['_media_']['start_time'] = time + settings['start_time'] = time if not (checkable and not self.stopGroup.isChecked()): time = self.stopEdit.time().msecsSinceStartOfDay() - conf['_media_']['stop_time'] = time + settings['stop_time'] = time if not (checkable and not self.loopGroup.isChecked()): - conf['_media_']['loop'] = self.spinLoop.value() + settings['loop'] = self.spinLoop.value() - return conf + return {'media': settings} def enableCheck(self, enabled): self.startGroup.setCheckable(enabled) @@ -111,19 +111,20 @@ def enableCheck(self, enabled): self.loopGroup.setChecked(False) def loadSettings(self, settings): - if '_media_' in settings: - if 'loop' in settings['_media_']: - self.spinLoop.setValue(settings['_media_']['loop']) - if 'start_time' in settings['_media_']: - t = self._to_qtime(settings['_media_']['start_time']) - self.startEdit.setTime(t) - if 'stop_time' in settings['_media_']: - t = self._to_qtime(settings['_media_']['stop_time']) - self.stopEdit.setTime(t) - - t = self._to_qtime(settings['_media_'].get('duration', 0)) - self.startEdit.setMaximumTime(t) - self.stopEdit.setMaximumTime(t) + settings = settings.get('media', {}) + + if 'loop' in settings: + self.spinLoop.setValue(settings['loop']) + if 'start_time' in settings: + time = self._to_qtime(settings['start_time']) + self.startEdit.setTime(time) + if 'stop_time' in settings: + time = self._to_qtime(settings['stop_time']) + self.stopEdit.setTime(time) + + time = self._to_qtime(settings.get('duration', 0)) + self.startEdit.setMaximumTime(time) + self.stopEdit.setMaximumTime(time) def _to_qtime(self, m_seconds): return QTime.fromMSecsSinceStartOfDay(m_seconds) diff --git a/lisp/ui/settings/cue_settings.py b/lisp/ui/settings/cue_settings.py index b444a1ae3..efaf44059 100644 --- a/lisp/ui/settings/cue_settings.py +++ b/lisp/ui/settings/cue_settings.py @@ -17,29 +17,24 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from copy import deepcopy - from PyQt5 import QtCore -from PyQt5.QtWidgets import QDialog, QTabWidget, QDialogButtonBox +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout from lisp.core.class_based_registry import ClassBasedRegistry from lisp.core.singleton import Singleton -from lisp.core.util import deep_update from lisp.cues.cue import Cue -from lisp.ui.settings.settings_page import SettingsPage, CueSettingsPage +from lisp.ui.settings.pages import SettingsPage, CuePageMixin, \ + TabsMultiSettingsPage from lisp.ui.ui_utils import translate class CueSettingsRegistry(ClassBasedRegistry, metaclass=Singleton): - def add_item(self, item, ref_class=Cue): + def add_item(self, item, target=Cue): if not issubclass(item, SettingsPage): raise TypeError( - 'item must be a SettingPage, not {0}'.format(item.__name__)) - if not issubclass(ref_class, Cue): - raise TypeError( - 'ref_class must be a Cue, not {0}'.format(ref_class.__name__)) + 'item must be a SettingPage, not {}'.format(item.__name__)) - return super().add_item(item, ref_class) + return super().add_item(item, target) def filter(self, ref_class=Cue): return super().filter(ref_class) @@ -48,76 +43,81 @@ def clear_class(self, ref_class=Cue): return super().filter(ref_class) -class CueSettings(QDialog): - on_apply = QtCore.pyqtSignal(dict) +class CueSettingsDialog(QDialog): + onApply = QtCore.pyqtSignal(dict) - def __init__(self, cue=None, cue_class=None, **kwargs): + def __init__(self, cue, **kwargs): """ - :param cue: Target cue, or None for multi-editing - :param cue_class: when cue is None, used to specify the reference class + :param cue: Target cue, or a cue-type for multi-editing """ super().__init__(**kwargs) - - if cue is not None: + self.setWindowModality(QtCore.Qt.ApplicationModal) + self.setMaximumSize(640, 510) + self.setMinimumSize(640, 510) + self.resize(640, 510) + self.setLayout(QVBoxLayout()) + + if isinstance(cue, type): + if isinstance(cue, Cue): + cue_properties = cue.class_defaults() + cue_class = cue + else: + raise TypeError( + 'invalid cue type, must be a Cue subclass or a Cue object, ' + 'not {}'.format(cue.__name__) + ) + elif isinstance(cue, Cue): + self.setWindowTitle(cue.name) + cue_properties = cue.properties() cue_class = cue.__class__ - cue_properties = deepcopy(cue.properties()) - self.setWindowTitle(cue_properties['name']) else: - cue_properties = {} - if cue_class is None: - cue_class = Cue - - self.setWindowModality(QtCore.Qt.ApplicationModal) - self.setMaximumSize(635, 530) - self.setMinimumSize(635, 530) - self.resize(635, 530) + raise TypeError( + 'invalid cue type, must be a Cue subclass or a Cue object, ' + 'not {}'.format(type(cue).__name__) + ) - self.sections = QTabWidget(self) - self.sections.setGeometry(QtCore.QRect(5, 10, 625, 470)) + self.mainPage = TabsMultiSettingsPage(parent=self) + self.layout().addWidget(self.mainPage) def sk(widget): # Sort-Key function return translate('SettingsPageName', widget.Name) - for widget in sorted(CueSettingsRegistry().filter(cue_class), key=sk): - if issubclass(widget, CueSettingsPage): - settings_widget = widget(cue_class) + for page in sorted(CueSettingsRegistry().filter(cue_class), key=sk): + if issubclass(page, CuePageMixin): + settings_widget = page(cue_class) else: - settings_widget = widget() + settings_widget = page() settings_widget.loadSettings(cue_properties) - settings_widget.enableCheck(cue is None) - self.sections.addTab(settings_widget, - translate('SettingsPageName', - settings_widget.Name)) - - self.dialogButtons = QDialogButtonBox(self) - self.dialogButtons.setGeometry(10, 490, 615, 30) - self.dialogButtons.setStandardButtons(QDialogButtonBox.Cancel | - QDialogButtonBox.Ok | - QDialogButtonBox.Apply) - - self.dialogButtons.rejected.connect(self.reject) - self.dialogButtons.accepted.connect(self.accept) - apply = self.dialogButtons.button(QDialogButtonBox.Apply) - apply.clicked.connect(self.apply) - - def load_settings(self, settings): - for n in range(self.sections.count()): - self.sections.widget(n).load_settings(settings) - - def get_settings(self): - settings = {} - - for n in range(self.sections.count()): - deep_update(settings, self.sections.widget(n).get_settings()) - - return settings - - def apply(self): - self.on_apply.emit(self.get_settings()) - - def accept(self): - self.apply() - super().accept() + settings_widget.enableCheck(cue is cue_class) + self.mainPage.addPage(settings_widget) + + self.dialogButtons = QDialogButtonBox(self) + self.dialogButtons.setStandardButtons( + QDialogButtonBox.Cancel | + QDialogButtonBox.Apply | + QDialogButtonBox.Ok + ) + self.layout().addWidget(self.dialogButtons) + + self.dialogButtons.button(QDialogButtonBox.Cancel).clicked.connect( + self.reject) + self.dialogButtons.button(QDialogButtonBox.Apply).clicked.connect( + self.__onApply) + self.dialogButtons.button(QDialogButtonBox.Ok).clicked.connect( + self.__onOk) + + def loadSettings(self, settings): + self.mainPage.loadSettings(settings) + + def getSettings(self): + return self.mainPage.getSettings() + + def __onApply(self): + self.onApply.emit(self.getSettings()) + + def __onOk(self): + self.__onApply() + self.accept() diff --git a/lisp/ui/settings/pages.py b/lisp/ui/settings/pages.py new file mode 100644 index 000000000..b81c48202 --- /dev/null +++ b/lisp/ui/settings/pages.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from abc import abstractmethod + +from PyQt5.QtWidgets import QWidget, QTabWidget, QVBoxLayout + +from lisp.core.qmeta import QABCMeta +from lisp.core.util import dict_merge +from lisp.ui.ui_utils import translate + + +class ABCSettingsPage(QWidget, metaclass=QABCMeta): + Name = 'ABCSettingPage' + + +class SettingsPage(ABCSettingsPage): + Name = 'SettingsPage' + + @abstractmethod + def loadSettings(self, settings): + """Load existing settings value into the widget + + :param settings: the settings to load + :type settings: dict + """ + + def getSettings(self): + """Return the new settings values + + If an option (or a group) has been disabled by `enableCheck` their + values should not be included. + + :rtype: dict + """ + return {} + + def enableCheck(self, enabled): + """Enable settings checks + + This should selectively enable/disable widgets in the page, + usually you want to work with groups (QGroupBox). + + If an option (or a group) is disable the values should not be included + in the return of `getSettings` + + :param enabled: if True enable checks + :type enabled: bool + """ + + +class CuePageMixin: + Name = 'CueSettingsPage' + + def __init__(self, cue_type): + self.cue_type = cue_type + + +class CueSettingsPage(SettingsPage, CuePageMixin): + + def __init__(self, cue_type, **kwargs): + super().__init__(cue_type=cue_type, **kwargs) + + +class ConfigurationPage(ABCSettingsPage): + Name = 'ConfigurationPage' + + def __init__(self, config, **kwargs): + """ + :param config: Configuration object to "edit" + :type config: lisp.core.configuration.Configuration + """ + super().__init__(**kwargs) + self.config = config + + @abstractmethod + def applySettings(self): + pass + + +class TabsMultiPage(QWidget): + _PagesBaseClass = ABCSettingsPage + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.setLayout(QVBoxLayout()) + + self.tabWidget = QTabWidget(parent=self) + self.layout().addWidget(self.tabWidget) + + self._pages = [] + + def page(self, index): + return self._pages[index] + + def addPage(self, page): + if isinstance(page, self._PagesBaseClass): + self._pages.append(page) + self.tabWidget.addTab( + page, translate('SettingsPageName', page.Name)) + else: + raise TypeError( + 'page must be an {}, not {}'.format( + self._PagesBaseClass.__name__, + type(page).__name__) + ) + + def removePage(self, index): + self.tabWidget.removeTab(index) + return self._pages.pop(index) + + def iterPages(self): + yield from self._pages + + def pageIndex(self, page): + return self._pages.index(page) + + +class TabsMultiSettingsPage(TabsMultiPage, SettingsPage): + _PagesBaseClass = SettingsPage + + def loadSettings(self, settings): + for page in self._pages: + page.loadSettings(settings) + + def getSettings(self): + settings = {} + for page in self._pages: + dict_merge(settings, page.getSettings()) + + return settings + + def enableCheck(self, enabled): + for page in self._pages: + page.enableCheck(enabled) + + +class TabsMultiConfigurationPage(TabsMultiPage, ConfigurationPage): + _PagesBaseClass = ConfigurationPage + + def applySettings(self): + for page in self._pages: + page.applySettings() diff --git a/lisp/ui/settings/pages_tree.py b/lisp/ui/settings/pages_tree_model.py similarity index 62% rename from lisp/ui/settings/pages_tree.py rename to lisp/ui/settings/pages_tree_model.py index 3ac1b472e..80ae49068 100644 --- a/lisp/ui/settings/pages_tree.py +++ b/lisp/ui/settings/pages_tree_model.py @@ -18,19 +18,18 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import QAbstractItemModel, Qt, QModelIndex -from PyQt5.QtWidgets import QTreeView, QHBoxLayout, QWidget -from lisp.ui.settings.settings_page import ABCSettingsPage +from lisp.ui.settings.pages import ABCSettingsPage -class TreeSettingsNode: +class SettingsPageNode: """ - :type parent: TreeSettingsNode - :type _children: list[TreeSettingsNode] + :type parent: SettingsPageNode + :type _children: list[SettingsPageNode] """ def __init__(self, page): self.parent = None - self.settingsPage = page + self.widget = page self._children = [] @@ -42,7 +41,6 @@ def removeChild(self, position): if 0 < position < len(self._children): child = self._children.pop(position) child.parent = None - return True return False @@ -66,12 +64,12 @@ def walk(self): yield from child.walk() -class TreeSettingsModel(QAbstractItemModel): +class SettingsPagesTreeModel(QAbstractItemModel): PageRole = Qt.UserRole + 1 def __init__(self, **kwargs): super().__init__(**kwargs) - self._root = TreeSettingsNode(None) + self._root = SettingsPageNode(None) def rowCount(self, parent=QModelIndex()): if parent.isValid(): @@ -86,9 +84,9 @@ def data(self, index, role=Qt.DisplayRole): if index.isValid(): node = index.internalPointer() if role == Qt.DisplayRole: - return node.settingsPage.Name - elif role == TreeSettingsModel.PageRole: - return node.settingsPage + return node.widget.Name + elif role == SettingsPagesTreeModel.PageRole: + return node.widget def headerData(self, section, orientation, role=Qt.DisplayRole): return None @@ -121,7 +119,7 @@ def index(self, row, column, parent=QModelIndex()): def pageIndex(self, page, parent=QModelIndex()): parentNode = self.node(parent) for row in range(parentNode.childCount()): - if parentNode.child(row).settingsPage is page: + if parentNode.child(row).widget is page: return self.index(row, 0, parent) return QModelIndex() @@ -132,14 +130,14 @@ def addPage(self, page, parent=QModelIndex()): position = parentNode.childCount() self.beginInsertRows(parent, position, position) - node = TreeSettingsNode(page) + node = SettingsPageNode(page) parentNode.addChild(node) self.endInsertRows() return self.index(position, 0, parent) else: raise TypeError( - 'TreeSettingsModel page must be an ABCSettingsPage, not {}' + 'SettingsPagesTreeModel page must be an ABCSettingsPage, not {}' .format(type(page).__name__) ) @@ -150,52 +148,3 @@ def removePage(self, row, parent=QModelIndex()): self.beginRemoveRows(parent, row, row) parentNode.removeChild(row) self.endRemoveRows() - - -class TreeMultiSettingsPage(ABCSettingsPage): - def __init__(self, navModel, **kwargs): - """ - :param navModel: The model that keeps all the pages-hierarchy - :type navModel: TreeSettingsModel - """ - super().__init__(**kwargs) - self.setLayout(QHBoxLayout()) - self.layout().setSpacing(0) - self.layout().setContentsMargins(0, 0, 0, 0) - self.navModel = navModel - - self.navWidget = QTreeView() - self.navWidget.setHeaderHidden(True) - self.navWidget.setModel(self.navModel) - self.layout().addWidget(self.navWidget) - - self._currentWidget = QWidget() - self.layout().addWidget(self._currentWidget) - - self.layout().setStretch(0, 2) - self.layout().setStretch(1, 5) - - self.navWidget.selectionModel().selectionChanged.connect( - self._changePage) - - def selectFirst(self): - self.navWidget.setCurrentIndex(self.navModel.index(0, 0, QModelIndex())) - - def currentWidget(self): - return self._currentWidget - - def applySettings(self): - root = self.navModel.node(QModelIndex()) - for node in root.walk(): - if node.settingsPage is not None: - node.settingsPage.applySettings() - - def _changePage(self, selected): - if selected.indexes(): - self.layout().removeWidget(self._currentWidget) - self._currentWidget.hide() - self._currentWidget = selected.indexes()[0].internalPointer().settingsPage - self._currentWidget.show() - self.layout().addWidget(self._currentWidget) - self.layout().setStretch(0, 2) - self.layout().setStretch(1, 5) diff --git a/lisp/ui/settings/settings_page.py b/lisp/ui/settings/settings_page.py deleted file mode 100644 index f52149a81..000000000 --- a/lisp/ui/settings/settings_page.py +++ /dev/null @@ -1,144 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from abc import abstractmethod - -from PyQt5.QtWidgets import QWidget, QDialog, QVBoxLayout, QDialogButtonBox, \ - QTabWidget - -from lisp.core.qmeta import QABCMeta -from lisp.ui.ui_utils import translate - - -class ABCSettingsPage(QWidget, metaclass=QABCMeta): - Name = 'SettingPage' - - @abstractmethod - def applySettings(self): - pass - - -class SettingsPage(QWidget): - def enableCheck(self, enabled): - """Enable options check""" - - def getSettings(self): - """Return the current settings.""" - return {} - - def loadSettings(self, settings): - """Load the settings.""" - - -class CueSettingsPage(SettingsPage): - Name = 'Cue page' - - def __init__(self, cue_class, **kwargs): - super().__init__(**kwargs) - self._cue_class = cue_class - - -class DummySettingsPage(ABCSettingsPage): - - def applySettings(self): - pass - - -class ConfigurationPage(ABCSettingsPage): - def __init__(self, config, **kwargs): - """ - :param config: Configuration object to "edit" - :type config: lisp.core.configuration.Configuration - """ - super().__init__(**kwargs) - self.config = config - - -class TabsMultiSettingsPage(QTabWidget, ABCSettingsPage): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._pages = [] - - def page(self, index): - return self._pages[index] - - def addPage(self, page): - if isinstance(page, ABCSettingsPage): - self._pages.append(page) - self.addTab(page, translate('SettingsPageName', page.Name)) - else: - raise TypeError( - 'page must be an ABCSettingsPage, not {}' - .format(type(page).__name__) - ) - - def removePage(self, index): - return self._pages.pop(index) - - def iterPages(self): - yield from self._pages - - def pageIndex(self, page): - return self._pages.index(page) - - def applySettings(self): - for page in self._pages: - page.applySettings() - - -class SettingsDialog(QDialog, ABCSettingsPage): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.setLayout(QVBoxLayout()) - self.setMaximumSize(640, 510) - self.setMinimumSize(640, 510) - self.resize(640, 510) - - self.mainPage = self._buildMainPage() - self.layout().addWidget(self.mainPage) - - self.dialogButtons = QDialogButtonBox(self) - self.dialogButtons.setStandardButtons( - QDialogButtonBox.Cancel - | QDialogButtonBox.Apply - | QDialogButtonBox.Ok - ) - self.layout().addWidget(self.dialogButtons) - - self.dialogButtons.button(QDialogButtonBox.Apply).clicked.connect( - self.applySettings) - self.dialogButtons.button(QDialogButtonBox.Ok).clicked.connect( - self.__onOK) - self.dialogButtons.button(QDialogButtonBox.Cancel).clicked.connect( - self.__onCancel) - - def applySettings(self): - self.mainPage.applySettings() - - @abstractmethod - def _buildMainPage(self): - """:rtype: ABCSettingsPage""" - pass - - def __onOK(self): - self.applySettings() - self.accept() - - def __onCancel(self): - self.reject() From b06ea1d0a527b70ecb762c909fec882f299d216b Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 22 Mar 2018 16:30:07 +0100 Subject: [PATCH 097/333] Fixes --- lisp/layouts/cue_layout.py | 2 +- lisp/plugins/gst_backend/gst_element.py | 57 +++++++++++++++++- lisp/plugins/gst_backend/gst_media.py | 58 +------------------ lisp/plugins/gst_backend/gst_media_cue.py | 2 + .../plugins/gst_backend/gst_media_settings.py | 4 +- lisp/plugins/presets/presets_ui.py | 2 +- lisp/ui/settings/app_configuration.py | 4 +- lisp/ui/settings/cue_settings.py | 8 ++- 8 files changed, 71 insertions(+), 66 deletions(-) diff --git a/lisp/layouts/cue_layout.py b/lisp/layouts/cue_layout.py index 625f46676..0a411bb30 100644 --- a/lisp/layouts/cue_layout.py +++ b/lisp/layouts/cue_layout.py @@ -138,7 +138,7 @@ def edit_selected_cues(self): if cues: # Use the greatest common superclass between the selected cues - edit_ui = CueSettingsDialog(cue_class=greatest_common_superclass(cues)) + edit_ui = CueSettingsDialog(greatest_common_superclass(cues)) def on_apply(settings): action = UpdateCuesAction(settings, cues) diff --git a/lisp/plugins/gst_backend/gst_element.py b/lisp/plugins/gst_backend/gst_element.py index c4afc7d46..665b2dd35 100644 --- a/lisp/plugins/gst_backend/gst_element.py +++ b/lisp/plugins/gst_backend/gst_element.py @@ -18,7 +18,8 @@ # along with Linux Show Player. If not, see . from lisp.backend.media_element import MediaElement, ElementType -from lisp.core.properties import Property +from lisp.core.has_properties import HasInstanceProperties +from lisp.core.properties import Property, InstanceProperty class GstProperty(Property): @@ -114,4 +115,56 @@ class GstSrcElement(GstMediaElement): def input_uri(self): """Return the input uri or None""" - return None \ No newline at end of file + return None + + +class GstMediaElements(HasInstanceProperties): + + def __init__(self): + super().__init__() + self.elements = [] + + def __getitem__(self, index): + return self.elements[index] + + def __len__(self): + return len(self.elements) + + def __contains__(self, item): + return item in self.elements + + def __iter__(self): + return iter(self.elements) + + def append(self, element): + """ + :type element: lisp.backend.media_element.MediaElement + """ + if self.elements: + self.elements[-1].link(element) + self.elements.append(element) + + # Add a property for the new added element + element_name = element.__class__.__name__ + setattr(self, element_name, InstanceProperty(default=None)) + setattr(self, element_name, element) + + def remove(self, element): + self.pop(self.elements.index(element)) + + def pop(self, index): + if index > 0: + self.elements[index - 1].unlink(self.elements[index]) + if index < len(self.elements) - 1: + self.elements[index].unlink(self.elements[index + 1]) + self.elements[index - 1].link(self.elements[index + 1]) + + element = self.elements.pop(index) + element.dispose() + + # Remove the element corresponding property + delattr(self, element.__class__.__name__) + + def clear(self): + while self.elements: + self.pop(len(self.elements) - 1) \ No newline at end of file diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index f852e181e..77996944d 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -21,10 +21,10 @@ import weakref from lisp.backend.media import Media, MediaState -from lisp.core.has_properties import HasInstanceProperties -from lisp.core.properties import Property, InstanceProperty +from lisp.core.properties import Property from lisp.plugins.gst_backend import elements as gst_elements from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gst_element import GstMediaElements logger = logging.getLogger(__name__) @@ -56,7 +56,7 @@ class GstMedia(Media): """Media implementation based on the GStreamer framework.""" pipe = Property(default=()) - elements = Property(default=None) + elements = Property(default=GstMediaElements.class_defaults()) def __init__(self): super().__init__() @@ -290,55 +290,3 @@ def __finalizer(pipeline, connection_handler, media_elements): bus.disconnect(connection_handler) media_elements.clear() - - -class GstMediaElements(HasInstanceProperties): - - def __init__(self): - super().__init__() - self.elements = [] - - def __getitem__(self, index): - return self.elements[index] - - def __len__(self): - return len(self.elements) - - def __contains__(self, item): - return item in self.elements - - def __iter__(self): - return iter(self.elements) - - def append(self, element): - """ - :type element: lisp.backend.media_element.MediaElement - """ - if self.elements: - self.elements[-1].link(element) - self.elements.append(element) - - # Add a property for the new added element - element_name = element.__class__.__name__ - setattr(self, element_name, InstanceProperty(default=None)) - setattr(self, element_name, element) - - def remove(self, element): - self.pop(self.elements.index(element)) - - def pop(self, index): - if index > 0: - self.elements[index - 1].unlink(self.elements[index]) - if index < len(self.elements) - 1: - self.elements[index].unlink(self.elements[index + 1]) - self.elements[index - 1].link(self.elements[index + 1]) - - element = self.elements.pop(index) - element.dispose() - - # Remove the element corresponding property - delattr(self, element.__class__.__name__) - - def clear(self): - while self.elements: - self.pop(len(self.elements) - 1) diff --git a/lisp/plugins/gst_backend/gst_media_cue.py b/lisp/plugins/gst_backend/gst_media_cue.py index c24f8404a..324286c58 100644 --- a/lisp/plugins/gst_backend/gst_media_cue.py +++ b/lisp/plugins/gst_backend/gst_media_cue.py @@ -17,11 +17,13 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +from lisp.core.properties import Property from lisp.cues.media_cue import MediaCue from lisp.plugins.gst_backend.gst_media import GstMedia class GstMediaCue(MediaCue): + media = Property(default=GstMedia.class_defaults()) def __init__(self, media, id=None, pipeline=None): super().__init__(media, id=id) diff --git a/lisp/plugins/gst_backend/gst_media_settings.py b/lisp/plugins/gst_backend/gst_media_settings.py index 74db24441..b1a21b90e 100644 --- a/lisp/plugins/gst_backend/gst_media_settings.py +++ b/lisp/plugins/gst_backend/gst_media_settings.py @@ -67,8 +67,8 @@ def loadSettings(self, settings): if page is not None and issubclass(page, SettingsPage): page = page(parent=self) page.loadSettings( - settings.get('elements', {}) - .get(element, page.ELEMENT.class_defaults())) + settings.get('elements', {}).get( + element, page.ELEMENT.class_defaults())) page.setVisible(False) self._pages.append(page) diff --git a/lisp/plugins/presets/presets_ui.py b/lisp/plugins/presets/presets_ui.py index 7aa0688bc..dad1975c9 100644 --- a/lisp/plugins/presets/presets_ui.py +++ b/lisp/plugins/presets/presets_ui.py @@ -275,7 +275,7 @@ def __edit_preset(self): except Exception: cue_class = Cue - edit_dialog = CueSettingsDialog(cue_class=cue_class) + edit_dialog = CueSettingsDialog(cue_class) edit_dialog.loadSettings(preset) if edit_dialog.exec_() == edit_dialog.Accepted: preset.update(edit_dialog.getSettings()) diff --git a/lisp/ui/settings/app_configuration.py b/lisp/ui/settings/app_configuration.py index b40a06a76..e8855f2aa 100644 --- a/lisp/ui/settings/app_configuration.py +++ b/lisp/ui/settings/app_configuration.py @@ -23,11 +23,11 @@ from PyQt5 import QtCore from PyQt5.QtCore import QModelIndex from PyQt5.QtWidgets import QVBoxLayout, QDialogButtonBox, QDialog, QTreeView, \ - QHBoxLayout, QWidget, QSizePolicy + QHBoxLayout, QWidget from lisp.core.dicttree import DictNode -from lisp.ui.settings.pages_tree_model import SettingsPagesTreeModel from lisp.ui.settings.pages import ConfigurationPage +from lisp.ui.settings.pages_tree_model import SettingsPagesTreeModel from lisp.ui.ui_utils import translate logger = logging.getLogger(__name__) diff --git a/lisp/ui/settings/cue_settings.py b/lisp/ui/settings/cue_settings.py index efaf44059..853615701 100644 --- a/lisp/ui/settings/cue_settings.py +++ b/lisp/ui/settings/cue_settings.py @@ -29,12 +29,12 @@ class CueSettingsRegistry(ClassBasedRegistry, metaclass=Singleton): - def add_item(self, item, target=Cue): + def add_item(self, item, ref_class=Cue): if not issubclass(item, SettingsPage): raise TypeError( 'item must be a SettingPage, not {}'.format(item.__name__)) - return super().add_item(item, target) + return super().add_item(item, ref_class) def filter(self, ref_class=Cue): return super().filter(ref_class) @@ -59,8 +59,10 @@ def __init__(self, cue, **kwargs): self.setLayout(QVBoxLayout()) if isinstance(cue, type): - if isinstance(cue, Cue): + if issubclass(cue, Cue): cue_properties = cue.class_defaults() + import pprint + pprint.pprint(cue_properties) cue_class = cue else: raise TypeError( From 0a2e9768dd8998d995e38d627b6972b4e92ca65f Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 28 Mar 2018 14:50:07 +0200 Subject: [PATCH 098/333] Refactoring of multiple modules Update: minor MediaCue interface, removed "Error" state and "interrupt" Update: Refactored GstMedia to better handle the pipeline Update: HasProperties can now filter properties Update: session files are now "minified", can be disabled for debugging Update: minor changes to improve TabWidget navigation Fix: GstMedia do not break anymore after GStreamer errors Fix: JackSink have now default connections when added from the cue-settings Other minor changes/fixes --- Pipfile | 15 +- Pipfile.lock | 98 ++++-- lisp/application.py | 15 +- lisp/backend/media.py | 15 +- lisp/core/decorators.py | 1 - lisp/core/has_properties.py | 62 +++- lisp/core/util.py | 10 + lisp/cues/media_cue.py | 21 +- lisp/default.json | 7 +- lisp/layouts/cart_layout/cue_widget.py | 5 +- lisp/layouts/cart_layout/layout.py | 5 +- lisp/layouts/list_layout/layout.py | 4 +- lisp/main.py | 2 +- .../plugins/gst_backend/elements/alsa_sink.py | 6 +- .../gst_backend/elements/audio_dynamic.py | 8 +- .../plugins/gst_backend/elements/audio_pan.py | 8 +- .../plugins/gst_backend/elements/auto_sink.py | 6 +- lisp/plugins/gst_backend/elements/auto_src.py | 6 +- lisp/plugins/gst_backend/elements/db_meter.py | 2 +- .../gst_backend/elements/equalizer10.py | 8 +- .../plugins/gst_backend/elements/jack_sink.py | 12 +- lisp/plugins/gst_backend/elements/pitch.py | 8 +- .../gst_backend/elements/preset_src.py | 8 +- .../gst_backend/elements/pulse_sink.py | 6 +- lisp/plugins/gst_backend/elements/speed.py | 2 +- .../plugins/gst_backend/elements/uri_input.py | 11 +- .../gst_backend/elements/user_element.py | 8 +- lisp/plugins/gst_backend/elements/volume.py | 10 +- lisp/plugins/gst_backend/gst_backend.py | 2 +- lisp/plugins/gst_backend/gst_element.py | 16 +- lisp/plugins/gst_backend/gst_media.py | 291 +++++++++--------- .../plugins/gst_backend/gst_media_settings.py | 3 +- .../gst_backend/settings/equalizer10.py | 4 +- .../plugins/gst_backend/settings/jack_sink.py | 4 +- lisp/plugins/midi/midi.py | 2 +- lisp/plugins/osc/osc.py | 2 +- lisp/plugins/timecode/timecode.py | 15 +- lisp/ui/settings/app_configuration.py | 104 ++----- .../ui/settings/app_pages/plugins_settings.py | 13 +- lisp/ui/settings/pages.py | 119 +++++-- lisp/ui/settings/pages_tree_model.py | 22 +- lisp/ui/themes/dark/theme.qss | 16 +- lisp/ui/themes/theme.py | 2 +- 43 files changed, 544 insertions(+), 440 deletions(-) diff --git a/Pipfile b/Pipfile index 876dc8d86..9ce8f49a1 100644 --- a/Pipfile +++ b/Pipfile @@ -1,21 +1,18 @@ [[source]] - url = "https://pypi.python.org/simple" verify_ssl = true name = "pypi" - [packages] - sortedcontainers = "*" mido = "*" python-rtmidi = "*" -jack-client = "*" +JACK-Client = "*" pyliblo = "*" -"pyqt5" = "*" -pygobject = "*" - +"PyQt5" = "*" +PyGObject = "*" [dev-packages] - -cython = "*" +Cython = "*" +pycallgraph = "*" +objgraph = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 9677d8946..6ea79c90b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "7758b232e7b383ea49c6f98a167e2d857ab7d1fa6c95efaed803bc014482cc7f" + "sha256": "d52a4582f1e103e0e2ba16e4e18c69fe8f16d3ef2e690124fad596a699d97e3e" }, "pipfile-spec": 6, "requires": {}, @@ -51,6 +51,7 @@ "sha256:46998c8065bcfa1b6c9faa0005786be2175f21bdced3a5105462489656b1b063", "sha256:58eb7004e0cbfd769b4ad352dca1faeb5f25a764f55c173363d09f08c7d7b64d" ], + "index": "pypi", "version": "==0.4.4" }, "mido": { @@ -58,6 +59,7 @@ "sha256:35142874d4521dc5fcebcdc3a645df87cb0ecad129dd031cbca391e2d052313f", "sha256:64b9d1595da8f319bff2eb866f9181257d3670a7803f7e38415f22c03a577560" ], + "index": "pypi", "version": "==1.2.8" }, "pycairo": { @@ -74,14 +76,16 @@ }, "pygobject": { "hashes": [ - "sha256:cb1db68b843df9c1c84a76c6b0c5f3566e6baf805bd218072a759269f8a9ae6b" + "sha256:f704f4be3b4ae3cae70acf82ac64a8c7c44d3acad2d33e9849056ac317345f5e" ], - "version": "==3.27.5" + "index": "pypi", + "version": "==3.28.2" }, "pyliblo": { "hashes": [ "sha256:fc67f1950b827272b00f9f0dc4ed7113c0ccef0c1c09e9976dead40ebbf1798f" ], + "index": "pypi", "version": "==0.10.0" }, "pyqt5": { @@ -91,6 +95,7 @@ "sha256:9c17ab3974c1fc7bbb04cc1c9dae780522c0ebc158613f3025fccae82227b5f7", "sha256:f6035baa009acf45e5f460cf88f73580ad5dc0e72330029acd99e477f20a5d61" ], + "index": "pypi", "version": "==5.10.1" }, "python-rtmidi": { @@ -107,6 +112,7 @@ "sha256:d82eb82e0b270f75375e3d5f9f45cb75950485700e6a3862192d0c121c802b0e", "sha256:dfba865b1e7c7793c7d222b1922ac5f901f5545af71bf70e06d88a77482aa20c" ], + "index": "pypi", "version": "==1.1.0" }, "sip": { @@ -131,44 +137,68 @@ "sha256:844daced0f20d75c02ce53f373d048ea2e401ad8a7b3a4c43b2aa544b569efb3", "sha256:fb9e22cd6ee4b459f0d7b9b4189b19631031c72ac05715563139162014c13672" ], + "index": "pypi", "version": "==1.5.9" } }, "develop": { "cython": { "hashes": [ - "sha256:251bb50e36044ad26ff45960a60cb75868757fd46900eaf00129a4af3a0d5fcc", - "sha256:264a68823751fe2a18d0b14dd44b614aecd508bd01453902921c23fd5be13d4c", - "sha256:26e24164d59e92280fa759a8a361c68490763ec230307e21990020bdeb87de50", - "sha256:28c938bc970d2b5fe296460cf6ed19d0328776a6295f68a79057060e60bfc82f", - "sha256:38a9e5afc5e096f26bfe5c4b458541e455032028cb5356a734ba235868b19823", - "sha256:3914ae1268334208e33b952e9f447e0d9f43decd562f939d7d07e8589d0473ba", - "sha256:4c958bd9e50fcd316830561f0725b3ccf893de328566c960b7bb38424d3b983d", - "sha256:56a9f99b42ad6ad1fdb7705c16aa69b2e1a4ac34f760286a6de64c5719499da7", - "sha256:5e0006c3a2849b7a36d2cb23bcf14753b6ffc3eafac48fa29fafad942cfa4627", - "sha256:6a00512de1f2e3ce66ba35c5420babaef1fe2d9c43a8faab4080b0dbcc26bc64", - "sha256:7bc07c32703e4e63f2f60c729b0ae99acbccbfd3ae4c64a322a75482d915d4af", - "sha256:7fbd49b1bd5240feb69ec4c67f4c76fc9dfdfc472d642833668ecaad0e14673d", - "sha256:82bbbeadbc579b6f2a7365db2d3ea21043699e424cfcca434b3baa11309260ae", - "sha256:83237d27b6dec0f6b76406de5be1cf249cffb4377079acc3f3dfa72de1e307cf", - "sha256:8a4733edf3ca1c7dd9c7e1289dd5974657f1147642ef8ded1e57a1266922e32a", - "sha256:8afdc3751c6269ef46879ca6ff32a1362f298c77f2cf0723c66ae65c35be5db0", - "sha256:9f8bbd876c4d68cb12b9de9dc6734c17ca295e4759e684417fd7f3dd96d3ed2c", - "sha256:a51c3a1c948664874f91636ba6169bca4dc68f5fac7b980fcb9c769a8d1f9ebc", - "sha256:aba08b4db9163c52a1106efa8b19c5a4e519e5f45644551bb1358ec189a14505", - "sha256:abfb2426266d806a7d794a8ecbd0fbb385b48bddf2db6ac9a2e95d48e2c603bd", - "sha256:b9bc1d7e681c5a7560280a5aa8000f736eb0e47efacb7b6206275ed004595a92", - "sha256:ca81d4b6f511f1e0d2a1c76ad5b3a4a3a0edf683f27600d4a86da55cb134372a", - "sha256:cefa9356770d71176c87ab60f6fdb186ec9cbc6399a6c2de1017852505d44963", - "sha256:d17a6a689bb3e6c0f280620dfddb1b91ffb8fcdae3b8707bdf6285df4100d011", - "sha256:d8e40814fefc300bc64251f5d6bd8988e49442d7ded0e5702b5366323a035afc", - "sha256:e30fd7a32cbe7fe6c186deefef03670bf75d76ead23418f8c91519899be5518e", - "sha256:e86984b90a0176bafb5890f1f14a2962317eee9e95e0a917e6b06a5b0c764e25", - "sha256:f01e79f4123205d0f99b97199b53345cb6378974e738792c620957c6223fded9", - "sha256:f441347d0d07b4cb9ca36bda70c3aa98a7484a262b8f44d0f82b70dbb5e11b47", - "sha256:fb5b8978b937677a32b5aecc72231560d29243622a298a5c31183b156f9f42e3" + "sha256:0a44c3645a84724d4f7f7c1126f3807cad14271f210bb1a9a15bfd330c8d20b9", + "sha256:152ee5f345012ca3bb7cc71da2d3736ee20f52cd8476e4d49e5e25c5a4102b12", + "sha256:15cbde95cdf6a346c63c0b38b895aa3186d0a49adcaf42b9e19c4cb0473cb921", + "sha256:175273eb6a90af364b9b2aea74e3c3eebcde9caec02d617cd8886c0933ec3304", + "sha256:1a434e7621ca6671ce949893fef94b13a580853ba976e729f68dda5ab270ee8a", + "sha256:1da199a5be7c486ee89b4a8bda7f00f9fd98b800d9af3a1fe3fc63e68dadea85", + "sha256:26196ff8fe00e7c4c5815a310c368edfc1a1c8a3c90ac9220435d1b01e795cf7", + "sha256:2b73b062658511167dde37a51acb80ae6ddea1ffa284ebdbc47a900f21e63c70", + "sha256:2b9aa64473fefbe988e36a30915732a0e2a75ffe0f3e81a70e3f8d367c2e4119", + "sha256:32638aefc164404ac70e5f86558cd3aece34df17db16da905abf5e664073bae4", + "sha256:330c95c03e39835976d1410f5fa877b75fcc5c50dc146d4c02377fc719b1f8c9", + "sha256:38b499fa317cf6939e46317457240553b13e974f08ed387523f10af26e269f6c", + "sha256:3c60caa0075aa1f1c5e10b5352d2e8bb9a18361ce9fca50fa7d4baea67804ade", + "sha256:3fc9b945541cadb5a10316e48b5e73f4b6f635b7d30156f502b66f3766545b23", + "sha256:427299c47cfe04d97f9f09ea55570c05898a87b64caf6ddebb529b6f64e5d228", + "sha256:4e07e619af85e7c1ec2344ecab558929bb4acbca25f8f170d07dc677e8ee413f", + "sha256:5010e048fb9791522fe626bd40137b8a847ba77a6e656bb64d6d7acdc45ece32", + "sha256:70bc2806fc9e5affcf74c0d4fa12cba57ffb94cdfc28b975692c8df56ea86402", + "sha256:7cdd5303121024269610236876d9f4beb6a909a1ea5d7bc48e7bbf5d8774a3f2", + "sha256:80856aa45004514a3ff5e102bd18fbd5230d234311de1f83d4e5b97ef42f6c93", + "sha256:996ae959ab2600b8ad4c80981afc32e89b42d0abe3234c48e960e40180459cb2", + "sha256:b5c2e31be55bc61d3c758889d06b16d84f4fda944832fbed63c113ec2dbc5f97", + "sha256:c39b3a042bf5ded7c8336c82b1fa817e1f46a7ef16d41d66b3d3340e7a3b60ed", + "sha256:d08f5dd2fbf7d1506c9d986a8352b2423170002ddb635b184d2a916d2b5df0d6", + "sha256:d2b636c16931663aeb8ffb91cf871a912e66e7200755ce68aa8c502c16eb366f", + "sha256:e27e12834ac315c7d67ca697a325d42ff8395e9b82fb62c8665bb583eb0df589", + "sha256:e312dd688b97e9f97199a8e4ba18b65db2747157630761d27193a18761b23fed", + "sha256:e47bbe74d6c87fab2e22f1580aa3e4a8e7482b4265b1fc76685fc49f90e18f99", + "sha256:ea113ff58e96152738e646b8ee77b41d3994735df77bee0a26cd413a67e03c0f", + "sha256:ea22d79133583b5b0f856dbfda097363228ae0a3d77431deaba90634e5d4853a" ], - "version": "==0.27.3" + "index": "pypi", + "version": "==0.28.1" + }, + "graphviz": { + "hashes": [ + "sha256:4e23ab07ea37a5bd16cf96d3c31e63d792cc05f9b2c277d553838477697feb1f", + "sha256:606741c028acc54b1a065b33045f8c89ee0927ea77273ec409ac988f2c3d1091" + ], + "version": "==0.8.2" + }, + "objgraph": { + "hashes": [ + "sha256:4a0c2c6268e10a9e8176ae054ff3faac9a432087801e1f95c3ebbe52550295a0", + "sha256:901dac7a4d73dd6c0e9697cf32676687f4e51d21b343543f795ec10461eb200e" + ], + "index": "pypi", + "version": "==3.4.0" + }, + "pycallgraph": { + "hashes": [ + "sha256:b1262b0f9831da889c6e9a9a82a22267df497013cd2f5b36c39359a607c91e71" + ], + "index": "pypi", + "version": "==1.0.1" } } } diff --git a/lisp/application.py b/lisp/application.py index 7c32fd554..53b9a61f6 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -28,6 +28,7 @@ from lisp.core.session import Session from lisp.core.signal import Signal from lisp.core.singleton import Singleton +from lisp.core.util import filter_live_properties from lisp.cues.cue import Cue from lisp.cues.cue_factory import CueFactory from lisp.cues.cue_model import CueModel @@ -58,11 +59,11 @@ def __init__(self, app_conf): self.__session = None # Register general settings widget - AppConfigurationDialog.registerSettingsWidget( + AppConfigurationDialog.registerSettingsPage( 'general', AppGeneral, self.conf) - AppConfigurationDialog.registerSettingsWidget( + AppConfigurationDialog.registerSettingsPage( 'general.cue', CueAppSettings, self.conf) - AppConfigurationDialog.registerSettingsWidget( + AppConfigurationDialog.registerSettingsPage( 'plugins', PluginsSettings, self.conf) # Register common cue-settings widgets @@ -162,7 +163,8 @@ def _save_to_file(self, session_file): session_dict = {'cues': []} for cue in self.__cue_model: - session_dict['cues'].append(cue.properties(defaults=False)) + session_dict['cues'].append( + cue.properties(defaults=False, filter=filter_live_properties)) # Sort cues by index, allow sorted-models to load properly session_dict['cues'].sort(key=lambda cue: cue['index']) @@ -173,7 +175,10 @@ def _save_to_file(self, session_file): # Write to a file the json-encoded dictionary with open(session_file, mode='w', encoding='utf-8') as file: - file.write(json.dumps(session_dict, sort_keys=True, indent=4)) + if self.conf['session.minSave']: + file.write(json.dumps(session_dict, separators=(',', ':'))) + else: + file.write(json.dumps(session_dict, sort_keys=True, indent=4)) MainActionsHandler.set_saved() self.__main_window.update_window_title() diff --git a/lisp/backend/media.py b/lisp/backend/media.py index 6e1b73aa4..9af8e464c 100644 --- a/lisp/backend/media.py +++ b/lisp/backend/media.py @@ -27,11 +27,10 @@ class MediaState(Enum): """Identify the current media state""" - Error = -1 Null = 0 Playing = 1 Paused = 2 - Stopped = 3 + Ready = 3 class Media(HasProperties): @@ -59,8 +58,6 @@ def __init__(self): # Emitted when played (self) self.stopped = Signal() # Emitted when stopped (self) - self.interrupted = Signal() - # Emitted after interruption (self) self.eos = Signal() # End-of-Stream (self) @@ -111,17 +108,13 @@ def input_uri(self): :rtype: str """ - @abstractmethod - def interrupt(self): - """Interrupt the playback (no fade) and go in STOPPED state.""" - @abstractmethod def pause(self): - """The media go in PAUSED state and pause the playback.""" + """The media go in PAUSED state (pause the playback).""" @abstractmethod def play(self): - """The media go in PLAYING state and starts the playback.""" + """The media go in PLAYING state (starts the playback).""" @abstractmethod def seek(self, position): @@ -133,4 +126,4 @@ def seek(self, position): @abstractmethod def stop(self): - """The media go in STOPPED state and stop the playback.""" + """The media go in READY state (stop the playback).""" diff --git a/lisp/core/decorators.py b/lisp/core/decorators.py index 048341882..ce569185d 100644 --- a/lisp/core/decorators.py +++ b/lisp/core/decorators.py @@ -18,7 +18,6 @@ # along with Linux Show Player. If not, see . import logging -import traceback from functools import wraps, partial from threading import Thread, Lock, RLock diff --git a/lisp/core/has_properties.py b/lisp/core/has_properties.py index 161220588..7c55d1606 100644 --- a/lisp/core/has_properties.py +++ b/lisp/core/has_properties.py @@ -102,21 +102,44 @@ def __init__(self): self.property_changed = Signal() # Emitted after property change (self, name, value) - def properties_names(self): - return self.__class__.__pro__ + def properties_names(self, filter=None): + """ + To work as intended `filter` must be a function that take a set as + parameter and return a set with the properties names filtered by + some custom rule, the given set can be modified in-place. + + :param filter: a function to filter the returned properties, or None + :rtype: set + :return: The object `Properties` names + """ + if callable(filter): + return filter(self._properties_names()) + else: + return self._properties_names() + + def _properties_names(self): + """Return a set of properties names, intended for internal usage. + + The returned set is a copy of the internal one, so it can be modified + in-place. - def properties_defaults(self): + :rtype: set + """ + return self.__class__.__pro__.copy() + + def properties_defaults(self, filter=None): """Instance properties defaults. Differently from `class_defaults` this works on instances, and it might give different results with some subclass. + :param filter: filter the properties, see `properties_names` :return: The default properties as a dictionary {name: default_value} :rtype: dict """ defaults = {} - for name in self.properties_names(): + for name in self.properties_names(filter=filter): value = self._pro(name).default if isinstance(value, HasProperties): value = value.properties_defaults() @@ -126,19 +149,30 @@ def properties_defaults(self): return defaults @classmethod - def class_defaults(cls): + def class_defaults(cls, filter=None): """Class properties defaults. + This function will not go into nested properties, the default + value should already be set to a suitable value. + + :param filter: filter the properties, see `properties_names` :return: The default properties as a dictionary {name: default_value} :rtype: dict """ - return { - name: getattr(cls, name).default - for name in cls.__pro__ - } + if callable(filter): + return { + name: getattr(cls, name).default + for name in filter(cls.__pro__.copy()) + } + else: + return { + name: getattr(cls, name).default + for name in cls.__pro__ + } - def properties(self, defaults=True): + def properties(self, defaults=True, filter=None): """ + :param filter: filter the properties, see `properties_names` :param defaults: include/exclude properties equals to their default :type defaults: bool @@ -147,11 +181,11 @@ def properties(self, defaults=True): """ properties = {} - for name in self.properties_names(): + for name in self.properties_names(filter=filter): value = getattr(self, name) if isinstance(value, HasProperties): - value = value.properties(defaults=defaults) + value = value.properties(defaults=defaults, filter=filter) if defaults or value: properties[name] = value elif defaults or value != self._pro(name).default: @@ -223,8 +257,8 @@ def __init__(self): self.__ipro__ = set() # Registry to keep track of instance-properties - def properties_names(self): - return super().properties_names().union(self.__ipro__) + def _properties_names(self): + return super()._properties_names().union(self.__ipro__) def __getattribute__(self, name): attribute = super().__getattribute__(name) diff --git a/lisp/core/util.py b/lisp/core/util.py index fa2e8429d..f5d95585b 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -201,6 +201,16 @@ def _getattr(obj, name): return functools.reduce(_getattr, attr.split('.'), obj) +def filter_live_properties(properties): + """Can be used to exclude "standard" live properties. + + :param properties: The properties set + :type properties: set + :return: + """ + return set(p for p in properties if not p.startswith('live_')) + + class EqEnum(Enum): """Value-comparable Enum. diff --git a/lisp/cues/media_cue.py b/lisp/cues/media_cue.py index 5ba0aceb9..853d21d99 100644 --- a/lisp/cues/media_cue.py +++ b/lisp/cues/media_cue.py @@ -33,6 +33,8 @@ class MediaCue(Cue): Name = QT_TRANSLATE_NOOP('CueName', 'Media Cue') media = Property() + default_start_action = Property(default=CueAction.FadeInStart.value) + default_stop_action = Property(default=CueAction.FadeOutStop.value) CueActions = (CueAction.Default, CueAction.Start, CueAction.FadeInStart, CueAction.Stop, CueAction.FadeOutStop, CueAction.Pause, @@ -41,9 +43,6 @@ class MediaCue(Cue): def __init__(self, media, id=None): super().__init__(id=id) - self.default_start_action = CueAction.FadeInStart.value - self.default_stop_action = CueAction.FadeOutStop.value - self.media = media self.media.changed('duration').connect(self._duration_change) self.media.elements_changed.connect(self.__elements_changed) @@ -63,7 +62,7 @@ def __elements_changed(self): def __start__(self, fade=False): if fade and self._can_fade(self.fadein_duration): - self.__volume.current_volume = 0 + self.__volume.live_volume = 0 self.media.play() if fade: @@ -111,7 +110,7 @@ def __interrupt__(self, fade=False): if self._state & CueState.Running and fade: self._on_stop_fade(interrupt=True) - self.media.interrupt() + self.media.stop() @async def fadein(self, duration, fade_type): @@ -123,7 +122,7 @@ def fadein(self, duration, fade_type): if self.__volume is not None: if duration <= 0: - self.__volume.current_volume = self.__volume.volume + self.__volume.live_volume = self.__volume.volume else: self._st_lock.release() self.__fadein(duration, self.__volume.volume, fade_type) @@ -141,7 +140,7 @@ def fadeout(self, duration, fade_type): if self.__volume is not None: if duration <= 0: - self.__volume.current_volume = 0 + self.__volume.live_volume = 0 else: self._st_lock.release() self.__fadeout(duration, 0, fade_type) @@ -201,9 +200,11 @@ def _can_fade(self, duration): @async def _on_start_fade(self): if self.__volume is not None: - self.__fadein(self.fadein_duration, - self.__volume.volume, - FadeInType[self.fadein_type]) + self.__fadein( + self.fadein_duration, + self.__volume.volume, + FadeInType[self.fadein_type] + ) def _on_stop_fade(self, interrupt=False): if interrupt: diff --git a/lisp/default.json b/lisp/default.json index 876b3e8fc..08abd34ac 100644 --- a/lisp/default.json +++ b/lisp/default.json @@ -1,5 +1,5 @@ { - "_version_": "0.6dev.10", + "_version_": "0.6dev.11", "cue": { "fadeAction": 3, "fadeActionType": "Linear", @@ -10,10 +10,11 @@ "theme": "Dark", "icons": "numix" }, + "session": { + "minSave": true + }, "logging": { "limit": 10000, - "msg_format": "%(asctime)s.%(msecs)03d\t%(name)s\t%(levelname)s\t%(message)s", - "date_format": "%H:%M:%S", "backgrounds": { "DEBUG": [230, 230, 230, 200], "INFO": [80, 255, 65, 200], diff --git a/lisp/layouts/cart_layout/cue_widget.py b/lisp/layouts/cart_layout/cue_widget.py index 0498407dd..db8811e8e 100644 --- a/lisp/layouts/cart_layout/cue_widget.py +++ b/lisp/layouts/cart_layout/cue_widget.py @@ -30,8 +30,7 @@ from lisp.cues.media_cue import MediaCue from lisp.layouts.cart_layout.page_widget import PageWidget from lisp.ui.icons import IconTheme -from lisp.ui.widgets import QClickLabel, QClickSlider, QDbMeter,\ - QDetailedMessageBox +from lisp.ui.widgets import QClickLabel, QClickSlider, QDbMeter class CueWidget(QWidget): @@ -286,8 +285,8 @@ def _clicked(self, event): elif event.modifiers() == Qt.ControlModifier: self.selected = not self.selected else: - self.cue_executed.emit(self.cue) self.cue.execute() + self.cue_executed.emit(self.cue) def _update_style(self, stylesheet): stylesheet += 'text-decoration: underline;' if self.selected else '' diff --git a/lisp/layouts/cart_layout/layout.py b/lisp/layouts/cart_layout/layout.py index 8aeeb50c1..5cb9b632b 100644 --- a/lisp/layouts/cart_layout/layout.py +++ b/lisp/layouts/cart_layout/layout.py @@ -51,10 +51,11 @@ class CartLayout(QTabWidget, CueLayout): def __init__(self, **kwargs): super().__init__(**kwargs) - AppConfigurationDialog.registerSettingsWidget( + AppConfigurationDialog.registerSettingsPage( 'cart_layout', CartLayoutSettings, self.app.conf) self.tabBar().setObjectName('CartTabBar') + self.setFocusPolicy(Qt.StrongFocus) self.__columns = self.app.conf['cartLayout.gridColumns'] self.__rows = self.app.conf['cartLayout.gridRows'] @@ -422,7 +423,7 @@ def __cue_added(self, cue): def __cue_removed(self, cue): if isinstance(cue, MediaCue): - cue.media.interrupt() + cue.interrupt() else: cue.stop() diff --git a/lisp/layouts/list_layout/layout.py b/lisp/layouts/list_layout/layout.py index ea4772abc..b4b06d134 100644 --- a/lisp/layouts/list_layout/layout.py +++ b/lisp/layouts/list_layout/layout.py @@ -62,7 +62,7 @@ class ListLayout(QWidget, CueLayout): def __init__(self, **kwargs): super().__init__(**kwargs) - AppConfigurationDialog.registerSettingsWidget( + AppConfigurationDialog.registerSettingsPage( 'list_layout', ListLayoutSettings, self.app.conf) self.setLayout(QGridLayout()) @@ -362,7 +362,7 @@ def __cue_added(self, cue): def __cue_removed(self, cue): if isinstance(cue, MediaCue): - cue.media.interrupt() + cue.interrupt() else: cue.stop() diff --git a/lisp/main.py b/lisp/main.py index 20ab9a184..1fdb64bfc 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -21,10 +21,10 @@ import logging import os import sys +from logging.handlers import RotatingFileHandler from PyQt5.QtCore import QLocale, QLibraryInfo from PyQt5.QtWidgets import QApplication -from logging.handlers import RotatingFileHandler from lisp import USER_DIR, DEFAULT_APP_CONFIG, USER_APP_CONFIG, plugins, \ I18N_PATH, LOGS_DIR diff --git a/lisp/plugins/gst_backend/elements/alsa_sink.py b/lisp/plugins/gst_backend/elements/alsa_sink.py index 0e23e1a65..2a2bb610f 100644 --- a/lisp/plugins/gst_backend/elements/alsa_sink.py +++ b/lisp/plugins/gst_backend/elements/alsa_sink.py @@ -31,11 +31,11 @@ class AlsaSink(GstMediaElement): device = GstProperty('alsa_sink', 'device', default='') - def __init__(self, pipe): - super().__init__() + def __init__(self, pipeline): + super().__init__(pipeline) self.alsa_sink = Gst.ElementFactory.make('alsasink', 'sink') - pipe.add(self.alsa_sink) + self.pipeline.add(self.alsa_sink) def sink(self): return self.alsa_sink diff --git a/lisp/plugins/gst_backend/elements/audio_dynamic.py b/lisp/plugins/gst_backend/elements/audio_dynamic.py index 4fd36607d..e1bb44e99 100644 --- a/lisp/plugins/gst_backend/elements/audio_dynamic.py +++ b/lisp/plugins/gst_backend/elements/audio_dynamic.py @@ -49,14 +49,14 @@ class Characteristics(Enum): default=Characteristics.HardKnee.value ) - def __init__(self, pipe): - super().__init__() + def __init__(self, pipeline): + super().__init__(pipeline) self.audio_dynamic = Gst.ElementFactory.make('audiodynamic', None) self.audio_converter = Gst.ElementFactory.make('audioconvert', None) - pipe.add(self.audio_dynamic) - pipe.add(self.audio_converter) + self.pipeline.add(self.audio_dynamic) + self.pipeline.add(self.audio_converter) self.audio_dynamic.link(self.audio_converter) diff --git a/lisp/plugins/gst_backend/elements/audio_pan.py b/lisp/plugins/gst_backend/elements/audio_pan.py index a03c94247..e6dcd960e 100644 --- a/lisp/plugins/gst_backend/elements/audio_pan.py +++ b/lisp/plugins/gst_backend/elements/audio_pan.py @@ -31,14 +31,14 @@ class AudioPan(GstMediaElement): pan = GstProperty('panorama', 'panorama', default=.0) - def __init__(self, pipe): - super().__init__() + def __init__(self, pipeline): + super().__init__(pipeline) self.panorama = Gst.ElementFactory.make("audiopanorama", None) self.audio_convert = Gst.ElementFactory.make("audioconvert", None) - pipe.add(self.panorama) - pipe.add(self.audio_convert) + self.pipeline.add(self.panorama) + self.pipeline.add(self.audio_convert) self.panorama.link(self.audio_convert) diff --git a/lisp/plugins/gst_backend/elements/auto_sink.py b/lisp/plugins/gst_backend/elements/auto_sink.py index 079a62fa8..8285bded7 100644 --- a/lisp/plugins/gst_backend/elements/auto_sink.py +++ b/lisp/plugins/gst_backend/elements/auto_sink.py @@ -29,11 +29,11 @@ class AutoSink(GstMediaElement): MediaType = MediaType.Audio Name = QT_TRANSLATE_NOOP('MediaElementName', 'System Out') - def __init__(self, pipe): - super().__init__() + def __init__(self, pipeline): + super().__init__(pipeline) self.auto_sink = Gst.ElementFactory.make('autoaudiosink', 'sink') - pipe.add(self.auto_sink) + self.pipeline.add(self.auto_sink) def sink(self): return self.auto_sink \ No newline at end of file diff --git a/lisp/plugins/gst_backend/elements/auto_src.py b/lisp/plugins/gst_backend/elements/auto_src.py index 476aca347..8775c43bd 100644 --- a/lisp/plugins/gst_backend/elements/auto_src.py +++ b/lisp/plugins/gst_backend/elements/auto_src.py @@ -28,11 +28,11 @@ class AutoSrc(GstSrcElement): MediaType = MediaType.Audio Name = QT_TRANSLATE_NOOP('MediaElementName', 'System Input') - def __init__(self, pipe): - super().__init__() + def __init__(self, pipeline): + super().__init__(pipeline) self.auto_src = Gst.ElementFactory.make("autoaudiosrc", "src") - pipe.add(self.auto_src) + self.pipeline.add(self.auto_src) def src(self): return self.auto_src diff --git a/lisp/plugins/gst_backend/elements/db_meter.py b/lisp/plugins/gst_backend/elements/db_meter.py index 5582f050f..741c4b2b1 100644 --- a/lisp/plugins/gst_backend/elements/db_meter.py +++ b/lisp/plugins/gst_backend/elements/db_meter.py @@ -35,7 +35,7 @@ class DbMeter(GstMediaElement): peak_falloff = GstProperty('level', 'peak-falloff', default=20) def __init__(self, pipeline): - super().__init__() + super().__init__(pipeline) self.level_ready = Signal() self.pipeline = pipeline diff --git a/lisp/plugins/gst_backend/elements/equalizer10.py b/lisp/plugins/gst_backend/elements/equalizer10.py index c62939a8b..ab9fe20c5 100644 --- a/lisp/plugins/gst_backend/elements/equalizer10.py +++ b/lisp/plugins/gst_backend/elements/equalizer10.py @@ -40,14 +40,14 @@ class Equalizer10(GstMediaElement): band8 = GstProperty('equalizer', 'band8', default=0) band9 = GstProperty('equalizer', 'band9', default=0) - def __init__(self, pipe): - super().__init__() + def __init__(self, pipeline): + super().__init__(pipeline) self.equalizer = Gst.ElementFactory.make("equalizer-10bands", None) self.audio_converter = Gst.ElementFactory.make("audioconvert", None) - pipe.add(self.equalizer) - pipe.add(self.audio_converter) + self.pipeline.add(self.equalizer) + self.pipeline.add(self.audio_converter) self.equalizer.link(self.audio_converter) diff --git a/lisp/plugins/gst_backend/elements/jack_sink.py b/lisp/plugins/gst_backend/elements/jack_sink.py index 3385bb06c..3f881954e 100644 --- a/lisp/plugins/gst_backend/elements/jack_sink.py +++ b/lisp/plugins/gst_backend/elements/jack_sink.py @@ -41,10 +41,10 @@ class JackSink(GstMediaElement): _ControlClient = None _clients = [] - connections = Property(default=[[] for _ in range(8)]) + connections = Property(default=[]) def __init__(self, pipeline): - super().__init__() + super().__init__(pipeline) if JackSink._ControlClient is None: JackSink._ControlClient = jack.Client('LinuxShowPlayer_Control') @@ -89,8 +89,8 @@ def default_connections(cls, client): if isinstance(client, jack.Client): # Search for default input ports - input_ports = client.get_ports(name_pattern='^system:', - is_audio=True, is_input=True) + input_ports = client.get_ports( + name_pattern='^system:', is_audio=True, is_input=True) for n, port in enumerate(input_ports): if n < len(connections): connections[n].append(port.name) @@ -133,8 +133,8 @@ def __jack_connect(self): for input_name in in_ports: if output < len(out_ports): try: - JackSink._ControlClient.connect(out_ports[output], - input_name) + JackSink._ControlClient.connect( + out_ports[output], input_name) except jack.JackError: logger.exception( 'An error occurred while connecting Jack ports') diff --git a/lisp/plugins/gst_backend/elements/pitch.py b/lisp/plugins/gst_backend/elements/pitch.py index f64be3eba..bbbe465bc 100644 --- a/lisp/plugins/gst_backend/elements/pitch.py +++ b/lisp/plugins/gst_backend/elements/pitch.py @@ -31,14 +31,14 @@ class Pitch(GstMediaElement): pitch = GstProperty('gst_pitch', 'pitch', default=1.0) - def __init__(self, pipe): - super().__init__() + def __init__(self, pipeline): + super().__init__(pipeline) self.gst_pitch = Gst.ElementFactory.make('pitch', None) self.audio_converter = Gst.ElementFactory.make('audioconvert', None) - pipe.add(self.gst_pitch) - pipe.add(self.audio_converter) + self.pipeline.add(self.gst_pitch) + self.pipeline.add(self.audio_converter) self.gst_pitch.link(self.audio_converter) diff --git a/lisp/plugins/gst_backend/elements/preset_src.py b/lisp/plugins/gst_backend/elements/preset_src.py index e7d0c6d60..ecd7e60e1 100644 --- a/lisp/plugins/gst_backend/elements/preset_src.py +++ b/lisp/plugins/gst_backend/elements/preset_src.py @@ -47,8 +47,8 @@ class PresetSrc(GstSrcElement): preset = Property(default='The 42 melody') - def __init__(self, pipe): - super().__init__() + def __init__(self, pipeline): + super().__init__(pipeline) self.n_sample = 0 self.caps = 'audio/x-raw,format=U8,channels=1,layout=interleaved,' \ 'rate=' + str(PresetSrc.FREQ) @@ -62,8 +62,8 @@ def __init__(self, pipe): self.audio_converter = Gst.ElementFactory.make('audioconvert', None) - pipe.add(self.app_src) - pipe.add(self.audio_converter) + self.pipeline.add(self.app_src) + self.pipeline.add(self.audio_converter) self.app_src.link(self.audio_converter) diff --git a/lisp/plugins/gst_backend/elements/pulse_sink.py b/lisp/plugins/gst_backend/elements/pulse_sink.py index 37849efa8..be0c32626 100644 --- a/lisp/plugins/gst_backend/elements/pulse_sink.py +++ b/lisp/plugins/gst_backend/elements/pulse_sink.py @@ -29,13 +29,13 @@ class PulseSink(GstMediaElement): MediaType = MediaType.Audio Name = QT_TRANSLATE_NOOP('MediaElementName', 'PulseAudio Out') - def __init__(self, pipe): - super().__init__() + def __init__(self, pipeline): + super().__init__(pipeline) self.pulse_sink = Gst.ElementFactory.make('pulsesink', 'sink') self.pulse_sink.set_property('client-name', 'Linux Show Player') - pipe.add(self.pulse_sink) + self.pipeline.add(self.pulse_sink) def sink(self): return self.pulse_sink diff --git a/lisp/plugins/gst_backend/elements/speed.py b/lisp/plugins/gst_backend/elements/speed.py index b21886544..473189c3e 100644 --- a/lisp/plugins/gst_backend/elements/speed.py +++ b/lisp/plugins/gst_backend/elements/speed.py @@ -33,7 +33,7 @@ class Speed(GstMediaElement): speed = Property(default=1.0) def __init__(self, pipeline): - super().__init__() + super().__init__(pipeline) self.pipeline = pipeline self.scale_tempo = Gst.ElementFactory.make("scaletempo", None) diff --git a/lisp/plugins/gst_backend/elements/uri_input.py b/lisp/plugins/gst_backend/elements/uri_input.py index 4088af25b..874865ff1 100644 --- a/lisp/plugins/gst_backend/elements/uri_input.py +++ b/lisp/plugins/gst_backend/elements/uri_input.py @@ -28,8 +28,7 @@ from lisp.core.decorators import async_in_pool from lisp.core.properties import Property from lisp.plugins.gst_backend.gi_repository import Gst -from lisp.plugins.gst_backend.gst_element import GstProperty, \ - GstSrcElement +from lisp.plugins.gst_backend.gst_element import GstProperty, GstSrcElement from lisp.plugins.gst_backend.gst_utils import gst_uri_duration @@ -56,15 +55,15 @@ class UriInput(GstSrcElement): buffer_size = GstProperty('decoder', 'buffer-size', default=-1) use_buffering = GstProperty('decoder', 'use-buffering', default=False) - def __init__(self, pipe): - super().__init__() + def __init__(self, pipeline): + super().__init__(pipeline) self.decoder = Gst.ElementFactory.make("uridecodebin", None) self.audio_convert = Gst.ElementFactory.make("audioconvert", None) self._handler = self.decoder.connect("pad-added", self.__on_pad_added) - pipe.add(self.decoder) - pipe.add(self.audio_convert) + self.pipeline.add(self.decoder) + self.pipeline.add(self.audio_convert) self.changed('uri').connect(self.__uri_changed) Application().session.changed('session_file').connect( diff --git a/lisp/plugins/gst_backend/elements/user_element.py b/lisp/plugins/gst_backend/elements/user_element.py index 670d6a6ef..7b35f5b7e 100644 --- a/lisp/plugins/gst_backend/elements/user_element.py +++ b/lisp/plugins/gst_backend/elements/user_element.py @@ -33,7 +33,7 @@ class UserElement(GstMediaElement): bin = Property(default='') def __init__(self, pipeline): - super().__init__() + super().__init__(pipeline) self.pipeline = pipeline self.audio_convert_sink = Gst.ElementFactory.make("audioconvert", None) @@ -42,9 +42,9 @@ def __init__(self, pipeline): self.gst_bin.set_property("signal-handoffs", False) self.audio_convert_src = Gst.ElementFactory.make("audioconvert", None) - pipeline.add(self.audio_convert_sink) - pipeline.add(self.gst_bin) - pipeline.add(self.audio_convert_src) + self.pipeline.add(self.audio_convert_sink) + self.pipeline.add(self.gst_bin) + self.pipeline.add(self.audio_convert_src) self.audio_convert_sink.link(self.gst_bin) self.gst_bin.link(self.audio_convert_src) diff --git a/lisp/plugins/gst_backend/elements/volume.py b/lisp/plugins/gst_backend/elements/volume.py index 22c79e7b2..92bb627eb 100644 --- a/lisp/plugins/gst_backend/elements/volume.py +++ b/lisp/plugins/gst_backend/elements/volume.py @@ -37,16 +37,16 @@ class Volume(GstMediaElement): live_volume = GstLiveProperty( 'gst_volume', 'volume', type=float, range=(0, 10)) - def __init__(self, pipe): - super().__init__() + def __init__(self, pipeline): + super().__init__(pipeline) self.gst_volume = Gst.ElementFactory.make("volume", None) self.gst_normal_volume = Gst.ElementFactory.make("volume", None) self.audio_convert = Gst.ElementFactory.make("audioconvert", None) - pipe.add(self.gst_normal_volume) - pipe.add(self.gst_volume) - pipe.add(self.audio_convert) + self.pipeline.add(self.gst_normal_volume) + self.pipeline.add(self.gst_volume) + self.pipeline.add(self.audio_convert) self.gst_volume.link(self.gst_normal_volume) self.gst_normal_volume.link(self.audio_convert) diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index 9b7f9bb05..d23df1dc6 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -56,7 +56,7 @@ def __init__(self, app): Gst.init(None) # Register GStreamer settings widgets - AppConfigurationDialog.registerSettingsWidget( + AppConfigurationDialog.registerSettingsPage( 'plugins.gst', GstSettings, GstBackend.Config) # Add MediaCue settings widget to the CueLayout CueSettingsRegistry().add_item(GstMediaSettings, MediaCue) diff --git a/lisp/plugins/gst_backend/gst_element.py b/lisp/plugins/gst_backend/gst_element.py index 665b2dd35..07e795707 100644 --- a/lisp/plugins/gst_backend/gst_element.py +++ b/lisp/plugins/gst_backend/gst_element.py @@ -27,7 +27,6 @@ class GstProperty(Property): def __init__(self, element_name, property_name, default=None, adapter=None, **meta): super().__init__(default=default, **meta) - self.element_name = element_name self.property_name = property_name self.adapter = adapter @@ -39,8 +38,8 @@ def __set__(self, instance, value): if self.adapter is not None: value = self.adapter(value) - getattr(instance, self.element_name).set_property( - self.property_name, value) + element = getattr(instance, self.element_name) + element.set_property(self.property_name, value) class GstLiveProperty(Property): @@ -62,14 +61,17 @@ def __set__(self, instance, value): if self.adapter is not None: value = self.adapter(value) - getattr(instance, self.element_name).set_property( - self.property_name, value) + element = getattr(instance, self.element_name) + element.set_property(self.property_name, value) -# TODO: base provide base implementation of __init__ class GstMediaElement(MediaElement): """All the subclass must take the pipeline as first __init__ argument""" + def __init__(self, pipeline): + super().__init__() + self.pipeline = pipeline + def interrupt(self): """Called before Media interrupt""" @@ -167,4 +169,4 @@ def pop(self, index): def clear(self): while self.elements: - self.pop(len(self.elements) - 1) \ No newline at end of file + self.pop(len(self.elements) - 1) diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index 77996944d..2fd2872f2 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -22,6 +22,7 @@ from lisp.backend.media import Media, MediaState from lisp.core.properties import Property +from lisp.core.util import weak_call_proxy from lisp.plugins.gst_backend import elements as gst_elements from lisp.plugins.gst_backend.gi_repository import Gst from lisp.plugins.gst_backend.gst_element import GstMediaElements @@ -29,27 +30,30 @@ logger = logging.getLogger(__name__) -def validate_pipeline(pipe, rebuild=False): - # The first must be an input element - if pipe[0] not in gst_elements.inputs().keys(): - return False +GST_TO_MEDIA_STATE = { + Gst.State.NULL: MediaState.Null, + Gst.State.READY: MediaState.Ready, + Gst.State.PAUSED: MediaState.Paused, + Gst.State.PLAYING: MediaState.Playing +} - # The middle elements must be plugins elements - if rebuild: - if not isinstance(pipe, list): - pipe = list(pipe) - pipe[1:-1] = set(pipe[1:-1]).intersection( - set(gst_elements.plugins().keys())) - else: - if len(set(pipe[1:-1]) - set(gst_elements.plugins().keys())) != 0: - return False +def media_finalizer(pipeline, message_handler, media_elements): + # Allow pipeline resources to be released + pipeline.set_state(Gst.State.NULL) - # The last must be an output element - if pipe[-1] not in gst_elements.outputs().keys(): - return False + # Disconnect message handler + bus = pipeline.get_bus() + bus.remove_signal_watch() + bus.disconnect(message_handler) + + # Dispose all the elements + media_elements.clear() - return pipe if rebuild else True + +class GstError(Exception): + """Used to wrap GStreamer debug messages for the logging system.""" + pass class GstMedia(Media): @@ -60,68 +64,44 @@ class GstMedia(Media): def __init__(self): super().__init__() - - self._state = MediaState.Null - self._old_pipe = '' - self._loop_count = 0 - self.elements = GstMediaElements() - self._gst_pipe = Gst.Pipeline() - self._gst_state = Gst.State.NULL - self._time_query = Gst.Query.new_position(Gst.Format.TIME) - - bus = self._gst_pipe.get_bus() - bus.add_signal_watch() - # Use a weakref instead of the method or the object will not be - # garbage-collected - on_message = weakref.WeakMethod(self.__on_message) - handler = bus.connect('message', lambda *args: on_message()(*args)) - weakref.finalize(self, self.__finalizer, self._gst_pipe, handler, - self.elements) + self.__pipeline = None + self.__finalizer = None + self.__loop = 0 # current number of loops left to do + self.__current_pipe = None # A copy of the pipe property - self.changed('loop').connect(self.__prepare_loops) - self.changed('pipe').connect(self.__prepare_pipe) + self.changed('loop').connect(self.__on_loops_changed) + self.changed('pipe').connect(self.__on_pipe_changed) @Media.state.getter def state(self): - return self._state - - def __prepare_loops(self, loops): - self._loop_count = loops + if self.__pipeline is None: + return MediaState.Null - def __prepare_pipe(self, pipe): - if pipe != self._old_pipe: - self._old_pipe = pipe - - # If the pipeline is invalid raise an error - pipe = validate_pipeline(pipe, rebuild=True) - if not pipe: - raise ValueError('Invalid pipeline "{0}"'.format(pipe)) - - # Build the pipeline - ep_copy = self.elements.properties() - self.__build_pipeline() - self.elements.update_properties(ep_copy) - - self.elements[0].changed('duration').connect( - self.__duration_changed) - self.__duration_changed(self.elements[0].duration) + return GST_TO_MEDIA_STATE.get( + self.__pipeline.get_state(Gst.MSECOND)[1], MediaState.Null) def current_time(self): - ok, position = self._gst_pipe.query_position(Gst.Format.TIME) - return position // Gst.MSECOND if ok else 0 + if self.__pipeline is not None: + ok, position = self.__pipeline.query_position(Gst.Format.TIME) + return position // Gst.MSECOND if ok else 0 + + return 0 def play(self): - if self.state == MediaState.Stopped or self.state == MediaState.Paused: + if self.state == MediaState.Null: + self.__init_pipeline() + + if self.state == MediaState.Ready or self.state == MediaState.Paused: self.on_play.emit(self) for element in self.elements: element.play() self._state = MediaState.Playing - self._gst_pipe.set_state(Gst.State.PLAYING) - self._gst_pipe.get_state(Gst.SECOND) + self.__pipeline.set_state(Gst.State.PLAYING) + self.__pipeline.get_state(Gst.SECOND) if self.start_time > 0 or self.stop_time > 0: self.seek(self.start_time) @@ -135,11 +115,9 @@ def pause(self): for element in self.elements: element.pause() - self._state = MediaState.Paused - self._gst_pipe.set_state(Gst.State.PAUSED) - self._gst_pipe.get_state(Gst.SECOND) - - # FIXME: the pipeline is not flushed (fucking GStreamer) + self.__pipeline.set_state(Gst.State.PAUSED) + self.__pipeline.get_state(Gst.SECOND) + # FIXME: the pipeline is not flushed self.paused.emit(self) @@ -150,9 +128,36 @@ def stop(self): for element in self.elements: element.stop() - self.interrupt(emit=False) + self.__pipeline.set_state(Gst.State.READY) + self.__pipeline.get_state(Gst.SECOND) + self.__reset_media() + self.stopped.emit(self) + def seek(self, position): + if self.__seek(position): + self.sought.emit(self, position) + + def element(self, class_name): + return getattr(self.elements, class_name, None) + + def input_uri(self): + try: + return self.elements[0].input_uri() + except Exception: + pass + + def update_properties(self, properties): + # In order to update the other properties we need the pipeline first + pipe = properties.pop('pipe', ()) + if pipe: + self.pipe = pipe + + super().update_properties(properties) + + def __reset_media(self): + self.__loop = self.loop + def __seek(self, position): if self.state == MediaState.Playing or self.state == MediaState.Paused: max_position = self.duration @@ -162,7 +167,7 @@ def __seek(self, position): if position < max_position: # Query segment info for the playback rate query = Gst.Query.new_segment(Gst.Format.TIME) - self._gst_pipe.query(query) + self.__pipeline.query(query) rate = Gst.Query.parse_segment(query)[0] # Check stop_position value @@ -171,7 +176,7 @@ def __seek(self, position): stop_type = Gst.SeekType.SET # Seek the pipeline - result = self._gst_pipe.seek( + result = self.__pipeline.seek( rate if rate > 0 else 1, Gst.Format.TIME, Gst.SeekFlags.FLUSH, @@ -184,109 +189,87 @@ def __seek(self, position): return False - def seek(self, position): - if self.__seek(position): - self.sought.emit(self, position) + def __on_loops_changed(self, loops): + self.__loop = loops - def element(self, class_name): - return getattr(self.elements, class_name, None) + def __on_pipe_changed(self, new_pipe): + # Rebuild the pipeline only if something is changed + if new_pipe != self.__current_pipe: + self.__current_pipe = new_pipe + self.__init_pipeline() - def input_uri(self): - try: - return self.elements[0].input_uri() - except Exception: - pass - - def interrupt(self, dispose=False, emit=True): - for element in self.elements: - element.interrupt() + def __init_pipeline(self): + # Make a copy of the current elements properties + elements_properties = self.elements.properties() - state = self._state + # Call the current media-finalizer, if any + if self.__finalizer is not None: + # Set pipeline to NULL, finalize bus-handler and elements + self.__finalizer() - self._gst_pipe.set_state(Gst.State.NULL) - if dispose: - self._state = MediaState.Null - else: - self._gst_pipe.set_state(Gst.State.READY) - self._state = MediaState.Stopped - - self._loop_count = self.loop - - if emit and (state == MediaState.Playing or - state == MediaState.Paused): - self.interrupted.emit(self) - - def update_properties(self, properties): - # In order to update the other properties we need the pipeline - pipe = properties.pop('pipe', None) - if pipe: - self.pipe = pipe - - super().update_properties(properties) - - if self.state == MediaState.Null or self.state == MediaState.Error: - self._state = MediaState.Stopped - - def __build_pipeline(self): - # Set to NULL the pipeline - self.interrupt(dispose=True) - - # Remove all pipeline children - for __ in range(self._gst_pipe.get_children_count()): - self._gst_pipe.remove(self._gst_pipe.get_child_by_index(0)) - - # Remove all the elements - self.elements.clear() + self.__pipeline = Gst.Pipeline() + # Add a callback to watch for pipeline bus-messages + bus = self.__pipeline.get_bus() + bus.add_signal_watch() + # Use a weakref or GStreamer will hold a reference of the callback + handler = bus.connect( + 'message', weak_call_proxy(weakref.WeakMethod(self.__on_message))) # Create all the new elements all_elements = gst_elements.all_elements() for element in self.pipe: - self.elements.append(all_elements[element](self._gst_pipe)) + try: + self.elements.append(all_elements[element](self.__pipeline)) + except KeyError: + logger.warning('Invalid pipeline element: {}'.format(element)) + + # Reload the elements properties + self.elements.update_properties(elements_properties) + + # The source element should provide the duration + self.elements[0].changed('duration').connect(self.__duration_changed) + self.duration = self.elements[0].duration + + # Create a new finalizer object to free the pipeline when the media + # is dereferenced + self.__finalizer = weakref.finalize( + self, media_finalizer, self.__pipeline, handler, self.elements) - # Set to Stopped/READY the pipeline - self._state = MediaState.Stopped - self._gst_pipe.set_state(Gst.State.READY) + # Set the pipeline to READY + self.__pipeline.set_state(Gst.State.READY) + self.__pipeline.get_state(Gst.SECOND) self.elements_changed.emit(self) def __on_message(self, bus, message): - if message.src == self._gst_pipe: - if message.type == Gst.MessageType.STATE_CHANGED: - self._gst_state = message.parse_state_changed()[1] - elif message.type == Gst.MessageType.EOS: - self.__on_eos() + if message.src == self.__pipeline: + if message.type == Gst.MessageType.EOS: + if self.__loop != 0: + # If we still have loops to do then seek to begin + # FIXME: this is not seamless + self.__loop -= 1 + self.seek(self.start_time) + else: + # Otherwise go in READY state + self.__pipeline.set_state(Gst.State.READY) + self.__pipeline.get_state(Gst.SECOND) + self.__reset_media() + self.eos.emit(self) elif message.type == Gst.MessageType.CLOCK_LOST: - self._gst_pipe.set_state(Gst.State.PAUSED) - self._gst_pipe.set_state(Gst.State.PLAYING) + self.__pipeline.set_state(Gst.State.PAUSED) + self.__pipeline.set_state(Gst.State.PLAYING) if message.type == Gst.MessageType.ERROR: - error, _ = message.parse_error() - logger.error('GStreamer: {}'.format(error.message), exc_info=error) + error, debug = message.parse_error() + logger.error( + 'GStreamer: {}'.format(error.message), exc_info=GstError(debug)) - self._state = MediaState.Error - self.interrupt(dispose=True, emit=False) + # Set the pipeline to NULL + self.__pipeline.set_state(Gst.State.NULL) + self.__pipeline.get_state(Gst.SECOND) + self.__reset_media() self.error.emit(self) - def __on_eos(self): - if self._loop_count != 0: - self._loop_count -= 1 - self.seek(self.start_time) - else: - self._state = MediaState.Stopped - self.eos.emit(self) - self.interrupt(emit=False) - def __duration_changed(self, duration): self.duration = duration - - @staticmethod - def __finalizer(pipeline, connection_handler, media_elements): - # Allow pipeline resources to be released - pipeline.set_state(Gst.State.NULL) - - bus = pipeline.get_bus() - bus.remove_signal_watch() - bus.disconnect(connection_handler) - - media_elements.clear() diff --git a/lisp/plugins/gst_backend/gst_media_settings.py b/lisp/plugins/gst_backend/gst_media_settings.py index b1a21b90e..919f55ed1 100644 --- a/lisp/plugins/gst_backend/gst_media_settings.py +++ b/lisp/plugins/gst_backend/gst_media_settings.py @@ -21,7 +21,7 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QGridLayout, QListWidget, QPushButton, \ - QListWidgetItem + QListWidgetItem, QSizePolicy from lisp.plugins.gst_backend.gst_pipe_edit import GstPipeEditDialog from lisp.plugins.gst_backend.settings import pages_by_element @@ -66,6 +66,7 @@ def loadSettings(self, settings): if page is not None and issubclass(page, SettingsPage): page = page(parent=self) + page.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) page.loadSettings( settings.get('elements', {}).get( element, page.ELEMENT.class_defaults())) diff --git a/lisp/plugins/gst_backend/settings/equalizer10.py b/lisp/plugins/gst_backend/settings/equalizer10.py index 012952d8c..130c6babf 100644 --- a/lisp/plugins/gst_backend/settings/equalizer10.py +++ b/lisp/plugins/gst_backend/settings/equalizer10.py @@ -31,8 +31,8 @@ class Equalizer10Settings(SettingsPage): ELEMENT = Equalizer10 Name = ELEMENT.Name - FREQ = ['30Hz', '60Hz', '120Hz', '240Hz', '475Hz', '950Hz', '1900Hz', - '3800Hz', '7525Hz', '15KHz'] + FREQ = ['30', '60', '120', '240', '475', '950', '1900', '3800', '7525', + '15K'] def __init__(self, **kwargs): super().__init__(**kwargs) diff --git a/lisp/plugins/gst_backend/settings/jack_sink.py b/lisp/plugins/gst_backend/settings/jack_sink.py index 2d2ffa863..3018a38f2 100644 --- a/lisp/plugins/gst_backend/settings/jack_sink.py +++ b/lisp/plugins/gst_backend/settings/jack_sink.py @@ -70,7 +70,9 @@ def getSettings(self): return settings def loadSettings(self, settings): - self.connections = settings.get('connections', self.connections).copy() + connections = settings.get('connections', []) + if connections: + self.connections = connections.copy() def enableCheck(self, enabled): self.jackGroup.setCheckable(enabled) diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index a103041f8..cd9daf689 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -37,7 +37,7 @@ def __init__(self, app): super().__init__(app) # Register the settings widget - AppConfigurationDialog.registerSettingsWidget( + AppConfigurationDialog.registerSettingsPage( 'plugins.midi', MIDISettings, Midi.Config) # Load the backend and set it as current mido backend diff --git a/lisp/plugins/osc/osc.py b/lisp/plugins/osc/osc.py index 740baf998..b8b8fbb65 100644 --- a/lisp/plugins/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -37,7 +37,7 @@ def __init__(self, app): super().__init__(app) # Register the settings widget - AppConfigurationDialog.registerSettingsWidget( + AppConfigurationDialog.registerSettingsPage( 'plugins.osc', OscSettings, Osc.Config) # Create a server instance diff --git a/lisp/plugins/timecode/timecode.py b/lisp/plugins/timecode/timecode.py index dabd4cd34..1d0652bce 100644 --- a/lisp/plugins/timecode/timecode.py +++ b/lisp/plugins/timecode/timecode.py @@ -34,22 +34,27 @@ class Timecode(Plugin): - Name = 'Timecode' - Authors = ('Thomas Achtner', ) - OptDepends = ('Midi', ) + Authors = ('Thomas Achtner',) + OptDepends = ('Midi',) Description = 'Provide timecode via multiple protocols' def __init__(self, app): super().__init__(app) # Register a new Cue property to store settings - Cue.timecode = Property(default={}) + Cue.timecode = Property( + default={ + 'enabled': False, + 'replace_hours': False, + 'track': 0 + } + ) # Register cue-settings-page CueSettingsRegistry().add_item(TimecodeSettings, MediaCue) # Register the settings widget - AppConfigurationDialog.registerSettingsWidget( + AppConfigurationDialog.registerSettingsPage( 'plugins.timecode', TimecodeAppSettings, Timecode.Config) # Load available protocols diff --git a/lisp/ui/settings/app_configuration.py b/lisp/ui/settings/app_configuration.py index e8855f2aa..7e4185a8b 100644 --- a/lisp/ui/settings/app_configuration.py +++ b/lisp/ui/settings/app_configuration.py @@ -22,17 +22,16 @@ from PyQt5 import QtCore from PyQt5.QtCore import QModelIndex -from PyQt5.QtWidgets import QVBoxLayout, QDialogButtonBox, QDialog, QTreeView, \ - QHBoxLayout, QWidget +from PyQt5.QtWidgets import QVBoxLayout, QDialogButtonBox, QDialog from lisp.core.dicttree import DictNode -from lisp.ui.settings.pages import ConfigurationPage -from lisp.ui.settings.pages_tree_model import SettingsPagesTreeModel +from lisp.ui.settings.pages import ConfigurationPage, TreeMultiConfigurationWidget +from lisp.ui.settings.pages_tree_model import PagesTreeModel from lisp.ui.ui_utils import translate logger = logging.getLogger(__name__) -PageEntry = namedtuple('PageEntry', ('widget', 'config')) +PageEntry = namedtuple('PageEntry', ('page', 'config')) class AppConfigurationDialog(QDialog): @@ -47,11 +46,11 @@ def __init__(self, **kwargs): self.resize(640, 510) self.setLayout(QVBoxLayout()) - self.model = SettingsPagesTreeModel() + self.model = PagesTreeModel() for r_node in AppConfigurationDialog.PagesRegistry.children: self._populateModel(QModelIndex(), r_node) - self.mainPage = TreeMultiSettingsWidget(self.model) + self.mainPage = TreeMultiConfigurationWidget(self.model) self.mainPage.selectFirst() self.layout().addWidget(self.mainPage) @@ -75,28 +74,28 @@ def applySettings(self): def _populateModel(self, m_parent, r_parent): if r_parent.value is not None: - widget = r_parent.value.widget + page = r_parent.value.page config = r_parent.value.config else: - widget = None + page = None config = None try: - if widget is None: - # The current node have no widget, use the parent model-index + if page is None: + # The current node have no page, use the parent model-index # as parent for it's children mod_index = m_parent - elif issubclass(widget, ConfigurationPage): - mod_index = self.model.addPage(widget(config), parent=m_parent) + elif issubclass(page, ConfigurationPage): + mod_index = self.model.addPage(page(config), parent=m_parent) else: - mod_index = self.model.addPage(widget(), parent=m_parent) + mod_index = self.model.addPage(page(), parent=m_parent) except Exception: - if not isinstance(widget, type): + if not isinstance(page, type): page_name = 'NoPage' - elif issubclass(widget, ConfigurationPage): - page_name = widget.Name + elif issubclass(page, ConfigurationPage): + page_name = page.Name else: - page_name = widget.__name__ + page_name = page.__name__ logger.warning( 'Cannot load configuration page: "{}" ({})'.format( @@ -110,69 +109,26 @@ def __onOk(self): self.accept() @staticmethod - def registerSettingsWidget(path, widget, config): + def registerSettingsPage(path, page, config): """ - :param path: indicate the widget "position": 'category.sub.key' + :param path: indicate the page "position": 'category.sub.key' :type path: str - :type widget: Type[lisp.ui.settings.settings_page.SettingsPage] + :type page: Type[lisp.ui.settings.settings_page.ConfigurationPage] :type config: lisp.core.configuration.Configuration """ - AppConfigurationDialog.PagesRegistry.set( - path, PageEntry(widget=widget, config=config)) + if issubclass(page, ConfigurationPage): + AppConfigurationDialog.PagesRegistry.set( + path, PageEntry(page=page, config=config)) + else: + raise TypeError( + 'AppConfiguration pages must be ConfigurationPage(s), not {}' + .format(type(page).__name__) + ) @staticmethod - def unregisterSettingsWidget(path): + def unregisterSettingsPage(path): """ - :param path: indicate the widget "position": 'category.sub.key' + :param path: indicate the page "position": 'category.sub.key' :type path: str """ AppConfigurationDialog.PagesRegistry.pop(path) - - -class TreeMultiSettingsWidget(QWidget): - def __init__(self, navModel, **kwargs): - """ - :param navModel: The model that keeps all the pages-hierarchy - :type navModel: SettingsPagesTreeModel - """ - super().__init__(**kwargs) - self.setLayout(QHBoxLayout()) - self.layout().setSpacing(0) - self.layout().setContentsMargins(0, 0, 0, 0) - self.navModel = navModel - - self.navWidget = QTreeView() - self.navWidget.setHeaderHidden(True) - self.navWidget.setModel(self.navModel) - self.layout().addWidget(self.navWidget) - - self._currentWidget = QWidget() - self.layout().addWidget(self._currentWidget) - - self.layout().setStretch(0, 2) - self.layout().setStretch(1, 5) - - self.navWidget.selectionModel().selectionChanged.connect( - self._changePage) - - def selectFirst(self): - self.navWidget.setCurrentIndex(self.navModel.index(0, 0, QModelIndex())) - - def currentWidget(self): - return self._currentWidget - - def applySettings(self): - root = self.navModel.node(QModelIndex()) - for node in root.walk(): - if node.widget is not None: - node.widget.applySettings() - - def _changePage(self, selected): - if selected.indexes(): - self.layout().removeWidget(self._currentWidget) - self._currentWidget.hide() - self._currentWidget = selected.indexes()[0].internalPointer().widget - self._currentWidget.show() - self.layout().addWidget(self._currentWidget) - self.layout().setStretch(0, 2) - self.layout().setStretch(1, 5) diff --git a/lisp/ui/settings/app_pages/plugins_settings.py b/lisp/ui/settings/app_pages/plugins_settings.py index 449c5f945..4ebb183f0 100644 --- a/lisp/ui/settings/app_pages/plugins_settings.py +++ b/lisp/ui/settings/app_pages/plugins_settings.py @@ -17,21 +17,21 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt, QSize +from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt, QSize, QModelIndex from PyQt5.QtWidgets import QListWidget, QListWidgetItem, QTextBrowser, \ QVBoxLayout from lisp import plugins from lisp.ui.icons import IconTheme -from lisp.ui.settings.pages import ABCSettingsPage +from lisp.ui.settings.pages import ConfigurationPage # TODO: just a proof-of concept -class PluginsSettings(ABCSettingsPage): +class PluginsSettings(ConfigurationPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Plugins') - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, config, **kwargs): + super().__init__(config, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -63,7 +63,8 @@ def __init__(self, **kwargs): self.layout().setStretch(0, 3) self.layout().setStretch(1, 1) - self.__selection_changed() + if self.pluginsList.count(): + self.pluginsList.setCurrentRow(0) def __selection_changed(self): item = self.pluginsList.currentItem() diff --git a/lisp/ui/settings/pages.py b/lisp/ui/settings/pages.py index b81c48202..98bf0a151 100644 --- a/lisp/ui/settings/pages.py +++ b/lisp/ui/settings/pages.py @@ -19,7 +19,8 @@ from abc import abstractmethod -from PyQt5.QtWidgets import QWidget, QTabWidget, QVBoxLayout +from PyQt5.QtCore import QModelIndex, Qt +from PyQt5.QtWidgets import QWidget, QTabWidget, QVBoxLayout, QTreeView, QHBoxLayout, QGridLayout, QSizePolicy from lisp.core.qmeta import QABCMeta from lisp.core.util import dict_merge @@ -65,26 +66,13 @@ def enableCheck(self, enabled): """ -class CuePageMixin: - Name = 'CueSettingsPage' - - def __init__(self, cue_type): - self.cue_type = cue_type - - -class CueSettingsPage(SettingsPage, CuePageMixin): - - def __init__(self, cue_type, **kwargs): - super().__init__(cue_type=cue_type, **kwargs) - - class ConfigurationPage(ABCSettingsPage): Name = 'ConfigurationPage' def __init__(self, config, **kwargs): """ :param config: Configuration object to "edit" - :type config: lisp.core.configuration.Configuration + :type config: lisp.core.configuration.Configuration """ super().__init__(**kwargs) self.config = config @@ -94,14 +82,26 @@ def applySettings(self): pass -class TabsMultiPage(QWidget): - _PagesBaseClass = ABCSettingsPage +class CuePageMixin: + Name = 'CueSettingsPage' + + def __init__(self, cue_type): + self.cue_type = cue_type + + +class CueSettingsPage(SettingsPage, CuePageMixin): + + def __init__(self, cue_type, **kwargs): + super().__init__(cue_type=cue_type, **kwargs) + +class TabsMultiPage(QWidget): def __init__(self, **kwargs): super().__init__(**kwargs) self.setLayout(QVBoxLayout()) self.tabWidget = QTabWidget(parent=self) + self.tabWidget.setFocusPolicy(Qt.StrongFocus) self.layout().addWidget(self.tabWidget) self._pages = [] @@ -110,7 +110,7 @@ def page(self, index): return self._pages[index] def addPage(self, page): - if isinstance(page, self._PagesBaseClass): + if isinstance(page, ABCSettingsPage): self._pages.append(page) self.tabWidget.addTab( page, translate('SettingsPageName', page.Name)) @@ -133,8 +133,6 @@ def pageIndex(self, page): class TabsMultiSettingsPage(TabsMultiPage, SettingsPage): - _PagesBaseClass = SettingsPage - def loadSettings(self, settings): for page in self._pages: page.loadSettings(settings) @@ -152,8 +150,85 @@ def enableCheck(self, enabled): class TabsMultiConfigurationPage(TabsMultiPage, ConfigurationPage): - _PagesBaseClass = ConfigurationPage - def applySettings(self): for page in self._pages: page.applySettings() + + +class TreeMultiPagesWidget(QWidget): + + def __init__(self, navModel, **kwargs): + """ + :param navModel: The model that keeps all the pages-hierarchy + :type navModel: lisp.ui.settings.pages_tree_model.PagesTreeModel + """ + super().__init__(**kwargs) + self.setLayout(QGridLayout()) + self.layout().setSpacing(0) + self.layout().setContentsMargins(0, 0, 0, 0) + self.navModel = navModel + + self.navWidget = QTreeView() + self.navWidget.setHeaderHidden(True) + self.navWidget.setModel(self.navModel) + self.layout().addWidget(self.navWidget, 0, 0) + + self._currentWidget = QWidget() + self.layout().addWidget(self._currentWidget, 0, 1) + + self.navWidget.selectionModel().selectionChanged.connect( + self._changePage) + + self._resetStretch() + + def selectFirst(self): + self.navWidget.setCurrentIndex(self.navModel.index(0, 0, QModelIndex())) + + def currentWidget(self): + return self._currentWidget + + def _resetStretch(self): + self.layout().setColumnStretch(0, 2) + self.layout().setColumnStretch(1, 5) + + def _changePage(self, selected): + if selected.indexes(): + self.layout().removeWidget(self._currentWidget) + self._currentWidget.hide() + self._currentWidget = selected.indexes()[0].internalPointer().page + self._currentWidget.setSizePolicy( + QSizePolicy.Ignored, QSizePolicy.Ignored) + self._currentWidget.show() + self.layout().addWidget(self._currentWidget, 0, 1) + self._resetStretch() + + +class TreeMultiConfigurationWidget(TreeMultiPagesWidget): + def applySettings(self): + root = self.navModel.node(QModelIndex()) + for node in root.walk(): + if node.page is not None: + node.page.applySettings() + + +class TreeMultiSettingsWidget(TreeMultiPagesWidget): + def loadSettings(self, settings): + root = self.navModel.node(QModelIndex()) + for node in root.walk(): + if node.page is not None: + node.page.loadSettings(settings) + + def getSettings(self): + settings = {} + root = self.navModel.node(QModelIndex()) + for node in root.walk(): + if node.page is not None: + dict_merge(settings, node.page.getSettings()) + + return settings + + def enableCheck(self, enabled): + root = self.navModel.node(QModelIndex()) + for node in root.walk(): + if node.page is not None: + node.page.enableCheck() diff --git a/lisp/ui/settings/pages_tree_model.py b/lisp/ui/settings/pages_tree_model.py index 80ae49068..ea82eff93 100644 --- a/lisp/ui/settings/pages_tree_model.py +++ b/lisp/ui/settings/pages_tree_model.py @@ -22,14 +22,14 @@ from lisp.ui.settings.pages import ABCSettingsPage -class SettingsPageNode: +class PageNode: """ - :type parent: SettingsPageNode - :type _children: list[SettingsPageNode] + :type parent: PageNode + :type _children: list[PageNode] """ def __init__(self, page): self.parent = None - self.widget = page + self.page = page self._children = [] @@ -64,12 +64,12 @@ def walk(self): yield from child.walk() -class SettingsPagesTreeModel(QAbstractItemModel): +class PagesTreeModel(QAbstractItemModel): PageRole = Qt.UserRole + 1 def __init__(self, **kwargs): super().__init__(**kwargs) - self._root = SettingsPageNode(None) + self._root = PageNode(None) def rowCount(self, parent=QModelIndex()): if parent.isValid(): @@ -84,9 +84,9 @@ def data(self, index, role=Qt.DisplayRole): if index.isValid(): node = index.internalPointer() if role == Qt.DisplayRole: - return node.widget.Name - elif role == SettingsPagesTreeModel.PageRole: - return node.widget + return node.page.Name + elif role == PagesTreeModel.PageRole: + return node.page def headerData(self, section, orientation, role=Qt.DisplayRole): return None @@ -119,7 +119,7 @@ def index(self, row, column, parent=QModelIndex()): def pageIndex(self, page, parent=QModelIndex()): parentNode = self.node(parent) for row in range(parentNode.childCount()): - if parentNode.child(row).widget is page: + if parentNode.child(row).page is page: return self.index(row, 0, parent) return QModelIndex() @@ -130,7 +130,7 @@ def addPage(self, page, parent=QModelIndex()): position = parentNode.childCount() self.beginInsertRows(parent, position, position) - node = SettingsPageNode(page) + node = PageNode(page) parentNode.addChild(node) self.endInsertRows() diff --git a/lisp/ui/themes/dark/theme.qss b/lisp/ui/themes/dark/theme.qss index cd98c8cd2..32d35ee28 100644 --- a/lisp/ui/themes/dark/theme.qss +++ b/lisp/ui/themes/dark/theme.qss @@ -1,10 +1,10 @@ /* - * Copyright 2012-2014 Ceruti Francesco & contributors + * Copyright 2012-2018 Francesco Ceruti * * This file is part of LiSP (Linux Show Player). - + * * Based on ColinDuquesnoy works at: https://github.com/ColinDuquesnoy/QDarkStyleSheet/ - + * * The MIT License (MIT) * * Copyright (c) <2013-2014> @@ -427,6 +427,10 @@ QTabBar::tab:selected { border-top-right-radius: 3px; } +QTabBar::tab:selected:focus { + border-top-color: #419BE6; +} + QDockWidget { color: white; titlebar-close-icon: url(:/assets/close.png); @@ -659,10 +663,16 @@ QRadioButton:indicator:checked:disabled { #CartTabBar::tab:selected { height: 40px; margin-bottom: -5px; + border-top-width: 2px; + border-top-color: #CCC; border-top-left-radius: 3px; border-top-right-radius: 3px; } +#CartTabBar::tab:selected:focus { + border-top-color: #419BE6; +} + #ButtonCueWidget { background-color: #464646; color: #AAAAAA; diff --git a/lisp/ui/themes/theme.py b/lisp/ui/themes/theme.py index 0c4120741..587b6deeb 100644 --- a/lisp/ui/themes/theme.py +++ b/lisp/ui/themes/theme.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From ff6eb2f79324aed50568e6b3c33578b2afb5a7a2 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 29 Mar 2018 12:47:59 +0200 Subject: [PATCH 099/333] Handle configurations changes in some plugin Update: improved configuration changes and signals to emit only when a value is actually changed Update: improved missing-plugin errors/handling Update: timecode, osc and midi plugins reconfigure automatically when the application settings are changed --- lisp/core/configuration.py | 23 ++++-- lisp/core/util.py | 27 +++++++ lisp/plugins/__init__.py | 10 ++- lisp/plugins/action_cues/midi_cue.py | 9 ++- lisp/plugins/action_cues/osc_cue.py | 12 ++-- lisp/plugins/controller/controller.py | 7 +- lisp/plugins/controller/protocols/midi.py | 15 ++-- lisp/plugins/controller/protocols/osc.py | 25 +++---- lisp/plugins/midi/midi.py | 13 ++++ lisp/plugins/osc/osc.py | 18 ++++- lisp/plugins/osc/osc_server.py | 71 +++++++++++++------ lisp/plugins/timecode/cue_tracker.py | 9 +-- lisp/plugins/timecode/protocols/__init__.py | 5 +- lisp/plugins/timecode/protocols/midi.py | 7 +- lisp/plugins/timecode/timecode.py | 38 +++++++--- .../ui/settings/app_pages/plugins_settings.py | 2 +- lisp/ui/settings/pages.py | 2 +- 17 files changed, 206 insertions(+), 87 deletions(-) diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index 15f08df14..f2df18845 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -30,7 +30,7 @@ from lisp import DEFAULT_APP_CONFIG, USER_APP_CONFIG from lisp.core.signal import Signal from lisp.core.singleton import ABCSingleton -from lisp.core.util import dict_merge +from lisp.core.util import dict_merge, dict_merge_diff logger = logging.getLogger(__name__) @@ -79,7 +79,11 @@ def get(self, path, default=_UNSET): def set(self, path, value): try: node, key = self.__traverse(self.sp(path), self._root, set_=True) - node[key] = value + if node.get(key, _UNSET) != value: + node[key] = value + return True + + return False except (KeyError, TypeError): raise ConfDictError('invalid path') @@ -96,7 +100,7 @@ def update(self, new_conf): :param new_conf: a dict containing the new values :type new_conf: dict """ - dict_merge(self._root, new_conf) + dict_merge(self._root, deepcopy(new_conf)) def deep_copy(self): """Return a deep-copy of the internal dictionary.""" @@ -156,12 +160,17 @@ def write(self): pass def set(self, path, value): - super().set(path, value) - self.changed.emit(path, value) + changed = super().set(path, value) + if changed: + self.changed.emit(path, value) + + return changed def update(self, new_conf): - super().update(new_conf) - self.updated.emit() + diff = dict_merge_diff(self._root, new_conf) + if diff: + super().update(diff) + self.updated.emit(diff) class DummyConfiguration(Configuration): diff --git a/lisp/core/util.py b/lisp/core/util.py index f5d95585b..a89dba85b 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -41,6 +41,33 @@ def dict_merge(dct, merge_dct): dct[key] = value +def dict_merge_diff(dct, cmp_dct): + """Return the (merge) difference between two dicts + + Can be considered the "complement" version of dict_merge, the return will + be a dictionary that contains all the keys/values that will change if + using `dict_merge` on the given dictionaries (in the same order). + + :type dct: Mapping + :type cmp_dct: Mapping + """ + diff = {} + + for key, cmp_value in cmp_dct.items(): + if key in dct: + value = dct[key] + if isinstance(value, Mapping) and isinstance(cmp_value, Mapping): + sub_diff = dict_merge_diff(value, cmp_value) + if sub_diff: + diff[key] = sub_diff + elif value != cmp_value: + diff[key] = cmp_value + else: + diff[key] = cmp_value + + return diff + + def find_packages(path='.'): """List the python packages in the given directory.""" diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index bc0e8a050..b1e95e9e8 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -34,6 +34,10 @@ logger = logging.getLogger(__name__) +class PluginNotLoadedError(Exception): + pass + + def load_plugins(application): """Load and instantiate available plugins.""" for name, plugin in load_classes(__package__, path.dirname(__file__)): @@ -144,4 +148,8 @@ def is_loaded(plugin_name): def get_plugin(plugin_name): - return LOADED[plugin_name] + if is_loaded(plugin_name): + return LOADED[plugin_name] + else: + raise PluginNotLoadedError( + 'the requested plugin is not loaded: {}'.format(plugin_name)) diff --git a/lisp/plugins/action_cues/midi_cue.py b/lisp/plugins/action_cues/midi_cue.py index 46c4fd32b..03b6a1de4 100644 --- a/lisp/plugins/action_cues/midi_cue.py +++ b/lisp/plugins/action_cues/midi_cue.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, QLabel, \ @@ -23,12 +24,14 @@ from lisp.core.properties import Property from lisp.cues.cue import Cue -from lisp.plugins import get_plugin +from lisp.plugins import get_plugin, PluginNotLoadedError from lisp.plugins.midi.midi_utils import str_msg_to_dict, dict_msg_to_str from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate +logger = logging.getLogger(__name__) + class MidiCue(Cue): Name = QT_TRANSLATE_NOOP('CueName', 'MIDI Cue') @@ -38,11 +41,11 @@ class MidiCue(Cue): def __init__(self, **kwargs): super().__init__(**kwargs) self.name = translate('CueName', self.Name) - self._output = get_plugin('Midi').output + self.__midi = get_plugin('Midi') def __start__(self, fade=False): if self.message: - self._output.send_from_str(self.message) + self.__midi.output.send_from_str(self.message) return False diff --git a/lisp/plugins/action_cues/osc_cue.py b/lisp/plugins/action_cues/osc_cue.py index 9e98948eb..b47979bd4 100644 --- a/lisp/plugins/action_cues/osc_cue.py +++ b/lisp/plugins/action_cues/osc_cue.py @@ -82,6 +82,8 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.name = translate('CueName', self.Name) + self.__osc = get_plugin('Osc') + self.__fader = Fader(self, '_position') self.__value = 0 self.__fadein = True @@ -102,10 +104,7 @@ def __set_position(self, value): else: args.append(row[COL_BASE_VAL]) - try: - get_plugin('Osc').server.send(self.path, *args) - except KeyError: - pass + self.__osc.server.send(self.path, *args) _position = property(__get_position, __set_position) @@ -357,10 +356,7 @@ def __test_message(self): else: args.append(row[COL_START_VAL]) - try: - get_plugin('Osc').server.send(self.path, *args) - except KeyError: - pass + self.__osc.server.send(self.path, *args) except ValueError: QMessageBox.critical( None, 'Error', 'Error on parsing argument list - nothing sent') diff --git a/lisp/plugins/controller/controller.py b/lisp/plugins/controller/controller.py index a40816506..f8e6efc57 100644 --- a/lisp/plugins/controller/controller.py +++ b/lisp/plugins/controller/controller.py @@ -25,6 +25,7 @@ from lisp.plugins.controller import protocols from lisp.plugins.controller.controller_settings import ControllerSettings from lisp.ui.settings.cue_settings import CueSettingsRegistry +from lisp.ui.ui_utils import translate logger = logging.getLogger(__name__) @@ -111,5 +112,7 @@ def __load_protocols(self): self.__protocols[protocol_class.__name__.lower()] = protocol except Exception: logger.exception( - 'Cannot setup controller protocol "{}"'.format( - protocol_class.__name__)) + translate( + 'Controller', 'Cannot load controller protocol: "{}"' + ).format(protocol_class.__name__) + ) diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index 7024ef96e..0a1822b67 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -21,7 +21,7 @@ from PyQt5.QtWidgets import QGroupBox, QPushButton, QComboBox, QVBoxLayout, \ QMessageBox, QTableView, QTableWidget, QHeaderView, QGridLayout -from lisp.plugins import get_plugin +from lisp.plugins import get_plugin, PluginNotLoadedError from lisp.plugins.controller.protocols.protocol import Protocol from lisp.ui.qdelegates import ComboBoxDelegate, SpinBoxDelegate, \ CueActionDelegate @@ -103,6 +103,10 @@ def __init__(self, cue_type, **kwargs): self.retranslateUi() self._default_action = self.cue_type.CueActions[0].name + try: + self.__midi = get_plugin('Midi').input + except PluginNotLoadedError: + self.setEnabled(False) def retranslateUi(self): self.addButton.setText(translate('ControllerSettings', 'Add')) @@ -136,13 +140,14 @@ def loadSettings(self, settings): self.midiModel.appendRow(m_type, channel+1, note, options[1]) def capture_message(self): - handler = get_plugin('Midi').input + handler = self.__midi.input handler.alternate_mode = True handler.new_message_alt.connect(self.__add_message) - QMessageBox.information(self, '', - translate('ControllerMidiSettings', - 'Listening MIDI messages ...')) + QMessageBox.information( + self, '', translate( + 'ControllerMidiSettings', 'Listening MIDI messages ...') + ) handler.new_message_alt.disconnect(self.__add_message) handler.alternate_mode = False diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index b3e58863f..1a4e4ec1c 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -26,12 +26,11 @@ QTableView, QTableWidget, QHeaderView, QGridLayout, QLabel, \ QDialog, QDialogButtonBox, QLineEdit, QMessageBox -from lisp.plugins import get_plugin +from lisp.plugins import get_plugin, PluginNotLoadedError from lisp.plugins.controller.protocols.protocol import Protocol from lisp.plugins.osc.osc_delegate import OscArgumentDelegate from lisp.plugins.osc.osc_server import OscMessageType -from lisp.ui.qdelegates import ComboBoxDelegate, \ - LineEditDelegate +from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate from lisp.ui.qmodels import SimpleTableModel from lisp.ui.settings.pages import CueSettingsPage from lisp.ui.ui_utils import translate @@ -41,11 +40,7 @@ class Osc(Protocol): def __init__(self): super().__init__() - try: - osc = get_plugin('Osc') - except KeyError: - raise RuntimeError('Osc plugin is not available.') - + osc = get_plugin('Osc') osc.server.new_message.connect(self.__new_message) def __new_message(self, path, args, types): @@ -250,6 +245,10 @@ def __init__(self, cue_type, **kwargs): self.retranslateUi() self._default_action = self.cue_type.CueActions[0].name + try: + self.__osc = get_plugin('Osc') + except PluginNotLoadedError: + self.setEnabled(False) def retranslateUi(self): self.addButton.setText(translate('ControllerOscSettings', 'Add')) @@ -286,13 +285,7 @@ def loadSettings(self, settings): ) def capture_message(self): - try: - osc = get_plugin('Osc') - except KeyError: - # TODO: non silent failure / disable option - return - - osc.server.new_message.connect(self.__show_message) + self.__osc.server.new_message.connect(self.__show_message) result = self.captureDialog.exec() if result == QDialog.Accepted and self.capturedMessage['path']: @@ -304,7 +297,7 @@ def capture_message(self): self._default_action ) - osc.server.new_message.disconnect(self.__show_message) + self.__osc.server.new_message.disconnect(self.__show_message) self.captureLabel.setText('Waiting for message:') diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index cd9daf689..8d522da99 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -53,6 +53,19 @@ def __init__(self, app): self._output_name(Midi.Config['outputDevice'])) self.__output.open() + Midi.Config.changed.connect(self.__config_change) + Midi.Config.updated.connect(self.__config_update) + + def __config_change(self, key, value): + if key == 'inputDevice': + self.__input.change_port(self._input_name(value)) + elif key == 'outputDevice': + self.__output.change_port(self._output_name(value)) + + def __config_update(self, diff): + for key, value in diff.items(): + self.__config_change(key, value) + def _input_name(self, port_name): """Check if port_name exists as an input port for the current backend. diff --git a/lisp/plugins/osc/osc.py b/lisp/plugins/osc/osc.py index b8b8fbb65..b0a9a4995 100644 --- a/lisp/plugins/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -25,7 +25,7 @@ from lisp.ui.settings.app_configuration import AppConfigurationDialog -# TODO: layout-controls in external plugin (now disabled, see osc_server.py) +# TODO: layout-controls in external plugin class Osc(Plugin): """Provide OSC I/O functionality""" @@ -46,6 +46,10 @@ def __init__(self, app): Osc.Config['inPort'], Osc.Config['outPort'] ) + self.__server.start() + + Osc.Config.changed.connect(self.__config_change) + Osc.Config.updated.connect(self.__config_update) @property def server(self): @@ -53,3 +57,15 @@ def server(self): def terminate(self): self.__server.stop() + + def __config_change(self, key, value): + if key == 'hostname': + self.__server.hostname = value + elif key == 'inPort': + self.__server.in_port = value + elif key == 'outPort': + self.__server.out_port = value + + def __config_update(self, diff): + for key, value in diff.items(): + self.__config_change(key, value) diff --git a/lisp/plugins/osc/osc_server.py b/lisp/plugins/osc/osc_server.py index bb3c4b002..f1934a5e0 100644 --- a/lisp/plugins/osc/osc_server.py +++ b/lisp/plugins/osc/osc_server.py @@ -21,7 +21,8 @@ import logging import traceback from enum import Enum -from liblo import ServerThread, Address, ServerError +from liblo import ServerThread, ServerError +from threading import Lock from lisp.core.signal import Signal @@ -103,45 +104,75 @@ class OscMessageType(Enum): class OscServer: - def __init__(self, hostname, iport, oport): + def __init__(self, hostname, in_port, out_port): + self.__in_port = in_port self.__hostname = hostname - self.__iport = iport - self.__oport = oport + self.__out_port = out_port self.__srv = None - self.__listening = False + self.__running = False + self.__lock = Lock() self.new_message = Signal() + @property + def out_port(self): + return self.__out_port + + @out_port.setter + def out_port(self, port): + with self.__lock: + self.__out_port = port + + @property + def hostname(self): + return self.__hostname + + @hostname.setter + def hostname(self, hostname): + with self.__lock: + self.__hostname = hostname + + @property + def in_port(self): + return self.__in_port + + @in_port.setter + def in_port(self, port): + self.__in_port = port + self.stop() + self.start() + + def is_running(self): + return self.__running + def start(self): - if self.__listening: + if self.__running: return try: - self.__srv = ServerThread(self.__iport) + self.__srv = ServerThread(self.__in_port) self.__srv.add_method(None, None, self.new_message.emit) self.__srv.start() - self.__listening = True + self.__running = True - logger.info('Server started at {}'.format(self.__srv.url)) + logger.info('OSC server started at {}'.format(self.__srv.url)) except ServerError: - logger.error('Cannot start sever') + logger.error('Cannot start OSC sever') logger.debug(traceback.format_exc()) def stop(self): if self.__srv is not None: - if self.__listening: - self.__srv.stop() - self.__listening = False + with self.__lock: + if self.__running: + self.__srv.stop() + self.__running = False self.__srv.free() - logger.info('Server stopped') - - @property - def listening(self): - return self.__listening + logger.info('OSC server stopped') def send(self, path, *args): - if self.__listening: - self.__srv.send(Address(self.__hostname, self.__oport), path, *args) + with self.__lock: + if self.__running: + self.__srv.send((self.__hostname, self.__out_port), path, *args) diff --git a/lisp/plugins/timecode/cue_tracker.py b/lisp/plugins/timecode/cue_tracker.py index 8d13ca50c..486eb2d56 100644 --- a/lisp/plugins/timecode/cue_tracker.py +++ b/lisp/plugins/timecode/cue_tracker.py @@ -104,11 +104,12 @@ def track(self, cue): def untrack(self): """Stop tracking the current cue""" - if self.__cue is not None: - self.__cue_time.notify.disconnect(self.send) + with self.__lock: + if self.__cue is not None: + self.__cue_time.notify.disconnect(self.send) - self.__cue_time = None - self.__cue = None + self.__cue_time = None + self.__cue = None def send(self, time): """Send time as timecode""" diff --git a/lisp/plugins/timecode/protocols/__init__.py b/lisp/plugins/timecode/protocols/__init__.py index efb097224..c8b620bc4 100644 --- a/lisp/plugins/timecode/protocols/__init__.py +++ b/lisp/plugins/timecode/protocols/__init__.py @@ -35,10 +35,7 @@ def get_protocol(name): :type name: str :rtype: type[TimecodeProtocol] """ - if name in __PROTOCOLS: - return __PROTOCOLS[name] - else: - raise AttributeError('Timecode-Protocol not found', name) + return __PROTOCOLS[name] def list_protocols(): diff --git a/lisp/plugins/timecode/protocols/midi.py b/lisp/plugins/timecode/protocols/midi.py index 05901179b..672daf830 100644 --- a/lisp/plugins/timecode/protocols/midi.py +++ b/lisp/plugins/timecode/protocols/midi.py @@ -51,12 +51,7 @@ def __init__(self): super().__init__() self.__last_time = -1 self.__last_frame = -1 - self.__midi = None - - try: - self.__midi = get_plugin('Midi') - except Exception: - raise RuntimeError('Midi plugin is not available') + self.__midi = get_plugin('Midi') def __send_full(self, fmt, hours, minutes, seconds, frame): """Sends fullframe timecode message. diff --git a/lisp/plugins/timecode/timecode.py b/lisp/plugins/timecode/timecode.py index 1d0652bce..d4a304e92 100644 --- a/lisp/plugins/timecode/timecode.py +++ b/lisp/plugins/timecode/timecode.py @@ -18,6 +18,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging from lisp.core.properties import Property from lisp.core.plugin import Plugin @@ -31,6 +32,9 @@ TimecodeSettings from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.settings.cue_settings import CueSettingsRegistry +from lisp.ui.ui_utils import translate + +logger = logging.getLogger(__name__) class Timecode(Plugin): @@ -60,16 +64,9 @@ def __init__(self, app): # Load available protocols protocols.load_protocols() - try: - protocol = protocols.get_protocol(Timecode.Config['Protocol'])() - except Exception: - # TODO: warn the user - # Use a dummy protocol in case of failure - protocol = TimecodeProtocol() - # Create the cue tracker object self.__cue_tracker = TimecodeCueTracker( - protocol, + self.__get_protocol(Timecode.Config['protocol']), TcFormat[Timecode.Config['format']] ) @@ -83,9 +80,34 @@ def __init__(self, app): self.app.cue_model.item_added.connect(self.__cue_added) self.app.cue_model.item_removed.connect(self.__cue_removed) + Timecode.Config.changed.connect(self.__config_change) + Timecode.Config.updated.connect(self.__config_update) + def finalize(self): self.__cue_tracker.finalize() + def __config_change(self, key, value): + if key == 'protocol': + self.__cue_tracker.protocol = self.__get_protocol(value) + elif key == 'format': + self.__cue_tracker.format = value + + def __config_update(self, diff): + for key, value in diff.items(): + self.__config_change(key, value) + + def __get_protocol(self, protocol_name): + try: + return protocols.get_protocol(protocol_name)() + except Exception: + logger.error( + translate('Timecode', 'Cannot load timecode protocol: "{}"') + .format(protocol_name), + exc_info=True + ) + # Use a dummy protocol in case of failure + return TimecodeProtocol() + def __session_finalize(self): self.__cue_tracker.untrack() self.__cues.clear() diff --git a/lisp/ui/settings/app_pages/plugins_settings.py b/lisp/ui/settings/app_pages/plugins_settings.py index 4ebb183f0..f7ee832fa 100644 --- a/lisp/ui/settings/app_pages/plugins_settings.py +++ b/lisp/ui/settings/app_pages/plugins_settings.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt, QSize, QModelIndex +from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt, QSize from PyQt5.QtWidgets import QListWidget, QListWidgetItem, QTextBrowser, \ QVBoxLayout diff --git a/lisp/ui/settings/pages.py b/lisp/ui/settings/pages.py index 98bf0a151..b5040a99f 100644 --- a/lisp/ui/settings/pages.py +++ b/lisp/ui/settings/pages.py @@ -20,7 +20,7 @@ from abc import abstractmethod from PyQt5.QtCore import QModelIndex, Qt -from PyQt5.QtWidgets import QWidget, QTabWidget, QVBoxLayout, QTreeView, QHBoxLayout, QGridLayout, QSizePolicy +from PyQt5.QtWidgets import QWidget, QTabWidget, QVBoxLayout, QTreeView, QGridLayout, QSizePolicy from lisp.core.qmeta import QABCMeta from lisp.core.util import dict_merge From 38f96747a3b14624dded5c6051e327ac48e1c400 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 14 May 2018 12:01:13 +0200 Subject: [PATCH 100/333] Layout refactoring and UI changes Add: new custom context-menu classes for cues, to allow more dynamic behaviours Add: global layout settings Update: Layouts are now loaded as plugins Update: refactored parts of the CueLayout API Update: ListLayout "current-item" is now determined by the QListWidget Update: ListLayout "current-item" have now a custom appearance Update: ListLayout now use the standard selection mechanism from QListWidget Update: ListLayout auto-scroll now use "PositionAtCenter" hint Update: ListLayout have now a "Selection" mode to enable/disable cue-selection Update: CartLayout improved cue-widget layout and selection display Update: CueModel, when removing a cues, will now attempt to interrupt/stop it Update: minor theme and icons improvements Update: various minor improvements --- Pipfile.lock | 78 +-- lisp/application.py | 15 +- lisp/backend/media.py | 4 +- lisp/core/class_based_registry.py | 8 +- lisp/core/configuration.py | 6 +- lisp/core/dicttree.py | 5 +- lisp/core/fader.py | 4 +- lisp/core/has_properties.py | 4 +- lisp/core/properties.py | 2 +- lisp/core/proxy_model.py | 64 +-- lisp/core/session.py | 7 +- lisp/core/util.py | 6 +- lisp/cues/cue.py | 4 +- lisp/cues/cue_factory.py | 4 +- lisp/cues/cue_memento_model.py | 2 +- lisp/cues/cue_model.py | 14 +- lisp/default.json | 25 +- lisp/layout/__init__.py | 13 + lisp/{layouts => layout}/cue_layout.py | 199 ++++---- lisp/layout/cue_menu.py | 148 ++++++ lisp/layouts/__init__.py | 18 - lisp/layouts/cart_layout/__init__.py | 0 lisp/layouts/cart_layout/layout.py | 452 ------------------ lisp/layouts/cue_menu_registry.py | 50 -- lisp/layouts/list_layout/__init__.py | 0 lisp/layouts/list_layout/cue_list_item.py | 60 --- lisp/layouts/list_layout/cue_list_view.py | 184 ------- lisp/layouts/list_layout/layout.py | 379 --------------- lisp/plugins/action_cues/collection_cue.py | 2 +- lisp/plugins/action_cues/command_cue.py | 2 +- lisp/plugins/action_cues/index_action_cue.py | 2 +- lisp/plugins/action_cues/midi_cue.py | 5 +- lisp/plugins/action_cues/osc_cue.py | 4 +- lisp/plugins/action_cues/seek_cue.py | 2 +- lisp/plugins/action_cues/stop_all.py | 2 +- lisp/plugins/action_cues/volume_control.py | 2 +- lisp/plugins/cart_layout/__init__.py | 19 + .../cart_layout/cue_widget.py | 304 ++++++------ lisp/plugins/cart_layout/default.json | 16 + lisp/plugins/cart_layout/layout.py | 416 ++++++++++++++++ .../cart_layout/model.py} | 0 .../cart_layout/page_widget.py | 106 ++-- .../cart_layout/settings.py} | 36 +- lisp/plugins/cart_layout/tab_widget.py | 47 ++ lisp/plugins/controller/controller.py | 2 +- lisp/plugins/controller/protocols/osc.py | 4 +- .../plugins/gst_backend/elements/auto_sink.py | 2 +- .../gst_backend/elements/user_element.py | 2 +- lisp/plugins/gst_backend/gst_backend.py | 4 +- lisp/plugins/gst_backend/gst_element.py | 5 +- lisp/plugins/gst_backend/gst_settings.py | 2 +- lisp/plugins/gst_backend/gst_utils.py | 2 +- lisp/plugins/list_layout/__init__.py | 19 + .../list_layout/control_buttons.py | 0 lisp/plugins/list_layout/default.json | 16 + .../list_layout/info_panel.py | 33 +- lisp/plugins/list_layout/layout.py | 344 +++++++++++++ lisp/plugins/list_layout/list_view.py | 260 ++++++++++ .../list_layout/list_widgets.py} | 153 ++++-- .../list_layout/models.py} | 6 +- .../list_layout/playing_view.py} | 2 +- .../list_layout/playing_widgets.py} | 4 +- .../list_layout/settings.py} | 87 ++-- lisp/plugins/media_info/media_info.py | 31 +- lisp/plugins/midi/midi_common.py | 2 +- lisp/plugins/presets/presets.py | 53 +- lisp/plugins/presets/presets_ui.py | 5 +- lisp/plugins/rename_cues/rename_action.py | 1 + lisp/plugins/rename_cues/rename_cues.py | 7 +- lisp/plugins/timecode/timecode.py | 2 +- lisp/plugins/triggers/triggers.py | 2 +- lisp/plugins/triggers/triggers_handler.py | 2 +- lisp/ui/about.py | 2 +- lisp/ui/icons/lisp/led-error.svg | 132 ++++- lisp/ui/icons/lisp/led-off.svg | 132 ++++- lisp/ui/icons/lisp/led-pause.svg | 132 ++++- lisp/ui/icons/lisp/led-running.svg | 131 ++++- lisp/ui/layoutselect.py | 4 +- lisp/ui/logging/models.py | 3 +- lisp/ui/mainwindow.py | 30 +- lisp/ui/settings/app_configuration.py | 3 +- lisp/ui/settings/app_pages/app_general.py | 8 +- .../ui/settings/app_pages/cue_app_settings.py | 2 +- .../ui/settings/app_pages/layouts_settings.py | 73 +++ .../ui/settings/app_pages/plugins_settings.py | 2 +- lisp/ui/settings/cue_settings.py | 9 +- lisp/ui/settings/pages.py | 4 +- lisp/ui/settings/pages_tree_model.py | 3 +- lisp/ui/themes/dark/theme.qss | 24 +- lisp/ui/ui_utils.py | 26 +- lisp/ui/widgets/fades.py | 2 +- lisp/ui/widgets/qclicklabel.py | 2 +- lisp/ui/widgets/qcolorbutton.py | 2 +- 93 files changed, 2675 insertions(+), 1823 deletions(-) create mode 100644 lisp/layout/__init__.py rename lisp/{layouts => layout}/cue_layout.py (52%) create mode 100644 lisp/layout/cue_menu.py delete mode 100644 lisp/layouts/__init__.py delete mode 100644 lisp/layouts/cart_layout/__init__.py delete mode 100644 lisp/layouts/cart_layout/layout.py delete mode 100644 lisp/layouts/cue_menu_registry.py delete mode 100644 lisp/layouts/list_layout/__init__.py delete mode 100644 lisp/layouts/list_layout/cue_list_item.py delete mode 100644 lisp/layouts/list_layout/cue_list_view.py delete mode 100644 lisp/layouts/list_layout/layout.py create mode 100644 lisp/plugins/cart_layout/__init__.py rename lisp/{layouts => plugins}/cart_layout/cue_widget.py (52%) create mode 100644 lisp/plugins/cart_layout/default.json create mode 100644 lisp/plugins/cart_layout/layout.py rename lisp/{layouts/cart_layout/cue_cart_model.py => plugins/cart_layout/model.py} (100%) rename lisp/{layouts => plugins}/cart_layout/page_widget.py (61%) rename lisp/{layouts/cart_layout/cart_layout_settings.py => plugins/cart_layout/settings.py} (76%) create mode 100644 lisp/plugins/cart_layout/tab_widget.py create mode 100644 lisp/plugins/list_layout/__init__.py rename lisp/{layouts => plugins}/list_layout/control_buttons.py (100%) create mode 100644 lisp/plugins/list_layout/default.json rename lisp/{layouts => plugins}/list_layout/info_panel.py (72%) create mode 100644 lisp/plugins/list_layout/layout.py create mode 100644 lisp/plugins/list_layout/list_view.py rename lisp/{layouts/list_layout/listwidgets.py => plugins/list_layout/list_widgets.py} (68%) rename lisp/{layouts/list_layout/cue_list_model.py => plugins/list_layout/models.py} (96%) rename lisp/{layouts/list_layout/playing_list_widget.py => plugins/list_layout/playing_view.py} (97%) rename lisp/{layouts/list_layout/playing_mediawidget.py => plugins/list_layout/playing_widgets.py} (98%) rename lisp/{layouts/list_layout/list_layout_settings.py => plugins/list_layout/settings.py} (55%) create mode 100644 lisp/ui/settings/app_pages/layouts_settings.py diff --git a/Pipfile.lock b/Pipfile.lock index 6ea79c90b..195cdb497 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -64,9 +64,9 @@ }, "pycairo": { "hashes": [ - "sha256:5bb321e5d4f8b3a51f56fc6a35c143f1b72ce0d748b43d8b623596e8215f01f7" + "sha256:cdd4d1d357325dec3a21720b85d273408ef83da5f15c184f2eff3212ff236b9f" ], - "version": "==1.16.3" + "version": "==1.17.0" }, "pycparser": { "hashes": [ @@ -134,56 +134,56 @@ }, "sortedcontainers": { "hashes": [ - "sha256:844daced0f20d75c02ce53f373d048ea2e401ad8a7b3a4c43b2aa544b569efb3", - "sha256:fb9e22cd6ee4b459f0d7b9b4189b19631031c72ac05715563139162014c13672" + "sha256:566cf5f8dbada3aed99737a19d98f03d15d76bf2a6c27e4fb0f4a718a99be761", + "sha256:fa96e9920a37bde76bfdcaca919a125c1d2e581af1137e25de54ee0da7835282" ], "index": "pypi", - "version": "==1.5.9" + "version": "==1.5.10" } }, "develop": { "cython": { "hashes": [ - "sha256:0a44c3645a84724d4f7f7c1126f3807cad14271f210bb1a9a15bfd330c8d20b9", - "sha256:152ee5f345012ca3bb7cc71da2d3736ee20f52cd8476e4d49e5e25c5a4102b12", - "sha256:15cbde95cdf6a346c63c0b38b895aa3186d0a49adcaf42b9e19c4cb0473cb921", - "sha256:175273eb6a90af364b9b2aea74e3c3eebcde9caec02d617cd8886c0933ec3304", - "sha256:1a434e7621ca6671ce949893fef94b13a580853ba976e729f68dda5ab270ee8a", - "sha256:1da199a5be7c486ee89b4a8bda7f00f9fd98b800d9af3a1fe3fc63e68dadea85", - "sha256:26196ff8fe00e7c4c5815a310c368edfc1a1c8a3c90ac9220435d1b01e795cf7", - "sha256:2b73b062658511167dde37a51acb80ae6ddea1ffa284ebdbc47a900f21e63c70", - "sha256:2b9aa64473fefbe988e36a30915732a0e2a75ffe0f3e81a70e3f8d367c2e4119", - "sha256:32638aefc164404ac70e5f86558cd3aece34df17db16da905abf5e664073bae4", - "sha256:330c95c03e39835976d1410f5fa877b75fcc5c50dc146d4c02377fc719b1f8c9", - "sha256:38b499fa317cf6939e46317457240553b13e974f08ed387523f10af26e269f6c", - "sha256:3c60caa0075aa1f1c5e10b5352d2e8bb9a18361ce9fca50fa7d4baea67804ade", - "sha256:3fc9b945541cadb5a10316e48b5e73f4b6f635b7d30156f502b66f3766545b23", - "sha256:427299c47cfe04d97f9f09ea55570c05898a87b64caf6ddebb529b6f64e5d228", - "sha256:4e07e619af85e7c1ec2344ecab558929bb4acbca25f8f170d07dc677e8ee413f", - "sha256:5010e048fb9791522fe626bd40137b8a847ba77a6e656bb64d6d7acdc45ece32", - "sha256:70bc2806fc9e5affcf74c0d4fa12cba57ffb94cdfc28b975692c8df56ea86402", - "sha256:7cdd5303121024269610236876d9f4beb6a909a1ea5d7bc48e7bbf5d8774a3f2", - "sha256:80856aa45004514a3ff5e102bd18fbd5230d234311de1f83d4e5b97ef42f6c93", - "sha256:996ae959ab2600b8ad4c80981afc32e89b42d0abe3234c48e960e40180459cb2", - "sha256:b5c2e31be55bc61d3c758889d06b16d84f4fda944832fbed63c113ec2dbc5f97", - "sha256:c39b3a042bf5ded7c8336c82b1fa817e1f46a7ef16d41d66b3d3340e7a3b60ed", - "sha256:d08f5dd2fbf7d1506c9d986a8352b2423170002ddb635b184d2a916d2b5df0d6", - "sha256:d2b636c16931663aeb8ffb91cf871a912e66e7200755ce68aa8c502c16eb366f", - "sha256:e27e12834ac315c7d67ca697a325d42ff8395e9b82fb62c8665bb583eb0df589", - "sha256:e312dd688b97e9f97199a8e4ba18b65db2747157630761d27193a18761b23fed", - "sha256:e47bbe74d6c87fab2e22f1580aa3e4a8e7482b4265b1fc76685fc49f90e18f99", - "sha256:ea113ff58e96152738e646b8ee77b41d3994735df77bee0a26cd413a67e03c0f", - "sha256:ea22d79133583b5b0f856dbfda097363228ae0a3d77431deaba90634e5d4853a" + "sha256:03db8c1b8120039f72493b95494a595be13b01b6860cfc93e2a651a001847b3b", + "sha256:0d2ccb812d73e67557fd16e7aa7bc5bac18933c1dfe306133cd0680ccab89f33", + "sha256:24f8ea864de733f5a447896cbeec2cac212247e33272539670b9f466f43f23db", + "sha256:30a8fd029eb932a7b5a74e158316d1d069ccb67a8607aa7b6c4ed19fab7fbd4a", + "sha256:37e680901e6a4b97ab67717f9b43fc58542cd10a77431efd2d8801d21d5a37d4", + "sha256:4984e097bc9da37862d97c1f66dacf2c80fadaea488d96ba0b5ea9d84dbc7521", + "sha256:4cfda677227af41e4502e088ee9875e71922238a207d0c40785a0fb09c703c21", + "sha256:4ec60a4086a175a81b9258f810440a6dd2671aa4b419d8248546d85a7de6a93f", + "sha256:51c7d48ea4cba532d11a6d128ebbc15373013f816e5d1c3a3946650b582a30b8", + "sha256:634e2f10fc8d026c633cffacb45cd8f4582149fa68e1428124e762dbc566e68a", + "sha256:67e0359709c8addc3ecb19e1dec6d84d67647e3906da618b953001f6d4480275", + "sha256:6a93d4ba0461edc7a359241f4ebbaa8f9bc9490b3540a8dd0460bef8c2c706db", + "sha256:6ba89d56c3ee45716378cda4f0490c3abe1edf79dce8b997f31608b14748a52b", + "sha256:6ca5436d470584ba6fd399a802c9d0bcf76cf1edb0123725a4de2f0048f9fa07", + "sha256:7656895cdd59d56dd4ed326d1ee9ede727020d4a5d8778a05af2d8e25af4b13d", + "sha256:85f7432776870d65639fed00f951a3c05ef1e534bc72a73cd1200d79b9a7d7d0", + "sha256:96dd674e72281d3feed74fd5adcf0514ba02884f123cdf4fb78567e7be6b1694", + "sha256:97bf06a89bcf9e8d7633cde89274d42b3b661dc974b58fca066fad762e46b4d8", + "sha256:9a465e7296a4629139be5d2015577f2ae5e08196eb7dc4c407beea130f362dc3", + "sha256:9a60355edca1cc9006be086e2633e190542aad2bf9e46948792a48b3ae28ed97", + "sha256:9eab3696f2cb88167db109d737c787fb9dd34ca414bd1e0c424e307956e02c94", + "sha256:c3ae7d40ebceb0d944dfeeceaf1fbf17e528f5327d97b008a8623ddddd1ecee3", + "sha256:c623d19fcc60ea27882f20cf484218926ddf6f978b958dae1070600a1974f809", + "sha256:c719a6e86d7c737afcc9729994f76b284d1c512099ee803eff11c2a9e6e33a42", + "sha256:cf17af0433218a1e33dc6f3069dd9e7cd0c80fe505972c3acd548e25f67973fd", + "sha256:daf96e0d232605e979995795f62ffd24c5c6ecea4526e4cbb86d80f01da954b2", + "sha256:db40de7d03842d3c4625028a74189ade52b27f8efaeb0d2ca06474f57e0813b2", + "sha256:deea1ef59445568dd7738fa3913aea7747e4927ff4ae3c10737844b8a5dd3e22", + "sha256:e05d28b5ce1ee5939d83e50344980659688ecaed65c5e10214d817ecf5d1fe6a", + "sha256:f5f6694ce668eb7a9b59550bfe4265258809c9b0665c206b26d697df2eef2a8b" ], "index": "pypi", - "version": "==0.28.1" + "version": "==0.28.2" }, "graphviz": { "hashes": [ - "sha256:4e23ab07ea37a5bd16cf96d3c31e63d792cc05f9b2c277d553838477697feb1f", - "sha256:606741c028acc54b1a065b33045f8c89ee0927ea77273ec409ac988f2c3d1091" + "sha256:c61de040e5354c088d2785ea447dd8c26fc572dcc389e4e23e2b46947808d43e", + "sha256:e1e839596d5ada2b7b5a80e8dffef343cd9f242e95c2387f765755795430ef3c" ], - "version": "==0.8.2" + "version": "==0.8.3" }, "objgraph": { "hashes": [ diff --git a/lisp/application.py b/lisp/application.py index 53b9a61f6..0f5aed932 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -23,7 +23,7 @@ from PyQt5.QtWidgets import QDialog, qApp -from lisp import layouts +from lisp import layout from lisp.core.actions_handler import MainActionsHandler from lisp.core.session import Session from lisp.core.signal import Signal @@ -36,6 +36,7 @@ from lisp.ui.layoutselect import LayoutSelect from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.app_configuration import AppConfigurationDialog +from lisp.ui.settings.app_pages.layouts_settings import LayoutsSettings from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.app_pages.app_general import AppGeneral from lisp.ui.settings.app_pages.cue_app_settings import CueAppSettings @@ -63,13 +64,15 @@ def __init__(self, app_conf): 'general', AppGeneral, self.conf) AppConfigurationDialog.registerSettingsPage( 'general.cue', CueAppSettings, self.conf) + AppConfigurationDialog.registerSettingsPage( + 'layouts', LayoutsSettings, self.conf) AppConfigurationDialog.registerSettingsPage( 'plugins', PluginsSettings, self.conf) # Register common cue-settings widgets - CueSettingsRegistry().add_item(CueGeneralSettings, Cue) - CueSettingsRegistry().add_item(MediaCueSettings, MediaCue) - CueSettingsRegistry().add_item(Appearance) + CueSettingsRegistry().add(CueGeneralSettings, Cue) + CueSettingsRegistry().add(MediaCueSettings, MediaCue) + CueSettingsRegistry().add(Appearance) # Connect mainWindow actions self.__main_window.new_session.connect(self._new_session_dialog) @@ -105,7 +108,7 @@ def start(self, session_file=''): layout = self.conf.get('layout.default', 'nodefault') if layout.lower() != 'nodefault': - self._new_session(layouts.get_layout(layout)) + self._new_session(layout.get_layout(layout)) else: self._new_session_dialog() @@ -191,7 +194,7 @@ def _load_from_file(self, session_file): # New session self._new_session( - layouts.get_layout(session_dict['session']['layout_type'])) + layout.get_layout(session_dict['session']['layout_type'])) self.__session.update_properties(session_dict['session']) self.__session.session_file = session_file diff --git a/lisp/backend/media.py b/lisp/backend/media.py index 9af8e464c..39574d64a 100644 --- a/lisp/backend/media.py +++ b/lisp/backend/media.py @@ -94,8 +94,8 @@ def current_time(self): @abstractmethod def element(self, class_name): """ - :param name: The element class-name - :type name: str + :param class_name: The element class-name + :type class_name: str :return: The element with the specified class-name or None :rtype: lisp.core.base.media_element.MediaElement diff --git a/lisp/core/class_based_registry.py b/lisp/core/class_based_registry.py index 779109c4a..cbd315226 100644 --- a/lisp/core/class_based_registry.py +++ b/lisp/core/class_based_registry.py @@ -27,8 +27,8 @@ class ClassBasedRegistry: .. highlight:: reg = ClassBasedRegistry() - reg.add_item('Object-Item', object) - reg.add_item('List-Item', list) + reg.add('Object-Item', object) + reg.add('List-Item', list) list(reg.filter(object)) # ['Object-Item', 'List-Item'] list(reg.filter(list)) # ['List-Item'] @@ -37,14 +37,14 @@ class ClassBasedRegistry: def __init__(self): self._registry = {} - def add_item(self, item, ref_class=object): + def add(self, item, ref_class=object): """Register a item for ref_class.""" if ref_class not in self._registry: self._registry[ref_class] = [item] elif item not in self._registry[ref_class]: self._registry[ref_class].append(item) - def remove_item(self, item): + def remove(self, item): """Remove all the occurrences of the given item.""" for ref_class in self._registry: try: diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index f2df18845..10d43c74f 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -30,7 +30,7 @@ from lisp import DEFAULT_APP_CONFIG, USER_APP_CONFIG from lisp.core.signal import Signal from lisp.core.singleton import ABCSingleton -from lisp.core.util import dict_merge, dict_merge_diff +from lisp.core.util import dict_merge, dict_merge_diff, typename logger = logging.getLogger(__name__) @@ -50,7 +50,7 @@ def __init__(self, root=None, sep='.'): if not isinstance(sep, str): raise TypeError( 'ConfDict separator must be a str, not {}'.format( - type(sep).__name__) + typename(sep)) ) self._sep = sep @@ -62,7 +62,7 @@ def __init__(self, root=None, sep='.'): else: raise TypeError( 'ConfDict root must be a dict, not {}'.format( - type(root).__name__) + typename(root)) ) def get(self, path, default=_UNSET): diff --git a/lisp/core/dicttree.py b/lisp/core/dicttree.py index 6d95cd88b..0f4a0815e 100644 --- a/lisp/core/dicttree.py +++ b/lisp/core/dicttree.py @@ -19,6 +19,7 @@ # Used to indicate the default behaviour when a specific option is not found to # raise an exception. Created to enable `None` as a valid fallback value. +from lisp.core.util import typename _UNSET = object() @@ -45,12 +46,12 @@ def add_child(self, node, name): if not isinstance(node, DictNode): raise TypeError( 'DictNode children must be a DictNode, not {}'.format( - type(node).__name__) + typename(node)) ) if not isinstance(name, str): raise TypeError( 'DictNode name must be a str, not {}'.format( - type(node).__name__) + typename(node)) ) if self.Sep in name: raise DictTreeError( diff --git a/lisp/core/fader.py b/lisp/core/fader.py index 39e7b40a5..c46a084c6 100644 --- a/lisp/core/fader.py +++ b/lisp/core/fader.py @@ -21,7 +21,7 @@ from lisp.core.decorators import locked_method from lisp.core.fade_functions import ntime, FadeInType, FadeOutType -from lisp.core.util import rsetattr, rgetattr +from lisp.core.util import rsetattr, rgetattr, typename class Fader: @@ -100,7 +100,7 @@ def fade(self, duration, to_value, fade_type): if not isinstance(fade_type, (FadeInType, FadeOutType)): raise AttributeError( 'fade_type must be one of FadeInType or FadeOutType members,' - 'not {}'.format(fade_type.__class__.__name__)) + 'not {}'.format(typename(fade_type))) try: self._time = 0 diff --git a/lisp/core/has_properties.py b/lisp/core/has_properties.py index 7c55d1606..22f36ab7f 100644 --- a/lisp/core/has_properties.py +++ b/lisp/core/has_properties.py @@ -21,6 +21,7 @@ from lisp.core.properties import Property, InstanceProperty from lisp.core.signal import Signal +from lisp.core.util import typename class HasPropertiesMeta(ABCMeta): @@ -243,8 +244,7 @@ def _pro(self, name): # TODO: PropertyError ?? raise AttributeError( - "'{}' object has no property '{}'".format( - type(self).__name__, name) + "'{}' object has no property '{}'".format(typename(self), name) ) diff --git a/lisp/core/properties.py b/lisp/core/properties.py index eb162b06a..a6c169621 100644 --- a/lisp/core/properties.py +++ b/lisp/core/properties.py @@ -88,4 +88,4 @@ def __pget__(self): return self.value def __pset__(self, value): - self.value = value \ No newline at end of file + self.value = value diff --git a/lisp/core/proxy_model.py b/lisp/core/proxy_model.py index ae10425e8..0a90038c9 100644 --- a/lisp/core/proxy_model.py +++ b/lisp/core/proxy_model.py @@ -22,41 +22,24 @@ from lisp.core.model import Model, ModelException -class ProxyModel(Model): - """Proxy that wrap a more generic model to extend its functionality. - - The add, remove, __iter__, __len__ and __contains__ default implementations - use the the model ones. - - .. note: - The base model cannot be changed. - Any ProxyModel could provide it's own methods/signals. - """ - +class ABCProxyModel(Model): def __init__(self, model): super().__init__() if not isinstance(model, Model): - raise TypeError('ProxyModel model must be a Model object, not {0}' - .format(model.__class__.__name__)) - - self.__model = model - self.__model.item_added.connect(self._item_added) - self.__model.item_removed.connect(self._item_removed) - self.__model.model_reset.connect(self._model_reset) - - def add(self, item): - self.__model.add(item) + raise TypeError( + 'ProxyModel model must be a Model object, not {0}'.format( + typename(model)) + ) - def remove(self, item): - self.__model.remove(item) - - def reset(self): - self.__model.reset() + self._model = model + self._model.item_added.connect(self._item_added) + self._model.item_removed.connect(self._item_removed) + self._model.model_reset.connect(self._model_reset) @property def model(self): - return self.__model + return self._model @abstractmethod def _model_reset(self): @@ -70,14 +53,35 @@ def _item_added(self, item): def _item_removed(self, item): pass + +class ProxyModel(ABCProxyModel): + """Proxy that wrap a more generic model to extend its functionality. + + The add, remove, __iter__, __len__ and __contains__ default implementations + use the the model ones. + + .. note: + The base model cannot be changed. + Any ProxyModel could provide it's own methods/signals. + """ + + def add(self, item): + self._model.add(item) + + def remove(self, item): + self._model.remove(item) + + def reset(self): + self._model.reset() + def __iter__(self): - return self.__model.__iter__() + return self._model.__iter__() def __len__(self): - return len(self.__model) + return len(self._model) def __contains__(self, item): - return item in self.__model + return item in self._model class ReadOnlyProxyModel(ProxyModel): diff --git a/lisp/core/session.py b/lisp/core/session.py index 53e411a15..463bb3aea 100644 --- a/lisp/core/session.py +++ b/lisp/core/session.py @@ -20,9 +20,8 @@ import os from lisp.core.has_properties import Property, HasInstanceProperties -from lisp.core.memento_model import MementoModelAdapter from lisp.core.signal import Signal -from lisp.cues.cue_memento_model import CueMementoAdapter +from lisp.core.util import typename class Session(HasInstanceProperties): @@ -33,11 +32,11 @@ def __init__(self, layout): super().__init__() self.finalized = Signal() - self.layout_type = type(layout).__name__ + self.layout_type = typename(layout) self.__layout = layout self.__cue_model = layout.cue_model - self.__memento_model = CueMementoAdapter(layout.model_adapter) + #self.__memento_model = CueMementoAdapter(layout.model_adapter) @property def cue_model(self): diff --git a/lisp/core/util.py b/lisp/core/util.py index a89dba85b..03fa3b5de 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -123,6 +123,10 @@ def greatest_common_superclass(instances): return x +def typename(object): + return object.__class__.__name__ + + def get_lan_ip(): """Return active interface LAN IP, or localhost if no address is assigned. @@ -133,7 +137,7 @@ def get_lan_ip(): # Doesn't have to be reachable s.connect(('10.10.10.10', 0)) ip = s.getsockname()[0] - except: + except OSError: ip = '127.0.0.1' finally: s.close() diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 77de6fad5..7afba4239 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -27,7 +27,7 @@ from lisp.core.properties import Property, WriteOnceProperty from lisp.core.rwait import RWait from lisp.core.signal import Signal -from lisp.core.util import EqEnum +from lisp.core.util import EqEnum, typename class CueState: @@ -131,7 +131,7 @@ class Cue(HasProperties): def __init__(self, id=None): super().__init__() self.id = str(uuid4()) if id is None else id - self._type_ = self.__class__.__name__ + self._type_ = typename(self) self._st_lock = Lock() self._state = CueState.Stop diff --git a/lisp/cues/cue_factory.py b/lisp/cues/cue_factory.py index 151170177..d0b015342 100644 --- a/lisp/cues/cue_factory.py +++ b/lisp/cues/cue_factory.py @@ -19,6 +19,8 @@ from copy import deepcopy +from lisp.core.util import typename + class CueFactory: """Provide a generic factory to build different cues types. @@ -89,7 +91,7 @@ def clone_cue(cls, cue): properties = deepcopy(cue.properties()) properties.pop('id') - cue = cls.create_cue(cue.__class__.__name__) + cue = cls.create_cue(typename(cue)) cue.update_properties(properties) return cue diff --git a/lisp/cues/cue_memento_model.py b/lisp/cues/cue_memento_model.py index 0585e6b1d..da7b39710 100644 --- a/lisp/cues/cue_memento_model.py +++ b/lisp/cues/cue_memento_model.py @@ -45,4 +45,4 @@ def log(self): class CueMoveAction(MoveItemAction): def log(self): return 'Move cue from "{}" to "{}"'.format( - self._old_index, self._new_index) + self._old_index + 1, self._new_index + 1) diff --git a/lisp/cues/cue_model.py b/lisp/cues/cue_model.py index 0bb32153b..521ab7f2d 100644 --- a/lisp/cues/cue_model.py +++ b/lisp/cues/cue_model.py @@ -18,7 +18,7 @@ # along with Linux Show Player. If not, see . from lisp.core.model import Model -from lisp.cues.cue import Cue +from lisp.cues.cue import Cue, CueAction class CueModel(Model): @@ -43,12 +43,24 @@ def remove(self, cue): self.pop(cue.id) def pop(self, cue_id): + """:rtype: Cue""" cue = self.__cues.pop(cue_id) + + # Try to interrupt/stop the cue + if CueAction.Interrupt in cue.CueActions: + cue.interrupt() + elif CueAction.Stop in cue.CueActions: + cue.stop() + self.item_removed.emit(cue) return cue def get(self, cue_id, default=None): + """Return the cue with the given id, or the default value. + + :rtype: Cue + """ return self.__cues.get(cue_id, default) def items(self): diff --git a/lisp/default.json b/lisp/default.json index 08abd34ac..1b4e4f752 100644 --- a/lisp/default.json +++ b/lisp/default.json @@ -1,5 +1,5 @@ { - "_version_": "0.6dev.11", + "_version_": "0.6dev.12", "cue": { "fadeAction": 3, "fadeActionType": "Linear", @@ -46,28 +46,5 @@ }, "actions": { "maxStackSize": 0 - }, - "cartLayout": { - "gridColumns": 7, - "gridRows": 4, - "showSeek": false, - "showDbMeters": false, - "showAccurate": false, - "showVolume": false, - "countdown": true, - "autoAddPage": true - }, - "listLayout": { - "showDbMeters": true, - "showSeek": true, - "showAccurate": false, - "showPlaying": true, - "autoContinue": true, - "endList": "Stop", - "goKey": "Space", - "stopCueFade": true, - "pauseCueFade": true, - "resumeCueFade": true, - "interruptCueFade": true } } diff --git a/lisp/layout/__init__.py b/lisp/layout/__init__.py new file mode 100644 index 000000000..022692bd6 --- /dev/null +++ b/lisp/layout/__init__.py @@ -0,0 +1,13 @@ +__LAYOUTS__ = {} + + +def get_layouts(): + return list(__LAYOUTS__.values()) + + +def get_layout(class_name): + return __LAYOUTS__[class_name] + + +def register_layout(layout): + __LAYOUTS__[layout.__name__] = layout diff --git a/lisp/layouts/cue_layout.py b/lisp/layout/cue_layout.py similarity index 52% rename from lisp/layouts/cue_layout.py rename to lisp/layout/cue_layout.py index 0a411bb30..05cab5a6c 100644 --- a/lisp/layouts/cue_layout.py +++ b/lisp/layout/cue_layout.py @@ -19,20 +19,21 @@ from abc import abstractmethod -from PyQt5.QtWidgets import QAction, QMenu, qApp - from lisp.core.actions_handler import MainActionsHandler +from lisp.core.qmeta import QABCMeta from lisp.core.signal import Signal from lisp.core.util import greatest_common_superclass from lisp.cues.cue import Cue, CueAction from lisp.cues.cue_actions import UpdateCueAction, UpdateCuesAction -from lisp.layouts.cue_menu_registry import CueMenuRegistry +from lisp.layout.cue_menu import CueContextMenu from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.cue_settings import CueSettingsDialog +from lisp.ui.ui_utils import adjust_widget_position -# TODO: split widget and "controller" -class CueLayout: +# TODO: split "view" and "controller" +# TODO: make an HasProperties (will go into Session objects) +class CueLayout(metaclass=QABCMeta): # Layout name NAME = 'Base' # Layout short description @@ -40,7 +41,7 @@ class CueLayout: # Layout details (some useful info) DETAILS = '' - cm_registry = CueMenuRegistry() + CuesMenu = CueContextMenu() def __init__(self, application=None): """ @@ -49,40 +50,77 @@ def __init__(self, application=None): self.app = application self.cue_executed = Signal() # After a cue is executed - self.focus_changed = Signal() # After the focused cue is changed self.key_pressed = Signal() # After a key is pressed @property def cue_model(self): - """:rtype: lisp.core.cue_model.CueModel""" + """:rtype: lisp.cues.cue_model.CueModel""" return self.app.cue_model - @property @abstractmethod - def model_adapter(self): - """:rtype: lisp.core.model_adapter.ModelAdapter""" + def view(self): + """:rtype: PyQt5.QtWidgets.QWidget""" - def current_cue(self): - """Return the current cue, or None. + @abstractmethod + def cues(self, cue_type=Cue): + """Iterator over the cues, ordered (by index) and filtered by type. - :rtype: lisp.cues.cue.Cue + :param cue_type: the "minimum" type of the cue + :type cue_type: type + + :rtype: typing.Iterable[Cue] + """ + + @abstractmethod + def cue_at(self, index): + """Return the cue at the given index, or raise IndexError + + :rtype: Cue """ - if self.model_adapter: - try: - return self.model_adapter.item(self.current_index()) - except IndexError: - pass - - def current_index(self): - """Return the current index, or -1.""" + + def standby_cue(self): + """Return the standby cue, or None. + + :rtype: Cue + """ + try: + return self.cue_at(self.standby_index()) + except IndexError: + return None + + def standby_index(self): + """Return the standby index, or -1.""" return -1 - def set_current_cue(self, cue): - """Set the current cue.""" - self.set_current_index(cue.index) + def set_standby_index(self, index): + """Set the current index""" + + @abstractmethod + def selected_cues(self, cue_type=Cue): + """Iterate the selected cues, filtered by type. + + :param cue_type: the "minimum" type of the cue + :type cue_type: type + :rtype: typing.Iterable + """ + + @abstractmethod + def invert_selection(self): + """Invert selection""" + + @abstractmethod + def select_all(self, cue_type=Cue): + """Select all the cues of type `cue_type` + + :param cue_type: the "minimum" type of the cue of the item to select. + """ + + @abstractmethod + def deselect_all(self, cue_type=Cue): + """Deselect all the cues of type `cue_type` - def set_current_index(self, index): - """Set the current cue by index""" + :param cue_type: the "minimum" type of the cue of the item to deselect. + """ def go(self, action=CueAction.Default, advance=1): """Execute the current cue and go ahead. @@ -95,116 +133,87 @@ def go(self, action=CueAction.Default, advance=1): :rtype: lisp.cues.cue.Cue """ + def execute_all(self, action): + """Execute the given action on all the layout cues + + :param action: The action to be executed + :type action: CueAction + """ + for cue in self.cues(): + cue.execute(action) + + # TODO: replace usage with execute_all(action) def stop_all(self): fade = self.app.conf.get('layout.stopAllFade', False) - for cue in self.model_adapter: + for cue in self.cues(): cue.stop(fade=fade) def interrupt_all(self): fade = self.app.conf.get('layout.interruptAllFade', False) - for cue in self.model_adapter: + for cue in self.cues(): cue.interrupt(fade=fade) def pause_all(self): fade = self.app.conf.get('layout.pauseAllFade', False) - for cue in self.model_adapter: + for cue in self.cues(): cue.pause(fade=fade) def resume_all(self): fade = self.app.conf.get('layout.resumeAllFade', True) - for cue in self.model_adapter: + for cue in self.cues(): cue.resume(fade=fade) def fadein_all(self): - for cue in self.model_adapter: + for cue in self.cues(): cue.execute(CueAction.FadeIn) def fadeout_all(self): - for cue in self.model_adapter: + for cue in self.cues(): cue.execute(CueAction.FadeOut) - def edit_cue(self, cue): - edit_ui = CueSettingsDialog(cue, parent=MainWindow()) + @staticmethod + def edit_cue(cue): + dialog = CueSettingsDialog(cue, parent=MainWindow()) def on_apply(settings): action = UpdateCueAction(settings, cue) MainActionsHandler.do_action(action) - edit_ui.onApply.connect(on_apply) - edit_ui.exec_() - - def edit_selected_cues(self): - cues = self.get_selected_cues() + dialog.onApply.connect(on_apply) + dialog.exec_() + @staticmethod + def edit_cues(cues): if cues: # Use the greatest common superclass between the selected cues - edit_ui = CueSettingsDialog(greatest_common_superclass(cues)) + dialog = CueSettingsDialog(greatest_common_superclass(cues)) def on_apply(settings): action = UpdateCuesAction(settings, cues) MainActionsHandler.do_action(action) - edit_ui.onApply.connect(on_apply) - edit_ui.exec_() - - @abstractmethod - def get_context_cue(self): - """Return the last cue in the context-menu scope, or None""" - return None - - @abstractmethod - def get_selected_cues(self, cue_class=Cue): - """Return a list of all selected cues, filtered by cue_class""" - return [] - - @abstractmethod - def invert_selection(self): - """Invert selection""" - - @abstractmethod - def select_all(self, cue_class=Cue): - """Select all the cues""" - - @abstractmethod - def deselect_all(self, cue_class=Cue): - """Deselect all the cues""" + dialog.onApply.connect(on_apply) + dialog.exec_() @staticmethod def show_context_menu(position): - menu_edit = MainWindow().menuEdit - menu_edit.move(position) - menu_edit.show() - - # Adjust the menu position - desktop = qApp.desktop().availableGeometry() - menu_rect = menu_edit.geometry() - - if menu_rect.bottom() > desktop.bottom(): - menu_edit.move(menu_edit.x(), menu_edit.y() - menu_edit.height()) - if menu_rect.right() > desktop.right(): - menu_edit.move(menu_edit.x() - menu_edit.width(), menu_edit.y()) - - def show_cue_context_menu(self, position): - menu = QMenu(self) - - cue_class = self.get_context_cue().__class__ - for item in self.cm_registry.filter(cue_class): - if isinstance(item, QAction): - menu.addAction(item) - elif isinstance(item, QMenu): - menu.addMenu(item) - + menu = MainWindow().menuEdit menu.move(position) menu.show() - # Adjust the menu position - desktop = qApp.desktop().availableGeometry() - menu_rect = menu.geometry() + adjust_widget_position(menu) - if menu_rect.bottom() > desktop.bottom(): - menu.move(menu.x(), menu.y() - menu.height()) - if menu_rect.right() > desktop.right(): - menu.move(menu.x() - menu.width(), menu.y()) + def show_cue_context_menu(self, cues, position): + if cues: + menu = self.CuesMenu.create_qmenu(cues, self.view()) + menu.move(position) + menu.show() + + adjust_widget_position(menu) def finalize(self): """Destroy all the layout elements""" + + def _remove_cues(self, cues): + for cue in cues: + self.cue_model.remove(cue) diff --git a/lisp/layout/cue_menu.py b/lisp/layout/cue_menu.py new file mode 100644 index 000000000..9dd57c72b --- /dev/null +++ b/lisp/layout/cue_menu.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from functools import partial + +from PyQt5.QtWidgets import QAction, QMenu + +from lisp.core.class_based_registry import ClassBasedRegistry +from lisp.core.util import greatest_common_superclass +from lisp.cues.cue import Cue + +MENU_PRIORITY_CUE = 0 +MENU_PRIORITY_LAYOUT = 100 +MENU_PRIORITY_PLUGIN = 1000 +MENU_PRIORITY_NONE = float('inf') + + +class MenuAction: + def __init__(self, priority=MENU_PRIORITY_NONE): + self.priority = priority + + def show(self, items): + return False + + def text(self, items): + pass + + def action(self, items): + pass + + +class SimpleMenuAction(MenuAction): + def __init__(self, s_text, s_action, m_text=None, m_action=None, **kwargs): + super().__init__(**kwargs) + self._s_text = s_text + self._s_action = s_action + self._m_text = m_text + self._m_action = m_action + + def show(self, cues): + return not (len(cues) > 1 and (self._m_text is None or + self._m_action is None)) + + def text(self, cues): + if len(cues) == 1: + return self._s_text + elif len(cues) > 1: + return self._m_text + + def action(self, cues): + if len(cues) == 1: + return self._s_action(cues[0]) + elif len(cues) > 1: + return self._m_action(cues) + + +class MenuActionsGroup: + def __init__(self, separators=True, submenu=False, text='', + priority=MENU_PRIORITY_NONE): + """ + :param separators: if True a separator is put before and after the group + :param submenu: if True the actions are put in a submenu + :param text: the text to use when `submenu` is True + :param priority: the group priority, same as `MenuAction` + """ + + self.submenu = submenu + self.separators = separators + self.priority = priority + self.text = text + + self.actions = [] + + def add(self, *actions): + self.actions.extend(actions) + + def remove(self, action): + self.actions.remove(action) + + +def create_qmenu(actions, cues, menu): + for action in sorted(actions, key=lambda a: a.priority): + if isinstance(action, MenuActionsGroup): + if action.separators: + menu.addSeparator() + + if action.submenu: + menu.addMenu(create_qmenu( + action.actions, + cues, + QMenu(action.text, parent=menu) + )) + else: + create_qmenu(action.actions, cues, menu) + + if action.separators: + menu.addSeparator() + + elif action.show(cues): + qaction = QAction(action.text(cues), parent=menu) + qaction.triggered.connect(partial(action.action, cues)) + menu.addAction(qaction) + + return menu + + +class CueContextMenu(ClassBasedRegistry): + + def add(self, item, ref_class=Cue): + """ + :type item: typing.Union[MenuAction, MenuActionGroup] + """ + if not isinstance(item, (MenuAction, MenuActionsGroup)): + raise TypeError() + + if not issubclass(ref_class, Cue): + raise TypeError( + 'ref_class must be Cue or a subclass, not {0}'.format( + ref_class.__name__) + ) + + super().add(item, ref_class) + + def filter(self, ref_class=Cue): + return super().filter(ref_class) + + def clear_class(self, ref_class=Cue): + return super().filter(ref_class) + + def create_qmenu(self, cues, parent): + ref_class = greatest_common_superclass(cues) + return create_qmenu(self.filter(ref_class), cues, QMenu(parent)) diff --git a/lisp/layouts/__init__.py b/lisp/layouts/__init__.py deleted file mode 100644 index b644bc998..000000000 --- a/lisp/layouts/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -from collections import OrderedDict - -from lisp.layouts.cart_layout.layout import CartLayout -from lisp.layouts.list_layout.layout import ListLayout - - -__LAYOUTS__ = OrderedDict({ - CartLayout.__name__: CartLayout, - ListLayout.__name__: ListLayout -}) - - -def get_layouts(): - return list(__LAYOUTS__.values()) - - -def get_layout(class_name): - return __LAYOUTS__[class_name] diff --git a/lisp/layouts/cart_layout/__init__.py b/lisp/layouts/cart_layout/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/lisp/layouts/cart_layout/layout.py b/lisp/layouts/cart_layout/layout.py deleted file mode 100644 index 5cb9b632b..000000000 --- a/lisp/layouts/cart_layout/layout.py +++ /dev/null @@ -1,452 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QTabWidget, QAction, QInputDialog, qApp, \ - QMessageBox - -from lisp.core.signal import Connection -from lisp.cues.cue import Cue -from lisp.cues.cue_factory import CueFactory -from lisp.cues.media_cue import MediaCue -from lisp.layouts.cart_layout.cart_layout_settings import CartLayoutSettings -from lisp.layouts.cart_layout.cue_cart_model import CueCartModel -from lisp.layouts.cart_layout.cue_widget import CueWidget -from lisp.layouts.cart_layout.page_widget import PageWidget -from lisp.layouts.cue_layout import CueLayout -from lisp.ui.mainwindow import MainWindow -from lisp.ui.settings.app_configuration import AppConfigurationDialog -from lisp.ui.ui_utils import translate - - -class CartLayout(QTabWidget, CueLayout): - NAME = 'Cart Layout' - DESCRIPTION = translate('LayoutDescription', - 'Organize cues in grid like pages') - DETAILS = [ - QT_TRANSLATE_NOOP('LayoutDetails', 'Click a cue to run it'), - QT_TRANSLATE_NOOP('LayoutDetails', 'SHIFT + Click to edit a cue'), - QT_TRANSLATE_NOOP('LayoutDetails', 'CTRL + Click to select a cue'), - QT_TRANSLATE_NOOP('LayoutDetails', - 'To copy cues drag them while pressing CTRL'), - QT_TRANSLATE_NOOP('LayoutDetails', - 'To copy cues drag them while pressing SHIFT') - ] - - def __init__(self, **kwargs): - super().__init__(**kwargs) - AppConfigurationDialog.registerSettingsPage( - 'cart_layout', CartLayoutSettings, self.app.conf) - - self.tabBar().setObjectName('CartTabBar') - self.setFocusPolicy(Qt.StrongFocus) - - self.__columns = self.app.conf['cartLayout.gridColumns'] - self.__rows = self.app.conf['cartLayout.gridRows'] - self.__pages = [] - self.__context_widget = None - - self._show_seek = self.app.conf['cartLayout.showSeek'] - self._show_dbmeter = self.app.conf['cartLayout.showDbMeters'] - self._show_volume = self.app.conf['cartLayout.showVolume'] - self._accurate_timing = self.app.conf['cartLayout.showAccurate'] - self._countdown_mode = self.app.conf['cartLayout.countdown'] - self._auto_add_page = self.app.conf['cartLayout.autoAddPage'] - - self._model_adapter = CueCartModel( - self.cue_model, self.__rows, self.__columns) - self._model_adapter.item_added.connect( - self.__cue_added, Connection.QtQueued) - self._model_adapter.item_removed.connect( - self.__cue_removed, Connection.QtQueued) - self._model_adapter.item_moved.connect( - self.__cue_moved, Connection.QtQueued) - self._model_adapter.model_reset.connect( - self.__model_reset) - - # Add layout-specific menus - self.new_page_action = QAction(self) - self.new_page_action.triggered.connect(self.add_page) - self.new_pages_action = QAction(self) - self.new_pages_action.triggered.connect(self.add_pages) - self.rm_current_page_action = QAction(self) - self.rm_current_page_action.triggered.connect(self.remove_current_page) - - self.countdown_mode = QAction(self) - self.countdown_mode.setCheckable(True) - self.countdown_mode.setChecked(self._countdown_mode) - self.countdown_mode.triggered.connect(self.set_countdown_mode) - - self.show_seek_action = QAction(self) - self.show_seek_action.setCheckable(True) - self.show_seek_action.setChecked(self._show_seek) - self.show_seek_action.triggered.connect(self.set_seek_visible) - - self.show_dbmeter_action = QAction(self) - self.show_dbmeter_action.setCheckable(True) - self.show_dbmeter_action.setChecked(self._show_dbmeter) - self.show_dbmeter_action.triggered.connect(self.set_dbmeter_visible) - - self.show_volume_action = QAction(self) - self.show_volume_action.setCheckable(True) - self.show_volume_action.setChecked(self._show_volume) - self.show_volume_action.triggered.connect(self.set_volume_visible) - - self.show_accurate_action = QAction(self) - self.show_accurate_action.setCheckable(True) - self.show_accurate_action.setChecked(self._accurate_timing) - self.show_accurate_action.triggered.connect(self.set_accurate) - - layoutMenu = MainWindow().menuLayout - layoutMenu.addAction(self.new_page_action) - layoutMenu.addAction(self.new_pages_action) - layoutMenu.addAction(self.rm_current_page_action) - layoutMenu.addSeparator() - layoutMenu.addAction(self.countdown_mode) - layoutMenu.addAction(self.show_seek_action) - layoutMenu.addAction(self.show_dbmeter_action) - layoutMenu.addAction(self.show_volume_action) - layoutMenu.addAction(self.show_accurate_action) - - # Cue(s) context-menu actions - self.edit_action = QAction(self) - self.edit_action.triggered.connect(self._edit_cue_action) - self.cm_registry.add_item(self.edit_action) - - self.sep1 = self.cm_registry.add_separator() - - self.remove_action = QAction(self) - self.remove_action.triggered.connect(self._remove_cue_action) - self.cm_registry.add_item(self.remove_action) - - self.select_action = QAction(self) - self.select_action.triggered.connect(self.select_context_cue) - self.cm_registry.add_item(self.select_action) - - self.sep2 = self.cm_registry.add_separator(MediaCue) - - # MediaCue(s) context-menu actions - self.play_action = QAction(self) - self.play_action.triggered.connect(self._play_context_cue) - self.cm_registry.add_item(self.play_action, MediaCue) - - self.pause_action = QAction(self) - self.pause_action.triggered.connect(self._pause_context_cue) - self.cm_registry.add_item(self.pause_action, MediaCue) - - self.stop_action = QAction(self) - self.stop_action.triggered.connect(self._stop_context_cue) - self.cm_registry.add_item(self.stop_action, MediaCue) - - self.reset_volume_action = QAction(self) - self.reset_volume_action.triggered.connect(self._reset_cue_volume) - self.cm_registry.add_item(self.reset_volume_action, MediaCue) - - self.setAcceptDrops(True) - self.retranslateUi() - - self.add_page() - - def retranslateUi(self): - self.new_page_action.setText(translate('CartLayout', 'Add page')) - self.new_pages_action.setText(translate('CartLayout', 'Add pages')) - self.rm_current_page_action.setText( - translate('CartLayout', 'Remove current page')) - self.countdown_mode.setText(translate('CartLayout', 'Countdown mode')) - self.show_seek_action.setText(translate('CartLayout', 'Show seek-bars')) - self.show_dbmeter_action.setText( - translate('CartLayout', 'Show dB-meters')) - self.show_volume_action.setText(translate('CartLayout', 'Show volume')) - self.show_accurate_action.setText( - translate('CartLayout', 'Show accurate time')) - - self.edit_action.setText(translate('CartLayout', 'Edit cue')) - self.remove_action.setText(translate('CartLayout', 'Remove')) - self.select_action.setText(translate('CartLayout', 'Select')) - self.play_action.setText(translate('CartLayout', 'Play')) - self.pause_action.setText(translate('CartLayout', 'Pause')) - self.stop_action.setText(translate('CartLayout', 'Stop')) - self.reset_volume_action.setText(translate('CartLayout', 'Reset volume')) - - @CueLayout.model_adapter.getter - def model_adapter(self): - return self._model_adapter - - def add_pages(self): - pages, accepted = QInputDialog.getInt( - self, - translate('CartLayout', 'Add pages'), - translate('CartLayout', 'Number of Pages:'), - value=1, min=1, max=10) - - if accepted: - for _ in range(pages): - self.add_page() - - def add_page(self): - page = PageWidget(self.__rows, self.__columns, self) - page.move_drop_event.connect(self._move_widget) - page.copy_drop_event.connect(self._copy_widget) - - self.addTab(page, 'Page ' + str(self.count() + 1)) - self.__pages.append(page) - - def select_context_cue(self): - self.__context_widget.selected = not self.__context_widget.selected - - def select_all(self, cue_class=Cue): - for widget in self.widgets(): - if isinstance(widget.cue, cue_class): - widget.selected = True - - def deselect_all(self, cue_class=Cue): - for widget in self.widgets(): - if isinstance(widget.cue, cue_class): - widget.selected = False - - def invert_selection(self): - for widget in self.widgets(): - widget.selected = not widget.selected - - def contextMenuEvent(self, event): - # For some reason the currentWidget geometry does not include the - # vertical offset created by the tabBar. - geometry = self.currentWidget().geometry().translated(0, self.tabBar().geometry().height()) - if geometry.contains(event.pos()): - self.show_context_menu(event.globalPos()) - - def dragEnterEvent(self, event): - if qApp.keyboardModifiers() == Qt.ControlModifier: - event.setDropAction(Qt.MoveAction) - event.accept() - elif qApp.keyboardModifiers() == Qt.ShiftModifier: - event.setDropAction(Qt.MoveAction) - event.accept() - else: - event.ignore() - - def dragMoveEvent(self, event): - if self.tabBar().contentsRect().contains(event.pos()): - self.setCurrentIndex(self.tabBar().tabAt(event.pos())) - event.accept() - - def dropEvent(self, e): - e.ignore() - - def keyPressEvent(self, event): - self.key_pressed.emit(event) - event.ignore() - - def get_context_cue(self): - if self.__context_widget is not None: - return self.__context_widget.cue - - def get_selected_cues(self, cue_class=Cue): - # w -> widget - cues = [] - for widget in self.widgets(): - if widget.selected: - cues.append(widget.cue) - return cues - - def remove_current_page(self): - if self.__pages: - confirm = QMessageBox() - confirm.setIcon(confirm.Question) - confirm.setWindowTitle(translate('CartLayout', 'Warning')) - confirm.setText( - translate('CartLayout', 'Every cue in the page will be lost.')) - confirm.setInformativeText( - translate('CartLayout', 'Are you sure to continue?')) - confirm.setStandardButtons(QMessageBox.Yes | QMessageBox.No) - confirm.setDefaultButton(QMessageBox.No) - - if confirm.exec_() == QMessageBox.Yes: - self.remove_page(self.currentIndex()) - - def remove_page(self, page): - if len(self.__pages) > page >= 0: - self._model_adapter.remove_page(page) - - self.removeTab(page) - self.tabRemoved(page) - page_widget = self.__pages.pop(page) - page_widget.move_drop_event.disconnect() - page_widget.copy_drop_event.disconnect() - - # Rename every successive tab accordingly - text = translate('CartLayout', 'Page') + ' {}' - for n in range(page, self.count()): - self.setTabText(n, text.format(n + 1)) - - def widgets(self): - for page in self.__pages: - for widget in page.widgets(): - yield widget - - def set_countdown_mode(self, mode): - self._countdown_mode = mode - for widget in self.widgets(): - widget.set_countdown_mode(mode) - - def set_accurate(self, enable): - self._accurate_timing = enable - for widget in self.widgets(): - widget.set_accurate_timing(enable) - - def set_seek_visible(self, visible): - self._show_seek = visible - for widget in self.widgets(): - widget.show_seek_slider(visible) - - def set_dbmeter_visible(self, visible): - self._show_dbmeter = visible - for widget in self.widgets(): - widget.show_dbmeters(visible) - - def set_volume_visible(self, visible): - self._show_volume = visible - for widget in self.widgets(): - widget.show_volume_slider(visible) - - def to_3d_index(self, index): - page_size = self.__rows * self.__columns - - page = index // page_size - row = (index % page_size) // self.__columns - column = (index % page_size) % self.__columns - - return page, row, column - - def to_1d_index(self, index): - try: - page, row, column = index - page *= self.__rows * self.__columns - row *= self.__columns - return page + row + column - except(TypeError, ValueError): - return -1 - - def finalize(self): - MainWindow().menuLayout.clear() - - # Disconnect menu-actions signals - self.edit_action.triggered.disconnect() - self.remove_action.triggered.disconnect() - self.select_action.triggered.disconnect() - self.play_action.triggered.disconnect() - self.pause_action.triggered.disconnect() - self.stop_action.triggered.disconnect() - self.reset_volume_action.disconnect() - - # Remove context-items - self.cm_registry.remove_item(self.edit_action) - self.cm_registry.remove_item(self.sep1) - self.cm_registry.remove_item(self.remove_action) - self.cm_registry.remove_item(self.select_action) - self.cm_registry.remove_item(self.sep2) - self.cm_registry.remove_item(self.play_action) - self.cm_registry.remove_item(self.pause_action) - self.cm_registry.remove_item(self.stop_action) - self.cm_registry.remove_item(self.reset_volume_action) - - # Delete the layout - self.deleteLater() - - def _move_widget(self, widget, to_row, to_column): - new_index = self.to_1d_index((self.currentIndex(), to_row, to_column)) - self._model_adapter.move(widget.cue.index, new_index) - - def _copy_widget(self, widget, to_row, to_column): - new_index = self.to_1d_index((self.currentIndex(), to_row, to_column)) - new_cue = CueFactory.clone_cue(widget.cue) - - self._model_adapter.insert(new_cue, new_index) - - def _play_context_cue(self): - self.get_context_cue().media.play() - - def _pause_context_cue(self): - self.get_context_cue().media.pause() - - def _stop_context_cue(self): - self.get_context_cue().media.stop() - - def _on_context_menu(self, widget, position): - self.__context_widget = widget - self.show_cue_context_menu(position) - - def _edit_cue_action(self): - self.edit_cue(self.get_context_cue()) - - def _remove_cue_action(self): - self._model_adapter.remove(self.get_context_cue()) - self.__context_widget = None - - def _reset_cue_volume(self): - self.__context_widget.reset_volume() - - def __cue_added(self, cue): - page, row, column = self.to_3d_index(cue.index) - - widget = CueWidget(cue) - widget.cue_executed.connect(self.cue_executed.emit) - widget.context_menu_request.connect(self._on_context_menu) - widget.edit_request.connect(self.edit_cue) - widget.set_accurate_timing(self._accurate_timing) - widget.set_countdown_mode(self._countdown_mode) - widget.show_dbmeters(self._show_dbmeter) - widget.show_seek_slider(self._show_seek) - widget.show_volume_slider(self._show_volume) - - if page >= len(self.__pages): - self.add_page() - - self.__pages[page].add_widget(widget, row, column) - self.setCurrentIndex(page) - - def __cue_removed(self, cue): - if isinstance(cue, MediaCue): - cue.interrupt() - else: - cue.stop() - - page, row, column = self.to_3d_index(cue.index) - widget = self.__pages[page].take_widget(row, column) - - widget.cue_executed.disconnect() - widget.context_menu_request.disconnect() - widget.edit_request.disconnect() - - widget.deleteLater() - - def __cue_moved(self, old_index, new_index): - o_page, o_row, o_column = self.to_3d_index(old_index) - n_page, n_row, n_column = self.to_3d_index(new_index) - - if o_page == n_page: - self.__pages[n_page].move_widget(o_row, o_column, n_row, n_column) - else: - widget = self.__pages[o_page].take_widget(o_row, o_column) - self.__pages[n_page].add_widget(widget, n_row, n_column) - - def __model_reset(self): - self.__context_widget = None - for page in self.__pages: - page.reset() diff --git a/lisp/layouts/cue_menu_registry.py b/lisp/layouts/cue_menu_registry.py deleted file mode 100644 index cc9c2f0ad..000000000 --- a/lisp/layouts/cue_menu_registry.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from PyQt5.QtWidgets import QAction, QMenu - -from lisp.core.class_based_registry import ClassBasedRegistry -from lisp.cues.cue import Cue - - -class CueMenuRegistry(ClassBasedRegistry): - - def add_item(self, item, ref_class=Cue): - if not isinstance(item, (QAction, QMenu)): - raise TypeError('items must be QAction(s) or QMenu(s), not {0}' - .format(item.__class__.__name__)) - if not issubclass(ref_class, Cue): - raise TypeError('ref_class must be Cue or a subclass, not {0}' - .format(ref_class.__name__)) - - return super().add_item(item, ref_class) - - def add_separator(self, ref_class=Cue): - """Register a separator menu-entry for ref_class and return it.""" - separator = QAction(None) - separator.setSeparator(True) - - self.add_item(separator, ref_class) - return separator - - def filter(self, ref_class=Cue): - return super().filter(ref_class) - - def clear_class(self, ref_class=Cue): - return super().filter(ref_class) diff --git a/lisp/layouts/list_layout/__init__.py b/lisp/layouts/list_layout/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/lisp/layouts/list_layout/cue_list_item.py b/lisp/layouts/list_layout/cue_list_item.py deleted file mode 100644 index 54685a0fb..000000000 --- a/lisp/layouts/list_layout/cue_list_item.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QTreeWidgetItem - -from lisp.core.signal import Connection -from lisp.ui.icons import IconTheme - - -class CueListItem(QTreeWidgetItem): - - def __init__(self, cue): - super().__init__() - - self.cue = cue - self.num_column = 1 - self.name_column = 2 - - self._selected = False - - self.cue.changed('name').connect(self._update_name, Connection.QtQueued) - self.cue.changed('index').connect( - self._update_index, Connection.QtQueued) - - self._update_name(self.cue.name) - self._update_index(self.cue.index) - - self.setTextAlignment(self.num_column, Qt.AlignCenter) - - @property - def selected(self): - return self._selected - - @selected.setter - def selected(self, value): - self._selected = value - self.setIcon(0, IconTheme.get('mark-location' if value else '')) - - def _update_index(self, index): - self.setText(self.num_column, str(index)) - - def _update_name(self, name): - self.setText(self.name_column, name) diff --git a/lisp/layouts/list_layout/cue_list_view.py b/lisp/layouts/list_layout/cue_list_view.py deleted file mode 100644 index 8a190df8d..000000000 --- a/lisp/layouts/list_layout/cue_list_view.py +++ /dev/null @@ -1,184 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from PyQt5 import QtCore -from PyQt5.QtCore import pyqtSignal, Qt, QDataStream, QIODevice, \ - QT_TRANSLATE_NOOP -from PyQt5.QtGui import QKeyEvent, QContextMenuEvent, QMouseEvent -from PyQt5.QtWidgets import QTreeWidget, QHeaderView, qApp - -from lisp.core.signal import Connection -from lisp.cues.cue_factory import CueFactory -from lisp.layouts.list_layout.cue_list_item import CueListItem -from lisp.layouts.list_layout.listwidgets import CueStatusIcon, PreWaitWidget, \ - CueTimeWidget, NextActionIcon, PostWaitWidget - - -# TODO: here we should build a custom qt model/view -from lisp.ui.ui_utils import translate - - -class CueListView(QTreeWidget): - - key_event = pyqtSignal(QKeyEvent) - context_event = pyqtSignal(QContextMenuEvent) - select_cue_event = pyqtSignal(QMouseEvent) - drop_move_event = QtCore.pyqtSignal(int, int) - drop_copy_event = QtCore.pyqtSignal(int, int) - - HEADER_NAMES = ['', '#', - QT_TRANSLATE_NOOP('ListLayoutHeader', 'Cue'), - QT_TRANSLATE_NOOP('ListLayoutHeader', 'Pre wait'), - QT_TRANSLATE_NOOP('ListLayoutHeader', 'Action'), - QT_TRANSLATE_NOOP('ListLayoutHeader', 'Post wait'), - ''] - HEADER_WIDGETS = [CueStatusIcon, None, None, PreWaitWidget, CueTimeWidget, - PostWaitWidget, NextActionIcon] - - def __init__(self, cue_model, parent=None): - """ - :type cue_model: lisp.layouts.list_layout.cue_list_model.CueListModel - """ - super().__init__(parent) - self._model = cue_model - self._model.item_added.connect(self.__cue_added, Connection.QtQueued) - self._model.item_moved.connect(self.__cue_moved, Connection.QtQueued) - self._model.item_removed.connect(self.__cue_removed, Connection.QtQueued) - self._model.model_reset.connect(self.__model_reset) - self.__item_moving = False - - self.setHeaderLabels( - [translate('ListLayoutHeader', h) for h in CueListView.HEADER_NAMES]) - self.header().setDragEnabled(False) - self.header().setStretchLastSection(False) - self.header().setSectionResizeMode(QHeaderView.Fixed) - self.header().setSectionResizeMode(1, QHeaderView.ResizeToContents) - self.header().setSectionResizeMode(2, QHeaderView.Stretch) - - self.setColumnWidth(0, 40) - self.setColumnWidth(len(CueListView.HEADER_NAMES) - 1, 18) - self.setSelectionMode(self.SingleSelection) - self.setDragDropMode(self.InternalMove) - self.setAlternatingRowColors(True) - self.setVerticalScrollMode(self.ScrollPerItem) - - self.setIndentation(0) - - self.currentItemChanged.connect(self.__current_changed) - - self.__guard = False - self.verticalScrollBar().rangeChanged.connect(self.__update_range) - - def dropEvent(self, event): - # Decode mimedata information about the drag&drop event, since only - # internal movement are allowed we assume the data format is correct - data = event.mimeData().data('application/x-qabstractitemmodeldatalist') - stream = QDataStream(data, QIODevice.ReadOnly) - - # Get the starting-item row - start_index = stream.readInt() - new_index = self.indexAt(event.pos()).row() - if not 0 <= new_index <= len(self._model): - new_index = len(self._model) - - if qApp.keyboardModifiers() == Qt.ControlModifier: - cue = self._model.item(start_index) - new_cue = CueFactory.clone_cue(cue) - - self._model.insert(new_cue, new_index) - else: - self._model.move(start_index, new_index) - - def contextMenuEvent(self, event): - if self.itemAt(event.pos()) is not None: - self.context_event.emit(event) - else: - super().contextMenuEvent(event) - - def keyPressEvent(self, event): - self.key_event.emit(event) - - if qApp.keyboardModifiers() == Qt.ControlModifier: - # Prevent items to be deselected - event.ignore() - else: - super().keyPressEvent(event) - - def mousePressEvent(self, event): - if qApp.keyboardModifiers() == Qt.ControlModifier: - # For cue selection - self.select_cue_event.emit(event) - else: - super().mousePressEvent(event) - - def mouseMoveEvent(self, event): - super().mouseMoveEvent(event) - if qApp.keyboardModifiers() == Qt.ControlModifier: - # Prevent items to be deselected - if self.state() == self.DragSelectingState: - item = self.itemAt(event.pos()) - self.setCurrentItem(item) - - def __current_changed(self, current_item, previous_item): - self.scrollToItem(current_item) - - def __cue_added(self, cue): - item = CueListItem(cue) - item.setFlags(item.flags() & ~Qt.ItemIsDropEnabled) - - self.insertTopLevelItem(cue.index, item) - self.__init_item(item, cue) - - # Select the added item and scroll to it - self.setCurrentItem(item) - # Ensure that the focus is set - self.setFocus() - - def __cue_moved(self, start, end): - item = self.takeTopLevelItem(start) - - self.insertTopLevelItem(end, item) - self.setCurrentItem(item) - self.__init_item(item, self._model.item(end)) - - def __cue_removed(self, cue): - self.takeTopLevelItem(cue.index) - - index = cue.index - if index > 0: - index -= 1 - self.setCurrentIndex(self.model().index(index, 0)) - - def __model_reset(self): - self.reset() - self.clear() - - def __init_item(self, item, cue): - item.name_column = CueListView.HEADER_NAMES.index('Cue') - for index, widget in enumerate(CueListView.HEADER_WIDGETS): - if widget is not None: - self.setItemWidget(item, index, widget(cue)) - - self.updateGeometries() - - def __update_range(self, min_, max_): - if not self.__guard: - self.__guard = True - self.verticalScrollBar().setMaximum(max_ + 1) - self.__guard = False diff --git a/lisp/layouts/list_layout/layout.py b/lisp/layouts/list_layout/layout.py deleted file mode 100644 index b4b06d134..000000000 --- a/lisp/layouts/list_layout/layout.py +++ /dev/null @@ -1,379 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2017 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from enum import Enum - -from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtGui import QKeySequence -from PyQt5.QtWidgets import QWidget, QAction, qApp, QGridLayout, \ - QPushButton, QSizePolicy - -from lisp.core.signal import Connection -from lisp.cues.cue import Cue, CueAction -from lisp.cues.media_cue import MediaCue -from lisp.layouts.cue_layout import CueLayout -from lisp.layouts.list_layout.control_buttons import ShowControlButtons -from lisp.layouts.list_layout.cue_list_model import CueListModel, \ - RunningCueModel -from lisp.layouts.list_layout.cue_list_view import CueListView -from lisp.layouts.list_layout.info_panel import InfoPanel -from lisp.layouts.list_layout.list_layout_settings import ListLayoutSettings -from lisp.layouts.list_layout.playing_list_widget import RunningCuesListWidget -from lisp.ui.mainwindow import MainWindow -from lisp.ui.settings.app_configuration import AppConfigurationDialog -from lisp.ui.ui_utils import translate - - -class EndListBehavior(Enum): - Stop = 'Stop' - Restart = 'Restart' - - -class ListLayout(QWidget, CueLayout): - NAME = 'List Layout' - DESCRIPTION = QT_TRANSLATE_NOOP( - 'LayoutDescription', 'Organize the cues in a list') - DETAILS = [ - QT_TRANSLATE_NOOP( - 'LayoutDetails', 'SHIFT + Space or Double-Click to edit a cue'), - QT_TRANSLATE_NOOP( - 'LayoutDetails', 'CTRL + Left Click to select cues'), - QT_TRANSLATE_NOOP( - 'LayoutDetails', 'To copy cues drag them while pressing CTRL'), - QT_TRANSLATE_NOOP( - 'LayoutDetails', 'To move cues drag them') - ] - - def __init__(self, **kwargs): - super().__init__(**kwargs) - AppConfigurationDialog.registerSettingsPage( - 'list_layout', ListLayoutSettings, self.app.conf) - - self.setLayout(QGridLayout()) - self.layout().setContentsMargins(0, 0, 0, 0) - - self._model_adapter = CueListModel(self.cue_model) - self._model_adapter.item_added.connect(self.__cue_added) - self._model_adapter.item_removed.connect(self.__cue_removed) - - self._playing_model = RunningCueModel(self.cue_model) - self._context_item = None - self._next_cue_index = 0 - - self._show_dbmeter = self.app.conf['listLayout.showDbMeters'] - self._seek_visible = self.app.conf['listLayout.showSeek'] - self._accurate_time = self.app.conf['listLayout.showAccurate'] - self._auto_continue = self.app.conf['listLayout.autoContinue'] - self._show_playing = self.app.conf['listLayout.showPlaying'] - self._go_key = self.app.conf['listLayout.goKey'] - self._go_key_sequence = QKeySequence( - self._go_key, QKeySequence.NativeText) - - self._end_list = EndListBehavior(self.app.conf['listLayout.endList']) - - # Add layout-specific menus - self.showPlayingAction = QAction(self) - self.showPlayingAction.setCheckable(True) - self.showPlayingAction.setChecked(self._show_playing) - self.showPlayingAction.triggered.connect(self.set_playing_visible) - - self.showDbMeterAction = QAction(self) - self.showDbMeterAction.setCheckable(True) - self.showDbMeterAction.setChecked(self._show_dbmeter) - self.showDbMeterAction.triggered.connect(self.set_dbmeter_visible) - - self.showSeekAction = QAction(self) - self.showSeekAction.setCheckable(True) - self.showSeekAction.setChecked(self._seek_visible) - self.showSeekAction.triggered.connect(self.set_seek_visible) - - self.accurateTimingAction = QAction(self) - self.accurateTimingAction.setCheckable(True) - self.accurateTimingAction.setChecked(self._accurate_time) - self.accurateTimingAction.triggered.connect(self.set_accurate_time) - - self.autoNextAction = QAction(self) - self.autoNextAction.setCheckable(True) - self.autoNextAction.setChecked(self._auto_continue) - self.autoNextAction.triggered.connect(self.set_auto_next) - - MainWindow().menuLayout.addAction(self.showPlayingAction) - MainWindow().menuLayout.addAction(self.showDbMeterAction) - MainWindow().menuLayout.addAction(self.showSeekAction) - MainWindow().menuLayout.addAction(self.accurateTimingAction) - MainWindow().menuLayout.addAction(self.autoNextAction) - - # GO-BUTTON (top-left) - self.goButton = QPushButton('GO', self) - self.goButton.setFocusPolicy(Qt.NoFocus) - self.goButton.setFixedWidth(120) - self.goButton.setFixedHeight(100) - self.goButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) - self.goButton.setStyleSheet('font-size: 48pt;') - self.goButton.clicked.connect(self.__go_slot) - self.layout().addWidget(self.goButton, 0, 0) - - # INFO PANEL (top center) - self.infoPanel = InfoPanel() - self.infoPanel.setFixedHeight(100) - self.layout().addWidget(self.infoPanel, 0, 1) - - # CONTROL-BUTTONS (top-right) - self.controlButtons = ShowControlButtons(parent=self) - self.controlButtons.setFixedHeight(100) - self.controlButtons.stopButton.clicked.connect(self.stop_all) - self.controlButtons.pauseButton.clicked.connect(self.pause_all) - self.controlButtons.fadeInButton.clicked.connect(self.fadein_all) - self.controlButtons.fadeOutButton.clicked.connect(self.fadeout_all) - self.controlButtons.resumeButton.clicked.connect(self.resume_all) - self.controlButtons.interruptButton.clicked.connect(self.interrupt_all) - self.layout().addWidget(self.controlButtons, 0, 2) - - # CUE VIEW (center left) - self.listView = CueListView(self._model_adapter, self) - self.listView.itemDoubleClicked.connect(self.double_clicked) - self.listView.currentItemChanged.connect(self.__current_changed) - self.listView.context_event.connect(self.context_event) - self.listView.key_event.connect(self.onKeyPressEvent) - self.listView.select_cue_event.connect(self.select_event) - self.layout().addWidget(self.listView, 1, 0, 1, 2) - - # PLAYING VIEW (center right) - self.playView = RunningCuesListWidget(self._playing_model, parent=self) - self.playView.dbmeter_visible = self._show_dbmeter - self.playView.accurate_time = self._accurate_time - self.playView.seek_visible = self._seek_visible - self.playView.setMinimumWidth(300) - self.playView.setMaximumWidth(300) - self.layout().addWidget(self.playView, 1, 2) - - self.set_playing_visible(self._show_playing) - - # Context menu actions - self.edit_action = QAction(self) - self.edit_action.triggered.connect(self.edit_context_cue) - - self.remove_action = QAction(self) - self.remove_action.triggered.connect(self.remove_context_cue) - - self.select_action = QAction(self) - self.select_action.triggered.connect(self.select_context_cue) - - self.cm_registry.add_item(self.edit_action) - self.sep1 = self.cm_registry.add_separator() - self.cm_registry.add_item(self.remove_action) - self.cm_registry.add_item(self.select_action) - - self.retranslateUi() - - def retranslateUi(self): - self.showPlayingAction.setText( - translate('ListLayout', 'Show playing cues')) - self.showDbMeterAction.setText( - translate('ListLayout', 'Show dB-meters')) - self.showSeekAction.setText(translate('ListLayout', 'Show seek-bars')) - self.accurateTimingAction.setText( - translate('ListLayout', 'Show accurate time')) - self.autoNextAction.setText( - translate('ListLayout', 'Auto-select next cue')) - - self.edit_action.setText(translate('ListLayout', 'Edit cue')) - self.remove_action.setText(translate('ListLayout', 'Remove')) - self.select_action.setText(translate('ListLayout', 'Select')) - - @CueLayout.model_adapter.getter - def model_adapter(self): - return self._model_adapter - - def current_index(self): - return self.listView.currentIndex().row() - - def set_current_index(self, index): - if self._end_list == EndListBehavior.Restart: - index %= len(self.model_adapter) - - if 0 <= index < self.listView.topLevelItemCount(): - next_item = self.listView.topLevelItem(index) - self.listView.setCurrentItem(next_item) - - def go(self, action=CueAction.Default, advance=1): - current_cue = self.current_cue() - if current_cue is not None: - current_cue.execute(action) - self.cue_executed.emit(current_cue) - - if self._auto_continue: - self.set_current_index(self.current_index() + advance) - - def current_item(self): - if self._model_adapter: - return self.listView.currentItem() - - def select_context_cue(self): - self._context_item.selected = not self._context_item.selected - - def set_accurate_time(self, accurate): - self._accurate_time = accurate - self.playView.accurate_time = accurate - - def set_auto_next(self, enable): - self._auto_continue = enable - - def set_seek_visible(self, visible): - self._seek_visible = visible - self.playView.seek_visible = visible - - def set_dbmeter_visible(self, visible): - self._show_dbmeter = visible - self.playView.dbmeter_visible = visible - - def set_playing_visible(self, visible): - self._show_playing = visible - self.playView.setVisible(visible) - self.controlButtons.setVisible(visible) - - def onKeyPressEvent(self, e): - if not e.isAutoRepeat(): - keys = e.key() - modifiers = e.modifiers() - - if modifiers & Qt.ShiftModifier: - keys += Qt.SHIFT - if modifiers & Qt.ControlModifier: - keys += Qt.CTRL - if modifiers & Qt.AltModifier: - keys += Qt.ALT - if modifiers & Qt.MetaModifier: - keys += Qt.META - - if QKeySequence(keys) in self._go_key_sequence: - self.go() - elif e.key() == Qt.Key_Space: - if qApp.keyboardModifiers() == Qt.ShiftModifier: - cue = self.current_cue() - if cue is not None: - self.edit_cue(cue) - elif qApp.keyboardModifiers() == Qt.ControlModifier: - item = self.current_item() - if item is not None: - item.selected = not item.selected - else: - self.key_pressed.emit(e) - - e.accept() - - def double_clicked(self, event): - cue = self.current_cue() - if cue is not None: - self.edit_cue(cue) - - def select_event(self, event): - item = self.listView.itemAt(event.pos()) - if item is not None: - item.selected = not item.selected - - def context_event(self, event): - self._context_item = self.listView.itemAt(event.pos()) - if self._context_item is not None: - self.show_cue_context_menu(event.globalPos()) - - def contextMenuEvent(self, event): - if self.listView.geometry().contains(event.pos()): - self.show_context_menu(event.globalPos()) - - def remove_context_cue(self): - self._model_adapter.remove(self.get_context_cue()) - - def edit_context_cue(self): - self.edit_cue(self.get_context_cue()) - - def get_selected_cues(self, cue_class=Cue): - cues = [] - for index in range(self.listView.topLevelItemCount()): - item = self.listView.topLevelItem(index) - if item.selected and isinstance(item.cue, cue_class): - cues.append(item.cue) - return cues - - def finalize(self): - MainWindow().menuLayout.clear() - - # Disconnect menu-actions signals - self.edit_action.triggered.disconnect() - self.remove_action.triggered.disconnect() - self.select_action.triggered.disconnect() - - # Remove context-items - self.cm_registry.remove_item(self.edit_action) - self.cm_registry.remove_item(self.sep1) - self.cm_registry.remove_item(self.remove_action) - self.cm_registry.remove_item(self.select_action) - - # Delete the layout - self.deleteLater() - - def get_context_cue(self): - return self._context_item.cue - - def select_all(self, cue_class=Cue): - for index in range(self.listView.topLevelItemCount()): - if isinstance(self.model_adapter.item(index), cue_class): - self.listView.topLevelItem(index).selected = True - - def deselect_all(self, cue_class=Cue): - for index in range(self.listView.topLevelItemCount()): - if isinstance(self.model_adapter.item(index), cue_class): - self.listView.topLevelItem(index).selected = False - - def invert_selection(self): - for index in range(self.listView.topLevelItemCount()): - item = self.listView.topLevelItem(index) - item.selected = not item.selected - - def __go_slot(self): - self.go() - - def __current_changed(self, new_item, current_item): - try: - index = self.listView.indexOfTopLevelItem(new_item) - cue = self.model_adapter.item(index) - self.infoPanel.cue_changed(cue) - except IndexError: - self.infoPanel.cue_changed(None) - - def __cue_added(self, cue): - cue.next.connect(self.__cue_next, Connection.QtQueued) - - def __cue_removed(self, cue): - if isinstance(cue, MediaCue): - cue.interrupt() - else: - cue.stop() - - def __cue_next(self, cue): - try: - next_index = cue.index + 1 - if next_index < len(self._model_adapter): - next_cue = self._model_adapter.item(next_index) - next_cue.execute() - - if self._auto_continue and next_cue == self.current_cue(): - self.set_current_index(next_index + 1) - except(IndexError, KeyError): - pass diff --git a/lisp/plugins/action_cues/collection_cue.py b/lisp/plugins/action_cues/collection_cue.py index 782de274e..a9196cbae 100644 --- a/lisp/plugins/action_cues/collection_cue.py +++ b/lisp/plugins/action_cues/collection_cue.py @@ -156,4 +156,4 @@ def setData(self, index, value, role=Qt.DisplayRole): return result -CueSettingsRegistry().add_item(CollectionCueSettings, CollectionCue) +CueSettingsRegistry().add(CollectionCueSettings, CollectionCue) diff --git a/lisp/plugins/action_cues/command_cue.py b/lisp/plugins/action_cues/command_cue.py index 8f40ead3b..5aedb25c7 100644 --- a/lisp/plugins/action_cues/command_cue.py +++ b/lisp/plugins/action_cues/command_cue.py @@ -173,4 +173,4 @@ def getSettings(self): return settings -CueSettingsRegistry().add_item(CommandCueSettings, CommandCue) +CueSettingsRegistry().add(CommandCueSettings, CommandCue) diff --git a/lisp/plugins/action_cues/index_action_cue.py b/lisp/plugins/action_cues/index_action_cue.py index b433c5b3f..0267e88a1 100644 --- a/lisp/plugins/action_cues/index_action_cue.py +++ b/lisp/plugins/action_cues/index_action_cue.py @@ -200,4 +200,4 @@ def _current_target(self): return None -CueSettingsRegistry().add_item(IndexActionCueSettings, IndexActionCue) +CueSettingsRegistry().add(IndexActionCueSettings, IndexActionCue) diff --git a/lisp/plugins/action_cues/midi_cue.py b/lisp/plugins/action_cues/midi_cue.py index 03b6a1de4..d9fd04aa4 100644 --- a/lisp/plugins/action_cues/midi_cue.py +++ b/lisp/plugins/action_cues/midi_cue.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . + import logging from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP @@ -24,7 +25,7 @@ from lisp.core.properties import Property from lisp.cues.cue import Cue -from lisp.plugins import get_plugin, PluginNotLoadedError +from lisp.plugins import get_plugin from lisp.plugins.midi.midi_utils import str_msg_to_dict, dict_msg_to_str from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.pages import SettingsPage @@ -157,4 +158,4 @@ def loadSettings(self, settings): spin.setValue(dict_msg.get(label.text().lower(), 0) - offset) -CueSettingsRegistry().add_item(MidiCueSettings, MidiCue) +CueSettingsRegistry().add(MidiCueSettings, MidiCue) diff --git a/lisp/plugins/action_cues/osc_cue.py b/lisp/plugins/action_cues/osc_cue.py index b47979bd4..fe907b0ec 100644 --- a/lisp/plugins/action_cues/osc_cue.py +++ b/lisp/plugins/action_cues/osc_cue.py @@ -359,7 +359,7 @@ def __test_message(self): self.__osc.server.send(self.path, *args) except ValueError: QMessageBox.critical( - None, 'Error', 'Error on parsing argument list - nothing sent') + self, 'Error', 'Error on parsing argument list - nothing sent') def __argument_changed(self, index_topleft, index_bottomright, roles): if not (Qt.EditRole in roles): @@ -437,4 +437,4 @@ def __init__(self, **kwargs): self.setItemDelegateForColumn(column, delegate) -CueSettingsRegistry().add_item(OscCueSettings, OscCue) +CueSettingsRegistry().add(OscCueSettings, OscCue) diff --git a/lisp/plugins/action_cues/seek_cue.py b/lisp/plugins/action_cues/seek_cue.py index a72ee9562..af9c13814 100644 --- a/lisp/plugins/action_cues/seek_cue.py +++ b/lisp/plugins/action_cues/seek_cue.py @@ -130,4 +130,4 @@ def loadSettings(self, settings): QTime.fromMSecsSinceStartOfDay(settings.get('time', 0))) -CueSettingsRegistry().add_item(SeekCueSettings, SeekCue) +CueSettingsRegistry().add(SeekCueSettings, SeekCue) diff --git a/lisp/plugins/action_cues/stop_all.py b/lisp/plugins/action_cues/stop_all.py index 8a3869bf9..9bea7452c 100644 --- a/lisp/plugins/action_cues/stop_all.py +++ b/lisp/plugins/action_cues/stop_all.py @@ -102,4 +102,4 @@ def loadSettings(self, settings): translate('CueAction', settings.get('action', ''))) -CueSettingsRegistry().add_item(StopAllSettings, StopAll) +CueSettingsRegistry().add(StopAllSettings, StopAll) diff --git a/lisp/plugins/action_cues/volume_control.py b/lisp/plugins/action_cues/volume_control.py index 7e398d794..f60c33c86 100644 --- a/lisp/plugins/action_cues/volume_control.py +++ b/lisp/plugins/action_cues/volume_control.py @@ -244,4 +244,4 @@ def __db_volume_change(self, value): self.__v_edit_flag = False -CueSettingsRegistry().add_item(VolumeSettings, VolumeControl) +CueSettingsRegistry().add(VolumeSettings, VolumeControl) diff --git a/lisp/plugins/cart_layout/__init__.py b/lisp/plugins/cart_layout/__init__.py new file mode 100644 index 000000000..9be16fdaa --- /dev/null +++ b/lisp/plugins/cart_layout/__init__.py @@ -0,0 +1,19 @@ +from lisp.core.plugin import Plugin +from lisp.layout import register_layout +from lisp.plugins.cart_layout.settings import CartLayoutSettings +from lisp.plugins.cart_layout.layout import CartLayout as _CartLayout +from lisp.ui.settings.app_configuration import AppConfigurationDialog + + +class CartLayout(Plugin): + Name = 'Cart Layout' + Description = 'Provide a layout that organize cues in grid-like pages' + Authors = ('Francesco Ceruti', ) + + def __init__(self, app): + super().__init__(app) + + _CartLayout.Config = CartLayout.Config + register_layout(_CartLayout) + AppConfigurationDialog.registerSettingsPage( + 'layouts.cart_layout', CartLayoutSettings, CartLayout.Config) diff --git a/lisp/layouts/cart_layout/cue_widget.py b/lisp/plugins/cart_layout/cue_widget.py similarity index 52% rename from lisp/layouts/cart_layout/cue_widget.py rename to lisp/plugins/cart_layout/cue_widget.py index db8811e8e..e366530c1 100644 --- a/lisp/layouts/cart_layout/cue_widget.py +++ b/lisp/plugins/cart_layout/cue_widget.py @@ -20,7 +20,7 @@ from PyQt5.QtCore import Qt, QMimeData, pyqtSignal, QPoint from PyQt5.QtGui import QColor, QDrag from PyQt5.QtWidgets import QProgressBar, QLCDNumber, QLabel, QHBoxLayout, \ - QWidget, QGridLayout, QSizePolicy + QWidget, QSizePolicy, QVBoxLayout from lisp.backend.audio_utils import slider_to_fader, fader_to_slider from lisp.core.signal import Connection @@ -28,7 +28,7 @@ from lisp.cues.cue import CueState from lisp.cues.cue_time import CueTime from lisp.cues.media_cue import MediaCue -from lisp.layouts.cart_layout.page_widget import PageWidget +from lisp.plugins.cart_layout.page_widget import CartPageWidget from lisp.ui.icons import IconTheme from lisp.ui.widgets import QClickLabel, QClickSlider, QDbMeter @@ -37,31 +37,33 @@ class CueWidget(QWidget): ICON_SIZE = 14 SLIDER_RANGE = 1000 - context_menu_request = pyqtSignal(object, QPoint) - edit_request = pyqtSignal(object) - cue_executed = pyqtSignal(object) + contextMenuRequested = pyqtSignal(QPoint) + editRequested = pyqtSignal(object) + cueExecuted = pyqtSignal(object) def __init__(self, cue, **kwargs): super().__init__(**kwargs) - self.cue = None - + self._cue = None self._selected = False - self._accurate_timing = False - self._show_dbmeter = False - self._show_volume = False - self._countdown_mode = True + self._accurateTiming = False + self._countdownMode = True + self._showDBMeter = False + self._showVolume = False - self._dbmeter_element = None - self._fade_element = None - self._volume_element = None + self._dBMeterElement = None + self._volumeElement = None + self._fadeElement = None self.setAttribute(Qt.WA_TranslucentBackground) - self.setLayout(QGridLayout()) + self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(2) - self.layout().setColumnStretch(0, 6) - self.layout().setRowStretch(0, 4) + + self.hLayout = QHBoxLayout() + self.hLayout.setContentsMargins(0, 0, 0, 0) + self.hLayout.setSpacing(2) + self.layout().addLayout(self.hLayout, 4) self.nameButton = QClickLabel(self) self.nameButton.setObjectName('ButtonCueWidget') @@ -70,7 +72,7 @@ def __init__(self, cue, **kwargs): self.nameButton.setFocusPolicy(Qt.NoFocus) self.nameButton.clicked.connect(self._clicked) self.nameButton.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) - self.layout().addWidget(self.nameButton, 0, 0) + self.hLayout.addWidget(self.nameButton, 5) self.statusIcon = QLabel(self.nameButton) self.statusIcon.setStyleSheet('background-color: transparent') @@ -89,7 +91,7 @@ def __init__(self, cue, **kwargs): self.volumeSlider.setRange(0, CueWidget.SLIDER_RANGE) self.volumeSlider.setPageStep(10) self.volumeSlider.valueChanged.connect( - self._change_volume, Qt.DirectConnection) + self._changeVolume, Qt.DirectConnection) self.volumeSlider.setVisible(False) self.dbMeter = QDbMeter(self) @@ -109,7 +111,11 @@ def __init__(self, cue, **kwargs): self.timeBar.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.timeBar.setVisible(False) - self._set_cue(cue) + self._setCue(cue) + + @property + def cue(self): + return self._cue @property def selected(self): @@ -118,258 +124,255 @@ def selected(self): @selected.setter def selected(self, value): self._selected = value - self._update_style(self.cue.stylesheet) + # Show the selection via stylesheet/qproperties + self.nameButton.setProperty('selected', self.selected) + self.nameButton.style().unpolish(self.nameButton) + self.nameButton.style().polish(self.nameButton) def contextMenuEvent(self, event): - self.context_menu_request.emit(self, event.globalPos()) + self.contextMenuRequested.emit(event.globalPos()) def mouseMoveEvent(self, event): if (event.buttons() == Qt.LeftButton and (event.modifiers() == Qt.ControlModifier or - event.modifiers() == Qt.ShiftModifier)): + event.modifiers() == Qt.ShiftModifier)): mime_data = QMimeData() - mime_data.setText(PageWidget.DRAG_MAGIC) + mime_data.setText(CartPageWidget.DRAG_MAGIC) drag = QDrag(self) drag.setMimeData(mime_data) drag.setPixmap(self.grab(self.rect())) if event.modifiers() == Qt.ControlModifier: - drag.exec_(Qt.MoveAction) - else: drag.exec_(Qt.CopyAction) + else: + drag.exec_(Qt.MoveAction) - event.accept() - else: - event.ignore() - - def set_countdown_mode(self, mode): - self._countdown_mode = mode - self._update_time(self.cue.current_time()) + def setCountdownMode(self, mode): + self._countdownMode = mode + self._updateTime(self._cue.current_time()) - def set_accurate_timing(self, enable): - self._accurate_timing = enable - if self.cue.state & CueState.Pause: - self._update_time(self.cue.current_time(), True) - elif not self.cue.state & CueState.Running: - self._update_duration(self.cue.duration) + def showAccurateTiming(self, enable): + self._accurateTiming = enable + if self._cue.state & CueState.Pause: + self._updateTime(self._cue.current_time(), True) + elif not self._cue.state & CueState.Running: + self._updateDuration(self._cue.duration) - def show_seek_slider(self, visible): - if isinstance(self.cue, MediaCue): + def showSeekSlider(self, visible): + if isinstance(self._cue, MediaCue): self.seekSlider.setVisible(visible) self.update() - def show_dbmeters(self, visible): - if isinstance(self.cue, MediaCue): - self._show_dbmeter = visible + def showDBMeters(self, visible): + if isinstance(self._cue, MediaCue): + self._showDBMeter = visible - if self._dbmeter_element is not None: - self._dbmeter_element.level_ready.disconnect(self.dbMeter.plot) - self._dbmeter_element = None + if self._dBMeterElement is not None: + self._dBMeterElement.level_ready.disconnect(self.dbMeter.plot) + self._dBMeterElement = None if visible: - self._dbmeter_element = self.cue.media.element('DbMeter') - if self._dbmeter_element is not None: - self._dbmeter_element.level_ready.connect(self.dbMeter.plot) + self._dBMeterElement = self._cue.media.element('DbMeter') + if self._dBMeterElement is not None: + self._dBMeterElement.level_ready.connect(self.dbMeter.plot) - self.layout().addWidget(self.dbMeter, 0, 2) - self.layout().setColumnStretch(2, 1) + self.hLayout.insertWidget(2, self.dbMeter, 1) self.dbMeter.show() else: - self.layout().removeWidget(self.dbMeter) - self.layout().setColumnStretch(2, 0) + self.hLayout.removeWidget(self.dbMeter) self.dbMeter.hide() self.update() - def show_volume_slider(self, visible): - if isinstance(self.cue, MediaCue): - self._show_volume = visible + def showVolumeSlider(self, visible): + if isinstance(self._cue, MediaCue): + self._showVolume = visible - if self._volume_element is not None: - self._volume_element.changed('volume').disconnect( - self.reset_volume) - self._volume_element = None + if self._volumeElement is not None: + self._volumeElement.changed('volume').disconnect( + self.resetVolume) + self._volumeElement = None if visible: - self.volumeSlider.setEnabled(self.cue.state & CueState.Running) - self._volume_element = self.cue.media.element('Volume') - if self._volume_element is not None: - self.reset_volume() - self._volume_element.changed('volume').connect( - self.reset_volume, - Connection.QtQueued) - - self.layout().addWidget(self.volumeSlider, 0, 1) - self.layout().setColumnStretch(1, 1) + self.volumeSlider.setEnabled(self._cue.state & CueState.Running) + self._volumeElement = self._cue.media.element('Volume') + if self._volumeElement is not None: + self.resetVolume() + self._volumeElement.changed('volume').connect( + self.resetVolume, + Connection.QtQueued + ) + + self.hLayout.insertWidget(1, self.volumeSlider, 1) self.volumeSlider.show() else: - self.layout().removeWidget(self.volumeSlider) - self.layout().setColumnStretch(1, 0) + self.hLayout.removeWidget(self.volumeSlider) self.volumeSlider.hide() self.update() - def reset_volume(self): - if self._volume_element is not None: + def resetVolume(self): + if self._volumeElement is not None: self.volumeSlider.setValue(round(fader_to_slider( - self._volume_element.volume) * CueWidget.SLIDER_RANGE)) + self._volumeElement.volume) * CueWidget.SLIDER_RANGE)) - def _set_cue(self, cue): - self.cue = cue + def _setCue(self, cue): + self._cue = cue # Cue properties changes - self.cue.changed('name').connect( - self._update_name, Connection.QtQueued) - self.cue.changed('stylesheet').connect( - self._update_style, Connection.QtQueued) - self.cue.changed('duration').connect( - self._update_duration, Connection.QtQueued) - self.cue.changed('description').connect( - self._update_description, Connection.QtQueued) - - # Fade enter/exit - self.cue.fadein_start.connect(self._enter_fadein, Connection.QtQueued) - self.cue.fadein_end.connect(self._exit_fade, Connection.QtQueued) - self.cue.fadeout_start.connect(self._enter_fadeout, Connection.QtQueued) - self.cue.fadeout_end.connect(self._exit_fade, Connection.QtQueued) + self._cue.changed('name').connect( + self._updateName, Connection.QtQueued) + self._cue.changed('stylesheet').connect( + self._updateStyle, Connection.QtQueued) + self._cue.changed('duration').connect( + self._updateDuration, Connection.QtQueued) + self._cue.changed('description').connect( + self._updateDescription, Connection.QtQueued) + + # FadeOut start/end + self._cue.fadein_start.connect(self._enterFadein, Connection.QtQueued) + self._cue.fadein_end.connect(self._exitFade, Connection.QtQueued) + + # FadeIn start/end + self._cue.fadeout_start.connect(self._enterFadeout, Connection.QtQueued) + self._cue.fadeout_end.connect(self._exitFade, Connection.QtQueued) # Cue status changed - self.cue.interrupted.connect(self._status_stopped, Connection.QtQueued) - self.cue.started.connect(self._status_playing, Connection.QtQueued) - self.cue.stopped.connect(self._status_stopped, Connection.QtQueued) - self.cue.paused.connect(self._status_paused, Connection.QtQueued) - self.cue.error.connect(self._status_error, Connection.QtQueued) - self.cue.end.connect(self._status_stopped, Connection.QtQueued) - - # DbMeter connections + self._cue.interrupted.connect(self._statusStopped, Connection.QtQueued) + self._cue.started.connect(self._statusPlaying, Connection.QtQueued) + self._cue.stopped.connect(self._statusStopped, Connection.QtQueued) + self._cue.paused.connect(self._statusPaused, Connection.QtQueued) + self._cue.error.connect(self._statusError, Connection.QtQueued) + self._cue.end.connect(self._statusStopped, Connection.QtQueued) + + # Media cues features dBMeter and seekSlider if isinstance(cue, MediaCue): - self.cue.media.elements_changed.connect( - self._media_updated, Connection.QtQueued) + self._cue.media.elements_changed.connect( + self._mediaUpdated, Connection.QtQueued) - self.cue.paused.connect(self.dbMeter.reset, Connection.QtQueued) - self.cue.stopped.connect(self.dbMeter.reset, Connection.QtQueued) - self.cue.end.connect(self.dbMeter.reset, Connection.QtQueued) - self.cue.error.connect(self.dbMeter.reset, Connection.QtQueued) + self._cue.paused.connect(self.dbMeter.reset, Connection.QtQueued) + self._cue.stopped.connect(self.dbMeter.reset, Connection.QtQueued) + self._cue.end.connect(self.dbMeter.reset, Connection.QtQueued) + self._cue.error.connect(self.dbMeter.reset, Connection.QtQueued) - self.seekSlider.sliderMoved.connect(self.cue.media.seek) - self.seekSlider.sliderJumped.connect(self.cue.media.seek) + self.seekSlider.sliderMoved.connect(self._cue.media.seek) + self.seekSlider.sliderJumped.connect(self._cue.media.seek) - self._cue_time = CueTime(self.cue) - self._cue_time.notify.connect(self._update_time, Connection.QtQueued) + self._cueTime = CueTime(self._cue) + self._cueTime.notify.connect(self._updateTime, Connection.QtQueued) - self._update_name(cue.name) - self._update_style(cue.stylesheet) - self._update_duration(self.cue.duration) + self._updateName(cue.name) + self._updateStyle(cue.stylesheet) + self._updateDuration(self._cue.duration) - def _media_updated(self): - self.show_dbmeters(self._show_dbmeter) - self.show_volume_slider(self._show_volume) + def _mediaUpdated(self): + self.showDBMeters(self._showDBMeter) + self.showVolumeSlider(self._showVolume) - def _update_name(self, name): + def _updateName(self, name): self.nameButton.setText(name) - def _update_description(self, description): + def _updateDescription(self, description): self.nameButton.setToolTip(description) - def _change_volume(self, new_volume): - self._volume_element.live_volume = slider_to_fader( + def _changeVolume(self, new_volume): + self._volumeElement.live_volume = slider_to_fader( new_volume / CueWidget.SLIDER_RANGE) def _clicked(self, event): if not (self.seekSlider.geometry().contains(event.pos()) and - self.seekSlider.isVisible()): + self.seekSlider.isVisible()): if event.button() != Qt.RightButton: if event.modifiers() == Qt.ShiftModifier: - self.edit_request.emit(self.cue) + self.editRequested.emit(self._cue) elif event.modifiers() == Qt.ControlModifier: self.selected = not self.selected else: - self.cue.execute() - self.cue_executed.emit(self.cue) + self._cue.execute() + self.cueExecuted.emit(self._cue) - def _update_style(self, stylesheet): - stylesheet += 'text-decoration: underline;' if self.selected else '' + def _updateStyle(self, stylesheet): self.nameButton.setStyleSheet(stylesheet) - def _enter_fadein(self): + def _enterFadein(self): p = self.timeDisplay.palette() p.setColor(p.WindowText, QColor(0, 255, 0)) self.timeDisplay.setPalette(p) - def _enter_fadeout(self): + def _enterFadeout(self): p = self.timeDisplay.palette() p.setColor(p.WindowText, QColor(255, 50, 50)) self.timeDisplay.setPalette(p) - def _exit_fade(self): + def _exitFade(self): self.timeDisplay.setPalette(self.timeBar.palette()) - def _status_stopped(self): + def _statusStopped(self): self.statusIcon.setPixmap( IconTheme.get('led-off').pixmap(CueWidget.ICON_SIZE)) self.volumeSlider.setEnabled(False) - self._update_time(0, True) - self.reset_volume() + self._updateTime(0, True) + self.resetVolume() - def _status_playing(self): + def _statusPlaying(self): self.statusIcon.setPixmap( IconTheme.get('led-running').pixmap(CueWidget.ICON_SIZE)) self.volumeSlider.setEnabled(True) - def _status_paused(self): + def _statusPaused(self): self.statusIcon.setPixmap( IconTheme.get('led-pause').pixmap(CueWidget.ICON_SIZE)) self.volumeSlider.setEnabled(False) - def _status_error(self): + def _statusError(self): self.statusIcon.setPixmap( IconTheme.get('led-error').pixmap(CueWidget.ICON_SIZE)) self.volumeSlider.setEnabled(False) - self.reset_volume() + self.resetVolume() - def _update_duration(self, duration): + def _updateDuration(self, duration): # Update the maximum values of seek-slider and time progress-bar if duration > 0: if not self.timeBar.isVisible(): - self.layout().addWidget(self.timeBar, 1, 0, 1, 3) - self.layout().setRowStretch(1, 1) + self.layout().addWidget(self.timeBar, 1) self.timeBar.show() self.timeBar.setMaximum(duration) self.seekSlider.setMaximum(duration) else: + self.layout().removeWidget(self.timeBar) self.timeBar.hide() - self.layout().setRowStretch(1, 0) - if not self.cue.state & CueState.Running: - self._update_time(duration, True) + if not self._cue.state & CueState.Running: + self._updateTime(duration, True) - def _update_time(self, time, ignore_visibility=False): + def _updateTime(self, time, ignore_visibility=False): if ignore_visibility or not self.visibleRegion().isEmpty(): # If the given value is the duration or < 0 set the time to 0 - if time == self.cue.duration or time < 0: + if time == self._cue.duration or time < 0: time = 0 # Set the value the seek slider self.seekSlider.setValue(time) # If in count-down mode the widget will show the remaining time - if self._countdown_mode: - time = self.cue.duration - time + if self._countdownMode: + time = self._cue.duration - time # Set the value of the timer progress-bar - if self.cue.duration > 0: + if self._cue.duration > 0: self.timeBar.setValue(time) # Show the time in the widget self.timeDisplay.display( - strtime(time, accurate=self._accurate_timing)) + strtime(time, accurate=self._accurateTiming)) def resizeEvent(self, event): self.update() def update(self): super().update() + self.hLayout.activate() self.layout().activate() s_width = self.nameButton.width() - 8 @@ -377,6 +380,5 @@ def update(self): s_ypos = self.nameButton.height() - s_height self.seekSlider.setGeometry(4, s_ypos, s_width, s_height) - self.statusIcon.setGeometry(4, 4, - CueWidget.ICON_SIZE, - CueWidget.ICON_SIZE) + self.statusIcon.setGeometry( + 4, 4, CueWidget.ICON_SIZE, CueWidget.ICON_SIZE) diff --git a/lisp/plugins/cart_layout/default.json b/lisp/plugins/cart_layout/default.json new file mode 100644 index 000000000..55fd53805 --- /dev/null +++ b/lisp/plugins/cart_layout/default.json @@ -0,0 +1,16 @@ +{ + "_version_": "1", + "_enabled_": true, + "autoAddPage": true, + "countdownMode": true, + "grid": { + "columns": 7, + "rows": 4 + }, + "show": { + "accurateTime": false, + "seekSliders": false, + "dBMeters": false, + "volumeControls": false + } +} \ No newline at end of file diff --git a/lisp/plugins/cart_layout/layout.py b/lisp/plugins/cart_layout/layout.py new file mode 100644 index 000000000..d1c002180 --- /dev/null +++ b/lisp/plugins/cart_layout/layout.py @@ -0,0 +1,416 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import QT_TRANSLATE_NOOP +from PyQt5.QtWidgets import QAction, QInputDialog, QMessageBox + +from lisp.core.configuration import DummyConfiguration +from lisp.core.signal import Connection +from lisp.cues.cue import Cue +from lisp.cues.cue_factory import CueFactory +from lisp.cues.cue_memento_model import CueMementoAdapter +from lisp.cues.media_cue import MediaCue +from lisp.layout.cue_layout import CueLayout +from lisp.layout.cue_menu import SimpleMenuAction, MENU_PRIORITY_CUE, MenuActionsGroup, MENU_PRIORITY_LAYOUT +from lisp.plugins.cart_layout.cue_widget import CueWidget +from lisp.plugins.cart_layout.model import CueCartModel +from lisp.plugins.cart_layout.page_widget import CartPageWidget +from lisp.plugins.cart_layout.tab_widget import CartTabWidget +from lisp.ui.ui_utils import translate + + +# TODO: custom tabs names +class CartLayout(CueLayout): + NAME = 'Cart Layout' + DESCRIPTION = translate( + 'LayoutDescription', 'Organize cues in grid like pages') + DETAILS = [ + QT_TRANSLATE_NOOP('LayoutDetails', 'Click a cue to run it'), + QT_TRANSLATE_NOOP('LayoutDetails', 'SHIFT + Click to edit a cue'), + QT_TRANSLATE_NOOP('LayoutDetails', 'CTRL + Click to select a cue'), + QT_TRANSLATE_NOOP( + 'LayoutDetails', 'To copy cues drag them while pressing CTRL'), + QT_TRANSLATE_NOOP( + 'LayoutDetails', 'To move cues drag them while pressing SHIFT') + ] + + Config = DummyConfiguration() + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self.__columns = CartLayout.Config['grid.columns'] + self.__rows = CartLayout.Config['grid.rows'] + + self._show_seek = CartLayout.Config['show.seekSliders'] + self._show_dbmeter = CartLayout.Config['show.dBMeters'] + self._show_volume = CartLayout.Config['show.volumeControls'] + self._accurate_timing = CartLayout.Config['show.accurateTime'] + self._countdown_mode = CartLayout.Config['countdownMode'] + self._auto_add_page = CartLayout.Config['autoAddPage'] + + self._cart_model = CueCartModel( + self.cue_model, self.__rows, self.__columns) + self._cart_model.item_added.connect( + self.__cue_added, Connection.QtQueued) + self._cart_model.item_removed.connect( + self.__cue_removed, Connection.QtQueued) + self._cart_model.item_moved.connect( + self.__cue_moved, Connection.QtQueued) + self._cart_model.model_reset.connect( + self.__model_reset) + self._memento_model = CueMementoAdapter(self._cart_model) + + self._cart_view = CartTabWidget() + + # Layout menu + layout_menu = self.app.window.menuLayout + + self.new_page_action = QAction(parent=layout_menu) + self.new_page_action.triggered.connect(self.add_page) + layout_menu.addAction(self.new_page_action) + + self.new_pages_action = QAction(parent=layout_menu) + self.new_pages_action.triggered.connect(self.add_pages) + layout_menu.addAction(self.new_pages_action) + + self.rm_current_page_action = QAction(parent=layout_menu) + self.rm_current_page_action.triggered.connect(self.remove_current_page) + layout_menu.addAction(self.rm_current_page_action) + + layout_menu.addSeparator() + + self.countdown_mode = QAction(parent=layout_menu) + self.countdown_mode.setCheckable(True) + self.countdown_mode.setChecked(self._countdown_mode) + self.countdown_mode.triggered.connect(self.set_countdown_mode) + layout_menu.addAction(self.countdown_mode) + + self.show_seek_action = QAction(parent=layout_menu) + self.show_seek_action.setCheckable(True) + self.show_seek_action.setChecked(self._show_seek) + self.show_seek_action.triggered.connect(self.set_seek_visible) + layout_menu.addAction(self.show_seek_action) + + self.show_dbmeter_action = QAction(parent=layout_menu) + self.show_dbmeter_action.setCheckable(True) + self.show_dbmeter_action.setChecked(self._show_dbmeter) + self.show_dbmeter_action.triggered.connect(self.set_dbmeter_visible) + layout_menu.addAction(self.show_dbmeter_action) + + self.show_volume_action = QAction(parent=layout_menu) + self.show_volume_action.setCheckable(True) + self.show_volume_action.setChecked(self._show_volume) + self.show_volume_action.triggered.connect(self.set_volume_visible) + layout_menu.addAction(self.show_volume_action) + + self.show_accurate_action = QAction(parent=layout_menu) + self.show_accurate_action.setCheckable(True) + self.show_accurate_action.setChecked(self._accurate_timing) + self.show_accurate_action.triggered.connect(self.set_accurate) + layout_menu.addAction(self.show_accurate_action) + + # Context menu actions + self._edit_actions_group = MenuActionsGroup(priority=MENU_PRIORITY_CUE) + self._edit_actions_group.add( + SimpleMenuAction( + translate('ListLayout', 'Edit cue'), + self.edit_cue, + translate('ListLayout', 'Edit selected cues'), + self.edit_cues, + ), + SimpleMenuAction( + translate('ListLayout', 'Remove cue'), + self.cue_model.remove, + translate('ListLayout', 'Remove selected cues'), + self._remove_cues + ) + ) + self.CuesMenu.add(self._edit_actions_group) + + self._media_actions_group = MenuActionsGroup( + priority=MENU_PRIORITY_CUE + 1) + self._media_actions_group.add( + SimpleMenuAction( + translate('CartLayout', 'Play'), + lambda cue: cue.start() + ), + SimpleMenuAction( + translate('CartLayout', 'Pause'), + lambda cue: cue.pause() + ), + SimpleMenuAction( + translate('CartLayout', 'Stop'), + lambda cue: cue.stop() + ) + ) + self.CuesMenu.add(self._media_actions_group, MediaCue) + + self._reset_volume_action = SimpleMenuAction( + translate('CartLayout', 'Reset volume'), + self._reset_cue_volume, + priority=MENU_PRIORITY_LAYOUT + ) + self.CuesMenu.add(self._reset_volume_action, MediaCue) + + self.retranslate() + self.add_page() + + def retranslate(self): + self.new_page_action.setText(translate('CartLayout', 'Add page')) + self.new_pages_action.setText(translate('CartLayout', 'Add pages')) + self.rm_current_page_action.setText( + translate('CartLayout', 'Remove current page')) + self.countdown_mode.setText(translate('CartLayout', 'Countdown mode')) + self.show_seek_action.setText(translate('CartLayout', 'Show seek-bars')) + self.show_dbmeter_action.setText( + translate('CartLayout', 'Show dB-meters')) + self.show_volume_action.setText(translate('CartLayout', 'Show volume')) + self.show_accurate_action.setText( + translate('CartLayout', 'Show accurate time')) + + def view(self): + return self._cart_view + + def cue_at(self, index): + return self._cart_model.item(index) + + def cues(self, cue_class=Cue): + for cue in self._cart_model: + if isinstance(cue, cue_class): + yield cue + + def selected_cues(self, cue_class=Cue): + for widget in self._widgets(): + if widget.selected and isinstance(widget.cue, cue_class): + yield widget.cue + + def select_all(self, cue_class=Cue): + for widget in self._widgets(): + if isinstance(widget.cue, cue_class): + widget.selected = True + + def deselect_all(self, cue_class=Cue): + for widget in self._widgets(): + if isinstance(widget.cue, cue_class): + widget.selected = False + + def invert_selection(self): + for widget in self._widgets(): + widget.selected = not widget.selected + + def add_pages(self): + pages, accepted = QInputDialog.getInt( + self._cart_view, + translate('CartLayout', 'Add pages'), + translate('CartLayout', 'Number of Pages:'), + value=1, min=1, max=10 + ) + + if accepted: + for _ in range(pages): + self.add_page() + + def add_page(self): + page = CartPageWidget(self.__rows, self.__columns, self._cart_view) + page.contextMenuRequested.connect(self.show_context_menu) + page.moveWidgetRequested.connect(self._move_widget) + page.copyWidgetRequested.connect(self._copy_widget) + + self._cart_view.addTab( + page, 'Page {}'.format(self._cart_view.count() + 1)) + + def remove_current_page(self): + if self._cart_view.count(): + confirm = RemovePageConfirmBox(self._cart_view) + + if confirm.exec_() == QMessageBox.Yes: + self.remove_page(self._cart_view.currentIndex()) + + def remove_page(self, index): + if self._cart_view.count() > index >= 0: + page = self._page(index) + page.moveDropEvent.disconnect() + page.copyDropEvent.disconnect() + + self._cart_model.remove_page(index) + self._cart_view.removeTab(index) + + page.deleteLater() + + # Rename every successive tab accordingly + text = translate('CartLayout', 'Page {number}') + for n in range(index, self._cart_view.count()): + self._cart_view.setTabText(n, text.format(number=n + 1)) + + def set_countdown_mode(self, mode): + self._countdown_mode = mode + for widget in self._widgets(): + widget.setCountdownMode(mode) + + def set_accurate(self, enable): + self._accurate_timing = enable + for widget in self._widgets(): + widget.showAccurateTiming(enable) + + def set_seek_visible(self, visible): + self._show_seek = visible + for widget in self._widgets(): + widget.showSeekSlider(visible) + + def set_dbmeter_visible(self, visible): + self._show_dbmeter = visible + for widget in self._widgets(): + widget.showDBMeters(visible) + + def set_volume_visible(self, visible): + self._show_volume = visible + for widget in self._widgets(): + widget.showVolumeSlider(visible) + + def to_3d_index(self, index): + page_size = self.__rows * self.__columns + + page = index // page_size + row = (index % page_size) // self.__columns + column = (index % page_size) % self.__columns + + return page, row, column + + def to_1d_index(self, index): + try: + page, row, column = index + page *= self.__rows * self.__columns + row *= self.__columns + return page + row + column + except(TypeError, ValueError): + return -1 + + def finalize(self): + # Clean layout menu + self.app.window.menuLayout.clear() + + # Clean context menu + self.CuesMenu.remove(self._edit_actions_group) + self.CuesMenu.remove(self._media_actions_group) + self.CuesMenu.remove(self._reset_volume_action) + + # Remove reference cycle + del self._edit_actions_group + del self._reset_volume_action + + def _widgets(self): + for page in self._cart_view.pages(): + yield from page.widgets() + + def _page(self, index): + """:rtype: CartPageWidget""" + return self._cart_view.widget(index) + + def _move_widget(self, widget, to_row, to_column): + new_index = self.to_1d_index( + (self._cart_view.currentIndex(), to_row, to_column)) + self._cart_model.move(widget.cue.index, new_index) + + def _copy_widget(self, widget, to_row, to_column): + new_index = self.to_1d_index( + (self._cart_view.currentIndex(), to_row, to_column)) + new_cue = CueFactory.clone_cue(widget.cue) + + self._cart_model.insert(new_cue, new_index) + + def _cue_context_menu(self, position): + current_page = self._cart_view.currentWidget() + cue_widget = current_page.widgetAt(current_page.mapFromGlobal(position)) + + if cue_widget.selected: + # If the context menu is requested from a selected cue-widget + cues = list(self.selected_cues()) + else: + cues = [cue_widget.cue] + + self.show_cue_context_menu(cues, position) + + def _remove_cue_action(self, cue): + self._cart_model.remove(cue) + + def _reset_cue_volume(self, cue): + page, row, column = self.to_3d_index(cue.index) + widget = self._page(page).widget(row, column) + + widget.resetVolume() + + def __cue_added(self, cue): + widget = CueWidget(cue) + + widget.contextMenuRequested.connect(self._cue_context_menu) + widget.cueExecuted.connect(self.cue_executed.emit) + widget.editRequested.connect(self.edit_cue) + + widget.showAccurateTiming(self._accurate_timing) + widget.setCountdownMode(self._countdown_mode) + widget.showVolumeSlider(self._show_volume) + widget.showDBMeters(self._show_dbmeter) + widget.showSeekSlider(self._show_seek) + + page, row, column = self.to_3d_index(cue.index) + if page >= self._cart_view.count(): + self.add_page() + + self._page(page).addWidget(widget, row, column) + self._cart_view.setCurrentIndex(page) + + def __cue_removed(self, cue): + page, row, column = self.to_3d_index(cue.index) + widget = self._page(page).takeWidget(row, column) + + widget.cueExecuted.disconnect() + widget.contextMenuRequested.disconnect() + widget.editRequested.disconnect() + + widget.deleteLater() + + def __cue_moved(self, old_index, new_index): + o_page, o_row, o_column = self.to_3d_index(old_index) + n_page, n_row, n_column = self.to_3d_index(new_index) + + if o_page == n_page: + self._page(n_page).moveWidget(o_row, o_column, n_row, n_column) + else: + widget = self._page(o_page).takeWidget(o_row, o_column) + self._page(n_page).addWidget(widget, n_row, n_column) + + def __model_reset(self): + for page in self._cart_view.pages(): + page.reset() + + +class RemovePageConfirmBox(QMessageBox): + def __init__(self, *args): + super().__init__(*args) + + self.setIcon(self.Question) + self.setWindowTitle(translate('CartLayout', 'Warning')) + self.setText( + translate('CartLayout', 'Every cue in the page will be lost.')) + self.setInformativeText( + translate('CartLayout', 'Are you sure to continue?')) + + self.setStandardButtons(QMessageBox.Yes | QMessageBox.No) + self.setDefaultButton(QMessageBox.No) diff --git a/lisp/layouts/cart_layout/cue_cart_model.py b/lisp/plugins/cart_layout/model.py similarity index 100% rename from lisp/layouts/cart_layout/cue_cart_model.py rename to lisp/plugins/cart_layout/model.py diff --git a/lisp/layouts/cart_layout/page_widget.py b/lisp/plugins/cart_layout/page_widget.py similarity index 61% rename from lisp/layouts/cart_layout/page_widget.py rename to lisp/plugins/cart_layout/page_widget.py index b9b0437d6..e317c563d 100644 --- a/lisp/layouts/cart_layout/page_widget.py +++ b/lisp/plugins/cart_layout/page_widget.py @@ -19,14 +19,17 @@ import math -from PyQt5.QtCore import pyqtSignal, Qt -from PyQt5.QtWidgets import QWidget, QGridLayout, QSizePolicy, qApp +from PyQt5.QtCore import pyqtSignal, Qt, QPoint +from PyQt5.QtWidgets import QWidget, QGridLayout, QSizePolicy from sortedcontainers import SortedDict +from lisp.core.util import typename -class PageWidget(QWidget): - move_drop_event = pyqtSignal(object, int, int) - copy_drop_event = pyqtSignal(object, int, int) + +class CartPageWidget(QWidget): + contextMenuRequested = pyqtSignal(QPoint) + moveWidgetRequested = pyqtSignal(object, int, int) + copyWidgetRequested = pyqtSignal(object, int, int) DRAG_MAGIC = 'LiSP_Drag&Drop' @@ -40,9 +43,9 @@ def __init__(self, rows, columns, *args): self.setLayout(QGridLayout()) self.layout().setContentsMargins(4, 4, 4, 4) - self.init_layout() + self.initLayout() - def init_layout(self): + def initLayout(self): for row in range(0, self.__rows): self.layout().setRowStretch(row, 1) # item = QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding) @@ -53,8 +56,8 @@ def init_layout(self): # item = QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum) # self.layout().addItem(item, 0, column) - def add_widget(self, widget, row, column): - self._check_index(row, column) + def addWidget(self, widget, row, column): + self._checkIndex(row, column) if (row, column) not in self.__widgets: widget.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.__widgets[(row, column)] = widget @@ -63,8 +66,8 @@ def add_widget(self, widget, row, column): else: raise IndexError('cell {} already used'.format((row, column))) - def take_widget(self, row, column): - self._check_index(row, column) + def takeWidget(self, row, column): + self._checkIndex(row, column) if (row, column) in self.__widgets: widget = self.__widgets.pop((row, column)) widget.hide() @@ -73,15 +76,15 @@ def take_widget(self, row, column): else: raise IndexError('cell {} is empty'.format((row, column))) - def move_widget(self, o_row, o_column, n_row, n_column): - widget = self.take_widget(o_row, o_column) - self.add_widget(widget, n_row, n_column) + def moveWidget(self, o_row, o_column, n_row, n_column): + widget = self.takeWidget(o_row, o_column) + self.addWidget(widget, n_row, n_column) def widget(self, row, column): - self._check_index(row, column) + self._checkIndex(row, column) return self.__widgets.get((row, column)) - def index(self, widget): + def indexOf(self, widget): for index, f_widget in self.__widgets.items(): if widget is f_widget: return index @@ -94,51 +97,23 @@ def widgets(self): def reset(self): self.__widgets.clear() - def dragEnterEvent(self, event): - if event.mimeData().hasText(): - if event.mimeData().text() == PageWidget.DRAG_MAGIC: - event.accept() - else: - event.ignore() - else: - event.ignore() - - def dragLeaveEvent(self, event): - event.ignore() + def contextMenuEvent(self, event): + self.contextMenuRequested.emit(event.globalPos()) - def dropEvent(self, event): - row, column = self._event_index(event) - if self.layout().itemAtPosition(row, column) is None: - if qApp.keyboardModifiers() == Qt.ControlModifier: - event.setDropAction(Qt.MoveAction) - event.accept() - self.move_drop_event.emit(event.source(), row, column) - elif qApp.keyboardModifiers() == Qt.ShiftModifier: - event.setDropAction(Qt.CopyAction) - self.copy_drop_event.emit(event.source(), row, column) - event.accept() - - event.ignore() - - def dragMoveEvent(self, event): - row, column = self._event_index(event) - if self.layout().itemAtPosition(row, column) is None: + def dragEnterEvent(self, event): + if event.mimeData().text() == CartPageWidget.DRAG_MAGIC: event.accept() - else: - event.ignore() - def _check_index(self, row, column): - if not isinstance(row, int): - raise TypeError('rows index must be integers, not {}' - .format(row.__class__.__name__)) - if not isinstance(column, int): - raise TypeError('columns index must be integers, not {}' - .format(column.__class__.__name__)) + def dropEvent(self, event): + row, column = self.indexAt(event.pos()) - if not 0 <= row < self.__rows or not 0 <= column < self.__columns: - raise IndexError('index out of bound {}'.format((row, column))) + if self.layout().itemAtPosition(row, column) is None: + if event.proposedAction() == Qt.MoveAction: + self.moveWidgetRequested.emit(event.source(), row, column) + elif event.proposedAction() == Qt.CopyAction: + self.copyWidgetRequested.emit(event.source(), row, column) - def _event_index(self, event): + def indexAt(self, pos): # Margins and spacings are equals space = self.layout().horizontalSpacing() margin = self.layout().contentsMargins().right() @@ -146,7 +121,22 @@ def _event_index(self, event): r_size = (self.height() + margin * 2) // self.__rows + space c_size = (self.width() + margin * 2) // self.__columns + space - row = math.ceil(event.pos().y() / r_size) - 1 - column = math.ceil(event.pos().x() / c_size) - 1 + row = math.ceil(pos.y() / r_size) - 1 + column = math.ceil(pos.x() / c_size) - 1 return row, column + + def widgetAt(self, pos): + return self.widget(*self.indexAt(pos)) + + def _checkIndex(self, row, column): + if not isinstance(row, int): + raise TypeError( + 'rows index must be integers, not {}'.format(typename(row))) + if not isinstance(column, int): + raise TypeError( + 'columns index must be integers, not {}'.format( + typename(column))) + + if not 0 <= row < self.__rows or not 0 <= column < self.__columns: + raise IndexError('index out of bound {}'.format((row, column))) diff --git a/lisp/layouts/cart_layout/cart_layout_settings.py b/lisp/plugins/cart_layout/settings.py similarity index 76% rename from lisp/layouts/cart_layout/cart_layout_settings.py rename to lisp/plugins/cart_layout/settings.py index f1a3ea704..71eb8e7c3 100644 --- a/lisp/layouts/cart_layout/cart_layout_settings.py +++ b/lisp/plugins/cart_layout/settings.py @@ -80,14 +80,14 @@ def __init__(self, config, **kwargs): self.retranslateUi() # Load data - self.columnsSpin.setValue(config['cartLayout.gridColumns']) - self.rowsSpin.setValue(config['cartLayout.gridRows']) - self.showSeek.setChecked(config['cartLayout.showSeek']) - self.showDbMeters.setChecked(config['cartLayout.showDbMeters']) - self.showAccurate.setChecked(config['cartLayout.showAccurate']) - self.showVolume.setChecked(config['cartLayout.showVolume']) - self.countdownMode.setChecked(config['cartLayout.countdown']) - self.autoAddPage.setChecked(config['cartLayout.autoAddPage']) + self.columnsSpin.setValue(config['grid.columns']) + self.rowsSpin.setValue(config['grid.rows']) + self.showSeek.setChecked(config['show.seekSliders']) + self.showDbMeters.setChecked(config['show.dBMeters']) + self.showAccurate.setChecked(config['show.accurateTime']) + self.showVolume.setChecked(config['show.volumeControls']) + self.countdownMode.setChecked(config['countdownMode']) + self.autoAddPage.setChecked(config['autoAddPage']) def retranslateUi(self): self.behaviorsGroup.setTitle( @@ -104,13 +104,13 @@ def retranslateUi(self): self.rowsLabel.setText(translate('CartLayout', 'Number of rows')) def applySettings(self): - self.config['cartLayout.gridColumns'] = self.columnsSpin.value() - self.config['cartLayout.gridRows'] = self.rowsSpin.value() - self.config['cartLayout.showDbMeters'] = self.showDbMeters.isChecked() - self.config['cartLayout.showSeek'] = self.showSeek.isChecked() - self.config['cartLayout.showAccurate'] = self.showAccurate.isChecked() - self.config['cartLayout.showVolume'] = self.showVolume.isChecked() - self.config['cartLayout.countdown'] = self.countdownMode.isChecked() - self.config['cartLayout.autoAddPage'] = self.autoAddPage.isChecked() - - self.config.write() \ No newline at end of file + self.config['grid.columns'] = self.columnsSpin.value() + self.config['grid.rows'] = self.rowsSpin.value() + self.config['show.dBMeters'] = self.showDbMeters.isChecked() + self.config['show.seekSliders'] = self.showSeek.isChecked() + self.config['show.accurateTime'] = self.showAccurate.isChecked() + self.config['show.volumeControls'] = self.showVolume.isChecked() + self.config['countdownMode'] = self.countdownMode.isChecked() + self.config['autoAddPage'] = self.autoAddPage.isChecked() + + self.config.write() diff --git a/lisp/plugins/cart_layout/tab_widget.py b/lisp/plugins/cart_layout/tab_widget.py new file mode 100644 index 000000000..521c318ba --- /dev/null +++ b/lisp/plugins/cart_layout/tab_widget.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QTabWidget + + +class CartTabWidget(QTabWidget): + DRAG_MAGIC = 'LiSP_Drag&Drop' + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.tabBar().setObjectName('CartTabBar') + self.setFocusPolicy(Qt.StrongFocus) + self.setAcceptDrops(True) + + def dragEnterEvent(self, event): + if event.mimeData().text() == CartTabWidget.DRAG_MAGIC: + event.accept() + + def dragMoveEvent(self, event): + if self.tabBar().contentsRect().contains(event.pos()): + self.setCurrentIndex(self.tabBar().tabAt(event.pos())) + event.accept() + + def dropEvent(self, event): + event.ignore() + + def pages(self): + for index in range(self.count()): + yield self.widget(index) diff --git a/lisp/plugins/controller/controller.py b/lisp/plugins/controller/controller.py index f8e6efc57..9bd51d78b 100644 --- a/lisp/plugins/controller/controller.py +++ b/lisp/plugins/controller/controller.py @@ -56,7 +56,7 @@ def __init__(self, app): self.app.cue_model.item_removed.connect(self.__cue_removed) # Register settings-page - CueSettingsRegistry().add_item(ControllerSettings) + CueSettingsRegistry().add(ControllerSettings) # Load available protocols self.__load_protocols() diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 1a4e4ec1c..b175109df 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -295,7 +295,7 @@ def capture_message(self): self.capturedMessage['types'], args, self._default_action - ) + ) self.__osc.server.new_message.disconnect(self.__show_message) @@ -319,7 +319,7 @@ def __new_message(self): path = dialog.pathEdit.text() if len(path) < 2 or path[0] is not '/': QMessageBox.warning( - None, 'Warning', + self, 'Warning', 'Osc path seems not valid, \ndo not forget to edit the ' 'path later.', ) diff --git a/lisp/plugins/gst_backend/elements/auto_sink.py b/lisp/plugins/gst_backend/elements/auto_sink.py index 8285bded7..e2a58b97c 100644 --- a/lisp/plugins/gst_backend/elements/auto_sink.py +++ b/lisp/plugins/gst_backend/elements/auto_sink.py @@ -36,4 +36,4 @@ def __init__(self, pipeline): self.pipeline.add(self.auto_sink) def sink(self): - return self.auto_sink \ No newline at end of file + return self.auto_sink diff --git a/lisp/plugins/gst_backend/elements/user_element.py b/lisp/plugins/gst_backend/elements/user_element.py index 7b35f5b7e..0ed4d72b9 100644 --- a/lisp/plugins/gst_backend/elements/user_element.py +++ b/lisp/plugins/gst_backend/elements/user_element.py @@ -88,4 +88,4 @@ def __prepare_bin(self, value): # Unblock the stream pad.remove_probe(probe) if playing: - self.pipeline.set_state(Gst.State.PLAYING) \ No newline at end of file + self.pipeline.set_state(Gst.State.PLAYING) diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index d23df1dc6..47224b816 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -59,7 +59,7 @@ def __init__(self, app): AppConfigurationDialog.registerSettingsPage( 'plugins.gst', GstSettings, GstBackend.Config) # Add MediaCue settings widget to the CueLayout - CueSettingsRegistry().add_item(GstMediaSettings, MediaCue) + CueSettingsRegistry().add(GstMediaSettings, MediaCue) # Register GstMediaCue factory CueFactory.register_factory('GstMediaCue', GstCueFactory(tuple())) @@ -121,4 +121,4 @@ def _add_uri_audio_cue(self): cue.name = os.path.splitext(os.path.basename(file))[0] self.app.cue_model.add(cue) - QApplication.restoreOverrideCursor() \ No newline at end of file + QApplication.restoreOverrideCursor() diff --git a/lisp/plugins/gst_backend/gst_element.py b/lisp/plugins/gst_backend/gst_element.py index 07e795707..1ff0bad09 100644 --- a/lisp/plugins/gst_backend/gst_element.py +++ b/lisp/plugins/gst_backend/gst_element.py @@ -20,6 +20,7 @@ from lisp.backend.media_element import MediaElement, ElementType from lisp.core.has_properties import HasInstanceProperties from lisp.core.properties import Property, InstanceProperty +from lisp.core.util import typename class GstProperty(Property): @@ -147,7 +148,7 @@ def append(self, element): self.elements.append(element) # Add a property for the new added element - element_name = element.__class__.__name__ + element_name = typename(element) setattr(self, element_name, InstanceProperty(default=None)) setattr(self, element_name, element) @@ -165,7 +166,7 @@ def pop(self, index): element.dispose() # Remove the element corresponding property - delattr(self, element.__class__.__name__) + delattr(self, typename(element)) def clear(self): while self.elements: diff --git a/lisp/plugins/gst_backend/gst_settings.py b/lisp/plugins/gst_backend/gst_settings.py index 714afa9e3..53db95c5c 100644 --- a/lisp/plugins/gst_backend/gst_settings.py +++ b/lisp/plugins/gst_backend/gst_settings.py @@ -47,4 +47,4 @@ def retranslateUi(self): def applySettings(self): self.config['pipeline'] = list(self.pipeEdit.get_pipe()) - self.config.write() \ No newline at end of file + self.config.write() diff --git a/lisp/plugins/gst_backend/gst_utils.py b/lisp/plugins/gst_backend/gst_utils.py index a42744f5d..1fcde6fc3 100644 --- a/lisp/plugins/gst_backend/gst_utils.py +++ b/lisp/plugins/gst_backend/gst_utils.py @@ -62,4 +62,4 @@ def parse_tag(gst_tag_list, tag_name, parsed_tags): parsed_tags[tag_name] = gst_tag_list.get_value_index(tag_name, 0) gst_tag_list.foreach(parse_tag, parsed_tags) - return parsed_tags \ No newline at end of file + return parsed_tags diff --git a/lisp/plugins/list_layout/__init__.py b/lisp/plugins/list_layout/__init__.py new file mode 100644 index 000000000..64334a175 --- /dev/null +++ b/lisp/plugins/list_layout/__init__.py @@ -0,0 +1,19 @@ +from lisp.core.plugin import Plugin +from lisp.layout import register_layout +from lisp.plugins.list_layout.settings import ListLayoutSettings +from lisp.plugins.list_layout.layout import ListLayout as _ListLayout +from lisp.ui.settings.app_configuration import AppConfigurationDialog + + +class ListLayout(Plugin): + Name = 'List Layout' + Description = 'Provide a layout that organize the cues in a list' + Authors = ('Francesco Ceruti', ) + + def __init__(self, app): + super().__init__(app) + + _ListLayout.Config = ListLayout.Config + register_layout(_ListLayout) + AppConfigurationDialog.registerSettingsPage( + 'layouts.layout_layout', ListLayoutSettings, ListLayout.Config) diff --git a/lisp/layouts/list_layout/control_buttons.py b/lisp/plugins/list_layout/control_buttons.py similarity index 100% rename from lisp/layouts/list_layout/control_buttons.py rename to lisp/plugins/list_layout/control_buttons.py diff --git a/lisp/plugins/list_layout/default.json b/lisp/plugins/list_layout/default.json new file mode 100644 index 000000000..67c1075ee --- /dev/null +++ b/lisp/plugins/list_layout/default.json @@ -0,0 +1,16 @@ +{ + "_version_": "1.1", + "_enabled_": true, + "show": { + "dBMeters": true, + "seekSliders": true, + "accurateTime": false, + "playingCues": true + }, + "autoContinue": true, + "goKey": "Space", + "stopCueFade": true, + "pauseCueFade": true, + "resumeCueFade": true, + "interruptCueFade": true +} \ No newline at end of file diff --git a/lisp/layouts/list_layout/info_panel.py b/lisp/plugins/list_layout/info_panel.py similarity index 72% rename from lisp/layouts/list_layout/info_panel.py rename to lisp/plugins/list_layout/info_panel.py index ccf5b7ed5..87ab15035 100644 --- a/lisp/layouts/list_layout/info_panel.py +++ b/lisp/plugins/list_layout/info_panel.py @@ -29,7 +29,7 @@ def __init__(self, **kwargs): self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) - self.__cue = None + self._item = None # cue name self.cueName = QLineEdit(self) @@ -52,25 +52,30 @@ def retranslateUi(self): self.cueDescription.setPlaceholderText( translate('ListLayoutInfoPanel', 'Cue description')) - def cue_changed(self, cue): - if self.__cue is not None: - self.__cue.changed('name').disconnect(self.__name_changed) - self.__cue.changed('description').disconnect(self.__descr_changed) + @property + def item(self): + return self._item - self.__cue = cue + @item.setter + def item(self, item): + if self._item is not None: + self._item.cue.changed('name').disconnect(self._name_changed) + self._item.cue.changed('description').disconnect(self._desc_changed) - if self.__cue is not None: - self.__cue.changed('name').connect(self.__name_changed) - self.__cue.changed('description').connect(self.__descr_changed) + self._item = item - self.__name_changed(self.__cue.name) - self.__descr_changed(self.__cue.description) + if self._item is not None: + self._item.cue.changed('name').connect(self._name_changed) + self._item.cue.changed('description').connect(self._desc_changed) + + self._name_changed(self._item.cue.name) + self._desc_changed(self._item.cue.description) else: self.cueName.clear() self.cueDescription.clear() - def __name_changed(self, name): - self.cueName.setText(str(self.__cue.index) + ' - ' + name) + def _name_changed(self, name): + self.cueName.setText(str(self.item.index + 1) + ' → ' + name) - def __descr_changed(self, description): + def _desc_changed(self, description): self.cueDescription.setText(description) diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py new file mode 100644 index 000000000..6b7186bd8 --- /dev/null +++ b/lisp/plugins/list_layout/layout.py @@ -0,0 +1,344 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2017 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from enum import Enum + +from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP +from PyQt5.QtGui import QKeySequence +from PyQt5.QtWidgets import QWidget, QAction, qApp, QGridLayout, \ + QPushButton, QSizePolicy + +from lisp.core.configuration import DummyConfiguration +from lisp.core.signal import Connection +from lisp.cues.cue import Cue, CueAction +from lisp.cues.cue_memento_model import CueMementoAdapter +from lisp.layout.cue_layout import CueLayout +from lisp.layout.cue_menu import SimpleMenuAction, MENU_PRIORITY_CUE, MenuActionsGroup +from lisp.plugins.list_layout.control_buttons import ShowControlButtons +from lisp.plugins.list_layout.info_panel import InfoPanel +from lisp.plugins.list_layout.list_view import CueListView +from lisp.plugins.list_layout.models import CueListModel, RunningCueModel +from lisp.plugins.list_layout.playing_view import RunningCuesListWidget +from lisp.ui.ui_utils import translate + + +class EndListBehavior(Enum): + Stop = 'Stop' + Restart = 'Restart' + + +class ListLayout(QWidget, CueLayout): + NAME = 'List Layout' + DESCRIPTION = QT_TRANSLATE_NOOP( + 'LayoutDescription', 'Organize the cues in a list') + DETAILS = [ + QT_TRANSLATE_NOOP( + 'LayoutDetails', 'SHIFT + Space or Double-Click to edit a cue'), + QT_TRANSLATE_NOOP( + 'LayoutDetails', 'CTRL + Left Click to select cues'), + QT_TRANSLATE_NOOP( + 'LayoutDetails', 'To copy cues drag them while pressing CTRL'), + QT_TRANSLATE_NOOP( + 'LayoutDetails', 'To move cues drag them') + ] + Config = DummyConfiguration() + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.setLayout(QGridLayout()) + self.layout().setContentsMargins(0, 0, 0, 0) + + self._list_model = CueListModel(self.cue_model) + self._list_model.item_added.connect(self.__cue_added) + self._memento_model = CueMementoAdapter(self._list_model) + + self._playing_model = RunningCueModel(self.cue_model) + self._current_index = -1 + self._context_item = None + + self._show_dbmeter = ListLayout.Config['show.dBMeters'] + self._show_playing = ListLayout.Config['show.playingCues'] + self._show_seekslider = ListLayout.Config['show.seekSliders'] + self._accurate_time = ListLayout.Config['show.accurateTime'] + self._auto_continue = ListLayout.Config['autoContinue'] + self._go_key_sequence = QKeySequence( + ListLayout.Config['goKey'], QKeySequence.NativeText) + + # Layout menu + menuLayout = self.app.window.menuLayout + + self.showPlayingAction = QAction(parent=menuLayout) + self.showPlayingAction.setCheckable(True) + self.showPlayingAction.setChecked(self._show_playing) + self.showPlayingAction.triggered.connect(self._set_playing_visible) + menuLayout.addAction(self.showPlayingAction) + + self.showDbMeterAction = QAction(parent=menuLayout) + self.showDbMeterAction.setCheckable(True) + self.showDbMeterAction.setChecked(self._show_dbmeter) + self.showDbMeterAction.triggered.connect(self._set_dbmeter_visible) + menuLayout.addAction(self.showDbMeterAction) + + self.showSeekAction = QAction(parent=menuLayout) + self.showSeekAction.setCheckable(True) + self.showSeekAction.setChecked(self._show_seekslider) + self.showSeekAction.triggered.connect(self._set_seeksliders_visible) + menuLayout.addAction(self.showSeekAction) + + self.accurateTimingAction = QAction(parent=menuLayout) + self.accurateTimingAction.setCheckable(True) + self.accurateTimingAction.setChecked(self._accurate_time) + self.accurateTimingAction.triggered.connect(self._set_accurate_time) + menuLayout.addAction(self.accurateTimingAction) + + self.autoNextAction = QAction(parent=menuLayout) + self.autoNextAction.setCheckable(True) + self.autoNextAction.setChecked(self._auto_continue) + self.autoNextAction.triggered.connect(self._set_auto_next) + menuLayout.addAction(self.autoNextAction) + + self.selectionModeAction = QAction(parent=menuLayout) + self.selectionModeAction.setCheckable(True) + self.selectionModeAction.setChecked(False) + self.selectionModeAction.triggered.connect(self._set_selection_mode) + menuLayout.addAction(self.selectionModeAction) + + # GO-BUTTON (top-left) + self.goButton = QPushButton('GO', self) + self.goButton.setFocusPolicy(Qt.NoFocus) + self.goButton.setFixedWidth(120) + self.goButton.setFixedHeight(100) + self.goButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) + self.goButton.setStyleSheet('font-size: 48pt;') + self.goButton.clicked.connect(self.__go_slot) + self.layout().addWidget(self.goButton, 0, 0) + + # INFO PANEL (top center) + self.infoPanel = InfoPanel() + self.infoPanel.setFixedHeight(100) + self.layout().addWidget(self.infoPanel, 0, 1) + + # CONTROL-BUTTONS (top-right) + self.controlButtons = ShowControlButtons(parent=self) + self.controlButtons.setFixedHeight(100) + self.controlButtons.stopButton.clicked.connect(self.stop_all) + self.controlButtons.pauseButton.clicked.connect(self.pause_all) + self.controlButtons.fadeInButton.clicked.connect(self.fadein_all) + self.controlButtons.fadeOutButton.clicked.connect(self.fadeout_all) + self.controlButtons.resumeButton.clicked.connect(self.resume_all) + self.controlButtons.interruptButton.clicked.connect(self.interrupt_all) + self.layout().addWidget(self.controlButtons, 0, 2) + + # CUE VIEW (center left) + self.listView = CueListView(self._list_model, self) + self.listView.setSelectionMode(CueListView.NoSelection) + self.listView.itemDoubleClicked.connect(self._double_clicked) + self.listView.contextMenuInvoked.connect(self._context_invoked) + self.listView.keyPressed.connect(self._key_press_event) + self.layout().addWidget(self.listView, 1, 0, 1, 2) + + # PLAYING VIEW (center right) + self.playView = RunningCuesListWidget(self._playing_model, parent=self) + self.playView.dbmeter_visible = self._show_dbmeter + self.playView.accurate_time = self._accurate_time + self.playView.seek_visible = self._show_seekslider + self.playView.setMinimumWidth(300) + self.playView.setMaximumWidth(300) + self.layout().addWidget(self.playView, 1, 2) + + self._set_playing_visible(self._show_playing) + + # Context menu actions + self._edit_actions_group = MenuActionsGroup(priority=MENU_PRIORITY_CUE) + self._edit_actions_group.add( + SimpleMenuAction( + translate('ListLayout', 'Edit cue'), + self.edit_cue, + translate('ListLayout', 'Edit selected cues'), + self.edit_cues, + ), + SimpleMenuAction( + translate('ListLayout', 'Remove cue'), + self.cue_model.remove, + translate('ListLayout', 'Remove selected cues'), + self._remove_cues + ), + ) + + self.CuesMenu.add(self._edit_actions_group) + + self.retranslateUi() + + def retranslateUi(self): + self.showPlayingAction.setText( + translate('ListLayout', 'Show playing cues')) + self.showDbMeterAction.setText( + translate('ListLayout', 'Show dB-meters')) + self.showSeekAction.setText(translate('ListLayout', 'Show seek-bars')) + self.accurateTimingAction.setText( + translate('ListLayout', 'Show accurate time')) + self.autoNextAction.setText( + translate('ListLayout', 'Auto-select next cue')) + self.selectionModeAction.setText( + translate('ListLayout', 'Selection mode')) + + def cues(self, cue_type=Cue): + yield from self._list_model + + def view(self): + return self + + def standby_index(self): + return self.listView.standbyIndex() + + def set_standby_index(self, index): + self.listView.setStandbyIndex(index) + + def go(self, action=CueAction.Default, advance=1): + standby_cue = self.standby_cue() + if standby_cue is not None: + standby_cue.execute(action) + self.cue_executed.emit(standby_cue) + + if self._auto_continue: + self.set_standby_index(self.standby_index() + advance) + + def cue_at(self, index): + return self._list_model.item(index) + + def contextMenuEvent(self, event): + if self.listView.geometry().contains(event.pos()): + self.show_context_menu(event.globalPos()) + + def selected_cues(self, cue_type=Cue): + for item in self.listView.selectedItems(): + yield self._list_model.item(self.listView.indexOfTopLevelItem(item)) + + def finalize(self): + # Clean layout menu + self.app.window.menuLayout.clear() + + # Clean context-menu + self.CuesMenu.remove(self._edit_actions_group) + + # Remove reference cycle + del self._edit_actions_group + + def select_all(self, cue_class=Cue): + for index in range(self.listView.topLevelItemCount()): + if isinstance(self._list_model.item(index), cue_class): + self.listView.topLevelItem(index).setSelected(True) + + def deselect_all(self, cue_class=Cue): + for index in range(self.listView.topLevelItemCount()): + if isinstance(self._list_model.item(index), cue_class): + self.listView.topLevelItem(index).setSelected(False) + + def invert_selection(self): + for index in range(self.listView.topLevelItemCount()): + item = self.listView.topLevelItem(index) + item.setSelected(not item.isSelected()) + + def _key_press_event(self, event): + event.ignore() + + if not event.isAutoRepeat(): + keys = event.key() + modifiers = event.modifiers() + + if modifiers & Qt.ShiftModifier: + keys += Qt.SHIFT + if modifiers & Qt.ControlModifier: + keys += Qt.CTRL + if modifiers & Qt.AltModifier: + keys += Qt.ALT + if modifiers & Qt.MetaModifier: + keys += Qt.META + + if QKeySequence(keys) in self._go_key_sequence: + event.accept() + self.go() + elif event.key() == Qt.Key_Space: + if qApp.keyboardModifiers() == Qt.ShiftModifier: + event.accept() + + cue = self.standby_cue() + if cue is not None: + self.edit_cue(cue) + + def _set_accurate_time(self, accurate): + self._accurate_time = accurate + self.playView.accurate_time = accurate + + def _set_auto_next(self, enable): + self._auto_continue = enable + + def _set_seeksliders_visible(self, visible): + self._show_seekslider = visible + self.playView.seek_visible = visible + + def _set_dbmeter_visible(self, visible): + self._show_dbmeter = visible + self.playView.dbmeter_visible = visible + + def _set_playing_visible(self, visible): + self._show_playing = visible + self.playView.setVisible(visible) + self.controlButtons.setVisible(visible) + + def _set_selection_mode(self, enable): + if enable: + self.listView.setSelectionMode(self.listView.ExtendedSelection) + else: + self.deselect_all() + self.listView.setSelectionMode(self.listView.NoSelection) + + def _double_clicked(self): + cue = self.standby_cue() + if cue is not None: + self.edit_cue(cue) + + def _context_invoked(self, event): + cues = list(self.selected_cues()) + if not cues: + context_index = self.listView.indexAt(event.pos()) + if context_index.isValid(): + cues.append(self._list_model.item(context_index.row())) + else: + return + + self.show_cue_context_menu(cues, event.globalPos()) + + def __go_slot(self): + self.go() + + def __cue_added(self, cue): + cue.next.connect(self.__cue_next, Connection.QtQueued) + + def __cue_next(self, cue): + try: + next_index = cue.index + 1 + if next_index < len(self._list_model): + next_cue = self._list_model.item(next_index) + next_cue.cue.execute() + + if self._auto_continue and next_cue is self.standby_cue(): + self.set_standby_index(next_index + 1) + except(IndexError, KeyError): + pass diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py new file mode 100644 index 000000000..4b359c898 --- /dev/null +++ b/lisp/plugins/list_layout/list_view.py @@ -0,0 +1,260 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5 import QtCore +from PyQt5.QtCore import pyqtSignal, Qt, QDataStream, QIODevice, \ + QT_TRANSLATE_NOOP +from PyQt5.QtGui import QKeyEvent, QContextMenuEvent, QBrush, QColor +from PyQt5.QtWidgets import QTreeWidget, QHeaderView, QTreeWidgetItem + +from lisp.core.signal import Connection +from lisp.cues.cue_factory import CueFactory +from lisp.plugins.list_layout.list_widgets import CueStatusIcons, NameWidget, \ + PreWaitWidget, CueTimeWidget, NextActionIcon, PostWaitWidget, IndexWidget +from lisp.ui.ui_utils import translate + + +class ListColumn: + def __init__(self, name, widget, resize=None, width=None, visible=True): + self.baseName = name + self.widget = widget + self.resize = resize + self.width = width + self.visible = visible + + @property + def name(self): + return translate('ListLayoutHeader', self.baseName) + + +class CueTreeWidgetItem(QTreeWidgetItem): + def __init__(self, cue, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.cue = cue + self.current = False + + +# TODO: consider using a custom Model/View +class CueListView(QTreeWidget): + keyPressed = pyqtSignal(QKeyEvent) + contextMenuInvoked = pyqtSignal(QContextMenuEvent) + + # TODO: add ability to show/hide + # TODO: implement columns (cue-type / target / etc..) + COLUMNS = [ + ListColumn('', CueStatusIcons, QHeaderView.Fixed, width=45), + ListColumn('#', IndexWidget, QHeaderView.ResizeToContents), + ListColumn( + QT_TRANSLATE_NOOP('ListLayoutHeader', 'Cue'), + NameWidget, + QHeaderView.Stretch + ), + ListColumn( + QT_TRANSLATE_NOOP('ListLayoutHeader', 'Pre wait'), + PreWaitWidget + ), + ListColumn( + QT_TRANSLATE_NOOP('ListLayoutHeader', 'Action'), + CueTimeWidget + ), + ListColumn( + QT_TRANSLATE_NOOP('ListLayoutHeader', 'Post wait'), + PostWaitWidget + ), + ListColumn('', NextActionIcon, QHeaderView.Fixed, width=18) + ] + + ITEM_DEFAULT_BG = QBrush(Qt.transparent) + ITEM_CURRENT_BG = QBrush(QColor(250, 220, 0, 100)) + + def __init__(self, listModel, parent=None): + """ + :type listModel: lisp.plugins.list_layout.models.CueListModel + """ + super().__init__(parent) + + self.__itemMoving = False + self.__scrollRangeGuard = False + + # Watch for model changes + self._model = listModel + self._model.item_added.connect(self.__cueAdded, Connection.QtQueued) + self._model.item_moved.connect(self.__cueMoved, Connection.QtQueued) + self._model.item_removed.connect(self.__cueRemoved, Connection.QtQueued) + self._model.model_reset.connect(self.__modelReset) + + # Setup the columns headers + self.setHeaderLabels((c.name for c in CueListView.COLUMNS)) + for i, column in enumerate(CueListView.COLUMNS): + if column.resize is not None: + self.header().setSectionResizeMode(i, column.resize) + if column.width is not None: + self.setColumnWidth(i, column.width) + + self.header().setDragEnabled(False) + self.header().setStretchLastSection(False) + + self.setDragDropMode(self.InternalMove) + + # Set some visual options + self.setIndentation(0) + self.setAlternatingRowColors(True) + self.setVerticalScrollMode(self.ScrollPerItem) + + # This allow to have some spared space at the end of the scroll-area + self.verticalScrollBar().rangeChanged.connect(self.__updateScrollRange) + self.currentItemChanged.connect(self.__currentItemChanged) + + def dropEvent(self, event): + # Decode mimedata information about the drag&drop event, since only + # internal movement are allowed we assume the data format is correct + data = event.mimeData().data('application/x-qabstractitemmodeldatalist') + stream = QDataStream(data, QIODevice.ReadOnly) + + # Get the starting-item row + to_index = self.indexAt(event.pos()).row() + if not 0 <= to_index <= len(self._model): + to_index = len(self._model) + + rows = [] + while not stream.atEnd(): + row = stream.readInt() + # Skip column and data + stream.readInt() + for i in range(stream.readInt()): + stream.readInt() + stream.readQVariant() + + if rows and row == rows[-1]: + continue + + rows.append(row) + + if event.proposedAction() == Qt.MoveAction: + before = 0 + after = 0 + for row in sorted(rows): + if row < to_index: + self._model.move(row - before, to_index) + before += 1 + elif row > to_index: + # if we have already moved something we need to shift, + # bool(before) is evaluated as 1 (True) or 0 (False) + self._model.move(row, to_index + after + bool(before)) + after += 1 + elif event.proposedAction() == Qt.CopyAction: + # TODO: add a copy/clone method to the model? + new_cues = [] + for row in sorted(rows): + new_cues.append(CueFactory.clone_cue(self._model.item(row))) + + for cue in new_cues: + self._model.insert(cue, to_index) + to_index += 1 + + def contextMenuEvent(self, event): + if self.itemAt(event.pos()) is not None: + self.contextMenuInvoked.emit(event) + else: + super().contextMenuEvent(event) + + def keyPressEvent(self, event): + event.ignore() + + if not event.isAccepted(): + self.keyPressed.emit(event) + # If the event object has been accepted during the `keyPressed` + # emission don't call the base implementation + if not event.isAccepted(): + super().keyPressEvent(event) + + def mousePressEvent(self, event): + if (not event.buttons() & Qt.RightButton or + not self.selectionMode() == QTreeWidget.NoSelection): + super().mousePressEvent(event) + + def standbyIndex(self): + return self.indexOfTopLevelItem(self.currentItem()) + + def setStandbyIndex(self, newIndex): + if 0 <= newIndex < self.topLevelItemCount(): + self.setCurrentItem(self.topLevelItem(newIndex)) + + def __currentItemChanged(self, current, previous): + if previous is not None: + self.__updateItem(previous, False) + + if current is not None: + self.__updateItem(current, True) + if self.selectionMode() == QTreeWidget.NoSelection: + # ensure the current item is in the middle + self.scrollToItem(current, QTreeWidget.PositionAtCenter) + + def __updateItem(self, item, current): + item.current = current + if current: + background = CueListView.ITEM_CURRENT_BG + else: + background = CueListView.ITEM_DEFAULT_BG + + for column in range(self.columnCount()): + item.setBackground(column, background) + + def __cueAdded(self, cue): + item = CueTreeWidgetItem(cue) + item.setFlags(item.flags() & ~Qt.ItemIsDropEnabled) + + self.insertTopLevelItem(cue.index, item) + self.__setupItemWidgets(item) + + if self.topLevelItemCount() == 1: + # If it's the only item in the view, set it as current + self.setCurrentItem(item) + else: + # Scroll to the last item added + self.scrollToItem(item) + + # Ensure that the focus is set + self.setFocus() + + def __cueMoved(self, before, after): + item = self.takeTopLevelItem(before) + self.insertTopLevelItem(after, item) + self.__setupItemWidgets(item) + + def __cueRemoved(self, cue): + index = cue.index + self.takeTopLevelItem(index) + + def __modelReset(self): + self.reset() + self.clear() + + def __setupItemWidgets(self, item): + for i, column in enumerate(CueListView.COLUMNS): + self.setItemWidget(item, i, column.widget(item)) + + self.updateGeometries() + + def __updateScrollRange(self, min_, max_): + if not self.__scrollRangeGuard: + self.__scrollRangeGuard = True + self.verticalScrollBar().setMaximum(max_ + 1) + self.__scrollRangeGuard = False diff --git a/lisp/layouts/list_layout/listwidgets.py b/lisp/plugins/list_layout/list_widgets.py similarity index 68% rename from lisp/layouts/list_layout/listwidgets.py rename to lisp/plugins/list_layout/list_widgets.py index f81925d83..67e358636 100644 --- a/lisp/layouts/list_layout/listwidgets.py +++ b/lisp/plugins/list_layout/list_widgets.py @@ -17,9 +17,9 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import QSize -from PyQt5.QtGui import QFont -from PyQt5.QtWidgets import QLabel, QProgressBar +from PyQt5.QtCore import QRect, Qt +from PyQt5.QtGui import QFont, QPainter, QBrush, QColor, QPen, QPainterPath +from PyQt5.QtWidgets import QLabel, QProgressBar, QWidget from lisp.core.signal import Connection from lisp.core.util import strtime @@ -28,53 +28,120 @@ from lisp.ui.icons import IconTheme -class CueStatusIcon(QLabel): - STYLESHEET = 'background: transparent; padding-left: 20px;' - SIZE = 16 +class IndexWidget(QLabel): + def __init__(self, item, *args, **kwargs): + super().__init__(*args, **kwargs) + self.setAttribute(Qt.WA_TranslucentBackground) + self.setAlignment(Qt.AlignCenter) + + item.cue.changed('index').connect(self.__update, Connection.QtQueued) + self.__update(item.cue.index) + + def __update(self, newIndex): + self.setText(str(newIndex + 1)) + + +class NameWidget(QLabel): + def __init__(self, item, *args, **kwargs): + super().__init__(*args, **kwargs) + self.setAttribute(Qt.WA_TranslucentBackground) + + item.cue.changed('name').connect(self.__update, Connection.QtQueued) + self.__update(item.cue.name) + + def __update(self, newName): + self.setText(newName) - def __init__(self, cue, *args): + +class CueStatusIcons(QWidget): + MARGIN = 6 + + def __init__(self, item, *args): super().__init__(*args) - self.setStyleSheet(CueStatusIcon.STYLESHEET) + self._statusPixmap = None + + self.item = item + self.item.cue.interrupted.connect(self._stop, Connection.QtQueued) + self.item.cue.started.connect(self._start, Connection.QtQueued) + self.item.cue.stopped.connect(self._stop, Connection.QtQueued) + self.item.cue.paused.connect(self._pause, Connection.QtQueued) + self.item.cue.error.connect(self._error, Connection.QtQueued) + self.item.cue.end.connect(self._stop, Connection.QtQueued) + + def setPixmap(self, pixmap): + self._statusPixmap = pixmap + self.update() - self.cue = cue - self.cue.interrupted.connect(self._stop, Connection.QtQueued) - self.cue.started.connect(self._start, Connection.QtQueued) - self.cue.stopped.connect(self._stop, Connection.QtQueued) - self.cue.paused.connect(self._pause, Connection.QtQueued) - self.cue.error.connect(self._error, Connection.QtQueued) - self.cue.end.connect(self._stop, Connection.QtQueued) + def _standbyChange(self): + self.update() def _start(self): - self.setPixmap(IconTheme.get('led-running').pixmap(self.SIZE)) + self.setPixmap(IconTheme.get('led-running').pixmap(self._size())) def _pause(self): - self.setPixmap(IconTheme.get('led-pause').pixmap(self.SIZE)) + self.setPixmap(IconTheme.get('led-pause').pixmap(self._size())) def _error(self): - self.setPixmap(IconTheme.get('led-error').pixmap(self.SIZE)) + self.setPixmap(IconTheme.get('led-error').pixmap(self._size())) def _stop(self): - self.setPixmap(IconTheme.get('').pixmap(self.SIZE)) - - def sizeHint(self): - return QSize(self.SIZE, self.SIZE) + self.setPixmap(None) + + def _size(self): + return self.height() - CueStatusIcons.MARGIN * 2 + + def paintEvent(self, event): + qp = QPainter() + qp.begin(self) + qp.setRenderHint(QPainter.HighQualityAntialiasing, True) + + status_size = self._size() + indicator_height = self.height() + indicator_width = indicator_height // 2 + + if self.item.current: + # Draw something like this + # |‾\ + # | \ + # | / + # |_/ + path = QPainterPath() + path.moveTo(0, 1) + path.lineTo(0, indicator_height - 1) + path.lineTo(indicator_width // 3, indicator_height - 1) + path.lineTo(indicator_width, indicator_width) + path.lineTo(indicator_width // 3, 0) + path.lineTo(0, 1) + + qp.setPen(QPen(QBrush(QColor(0, 0, 0)), 2)) + qp.setBrush(QBrush(QColor(250, 220, 0))) + qp.drawPath(path) + if self._statusPixmap is not None: + qp.drawPixmap( + QRect( + indicator_width + CueStatusIcons.MARGIN, + CueStatusIcons.MARGIN, + status_size, status_size + ), + self._statusPixmap + ) + + qp.end() class NextActionIcon(QLabel): STYLESHEET = 'background: transparent; padding-left: 1px' SIZE = 16 - def __init__(self, cue, *args): + def __init__(self, item, *args): super().__init__(*args) self.setStyleSheet(self.STYLESHEET) - self.cue = cue - self.cue.changed('next_action').connect( - self._update_icon, Connection.QtQueued) - - self._update_icon(self.cue.next_action) + item.cue.changed('next_action').connect( + self.__update, Connection.QtQueued) + self.__update(item.cue.next_action) - def _update_icon(self, next_action): + def __update(self, next_action): next_action = CueNextAction(next_action) pixmap = IconTheme.get('').pixmap(self.SIZE) @@ -89,13 +156,10 @@ def _update_icon(self, next_action): self.setPixmap(pixmap) - def sizeHint(self): - return QSize(self.SIZE + 2, self.SIZE) - class TimeWidget(QProgressBar): - def __init__(self, cue, *args): + def __init__(self, item, *args): super().__init__(*args) self.setObjectName('ListTimeWidget') self.setValue(0) @@ -106,7 +170,7 @@ def __init__(self, cue, *args): self.show_zero_duration = False self.accurate_time = True - self.cue = cue + self.cue = item.cue def _update_time(self, time): self.setValue(time) @@ -127,7 +191,6 @@ def _update_style(self, state): self.setProperty('state', state) self.style().unpolish(self) self.style().polish(self) - self.update() def _running(self): self._update_style('running') @@ -147,8 +210,8 @@ def _error(self): class CueTimeWidget(TimeWidget): - def __init__(self, cue, *args): - super().__init__(cue, *args) + def __init__(self, *args): + super().__init__(*args) self.cue.interrupted.connect(self._stop, Connection.QtQueued) self.cue.started.connect(self._running, Connection.QtQueued) @@ -162,11 +225,11 @@ def __init__(self, cue, *args): self.cue_time = CueTime(self.cue) self.cue_time.notify.connect(self._update_time, Connection.QtQueued) - if cue.state & CueState.Running: + if self.cue.state & CueState.Running: self._running() - elif cue.state & CueState.Pause: + elif self.cue.state & CueState.Pause: self._pause() - elif cue.state & CueState.Error: + elif self.cue.state & CueState.Error: self._error() else: self._stop() @@ -178,8 +241,8 @@ def _stop(self): class PreWaitWidget(TimeWidget): - def __init__(self, cue, *args): - super().__init__(cue, *args) + def __init__(self, *args): + super().__init__(*args) self.show_zero_duration = True self.cue.prewait_start.connect(self._running, Connection.QtQueued) @@ -205,8 +268,8 @@ def _stop(self): class PostWaitWidget(TimeWidget): - def __init__(self, cue, *args): - super().__init__(cue, *args) + def __init__(self, *args): + super().__init__(*args) self.show_zero_duration = True self.cue.changed('next_action').connect( @@ -273,4 +336,4 @@ def _stop(self): if self.cue.next_action == CueNextAction.AutoFollow.value: self._update_duration(self.cue.duration) else: - self._update_duration(self.cue.post_wait) \ No newline at end of file + self._update_duration(self.cue.post_wait) diff --git a/lisp/layouts/list_layout/cue_list_model.py b/lisp/plugins/list_layout/models.py similarity index 96% rename from lisp/layouts/list_layout/cue_list_model.py rename to lisp/plugins/list_layout/models.py index 52dad4197..7746bb6d7 100644 --- a/lisp/layouts/list_layout/cue_list_model.py +++ b/lisp/plugins/list_layout/models.py @@ -62,7 +62,8 @@ def _model_reset(self): self.model_reset.emit() def _item_added(self, item): - if not isinstance(item.index, int) or not 0 <= item.index <= len(self.__cues): + if (not isinstance(item.index, int) or + not 0 <= item.index <= len(self.__cues)): item.index = len(self.__cues) self.__cues.insert(item.index, item) @@ -85,7 +86,8 @@ def _update_indices(self, start, stop=-1): self.__cues[index].index = index def __iter__(self): - return self.__cues.__iter__() + for cue in self.__cues: + yield cue class RunningCueModel(ReadOnlyProxyModel): diff --git a/lisp/layouts/list_layout/playing_list_widget.py b/lisp/plugins/list_layout/playing_view.py similarity index 97% rename from lisp/layouts/list_layout/playing_list_widget.py rename to lisp/plugins/list_layout/playing_view.py index 3e5a89b06..010b9a188 100644 --- a/lisp/layouts/list_layout/playing_list_widget.py +++ b/lisp/plugins/list_layout/playing_view.py @@ -21,7 +21,7 @@ from PyQt5.QtWidgets import QListWidget, QListWidgetItem from lisp.core.signal import Connection -from lisp.layouts.list_layout.playing_mediawidget import get_running_widget +from lisp.plugins.list_layout.playing_widgets import get_running_widget class RunningCuesListWidget(QListWidget): diff --git a/lisp/layouts/list_layout/playing_mediawidget.py b/lisp/plugins/list_layout/playing_widgets.py similarity index 98% rename from lisp/layouts/list_layout/playing_mediawidget.py rename to lisp/plugins/list_layout/playing_widgets.py index aaa0295e4..77cce488b 100644 --- a/lisp/layouts/list_layout/playing_mediawidget.py +++ b/lisp/plugins/list_layout/playing_widgets.py @@ -28,7 +28,7 @@ from lisp.core.util import strtime from lisp.cues.cue_time import CueTime from lisp.cues.media_cue import MediaCue -from lisp.layouts.list_layout.control_buttons import CueControlButtons +from lisp.plugins.list_layout.control_buttons import CueControlButtons from lisp.ui.widgets import QClickSlider, QDbMeter @@ -228,4 +228,4 @@ def _update_duration(self, duration): def _update_timers(self, time): super()._update_timers(time) - self.seekSlider.setValue(time) \ No newline at end of file + self.seekSlider.setValue(time) diff --git a/lisp/layouts/list_layout/list_layout_settings.py b/lisp/plugins/list_layout/settings.py similarity index 55% rename from lisp/layouts/list_layout/list_layout_settings.py rename to lisp/plugins/list_layout/settings.py index 2577aadc7..db0df58ce 100644 --- a/lisp/layouts/list_layout/list_layout_settings.py +++ b/lisp/plugins/list_layout/settings.py @@ -53,15 +53,6 @@ def __init__(self, config, **kwargs): self.autoNext = QCheckBox(self.behaviorsGroup) self.behaviorsGroup.layout().addWidget(self.autoNext) - self.endListLayout = QHBoxLayout() - self.behaviorsGroup.layout().addLayout(self.endListLayout) - self.endListLabel = QLabel(self.behaviorsGroup) - self.endListLayout.addWidget(self.endListLabel) - self.endListBehavior = QComboBox(self.behaviorsGroup) - self.endListLayout.addWidget(self.endListBehavior) - self.endListLayout.setStretch(0, 2) - self.endListLayout.setStretch(1, 5) - self.goKeyLayout = QHBoxLayout() self.behaviorsGroup.layout().addLayout(self.goKeyLayout) self.goKeyLabel = QLabel(self.behaviorsGroup) @@ -75,7 +66,7 @@ def __init__(self, config, **kwargs): self.useFadeGroup.setLayout(QGridLayout()) self.layout().addWidget(self.useFadeGroup) - # per-cue + # Fade settings self.stopCueFade = QCheckBox(self.useFadeGroup) self.useFadeGroup.layout().addWidget(self.stopCueFade, 0, 0) self.pauseCueFade = QCheckBox(self.useFadeGroup) @@ -85,33 +76,8 @@ def __init__(self, config, **kwargs): self.interruptCueFade = QCheckBox(self.useFadeGroup) self.useFadeGroup.layout().addWidget(self.interruptCueFade, 3, 0) - # all - #self.stopAllFade = QCheckBox(self.useFadeGroup) - #self.useFadeGroup.layout().addWidget(self.stopAllFade, 0, 1) - #self.pauseAllFade = QCheckBox(self.useFadeGroup) - #self.useFadeGroup.layout().addWidget(self.pauseAllFade, 1, 1) - #self.resumeAllFade = QCheckBox(self.useFadeGroup) - #self.useFadeGroup.layout().addWidget(self.resumeAllFade, 2, 1) - #self.interruptAllFade = QCheckBox(self.useFadeGroup) - #self.useFadeGroup.layout().addWidget(self.interruptAllFade, 3, 1) - self.retranslateUi() - - # Load configuration - self.showPlaying.setChecked(config['listLayout.showPlaying']) - self.showDbMeters.setChecked(config['listLayout.showDbMeters']) - self.showAccurate.setChecked(config['listLayout.showAccurate']) - self.showSeek.setChecked(config['listLayout.showSeek']) - self.autoNext.setChecked(config['listLayout.autoContinue']) - self.endListBehavior.setCurrentText( - translate('ListLayout', config['listLayout.endList'])) - self.goKeyEdit.setKeySequence( - QKeySequence(config['listLayout.goKey'], QKeySequence.NativeText)) - - self.stopCueFade.setChecked(config['listLayout.stopCueFade']) - self.pauseCueFade.setChecked(config['listLayout.pauseCueFade']) - self.resumeCueFade.setChecked(config['listLayout.resumeCueFade']) - self.interruptCueFade.setChecked(config['listLayout.interruptCueFade']) + self.loadSettings() def retranslateUi(self): self.behaviorsGroup.setTitle( @@ -121,35 +87,42 @@ def retranslateUi(self): self.showAccurate.setText(translate('ListLayout', 'Show accurate time')) self.showSeek.setText(translate('ListLayout', 'Show seek-bars')) self.autoNext.setText(translate('ListLayout', 'Auto-select next cue')) - self.endListLabel.setText(translate('ListLayout', 'At list end:')) - self.endListBehavior.addItem(translate('ListLayout', 'Stop'), 'Stop') - self.endListBehavior.addItem( - translate('ListLayout', 'Restart'), 'Restart') self.goKeyLabel.setText(translate('ListLayout', 'Go key:')) - self.useFadeGroup.setTitle(translate('ListLayout', 'Use fade')) + self.useFadeGroup.setTitle( + translate('ListLayout', 'Use fade (buttons)')) self.stopCueFade.setText(translate('ListLayout', 'Stop Cue')) self.pauseCueFade.setText(translate('ListLayout', 'Pause Cue')) self.resumeCueFade.setText(translate('ListLayout', 'Resume Cue')) self.interruptCueFade.setText(translate('ListLayout', 'Interrupt Cue')) - #self.stopAllFade.setText(translate('ListLayout', 'Stop All')) - #self.pauseAllFade.setText(translate('ListLayout', 'Pause All')) - #self.resumeAllFade.setText(translate('ListLayout', 'Resume All')) - #self.interruptAllFade.setText(translate('ListLayout', 'Interrupt All')) + def loadSettings(self): + self.showPlaying.setChecked(self.config['show.playingCues']) + self.showDbMeters.setChecked(self.config['show.dBMeters']) + self.showAccurate.setChecked(self.config['show.accurateTime']) + self.showSeek.setChecked(self.config['show.seekSliders']) + self.autoNext.setChecked(self.config['autoContinue']) + self.goKeyEdit.setKeySequence( + QKeySequence(self.config['goKey'], QKeySequence.NativeText)) + + self.stopCueFade.setChecked(self.config['stopCueFade']) + self.pauseCueFade.setChecked(self.config['pauseCueFade']) + self.resumeCueFade.setChecked(self.config['resumeCueFade']) + self.interruptCueFade.setChecked(self.config['interruptCueFade']) def applySettings(self): - self.config['listLayout.showPlaying'] = self.showPlaying.isChecked() - self.config['listLayout.showDbMeters'] = self.showDbMeters.isChecked() - self.config['listLayout.showSeek'] = self.showSeek.isChecked() - self.config['listLayout.showAccurate'] = self.showAccurate.isChecked() - self.config['listLayout.autoContinue'] = self.autoNext.isChecked() - self.config['listLayout.endList'] = self.endListBehavior.currentData() - self.config['listLayout.goKey'] = self.goKeyEdit.keySequence().toString( + self.config['show.accurateTime'] = self.showAccurate.isChecked() + self.config['show.playingCues'] = self.showPlaying.isChecked() + self.config['show.dBMeters'] = self.showDbMeters.isChecked() + self.config['show.seekBars'] = self.showSeek.isChecked() + self.config['autoContinue'] = self.autoNext.isChecked() + + self.config['goKey'] = self.goKeyEdit.keySequence().toString( QKeySequence.NativeText) - self.config['listLayout.stopCueFade'] = self.stopCueFade.isChecked() - self.config['listLayout.pauseCueFade'] = self.pauseCueFade.isChecked() - self.config['listLayout.resumeCueFade'] = self.resumeCueFade.isChecked() - self.config['listLayout.interruptCueFade'] = self.interruptCueFade.isChecked() - self.config.write() \ No newline at end of file + self.config['stopCueFade'] = self.stopCueFade.isChecked() + self.config['pauseCueFade'] = self.pauseCueFade.isChecked() + self.config['resumeCueFade'] = self.resumeCueFade.isChecked() + self.config['interruptCueFade'] = self.interruptCueFade.isChecked() + + self.config.write() diff --git a/lisp/plugins/media_info/media_info.py b/lisp/plugins/media_info/media_info.py index aa677b101..8c2748472 100644 --- a/lisp/plugins/media_info/media_info.py +++ b/lisp/plugins/media_info/media_info.py @@ -20,16 +20,16 @@ from urllib.request import unquote from PyQt5 import QtCore -from PyQt5.QtWidgets import QAction, QMessageBox, QDialog, QVBoxLayout, \ +from PyQt5.QtWidgets import QMessageBox, QDialog, QVBoxLayout, \ QTreeWidget, QAbstractItemView, QDialogButtonBox, QTreeWidgetItem, \ QHeaderView from lisp.core.plugin import Plugin from lisp.cues.media_cue import MediaCue -from lisp.layouts.cue_layout import CueLayout +from lisp.layout.cue_layout import CueLayout +from lisp.layout.cue_menu import MenuActionsGroup, SimpleMenuAction, MENU_PRIORITY_PLUGIN from lisp.plugins.gst_backend.gst_utils import gst_uri_metadata, \ gst_parse_tags_list -from lisp.ui.mainwindow import MainWindow from lisp.ui.ui_utils import translate @@ -42,15 +42,19 @@ class MediaInfo(Plugin): def __init__(self, app): super().__init__(app) - self.menuAction = QAction(None) - self.menuAction.triggered.connect(self.show_info) - self.menuAction.setText(translate('MediaInfo', 'Media Info')) + self.cue_action_group = MenuActionsGroup( + priority=MENU_PRIORITY_PLUGIN) + self.cue_action_group.add( + SimpleMenuAction( + translate('MediaInfo', 'Media Info'), + self._show_info, + ) + ) - CueLayout.cm_registry.add_separator(MediaCue) - CueLayout.cm_registry.add_item(self.menuAction, MediaCue) + CueLayout.CuesMenu.add(self.cue_action_group, MediaCue) - def show_info(self): - media_uri = self.app.layout.get_context_cue().media.input_uri() + def _show_info(self, cue): + media_uri = cue.media.input_uri() if media_uri is None: QMessageBox.warning( @@ -101,8 +105,7 @@ def show_info(self): info['Tags'] = tags # Show the dialog - dialog = InfoDialog( - self.app.window, info, self.app.layout.get_context_cue().name) + dialog = InfoDialog(self.app.window, info, cue.name) dialog.exec_() @@ -113,8 +116,8 @@ def __init__(self, parent, info, title): self.setWindowTitle( translate('MediaInfo', 'Media Info') + ' - ' + title) self.setWindowModality(QtCore.Qt.ApplicationModal) - self.setMinimumSize(550, 300) - self.resize(550, 500) + self.setMinimumSize(600, 300) + self.resize(600, 300) self.setLayout(QVBoxLayout(self)) self.infoTree = QTreeWidget(self) diff --git a/lisp/plugins/midi/midi_common.py b/lisp/plugins/midi/midi_common.py index 5e6c0bb7c..bd8a89b6d 100644 --- a/lisp/plugins/midi/midi_common.py +++ b/lisp/plugins/midi/midi_common.py @@ -20,7 +20,7 @@ from abc import abstractmethod -class MIDICommon(): +class MIDICommon: def __init__(self, port_name=None): """ :param port_name: the port name diff --git a/lisp/plugins/presets/presets.py b/lisp/plugins/presets/presets.py index 246700231..06a23f20b 100644 --- a/lisp/plugins/presets/presets.py +++ b/lisp/plugins/presets/presets.py @@ -22,14 +22,16 @@ from PyQt5.QtWidgets import QAction from lisp.core.plugin import Plugin -from lisp.layouts.cue_layout import CueLayout -from lisp.plugins.presets.lib import PRESETS_DIR, load_on_cue, preset_exists +from lisp.layout.cue_layout import CueLayout +from lisp.layout.cue_menu import MenuActionsGroup, SimpleMenuAction, MENU_PRIORITY_PLUGIN +from lisp.plugins.presets.lib import PRESETS_DIR, load_on_cue, preset_exists, load_on_cues from lisp.plugins.presets.presets_ui import select_preset_dialog, \ PresetsDialog, save_preset_dialog, check_override_dialog, write_preset, \ load_preset_error, write_preset_error from lisp.ui.ui_utils import translate +# TODO: use logging to report errors class Presets(Plugin): Name = 'Preset' @@ -49,33 +51,48 @@ def __init__(self, app): self.menu_action = self.app.window.menuTools.addAction(self.manageAction) - self.loadOnCueAction = QAction(None) - self.loadOnCueAction.triggered.connect(self.__load_on_cue) - self.loadOnCueAction.setText(translate('Presets', 'Load preset')) - - self.createFromCueAction = QAction(None) - self.createFromCueAction.triggered.connect(self.__create_from_cue) - self.createFromCueAction.setText(translate('Presets', 'Save as preset')) - - CueLayout.cm_registry.add_separator() - CueLayout.cm_registry.add_item(self.loadOnCueAction) - CueLayout.cm_registry.add_item(self.createFromCueAction) - CueLayout.cm_registry.add_separator() + # Cue menu (context-action) + self.cueActionsGroup = MenuActionsGroup( + submenu=True, + text=translate('Presets', 'Presets'), + priority=MENU_PRIORITY_PLUGIN, + ) + self.cueActionsGroup.add( + SimpleMenuAction( + translate('Presets', 'Load on cue'), + self.__load_on_cue, + translate('Presets', 'Load on selected cues'), + self.__load_on_cues + ), + SimpleMenuAction( + translate('Presets', 'Save as preset'), + self.__create_from_cue + ) + ) + + CueLayout.CuesMenu.add(self.cueActionsGroup) def __edit_presets(self): ui = PresetsDialog(self.app, parent=self.app.window) ui.show() - def __load_on_cue(self): + def __load_on_cue(self, cue): + preset_name = select_preset_dialog() + if preset_name is not None: + try: + load_on_cue(preset_name, cue) + except OSError as e: + load_preset_error(e, preset_name, parent=self.app.window) + + def __load_on_cues(self, cues): preset_name = select_preset_dialog() if preset_name is not None: try: - load_on_cue(preset_name, self.app.layout.get_context_cue()) + load_on_cues(preset_name, cues) except OSError as e: load_preset_error(e, preset_name, parent=self.app.window) - def __create_from_cue(self): - cue = self.app.layout.get_context_cue() + def __create_from_cue(self, cue): name = save_preset_dialog(cue.name) if name is not None: diff --git a/lisp/plugins/presets/presets_ui.py b/lisp/plugins/presets/presets_ui.py index dad1975c9..ec996088b 100644 --- a/lisp/plugins/presets/presets_ui.py +++ b/lisp/plugins/presets/presets_ui.py @@ -105,7 +105,10 @@ def check_override_dialog(preset_name): answer = QMessageBox.question( MainWindow(), translate('Presets', 'Presets'), - translate('Presets', 'Preset already exists, overwrite?'), + translate( + 'Presets', 'Preset "{}" already exists, overwrite?' + .format(preset_name) + ), buttons=QMessageBox.Yes | QMessageBox.Cancel ) diff --git a/lisp/plugins/rename_cues/rename_action.py b/lisp/plugins/rename_cues/rename_action.py index c294d53d1..76eebda51 100644 --- a/lisp/plugins/rename_cues/rename_action.py +++ b/lisp/plugins/rename_cues/rename_action.py @@ -20,6 +20,7 @@ from lisp.core.action import Action + class RenameCueAction(Action): # Store names for undo/redo in a dict like that : {'id': name} diff --git a/lisp/plugins/rename_cues/rename_cues.py b/lisp/plugins/rename_cues/rename_cues.py index 40c1f8251..4b4b04422 100644 --- a/lisp/plugins/rename_cues/rename_cues.py +++ b/lisp/plugins/rename_cues/rename_cues.py @@ -44,14 +44,13 @@ def __init__(self, app): def rename(self): # Test if some cues are selected, else select all cues - if self.app.layout.get_selected_cues(): - selected_cues = self.app.layout.get_selected_cues() - else: + selected_cues = list(self.app.layout.selected_cues()) + if not selected_cues: # TODO : implement dialog box if/when QSettings is implemented # the dialog should inform the user that rename_module load only selected cues if needed # but it will bother more than being useful if we can't provide a "Don't show again" # Could be provided by QErrorMessage if QSettings is supported - selected_cues = self.app.cue_model + selected_cues = list(self.app.cue_model) # Initiate rename windows renameUi = RenameUi(self.app.window, selected_cues) diff --git a/lisp/plugins/timecode/timecode.py b/lisp/plugins/timecode/timecode.py index d4a304e92..22e618031 100644 --- a/lisp/plugins/timecode/timecode.py +++ b/lisp/plugins/timecode/timecode.py @@ -56,7 +56,7 @@ def __init__(self, app): ) # Register cue-settings-page - CueSettingsRegistry().add_item(TimecodeSettings, MediaCue) + CueSettingsRegistry().add(TimecodeSettings, MediaCue) # Register the settings widget AppConfigurationDialog.registerSettingsPage( 'plugins.timecode', TimecodeAppSettings, Timecode.Config) diff --git a/lisp/plugins/triggers/triggers.py b/lisp/plugins/triggers/triggers.py index 11b0095f1..52a51d35d 100644 --- a/lisp/plugins/triggers/triggers.py +++ b/lisp/plugins/triggers/triggers.py @@ -41,7 +41,7 @@ def __init__(self, app): # Cue.triggers -> {trigger: [(target_id, action), ...]} # Register SettingsPage - CueSettingsRegistry().add_item(TriggersSettings) + CueSettingsRegistry().add(TriggersSettings) # On session destroy self.app.session_before_finalize.connect(self.session_reset) diff --git a/lisp/plugins/triggers/triggers_handler.py b/lisp/plugins/triggers/triggers_handler.py index b27964111..76bcc2b13 100644 --- a/lisp/plugins/triggers/triggers_handler.py +++ b/lisp/plugins/triggers/triggers_handler.py @@ -29,7 +29,7 @@ class CueTriggers(Enum): Started = QT_TRANSLATE_NOOP('CueTriggers', 'Started') Paused = QT_TRANSLATE_NOOP('CueTriggers', 'Paused') Stopped = QT_TRANSLATE_NOOP('CueTriggers', 'Stopped') - Ended = QT_TRANSLATE_NOOP('CueTriggers','Ended') + Ended = QT_TRANSLATE_NOOP('CueTriggers', 'Ended') class CueHandler: diff --git a/lisp/ui/about.py b/lisp/ui/about.py index 4eb68f6b8..0eca45c7d 100644 --- a/lisp/ui/about.py +++ b/lisp/ui/about.py @@ -155,4 +155,4 @@ def __contributors(self): text += '
' - return text \ No newline at end of file + return text diff --git a/lisp/ui/icons/lisp/led-error.svg b/lisp/ui/icons/lisp/led-error.svg index 43fff65ad..caa9221e8 100644 --- a/lisp/ui/icons/lisp/led-error.svg +++ b/lisp/ui/icons/lisp/led-error.svg @@ -1 +1,131 @@ - \ No newline at end of file + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/lisp/led-off.svg b/lisp/ui/icons/lisp/led-off.svg index e9d97d34c..4241e4ed5 100644 --- a/lisp/ui/icons/lisp/led-off.svg +++ b/lisp/ui/icons/lisp/led-off.svg @@ -1 +1,131 @@ - \ No newline at end of file + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/lisp/led-pause.svg b/lisp/ui/icons/lisp/led-pause.svg index c181410da..60c483732 100644 --- a/lisp/ui/icons/lisp/led-pause.svg +++ b/lisp/ui/icons/lisp/led-pause.svg @@ -1 +1,131 @@ - \ No newline at end of file + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/lisp/led-running.svg b/lisp/ui/icons/lisp/led-running.svg index d56e3e11c..c77509508 100644 --- a/lisp/ui/icons/lisp/led-running.svg +++ b/lisp/ui/icons/lisp/led-running.svg @@ -1 +1,130 @@ - \ No newline at end of file + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/layoutselect.py b/lisp/ui/layoutselect.py index 6a3d3eace..266820d43 100644 --- a/lisp/ui/layoutselect.py +++ b/lisp/ui/layoutselect.py @@ -23,7 +23,7 @@ from PyQt5.QtWidgets import QDialog, QComboBox, QPushButton, QFrame,\ QTextBrowser, QFileDialog, QGridLayout -from lisp import layouts +from lisp import layout from lisp.ui.ui_utils import translate @@ -67,7 +67,7 @@ def __init__(self, parent=None): self.description = QTextBrowser(self) self.layout().addWidget(self.description, 2, 0, 1, 3) - for layout_class in layouts.get_layouts(): + for layout_class in layout.get_layouts(): self.layoutCombo.addItem(layout_class.NAME, layout_class) if self.layoutCombo.count() == 0: diff --git a/lisp/ui/logging/models.py b/lisp/ui/logging/models.py index 0e2bd32e9..57fe3c531 100644 --- a/lisp/ui/logging/models.py +++ b/lisp/ui/logging/models.py @@ -23,6 +23,7 @@ QAbstractTableModel from PyQt5.QtGui import QFont, QColor +from lisp.core.util import typename from lisp.ui.logging.common import LogRecordRole, LOG_ATTRIBUTES, LogAttributeRole @@ -129,7 +130,7 @@ def setSourceModel(self, source_model): else: raise TypeError( 'LogRecordFilterModel source must be LogRecordModel, not {}' - .format(type(source_model).__name__) + .format(typename(source_model)) ) def filterAcceptsRow(self, source_row, source_parent): diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 62d5f066f..0981a321f 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -119,10 +119,15 @@ def __init__(self, conf, title='Linux Show Player', **kwargs): self.actionRedo = QAction(self) self.actionRedo.triggered.connect(MainActionsHandler.redo_action) self.multiEdit = QAction(self) + self.multiEdit.triggered.connect(self._edit_selected_cue) self.selectAll = QAction(self) + self.selectAll.triggered.connect(self._select_all) self.selectAllMedia = QAction(self) + self.selectAllMedia.triggered.connect(self._select_all_media) self.deselectAll = QAction(self) + self.deselectAll.triggered.connect(self._deselect_all) self.invertSelection = QAction(self) + self.invertSelection.triggered.connect(self._invert_selection) self.cueSeparator = self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionUndo) @@ -214,26 +219,13 @@ def retranslateUi(self): def set_session(self, session): if self.session is not None: - layout = self.session.layout - self.centralWidget().layout().removeWidget(layout) - - self.multiEdit.triggered.disconnect() - self.selectAll.triggered.disconnect() - self.selectAllMedia.triggered.disconnect() - self.deselectAll.triggered.disconnect() - self.invertSelection.triggered.disconnect() + self.centralWidget().layout().removeWidget(self.session.layout.view()) + # Remove ownership, this allow the widget to be deleted + self.session.layout.view().setParent(None) self.session = session - layout = self.session.layout - - self.centralWidget().layout().addWidget(layout) - layout.show() - - self.multiEdit.triggered.connect(self._edit_selected_cue) - self.selectAll.triggered.connect(self._select_all) - self.invertSelection.triggered.connect(self._invert_selection) - self.deselectAll.triggered.connect(self._deselect_all) - self.selectAllMedia.triggered.connect(self._select_all_media) + self.session.layout.view().show() + self.centralWidget().layout().addWidget(self.session.layout.view()) def closeEvent(self, event): self._exit() @@ -360,4 +352,4 @@ def _deselect_all(self): self.session.layout.deselect_all() def _select_all_media(self): - self.session.layout.select_all(cue_class=MediaCue) \ No newline at end of file + self.session.layout.select_all(cue_class=MediaCue) diff --git a/lisp/ui/settings/app_configuration.py b/lisp/ui/settings/app_configuration.py index 7e4185a8b..111fe7727 100644 --- a/lisp/ui/settings/app_configuration.py +++ b/lisp/ui/settings/app_configuration.py @@ -25,6 +25,7 @@ from PyQt5.QtWidgets import QVBoxLayout, QDialogButtonBox, QDialog from lisp.core.dicttree import DictNode +from lisp.core.util import typename from lisp.ui.settings.pages import ConfigurationPage, TreeMultiConfigurationWidget from lisp.ui.settings.pages_tree_model import PagesTreeModel from lisp.ui.ui_utils import translate @@ -122,7 +123,7 @@ def registerSettingsPage(path, page, config): else: raise TypeError( 'AppConfiguration pages must be ConfigurationPage(s), not {}' - .format(type(page).__name__) + .format(typename(page)) ) @staticmethod diff --git a/lisp/ui/settings/app_pages/app_general.py b/lisp/ui/settings/app_pages/app_general.py index ed649a6b4..2dd0db6be 100644 --- a/lisp/ui/settings/app_pages/app_general.py +++ b/lisp/ui/settings/app_pages/app_general.py @@ -21,7 +21,7 @@ from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QComboBox, \ QGridLayout, QLabel -from lisp import layouts +from lisp import layout from lisp.ui.icons import icon_themes_names from lisp.ui.settings.pages import ConfigurationPage from lisp.ui.themes import themes_names @@ -45,8 +45,8 @@ def __init__(self, config, **kwargs): self.layoutGroup.layout().addWidget(self.startupDialogCheck) self.layoutCombo = QComboBox(self.layoutGroup) - for layout in layouts.get_layouts(): - self.layoutCombo.addItem(layout.NAME, layout.__name__) + for lay in layout.get_layouts(): + self.layoutCombo.addItem(lay.NAME, lay.__name__) self.layoutGroup.layout().addWidget(self.layoutCombo) self.startupDialogCheck.stateChanged.connect( @@ -105,4 +105,4 @@ def loadConfiguration(self): self.layoutCombo.setCurrentText(layout_name) self.themeCombo.setCurrentText(self.config.get('theme.theme', '')) - self.iconsCombo.setCurrentText(self.config.get('theme.icons', '')) \ No newline at end of file + self.iconsCombo.setCurrentText(self.config.get('theme.icons', '')) diff --git a/lisp/ui/settings/app_pages/cue_app_settings.py b/lisp/ui/settings/app_pages/cue_app_settings.py index c64b06e40..0ed5af68d 100644 --- a/lisp/ui/settings/app_pages/cue_app_settings.py +++ b/lisp/ui/settings/app_pages/cue_app_settings.py @@ -72,4 +72,4 @@ def loadConfiguration(self): self.fadeActionEdit.setDuration(self.config.get('cue.fadeAction', 0)) self.fadeActionEdit.setFadeType( - self.config.get('cue.fadeActionType', '')) \ No newline at end of file + self.config.get('cue.fadeActionType', '')) diff --git a/lisp/ui/settings/app_pages/layouts_settings.py b/lisp/ui/settings/app_pages/layouts_settings.py new file mode 100644 index 000000000..1c76973c7 --- /dev/null +++ b/lisp/ui/settings/app_pages/layouts_settings.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QGridLayout, QCheckBox + +from lisp.ui.settings.pages import ConfigurationPage +from lisp.ui.ui_utils import translate + + +class LayoutsSettings(ConfigurationPage): + Name = 'Layouts' + + def __init__(self, config, **kwargs): + super().__init__(config, **kwargs) + self.setLayout(QVBoxLayout()) + self.layout().setAlignment(Qt.AlignTop) + + self.useFadeGroup = QGroupBox(self) + self.useFadeGroup.setLayout(QGridLayout()) + self.layout().addWidget(self.useFadeGroup) + + self.stopAllFade = QCheckBox(self.useFadeGroup) + self.useFadeGroup.layout().addWidget(self.stopAllFade, 0, 1) + + self.pauseAllFade = QCheckBox(self.useFadeGroup) + self.useFadeGroup.layout().addWidget(self.pauseAllFade, 1, 1) + + self.resumeAllFade = QCheckBox(self.useFadeGroup) + self.useFadeGroup.layout().addWidget(self.resumeAllFade, 2, 1) + + self.interruptAllFade = QCheckBox(self.useFadeGroup) + self.useFadeGroup.layout().addWidget(self.interruptAllFade, 3, 1) + + self.retranslateUi() + + def retranslateUi(self): + self.useFadeGroup.setTitle( + translate('ListLayout', 'Use fade (global actions)')) + self.stopAllFade.setText(translate('ListLayout', 'Stop All')) + self.pauseAllFade.setText(translate('ListLayout', 'Pause All')) + self.resumeAllFade.setText(translate('ListLayout', 'Resume All')) + self.interruptAllFade.setText(translate('ListLayout', 'Interrupt All')) + + def loadSettings(self): + self.stopAllFade.setChecked(self.config['layout.stopAllFade']) + self.pauseAllFade.setChecked(self.config['layout.pauseAllFade']) + self.resumeAllFade.setChecked(self.config['layout.resumeAllFade']) + self.interruptAllFade.setChecked(self.config['layout.interruptAllFade']) + + def applySettings(self): + self.config['layout.stopAllFade'] = self.stopAllFade.isChecked() + self.config['layout.pauseAllFade'] = self.pauseAllFade.isChecked() + self.config['layout.resumeAllFade'] = self.resumeAllFade.isChecked() + self.config['layout.interruptAllFade'] = self.interruptAllFade.isChecked() + + self.config.write() \ No newline at end of file diff --git a/lisp/ui/settings/app_pages/plugins_settings.py b/lisp/ui/settings/app_pages/plugins_settings.py index f7ee832fa..4f59cbe40 100644 --- a/lisp/ui/settings/app_pages/plugins_settings.py +++ b/lisp/ui/settings/app_pages/plugins_settings.py @@ -26,7 +26,7 @@ from lisp.ui.settings.pages import ConfigurationPage -# TODO: just a proof-of concept +# TODO: add Enable/Disable options for plugins class PluginsSettings(ConfigurationPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Plugins') diff --git a/lisp/ui/settings/cue_settings.py b/lisp/ui/settings/cue_settings.py index 853615701..f2da3df4c 100644 --- a/lisp/ui/settings/cue_settings.py +++ b/lisp/ui/settings/cue_settings.py @@ -22,6 +22,7 @@ from lisp.core.class_based_registry import ClassBasedRegistry from lisp.core.singleton import Singleton +from lisp.core.util import typename from lisp.cues.cue import Cue from lisp.ui.settings.pages import SettingsPage, CuePageMixin, \ TabsMultiSettingsPage @@ -29,12 +30,12 @@ class CueSettingsRegistry(ClassBasedRegistry, metaclass=Singleton): - def add_item(self, item, ref_class=Cue): + def add(self, item, ref_class=Cue): if not issubclass(item, SettingsPage): raise TypeError( 'item must be a SettingPage, not {}'.format(item.__name__)) - return super().add_item(item, ref_class) + return super().add(item, ref_class) def filter(self, ref_class=Cue): return super().filter(ref_class) @@ -61,8 +62,6 @@ def __init__(self, cue, **kwargs): if isinstance(cue, type): if issubclass(cue, Cue): cue_properties = cue.class_defaults() - import pprint - pprint.pprint(cue_properties) cue_class = cue else: raise TypeError( @@ -76,7 +75,7 @@ def __init__(self, cue, **kwargs): else: raise TypeError( 'invalid cue type, must be a Cue subclass or a Cue object, ' - 'not {}'.format(type(cue).__name__) + 'not {}'.format(typename(cue)) ) self.mainPage = TabsMultiSettingsPage(parent=self) diff --git a/lisp/ui/settings/pages.py b/lisp/ui/settings/pages.py index b5040a99f..41b15e6f3 100644 --- a/lisp/ui/settings/pages.py +++ b/lisp/ui/settings/pages.py @@ -23,7 +23,7 @@ from PyQt5.QtWidgets import QWidget, QTabWidget, QVBoxLayout, QTreeView, QGridLayout, QSizePolicy from lisp.core.qmeta import QABCMeta -from lisp.core.util import dict_merge +from lisp.core.util import dict_merge, typename from lisp.ui.ui_utils import translate @@ -118,7 +118,7 @@ def addPage(self, page): raise TypeError( 'page must be an {}, not {}'.format( self._PagesBaseClass.__name__, - type(page).__name__) + typename(page)) ) def removePage(self, index): diff --git a/lisp/ui/settings/pages_tree_model.py b/lisp/ui/settings/pages_tree_model.py index ea82eff93..a54e7b55e 100644 --- a/lisp/ui/settings/pages_tree_model.py +++ b/lisp/ui/settings/pages_tree_model.py @@ -19,6 +19,7 @@ from PyQt5.QtCore import QAbstractItemModel, Qt, QModelIndex +from lisp.core.util import typename from lisp.ui.settings.pages import ABCSettingsPage @@ -138,7 +139,7 @@ def addPage(self, page, parent=QModelIndex()): else: raise TypeError( 'SettingsPagesTreeModel page must be an ABCSettingsPage, not {}' - .format(type(page).__name__) + .format(typename(page)) ) def removePage(self, row, parent=QModelIndex()): diff --git a/lisp/ui/themes/dark/theme.qss b/lisp/ui/themes/dark/theme.qss index 32d35ee28..aeea611c2 100644 --- a/lisp/ui/themes/dark/theme.qss +++ b/lisp/ui/themes/dark/theme.qss @@ -107,9 +107,9 @@ QMenu:item:selected{ QAbstractItemView { alternate-background-color: #252525; + /*paint-alternating-row-colors-for-empty-area: true;*/ color: white; border-radius: 3px; - padding: 1px; } QTreeView::branch:hover, @@ -130,7 +130,7 @@ QLineEdit { } QGroupBox { - border:1px solid #363636; + border: 1px solid #363636; border-radius: 2px; margin-top: 2ex; /* NOT px */ } @@ -306,7 +306,6 @@ QPushButton:focus { } QComboBox { - selection-background-color: #419BE6; background-color: #202020; border-style: solid; border: 1px solid #3A3A3A; @@ -681,10 +680,23 @@ QRadioButton:indicator:checked:disabled { /*font-size: 11pt;*/ } +#ButtonCueWidget[selected="true"] { + border: 2px solid #419BE6; +} + +CueListView { + border: 1px solid #808080; +} + +CueListView:focus { + border: 1px solid #3A3A3A; +} + #ListTimeWidget { height: 25px; border-radius: 0px; - margin: 1px; + margin: 0px; + padding: 0px; background-color: transparent; } @@ -699,6 +711,10 @@ QRadioButton:indicator:checked:disabled { background-color: transparent; } +#ListTimeWidget:horizontal { + border: 1px solid transparent; /* so the widget size is the same */ +} + /* running */ #ListTimeWidget:horizontal[state="running"] { border: 1px solid #00FF00; diff --git a/lisp/ui/ui_utils.py b/lisp/ui/ui_utils.py index f01b29363..a202a6ee0 100644 --- a/lisp/ui/ui_utils.py +++ b/lisp/ui/ui_utils.py @@ -22,13 +22,37 @@ from os import path from PyQt5.QtCore import QTranslator, QLocale -from PyQt5.QtWidgets import QApplication +from PyQt5.QtWidgets import QApplication, qApp from lisp import I18N_PATH logger = logging.getLogger(__name__) +def adjust_widget_position(widget): + """Adjust the widget position to ensure it's in the desktop. + + :type widget: PyQt5.QtWidgets.QWidget + """ + widget.setGeometry(adjust_position(widget.geometry())) + + +def adjust_position(rect): + """Adjust the given rect to ensure it's in the desktop space. + + :type widget: PyQt5.QtCore.QRect + :return: PyQt5.QtCore.QRect + """ + desktop = qApp.desktop().availableGeometry() + + if rect.bottom() > desktop.bottom(): + rect.moveTo(rect.x(), rect.y() - rect.height()) + if rect.right() > desktop.right(): + rect.moveTo(rect.x() - rect.width(), rect.y()) + + return rect + + def qfile_filters(extensions, allexts=True, anyfile=True): """Create a filter-string for a FileChooser. diff --git a/lisp/ui/widgets/fades.py b/lisp/ui/widgets/fades.py index e3933be27..3041cf5b6 100644 --- a/lisp/ui/widgets/fades.py +++ b/lisp/ui/widgets/fades.py @@ -104,4 +104,4 @@ def fadeType(self): return self.fadeTypeCombo.currentType() def setFadeType(self, fade_type): - self.fadeTypeCombo.setCurrentType(fade_type) \ No newline at end of file + self.fadeTypeCombo.setCurrentType(fade_type) diff --git a/lisp/ui/widgets/qclicklabel.py b/lisp/ui/widgets/qclicklabel.py index fb2b7ca6f..76cb80797 100644 --- a/lisp/ui/widgets/qclicklabel.py +++ b/lisp/ui/widgets/qclicklabel.py @@ -29,5 +29,5 @@ def __init(self, parent): super().__init__(parent) def mouseReleaseEvent(self, e): - if(self.contentsRect().contains(e.pos())): + if self.contentsRect().contains(e.pos()): self.clicked.emit(e) diff --git a/lisp/ui/widgets/qcolorbutton.py b/lisp/ui/widgets/qcolorbutton.py index d361a39cf..a5ac56645 100644 --- a/lisp/ui/widgets/qcolorbutton.py +++ b/lisp/ui/widgets/qcolorbutton.py @@ -66,4 +66,4 @@ def mousePressEvent(self, e): if e.button() == Qt.RightButton: self.setColor(None) - return super(QColorButton, self).mousePressEvent(e) \ No newline at end of file + return super(QColorButton, self).mousePressEvent(e) From 1f6befce782a8a77dd9f06e9d05104c04569203f Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 14 May 2018 12:30:39 +0200 Subject: [PATCH 101/333] Minor fixes/changes --- lisp/core/util.py | 4 ++-- lisp/layout/cue_layout.py | 17 +++++++-------- lisp/layout/cue_menu.py | 6 +++--- lisp/plugins/cart_layout/layout.py | 16 +++++++------- lisp/plugins/list_layout/layout.py | 8 +++---- lisp/plugins/list_layout/list_view.py | 3 +-- lisp/plugins/list_layout/models.py | 30 +++++++++++++-------------- lisp/plugins/list_layout/settings.py | 4 ++-- lisp/plugins/presets/presets_ui.py | 2 +- 9 files changed, 43 insertions(+), 47 deletions(-) diff --git a/lisp/core/util.py b/lisp/core/util.py index 03fa3b5de..0201d5698 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -123,8 +123,8 @@ def greatest_common_superclass(instances): return x -def typename(object): - return object.__class__.__name__ +def typename(obj): + return obj.__class__.__name__ def get_lan_ip(): diff --git a/lisp/layout/cue_layout.py b/lisp/layout/cue_layout.py index 05cab5a6c..07a68b868 100644 --- a/lisp/layout/cue_layout.py +++ b/lisp/layout/cue_layout.py @@ -26,7 +26,6 @@ from lisp.cues.cue import Cue, CueAction from lisp.cues.cue_actions import UpdateCueAction, UpdateCuesAction from lisp.layout.cue_menu import CueContextMenu -from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.cue_settings import CueSettingsDialog from lisp.ui.ui_utils import adjust_widget_position @@ -171,9 +170,8 @@ def fadeout_all(self): for cue in self.cues(): cue.execute(CueAction.FadeOut) - @staticmethod - def edit_cue(cue): - dialog = CueSettingsDialog(cue, parent=MainWindow()) + def edit_cue(self, cue): + dialog = CueSettingsDialog(cue, parent=self.app.window) def on_apply(settings): action = UpdateCueAction(settings, cue) @@ -182,11 +180,11 @@ def on_apply(settings): dialog.onApply.connect(on_apply) dialog.exec_() - @staticmethod - def edit_cues(cues): + def edit_cues(self, cues): if cues: # Use the greatest common superclass between the selected cues - dialog = CueSettingsDialog(greatest_common_superclass(cues)) + dialog = CueSettingsDialog( + greatest_common_superclass(cues), parent=self.app.window) def on_apply(settings): action = UpdateCuesAction(settings, cues) @@ -195,9 +193,8 @@ def on_apply(settings): dialog.onApply.connect(on_apply) dialog.exec_() - @staticmethod - def show_context_menu(position): - menu = MainWindow().menuEdit + def show_context_menu(self, position): + menu = self.app.window.menuEdit menu.move(position) menu.show() diff --git a/lisp/layout/cue_menu.py b/lisp/layout/cue_menu.py index 9dd57c72b..244183bce 100644 --- a/lisp/layout/cue_menu.py +++ b/lisp/layout/cue_menu.py @@ -35,13 +35,13 @@ class MenuAction: def __init__(self, priority=MENU_PRIORITY_NONE): self.priority = priority - def show(self, items): + def show(self, cues): return False - def text(self, items): + def text(self, cues): pass - def action(self, items): + def action(self, cues): pass diff --git a/lisp/plugins/cart_layout/layout.py b/lisp/plugins/cart_layout/layout.py index d1c002180..d0d09c23b 100644 --- a/lisp/plugins/cart_layout/layout.py +++ b/lisp/plugins/cart_layout/layout.py @@ -191,24 +191,24 @@ def view(self): def cue_at(self, index): return self._cart_model.item(index) - def cues(self, cue_class=Cue): + def cues(self, cue_type=Cue): for cue in self._cart_model: - if isinstance(cue, cue_class): + if isinstance(cue, cue_type): yield cue - def selected_cues(self, cue_class=Cue): + def selected_cues(self, cue_type=Cue): for widget in self._widgets(): - if widget.selected and isinstance(widget.cue, cue_class): + if widget.selected and isinstance(widget.cue, cue_type): yield widget.cue - def select_all(self, cue_class=Cue): + def select_all(self, cue_type=Cue): for widget in self._widgets(): - if isinstance(widget.cue, cue_class): + if isinstance(widget.cue, cue_type): widget.selected = True - def deselect_all(self, cue_class=Cue): + def deselect_all(self, cue_type=Cue): for widget in self._widgets(): - if isinstance(widget.cue, cue_class): + if isinstance(widget.cue, cue_type): widget.selected = False def invert_selection(self): diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index 6b7186bd8..cfa7f1c8d 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -240,14 +240,14 @@ def finalize(self): # Remove reference cycle del self._edit_actions_group - def select_all(self, cue_class=Cue): + def select_all(self, cue_type=Cue): for index in range(self.listView.topLevelItemCount()): - if isinstance(self._list_model.item(index), cue_class): + if isinstance(self._list_model.item(index), cue_type): self.listView.topLevelItem(index).setSelected(True) - def deselect_all(self, cue_class=Cue): + def deselect_all(self, cue_type=Cue): for index in range(self.listView.topLevelItemCount()): - if isinstance(self._list_model.item(index), cue_class): + if isinstance(self._list_model.item(index), cue_type): self.listView.topLevelItem(index).setSelected(False) def invert_selection(self): diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py index 4b359c898..ed34c0191 100644 --- a/lisp/plugins/list_layout/list_view.py +++ b/lisp/plugins/list_layout/list_view.py @@ -17,7 +17,6 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5 import QtCore from PyQt5.QtCore import pyqtSignal, Qt, QDataStream, QIODevice, \ QT_TRANSLATE_NOOP from PyQt5.QtGui import QKeyEvent, QContextMenuEvent, QBrush, QColor @@ -138,7 +137,7 @@ def dropEvent(self, event): row = stream.readInt() # Skip column and data stream.readInt() - for i in range(stream.readInt()): + for _ in range(stream.readInt()): stream.readInt() stream.readQVariant() diff --git a/lisp/plugins/list_layout/models.py b/lisp/plugins/list_layout/models.py index 7746bb6d7..ce8a6b9d1 100644 --- a/lisp/plugins/list_layout/models.py +++ b/lisp/plugins/list_layout/models.py @@ -96,21 +96,21 @@ def __init__(self, model): super().__init__(model) self.__playing = [] - def _item_added(self, cue): - cue.end.connect(self._remove) - cue.started.connect(self._add) - cue.error.connect(self._remove) - cue.stopped.connect(self._remove) - cue.interrupted.connect(self._remove) - - def _item_removed(self, cue): - cue.end.disconnect(self._remove) - cue.started.disconnect(self._add) - cue.error.disconnect(self._remove) - cue.stopped.disconnect(self._remove) - cue.interrupted.disconnect(self._remove) - - self._remove(cue) + def _item_added(self, item): + item.end.connect(self._remove) + item.started.connect(self._add) + item.error.connect(self._remove) + item.stopped.connect(self._remove) + item.interrupted.connect(self._remove) + + def _item_removed(self, item): + item.end.disconnect(self._remove) + item.started.disconnect(self._add) + item.error.disconnect(self._remove) + item.stopped.disconnect(self._remove) + item.interrupted.disconnect(self._remove) + + self._remove(item) def _model_reset(self): for cue in self.model: diff --git a/lisp/plugins/list_layout/settings.py b/lisp/plugins/list_layout/settings.py index db0df58ce..30c815670 100644 --- a/lisp/plugins/list_layout/settings.py +++ b/lisp/plugins/list_layout/settings.py @@ -19,8 +19,8 @@ from PyQt5.QtCore import Qt from PyQt5.QtGui import QKeySequence -from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QComboBox, \ - QHBoxLayout, QLabel, QKeySequenceEdit, QGridLayout +from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QHBoxLayout,\ + QLabel, QKeySequenceEdit, QGridLayout from lisp.ui.settings.pages import ConfigurationPage from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/presets/presets_ui.py b/lisp/plugins/presets/presets_ui.py index ec996088b..59cf8da6b 100644 --- a/lisp/plugins/presets/presets_ui.py +++ b/lisp/plugins/presets/presets_ui.py @@ -321,7 +321,7 @@ def __load_on_selected(self): if cues: load_on_cues(preset_name, cues) except OSError as e: - load_preset_error(e, preset_name, parent=MainWindow()) + load_preset_error(e, preset_name, parent=self) def __export_presets(self): names = [item.text() for item in self.presetsList.selectedItems()] From 7e9896f126f8d6e6c0ced85039f5d82207794816 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 16 May 2018 15:58:57 +0200 Subject: [PATCH 102/333] Add new "network" plugin to provide a REST API, replacing "remote". Add: new "Resume" action for cues (start only when paused) Add: new QProgressWheel widget (busy indicator) Update: refactoring of "core.loading" module for better flexibility Update: refactoring of "synchronizer" plugin to support the new "network" plugin Update: various updates --- Pipfile | 2 + Pipfile.lock | 60 +++++- lisp/application.py | 2 +- lisp/core/loading.py | 111 +++++++---- lisp/core/session.py | 3 +- lisp/core/util.py | 6 +- lisp/cues/cue.py | 14 +- lisp/cues/media_cue.py | 13 +- lisp/layout/cue_layout.py | 11 +- lisp/modules/remote2/__init__.py | 1 - lisp/modules/remote2/api/__init__.py | 19 -- lisp/modules/remote2/remote.py | 53 ----- lisp/plugins/network/__init__.py | 1 + lisp/plugins/network/api/__init__.py | 13 ++ .../remote2 => plugins/network}/api/cues.py | 60 +++--- .../remote2 => plugins/network}/api/layout.py | 23 +-- lisp/plugins/network/default.json | 10 + lisp/plugins/network/discovery.py | 122 ++++++++++++ lisp/plugins/network/discovery_dialogs.py | 186 ++++++++++++++++++ .../api.py => plugins/network/endpoint.py} | 12 +- lisp/plugins/network/network.py | 59 ++++++ lisp/plugins/network/server.py | 53 +++++ lisp/plugins/remote/__init__.py | 1 - lisp/plugins/remote/controller.py | 83 -------- lisp/plugins/remote/default.json | 10 - lisp/plugins/remote/discovery.py | 105 ---------- lisp/plugins/remote/dispatcher.py | 44 ----- lisp/plugins/remote/remote.py | 48 ----- lisp/plugins/synchronizer/default.json | 10 +- lisp/plugins/synchronizer/peers_dialog.py | 135 ------------- .../synchronizer/peers_discovery_dialog.py | 78 -------- lisp/plugins/synchronizer/synchronizer.py | 64 +++--- lisp/ui/widgets/cue_actions.py | 10 +- lisp/ui/widgets/qprogresswheel.py | 122 ++++++++++++ setup.py | 4 +- 35 files changed, 827 insertions(+), 721 deletions(-) delete mode 100644 lisp/modules/remote2/__init__.py delete mode 100644 lisp/modules/remote2/api/__init__.py delete mode 100644 lisp/modules/remote2/remote.py create mode 100644 lisp/plugins/network/__init__.py create mode 100644 lisp/plugins/network/api/__init__.py rename lisp/{modules/remote2 => plugins/network}/api/cues.py (55%) rename lisp/{modules/remote2 => plugins/network}/api/layout.py (65%) create mode 100644 lisp/plugins/network/default.json create mode 100644 lisp/plugins/network/discovery.py create mode 100644 lisp/plugins/network/discovery_dialogs.py rename lisp/{modules/remote2/api/api.py => plugins/network/endpoint.py} (75%) create mode 100644 lisp/plugins/network/network.py create mode 100644 lisp/plugins/network/server.py delete mode 100644 lisp/plugins/remote/__init__.py delete mode 100644 lisp/plugins/remote/controller.py delete mode 100644 lisp/plugins/remote/default.json delete mode 100644 lisp/plugins/remote/discovery.py delete mode 100644 lisp/plugins/remote/dispatcher.py delete mode 100644 lisp/plugins/remote/remote.py delete mode 100644 lisp/plugins/synchronizer/peers_dialog.py delete mode 100644 lisp/plugins/synchronizer/peers_discovery_dialog.py create mode 100644 lisp/ui/widgets/qprogresswheel.py diff --git a/Pipfile b/Pipfile index 9ce8f49a1..513ecf7bc 100644 --- a/Pipfile +++ b/Pipfile @@ -11,6 +11,8 @@ JACK-Client = "*" pyliblo = "*" "PyQt5" = "*" PyGObject = "*" +falcon = "*" +requests = "*" [dev-packages] Cython = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 195cdb497..108464cc0 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d52a4582f1e103e0e2ba16e4e18c69fe8f16d3ef2e690124fad596a699d97e3e" + "sha256": "c1700f263a741ff95932519e9a58f1dbcc400242fcd26fa20f8b6d2e90fbd9af" }, "pipfile-spec": 6, "requires": {}, @@ -14,6 +14,13 @@ ] }, "default": { + "certifi": { + "hashes": [ + "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7", + "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0" + ], + "version": "==2018.4.16" + }, "cffi": { "hashes": [ "sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743", @@ -46,6 +53,28 @@ ], "version": "==1.11.5" }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "falcon": { + "hashes": [ + "sha256:0a66b33458fab9c1e400a9be1a68056abda178eb02a8cb4b8f795e9df20b053b", + "sha256:3981f609c0358a9fcdb25b0e7fab3d9e23019356fb429c635ce4133135ae1bc4" + ], + "index": "pypi", + "version": "==1.4.1" + }, + "idna": { + "hashes": [ + "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", + "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4" + ], + "version": "==2.6" + }, "jack-client": { "hashes": [ "sha256:46998c8065bcfa1b6c9faa0005786be2175f21bdced3a5105462489656b1b063", @@ -98,6 +127,13 @@ "index": "pypi", "version": "==5.10.1" }, + "python-mimeparse": { + "hashes": [ + "sha256:76e4b03d700a641fd7761d3cd4fdbbdcd787eade1ebfac43f877016328334f78", + "sha256:a295f03ff20341491bfe4717a39cd0a8cc9afad619ba44b77e86b0ab8a2b8282" + ], + "version": "==1.6.0" + }, "python-rtmidi": { "hashes": [ "sha256:1187b2a3703d4ede2b02c0fccd395648a3a468d3cd57d7932f445ef1bfa9fc5d", @@ -115,6 +151,14 @@ "index": "pypi", "version": "==1.1.0" }, + "requests": { + "hashes": [ + "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", + "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" + ], + "index": "pypi", + "version": "==2.18.4" + }, "sip": { "hashes": [ "sha256:09f9a4e6c28afd0bafedb26ffba43375b97fe7207bd1a0d3513f79b7d168b331", @@ -132,6 +176,13 @@ ], "version": "==4.19.8" }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, "sortedcontainers": { "hashes": [ "sha256:566cf5f8dbada3aed99737a19d98f03d15d76bf2a6c27e4fb0f4a718a99be761", @@ -139,6 +190,13 @@ ], "index": "pypi", "version": "==1.5.10" + }, + "urllib3": { + "hashes": [ + "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", + "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" + ], + "version": "==1.22" } }, "develop": { diff --git a/lisp/application.py b/lisp/application.py index 0f5aed932..31f14b3fb 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -81,7 +81,7 @@ def __init__(self, app_conf): @property def session(self): - """:rtype: lisp.core.session.BaseSession""" + """:rtype: lisp.core.session.Session""" return self.__session @property diff --git a/lisp/core/loading.py b/lisp/core/loading.py index 58dbd22da..af681291a 100644 --- a/lisp/core/loading.py +++ b/lisp/core/loading.py @@ -29,7 +29,52 @@ logger = logging.getLogger(__name__) -class load_classes: +class ModulesLoader: + def __init__(self, pkg, pkg_path, exclude=()): + """ + :param pkg: dotted name of the package + :param pkg_path: path of the package to scan + :param exclude: iterable of excluded modules names + """ + self.pkg = pkg + self.excluded = exclude + self.pkg_path = pkg_path + + def __iter__(self): + return self.load_modules() + + def load_modules(self): + """Generate lists of tuples (class-name, class-object).""" + for entry in scandir(self.pkg_path): + + # Exclude __init__, __pycache__ and likely + if re.match('^__.*', entry.name): + continue + + mod_name = entry.name + if entry.is_file(): + # Split filename and extension + mod_name, ext = os.path.splitext(entry.name) + + # Exclude all non-python files + if not re.match('.py[cod]?', ext): + continue + + # Exclude excluded ¯\_(°^°)_/¯ + if mod_name in self.excluded: + continue + + mod_path = self.pkg + '.' + mod_name + + try: + # Import module + yield mod_name, import_module(mod_path) + except Exception: + logger.warning( + 'Cannot load module: {0}'.format(mod_name), exc_info=True) + + +class ClassLoader: """Generator for iterating over classes in a package. The class name must be the same as the module name, optionally @@ -68,51 +113,33 @@ def __init__(self, pkg, pkg_path, pre=('',), suf=('',), exclude=()): `pre` and `suf` must have the same length to work correctly, if some of the classes to load have no prefix/suffix an empty one should be used. """ - self.pkg = pkg self.prefixes = pre self.suffixes = suf - self.excluded = exclude - self.pkg_path = pkg_path - - def __iter__(self): - return self.load() - - def load(self): - """Generate lists of tuples (class-name, class-object).""" - for entry in scandir(self.pkg_path): - - # Exclude __init__, __pycache__ and likely - if re.match('^__.*', entry.name): - continue - - mod_name = entry.name - if entry.is_file(): - # Split filename and extension - mod_name, ext = os.path.splitext(entry.name) - - # Exclude all non-python files - if not re.match('.py[cod]?', ext): - continue - - # Exclude excluded ¯\_(°^°)_/¯ - if mod_name in self.excluded: - continue - mod_path = self.pkg + '.' + mod_name + self._mods_loader = ModulesLoader(pkg, pkg_path, exclude) - try: - # Import module - module = import_module(mod_path) - - # Load class from imported module - for prefix, suffix in zip(self.prefixes, self.suffixes): - name = module_to_class_name(mod_name, prefix, suffix) - if hasattr(module, name): - cls = getattr(module, name) - yield (name, cls) - except Exception: - logger.warning( - 'Cannot load module: {0}'.format(mod_name), exc_info=True) + def __iter__(self): + return self.load_classes() + + def load_classes(self): + for mod_name, module in self._mods_loader: + # Load classes from imported module + for prefix, suffix in zip(self.prefixes, self.suffixes): + cls_name = 'undefined' + try: + cls_name = module_to_class_name(mod_name, prefix, suffix) + if hasattr(module, cls_name): + cls = getattr(module, cls_name) + yield cls_name, cls + except Exception: + logger.warning( + 'Cannot load class: {0}'.format(cls_name), + exc_info=True + ) + + +def load_classes(pkg, pkg_path, pre=('',), suf=('',), exclude=()): + return ClassLoader(pkg, pkg_path, pre, suf, exclude) def module_to_class_name(mod_name, pre='', suf=''): diff --git a/lisp/core/session.py b/lisp/core/session.py index 463bb3aea..1cb66a68f 100644 --- a/lisp/core/session.py +++ b/lisp/core/session.py @@ -36,7 +36,6 @@ def __init__(self, layout): self.__layout = layout self.__cue_model = layout.cue_model - #self.__memento_model = CueMementoAdapter(layout.model_adapter) @property def cue_model(self): @@ -48,7 +47,7 @@ def cue_model(self): @property def layout(self): """ - :rtype: lisp.layouts.cue_layout.CueLayout + :rtype: lisp.layout.cue_layout.CueLayout """ return self.__layout diff --git a/lisp/core/util.py b/lisp/core/util.py index 0201d5698..d9a4eabb5 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -108,12 +108,12 @@ def strtime(time, accurate=False): return '{:02}:{:02}.00'.format(*time[1:3]) -def compose_http_url(address, port, path='/'): - """Compose an http URL.""" +def compose_url(protocol, host, port, path='/'): + """Compose a URL.""" if not path.startswith('/'): path = '/' + path - return 'http://{}:{}{}'.format(address, port, path) + return '{}://{}:{}{}'.format(protocol, host, port, path) def greatest_common_superclass(instances): diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 7afba4239..988945ed9 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -53,13 +53,15 @@ class CueAction(EqEnum): FadeIn = 'FadeIn' FadeOut = 'FadeOut' FadeInStart = 'FadeInStart' - FadeOutStop = 'FadeOutStop' + FadeInResume = 'FadeInResume' FadeOutPause = 'FadeOutPause' + FadeOutStop = 'FadeOutStop' FadeOutInterrupt = 'FadeOutInterrupt' Interrupt = 'Interrupt' Start = 'Start' - Stop = 'Stop' + Resume = 'Resume' Pause = 'Pause' + Stop = 'Stop' DoNothing = 'DoNothing' @@ -178,7 +180,7 @@ def execute(self, action=CueAction.Default): :param action: the action to be performed :type action: CueAction """ - if action == CueAction.Default: + if action is CueAction.Default: if self._state & CueState.IsRunning: action = CueAction(self.default_stop_action) else: @@ -191,7 +193,7 @@ def execute(self, action=CueAction.Default): self.interrupt() elif action is CueAction.FadeOutInterrupt: self.interrupt(fade=True) - if action is CueAction.Start: + elif action is CueAction.Start: self.start() elif action is CueAction.FadeInStart: self.start(fade=self.fadein_duration > 0) @@ -203,6 +205,10 @@ def execute(self, action=CueAction.Default): self.pause() elif action is CueAction.FadeOutPause: self.pause(fade=self.fadeout_duration > 0) + elif action is CueAction.Resume: + self.resume() + elif action is CueAction.FadeInResume: + self.resume(fade=self.fadein_duration > 0) elif action is CueAction.FadeOut: duration = AppConfig().get('cue.fadeAction', 0) fade = AppConfig().get( diff --git a/lisp/cues/media_cue.py b/lisp/cues/media_cue.py index 853d21d99..e8e46abd6 100644 --- a/lisp/cues/media_cue.py +++ b/lisp/cues/media_cue.py @@ -36,10 +36,15 @@ class MediaCue(Cue): default_start_action = Property(default=CueAction.FadeInStart.value) default_stop_action = Property(default=CueAction.FadeOutStop.value) - CueActions = (CueAction.Default, CueAction.Start, CueAction.FadeInStart, - CueAction.Stop, CueAction.FadeOutStop, CueAction.Pause, - CueAction.FadeOut, CueAction.FadeIn, CueAction.FadeOutPause, - CueAction.Interrupt, CueAction.FadeOutInterrupt) + CueActions = ( + CueAction.Default, + CueAction.Start, CueAction.FadeInStart, + CueAction.Resume, CueAction.FadeInResume, + CueAction.Stop, CueAction.FadeOutStop, + CueAction.Pause, CueAction.FadeOutPause, + CueAction.FadeOut, CueAction.FadeIn, + CueAction.Interrupt, CueAction.FadeOutInterrupt + ) def __init__(self, media, id=None): super().__init__(id=id) diff --git a/lisp/layout/cue_layout.py b/lisp/layout/cue_layout.py index 07a68b868..970587b87 100644 --- a/lisp/layout/cue_layout.py +++ b/lisp/layout/cue_layout.py @@ -49,6 +49,8 @@ def __init__(self, application=None): self.app = application self.cue_executed = Signal() # After a cue is executed + self.all_executed = Signal() # After execute_all is called + self.key_pressed = Signal() # After a key is pressed @property @@ -132,16 +134,21 @@ def go(self, action=CueAction.Default, advance=1): :rtype: lisp.cues.cue.Cue """ - def execute_all(self, action): + def execute_all(self, action, quiet=False): """Execute the given action on all the layout cues :param action: The action to be executed :type action: CueAction + :param quiet: If True `all_executed` is not emitted + :type quiet: bool """ for cue in self.cues(): cue.execute(action) - # TODO: replace usage with execute_all(action) + if not quiet: + self.all_executed.emit(action) + + # TODO: replace usage with execute_all(action) and remove def stop_all(self): fade = self.app.conf.get('layout.stopAllFade', False) for cue in self.cues(): diff --git a/lisp/modules/remote2/__init__.py b/lisp/modules/remote2/__init__.py deleted file mode 100644 index 9cc0c494e..000000000 --- a/lisp/modules/remote2/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .remote import Remote as Remote2 \ No newline at end of file diff --git a/lisp/modules/remote2/api/__init__.py b/lisp/modules/remote2/api/__init__.py deleted file mode 100644 index 48288fc6a..000000000 --- a/lisp/modules/remote2/api/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -import logging - -from .api import API - - -def load_api(app): - from . import cues - from . import layout - - load_module(cues, app) - load_module(layout, app) - - -def load_module(mod, app): - for cls in mod.API_EXPORT: - cls.route_to_app(app) - - logging.debug( - 'REMOTE2: Routed {} at {}'.format(cls.__name__, cls.UriTemplate)) diff --git a/lisp/modules/remote2/remote.py b/lisp/modules/remote2/remote.py deleted file mode 100644 index 43f38d455..000000000 --- a/lisp/modules/remote2/remote.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2017 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -import logging -from wsgiref.simple_server import make_server - -import falcon - -from lisp.core.decorators import async -from lisp.core.module import Module -from .api import load_api - - -class Remote(Module): - def __init__(self): - self.server = None - self.app = falcon.API() - # for development environment - self.app.resp_options.secure_cookies_by_default = False - - load_api(self.app) - - self.start() - - @async - def start(self): - with make_server('', 9090, self.app) as self.server: - logging.info('REMOTE2: Server started at {}'.format( - self.server.server_address)) - - self.server.serve_forever() - - logging.info('REMOTE2: Server stopped') - - def terminate(self): - if self.server is not None: - self.server.server_close() diff --git a/lisp/plugins/network/__init__.py b/lisp/plugins/network/__init__.py new file mode 100644 index 000000000..8779e25f1 --- /dev/null +++ b/lisp/plugins/network/__init__.py @@ -0,0 +1 @@ +from .network import Network \ No newline at end of file diff --git a/lisp/plugins/network/api/__init__.py b/lisp/plugins/network/api/__init__.py new file mode 100644 index 000000000..c998d2576 --- /dev/null +++ b/lisp/plugins/network/api/__init__.py @@ -0,0 +1,13 @@ +import logging +from os import path + +from lisp.core.loading import ModulesLoader + +logger = logging.getLogger(__name__) + + +def route_all(app, api): + for name, module in ModulesLoader(__package__, path.dirname(__file__)): + for endpoint in getattr(module, '__endpoints__', ()): + api.add_route(endpoint.UriTemplate, endpoint(app)) + logger.debug('New end-point: {}'.format(endpoint.UriTemplate)) diff --git a/lisp/modules/remote2/api/cues.py b/lisp/plugins/network/api/cues.py similarity index 55% rename from lisp/modules/remote2/api/cues.py rename to lisp/plugins/network/api/cues.py index fd4ed03f7..85b62b462 100644 --- a/lisp/modules/remote2/api/cues.py +++ b/lisp/plugins/network/api/cues.py @@ -21,66 +21,70 @@ import falcon -from lisp.application import Application from lisp.cues.cue import CueAction -from .api import API +from lisp.plugins.network.endpoint import EndPoint -def resolve_cue(req, resp, resource, params): - cue_id = params.pop('cue_id', None) - cue = Application().cue_model.get(cue_id) +def resolve_cue(app, cue_id): + cue = app.cue_model.get(cue_id) if cue is None: raise falcon.HTTPNotFound() - params['cue'] = cue + return cue -class API_CuesList(API): +class CuesListEndPoint(EndPoint): UriTemplate = '/cues' - def on_get(self, req, resp): - resp.status = falcon.HTTP_OK - resp.body = json.dumps({ - 'cues': tuple(Application().cue_model.keys()) + def on_get(self, request, response): + response.status = falcon.HTTP_OK + response.body = json.dumps({ + 'cues': tuple(self.app.cue_model.keys()) }) -@falcon.before(resolve_cue) -class API_Cue(API): +class CueEndPoint(EndPoint): UriTemplate = '/cues/{cue_id}' - def on_get(self, req, resp, cue): - resp.status = falcon.HTTP_OK - resp.body = json.dumps(cue.properties()) + def on_get(self, request, response, cue_id): + cue = resolve_cue(self.app, cue_id) + response.status = falcon.HTTP_OK + response.body = json.dumps(cue.properties()) -@falcon.before(resolve_cue) -class API_CueAction(API): + +class CueActionEndPoint(EndPoint): UriTemplate = '/cues/{cue_id}/action' - def on_post(self, req, resp, cue): + def on_post(self, request, response, cue_id): + cue = resolve_cue(self.app, cue_id) + try: - data = json.load(req.stream) + data = json.load(request.stream) action = CueAction(data['action']) cue.execute(action=action) - resp.status = falcon.HTTP_CREATED + response.status = falcon.HTTP_CREATED except(KeyError, json.JSONDecodeError): - resp.status = falcon.HTTP_BAD_REQUEST + response.status = falcon.HTTP_BAD_REQUEST -@falcon.before(resolve_cue) -class API_CueState(API): +class CueStateEndPoint(EndPoint): UriTemplate = '/cues/{cue_id}/state' - def on_get(self, req, resp, cue): - resp.status = falcon.HTTP_OK - resp.body = json.dumps({ + def on_get(self, request, response, cue_id): + cue = resolve_cue(self.app, cue_id) + + response.status = falcon.HTTP_OK + response.body = json.dumps({ 'state': cue.state, 'current_time': cue.current_time(), 'prewait_time': cue.prewait_time(), 'postwait_time': cue.postwait_time() }) -API_EXPORT = (API_Cue, API_CueAction, API_CuesList, API_CueState) + +__endpoints__ = ( + CueEndPoint, CueActionEndPoint, CuesListEndPoint, CueStateEndPoint +) diff --git a/lisp/modules/remote2/api/layout.py b/lisp/plugins/network/api/layout.py similarity index 65% rename from lisp/modules/remote2/api/layout.py rename to lisp/plugins/network/api/layout.py index 519b371c9..f3e5c759f 100644 --- a/lisp/modules/remote2/api/layout.py +++ b/lisp/plugins/network/api/layout.py @@ -21,33 +21,22 @@ import falcon -from lisp.application import Application -from .api import API +from lisp.cues.cue import CueAction +from lisp.plugins.network.endpoint import EndPoint -class API_LayoutAction(API): +class LayoutActionEndPoint(EndPoint): UriTemplate = '/layout/action' def on_post(self, req, resp): try: data = json.load(req.stream) - action = data['action'] + action = CueAction(data['action']) + self.app.layout.execute_all(action, quiet=True) resp.status = falcon.HTTP_CREATED - - if action == 'GO': - Application().layout.go() - elif action == 'STOP_ALL': - Application().layout.stop_all() - elif action == 'PAUSE_ALL': - Application().layout.pause_all() - elif action == 'RESUME_ALL': - Application().layout.resume_all() - else: - resp.status = falcon.HTTP_BAD_REQUEST - except(KeyError, json.JSONDecodeError): resp.status = falcon.HTTP_BAD_REQUEST -API_EXPORT = (API_LayoutAction, ) \ No newline at end of file +__endpoints__ = (LayoutActionEndPoint, ) diff --git a/lisp/plugins/network/default.json b/lisp/plugins/network/default.json new file mode 100644 index 000000000..888ac7cc7 --- /dev/null +++ b/lisp/plugins/network/default.json @@ -0,0 +1,10 @@ +{ + "_version_": "1", + "_enabled_": true, + "host": "0.0.0.0", + "port": 9090, + "discovery": { + "magic": "L1SP_R3m0te", + "port": 50000 + } +} \ No newline at end of file diff --git a/lisp/plugins/network/discovery.py b/lisp/plugins/network/discovery.py new file mode 100644 index 000000000..26043c669 --- /dev/null +++ b/lisp/plugins/network/discovery.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import socket +import socketserver +from threading import Thread + +from lisp.core.signal import Signal + + +class Announcer(Thread): + """Allow other hosts on a network to discover a specific "service" via UPD. + + While this class is called "Announcer" it act passively, only responding + to requests, it does not advertise itself on the network. + """ + + def __init__(self, ip, port, magic): + super().__init__(daemon=True) + self.server = socketserver.UDPServer((ip, port), AnnouncerUDPHandler) + self.server.bytes_magic = bytes(magic, 'utf-8') + + def run(self): + with self.server: + self.server.serve_forever() + + def stop(self): + self.server.shutdown() + self.join() + + +class AnnouncerUDPHandler(socketserver.BaseRequestHandler): + + def handle(self): + data = self.request[0].strip() + socket = self.request[1] + + if data == self.server.bytes_magic: + socket.sendto(data, self.client_address) + + +class Discoverer(Thread): + """Actively search for an announced "service" on a network. + + While it can run a continue scan, by default it only does a small number + of attempts to find active (announced) host on the network. + """ + + def __init__(self, port, magic, max_attempts=3, timeout=0.5, target=''): + super().__init__(daemon=True) + + self.address = (target, port) + self.bytes_magic = bytes(magic, 'utf-8') + self.max_attempts = max_attempts + self.timeout = timeout + + # Emitted when every time a new host is discovered + self.discovered = Signal() + # Emitted when the discovery is ended + self.ended = Signal() + + self._stop_flag = False + self._cache = set() + + def _new_socket(self): + """Return a new broadcast UDP socket""" + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True) + s.settimeout(self.timeout) + + return s + + def _send_beacon(self, socket): + socket.sendto(self.bytes_magic, self.address) + + def run(self): + self._stop_flag = False + + with self._new_socket() as socket: + self._send_beacon(socket) + attempts = 1 + + while not self._stop_flag: + try: + data, address = socket.recvfrom(1024) + host = address[0] + # Check if the response is valid and if the host + # has already replied + if data == self.bytes_magic and host not in self._cache: + self._cache.add(host) + self.discovered.emit(host) + except OSError: + if attempts >= self.max_attempts: + break + + attempts += 1 + self._send_beacon(socket) + + self.ended.emit() + + def stop(self): + self._stop_flag = True + self.join() + + def get_discovered(self): + return list(self._cache) diff --git a/lisp/plugins/network/discovery_dialogs.py b/lisp/plugins/network/discovery_dialogs.py new file mode 100644 index 000000000..03cda7e4e --- /dev/null +++ b/lisp/plugins/network/discovery_dialogs.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import Qt, QSize +from PyQt5.QtWidgets import QVBoxLayout, QListWidget, QDialogButtonBox, \ + QDialog, QHBoxLayout, QPushButton, QInputDialog, QListWidgetItem + +from lisp.core.signal import Connection +from lisp.plugins.network.discovery import Discoverer +from lisp.ui.ui_utils import translate +from lisp.ui.widgets.qprogresswheel import QProgressWheel + + +class HostDiscoveryDialog(QDialog): + def __init__(self, port, magic, **kwargs): + super().__init__(**kwargs) + self.setWindowModality(Qt.WindowModal) + self.setLayout(QVBoxLayout()) + self.setMaximumSize(300, 200) + self.setMinimumSize(300, 200) + self.resize(300, 200) + + self.listWidget = QListWidget(self) + self.listWidget.setAlternatingRowColors(True) + self.listWidget.setSelectionMode(self.listWidget.MultiSelection) + self.layout().addWidget(self.listWidget) + + self.progressWheel = QProgressWheel() + + self.progressItem = QListWidgetItem() + self.progressItem.setFlags(Qt.NoItemFlags) + self.progressItem.setSizeHint(QSize(30, 30)) + self.listWidget.addItem(self.progressItem) + self.listWidget.setItemWidget(self.progressItem, self.progressWheel) + + self.dialogButton = QDialogButtonBox(self) + self.dialogButton.setStandardButtons(self.dialogButton.Ok) + self.dialogButton.accepted.connect(self.accept) + self.layout().addWidget(self.dialogButton) + + self.retranslateUi() + + self._discoverer = Discoverer(port, magic, max_attempts=10) + self._discoverer.discovered.connect( + self._host_discovered, Connection.QtQueued) + self._discoverer.ended.connect(self._search_ended, Connection.QtQueued) + + def retranslateUi(self): + self.setWindowTitle(translate('NetworkDiscovery', 'Host discovery')) + + def accept(self): + self._discoverer.stop() + return super().accept() + + def reject(self): + self._discoverer.stop() + return super().reject() + + def exec_(self): + self.layout().activate() + self.progressWheel.startAnimation() + + self._discoverer.start() + return super().exec_() + + def hosts(self): + return [item.text() for item in self.listWidget.selectedItems()] + + def _host_discovered(self, host): + self.listWidget.insertItem(self.listWidget.count() - 1, host) + + def _search_ended(self): + self.listWidget.takeItem(self.listWidget.count() - 1) + self.progressWheel.stopAnimation() + + +class HostManagementDialog(QDialog): + def __init__(self, hosts, discovery_port, discovery_magic, **kwargs): + super().__init__(**kwargs) + self.discovery_port = discovery_port + self.discovery_magic = discovery_magic + + self.setWindowModality(Qt.WindowModal) + self.setLayout(QHBoxLayout()) + self.setMaximumSize(500, 200) + self.setMinimumSize(500, 200) + self.resize(500, 200) + + self.listWidget = QListWidget(self) + self.listWidget.setAlternatingRowColors(True) + self.listWidget.addItems(hosts) + self.layout().addWidget(self.listWidget) + + self.buttonsLayout = QVBoxLayout() + self.layout().addLayout(self.buttonsLayout) + + self.discoverHostsButton = QPushButton(self) + self.discoverHostsButton.clicked.connect(self.discoverHosts) + self.buttonsLayout.addWidget(self.discoverHostsButton) + + self.addHostButton = QPushButton(self) + self.addHostButton.clicked.connect(self.addHostDialog) + self.buttonsLayout.addWidget(self.addHostButton) + + self.removeHostButton = QPushButton(self) + self.removeHostButton.clicked.connect(self.removeSelectedHosts) + self.buttonsLayout.addWidget(self.removeHostButton) + + self.removeAllButton = QPushButton(self) + self.removeAllButton.clicked.connect(self.removeAllHosts) + self.buttonsLayout.addWidget(self.removeAllButton) + + self.buttonsLayout.addSpacing(70) + + self.dialogButton = QDialogButtonBox(self) + self.dialogButton.setStandardButtons(self.dialogButton.Ok) + self.dialogButton.accepted.connect(self.accept) + self.buttonsLayout.addWidget(self.dialogButton) + + self.layout().setStretch(0, 2) + self.layout().setStretch(1, 1) + + self.retranslateUi() + + def retranslateUi(self): + self.setWindowTitle( + translate('NetworkDiscovery', 'Manage hosts')) + self.discoverHostsButton.setText( + translate('NetworkDiscovery', 'Discover hosts')) + self.addHostButton.setText( + translate('NetworkDiscovery', 'Manually add a host')) + self.removeHostButton.setText( + translate('NetworkDiscovery', 'Remove selected host')) + self.removeAllButton.setText( + translate('NetworkDiscovery', 'Remove all host')) + + def addHostDialog(self): + host, ok = QInputDialog.getText( + self, + translate('NetworkDiscovery', 'Address'), + translate('NetworkDiscovery', 'Host IP')) + + if ok: + self.addHost(host) + + def addHost(self, host): + if not self.listWidget.findItems(host, Qt.MatchExactly): + self.listWidget.addItem(host) + + def discoverHosts(self): + dialog = HostDiscoveryDialog( + self.discovery_port, self.discovery_magic, parent=self) + + if dialog.exec_() == dialog.Accepted: + for host in dialog.hosts(): + self.addHost(host) + + def removeSelectedHosts(self): + for index in self.listWidget.selectedIndexes(): + self.listWidget.takeItem(index) + + def removeAllHosts(self): + self.listWidget.clear() + + def hosts(self): + hosts = [] + for index in range(self.listWidget.count()): + hosts.append(self.listWidget.item(index).text()) + + return hosts diff --git a/lisp/modules/remote2/api/api.py b/lisp/plugins/network/endpoint.py similarity index 75% rename from lisp/modules/remote2/api/api.py rename to lisp/plugins/network/endpoint.py index 34484a1d6..57d6d910a 100644 --- a/lisp/modules/remote2/api/api.py +++ b/lisp/plugins/network/endpoint.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,13 +18,11 @@ # along with Linux Show Player. If not, see . -class API: +class EndPoint: UriTemplate = '' - @classmethod - def route_to_app(cls, app): + def __init__(self, application): """ - :param app: The app to add the route to - :type app: falcon.API + :type application: lisp.application.Application """ - app.add_route(cls.UriTemplate, cls()) + self.app = application diff --git a/lisp/plugins/network/network.py b/lisp/plugins/network/network.py new file mode 100644 index 000000000..63d068ab0 --- /dev/null +++ b/lisp/plugins/network/network.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2017 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import falcon + +from lisp.core.plugin import Plugin +from lisp.plugins.network.api import route_all +from lisp.plugins.network.server import APIServerThread +from lisp.plugins.network.discovery import Announcer + + +class Network(Plugin): + Name = 'Network' + Description = 'Allow the application to be controlled via network.' + Authors = ('Francesco Ceruti', ) + + def __init__(self, app): + super().__init__(app) + self.api = falcon.API() + # We don't support HTTPS (yet?) + self.api.resp_options.secure_cookies_by_default = False + # Load all the api endpoints + route_all(self.app, self.api) + + # WSGI Server + self.server = APIServerThread( + Network.Config['host'], + Network.Config['port'], + self.api + ) + self.server.start() + + # Announcer + self.announcer = Announcer( + Network.Config['host'], + Network.Config['discovery.port'], + Network.Config['discovery.magic'] + ) + self.announcer.start() + + def terminate(self): + self.announcer.stop() + self.server.stop() diff --git a/lisp/plugins/network/server.py b/lisp/plugins/network/server.py new file mode 100644 index 000000000..84b9ada8d --- /dev/null +++ b/lisp/plugins/network/server.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import logging +from threading import Thread +from wsgiref.simple_server import make_server + +from lisp.core.decorators import async + +logger = logging.getLogger(__name__) + + +class APIServerThread(Thread): + def __init__(self, host, port, api): + super().__init__(daemon=True) + self.wsgi_server = make_server(host, port, api) + + def run(self): + try: + logger.info( + 'Start serving remote API at: Host="{}" Port="{}"'.format( + self.wsgi_server.server_address[0], + self.wsgi_server.server_address[1], + ) + ) + + self.wsgi_server.serve_forever() + + logger.info('Stop serving remote API') + except Exception: + logger.exception('Remote API server stopped working.') + + def stop(self): + self.wsgi_server.shutdown() + self.wsgi_server.server_close() + + self.join() diff --git a/lisp/plugins/remote/__init__.py b/lisp/plugins/remote/__init__.py deleted file mode 100644 index 1c595aa74..000000000 --- a/lisp/plugins/remote/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .remote import Remote diff --git a/lisp/plugins/remote/controller.py b/lisp/plugins/remote/controller.py deleted file mode 100644 index 04101c451..000000000 --- a/lisp/plugins/remote/controller.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2018 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -import logging -import socket -from http.client import HTTPConnection -from xmlrpc.client import ServerProxy, Fault, Transport -from xmlrpc.server import SimpleXMLRPCServer - -from lisp.core.decorators import async -from lisp.plugins.remote.dispatcher import RemoteDispatcher - -logger = logging.getLogger(__name__) - - -class TimeoutTransport(Transport): - timeout = 2.0 - - def set_timeout(self, timeout): - self.timeout = timeout - - def make_connection(self, host): - return HTTPConnection(host, timeout=self.timeout) - - -class RemoteController: - """Provide control over a SimpleXMLRPCServer.""" - - def __init__(self, app, ip, port): - self.server = SimpleXMLRPCServer( - (ip, port), allow_none=True, logRequests=False) - - self.server.register_introspection_functions() - self.server.register_instance(RemoteDispatcher(app)) - - @async - def start(self): - logger.info( - 'Remote network session started: IP="{}" Port="{}"'.format( - self.server.server_address[0], - self.server.server_address[1] - ) - ) - - # Blocking call - self.server.serve_forever() - - logger.info('Remote network session ended.') - - def stop(self): - self.server.shutdown() - - @staticmethod - def connect_to(uri): - proxy = ServerProxy(uri, transport=TimeoutTransport()) - - try: - # Call a fictive method. - proxy._() - except Fault: - # Connected, the method doesn't exist, which is expected. - pass - except socket.error: - # Not connected, socket error mean that the service is unreachable. - raise OSError('Session at ' + uri + ' is unreachable') - - return proxy diff --git a/lisp/plugins/remote/default.json b/lisp/plugins/remote/default.json deleted file mode 100644 index dc14669ee..000000000 --- a/lisp/plugins/remote/default.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "_version_": "2", - "_enabled_": true, - "ip": "0.0.0.0", - "port": 8070, - "discovery": { - "port": 50000, - "magic": "L1SPR3m0t3" - } -} \ No newline at end of file diff --git a/lisp/plugins/remote/discovery.py b/lisp/plugins/remote/discovery.py deleted file mode 100644 index ba5c30fc8..000000000 --- a/lisp/plugins/remote/discovery.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -import socket -from threading import Thread - -from lisp.core.signal import Signal - - -class Announcer(Thread): - - def __init__(self, ip, port, magic): - super().__init__(daemon=True) - self.magic = magic - self.address = (ip, port) - - # Create UDP socket - self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True) - self._socket.bind(self.address) - - self._running = True - - def run(self): - while self._running: - data, addr = self._socket.recvfrom(1024) - if data is not None and addr is not None: - data = str(data, 'utf-8') - if data == self.magic: - data += socket.gethostname() - self._socket.sendto(bytes(data, 'utf-8'), addr) - - def stop(self): - self._running = False - try: - self._socket.shutdown(socket.SHUT_RDWR) - except Exception: - pass - self._socket.close() - self.join() - - -class Discoverer(Thread): - - def __init__(self, ip, port, magic): - super().__init__(daemon=True) - self.magic = magic - self.address = (ip, port) - - self.discovered = Signal() - - # Create UDP socket - self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True) - - self._running = True - self._cache = [] - - def run(self): - self._socket.sendto( - bytes(self.magic, 'utf-8'), self.address - ) - - while self._running: - data, addr = self._socket.recvfrom(1024) - if data is not None and addr is not None: - data = str(data, 'utf-8') - # Take only the IP, discard the port - addr = addr[0] - # Check if valid and new announcement - if data.startswith(self.magic) and addr not in self._cache: - # Get the host name - host = data[len(self.magic):] - # Append the adders to the cache - self._cache.append(addr) - # Emit - self.discovered.emit((addr, host)) - - def stop(self): - self._running = False - try: - self._socket.shutdown(socket.SHUT_RDWR) - except Exception: - pass - self._socket.close() - self.join() - - def get_discovered(self): - return self._cache.copy() diff --git a/lisp/plugins/remote/dispatcher.py b/lisp/plugins/remote/dispatcher.py deleted file mode 100644 index f4623387b..000000000 --- a/lisp/plugins/remote/dispatcher.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from lisp.cues.cue import Cue - - -class RemoteDispatcher: - # Layout functions - - def __init__(self, app): - self.app = app - - def get_cue_at(self, index): - cue = self.app.layout.model_adapter.item(index) - if cue is not None: - return cue.properties() - return {} - - def get_cues(self, cue_class=Cue): - cues = self.app.cue_model.filter(cue_class) - return [cue.properties() for cue in cues] - - # Cue function - - def execute(self, index): - cue = self.app.layout.model_adapter.item(index) - if cue is not None: - cue.execute() diff --git a/lisp/plugins/remote/remote.py b/lisp/plugins/remote/remote.py deleted file mode 100644 index 10c6d8013..000000000 --- a/lisp/plugins/remote/remote.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from lisp.core.plugin import Plugin -from lisp.plugins.remote.controller import RemoteController -from lisp.plugins.remote.discovery import Announcer - - -class Remote(Plugin): - """Provide remote control over cues via RPCs calls.""" - - Name = 'Remote' - Authors = ('Francesco Ceruti', ) - Description = 'Provide remote control of cues over network' - - def __init__(self, app): - super().__init__(app) - - self._controller = RemoteController( - app, Remote.Config['ip'], Remote.Config['port']) - self._controller.start() - - self._announcer = Announcer( - Remote.Config['ip'], - Remote.Config['discovery.port'], - Remote.Config['discovery.magic'] - ) - self._announcer.start() - - def terminate(self): - self._controller.stop() - self._announcer.stop() diff --git a/lisp/plugins/synchronizer/default.json b/lisp/plugins/synchronizer/default.json index dc14669ee..fa0688dfc 100644 --- a/lisp/plugins/synchronizer/default.json +++ b/lisp/plugins/synchronizer/default.json @@ -1,10 +1,10 @@ { - "_version_": "2", + "_version_": "1.2", "_enabled_": true, - "ip": "0.0.0.0", - "port": 8070, + "port": 9090, + "protocol": "http", "discovery": { - "port": 50000, - "magic": "L1SPR3m0t3" + "magic": "L1SP_R3m0te", + "port": 50000 } } \ No newline at end of file diff --git a/lisp/plugins/synchronizer/peers_dialog.py b/lisp/plugins/synchronizer/peers_dialog.py deleted file mode 100644 index 600e48600..000000000 --- a/lisp/plugins/synchronizer/peers_dialog.py +++ /dev/null @@ -1,135 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2018 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -import logging - -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QDialog, QHBoxLayout, QListWidget, QVBoxLayout, \ - QPushButton, QDialogButtonBox, QInputDialog, QMessageBox - -from lisp.core.util import compose_http_url -from lisp.plugins.remote.remote import RemoteController -from lisp.ui.ui_utils import translate -from .peers_discovery_dialog import PeersDiscoveryDialog - - -class PeersDialog(QDialog): - def __init__(self, peers, config, **kwargs): - super().__init__(**kwargs) - self.peers = peers - self.config = config - - self.setWindowModality(Qt.ApplicationModal) - self.setMaximumSize(500, 200) - self.setMinimumSize(500, 200) - self.resize(500, 200) - - self.setLayout(QHBoxLayout()) - - self.listWidget = QListWidget(self) - self.listWidget.setAlternatingRowColors(True) - self.layout().addWidget(self.listWidget) - - for peer in self.peers: - self.listWidget.addItem(peer['uri']) - - self.buttonsLayout = QVBoxLayout() - self.layout().addLayout(self.buttonsLayout) - - self.discoverPeersButton = QPushButton(self) - self.addPeerButton = QPushButton(self) - self.removePeerButton = QPushButton(self) - self.removeAllButton = QPushButton(self) - - self.discoverPeersButton.clicked.connect(self.discover_peers) - self.addPeerButton.clicked.connect(self.add_peer) - self.removePeerButton.clicked.connect(self.remove_peer) - self.removeAllButton.clicked.connect(self.remove_all) - - self.buttonsLayout.addWidget(self.discoverPeersButton) - self.buttonsLayout.addWidget(self.addPeerButton) - self.buttonsLayout.addWidget(self.removePeerButton) - self.buttonsLayout.addWidget(self.removeAllButton) - self.buttonsLayout.addSpacing(70) - - self.dialogButton = QDialogButtonBox(self) - self.dialogButton.setStandardButtons(self.dialogButton.Ok) - self.dialogButton.accepted.connect(self.accept) - self.buttonsLayout.addWidget(self.dialogButton) - - self.layout().setStretch(0, 2) - self.layout().setStretch(1, 1) - - self.retranslateUi() - - def retranslateUi(self): - self.setWindowTitle( - translate('SyncPeerDialog', 'Manage connected peers')) - self.discoverPeersButton.setText( - translate('SyncPeerDialog', 'Discover peers')) - self.addPeerButton.setText( - translate('SyncPeerDialog', 'Manually add a peer')) - self.removePeerButton.setText( - translate('SyncPeerDialog', 'Remove selected peer')) - self.removeAllButton.setText( - translate('SyncPeerDialog', 'Remove all peers')) - - def add_peer(self): - ip, ok = QInputDialog.getText( - self, - translate('SyncPeerDialog', 'Address'), - translate('SyncPeerDialog', 'Peer IP')) - if ok: - self._add_peer(ip) - - def _add_peer(self, ip): - uri = compose_http_url(ip, self.config['port']) - - for peer in self.peers: - if peer['uri'] == uri: - QMessageBox.critical( - self, - translate('SyncPeerDialog', 'Error'), - translate('SyncPeerDialog', 'Already connected')) - return - - try: - peer = {'proxy': RemoteController.connect_to(uri), 'uri': uri} - self.peers.append(peer) - self.listWidget.addItem(peer['uri']) - except Exception: - logging.exception(translate('SyncPeerDialog', 'Cannot add peer.')) - - def discover_peers(self): - dialog = PeersDiscoveryDialog(self.config, parent=self) - if dialog.exec_() == dialog.Accepted: - for peer in dialog.get_peers(): - self._add_peer(peer) - - def remove_peer(self): - if self.listWidget.selectedIndexes(): - self.peers.pop(self.current_index()) - self.listWidget.takeItem(self.current_index()) - - def remove_all(self): - self.peers.clear() - self.listWidget.clear() - - def current_index(self): - return self.listWidget.selectedIndexes()[0].row() diff --git a/lisp/plugins/synchronizer/peers_discovery_dialog.py b/lisp/plugins/synchronizer/peers_discovery_dialog.py deleted file mode 100644 index fc2d0bef4..000000000 --- a/lisp/plugins/synchronizer/peers_discovery_dialog.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QVBoxLayout, QListWidget, QDialogButtonBox, \ - QListWidgetItem, QDialog - -from lisp.core.signal import Connection -from lisp.plugins.remote.discovery import Discoverer -from lisp.ui.ui_utils import translate - - -class PeersDiscoveryDialog(QDialog): - def __init__(self, config, **kwargs): - super().__init__(**kwargs) - - self.setWindowModality(Qt.ApplicationModal) - self.setLayout(QVBoxLayout()) - self.setMaximumSize(300, 200) - self.setMinimumSize(300, 200) - self.resize(300, 200) - - self.listWidget = QListWidget(self) - self.listWidget.setAlternatingRowColors(True) - self.listWidget.setSelectionMode(self.listWidget.MultiSelection) - self.layout().addWidget(self.listWidget) - - self.dialogButton = QDialogButtonBox(self) - self.dialogButton.setStandardButtons(self.dialogButton.Ok) - self.dialogButton.accepted.connect(self.accept) - self.layout().addWidget(self.dialogButton) - - self.retranslateUi() - - self._discoverer = Discoverer( - config['ip'], config['discovery.port'], config['discovery.magic'] - ) - self._discoverer.discovered.connect(self._new_peer, Connection.QtQueued) - - def retranslateUi(self): - self.setWindowTitle(translate('Synchronizer', 'Discovering peers ...')) - - def accept(self): - self._discoverer.stop() - return super().accept() - - def reject(self): - self._discoverer.stop() - return super().reject() - - def exec_(self): - self._discoverer.start() - return super().exec_() - - def get_peers(self): - return [item.address for item in self.listWidget.selectedItems()] - - def _new_peer(self, peer): - item = QListWidgetItem(peer[1] + ' - ' + peer[0]) - item.address = peer[0] - - self.listWidget.addItem(item) diff --git a/lisp/plugins/synchronizer/synchronizer.py b/lisp/plugins/synchronizer/synchronizer.py index 710cb9a4f..66c754b6e 100644 --- a/lisp/plugins/synchronizer/synchronizer.py +++ b/lisp/plugins/synchronizer/synchronizer.py @@ -18,14 +18,15 @@ # along with Linux Show Player. If not, see . import logging - +import requests from PyQt5.QtWidgets import QMenu, QAction, QMessageBox from lisp.core.plugin import Plugin from lisp.core.signal import Connection -from lisp.core.util import get_lan_ip +from lisp.core.util import get_lan_ip, compose_url +from lisp.cues.cue import CueAction +from lisp.plugins.network.discovery_dialogs import HostManagementDialog from lisp.ui.ui_utils import translate -from .peers_dialog import PeersDialog logger = logging.getLogger(__name__) @@ -33,17 +34,21 @@ class Synchronizer(Plugin): Name = 'Synchronizer' Authors = ('Francesco Ceruti',) - Depends = ('Remote',) + OptDepends = ('Network',) Description = 'Keep multiple sessions, on a network, synchronized' def __init__(self, app): super().__init__(app) + self.peers = [] + self.app.session_created.connect(self.session_init) self.syncMenu = QMenu(translate('Synchronizer', 'Synchronization')) self.menu_action = self.app.window.menuTools.addMenu(self.syncMenu) self.addPeerAction = QAction( - translate('Synchronizer', 'Manage connected peers'), self.app.window) + translate('Synchronizer', 'Manage connected peers'), + self.app.window + ) self.addPeerAction.triggered.connect(self.manage_peers) self.syncMenu.addAction(self.addPeerAction) @@ -52,32 +57,45 @@ def __init__(self, app): self.showIpAction.triggered.connect(self.show_ip) self.syncMenu.addAction(self.showIpAction) - self.peers = [] - self.cue_media = {} - - self.app.session_created.connect(self.session_init) - self.app.session_before_finalize.connect(self.session_reset) - def session_init(self): self.app.layout.cue_executed.connect( - self.remote_execute, mode=Connection.Async) - - def session_reset(self): - self.peers.clear() - self.cue_media.clear() + self._cue_executes, mode=Connection.Async) + self.app.layout.all_executed.connect( + self._all_executed, mode=Connection.Async) def manage_peers(self): - manager = PeersDialog( - self.peers, Synchronizer.Config, parent=self.app.window) + manager = HostManagementDialog( + self.peers, + Synchronizer.Config['discovery.port'], + Synchronizer.Config['discovery.magic'], + parent=self.app.window + ) manager.exec_() + self.peers = manager.hosts() + def show_ip(self): ip = translate('Synchronizer', 'Your IP is:') + ' ' + str(get_lan_ip()) QMessageBox.information(self.app.window, ' ', ip) - def remote_execute(self, cue): + def _url(self, host, path): + return compose_url( + Synchronizer.Config['protocol'], + host, + Synchronizer.Config['port'], + path + ) + + def _cue_executes(self, cue): + for peer in self.peers: + requests.post( + self._url(peer, '/cues/{}/action'.format(cue.id)), + json={'action': CueAction.Default.value} + ) + + def _all_executed(self, action): for peer in self.peers: - try: - peer['proxy'].execute(cue.index) - except Exception: - logger.warning('Remote control failed.', exc_info=True) + requests.post( + self._url(peer, '/layout/action'), + json={'action': action.value} + ) diff --git a/lisp/ui/widgets/cue_actions.py b/lisp/ui/widgets/cue_actions.py index 0ba91063e..f3d3834e7 100644 --- a/lisp/ui/widgets/cue_actions.py +++ b/lisp/ui/widgets/cue_actions.py @@ -29,13 +29,15 @@ CueActionsStrings = { CueAction.Default: QT_TRANSLATE_NOOP('CueAction', 'Default'), CueAction.FadeInStart: QT_TRANSLATE_NOOP('CueAction', 'Faded Start'), - CueAction.FadeOutStop: QT_TRANSLATE_NOOP('CueAction', 'Faded Stop'), + CueAction.FadeInResume: QT_TRANSLATE_NOOP('CueAction', 'Faded Resume'), CueAction.FadeOutPause: QT_TRANSLATE_NOOP('CueAction', 'Faded Pause'), - CueAction.FadeOutInterrupt: QT_TRANSLATE_NOOP( - 'CueAction', 'Faded Interrupt'), + CueAction.FadeOutStop: QT_TRANSLATE_NOOP('CueAction', 'Faded Stop'), + CueAction.FadeOutInterrupt: + QT_TRANSLATE_NOOP('CueAction', 'Faded Interrupt'), CueAction.Start: QT_TRANSLATE_NOOP('CueAction', 'Start'), - CueAction.Stop: QT_TRANSLATE_NOOP('CueAction', 'Stop'), + CueAction.Resume: QT_TRANSLATE_NOOP('CueAction', 'Resume'), CueAction.Pause: QT_TRANSLATE_NOOP('CueAction', 'Pause'), + CueAction.Stop: QT_TRANSLATE_NOOP('CueAction', 'Stop'), CueAction.DoNothing: QT_TRANSLATE_NOOP('CueAction', 'Do Nothing'), } diff --git a/lisp/ui/widgets/qprogresswheel.py b/lisp/ui/widgets/qprogresswheel.py new file mode 100644 index 000000000..43d7fcff8 --- /dev/null +++ b/lisp/ui/widgets/qprogresswheel.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# This file contains a PyQt5 version of QProgressIndicator.cpp from +# https://github.com/mojocorp/QProgressIndicator +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + + +from PyQt5.QtCore import Qt, QSize, QRectF +from PyQt5.QtGui import QPainter +from PyQt5.QtWidgets import QWidget, QSizePolicy + + +class QProgressWheel(QWidget): + + def __init__(self, *args): + super().__init__(*args) + self._angle = 0 + self._delay = 100 + self._displayedWhenStopped = False + self._timerId = -1 + + self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.setFocusPolicy(Qt.NoFocus) + + @property + def delay(self): + return self._delay + + @delay.setter + def delay(self, delay): + if self._timerId != -1: + self.killTimer(self._timerId) + + self._delay = delay + + if self._timerId != -1: + self._timerId = self.startTimer(self._delay) + + @property + def displayedWhenStopped(self): + return self._displayedWhenStopped + + @displayedWhenStopped.setter + def displayedWhenStopped(self, display): + self._displayedWhenStopped = display + self.update() + + def isAnimated(self): + return self._timerId != -1 + + def startAnimation(self): + self._angle = 0 + + if self._timerId == -1: + self._timerId = self.startTimer(self._delay) + + def stopAnimation(self): + if self._timerId != -1: + self.killTimer(self._timerId) + + self._timerId = -1 + self.update() + + def sizeHint(self): + return QSize(20, 20) + + def timerEvent(self, event): + self._angle = (self._angle + 30) % 360 + self.update() + + def paintEvent(self, event): + super().paintEvent(event) + + if not self._displayedWhenStopped and not self.isAnimated(): + return + + width = min(self.width(), self.height()) + + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) + + outerRadius = (width - 1) * 0.5 + innerRadius = (width - 1) * 0.5 * 0.38 + + capsuleHeight = outerRadius - innerRadius + capsuleWidth = capsuleHeight * (.23 if width > 32 else .35) + capsuleRadius = capsuleWidth / 2 + + # Use the pen (text) color to draw the "capsules" + color = painter.pen().color() + painter.setPen(Qt.NoPen) + painter.translate(self.rect().center()) + painter.rotate(self._angle) + + for i in range(0, 12): + color.setAlphaF(1.0 - (i / 12.0) if self.isAnimated() else 0.2) + painter.setBrush(color) + painter.rotate(-30) + + painter.drawRoundedRect( + QRectF( + capsuleWidth * -0.5, (innerRadius + capsuleHeight) * -1, + capsuleWidth, capsuleHeight + ), + capsuleRadius, capsuleRadius + ) diff --git a/setup.py b/setup.py index 58dca74ca..61037ff6c 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,9 @@ def find_files(directory, regex='.*', rel=''): 'python-rtmidi', 'JACK-Client', 'pyliblo', - 'scandir;python_version<"3.5"' + 'flacon', + 'requests', + 'scandir;python_version<"3.5"', 'falcon' ], packages=find_packages(), package_data={ From 903618cd96a427d652de7a9dba4ed8541f08e827 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 16 May 2018 23:40:53 +0200 Subject: [PATCH 103/333] Improved network wsgi server logging --- lisp/plugins/list_layout/layout.py | 2 ++ lisp/plugins/network/server.py | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index cfa7f1c8d..a855082ee 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -137,6 +137,8 @@ def __init__(self, **kwargs): # CONTROL-BUTTONS (top-right) self.controlButtons = ShowControlButtons(parent=self) self.controlButtons.setFixedHeight(100) + + # TODO execute_all self.controlButtons.stopButton.clicked.connect(self.stop_all) self.controlButtons.pauseButton.clicked.connect(self.pause_all) self.controlButtons.fadeInButton.clicked.connect(self.fadein_all) diff --git a/lisp/plugins/network/server.py b/lisp/plugins/network/server.py index 84b9ada8d..9734816c9 100644 --- a/lisp/plugins/network/server.py +++ b/lisp/plugins/network/server.py @@ -19,9 +19,7 @@ import logging from threading import Thread -from wsgiref.simple_server import make_server - -from lisp.core.decorators import async +from wsgiref.simple_server import make_server, WSGIRequestHandler logger = logging.getLogger(__name__) @@ -29,12 +27,13 @@ class APIServerThread(Thread): def __init__(self, host, port, api): super().__init__(daemon=True) - self.wsgi_server = make_server(host, port, api) + self.wsgi_server = make_server( + host, port, api, handler_class=APIRequestHandler) def run(self): try: logger.info( - 'Start serving remote API at: Host="{}" Port="{}"'.format( + 'Start serving network API at: http://{}:{}/'.format( self.wsgi_server.server_address[0], self.wsgi_server.server_address[1], ) @@ -42,12 +41,18 @@ def run(self): self.wsgi_server.serve_forever() - logger.info('Stop serving remote API') + logger.info('Stop serving network API') except Exception: - logger.exception('Remote API server stopped working.') + logger.exception('Network API server stopped working.') def stop(self): self.wsgi_server.shutdown() self.wsgi_server.server_close() self.join() + + +class APIRequestHandler(WSGIRequestHandler): + + def log_message(self, format, *args): + logger.debug(format % args) From 8ba185ff04a697ce45630521db63e24e381b97eb Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 18 May 2018 09:28:36 +0200 Subject: [PATCH 104/333] Changed some function to use `CueLayout.execute_all`. --- lisp/layout/cue_layout.py | 35 ++++++++++++++-------------- lisp/plugins/action_cues/stop_all.py | 30 ++++++------------------ lisp/plugins/list_layout/layout.py | 1 - 3 files changed, 25 insertions(+), 41 deletions(-) diff --git a/lisp/layout/cue_layout.py b/lisp/layout/cue_layout.py index 970587b87..f64dd21b6 100644 --- a/lisp/layout/cue_layout.py +++ b/lisp/layout/cue_layout.py @@ -148,34 +148,35 @@ def execute_all(self, action, quiet=False): if not quiet: self.all_executed.emit(action) - # TODO: replace usage with execute_all(action) and remove def stop_all(self): - fade = self.app.conf.get('layout.stopAllFade', False) - for cue in self.cues(): - cue.stop(fade=fade) + if self.app.conf.get('layout.stopAllFade', False): + self.execute_all(CueAction.FadeOutStop) + else: + self.execute_all(CueAction.Stop) def interrupt_all(self): - fade = self.app.conf.get('layout.interruptAllFade', False) - for cue in self.cues(): - cue.interrupt(fade=fade) + if self.app.conf.get('layout.interruptAllFade', False): + self.execute_all(CueAction.FadeOutInterrupt) + else: + self.execute_all(CueAction.Interrupt) def pause_all(self): - fade = self.app.conf.get('layout.pauseAllFade', False) - for cue in self.cues(): - cue.pause(fade=fade) + if self.app.conf.get('layout.pauseAllFade', False): + self.execute_all(CueAction.FadeOutPause) + else: + self.execute_all(CueAction.FadeOut) def resume_all(self): - fade = self.app.conf.get('layout.resumeAllFade', True) - for cue in self.cues(): - cue.resume(fade=fade) + if self.app.conf.get('layout.resumeAllFade', True): + self.execute_all(CueAction.FadeInResume) + else: + self.execute_all(CueAction.Resume) def fadein_all(self): - for cue in self.cues(): - cue.execute(CueAction.FadeIn) + self.execute_all(CueAction.FadeIn) def fadeout_all(self): - for cue in self.cues(): - cue.execute(CueAction.FadeOut) + self.execute_all(CueAction.FadeOut) def edit_cue(self, cue): dialog = CueSettingsDialog(cue, parent=self.app.window) diff --git a/lisp/plugins/action_cues/stop_all.py b/lisp/plugins/action_cues/stop_all.py index 9bea7452c..0f72a8db8 100644 --- a/lisp/plugins/action_cues/stop_all.py +++ b/lisp/plugins/action_cues/stop_all.py @@ -38,31 +38,17 @@ def __init__(self, **kwargs): self.name = translate('CueName', self.Name) def __start__(self, fade=False): - for cue in Application().cue_model: - action = self.__adjust_action(cue, CueAction(self.action)) - if action: - cue.execute(action=action) - - return False - - def __adjust_action(self, cue, action, fade=False): - if action in cue.CueActions: - return action - elif action is CueAction.FadeOutPause: - return self.__adjust_action(cue, CueAction.Pause, True) - elif action is CueAction.Pause and fade: - return self.__adjust_action(cue, CueAction.FadeOutStop) - elif action is CueAction.FadeOutInterrupt: - return self.__adjust_action(cue, CueAction.Interrupt) - elif action is CueAction.FadeOutStop: - return self.__adjust_action(cue, CueAction.Stop) - - return None + Application().layout.execute_all(action=self.action, quiet=True) class StopAllSettings(SettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Stop Settings') + SupportedActions = [ + CueAction.Stop, CueAction.FadeOutStop, CueAction.Pause, + CueAction.FadeOutPause, CueAction.Interrupt, CueAction.FadeOutInterrupt + ] + def __init__(self, **kwargs): super().__init__(**kwargs) self.setLayout(QVBoxLayout()) @@ -73,9 +59,7 @@ def __init__(self, **kwargs): self.layout().addWidget(self.group) self.actionCombo = QComboBox(self.group) - for action in [CueAction.Stop, CueAction.FadeOutStop, CueAction.Pause, - CueAction.FadeOutPause, CueAction.Interrupt, - CueAction.FadeOutInterrupt]: + for action in StopAllSettings.SupportedActions: self.actionCombo.addItem( translate('CueAction', action.name), action.value) self.group.layout().addWidget(self.actionCombo) diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index a855082ee..cecd70312 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -138,7 +138,6 @@ def __init__(self, **kwargs): self.controlButtons = ShowControlButtons(parent=self) self.controlButtons.setFixedHeight(100) - # TODO execute_all self.controlButtons.stopButton.clicked.connect(self.stop_all) self.controlButtons.pauseButton.clicked.connect(self.pause_all) self.controlButtons.fadeInButton.clicked.connect(self.fadein_all) From 0db82a8f620343facbaf0990e0344d5505797a00 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 23 May 2018 11:50:22 +0200 Subject: [PATCH 105/333] Fixed setup.py --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 61037ff6c..55a3760d5 100644 --- a/setup.py +++ b/setup.py @@ -38,9 +38,9 @@ def find_files(directory, regex='.*', rel=''): 'python-rtmidi', 'JACK-Client', 'pyliblo', - 'flacon', + 'falcon', 'requests', - 'scandir;python_version<"3.5"', 'falcon' + 'scandir;python_version<"3.5"', ], packages=find_packages(), package_data={ From 5f8ecd3455d904c916b374b3ec4b559788a6b646 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 23 May 2018 12:42:48 +0200 Subject: [PATCH 106/333] Fixed setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 55a3760d5..f8ee11854 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ def find_files(directory, regex='.*', rel=''): ], packages=find_packages(), package_data={ - '': ['i18n/*.qm', '*.qss', '*.cfg'], + '': ['i18n/*.qm', '*.qss', '*.json'], 'lisp.ui.styles': lisp_icons, }, scripts=['linux-show-player'] From 90fb9c129c0fe141bc6feefb8a3959398eef3e14 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 23 May 2018 14:04:48 +0200 Subject: [PATCH 107/333] Fixed setup.py --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f8ee11854..85aa70e40 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ def find_files(directory, regex='.*', rel=''): # List the directories with icons to be installed -lisp_icons = find_files('lisp/ui/styles/icons', rel='lisp/ui/styles/') +lisp_icons = find_files('lisp/ui/icons', rel='lisp/ui/icons/') # Setup function setup( @@ -45,7 +45,7 @@ def find_files(directory, regex='.*', rel=''): packages=find_packages(), package_data={ '': ['i18n/*.qm', '*.qss', '*.json'], - 'lisp.ui.styles': lisp_icons, + 'lisp.ui.icons': lisp_icons, }, scripts=['linux-show-player'] ) From 2009bdf433d5695e7b09a75baa3c427c63053f6e Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 28 May 2018 15:09:41 +0200 Subject: [PATCH 108/333] Layout support properties, saved into the session. Add: New ProxyProperty, similar to builtin `property` Update: Now CueLayout extends HasProperties Update: CartLayout tabs can now be renamed Update: CartLayout saves layout-menu options into the session --- lisp/application.py | 1 + lisp/core/properties.py | 34 +++- lisp/core/session.py | 24 +-- lisp/layout/cue_layout.py | 10 +- lisp/plugins/action_cues/index_action_cue.py | 4 +- lisp/plugins/cart_layout/cue_widget.py | 2 +- lisp/plugins/cart_layout/default.json | 1 - lisp/plugins/cart_layout/layout.py | 127 ++++++++----- lisp/plugins/cart_layout/tab_widget.py | 16 ++ lisp/plugins/list_layout/control_buttons.py | 4 +- lisp/plugins/list_layout/info_panel.py | 34 ++-- lisp/plugins/list_layout/layout.py | 187 +++++++------------ lisp/plugins/list_layout/list_view.py | 5 +- lisp/plugins/list_layout/view.py | 72 +++++++ lisp/ui/themes/dark/theme.qss | 46 ++--- lisp/ui/widgets/qeditabletabbar.py | 79 ++++++++ plugins_utils.py | 2 +- 17 files changed, 400 insertions(+), 248 deletions(-) create mode 100644 lisp/plugins/list_layout/view.py create mode 100644 lisp/ui/widgets/qeditabletabbar.py diff --git a/lisp/application.py b/lisp/application.py index 31f14b3fb..2e6a1d636 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -162,6 +162,7 @@ def _save_to_file(self, session_file): """Save the current session into a file.""" self.session.session_file = session_file + # TODO: move cues into session ? # Add the cues session_dict = {'cues': []} diff --git a/lisp/core/properties.py b/lisp/core/properties.py index a6c169621..8b8a85b8b 100644 --- a/lisp/core/properties.py +++ b/lisp/core/properties.py @@ -50,7 +50,7 @@ def __init__(self, default=None, **meta): def __get__(self, instance, owner=None): if instance is None: return self - elif instance not in self._values: + if instance not in self._values: self._values[instance] = deepcopy(self.default) return self._values[instance] @@ -72,11 +72,39 @@ def __set__(self, instance, value): super().__set__(instance, value) +class ProxyProperty(Property): + """Property that use custom getter/setter, similar to builtin `property`""" + + def __init__(self, fget=None, fset=None, default=None, **meta): + super().__init__(default=default, **meta) + self._fget = fget + self._fset = fset + + def get(self, getter): + """Set the getter function (can be used as decorator).""" + self._fget = getter + return getter + + def set(self, setter): + """Set the setter function (can be used as decorator).""" + self._fset = setter + return setter + + def __get__(self, instance, owner=None): + if instance is None: + return self + return self._fget(instance) + + def __set__(self, instance, value): + if instance is not None: + self._fset(instance, value) + + class InstanceProperty: """Per-instance property, not a descriptor. - To be of any use an InstanceProperty should be used in combination - of an HasInstanceProperties object. + To be used an InstanceProperty should be used in combination of an + HasInstanceProperties object. """ __slots__ = ('value', 'default') diff --git a/lisp/core/session.py b/lisp/core/session.py index 1cb66a68f..41e02228f 100644 --- a/lisp/core/session.py +++ b/lisp/core/session.py @@ -26,6 +26,8 @@ class Session(HasInstanceProperties): layout_type = Property(default='') + layout = Property(default={}) + session_file = Property(default='') def __init__(self, layout): @@ -33,23 +35,9 @@ def __init__(self, layout): self.finalized = Signal() self.layout_type = typename(layout) + self.layout = layout - self.__layout = layout - self.__cue_model = layout.cue_model - - @property - def cue_model(self): - """ - :rtype: lisp.cues.cue_model.CueModel - """ - return self.__cue_model - - @property - def layout(self): - """ - :rtype: lisp.layout.cue_layout.CueLayout - """ - return self.__layout + self.cue_model = layout.cue_model def name(self): """Return the name of session, depending on the session-file.""" @@ -77,7 +65,7 @@ def rel_path(self, abs_path): return os.path.relpath(abs_path, start=self.path()) def finalize(self): - self.__layout.finalize() - self.__cue_model.reset() + self.layout.finalize() + self.cue_model.reset() self.finalized.emit() diff --git a/lisp/layout/cue_layout.py b/lisp/layout/cue_layout.py index f64dd21b6..7902f79dc 100644 --- a/lisp/layout/cue_layout.py +++ b/lisp/layout/cue_layout.py @@ -20,7 +20,7 @@ from abc import abstractmethod from lisp.core.actions_handler import MainActionsHandler -from lisp.core.qmeta import QABCMeta +from lisp.core.has_properties import HasProperties from lisp.core.signal import Signal from lisp.core.util import greatest_common_superclass from lisp.cues.cue import Cue, CueAction @@ -30,9 +30,7 @@ from lisp.ui.ui_utils import adjust_widget_position -# TODO: split "view" and "controller" -# TODO: make an HasProperties (will go into Session objects) -class CueLayout(metaclass=QABCMeta): +class CueLayout(HasProperties): # Layout name NAME = 'Base' # Layout short description @@ -42,15 +40,17 @@ class CueLayout(metaclass=QABCMeta): CuesMenu = CueContextMenu() - def __init__(self, application=None): + def __init__(self, application): """ :type application: lisp.application.Application """ + super().__init__() self.app = application self.cue_executed = Signal() # After a cue is executed self.all_executed = Signal() # After execute_all is called + # TODO: self.standby_changed = Signal() self.key_pressed = Signal() # After a key is pressed @property diff --git a/lisp/plugins/action_cues/index_action_cue.py b/lisp/plugins/action_cues/index_action_cue.py index 0267e88a1..bb74e169c 100644 --- a/lisp/plugins/action_cues/index_action_cue.py +++ b/lisp/plugins/action_cues/index_action_cue.py @@ -48,7 +48,7 @@ def __start__(self, fade=False): index = self.target_index try: - cue = Application().layout.model_adapter.item(index) + cue = Application().layout.cue_at(index) if cue is not self: cue.execute(CueAction(self.action)) except IndexError: @@ -195,7 +195,7 @@ def _current_target(self): index += self._cue_index try: - return Application().layout.model_adapter.item(index) + return Application().layout.cue_at(index) except IndexError: return None diff --git a/lisp/plugins/cart_layout/cue_widget.py b/lisp/plugins/cart_layout/cue_widget.py index e366530c1..4495dfb96 100644 --- a/lisp/plugins/cart_layout/cue_widget.py +++ b/lisp/plugins/cart_layout/cue_widget.py @@ -150,7 +150,7 @@ def mouseMoveEvent(self, event): def setCountdownMode(self, mode): self._countdownMode = mode - self._updateTime(self._cue.current_time()) + self._updateTime(self._cue.current_time(), True) def showAccurateTiming(self, enable): self._accurateTiming = enable diff --git a/lisp/plugins/cart_layout/default.json b/lisp/plugins/cart_layout/default.json index 55fd53805..a8a77eeba 100644 --- a/lisp/plugins/cart_layout/default.json +++ b/lisp/plugins/cart_layout/default.json @@ -1,7 +1,6 @@ { "_version_": "1", "_enabled_": true, - "autoAddPage": true, "countdownMode": true, "grid": { "columns": 7, diff --git a/lisp/plugins/cart_layout/layout.py b/lisp/plugins/cart_layout/layout.py index d0d09c23b..f8cf6db8d 100644 --- a/lisp/plugins/cart_layout/layout.py +++ b/lisp/plugins/cart_layout/layout.py @@ -21,6 +21,7 @@ from PyQt5.QtWidgets import QAction, QInputDialog, QMessageBox from lisp.core.configuration import DummyConfiguration +from lisp.core.properties import ProxyProperty from lisp.core.signal import Connection from lisp.cues.cue import Cue from lisp.cues.cue_factory import CueFactory @@ -35,7 +36,6 @@ from lisp.ui.ui_utils import translate -# TODO: custom tabs names class CartLayout(CueLayout): NAME = 'Cart Layout' DESCRIPTION = translate( @@ -52,21 +52,22 @@ class CartLayout(CueLayout): Config = DummyConfiguration() - def __init__(self, **kwargs): - super().__init__(**kwargs) + tabs = ProxyProperty() + seek_sliders_visible = ProxyProperty() + volume_control_visible = ProxyProperty() + dbmeters_visible = ProxyProperty() + accurate_time = ProxyProperty() + countdown_mode = ProxyProperty() + + def __init__(self, application): + super().__init__(application) self.__columns = CartLayout.Config['grid.columns'] self.__rows = CartLayout.Config['grid.rows'] - self._show_seek = CartLayout.Config['show.seekSliders'] - self._show_dbmeter = CartLayout.Config['show.dBMeters'] - self._show_volume = CartLayout.Config['show.volumeControls'] - self._accurate_timing = CartLayout.Config['show.accurateTime'] - self._countdown_mode = CartLayout.Config['countdownMode'] - self._auto_add_page = CartLayout.Config['autoAddPage'] - self._cart_model = CueCartModel( self.cue_model, self.__rows, self.__columns) + # TODO: move this logic in CartTabWidget ? self._cart_model.item_added.connect( self.__cue_added, Connection.QtQueued) self._cart_model.item_removed.connect( @@ -96,34 +97,38 @@ def __init__(self, **kwargs): layout_menu.addSeparator() - self.countdown_mode = QAction(parent=layout_menu) - self.countdown_mode.setCheckable(True) - self.countdown_mode.setChecked(self._countdown_mode) - self.countdown_mode.triggered.connect(self.set_countdown_mode) - layout_menu.addAction(self.countdown_mode) + self.countdown_mode_action = QAction(parent=layout_menu) + self.countdown_mode_action.setCheckable(True) + self.countdown_mode_action.setChecked( + CartLayout.Config['countdownMode']) + self.countdown_mode_action.triggered.connect(self._set_countdown_mode) + layout_menu.addAction(self.countdown_mode_action) self.show_seek_action = QAction(parent=layout_menu) self.show_seek_action.setCheckable(True) - self.show_seek_action.setChecked(self._show_seek) - self.show_seek_action.triggered.connect(self.set_seek_visible) + self.show_seek_action.setChecked( + CartLayout.Config['show.seekSliders']) + self.show_seek_action.triggered.connect(self._set_seek_bars_visible) layout_menu.addAction(self.show_seek_action) self.show_dbmeter_action = QAction(parent=layout_menu) self.show_dbmeter_action.setCheckable(True) - self.show_dbmeter_action.setChecked(self._show_dbmeter) - self.show_dbmeter_action.triggered.connect(self.set_dbmeter_visible) + self.show_dbmeter_action.setChecked(CartLayout.Config['show.dBMeters']) + self.show_dbmeter_action.triggered.connect(self._set_dbmeters_visible) layout_menu.addAction(self.show_dbmeter_action) self.show_volume_action = QAction(parent=layout_menu) self.show_volume_action.setCheckable(True) - self.show_volume_action.setChecked(self._show_volume) - self.show_volume_action.triggered.connect(self.set_volume_visible) + self.show_volume_action.setChecked( + CartLayout.Config['show.volumeControls']) + self.show_volume_action.triggered.connect(self._set_volume_controls_visible) layout_menu.addAction(self.show_volume_action) self.show_accurate_action = QAction(parent=layout_menu) self.show_accurate_action.setCheckable(True) - self.show_accurate_action.setChecked(self._accurate_timing) - self.show_accurate_action.triggered.connect(self.set_accurate) + self.show_accurate_action.setChecked( + CartLayout.Config['show.accurateTime']) + self.show_accurate_action.triggered.connect(self._set_accurate_time) layout_menu.addAction(self.show_accurate_action) # Context menu actions @@ -177,7 +182,7 @@ def retranslate(self): self.new_pages_action.setText(translate('CartLayout', 'Add pages')) self.rm_current_page_action.setText( translate('CartLayout', 'Remove current page')) - self.countdown_mode.setText(translate('CartLayout', 'Countdown mode')) + self.countdown_mode_action.setText(translate('CartLayout', 'Countdown mode')) self.show_seek_action.setText(translate('CartLayout', 'Show seek-bars')) self.show_dbmeter_action.setText( translate('CartLayout', 'Show dB-meters')) @@ -246,8 +251,8 @@ def remove_current_page(self): def remove_page(self, index): if self._cart_view.count() > index >= 0: page = self._page(index) - page.moveDropEvent.disconnect() - page.copyDropEvent.disconnect() + page.moveWidgetRequested.disconnect() + page.copyWidgetRequested.disconnect() self._cart_model.remove_page(index) self._cart_view.removeTab(index) @@ -259,31 +264,69 @@ def remove_page(self, index): for n in range(index, self._cart_view.count()): self._cart_view.setTabText(n, text.format(number=n + 1)) - def set_countdown_mode(self, mode): - self._countdown_mode = mode + @tabs.get + def _get_tabs(self): + return self._cart_view.tabTexts() + + @tabs.set + def _set_tabs(self, texts): + missing = len(texts) - self._cart_view.count() + if missing > 0: + for _ in range(missing): + self.add_page() + + self._cart_view.setTabTexts(texts) + + @countdown_mode.set + def _set_countdown_mode(self, enable): + self.countdown_mode_action.setChecked(enable) for widget in self._widgets(): - widget.setCountdownMode(mode) + widget.setCountdownMode(enable) - def set_accurate(self, enable): - self._accurate_timing = enable + @countdown_mode.get + def _get_countdown_mode(self): + return self.countdown_mode_action.isChecked() + + @accurate_time.set + def _set_accurate_time(self, enable): + self.show_accurate_action.setChecked(enable) for widget in self._widgets(): widget.showAccurateTiming(enable) - def set_seek_visible(self, visible): - self._show_seek = visible + @accurate_time.get + def _get_accurate_time(self): + return self.show_accurate_action.isChecked() + + @seek_sliders_visible.set + def _set_seek_bars_visible(self, visible): + self.show_seek_action.setChecked(visible) for widget in self._widgets(): widget.showSeekSlider(visible) - def set_dbmeter_visible(self, visible): - self._show_dbmeter = visible + @seek_sliders_visible.get + def _get_seek_bars_visible(self): + return self.show_seek_action.isChecked() + + @dbmeters_visible.set + def _set_dbmeters_visible(self, visible): + self.show_dbmeter_action.setChecked(visible) for widget in self._widgets(): widget.showDBMeters(visible) - def set_volume_visible(self, visible): - self._show_volume = visible + @dbmeters_visible.get + def _get_dbmeters_visible(self): + return self.show_dbmeter_action.isChecked() + + @volume_control_visible.set + def _set_volume_controls_visible(self, visible): + self.show_volume_action.setChecked(visible) for widget in self._widgets(): widget.showVolumeSlider(visible) + @volume_control_visible.get + def _get_volume_controls_visible(self): + return self.show_volume_action.isChecked() + def to_3d_index(self, index): page_size = self.__rows * self.__columns @@ -363,11 +406,11 @@ def __cue_added(self, cue): widget.cueExecuted.connect(self.cue_executed.emit) widget.editRequested.connect(self.edit_cue) - widget.showAccurateTiming(self._accurate_timing) - widget.setCountdownMode(self._countdown_mode) - widget.showVolumeSlider(self._show_volume) - widget.showDBMeters(self._show_dbmeter) - widget.showSeekSlider(self._show_seek) + widget.showAccurateTiming(self.accurate_time) + widget.setCountdownMode(self.countdown_mode) + widget.showVolumeSlider(self.volume_control_visible) + widget.showDBMeters(self.dbmeters_visible) + widget.showSeekSlider(self.seek_sliders_visible) page, row, column = self.to_3d_index(cue.index) if page >= self._cart_view.count(): diff --git a/lisp/plugins/cart_layout/tab_widget.py b/lisp/plugins/cart_layout/tab_widget.py index 521c318ba..bf6a61143 100644 --- a/lisp/plugins/cart_layout/tab_widget.py +++ b/lisp/plugins/cart_layout/tab_widget.py @@ -20,13 +20,18 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QTabWidget +from lisp.ui.widgets.qeditabletabbar import QEditableTabBar + class CartTabWidget(QTabWidget): DRAG_MAGIC = 'LiSP_Drag&Drop' def __init__(self, **kwargs): super().__init__(**kwargs) + self.setTabBar(QEditableTabBar(self)) + self.tabBar().setDrawBase(False) self.tabBar().setObjectName('CartTabBar') + self.setFocusPolicy(Qt.StrongFocus) self.setAcceptDrops(True) @@ -42,6 +47,17 @@ def dragMoveEvent(self, event): def dropEvent(self, event): event.ignore() + def tabTexts(self): + texts = [] + for i in range(self.tabBar().count()): + texts.append(self.tabBar().tabText(i)) + + return texts + + def setTabTexts(self, texts): + for i, text in enumerate(texts): + self.tabBar().setTabText(i, text) + def pages(self): for index in range(self.count()): yield self.widget(index) diff --git a/lisp/plugins/list_layout/control_buttons.py b/lisp/plugins/list_layout/control_buttons.py index 84ad2190b..374bcd34f 100644 --- a/lisp/plugins/list_layout/control_buttons.py +++ b/lisp/plugins/list_layout/control_buttons.py @@ -26,8 +26,8 @@ class ShowControlButtons(QWidget): - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args): + super().__init__(*args) self.setLayout(QGridLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(5) diff --git a/lisp/plugins/list_layout/info_panel.py b/lisp/plugins/list_layout/info_panel.py index 87ab15035..9ba78e1c8 100644 --- a/lisp/plugins/list_layout/info_panel.py +++ b/lisp/plugins/list_layout/info_panel.py @@ -24,12 +24,12 @@ class InfoPanel(QWidget): - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args): + super().__init__(*args) self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) - self._item = None + self._cue = None # cue name self.cueName = QLineEdit(self) @@ -53,29 +53,29 @@ def retranslateUi(self): translate('ListLayoutInfoPanel', 'Cue description')) @property - def item(self): - return self._item + def cue(self): + return self._cue - @item.setter - def item(self, item): - if self._item is not None: - self._item.cue.changed('name').disconnect(self._name_changed) - self._item.cue.changed('description').disconnect(self._desc_changed) + @cue.setter + def cue(self, item): + if self._cue is not None: + self._cue.changed('name').disconnect(self._name_changed) + self._cue.changed('description').disconnect(self._desc_changed) - self._item = item + self._cue = item - if self._item is not None: - self._item.cue.changed('name').connect(self._name_changed) - self._item.cue.changed('description').connect(self._desc_changed) + if self._cue is not None: + self._cue.changed('name').connect(self._name_changed) + self._cue.changed('description').connect(self._desc_changed) - self._name_changed(self._item.cue.name) - self._desc_changed(self._item.cue.description) + self._name_changed(self._cue.name) + self._desc_changed(self._cue.description) else: self.cueName.clear() self.cueDescription.clear() def _name_changed(self, name): - self.cueName.setText(str(self.item.index + 1) + ' → ' + name) + self.cueName.setText(str(self.cue.index + 1) + ' → ' + name) def _desc_changed(self, description): self.cueDescription.setText(description) diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index cecd70312..02ce4eee3 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -17,12 +17,9 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from enum import Enum - from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtGui import QKeySequence -from PyQt5.QtWidgets import QWidget, QAction, qApp, QGridLayout, \ - QPushButton, QSizePolicy +from PyQt5.QtWidgets import QAction from lisp.core.configuration import DummyConfiguration from lisp.core.signal import Connection @@ -30,28 +27,19 @@ from lisp.cues.cue_memento_model import CueMementoAdapter from lisp.layout.cue_layout import CueLayout from lisp.layout.cue_menu import SimpleMenuAction, MENU_PRIORITY_CUE, MenuActionsGroup -from lisp.plugins.list_layout.control_buttons import ShowControlButtons -from lisp.plugins.list_layout.info_panel import InfoPanel from lisp.plugins.list_layout.list_view import CueListView from lisp.plugins.list_layout.models import CueListModel, RunningCueModel -from lisp.plugins.list_layout.playing_view import RunningCuesListWidget +from lisp.plugins.list_layout.view import ListLayoutView from lisp.ui.ui_utils import translate -class EndListBehavior(Enum): - Stop = 'Stop' - Restart = 'Restart' - - -class ListLayout(QWidget, CueLayout): +class ListLayout(CueLayout): NAME = 'List Layout' DESCRIPTION = QT_TRANSLATE_NOOP( 'LayoutDescription', 'Organize the cues in a list') DETAILS = [ QT_TRANSLATE_NOOP( 'LayoutDetails', 'SHIFT + Space or Double-Click to edit a cue'), - QT_TRANSLATE_NOOP( - 'LayoutDetails', 'CTRL + Left Click to select cues'), QT_TRANSLATE_NOOP( 'LayoutDetails', 'To copy cues drag them while pressing CTRL'), QT_TRANSLATE_NOOP( @@ -59,45 +47,57 @@ class ListLayout(QWidget, CueLayout): ] Config = DummyConfiguration() - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.setLayout(QGridLayout()) - self.layout().setContentsMargins(0, 0, 0, 0) - + def __init__(self, application): + super().__init__(application) self._list_model = CueListModel(self.cue_model) self._list_model.item_added.connect(self.__cue_added) self._memento_model = CueMementoAdapter(self._list_model) + self._running_model = RunningCueModel(self.cue_model) - self._playing_model = RunningCueModel(self.cue_model) - self._current_index = -1 - self._context_item = None - - self._show_dbmeter = ListLayout.Config['show.dBMeters'] - self._show_playing = ListLayout.Config['show.playingCues'] - self._show_seekslider = ListLayout.Config['show.seekSliders'] - self._accurate_time = ListLayout.Config['show.accurateTime'] self._auto_continue = ListLayout.Config['autoContinue'] self._go_key_sequence = QKeySequence( ListLayout.Config['goKey'], QKeySequence.NativeText) + self._view = ListLayoutView(self._list_model, self._running_model) + # GO button + self._view.goButton.clicked.connect(self.__go_slot) + # Global actions + self._view.controlButtons.stopButton.clicked.connect(self.stop_all) + self._view.controlButtons.pauseButton.clicked.connect(self.pause_all) + self._view.controlButtons.fadeInButton.clicked.connect(self.fadein_all) + self._view.controlButtons.fadeOutButton.clicked.connect(self.fadeout_all) + self._view.controlButtons.resumeButton.clicked.connect(self.resume_all) + self._view.controlButtons.interruptButton.clicked.connect(self.interrupt_all) + # Cue list + self._view.listView.itemDoubleClicked.connect(self._double_clicked) + self._view.listView.contextMenuInvoked.connect(self._context_invoked) + self._view.listView.keyPressed.connect(self._key_pressed) + + # TODO: session values + self._set_running_visible(ListLayout.Config['show.playingCues']) + self._set_dbmeters_visible(ListLayout.Config['show.dBMeters']) + self._set_seeksliders_visible(ListLayout.Config['show.seekSliders']) + self._set_accurate_time(ListLayout.Config['show.accurateTime']) + self._set_selection_mode(False) + # Layout menu menuLayout = self.app.window.menuLayout self.showPlayingAction = QAction(parent=menuLayout) self.showPlayingAction.setCheckable(True) self.showPlayingAction.setChecked(self._show_playing) - self.showPlayingAction.triggered.connect(self._set_playing_visible) + self.showPlayingAction.triggered.connect(self._set_running_visible) menuLayout.addAction(self.showPlayingAction) self.showDbMeterAction = QAction(parent=menuLayout) self.showDbMeterAction.setCheckable(True) self.showDbMeterAction.setChecked(self._show_dbmeter) - self.showDbMeterAction.triggered.connect(self._set_dbmeter_visible) + self.showDbMeterAction.triggered.connect(self._set_dbmeters_visible) menuLayout.addAction(self.showDbMeterAction) self.showSeekAction = QAction(parent=menuLayout) self.showSeekAction.setCheckable(True) - self.showSeekAction.setChecked(self._show_seekslider) + self.showSeekAction.setChecked(self._seeksliders_visible) self.showSeekAction.triggered.connect(self._set_seeksliders_visible) menuLayout.addAction(self.showSeekAction) @@ -119,52 +119,6 @@ def __init__(self, **kwargs): self.selectionModeAction.triggered.connect(self._set_selection_mode) menuLayout.addAction(self.selectionModeAction) - # GO-BUTTON (top-left) - self.goButton = QPushButton('GO', self) - self.goButton.setFocusPolicy(Qt.NoFocus) - self.goButton.setFixedWidth(120) - self.goButton.setFixedHeight(100) - self.goButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) - self.goButton.setStyleSheet('font-size: 48pt;') - self.goButton.clicked.connect(self.__go_slot) - self.layout().addWidget(self.goButton, 0, 0) - - # INFO PANEL (top center) - self.infoPanel = InfoPanel() - self.infoPanel.setFixedHeight(100) - self.layout().addWidget(self.infoPanel, 0, 1) - - # CONTROL-BUTTONS (top-right) - self.controlButtons = ShowControlButtons(parent=self) - self.controlButtons.setFixedHeight(100) - - self.controlButtons.stopButton.clicked.connect(self.stop_all) - self.controlButtons.pauseButton.clicked.connect(self.pause_all) - self.controlButtons.fadeInButton.clicked.connect(self.fadein_all) - self.controlButtons.fadeOutButton.clicked.connect(self.fadeout_all) - self.controlButtons.resumeButton.clicked.connect(self.resume_all) - self.controlButtons.interruptButton.clicked.connect(self.interrupt_all) - self.layout().addWidget(self.controlButtons, 0, 2) - - # CUE VIEW (center left) - self.listView = CueListView(self._list_model, self) - self.listView.setSelectionMode(CueListView.NoSelection) - self.listView.itemDoubleClicked.connect(self._double_clicked) - self.listView.contextMenuInvoked.connect(self._context_invoked) - self.listView.keyPressed.connect(self._key_press_event) - self.layout().addWidget(self.listView, 1, 0, 1, 2) - - # PLAYING VIEW (center right) - self.playView = RunningCuesListWidget(self._playing_model, parent=self) - self.playView.dbmeter_visible = self._show_dbmeter - self.playView.accurate_time = self._accurate_time - self.playView.seek_visible = self._show_seekslider - self.playView.setMinimumWidth(300) - self.playView.setMaximumWidth(300) - self.layout().addWidget(self.playView, 1, 2) - - self._set_playing_visible(self._show_playing) - # Context menu actions self._edit_actions_group = MenuActionsGroup(priority=MENU_PRIORITY_CUE) self._edit_actions_group.add( @@ -184,9 +138,9 @@ def __init__(self, **kwargs): self.CuesMenu.add(self._edit_actions_group) - self.retranslateUi() + self.retranslate() - def retranslateUi(self): + def retranslate(self): self.showPlayingAction.setText( translate('ListLayout', 'Show playing cues')) self.showDbMeterAction.setText( @@ -203,13 +157,13 @@ def cues(self, cue_type=Cue): yield from self._list_model def view(self): - return self + return self._view def standby_index(self): - return self.listView.standbyIndex() + return self._view.listView.standbyIndex() def set_standby_index(self, index): - self.listView.setStandbyIndex(index) + self._view.listView.setStandbyIndex(index) def go(self, action=CueAction.Default, advance=1): standby_cue = self.standby_cue() @@ -223,40 +177,35 @@ def go(self, action=CueAction.Default, advance=1): def cue_at(self, index): return self._list_model.item(index) - def contextMenuEvent(self, event): - if self.listView.geometry().contains(event.pos()): - self.show_context_menu(event.globalPos()) - def selected_cues(self, cue_type=Cue): - for item in self.listView.selectedItems(): - yield self._list_model.item(self.listView.indexOfTopLevelItem(item)) + for item in self._view.listView.selectedItems(): + yield self._list_model.item( + self._view.listView.indexOfTopLevelItem(item)) def finalize(self): # Clean layout menu self.app.window.menuLayout.clear() - # Clean context-menu self.CuesMenu.remove(self._edit_actions_group) - # Remove reference cycle del self._edit_actions_group def select_all(self, cue_type=Cue): - for index in range(self.listView.topLevelItemCount()): + for index in range(self._view.listView.topLevelItemCount()): if isinstance(self._list_model.item(index), cue_type): - self.listView.topLevelItem(index).setSelected(True) + self._view.listView.topLevelItem(index).setSelected(True) def deselect_all(self, cue_type=Cue): - for index in range(self.listView.topLevelItemCount()): + for index in range(self._view.listView.topLevelItemCount()): if isinstance(self._list_model.item(index), cue_type): - self.listView.topLevelItem(index).setSelected(False) + self._view.listView.topLevelItem(index).setSelected(False) def invert_selection(self): - for index in range(self.listView.topLevelItemCount()): - item = self.listView.topLevelItem(index) + for index in range(self._view.listView.topLevelItemCount()): + item = self._view.listView.topLevelItem(index) item.setSelected(not item.isSelected()) - def _key_press_event(self, event): + def _key_pressed(self, event): event.ignore() if not event.isAutoRepeat(): @@ -276,7 +225,7 @@ def _key_press_event(self, event): event.accept() self.go() elif event.key() == Qt.Key_Space: - if qApp.keyboardModifiers() == Qt.ShiftModifier: + if event.modifiers() == Qt.ShiftModifier: event.accept() cue = self.standby_cue() @@ -285,30 +234,30 @@ def _key_press_event(self, event): def _set_accurate_time(self, accurate): self._accurate_time = accurate - self.playView.accurate_time = accurate + self._view.runView.accurate_time = accurate def _set_auto_next(self, enable): self._auto_continue = enable def _set_seeksliders_visible(self, visible): - self._show_seekslider = visible - self.playView.seek_visible = visible + self._seeksliders_visible = visible + self._view.runView.seek_visible = visible - def _set_dbmeter_visible(self, visible): + def _set_dbmeters_visible(self, visible): self._show_dbmeter = visible - self.playView.dbmeter_visible = visible + self._view.runView.dbmeter_visible = visible - def _set_playing_visible(self, visible): + def _set_running_visible(self, visible): self._show_playing = visible - self.playView.setVisible(visible) - self.controlButtons.setVisible(visible) + self._view.runView.setVisible(visible) + self._view.controlButtons.setVisible(visible) def _set_selection_mode(self, enable): if enable: - self.listView.setSelectionMode(self.listView.ExtendedSelection) + self._view.listView.setSelectionMode(CueListView.ExtendedSelection) else: self.deselect_all() - self.listView.setSelectionMode(self.listView.NoSelection) + self._view.listView.setSelectionMode(CueListView.NoSelection) def _double_clicked(self): cue = self.standby_cue() @@ -316,15 +265,19 @@ def _double_clicked(self): self.edit_cue(cue) def _context_invoked(self, event): - cues = list(self.selected_cues()) - if not cues: - context_index = self.listView.indexAt(event.pos()) - if context_index.isValid(): - cues.append(self._list_model.item(context_index.row())) - else: - return - - self.show_cue_context_menu(cues, event.globalPos()) + # This is called in response to CueListView context-events + if self._view.listView.itemAt(event.pos()) is not None: + cues = list(self.selected_cues()) + if not cues: + context_index = self._view.listView.indexAt(event.pos()) + if context_index.isValid(): + cues.append(self._list_model.item(context_index.row())) + else: + return + + self.show_cue_context_menu(cues, event.globalPos()) + else: + self.show_context_menu(event.globalPos()) def __go_slot(self): self.go() diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py index ed34c0191..924bef6de 100644 --- a/lisp/plugins/list_layout/list_view.py +++ b/lisp/plugins/list_layout/list_view.py @@ -169,10 +169,7 @@ def dropEvent(self, event): to_index += 1 def contextMenuEvent(self, event): - if self.itemAt(event.pos()) is not None: - self.contextMenuInvoked.emit(event) - else: - super().contextMenuEvent(event) + self.contextMenuInvoked.emit(event) def keyPressEvent(self, event): event.ignore() diff --git a/lisp/plugins/list_layout/view.py b/lisp/plugins/list_layout/view.py new file mode 100644 index 000000000..21b521707 --- /dev/null +++ b/lisp/plugins/list_layout/view.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QWidget, QGridLayout, QPushButton, QSizePolicy + +from lisp.plugins.list_layout.list_view import CueListView +from lisp.plugins.list_layout.playing_view import RunningCuesListWidget +from .control_buttons import ShowControlButtons +from .info_panel import InfoPanel + + +class ListLayoutView(QWidget): + def __init__(self, listModel, runModel, *args): + super().__init__(*args) + self.setLayout(QGridLayout()) + self.layout().setContentsMargins(0, 0, 0, 0) + + self.listModel = listModel + + # GO-BUTTON (top-left) + self.goButton = QPushButton('GO', self) + self.goButton.setFocusPolicy(Qt.NoFocus) + self.goButton.setFixedWidth(120) + self.goButton.setFixedHeight(100) + self.goButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) + self.goButton.setStyleSheet('font-size: 48pt;') + self.layout().addWidget(self.goButton, 0, 0) + + # INFO PANEL (top center) + self.infoPanel = InfoPanel(self) + self.infoPanel.setFixedHeight(120) + self.layout().addWidget(self.infoPanel, 0, 1) + + # CONTROL-BUTTONS (top-right) + self.controlButtons = ShowControlButtons(self) + self.controlButtons.setFixedHeight(120) + self.layout().addWidget(self.controlButtons, 0, 2) + + # CUE VIEW (center left) + self.listView = CueListView(listModel, self) + self.listView.currentItemChanged.connect(self.__listViewCurrentChanged) + self.layout().addWidget(self.listView, 1, 0, 1, 2) + + # PLAYING VIEW (center right) + self.runView = RunningCuesListWidget(runModel, parent=self) + self.runView.setMinimumWidth(300) + self.runView.setMaximumWidth(300) + self.layout().addWidget(self.runView, 1, 2) + + def __listViewCurrentChanged(self, current, previous): + if current is not None: + index = self.listView.indexOfTopLevelItem(current) + self.infoPanel.cue = self.listModel.item(index) + else: + self.infoPanel.cue = None diff --git a/lisp/ui/themes/dark/theme.qss b/lisp/ui/themes/dark/theme.qss index aeea611c2..cb37e1acb 100644 --- a/lisp/ui/themes/dark/theme.qss +++ b/lisp/ui/themes/dark/theme.qss @@ -390,7 +390,6 @@ QLabel { QTabBar::tab { color: white; border: 1px solid #444; - border-bottom-style: none; background-color: #333; padding-left: 10px; padding-right: 10px; @@ -410,20 +409,28 @@ QTabBar::tab:last { border-top-right-radius: 3px; } +QTabBar::tab:only-one, +QTabBar::tab:first { + border-top-left-radius: 3px; +} + QTabBar::tab:first:!selected { margin-left: 0px; +} + +QTabBar::tab:selected { + border-bottom-style: none; border-top-left-radius: 3px; + border-top-right-radius: 3px; } QTabBar::tab:!selected { - border-bottom-style: solid; margin-top: 3px; background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:1 #212121, stop:.4 #343434); } QTabBar::tab:selected { - border-top-left-radius: 3px; - border-top-right-radius: 3px; + border-top-color: #CCC; } QTabBar::tab:selected:focus { @@ -630,46 +637,15 @@ QRadioButton:indicator:checked:disabled { #CartTabBar::tab { height: 35px; width: 100px; - border: 1px solid #444; - border-bottom-style: none; - background-color: #333; - padding-left: 10px; - padding-right: 10px; - padding-top: 3px; - padding-bottom: 2px; - margin-right: -1px; font-size: 13pt; font-weight: bold; color: white; } -#CartTabBar::tab:only-one, -#CartTabBar::tab:last { - margin-right: 0px; - border-top-right-radius: 3px; -} - -#CartTabBar::tab:first:!selected { - border-top-left-radius: 3px; -} - -#CartTabBar::tab:!selected { - margin-top: 3px; - border-bottom-style: solid; - background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:1 #212121, stop:.4 #343434); -} - #CartTabBar::tab:selected { height: 40px; margin-bottom: -5px; border-top-width: 2px; - border-top-color: #CCC; - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} - -#CartTabBar::tab:selected:focus { - border-top-color: #419BE6; } #ButtonCueWidget { diff --git a/lisp/ui/widgets/qeditabletabbar.py b/lisp/ui/widgets/qeditabletabbar.py new file mode 100644 index 000000000..9bd12eee2 --- /dev/null +++ b/lisp/ui/widgets/qeditabletabbar.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import Qt, QEvent, pyqtSignal +from PyQt5.QtWidgets import QTabBar, QLineEdit + + +class QEditableTabBar(QTabBar): + textChanged = pyqtSignal(int, str) + + def __init__(self, *args): + super().__init__(*args) + + self._editor = QLineEdit(self) + self._editor.setWindowFlags(Qt.Popup) + self._editor.setFocusProxy(self) + + self._editor.editingFinished.connect(self.handleEditingFinished) + self._editor.installEventFilter(self) + + def eventFilter(self, widget, event): + clickOutside = ( + event.type() == QEvent.MouseButtonPress and + not self._editor.geometry().contains(event.globalPos()) + ) + escKey = ( + event.type() == QEvent.KeyPress and + event.key() == Qt.Key_Escape + ) + + if clickOutside or escKey: + self._editor.hide() + return True + + return super().eventFilter(widget, event) + + def keyPressEvent(self, event): + if event.key() == Qt.Key_F2: + self.editTab(self.currentIndex()) + else: + super().keyPressEvent(event) + + def mouseDoubleClickEvent(self, event): + index = self.tabAt(event.pos()) + self.editTab(index) + + def editTab(self, index): + if 0 <= index < self.count(): + rect = self.tabRect(index) + + self._editor.setFixedSize(rect.size()) + self._editor.move(self.parent().mapToGlobal(rect.topLeft())) + self._editor.setText(self.tabText(index)) + self._editor.show() + + def handleEditingFinished(self): + self._editor.hide() + + index = self.currentIndex() + text = self._editor.text() + if index >= 0: + self.setTabText(index, text) + self.textChanged.emit(index, text) diff --git a/plugins_utils.py b/plugins_utils.py index 12a82c51b..6374e0595 100755 --- a/plugins_utils.py +++ b/plugins_utils.py @@ -48,7 +48,7 @@ def create_plugin(name: str): print('>>> CREATE DEFAULT SETTINGS FILE') copyfile(os.path.join(PLUGINS_DIRECTORY, 'default.json'), - os.path.join(plugin_path, name + '.json')) + os.path.join(plugin_path, 'default.json')) print('>>> CREATE DEFAULT INIT FILE') with open(os.path.join(plugin_path, '__init__.py'), 'w') as init_file: From c6a00293e9d8babd4dcbda27239e5403472e4d15 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 8 Jun 2018 12:42:59 +0200 Subject: [PATCH 109/333] Add flatpak support and travis-ci integration travis-ci is currently used only to build the flatpak, the result is uploaded to bintray as `.flatpak` bundle. --- .travis.yml | 40 ++ dist/Flatpak/TODO | 7 + dist/Flatpak/build.sh | 24 ++ ...ithub.FrancescoCeruti.LinuxShowPlayer.json | 352 ++++++++++++++++++ dist/Flatpak/travis_flatpak.py | 52 +++ dist/linuxshowplayer.appdata.xml | 36 ++ dist/travis_bintray.py | 87 +++++ 7 files changed, 598 insertions(+) create mode 100644 .travis.yml create mode 100644 dist/Flatpak/TODO create mode 100755 dist/Flatpak/build.sh create mode 100644 dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json create mode 100644 dist/Flatpak/travis_flatpak.py create mode 100644 dist/linuxshowplayer.appdata.xml create mode 100644 dist/travis_bintray.py diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..c8d1e34af --- /dev/null +++ b/.travis.yml @@ -0,0 +1,40 @@ +language: generic +sudo: required + +services: + - docker + +branches: + only: + - develop + - master + +before_install: + - docker pull francescoceruti/flatpack + +script: + - cd dist/Flatpak + # Patch the flatpak manifest to work with travis + - python3 travis_flatpak.py + # Run the build in a container, mounting the current directory + # FIXME: if this fails at any stage the cache is not committed by travis + - docker run --privileged -v ${PWD}:/src -i francescoceruti/flatpack src/build.sh $TRAVIS_BRANCH + # Return into the build-dir + - cd $TRAVIS_BUILD_DIR + +cache: + directories: + # Flatpak build cache, avoid rebuilding unchanged modules + - $TRAVIS_BUILD_DIR/dist/Flatpak/.flatpak-builder + +before_deploy: + - python3 dist/travis_bintray.py + +deploy: + provider: bintray + file: dist/bintray.json + user: "francescoceruti" + key: $BINTRAY_API_KEY + skip_cleanup: true # since we are building inside the working-directory + on: + all_branches: true diff --git a/dist/Flatpak/TODO b/dist/Flatpak/TODO new file mode 100644 index 000000000..3ec035eba --- /dev/null +++ b/dist/Flatpak/TODO @@ -0,0 +1,7 @@ +- If we need custom build for "pyqt5" and "sip" the best approach is probably to + create an ad-hoc repository to produce a wheel file like the ones distributed from pypi +- We should avoid using pip, see the "upgrade-pip" module + +- Avoid crashes because of "missing" jackd binaries + +(╯°□°)╯︵ ┻━┻ \ No newline at end of file diff --git a/dist/Flatpak/build.sh b/dist/Flatpak/build.sh new file mode 100755 index 000000000..ac1277625 --- /dev/null +++ b/dist/Flatpak/build.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +# Make sure we are in the same directory of this file +cd "${0%/*}" +BRANCH="$1" + +set -xe + +# Create and add a local repository +ostree init --mode=archive-z2 --repo=repository +flatpak remote-add --user --no-gpg-verify --if-not-exists repo repository + +# Install runtime and sdk +flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo +flatpak install --user flathub org.gnome.Platform//3.28 org.gnome.Sdk//3.28 + +# Run the build +flatpak-builder --verbose --force-clean --ccache --repo=repo build com.github.FrancescoCeruti.LinuxShowPlayer.json + +# Bundle +mkdir -p out +flatpak build-bundle repo out/LinuxShowPlayer.flatpak com.github.FrancescoCeruti.LinuxShowPlayer $BRANCH + +set +xe \ No newline at end of file diff --git a/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json b/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json new file mode 100644 index 000000000..330b78541 --- /dev/null +++ b/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json @@ -0,0 +1,352 @@ +{ + "app-id": "com.github.FrancescoCeruti.LinuxShowPlayer", + "runtime": "org.gnome.Platform", + "runtime-version": "3.28", + "sdk": "org.gnome.Sdk", + "command": "linux-show-player", + "build-options": { + "no-debuginfo": true + }, + "finish-args": [ + "--share=network", + "--socket=x11", + "--socket=wayland", + "--socket=pulseaudio", + "--filesystem=home", + "--device=all" + ], + "cleanup-commands": [ + "pip3 uninstall --yes cython || true" + ], + "rename-appdata-file": "linuxshowplayer.appdata.xml", + "rename-desktop-file": "linuxshowplayer.desktop", + "rename-icon": "linuxshowplayer", + "modules": [ + { + "name": "cpython", + "build-options": { + "strip": false + }, + "config-opts": [ + "--enable-shared", + "--enable-optimizations" + ], + "sources": [ + { + "type": "archive", + "url": "https://www.python.org/ftp/python/3.6.5/Python-3.6.5.tar.xz", + "sha256": "f434053ba1b5c8a5cc597e966ead3c5143012af827fd3f0697d21450bb8d87a6" + } + ] + }, + { + "name": "upgrade-pip", + "buildsystem": "simple", + "build-commands": [ + "python3 setup.py install --prefix=/app" + ], + "sources": [ + { + "type": "archive", + "url": "https://files.pythonhosted.org/packages/ae/e8/2340d46ecadb1692a1e455f13f75e596d4eab3d11a57446f08259dee8f02/pip-10.0.1.tar.gz", + "sha256": "f2bd08e0cd1b06e10218feaf6fef299f473ba706582eb3bd9d52203fdbd7ee68" + } + ] + }, + { + "name": "Jack", + "buildsystem": "simple", + "build-commands": [ + "./waf configure --prefix='/app/'", + "./waf", + "./waf install" + ], + "sources": [ + { + "type": "archive", + "url": "https://github.com/jackaudio/jack2/releases/download/v1.9.12/jack2-1.9.12.tar.gz", + "sha1": "8ab6329c6a107cdf370c40afac154370b406437d" + } + ], + "cleanup": [ + "/bin" + ] + }, + { + "name": "gstreamer-plugins-good", + "config-opts": [ + "--enable-silent-rules", + "--enable-experimental", + "--disable-monoscope", + "--disable-aalib", + "--enable-cairo", + "--disable-libcaca", + "--disable-gtk-doc-html", + "--with-default-visualizer=autoaudiosink" + ], + "sources": [ + { + "type": "archive", + "url": "http://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-1.10.5.tar.xz", + "sha256": "be053f6ed716eeb517cec148cec637cdce571c6e04d5c21409e2876fb76c7639" + } + ] + }, + { + "name": "RtMidi", + "config-opts": [ + "--with-jack", + "--with-alsa" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/thestk/rtmidi.git", + "tag": "v3.0.0", + "commit": "88e53b9cfe60719c9ade800795313f3c6026c48c" + } + ] + }, + { + "name": "liblo", + "config-opts": [ + "--disable-examples", + "--disable-tools" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/radarsat1/liblo.git", + "tag": "0.29", + "commit": "901c6ff1ab269d964e256936a609779f86047ebd" + } + ] + }, + { + "name": "cpp-google-protobuf", + "config-opts": [ + "--disable-static" + ], + "sources": [ + { + "type": "archive", + "url": "https://github.com/google/protobuf/releases/download/v3.1.0/protobuf-cpp-3.1.0.tar.gz", + "sha1": "b7b7275405ac18784965b02bea7d62f836873564" + } + ] + }, + { + "name": "sip", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --prefix=${FLATPAK_DEST} sip-4.19.8-cp36-cp36m-manylinux1_x86_64.whl" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/8a/ea/d317ce5696dda4df7c156cd60447cda22833b38106c98250eae1451f03ec/sip-4.19.8-cp36-cp36m-manylinux1_x86_64.whl", + "sha256": "cf98150a99e43fda7ae22abe655b6f202e491d6291486548daa56cb15a2fcf85" + } + ], + "cleanup": [ + "/bin", + "/include" + ] + }, + { + "name": "PyQt5", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --prefix=${FLATPAK_DEST} PyQt5-5.10.1-5.10.1-cp35.cp36.cp37.cp38-abi3-manylinux1_x86_64.whl" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/e4/15/4e2e49f64884edbab6f833c6fd3add24d7938f2429aec1f2883e645d4d8f/PyQt5-5.10.1-5.10.1-cp35.cp36.cp37.cp38-abi3-manylinux1_x86_64.whl", + "sha256": "1e652910bd1ffd23a3a48c510ecad23a57a853ed26b782cd54b16658e6f271ac" + } + ] + }, + { + "name": "python-google-protobuf", + "buildsystem": "simple", + "build-options": { + "build-args": [ + "--share=network" + ] + }, + "build-commands": [ + "pip3 install --prefix=${FLATPAK_DEST} protobuf==3.1.0.post1" + ] + }, + { + "name": "ola", + "build-options": { + "env": { + "PYTHON": "python3" + } + }, + "config-opts": [ + "--disable-unittests", + "--disable-examples", + "--disable-osc", + "--disable-http", + "--enable-python-libs" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/OpenLightingProject/ola.git", + "tag": "0.10.6", + "commit": "6e57342c414a72cdd721e8df5bc7967e17459647" + }, + { + "type": "script", + "dest-filename": "autogen.sh", + "commands": [ + "autoreconf -fi" + ] + } + ] + }, + { + "name": "cython", + "buildsystem": "simple", + "build-options": { + "build-args": [ + "--share=network" + ] + }, + "build-commands": [ + "pip3 install --prefix=${FLATPAK_DEST} cython" + ] + }, + { + "name": "PyGobject", + "buildsystem": "simple", + "build-options": { + "build-args": [ + "--share=network" + ] + }, + "build-commands": [ + "pip3 install --prefix=${FLATPAK_DEST} PyGobject" + ] + }, + { + "name": "sortedcontainers", + "buildsystem": "simple", + "build-options": { + "build-args": [ + "--share=network" + ] + }, + "build-commands": [ + "pip3 install --prefix=${FLATPAK_DEST} sortedcontainers" + ] + }, + { + "name": "mido", + "buildsystem": "simple", + "build-options": { + "build-args": [ + "--share=network" + ] + }, + "build-commands": [ + "pip3 install --prefix=${FLATPAK_DEST} mido" + ], + "cleanup": [ + "/bin" + ] + }, + { + "name": "python-rtmidi", + "buildsystem": "simple", + "build-options": { + "build-args": [ + "--share=network" + ] + }, + "build-commands": [ + "pip3 install --prefix=${FLATPAK_DEST} python-rtmidi" + ] + }, + { + "name": "JACK-Client", + "buildsystem": "simple", + "build-options": { + "build-args": [ + "--share=network" + ] + }, + "build-commands": [ + "pip3 install --prefix=${FLATPAK_DEST} JACK-Client" + ] + }, + { + "name": "falcon", + "buildsystem": "simple", + "build-options": { + "build-args": [ + "--share=network" + ] + }, + "build-commands": [ + "pip3 install --prefix=${FLATPAK_DEST} falcon" + ] + }, + { + "name": "pyliblo", + "buildsystem": "simple", + "build-options": { + "build-args": [ + "--share=network" + ] + }, + "build-commands": [ + "pip3 install --prefix=${FLATPAK_DEST} pyliblo" + ] + }, + { + "name": "requests", + "buildsystem": "simple", + "build-options": { + "build-args": [ + "--share=network" + ] + }, + "build-commands": [ + "pip3 install --prefix=${FLATPAK_DEST} requests" + ] + }, + { + "name": "linux-show-player", + "buildsystem": "simple", + "build-options": { + "build-args": [ + "--share=network" + ] + }, + "build-commands": [ + "pip3 install --prefix=${FLATPAK_DEST} .", + "mkdir -p /app/share/applications/", + "cp dist/linuxshowplayer.desktop /app/share/applications/", + "mkdir -p /app/share/mime/packages/", + "cp dist/linuxshowplayer.xml /app/share/mime/packages/com.github.FrancescoCeruti.LinuxShowPlayer.xml", + "mkdir -p /app/share/metainfo/", + "sed -i -- 's/linuxshowplayer.desktop/com.github.FrancescoCeruti.LinuxShowPlayer.desktop/g' dist/linuxshowplayer.appdata.xml", + "cp dist/linuxshowplayer.appdata.xml /app/share/metainfo/", + "mkdir -p /app/share/icons/hicolor/512x512/apps/", + "cp dist/linuxshowplayer.png /app/share/icons/hicolor/512x512/apps/" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/FrancescoCeruti/linux-show-player.git", + "branch": "master" + } + ] + } + ] +} diff --git a/dist/Flatpak/travis_flatpak.py b/dist/Flatpak/travis_flatpak.py new file mode 100644 index 000000000..e75a1b616 --- /dev/null +++ b/dist/Flatpak/travis_flatpak.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import os +import json + +DIR = os.path.dirname(__file__) +APP_ID = "com.github.FrancescoCeruti.LinuxShowPlayer" +MANIFEST = os.path.join(DIR, APP_ID + '.json') +BRANCH = os.environ['TRAVIS_BRANCH'] +COMMIT = os.environ['TRAVIS_COMMIT'] + +LiSP_MODULE = 'linux-show-player' + +# Load the manifest (as dictionary) +with open(MANIFEST, mode='r') as template: + manifest = json.load(template) + +# Patch top-Level attributes +manifest['branch'] = BRANCH +if BRANCH != 'master': + manifest['desktop-file-name-suffix'] = ' ({})'.format(BRANCH) + +# Patch modules attributes +source = {} +for module in reversed(manifest['modules']): + if module['name'] == LiSP_MODULE: + source = module['sources'][0] + break + +source['branch'] = BRANCH +source['commit'] = COMMIT + +# Save the patched manifest +with open(MANIFEST, mode='w') as out: + json.dump(manifest, out, indent=4) diff --git a/dist/linuxshowplayer.appdata.xml b/dist/linuxshowplayer.appdata.xml new file mode 100644 index 000000000..167f0e31a --- /dev/null +++ b/dist/linuxshowplayer.appdata.xml @@ -0,0 +1,36 @@ + + + com.github.FrancescoCeruti.LinuxShowPlayer + linuxshowplayer.desktop + CC-BY-3.0 + GPL-3.0+ + Linux Show Player + Cue player designed for stage productions + Francesco Ceruti + ceppofrancy@gmail.com + +

+ Linux Show Player (or LiSP for short) is a free cue player designed for sound-playback in stage production. + The goal of the project is to provide a complete playback software for musical plays, + theater shows and similar. +

+

Some of the features include:

+
    +
  • Cart layout suited for touchscreens
  • +
  • List layout suited for keyboards
  • +
  • Pre/Post wait
  • +
  • Undo/Redo changes
  • +
  • Multiple concurrent cue playback
  • +
  • Realtime sound effects: equalization, pitch shift, speed control, compression, ...
  • +
  • Peak and ReplayGain normalization
  • +
  • Remote control over network, between two or more sessions
  • +
  • MIDI support
  • +
  • OSC support
  • +
  • Large media-format support thanks to GStreamer
  • +
+
+ http://linux-show-player.sourceforge.net + https://github.com/FrancescoCeruti/linux-show-player/issues + http://linux-show-player-users.readthedocs.io/en/latest/index.html + https://github.com/FrancescoCeruti/linux-show-player/wiki/Translations +
\ No newline at end of file diff --git a/dist/travis_bintray.py b/dist/travis_bintray.py new file mode 100644 index 000000000..3aa9e3195 --- /dev/null +++ b/dist/travis_bintray.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import os +import datetime +import json + +TEMPLATE = { + "package": { + "subject": "francescoceruti", + "repo": "LinuxShowPlayer", + "name": "", + "desc": "Automatic builds from travis-ci", + "website_url": "https://github.com/FrancescoCeruti/linux-show-player/issues", + "vcs_url": "https://github.com/FrancescoCeruti/linux-show-player.git", + "issue_tracker_url": "https://github.com/FrancescoCeruti/linux-show-player/issues", + "licenses": ["GPL-3.0"], + }, + + "version": { + "name": "", + "released": "" + }, + + "files": [ + ], + + "publish": True +} + +DIR = os.path.dirname(__file__) +TODAY = datetime.datetime.now().strftime('%Y-%m-%d') +DESC_FILE = 'bintray.json' +DESC_PATH = os.path.join(DIR, DESC_FILE) + +BRANCH = os.environ['TRAVIS_BRANCH'] +COMMIT = os.environ['TRAVIS_COMMIT'] +TAG = os.environ.get('TRAVIS_TAG', '') +VERSION = '{}_{}'.format(TODAY.replace('-', ''), COMMIT[:7]) + +print('Creating "{}" ...'.format(DESC_FILE)) + +# Package +TEMPLATE['package']['name'] = BRANCH +print('>>> Package name: {}'.format(BRANCH)) + +# Version +if TAG: + TEMPLATE['version']['name'] = TAG + TEMPLATE['version']['vcs_tag'] = TAG + print('>>> Version name: {}'.format(TAG)) +else: + TEMPLATE['version']['name'] = VERSION + print('>>> Version name: {}'.format(VERSION)) + +TEMPLATE['version']['released'] = TODAY +print('>>> Version date: {}'.format(TODAY)) + +# Files +TEMPLATE['files'].append( + { + "includePattern": "dist/Flatpak/out/LinuxShowPlayer.flatpak", + "uploadPattern": '/{0}/LinuxShowPlayer.flatpak'.format(VERSION), + } +) + +# Save the bintray description +with open(DESC_PATH, mode='w') as out: + json.dump(TEMPLATE, out, indent=4) + +print('{} created.'.format(DESC_FILE)) From 2828d55b9f161a393652fa9ba9e812b8fa83ac1a Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 11 Jun 2018 14:12:58 +0200 Subject: [PATCH 110/333] use a pre-build python version, to avoid travis timeout --- .../com.github.FrancescoCeruti.LinuxShowPlayer.json | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json b/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json index 330b78541..4922415f8 100644 --- a/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json +++ b/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json @@ -24,18 +24,15 @@ "modules": [ { "name": "cpython", - "build-options": { - "strip": false - }, - "config-opts": [ - "--enable-shared", - "--enable-optimizations" + "buildsystem": "simple", + "build-commands": [ + "cp -r bin/ include/ lib/ share/ /app" ], "sources": [ { "type": "archive", - "url": "https://www.python.org/ftp/python/3.6.5/Python-3.6.5.tar.xz", - "sha256": "f434053ba1b5c8a5cc597e966ead3c5143012af827fd3f0697d21450bb8d87a6" + "url": "https://bintray.com/francescoceruti/flatpak-builds/download_file?file_path=py3.6.5-freedesktop1.6.tar.gz", + "sha256": "623043582870b2c92a5d423d157113a1e8e3acc4f605e2c3df859ca43a45183a" } ] }, From da874cbd7f811d86edee7b5e2bdcec6538300dd9 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 11 Jun 2018 14:41:07 +0200 Subject: [PATCH 111/333] fix bintray url --- dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json b/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json index 4922415f8..1faff713e 100644 --- a/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json +++ b/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json @@ -31,7 +31,7 @@ "sources": [ { "type": "archive", - "url": "https://bintray.com/francescoceruti/flatpak-builds/download_file?file_path=py3.6.5-freedesktop1.6.tar.gz", + "url": "https://dl.bintray.com/francescoceruti/flatpak-builds/py3.6.5-freedesktop1.6.tar.gz", "sha256": "623043582870b2c92a5d423d157113a1e8e3acc4f605e2c3df859ca43a45183a" } ] From a03b9f72141d214d564bcac49b64a720562b70a9 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 11 Jun 2018 19:23:36 +0200 Subject: [PATCH 112/333] update cpython sha256 --- dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json b/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json index 1faff713e..c595f8856 100644 --- a/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json +++ b/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json @@ -32,7 +32,7 @@ { "type": "archive", "url": "https://dl.bintray.com/francescoceruti/flatpak-builds/py3.6.5-freedesktop1.6.tar.gz", - "sha256": "623043582870b2c92a5d423d157113a1e8e3acc4f605e2c3df859ca43a45183a" + "sha256": "d55bea2a7e62f90df90c2a8069d5efbd7a1bb6698b0293db5947f874eaa243be" } ] }, From 3b3c51b4fd52d10907e131c045935c7c1996a375 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 11 Jun 2018 20:33:10 +0200 Subject: [PATCH 113/333] Prevent crash when jackd binary is missing Update: jack is not auto-started anymore --- dist/Flatpak/TODO | 2 -- lisp/core/signal.py | 4 ++-- .../plugins/gst_backend/elements/jack_sink.py | 3 ++- lisp/plugins/gst_backend/gst_media.py | 1 - .../plugins/gst_backend/settings/jack_sink.py | 19 +++++++++++++++++-- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/dist/Flatpak/TODO b/dist/Flatpak/TODO index 3ec035eba..740fcc79e 100644 --- a/dist/Flatpak/TODO +++ b/dist/Flatpak/TODO @@ -2,6 +2,4 @@ create an ad-hoc repository to produce a wheel file like the ones distributed from pypi - We should avoid using pip, see the "upgrade-pip" module -- Avoid crashes because of "missing" jackd binaries - (╯°□°)╯︵ ┻━┻ \ No newline at end of file diff --git a/lisp/core/signal.py b/lisp/core/signal.py index 57ba102bb..618d068bf 100644 --- a/lisp/core/signal.py +++ b/lisp/core/signal.py @@ -73,8 +73,8 @@ def call(self, *args, **kwargs): self._reference()() else: self._reference()(*args, **kwargs) - except Exception: - logger.warning('Error in callback method.', exc_info=True) + except Exception as e: + logger.warning(str(e), exc_info=True) def is_alive(self): return self._reference() is not None diff --git a/lisp/plugins/gst_backend/elements/jack_sink.py b/lisp/plugins/gst_backend/elements/jack_sink.py index 3f881954e..79ccdd9ff 100644 --- a/lisp/plugins/gst_backend/elements/jack_sink.py +++ b/lisp/plugins/gst_backend/elements/jack_sink.py @@ -47,7 +47,8 @@ def __init__(self, pipeline): super().__init__(pipeline) if JackSink._ControlClient is None: - JackSink._ControlClient = jack.Client('LinuxShowPlayer_Control') + JackSink._ControlClient = jack.Client( + 'LinuxShowPlayer_Control', no_start_server=True) self.pipeline = pipeline self.audio_resample = Gst.ElementFactory.make('audioresample') diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index 2fd2872f2..3a9c35053 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -99,7 +99,6 @@ def play(self): for element in self.elements: element.play() - self._state = MediaState.Playing self.__pipeline.set_state(Gst.State.PLAYING) self.__pipeline.get_state(Gst.SECOND) diff --git a/lisp/plugins/gst_backend/settings/jack_sink.py b/lisp/plugins/gst_backend/settings/jack_sink.py index 3018a38f2..e1c9cdf79 100644 --- a/lisp/plugins/gst_backend/settings/jack_sink.py +++ b/lisp/plugins/gst_backend/settings/jack_sink.py @@ -18,6 +18,7 @@ # along with Linux Show Player. If not, see . import jack +import logging from PyQt5.QtCore import Qt from PyQt5.QtGui import QPainter, QPolygon, QPainterPath from PyQt5.QtWidgets import QGroupBox, QWidget, \ @@ -29,6 +30,9 @@ from lisp.ui.ui_utils import translate +logger = logging.getLogger(__name__) + + class JackSinkSettings(SettingsPage): ELEMENT = JackSink Name = ELEMENT.Name @@ -46,7 +50,17 @@ def __init__(self, **kwargs): self.connectionsEdit.clicked.connect(self.__edit_connections) self.jackGroup.layout().addWidget(self.connectionsEdit) - self.__jack_client = jack.Client('LinuxShowPlayer_SettingsControl') + self.__jack_client = None + try: + self.__jack_client = jack.Client( + 'LinuxShowPlayer_SettingsControl', no_start_server=True) + except jack.JackError: + # Disable the widget + self.setEnabled(False) + logger.error( + 'Cannot connect with a running Jack server.', exc_info=True) + + # if __jack_client is None this will return a default value self.connections = JackSink.default_connections(self.__jack_client) self.retranlsateUi() @@ -57,7 +71,8 @@ def retranlsateUi(self): translate('JackSinkSettings', 'Edit connections')) def closeEvent(self, event): - self.__jack_client.close() + if self.__jack_client is not None: + self.__jack_client.close() super().closeEvent(event) def getSettings(self): From c0e790e39faa7ff560d059395683057ea6cea883 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 12 Jun 2018 12:29:19 +0200 Subject: [PATCH 114/333] ListLayout options values are now saved per session Update: added selection-mode in ListLayout configuration --- lisp/plugins/cart_layout/layout.py | 16 ++- lisp/plugins/list_layout/default.json | 3 +- lisp/plugins/list_layout/layout.py | 154 ++++++++++++++++---------- lisp/plugins/list_layout/settings.py | 7 ++ 4 files changed, 109 insertions(+), 71 deletions(-) diff --git a/lisp/plugins/cart_layout/layout.py b/lisp/plugins/cart_layout/layout.py index f8cf6db8d..66d724233 100644 --- a/lisp/plugins/cart_layout/layout.py +++ b/lisp/plugins/cart_layout/layout.py @@ -99,38 +99,36 @@ def __init__(self, application): self.countdown_mode_action = QAction(parent=layout_menu) self.countdown_mode_action.setCheckable(True) - self.countdown_mode_action.setChecked( - CartLayout.Config['countdownMode']) self.countdown_mode_action.triggered.connect(self._set_countdown_mode) layout_menu.addAction(self.countdown_mode_action) self.show_seek_action = QAction(parent=layout_menu) self.show_seek_action.setCheckable(True) - self.show_seek_action.setChecked( - CartLayout.Config['show.seekSliders']) self.show_seek_action.triggered.connect(self._set_seek_bars_visible) layout_menu.addAction(self.show_seek_action) self.show_dbmeter_action = QAction(parent=layout_menu) self.show_dbmeter_action.setCheckable(True) - self.show_dbmeter_action.setChecked(CartLayout.Config['show.dBMeters']) self.show_dbmeter_action.triggered.connect(self._set_dbmeters_visible) layout_menu.addAction(self.show_dbmeter_action) self.show_volume_action = QAction(parent=layout_menu) self.show_volume_action.setCheckable(True) - self.show_volume_action.setChecked( - CartLayout.Config['show.volumeControls']) self.show_volume_action.triggered.connect(self._set_volume_controls_visible) layout_menu.addAction(self.show_volume_action) self.show_accurate_action = QAction(parent=layout_menu) self.show_accurate_action.setCheckable(True) - self.show_accurate_action.setChecked( - CartLayout.Config['show.accurateTime']) self.show_accurate_action.triggered.connect(self._set_accurate_time) layout_menu.addAction(self.show_accurate_action) + self._set_countdown_mode(CartLayout.Config['countdownMode']) + self._set_dbmeters_visible(CartLayout.Config['show.dBMeters']) + self._set_accurate_time(CartLayout.Config['show.accurateTime']) + self._set_seek_bars_visible(CartLayout.Config['show.seekSliders']) + self._set_volume_controls_visible( + CartLayout.Config['show.volumeControls']) + # Context menu actions self._edit_actions_group = MenuActionsGroup(priority=MENU_PRIORITY_CUE) self._edit_actions_group.add( diff --git a/lisp/plugins/list_layout/default.json b/lisp/plugins/list_layout/default.json index 67c1075ee..61b51bf9f 100644 --- a/lisp/plugins/list_layout/default.json +++ b/lisp/plugins/list_layout/default.json @@ -1,5 +1,5 @@ { - "_version_": "1.1", + "_version_": "1.2", "_enabled_": true, "show": { "dBMeters": true, @@ -7,6 +7,7 @@ "accurateTime": false, "playingCues": true }, + "selectionMode": false, "autoContinue": true, "goKey": "Space", "stopCueFade": true, diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index 02ce4eee3..5f7acc7b4 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -22,6 +22,7 @@ from PyQt5.QtWidgets import QAction from lisp.core.configuration import DummyConfiguration +from lisp.core.properties import ProxyProperty from lisp.core.signal import Connection from lisp.cues.cue import Cue, CueAction from lisp.cues.cue_memento_model import CueMementoAdapter @@ -47,6 +48,13 @@ class ListLayout(CueLayout): ] Config = DummyConfiguration() + auto_continue = ProxyProperty() + running_visible = ProxyProperty() + dbmeters_visible = ProxyProperty() + seek_sliders_visible = ProxyProperty() + accurate_time = ProxyProperty() + selection_mode = ProxyProperty() + def __init__(self, application): super().__init__(application) self._list_model = CueListModel(self.cue_model) @@ -54,10 +62,6 @@ def __init__(self, application): self._memento_model = CueMementoAdapter(self._list_model) self._running_model = RunningCueModel(self.cue_model) - self._auto_continue = ListLayout.Config['autoContinue'] - self._go_key_sequence = QKeySequence( - ListLayout.Config['goKey'], QKeySequence.NativeText) - self._view = ListLayoutView(self._list_model, self._running_model) # GO button self._view.goButton.clicked.connect(self.__go_slot) @@ -73,51 +77,48 @@ def __init__(self, application): self._view.listView.contextMenuInvoked.connect(self._context_invoked) self._view.listView.keyPressed.connect(self._key_pressed) - # TODO: session values - self._set_running_visible(ListLayout.Config['show.playingCues']) - self._set_dbmeters_visible(ListLayout.Config['show.dBMeters']) + # Layout menu + layout_menu = self.app.window.menuLayout + + self.show_running_action = QAction(parent=layout_menu) + self.show_running_action.setCheckable(True) + self.show_running_action.triggered.connect(self._set_running_visible) + layout_menu.addAction(self.show_running_action) + + self.show_dbmeter_action = QAction(parent=layout_menu) + self.show_dbmeter_action.setCheckable(True) + self.show_dbmeter_action.triggered.connect(self._set_dbmeters_visible) + layout_menu.addAction(self.show_dbmeter_action) + + self.show_seek_action = QAction(parent=layout_menu) + self.show_seek_action.setCheckable(True) + self.show_seek_action.triggered.connect(self._set_seeksliders_visible) + layout_menu.addAction(self.show_seek_action) + + self.show_accurate_action = QAction(parent=layout_menu) + self.show_accurate_action.setCheckable(True) + self.show_accurate_action.triggered.connect(self._set_accurate_time) + layout_menu.addAction(self.show_accurate_action) + + self.auto_continue_action = QAction(parent=layout_menu) + self.auto_continue_action.setCheckable(True) + self.auto_continue_action.triggered.connect(self._set_auto_continue) + layout_menu.addAction(self.auto_continue_action) + + self.selection_mode_action = QAction(parent=layout_menu) + self.selection_mode_action.setCheckable(True) + self.selection_mode_action.triggered.connect(self._set_selection_mode) + layout_menu.addAction(self.selection_mode_action) + + # Load settings + self._go_key_sequence = QKeySequence( + ListLayout.Config['goKey'], QKeySequence.NativeText) self._set_seeksliders_visible(ListLayout.Config['show.seekSliders']) + self._set_running_visible(ListLayout.Config['show.playingCues']) self._set_accurate_time(ListLayout.Config['show.accurateTime']) - self._set_selection_mode(False) - - # Layout menu - menuLayout = self.app.window.menuLayout - - self.showPlayingAction = QAction(parent=menuLayout) - self.showPlayingAction.setCheckable(True) - self.showPlayingAction.setChecked(self._show_playing) - self.showPlayingAction.triggered.connect(self._set_running_visible) - menuLayout.addAction(self.showPlayingAction) - - self.showDbMeterAction = QAction(parent=menuLayout) - self.showDbMeterAction.setCheckable(True) - self.showDbMeterAction.setChecked(self._show_dbmeter) - self.showDbMeterAction.triggered.connect(self._set_dbmeters_visible) - menuLayout.addAction(self.showDbMeterAction) - - self.showSeekAction = QAction(parent=menuLayout) - self.showSeekAction.setCheckable(True) - self.showSeekAction.setChecked(self._seeksliders_visible) - self.showSeekAction.triggered.connect(self._set_seeksliders_visible) - menuLayout.addAction(self.showSeekAction) - - self.accurateTimingAction = QAction(parent=menuLayout) - self.accurateTimingAction.setCheckable(True) - self.accurateTimingAction.setChecked(self._accurate_time) - self.accurateTimingAction.triggered.connect(self._set_accurate_time) - menuLayout.addAction(self.accurateTimingAction) - - self.autoNextAction = QAction(parent=menuLayout) - self.autoNextAction.setCheckable(True) - self.autoNextAction.setChecked(self._auto_continue) - self.autoNextAction.triggered.connect(self._set_auto_next) - menuLayout.addAction(self.autoNextAction) - - self.selectionModeAction = QAction(parent=menuLayout) - self.selectionModeAction.setCheckable(True) - self.selectionModeAction.setChecked(False) - self.selectionModeAction.triggered.connect(self._set_selection_mode) - menuLayout.addAction(self.selectionModeAction) + self._set_dbmeters_visible(ListLayout.Config['show.dBMeters']) + self._set_selection_mode(ListLayout.Config['selectionMode']) + self._set_auto_continue(ListLayout.Config['autoContinue']) # Context menu actions self._edit_actions_group = MenuActionsGroup(priority=MENU_PRIORITY_CUE) @@ -141,16 +142,16 @@ def __init__(self, application): self.retranslate() def retranslate(self): - self.showPlayingAction.setText( + self.show_running_action.setText( translate('ListLayout', 'Show playing cues')) - self.showDbMeterAction.setText( + self.show_dbmeter_action.setText( translate('ListLayout', 'Show dB-meters')) - self.showSeekAction.setText(translate('ListLayout', 'Show seek-bars')) - self.accurateTimingAction.setText( + self.show_seek_action.setText(translate('ListLayout', 'Show seek-bars')) + self.show_accurate_action.setText( translate('ListLayout', 'Show accurate time')) - self.autoNextAction.setText( + self.auto_continue_action.setText( translate('ListLayout', 'Auto-select next cue')) - self.selectionModeAction.setText( + self.selection_mode_action.setText( translate('ListLayout', 'Selection mode')) def cues(self, cue_type=Cue): @@ -171,7 +172,7 @@ def go(self, action=CueAction.Default, advance=1): standby_cue.execute(action) self.cue_executed.emit(standby_cue) - if self._auto_continue: + if self.auto_continue: self.set_standby_index(self.standby_index() + advance) def cue_at(self, index): @@ -232,33 +233,64 @@ def _key_pressed(self, event): if cue is not None: self.edit_cue(cue) + @accurate_time.set def _set_accurate_time(self, accurate): - self._accurate_time = accurate + self.show_accurate_action.setChecked(accurate) self._view.runView.accurate_time = accurate - def _set_auto_next(self, enable): - self._auto_continue = enable + @accurate_time.get + def _get_accurate_time(self): + return self.show_accurate_action.isChecked() + + @auto_continue.set + def _set_auto_continue(self, enable): + self.auto_continue_action.setChecked(enable) + + @auto_continue.get + def _get_auto_select_next(self): + return self.auto_continue_action.isChecked() + @seek_sliders_visible.set def _set_seeksliders_visible(self, visible): - self._seeksliders_visible = visible + self.show_seek_action.setChecked(visible) self._view.runView.seek_visible = visible + @seek_sliders_visible.get + def _get_seeksliders_visible(self): + return self.show_seek_action.isChecked() + + @dbmeters_visible.set def _set_dbmeters_visible(self, visible): - self._show_dbmeter = visible + self.show_dbmeter_action.setChecked(visible) self._view.runView.dbmeter_visible = visible + @dbmeters_visible.get + def _get_dbmeters_visible(self): + return self.show_dbmeter_action.isChecked() + + @running_visible.set def _set_running_visible(self, visible): - self._show_playing = visible + self.show_running_action.setChecked(visible) self._view.runView.setVisible(visible) self._view.controlButtons.setVisible(visible) + @running_visible.get + def _get_running_visible(self): + return self.show_running_action.isChecked() + + @selection_mode.set def _set_selection_mode(self, enable): + self.selection_mode_action.setChecked(enable) if enable: self._view.listView.setSelectionMode(CueListView.ExtendedSelection) else: self.deselect_all() self._view.listView.setSelectionMode(CueListView.NoSelection) + @selection_mode.get + def _get_selection_mode(self): + return self.selection_mode_action.isChecked() + def _double_clicked(self): cue = self.standby_cue() if cue is not None: @@ -292,7 +324,7 @@ def __cue_next(self, cue): next_cue = self._list_model.item(next_index) next_cue.cue.execute() - if self._auto_continue and next_cue is self.standby_cue(): + if self.auto_continue and next_cue is self.standby_cue(): self.set_standby_index(next_index + 1) except(IndexError, KeyError): pass diff --git a/lisp/plugins/list_layout/settings.py b/lisp/plugins/list_layout/settings.py index 30c815670..29e279199 100644 --- a/lisp/plugins/list_layout/settings.py +++ b/lisp/plugins/list_layout/settings.py @@ -53,6 +53,9 @@ def __init__(self, config, **kwargs): self.autoNext = QCheckBox(self.behaviorsGroup) self.behaviorsGroup.layout().addWidget(self.autoNext) + self.selectionMode = QCheckBox(self.behaviorsGroup) + self.behaviorsGroup.layout().addWidget(self.selectionMode) + self.goKeyLayout = QHBoxLayout() self.behaviorsGroup.layout().addLayout(self.goKeyLayout) self.goKeyLabel = QLabel(self.behaviorsGroup) @@ -87,6 +90,8 @@ def retranslateUi(self): self.showAccurate.setText(translate('ListLayout', 'Show accurate time')) self.showSeek.setText(translate('ListLayout', 'Show seek-bars')) self.autoNext.setText(translate('ListLayout', 'Auto-select next cue')) + self.selectionMode.setText( + translate('ListLayout', 'Enable selection mode')) self.goKeyLabel.setText(translate('ListLayout', 'Go key:')) self.useFadeGroup.setTitle( @@ -102,6 +107,7 @@ def loadSettings(self): self.showAccurate.setChecked(self.config['show.accurateTime']) self.showSeek.setChecked(self.config['show.seekSliders']) self.autoNext.setChecked(self.config['autoContinue']) + self.selectionMode.setChecked(self.config['selectionMode']) self.goKeyEdit.setKeySequence( QKeySequence(self.config['goKey'], QKeySequence.NativeText)) @@ -116,6 +122,7 @@ def applySettings(self): self.config['show.dBMeters'] = self.showDbMeters.isChecked() self.config['show.seekBars'] = self.showSeek.isChecked() self.config['autoContinue'] = self.autoNext.isChecked() + self.config['selectionMode'] = self.selectionMode.isChecked() self.config['goKey'] = self.goKeyEdit.keySequence().toString( QKeySequence.NativeText) From 0c6f71e5b0ef674c993ef4d6bac828ba2b7373cf Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 12 Jun 2018 13:05:30 +0200 Subject: [PATCH 115/333] Update bintray upload version to include the time of the build --- dist/travis_bintray.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dist/travis_bintray.py b/dist/travis_bintray.py index 3aa9e3195..9ecf9b658 100644 --- a/dist/travis_bintray.py +++ b/dist/travis_bintray.py @@ -52,7 +52,9 @@ BRANCH = os.environ['TRAVIS_BRANCH'] COMMIT = os.environ['TRAVIS_COMMIT'] TAG = os.environ.get('TRAVIS_TAG', '') -VERSION = '{}_{}'.format(TODAY.replace('-', ''), COMMIT[:7]) + +VERSION = datetime.datetime.now().strftime('%Y%m%d_%H%M') +VERSION += '_{}'.format(TAG if TAG else COMMIT[:7]) print('Creating "{}" ...'.format(DESC_FILE)) @@ -61,17 +63,15 @@ print('>>> Package name: {}'.format(BRANCH)) # Version -if TAG: - TEMPLATE['version']['name'] = TAG - TEMPLATE['version']['vcs_tag'] = TAG - print('>>> Version name: {}'.format(TAG)) -else: - TEMPLATE['version']['name'] = VERSION - print('>>> Version name: {}'.format(VERSION)) +TEMPLATE['version']['name'] = VERSION +print('>>> Version name: {}'.format(VERSION)) TEMPLATE['version']['released'] = TODAY print('>>> Version date: {}'.format(TODAY)) +if TAG: + TEMPLATE['version']['vcs_tag'] = TAG + # Files TEMPLATE['files'].append( { From 76f7faa68812d09f1f7dd46c9bf0581b778b581c Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 12 Jun 2018 14:08:10 +0200 Subject: [PATCH 116/333] Handle some error in the backend Fix: UriInput doesn't crash when the "uri" is empty/invalid Fix: GstMedia now handle errors per element when creating the pipeline, this avoid loosing working elements, and their settings, in some case it allow the keep the media playable (without the faulty element) Update: add commit message as bintray versions description --- dist/travis_bintray.py | 6 +++++- lisp/plugins/gst_backend/elements/uri_input.py | 7 ++++++- lisp/plugins/gst_backend/gst_media.py | 5 +++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/dist/travis_bintray.py b/dist/travis_bintray.py index 9ecf9b658..986238072 100644 --- a/dist/travis_bintray.py +++ b/dist/travis_bintray.py @@ -35,7 +35,8 @@ "version": { "name": "", - "released": "" + "released": "", + "desc": "" }, "files": [ @@ -51,6 +52,7 @@ BRANCH = os.environ['TRAVIS_BRANCH'] COMMIT = os.environ['TRAVIS_COMMIT'] +COMMIT_MSG = os.environ['TRAVIS_COMMIT_MESSAGE'] TAG = os.environ.get('TRAVIS_TAG', '') VERSION = datetime.datetime.now().strftime('%Y%m%d_%H%M') @@ -69,6 +71,8 @@ TEMPLATE['version']['released'] = TODAY print('>>> Version date: {}'.format(TODAY)) +TEMPLATE['version']['desc'] = COMMIT_MSG + if TAG: TEMPLATE['version']['vcs_tag'] = TAG diff --git a/lisp/plugins/gst_backend/elements/uri_input.py b/lisp/plugins/gst_backend/elements/uri_input.py index 874865ff1..606920a7c 100644 --- a/lisp/plugins/gst_backend/elements/uri_input.py +++ b/lisp/plugins/gst_backend/elements/uri_input.py @@ -37,7 +37,12 @@ def abs_path(path_): def uri_split(uri): - return uri.split('://') + try: + scheme, path = uri.split('://') + except ValueError: + scheme = path = '' + + return scheme, path def uri_adapter(uri): diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index 3a9c35053..4cc4f6180 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -221,6 +221,11 @@ def __init_pipeline(self): self.elements.append(all_elements[element](self.__pipeline)) except KeyError: logger.warning('Invalid pipeline element: {}'.format(element)) + except Exception: + logger.warning( + 'Cannot create pipeline element: {}'.format(element), + exc_info=True + ) # Reload the elements properties self.elements.update_properties(elements_properties) From a6ef7464e76060536149fcb9cf5eb2e4277565f9 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 13 Jun 2018 20:19:52 +0200 Subject: [PATCH 117/333] revert to bintray uploads --- .travis.yml | 1 + dist/Flatpak/build.sh | 44 ++++++++++----- ...ithub.FrancescoCeruti.LinuxShowPlayer.json | 1 - dist/Flatpak/functions.sh | 53 +++++++++++++++++++ dist/linuxshowplayer.appdata.xml | 5 +- dist/travis_bintray.py | 2 +- 6 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 dist/Flatpak/functions.sh diff --git a/.travis.yml b/.travis.yml index c8d1e34af..ddc37d722 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ before_install: - docker pull francescoceruti/flatpack script: + # Enter the flatpak buid directory - cd dist/Flatpak # Patch the flatpak manifest to work with travis - python3 travis_flatpak.py diff --git a/dist/Flatpak/build.sh b/dist/Flatpak/build.sh index ac1277625..d005a1e27 100755 --- a/dist/Flatpak/build.sh +++ b/dist/Flatpak/build.sh @@ -1,24 +1,42 @@ -#!/bin/sh +#!/bin/bash # Make sure we are in the same directory of this file cd "${0%/*}" +# Include some travis utility function +source "functions.sh" + BRANCH="$1" -set -xe +set -e + +# Prepare the repository +travis_fold start "flatpak_prepare" + ostree init --mode=archive-z2 --repo=repo +travis_fold end "flatpak_prepare" -# Create and add a local repository -ostree init --mode=archive-z2 --repo=repository -flatpak remote-add --user --no-gpg-verify --if-not-exists repo repository # Install runtime and sdk -flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo -flatpak install --user flathub org.gnome.Platform//3.28 org.gnome.Sdk//3.28 +travis_fold start "flatpak_install_deps" + travis_time_start + flatpak --user remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo + flatpak --user install flathub org.gnome.Platform//3.28 org.gnome.Sdk//3.28 + travis_time_finish +travis_fold end "flatpak_install_deps" -# Run the build -flatpak-builder --verbose --force-clean --ccache --repo=repo build com.github.FrancescoCeruti.LinuxShowPlayer.json +# Build +travis_fold start "flatpak_build" + travis_time_start + flatpak-builder --verbose --force-clean --ccache --repo=repo \ + build com.github.FrancescoCeruti.LinuxShowPlayer.json + travis_time_finish +travis_fold end "flatpak_build" -# Bundle -mkdir -p out -flatpak build-bundle repo out/LinuxShowPlayer.flatpak com.github.FrancescoCeruti.LinuxShowPlayer $BRANCH +# Bundle the build +travis_fold start "flatpak_bundle" + travis_time_start + mkdir -p out + flatpak build-bundle repo out/LinuxShowPlayer.flatpak com.github.FrancescoCeruti.LinuxShowPlayer $BRANCH + travis_time_finish +travis_fold end "flatpak_update_repo" -set +xe \ No newline at end of file +set +e \ No newline at end of file diff --git a/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json b/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json index c595f8856..10e23a220 100644 --- a/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json +++ b/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json @@ -332,7 +332,6 @@ "mkdir -p /app/share/mime/packages/", "cp dist/linuxshowplayer.xml /app/share/mime/packages/com.github.FrancescoCeruti.LinuxShowPlayer.xml", "mkdir -p /app/share/metainfo/", - "sed -i -- 's/linuxshowplayer.desktop/com.github.FrancescoCeruti.LinuxShowPlayer.desktop/g' dist/linuxshowplayer.appdata.xml", "cp dist/linuxshowplayer.appdata.xml /app/share/metainfo/", "mkdir -p /app/share/icons/hicolor/512x512/apps/", "cp dist/linuxshowplayer.png /app/share/icons/hicolor/512x512/apps/" diff --git a/dist/Flatpak/functions.sh b/dist/Flatpak/functions.sh new file mode 100644 index 000000000..9b9663511 --- /dev/null +++ b/dist/Flatpak/functions.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# +# Derived from: https://github.com/travis-ci/travis-build/blob/e12e2fc01fd96cae8671b1955397813023953743/lib/travis/build/templates/header.sh +# +# MIT LICENSE +# +# Copyright (c) 2016 Travis CI GmbH +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +ANSI_CLEAR="\033[0K" + +travis_time_start() { + travis_timer_id=$(printf %08x $(( RANDOM * RANDOM ))) + travis_start_time=$(travis_nanoseconds) + echo -en "travis_time:start:$travis_timer_id\r${ANSI_CLEAR}" +} + +travis_time_finish() { + local result=$? + travis_end_time=$(travis_nanoseconds) + local duration=$(($travis_end_time-$travis_start_time)) + echo -en "\ntravis_time:end:$travis_timer_id:start=$travis_start_time,finish=$travis_end_time,duration=$duration\r${ANSI_CLEAR}" + return $result +} + +travis_nanoseconds() { + local cmd="date" + local format="+%s%N" + $cmd -u $format +} + +travis_fold() { + local action=$1 + local name=$2 + echo -en "travis_fold:${action}:${name}\r${ANSI_CLEAR}" +} \ No newline at end of file diff --git a/dist/linuxshowplayer.appdata.xml b/dist/linuxshowplayer.appdata.xml index 167f0e31a..1c86c0da0 100644 --- a/dist/linuxshowplayer.appdata.xml +++ b/dist/linuxshowplayer.appdata.xml @@ -1,7 +1,6 @@ - - com.github.FrancescoCeruti.LinuxShowPlayer - linuxshowplayer.desktop + + com.github.FrancescoCeruti.LinuxShowPlayer.desktop CC-BY-3.0 GPL-3.0+ Linux Show Player diff --git a/dist/travis_bintray.py b/dist/travis_bintray.py index 986238072..9aa7a7c31 100644 --- a/dist/travis_bintray.py +++ b/dist/travis_bintray.py @@ -55,7 +55,7 @@ COMMIT_MSG = os.environ['TRAVIS_COMMIT_MESSAGE'] TAG = os.environ.get('TRAVIS_TAG', '') -VERSION = datetime.datetime.now().strftime('%Y%m%d_%H%M') +VERSION = datetime.datetime.now().strftime('%Y.%m.%d_%H:%M') VERSION += '_{}'.format(TAG if TAG else COMMIT[:7]) print('Creating "{}" ...'.format(DESC_FILE)) From 0ffab25268409e86b2b83a01b96d43e3e54bfba8 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 14 Jun 2018 13:37:44 +0200 Subject: [PATCH 118/333] Fixes Fix: key_pressed is now emitted by the layouts Fix: midi capture in `controller` plugin settings now work properly --- dist/Flatpak/build.sh | 11 ++++++----- lisp/application.py | 1 + lisp/plugins/cart_layout/layout.py | 4 ++++ lisp/plugins/cart_layout/tab_widget.py | 11 ++++++++++- lisp/plugins/controller/protocols/midi.py | 2 +- lisp/plugins/list_layout/layout.py | 3 +++ lisp/plugins/list_layout/list_view.py | 10 ++++------ lisp/plugins/presets/presets.py | 2 +- 8 files changed, 30 insertions(+), 14 deletions(-) diff --git a/dist/Flatpak/build.sh b/dist/Flatpak/build.sh index d005a1e27..d60752ea8 100755 --- a/dist/Flatpak/build.sh +++ b/dist/Flatpak/build.sh @@ -10,10 +10,7 @@ BRANCH="$1" set -e # Prepare the repository -travis_fold start "flatpak_prepare" - ostree init --mode=archive-z2 --repo=repo -travis_fold end "flatpak_prepare" - +ostree init --mode=archive-z2 --repo=repo # Install runtime and sdk travis_fold start "flatpak_install_deps" @@ -22,6 +19,7 @@ travis_fold start "flatpak_install_deps" flatpak --user install flathub org.gnome.Platform//3.28 org.gnome.Sdk//3.28 travis_time_finish travis_fold end "flatpak_install_deps" +echo -e \\n # Build travis_fold start "flatpak_build" @@ -30,13 +28,16 @@ travis_fold start "flatpak_build" build com.github.FrancescoCeruti.LinuxShowPlayer.json travis_time_finish travis_fold end "flatpak_build" +echo -e \\n # Bundle the build travis_fold start "flatpak_bundle" travis_time_start mkdir -p out - flatpak build-bundle repo out/LinuxShowPlayer.flatpak com.github.FrancescoCeruti.LinuxShowPlayer $BRANCH + flatpak build-bundle --verbose repo out/LinuxShowPlayer.flatpak \ + com.github.FrancescoCeruti.LinuxShowPlayer $BRANCH travis_time_finish travis_fold end "flatpak_update_repo" +echo -e \\n set +e \ No newline at end of file diff --git a/lisp/application.py b/lisp/application.py index 2e6a1d636..6566f591c 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -91,6 +91,7 @@ def window(self): @property def layout(self): + """:rtype: lisp.layout.cue_layout.CueLayout""" return self.session.layout @property diff --git a/lisp/plugins/cart_layout/layout.py b/lisp/plugins/cart_layout/layout.py index 66d724233..aa330bfac 100644 --- a/lisp/plugins/cart_layout/layout.py +++ b/lisp/plugins/cart_layout/layout.py @@ -79,6 +79,7 @@ def __init__(self, application): self._memento_model = CueMementoAdapter(self._cart_model) self._cart_view = CartTabWidget() + self._cart_view.keyPressed.connect(self._key_pressed) # Layout menu layout_menu = self.app.window.menuLayout @@ -325,6 +326,9 @@ def _set_volume_controls_visible(self, visible): def _get_volume_controls_visible(self): return self.show_volume_action.isChecked() + def _key_pressed(self, event): + self.key_pressed.emit(event) + def to_3d_index(self, index): page_size = self.__rows * self.__columns diff --git a/lisp/plugins/cart_layout/tab_widget.py b/lisp/plugins/cart_layout/tab_widget.py index bf6a61143..28b021eec 100644 --- a/lisp/plugins/cart_layout/tab_widget.py +++ b/lisp/plugins/cart_layout/tab_widget.py @@ -17,7 +17,8 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, pyqtSignal +from PyQt5.QtGui import QKeyEvent from PyQt5.QtWidgets import QTabWidget from lisp.ui.widgets.qeditabletabbar import QEditableTabBar @@ -26,6 +27,8 @@ class CartTabWidget(QTabWidget): DRAG_MAGIC = 'LiSP_Drag&Drop' + keyPressed = pyqtSignal(QKeyEvent) + def __init__(self, **kwargs): super().__init__(**kwargs) self.setTabBar(QEditableTabBar(self)) @@ -47,6 +50,12 @@ def dragMoveEvent(self, event): def dropEvent(self, event): event.ignore() + def keyPressEvent(self, event): + if not event.isAutoRepeat(): + self.keyPressed.emit(event) + + super().keyPressEvent(event) + def tabTexts(self): texts = [] for i in range(self.tabBar().count()): diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index 0a1822b67..98e279448 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -104,7 +104,7 @@ def __init__(self, cue_type, **kwargs): self._default_action = self.cue_type.CueActions[0].name try: - self.__midi = get_plugin('Midi').input + self.__midi = get_plugin('Midi') except PluginNotLoadedError: self.setEnabled(False) diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index 5f7acc7b4..948d16b5e 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -232,6 +232,9 @@ def _key_pressed(self, event): cue = self.standby_cue() if cue is not None: self.edit_cue(cue) + else: + self.key_pressed.emit(event) + event.ignore() @accurate_time.set def _set_accurate_time(self, accurate): diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py index 924bef6de..e09ab5867 100644 --- a/lisp/plugins/list_layout/list_view.py +++ b/lisp/plugins/list_layout/list_view.py @@ -173,13 +173,11 @@ def contextMenuEvent(self, event): def keyPressEvent(self, event): event.ignore() - + self.keyPressed.emit(event) + # If the event object has been accepted during the `keyPressed` + # emission don't call the base implementation if not event.isAccepted(): - self.keyPressed.emit(event) - # If the event object has been accepted during the `keyPressed` - # emission don't call the base implementation - if not event.isAccepted(): - super().keyPressEvent(event) + super().keyPressEvent(event) def mousePressEvent(self, event): if (not event.buttons() & Qt.RightButton or diff --git a/lisp/plugins/presets/presets.py b/lisp/plugins/presets/presets.py index 06a23f20b..65b862a47 100644 --- a/lisp/plugins/presets/presets.py +++ b/lisp/plugins/presets/presets.py @@ -49,7 +49,7 @@ def __init__(self, app): self.manageAction.triggered.connect(self.__edit_presets) self.manageAction.setText(translate('Presets', 'Presets')) - self.menu_action = self.app.window.menuTools.addAction(self.manageAction) + self.app.window.menuTools.addAction(self.manageAction) # Cue menu (context-action) self.cueActionsGroup = MenuActionsGroup( From 9bcb21d9886ed627c448b86a9f5d53f97a039345 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 20 Jun 2018 15:17:59 +0200 Subject: [PATCH 119/333] Update Crowdin configuration file --- crowdin.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 crowdin.yml diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..fc6e50d32 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,3 @@ +files: + - source: /lisp/i18n/ts/en/*.ts + translation: /lisp/i18n/%locale_with_underscore%/%original_file_name% From 2fd177bb02de11b5003fcb5eb7a6b4f9b83628bd Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 20 Jun 2018 19:42:54 +0200 Subject: [PATCH 120/333] change i18n folder structure --- lisp/__init__.py | 2 +- lisp/default.json | 3 -- .../i18n => i18n/qm}/action_cues_cs.qm | Bin .../i18n => i18n/qm}/action_cues_en.qm | Bin .../i18n => i18n/qm}/action_cues_es.qm | Bin .../i18n => i18n/qm}/action_cues_fr.qm | Bin .../i18n => i18n/qm}/action_cues_it.qm | Bin .../i18n => i18n/qm}/action_cues_sl_SI.qm | Bin .../i18n => i18n/qm}/controller_cs.qm | Bin .../i18n => i18n/qm}/controller_en.qm | Bin .../i18n => i18n/qm}/controller_es.qm | Bin .../i18n => i18n/qm}/controller_fr.qm | Bin .../i18n => i18n/qm}/controller_it.qm | Bin .../i18n => i18n/qm}/controller_sl_SI.qm | Bin .../i18n => i18n/qm}/gst_backend_cs.qm | Bin .../i18n => i18n/qm}/gst_backend_en.qm | Bin .../i18n => i18n/qm}/gst_backend_es.qm | Bin .../i18n => i18n/qm}/gst_backend_fr.qm | Bin .../i18n => i18n/qm}/gst_backend_it.qm | Bin .../i18n => i18n/qm}/gst_backend_sl_SI.qm | Bin lisp/i18n/{ => qm}/lisp_cs.qm | Bin lisp/i18n/{ => qm}/lisp_en.qm | Bin lisp/i18n/{ => qm}/lisp_es.qm | Bin lisp/i18n/{ => qm}/lisp_fr.qm | Bin lisp/i18n/{ => qm}/lisp_it.qm | Bin lisp/i18n/{ => qm}/lisp_sl_SI.qm | Bin .../i18n => i18n/qm}/media_info_cs.qm | Bin .../i18n => i18n/qm}/media_info_en.qm | Bin .../i18n => i18n/qm}/media_info_es.qm | Bin .../i18n => i18n/qm}/media_info_fr.qm | Bin .../i18n => i18n/qm}/media_info_it.qm | Bin .../i18n => i18n/qm}/media_info_sl_SI.qm | Bin .../{plugins/midi/i18n => i18n/qm}/midi_cs.qm | Bin .../{plugins/midi/i18n => i18n/qm}/midi_en.qm | Bin .../{plugins/midi/i18n => i18n/qm}/midi_es.qm | Bin .../{plugins/midi/i18n => i18n/qm}/midi_fr.qm | Bin .../{plugins/midi/i18n => i18n/qm}/midi_it.qm | Bin .../midi/i18n => i18n/qm}/midi_sl_SI.qm | Bin .../presets/i18n => i18n/qm}/presets_cs.qm | Bin .../presets/i18n => i18n/qm}/presets_en.qm | Bin .../presets/i18n => i18n/qm}/presets_es.qm | Bin .../presets/i18n => i18n/qm}/presets_fr.qm | Bin .../presets/i18n => i18n/qm}/presets_it.qm | Bin .../presets/i18n => i18n/qm}/presets_sl_SI.qm | Bin .../i18n => i18n/qm}/replay_gain_cs.qm | Bin .../i18n => i18n/qm}/replay_gain_en.qm | Bin .../i18n => i18n/qm}/replay_gain_es.qm | Bin .../i18n => i18n/qm}/replay_gain_fr.qm | Bin .../i18n => i18n/qm}/replay_gain_it.qm | Bin .../i18n => i18n/qm}/replay_gain_sl_SI.qm | Bin .../i18n => i18n/qm}/synchronizer_cs.qm | Bin .../i18n => i18n/qm}/synchronizer_en.qm | Bin .../i18n => i18n/qm}/synchronizer_es.qm | Bin .../i18n => i18n/qm}/synchronizer_fr.qm | Bin .../i18n => i18n/qm}/synchronizer_it.qm | Bin .../i18n => i18n/qm}/synchronizer_sl_SI.qm | Bin .../timecode/i18n => i18n/qm}/timecode_cs.qm | Bin .../timecode/i18n => i18n/qm}/timecode_en.qm | Bin .../timecode/i18n => i18n/qm}/timecode_es.qm | Bin .../timecode/i18n => i18n/qm}/timecode_fr.qm | Bin .../timecode/i18n => i18n/qm}/timecode_it.qm | Bin .../i18n => i18n/qm}/timecode_sl_SI.qm | Bin .../triggers/i18n => i18n/qm}/triggers_cs.qm | Bin .../triggers/i18n => i18n/qm}/triggers_en.qm | Bin .../triggers/i18n => i18n/qm}/triggers_es.qm | Bin .../triggers/i18n => i18n/qm}/triggers_fr.qm | Bin .../triggers/i18n => i18n/qm}/triggers_it.qm | Bin .../i18n => i18n/qm}/triggers_sl_SI.qm | Bin .../ts/cs_CZ/action_cues.ts} | 0 .../ts/cs_CZ/controller.ts} | 0 .../ts/cs_CZ/gst_backend.ts} | 0 lisp/i18n/{lisp_cs.ts => ts/cs_CZ/lisp.ts} | 0 .../ts/cs_CZ/media_info.ts} | 0 .../i18n/midi_cs.ts => i18n/ts/cs_CZ/midi.ts} | 0 .../ts/cs_CZ/presets.ts} | 0 .../ts/cs_CZ/replay_gain.ts} | 0 .../ts/cs_CZ/synchronizer.ts} | 0 .../ts/cs_CZ/timecode.ts} | 0 .../ts/cs_CZ/triggers.ts} | 0 .../ts/en/action_cues.ts} | 0 .../ts/en/controller.ts} | 0 .../ts/en/gst_backend.ts} | 0 lisp/i18n/{lisp_en.ts => ts/en/lisp.ts} | 0 .../ts/en/media_info.ts} | 0 .../i18n/midi_en.ts => i18n/ts/en/midi.ts} | 0 .../presets_en.ts => i18n/ts/en/presets.ts} | 0 .../ts/en/replay_gain.ts} | 0 .../ts/en/synchronizer.ts} | 0 .../timecode_en.ts => i18n/ts/en/timecode.ts} | 0 .../triggers_en.ts => i18n/ts/en/triggers.ts} | 0 .../ts/es_ES/action_cues.ts} | 0 .../ts/es_ES/controller.ts} | 0 .../ts/es_ES/gst_backend.ts} | 0 lisp/i18n/{lisp_es.ts => ts/es_ES/lisp.ts} | 0 .../ts/es_ES/media_info.ts} | 0 .../i18n/midi_es.ts => i18n/ts/es_ES/midi.ts} | 0 .../ts/es_ES/presets.ts} | 0 .../ts/es_ES/replay_gain.ts} | 0 .../ts/es_ES/synchronizer.ts} | 0 .../ts/es_ES/timecode.ts} | 0 .../ts/es_ES/triggers.ts} | 0 .../ts/fr_FR/action_cues.ts} | 0 .../ts/fr_FR/controller.ts} | 0 .../ts/fr_FR/gst_backend.ts} | 0 lisp/i18n/{lisp_fr.ts => ts/fr_FR/lisp.ts} | 0 .../ts/fr_FR/media_info.ts} | 0 .../i18n/midi_fr.ts => i18n/ts/fr_FR/midi.ts} | 0 .../ts/fr_FR/presets.ts} | 0 .../ts/fr_FR/replay_gain.ts} | 0 .../ts/fr_FR/synchronizer.ts} | 0 .../ts/fr_FR/timecode.ts} | 0 .../ts/fr_FR/triggers.ts} | 0 .../ts/it_IT/action_cues.ts} | 0 .../ts/it_IT/controller.ts} | 0 .../ts/it_IT/gst_backend.ts} | 0 lisp/i18n/{lisp_it.ts => ts/it_IT/lisp.ts} | 0 .../ts/it_IT/media_info.ts} | 0 .../i18n/midi_it.ts => i18n/ts/it_IT/midi.ts} | 0 .../ts/it_IT/presets.ts} | 0 .../ts/it_IT/replay_gain.ts} | 0 .../ts/it_IT/synchronizer.ts} | 0 .../ts/it_IT/timecode.ts} | 0 .../ts/it_IT/triggers.ts} | 0 .../ts/sl_SI/action_cues.ts} | 0 .../ts/sl_SI/controller.ts} | 0 .../ts/sl_SI/gst_backend.ts} | 0 lisp/i18n/{lisp_sl_SI.ts => ts/sl_SI/lisp.ts} | 0 .../ts/sl_SI/media_info.ts} | 0 .../midi_sl_SI.ts => i18n/ts/sl_SI/midi.ts} | 0 .../ts/sl_SI/presets.ts} | 0 .../ts/sl_SI/replay_gain.ts} | 0 .../ts/sl_SI/synchronizer.ts} | 0 .../ts/sl_SI/timecode.ts} | 0 .../ts/sl_SI/triggers.ts} | 0 lisp/ui/about.py | 38 +++++++++++------- 135 files changed, 24 insertions(+), 19 deletions(-) rename lisp/{plugins/action_cues/i18n => i18n/qm}/action_cues_cs.qm (100%) rename lisp/{plugins/action_cues/i18n => i18n/qm}/action_cues_en.qm (100%) rename lisp/{plugins/action_cues/i18n => i18n/qm}/action_cues_es.qm (100%) rename lisp/{plugins/action_cues/i18n => i18n/qm}/action_cues_fr.qm (100%) rename lisp/{plugins/action_cues/i18n => i18n/qm}/action_cues_it.qm (100%) rename lisp/{plugins/action_cues/i18n => i18n/qm}/action_cues_sl_SI.qm (100%) rename lisp/{plugins/controller/i18n => i18n/qm}/controller_cs.qm (100%) rename lisp/{plugins/controller/i18n => i18n/qm}/controller_en.qm (100%) rename lisp/{plugins/controller/i18n => i18n/qm}/controller_es.qm (100%) rename lisp/{plugins/controller/i18n => i18n/qm}/controller_fr.qm (100%) rename lisp/{plugins/controller/i18n => i18n/qm}/controller_it.qm (100%) rename lisp/{plugins/controller/i18n => i18n/qm}/controller_sl_SI.qm (100%) rename lisp/{plugins/gst_backend/i18n => i18n/qm}/gst_backend_cs.qm (100%) rename lisp/{plugins/gst_backend/i18n => i18n/qm}/gst_backend_en.qm (100%) rename lisp/{plugins/gst_backend/i18n => i18n/qm}/gst_backend_es.qm (100%) rename lisp/{plugins/gst_backend/i18n => i18n/qm}/gst_backend_fr.qm (100%) rename lisp/{plugins/gst_backend/i18n => i18n/qm}/gst_backend_it.qm (100%) rename lisp/{plugins/gst_backend/i18n => i18n/qm}/gst_backend_sl_SI.qm (100%) rename lisp/i18n/{ => qm}/lisp_cs.qm (100%) rename lisp/i18n/{ => qm}/lisp_en.qm (100%) rename lisp/i18n/{ => qm}/lisp_es.qm (100%) rename lisp/i18n/{ => qm}/lisp_fr.qm (100%) rename lisp/i18n/{ => qm}/lisp_it.qm (100%) rename lisp/i18n/{ => qm}/lisp_sl_SI.qm (100%) rename lisp/{plugins/media_info/i18n => i18n/qm}/media_info_cs.qm (100%) rename lisp/{plugins/media_info/i18n => i18n/qm}/media_info_en.qm (100%) rename lisp/{plugins/media_info/i18n => i18n/qm}/media_info_es.qm (100%) rename lisp/{plugins/media_info/i18n => i18n/qm}/media_info_fr.qm (100%) rename lisp/{plugins/media_info/i18n => i18n/qm}/media_info_it.qm (100%) rename lisp/{plugins/media_info/i18n => i18n/qm}/media_info_sl_SI.qm (100%) rename lisp/{plugins/midi/i18n => i18n/qm}/midi_cs.qm (100%) rename lisp/{plugins/midi/i18n => i18n/qm}/midi_en.qm (100%) rename lisp/{plugins/midi/i18n => i18n/qm}/midi_es.qm (100%) rename lisp/{plugins/midi/i18n => i18n/qm}/midi_fr.qm (100%) rename lisp/{plugins/midi/i18n => i18n/qm}/midi_it.qm (100%) rename lisp/{plugins/midi/i18n => i18n/qm}/midi_sl_SI.qm (100%) rename lisp/{plugins/presets/i18n => i18n/qm}/presets_cs.qm (100%) rename lisp/{plugins/presets/i18n => i18n/qm}/presets_en.qm (100%) rename lisp/{plugins/presets/i18n => i18n/qm}/presets_es.qm (100%) rename lisp/{plugins/presets/i18n => i18n/qm}/presets_fr.qm (100%) rename lisp/{plugins/presets/i18n => i18n/qm}/presets_it.qm (100%) rename lisp/{plugins/presets/i18n => i18n/qm}/presets_sl_SI.qm (100%) rename lisp/{plugins/replay_gain/i18n => i18n/qm}/replay_gain_cs.qm (100%) rename lisp/{plugins/replay_gain/i18n => i18n/qm}/replay_gain_en.qm (100%) rename lisp/{plugins/replay_gain/i18n => i18n/qm}/replay_gain_es.qm (100%) rename lisp/{plugins/replay_gain/i18n => i18n/qm}/replay_gain_fr.qm (100%) rename lisp/{plugins/replay_gain/i18n => i18n/qm}/replay_gain_it.qm (100%) rename lisp/{plugins/replay_gain/i18n => i18n/qm}/replay_gain_sl_SI.qm (100%) rename lisp/{plugins/synchronizer/i18n => i18n/qm}/synchronizer_cs.qm (100%) rename lisp/{plugins/synchronizer/i18n => i18n/qm}/synchronizer_en.qm (100%) rename lisp/{plugins/synchronizer/i18n => i18n/qm}/synchronizer_es.qm (100%) rename lisp/{plugins/synchronizer/i18n => i18n/qm}/synchronizer_fr.qm (100%) rename lisp/{plugins/synchronizer/i18n => i18n/qm}/synchronizer_it.qm (100%) rename lisp/{plugins/synchronizer/i18n => i18n/qm}/synchronizer_sl_SI.qm (100%) rename lisp/{plugins/timecode/i18n => i18n/qm}/timecode_cs.qm (100%) rename lisp/{plugins/timecode/i18n => i18n/qm}/timecode_en.qm (100%) rename lisp/{plugins/timecode/i18n => i18n/qm}/timecode_es.qm (100%) rename lisp/{plugins/timecode/i18n => i18n/qm}/timecode_fr.qm (100%) rename lisp/{plugins/timecode/i18n => i18n/qm}/timecode_it.qm (100%) rename lisp/{plugins/timecode/i18n => i18n/qm}/timecode_sl_SI.qm (100%) rename lisp/{plugins/triggers/i18n => i18n/qm}/triggers_cs.qm (100%) rename lisp/{plugins/triggers/i18n => i18n/qm}/triggers_en.qm (100%) rename lisp/{plugins/triggers/i18n => i18n/qm}/triggers_es.qm (100%) rename lisp/{plugins/triggers/i18n => i18n/qm}/triggers_fr.qm (100%) rename lisp/{plugins/triggers/i18n => i18n/qm}/triggers_it.qm (100%) rename lisp/{plugins/triggers/i18n => i18n/qm}/triggers_sl_SI.qm (100%) rename lisp/{plugins/action_cues/i18n/action_cues_cs.ts => i18n/ts/cs_CZ/action_cues.ts} (100%) rename lisp/{plugins/controller/i18n/controller_cs.ts => i18n/ts/cs_CZ/controller.ts} (100%) rename lisp/{plugins/gst_backend/i18n/gst_backend_cs.ts => i18n/ts/cs_CZ/gst_backend.ts} (100%) rename lisp/i18n/{lisp_cs.ts => ts/cs_CZ/lisp.ts} (100%) rename lisp/{plugins/media_info/i18n/media_info_cs.ts => i18n/ts/cs_CZ/media_info.ts} (100%) rename lisp/{plugins/midi/i18n/midi_cs.ts => i18n/ts/cs_CZ/midi.ts} (100%) rename lisp/{plugins/presets/i18n/presets_cs.ts => i18n/ts/cs_CZ/presets.ts} (100%) rename lisp/{plugins/replay_gain/i18n/replay_gain_cs.ts => i18n/ts/cs_CZ/replay_gain.ts} (100%) rename lisp/{plugins/synchronizer/i18n/synchronizer_cs.ts => i18n/ts/cs_CZ/synchronizer.ts} (100%) rename lisp/{plugins/timecode/i18n/timecode_cs.ts => i18n/ts/cs_CZ/timecode.ts} (100%) rename lisp/{plugins/triggers/i18n/triggers_cs.ts => i18n/ts/cs_CZ/triggers.ts} (100%) rename lisp/{plugins/action_cues/i18n/action_cues_en.ts => i18n/ts/en/action_cues.ts} (100%) rename lisp/{plugins/controller/i18n/controller_en.ts => i18n/ts/en/controller.ts} (100%) rename lisp/{plugins/gst_backend/i18n/gst_backend_en.ts => i18n/ts/en/gst_backend.ts} (100%) rename lisp/i18n/{lisp_en.ts => ts/en/lisp.ts} (100%) rename lisp/{plugins/media_info/i18n/media_info_en.ts => i18n/ts/en/media_info.ts} (100%) rename lisp/{plugins/midi/i18n/midi_en.ts => i18n/ts/en/midi.ts} (100%) rename lisp/{plugins/presets/i18n/presets_en.ts => i18n/ts/en/presets.ts} (100%) rename lisp/{plugins/replay_gain/i18n/replay_gain_en.ts => i18n/ts/en/replay_gain.ts} (100%) rename lisp/{plugins/synchronizer/i18n/synchronizer_en.ts => i18n/ts/en/synchronizer.ts} (100%) rename lisp/{plugins/timecode/i18n/timecode_en.ts => i18n/ts/en/timecode.ts} (100%) rename lisp/{plugins/triggers/i18n/triggers_en.ts => i18n/ts/en/triggers.ts} (100%) rename lisp/{plugins/action_cues/i18n/action_cues_es.ts => i18n/ts/es_ES/action_cues.ts} (100%) rename lisp/{plugins/controller/i18n/controller_es.ts => i18n/ts/es_ES/controller.ts} (100%) rename lisp/{plugins/gst_backend/i18n/gst_backend_es.ts => i18n/ts/es_ES/gst_backend.ts} (100%) rename lisp/i18n/{lisp_es.ts => ts/es_ES/lisp.ts} (100%) rename lisp/{plugins/media_info/i18n/media_info_es.ts => i18n/ts/es_ES/media_info.ts} (100%) rename lisp/{plugins/midi/i18n/midi_es.ts => i18n/ts/es_ES/midi.ts} (100%) rename lisp/{plugins/presets/i18n/presets_es.ts => i18n/ts/es_ES/presets.ts} (100%) rename lisp/{plugins/replay_gain/i18n/replay_gain_es.ts => i18n/ts/es_ES/replay_gain.ts} (100%) rename lisp/{plugins/synchronizer/i18n/synchronizer_es.ts => i18n/ts/es_ES/synchronizer.ts} (100%) rename lisp/{plugins/timecode/i18n/timecode_es.ts => i18n/ts/es_ES/timecode.ts} (100%) rename lisp/{plugins/triggers/i18n/triggers_es.ts => i18n/ts/es_ES/triggers.ts} (100%) rename lisp/{plugins/action_cues/i18n/action_cues_fr.ts => i18n/ts/fr_FR/action_cues.ts} (100%) rename lisp/{plugins/controller/i18n/controller_fr.ts => i18n/ts/fr_FR/controller.ts} (100%) rename lisp/{plugins/gst_backend/i18n/gst_backend_fr.ts => i18n/ts/fr_FR/gst_backend.ts} (100%) rename lisp/i18n/{lisp_fr.ts => ts/fr_FR/lisp.ts} (100%) rename lisp/{plugins/media_info/i18n/media_info_fr.ts => i18n/ts/fr_FR/media_info.ts} (100%) rename lisp/{plugins/midi/i18n/midi_fr.ts => i18n/ts/fr_FR/midi.ts} (100%) rename lisp/{plugins/presets/i18n/presets_fr.ts => i18n/ts/fr_FR/presets.ts} (100%) rename lisp/{plugins/replay_gain/i18n/replay_gain_fr.ts => i18n/ts/fr_FR/replay_gain.ts} (100%) rename lisp/{plugins/synchronizer/i18n/synchronizer_fr.ts => i18n/ts/fr_FR/synchronizer.ts} (100%) rename lisp/{plugins/timecode/i18n/timecode_fr.ts => i18n/ts/fr_FR/timecode.ts} (100%) rename lisp/{plugins/triggers/i18n/triggers_fr.ts => i18n/ts/fr_FR/triggers.ts} (100%) rename lisp/{plugins/action_cues/i18n/action_cues_it.ts => i18n/ts/it_IT/action_cues.ts} (100%) rename lisp/{plugins/controller/i18n/controller_it.ts => i18n/ts/it_IT/controller.ts} (100%) rename lisp/{plugins/gst_backend/i18n/gst_backend_it.ts => i18n/ts/it_IT/gst_backend.ts} (100%) rename lisp/i18n/{lisp_it.ts => ts/it_IT/lisp.ts} (100%) rename lisp/{plugins/media_info/i18n/media_info_it.ts => i18n/ts/it_IT/media_info.ts} (100%) rename lisp/{plugins/midi/i18n/midi_it.ts => i18n/ts/it_IT/midi.ts} (100%) rename lisp/{plugins/presets/i18n/presets_it.ts => i18n/ts/it_IT/presets.ts} (100%) rename lisp/{plugins/replay_gain/i18n/replay_gain_it.ts => i18n/ts/it_IT/replay_gain.ts} (100%) rename lisp/{plugins/synchronizer/i18n/synchronizer_it.ts => i18n/ts/it_IT/synchronizer.ts} (100%) rename lisp/{plugins/timecode/i18n/timecode_it.ts => i18n/ts/it_IT/timecode.ts} (100%) rename lisp/{plugins/triggers/i18n/triggers_it.ts => i18n/ts/it_IT/triggers.ts} (100%) rename lisp/{plugins/action_cues/i18n/action_cues_sl_SI.ts => i18n/ts/sl_SI/action_cues.ts} (100%) rename lisp/{plugins/controller/i18n/controller_sl_SI.ts => i18n/ts/sl_SI/controller.ts} (100%) rename lisp/{plugins/gst_backend/i18n/gst_backend_sl_SI.ts => i18n/ts/sl_SI/gst_backend.ts} (100%) rename lisp/i18n/{lisp_sl_SI.ts => ts/sl_SI/lisp.ts} (100%) rename lisp/{plugins/media_info/i18n/media_info_sl_SI.ts => i18n/ts/sl_SI/media_info.ts} (100%) rename lisp/{plugins/midi/i18n/midi_sl_SI.ts => i18n/ts/sl_SI/midi.ts} (100%) rename lisp/{plugins/presets/i18n/presets_sl_SI.ts => i18n/ts/sl_SI/presets.ts} (100%) rename lisp/{plugins/replay_gain/i18n/replay_gain_sl_SI.ts => i18n/ts/sl_SI/replay_gain.ts} (100%) rename lisp/{plugins/synchronizer/i18n/synchronizer_sl_SI.ts => i18n/ts/sl_SI/synchronizer.ts} (100%) rename lisp/{plugins/timecode/i18n/timecode_sl_SI.ts => i18n/ts/sl_SI/timecode.ts} (100%) rename lisp/{plugins/triggers/i18n/triggers_sl_SI.ts => i18n/ts/sl_SI/triggers.ts} (100%) diff --git a/lisp/__init__.py b/lisp/__init__.py index f69dcbdeb..9ad39759b 100644 --- a/lisp/__init__.py +++ b/lisp/__init__.py @@ -35,7 +35,7 @@ DEFAULT_APP_CONFIG = path.join(APP_DIR, 'default.json') USER_APP_CONFIG = path.join(USER_DIR, 'lisp.json') -I18N_PATH = path.join(APP_DIR, 'i18n') +I18N_PATH = path.join(APP_DIR, 'i18n', 'qm') ICON_THEMES_DIR = path.join(APP_DIR, 'ui', 'icons') ICON_THEME_COMMON = 'lisp' diff --git a/lisp/default.json b/lisp/default.json index 1b4e4f752..e9cd69fe8 100644 --- a/lisp/default.json +++ b/lisp/default.json @@ -34,9 +34,6 @@ "visibleColumns": ["asctime", "name", "message"] } }, - "backend": { - "default": "gst" - }, "layout": { "default": "NoDefault", "stopAllFade": false, diff --git a/lisp/plugins/action_cues/i18n/action_cues_cs.qm b/lisp/i18n/qm/action_cues_cs.qm similarity index 100% rename from lisp/plugins/action_cues/i18n/action_cues_cs.qm rename to lisp/i18n/qm/action_cues_cs.qm diff --git a/lisp/plugins/action_cues/i18n/action_cues_en.qm b/lisp/i18n/qm/action_cues_en.qm similarity index 100% rename from lisp/plugins/action_cues/i18n/action_cues_en.qm rename to lisp/i18n/qm/action_cues_en.qm diff --git a/lisp/plugins/action_cues/i18n/action_cues_es.qm b/lisp/i18n/qm/action_cues_es.qm similarity index 100% rename from lisp/plugins/action_cues/i18n/action_cues_es.qm rename to lisp/i18n/qm/action_cues_es.qm diff --git a/lisp/plugins/action_cues/i18n/action_cues_fr.qm b/lisp/i18n/qm/action_cues_fr.qm similarity index 100% rename from lisp/plugins/action_cues/i18n/action_cues_fr.qm rename to lisp/i18n/qm/action_cues_fr.qm diff --git a/lisp/plugins/action_cues/i18n/action_cues_it.qm b/lisp/i18n/qm/action_cues_it.qm similarity index 100% rename from lisp/plugins/action_cues/i18n/action_cues_it.qm rename to lisp/i18n/qm/action_cues_it.qm diff --git a/lisp/plugins/action_cues/i18n/action_cues_sl_SI.qm b/lisp/i18n/qm/action_cues_sl_SI.qm similarity index 100% rename from lisp/plugins/action_cues/i18n/action_cues_sl_SI.qm rename to lisp/i18n/qm/action_cues_sl_SI.qm diff --git a/lisp/plugins/controller/i18n/controller_cs.qm b/lisp/i18n/qm/controller_cs.qm similarity index 100% rename from lisp/plugins/controller/i18n/controller_cs.qm rename to lisp/i18n/qm/controller_cs.qm diff --git a/lisp/plugins/controller/i18n/controller_en.qm b/lisp/i18n/qm/controller_en.qm similarity index 100% rename from lisp/plugins/controller/i18n/controller_en.qm rename to lisp/i18n/qm/controller_en.qm diff --git a/lisp/plugins/controller/i18n/controller_es.qm b/lisp/i18n/qm/controller_es.qm similarity index 100% rename from lisp/plugins/controller/i18n/controller_es.qm rename to lisp/i18n/qm/controller_es.qm diff --git a/lisp/plugins/controller/i18n/controller_fr.qm b/lisp/i18n/qm/controller_fr.qm similarity index 100% rename from lisp/plugins/controller/i18n/controller_fr.qm rename to lisp/i18n/qm/controller_fr.qm diff --git a/lisp/plugins/controller/i18n/controller_it.qm b/lisp/i18n/qm/controller_it.qm similarity index 100% rename from lisp/plugins/controller/i18n/controller_it.qm rename to lisp/i18n/qm/controller_it.qm diff --git a/lisp/plugins/controller/i18n/controller_sl_SI.qm b/lisp/i18n/qm/controller_sl_SI.qm similarity index 100% rename from lisp/plugins/controller/i18n/controller_sl_SI.qm rename to lisp/i18n/qm/controller_sl_SI.qm diff --git a/lisp/plugins/gst_backend/i18n/gst_backend_cs.qm b/lisp/i18n/qm/gst_backend_cs.qm similarity index 100% rename from lisp/plugins/gst_backend/i18n/gst_backend_cs.qm rename to lisp/i18n/qm/gst_backend_cs.qm diff --git a/lisp/plugins/gst_backend/i18n/gst_backend_en.qm b/lisp/i18n/qm/gst_backend_en.qm similarity index 100% rename from lisp/plugins/gst_backend/i18n/gst_backend_en.qm rename to lisp/i18n/qm/gst_backend_en.qm diff --git a/lisp/plugins/gst_backend/i18n/gst_backend_es.qm b/lisp/i18n/qm/gst_backend_es.qm similarity index 100% rename from lisp/plugins/gst_backend/i18n/gst_backend_es.qm rename to lisp/i18n/qm/gst_backend_es.qm diff --git a/lisp/plugins/gst_backend/i18n/gst_backend_fr.qm b/lisp/i18n/qm/gst_backend_fr.qm similarity index 100% rename from lisp/plugins/gst_backend/i18n/gst_backend_fr.qm rename to lisp/i18n/qm/gst_backend_fr.qm diff --git a/lisp/plugins/gst_backend/i18n/gst_backend_it.qm b/lisp/i18n/qm/gst_backend_it.qm similarity index 100% rename from lisp/plugins/gst_backend/i18n/gst_backend_it.qm rename to lisp/i18n/qm/gst_backend_it.qm diff --git a/lisp/plugins/gst_backend/i18n/gst_backend_sl_SI.qm b/lisp/i18n/qm/gst_backend_sl_SI.qm similarity index 100% rename from lisp/plugins/gst_backend/i18n/gst_backend_sl_SI.qm rename to lisp/i18n/qm/gst_backend_sl_SI.qm diff --git a/lisp/i18n/lisp_cs.qm b/lisp/i18n/qm/lisp_cs.qm similarity index 100% rename from lisp/i18n/lisp_cs.qm rename to lisp/i18n/qm/lisp_cs.qm diff --git a/lisp/i18n/lisp_en.qm b/lisp/i18n/qm/lisp_en.qm similarity index 100% rename from lisp/i18n/lisp_en.qm rename to lisp/i18n/qm/lisp_en.qm diff --git a/lisp/i18n/lisp_es.qm b/lisp/i18n/qm/lisp_es.qm similarity index 100% rename from lisp/i18n/lisp_es.qm rename to lisp/i18n/qm/lisp_es.qm diff --git a/lisp/i18n/lisp_fr.qm b/lisp/i18n/qm/lisp_fr.qm similarity index 100% rename from lisp/i18n/lisp_fr.qm rename to lisp/i18n/qm/lisp_fr.qm diff --git a/lisp/i18n/lisp_it.qm b/lisp/i18n/qm/lisp_it.qm similarity index 100% rename from lisp/i18n/lisp_it.qm rename to lisp/i18n/qm/lisp_it.qm diff --git a/lisp/i18n/lisp_sl_SI.qm b/lisp/i18n/qm/lisp_sl_SI.qm similarity index 100% rename from lisp/i18n/lisp_sl_SI.qm rename to lisp/i18n/qm/lisp_sl_SI.qm diff --git a/lisp/plugins/media_info/i18n/media_info_cs.qm b/lisp/i18n/qm/media_info_cs.qm similarity index 100% rename from lisp/plugins/media_info/i18n/media_info_cs.qm rename to lisp/i18n/qm/media_info_cs.qm diff --git a/lisp/plugins/media_info/i18n/media_info_en.qm b/lisp/i18n/qm/media_info_en.qm similarity index 100% rename from lisp/plugins/media_info/i18n/media_info_en.qm rename to lisp/i18n/qm/media_info_en.qm diff --git a/lisp/plugins/media_info/i18n/media_info_es.qm b/lisp/i18n/qm/media_info_es.qm similarity index 100% rename from lisp/plugins/media_info/i18n/media_info_es.qm rename to lisp/i18n/qm/media_info_es.qm diff --git a/lisp/plugins/media_info/i18n/media_info_fr.qm b/lisp/i18n/qm/media_info_fr.qm similarity index 100% rename from lisp/plugins/media_info/i18n/media_info_fr.qm rename to lisp/i18n/qm/media_info_fr.qm diff --git a/lisp/plugins/media_info/i18n/media_info_it.qm b/lisp/i18n/qm/media_info_it.qm similarity index 100% rename from lisp/plugins/media_info/i18n/media_info_it.qm rename to lisp/i18n/qm/media_info_it.qm diff --git a/lisp/plugins/media_info/i18n/media_info_sl_SI.qm b/lisp/i18n/qm/media_info_sl_SI.qm similarity index 100% rename from lisp/plugins/media_info/i18n/media_info_sl_SI.qm rename to lisp/i18n/qm/media_info_sl_SI.qm diff --git a/lisp/plugins/midi/i18n/midi_cs.qm b/lisp/i18n/qm/midi_cs.qm similarity index 100% rename from lisp/plugins/midi/i18n/midi_cs.qm rename to lisp/i18n/qm/midi_cs.qm diff --git a/lisp/plugins/midi/i18n/midi_en.qm b/lisp/i18n/qm/midi_en.qm similarity index 100% rename from lisp/plugins/midi/i18n/midi_en.qm rename to lisp/i18n/qm/midi_en.qm diff --git a/lisp/plugins/midi/i18n/midi_es.qm b/lisp/i18n/qm/midi_es.qm similarity index 100% rename from lisp/plugins/midi/i18n/midi_es.qm rename to lisp/i18n/qm/midi_es.qm diff --git a/lisp/plugins/midi/i18n/midi_fr.qm b/lisp/i18n/qm/midi_fr.qm similarity index 100% rename from lisp/plugins/midi/i18n/midi_fr.qm rename to lisp/i18n/qm/midi_fr.qm diff --git a/lisp/plugins/midi/i18n/midi_it.qm b/lisp/i18n/qm/midi_it.qm similarity index 100% rename from lisp/plugins/midi/i18n/midi_it.qm rename to lisp/i18n/qm/midi_it.qm diff --git a/lisp/plugins/midi/i18n/midi_sl_SI.qm b/lisp/i18n/qm/midi_sl_SI.qm similarity index 100% rename from lisp/plugins/midi/i18n/midi_sl_SI.qm rename to lisp/i18n/qm/midi_sl_SI.qm diff --git a/lisp/plugins/presets/i18n/presets_cs.qm b/lisp/i18n/qm/presets_cs.qm similarity index 100% rename from lisp/plugins/presets/i18n/presets_cs.qm rename to lisp/i18n/qm/presets_cs.qm diff --git a/lisp/plugins/presets/i18n/presets_en.qm b/lisp/i18n/qm/presets_en.qm similarity index 100% rename from lisp/plugins/presets/i18n/presets_en.qm rename to lisp/i18n/qm/presets_en.qm diff --git a/lisp/plugins/presets/i18n/presets_es.qm b/lisp/i18n/qm/presets_es.qm similarity index 100% rename from lisp/plugins/presets/i18n/presets_es.qm rename to lisp/i18n/qm/presets_es.qm diff --git a/lisp/plugins/presets/i18n/presets_fr.qm b/lisp/i18n/qm/presets_fr.qm similarity index 100% rename from lisp/plugins/presets/i18n/presets_fr.qm rename to lisp/i18n/qm/presets_fr.qm diff --git a/lisp/plugins/presets/i18n/presets_it.qm b/lisp/i18n/qm/presets_it.qm similarity index 100% rename from lisp/plugins/presets/i18n/presets_it.qm rename to lisp/i18n/qm/presets_it.qm diff --git a/lisp/plugins/presets/i18n/presets_sl_SI.qm b/lisp/i18n/qm/presets_sl_SI.qm similarity index 100% rename from lisp/plugins/presets/i18n/presets_sl_SI.qm rename to lisp/i18n/qm/presets_sl_SI.qm diff --git a/lisp/plugins/replay_gain/i18n/replay_gain_cs.qm b/lisp/i18n/qm/replay_gain_cs.qm similarity index 100% rename from lisp/plugins/replay_gain/i18n/replay_gain_cs.qm rename to lisp/i18n/qm/replay_gain_cs.qm diff --git a/lisp/plugins/replay_gain/i18n/replay_gain_en.qm b/lisp/i18n/qm/replay_gain_en.qm similarity index 100% rename from lisp/plugins/replay_gain/i18n/replay_gain_en.qm rename to lisp/i18n/qm/replay_gain_en.qm diff --git a/lisp/plugins/replay_gain/i18n/replay_gain_es.qm b/lisp/i18n/qm/replay_gain_es.qm similarity index 100% rename from lisp/plugins/replay_gain/i18n/replay_gain_es.qm rename to lisp/i18n/qm/replay_gain_es.qm diff --git a/lisp/plugins/replay_gain/i18n/replay_gain_fr.qm b/lisp/i18n/qm/replay_gain_fr.qm similarity index 100% rename from lisp/plugins/replay_gain/i18n/replay_gain_fr.qm rename to lisp/i18n/qm/replay_gain_fr.qm diff --git a/lisp/plugins/replay_gain/i18n/replay_gain_it.qm b/lisp/i18n/qm/replay_gain_it.qm similarity index 100% rename from lisp/plugins/replay_gain/i18n/replay_gain_it.qm rename to lisp/i18n/qm/replay_gain_it.qm diff --git a/lisp/plugins/replay_gain/i18n/replay_gain_sl_SI.qm b/lisp/i18n/qm/replay_gain_sl_SI.qm similarity index 100% rename from lisp/plugins/replay_gain/i18n/replay_gain_sl_SI.qm rename to lisp/i18n/qm/replay_gain_sl_SI.qm diff --git a/lisp/plugins/synchronizer/i18n/synchronizer_cs.qm b/lisp/i18n/qm/synchronizer_cs.qm similarity index 100% rename from lisp/plugins/synchronizer/i18n/synchronizer_cs.qm rename to lisp/i18n/qm/synchronizer_cs.qm diff --git a/lisp/plugins/synchronizer/i18n/synchronizer_en.qm b/lisp/i18n/qm/synchronizer_en.qm similarity index 100% rename from lisp/plugins/synchronizer/i18n/synchronizer_en.qm rename to lisp/i18n/qm/synchronizer_en.qm diff --git a/lisp/plugins/synchronizer/i18n/synchronizer_es.qm b/lisp/i18n/qm/synchronizer_es.qm similarity index 100% rename from lisp/plugins/synchronizer/i18n/synchronizer_es.qm rename to lisp/i18n/qm/synchronizer_es.qm diff --git a/lisp/plugins/synchronizer/i18n/synchronizer_fr.qm b/lisp/i18n/qm/synchronizer_fr.qm similarity index 100% rename from lisp/plugins/synchronizer/i18n/synchronizer_fr.qm rename to lisp/i18n/qm/synchronizer_fr.qm diff --git a/lisp/plugins/synchronizer/i18n/synchronizer_it.qm b/lisp/i18n/qm/synchronizer_it.qm similarity index 100% rename from lisp/plugins/synchronizer/i18n/synchronizer_it.qm rename to lisp/i18n/qm/synchronizer_it.qm diff --git a/lisp/plugins/synchronizer/i18n/synchronizer_sl_SI.qm b/lisp/i18n/qm/synchronizer_sl_SI.qm similarity index 100% rename from lisp/plugins/synchronizer/i18n/synchronizer_sl_SI.qm rename to lisp/i18n/qm/synchronizer_sl_SI.qm diff --git a/lisp/plugins/timecode/i18n/timecode_cs.qm b/lisp/i18n/qm/timecode_cs.qm similarity index 100% rename from lisp/plugins/timecode/i18n/timecode_cs.qm rename to lisp/i18n/qm/timecode_cs.qm diff --git a/lisp/plugins/timecode/i18n/timecode_en.qm b/lisp/i18n/qm/timecode_en.qm similarity index 100% rename from lisp/plugins/timecode/i18n/timecode_en.qm rename to lisp/i18n/qm/timecode_en.qm diff --git a/lisp/plugins/timecode/i18n/timecode_es.qm b/lisp/i18n/qm/timecode_es.qm similarity index 100% rename from lisp/plugins/timecode/i18n/timecode_es.qm rename to lisp/i18n/qm/timecode_es.qm diff --git a/lisp/plugins/timecode/i18n/timecode_fr.qm b/lisp/i18n/qm/timecode_fr.qm similarity index 100% rename from lisp/plugins/timecode/i18n/timecode_fr.qm rename to lisp/i18n/qm/timecode_fr.qm diff --git a/lisp/plugins/timecode/i18n/timecode_it.qm b/lisp/i18n/qm/timecode_it.qm similarity index 100% rename from lisp/plugins/timecode/i18n/timecode_it.qm rename to lisp/i18n/qm/timecode_it.qm diff --git a/lisp/plugins/timecode/i18n/timecode_sl_SI.qm b/lisp/i18n/qm/timecode_sl_SI.qm similarity index 100% rename from lisp/plugins/timecode/i18n/timecode_sl_SI.qm rename to lisp/i18n/qm/timecode_sl_SI.qm diff --git a/lisp/plugins/triggers/i18n/triggers_cs.qm b/lisp/i18n/qm/triggers_cs.qm similarity index 100% rename from lisp/plugins/triggers/i18n/triggers_cs.qm rename to lisp/i18n/qm/triggers_cs.qm diff --git a/lisp/plugins/triggers/i18n/triggers_en.qm b/lisp/i18n/qm/triggers_en.qm similarity index 100% rename from lisp/plugins/triggers/i18n/triggers_en.qm rename to lisp/i18n/qm/triggers_en.qm diff --git a/lisp/plugins/triggers/i18n/triggers_es.qm b/lisp/i18n/qm/triggers_es.qm similarity index 100% rename from lisp/plugins/triggers/i18n/triggers_es.qm rename to lisp/i18n/qm/triggers_es.qm diff --git a/lisp/plugins/triggers/i18n/triggers_fr.qm b/lisp/i18n/qm/triggers_fr.qm similarity index 100% rename from lisp/plugins/triggers/i18n/triggers_fr.qm rename to lisp/i18n/qm/triggers_fr.qm diff --git a/lisp/plugins/triggers/i18n/triggers_it.qm b/lisp/i18n/qm/triggers_it.qm similarity index 100% rename from lisp/plugins/triggers/i18n/triggers_it.qm rename to lisp/i18n/qm/triggers_it.qm diff --git a/lisp/plugins/triggers/i18n/triggers_sl_SI.qm b/lisp/i18n/qm/triggers_sl_SI.qm similarity index 100% rename from lisp/plugins/triggers/i18n/triggers_sl_SI.qm rename to lisp/i18n/qm/triggers_sl_SI.qm diff --git a/lisp/plugins/action_cues/i18n/action_cues_cs.ts b/lisp/i18n/ts/cs_CZ/action_cues.ts similarity index 100% rename from lisp/plugins/action_cues/i18n/action_cues_cs.ts rename to lisp/i18n/ts/cs_CZ/action_cues.ts diff --git a/lisp/plugins/controller/i18n/controller_cs.ts b/lisp/i18n/ts/cs_CZ/controller.ts similarity index 100% rename from lisp/plugins/controller/i18n/controller_cs.ts rename to lisp/i18n/ts/cs_CZ/controller.ts diff --git a/lisp/plugins/gst_backend/i18n/gst_backend_cs.ts b/lisp/i18n/ts/cs_CZ/gst_backend.ts similarity index 100% rename from lisp/plugins/gst_backend/i18n/gst_backend_cs.ts rename to lisp/i18n/ts/cs_CZ/gst_backend.ts diff --git a/lisp/i18n/lisp_cs.ts b/lisp/i18n/ts/cs_CZ/lisp.ts similarity index 100% rename from lisp/i18n/lisp_cs.ts rename to lisp/i18n/ts/cs_CZ/lisp.ts diff --git a/lisp/plugins/media_info/i18n/media_info_cs.ts b/lisp/i18n/ts/cs_CZ/media_info.ts similarity index 100% rename from lisp/plugins/media_info/i18n/media_info_cs.ts rename to lisp/i18n/ts/cs_CZ/media_info.ts diff --git a/lisp/plugins/midi/i18n/midi_cs.ts b/lisp/i18n/ts/cs_CZ/midi.ts similarity index 100% rename from lisp/plugins/midi/i18n/midi_cs.ts rename to lisp/i18n/ts/cs_CZ/midi.ts diff --git a/lisp/plugins/presets/i18n/presets_cs.ts b/lisp/i18n/ts/cs_CZ/presets.ts similarity index 100% rename from lisp/plugins/presets/i18n/presets_cs.ts rename to lisp/i18n/ts/cs_CZ/presets.ts diff --git a/lisp/plugins/replay_gain/i18n/replay_gain_cs.ts b/lisp/i18n/ts/cs_CZ/replay_gain.ts similarity index 100% rename from lisp/plugins/replay_gain/i18n/replay_gain_cs.ts rename to lisp/i18n/ts/cs_CZ/replay_gain.ts diff --git a/lisp/plugins/synchronizer/i18n/synchronizer_cs.ts b/lisp/i18n/ts/cs_CZ/synchronizer.ts similarity index 100% rename from lisp/plugins/synchronizer/i18n/synchronizer_cs.ts rename to lisp/i18n/ts/cs_CZ/synchronizer.ts diff --git a/lisp/plugins/timecode/i18n/timecode_cs.ts b/lisp/i18n/ts/cs_CZ/timecode.ts similarity index 100% rename from lisp/plugins/timecode/i18n/timecode_cs.ts rename to lisp/i18n/ts/cs_CZ/timecode.ts diff --git a/lisp/plugins/triggers/i18n/triggers_cs.ts b/lisp/i18n/ts/cs_CZ/triggers.ts similarity index 100% rename from lisp/plugins/triggers/i18n/triggers_cs.ts rename to lisp/i18n/ts/cs_CZ/triggers.ts diff --git a/lisp/plugins/action_cues/i18n/action_cues_en.ts b/lisp/i18n/ts/en/action_cues.ts similarity index 100% rename from lisp/plugins/action_cues/i18n/action_cues_en.ts rename to lisp/i18n/ts/en/action_cues.ts diff --git a/lisp/plugins/controller/i18n/controller_en.ts b/lisp/i18n/ts/en/controller.ts similarity index 100% rename from lisp/plugins/controller/i18n/controller_en.ts rename to lisp/i18n/ts/en/controller.ts diff --git a/lisp/plugins/gst_backend/i18n/gst_backend_en.ts b/lisp/i18n/ts/en/gst_backend.ts similarity index 100% rename from lisp/plugins/gst_backend/i18n/gst_backend_en.ts rename to lisp/i18n/ts/en/gst_backend.ts diff --git a/lisp/i18n/lisp_en.ts b/lisp/i18n/ts/en/lisp.ts similarity index 100% rename from lisp/i18n/lisp_en.ts rename to lisp/i18n/ts/en/lisp.ts diff --git a/lisp/plugins/media_info/i18n/media_info_en.ts b/lisp/i18n/ts/en/media_info.ts similarity index 100% rename from lisp/plugins/media_info/i18n/media_info_en.ts rename to lisp/i18n/ts/en/media_info.ts diff --git a/lisp/plugins/midi/i18n/midi_en.ts b/lisp/i18n/ts/en/midi.ts similarity index 100% rename from lisp/plugins/midi/i18n/midi_en.ts rename to lisp/i18n/ts/en/midi.ts diff --git a/lisp/plugins/presets/i18n/presets_en.ts b/lisp/i18n/ts/en/presets.ts similarity index 100% rename from lisp/plugins/presets/i18n/presets_en.ts rename to lisp/i18n/ts/en/presets.ts diff --git a/lisp/plugins/replay_gain/i18n/replay_gain_en.ts b/lisp/i18n/ts/en/replay_gain.ts similarity index 100% rename from lisp/plugins/replay_gain/i18n/replay_gain_en.ts rename to lisp/i18n/ts/en/replay_gain.ts diff --git a/lisp/plugins/synchronizer/i18n/synchronizer_en.ts b/lisp/i18n/ts/en/synchronizer.ts similarity index 100% rename from lisp/plugins/synchronizer/i18n/synchronizer_en.ts rename to lisp/i18n/ts/en/synchronizer.ts diff --git a/lisp/plugins/timecode/i18n/timecode_en.ts b/lisp/i18n/ts/en/timecode.ts similarity index 100% rename from lisp/plugins/timecode/i18n/timecode_en.ts rename to lisp/i18n/ts/en/timecode.ts diff --git a/lisp/plugins/triggers/i18n/triggers_en.ts b/lisp/i18n/ts/en/triggers.ts similarity index 100% rename from lisp/plugins/triggers/i18n/triggers_en.ts rename to lisp/i18n/ts/en/triggers.ts diff --git a/lisp/plugins/action_cues/i18n/action_cues_es.ts b/lisp/i18n/ts/es_ES/action_cues.ts similarity index 100% rename from lisp/plugins/action_cues/i18n/action_cues_es.ts rename to lisp/i18n/ts/es_ES/action_cues.ts diff --git a/lisp/plugins/controller/i18n/controller_es.ts b/lisp/i18n/ts/es_ES/controller.ts similarity index 100% rename from lisp/plugins/controller/i18n/controller_es.ts rename to lisp/i18n/ts/es_ES/controller.ts diff --git a/lisp/plugins/gst_backend/i18n/gst_backend_es.ts b/lisp/i18n/ts/es_ES/gst_backend.ts similarity index 100% rename from lisp/plugins/gst_backend/i18n/gst_backend_es.ts rename to lisp/i18n/ts/es_ES/gst_backend.ts diff --git a/lisp/i18n/lisp_es.ts b/lisp/i18n/ts/es_ES/lisp.ts similarity index 100% rename from lisp/i18n/lisp_es.ts rename to lisp/i18n/ts/es_ES/lisp.ts diff --git a/lisp/plugins/media_info/i18n/media_info_es.ts b/lisp/i18n/ts/es_ES/media_info.ts similarity index 100% rename from lisp/plugins/media_info/i18n/media_info_es.ts rename to lisp/i18n/ts/es_ES/media_info.ts diff --git a/lisp/plugins/midi/i18n/midi_es.ts b/lisp/i18n/ts/es_ES/midi.ts similarity index 100% rename from lisp/plugins/midi/i18n/midi_es.ts rename to lisp/i18n/ts/es_ES/midi.ts diff --git a/lisp/plugins/presets/i18n/presets_es.ts b/lisp/i18n/ts/es_ES/presets.ts similarity index 100% rename from lisp/plugins/presets/i18n/presets_es.ts rename to lisp/i18n/ts/es_ES/presets.ts diff --git a/lisp/plugins/replay_gain/i18n/replay_gain_es.ts b/lisp/i18n/ts/es_ES/replay_gain.ts similarity index 100% rename from lisp/plugins/replay_gain/i18n/replay_gain_es.ts rename to lisp/i18n/ts/es_ES/replay_gain.ts diff --git a/lisp/plugins/synchronizer/i18n/synchronizer_es.ts b/lisp/i18n/ts/es_ES/synchronizer.ts similarity index 100% rename from lisp/plugins/synchronizer/i18n/synchronizer_es.ts rename to lisp/i18n/ts/es_ES/synchronizer.ts diff --git a/lisp/plugins/timecode/i18n/timecode_es.ts b/lisp/i18n/ts/es_ES/timecode.ts similarity index 100% rename from lisp/plugins/timecode/i18n/timecode_es.ts rename to lisp/i18n/ts/es_ES/timecode.ts diff --git a/lisp/plugins/triggers/i18n/triggers_es.ts b/lisp/i18n/ts/es_ES/triggers.ts similarity index 100% rename from lisp/plugins/triggers/i18n/triggers_es.ts rename to lisp/i18n/ts/es_ES/triggers.ts diff --git a/lisp/plugins/action_cues/i18n/action_cues_fr.ts b/lisp/i18n/ts/fr_FR/action_cues.ts similarity index 100% rename from lisp/plugins/action_cues/i18n/action_cues_fr.ts rename to lisp/i18n/ts/fr_FR/action_cues.ts diff --git a/lisp/plugins/controller/i18n/controller_fr.ts b/lisp/i18n/ts/fr_FR/controller.ts similarity index 100% rename from lisp/plugins/controller/i18n/controller_fr.ts rename to lisp/i18n/ts/fr_FR/controller.ts diff --git a/lisp/plugins/gst_backend/i18n/gst_backend_fr.ts b/lisp/i18n/ts/fr_FR/gst_backend.ts similarity index 100% rename from lisp/plugins/gst_backend/i18n/gst_backend_fr.ts rename to lisp/i18n/ts/fr_FR/gst_backend.ts diff --git a/lisp/i18n/lisp_fr.ts b/lisp/i18n/ts/fr_FR/lisp.ts similarity index 100% rename from lisp/i18n/lisp_fr.ts rename to lisp/i18n/ts/fr_FR/lisp.ts diff --git a/lisp/plugins/media_info/i18n/media_info_fr.ts b/lisp/i18n/ts/fr_FR/media_info.ts similarity index 100% rename from lisp/plugins/media_info/i18n/media_info_fr.ts rename to lisp/i18n/ts/fr_FR/media_info.ts diff --git a/lisp/plugins/midi/i18n/midi_fr.ts b/lisp/i18n/ts/fr_FR/midi.ts similarity index 100% rename from lisp/plugins/midi/i18n/midi_fr.ts rename to lisp/i18n/ts/fr_FR/midi.ts diff --git a/lisp/plugins/presets/i18n/presets_fr.ts b/lisp/i18n/ts/fr_FR/presets.ts similarity index 100% rename from lisp/plugins/presets/i18n/presets_fr.ts rename to lisp/i18n/ts/fr_FR/presets.ts diff --git a/lisp/plugins/replay_gain/i18n/replay_gain_fr.ts b/lisp/i18n/ts/fr_FR/replay_gain.ts similarity index 100% rename from lisp/plugins/replay_gain/i18n/replay_gain_fr.ts rename to lisp/i18n/ts/fr_FR/replay_gain.ts diff --git a/lisp/plugins/synchronizer/i18n/synchronizer_fr.ts b/lisp/i18n/ts/fr_FR/synchronizer.ts similarity index 100% rename from lisp/plugins/synchronizer/i18n/synchronizer_fr.ts rename to lisp/i18n/ts/fr_FR/synchronizer.ts diff --git a/lisp/plugins/timecode/i18n/timecode_fr.ts b/lisp/i18n/ts/fr_FR/timecode.ts similarity index 100% rename from lisp/plugins/timecode/i18n/timecode_fr.ts rename to lisp/i18n/ts/fr_FR/timecode.ts diff --git a/lisp/plugins/triggers/i18n/triggers_fr.ts b/lisp/i18n/ts/fr_FR/triggers.ts similarity index 100% rename from lisp/plugins/triggers/i18n/triggers_fr.ts rename to lisp/i18n/ts/fr_FR/triggers.ts diff --git a/lisp/plugins/action_cues/i18n/action_cues_it.ts b/lisp/i18n/ts/it_IT/action_cues.ts similarity index 100% rename from lisp/plugins/action_cues/i18n/action_cues_it.ts rename to lisp/i18n/ts/it_IT/action_cues.ts diff --git a/lisp/plugins/controller/i18n/controller_it.ts b/lisp/i18n/ts/it_IT/controller.ts similarity index 100% rename from lisp/plugins/controller/i18n/controller_it.ts rename to lisp/i18n/ts/it_IT/controller.ts diff --git a/lisp/plugins/gst_backend/i18n/gst_backend_it.ts b/lisp/i18n/ts/it_IT/gst_backend.ts similarity index 100% rename from lisp/plugins/gst_backend/i18n/gst_backend_it.ts rename to lisp/i18n/ts/it_IT/gst_backend.ts diff --git a/lisp/i18n/lisp_it.ts b/lisp/i18n/ts/it_IT/lisp.ts similarity index 100% rename from lisp/i18n/lisp_it.ts rename to lisp/i18n/ts/it_IT/lisp.ts diff --git a/lisp/plugins/media_info/i18n/media_info_it.ts b/lisp/i18n/ts/it_IT/media_info.ts similarity index 100% rename from lisp/plugins/media_info/i18n/media_info_it.ts rename to lisp/i18n/ts/it_IT/media_info.ts diff --git a/lisp/plugins/midi/i18n/midi_it.ts b/lisp/i18n/ts/it_IT/midi.ts similarity index 100% rename from lisp/plugins/midi/i18n/midi_it.ts rename to lisp/i18n/ts/it_IT/midi.ts diff --git a/lisp/plugins/presets/i18n/presets_it.ts b/lisp/i18n/ts/it_IT/presets.ts similarity index 100% rename from lisp/plugins/presets/i18n/presets_it.ts rename to lisp/i18n/ts/it_IT/presets.ts diff --git a/lisp/plugins/replay_gain/i18n/replay_gain_it.ts b/lisp/i18n/ts/it_IT/replay_gain.ts similarity index 100% rename from lisp/plugins/replay_gain/i18n/replay_gain_it.ts rename to lisp/i18n/ts/it_IT/replay_gain.ts diff --git a/lisp/plugins/synchronizer/i18n/synchronizer_it.ts b/lisp/i18n/ts/it_IT/synchronizer.ts similarity index 100% rename from lisp/plugins/synchronizer/i18n/synchronizer_it.ts rename to lisp/i18n/ts/it_IT/synchronizer.ts diff --git a/lisp/plugins/timecode/i18n/timecode_it.ts b/lisp/i18n/ts/it_IT/timecode.ts similarity index 100% rename from lisp/plugins/timecode/i18n/timecode_it.ts rename to lisp/i18n/ts/it_IT/timecode.ts diff --git a/lisp/plugins/triggers/i18n/triggers_it.ts b/lisp/i18n/ts/it_IT/triggers.ts similarity index 100% rename from lisp/plugins/triggers/i18n/triggers_it.ts rename to lisp/i18n/ts/it_IT/triggers.ts diff --git a/lisp/plugins/action_cues/i18n/action_cues_sl_SI.ts b/lisp/i18n/ts/sl_SI/action_cues.ts similarity index 100% rename from lisp/plugins/action_cues/i18n/action_cues_sl_SI.ts rename to lisp/i18n/ts/sl_SI/action_cues.ts diff --git a/lisp/plugins/controller/i18n/controller_sl_SI.ts b/lisp/i18n/ts/sl_SI/controller.ts similarity index 100% rename from lisp/plugins/controller/i18n/controller_sl_SI.ts rename to lisp/i18n/ts/sl_SI/controller.ts diff --git a/lisp/plugins/gst_backend/i18n/gst_backend_sl_SI.ts b/lisp/i18n/ts/sl_SI/gst_backend.ts similarity index 100% rename from lisp/plugins/gst_backend/i18n/gst_backend_sl_SI.ts rename to lisp/i18n/ts/sl_SI/gst_backend.ts diff --git a/lisp/i18n/lisp_sl_SI.ts b/lisp/i18n/ts/sl_SI/lisp.ts similarity index 100% rename from lisp/i18n/lisp_sl_SI.ts rename to lisp/i18n/ts/sl_SI/lisp.ts diff --git a/lisp/plugins/media_info/i18n/media_info_sl_SI.ts b/lisp/i18n/ts/sl_SI/media_info.ts similarity index 100% rename from lisp/plugins/media_info/i18n/media_info_sl_SI.ts rename to lisp/i18n/ts/sl_SI/media_info.ts diff --git a/lisp/plugins/midi/i18n/midi_sl_SI.ts b/lisp/i18n/ts/sl_SI/midi.ts similarity index 100% rename from lisp/plugins/midi/i18n/midi_sl_SI.ts rename to lisp/i18n/ts/sl_SI/midi.ts diff --git a/lisp/plugins/presets/i18n/presets_sl_SI.ts b/lisp/i18n/ts/sl_SI/presets.ts similarity index 100% rename from lisp/plugins/presets/i18n/presets_sl_SI.ts rename to lisp/i18n/ts/sl_SI/presets.ts diff --git a/lisp/plugins/replay_gain/i18n/replay_gain_sl_SI.ts b/lisp/i18n/ts/sl_SI/replay_gain.ts similarity index 100% rename from lisp/plugins/replay_gain/i18n/replay_gain_sl_SI.ts rename to lisp/i18n/ts/sl_SI/replay_gain.ts diff --git a/lisp/plugins/synchronizer/i18n/synchronizer_sl_SI.ts b/lisp/i18n/ts/sl_SI/synchronizer.ts similarity index 100% rename from lisp/plugins/synchronizer/i18n/synchronizer_sl_SI.ts rename to lisp/i18n/ts/sl_SI/synchronizer.ts diff --git a/lisp/plugins/timecode/i18n/timecode_sl_SI.ts b/lisp/i18n/ts/sl_SI/timecode.ts similarity index 100% rename from lisp/plugins/timecode/i18n/timecode_sl_SI.ts rename to lisp/i18n/ts/sl_SI/timecode.ts diff --git a/lisp/plugins/triggers/i18n/triggers_sl_SI.ts b/lisp/i18n/ts/sl_SI/triggers.ts similarity index 100% rename from lisp/plugins/triggers/i18n/triggers_sl_SI.ts rename to lisp/i18n/ts/sl_SI/triggers.ts diff --git a/lisp/ui/about.py b/lisp/ui/about.py index 0eca45c7d..d9596b774 100644 --- a/lisp/ui/about.py +++ b/lisp/ui/about.py @@ -44,10 +44,12 @@ class About(QDialog):

''' - DESCRIPTION = QT_TRANSLATE_NOOP('AboutDialog', - 'Linux Show Player is a cue-player designed for stage productions.') + DESCRIPTION = QT_TRANSLATE_NOOP( + 'AboutDialog', + 'Linux Show Player is a cue-player designed for stage productions.' + ) WEB_SITE = 'http://linux-show-player.sourceforge.net' - USER_GROUP = 'http://groups.google.com/group/linux-show-player---users' + DISCUSSION = 'https://gitter.im/linux-show-player/linux-show-player' SOURCE_CODE = 'https://github.com/FrancescoCeruti/linux-show-player' CONTRIBUTORS = OrderedDict({ @@ -57,14 +59,14 @@ class About(QDialog): QT_TRANSLATE_NOOP('About', 'Contributors'): [ ('Yinameah', 'https://github.com/Yinameah'), ('nodiscc', 'https://github.com/nodiscc'), - ('Thomas Achtner', 'info@offtools.de') + ('Thomas Achtner', 'https://github.com/offtools') ], QT_TRANSLATE_NOOP('About', 'Translators'): [ - ('aroomthedoomed', 'https://github.com/aroomthedoomed'), - ('fri', 'https://www.transifex.com/user/profile/fri'), - ('Luis García-Tornel', 'tornel@gmail.com'), - ('miharix', 'https://github.com/miharix'), - ('Olivier Humbert', 'https://github.com/trebmuh') + ('fri', 'https://www.transifex.com/user/profile/fri', 'Czech'), + ('Olivier Humbert', 'https://github.com/trebmuh', 'French'), + ('aroomthedoomed', 'https://github.com/aroomthedoomed', 'French'), + ('Luis García-Tornel', 'tornel@gmail.com', 'Spanish'), + ('miharix', 'https://github.com/miharix', 'Slovenian'), ], }) @@ -86,9 +88,10 @@ def __init__(self, *args, **kwargs): self.shortInfo = QLabel(self) self.shortInfo.setAlignment(Qt.AlignCenter) - self.shortInfo.setText('

Linux Show Player {0}

' - 'Copyright © Francesco Ceruti' - .format(str(lisp.__version__))) + self.shortInfo.setText( + '

Linux Show Player {0}

Copyright © Francesco Ceruti' + .format(str(lisp.__version__)) + ) self.layout().addWidget(self.shortInfo, 0, 1) self.layout().addWidget(QWidget(), 1, 0, 1, 2) @@ -106,7 +109,7 @@ def __init__(self, *args, **kwargs): {6}
'''.format( translate('AboutDialog', self.DESCRIPTION), self.WEB_SITE, translate('AboutDialog', 'Web site'), - self.USER_GROUP, translate('AboutDialog', 'Users group'), + self.DISCUSSION, translate('AboutDialog', 'Discussion'), self.SOURCE_CODE, translate('AboutDialog', 'Source code')) ) self.tabWidget.addTab(self.info, translate('AboutDialog', 'Info')) @@ -141,8 +144,9 @@ def __init__(self, *args, **kwargs): def __contributors(self): text = '' for section, people in self.CONTRIBUTORS.items(): - text += '{0}:
'.format(translate('About', - section)) + text += '{0}:
'.format( + translate('About', section) + ) for person in people: text += person[0] @@ -151,6 +155,10 @@ def __contributors(self): person[1], person[1][person[1].index('://')+3:]) elif person[1]: text += ' - {0}'.format(person[1]) + + if len(person) >= 3: + text += ' ({})'.format(person[2]) + text += '
' text += '
' From ea82417cfaff97176ecf67e4f79681d354674575 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 20 Jun 2018 20:20:13 +0200 Subject: [PATCH 121/333] Update Crowdin configuration file --- crowdin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crowdin.yml b/crowdin.yml index fc6e50d32..0ac4134f9 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,3 +1,3 @@ files: - source: /lisp/i18n/ts/en/*.ts - translation: /lisp/i18n/%locale_with_underscore%/%original_file_name% + translation: /lisp/i18n/ts/%locale_with_underscore%/%original_file_name% From c85fffeb5b09c5b05bff9d02d614269f18287bc5 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 20 Jun 2018 20:29:41 +0200 Subject: [PATCH 122/333] i18n stuffs --- crowdin.yml => .crowdin.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename crowdin.yml => .crowdin.yml (100%) diff --git a/crowdin.yml b/.crowdin.yml similarity index 100% rename from crowdin.yml rename to .crowdin.yml From dde40cea013dd43ca9f0439e3a09624bba6ce124 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 21 Jun 2018 11:36:06 +0200 Subject: [PATCH 123/333] update "i18n" script and base "en" ts files --- i18n_update.py | 32 +- lisp/i18n/ts/en/action_cues.ts | 190 +++++++--- lisp/i18n/ts/en/cart_layout.ts | 175 ++++++++++ lisp/i18n/ts/en/controller.ts | 136 +++++++- lisp/i18n/ts/en/gst_backend.ts | 149 ++++---- lisp/i18n/ts/en/lisp.ts | 594 ++++++++++++++++++++++---------- lisp/i18n/ts/en/list_layout.ts | 188 ++++++++++ lisp/i18n/ts/en/media_info.ts | 20 +- lisp/i18n/ts/en/midi.ts | 13 +- lisp/i18n/ts/en/network.ts | 46 +++ lisp/i18n/ts/en/osc.ts | 34 ++ lisp/i18n/ts/en/presets.ts | 67 ++-- lisp/i18n/ts/en/rename_cues.ts | 85 +++++ lisp/i18n/ts/en/replay_gain.ts | 23 +- lisp/i18n/ts/en/synchronizer.ts | 35 +- lisp/i18n/ts/en/timecode.ts | 59 +++- lisp/i18n/ts/en/triggers.ts | 25 +- 17 files changed, 1452 insertions(+), 419 deletions(-) create mode 100644 lisp/i18n/ts/en/cart_layout.ts create mode 100644 lisp/i18n/ts/en/list_layout.ts create mode 100644 lisp/i18n/ts/en/network.ts create mode 100644 lisp/i18n/ts/en/osc.ts create mode 100644 lisp/i18n/ts/en/rename_cues.ts diff --git a/i18n_update.py b/i18n_update.py index d582cab48..ded2dae2e 100755 --- a/i18n_update.py +++ b/i18n_update.py @@ -47,9 +47,10 @@ def existing_locales(): - for entry in scandir('lisp/i18n'): - if entry.name.startswith('lisp_') and entry.name.endswith('.ts'): - yield entry.name[5:-3] + for entry in scandir('lisp/i18n/ts/'): + if entry.is_dir(): + yield entry.name + # Locales of which generate translations files LOCALES = args.locales @@ -77,9 +78,12 @@ def search_files(root, exclude=(), extensions=()): def create_pro_file(root, exclude=(), extensions=('py',)): base_name = os.path.basename(os.path.normpath(root)) + back = '../' * (len(root.split('/')) - 1) + translations = 'TRANSLATIONS = ' - for local in LOCALES: - translations += os.path.join('i18n', base_name + '_' + local + '.ts ') + for locale in LOCALES: + translations += os.path.join( + back, 'i18n/ts/', locale, base_name + '.ts ') files = 'SOURCES = ' + ' '.join(search_files(root, exclude, extensions)) @@ -92,17 +96,21 @@ def create_pro_file(root, exclude=(), extensions=('py',)): def generate_for_submodules(path, qm=False): modules = [entry.path for entry in scandir(path) if entry.is_dir()] for module in modules: - if os.path.exists(os.path.join(module, 'i18n')): + if '__pycache__' not in module: create_pro_file(module) p_file = os.path.join(module, os.path.basename(module) + '.pro') if qm: - subprocess.run(['lrelease', p_file], - stdout=sys.stdout, - stderr=sys.stderr) + subprocess.run( + ['lrelease', p_file], + stdout=sys.stdout, + stderr=sys.stderr + ) else: - subprocess.run(PYLUPDATE_CMD + [p_file], - stdout=sys.stdout, - stderr=sys.stderr) + subprocess.run( + PYLUPDATE_CMD + [p_file], + stdout=sys.stdout, + stderr=sys.stderr + ) print('>>> UPDATE TRANSLATIONS FOR APPLICATION') diff --git a/lisp/i18n/ts/en/action_cues.ts b/lisp/i18n/ts/en/action_cues.ts index 16492695b..1ec356d88 100644 --- a/lisp/i18n/ts/en/action_cues.ts +++ b/lisp/i18n/ts/en/action_cues.ts @@ -1,23 +1,24 @@ - + + CollectionCue - + Add Add - + Remove Remove - + Cue Cue - + Action Action @@ -27,137 +28,236 @@ Process ended with an error status. - Process ended with an error status. + Process ended with an error status. Exit code: - Exit code: + Exit code: - + Command Command - + Command to execute, as in a shell Command to execute, as in a shell - + Discard command output Discard command output - + Ignore command errors Ignore command errors - + Kill instead of terminate Kill instead of terminate + + + Command cue ended with an error status. Exit code: {} + + + + + Cue Name + + + OSC Settings + + CueName - + Command Cue Command Cue - + MIDI Cue MIDI Cue - + Volume Control Volume Control - + Seek Cue Seek Cue - + Collection Cue Collection Cue - + Stop-All Stop-All - + Index Action Index Action + + + OSC Cue + + IndexActionCue - + Index Index - + Use a relative index Use a relative index - + Target index Target index - + Action Action + + + No suggestion + + + + + Suggested cue name + + MIDICue - + MIDI Message MIDI Message - + Message type Message type + + Osc Cue + + + Type + + + + + Argument + + + + + FadeTo + + + + + Fade + Fade + + + + OscCue + + + Error during cue execution. + + + + + OSC Message + + + + + Add + Add + + + + Remove + Remove + + + + Test + + + + + OSC Path: (example: "/path/to/something") + + + + + Fade + Fade + + + + Time (sec) + + + + + Curve + + + SeekCue - + Cue Cue - + Click to select Click to select - + Not selected Not selected - + Seek Seek - + Time to reach Time to reach @@ -165,37 +265,37 @@ SettingsPageName - + Command Command - + MIDI Settings MIDI Settings - + Volume Settings Volume Settings - + Seek Settings Seek Settings - + Edit Collection Edit Collection - + Action Settings Action Settings - + Stop Settings Stop Settings @@ -203,7 +303,7 @@ StopAll - + Stop Action Stop Action @@ -213,32 +313,32 @@ Error during cue execution - Error during cue execution + Error during cue execution - + Cue Cue - + Click to select Click to select - + Not selected Not selected - + Volume to reach Volume to reach - + Fade Fade - \ No newline at end of file + diff --git a/lisp/i18n/ts/en/cart_layout.ts b/lisp/i18n/ts/en/cart_layout.ts new file mode 100644 index 000000000..ea0c937ac --- /dev/null +++ b/lisp/i18n/ts/en/cart_layout.ts @@ -0,0 +1,175 @@ + + + + CartLayout + + + Default behaviors + + + + + Countdown mode + + + + + Show seek-bars + + + + + Show dB-meters + + + + + Show accurate time + + + + + Show volume + + + + + Automatically add new page + + + + + Grid size + + + + + Number of columns + + + + + Number of rows + + + + + Play + + + + + Pause + + + + + Stop + + + + + Reset volume + + + + + Add page + + + + + Add pages + + + + + Remove current page + + + + + Number of Pages: + + + + + Page {number} + + + + + Warning + + + + + Every cue in the page will be lost. + + + + + Are you sure to continue? + + + + + LayoutDescription + + + Organize cues in grid like pages + + + + + LayoutDetails + + + Click a cue to run it + + + + + SHIFT + Click to edit a cue + + + + + CTRL + Click to select a cue + + + + + To copy cues drag them while pressing CTRL + + + + + To move cues drag them while pressing SHIFT + + + + + ListLayout + + + Edit cue + + + + + Edit selected cues + + + + + Remove cue + + + + + Remove selected cues + + + + diff --git a/lisp/i18n/ts/en/controller.ts b/lisp/i18n/ts/en/controller.ts index 58df8911c..a5a4ff5cf 100644 --- a/lisp/i18n/ts/en/controller.ts +++ b/lisp/i18n/ts/en/controller.ts @@ -1,18 +1,27 @@ - + + + + Controller + + + Cannot load controller protocol: "{}" + + + ControllerKeySettings - + Key Key - + Action Action - + Shortcuts Shortcuts @@ -20,80 +29,169 @@ ControllerMidiSettings - + MIDI MIDI - + Type Type - + Channel Channel - + Note Note - + Action Action - + Filter "note on" Filter "note on" - + Filter "note off" Filter "note off" - + Capture Capture - + Listening MIDI messages ... Listening MIDI messages ... + + ControllerOscSettings + + + OSC Message + + + + + OSC Path: (example: "/path/to/something") + + + + + OSC + + + + + Path + + + + + Types + + + + + Arguments + + + + + Actions + + + + + OSC Capture + + + + + Add + Add + + + + Remove + Remove + + + + Capture + Capture + + ControllerSettings - + Add Add - + Remove Remove + + Osc Cue + + + Type + Type + + + + Argument + + + + + OscCue + + + Add + Add + + + + Remove + Remove + + SettingsPageName - + Cue Control Cue Control - + MIDI Controls MIDI Controls - + Keyboard Shortcuts Keyboard Shortcuts + + + OSC Controls + + - \ No newline at end of file + diff --git a/lisp/i18n/ts/en/gst_backend.ts b/lisp/i18n/ts/en/gst_backend.ts index b6cd1ace1..afcdc40f3 100644 --- a/lisp/i18n/ts/en/gst_backend.ts +++ b/lisp/i18n/ts/en/gst_backend.ts @@ -1,13 +1,14 @@ - + + AlsaSinkSettings - + ALSA device ALSA device - + ALSA devices, as defined in an asound configuration file ALSA devices, as defined in an asound configuration file @@ -15,47 +16,47 @@ AudioDynamicSettings - + Compressor Compressor - + Expander Expander - + Soft Knee Soft Knee - + Hard Knee Hard Knee - + Compressor/Expander Compressor/Expander - + Type Type - + Curve Shape Curve Shape - + Ratio Ratio - + Threshold (dB) Threshold (dB) @@ -63,22 +64,22 @@ AudioPanSettings - + Audio Pan Audio Pan - + Center Center - + Left Left - + Right Right @@ -86,22 +87,22 @@ DbMeterSettings - + DbMeter settings DbMeter settings - + Time between levels (ms) Time between levels (ms) - + Peak ttl (ms) Peak ttl (ms) - + Peak falloff (dB/sec) Peak falloff (dB/sec) @@ -109,15 +110,28 @@ Equalizer10Settings - + 10 Bands Equalizer (IIR) 10 Bands Equalizer (IIR) + + GstBackend + + + Audio cue (from file) + + + + + Select media files + + + GstMediaSettings - + Change Pipeline Change Pipeline @@ -125,7 +139,7 @@ GstPipelineEdit - + Edit Pipeline Edit Pipeline @@ -133,7 +147,7 @@ GstSettings - + Pipeline Pipeline @@ -141,32 +155,32 @@ JackSinkSettings - + Connections Connections - + Edit connections Edit connections - + Output ports Output ports - + Input ports Input ports - + Connect Connect - + Disconnect Disconnect @@ -174,77 +188,77 @@ MediaElementName - + Compressor/Expander Compressor/Expander - + Audio Pan Audio Pan - + PulseAudio Out PulseAudio Out - + Volume Volume - + dB Meter dB Meter - + System Input System Input - + ALSA Out ALSA Out - + JACK Out JACK Out - + Custom Element Custom Element - + System Out System Out - + Pitch Pitch - + URI Input URI Input - + 10 Bands Equalizer 10 Bands Equalizer - + Speed Speed - + Preset Input Preset Input @@ -252,12 +266,12 @@ PitchSettings - + Pitch Pitch - + {0:+} semitones {0:+} semitones @@ -265,7 +279,7 @@ PresetSrcSettings - + Presets Presets @@ -275,18 +289,23 @@ GStreamer settings - GStreamer settings + GStreamer settings - + Media Settings Media Settings + + + GStreamer + + SpeedSettings - + Speed Speed @@ -294,42 +313,42 @@ UriInputSettings - + Source Source - + Find File Find File - + Buffering Buffering - + Use Buffering Use Buffering - + Attempt download on network streams Attempt download on network streams - + Buffer size (-1 default value) Buffer size (-1 default value) - + Choose file Choose file - + All files All files @@ -337,12 +356,12 @@ UserElementSettings - + User defined elements User defined elements - + Only for advanced user! Only for advanced user! @@ -350,19 +369,19 @@ VolumeSettings - + Volume Volume - + Normalized volume Normalized volume - + Reset Reset - \ No newline at end of file + diff --git a/lisp/i18n/ts/en/lisp.ts b/lisp/i18n/ts/en/lisp.ts index 4e9093945..68d66ead0 100644 --- a/lisp/i18n/ts/en/lisp.ts +++ b/lisp/i18n/ts/en/lisp.ts @@ -1,23 +1,24 @@ - + + About - + Authors Authors - + Contributors Contributors - + Translators Translators - + About Linux Show Player About Linux Show Player @@ -25,65 +26,108 @@ AboutDialog - + Linux Show Player is a cue-player designed for stage productions. Linux Show Player is a cue-player designed for stage productions. - + Web site Web site Users group - Users group + Users group - + Source code Source code - + Info Info - + License License - + Contributors Contributors + + + Discussion + + + + + Actions + + + Undo: {} + + + + + Redo: {} + + + + + AppConfiguration + + + LiSP preferences + LiSP preferences + AppGeneralSettings Startup layout - Startup layout + Startup layout Use startup dialog - Use startup dialog + Use startup dialog Application theme - Application theme + Application theme - - - AppConfiguration - - LiSP preferences - LiSP preferences + + Default layout + + + + + Enable startup layout selector + + + + + Application themes + + + + + UI theme + + + + + Icons theme + @@ -91,176 +135,211 @@ Add page - Add page + Add page Add pages - Add pages + Add pages Remove current page - Remove current page + Remove current page Countdown mode - Countdown mode + Countdown mode Show seek-bars - Show seek-bars + Show seek-bars Show dB-meters - Show dB-meters + Show dB-meters Show volume - Show volume + Show volume Show accurate time - Show accurate time + Show accurate time Edit cue - Edit cue + Edit cue Remove - Remove + Remove Select - Select + Select Play - Play + Play Pause - Pause + Pause Stop - Stop + Stop Reset volume - Reset volume + Reset volume Number of Pages: - Number of Pages: + Number of Pages: Warning - Warning + Warning Every cue in the page will be lost. - Every cue in the page will be lost. + Every cue in the page will be lost. Are you sure to continue? - Are you sure to continue? + Are you sure to continue? Page - Page + Page Default behaviors - Default behaviors + Default behaviors Automatically add new page - Automatically add new page + Automatically add new page Grid size - Grid size + Grid size Number of columns - Number of columns + Number of columns Number of rows - Number of rows + Number of rows CueAction - + Default Default - + Pause Pause - + Start Start - + Stop Stop FadeInStart - FadeInStart + FadeInStart FadeOutStop - FadeOutStop + FadeOutStop FadeOutPause - FadeOutPause + FadeOutPause + + + + Faded Start + + + + + Faded Resume + + + + + Faded Pause + + + + + Faded Stop + + + + + Faded Interrupt + + + + + Resume + + + + + Do Nothing + CueActionLog - + Cue settings changed: "{}" Cue settings changed: "{}" - + Cues settings changed. Cues settings changed. @@ -268,42 +347,42 @@ CueAppearanceSettings - + The appearance depends on the layout The appearance depends on the layout - + Cue name Cue name - + NoName NoName - + Description/Note Description/Note - + Set Font Size Set Font Size - + Color Color - + Select background color Select background color - + Select font color Select font color @@ -311,98 +390,126 @@ CueName - + Media Cue Media Cue + + CueNextAction + + + Do Nothing + + + + + Auto Follow + + + + + Auto Next + + + CueSettings - + Pre wait Pre wait - + Wait before cue execution Wait before cue execution - + Post wait Post wait - + Wait after cue execution Wait after cue execution - + Next action Next action Interrupt Fade - Interrupt Fade + Interrupt Fade Fade Action - Fade Action + Fade Action Behaviours - Behaviours + Behaviours Pre/Post Wait - Pre/Post Wait + Pre/Post Wait Fade In/Out - Fade In/Out + Fade In/Out - + Start action Start action - + Default action to start the cue Default action to start the cue - + Stop action Stop action - + Default action to stop the cue Default action to stop the cue + + + Interrupt fade + + + + + Fade actions + + Fade - + Linear Linear - + Quadratic Quadratic - + Quadratic2 Quadratic2 @@ -410,12 +517,12 @@ FadeEdit - + Duration (sec) Duration (sec) - + Curve Curve @@ -423,12 +530,12 @@ FadeSettings - + Fade In Fade In - + Fade Out Fade Out @@ -438,12 +545,12 @@ Organize cues in grid like pages - Organize cues in grid like pages + Organize cues in grid like pages Organize the cues in a list - Organize the cues in a list + Organize the cues in a list @@ -451,58 +558,58 @@ Click a cue to run it - Click a cue to run it + Click a cue to run it SHIFT + Click to edit a cue - SHIFT + Click to edit a cue + SHIFT + Click to edit a cue CTRL + Click to select a cue - CTRL + Click to select a cue + CTRL + Click to select a cue SHIFT + Space or Double-Click to edit a cue - SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL To copy cues drag them while pressing SHIFT - To copy cues drag them while pressing SHIFT + To copy cues drag them while pressing SHIFT CTRL + Left Click to select cues - CTRL + Left Click to select cues + CTRL + Left Click to select cues To move cues drag them - To move cues drag them + To move cues drag them LayoutSelect - + Layout selection Layout selection - + Select layout Select layout - + Open file Open file @@ -512,165 +619,175 @@ Show playing cues - Show playing cues + Show playing cues Show dB-meters - Show dB-meters + Show dB-meters Show seek-bars - Show seek-bars + Show seek-bars Show accurate time - Show accurate time + Show accurate time Auto-select next cue - Auto-select next cue + Auto-select next cue Edit cue - Edit cue + Edit cue Remove - Remove + Remove Select - Select + Select Stop all - Stop all + Stop all Pause all - Pause all + Pause all Restart all - Restart all + Restart all Stop - Stop + Stop Restart - Restart + Restart Default behaviors - Default behaviors + Default behaviors At list end: - At list end: + At list end: Go key: - Go key: + Go key: Interrupt all - Interrupt all + Interrupt all Fade-Out all - Fade-Out all + Fade-Out all Fade-In all - Fade-In all + Fade-In all Use fade - Use fade + Use fade Stop Cue - Stop Cue + Stop Cue Pause Cue - Pause Cue + Pause Cue Restart Cue - Restart Cue + Restart Cue Interrupt Cue - Interrupt Cue + Interrupt Cue - + Stop All Stop All - + Pause All Pause All Restart All - Restart All + Restart All - + Interrupt All Interrupt All + + + Use fade (global actions) + + + + + Resume All + + ListLayoutHeader Cue - Cue + Cue Pre wait - Pre wait + Pre wait Action - Action + Action Post wait - Post wait + Post wait @@ -678,12 +795,12 @@ Cue name - Cue name + Cue name Cue description - Cue description + Cue description @@ -691,178 +808,273 @@ Information - Information + Information - + Debug Debug - + Warning Warning - + Error Error Details: - Details: + Details: + + + + Dismiss all + + + + + Show details + + + + + Linux Show Player - Log Viewer + + + + + Info + Info + + + + Critical + + + + + Time + + + + + Milliseconds + + + + + Logger name + + + + + Level + + + + + Message + + + + + Function + + + + + Path name + + + + + File name + + + + + Line no. + + + + + Module + + + + + Process ID + + + + + Process name + + + + + Thread ID + + + + + Thread name + MainWindow - + &File &File - + New session New session - + Open Open - + Save session Save session - + Preferences Preferences - + Save as Save as - + Full Screen Full Screen - + Exit Exit - + &Edit &Edit - + Undo Undo - + Redo Redo - + Select all Select all - + Select all media cues Select all media cues - + Deselect all Deselect all - + CTRL+SHIFT+A CTRL+SHIFT+A - + Invert selection Invert selection - + CTRL+I CTRL+I - + Edit selected Edit selected - + CTRL+SHIFT+E CTRL+SHIFT+E - + &Layout &Layout - + &Tools &Tools - + Edit selection Edit selection - + &About &About - + About About - + About Qt About Qt Undone: - Undone: + Undone: Redone: - Redone: + Redone: - + Close session Close session - + The current session is not saved. The current session is not saved. - + Discard the changes? Discard the changes? @@ -872,43 +1084,43 @@ Audio cue (from file) - Audio cue (from file) + Audio cue (from file) Select media files - Select media files + Select media files MediaCueSettings - + Start time Start time - + Stop position of the media Stop position of the media - + Stop time Stop time - + Start position of the media Start position of the media - + Loop Loop - + Repetition after first play (-1 = infinite) Repetition after first play (-1 = infinite) @@ -916,7 +1128,7 @@ QColorButton - + Right click to reset Right click to reset @@ -924,24 +1136,44 @@ SettingsPageName - + Appearance Appearance - + General General - + Cue Cue - + Cue Settings Cue Settings + + + Plugins + + + + + Behaviours + Behaviours + + + + Pre/Post Wait + Pre/Post Wait + + + + Fade In/Out + Fade In/Out + - \ No newline at end of file + diff --git a/lisp/i18n/ts/en/list_layout.ts b/lisp/i18n/ts/en/list_layout.ts new file mode 100644 index 000000000..6cd3c2f17 --- /dev/null +++ b/lisp/i18n/ts/en/list_layout.ts @@ -0,0 +1,188 @@ + + + + LayoutDescription + + + Organize the cues in a list + + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + + + + + To copy cues drag them while pressing CTRL + + + + + To move cues drag them + + + + + ListLayout + + + Default behaviors + + + + + Show playing cues + + + + + Show dB-meters + + + + + Show accurate time + + + + + Show seek-bars + + + + + Auto-select next cue + + + + + Enable selection mode + + + + + Go key: + + + + + Use fade (buttons) + + + + + Stop Cue + + + + + Pause Cue + + + + + Resume Cue + + + + + Interrupt Cue + + + + + Edit cue + + + + + Edit selected cues + + + + + Remove cue + + + + + Remove selected cues + + + + + Selection mode + + + + + Pause all + + + + + Stop all + + + + + Interrupt all + + + + + Resume all + + + + + Fade-Out all + + + + + Fade-In all + + + + + ListLayoutHeader + + + Cue + + + + + Pre wait + + + + + Action + + + + + Post wait + + + + + ListLayoutInfoPanel + + + Cue name + + + + + Cue description + + + + diff --git a/lisp/i18n/ts/en/media_info.ts b/lisp/i18n/ts/en/media_info.ts index 3d60f1e4e..8d5287183 100644 --- a/lisp/i18n/ts/en/media_info.ts +++ b/lisp/i18n/ts/en/media_info.ts @@ -1,30 +1,36 @@ - + + MediaInfo - + Media Info Media Info Error - Error + Error - + No info to display No info to display - + Info Info - + Value Value + + + Warning + + - \ No newline at end of file + diff --git a/lisp/i18n/ts/en/midi.ts b/lisp/i18n/ts/en/midi.ts index f0041db35..4e2aa34f6 100644 --- a/lisp/i18n/ts/en/midi.ts +++ b/lisp/i18n/ts/en/midi.ts @@ -1,18 +1,19 @@ - + + MIDISettings - + MIDI default devices MIDI default devices - + Input Input - + Output Output @@ -20,9 +21,9 @@ SettingsPageName - + MIDI settings MIDI settings - \ No newline at end of file + diff --git a/lisp/i18n/ts/en/network.ts b/lisp/i18n/ts/en/network.ts new file mode 100644 index 000000000..bd35353ec --- /dev/null +++ b/lisp/i18n/ts/en/network.ts @@ -0,0 +1,46 @@ + + + + NetworkDiscovery + + + Host discovery + + + + + Manage hosts + + + + + Discover hosts + + + + + Manually add a host + + + + + Remove selected host + + + + + Remove all host + + + + + Address + + + + + Host IP + + + + diff --git a/lisp/i18n/ts/en/osc.ts b/lisp/i18n/ts/en/osc.ts new file mode 100644 index 000000000..d3b902b5c --- /dev/null +++ b/lisp/i18n/ts/en/osc.ts @@ -0,0 +1,34 @@ + + + + OscSettings + + + OSC Settings + + + + + Input Port: + + + + + Output Port: + + + + + Hostname: + + + + + SettingsPageName + + + OSC settings + + + + diff --git a/lisp/i18n/ts/en/presets.ts b/lisp/i18n/ts/en/presets.ts index b08af9286..59e613472 100644 --- a/lisp/i18n/ts/en/presets.ts +++ b/lisp/i18n/ts/en/presets.ts @@ -1,13 +1,14 @@ - + + Preset - + Create Cue Create Cue - + Load on selected Cues Load on selected Cues @@ -15,124 +16,134 @@ Presets - + Presets Presets Load preset - Load preset + Load preset - + Save as preset Save as preset - + Cannot scan presets Cannot scan presets - + Error while deleting preset "{}" Error while deleting preset "{}" - + Cannot load preset "{}" Cannot load preset "{}" - + Cannot save preset "{}" Cannot save preset "{}" - + Cannot rename preset "{}" Cannot rename preset "{}" - + Select Preset Select Preset Preset already exists, overwrite? - Preset already exists, overwrite? + Preset already exists, overwrite? - + Preset name Preset name - + Add Add - + Rename Rename - + Edit Edit - + Remove Remove - + Export selected Export selected - + Import Import - + Warning Warning - + The same name is already used! The same name is already used! - + Cannot create a cue from this preset: {} Cannot create a cue from this preset: {} - + Cannot export correctly. Cannot export correctly. - + Some presets already exists, overwrite? Some presets already exists, overwrite? - + Cannot import correctly. Cannot import correctly. - + Cue type Cue type + + + Load on cue + + + + + Load on selected cues + + - \ No newline at end of file + diff --git a/lisp/i18n/ts/en/rename_cues.ts b/lisp/i18n/ts/en/rename_cues.ts new file mode 100644 index 000000000..15fa7321c --- /dev/null +++ b/lisp/i18n/ts/en/rename_cues.ts @@ -0,0 +1,85 @@ + + + + RenameCues + + + Rename Cues + + + + + Rename cues + + + + + Current + + + + + Preview + + + + + Capitalize + + + + + Lowercase + + + + + Uppercase + + + + + Remove Numbers + + + + + Add numbering + + + + + Reset + + + + + Rename all cue. () in regex below usable with $0, $1 ... + + + + + Type your regex here: + + + + + Regex help + + + + + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple : +^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation + + + + diff --git a/lisp/i18n/ts/en/replay_gain.ts b/lisp/i18n/ts/en/replay_gain.ts index fef5ec42d..400a0fe36 100644 --- a/lisp/i18n/ts/en/replay_gain.ts +++ b/lisp/i18n/ts/en/replay_gain.ts @@ -1,50 +1,51 @@ - + + ReplayGain - + ReplayGain / Normalization ReplayGain / Normalization - + Calculate Calculate - + Reset all Reset all - + Reset selected Reset selected - + Threads number Threads number - + Apply only to selected media Apply only to selected media - + ReplayGain to (dB SPL) ReplayGain to (dB SPL) - + Normalize to (dB) Normalize to (dB) - + Processing files ... Processing files ... - \ No newline at end of file + diff --git a/lisp/i18n/ts/en/synchronizer.ts b/lisp/i18n/ts/en/synchronizer.ts index c90606e33..098a08f3e 100644 --- a/lisp/i18n/ts/en/synchronizer.ts +++ b/lisp/i18n/ts/en/synchronizer.ts @@ -1,83 +1,84 @@ - + + SyncPeerDialog Manage connected peers - Manage connected peers + Manage connected peers Discover peers - Discover peers + Discover peers Manually add a peer - Manually add a peer + Manually add a peer Remove selected peer - Remove selected peer + Remove selected peer Remove all peers - Remove all peers + Remove all peers Address - Address + Address Peer IP - Peer IP + Peer IP Error - Error + Error Already connected - Already connected + Already connected Cannot add peer - Cannot add peer + Cannot add peer Synchronizer - + Synchronization Synchronization - + Manage connected peers Manage connected peers - + Show your IP Show your IP - + Your IP is: Your IP is: Discovering peers ... - Discovering peers ... + Discovering peers ... - \ No newline at end of file + diff --git a/lisp/i18n/ts/en/timecode.ts b/lisp/i18n/ts/en/timecode.ts index 410adc09c..d4050d4c2 100644 --- a/lisp/i18n/ts/en/timecode.ts +++ b/lisp/i18n/ts/en/timecode.ts @@ -1,13 +1,14 @@ - + + SettingsPageName - + Timecode Settings Timecode Settings - + Timecode Timecode @@ -15,14 +16,25 @@ Timecode - + Cannot send timecode. Cannot send timecode. OLA has stopped. - OLA has stopped. + OLA has stopped. + + + + Cannot load timecode protocol: "{}" + + + + + Cannot send timecode. +OLA has stopped. + @@ -30,52 +42,67 @@ OLA Timecode Settings - OLA Timecode Settings + OLA Timecode Settings Enable Plugin - Enable Plugin + Enable Plugin High-Resolution Timecode - High-Resolution Timecode + High-Resolution Timecode - + Timecode Format: Timecode Format: OLA status - OLA status + OLA status OLA is not running - start the OLA daemon. - OLA is not running - start the OLA daemon. + OLA is not running - start the OLA daemon. - + Replace HOURS by a static track number Replace HOURS by a static track number Enable ArtNet Timecode - Enable ArtNet Timecode + Enable ArtNet Timecode - + Track number Track number To send ArtNet Timecode you need to setup a running OLA session! - To send ArtNet Timecode you need to setup a running OLA session! + To send ArtNet Timecode you need to setup a running OLA session! + + + + Enable Timecode + + + + + Timecode Settings + Timecode Settings + + + + Timecode Protocol: + - \ No newline at end of file + diff --git a/lisp/i18n/ts/en/triggers.ts b/lisp/i18n/ts/en/triggers.ts index 51d73f484..5f9ccdca5 100644 --- a/lisp/i18n/ts/en/triggers.ts +++ b/lisp/i18n/ts/en/triggers.ts @@ -1,23 +1,24 @@ - + + CueTriggers - + Started Started - + Paused Paused - + Stopped Stopped - + Ended Ended @@ -25,7 +26,7 @@ SettingsPageName - + Triggers Triggers @@ -33,29 +34,29 @@ TriggersSettings - + Add Add - + Remove Remove - + Trigger Trigger - + Cue Cue - + Action Action - \ No newline at end of file + From 9dfd4a8476c68a14a669a17e94ca5cf4eba5d2d5 Mon Sep 17 00:00:00 2001 From: s0600204 <2053873+s0600204@users.noreply.github.com> Date: Fri, 29 Jun 2018 09:30:14 +0100 Subject: [PATCH 124/333] Layout settings fixes (#124) Fix: resolve name conflict preventing layout creation when a default was specified Fix: remove "autoAddPage" option from the UI --- lisp/application.py | 6 +++--- lisp/plugins/cart_layout/__init__.py | 2 +- lisp/plugins/cart_layout/default.json | 4 ++-- lisp/plugins/cart_layout/settings.py | 7 ------- lisp/ui/settings/app_pages/app_general.py | 4 ++-- 5 files changed, 8 insertions(+), 15 deletions(-) diff --git a/lisp/application.py b/lisp/application.py index 6566f591c..ecada2bac 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -106,10 +106,10 @@ def start(self, session_file=''): if exists(session_file): self._load_from_file(session_file) else: - layout = self.conf.get('layout.default', 'nodefault') + layout_name = self.conf.get('layout.default', 'nodefault') - if layout.lower() != 'nodefault': - self._new_session(layout.get_layout(layout)) + if layout_name.lower() != 'nodefault': + self._new_session(layout.get_layout(layout_name)) else: self._new_session_dialog() diff --git a/lisp/plugins/cart_layout/__init__.py b/lisp/plugins/cart_layout/__init__.py index 9be16fdaa..aeafe7c98 100644 --- a/lisp/plugins/cart_layout/__init__.py +++ b/lisp/plugins/cart_layout/__init__.py @@ -7,7 +7,7 @@ class CartLayout(Plugin): Name = 'Cart Layout' - Description = 'Provide a layout that organize cues in grid-like pages' + Description = 'Provide a layout that organizes cues in grid-like pages' Authors = ('Francesco Ceruti', ) def __init__(self, app): diff --git a/lisp/plugins/cart_layout/default.json b/lisp/plugins/cart_layout/default.json index a8a77eeba..d60b8bad0 100644 --- a/lisp/plugins/cart_layout/default.json +++ b/lisp/plugins/cart_layout/default.json @@ -1,5 +1,5 @@ { - "_version_": "1", + "_version_": "1.1", "_enabled_": true, "countdownMode": true, "grid": { @@ -12,4 +12,4 @@ "dBMeters": false, "volumeControls": false } -} \ No newline at end of file +} diff --git a/lisp/plugins/cart_layout/settings.py b/lisp/plugins/cart_layout/settings.py index 71eb8e7c3..8e7ac82e9 100644 --- a/lisp/plugins/cart_layout/settings.py +++ b/lisp/plugins/cart_layout/settings.py @@ -53,9 +53,6 @@ def __init__(self, config, **kwargs): self.showVolume = QCheckBox(self.behaviorsGroup) self.behaviorsGroup.layout().addWidget(self.showVolume) - self.autoAddPage = QCheckBox(self.behaviorsGroup) - self.behaviorsGroup.layout().addWidget(self.autoAddPage) - self.gridSizeGroup = QGroupBox(self) self.gridSizeGroup.setLayout(QGridLayout()) self.layout().addWidget(self.gridSizeGroup) @@ -87,7 +84,6 @@ def __init__(self, config, **kwargs): self.showAccurate.setChecked(config['show.accurateTime']) self.showVolume.setChecked(config['show.volumeControls']) self.countdownMode.setChecked(config['countdownMode']) - self.autoAddPage.setChecked(config['autoAddPage']) def retranslateUi(self): self.behaviorsGroup.setTitle( @@ -97,8 +93,6 @@ def retranslateUi(self): self.showDbMeters.setText(translate('CartLayout', 'Show dB-meters')) self.showAccurate.setText(translate('CartLayout', 'Show accurate time')) self.showVolume.setText(translate('CartLayout', 'Show volume')) - self.autoAddPage.setText( - translate('CartLayout', 'Automatically add new page')) self.gridSizeGroup.setTitle(translate('CartLayout', 'Grid size')) self.columnsLabel.setText(translate('CartLayout', 'Number of columns')) self.rowsLabel.setText(translate('CartLayout', 'Number of rows')) @@ -111,6 +105,5 @@ def applySettings(self): self.config['show.accurateTime'] = self.showAccurate.isChecked() self.config['show.volumeControls'] = self.showVolume.isChecked() self.config['countdownMode'] = self.countdownMode.isChecked() - self.config['autoAddPage'] = self.autoAddPage.isChecked() self.config.write() diff --git a/lisp/ui/settings/app_pages/app_general.py b/lisp/ui/settings/app_pages/app_general.py index 2dd0db6be..b7391d8e4 100644 --- a/lisp/ui/settings/app_pages/app_general.py +++ b/lisp/ui/settings/app_pages/app_general.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2012-2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -102,7 +102,7 @@ def loadConfiguration(self): if layout_name.lower() == 'nodefault': self.startupDialogCheck.setChecked(True) else: - self.layoutCombo.setCurrentText(layout_name) + self.layoutCombo.setCurrentIndex(self.layoutCombo.findData(layout_name)) self.themeCombo.setCurrentText(self.config.get('theme.theme', '')) self.iconsCombo.setCurrentText(self.config.get('theme.icons', '')) From ac3403ea2d879874d5258e62a6cdb0214ce2ab9c Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 3 Aug 2018 12:00:58 +0200 Subject: [PATCH 125/333] Support for python 3.7 Fix: renamed "async" decorator to accommodate new python 3.7 keyword --- Pipfile.lock | 140 ++++++++++----------- lisp/core/decorators.py | 3 +- lisp/core/signal.py | 4 +- lisp/cues/cue.py | 10 +- lisp/cues/media_cue.py | 8 +- lisp/plugins/action_cues/command_cue.py | 4 +- lisp/plugins/action_cues/osc_cue.py | 4 +- lisp/plugins/action_cues/volume_control.py | 4 +- lisp/plugins/controller/protocols/osc.py | 2 +- 9 files changed, 87 insertions(+), 92 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 108464cc0..85d1b3b8d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -27,9 +27,12 @@ "sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef", "sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50", "sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f", + "sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30", "sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93", "sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257", + "sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b", "sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3", + "sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e", "sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc", "sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04", "sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6", @@ -38,11 +41,13 @@ "sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b", "sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd", "sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95", + "sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5", "sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e", "sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6", "sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca", "sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31", "sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1", + "sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2", "sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085", "sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801", "sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4", @@ -70,10 +75,10 @@ }, "idna": { "hashes": [ - "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", - "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4" + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" ], - "version": "==2.6" + "version": "==2.7" }, "jack-client": { "hashes": [ @@ -93,9 +98,9 @@ }, "pycairo": { "hashes": [ - "sha256:cdd4d1d357325dec3a21720b85d273408ef83da5f15c184f2eff3212ff236b9f" + "sha256:0f0a35ec923d87bc495f6753b1e540fd046d95db56a35250c44089fbce03b698" ], - "version": "==1.17.0" + "version": "==1.17.1" }, "pycparser": { "hashes": [ @@ -105,10 +110,10 @@ }, "pygobject": { "hashes": [ - "sha256:f704f4be3b4ae3cae70acf82ac64a8c7c44d3acad2d33e9849056ac317345f5e" + "sha256:250fb669b6ac64eba034cc4404fcbcc993717b1f77c29dff29f8c9202da20d55" ], "index": "pypi", - "version": "==3.28.2" + "version": "==3.28.3" }, "pyliblo": { "hashes": [ @@ -119,13 +124,30 @@ }, "pyqt5": { "hashes": [ - "sha256:1e652910bd1ffd23a3a48c510ecad23a57a853ed26b782cd54b16658e6f271ac", - "sha256:4db7113f464c733a99fcb66c4c093a47cf7204ad3f8b3bda502efcc0839ac14b", - "sha256:9c17ab3974c1fc7bbb04cc1c9dae780522c0ebc158613f3025fccae82227b5f7", - "sha256:f6035baa009acf45e5f460cf88f73580ad5dc0e72330029acd99e477f20a5d61" + "sha256:700b8bb0357bf0ac312bce283449de733f5773dfc77083664be188c8e964c007", + "sha256:76d52f3627fac8bfdbc4857ce52a615cd879abd79890cde347682ff9b4b245a2", + "sha256:7d0f7c0aed9c3ef70d5856e99f30ebcfe25a58300158dd46ee544cbe1c5b53db", + "sha256:d5dc2faf0aeacd0e8b69af1dc9f1276a64020193148356bb319bdfae22b78f88" ], "index": "pypi", - "version": "==5.10.1" + "version": "==5.11.2" + }, + "pyqt5-sip": { + "hashes": [ + "sha256:3bcd8efae7798ce41aa7c3a052bd5ce1849f437530b8a717bae39197e780f505", + "sha256:4a3c5767d6c238d8c62d252ac59312fac8b2264a1e8a5670081d7f3545893005", + "sha256:67481d70fb0c7fb83e77b9025e15d0e78c7647c228eef934bd20ba716845a519", + "sha256:7b2e563e4e56adee00101a29913fdcc49cc714f6c4f7eb35449f493c3a88fc45", + "sha256:92a4950cba7ad7b7f67c09bdf80170ac225b38844b3a10f1271b02bace2ffc64", + "sha256:9309c10f9e648521cfe03b62f4658dad2314f81886062cb30e0ad31b337e14b0", + "sha256:9f524e60fa6113b50c48fbd869b2aef19833f3fe278097b1e7403e8f4dd5392c", + "sha256:a10f59ad65b34e183853e1387b68901f473a2041f7398fac87c4e445ab149830", + "sha256:abc2b2df469b4efb01d9dba4b804cbf0312f109ed74752dc3a37394a77d55b1f", + "sha256:c09c17009a2dd2a6317a14d3cea9b2300fdb2206cf9bc4bae0870d1919897935", + "sha256:c30c162e1430fd5a02207f1bd478e170c61d89fcca11ac6d8babb73cb33a86a8", + "sha256:f00ceceef75a2140fda737bd30847ac69b7d92fbd32b6ea7b387017e72176bd8" + ], + "version": "==4.19.12" }, "python-mimeparse": { "hashes": [ @@ -153,28 +175,11 @@ }, "requests": { "hashes": [ - "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", - "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" + "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", + "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" ], "index": "pypi", - "version": "==2.18.4" - }, - "sip": { - "hashes": [ - "sha256:09f9a4e6c28afd0bafedb26ffba43375b97fe7207bd1a0d3513f79b7d168b331", - "sha256:105edaaa1c8aa486662226360bd3999b4b89dd56de3e314d82b83ed0587d8783", - "sha256:1bb10aac55bd5ab0e2ee74b3047aa2016cfa7932077c73f602a6f6541af8cd51", - "sha256:265ddf69235dd70571b7d4da20849303b436192e875ce7226be7144ca702a45c", - "sha256:52074f7cb5488e8b75b52f34ec2230bc75d22986c7fe5cd3f2d266c23f3349a7", - "sha256:5ff887a33839de8fc77d7f69aed0259b67a384dc91a1dc7588e328b0b980bde2", - "sha256:74da4ddd20c5b35c19cda753ce1e8e1f71616931391caeac2de7a1715945c679", - "sha256:7d69e9cf4f8253a3c0dfc5ba6bb9ac8087b8239851f22998e98cb35cfe497b68", - "sha256:97bb93ee0ef01ba90f57be2b606e08002660affd5bc380776dd8b0fcaa9e093a", - "sha256:cf98150a99e43fda7ae22abe655b6f202e491d6291486548daa56cb15a2fcf85", - "sha256:d9023422127b94d11c1a84bfa94933e959c484f2c79553c1ef23c69fe00d25f8", - "sha256:e72955e12f4fccf27aa421be383453d697b8a44bde2cc26b08d876fd492d0174" - ], - "version": "==4.19.8" + "version": "==2.19.1" }, "six": { "hashes": [ @@ -185,63 +190,54 @@ }, "sortedcontainers": { "hashes": [ - "sha256:566cf5f8dbada3aed99737a19d98f03d15d76bf2a6c27e4fb0f4a718a99be761", - "sha256:fa96e9920a37bde76bfdcaca919a125c1d2e581af1137e25de54ee0da7835282" + "sha256:607294c6e291a270948420f7ffa1fb3ed47384a4c08db6d1e9c92d08a6981982", + "sha256:ef38b128302ee8f65d81e31c9d8fbf10d81df4d6d06c9c0b66f01d33747525bb" ], "index": "pypi", - "version": "==1.5.10" + "version": "==2.0.4" }, "urllib3": { "hashes": [ - "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", - "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" + "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", + "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" ], - "version": "==1.22" + "markers": "python_version != '3.0.*' and python_version != '3.3.*' and python_version >= '2.6' and python_version != '3.2.*' and python_version < '4' and python_version != '3.1.*'", + "version": "==1.23" } }, "develop": { "cython": { "hashes": [ - "sha256:03db8c1b8120039f72493b95494a595be13b01b6860cfc93e2a651a001847b3b", - "sha256:0d2ccb812d73e67557fd16e7aa7bc5bac18933c1dfe306133cd0680ccab89f33", - "sha256:24f8ea864de733f5a447896cbeec2cac212247e33272539670b9f466f43f23db", - "sha256:30a8fd029eb932a7b5a74e158316d1d069ccb67a8607aa7b6c4ed19fab7fbd4a", - "sha256:37e680901e6a4b97ab67717f9b43fc58542cd10a77431efd2d8801d21d5a37d4", - "sha256:4984e097bc9da37862d97c1f66dacf2c80fadaea488d96ba0b5ea9d84dbc7521", - "sha256:4cfda677227af41e4502e088ee9875e71922238a207d0c40785a0fb09c703c21", - "sha256:4ec60a4086a175a81b9258f810440a6dd2671aa4b419d8248546d85a7de6a93f", - "sha256:51c7d48ea4cba532d11a6d128ebbc15373013f816e5d1c3a3946650b582a30b8", - "sha256:634e2f10fc8d026c633cffacb45cd8f4582149fa68e1428124e762dbc566e68a", - "sha256:67e0359709c8addc3ecb19e1dec6d84d67647e3906da618b953001f6d4480275", - "sha256:6a93d4ba0461edc7a359241f4ebbaa8f9bc9490b3540a8dd0460bef8c2c706db", - "sha256:6ba89d56c3ee45716378cda4f0490c3abe1edf79dce8b997f31608b14748a52b", - "sha256:6ca5436d470584ba6fd399a802c9d0bcf76cf1edb0123725a4de2f0048f9fa07", - "sha256:7656895cdd59d56dd4ed326d1ee9ede727020d4a5d8778a05af2d8e25af4b13d", - "sha256:85f7432776870d65639fed00f951a3c05ef1e534bc72a73cd1200d79b9a7d7d0", - "sha256:96dd674e72281d3feed74fd5adcf0514ba02884f123cdf4fb78567e7be6b1694", - "sha256:97bf06a89bcf9e8d7633cde89274d42b3b661dc974b58fca066fad762e46b4d8", - "sha256:9a465e7296a4629139be5d2015577f2ae5e08196eb7dc4c407beea130f362dc3", - "sha256:9a60355edca1cc9006be086e2633e190542aad2bf9e46948792a48b3ae28ed97", - "sha256:9eab3696f2cb88167db109d737c787fb9dd34ca414bd1e0c424e307956e02c94", - "sha256:c3ae7d40ebceb0d944dfeeceaf1fbf17e528f5327d97b008a8623ddddd1ecee3", - "sha256:c623d19fcc60ea27882f20cf484218926ddf6f978b958dae1070600a1974f809", - "sha256:c719a6e86d7c737afcc9729994f76b284d1c512099ee803eff11c2a9e6e33a42", - "sha256:cf17af0433218a1e33dc6f3069dd9e7cd0c80fe505972c3acd548e25f67973fd", - "sha256:daf96e0d232605e979995795f62ffd24c5c6ecea4526e4cbb86d80f01da954b2", - "sha256:db40de7d03842d3c4625028a74189ade52b27f8efaeb0d2ca06474f57e0813b2", - "sha256:deea1ef59445568dd7738fa3913aea7747e4927ff4ae3c10737844b8a5dd3e22", - "sha256:e05d28b5ce1ee5939d83e50344980659688ecaed65c5e10214d817ecf5d1fe6a", - "sha256:f5f6694ce668eb7a9b59550bfe4265258809c9b0665c206b26d697df2eef2a8b" + "sha256:022592d419fc754509d0e0461eb2958dbaa45fb60d51c8a61778c58994edbe36", + "sha256:13c73e2ffa93a615851e03fad97591954d143b5b62361b9adef81f46a31cd8ef", + "sha256:13eab5a2835a84ff62db343035603044c908d2b3b6eec09d67fdf9970acf7ac9", + "sha256:183b35a48f58862c4ec1e821f07bb7b1156c8c8559c85c32ae086f28947474eb", + "sha256:2f526b0887128bf20ab2acc905a975f62b5a04ab2f63ecbe5a30fc28285d0e0c", + "sha256:32de8637f5e6c5a76667bc7c8fc644bd9314dc19af36db8ce30a0b92ada0f642", + "sha256:4172c183ef4fb2ace6a29cdf7fc9200c5a471a7f775ff691975b774bd9ed3ad2", + "sha256:553956ec06ecbd731ef0c538eb28a5b46bedea7ab89b18237ff28b4b99d65eee", + "sha256:660eeb6870687fd3eda91e00ba4e72220545c254c8c4d967fd0c910f4fbb8cbc", + "sha256:693a8619ef066ece055ed065a15cf440f9d3ebd1bca60e87ea19144833756433", + "sha256:827d3a91b7a7c31ce69e5974496fd9a8ba28eb498b988affb66d0d30de11d934", + "sha256:9ec27681c5b1b457aacb1cbda5db04aa28b76da2af6e1e1fd15f233eafe6a0b0", + "sha256:ae4784f040a3313c8bd00c8d04934b7ade63dc59692d8f00a5235be8ed72a445", + "sha256:b64575241f64f6ec005a4d4137339fb0ba5e156e826db2fdb5f458060d9979e0", + "sha256:cdbb917e41220bd3812234dbe59d15391adbc2c5d91ae11a5273aab9e32ba7ec", + "sha256:ea5c16c48e561f4a6f6b8c24807494b77a79e156b8133521c400f22ca712101b", + "sha256:ef86de9299e4ab2ebb129fb84b886bf40b9aced9807c6d6d5f28b46fb905f82c", + "sha256:f3e4860f5458a9875caa3de65e255720c0ed2ce71f0bcdab02497b32104f9db8" ], "index": "pypi", - "version": "==0.28.2" + "markers": "python_version != '3.2.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version >= '2.6'", + "version": "==0.28.5" }, "graphviz": { "hashes": [ - "sha256:c61de040e5354c088d2785ea447dd8c26fc572dcc389e4e23e2b46947808d43e", - "sha256:e1e839596d5ada2b7b5a80e8dffef343cd9f242e95c2387f765755795430ef3c" + "sha256:4958a19cbd8461757a08db308a4a15c3d586660417e1e364f0107d2fe481689f", + "sha256:7caa53f0b0be42c5f2eaa3f3d71dcc863b15bacceb5d531c2ad7519e1980ff82" ], - "version": "==0.8.3" + "markers": "python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.7'", + "version": "==0.8.4" }, "objgraph": { "hashes": [ diff --git a/lisp/core/decorators.py b/lisp/core/decorators.py index ce569185d..8555403b8 100644 --- a/lisp/core/decorators.py +++ b/lisp/core/decorators.py @@ -22,8 +22,7 @@ from threading import Thread, Lock, RLock -# TODO: rename to ensure compatibility with Python 3.7 -def async(target): +def async_function(target): """Decorator. Make a function asynchronous. The decorated function is executed in a differed thread. diff --git a/lisp/core/signal.py b/lisp/core/signal.py index 618d068bf..aa110a76c 100644 --- a/lisp/core/signal.py +++ b/lisp/core/signal.py @@ -28,7 +28,7 @@ from PyQt5.QtCore import QEvent, QObject from PyQt5.QtWidgets import QApplication -from lisp.core.decorators import async +from lisp.core.decorators import async_function from lisp.core.util import weak_call_proxy __all__ = ['Signal', 'Connection'] @@ -86,7 +86,7 @@ def _expired(self, reference): class AsyncSlot(Slot): """Asynchronous slot, NOT queued, any call is performed in a new thread.""" - @async + @async_function def call(self, *args, **kwargs): super().call(*args, **kwargs) diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 988945ed9..1d952378b 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -21,7 +21,7 @@ from uuid import uuid4 from lisp.core.configuration import AppConfig -from lisp.core.decorators import async +from lisp.core.decorators import async_function from lisp.core.fade_functions import FadeInType, FadeOutType from lisp.core.has_properties import HasProperties from lisp.core.properties import Property, WriteOnceProperty @@ -222,7 +222,7 @@ def execute(self, action=CueAction.Default): self.fadein(duration, FadeInType[fade]) - @async + @async_function def start(self, fade=False): """Start the cue.""" @@ -307,7 +307,7 @@ def __start__(self, fade=False): """ return False - @async + @async_function def stop(self, fade=False): """Stop the cue.""" @@ -365,7 +365,7 @@ def __stop__(self, fade=False): """ return False - @async + @async_function def pause(self, fade=False): """Pause the cue.""" @@ -419,7 +419,7 @@ def __pause__(self, fade=False): """ return False - @async + @async_function def interrupt(self, fade=False): """Interrupt the cue. diff --git a/lisp/cues/media_cue.py b/lisp/cues/media_cue.py index e8e46abd6..9acc9c6fc 100644 --- a/lisp/cues/media_cue.py +++ b/lisp/cues/media_cue.py @@ -22,7 +22,7 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.core.configuration import AppConfig -from lisp.core.decorators import async +from lisp.core.decorators import async_function from lisp.core.fade_functions import FadeInType, FadeOutType from lisp.core.fader import Fader from lisp.core.properties import Property @@ -117,7 +117,7 @@ def __interrupt__(self, fade=False): self.media.stop() - @async + @async_function def fadein(self, duration, fade_type): if not self._st_lock.acquire(timeout=0.1): return @@ -135,7 +135,7 @@ def fadein(self, duration, fade_type): self._st_lock.release() - @async + @async_function def fadeout(self, duration, fade_type): if not self._st_lock.acquire(timeout=0.1): return @@ -202,7 +202,7 @@ def _on_error(self): def _can_fade(self, duration): return self.__volume is not None and duration > 0 - @async + @async_function def _on_start_fade(self): if self.__volume is not None: self.__fadein( diff --git a/lisp/plugins/action_cues/command_cue.py b/lisp/plugins/action_cues/command_cue.py index 5aedb25c7..859e88919 100644 --- a/lisp/plugins/action_cues/command_cue.py +++ b/lisp/plugins/action_cues/command_cue.py @@ -23,7 +23,7 @@ from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLineEdit, QCheckBox -from lisp.core.decorators import async +from lisp.core.decorators import async_function from lisp.core.properties import Property from lisp.cues.cue import Cue, CueAction from lisp.ui.settings.cue_settings import CueSettingsRegistry @@ -58,7 +58,7 @@ def __start__(self, fade=False): self.__exec_command() return True - @async + @async_function def __exec_command(self): if not self.command.strip(): return diff --git a/lisp/plugins/action_cues/osc_cue.py b/lisp/plugins/action_cues/osc_cue.py index fe907b0ec..aa343ca57 100644 --- a/lisp/plugins/action_cues/osc_cue.py +++ b/lisp/plugins/action_cues/osc_cue.py @@ -25,7 +25,7 @@ from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, QLineEdit, \ QTableView, QTableWidget, QHeaderView, QPushButton, QLabel, QDoubleSpinBox, QMessageBox -from lisp.core.decorators import async +from lisp.core.decorators import async_function from lisp.core.fade_functions import FadeInType, FadeOutType from lisp.core.fader import Fader from lisp.core.has_properties import Property @@ -180,7 +180,7 @@ def __pause__(self, fade=False): __interrupt__ = __stop__ - @async + @async_function def __fade(self, fade_type): try: self.__fader.prepare() diff --git a/lisp/plugins/action_cues/volume_control.py b/lisp/plugins/action_cues/volume_control.py index f60c33c86..31d4c4c9e 100644 --- a/lisp/plugins/action_cues/volume_control.py +++ b/lisp/plugins/action_cues/volume_control.py @@ -27,7 +27,7 @@ from lisp.application import Application from lisp.backend.audio_utils import MIN_VOLUME_DB, MAX_VOLUME_DB, \ linear_to_db, db_to_linear -from lisp.core.decorators import async +from lisp.core.decorators import async_function from lisp.core.fade_functions import FadeInType, FadeOutType from lisp.core.fader import Fader from lisp.core.properties import Property @@ -99,7 +99,7 @@ def __pause__(self, fade=False): __interrupt__ = __stop__ - @async + @async_function def __fade(self, fade_type): try: self.__fader.prepare() diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index b175109df..0ecd1aef2 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -54,7 +54,7 @@ def key_from_message(path, types, args): @staticmethod def key_from_values(path, types, args): - if not len(types): + if not types: return "OSC['{0}', '{1}']".format(path, types) else: return "OSC['{0}', '{1}', {2}]".format(path, types, args) From bf6a4dde0313fa7e9d7872a93bc5f761c35a115a Mon Sep 17 00:00:00 2001 From: s0600204 Date: Wed, 22 Aug 2018 22:33:35 +0100 Subject: [PATCH 126/333] Fix opening a MIDI cue to edit it --- lisp/plugins/midi/midi_utils.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/lisp/plugins/midi/midi_utils.py b/lisp/plugins/midi/midi_utils.py index c3351db14..866edcc0d 100644 --- a/lisp/plugins/midi/midi_utils.py +++ b/lisp/plugins/midi/midi_utils.py @@ -19,21 +19,12 @@ import mido - def str_msg_to_dict(str_message): - message = mido.parse_string(str_message) - dict_msg = {'type': message.type} - - for name in message._spec.arguments: - dict_msg[name] = getattr(message, name) - - return dict_msg + return mido.parse_string(str_message).dict() def dict_msg_to_str(dict_message): - msg_type = dict_message.pop('type') - message = mido.Message(msg_type, **dict_message) - + message = mido.Message.from_dict(dict_message) return mido.format_as_string(message, include_time=False) From abbc95bed6881c10c881a4b2be50fa053f02d721 Mon Sep 17 00:00:00 2001 From: s0600204 <2053873+s0600204@users.noreply.github.com> Date: Wed, 5 Sep 2018 10:06:31 +0100 Subject: [PATCH 127/333] List Layout fixes (#134) Fix: Make ListLayout's global "Pause All" button actually pause cues Fix: Make ListLayout's cue-specific action buttons adhere to user-preference Fix: Load global layout settings --- lisp/layout/cue_layout.py | 2 +- lisp/plugins/list_layout/layout.py | 2 +- lisp/plugins/list_layout/playing_view.py | 6 +++-- lisp/plugins/list_layout/playing_widgets.py | 22 +++++++++---------- lisp/plugins/list_layout/view.py | 4 ++-- .../ui/settings/app_pages/layouts_settings.py | 4 ++-- 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/lisp/layout/cue_layout.py b/lisp/layout/cue_layout.py index 7902f79dc..288b8cdc9 100644 --- a/lisp/layout/cue_layout.py +++ b/lisp/layout/cue_layout.py @@ -164,7 +164,7 @@ def pause_all(self): if self.app.conf.get('layout.pauseAllFade', False): self.execute_all(CueAction.FadeOutPause) else: - self.execute_all(CueAction.FadeOut) + self.execute_all(CueAction.Pause) def resume_all(self): if self.app.conf.get('layout.resumeAllFade', True): diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index 948d16b5e..2f5b414e1 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -62,7 +62,7 @@ def __init__(self, application): self._memento_model = CueMementoAdapter(self._list_model) self._running_model = RunningCueModel(self.cue_model) - self._view = ListLayoutView(self._list_model, self._running_model) + self._view = ListLayoutView(self._list_model, self._running_model, self.Config) # GO button self._view.goButton.clicked.connect(self.__go_slot) # Global actions diff --git a/lisp/plugins/list_layout/playing_view.py b/lisp/plugins/list_layout/playing_view.py index 010b9a188..ee6fcbe28 100644 --- a/lisp/plugins/list_layout/playing_view.py +++ b/lisp/plugins/list_layout/playing_view.py @@ -26,12 +26,14 @@ class RunningCuesListWidget(QListWidget): - def __init__(self, running_model, **kwargs): + def __init__(self, running_model, config, **kwargs): super().__init__(**kwargs) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setFocusPolicy(Qt.NoFocus) self.setSelectionMode(self.NoSelection) + self._config = config + self._running_cues = {} self._running_model = running_model self._running_model.item_added.connect( @@ -82,7 +84,7 @@ def accurate_time(self, accurate): self.itemWidget(item).set_accurate_time(accurate) def _item_added(self, cue): - widget = get_running_widget(cue, parent=self) + widget = get_running_widget(cue, self._config, parent=self) widget.set_accurate_time(self.__accurate_time) try: widget.set_dbmeter_visible(self.__dbmeter_visible) diff --git a/lisp/plugins/list_layout/playing_widgets.py b/lisp/plugins/list_layout/playing_widgets.py index 77cce488b..9a932247d 100644 --- a/lisp/plugins/list_layout/playing_widgets.py +++ b/lisp/plugins/list_layout/playing_widgets.py @@ -23,7 +23,6 @@ QLCDNumber, QHBoxLayout from lisp.cues.cue import CueAction -from lisp.core.configuration import AppConfig from lisp.core.signal import Connection from lisp.core.util import strtime from lisp.cues.cue_time import CueTime @@ -32,15 +31,15 @@ from lisp.ui.widgets import QClickSlider, QDbMeter -def get_running_widget(cue, **kwargs): +def get_running_widget(cue, config, **kwargs): if isinstance(cue, MediaCue): - return RunningMediaCueWidget(cue, **kwargs) + return RunningMediaCueWidget(cue, config, **kwargs) else: - return RunningCueWidget(cue, **kwargs) + return RunningCueWidget(cue, config, **kwargs) class RunningCueWidget(QWidget): - def __init__(self, cue, **kwargs): + def __init__(self, cue, config, **kwargs): super().__init__(**kwargs) self.setGeometry(0, 0, self.parent().viewport().width(), 80) self.setFocusPolicy(Qt.NoFocus) @@ -48,6 +47,7 @@ def __init__(self, cue, **kwargs): self.layout().setContentsMargins(0, 0, 0, 1) self._accurate_time = False + self._config = config self.cue = cue self.cue_time = CueTime(cue) @@ -148,19 +148,19 @@ def _update_timers(self, time): def _pause(self): self.cue.pause( - fade=AppConfig().get('listLayout.pauseCueFade', True)) + fade=self._config.get('pauseCueFade', True)) def _resume(self): self.cue.resume( - fade=AppConfig().get('listLayout.resumeCueFade', True)) + fade=self._config.get('resumeCueFade', True)) def _stop(self): self.cue.stop( - fade=AppConfig().get('listLayout.stopCueFade', True)) + fade=self._config.get('stopCueFade', True)) def _interrupt(self): self.cue.interrupt( - fade=AppConfig().get('listLayout.interruptCueFade', True)) + fade=self._config.get('interruptCueFade', True)) def _fadeout(self): self.cue.execute(CueAction.FadeOut) @@ -170,8 +170,8 @@ def _fadein(self): class RunningMediaCueWidget(RunningCueWidget): - def __init__(self, cue, **kwargs): - super().__init__(cue, **kwargs) + def __init__(self, cue, config, **kwargs): + super().__init__(cue, config, **kwargs) self.setGeometry(0, 0, self.width(), 110) self._dbmeter_element = None diff --git a/lisp/plugins/list_layout/view.py b/lisp/plugins/list_layout/view.py index 21b521707..94e067688 100644 --- a/lisp/plugins/list_layout/view.py +++ b/lisp/plugins/list_layout/view.py @@ -27,7 +27,7 @@ class ListLayoutView(QWidget): - def __init__(self, listModel, runModel, *args): + def __init__(self, listModel, runModel, config, *args): super().__init__(*args) self.setLayout(QGridLayout()) self.layout().setContentsMargins(0, 0, 0, 0) @@ -59,7 +59,7 @@ def __init__(self, listModel, runModel, *args): self.layout().addWidget(self.listView, 1, 0, 1, 2) # PLAYING VIEW (center right) - self.runView = RunningCuesListWidget(runModel, parent=self) + self.runView = RunningCuesListWidget(runModel, config, parent=self) self.runView.setMinimumWidth(300) self.runView.setMaximumWidth(300) self.layout().addWidget(self.runView, 1, 2) diff --git a/lisp/ui/settings/app_pages/layouts_settings.py b/lisp/ui/settings/app_pages/layouts_settings.py index 1c76973c7..54998eee5 100644 --- a/lisp/ui/settings/app_pages/layouts_settings.py +++ b/lisp/ui/settings/app_pages/layouts_settings.py @@ -49,6 +49,7 @@ def __init__(self, config, **kwargs): self.useFadeGroup.layout().addWidget(self.interruptAllFade, 3, 1) self.retranslateUi() + self.loadSettings() def retranslateUi(self): self.useFadeGroup.setTitle( @@ -69,5 +70,4 @@ def applySettings(self): self.config['layout.pauseAllFade'] = self.pauseAllFade.isChecked() self.config['layout.resumeAllFade'] = self.resumeAllFade.isChecked() self.config['layout.interruptAllFade'] = self.interruptAllFade.isChecked() - - self.config.write() \ No newline at end of file + self.config.write() From 1e7edaf93fb0da4324347967f519ccdeea604834 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 7 Sep 2018 11:15:11 +0200 Subject: [PATCH 128/333] Initial support for layout/global controls is "controller" plugin Add: controllable (MIDI/OSC/...) operations for the layout via (#58) Add: ability to correctly handle SIGINT/SINGTERM (#129) Add: new generic widget and delegate to display/select Enum values Update: OSC message are now logged to DEBUG Update: App-Configuration Window is now larger Fix: fade-in, after a complete fade-out, now works properly Fix: ALSA devices are now correctly changed Fix: UriInput current settings are now shown correctly Fix: ListLayout "Auto Next/Follow" should now work properly --- Pipfile.lock | 60 +++++----- lisp/application.py | 11 +- lisp/core/configuration.py | 32 ++++- lisp/core/model.py | 2 +- lisp/core/util.py | 2 +- lisp/cues/media_cue.py | 3 +- lisp/main.py | 25 ++-- lisp/plugins/action_cues/index_action_cue.py | 4 +- lisp/plugins/action_cues/osc_cue.py | 3 +- lisp/plugins/controller/common.py | 58 +++++++++ lisp/plugins/controller/controller.py | 91 +++++++++++---- .../plugins/controller/controller_settings.py | 33 +++++- lisp/plugins/controller/default.json | 9 ++ lisp/plugins/controller/protocols/__init__.py | 18 +-- lisp/plugins/controller/protocols/keyboard.py | 85 +++++++++----- lisp/plugins/controller/protocols/midi.py | 108 ++++++++++------- lisp/plugins/controller/protocols/osc.py | 110 +++++++++++------- lisp/plugins/controller/protocols/protocol.py | 5 +- .../plugins/gst_backend/elements/alsa_sink.py | 22 +++- .../plugins/gst_backend/settings/uri_input.py | 3 + lisp/plugins/list_layout/layout.py | 2 +- lisp/plugins/osc/osc_server.py | 75 ++---------- lisp/plugins/timecode/settings.py | 4 +- lisp/ui/mainwindow.py | 2 +- lisp/ui/qdelegates.py | 78 ++++++++----- lisp/ui/settings/app_configuration.py | 6 +- lisp/ui/settings/cue_pages/cue_general.py | 40 +++---- lisp/ui/settings/cue_settings.py | 7 +- lisp/ui/settings/pages.py | 57 ++++----- lisp/ui/settings/pages_tree_model.py | 22 ++-- lisp/ui/ui_utils.py | 61 +++++++++- lisp/ui/widgets/cue_actions.py | 45 +------ lisp/ui/widgets/qenumcombobox.py | 44 +++++++ 33 files changed, 703 insertions(+), 424 deletions(-) create mode 100644 lisp/plugins/controller/common.py create mode 100644 lisp/plugins/controller/default.json create mode 100644 lisp/ui/widgets/qenumcombobox.py diff --git a/Pipfile.lock b/Pipfile.lock index 85d1b3b8d..f6fc4e7bf 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -16,10 +16,10 @@ "default": { "certifi": { "hashes": [ - "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7", - "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0" + "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", + "sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a" ], - "version": "==2018.4.16" + "version": "==2018.8.24" }, "cffi": { "hashes": [ @@ -82,11 +82,11 @@ }, "jack-client": { "hashes": [ - "sha256:46998c8065bcfa1b6c9faa0005786be2175f21bdced3a5105462489656b1b063", - "sha256:58eb7004e0cbfd769b4ad352dca1faeb5f25a764f55c173363d09f08c7d7b64d" + "sha256:7b2fe976775e38324412b26a3d7111f641dbdb596cb594b080a39a6e374377fc", + "sha256:8d7d8b4b505528cf29aa2a25793167318857d7c576d6db1a9be0f3e611e4b37a" ], "index": "pypi", - "version": "==0.4.4" + "version": "==0.4.5" }, "mido": { "hashes": [ @@ -110,10 +110,10 @@ }, "pygobject": { "hashes": [ - "sha256:250fb669b6ac64eba034cc4404fcbcc993717b1f77c29dff29f8c9202da20d55" + "sha256:c5c888068b96d55aa8efcbf1253fe824c74462169d80c0333967e295aabbee5e" ], "index": "pypi", - "version": "==3.28.3" + "version": "==3.30.0" }, "pyliblo": { "hashes": [ @@ -158,20 +158,10 @@ }, "python-rtmidi": { "hashes": [ - "sha256:1187b2a3703d4ede2b02c0fccd395648a3a468d3cd57d7932f445ef1bfa9fc5d", - "sha256:5e1bb542ceebfd44290ecafa6367ade1a712cc9a73b61e843b2fa802105a2759", - "sha256:659fb40e69cbc57cc7d970cd7bcd85e97ee2eb18992e147ed7bb690a83a7bd3f", - "sha256:7c82bed1b0cb5e11495bc3a779500c3d5cde6d84f68416f276622b0952174d25", - "sha256:80c6d31508ec8435b4dc1d4a979aa6046edb83413a837b790f33f65f176ad018", - "sha256:8685ffc012e007f10ddb9ed04a3cf1648d26a8ef95bd261bfeca0b90d11c971f", - "sha256:887fd11551f77b27d566235916723b065c30d1e3959c9d665131a7919c012df2", - "sha256:9d0fddaf38c4408e6f8466d6579ce3cf7b1da9a878f26454bc62fdf66228ea65", - "sha256:c0f54d1e6dd1c04958130611d0fe6a821194175847d9f696824d011dffce2092", - "sha256:d82eb82e0b270f75375e3d5f9f45cb75950485700e6a3862192d0c121c802b0e", - "sha256:dfba865b1e7c7793c7d222b1922ac5f901f5545af71bf70e06d88a77482aa20c" + "sha256:a796fc764febb9240a3c3de5ed230ae4d74f505c4e6298fb7e5f811bef845501" ], "index": "pypi", - "version": "==1.1.0" + "version": "==1.1.1" }, "requests": { "hashes": [ @@ -190,18 +180,18 @@ }, "sortedcontainers": { "hashes": [ - "sha256:607294c6e291a270948420f7ffa1fb3ed47384a4c08db6d1e9c92d08a6981982", - "sha256:ef38b128302ee8f65d81e31c9d8fbf10d81df4d6d06c9c0b66f01d33747525bb" + "sha256:220bb2e3e1886297fd7cdd6d164cb5cf237be1cfae1a3a3e526d149c52816682", + "sha256:b74f2756fb5e23512572cc76f0fe0832fd86310f77dfee54335a35fb33f6b950" ], "index": "pypi", - "version": "==2.0.4" + "version": "==2.0.5" }, "urllib3": { "hashes": [ "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" ], - "markers": "python_version != '3.0.*' and python_version != '3.3.*' and python_version >= '2.6' and python_version != '3.2.*' and python_version < '4' and python_version != '3.1.*'", + "markers": "python_version >= '2.6' and python_version != '3.2.*' and python_version < '4' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.1.*'", "version": "==1.23" } }, @@ -209,6 +199,7 @@ "cython": { "hashes": [ "sha256:022592d419fc754509d0e0461eb2958dbaa45fb60d51c8a61778c58994edbe36", + "sha256:07659f4c57582104d9486c071de512fbd7e087a3a630535298442cc0e20a3f5a", "sha256:13c73e2ffa93a615851e03fad97591954d143b5b62361b9adef81f46a31cd8ef", "sha256:13eab5a2835a84ff62db343035603044c908d2b3b6eec09d67fdf9970acf7ac9", "sha256:183b35a48f58862c4ec1e821f07bb7b1156c8c8559c85c32ae086f28947474eb", @@ -218,26 +209,35 @@ "sha256:553956ec06ecbd731ef0c538eb28a5b46bedea7ab89b18237ff28b4b99d65eee", "sha256:660eeb6870687fd3eda91e00ba4e72220545c254c8c4d967fd0c910f4fbb8cbc", "sha256:693a8619ef066ece055ed065a15cf440f9d3ebd1bca60e87ea19144833756433", + "sha256:759c799e9ef418f163b5412e295e14c0a48fe3b4dcba9ab8aab69e9f511cfefd", "sha256:827d3a91b7a7c31ce69e5974496fd9a8ba28eb498b988affb66d0d30de11d934", + "sha256:87e57b5d730cfab225d95e7b23abbc0c6f77598bd66639e93c73ce8afbae6f38", + "sha256:9400e5db8383346b0694a3e794d8bded18a27b21123516dcdf4b79d7ec28e98b", "sha256:9ec27681c5b1b457aacb1cbda5db04aa28b76da2af6e1e1fd15f233eafe6a0b0", "sha256:ae4784f040a3313c8bd00c8d04934b7ade63dc59692d8f00a5235be8ed72a445", + "sha256:b2ba8310ebd3c0e0b884d5e95bbd99d467d6af922acd1e44fe4b819839b2150e", "sha256:b64575241f64f6ec005a4d4137339fb0ba5e156e826db2fdb5f458060d9979e0", + "sha256:c78ad0df75a9fc03ab28ca1b950c893a208c451a18f76796c3e25817d6994001", "sha256:cdbb917e41220bd3812234dbe59d15391adbc2c5d91ae11a5273aab9e32ba7ec", + "sha256:d2223a80c623e2a8e97953ab945dfaa9385750a494438dcb55562eb1ddd9565a", + "sha256:e22f21cf92a9f8f007a280e3b3462c886d9068132a6c698dec10ad6125e3ca1e", "sha256:ea5c16c48e561f4a6f6b8c24807494b77a79e156b8133521c400f22ca712101b", + "sha256:ee7a9614d51fe16e32ca5befe72e0808baff481791728449d0b17c8b0fe29eb9", "sha256:ef86de9299e4ab2ebb129fb84b886bf40b9aced9807c6d6d5f28b46fb905f82c", - "sha256:f3e4860f5458a9875caa3de65e255720c0ed2ce71f0bcdab02497b32104f9db8" + "sha256:f3e4860f5458a9875caa3de65e255720c0ed2ce71f0bcdab02497b32104f9db8", + "sha256:fc6c20a8ac22202a779ad4c59756647be0826993d2151a03c015e76d2368ae5f" ], "index": "pypi", - "markers": "python_version != '3.2.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version >= '2.6'", + "markers": "python_version >= '2.6' and python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.1.*'", "version": "==0.28.5" }, "graphviz": { "hashes": [ - "sha256:4958a19cbd8461757a08db308a4a15c3d586660417e1e364f0107d2fe481689f", - "sha256:7caa53f0b0be42c5f2eaa3f3d71dcc863b15bacceb5d531c2ad7519e1980ff82" + "sha256:310bacfb969f0ac7c872610500e017c3e82b24a8abd33d289e99af162de30cb8", + "sha256:865afa6ab9775cf29db03abd8e571a164042c726c35a1b3c1e2b8c4c645e2993" ], - "markers": "python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.7'", - "version": "==0.8.4" + "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*'", + "version": "==0.9" }, "objgraph": { "hashes": [ diff --git a/lisp/application.py b/lisp/application.py index ecada2bac..7db22278a 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -19,9 +19,8 @@ import json import logging -from os.path import exists - from PyQt5.QtWidgets import QDialog, qApp +from os.path import exists from lisp import layout from lisp.core.actions_handler import MainActionsHandler @@ -36,14 +35,14 @@ from lisp.ui.layoutselect import LayoutSelect from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.app_configuration import AppConfigurationDialog -from lisp.ui.settings.app_pages.layouts_settings import LayoutsSettings -from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.app_pages.app_general import AppGeneral from lisp.ui.settings.app_pages.cue_app_settings import CueAppSettings +from lisp.ui.settings.app_pages.layouts_settings import LayoutsSettings +from lisp.ui.settings.app_pages.plugins_settings import PluginsSettings from lisp.ui.settings.cue_pages.cue_appearance import Appearance from lisp.ui.settings.cue_pages.cue_general import CueGeneralSettings from lisp.ui.settings.cue_pages.media_cue_settings import MediaCueSettings -from lisp.ui.settings.app_pages.plugins_settings import PluginsSettings +from lisp.ui.settings.cue_settings import CueSettingsRegistry logger = logging.getLogger(__name__) @@ -140,7 +139,6 @@ def _new_session_dialog(self): except Exception: logger.critical('Startup error', exc_info=True) qApp.quit() - exit(-1) def _new_session(self, layout): self._delete_session() @@ -163,7 +161,6 @@ def _save_to_file(self, session_file): """Save the current session into a file.""" self.session.session_file = session_file - # TODO: move cues into session ? # Add the cues session_dict = {'cues': []} diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index 10d43c74f..dfb37b963 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -71,7 +71,8 @@ def get(self, path, default=_UNSET): return node[key] except (KeyError, TypeError): if default is not _UNSET: - logger.debug('Invalid path, return default: {}'.format(path)) + logger.warning( + 'Invalid path "{}", return default.'.format(path)) return default raise ConfDictError('invalid path') @@ -183,6 +184,35 @@ def write(self): pass +class SubConfiguration(Configuration): + """Provide a view on a parent configuration "section" + + If a parent configuration is reloaded all related SubConfiguration + should be reloaded, using the `read` method, or it may + (depending on the parent implementation) hold an invalid reference to the + parent "sub-section". + + The `write` method is a no-op, you need to call the parent one to actually + do something. + """ + + def __init__(self, parent, root_path): + """ + :param parent: The parent configuration + :type parent: Configuration + :param root_path: The path of the parent "section" that will be visible + """ + super().__init__(root=parent.get(root_path)) + self._root_path = root_path + self._parent = parent + + def read(self): + self._root = self._parent.get(self._root_path) + + def write(self): + pass + + class JSONFileConfiguration(Configuration): """Read/Write configurations from/to a JSON file. diff --git a/lisp/core/model.py b/lisp/core/model.py index 5ef04ab19..a4b647588 100644 --- a/lisp/core/model.py +++ b/lisp/core/model.py @@ -18,7 +18,7 @@ # along with Linux Show Player. If not, see . from abc import abstractmethod -from collections import Sized, Iterable, Container +from collections.abc import Sized, Iterable, Container from lisp.core.signal import Signal diff --git a/lisp/core/util.py b/lisp/core/util.py index d9a4eabb5..c357d3894 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -20,7 +20,7 @@ import functools import re import socket -from collections import Mapping, MutableMapping +from collections.abc import Mapping, MutableMapping from enum import Enum from os import listdir from os.path import isdir, exists, join diff --git a/lisp/cues/media_cue.py b/lisp/cues/media_cue.py index 9acc9c6fc..4cd5969a7 100644 --- a/lisp/cues/media_cue.py +++ b/lisp/cues/media_cue.py @@ -21,6 +21,7 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP +from lisp.backend.audio_utils import MIN_VOLUME from lisp.core.configuration import AppConfig from lisp.core.decorators import async_function from lisp.core.fade_functions import FadeInType, FadeOutType @@ -148,7 +149,7 @@ def fadeout(self, duration, fade_type): self.__volume.live_volume = 0 else: self._st_lock.release() - self.__fadeout(duration, 0, fade_type) + self.__fadeout(duration, MIN_VOLUME, fade_type) return self._st_lock.release() diff --git a/lisp/main.py b/lisp/main.py index 1fdb64bfc..352bc26c3 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -21,6 +21,8 @@ import logging import os import sys + +import signal from logging.handlers import RotatingFileHandler from PyQt5.QtCore import QLocale, QLibraryInfo @@ -32,7 +34,7 @@ from lisp.core.configuration import JSONFileConfiguration from lisp.ui import themes from lisp.ui.icons import IconTheme -from lisp.ui.ui_utils import install_translation +from lisp.ui.ui_utils import install_translation, PyQtUnixSignalHandler def main(): @@ -123,13 +125,22 @@ def main(): lisp_app = Application(app_conf) plugins.load_plugins(lisp_app) - # Start the application - lisp_app.start(session_file=args.file) - exit_code = qt_app.exec_() # block until exit + # Handle SIGTERM and SIGINT by quitting the QApplication + def handle_quit_signal(*_): + qt_app.quit() + + signal.signal(signal.SIGTERM, handle_quit_signal) + signal.signal(signal.SIGINT, handle_quit_signal) + + with PyQtUnixSignalHandler(): + # Start the application + lisp_app.start(session_file=args.file) + exit_code = qt_app.exec_() # block until exit + + # Finalize all and exit + plugins.finalize_plugins() + lisp_app.finalize() - # Finalize all and exit - plugins.finalize_plugins() - lisp_app.finalize() sys.exit(exit_code) diff --git a/lisp/plugins/action_cues/index_action_cue.py b/lisp/plugins/action_cues/index_action_cue.py index bb74e169c..f6875bbb3 100644 --- a/lisp/plugins/action_cues/index_action_cue.py +++ b/lisp/plugins/action_cues/index_action_cue.py @@ -145,7 +145,7 @@ def loadSettings(self, settings): self.relativeCheck.setChecked(settings.get('relative', True)) self.targetIndexSpin.setValue(settings.get('target_index', 0)) self._target_changed() # Ensure that the correct options are displayed - self.actionCombo.setCurrentAction(settings.get('action', '')) + self.actionCombo.setCurrentItem(settings.get('action', '')) def _target_changed(self): target = self._current_target() @@ -157,7 +157,7 @@ def _target_changed(self): if target_class is not self._target_class: self._target_class = target_class - self.actionCombo.rebuild(self._target_class.CueActions) + self.actionCombo.setItems(self._target_class.CueActions) self._update_suggestion() diff --git a/lisp/plugins/action_cues/osc_cue.py b/lisp/plugins/action_cues/osc_cue.py index aa343ca57..aafc2fbd8 100644 --- a/lisp/plugins/action_cues/osc_cue.py +++ b/lisp/plugins/action_cues/osc_cue.py @@ -35,7 +35,8 @@ from lisp.ui.qdelegates import ComboBoxDelegate, CheckBoxDelegate from lisp.plugins.osc.osc_delegate import OscArgumentDelegate from lisp.ui.qmodels import SimpleTableModel -from lisp.ui.settings.cue_settings import CueSettingsRegistry, SettingsPage +from lisp.ui.settings.cue_settings import CueSettingsRegistry +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate from lisp.ui.widgets import FadeComboBox, QDetailedMessageBox diff --git a/lisp/plugins/controller/common.py b/lisp/plugins/controller/common.py new file mode 100644 index 000000000..a509e985f --- /dev/null +++ b/lisp/plugins/controller/common.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import QT_TRANSLATE_NOOP +from enum import Enum + +from lisp.ui.ui_utils import translate + + +class LayoutAction(Enum): + Go = 'Go' + Reset = 'Reset' + + StopAll = 'StopAll' + PauseAll = 'PauseAll' + ResumeAll = 'ResumeAll' + InterruptAll = 'InterrupAll' + FadeOutAll = 'FadeOutAll' + FadeInAll = 'FadeInAll' + + StandbyForward = 'StandbyForward' + StandbyBack = 'StandbyBack' + + +LayoutActionsStrings = { + LayoutAction.Go: QT_TRANSLATE_NOOP('GlobalAction', 'Go'), + LayoutAction.Reset: QT_TRANSLATE_NOOP('GlobalAction', 'Reset'), + LayoutAction.StopAll: QT_TRANSLATE_NOOP('GlobalAction', 'Stop all cues'), + LayoutAction.PauseAll: QT_TRANSLATE_NOOP('GlobalAction', 'Pause all cues'), + LayoutAction.ResumeAll: QT_TRANSLATE_NOOP('GlobalAction', 'Resume all cues'), + LayoutAction.InterruptAll: QT_TRANSLATE_NOOP('GlobalAction', 'Interrupt all cues'), + LayoutAction.FadeOutAll: QT_TRANSLATE_NOOP('GlobalAction', 'Fade-out all cues'), + LayoutAction.FadeInAll: QT_TRANSLATE_NOOP('GlobalAction', 'Fade-in all cues'), + LayoutAction.StandbyForward: QT_TRANSLATE_NOOP('GlobalAction', 'Move standby forward'), + LayoutAction.StandbyBack: QT_TRANSLATE_NOOP('GlobalAction', 'Move standby back'), +} + + +def tr_layout_action(action): + return translate( + 'CueAction', LayoutActionsStrings.get(action, action.name) + ) diff --git a/lisp/plugins/controller/controller.py b/lisp/plugins/controller/controller.py index 9bd51d78b..fb10d36b5 100644 --- a/lisp/plugins/controller/controller.py +++ b/lisp/plugins/controller/controller.py @@ -23,7 +23,9 @@ from lisp.core.properties import Property from lisp.cues.cue import Cue, CueAction from lisp.plugins.controller import protocols -from lisp.plugins.controller.controller_settings import ControllerSettings +from lisp.plugins.controller.common import LayoutAction +from lisp.plugins.controller.controller_settings import CueControllerSettings, ControllerLayoutConfiguration +from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.ui_utils import translate @@ -40,8 +42,8 @@ class Controller(Plugin): def __init__(self, app): super().__init__(app) - self.__map = {} - self.__actions_map = {} + self.__cue_map = {} + self.__global_map = {} self.__protocols = {} # Register a new Cue property to store settings @@ -55,43 +57,83 @@ def __init__(self, app): self.app.cue_model.item_added.connect(self.__cue_added) self.app.cue_model.item_removed.connect(self.__cue_removed) + # Register the settings widget + AppConfigurationDialog.registerSettingsPage( + 'plugins.controller', ControllerLayoutConfiguration, Controller.Config) # Register settings-page - CueSettingsRegistry().add(ControllerSettings) + CueSettingsRegistry().add(CueControllerSettings) # Load available protocols self.__load_protocols() + self.global_changed(Controller.Config) + Controller.Config.updated.connect(self.global_changed) + Controller.Config.changed.connect(self.global_changed) + def session_init(self): for protocol in self.__protocols.values(): protocol.init() def session_reset(self): - self.__map.clear() - self.__actions_map.clear() + self.__cue_map.clear() for protocol in self.__protocols.values(): protocol.reset() + def global_changed(self, *args): + # Here we just rebuild everything which is not the best solution + # but is the easiest + self.__global_map = {} + for protocol in Controller.Config['protocols'].values(): + for key, action in protocol: + self.__global_map.setdefault(key, set()).add( + LayoutAction(action)) + def cue_changed(self, cue, property_name, value): if property_name == 'controller': - self.delete_from_map(cue) + self.delete_from_cue_map(cue) for protocol in self.__protocols: - for key, action in value.get(protocol, []): - if key not in self.__map: - self.__map[key] = set() - - self.__map[key].add(cue) - self.__actions_map[(key, cue)] = CueAction(action) - - def delete_from_map(self, cue): - for key in self.__map: - self.__map[key].discard(cue) - self.__actions_map.pop((key, cue), None) - - def perform_action(self, key): - for cue in self.__map.get(key, []): - cue.execute(self.__actions_map[(key, cue)]) + for key, action in value.get(protocol, ()): + actions_map = self.__cue_map.setdefault(key, {}) + actions_map.setdefault(cue, set()).add(CueAction(action)) + + def delete_from_cue_map(self, cue): + for actions_map in self.__cue_map: + actions_map.pop(cue) + + def perform_cue_action(self, key): + for actions_map in self.__cue_map.get(key, {}): + for cue, action in actions_map: + cue.execute(action) + + def perform_session_action(self, key): + for action in self.__global_map.get(key, ()): + if action is LayoutAction.Go: + self.app.layout.go() + elif action is LayoutAction.Reset: + self.app.layout.interrupt_all() + self.app.layout.set_standby_index(0) + elif action is LayoutAction.StopAll: + self.app.layout.stop_all() + elif action is LayoutAction.PauseAll: + self.app.layout.pause_all() + elif action is LayoutAction.ResumeAll: + self.app.layout.resume_all() + elif action is LayoutAction.InterruptAll: + self.app.layout.interrupt_all() + elif action is LayoutAction.FadeOutAll: + self.app.layout.fadeout_all() + elif action is LayoutAction.FadeInAll: + self.app.layout.fadeout_all() + elif action is LayoutAction.StandbyForward: + self.app.layout.set_standby_index( + self.app.layout.standby_index() + 1) + elif action is LayoutAction.StandbyBack: + self.app.layout.set_standby_index( + self.app.layout.standby_index() - 1) + else: + self.app.finalize() def __cue_added(self, cue): cue.property_changed.connect(self.cue_changed) @@ -99,7 +141,7 @@ def __cue_added(self, cue): def __cue_removed(self, cue): cue.property_changed.disconnect(self.cue_changed) - self.delete_from_map(cue) + self.delete_from_cue_map(cue) def __load_protocols(self): protocols.load() @@ -107,7 +149,8 @@ def __load_protocols(self): for protocol_class in protocols.Protocols: try: protocol = protocol_class() - protocol.protocol_event.connect(self.perform_action) + protocol.protocol_event.connect(self.perform_cue_action) + protocol.protocol_event.connect(self.perform_session_action) self.__protocols[protocol_class.__name__.lower()] = protocol except Exception: diff --git a/lisp/plugins/controller/controller_settings.py b/lisp/plugins/controller/controller_settings.py index b4a1259e4..b94d6fb5e 100644 --- a/lisp/plugins/controller/controller_settings.py +++ b/lisp/plugins/controller/controller_settings.py @@ -20,20 +20,41 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.plugins.controller import protocols -from lisp.ui.settings.pages import TabsMultiSettingsPage, CuePageMixin +from lisp.ui.settings.pages import TabsMultiSettingsPage, CuePageMixin, \ + TabsMultiConfigurationPage, ConfigurationPage -class ControllerSettings(TabsMultiSettingsPage, CuePageMixin): +class CueControllerSettings(TabsMultiSettingsPage, CuePageMixin): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Cue Control') - def __init__(self, cue_type, **kwargs): - super().__init__(cue_type=cue_type, **kwargs) + def __init__(self, cueType, **kwargs): + super().__init__(cueType=cueType, **kwargs) - for page in protocols.ProtocolsSettingsPages: - self.addPage(page(cue_type, parent=self)) + for page in protocols.CueSettingsPages: + self.addPage(page(cueType, parent=self)) def getSettings(self): return {'controller': super().getSettings()} def loadSettings(self, settings): super().loadSettings(settings.get('controller', {})) + + +class ControllerLayoutConfiguration(TabsMultiConfigurationPage, ConfigurationPage): + Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Layout Controls') + + def __init__(self, config, **kwargs): + super().__init__(config=config, **kwargs) + + for page in protocols.LayoutSettingsPages: + newPage = page(parent=self) + newPage.loadSettings(config['protocols']) + self.addPage(newPage) + + def applySettings(self): + protocolsConf = {} + for page in self.iterPages(): + protocolsConf.update(page.getSettings()) + + self.config.update({'protocols': protocolsConf}) + self.config.write() diff --git a/lisp/plugins/controller/default.json b/lisp/plugins/controller/default.json new file mode 100644 index 000000000..77ff608b4 --- /dev/null +++ b/lisp/plugins/controller/default.json @@ -0,0 +1,9 @@ +{ + "_version_": "0.2", + "_enabled_": true, + "protocols": { + "keyboard": {}, + "midi": {}, + "osc": {} + } +} \ No newline at end of file diff --git a/lisp/plugins/controller/protocols/__init__.py b/lisp/plugins/controller/protocols/__init__.py index 172148930..99a44dd56 100644 --- a/lisp/plugins/controller/protocols/__init__.py +++ b/lisp/plugins/controller/protocols/__init__.py @@ -20,16 +20,18 @@ from os.path import dirname from lisp.core.loading import load_classes -from lisp.ui.settings.pages import SettingsPage Protocols = [] -ProtocolsSettingsPages = [] +CueSettingsPages = [] +LayoutSettingsPages = [] def load(): - for _, protocol in load_classes(__package__, dirname(__file__), - pre=('', ''), suf=('', 'Settings',)): - if issubclass(protocol, SettingsPage): - ProtocolsSettingsPages.append(protocol) - else: - Protocols.append(protocol) + for _, protocol in load_classes(__package__, dirname(__file__)): + Protocols.append(protocol) + + # Get settings pages + if protocol.CueSettings is not None: + CueSettingsPages.append(protocol.CueSettings) + if protocol.LayoutSettings is not None: + LayoutSettingsPages.append(protocol.LayoutSettings) diff --git a/lisp/plugins/controller/protocols/keyboard.py b/lisp/plugins/controller/protocols/keyboard.py index 7c10cfeeb..bbf7fb87d 100644 --- a/lisp/plugins/controller/protocols/keyboard.py +++ b/lisp/plugins/controller/protocols/keyboard.py @@ -22,32 +22,20 @@ QPushButton, QVBoxLayout from lisp.application import Application +from lisp.plugins.controller.common import LayoutAction, tr_layout_action from lisp.plugins.controller.protocols.protocol import Protocol -from lisp.ui.qdelegates import LineEditDelegate, \ - CueActionDelegate +from lisp.ui.qdelegates import LineEditDelegate, CueActionDelegate,\ + EnumComboBoxDelegate from lisp.ui.qmodels import SimpleTableModel -from lisp.ui.settings.pages import CueSettingsPage +from lisp.ui.settings.pages import SettingsPage, CuePageMixin from lisp.ui.ui_utils import translate -class Keyboard(Protocol): - - def init(self): - Application().layout.key_pressed.connect(self.__key_pressed) - - def reset(self): - Application().layout.key_pressed.disconnect(self.__key_pressed) - - def __key_pressed(self, key_event): - if not key_event.isAutoRepeat() and key_event.text() != '': - self.protocol_event.emit(key_event.text()) - - -class KeyboardSettings(CueSettingsPage): +class KeyboardSettings(SettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Keyboard Shortcuts') - def __init__(self, cue_type, **kwargs): - super().__init__(cue_type, **kwargs) + def __init__(self, actionDelegate, **kwargs): + super().__init__(**kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -59,19 +47,20 @@ def __init__(self, cue_type, **kwargs): translate('ControllerKeySettings', 'Key'), translate('ControllerKeySettings', 'Action')]) - self.keyboardView = KeyboardView(cue_type, parent=self.keyGroup) + self.keyboardView = KeyboardView(actionDelegate, parent=self.keyGroup) self.keyboardView.setModel(self.keyboardModel) self.keyGroup.layout().addWidget(self.keyboardView, 0, 0, 1, 2) self.addButton = QPushButton(self.keyGroup) - self.addButton.clicked.connect(self.__new_key) + self.addButton.clicked.connect(self._addEmpty) self.keyGroup.layout().addWidget(self.addButton, 1, 0) self.removeButton = QPushButton(self.keyGroup) - self.removeButton.clicked.connect(self.__remove_key) + self.removeButton.clicked.connect(self._removeCurrent) self.keyGroup.layout().addWidget(self.removeButton, 1, 1) self.retranslateUi() + self._defaultAction = None def retranslateUi(self): self.keyGroup.setTitle(translate('ControllerKeySettings', 'Shortcuts')) @@ -94,16 +83,41 @@ def loadSettings(self, settings): for key, action in settings.get('keyboard', []): self.keyboardModel.appendRow(key, action) - def __new_key(self): - self.keyboardModel.appendRow('', self.cue_type.CueActions[0].name) + def _addEmpty(self): + self.keyboardModel.appendRow('', self._defaultAction) - def __remove_key(self): + def _removeCurrent(self): self.keyboardModel.removeRow(self.keyboardView.currentIndex().row()) +class KeyboardCueSettings(KeyboardSettings, CuePageMixin): + def __init__(self, cueType, **kwargs): + super().__init__( + actionDelegate=CueActionDelegate( + cue_class=cueType, + mode=CueActionDelegate.Mode.Name), + cueType=cueType, + **kwargs + ) + self._defaultAction = self.cueType.CueActions[0].name + + +class KeyboardLayoutSettings(KeyboardSettings): + def __init__(self, **kwargs): + super().__init__( + actionDelegate=EnumComboBoxDelegate( + LayoutAction, + mode=EnumComboBoxDelegate.Mode.Name, + trItem=tr_layout_action + ), + **kwargs + ) + self._defaultAction = LayoutAction.Go.name + + class KeyboardView(QTableView): - def __init__(self, cue_class, **kwargs): + def __init__(self, actionDelegate, **kwargs): super().__init__(**kwargs) self.setSelectionBehavior(QTableView.SelectRows) @@ -119,9 +133,22 @@ def __init__(self, cue_class, **kwargs): self.verticalHeader().setDefaultSectionSize(24) self.verticalHeader().setHighlightSections(False) - self.delegates = [LineEditDelegate(max_length=1), - CueActionDelegate(cue_class=cue_class, - mode=CueActionDelegate.Mode.Name)] + self.delegates = [LineEditDelegate(max_length=1), actionDelegate] for column, delegate in enumerate(self.delegates): self.setItemDelegateForColumn(column, delegate) + + +class Keyboard(Protocol): + CueSettings = KeyboardCueSettings + LayoutSettings = KeyboardLayoutSettings + + def init(self): + Application().layout.key_pressed.connect(self.__key_pressed) + + def reset(self): + Application().layout.key_pressed.disconnect(self.__key_pressed) + + def __key_pressed(self, key_event): + if not key_event.isAutoRepeat() and key_event.text() != '': + self.protocol_event.emit(key_event.text()) diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index 98e279448..8360ec262 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -22,44 +22,20 @@ QMessageBox, QTableView, QTableWidget, QHeaderView, QGridLayout from lisp.plugins import get_plugin, PluginNotLoadedError +from lisp.plugins.controller.common import LayoutAction, tr_layout_action from lisp.plugins.controller.protocols.protocol import Protocol from lisp.ui.qdelegates import ComboBoxDelegate, SpinBoxDelegate, \ - CueActionDelegate + CueActionDelegate, EnumComboBoxDelegate from lisp.ui.qmodels import SimpleTableModel -from lisp.ui.settings.pages import CueSettingsPage +from lisp.ui.settings.pages import CuePageMixin, SettingsPage from lisp.ui.ui_utils import translate -class Midi(Protocol): - def __init__(self): - super().__init__() - - # Install callback for new MIDI messages - get_plugin('Midi').input.new_message.connect(self.__new_message) - - def __new_message(self, message): - if message.type == 'note_on' or message.type == 'note_off': - self.protocol_event.emit(Midi.str_from_message(message)) - - @staticmethod - def str_from_message(message): - return Midi.str_from_values(message.type, message.channel, message.note) - - @staticmethod - def str_from_values(m_type, channel, note): - return '{} {} {}'.format(m_type, channel, note) - - @staticmethod - def from_string(message_str): - m_type, channel, note = message_str.split() - return m_type, int(channel), int(note) - - -class MidiSettings(CueSettingsPage): +class MidiSettings(SettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'MIDI Controls') - def __init__(self, cue_type, **kwargs): - super().__init__(cue_type, **kwargs) + def __init__(self, actionDelegate, **kwargs): + super().__init__(**kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -75,7 +51,7 @@ def __init__(self, cue_type, **kwargs): translate('ControllerMidiSettings', 'Note'), translate('ControllerMidiSettings', 'Action')]) - self.midiView = MidiView(cue_type, parent=self.midiGroup) + self.midiView = MidiView(actionDelegate, parent=self.midiGroup) self.midiView.setModel(self.midiModel) self.midiGroup.layout().addWidget(self.midiView, 0, 0, 1, 2) @@ -102,7 +78,7 @@ def __init__(self, cue_type, **kwargs): self.retranslateUi() - self._default_action = self.cue_type.CueActions[0].name + self._defaultAction = None try: self.__midi = get_plugin('Midi') except PluginNotLoadedError: @@ -144,9 +120,8 @@ def capture_message(self): handler.alternate_mode = True handler.new_message_alt.connect(self.__add_message) - QMessageBox.information( - self, '', translate( - 'ControllerMidiSettings', 'Listening MIDI messages ...') + QMessageBox.information(self, '', translate( + 'ControllerMidiSettings', 'Listening MIDI messages ...') ) handler.new_message_alt.disconnect(self.__add_message) @@ -154,27 +129,51 @@ def capture_message(self): def __add_message(self, msg): if self.msgTypeCombo.currentData(Qt.UserRole) == msg.type: - self.midiModel.appendRow(msg.type, msg.channel+1, msg.note, - self._default_action) + self.midiModel.appendRow( + msg.type, msg.channel+1, msg.note, self._defaultAction) def __new_message(self): message_type = self.msgTypeCombo.currentData(Qt.UserRole) - self.midiModel.appendRow(message_type, 1, 0, self._default_action) + self.midiModel.appendRow(message_type, 1, 0, self._defaultAction) def __remove_message(self): self.midiModel.removeRow(self.midiView.currentIndex().row()) +class MidiCueSettings(MidiSettings, CuePageMixin): + def __init__(self, cueType, **kwargs): + super().__init__( + actionDelegate=CueActionDelegate( + cue_class=cueType, + mode=CueActionDelegate.Mode.Name), + cueType=cueType, + **kwargs + ) + self._defaultAction = self.cueType.CueActions[0].name + + +class MidiLayoutSettings(MidiSettings): + def __init__(self, **kwargs): + super().__init__( + actionDelegate=EnumComboBoxDelegate( + LayoutAction, + mode=EnumComboBoxDelegate.Mode.Name, + trItem=tr_layout_action + ), + **kwargs + ) + self._defaultAction = LayoutAction.Go.name + + class MidiView(QTableView): - def __init__(self, cue_class, **kwargs): + def __init__(self, actionDelegate, **kwargs): super().__init__(**kwargs) self.delegates = [ ComboBoxDelegate(options=['note_on', 'note_off']), SpinBoxDelegate(minimum=1, maximum=16), SpinBoxDelegate(minimum=0, maximum=127), - CueActionDelegate(cue_class=cue_class, - mode=CueActionDelegate.Mode.Name) + actionDelegate ] self.setSelectionBehavior(QTableWidget.SelectRows) @@ -192,3 +191,30 @@ def __init__(self, cue_class, **kwargs): for column, delegate in enumerate(self.delegates): self.setItemDelegateForColumn(column, delegate) + + +class Midi(Protocol): + CueSettings = MidiCueSettings + LayoutSettings = MidiLayoutSettings + + def __init__(self): + super().__init__() + # Install callback for new MIDI messages + get_plugin('Midi').input.new_message.connect(self.__new_message) + + def __new_message(self, message): + if message.type == 'note_on' or message.type == 'note_off': + self.protocol_event.emit(Midi.str_from_message(message)) + + @staticmethod + def str_from_message(message): + return Midi.str_from_values(message.type, message.channel, message.note) + + @staticmethod + def str_from_values(m_type, channel, note): + return '{} {} {}'.format(m_type, channel, note) + + @staticmethod + def from_string(message_str): + m_type, channel, note = message_str.split() + return m_type, int(channel), int(note) \ No newline at end of file diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 0ecd1aef2..58cd436e9 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -27,44 +27,16 @@ QDialog, QDialogButtonBox, QLineEdit, QMessageBox from lisp.plugins import get_plugin, PluginNotLoadedError +from lisp.plugins.controller.common import LayoutAction, tr_layout_action from lisp.plugins.controller.protocols.protocol import Protocol from lisp.plugins.osc.osc_delegate import OscArgumentDelegate from lisp.plugins.osc.osc_server import OscMessageType -from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate +from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate, CueActionDelegate, EnumComboBoxDelegate from lisp.ui.qmodels import SimpleTableModel -from lisp.ui.settings.pages import CueSettingsPage +from lisp.ui.settings.pages import CueSettingsPage, SettingsPage, CuePageMixin from lisp.ui.ui_utils import translate -class Osc(Protocol): - def __init__(self): - super().__init__() - - osc = get_plugin('Osc') - osc.server.new_message.connect(self.__new_message) - - def __new_message(self, path, args, types): - key = self.key_from_message(path, types, args) - self.protocol_event.emit(key) - - @staticmethod - def key_from_message(path, types, args): - key = [path, types, *args] - return 'OSC{}'.format(key) - - @staticmethod - def key_from_values(path, types, args): - if not types: - return "OSC['{0}', '{1}']".format(path, types) - else: - return "OSC['{0}', '{1}', {2}]".format(path, types, args) - - @staticmethod - def message_from_key(key): - key = ast.literal_eval(key[3:]) - return key - - class OscMessageDialog(QDialog): def __init__(self, **kwargs): super().__init__(**kwargs) @@ -186,11 +158,11 @@ def __init__(self, **kwargs): self.setItemDelegateForColumn(column, delegate) -class OscSettings(CueSettingsPage): +class OscSettings(SettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'OSC Controls') - def __init__(self, cue_type, **kwargs): - super().__init__(cue_type, **kwargs) + def __init__(self, actionDelegate, **kwargs): + super().__init__(**kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -205,7 +177,7 @@ def __init__(self, cue_type, **kwargs): translate('ControllerOscSettings', 'Arguments'), translate('ControllerOscSettings', 'Actions')]) - self.OscView = OscView(cue_type, parent=self.oscGroup) + self.OscView = OscView(actionDelegate, parent=self.oscGroup) self.OscView.setModel(self.oscModel) self.oscGroup.layout().addWidget(self.OscView, 0, 0, 1, 2) @@ -244,7 +216,7 @@ def __init__(self, cue_type, **kwargs): self.retranslateUi() - self._default_action = self.cue_type.CueActions[0].name + self._defaultAction = None try: self.__osc = get_plugin('Osc') except PluginNotLoadedError: @@ -294,7 +266,7 @@ def capture_message(self): self.capturedMessage['path'], self.capturedMessage['types'], args, - self._default_action + self._defaultAction ) self.__osc.server.new_message.disconnect(self.__show_message) @@ -347,7 +319,7 @@ def __new_message(self): path, types, '{}'.format(arguments)[1:-1], - self._default_action + self._defaultAction ) def __remove_message(self): @@ -355,16 +327,40 @@ def __remove_message(self): self.oscModel.removeRow(self.OscView.currentIndex().row()) +class OscCueSettings(OscSettings, CuePageMixin): + def __init__(self, cueType, **kwargs): + super().__init__( + actionDelegate=CueActionDelegate( + cue_class=cueType, + mode=CueActionDelegate.Mode.Name), + cueType=cueType, + **kwargs + ) + self._defaultAction = self.cueType.CueActions[0].name + + +class OscLayoutSettings(OscSettings): + def __init__(self, **kwargs): + super().__init__( + actionDelegate=EnumComboBoxDelegate( + LayoutAction, + mode=EnumComboBoxDelegate.Mode.Name, + trItem=tr_layout_action + ), + **kwargs + ) + self._defaultAction = LayoutAction.Go.name + + class OscView(QTableView): - def __init__(self, cue_class, **kwargs): + def __init__(self, actionDelegate, **kwargs): super().__init__(**kwargs) - cue_actions = [action.name for action in cue_class.CueActions] self.delegates = [ LineEditDelegate(), LineEditDelegate(), LineEditDelegate(), - ComboBoxDelegate(options=cue_actions, tr_context='CueAction') + actionDelegate ] self.setSelectionBehavior(QTableWidget.SelectRows) @@ -382,3 +378,35 @@ def __init__(self, cue_class, **kwargs): for column, delegate in enumerate(self.delegates): self.setItemDelegateForColumn(column, delegate) + + +class Osc(Protocol): + CueSettings = OscCueSettings + LayoutSettings = OscLayoutSettings + + def __init__(self): + super().__init__() + + osc = get_plugin('Osc') + osc.server.new_message.connect(self.__new_message) + + def __new_message(self, path, args, types, *_, **__): + key = self.key_from_message(path, types, args) + self.protocol_event.emit(key) + + @staticmethod + def key_from_message(path, types, args): + key = [path, types, *args] + return 'OSC{}'.format(key) + + @staticmethod + def key_from_values(path, types, args): + if not types: + return "OSC['{0}', '{1}']".format(path, types) + else: + return "OSC['{0}', '{1}', {2}]".format(path, types, args) + + @staticmethod + def message_from_key(key): + key = ast.literal_eval(key[3:]) + return key diff --git a/lisp/plugins/controller/protocols/protocol.py b/lisp/plugins/controller/protocols/protocol.py index 7b7426d7c..16275a8b0 100644 --- a/lisp/plugins/controller/protocols/protocol.py +++ b/lisp/plugins/controller/protocols/protocol.py @@ -31,10 +31,11 @@ class Protocol: To be loaded correctly the class should follow the ClassesLoader specification. - To define the settings, only define a class with same name plus 'Settings' - as suffix (e.g. Protocol -> ProtocolSettings), in the same file. """ + CueSettings = None + LayoutSettings = None + def __init__(self): self.protocol_event = Signal() diff --git a/lisp/plugins/gst_backend/elements/alsa_sink.py b/lisp/plugins/gst_backend/elements/alsa_sink.py index 2a2bb610f..ee0926a5a 100644 --- a/lisp/plugins/gst_backend/elements/alsa_sink.py +++ b/lisp/plugins/gst_backend/elements/alsa_sink.py @@ -34,8 +34,28 @@ class AlsaSink(GstMediaElement): def __init__(self, pipeline): super().__init__(pipeline) + self.audio_resample = Gst.ElementFactory.make('audioresample', None) self.alsa_sink = Gst.ElementFactory.make('alsasink', 'sink') + + self.pipeline.add(self.audio_resample) self.pipeline.add(self.alsa_sink) + self.audio_resample.link(self.alsa_sink) + + self.changed('device').connect(self._update_device) + def sink(self): - return self.alsa_sink + return self.audio_resample + + def _update_device(self, new_device): + # Remove and dispose element + self.audio_resample.unlink(self.alsa_sink) + self.pipeline.remove(self.alsa_sink) + self.alsa_sink.set_state(Gst.State.NULL) + + # Create new element and add it to the pipeline + self.alsa_sink = Gst.ElementFactory.make('alsasink', 'sink') + self.alsa_sink.set_property('device', new_device) + + self.pipeline.add(self.alsa_sink) + self.audio_resample.link(self.alsa_sink) diff --git a/lisp/plugins/gst_backend/settings/uri_input.py b/lisp/plugins/gst_backend/settings/uri_input.py index 5b584413f..34552e429 100644 --- a/lisp/plugins/gst_backend/settings/uri_input.py +++ b/lisp/plugins/gst_backend/settings/uri_input.py @@ -95,6 +95,9 @@ def getSettings(self): def loadSettings(self, settings): self.filePath.setText(settings.get('uri', '')) + self.useBuffering.setChecked(settings.get('use_buffering', False)) + self.download.setChecked(settings.get('download', False)) + self.bufferSize.setValue(settings.get('buffer_size', -1)) def enableCheck(self, enabled): self.fileGroup.setCheckable(enabled) diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index 2f5b414e1..ab7e6fd95 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -325,7 +325,7 @@ def __cue_next(self, cue): next_index = cue.index + 1 if next_index < len(self._list_model): next_cue = self._list_model.item(next_index) - next_cue.cue.execute() + next_cue.execute() if self.auto_continue and next_cue is self.standby_cue(): self.set_standby_index(next_index + 1) diff --git a/lisp/plugins/osc/osc_server.py b/lisp/plugins/osc/osc_server.py index f1934a5e0..3bb61b73b 100644 --- a/lisp/plugins/osc/osc_server.py +++ b/lisp/plugins/osc/osc_server.py @@ -26,73 +26,6 @@ from lisp.core.signal import Signal -'''def callback_go(_, args, types): - if not isinstance(Application().layout, ListLayout): - return - - if (types == 'i' and args[0] == 1) or types == '': - Application().layout.go() - - -def callback_reset(_, args, types): - if not isinstance(Application().layout, ListLayout): - return - - if (types == 'i' and args[0] == 1) or types == '': - Application().layout.interrupt_all() - Application().layout.set_current_index(0) - - -def callback_restart(_, args, types): - if not isinstance(Application().layout, ListLayout): - return - - if (types == 'i' and args[0] == 1) or types == '': - Application().layout.resume_all() - - -def callback_pause(_, args, types): - if not isinstance(Application().layout, ListLayout): - return - - if (types == 'i' and args[0] == 1) or types == '': - Application().layout.pause_all() - - -def callback_stop(_, args, types): - if not isinstance(Application().layout, ListLayout): - return - - if (types == 'i' and args[0] == 1) or types == '': - Application().layout.stop_all() - - -def callback_select(_, args, types): - if not isinstance(Application().layout, ListLayout): - return - - if types == 'i' and args[0] > -1: - Application().layout.set_current_index(args[0]) - - -def callback_interrupt(_, args, types): - if not isinstance(Application().layout, ListLayout): - return - - if (types == 'i' and args[0] == 1) or types == '': - Application().layout.interrupt_all() - - -GLOBAL_CALLBACKS = [ - ['/lisp/list/go', None, callback_go], - ['/lisp/list/reset', None, callback_reset], - ['/lisp/list/select', 'i', callback_select], - ['/lisp/list/pause', None, callback_pause], - ['/lisp/list/restart', None, callback_restart], - ['/lisp/list/stop', None, callback_stop], - ['/lisp/list/interrupt', None, callback_interrupt] -]''' - logger = logging.getLogger(__name__) @@ -152,6 +85,7 @@ def start(self): try: self.__srv = ServerThread(self.__in_port) + self.__srv.add_method(None, None, self.__log_message) self.__srv.add_method(None, None, self.new_message.emit) self.__srv.start() @@ -176,3 +110,10 @@ def send(self, path, *args): with self.__lock: if self.__running: self.__srv.send((self.__hostname, self.__out_port), path, *args) + + def __log_message(self, path, args, types, src, user_data): + logger.debug( + 'Message from {} -> path: "{}" args: {}'.format( + src.get_url(), path, args + ) + ) diff --git a/lisp/plugins/timecode/settings.py b/lisp/plugins/timecode/settings.py index 5da574326..fcbb8069b 100644 --- a/lisp/plugins/timecode/settings.py +++ b/lisp/plugins/timecode/settings.py @@ -32,8 +32,8 @@ class TimecodeSettings(CueSettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Timecode') - def __init__(self, cue_type, **kwargs): - super().__init__(cue_type, **kwargs) + def __init__(self, cueType, **kwargs): + super().__init__(cueType, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 0981a321f..10970c5dd 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -340,7 +340,7 @@ def _exit(self): qApp.quit() def _edit_selected_cue(self): - self.session.layout.edit_selected_cues() + self.session.layout.edit_cues(list(self.session.layout.selected_cues())) def _select_all(self): self.session.layout.select_all() diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index 74eeb2489..b8f06dec6 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -26,6 +26,8 @@ from lisp.ui.qmodels import CueClassRole from lisp.ui.ui_utils import translate from lisp.ui.widgets import CueActionComboBox +from lisp.ui.widgets.cue_actions import tr_action +from lisp.ui.widgets.qenumcombobox import QEnumComboBox class LabelDelegate(QStyledItemDelegate): @@ -161,57 +163,71 @@ def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) -class CueActionDelegate(LabelDelegate): - Mode = CueActionComboBox.Mode +class EnumComboBoxDelegate(LabelDelegate): + Mode = QEnumComboBox.Mode - def __init__(self, cue_class=None, mode=Mode.Action, **kwargs): + def __init__(self, enum, mode=Mode.Enum, trItem=str, **kwargs): super().__init__(**kwargs) - self.cue_class = cue_class + self.enum = enum self.mode = mode + self.trItem = trItem def _text(self, painter, option, index): - value = index.data(Qt.EditRole) - if self.mode == CueActionDelegate.Mode.Action: - name = value.name - elif self.mode == CueActionDelegate.Mode.Name: - name = value - else: - name = CueAction(value).name - - return translate('CueAction', name) + return self.trItem(self.itemFromData(index.data(Qt.EditRole))) def paint(self, painter, option, index): option.displayAlignment = Qt.AlignHCenter | Qt.AlignVCenter super().paint(painter, option, index) def createEditor(self, parent, option, index): - if self.cue_class is None: - self.cue_class = index.data(CueClassRole) - - editor = CueActionComboBox(self.cue_class.CueActions, - mode=self.mode, - parent=parent) + editor = QEnumComboBox( + self.enum, + mode=self.mode, + trItem=self.trItem, + parent=parent + ) editor.setFrame(False) return editor - def setEditorData(self, comboBox, index): - value = index.data(Qt.EditRole) - if self.mode == CueActionDelegate.Mode.Action: - action = value - elif self.mode == CueActionDelegate.Mode.Name: - action = CueAction[value] - else: - action = CueAction(value) + def setEditorData(self, editor, index): + editor.setCurrentItem(index.data(Qt.EditRole)) - comboBox.setCurrentText(translate('CueAction', action.name)) - - def setModelData(self, comboBox, model, index): - model.setData(index, comboBox.currentData(), Qt.EditRole) + def setModelData(self, editor, model, index): + model.setData(index, editor.currentData(), Qt.EditRole) def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) + def itemFromData(self, data): + if self.mode == EnumComboBoxDelegate.Mode.Name: + return self.enum[data] + elif self.mode == EnumComboBoxDelegate.Mode.Value: + return self.enum(data) + + return data + + +class CueActionDelegate(EnumComboBoxDelegate): + Mode = CueActionComboBox.Mode + + def __init__(self, cue_class=None, **kwargs): + super().__init__(CueAction, trItem=tr_action, **kwargs) + self.cue_class = cue_class + + def createEditor(self, parent, option, index): + if self.cue_class is None: + self.cue_class = index.data(CueClassRole) + + editor = CueActionComboBox( + self.cue_class.CueActions, + mode=self.mode, + parent=parent + ) + editor.setFrame(False) + + return editor + class CueSelectionDelegate(LabelDelegate): def __init__(self, cue_select_dialog, **kwargs): diff --git a/lisp/ui/settings/app_configuration.py b/lisp/ui/settings/app_configuration.py index 111fe7727..63dd559f9 100644 --- a/lisp/ui/settings/app_configuration.py +++ b/lisp/ui/settings/app_configuration.py @@ -42,9 +42,9 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.setWindowTitle(translate('AppConfiguration', 'LiSP preferences')) self.setWindowModality(QtCore.Qt.WindowModal) - self.setMaximumSize(640, 510) - self.setMinimumSize(640, 510) - self.resize(640, 510) + self.setMaximumSize(800, 510) + self.setMinimumSize(800, 510) + self.resize(800, 510) self.setLayout(QVBoxLayout()) self.model = PagesTreeModel() diff --git a/lisp/ui/settings/cue_pages/cue_general.py b/lisp/ui/settings/cue_pages/cue_general.py index 19f2f70a4..9c8af6e3c 100644 --- a/lisp/ui/settings/cue_pages/cue_general.py +++ b/lisp/ui/settings/cue_pages/cue_general.py @@ -31,18 +31,18 @@ class CueGeneralSettings(TabsMultiSettingsPage, CuePageMixin): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Cue') - def __init__(self, cue_type, **kwargs): - super().__init__(cue_type=cue_type, **kwargs) - self.addPage(CueBehavioursPage(self.cue_type)) - self.addPage(CueWaitsPage(self.cue_type)) - self.addPage(CueFadePage(self.cue_type)) + def __init__(self, cueType, **kwargs): + super().__init__(cueType=cueType, **kwargs) + self.addPage(CueBehavioursPage(self.cueType)) + self.addPage(CueWaitsPage(self.cueType)) + self.addPage(CueFadePage(self.cueType)) class CueBehavioursPage(CueSettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Behaviours') - def __init__(self, cue_type, **kwargs): - super().__init__(cue_type, **kwargs) + def __init__(self, cueType, **kwargs): + super().__init__(cueType, **kwargs) self.setLayout(QVBoxLayout()) # Start-Action @@ -54,7 +54,7 @@ def __init__(self, cue_type, **kwargs): { CueAction.Start, CueAction.FadeInStart - }.intersection(self.cue_type.CueActions).union({ + }.intersection(self.cueType.CueActions).union({ CueAction.DoNothing }), mode=CueActionComboBox.Mode.Value, @@ -78,7 +78,7 @@ def __init__(self, cue_type, **kwargs): CueAction.Pause, CueAction.FadeOutStop, CueAction.FadeOutPause - }.intersection(self.cue_type.CueActions).union({ + }.intersection(self.cueType.CueActions).union({ CueAction.DoNothing }), mode=CueActionComboBox.Mode.Value, @@ -124,25 +124,25 @@ def getSettings(self): if ((not checkable or self.startActionGroup.isChecked()) and self.startActionCombo.isEnabled()): - settings['default_start_action'] = self.startActionCombo.currentAction() + settings['default_start_action'] = self.startActionCombo.currentItem() if ((not checkable or self.stopActionGroup.isChecked()) and self.stopActionCombo.isEnabled()): - settings['default_stop_action'] = self.stopActionCombo.currentAction() + settings['default_stop_action'] = self.stopActionCombo.currentItem() return settings def loadSettings(self, settings): - self.startActionCombo.setCurrentAction( + self.startActionCombo.setCurrentItem( settings.get('default_start_action', '')) - self.stopActionCombo.setCurrentAction( + self.stopActionCombo.setCurrentItem( settings.get('default_stop_action', '')) class CueWaitsPage(CueSettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Pre/Post Wait') - def __init__(self, cue_type, **kwargs): - super().__init__(cue_type=cue_type, **kwargs) + def __init__(self, cueType, **kwargs): + super().__init__(cueType=cueType, **kwargs) self.setLayout(QVBoxLayout()) # Pre wait @@ -224,14 +224,14 @@ def getSettings(self): class CueFadePage(CueSettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Fade In/Out') - def __init__(self, cue_type, **kwargs): - super().__init__(cue_type, **kwargs) + def __init__(self, cueType, **kwargs): + super().__init__(cueType, **kwargs) self.setLayout(QVBoxLayout()) # FadeIn self.fadeInGroup = QGroupBox(self) self.fadeInGroup.setEnabled( - CueAction.FadeInStart in cue_type.CueActions + CueAction.FadeInStart in cueType.CueActions ) self.fadeInGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.fadeInGroup) @@ -243,8 +243,8 @@ def __init__(self, cue_type, **kwargs): # FadeOut self.fadeOutGroup = QGroupBox(self) self.fadeOutGroup.setEnabled( - CueAction.FadeOutPause in cue_type.CueActions or - CueAction.FadeOutStop in cue_type.CueActions + CueAction.FadeOutPause in cueType.CueActions or + CueAction.FadeOutStop in cueType.CueActions ) self.fadeOutGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.fadeOutGroup) diff --git a/lisp/ui/settings/cue_settings.py b/lisp/ui/settings/cue_settings.py index f2da3df4c..c469575f8 100644 --- a/lisp/ui/settings/cue_settings.py +++ b/lisp/ui/settings/cue_settings.py @@ -24,17 +24,12 @@ from lisp.core.singleton import Singleton from lisp.core.util import typename from lisp.cues.cue import Cue -from lisp.ui.settings.pages import SettingsPage, CuePageMixin, \ - TabsMultiSettingsPage +from lisp.ui.settings.pages import CuePageMixin, TabsMultiSettingsPage from lisp.ui.ui_utils import translate class CueSettingsRegistry(ClassBasedRegistry, metaclass=Singleton): def add(self, item, ref_class=Cue): - if not issubclass(item, SettingsPage): - raise TypeError( - 'item must be a SettingPage, not {}'.format(item.__name__)) - return super().add(item, ref_class) def filter(self, ref_class=Cue): diff --git a/lisp/ui/settings/pages.py b/lisp/ui/settings/pages.py index 41b15e6f3..d640ff03a 100644 --- a/lisp/ui/settings/pages.py +++ b/lisp/ui/settings/pages.py @@ -20,19 +20,21 @@ from abc import abstractmethod from PyQt5.QtCore import QModelIndex, Qt -from PyQt5.QtWidgets import QWidget, QTabWidget, QVBoxLayout, QTreeView, QGridLayout, QSizePolicy +from PyQt5.QtWidgets import QWidget, QTabWidget, QVBoxLayout, QTreeView, \ + QGridLayout, QSizePolicy from lisp.core.qmeta import QABCMeta from lisp.core.util import dict_merge, typename from lisp.ui.ui_utils import translate -class ABCSettingsPage(QWidget, metaclass=QABCMeta): - Name = 'ABCSettingPage' +class ABCPage(QWidget, metaclass=QABCMeta): + Name = 'PageName' -class SettingsPage(ABCSettingsPage): - Name = 'SettingsPage' +class SettingsPage(ABCPage): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) @abstractmethod def loadSettings(self, settings): @@ -66,15 +68,13 @@ def enableCheck(self, enabled): """ -class ConfigurationPage(ABCSettingsPage): - Name = 'ConfigurationPage' - - def __init__(self, config, **kwargs): +class ConfigurationPage(ABCPage): + def __init__(self, config, *args, **kwargs): """ :param config: Configuration object to "edit" :type config: lisp.core.configuration.Configuration """ - super().__init__(**kwargs) + super().__init__(*args, **kwargs) self.config = config @abstractmethod @@ -83,19 +83,17 @@ def applySettings(self): class CuePageMixin: - Name = 'CueSettingsPage' - - def __init__(self, cue_type): - self.cue_type = cue_type + def __init__(self, cueType, *args, **kwargs): + super().__init__(*args, **kwargs) + self.cueType = cueType -class CueSettingsPage(SettingsPage, CuePageMixin): +class CueSettingsPage(CuePageMixin, SettingsPage): + def __init__(self, cueType, **kwargs): + super().__init__(cueType=cueType, **kwargs) - def __init__(self, cue_type, **kwargs): - super().__init__(cue_type=cue_type, **kwargs) - -class TabsMultiPage(QWidget): +class TabsMultiPage(ABCPage): def __init__(self, **kwargs): super().__init__(**kwargs) self.setLayout(QVBoxLayout()) @@ -110,16 +108,8 @@ def page(self, index): return self._pages[index] def addPage(self, page): - if isinstance(page, ABCSettingsPage): - self._pages.append(page) - self.tabWidget.addTab( - page, translate('SettingsPageName', page.Name)) - else: - raise TypeError( - 'page must be an {}, not {}'.format( - self._PagesBaseClass.__name__, - typename(page)) - ) + self._pages.append(page) + self.tabWidget.addTab(page, translate('SettingsPageName', page.Name)) def removePage(self, index): self.tabWidget.removeTab(index) @@ -132,7 +122,7 @@ def pageIndex(self, page): return self._pages.index(page) -class TabsMultiSettingsPage(TabsMultiPage, SettingsPage): +class TabsMultiSettingsPage(TabsMultiPage): def loadSettings(self, settings): for page in self._pages: page.loadSettings(settings) @@ -149,14 +139,13 @@ def enableCheck(self, enabled): page.enableCheck(enabled) -class TabsMultiConfigurationPage(TabsMultiPage, ConfigurationPage): +class TabsMultiConfigurationPage(TabsMultiPage): def applySettings(self): for page in self._pages: page.applySettings() -class TreeMultiPagesWidget(QWidget): - +class TreeMultiPagesWidget(ABCPage): def __init__(self, navModel, **kwargs): """ :param navModel: The model that keeps all the pages-hierarchy @@ -231,4 +220,4 @@ def enableCheck(self, enabled): root = self.navModel.node(QModelIndex()) for node in root.walk(): if node.page is not None: - node.page.enableCheck() + node.page.enableCheck(enabled) diff --git a/lisp/ui/settings/pages_tree_model.py b/lisp/ui/settings/pages_tree_model.py index a54e7b55e..fc0598fd0 100644 --- a/lisp/ui/settings/pages_tree_model.py +++ b/lisp/ui/settings/pages_tree_model.py @@ -20,7 +20,7 @@ from PyQt5.QtCore import QAbstractItemModel, Qt, QModelIndex from lisp.core.util import typename -from lisp.ui.settings.pages import ABCSettingsPage +from lisp.ui.settings.pages import ABCPage class PageNode: @@ -126,21 +126,15 @@ def pageIndex(self, page, parent=QModelIndex()): return QModelIndex() def addPage(self, page, parent=QModelIndex()): - if isinstance(page, ABCSettingsPage): - parentNode = self.node(parent) - position = parentNode.childCount() + parentNode = self.node(parent) + position = parentNode.childCount() - self.beginInsertRows(parent, position, position) - node = PageNode(page) - parentNode.addChild(node) - self.endInsertRows() + self.beginInsertRows(parent, position, position) + node = PageNode(page) + parentNode.addChild(node) + self.endInsertRows() - return self.index(position, 0, parent) - else: - raise TypeError( - 'SettingsPagesTreeModel page must be an ABCSettingsPage, not {}' - .format(typename(page)) - ) + return self.index(position, 0, parent) def removePage(self, row, parent=QModelIndex()): parentNode = self.node(parent) diff --git a/lisp/ui/ui_utils.py b/lisp/ui/ui_utils.py index a202a6ee0..faa07d451 100644 --- a/lisp/ui/ui_utils.py +++ b/lisp/ui/ui_utils.py @@ -16,12 +16,17 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import fcntl + +import os import logging from itertools import chain + +import signal from os import path -from PyQt5.QtCore import QTranslator, QLocale +from PyQt5.QtCore import QTranslator, QLocale, QSocketNotifier from PyQt5.QtWidgets import QApplication, qApp from lisp import I18N_PATH @@ -125,3 +130,57 @@ def tr_key(item): translate(context, item) return sorted(iterable, key=tr_key, reverse=reverse) + + +class PyQtUnixSignalHandler: + """ + Some magic horror to allow Python to execute signal handlers, this + works only on posix systems where non-blocking anonymous pipe or socket are + available. + Can be used as a context manager, but after that is not reusable. + + From here: https://bitbucket.org/tortoisehg/thg/commits/550e1df5fbad + """ + + def __init__(self): + # Create a non-blocking pipe + self._rfd, self._wfd = os.pipe() + for fd in (self._rfd, self._wfd): + flags = fcntl.fcntl(fd, fcntl.F_GETFL) + fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) + + # QSocketNotifier will look if something is written in the pipe + # and call the `_handle` method + self._notifier = QSocketNotifier(self._rfd, QSocketNotifier.Read) + self._notifier.activated.connect(self._handle) + # Tell Python to write to the pipe when there is a signal to handle + self._orig_wfd = signal.set_wakeup_fd(self._wfd) + + def release(self): + # Stop the notifier + self._notifier.setEnabled(False) + # Restore the original descriptor + signal.set_wakeup_fd(self._orig_wfd) + + # Cleanup + self._orig_wfd = None + os.close(self._rfd) + os.close(self._wfd) + + def _handle(self): + # Here Python signal handler will be invoked + # We disable the notifier while doing so + self._notifier.setEnabled(False) + + try: + os.read(self._rfd, 1) + except OSError: + pass + + self._notifier.setEnabled(True) + + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_val, exc_tb): + self.release() diff --git a/lisp/ui/widgets/cue_actions.py b/lisp/ui/widgets/cue_actions.py index f3d3834e7..fe9890985 100644 --- a/lisp/ui/widgets/cue_actions.py +++ b/lisp/ui/widgets/cue_actions.py @@ -17,14 +17,11 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from enum import Enum - from PyQt5.QtCore import QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QComboBox from lisp.cues.cue import CueAction from lisp.ui.ui_utils import translate - +from lisp.ui.widgets.qenumcombobox import QEnumComboBox CueActionsStrings = { CueAction.Default: QT_TRANSLATE_NOOP('CueAction', 'Default'), @@ -54,40 +51,6 @@ def tr_action(action): 'CueAction', CueActionsStrings.get(action, action.name)) -class CueActionComboBox(QComboBox): - class Mode(Enum): - Action = 0 - Value = 1 - Name = 2 - - def __init__(self, actions, mode=Mode.Action, **kwargs): - super().__init__(**kwargs) - self.mode = mode - self.rebuild(actions) - - def rebuild(self, actions): - self.clear() - - for action in actions: - if self.mode is CueActionComboBox.Mode.Value: - value = action.value - elif self.mode is CueActionComboBox.Mode.Name: - value = action.name - else: - value = action - - self.addItem(tr_action(action), value) - - def currentAction(self): - return self.currentData() - - def setCurrentAction(self, action): - try: - if self.mode is CueActionComboBox.Mode.Value: - action = CueAction(action) - elif self.mode is CueActionComboBox.Mode.Name: - action = CueAction[action] - - self.setCurrentText(tr_action(action)) - except(ValueError, KeyError): - pass +class CueActionComboBox(QEnumComboBox): + def __init__(self, actions=(), **kwargs): + super().__init__(CueAction, items=actions, trItem=tr_action, **kwargs) diff --git a/lisp/ui/widgets/qenumcombobox.py b/lisp/ui/widgets/qenumcombobox.py new file mode 100644 index 000000000..b4119bc46 --- /dev/null +++ b/lisp/ui/widgets/qenumcombobox.py @@ -0,0 +1,44 @@ +from PyQt5.QtWidgets import QComboBox +from enum import Enum + + +class QEnumComboBox(QComboBox): + class Mode(Enum): + Enum = 0 + Value = 1 + Name = 2 + + def __init__(self, enum, items=(), mode=Mode.Enum, trItem=str, **kwargs): + super().__init__(**kwargs) + self.enum = enum + self.mode = mode + self.trItem = trItem + + self.setItems(items if items else enum) + + def setItems(self, items): + self.clear() + + for item in items: + if self.mode is QEnumComboBox.Mode.Value: + value = item.value + elif self.mode is QEnumComboBox.Mode.Name: + value = item.name + else: + value = item + + self.addItem(self.trItem(item), value) + + def currentItem(self): + return self.currentData() + + def setCurrentItem(self, item): + try: + if self.mode is QEnumComboBox.Mode.Value: + item = self.enum(item) + elif self.mode is QEnumComboBox.Mode.Name: + item = self.enum[item] + + self.setCurrentText(self.trItem(item)) + except(ValueError, KeyError): + pass From 29ef1940599d142731e50d43921b819bfed54ff4 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 3 Oct 2018 09:28:10 +0200 Subject: [PATCH 129/333] Modified `ConfigurationPage`(s) into `SettingsPage`(s), the log viewer now show records details, including exception traceback. Update: minor changes to the dark theme Update: update Pipfile.lock Update: moved/renamed some module --- Pipfile.lock | 9 +- lisp/application.py | 14 +-- lisp/core/configuration.py | 29 ----- lisp/core/util.py | 2 +- lisp/plugins/cart_layout/settings.py | 50 +++++---- lisp/plugins/controller/controller.py | 4 +- .../plugins/controller/controller_settings.py | 25 ++--- .../controller/{protocols => }/protocol.py | 0 lisp/plugins/controller/protocols/keyboard.py | 2 +- lisp/plugins/controller/protocols/midi.py | 2 +- lisp/plugins/controller/protocols/osc.py | 5 +- lisp/plugins/gst_backend/gst_settings.py | 19 ++-- lisp/plugins/list_layout/__init__.py | 2 +- lisp/plugins/list_layout/settings.py | 74 ++++++------- lisp/plugins/midi/midi_settings.py | 39 ++++--- lisp/plugins/osc/osc_server.py | 2 +- lisp/plugins/osc/osc_settings.py | 30 +++--- lisp/plugins/timecode/settings.py | 36 ++++--- lisp/ui/logging/common.py | 1 + lisp/ui/logging/details.py | 54 ++++++++++ lisp/ui/logging/viewer.py | 101 ++++++++++-------- lisp/ui/mainwindow.py | 2 +- lisp/ui/settings/app_configuration.py | 50 +++++---- .../app_pages/{cue_app_settings.py => cue.py} | 35 +++--- .../app_pages/{app_general.py => general.py} | 39 ++++--- .../{layouts_settings.py => layouts.py} | 46 ++++---- .../{plugins_settings.py => plugins.py} | 11 +- lisp/ui/settings/cue_pages/cue_general.py | 4 +- .../{media_cue_settings.py => media_cue.py} | 0 lisp/ui/settings/cue_settings.py | 6 +- lisp/ui/settings/pages.py | 99 ++--------------- lisp/ui/themes/__init__.py | 4 +- lisp/ui/themes/dark/dark.py | 4 +- lisp/ui/themes/dark/theme.qss | 10 +- lisp/ui/themes/theme.py | 25 ----- .../pagestreewidget.py} | 55 +++++++++- 36 files changed, 436 insertions(+), 454 deletions(-) rename lisp/plugins/controller/{protocols => }/protocol.py (100%) create mode 100644 lisp/ui/logging/details.py rename lisp/ui/settings/app_pages/{cue_app_settings.py => cue.py} (68%) rename lisp/ui/settings/app_pages/{app_general.py => general.py} (79%) rename lisp/ui/settings/app_pages/{layouts_settings.py => layouts.py} (71%) rename lisp/ui/settings/app_pages/{plugins_settings.py => plugins.py} (92%) rename lisp/ui/settings/cue_pages/{media_cue_settings.py => media_cue.py} (100%) delete mode 100644 lisp/ui/themes/theme.py rename lisp/ui/{settings/pages_tree_model.py => widgets/pagestreewidget.py} (68%) diff --git a/Pipfile.lock b/Pipfile.lock index f6fc4e7bf..4b9686be5 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -104,16 +104,17 @@ }, "pycparser": { "hashes": [ - "sha256:99a8ca03e29851d96616ad0404b4aad7d9ee16f25c9f9708a11faf2810f7b226" + "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" ], - "version": "==2.18" + "markers": "python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.1.*' and python_version >= '2.7'", + "version": "==2.19" }, "pygobject": { "hashes": [ - "sha256:c5c888068b96d55aa8efcbf1253fe824c74462169d80c0333967e295aabbee5e" + "sha256:5e642a76cfddd3e488a32bcdf7cef189e29226522be8726bc0e050dd53fa2d1c" ], "index": "pypi", - "version": "==3.30.0" + "version": "==3.30.1" }, "pyliblo": { "hashes": [ diff --git a/lisp/application.py b/lisp/application.py index 7db22278a..93eeb1dd9 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -35,13 +35,13 @@ from lisp.ui.layoutselect import LayoutSelect from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.app_configuration import AppConfigurationDialog -from lisp.ui.settings.app_pages.app_general import AppGeneral -from lisp.ui.settings.app_pages.cue_app_settings import CueAppSettings -from lisp.ui.settings.app_pages.layouts_settings import LayoutsSettings -from lisp.ui.settings.app_pages.plugins_settings import PluginsSettings +from lisp.ui.settings.app_pages.general import AppGeneral +from lisp.ui.settings.app_pages.cue import CueAppSettings +from lisp.ui.settings.app_pages.layouts import LayoutsSettings +from lisp.ui.settings.app_pages.plugins import PluginsSettings from lisp.ui.settings.cue_pages.cue_appearance import Appearance -from lisp.ui.settings.cue_pages.cue_general import CueGeneralSettings -from lisp.ui.settings.cue_pages.media_cue_settings import MediaCueSettings +from lisp.ui.settings.cue_pages.cue_general import CueGeneralSettingsPage +from lisp.ui.settings.cue_pages.media_cue import MediaCueSettings from lisp.ui.settings.cue_settings import CueSettingsRegistry logger = logging.getLogger(__name__) @@ -69,7 +69,7 @@ def __init__(self, app_conf): 'plugins', PluginsSettings, self.conf) # Register common cue-settings widgets - CueSettingsRegistry().add(CueGeneralSettings, Cue) + CueSettingsRegistry().add(CueGeneralSettingsPage, Cue) CueSettingsRegistry().add(MediaCueSettings, MediaCue) CueSettingsRegistry().add(Appearance) diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index dfb37b963..b68a561ea 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -184,35 +184,6 @@ def write(self): pass -class SubConfiguration(Configuration): - """Provide a view on a parent configuration "section" - - If a parent configuration is reloaded all related SubConfiguration - should be reloaded, using the `read` method, or it may - (depending on the parent implementation) hold an invalid reference to the - parent "sub-section". - - The `write` method is a no-op, you need to call the parent one to actually - do something. - """ - - def __init__(self, parent, root_path): - """ - :param parent: The parent configuration - :type parent: Configuration - :param root_path: The path of the parent "section" that will be visible - """ - super().__init__(root=parent.get(root_path)) - self._root_path = root_path - self._parent = parent - - def read(self): - self._root = self._parent.get(self._root_path) - - def write(self): - pass - - class JSONFileConfiguration(Configuration): """Read/Write configurations from/to a JSON file. diff --git a/lisp/core/util.py b/lisp/core/util.py index c357d3894..b0c04c91d 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -273,7 +273,7 @@ def __eq__(self, other): class FunctionProxy: - """Allow to mask a function as an Object. + """Encapsulate a function into an object. Can be useful in enum.Enum (Python enumeration) to have callable values. """ diff --git a/lisp/plugins/cart_layout/settings.py b/lisp/plugins/cart_layout/settings.py index 8e7ac82e9..e187c4b7a 100644 --- a/lisp/plugins/cart_layout/settings.py +++ b/lisp/plugins/cart_layout/settings.py @@ -21,16 +21,16 @@ from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QGridLayout, \ QSpinBox, QLabel -from lisp.ui.settings.pages import ConfigurationPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate -class CartLayoutSettings(ConfigurationPage): +class CartLayoutSettings(SettingsPage): Name = 'Cart Layout' - def __init__(self, config, **kwargs): - super().__init__(config, **kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -76,15 +76,6 @@ def __init__(self, config, **kwargs): self.retranslateUi() - # Load data - self.columnsSpin.setValue(config['grid.columns']) - self.rowsSpin.setValue(config['grid.rows']) - self.showSeek.setChecked(config['show.seekSliders']) - self.showDbMeters.setChecked(config['show.dBMeters']) - self.showAccurate.setChecked(config['show.accurateTime']) - self.showVolume.setChecked(config['show.volumeControls']) - self.countdownMode.setChecked(config['countdownMode']) - def retranslateUi(self): self.behaviorsGroup.setTitle( translate('CartLayout', 'Default behaviors')) @@ -97,13 +88,26 @@ def retranslateUi(self): self.columnsLabel.setText(translate('CartLayout', 'Number of columns')) self.rowsLabel.setText(translate('CartLayout', 'Number of rows')) - def applySettings(self): - self.config['grid.columns'] = self.columnsSpin.value() - self.config['grid.rows'] = self.rowsSpin.value() - self.config['show.dBMeters'] = self.showDbMeters.isChecked() - self.config['show.seekSliders'] = self.showSeek.isChecked() - self.config['show.accurateTime'] = self.showAccurate.isChecked() - self.config['show.volumeControls'] = self.showVolume.isChecked() - self.config['countdownMode'] = self.countdownMode.isChecked() - - self.config.write() + def loadSettings(self, settings): + self.columnsSpin.setValue(settings['grid']['columns']) + self.rowsSpin.setValue(settings['grid']['rows']) + self.showSeek.setChecked(settings['show']['seekSliders']) + self.showDbMeters.setChecked(settings['show']['dBMeters']) + self.showAccurate.setChecked(settings['show']['accurateTime']) + self.showVolume.setChecked(settings['show']['volumeControls']) + self.countdownMode.setChecked(settings['countdownMode']) + + def getSettings(self): + return { + 'grid': { + 'columns': self.columnsSpin.value(), + 'rows': self.rowsSpin.value() + }, + 'show': { + 'dBMeters': self.showDbMeters.isChecked(), + 'seekSliders': self.showSeek.isChecked(), + 'accurateTime': self.showAccurate.isChecked(), + 'volumeControls': self.showVolume.isChecked() + }, + 'countdownMode': self.countdownMode.isChecked() + } diff --git a/lisp/plugins/controller/controller.py b/lisp/plugins/controller/controller.py index fb10d36b5..b2a7bf84e 100644 --- a/lisp/plugins/controller/controller.py +++ b/lisp/plugins/controller/controller.py @@ -24,7 +24,7 @@ from lisp.cues.cue import Cue, CueAction from lisp.plugins.controller import protocols from lisp.plugins.controller.common import LayoutAction -from lisp.plugins.controller.controller_settings import CueControllerSettings, ControllerLayoutConfiguration +from lisp.plugins.controller.controller_settings import CueControllerSettingsPage, ControllerLayoutConfiguration from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.ui_utils import translate @@ -61,7 +61,7 @@ def __init__(self, app): AppConfigurationDialog.registerSettingsPage( 'plugins.controller', ControllerLayoutConfiguration, Controller.Config) # Register settings-page - CueSettingsRegistry().add(CueControllerSettings) + CueSettingsRegistry().add(CueControllerSettingsPage) # Load available protocols self.__load_protocols() diff --git a/lisp/plugins/controller/controller_settings.py b/lisp/plugins/controller/controller_settings.py index b94d6fb5e..119dde09a 100644 --- a/lisp/plugins/controller/controller_settings.py +++ b/lisp/plugins/controller/controller_settings.py @@ -20,11 +20,10 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.plugins.controller import protocols -from lisp.ui.settings.pages import TabsMultiSettingsPage, CuePageMixin, \ - TabsMultiConfigurationPage, ConfigurationPage +from lisp.ui.settings.pages import SettingsPagesTabWidget, CuePageMixin -class CueControllerSettings(TabsMultiSettingsPage, CuePageMixin): +class CueControllerSettingsPage(SettingsPagesTabWidget, CuePageMixin): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Cue Control') def __init__(self, cueType, **kwargs): @@ -40,21 +39,17 @@ def loadSettings(self, settings): super().loadSettings(settings.get('controller', {})) -class ControllerLayoutConfiguration(TabsMultiConfigurationPage, ConfigurationPage): +class ControllerLayoutConfiguration(SettingsPagesTabWidget): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Layout Controls') - def __init__(self, config, **kwargs): - super().__init__(config=config, **kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) for page in protocols.LayoutSettingsPages: - newPage = page(parent=self) - newPage.loadSettings(config['protocols']) - self.addPage(newPage) + self.addPage(page(parent=self)) - def applySettings(self): - protocolsConf = {} - for page in self.iterPages(): - protocolsConf.update(page.getSettings()) + def loadSettings(self, settings): + super().loadSettings(settings['protocols']) - self.config.update({'protocols': protocolsConf}) - self.config.write() + def getSettings(self): + return {'protocols': super().getSettings()} diff --git a/lisp/plugins/controller/protocols/protocol.py b/lisp/plugins/controller/protocol.py similarity index 100% rename from lisp/plugins/controller/protocols/protocol.py rename to lisp/plugins/controller/protocol.py diff --git a/lisp/plugins/controller/protocols/keyboard.py b/lisp/plugins/controller/protocols/keyboard.py index bbf7fb87d..68837f418 100644 --- a/lisp/plugins/controller/protocols/keyboard.py +++ b/lisp/plugins/controller/protocols/keyboard.py @@ -23,7 +23,7 @@ from lisp.application import Application from lisp.plugins.controller.common import LayoutAction, tr_layout_action -from lisp.plugins.controller.protocols.protocol import Protocol +from lisp.plugins.controller.protocol import Protocol from lisp.ui.qdelegates import LineEditDelegate, CueActionDelegate,\ EnumComboBoxDelegate from lisp.ui.qmodels import SimpleTableModel diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index 8360ec262..d234eaccb 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -23,7 +23,7 @@ from lisp.plugins import get_plugin, PluginNotLoadedError from lisp.plugins.controller.common import LayoutAction, tr_layout_action -from lisp.plugins.controller.protocols.protocol import Protocol +from lisp.plugins.controller.protocol import Protocol from lisp.ui.qdelegates import ComboBoxDelegate, SpinBoxDelegate, \ CueActionDelegate, EnumComboBoxDelegate from lisp.ui.qmodels import SimpleTableModel diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 58cd436e9..ffe92bef0 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -20,7 +20,6 @@ import ast - from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QGroupBox, QPushButton, QVBoxLayout, \ QTableView, QTableWidget, QHeaderView, QGridLayout, QLabel, \ @@ -28,12 +27,12 @@ from lisp.plugins import get_plugin, PluginNotLoadedError from lisp.plugins.controller.common import LayoutAction, tr_layout_action -from lisp.plugins.controller.protocols.protocol import Protocol +from lisp.plugins.controller.protocol import Protocol from lisp.plugins.osc.osc_delegate import OscArgumentDelegate from lisp.plugins.osc.osc_server import OscMessageType from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate, CueActionDelegate, EnumComboBoxDelegate from lisp.ui.qmodels import SimpleTableModel -from lisp.ui.settings.pages import CueSettingsPage, SettingsPage, CuePageMixin +from lisp.ui.settings.pages import SettingsPage, CuePageMixin from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/gst_backend/gst_settings.py b/lisp/plugins/gst_backend/gst_settings.py index 53db95c5c..98ddfbbae 100644 --- a/lisp/plugins/gst_backend/gst_settings.py +++ b/lisp/plugins/gst_backend/gst_settings.py @@ -21,15 +21,15 @@ from PyQt5.QtWidgets import QVBoxLayout, QGroupBox from lisp.plugins.gst_backend.gst_pipe_edit import GstPipeEdit -from lisp.ui.settings.pages import ConfigurationPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate -class GstSettings(ConfigurationPage): +class GstSettings(SettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'GStreamer') - def __init__(self, config, **kwargs): - super().__init__(config, **kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -37,7 +37,7 @@ def __init__(self, config, **kwargs): self.pipeGroup.setLayout(QVBoxLayout()) self.layout().addWidget(self.pipeGroup) - self.pipeEdit = GstPipeEdit(config.get('pipeline'), app_mode=True) + self.pipeEdit = GstPipeEdit('', app_mode=True) self.pipeGroup.layout().addWidget(self.pipeEdit) self.retranslateUi() @@ -45,6 +45,9 @@ def __init__(self, config, **kwargs): def retranslateUi(self): self.pipeGroup.setTitle(translate('GstSettings', 'Pipeline')) - def applySettings(self): - self.config['pipeline'] = list(self.pipeEdit.get_pipe()) - self.config.write() + def loadSettings(self, settings): + self.pipeEdit.set_pipe(settings['pipeline']) + + def getSettings(self): + return {'pipeline': list(self.pipeEdit.get_pipe())} + diff --git a/lisp/plugins/list_layout/__init__.py b/lisp/plugins/list_layout/__init__.py index 64334a175..aa70469d2 100644 --- a/lisp/plugins/list_layout/__init__.py +++ b/lisp/plugins/list_layout/__init__.py @@ -16,4 +16,4 @@ def __init__(self, app): _ListLayout.Config = ListLayout.Config register_layout(_ListLayout) AppConfigurationDialog.registerSettingsPage( - 'layouts.layout_layout', ListLayoutSettings, ListLayout.Config) + 'layouts.list_layout', ListLayoutSettings, ListLayout.Config) diff --git a/lisp/plugins/list_layout/settings.py b/lisp/plugins/list_layout/settings.py index 29e279199..000045731 100644 --- a/lisp/plugins/list_layout/settings.py +++ b/lisp/plugins/list_layout/settings.py @@ -19,18 +19,18 @@ from PyQt5.QtCore import Qt from PyQt5.QtGui import QKeySequence -from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QHBoxLayout,\ +from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QHBoxLayout, \ QLabel, QKeySequenceEdit, QGridLayout -from lisp.ui.settings.pages import ConfigurationPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate -class ListLayoutSettings(ConfigurationPage): +class ListLayoutSettings(SettingsPage): Name = 'List Layout' - def __init__(self, config, **kwargs): - super().__init__(config, **kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -80,7 +80,6 @@ def __init__(self, config, **kwargs): self.useFadeGroup.layout().addWidget(self.interruptCueFade, 3, 0) self.retranslateUi() - self.loadSettings() def retranslateUi(self): self.behaviorsGroup.setTitle( @@ -101,35 +100,36 @@ def retranslateUi(self): self.resumeCueFade.setText(translate('ListLayout', 'Resume Cue')) self.interruptCueFade.setText(translate('ListLayout', 'Interrupt Cue')) - def loadSettings(self): - self.showPlaying.setChecked(self.config['show.playingCues']) - self.showDbMeters.setChecked(self.config['show.dBMeters']) - self.showAccurate.setChecked(self.config['show.accurateTime']) - self.showSeek.setChecked(self.config['show.seekSliders']) - self.autoNext.setChecked(self.config['autoContinue']) - self.selectionMode.setChecked(self.config['selectionMode']) + def loadSettings(self, settings): + self.showPlaying.setChecked(settings['show']['playingCues']) + self.showDbMeters.setChecked(settings['show']['dBMeters']) + self.showAccurate.setChecked(settings['show']['accurateTime']) + self.showSeek.setChecked(settings['show']['seekSliders']) + self.autoNext.setChecked(settings['autoContinue']) + self.selectionMode.setChecked(settings['selectionMode']) self.goKeyEdit.setKeySequence( - QKeySequence(self.config['goKey'], QKeySequence.NativeText)) - - self.stopCueFade.setChecked(self.config['stopCueFade']) - self.pauseCueFade.setChecked(self.config['pauseCueFade']) - self.resumeCueFade.setChecked(self.config['resumeCueFade']) - self.interruptCueFade.setChecked(self.config['interruptCueFade']) - - def applySettings(self): - self.config['show.accurateTime'] = self.showAccurate.isChecked() - self.config['show.playingCues'] = self.showPlaying.isChecked() - self.config['show.dBMeters'] = self.showDbMeters.isChecked() - self.config['show.seekBars'] = self.showSeek.isChecked() - self.config['autoContinue'] = self.autoNext.isChecked() - self.config['selectionMode'] = self.selectionMode.isChecked() - - self.config['goKey'] = self.goKeyEdit.keySequence().toString( - QKeySequence.NativeText) - - self.config['stopCueFade'] = self.stopCueFade.isChecked() - self.config['pauseCueFade'] = self.pauseCueFade.isChecked() - self.config['resumeCueFade'] = self.resumeCueFade.isChecked() - self.config['interruptCueFade'] = self.interruptCueFade.isChecked() - - self.config.write() + QKeySequence(settings['goKey'], QKeySequence.NativeText)) + + self.stopCueFade.setChecked(settings['stopCueFade']) + self.pauseCueFade.setChecked(settings['pauseCueFade']) + self.resumeCueFade.setChecked(settings['resumeCueFade']) + self.interruptCueFade.setChecked(settings['interruptCueFade']) + + def getSettings(self): + return { + 'show': { + 'accurateTime': self.showAccurate.isChecked(), + 'playingCues': self.showPlaying.isChecked(), + 'dBMeters': self.showDbMeters.isChecked(), + 'seekBars': self.showSeek.isChecked() + }, + 'autoContinue': self.autoNext.isChecked(), + 'selectionMode': self.selectionMode.isChecked(), + 'goKey': self.goKeyEdit.keySequence().toString( + QKeySequence.NativeText), + + 'stopCueFade': self.stopCueFade.isChecked(), + 'pauseCueFade': self.pauseCueFade.isChecked(), + 'resumeCueFade': self.resumeCueFade.isChecked(), + 'interruptCueFade': self.interruptCueFade.isChecked() + } diff --git a/lisp/plugins/midi/midi_settings.py b/lisp/plugins/midi/midi_settings.py index 7034776a3..d7040ca6c 100644 --- a/lisp/plugins/midi/midi_settings.py +++ b/lisp/plugins/midi/midi_settings.py @@ -22,15 +22,15 @@ QLabel from lisp.plugins.midi.midi_utils import mido_backend -from lisp.ui.settings.pages import ConfigurationPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate -class MIDISettings(ConfigurationPage): +class MIDISettings(SettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'MIDI settings') - def __init__(self, conf, **kwargs): - super().__init__(conf, **kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -60,27 +60,24 @@ def __init__(self, conf, **kwargs): except Exception: self.setEnabled(False) - # TODO: instead of forcing 'Default' add a warning label + def loadSettings(self, settings): + # TODO: instead of forcing 'Default' add a warning label and keep the invalid device as an option self.inputCombo.setCurrentText('Default') - self.inputCombo.setCurrentText(conf['inputDevice']) + self.inputCombo.setCurrentText(settings['inputDevice']) self.outputCombo.setCurrentText('Default') - self.outputCombo.setCurrentText(conf['outputDevice']) + self.outputCombo.setCurrentText(settings['outputDevice']) - def applySettings(self): + def getSettings(self): if self.isEnabled(): - inputDevice = self.inputCombo.currentText() - if inputDevice == 'Default': - self.config['inputDevice'] = '' - else: - self.config['inputDevice'] = inputDevice - - outputDevice = self.outputCombo.currentText() - if outputDevice == 'Default': - self.config['outputDevice'] = '' - else: - self.config['outputDevice'] = outputDevice - - self.config.write() + input = self.inputCombo.currentText() + output = self.outputCombo.currentText() + + return { + 'inputDevice': '' if input == 'Default' else input, + 'outputDevice': '' if output == 'Default' else output + } + + return {} def _loadDevices(self): backend = mido_backend() diff --git a/lisp/plugins/osc/osc_server.py b/lisp/plugins/osc/osc_server.py index 3bb61b73b..8258221a9 100644 --- a/lisp/plugins/osc/osc_server.py +++ b/lisp/plugins/osc/osc_server.py @@ -93,7 +93,7 @@ def start(self): logger.info('OSC server started at {}'.format(self.__srv.url)) except ServerError: - logger.error('Cannot start OSC sever') + logger.exception('Cannot start OSC sever') logger.debug(traceback.format_exc()) def stop(self): diff --git a/lisp/plugins/osc/osc_settings.py b/lisp/plugins/osc/osc_settings.py index fb42b9b5d..fd33af2e0 100644 --- a/lisp/plugins/osc/osc_settings.py +++ b/lisp/plugins/osc/osc_settings.py @@ -22,15 +22,15 @@ from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel, \ QHBoxLayout, QSpinBox, QLineEdit -from lisp.ui.settings.pages import ConfigurationPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate -class OscSettings(ConfigurationPage): +class OscSettings(SettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'OSC settings') - def __init__(self, config, **kwargs): - super().__init__(config, **kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -66,16 +66,14 @@ def __init__(self, config, **kwargs): hbox.layout().addWidget(self.hostnameEdit) self.groupBox.layout().addLayout(hbox) - self.loadConfiguration() + def getSettings(self): + return { + 'inPort': self.inportBox.value(), + 'outPort': self.outportBox.value(), + 'hostname': self.hostnameEdit.text() + } - def applySettings(self): - self.config['inPort'] = self.inportBox.value() - self.config['outPort'] = self.outportBox.value() - self.config['hostname'] = self.hostnameEdit.text() - - self.config.write() - - def loadConfiguration(self): - self.inportBox.setValue(self.config['inPort']) - self.outportBox.setValue(self.config['outPort']) - self.hostnameEdit.setText(self.config['hostname']) + def loadSettings(self, settings): + self.inportBox.setValue(settings['inPort']) + self.outportBox.setValue(settings['outPort']) + self.hostnameEdit.setText(settings['hostname']) diff --git a/lisp/plugins/timecode/settings.py b/lisp/plugins/timecode/settings.py index fcbb8069b..22ec179da 100644 --- a/lisp/plugins/timecode/settings.py +++ b/lisp/plugins/timecode/settings.py @@ -25,7 +25,7 @@ from lisp.plugins.timecode import protocols from lisp.plugins.timecode.cue_tracker import TcFormat -from lisp.ui.settings.pages import SettingsPage, CueSettingsPage, ConfigurationPage +from lisp.ui.settings.pages import SettingsPage, CueSettingsPage from lisp.ui.ui_utils import translate @@ -81,11 +81,13 @@ def retranslateUi(self): translate('TimecodeSettings', 'Track number')) def getSettings(self): - return {'timecode': { - 'enabled': self.enableTimecodeCheck.isChecked(), - 'replace_hours': self.useHoursCheck.isChecked(), - 'track': self.trackSpin.value() - }} + return { + 'timecode': { + 'enabled': self.enableTimecodeCheck.isChecked(), + 'replace_hours': self.useHoursCheck.isChecked(), + 'track': self.trackSpin.value() + } + } def loadSettings(self, settings): settings = settings.get('timecode', {}) @@ -95,11 +97,11 @@ def loadSettings(self, settings): self.trackSpin.setValue(settings.get('track', 0)) -class TimecodeAppSettings(ConfigurationPage): +class TimecodeAppSettings(SettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Timecode Settings') - def __init__(self, config, **kwargs): - super().__init__(config, **kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -124,7 +126,6 @@ def __init__(self, config, **kwargs): self.groupBox.layout().addWidget(self.protocolCombo, 1, 1) self.retranslateUi() - self.loadConfiguration() def retranslateUi(self): self.groupBox.setTitle( @@ -134,11 +135,12 @@ def retranslateUi(self): self.protocolLabel.setText( translate('TimecodeSettings', 'Timecode Protocol:')) - def applySettings(self): - self.config['format'] = self.formatBox.currentText() - self.config['protocol'] = self.protocolCombo.currentText() - self.config.write() + def getSettings(self): + return { + 'format': self.formatBox.currentText(), + 'protocol': self.protocolCombo.currentText() + } - def loadConfiguration(self): - self.formatBox.setCurrentText(self.config['format']) - self.protocolCombo.setCurrentText(self.config['protocol']) + def loadSettings(self, settings): + self.formatBox.setCurrentText(settings['format']) + self.protocolCombo.setCurrentText(settings['protocol']) diff --git a/lisp/ui/logging/common.py b/lisp/ui/logging/common.py index 40c215df6..4b48b8ff5 100644 --- a/lisp/ui/logging/common.py +++ b/lisp/ui/logging/common.py @@ -50,6 +50,7 @@ 'processName': QT_TRANSLATE_NOOP('Logging', 'Process name'), 'thread': QT_TRANSLATE_NOOP('Logging', 'Thread ID'), 'threadName': QT_TRANSLATE_NOOP('Logging', 'Thread name'), + 'exc_info': QT_TRANSLATE_NOOP('Logging', 'Exception info') } LogRecordRole = Qt.UserRole diff --git a/lisp/ui/logging/details.py b/lisp/ui/logging/details.py new file mode 100644 index 000000000..a143c0ae4 --- /dev/null +++ b/lisp/ui/logging/details.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import traceback +from PyQt5.QtGui import QFont +from PyQt5.QtWidgets import QTextEdit + +from lisp.ui.logging.common import LOG_ATTRIBUTES + + +class LogDetails(QTextEdit): + def __init__(self, *args): + super().__init__(*args) + self._record = None + + self.setLineWrapMode(self.NoWrap) + self.setReadOnly(True) + + font = QFont('Monospace') + font.setStyleHint(QFont.Monospace) + self.setFont(font) + + def setLogRecord(self, record): + self._record = record + text = '' + for attr, attr_text in LOG_ATTRIBUTES.items(): + text += '⇨{}:\n {}'.format( + attr_text, self.formatAttribute(attr)) + + self.setText(text) + + def formatAttribute(self, attribute): + value = getattr(self._record, attribute, None) + if attribute == 'exc_info': + if value is not None: + return ' '.join(traceback.format_exception(*value)) + + return str(value) + '\n' diff --git a/lisp/ui/logging/viewer.py b/lisp/ui/logging/viewer.py index b97d5d206..e52f142aa 100644 --- a/lisp/ui/logging/viewer.py +++ b/lisp/ui/logging/viewer.py @@ -18,10 +18,13 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QAction, QToolBar, \ - QMainWindow, QStatusBar, QLabel, QTableView, QToolButton +from PyQt5.QtWidgets import QAction, QToolBar, QMainWindow, QStatusBar, \ + QLabel, QTableView, QVBoxLayout, QWidget -from lisp.ui.logging.common import LOG_LEVELS, LogAttributeRole, LOG_ATTRIBUTES +from lisp.ui.icons import IconTheme +from lisp.ui.logging.common import LOG_LEVELS, LogAttributeRole, \ + LOG_ICONS_NAMES, LogRecordRole +from lisp.ui.logging.details import LogDetails from lisp.ui.logging.models import LogRecordFilterModel from lisp.ui.ui_utils import translate @@ -38,9 +41,11 @@ def __init__(self, log_model, config, **kwargs): :type config: lisp.core.configuration.Configuration """ super().__init__(**kwargs) - self.resize(700, 500) self.setWindowTitle( translate('Logging', 'Linux Show Player - Log Viewer')) + self.resize(800, 600) + self.setCentralWidget(QWidget()) + self.centralWidget().setLayout(QVBoxLayout()) self.setStatusBar(QStatusBar(self)) # Add a permanent label to the toolbar to display shown/filter records @@ -49,20 +54,15 @@ def __init__(self, log_model, config, **kwargs): # ToolBar self.optionsToolBar = QToolBar(self) + self.optionsToolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.addToolBar(self.optionsToolBar) # Create level-toggle actions - self.levelsActions = self._create_actions( - self.optionsToolBar, LOG_LEVELS, self._toggle_level) - - self.columnsMenu = QToolButton() - self.columnsMenu.setText('Columns') - self.columnsMenu.setPopupMode(QToolButton.InstantPopup) - # Create column-toggle actions - self.columnsActions = self._create_actions( - self.columnsMenu, LOG_ATTRIBUTES, self._toggle_column) - - self.optionsToolBar.addSeparator() - self.optionsToolBar.addWidget(self.columnsMenu) + self.levelsActions = self._createActions( + self.optionsToolBar, + LOG_LEVELS, + LOG_ICONS_NAMES, + self._toggleLevel + ) # Setup level filters and columns visible_levels = config.get('logging.viewer.visibleLevels', ()) @@ -74,42 +74,69 @@ def __init__(self, log_model, config, **kwargs): self.filterModel = LogRecordFilterModel(visible_levels) self.filterModel.setSourceModel(log_model) - # List view to display the messages stored in the model + # View to display the messages stored in the model self.logView = QTableView(self) self.logView.setModel(self.filterModel) - self.logView.setSelectionMode(QTableView.NoSelection) - self.logView.setFocusPolicy(Qt.NoFocus) - self.setCentralWidget(self.logView) + self.logView.setSelectionMode(QTableView.SingleSelection) + self.logView.setSelectionBehavior(QTableView.SelectRows) + self.logView.horizontalHeader().setStretchLastSection(True) + self.centralWidget().layout().addWidget(self.logView) + + # Display selected entry details + self.detailsView = LogDetails(self) + self.centralWidget().layout().addWidget(self.detailsView) + + self.centralWidget().layout().setStretch(0, 5) + self.centralWidget().layout().setStretch(1, 2) # Setup visible columns for n in range(self.filterModel.columnCount()): column = self.filterModel.headerData( n, Qt.Horizontal, LogAttributeRole) - visible = column in visible_columns - self.columnsActions[column].setChecked(visible) - self.logView.setColumnHidden(n, not visible) + self.logView.setColumnHidden(n, not column in visible_columns) # When the filter model change, update the status-label - self.filterModel.rowsInserted.connect(self._rows_changed) - self.filterModel.rowsRemoved.connect(self._rows_changed) - self.filterModel.modelReset.connect(self._rows_changed) + self.filterModel.rowsInserted.connect(self._rowsChanged) + self.filterModel.rowsRemoved.connect(self._rowsChanged) + self.filterModel.modelReset.connect(self._rowsChanged) + + self.logView.selectionModel().selectionChanged.connect( + self._selectionChanged) + + def _selectionChanged(self, selection): + if selection.indexes(): + self.detailsView.setLogRecord( + self.filterModel.data(selection.indexes()[0], LogRecordRole)) + else: + self.detailsView.setLogRecord(None) - def _rows_changed(self, *args): + def _rowsChanged(self): self.statusLabel.setText( 'Showing {} of {} records'.format( self.filterModel.rowCount(), self.filterModel.sourceModel().rowCount()) ) self.logView.resizeColumnsToContents() + # QT Misbehavior: we need to reset the flag + self.logView.horizontalHeader().setStretchLastSection(False) + self.logView.horizontalHeader().setStretchLastSection(True) + self.logView.scrollToBottom() + # Select the last row (works also if the index is invalid) + self.logView.setCurrentIndex( + self.filterModel.index(self.filterModel.rowCount() - 1, 0) + ) - def _create_actions(self, menu, actions, trigger_slot): + def _createActions(self, menu, actions, icons, trigger_slot): menu_actions = {} for key, name in actions.items(): - action = QAction(translate('Logging', name)) + action = QAction( + IconTheme.get(icons.get(key)), + translate('Logging', name) + ) action.setCheckable(True) - action.triggered.connect(self._action_slot(trigger_slot, key)) + action.triggered.connect(self._actionSlot(trigger_slot, key)) menu.addAction(action) menu_actions[key] = action @@ -117,24 +144,14 @@ def _create_actions(self, menu, actions, trigger_slot): return menu_actions @staticmethod - def _action_slot(target, attr): + def _actionSlot(target, attr): def slot(checked): target(attr, checked) return slot - def _toggle_level(self, level, toggle): + def _toggleLevel(self, level, toggle): if toggle: self.filterModel.showLevel(level) else: self.filterModel.hideLevel(level) - - def _toggle_column(self, column, toggle): - for n in range(self.filterModel.columnCount()): - column_n = self.filterModel.headerData( - n, Qt.Horizontal, LogAttributeRole) - - if column_n == column: - self.logView.setColumnHidden(n, not toggle) - self.logView.resizeColumnsToContents() - return diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 10970c5dd..e30b51f4c 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -163,7 +163,7 @@ def __init__(self, conf, title='Linux Show Player', **kwargs): # Logging status widget self.logStatus = LogStatusView(self.logModel) - self.logStatus.double_clicked.connect(self.logViewer.show) + self.logStatus.double_clicked.connect(self.logViewer.showMaximized) self.statusBar().addPermanentWidget(self.logStatus) # Logging dialogs for errors diff --git a/lisp/ui/settings/app_configuration.py b/lisp/ui/settings/app_configuration.py index 63dd559f9..dc4f46690 100644 --- a/lisp/ui/settings/app_configuration.py +++ b/lisp/ui/settings/app_configuration.py @@ -17,18 +17,16 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import logging from collections import namedtuple +import logging from PyQt5 import QtCore from PyQt5.QtCore import QModelIndex from PyQt5.QtWidgets import QVBoxLayout, QDialogButtonBox, QDialog from lisp.core.dicttree import DictNode -from lisp.core.util import typename -from lisp.ui.settings.pages import ConfigurationPage, TreeMultiConfigurationWidget -from lisp.ui.settings.pages_tree_model import PagesTreeModel from lisp.ui.ui_utils import translate +from lisp.ui.widgets.pagestreewidget import PagesTreeModel, PagesTreeWidget logger = logging.getLogger(__name__) @@ -47,11 +45,12 @@ def __init__(self, **kwargs): self.resize(800, 510) self.setLayout(QVBoxLayout()) + self._confsMap = {} self.model = PagesTreeModel() for r_node in AppConfigurationDialog.PagesRegistry.children: self._populateModel(QModelIndex(), r_node) - self.mainPage = TreeMultiConfigurationWidget(self.model) + self.mainPage = PagesTreeWidget(self.model) self.mainPage.selectFirst() self.layout().addWidget(self.mainPage) @@ -71,32 +70,37 @@ def __init__(self, **kwargs): self.__onOk) def applySettings(self): - self.mainPage.applySettings() + for conf, pages in self._confsMap.items(): + for page in pages: + conf.update(page.getSettings()) + + conf.write() def _populateModel(self, m_parent, r_parent): if r_parent.value is not None: - page = r_parent.value.page + page_class = r_parent.value.page config = r_parent.value.config else: - page = None + page_class = None config = None try: - if page is None: + if page_class is None: # The current node have no page, use the parent model-index # as parent for it's children mod_index = m_parent - elif issubclass(page, ConfigurationPage): - mod_index = self.model.addPage(page(config), parent=m_parent) else: - mod_index = self.model.addPage(page(), parent=m_parent) + page_instance = page_class() + page_instance.loadSettings(config) + mod_index = self.model.addPage(page_instance, parent=m_parent) + + # Keep track of configurations and corresponding pages + self._confsMap.setdefault(config, []).append(page_instance) except Exception: - if not isinstance(page, type): - page_name = 'NoPage' - elif issubclass(page, ConfigurationPage): - page_name = page.Name + if not isinstance(page_class, type): + page_name = 'InvalidPage' else: - page_name = page.__name__ + page_name = getattr(page_class, 'Name', page_class.__name__) logger.warning( 'Cannot load configuration page: "{}" ({})'.format( @@ -114,17 +118,11 @@ def registerSettingsPage(path, page, config): """ :param path: indicate the page "position": 'category.sub.key' :type path: str - :type page: Type[lisp.ui.settings.settings_page.ConfigurationPage] + :type page: typing.Type[lisp.ui.settings.pages.SettingsPage] :type config: lisp.core.configuration.Configuration """ - if issubclass(page, ConfigurationPage): - AppConfigurationDialog.PagesRegistry.set( - path, PageEntry(page=page, config=config)) - else: - raise TypeError( - 'AppConfiguration pages must be ConfigurationPage(s), not {}' - .format(typename(page)) - ) + AppConfigurationDialog.PagesRegistry.set( + path, PageEntry(page=page, config=config)) @staticmethod def unregisterSettingsPage(path): diff --git a/lisp/ui/settings/app_pages/cue_app_settings.py b/lisp/ui/settings/app_pages/cue.py similarity index 68% rename from lisp/ui/settings/app_pages/cue_app_settings.py rename to lisp/ui/settings/app_pages/cue.py index 0ed5af68d..0563c172b 100644 --- a/lisp/ui/settings/app_pages/cue_app_settings.py +++ b/lisp/ui/settings/app_pages/cue.py @@ -20,16 +20,16 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt from PyQt5.QtWidgets import QVBoxLayout, QGroupBox -from lisp.ui.settings.pages import ConfigurationPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate from lisp.ui.widgets import FadeEdit -class CueAppSettings(ConfigurationPage): +class CueAppSettings(SettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Cue Settings') - def __init__(self, config, **kwargs): - super().__init__(config, **kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -57,19 +57,18 @@ def retranslateUi(self): self.actionGroup.setTitle(translate('CueSettings', 'Fade actions')) def applySettings(self): - self.config['cue.interruptFade'] = self.interruptFadeEdit.duration() - self.config['cue.interruptFadeType'] = self.interruptFadeEdit.fadeType() - self.config['cue.fadeAction'] = self.fadeActionEdit.duration() - self.config['cue.fadeActionType'] = self.fadeActionEdit.fadeType() + return { + 'cue': { + 'interruptFade': self.interruptFadeEdit.duration(), + 'interruptFadeType': self.interruptFadeEdit.fadeType(), + 'fadeAction': self.fadeActionEdit.duration(), + 'fadeActionType': self.fadeActionEdit.fadeType() + } + } - self.config.write() + def loadSettings(self, settings): + self.interruptFadeEdit.setDuration(settings['cue']['interruptFade']) + self.interruptFadeEdit.setFadeType(settings['cue']['interruptFadeType']) - def loadConfiguration(self): - self.interruptFadeEdit.setDuration( - self.config.get('cue.interruptFade', 0)) - self.interruptFadeEdit.setFadeType( - self.config.get('cue.interruptFadeType', '')) - - self.fadeActionEdit.setDuration(self.config.get('cue.fadeAction', 0)) - self.fadeActionEdit.setFadeType( - self.config.get('cue.fadeActionType', '')) + self.fadeActionEdit.setDuration(settings['cue']['fadeAction']) + self.fadeActionEdit.setFadeType(settings['cue']['fadeActionType']) diff --git a/lisp/ui/settings/app_pages/app_general.py b/lisp/ui/settings/app_pages/general.py similarity index 79% rename from lisp/ui/settings/app_pages/app_general.py rename to lisp/ui/settings/app_pages/general.py index b7391d8e4..f3c76e943 100644 --- a/lisp/ui/settings/app_pages/app_general.py +++ b/lisp/ui/settings/app_pages/general.py @@ -23,16 +23,16 @@ from lisp import layout from lisp.ui.icons import icon_themes_names -from lisp.ui.settings.pages import ConfigurationPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.themes import themes_names from lisp.ui.ui_utils import translate -class AppGeneral(ConfigurationPage): +class AppGeneral(SettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'General') - def __init__(self, config, **kwargs): - super().__init__(config, **kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -72,7 +72,6 @@ def __init__(self, config, **kwargs): self.themeGroup.layout().addWidget(self.iconsCombo, 1, 1) self.retranslateUi() - self.loadConfiguration() def retranslateUi(self): self.layoutGroup.setTitle( @@ -86,23 +85,29 @@ def retranslateUi(self): self.iconsLabel.setText( translate('AppGeneralSettings', 'Icons theme')) - def applySettings(self): + def getSettings(self): + settings = { + 'theme': { + 'theme': self.themeCombo.currentText(), + 'icons': self.iconsCombo.currentText() + }, + 'layout': {} + } + if self.startupDialogCheck.isChecked(): - self.config['layout.default'] = 'NoDefault' + settings['layout']['default'] = 'NoDefault' else: - self.config['layout.default'] = self.layoutCombo.currentData() - - self.config['theme.theme'] = self.themeCombo.currentText() - self.config['theme.icons'] = self.iconsCombo.currentText() + settings['layout']['default'] = self.layoutCombo.currentData() - self.config.write() + return settings - def loadConfiguration(self): - layout_name = self.config.get('layout.default', 'nodefault') + def loadSettings(self, settings): + layout_name = settings['layout']['default'] if layout_name.lower() == 'nodefault': self.startupDialogCheck.setChecked(True) else: - self.layoutCombo.setCurrentIndex(self.layoutCombo.findData(layout_name)) + self.layoutCombo.setCurrentIndex( + self.layoutCombo.findData(layout_name)) - self.themeCombo.setCurrentText(self.config.get('theme.theme', '')) - self.iconsCombo.setCurrentText(self.config.get('theme.icons', '')) + self.themeCombo.setCurrentText(settings['theme']['theme']) + self.iconsCombo.setCurrentText(settings['theme']['icons']) diff --git a/lisp/ui/settings/app_pages/layouts_settings.py b/lisp/ui/settings/app_pages/layouts.py similarity index 71% rename from lisp/ui/settings/app_pages/layouts_settings.py rename to lisp/ui/settings/app_pages/layouts.py index 54998eee5..a22891fa7 100644 --- a/lisp/ui/settings/app_pages/layouts_settings.py +++ b/lisp/ui/settings/app_pages/layouts.py @@ -20,15 +20,15 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QGridLayout, QCheckBox -from lisp.ui.settings.pages import ConfigurationPage +from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate -class LayoutsSettings(ConfigurationPage): +class LayoutsSettings(SettingsPage): Name = 'Layouts' - def __init__(self, config, **kwargs): - super().__init__(config, **kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -37,19 +37,18 @@ def __init__(self, config, **kwargs): self.layout().addWidget(self.useFadeGroup) self.stopAllFade = QCheckBox(self.useFadeGroup) - self.useFadeGroup.layout().addWidget(self.stopAllFade, 0, 1) + self.useFadeGroup.layout().addWidget(self.stopAllFade, 0, 0) + + self.interruptAllFade = QCheckBox(self.useFadeGroup) + self.useFadeGroup.layout().addWidget(self.interruptAllFade, 0, 1) self.pauseAllFade = QCheckBox(self.useFadeGroup) - self.useFadeGroup.layout().addWidget(self.pauseAllFade, 1, 1) + self.useFadeGroup.layout().addWidget(self.pauseAllFade, 1, 0) self.resumeAllFade = QCheckBox(self.useFadeGroup) - self.useFadeGroup.layout().addWidget(self.resumeAllFade, 2, 1) - - self.interruptAllFade = QCheckBox(self.useFadeGroup) - self.useFadeGroup.layout().addWidget(self.interruptAllFade, 3, 1) + self.useFadeGroup.layout().addWidget(self.resumeAllFade, 1, 1) self.retranslateUi() - self.loadSettings() def retranslateUi(self): self.useFadeGroup.setTitle( @@ -59,15 +58,18 @@ def retranslateUi(self): self.resumeAllFade.setText(translate('ListLayout', 'Resume All')) self.interruptAllFade.setText(translate('ListLayout', 'Interrupt All')) - def loadSettings(self): - self.stopAllFade.setChecked(self.config['layout.stopAllFade']) - self.pauseAllFade.setChecked(self.config['layout.pauseAllFade']) - self.resumeAllFade.setChecked(self.config['layout.resumeAllFade']) - self.interruptAllFade.setChecked(self.config['layout.interruptAllFade']) + def loadSettings(self, settings): + self.stopAllFade.setChecked(settings['layout']['stopAllFade']) + self.pauseAllFade.setChecked(settings['layout']['pauseAllFade']) + self.resumeAllFade.setChecked(settings['layout']['resumeAllFade']) + self.interruptAllFade.setChecked(settings['layout']['interruptAllFade']) - def applySettings(self): - self.config['layout.stopAllFade'] = self.stopAllFade.isChecked() - self.config['layout.pauseAllFade'] = self.pauseAllFade.isChecked() - self.config['layout.resumeAllFade'] = self.resumeAllFade.isChecked() - self.config['layout.interruptAllFade'] = self.interruptAllFade.isChecked() - self.config.write() + def getSettings(self): + return { + 'layout': { + 'stopAllFade': self.stopAllFade.isChecked(), + 'pauseAllFade': self.pauseAllFade.isChecked(), + 'resumeAllFade': self.resumeAllFade.isChecked(), + 'interruptAllFade': self.interruptAllFade.isChecked() + } + } diff --git a/lisp/ui/settings/app_pages/plugins_settings.py b/lisp/ui/settings/app_pages/plugins.py similarity index 92% rename from lisp/ui/settings/app_pages/plugins_settings.py rename to lisp/ui/settings/app_pages/plugins.py index 4f59cbe40..a108523d4 100644 --- a/lisp/ui/settings/app_pages/plugins_settings.py +++ b/lisp/ui/settings/app_pages/plugins.py @@ -23,15 +23,15 @@ from lisp import plugins from lisp.ui.icons import IconTheme -from lisp.ui.settings.pages import ConfigurationPage +from lisp.ui.settings.pages import SettingsPage # TODO: add Enable/Disable options for plugins -class PluginsSettings(ConfigurationPage): +class PluginsSettings(SettingsPage): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Plugins') - def __init__(self, config, **kwargs): - super().__init__(config, **kwargs) + def __init__(self, **kwargs): + super().__init__(**kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) @@ -79,6 +79,3 @@ def __selection_changed(self): else: self.pluginDescription.setHtml( 'Description:

Authors: ') - - def applySettings(self): - pass diff --git a/lisp/ui/settings/cue_pages/cue_general.py b/lisp/ui/settings/cue_pages/cue_general.py index 9c8af6e3c..5abb5644e 100644 --- a/lisp/ui/settings/cue_pages/cue_general.py +++ b/lisp/ui/settings/cue_pages/cue_general.py @@ -22,13 +22,13 @@ QDoubleSpinBox from lisp.cues.cue import CueAction -from lisp.ui.settings.pages import CueSettingsPage, TabsMultiSettingsPage, CuePageMixin +from lisp.ui.settings.pages import CueSettingsPage, SettingsPagesTabWidget, CuePageMixin from lisp.ui.ui_utils import translate from lisp.ui.widgets import FadeComboBox, CueActionComboBox, \ CueNextActionComboBox, FadeEdit -class CueGeneralSettings(TabsMultiSettingsPage, CuePageMixin): +class CueGeneralSettingsPage(SettingsPagesTabWidget, CuePageMixin): Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Cue') def __init__(self, cueType, **kwargs): diff --git a/lisp/ui/settings/cue_pages/media_cue_settings.py b/lisp/ui/settings/cue_pages/media_cue.py similarity index 100% rename from lisp/ui/settings/cue_pages/media_cue_settings.py rename to lisp/ui/settings/cue_pages/media_cue.py diff --git a/lisp/ui/settings/cue_settings.py b/lisp/ui/settings/cue_settings.py index c469575f8..2a2dea4a5 100644 --- a/lisp/ui/settings/cue_settings.py +++ b/lisp/ui/settings/cue_settings.py @@ -24,7 +24,7 @@ from lisp.core.singleton import Singleton from lisp.core.util import typename from lisp.cues.cue import Cue -from lisp.ui.settings.pages import CuePageMixin, TabsMultiSettingsPage +from lisp.ui.settings.pages import CuePageMixin, SettingsPagesTabWidget from lisp.ui.ui_utils import translate @@ -53,6 +53,7 @@ def __init__(self, cue, **kwargs): self.setMinimumSize(640, 510) self.resize(640, 510) self.setLayout(QVBoxLayout()) + #self.layout().setContentsMargins(5, 5, 5, 10) if isinstance(cue, type): if issubclass(cue, Cue): @@ -73,7 +74,8 @@ def __init__(self, cue, **kwargs): 'not {}'.format(typename(cue)) ) - self.mainPage = TabsMultiSettingsPage(parent=self) + self.mainPage = SettingsPagesTabWidget(parent=self) + self.mainPage.layout().setContentsMargins(0, 0, 0, 0) self.layout().addWidget(self.mainPage) def sk(widget): diff --git a/lisp/ui/settings/pages.py b/lisp/ui/settings/pages.py index d640ff03a..36cc1030d 100644 --- a/lisp/ui/settings/pages.py +++ b/lisp/ui/settings/pages.py @@ -17,26 +17,16 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from abc import abstractmethod - from PyQt5.QtCore import QModelIndex, Qt -from PyQt5.QtWidgets import QWidget, QTabWidget, QVBoxLayout, QTreeView, \ - QGridLayout, QSizePolicy +from PyQt5.QtWidgets import QWidget, QTabWidget, QVBoxLayout -from lisp.core.qmeta import QABCMeta -from lisp.core.util import dict_merge, typename +from lisp.core.util import dict_merge from lisp.ui.ui_utils import translate +from lisp.ui.widgets.pagestreewidget import PagesTreeWidget -class ABCPage(QWidget, metaclass=QABCMeta): - Name = 'PageName' - - -class SettingsPage(ABCPage): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) +class SettingsPage(QWidget): - @abstractmethod def loadSettings(self, settings): """Load existing settings value into the widget @@ -68,20 +58,6 @@ def enableCheck(self, enabled): """ -class ConfigurationPage(ABCPage): - def __init__(self, config, *args, **kwargs): - """ - :param config: Configuration object to "edit" - :type config: lisp.core.configuration.Configuration - """ - super().__init__(*args, **kwargs) - self.config = config - - @abstractmethod - def applySettings(self): - pass - - class CuePageMixin: def __init__(self, cueType, *args, **kwargs): super().__init__(*args, **kwargs) @@ -93,7 +69,7 @@ def __init__(self, cueType, **kwargs): super().__init__(cueType=cueType, **kwargs) -class TabsMultiPage(ABCPage): +class SettingsPagesTabWidget(SettingsPage): def __init__(self, **kwargs): super().__init__(**kwargs) self.setLayout(QVBoxLayout()) @@ -121,8 +97,6 @@ def iterPages(self): def pageIndex(self, page): return self._pages.index(page) - -class TabsMultiSettingsPage(TabsMultiPage): def loadSettings(self, settings): for page in self._pages: page.loadSettings(settings) @@ -139,68 +113,7 @@ def enableCheck(self, enabled): page.enableCheck(enabled) -class TabsMultiConfigurationPage(TabsMultiPage): - def applySettings(self): - for page in self._pages: - page.applySettings() - - -class TreeMultiPagesWidget(ABCPage): - def __init__(self, navModel, **kwargs): - """ - :param navModel: The model that keeps all the pages-hierarchy - :type navModel: lisp.ui.settings.pages_tree_model.PagesTreeModel - """ - super().__init__(**kwargs) - self.setLayout(QGridLayout()) - self.layout().setSpacing(0) - self.layout().setContentsMargins(0, 0, 0, 0) - self.navModel = navModel - - self.navWidget = QTreeView() - self.navWidget.setHeaderHidden(True) - self.navWidget.setModel(self.navModel) - self.layout().addWidget(self.navWidget, 0, 0) - - self._currentWidget = QWidget() - self.layout().addWidget(self._currentWidget, 0, 1) - - self.navWidget.selectionModel().selectionChanged.connect( - self._changePage) - - self._resetStretch() - - def selectFirst(self): - self.navWidget.setCurrentIndex(self.navModel.index(0, 0, QModelIndex())) - - def currentWidget(self): - return self._currentWidget - - def _resetStretch(self): - self.layout().setColumnStretch(0, 2) - self.layout().setColumnStretch(1, 5) - - def _changePage(self, selected): - if selected.indexes(): - self.layout().removeWidget(self._currentWidget) - self._currentWidget.hide() - self._currentWidget = selected.indexes()[0].internalPointer().page - self._currentWidget.setSizePolicy( - QSizePolicy.Ignored, QSizePolicy.Ignored) - self._currentWidget.show() - self.layout().addWidget(self._currentWidget, 0, 1) - self._resetStretch() - - -class TreeMultiConfigurationWidget(TreeMultiPagesWidget): - def applySettings(self): - root = self.navModel.node(QModelIndex()) - for node in root.walk(): - if node.page is not None: - node.page.applySettings() - - -class TreeMultiSettingsWidget(TreeMultiPagesWidget): +class TreeMultiSettingsWidget(SettingsPage, PagesTreeWidget): def loadSettings(self, settings): root = self.navModel.node(QModelIndex()) for node in root.walk(): diff --git a/lisp/ui/themes/__init__.py b/lisp/ui/themes/__init__.py index 1bd7774ed..5c5bdacd9 100644 --- a/lisp/ui/themes/__init__.py +++ b/lisp/ui/themes/__init__.py @@ -8,9 +8,7 @@ def load_themes(): if not _THEMES: - for name, theme in load_classes(__package__, - path.dirname(__file__), - exclude='theme'): + for name, theme in load_classes(__package__, path.dirname(__file__)): _THEMES[name] = theme() diff --git a/lisp/ui/themes/dark/dark.py b/lisp/ui/themes/dark/dark.py index ae6efa718..e8a1ec87d 100644 --- a/lisp/ui/themes/dark/dark.py +++ b/lisp/ui/themes/dark/dark.py @@ -18,16 +18,14 @@ # along with Linux Show Player. If not, see . import os - from PyQt5.QtGui import QColor, QPalette -from lisp.ui.themes.theme import Theme # Import resources # noinspection PyUnresolvedReferences from . import assetes -class Dark(Theme): +class Dark: QssPath = os.path.join(os.path.dirname(__file__), 'theme.qss') def apply(self, qt_app): diff --git a/lisp/ui/themes/dark/theme.qss b/lisp/ui/themes/dark/theme.qss index cb37e1acb..18396375a 100644 --- a/lisp/ui/themes/dark/theme.qss +++ b/lisp/ui/themes/dark/theme.qss @@ -191,6 +191,9 @@ QScrollBar:handle:vertical { QTextEdit[readOnly="true"] { background-color: #333; +} + +QTextBrowser[readOnly="true"] { border: none; } @@ -437,6 +440,10 @@ QTabBar::tab:selected:focus { border-top-color: #419BE6; } +QTabBar QToolButton { + border: 1px solid #3A3A3A; +} + QDockWidget { color: white; titlebar-close-icon: url(:/assets/close.png); @@ -505,7 +512,6 @@ QSlider:disabled { background: none; } - QSlider:groove { border: 1px solid #3A3A3A; border-radius: 1px; @@ -636,7 +642,7 @@ QRadioButton:indicator:checked:disabled { #CartTabBar::tab { height: 35px; - width: 100px; + min-width: 100px; font-size: 13pt; font-weight: bold; color: white; diff --git a/lisp/ui/themes/theme.py b/lisp/ui/themes/theme.py deleted file mode 100644 index 587b6deeb..000000000 --- a/lisp/ui/themes/theme.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2018 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - - -class Theme: - def apply(self, qt_app): - """ - :type qt_app: PyQt5.QtWidgets.QApplication - """ diff --git a/lisp/ui/settings/pages_tree_model.py b/lisp/ui/widgets/pagestreewidget.py similarity index 68% rename from lisp/ui/settings/pages_tree_model.py rename to lisp/ui/widgets/pagestreewidget.py index fc0598fd0..b9bcf3a64 100644 --- a/lisp/ui/settings/pages_tree_model.py +++ b/lisp/ui/widgets/pagestreewidget.py @@ -17,10 +17,57 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import QAbstractItemModel, Qt, QModelIndex +from PyQt5.QtCore import QModelIndex, QAbstractItemModel, Qt +from PyQt5.QtWidgets import QWidget, QGridLayout, QTreeView, QSizePolicy -from lisp.core.util import typename -from lisp.ui.settings.pages import ABCPage + +class PagesTreeWidget(QWidget): + def __init__(self, navModel, **kwargs): + """ + :param navModel: The model that keeps all the pages-hierarchy + :type navModel: lisp.ui.widgets.pagestreewidget.PagesTreeModel + """ + super().__init__(**kwargs) + self.setLayout(QGridLayout()) + self.layout().setSpacing(0) + self.layout().setContentsMargins(0, 0, 0, 0) + self.navModel = navModel + + self.navWidget = QTreeView() + self.navWidget.setHeaderHidden(True) + self.navWidget.setModel(self.navModel) + self.layout().addWidget(self.navWidget, 0, 0) + + self._currentWidget = QWidget() + self.layout().addWidget(self._currentWidget, 0, 1) + + self.navWidget.selectionModel().selectionChanged.connect( + self._changePage) + + self._resetStretch() + + def selectFirst(self): + if self.navModel.rowCount(): + self.navWidget.setCurrentIndex( + self.navModel.index(0, 0, QModelIndex())) + + def currentWidget(self): + return self._currentWidget + + def _resetStretch(self): + self.layout().setColumnStretch(0, 2) + self.layout().setColumnStretch(1, 5) + + def _changePage(self, selected): + if selected.indexes(): + self.layout().removeWidget(self._currentWidget) + self._currentWidget.hide() + self._currentWidget = selected.indexes()[0].internalPointer().page + self._currentWidget.setSizePolicy( + QSizePolicy.Ignored, QSizePolicy.Ignored) + self._currentWidget.show() + self.layout().addWidget(self._currentWidget, 0, 1) + self._resetStretch() class PageNode: @@ -142,4 +189,4 @@ def removePage(self, row, parent=QModelIndex()): if row < parentNode.childCount(): self.beginRemoveRows(parent, row, row) parentNode.removeChild(row) - self.endRemoveRows() + self.endRemoveRows() \ No newline at end of file From c731c13f9fbe6ace135e6a7c926ba29a71181a5c Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 3 Oct 2018 14:16:54 +0200 Subject: [PATCH 130/333] Implement some requested enhancement Add: Option to select `GO` behaviour (#74) Update: CartLayout now insert new cues into current page (#125) Update: When adding MediaCue(s) the dialog will start from the last visited directory (#125) Fix: Cue application-settings not showing --- lisp/plugins/cart_layout/layout.py | 4 ++++ lisp/plugins/cart_layout/model.py | 27 ++++++++++++++++------- lisp/plugins/gst_backend/gst_backend.py | 11 +++++++--- lisp/plugins/list_layout/default.json | 3 ++- lisp/plugins/list_layout/layout.py | 8 ++++--- lisp/plugins/list_layout/settings.py | 29 ++++++++++++++++++++----- lisp/plugins/list_layout/view.py | 15 +++++++------ 7 files changed, 69 insertions(+), 28 deletions(-) diff --git a/lisp/plugins/cart_layout/layout.py b/lisp/plugins/cart_layout/layout.py index aa330bfac..80c5bed15 100644 --- a/lisp/plugins/cart_layout/layout.py +++ b/lisp/plugins/cart_layout/layout.py @@ -80,6 +80,7 @@ def __init__(self, application): self._cart_view = CartTabWidget() self._cart_view.keyPressed.connect(self._key_pressed) + self._cart_view.currentChanged.connect(self._tab_changed) # Layout menu layout_menu = self.app.window.menuLayout @@ -401,6 +402,9 @@ def _reset_cue_volume(self, cue): widget.resetVolume() + def _tab_changed(self, index): + self._cart_model.current_page = index + def __cue_added(self, cue): widget = CueWidget(cue) diff --git a/lisp/plugins/cart_layout/model.py b/lisp/plugins/cart_layout/model.py index 75b265fd4..7fbfd8a3c 100644 --- a/lisp/plugins/cart_layout/model.py +++ b/lisp/plugins/cart_layout/model.py @@ -24,9 +24,13 @@ class CueCartModel(ModelAdapter): - def __init__(self, model, rows, columns): + def __init__(self, model, rows, columns, current_page=0): super().__init__(model) + # Used in first empty, so we can insert cues in the page the user + # is visible while adding the cue + self.current_page = current_page + self.__cues = SortedDict() self.__rows = rows self.__columns = columns @@ -45,13 +49,20 @@ def flat(self, index): return index def first_empty(self): - """Return the first empty index.""" - n = -1 - for n, index in enumerate(self.__cues.keys()): - if n != index: - return n - - return n + 1 + """Return the first empty index, starting from the current page.""" + offset = (self.__rows * self.__columns) * self.current_page + last_index = self.__cues.peekitem(-1)[0] if self.__cues else -1 + + if last_index > offset: + for n in range(offset, last_index): + if n not in self.__cues: # O(1) + return n + + return last_index + 1 + if last_index < offset: + return offset + else: + return offset + 1 def item(self, index): index = self.flat(index) diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index 47224b816..3ba7e0316 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -18,11 +18,9 @@ # along with Linux Show Player. If not, see . import os.path - from PyQt5.QtCore import Qt from PyQt5.QtGui import QCursor from PyQt5.QtWidgets import QFileDialog, QApplication -from lisp.ui.ui_utils import translate, qfile_filters from lisp import backend from lisp.backend.backend import Backend as BaseBackend @@ -40,6 +38,7 @@ gst_uri_metadata, gst_mime_types, gst_uri_duration from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.settings.cue_settings import CueSettingsRegistry +from lisp.ui.ui_utils import translate, qfile_filters class GstBackend(Plugin, BaseBackend): @@ -101,13 +100,19 @@ def supported_extensions(self): def _add_uri_audio_cue(self): """Add audio MediaCue(s) form user-selected files""" + dir = GstBackend.Config.get('mediaLookupDir', '') + if not os.path.exists(dir): + dir = self.app.session.path() files, _ = QFileDialog.getOpenFileNames( self.app.window, translate('GstBackend', 'Select media files'), - self.app.session.path(), + dir, qfile_filters(self.supported_extensions(), anyfile=True) ) + if files: + GstBackend.Config['mediaLookupDir'] = os.path.dirname(files[0]) + GstBackend.Config.write() QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) diff --git a/lisp/plugins/list_layout/default.json b/lisp/plugins/list_layout/default.json index 61b51bf9f..bdbfba2fa 100644 --- a/lisp/plugins/list_layout/default.json +++ b/lisp/plugins/list_layout/default.json @@ -1,5 +1,5 @@ { - "_version_": "1.2", + "_version_": "1.3", "_enabled_": true, "show": { "dBMeters": true, @@ -10,6 +10,7 @@ "selectionMode": false, "autoContinue": true, "goKey": "Space", + "goAction": "Default", "stopCueFade": true, "pauseCueFade": true, "resumeCueFade": true, diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index ab7e6fd95..b87e91b7a 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -62,7 +62,8 @@ def __init__(self, application): self._memento_model = CueMementoAdapter(self._list_model) self._running_model = RunningCueModel(self.cue_model) - self._view = ListLayoutView(self._list_model, self._running_model, self.Config) + self._view = ListLayoutView( + self._list_model, self._running_model, self.Config) # GO button self._view.goButton.clicked.connect(self.__go_slot) # Global actions @@ -224,7 +225,7 @@ def _key_pressed(self, event): if QKeySequence(keys) in self._go_key_sequence: event.accept() - self.go() + self.__go_slot() elif event.key() == Qt.Key_Space: if event.modifiers() == Qt.ShiftModifier: event.accept() @@ -315,7 +316,8 @@ def _context_invoked(self, event): self.show_context_menu(event.globalPos()) def __go_slot(self): - self.go() + action = CueAction(ListLayout.Config.get('goAction')) + self.go(action=action) def __cue_added(self, cue): cue.next.connect(self.__cue_next, Connection.QtQueued) diff --git a/lisp/plugins/list_layout/settings.py b/lisp/plugins/list_layout/settings.py index 000045731..575307111 100644 --- a/lisp/plugins/list_layout/settings.py +++ b/lisp/plugins/list_layout/settings.py @@ -22,8 +22,10 @@ from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QHBoxLayout, \ QLabel, QKeySequenceEdit, QGridLayout +from lisp.cues.cue import CueAction from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate +from lisp.ui.widgets import CueActionComboBox class ListLayoutSettings(SettingsPage): @@ -56,14 +58,23 @@ def __init__(self, **kwargs): self.selectionMode = QCheckBox(self.behaviorsGroup) self.behaviorsGroup.layout().addWidget(self.selectionMode) - self.goKeyLayout = QHBoxLayout() - self.behaviorsGroup.layout().addLayout(self.goKeyLayout) + self.goLayout = QGridLayout() + self.goLayout.setColumnStretch(0, 2) + self.goLayout.setColumnStretch(1, 5) + self.behaviorsGroup.layout().addLayout(self.goLayout) + self.goKeyLabel = QLabel(self.behaviorsGroup) - self.goKeyLayout.addWidget(self.goKeyLabel) + self.goLayout.addWidget(self.goKeyLabel, 0, 0) self.goKeyEdit = QKeySequenceEdit(self.behaviorsGroup) - self.goKeyLayout.addWidget(self.goKeyEdit) - self.goKeyLayout.setStretch(0, 2) - self.goKeyLayout.setStretch(1, 5) + self.goLayout.addWidget(self.goKeyEdit, 0, 1) + + self.goActionLabel = QLabel(self.behaviorsGroup) + self.goLayout.addWidget(self.goActionLabel, 1, 0) + self.goActionCombo = CueActionComboBox( + actions=(CueAction.Default, CueAction.Start, CueAction.FadeInStart), + mode=CueActionComboBox.Mode.Value + ) + self.goLayout.addWidget(self.goActionCombo, 1, 1) self.useFadeGroup = QGroupBox(self) self.useFadeGroup.setLayout(QGridLayout()) @@ -91,7 +102,9 @@ def retranslateUi(self): self.autoNext.setText(translate('ListLayout', 'Auto-select next cue')) self.selectionMode.setText( translate('ListLayout', 'Enable selection mode')) + self.goKeyLabel.setText(translate('ListLayout', 'Go key:')) + self.goActionLabel.setText(translate('ListLayout', 'Go action')) self.useFadeGroup.setTitle( translate('ListLayout', 'Use fade (buttons)')) @@ -107,8 +120,10 @@ def loadSettings(self, settings): self.showSeek.setChecked(settings['show']['seekSliders']) self.autoNext.setChecked(settings['autoContinue']) self.selectionMode.setChecked(settings['selectionMode']) + self.goKeyEdit.setKeySequence( QKeySequence(settings['goKey'], QKeySequence.NativeText)) + self.goActionCombo.setCurrentItem(settings['goAction']) self.stopCueFade.setChecked(settings['stopCueFade']) self.pauseCueFade.setChecked(settings['pauseCueFade']) @@ -125,8 +140,10 @@ def getSettings(self): }, 'autoContinue': self.autoNext.isChecked(), 'selectionMode': self.selectionMode.isChecked(), + 'goKey': self.goKeyEdit.keySequence().toString( QKeySequence.NativeText), + 'goAction': self.goActionCombo.currentItem(), 'stopCueFade': self.stopCueFade.isChecked(), 'pauseCueFade': self.pauseCueFade.isChecked(), diff --git a/lisp/plugins/list_layout/view.py b/lisp/plugins/list_layout/view.py index 94e067688..a2bf00675 100644 --- a/lisp/plugins/list_layout/view.py +++ b/lisp/plugins/list_layout/view.py @@ -43,7 +43,7 @@ def __init__(self, listModel, runModel, config, *args): self.goButton.setStyleSheet('font-size: 48pt;') self.layout().addWidget(self.goButton, 0, 0) - # INFO PANEL (top center) + # INFO PANEL (top-center) self.infoPanel = InfoPanel(self) self.infoPanel.setFixedHeight(120) self.layout().addWidget(self.infoPanel, 0, 1) @@ -53,20 +53,21 @@ def __init__(self, listModel, runModel, config, *args): self.controlButtons.setFixedHeight(120) self.layout().addWidget(self.controlButtons, 0, 2) - # CUE VIEW (center left) + # CUE VIEW (center-left) self.listView = CueListView(listModel, self) self.listView.currentItemChanged.connect(self.__listViewCurrentChanged) self.layout().addWidget(self.listView, 1, 0, 1, 2) - # PLAYING VIEW (center right) + # PLAYING VIEW (center-right) self.runView = RunningCuesListWidget(runModel, config, parent=self) self.runView.setMinimumWidth(300) self.runView.setMaximumWidth(300) self.layout().addWidget(self.runView, 1, 2) - def __listViewCurrentChanged(self, current, previous): + def __listViewCurrentChanged(self, current, _): + cue = None if current is not None: index = self.listView.indexOfTopLevelItem(current) - self.infoPanel.cue = self.listModel.item(index) - else: - self.infoPanel.cue = None + cue = self.listModel.item(index) + + self.infoPanel.cue = cue From 2ed91a18ea3cbccf01e39a861316850bf6cc7899 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 3 Oct 2018 14:19:02 +0200 Subject: [PATCH 131/333] missed some file --- lisp/application.py | 2 +- lisp/ui/layoutselect.py | 7 +++---- lisp/ui/settings/app_configuration.py | 2 +- lisp/ui/settings/app_pages/cue.py | 1 - 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lisp/application.py b/lisp/application.py index 93eeb1dd9..6c170ec59 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -122,7 +122,7 @@ def _new_session_dialog(self): """Show the layout-selection dialog""" try: # Prompt the user for a new layout - dialog = LayoutSelect(parent=self.__main_window) + dialog = LayoutSelect(parent=self.window) if dialog.exec_() == QDialog.Accepted: # If a valid file is selected load it, otherwise load the layout if exists(dialog.filepath): diff --git a/lisp/ui/layoutselect.py b/lisp/ui/layoutselect.py index 266820d43..8ef1cc57c 100644 --- a/lisp/ui/layoutselect.py +++ b/lisp/ui/layoutselect.py @@ -29,12 +29,11 @@ class LayoutSelect(QDialog): - def __init__(self, parent=None): - super().__init__(parent) - + def __init__(self, **kwargs): + super().__init__(**kwargs) self.filepath = '' - self.setWindowModality(Qt.ApplicationModal) + self.setWindowModality(Qt.WindowModal) self.setWindowTitle(translate('LayoutSelect', 'Layout selection')) self.setMaximumSize(675, 300) self.setMinimumSize(675, 300) diff --git a/lisp/ui/settings/app_configuration.py b/lisp/ui/settings/app_configuration.py index dc4f46690..09662ca40 100644 --- a/lisp/ui/settings/app_configuration.py +++ b/lisp/ui/settings/app_configuration.py @@ -118,7 +118,7 @@ def registerSettingsPage(path, page, config): """ :param path: indicate the page "position": 'category.sub.key' :type path: str - :type page: typing.Type[lisp.ui.settings.pages.SettingsPage] + :type page: type :type config: lisp.core.configuration.Configuration """ AppConfigurationDialog.PagesRegistry.set( diff --git a/lisp/ui/settings/app_pages/cue.py b/lisp/ui/settings/app_pages/cue.py index 0563c172b..2817e2ff0 100644 --- a/lisp/ui/settings/app_pages/cue.py +++ b/lisp/ui/settings/app_pages/cue.py @@ -50,7 +50,6 @@ def __init__(self, **kwargs): self.actionGroup.layout().addWidget(self.fadeActionEdit) self.retranslateUi() - self.loadConfiguration() def retranslateUi(self): self.interruptGroup.setTitle(translate('CueSettings', 'Interrupt fade')) From 5d7e83b0bc406765bc6ee071dc78a0164240a939 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 4 Oct 2018 12:23:46 +0200 Subject: [PATCH 132/333] Improve cues "next-action(s)" Add: new "next-action(s)" to select (close #74) Update: changed "next-action(s)" text to make clear what they do Update: logging viewer panels can be now resize Update: error dialogs resize when showing details, to accommodate the content Fix: now "next-action(s)" icons are visible --- lisp/cues/cue.py | 12 ++-- lisp/plugins/list_layout/layout.py | 15 ++-- lisp/plugins/list_layout/list_widgets.py | 29 ++++---- .../ui/icons/numix/custom/cue-select-next.svg | 65 +++++++++++++++++ .../icons/numix/custom/cue-trigger-next.svg | 65 +++++++++++++++++ .../custom/cue-select-next.svg | 69 +++++++++++++++++++ .../custom/cue-trigger-next.svg | 59 ++++++++++++++++ lisp/ui/logging/dialog.py | 6 ++ lisp/ui/logging/viewer.py | 14 ++-- lisp/ui/widgets/cue_next_actions.py | 11 ++- 10 files changed, 314 insertions(+), 31 deletions(-) create mode 100644 lisp/ui/icons/numix/custom/cue-select-next.svg create mode 100644 lisp/ui/icons/numix/custom/cue-trigger-next.svg create mode 100644 lisp/ui/icons/papirus-symbolic/custom/cue-select-next.svg create mode 100644 lisp/ui/icons/papirus-symbolic/custom/cue-trigger-next.svg diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 1d952378b..f6b1012ea 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -67,8 +67,10 @@ class CueAction(EqEnum): class CueNextAction(EqEnum): DoNothing = 'DoNothing' - AutoNext = 'AutoNext' - AutoFollow = 'AutoFollow' + TriggerAfterWait = 'TriggerAfterWait' + TriggerAfterEnd = 'TriggerAfterEnd' + SelectAfterWait = 'SelectAfterWait' + SelectAfterEnd = 'SelectAfterEnd' class Cue(HasProperties): @@ -265,7 +267,8 @@ def start(self, fade=False): if state & (CueState.IsStopped | CueState.PreWait_Pause | CueState.PostWait_Pause): - if self.next_action == CueNextAction.AutoNext: + if (self.next_action == CueNextAction.TriggerAfterWait or + self.next_action == CueNextAction.SelectAfterWait): self._state |= CueState.PostWait if self._postwait.wait(self.post_wait, lock=self._st_lock): @@ -533,5 +536,6 @@ def state(self): def __next_action_changed(self, next_action): self.end.disconnect(self.next.emit) - if next_action == CueNextAction.AutoFollow: + if (next_action == CueNextAction.TriggerAfterEnd or + next_action == CueNextAction.SelectAfterEnd): self.end.connect(self.next.emit) diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index b87e91b7a..39829fbde 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -24,7 +24,7 @@ from lisp.core.configuration import DummyConfiguration from lisp.core.properties import ProxyProperty from lisp.core.signal import Connection -from lisp.cues.cue import Cue, CueAction +from lisp.cues.cue import Cue, CueAction, CueNextAction from lisp.cues.cue_memento_model import CueMementoAdapter from lisp.layout.cue_layout import CueLayout from lisp.layout.cue_menu import SimpleMenuAction, MENU_PRIORITY_CUE, MenuActionsGroup @@ -326,10 +326,15 @@ def __cue_next(self, cue): try: next_index = cue.index + 1 if next_index < len(self._list_model): - next_cue = self._list_model.item(next_index) - next_cue.execute() + action = CueNextAction(cue.next_action) + if (action == CueNextAction.SelectAfterEnd or + action == CueNextAction.SelectAfterWait): + self.set_standby_index(next_index) + else: + next_cue = self._list_model.item(next_index) + next_cue.execute() - if self.auto_continue and next_cue is self.standby_cue(): - self.set_standby_index(next_index + 1) + if self.auto_continue and next_cue is self.standby_cue(): + self.set_standby_index(next_index + 1) except(IndexError, KeyError): pass diff --git a/lisp/plugins/list_layout/list_widgets.py b/lisp/plugins/list_layout/list_widgets.py index 67e358636..f5536283f 100644 --- a/lisp/plugins/list_layout/list_widgets.py +++ b/lisp/plugins/list_layout/list_widgets.py @@ -26,6 +26,7 @@ from lisp.cues.cue import CueNextAction, CueState from lisp.cues.cue_time import CueTime, CueWaitTime from lisp.ui.icons import IconTheme +from lisp.ui.widgets.cue_next_actions import tr_next_action class IndexWidget(QLabel): @@ -130,12 +131,13 @@ def paintEvent(self, event): class NextActionIcon(QLabel): - STYLESHEET = 'background: transparent; padding-left: 1px' + STYLESHEET = 'background: transparent;' SIZE = 16 def __init__(self, item, *args): super().__init__(*args) self.setStyleSheet(self.STYLESHEET) + self.setAlignment(Qt.AlignCenter) item.cue.changed('next_action').connect( self.__update, Connection.QtQueued) @@ -145,14 +147,14 @@ def __update(self, next_action): next_action = CueNextAction(next_action) pixmap = IconTheme.get('').pixmap(self.SIZE) - if next_action == CueNextAction.AutoNext: - pixmap = IconTheme.get('auto-next').pixmap(self.SIZE) - self.setToolTip(CueNextAction.AutoNext.value) - elif next_action == CueNextAction.AutoFollow: - pixmap = IconTheme.get('auto-follow').pixmap(self.SIZE) - self.setToolTip(CueNextAction.AutoFollow.value) - else: - self.setToolTip('') + if (next_action == CueNextAction.TriggerAfterWait or + next_action == CueNextAction.TriggerAfterEnd): + pixmap = IconTheme.get('cue-trigger-next').pixmap(self.SIZE) + elif (next_action == CueNextAction.SelectAfterWait or + next_action == CueNextAction.SelectAfterEnd): + pixmap = IconTheme.get('cue-select-next').pixmap(self.SIZE) + + self.setToolTip(tr_next_action(next_action)) self.setPixmap(pixmap) @@ -281,7 +283,8 @@ def __init__(self, *args): self._next_action_changed(self.cue.next_action) def _update_duration(self, duration): - if self.cue.next_action != CueNextAction.AutoFollow.value: + if (self.cue.next_action == CueNextAction.TriggerAfterWait or + self.cue.next_action == CueNextAction.SelectAfterWait): # The wait time is in seconds, we need milliseconds duration *= 1000 @@ -306,7 +309,8 @@ def _next_action_changed(self, next_action): self.cue.changed('post_wait').disconnect(self._update_duration) self.cue.changed('duration').disconnect(self._update_duration) - if next_action == CueNextAction.AutoFollow.value: + if (next_action == CueNextAction.TriggerAfterEnd or + next_action == CueNextAction.SelectAfterEnd): self.cue.interrupted.connect(self._stop, Connection.QtQueued) self.cue.started.connect(self._running, Connection.QtQueued) self.cue.stopped.connect(self._stop, Connection.QtQueued) @@ -333,7 +337,8 @@ def _next_action_changed(self, next_action): def _stop(self): super()._stop() - if self.cue.next_action == CueNextAction.AutoFollow.value: + if (self.cue.next_action == CueNextAction.TriggerAfterEnd or + self.cue.next_action == CueNextAction.SelectAfterEnd): self._update_duration(self.cue.duration) else: self._update_duration(self.cue.post_wait) diff --git a/lisp/ui/icons/numix/custom/cue-select-next.svg b/lisp/ui/icons/numix/custom/cue-select-next.svg new file mode 100644 index 000000000..50a38ac04 --- /dev/null +++ b/lisp/ui/icons/numix/custom/cue-select-next.svg @@ -0,0 +1,65 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/lisp/ui/icons/numix/custom/cue-trigger-next.svg b/lisp/ui/icons/numix/custom/cue-trigger-next.svg new file mode 100644 index 000000000..1deff6fd6 --- /dev/null +++ b/lisp/ui/icons/numix/custom/cue-trigger-next.svg @@ -0,0 +1,65 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/custom/cue-select-next.svg b/lisp/ui/icons/papirus-symbolic/custom/cue-select-next.svg new file mode 100644 index 000000000..f1c11003f --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/custom/cue-select-next.svg @@ -0,0 +1,69 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/lisp/ui/icons/papirus-symbolic/custom/cue-trigger-next.svg b/lisp/ui/icons/papirus-symbolic/custom/cue-trigger-next.svg new file mode 100644 index 000000000..13217be6b --- /dev/null +++ b/lisp/ui/icons/papirus-symbolic/custom/cue-trigger-next.svg @@ -0,0 +1,59 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/lisp/ui/logging/dialog.py b/lisp/ui/logging/dialog.py index 5131c7566..30b7b0505 100644 --- a/lisp/ui/logging/dialog.py +++ b/lisp/ui/logging/dialog.py @@ -61,6 +61,7 @@ def __init__(self, **kwargs): self.layout().addWidget(self.iconLabel, 0, 0) self.messageLabel = QLabel(self) + self.messageLabel.setMinimumWidth(300) self.layout().addWidget(self.messageLabel, 0, 1) self.buttonBox = QDialogButtonBox(self) @@ -113,6 +114,11 @@ def updateDisplayed(self): details = self._formatter.formatException(record.exc_info) self.detailsText.setText(details) + + width = self.detailsText.document().idealWidth() +\ + self.detailsText.contentsMargins().left() * 2 + self.detailsText.setFixedWidth(width if width < 800 else 800) + self.detailsButton.setVisible(bool(details)) # If no details, than hide the widget, otherwise keep it's current # visibility unchanged diff --git a/lisp/ui/logging/viewer.py b/lisp/ui/logging/viewer.py index e52f142aa..5e447ac0e 100644 --- a/lisp/ui/logging/viewer.py +++ b/lisp/ui/logging/viewer.py @@ -19,7 +19,7 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QAction, QToolBar, QMainWindow, QStatusBar, \ - QLabel, QTableView, QVBoxLayout, QWidget + QLabel, QTableView, QVBoxLayout, QWidget, QSplitter from lisp.ui.icons import IconTheme from lisp.ui.logging.common import LOG_LEVELS, LogAttributeRole, \ @@ -44,8 +44,8 @@ def __init__(self, log_model, config, **kwargs): self.setWindowTitle( translate('Logging', 'Linux Show Player - Log Viewer')) self.resize(800, 600) - self.setCentralWidget(QWidget()) - self.centralWidget().setLayout(QVBoxLayout()) + self.setCentralWidget(QSplitter()) + self.centralWidget().setOrientation(Qt.Vertical) self.setStatusBar(QStatusBar(self)) # Add a permanent label to the toolbar to display shown/filter records @@ -80,14 +80,14 @@ def __init__(self, log_model, config, **kwargs): self.logView.setSelectionMode(QTableView.SingleSelection) self.logView.setSelectionBehavior(QTableView.SelectRows) self.logView.horizontalHeader().setStretchLastSection(True) - self.centralWidget().layout().addWidget(self.logView) + self.centralWidget().addWidget(self.logView) # Display selected entry details self.detailsView = LogDetails(self) - self.centralWidget().layout().addWidget(self.detailsView) + self.centralWidget().addWidget(self.detailsView) - self.centralWidget().layout().setStretch(0, 5) - self.centralWidget().layout().setStretch(1, 2) + self.centralWidget().setStretchFactor(0, 3) + self.centralWidget().setStretchFactor(1, 2) # Setup visible columns for n in range(self.filterModel.columnCount()): diff --git a/lisp/ui/widgets/cue_next_actions.py b/lisp/ui/widgets/cue_next_actions.py index f3c38f8e4..0eebcc281 100644 --- a/lisp/ui/widgets/cue_next_actions.py +++ b/lisp/ui/widgets/cue_next_actions.py @@ -27,9 +27,14 @@ CueNextActionsStrings = { CueNextAction.DoNothing: QT_TRANSLATE_NOOP( 'CueNextAction', 'Do Nothing'), - CueNextAction.AutoFollow: QT_TRANSLATE_NOOP( - 'CueNextAction', 'Auto Follow'), - CueNextAction.AutoNext: QT_TRANSLATE_NOOP('CueNextAction', 'Auto Next') + CueNextAction.TriggerAfterEnd: QT_TRANSLATE_NOOP( + 'CueNextAction', 'Trigger after the end'), + CueNextAction.TriggerAfterWait: QT_TRANSLATE_NOOP( + 'CueNextAction', 'Trigger after post wait'), + CueNextAction.SelectAfterEnd: QT_TRANSLATE_NOOP( + 'CueNextAction', 'Select after the end'), + CueNextAction.SelectAfterWait: QT_TRANSLATE_NOOP( + 'CueNextAction', 'Select after post wait') } From e91c4f77b04747b47deeaa51f1162789fe3d5b47 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 13 Oct 2018 12:22:09 +0200 Subject: [PATCH 133/333] Add logging translation support --- lisp/application.py | 18 +++++--- lisp/core/configuration.py | 17 ++++++-- lisp/core/loading.py | 14 +++++- lisp/plugins/__init__.py | 40 +++++++++++------ lisp/plugins/action_cues/__init__.py | 10 +++-- lisp/plugins/action_cues/command_cue.py | 10 +++-- lisp/plugins/action_cues/osc_cue.py | 20 ++++++--- lisp/plugins/action_cues/volume_control.py | 3 +- lisp/plugins/cart_layout/layout.py | 2 +- lisp/plugins/cart_layout/settings.py | 5 +-- .../plugins/gst_backend/elements/jack_sink.py | 5 ++- lisp/plugins/gst_backend/gst_media.py | 11 ++++- lisp/plugins/list_layout/layout.py | 2 +- lisp/plugins/list_layout/settings.py | 8 ++-- lisp/plugins/network/api/__init__.py | 7 ++- lisp/plugins/network/server.py | 13 ++++-- lisp/plugins/osc/osc_server.py | 20 ++++++--- lisp/plugins/rename_cues/rename_ui.py | 43 +++++++++++-------- lisp/plugins/replay_gain/replay_gain.py | 30 +++++++++---- lisp/plugins/timecode/cue_tracker.py | 6 ++- lisp/plugins/timecode/protocols/artnet.py | 10 +++-- lisp/plugins/timecode/timecode.py | 5 ++- lisp/ui/settings/app_configuration.py | 8 +++- lisp/ui/settings/app_pages/layouts.py | 4 +- lisp/ui/ui_utils.py | 2 +- 25 files changed, 214 insertions(+), 99 deletions(-) diff --git a/lisp/application.py b/lisp/application.py index 6c170ec59..fceefb7ef 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -43,6 +43,7 @@ from lisp.ui.settings.cue_pages.cue_general import CueGeneralSettingsPage from lisp.ui.settings.cue_pages.media_cue import MediaCueSettings from lisp.ui.settings.cue_settings import CueSettingsRegistry +from lisp.ui.ui_utils import translate logger = logging.getLogger(__name__) @@ -137,7 +138,8 @@ def _new_session_dialog(self): qApp.quit() exit(0) except Exception: - logger.critical('Startup error', exc_info=True) + logger.critical( + translate('ApplicationError', 'Startup error'), exc_info=True) qApp.quit() def _new_session(self, layout): @@ -207,13 +209,17 @@ def _load_from_file(self, session_file): self.__cue_model.add(cue) except Exception: name = cues_dict.get('name', 'No name') - logging.exception( - 'Unable to create the cue "{}"'.format(name)) + logger.exception(translate( + 'ApplicationError', 'Unable to create the cue "{}"' + .format(name)) + ) MainActionsHandler.set_saved() self.__main_window.update_window_title() except Exception: - logging.exception( - 'Error while reading the session file "{}"'.format( - session_file)) + logger.exception(translate( + 'ApplicationError', + 'Error while reading the session file "{}"') + .format(session_file) + ) self._new_session_dialog() diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index b68a561ea..460c4f4ee 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -31,6 +31,7 @@ from lisp.core.signal import Signal from lisp.core.singleton import ABCSingleton from lisp.core.util import dict_merge, dict_merge_diff, typename +from lisp.ui.ui_utils import translate logger = logging.getLogger(__name__) @@ -72,7 +73,11 @@ def get(self, path, default=_UNSET): except (KeyError, TypeError): if default is not _UNSET: logger.warning( - 'Invalid path "{}", return default.'.format(path)) + translate( + 'ConfigurationWarning', + 'Invalid path "{}", return default.' + ).format(path) + ) return default raise ConfDictError('invalid path') @@ -217,7 +222,10 @@ def write(self): json.dump(self._root, f, indent=True) logger.debug( - 'Configuration written at {}'.format(self.user_path)) + translate( + 'ConfigurationDebug', 'Configuration written at {}' + ).format(self.user_path) + ) def _check_file(self): """Ensure the last configuration is present at the user-path position""" @@ -237,7 +245,10 @@ def _check_file(self): # Copy the new configuration copyfile(self.default_path, self.user_path) logger.info( - 'New configuration installed at {}'.format(self.user_path)) + translate( + 'ConfigurationInfo', 'New configuration installed at {}' + ).format(self.user_path) + ) @staticmethod def _read_json(path): diff --git a/lisp/core/loading.py b/lisp/core/loading.py index af681291a..e8322e744 100644 --- a/lisp/core/loading.py +++ b/lisp/core/loading.py @@ -21,6 +21,8 @@ import os.path import re +from lisp.ui.ui_utils import translate + try: from os import scandir except ImportError: @@ -71,7 +73,12 @@ def load_modules(self): yield mod_name, import_module(mod_path) except Exception: logger.warning( - 'Cannot load module: {0}'.format(mod_name), exc_info=True) + translate( + 'ModulesLoaderWarning', + 'Cannot load python module: "{0}"' + ).format(mod_name), + exc_info=True + ) class ClassLoader: @@ -133,7 +140,10 @@ def load_classes(self): yield cls_name, cls except Exception: logger.warning( - 'Cannot load class: {0}'.format(cls_name), + translate( + 'ClassLoaderWarning', + 'Cannot load python class: "{0}"' + ).format(cls_name), exc_info=True ) diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index b1e95e9e8..05b391a63 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -24,7 +24,7 @@ from lisp import USER_DIR from lisp.core.configuration import JSONFileConfiguration from lisp.core.loading import load_classes -from lisp.ui.ui_utils import install_translation +from lisp.ui.ui_utils import install_translation, translate PLUGINS = {} LOADED = {} @@ -63,7 +63,8 @@ def load_plugins(application): install_translation(mod_name) install_translation(mod_name, tr_path=path.join(mod_path, 'i18n')) except Exception: - logger.exception('PLUGINS: Failed "{}" load'.format(name)) + logger.exception( + translate('PluginsError', 'Failed to load "{}"').format(name)) __init_plugins(application) @@ -85,9 +86,9 @@ def __init_plugins(application): # We've go through all the not loaded plugins and weren't able # to resolve their dependencies, which means there are cyclic or # missing/disabled dependencies - logger.warning( - 'Cannot satisfy some plugin dependencies: {}'.format( - ', '.join(pending)) + logger.warning(translate( + 'PluginsWarning', 'Cannot satisfy dependencies for: {}') + .format(', '.join(pending)) ) return @@ -99,7 +100,7 @@ def __load_plugins(plugins, application, optionals=True): otherwise leave it in the pending dict. If all of it's dependencies are satisfied then try to load it. - :type plugins: typing.MutableMapping[str, Type[lisp.core.plugin.Plugin]] + :type typing.MutableMapping[str, Type[lisp.core.plugin.Plugin]] :type application: lisp.application.Application :type optionals: bool :rtype: bool @@ -123,12 +124,17 @@ def __load_plugins(plugins, application, optionals=True): if plugin.Config.get('_enabled_', False): # Create an instance of the plugin and save it LOADED[name] = plugin(application) - logger.info('Plugin loaded: "{}"'.format(name)) + logger.info(translate( + 'PluginsInfo', 'Plugin loaded: "{}"').format(name)) else: - logger.debug( - 'Plugin disabled in configuration: "{}"'.format(name)) + logger.debug(translate( + 'PluginsDebug', + 'Plugin disabled in configuration: "{}"').format(name) + ) except Exception: - logger.exception('Failed to load plugin: "{}"'.format(name)) + logger.exception(translate( + 'PluginsError', 'Failed to load plugin: "{}"'.format(name)) + ) return resolved @@ -138,9 +144,13 @@ def finalize_plugins(): for plugin in LOADED: try: LOADED[plugin].finalize() - logger.info('Plugin terminated: "{}"'.format(plugin)) + logger.info(translate( + 'PluginsInfo', 'Plugin terminated: "{}"').format(plugin)) except Exception: - logger.exception('Failed to terminate plugin: "{}"'.format(plugin)) + logger.exception(translate( + 'PluginsError', + 'Failed to terminate plugin: "{}"').format(plugin) + ) def is_loaded(plugin_name): @@ -151,5 +161,7 @@ def get_plugin(plugin_name): if is_loaded(plugin_name): return LOADED[plugin_name] else: - raise PluginNotLoadedError( - 'the requested plugin is not loaded: {}'.format(plugin_name)) + raise PluginNotLoadedError(translate( + 'PluginsError', + 'the requested plugin is not loaded: {}').format(plugin_name) + ) diff --git a/lisp/plugins/action_cues/__init__.py b/lisp/plugins/action_cues/__init__.py index 9b61cbd5c..5f3103b05 100644 --- a/lisp/plugins/action_cues/__init__.py +++ b/lisp/plugins/action_cues/__init__.py @@ -20,7 +20,6 @@ import logging from os import path -from lisp.application import Application from lisp.core.loading import load_classes from lisp.core.plugin import Plugin from lisp.cues.cue_factory import CueFactory @@ -48,7 +47,8 @@ def __init__(self, app): 'Action cues' ) - logger.debug('Loaded "' + name + '"') + logger.debug(translate( + 'ActionCuesDebug', 'Registered cue: "{}"').format(name)) def _new_cue_factory(self, cue_class): def cue_factory(): @@ -56,7 +56,9 @@ def cue_factory(): cue = CueFactory.create_cue(cue_class.__name__) self.app.cue_model.add(cue) except Exception: - logger.exception( - 'Cannot create cue {}'.format(cue_class.__name__)) + logger.exception(translate( + 'ActionsCuesError', + 'Cannot create cue {}').format(cue_class.__name__) + ) return cue_factory diff --git a/lisp/plugins/action_cues/command_cue.py b/lisp/plugins/action_cues/command_cue.py index 859e88919..ee0b49cd7 100644 --- a/lisp/plugins/action_cues/command_cue.py +++ b/lisp/plugins/action_cues/command_cue.py @@ -76,10 +76,12 @@ def __exec_command(self): self._ended() elif not self.no_error: # If an error occurs and not in no-error mode - logger.error(translate( - 'CommandCue', - 'Command cue ended with an error status. ' - 'Exit code: {}').format(rcode)) + logger.error( + translate( + 'CommandCue', + 'Command cue ended with an error status. Exit code: {}' + ).format(rcode) + ) self._error() self.__process = None diff --git a/lisp/plugins/action_cues/osc_cue.py b/lisp/plugins/action_cues/osc_cue.py index aafc2fbd8..4f6156f2c 100644 --- a/lisp/plugins/action_cues/osc_cue.py +++ b/lisp/plugins/action_cues/osc_cue.py @@ -128,7 +128,8 @@ def __init_arguments(self): arg['start'], 0]) except KeyError: - logger.error('Could not parse argument list, nothing sent') + logger.error(translate( + 'OscCueError', 'Could not parse argument list, nothing sent')) return False # set fade type, based on the first argument, which will have a fade @@ -167,7 +168,8 @@ def __start__(self, fade=False): else: self._position = 1 else: - logger.error('Error while parsing arguments, nothing sent') + logger.error(translate( + 'OscCueError', 'Error while parsing arguments, nothing sent')) return False @@ -193,7 +195,8 @@ def __fade(self, fade_type): if ended: self._ended() except Exception: - logger.exception(translate('OscCue', 'Error during cue execution.')) + logger.exception( + translate('OscCueError', 'Error during cue execution.')) self._error() def current_time(self): @@ -287,7 +290,10 @@ def getSettings(self): if not (checkable and not self.oscGroup.isChecked()): if not test_path(self.pathEdit.text()): - logger.error('Error parsing OSC path, message will be unable to send') + logger.error(translate( + 'OscCueError', + 'Error parsing OSC path, message will be unable to send') + ) if not (checkable and not self.oscGroup.isChecked()): try: @@ -305,7 +311,11 @@ def getSettings(self): conf['args'] = args_list except ValueError: - logger.error('Error parsing OSC arguments, message will be unable to send') + logger.error(translate( + 'OscCueError', + 'Error parsing OSC arguments, ' + 'message will be unable to send') + ) if not (checkable and not self.fadeGroup.isCheckable()): conf['duration'] = self.fadeSpin.value() * 1000 diff --git a/lisp/plugins/action_cues/volume_control.py b/lisp/plugins/action_cues/volume_control.py index 31d4c4c9e..f0aff5eae 100644 --- a/lisp/plugins/action_cues/volume_control.py +++ b/lisp/plugins/action_cues/volume_control.py @@ -111,7 +111,8 @@ def __fade(self, fade_type): self.__fader.target.live_volume = self.volume self._ended() except Exception: - logger.exception(translate('OscCue', 'Error during cue execution.')) + logger.exception( + translate('VolumeControlError', 'Error during cue execution.')) self._error() def current_time(self): diff --git a/lisp/plugins/cart_layout/layout.py b/lisp/plugins/cart_layout/layout.py index 80c5bed15..50a572e84 100644 --- a/lisp/plugins/cart_layout/layout.py +++ b/lisp/plugins/cart_layout/layout.py @@ -37,7 +37,7 @@ class CartLayout(CueLayout): - NAME = 'Cart Layout' + NAME = QT_TRANSLATE_NOOP('LayoutName', 'Cart Layout') DESCRIPTION = translate( 'LayoutDescription', 'Organize cues in grid like pages') DETAILS = [ diff --git a/lisp/plugins/cart_layout/settings.py b/lisp/plugins/cart_layout/settings.py index e187c4b7a..695162376 100644 --- a/lisp/plugins/cart_layout/settings.py +++ b/lisp/plugins/cart_layout/settings.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QGridLayout, \ QSpinBox, QLabel @@ -26,8 +26,7 @@ class CartLayoutSettings(SettingsPage): - - Name = 'Cart Layout' + Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Cart Layout') def __init__(self, **kwargs): super().__init__(**kwargs) diff --git a/lisp/plugins/gst_backend/elements/jack_sink.py b/lisp/plugins/gst_backend/elements/jack_sink.py index 79ccdd9ff..3ff62dec8 100644 --- a/lisp/plugins/gst_backend/elements/jack_sink.py +++ b/lisp/plugins/gst_backend/elements/jack_sink.py @@ -26,6 +26,7 @@ from lisp.core.properties import Property from lisp.plugins.gst_backend.gi_repository import Gst from lisp.plugins.gst_backend.gst_element import GstMediaElement +from lisp.ui.ui_utils import translate logger = logging.getLogger(__name__) @@ -127,8 +128,10 @@ def __jack_connect(self): try: JackSink._ControlClient.disconnect(port, conn_port) except jack.JackError: - logger.exception( + logger.exception(translate( + 'JackSinkError', 'An error occurred while disconnection Jack ports') + ) for output, in_ports in enumerate(self.connections): for input_name in in_ports: diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index 4cc4f6180..bb1869b04 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -26,6 +26,7 @@ from lisp.plugins.gst_backend import elements as gst_elements from lisp.plugins.gst_backend.gi_repository import Gst from lisp.plugins.gst_backend.gst_element import GstMediaElements +from lisp.ui.ui_utils import translate logger = logging.getLogger(__name__) @@ -220,10 +221,16 @@ def __init_pipeline(self): try: self.elements.append(all_elements[element](self.__pipeline)) except KeyError: - logger.warning('Invalid pipeline element: {}'.format(element)) + logger.warning( + translate( + 'GstMediaWarning', 'Invalid pipeline element: "{}"' + ).format(element) + ) except Exception: logger.warning( - 'Cannot create pipeline element: {}'.format(element), + translate( + 'GstMediaError', 'Cannot create pipeline element: "{}"' + ).format(element), exc_info=True ) diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index 39829fbde..691c64416 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -35,7 +35,7 @@ class ListLayout(CueLayout): - NAME = 'List Layout' + NAME = QT_TRANSLATE_NOOP('LayoutName', 'List Layout') DESCRIPTION = QT_TRANSLATE_NOOP( 'LayoutDescription', 'Organize the cues in a list') DETAILS = [ diff --git a/lisp/plugins/list_layout/settings.py b/lisp/plugins/list_layout/settings.py index 575307111..7ac854f64 100644 --- a/lisp/plugins/list_layout/settings.py +++ b/lisp/plugins/list_layout/settings.py @@ -17,10 +17,10 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtGui import QKeySequence -from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QHBoxLayout, \ - QLabel, QKeySequenceEdit, QGridLayout +from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QLabel, \ + QKeySequenceEdit, QGridLayout from lisp.cues.cue import CueAction from lisp.ui.settings.pages import SettingsPage @@ -29,7 +29,7 @@ class ListLayoutSettings(SettingsPage): - Name = 'List Layout' + Name = QT_TRANSLATE_NOOP('SettingsPageName', 'List Layout') def __init__(self, **kwargs): super().__init__(**kwargs) diff --git a/lisp/plugins/network/api/__init__.py b/lisp/plugins/network/api/__init__.py index c998d2576..2b02cb086 100644 --- a/lisp/plugins/network/api/__init__.py +++ b/lisp/plugins/network/api/__init__.py @@ -2,6 +2,7 @@ from os import path from lisp.core.loading import ModulesLoader +from lisp.ui.ui_utils import translate logger = logging.getLogger(__name__) @@ -10,4 +11,8 @@ def route_all(app, api): for name, module in ModulesLoader(__package__, path.dirname(__file__)): for endpoint in getattr(module, '__endpoints__', ()): api.add_route(endpoint.UriTemplate, endpoint(app)) - logger.debug('New end-point: {}'.format(endpoint.UriTemplate)) + logger.debug( + translate( + 'NetworkApiDebug','New end-point: {}' + ).format(endpoint.UriTemplate) + ) diff --git a/lisp/plugins/network/server.py b/lisp/plugins/network/server.py index 9734816c9..46cdd888d 100644 --- a/lisp/plugins/network/server.py +++ b/lisp/plugins/network/server.py @@ -21,6 +21,8 @@ from threading import Thread from wsgiref.simple_server import make_server, WSGIRequestHandler +from lisp.ui.ui_utils import translate + logger = logging.getLogger(__name__) @@ -33,7 +35,10 @@ def __init__(self, host, port, api): def run(self): try: logger.info( - 'Start serving network API at: http://{}:{}/'.format( + translate( + 'ApiServerInfo', + 'Start serving network API at: http://{}:{}/' + ).format( self.wsgi_server.server_address[0], self.wsgi_server.server_address[1], ) @@ -41,9 +46,10 @@ def run(self): self.wsgi_server.serve_forever() - logger.info('Stop serving network API') + logger.info(translate('APIServerInfo', 'Stop serving network API')) except Exception: - logger.exception('Network API server stopped working.') + logger.exception(translate( + 'ApiServerError', 'Network API server stopped working.')) def stop(self): self.wsgi_server.shutdown() @@ -53,6 +59,7 @@ def stop(self): class APIRequestHandler(WSGIRequestHandler): + """Implement custom logging.""" def log_message(self, format, *args): logger.debug(format % args) diff --git a/lisp/plugins/osc/osc_server.py b/lisp/plugins/osc/osc_server.py index 8258221a9..f5d45d1de 100644 --- a/lisp/plugins/osc/osc_server.py +++ b/lisp/plugins/osc/osc_server.py @@ -25,6 +25,7 @@ from threading import Lock from lisp.core.signal import Signal +from lisp.ui.ui_utils import translate logger = logging.getLogger(__name__) @@ -91,10 +92,14 @@ def start(self): self.__running = True - logger.info('OSC server started at {}'.format(self.__srv.url)) + logger.info( + translate( + 'OscServerInfo', 'OSC server started at {}' + ).format(self.__srv.url) + ) except ServerError: - logger.exception('Cannot start OSC sever') - logger.debug(traceback.format_exc()) + logger.exception( + translate('OscServerError', 'Cannot start OSC sever')) def stop(self): if self.__srv is not None: @@ -104,7 +109,7 @@ def stop(self): self.__running = False self.__srv.free() - logger.info('OSC server stopped') + logger.info(translate('OscServerInfo', 'OSC server stopped')) def send(self, path, *args): with self.__lock: @@ -113,7 +118,8 @@ def send(self, path, *args): def __log_message(self, path, args, types, src, user_data): logger.debug( - 'Message from {} -> path: "{}" args: {}'.format( - src.get_url(), path, args - ) + translate( + 'OscServerDebug', + 'Message from {} -> path: "{}" args: {}' + ).format(src.get_url(), path, args) ) diff --git a/lisp/plugins/rename_cues/rename_ui.py b/lisp/plugins/rename_cues/rename_ui.py index 594d9da96..32a3cd07d 100644 --- a/lisp/plugins/rename_cues/rename_ui.py +++ b/lisp/plugins/rename_cues/rename_ui.py @@ -204,19 +204,22 @@ def onHelpButtonClicked(self): msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setWindowTitle(translate('RenameCues', 'Regex help')) - msg.setText(translate('RenameCues', - "You can use Regexes to rename your cues.\n\n" - "Insert expressions captured with regexes in the " - "line below with $0 for the first parenthesis, $1 for" - "the second, etc...\n" - "In the second line, you can use standard Python Regexes " - "to match expressions in the original cues names. Use " - "parenthesis to capture parts of the matched expression.\n\n" - "Exemple : \n^[a-z]([0-9]+) will find a lower case character ([a-z]), " - "followed by one or more number.\n" - "Only the numbers are between parenthesis and will be usable with " - "$0 in the first line.\n\n" - "For more information about Regexes, consult python documentation")) + msg.setText(translate( + 'RenameCues', + 'You can use Regexes to rename your cues.\n\n' + 'Insert expressions captured with regexes in the ' + 'line below with $0 for the first parenthesis, $1 for' + 'the second, etc...\n' + 'In the second line, you can use standard Python Regexes ' + 'to match expressions in the original cues names. Use ' + 'parenthesis to capture parts of the matched expression.\n\n' + 'Exemple: \n^[a-z]([0-9]+) will find a lower case character' + '([a-z]), followed by one or more number.\n' + 'Only the numbers are between parenthesis and will be usable with ' + '$0 in the first line.\n\n' + 'For more information about Regexes, consult python documentation ' + 'at: https://docs.python.org/3/howto/regex.html#regex-howto' + )) msg.exec_() def onRegexLineChanged(self): @@ -224,7 +227,11 @@ def onRegexLineChanged(self): try: regex = re.compile(pattern) except re.error: - logger.debug("Regex error: invalid pattern") + logger.debug( + translate( + 'RenameUiDebug', 'Regex error: Invalid pattern'), + exc_info=True + ) else: for cue in self.cues_list: result = regex.search(cue['cue_name']) @@ -244,7 +251,7 @@ def onOutRegexChanged(self): for cue in self.cues_list: out_string = out_pattern for n in range(len(cue['regex_groups'])): - pattern = f"\${n}" + pattern = rf'\${n}' try: out_string = re.sub( pattern, @@ -252,8 +259,10 @@ def onOutRegexChanged(self): out_string ) except IndexError: - logger.debug( - "Regex error: catch with () before display with $n") + logger.debug(translate( + 'RenameUiDebug', + 'Regex error: catch with () before display with $n') + ) if cue['selected']: cue['cue_preview'] = out_string diff --git a/lisp/plugins/replay_gain/replay_gain.py b/lisp/plugins/replay_gain/replay_gain.py index a0e957048..576a9fe8a 100644 --- a/lisp/plugins/replay_gain/replay_gain.py +++ b/lisp/plugins/replay_gain/replay_gain.py @@ -207,8 +207,10 @@ def run(self): try: self._post_process(future.result()) except Exception: - logger.exception( - 'An error occurred while processing gain results.') + logger.exception(translate( + 'ReplayGainError', + 'An error occurred while processing gain results.' + )) finally: self.on_progress.emit(1) else: @@ -217,7 +219,8 @@ def run(self): if self._running: MainActionsHandler.do_action(self._action) else: - logger.info('Gain processing stopped by user.') + logger.info(translate( + 'ReplayGainInfo', 'Gain processing stopped by user.')) self.on_progress.emit(-1) self.on_progress.disconnect() @@ -235,9 +238,11 @@ def _post_process(self, gain): for media in self.files[gain.uri]: self._action.add_media(media, volume) - logger.debug('Applied gain for: {}'.format(gain.uri)) + logger.debug(translate( + 'ReplayGainDebug', 'Applied gain for: {}').format(gain.uri)) else: - logger.debug('Discarded gain for: {}'.format(gain.uri)) + logger.debug(translate( + 'ReplayGainDebug', 'Discarded gain for: {}').format(gain.uri)) class GstGain: @@ -269,7 +274,10 @@ def gain(self): gain_bus.connect('message::error', self._on_message) self.gain_pipe.set_state(Gst.State.PLAYING) - logger.info('Started gain calculation for: {}'.format(self.uri)) + logger.info(translate( + 'ReplayGainInfo', + 'Started gain calculation for: {}').format(self.uri) + ) # Block here until EOS self.__lock.acquire(False) @@ -300,7 +308,10 @@ def _on_message(self, bus, message): self.gain_value = tag[1] self.peak_value = peak[1] - logger.info('Gain calculated for: {}'.format(self.uri)) + logger.info(translate( + 'ReplayGainInfo', + 'Gain calculated for: {}').format(self.uri) + ) self.completed = True self.__release() elif message.type == Gst.MessageType.ERROR: @@ -311,7 +322,10 @@ def _on_message(self, bus, message): 'GStreamer: {}'.format(error.message), exc_info=error) self.__release() except Exception: - logger.exception('An error occurred during gain calculation.') + logger.exception(translate( + 'ReplayGainError', + 'An error occurred during gain calculation.') + ) self.__release() def __release(self): diff --git a/lisp/plugins/timecode/cue_tracker.py b/lisp/plugins/timecode/cue_tracker.py index 486eb2d56..a458b7827 100644 --- a/lisp/plugins/timecode/cue_tracker.py +++ b/lisp/plugins/timecode/cue_tracker.py @@ -25,6 +25,7 @@ from lisp.core.clock import Clock from lisp.core.signal import Connection from lisp.cues.cue_time import CueTime +from lisp.ui.ui_utils import translate logger = logging.getLogger(__name__) @@ -116,7 +117,10 @@ def send(self, time): if self.__lock.acquire(blocking=False): try: if not self.__protocol.send(self.format, time, self.__track): - logger.warning('Cannot send timecode, untracking cue') + logger.warning(translate( + 'TimecodeWarning', + 'Cannot send timecode, untracking cue') + ) self.untrack() except Exception: self.__lock.release() diff --git a/lisp/plugins/timecode/protocols/artnet.py b/lisp/plugins/timecode/protocols/artnet.py index 10bcd3e6e..7d55bc179 100644 --- a/lisp/plugins/timecode/protocols/artnet.py +++ b/lisp/plugins/timecode/protocols/artnet.py @@ -62,12 +62,14 @@ def send(self, fmt, time, track=-1): self.__client.SendTimeCode( ARTNET_FORMATS[fmt], hours, minutes, seconds, frame) except OLADNotRunningException: - logger.error( - translate( - 'Timecode', 'Cannot send timecode. \nOLA has stopped.')) + logger.error(translate( + 'TimecodeError', + 'Cannot send timecode. \nOLA daemon has stopped.') + ) return False except Exception: - logger.exception(translate('Timecode', 'Cannot send timecode.')) + logger.exception( + translate('TimecodeError', 'Cannot send timecode.')) return False self.__last_time = time diff --git a/lisp/plugins/timecode/timecode.py b/lisp/plugins/timecode/timecode.py index 22e618031..c1bb6f609 100644 --- a/lisp/plugins/timecode/timecode.py +++ b/lisp/plugins/timecode/timecode.py @@ -101,8 +101,9 @@ def __get_protocol(self, protocol_name): return protocols.get_protocol(protocol_name)() except Exception: logger.error( - translate('Timecode', 'Cannot load timecode protocol: "{}"') - .format(protocol_name), + translate( + 'Timecode', 'Cannot load timecode protocol: "{}"' + ).format(protocol_name), exc_info=True ) # Use a dummy protocol in case of failure diff --git a/lisp/ui/settings/app_configuration.py b/lisp/ui/settings/app_configuration.py index 09662ca40..cb91637e3 100644 --- a/lisp/ui/settings/app_configuration.py +++ b/lisp/ui/settings/app_configuration.py @@ -103,8 +103,12 @@ def _populateModel(self, m_parent, r_parent): page_name = getattr(page_class, 'Name', page_class.__name__) logger.warning( - 'Cannot load configuration page: "{}" ({})'.format( - page_name, r_parent.path()), exc_info=True) + translate( + 'AppConfigurationWarning', + 'Cannot load configuration page: "{}" ({})' + ).format(page_name, r_parent.path()), + exc_info=True + ) else: for r_node in r_parent.children: self._populateModel(mod_index, r_node) diff --git a/lisp/ui/settings/app_pages/layouts.py b/lisp/ui/settings/app_pages/layouts.py index a22891fa7..c69a512f4 100644 --- a/lisp/ui/settings/app_pages/layouts.py +++ b/lisp/ui/settings/app_pages/layouts.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QGridLayout, QCheckBox from lisp.ui.settings.pages import SettingsPage @@ -25,7 +25,7 @@ class LayoutsSettings(SettingsPage): - Name = 'Layouts' + Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Layouts') def __init__(self, **kwargs): super().__init__(**kwargs) diff --git a/lisp/ui/ui_utils.py b/lisp/ui/ui_utils.py index faa07d451..9713f0b45 100644 --- a/lisp/ui/ui_utils.py +++ b/lisp/ui/ui_utils.py @@ -104,7 +104,7 @@ def install_translation(name, tr_path=I18N_PATH): _TRANSLATORS.append(translator) logger.debug('Installed translation: {}'.format(tr_file)) else: - logger.debug('No translation for: {}'.format(tr_file)) + logger.debug('No translation at: {}'.format(tr_file)) def translate(context, text, disambiguation=None, n=-1): From 5f7b17e20936815c27414f2aa6bc06073e2574c1 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 23 Oct 2018 08:37:41 +0200 Subject: [PATCH 134/333] Start using "black" auto-formatting --- Pipfile.lock | 137 ++++++------ README.md | 15 +- i18n_update.py | 69 ++++--- lisp/__init__.py | 24 +-- lisp/application.py | 72 ++++--- lisp/backend/__init__.py | 1 - lisp/backend/audio_utils.py | 12 +- lisp/backend/media.py | 1 + lisp/backend/media_element.py | 4 +- lisp/core/action.py | 2 +- lisp/core/actions_handler.py | 7 +- lisp/core/configuration.py | 32 ++- lisp/core/decorators.py | 9 +- lisp/core/dicttree.py | 21 +- lisp/core/fader.py | 19 +- lisp/core/has_properties.py | 5 +- lisp/core/loading.py | 30 +-- lisp/core/memento_model.py | 10 +- lisp/core/memento_model_actions.py | 8 +- lisp/core/plugin.py | 6 +- lisp/core/properties.py | 3 +- lisp/core/proxy_model.py | 11 +- lisp/core/session.py | 8 +- lisp/core/signal.py | 19 +- lisp/core/util.py | 46 +++-- lisp/cues/cue.py | 130 ++++++------ lisp/cues/cue_actions.py | 11 +- lisp/cues/cue_factory.py | 5 +- lisp/cues/cue_memento_model.py | 11 +- lisp/cues/cue_model.py | 2 +- lisp/cues/cue_time.py | 3 +- lisp/cues/media_cue.py | 35 ++-- lisp/layout/cue_layout.py | 23 ++- lisp/layout/cue_menu.py | 32 +-- lisp/main.py | 66 ++++-- lisp/plugins/__init__.py | 68 +++--- lisp/plugins/action_cues/__init__.py | 24 ++- lisp/plugins/action_cues/collection_cue.py | 56 +++-- lisp/plugins/action_cues/command_cue.py | 47 +++-- lisp/plugins/action_cues/index_action_cue.py | 57 ++--- lisp/plugins/action_cues/midi_cue.py | 81 +++++--- lisp/plugins/action_cues/osc_cue.py | 183 +++++++++------- lisp/plugins/action_cues/seek_cue.py | 50 +++-- lisp/plugins/action_cues/stop_all.py | 24 ++- lisp/plugins/action_cues/volume_control.py | 79 ++++--- lisp/plugins/cart_layout/__init__.py | 9 +- lisp/plugins/cart_layout/cue_widget.py | 106 ++++++---- lisp/plugins/cart_layout/layout.py | 138 ++++++++----- lisp/plugins/cart_layout/model.py | 6 +- lisp/plugins/cart_layout/page_widget.py | 17 +- lisp/plugins/cart_layout/settings.py | 63 +++--- lisp/plugins/cart_layout/tab_widget.py | 4 +- lisp/plugins/controller/__init__.py | 2 +- lisp/plugins/controller/common.py | 56 ++--- lisp/plugins/controller/controller.py | 44 ++-- .../plugins/controller/controller_settings.py | 12 +- lisp/plugins/controller/protocols/keyboard.py | 58 +++--- lisp/plugins/controller/protocols/midi.py | 113 +++++----- lisp/plugins/controller/protocols/osc.py | 190 +++++++++-------- .../plugins/gst_backend/elements/alsa_sink.py | 14 +- .../gst_backend/elements/audio_dynamic.py | 27 ++- .../plugins/gst_backend/elements/audio_pan.py | 4 +- .../plugins/gst_backend/elements/auto_sink.py | 4 +- lisp/plugins/gst_backend/elements/auto_src.py | 2 +- lisp/plugins/gst_backend/elements/db_meter.py | 30 +-- .../gst_backend/elements/equalizer10.py | 24 +-- .../plugins/gst_backend/elements/jack_sink.py | 49 +++-- lisp/plugins/gst_backend/elements/pitch.py | 8 +- .../gst_backend/elements/preset_src.py | 66 ++++-- .../gst_backend/elements/pulse_sink.py | 6 +- lisp/plugins/gst_backend/elements/speed.py | 14 +- .../plugins/gst_backend/elements/uri_input.py | 33 +-- .../gst_backend/elements/user_element.py | 8 +- lisp/plugins/gst_backend/elements/volume.py | 18 +- lisp/plugins/gst_backend/gi_repository.py | 8 +- lisp/plugins/gst_backend/gst_backend.py | 50 +++-- lisp/plugins/gst_backend/gst_element.py | 8 +- lisp/plugins/gst_backend/gst_media.py | 53 +++-- lisp/plugins/gst_backend/gst_media_cue.py | 8 +- .../plugins/gst_backend/gst_media_settings.py | 42 ++-- lisp/plugins/gst_backend/gst_pipe_edit.py | 42 ++-- lisp/plugins/gst_backend/gst_settings.py | 11 +- lisp/plugins/gst_backend/settings/__init__.py | 6 +- .../plugins/gst_backend/settings/alsa_sink.py | 42 ++-- .../gst_backend/settings/audio_dynamic.py | 68 +++--- .../plugins/gst_backend/settings/audio_pan.py | 16 +- lisp/plugins/gst_backend/settings/db_meter.py | 31 +-- .../gst_backend/settings/equalizer10.py | 23 ++- .../plugins/gst_backend/settings/jack_sink.py | 89 +++++--- lisp/plugins/gst_backend/settings/pitch.py | 10 +- .../gst_backend/settings/preset_src.py | 21 +- lisp/plugins/gst_backend/settings/speed.py | 8 +- .../plugins/gst_backend/settings/uri_input.py | 60 ++++-- .../gst_backend/settings/user_element.py | 12 +- lisp/plugins/gst_backend/settings/volume.py | 42 ++-- lisp/plugins/list_layout/__init__.py | 9 +- lisp/plugins/list_layout/control_buttons.py | 37 ++-- lisp/plugins/list_layout/info_panel.py | 18 +- lisp/plugins/list_layout/layout.py | 86 +++++--- lisp/plugins/list_layout/list_view.py | 49 +++-- lisp/plugins/list_layout/list_widgets.py | 110 +++++----- lisp/plugins/list_layout/models.py | 7 +- lisp/plugins/list_layout/playing_view.py | 10 +- lisp/plugins/list_layout/playing_widgets.py | 36 ++-- lisp/plugins/list_layout/settings.py | 101 ++++----- lisp/plugins/list_layout/view.py | 4 +- lisp/plugins/media_info/__init__.py | 2 +- lisp/plugins/media_info/media_info.py | 78 ++++--- lisp/plugins/midi/__init__.py | 2 +- lisp/plugins/midi/midi.py | 21 +- lisp/plugins/midi/midi_input.py | 3 +- lisp/plugins/midi/midi_settings.py | 36 ++-- lisp/plugins/midi/midi_utils.py | 6 +- lisp/plugins/network/__init__.py | 2 +- lisp/plugins/network/api/__init__.py | 8 +- lisp/plugins/network/api/cues.py | 35 ++-- lisp/plugins/network/api/layout.py | 8 +- lisp/plugins/network/discovery.py | 9 +- lisp/plugins/network/discovery_dialogs.py | 40 ++-- lisp/plugins/network/endpoint.py | 2 +- lisp/plugins/network/network.py | 16 +- lisp/plugins/network/server.py | 16 +- lisp/plugins/osc/osc.py | 19 +- lisp/plugins/osc/osc_delegate.py | 13 +- lisp/plugins/osc/osc_server.py | 22 +- lisp/plugins/osc/osc_settings.py | 34 +-- lisp/plugins/presets/__init__.py | 2 +- lisp/plugins/presets/lib.py | 22 +- lisp/plugins/presets/presets.py | 50 +++-- lisp/plugins/presets/presets_ui.py | 175 +++++++++------- lisp/plugins/rename_cues/__init__.py | 2 +- lisp/plugins/rename_cues/rename_action.py | 2 +- lisp/plugins/rename_cues/rename_cues.py | 9 +- lisp/plugins/rename_cues/rename_ui.py | 195 ++++++++++-------- lisp/plugins/replay_gain/__init__.py | 2 +- lisp/plugins/replay_gain/gain_ui.py | 39 ++-- lisp/plugins/replay_gain/replay_gain.py | 108 ++++++---- lisp/plugins/synchronizer/__init__.py | 2 +- lisp/plugins/synchronizer/synchronizer.py | 45 ++-- lisp/plugins/timecode/cue_tracker.py | 12 +- lisp/plugins/timecode/protocol.py | 3 +- lisp/plugins/timecode/protocols/artnet.py | 18 +- lisp/plugins/timecode/protocols/midi.py | 35 ++-- lisp/plugins/timecode/settings.py | 64 +++--- lisp/plugins/timecode/timecode.py | 41 ++-- lisp/plugins/triggers/__init__.py | 2 +- lisp/plugins/triggers/triggers.py | 14 +- lisp/plugins/triggers/triggers_handler.py | 8 +- lisp/plugins/triggers/triggers_settings.py | 76 ++++--- lisp/ui/about.py | 120 ++++++----- lisp/ui/cuelistdialog.py | 21 +- lisp/ui/icons/__init__.py | 9 +- lisp/ui/layoutselect.py | 37 ++-- lisp/ui/logging/common.py | 50 ++--- lisp/ui/logging/details.py | 13 +- lisp/ui/logging/dialog.py | 35 ++-- lisp/ui/logging/models.py | 32 +-- lisp/ui/logging/status.py | 9 +- lisp/ui/logging/viewer.py | 50 +++-- lisp/ui/mainwindow.py | 119 ++++++----- lisp/ui/qdelegates.py | 31 +-- lisp/ui/qmodels.py | 8 +- lisp/ui/settings/app_configuration.py | 32 +-- lisp/ui/settings/app_pages/cue.py | 24 +-- lisp/ui/settings/app_pages/general.py | 53 +++-- lisp/ui/settings/app_pages/layouts.py | 31 +-- lisp/ui/settings/app_pages/plugins.py | 27 ++- lisp/ui/settings/cue_pages/cue_appearance.py | 83 +++++--- lisp/ui/settings/cue_pages/cue_general.py | 153 ++++++++------ lisp/ui/settings/cue_pages/media_cue.py | 61 +++--- lisp/ui/settings/cue_settings.py | 27 +-- lisp/ui/settings/pages.py | 3 +- lisp/ui/themes/dark/__init__.py | 2 +- lisp/ui/themes/dark/assetes.py | 11 +- lisp/ui/themes/dark/dark.py | 4 +- lisp/ui/ui_utils.py | 20 +- lisp/ui/widgets/cue_actions.py | 28 +-- lisp/ui/widgets/cue_next_actions.py | 18 +- lisp/ui/widgets/fades.py | 37 ++-- lisp/ui/widgets/pagestreewidget.py | 12 +- lisp/ui/widgets/qclickslider.py | 5 +- lisp/ui/widgets/qcolorbutton.py | 7 +- lisp/ui/widgets/qdbmeter.py | 23 ++- lisp/ui/widgets/qeditabletabbar.py | 8 +- lisp/ui/widgets/qenumcombobox.py | 2 +- lisp/ui/widgets/qmessagebox.py | 26 +-- lisp/ui/widgets/qmutebutton.py | 4 +- lisp/ui/widgets/qprogresswheel.py | 12 +- lisp/ui/widgets/qsteptimeedit.py | 5 +- lisp/ui/widgets/qstyledslider.py | 27 ++- lisp/ui/widgets/qvertiacallabel.py | 3 +- plugins_utils.py | 67 +++--- pyproject.toml | 12 ++ setup.py | 34 +-- 194 files changed, 3744 insertions(+), 2749 deletions(-) create mode 100644 pyproject.toml diff --git a/Pipfile.lock b/Pipfile.lock index 4b9686be5..a42ed4ea6 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -16,10 +16,10 @@ "default": { "certifi": { "hashes": [ - "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", - "sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a" + "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", + "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" ], - "version": "==2018.8.24" + "version": "==2018.10.15" }, "cffi": { "hashes": [ @@ -90,11 +90,11 @@ }, "mido": { "hashes": [ - "sha256:35142874d4521dc5fcebcdc3a645df87cb0ecad129dd031cbca391e2d052313f", - "sha256:64b9d1595da8f319bff2eb866f9181257d3670a7803f7e38415f22c03a577560" + "sha256:c4a7d5528fefa3d3dcaa2056d4bc873e2c96a395658d15af5a89c8c3fa7c7acc", + "sha256:fc6364efa028c8405166f63e6a83cbc6c17aaeac2c28680abe64ae48703a89dd" ], "index": "pypi", - "version": "==1.2.8" + "version": "==1.2.9" }, "pycairo": { "hashes": [ @@ -106,7 +106,6 @@ "hashes": [ "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" ], - "markers": "python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.1.*' and python_version >= '2.7'", "version": "==2.19" }, "pygobject": { @@ -125,30 +124,30 @@ }, "pyqt5": { "hashes": [ - "sha256:700b8bb0357bf0ac312bce283449de733f5773dfc77083664be188c8e964c007", - "sha256:76d52f3627fac8bfdbc4857ce52a615cd879abd79890cde347682ff9b4b245a2", - "sha256:7d0f7c0aed9c3ef70d5856e99f30ebcfe25a58300158dd46ee544cbe1c5b53db", - "sha256:d5dc2faf0aeacd0e8b69af1dc9f1276a64020193148356bb319bdfae22b78f88" + "sha256:0e1b099e036ea5e851fb4475b714b2229947dc848022c77420e47f55b642b299", + "sha256:ac1eb5a114b6e7788e8be378be41c5e54b17d5158994504e85e43b5fca006a39", + "sha256:d2309296a5a79d0a1c0e6c387c30f0398b65523a6dcc8a19cc172e46b949e00d", + "sha256:e85936bae1581bcb908847d2038e5b34237a5e6acc03130099a78930770e7ead" ], "index": "pypi", - "version": "==5.11.2" + "version": "==5.11.3" }, "pyqt5-sip": { "hashes": [ - "sha256:3bcd8efae7798ce41aa7c3a052bd5ce1849f437530b8a717bae39197e780f505", - "sha256:4a3c5767d6c238d8c62d252ac59312fac8b2264a1e8a5670081d7f3545893005", - "sha256:67481d70fb0c7fb83e77b9025e15d0e78c7647c228eef934bd20ba716845a519", - "sha256:7b2e563e4e56adee00101a29913fdcc49cc714f6c4f7eb35449f493c3a88fc45", - "sha256:92a4950cba7ad7b7f67c09bdf80170ac225b38844b3a10f1271b02bace2ffc64", - "sha256:9309c10f9e648521cfe03b62f4658dad2314f81886062cb30e0ad31b337e14b0", - "sha256:9f524e60fa6113b50c48fbd869b2aef19833f3fe278097b1e7403e8f4dd5392c", - "sha256:a10f59ad65b34e183853e1387b68901f473a2041f7398fac87c4e445ab149830", - "sha256:abc2b2df469b4efb01d9dba4b804cbf0312f109ed74752dc3a37394a77d55b1f", - "sha256:c09c17009a2dd2a6317a14d3cea9b2300fdb2206cf9bc4bae0870d1919897935", - "sha256:c30c162e1430fd5a02207f1bd478e170c61d89fcca11ac6d8babb73cb33a86a8", - "sha256:f00ceceef75a2140fda737bd30847ac69b7d92fbd32b6ea7b387017e72176bd8" + "sha256:125f77c087572c9272219cda030a63c2f996b8507592b2a54d7ef9b75f9f054d", + "sha256:14c37b06e3fb7c2234cb208fa461ec4e62b4ba6d8b32ca3753c0b2cfd61b00e3", + "sha256:1cb2cf52979f9085fc0eab7e0b2438eb4430d4aea8edec89762527e17317175b", + "sha256:4babef08bccbf223ec34464e1ed0a23caeaeea390ca9a3529227d9a57f0d6ee4", + "sha256:53cb9c1208511cda0b9ed11cffee992a5a2f5d96eb88722569b2ce65ecf6b960", + "sha256:549449d9461d6c665cbe8af4a3808805c5e6e037cd2ce4fd93308d44a049bfac", + "sha256:5f5b3089b200ff33de3f636b398e7199b57a6b5c1bb724bdb884580a072a14b5", + "sha256:a4d9bf6e1fa2dd6e73f1873f1a47cee11a6ba0cf9ba8cf7002b28c76823600d0", + "sha256:a4ee6026216f1fbe25c8847f9e0fbce907df5b908f84816e21af16ec7666e6fe", + "sha256:a91a308a5e0cc99de1e97afd8f09f46dd7ca20cfaa5890ef254113eebaa1adff", + "sha256:b0342540da479d2713edc68fb21f307473f68da896ad5c04215dae97630e0069", + "sha256:f997e21b4e26a3397cb7b255b8d1db5b9772c8e0c94b6d870a5a0ab5c27eacaa" ], - "version": "==4.19.12" + "version": "==4.19.13" }, "python-mimeparse": { "hashes": [ @@ -159,18 +158,29 @@ }, "python-rtmidi": { "hashes": [ - "sha256:a796fc764febb9240a3c3de5ed230ae4d74f505c4e6298fb7e5f811bef845501" + "sha256:0693c98d12bd568f359f8e25816f06bda1e921595d41f13e6af549e68871be0b", + "sha256:1a126193398d88715c1c5b9b2e715399d7b59435c4d21c1541babfd467eb2fa3", + "sha256:558f2d05a8aace53c970bb5bad7f0599eaf178dd1d65db0a3d6e19949623ca31", + "sha256:67e149aaf9392d1d3bf9ad9bd7fb2b0a966a215c3f7560d7dcf8c61c8425dc61", + "sha256:68c3f5f880f0c62c85f7b627539020bd512939e18a234a40c7bba3fc67dd7230", + "sha256:776e0ed1c0c66d3794b380f1e6af8aae9288e6d6ab6b2ef32b88a8f02320c445", + "sha256:7ea0ec498299d9663e9003476e3ad9caecae6acbd5db1453cce967cd18309d0d", + "sha256:82e6fde0d37a25726163245120eebd8873e959ec608c855cb32193c061f192f5", + "sha256:c09dd7a5b24619bc09a89eac2f3c1435cccad75c10eec4b88bfd4be6c09b0e00", + "sha256:cf6b8a742989feda2009260ec6e019d960fe8f9c22831189c8f2d9795faa009e", + "sha256:e5788048b0c73b8b4f76c851aa56c75a649e211906d1a42e9d59ed144388452e", + "sha256:eab13d4d7950646234a4a4526c8b655b136e7722dee8e4f1db9fc4e05dd5a477" ], "index": "pypi", - "version": "==1.1.1" + "version": "==1.1.2" }, "requests": { "hashes": [ - "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", - "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" + "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c", + "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279" ], "index": "pypi", - "version": "==2.19.1" + "version": "==2.20.0" }, "six": { "hashes": [ @@ -189,55 +199,52 @@ }, "urllib3": { "hashes": [ - "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", - "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" + "sha256:41c3db2fc01e5b907288010dec72f9d0a74e37d6994e6eb56849f59fea2265ae", + "sha256:8819bba37a02d143296a4d032373c4dd4aca11f6d4c9973335ca75f9c8475f59" ], - "markers": "python_version >= '2.6' and python_version != '3.2.*' and python_version < '4' and python_version != '3.3.*' and python_version != '3.0.*' and python_version != '3.1.*'", - "version": "==1.23" + "version": "==1.24" } }, "develop": { "cython": { "hashes": [ - "sha256:022592d419fc754509d0e0461eb2958dbaa45fb60d51c8a61778c58994edbe36", - "sha256:07659f4c57582104d9486c071de512fbd7e087a3a630535298442cc0e20a3f5a", - "sha256:13c73e2ffa93a615851e03fad97591954d143b5b62361b9adef81f46a31cd8ef", - "sha256:13eab5a2835a84ff62db343035603044c908d2b3b6eec09d67fdf9970acf7ac9", - "sha256:183b35a48f58862c4ec1e821f07bb7b1156c8c8559c85c32ae086f28947474eb", - "sha256:2f526b0887128bf20ab2acc905a975f62b5a04ab2f63ecbe5a30fc28285d0e0c", - "sha256:32de8637f5e6c5a76667bc7c8fc644bd9314dc19af36db8ce30a0b92ada0f642", - "sha256:4172c183ef4fb2ace6a29cdf7fc9200c5a471a7f775ff691975b774bd9ed3ad2", - "sha256:553956ec06ecbd731ef0c538eb28a5b46bedea7ab89b18237ff28b4b99d65eee", - "sha256:660eeb6870687fd3eda91e00ba4e72220545c254c8c4d967fd0c910f4fbb8cbc", - "sha256:693a8619ef066ece055ed065a15cf440f9d3ebd1bca60e87ea19144833756433", - "sha256:759c799e9ef418f163b5412e295e14c0a48fe3b4dcba9ab8aab69e9f511cfefd", - "sha256:827d3a91b7a7c31ce69e5974496fd9a8ba28eb498b988affb66d0d30de11d934", - "sha256:87e57b5d730cfab225d95e7b23abbc0c6f77598bd66639e93c73ce8afbae6f38", - "sha256:9400e5db8383346b0694a3e794d8bded18a27b21123516dcdf4b79d7ec28e98b", - "sha256:9ec27681c5b1b457aacb1cbda5db04aa28b76da2af6e1e1fd15f233eafe6a0b0", - "sha256:ae4784f040a3313c8bd00c8d04934b7ade63dc59692d8f00a5235be8ed72a445", - "sha256:b2ba8310ebd3c0e0b884d5e95bbd99d467d6af922acd1e44fe4b819839b2150e", - "sha256:b64575241f64f6ec005a4d4137339fb0ba5e156e826db2fdb5f458060d9979e0", - "sha256:c78ad0df75a9fc03ab28ca1b950c893a208c451a18f76796c3e25817d6994001", - "sha256:cdbb917e41220bd3812234dbe59d15391adbc2c5d91ae11a5273aab9e32ba7ec", - "sha256:d2223a80c623e2a8e97953ab945dfaa9385750a494438dcb55562eb1ddd9565a", - "sha256:e22f21cf92a9f8f007a280e3b3462c886d9068132a6c698dec10ad6125e3ca1e", - "sha256:ea5c16c48e561f4a6f6b8c24807494b77a79e156b8133521c400f22ca712101b", - "sha256:ee7a9614d51fe16e32ca5befe72e0808baff481791728449d0b17c8b0fe29eb9", - "sha256:ef86de9299e4ab2ebb129fb84b886bf40b9aced9807c6d6d5f28b46fb905f82c", - "sha256:f3e4860f5458a9875caa3de65e255720c0ed2ce71f0bcdab02497b32104f9db8", - "sha256:fc6c20a8ac22202a779ad4c59756647be0826993d2151a03c015e76d2368ae5f" + "sha256:019008a69e6b7c102f2ed3d733a288d1784363802b437dd2b91e6256b12746da", + "sha256:1441fe19c56c90b8c2159d7b861c31a134d543ef7886fd82a5d267f9f11f35ac", + "sha256:1d1a5e9d6ed415e75a676b72200ad67082242ec4d2d76eb7446da255ae72d3f7", + "sha256:339f5b985de3662b1d6c69991ab46fdbdc736feb4ac903ef6b8c00e14d87f4d8", + "sha256:35bdf3f48535891fee2eaade70e91d5b2cc1ee9fc2a551847c7ec18bce55a92c", + "sha256:3d0afba0aec878639608f013045697fb0969ff60b3aea2daec771ea8d01ad112", + "sha256:42c53786806e24569571a7a24ebe78ec6b364fe53e79a3f27eddd573cacd398f", + "sha256:48b919da89614d201e72fbd8247b5ae8881e296cf968feb5595a015a14c67f1f", + "sha256:49906e008eeb91912654a36c200566392bd448b87a529086694053a280f8af2d", + "sha256:49fc01a7c9c4e3c1784e9a15d162c2cac3990fcc28728227a6f8f0837aabda7c", + "sha256:501b671b639b9ca17ad303f8807deb1d0ff754d1dab106f2607d14b53cb0ff0b", + "sha256:5574574142364804423ab4428bd331a05c65f7ecfd31ac97c936f0c720fe6a53", + "sha256:6092239a772b3c6604be9e94b9ab4f0dacb7452e8ad299fd97eae0611355b679", + "sha256:71ff5c7632501c4f60edb8a24fd0a772e04c5bdca2856d978d04271b63666ef7", + "sha256:7dcf2ad14e25b05eda8bdd104f8c03a642a384aeefd25a5b51deac0826e646fa", + "sha256:8ca3a99f5a7443a6a8f83a5d8fcc11854b44e6907e92ba8640d8a8f7b9085e21", + "sha256:927da3b5710fb705aab173ad630b45a4a04c78e63dcd89411a065b2fe60e4770", + "sha256:94916d1ede67682638d3cc0feb10648ff14dc51fb7a7f147f4fedce78eaaea97", + "sha256:a3e5e5ca325527d312cdb12a4dab8b0459c458cad1c738c6f019d0d8d147081c", + "sha256:a7716a98f0b9b8f61ddb2bae7997daf546ac8fc594be6ba397f4bde7d76bfc62", + "sha256:acf10d1054de92af8d5bfc6620bb79b85f04c98214b4da7db77525bfa9fc2a89", + "sha256:de46ffb67e723975f5acab101c5235747af1e84fbbc89bf3533e2ea93fb26947", + "sha256:df428969154a9a4cd9748c7e6efd18432111fbea3d700f7376046c38c5e27081", + "sha256:f5ebf24b599caf466f9da8c4115398d663b2567b89e92f58a835e9da4f74669f", + "sha256:f79e45d5c122c4fb1fd54029bf1d475cecc05f4ed5b68136b0d6ec268bae68b6", + "sha256:f7a43097d143bd7846ffba6d2d8cd1cc97f233318dbd0f50a235ea01297a096b", + "sha256:fceb8271bc2fd3477094ca157c824e8ea840a7b393e89e766eea9a3b9ce7e0c6", + "sha256:ff919ceb40259f5332db43803aa6c22ff487e86036ce3921ae04b9185efc99a4" ], "index": "pypi", - "markers": "python_version >= '2.6' and python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.1.*'", - "version": "==0.28.5" + "version": "==0.29" }, "graphviz": { "hashes": [ "sha256:310bacfb969f0ac7c872610500e017c3e82b24a8abd33d289e99af162de30cb8", "sha256:865afa6ab9775cf29db03abd8e571a164042c726c35a1b3c1e2b8c4c645e2993" ], - "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*'", "version": "==0.9" }, "objgraph": { diff --git a/README.md b/README.md index 8e885fce4..c52e63bfd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ -# Linux Show Player [![GitHub release](https://img.shields.io/github/release/FrancescoCeruti/linux-show-player.svg?maxAge=2592000)](https://github.com/FrancescoCeruti/linux-show-player/releases) [![Github All Releases](https://img.shields.io/github/downloads/FrancescoCeruti/linux-show-player/total.svg?maxAge=2592000)](https://github.com/FrancescoCeruti/linux-show-player/releases/) [![Code Health](https://landscape.io/github/FrancescoCeruti/linux-show-player/master/landscape.svg?style=flat)](https://landscape.io/github/FrancescoCeruti/linux-show-player/master) [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg?maxAge=2592000)](https://gitter.im/linux-show-player/linux-show-player) -Linux Show Player (LiSP) - Sound player designed for stage productions +![Linux Show Player Logo](https://raw.githubusercontent.com/wiki/FrancescoCeruti/linux-show-player/media/site_logo.png) +

Sound player designed for stage productions

+ +

+License: GPL +GitHub release +Github All Releases +Code Health +Gitter +Code style: black +

--- @@ -34,4 +43,4 @@ Use the installed launcher from the menu (for the package installation), or $ linux-show-player -f # Open a saved session $ linux-show-player --locale # Launch using the given locale -*User documentation downloaded under the GitHub release page or [viewed online](http://linux-show-player-users.readthedocs.io/en/latest/index.html)* +*User documentation downloaded under the GitHub release page or [viewed online](http://linux-show-player-users.readthedocs.io/en/latest/index.html)* \ No newline at end of file diff --git a/i18n_update.py b/i18n_update.py index ded2dae2e..ece220dba 100755 --- a/i18n_update.py +++ b/i18n_update.py @@ -30,24 +30,26 @@ from scandir import scandir -parser = argparse.ArgumentParser(description='i18n utility for LiSP') -parser.add_argument('locales', nargs='*', - help='Locales of witch generate translations') -parser.add_argument('-n', '--noobsolete', help='Discard obsolete strings', - action='store_true') -parser.add_argument('-q', '--qm', help='Release .qm files', action='store_true') +parser = argparse.ArgumentParser(description="i18n utility for LiSP") +parser.add_argument( + "locales", nargs="*", help="Locales of witch generate translations" +) +parser.add_argument( + "-n", "--noobsolete", help="Discard obsolete strings", action="store_true" +) +parser.add_argument("-q", "--qm", help="Release .qm files", action="store_true") args = parser.parse_args() # pylupdate command with arguments -PYLUPDATE_CMD = ['pylupdate5'] +PYLUPDATE_CMD = ["pylupdate5"] if args.noobsolete: - PYLUPDATE_CMD.append('-noobsolete') + PYLUPDATE_CMD.append("-noobsolete") def existing_locales(): - for entry in scandir('lisp/i18n/ts/'): + for entry in scandir("lisp/i18n/ts/"): if entry.is_dir(): yield entry.name @@ -56,12 +58,12 @@ def existing_locales(): LOCALES = args.locales if not LOCALES: LOCALES = list(existing_locales()) - print('>>> UPDATE EXISTING:', ', '.join(LOCALES)) + print(">>> UPDATE EXISTING:", ", ".join(LOCALES)) def search_files(root, exclude=(), extensions=()): - exc_regex = '^(' + '|'.join(exclude) + ').*' if exclude else '$^' - ext_regex = '.*\.(' + '|'.join(extensions) + ')$' if extensions else '.*' + exc_regex = "^(" + "|".join(exclude) + ").*" if exclude else "$^" + ext_regex = ".*\.(" + "|".join(extensions) + ")$" if extensions else ".*" for path, directories, filenames in os.walk(root): if re.match(exc_regex, path): @@ -76,18 +78,19 @@ def search_files(root, exclude=(), extensions=()): yield filename -def create_pro_file(root, exclude=(), extensions=('py',)): +def create_pro_file(root, exclude=(), extensions=("py",)): base_name = os.path.basename(os.path.normpath(root)) - back = '../' * (len(root.split('/')) - 1) + back = "../" * (len(root.split("/")) - 1) - translations = 'TRANSLATIONS = ' + translations = "TRANSLATIONS = " for locale in LOCALES: translations += os.path.join( - back, 'i18n/ts/', locale, base_name + '.ts ') + back, "i18n/ts/", locale, base_name + ".ts " + ) - files = 'SOURCES = ' + ' '.join(search_files(root, exclude, extensions)) + files = "SOURCES = " + " ".join(search_files(root, exclude, extensions)) - with open(os.path.join(root, base_name + '.pro'), mode='w') as pro_file: + with open(os.path.join(root, base_name + ".pro"), mode="w") as pro_file: pro_file.write(translations) pro_file.write(os.linesep) pro_file.write(files) @@ -96,33 +99,31 @@ def create_pro_file(root, exclude=(), extensions=('py',)): def generate_for_submodules(path, qm=False): modules = [entry.path for entry in scandir(path) if entry.is_dir()] for module in modules: - if '__pycache__' not in module: + if "__pycache__" not in module: create_pro_file(module) - p_file = os.path.join(module, os.path.basename(module) + '.pro') + p_file = os.path.join(module, os.path.basename(module) + ".pro") if qm: subprocess.run( - ['lrelease', p_file], - stdout=sys.stdout, - stderr=sys.stderr + ["lrelease", p_file], stdout=sys.stdout, stderr=sys.stderr ) else: subprocess.run( PYLUPDATE_CMD + [p_file], stdout=sys.stdout, - stderr=sys.stderr + stderr=sys.stderr, ) -print('>>> UPDATE TRANSLATIONS FOR APPLICATION') -create_pro_file('lisp', exclude=('lisp/plugins/', )) +print(">>> UPDATE TRANSLATIONS FOR APPLICATION") +create_pro_file("lisp", exclude=("lisp/plugins/",)) if args.qm: - subprocess.run(['lrelease', 'lisp/lisp.pro'], - stdout=sys.stdout, - stderr=sys.stderr) + subprocess.run( + ["lrelease", "lisp/lisp.pro"], stdout=sys.stdout, stderr=sys.stderr + ) else: - subprocess.run(PYLUPDATE_CMD + ['lisp/lisp.pro'], - stdout=sys.stdout, - stderr=sys.stderr) + subprocess.run( + PYLUPDATE_CMD + ["lisp/lisp.pro"], stdout=sys.stdout, stderr=sys.stderr + ) -print('>>> UPDATE TRANSLATIONS FOR PLUGINS') -generate_for_submodules('lisp/plugins', qm=args.qm) +print(">>> UPDATE TRANSLATIONS FOR PLUGINS") +generate_for_submodules("lisp/plugins", qm=args.qm) diff --git a/lisp/__init__.py b/lisp/__init__.py index 9ad39759b..4410f9bf7 100644 --- a/lisp/__init__.py +++ b/lisp/__init__.py @@ -19,23 +19,23 @@ from os import path -__author__ = 'Francesco Ceruti' -__email__ = 'ceppofrancy@gmail.com' -__url__ = 'https://github.com/FrancescoCeruti/linux-show-player' -__license__ = 'GPLv3' -__version__ = '0.6dev' +__author__ = "Francesco Ceruti" +__email__ = "ceppofrancy@gmail.com" +__url__ = "https://github.com/FrancescoCeruti/linux-show-player" +__license__ = "GPLv3" +__version__ = "0.6dev" # Application wide "constants" APP_DIR = path.dirname(__file__) -USER_DIR = path.join(path.expanduser("~"), '.linux_show_player') +USER_DIR = path.join(path.expanduser("~"), ".linux_show_player") -LOGS_DIR = path.join(USER_DIR, 'logs') +LOGS_DIR = path.join(USER_DIR, "logs") -DEFAULT_APP_CONFIG = path.join(APP_DIR, 'default.json') -USER_APP_CONFIG = path.join(USER_DIR, 'lisp.json') +DEFAULT_APP_CONFIG = path.join(APP_DIR, "default.json") +USER_APP_CONFIG = path.join(USER_DIR, "lisp.json") -I18N_PATH = path.join(APP_DIR, 'i18n', 'qm') +I18N_PATH = path.join(APP_DIR, "i18n", "qm") -ICON_THEMES_DIR = path.join(APP_DIR, 'ui', 'icons') -ICON_THEME_COMMON = 'lisp' +ICON_THEMES_DIR = path.join(APP_DIR, "ui", "icons") +ICON_THEME_COMMON = "lisp" diff --git a/lisp/application.py b/lisp/application.py index fceefb7ef..babb50d47 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -61,13 +61,17 @@ def __init__(self, app_conf): # Register general settings widget AppConfigurationDialog.registerSettingsPage( - 'general', AppGeneral, self.conf) + "general", AppGeneral, self.conf + ) AppConfigurationDialog.registerSettingsPage( - 'general.cue', CueAppSettings, self.conf) + "general.cue", CueAppSettings, self.conf + ) AppConfigurationDialog.registerSettingsPage( - 'layouts', LayoutsSettings, self.conf) + "layouts", LayoutsSettings, self.conf + ) AppConfigurationDialog.registerSettingsPage( - 'plugins', PluginsSettings, self.conf) + "plugins", PluginsSettings, self.conf + ) # Register common cue-settings widgets CueSettingsRegistry().add(CueGeneralSettingsPage, Cue) @@ -99,16 +103,16 @@ def cue_model(self): """:rtype: lisp.cues.cue_model.CueModel""" return self.__cue_model - def start(self, session_file=''): + def start(self, session_file=""): # Show the mainWindow maximized self.__main_window.showMaximized() if exists(session_file): self._load_from_file(session_file) else: - layout_name = self.conf.get('layout.default', 'nodefault') + layout_name = self.conf.get("layout.default", "nodefault") - if layout_name.lower() != 'nodefault': + if layout_name.lower() != "nodefault": self._new_session(layout.get_layout(layout_name)) else: self._new_session_dialog() @@ -139,7 +143,8 @@ def _new_session_dialog(self): exit(0) except Exception: logger.critical( - translate('ApplicationError', 'Startup error'), exc_info=True) + translate("ApplicationError", "Startup error"), exc_info=True + ) qApp.quit() def _new_session(self, layout): @@ -164,23 +169,24 @@ def _save_to_file(self, session_file): self.session.session_file = session_file # Add the cues - session_dict = {'cues': []} + session_dict = {"cues": []} for cue in self.__cue_model: - session_dict['cues'].append( - cue.properties(defaults=False, filter=filter_live_properties)) + session_dict["cues"].append( + cue.properties(defaults=False, filter=filter_live_properties) + ) # Sort cues by index, allow sorted-models to load properly - session_dict['cues'].sort(key=lambda cue: cue['index']) + session_dict["cues"].sort(key=lambda cue: cue["index"]) # Get session settings - session_dict['session'] = self.__session.properties() + session_dict["session"] = self.__session.properties() # Remove the 'session_file' property (not needed in the session file) - session_dict['session'].pop('session_file', None) + session_dict["session"].pop("session_file", None) # Write to a file the json-encoded dictionary - with open(session_file, mode='w', encoding='utf-8') as file: - if self.conf['session.minSave']: - file.write(json.dumps(session_dict, separators=(',', ':'))) + with open(session_file, mode="w", encoding="utf-8") as file: + if self.conf["session.minSave"]: + file.write(json.dumps(session_dict, separators=(",", ":"))) else: file.write(json.dumps(session_dict, sort_keys=True, indent=4)) @@ -190,36 +196,40 @@ def _save_to_file(self, session_file): def _load_from_file(self, session_file): """ Load a saved session from file """ try: - with open(session_file, mode='r', encoding='utf-8') as file: + with open(session_file, mode="r", encoding="utf-8") as file: session_dict = json.load(file) # New session self._new_session( - layout.get_layout(session_dict['session']['layout_type'])) - self.__session.update_properties(session_dict['session']) + layout.get_layout(session_dict["session"]["layout_type"]) + ) + self.__session.update_properties(session_dict["session"]) self.__session.session_file = session_file # Load cues - for cues_dict in session_dict.get('cues', {}): - cue_type = cues_dict.pop('_type_', 'Undefined') - cue_id = cues_dict.pop('id') + for cues_dict in session_dict.get("cues", {}): + cue_type = cues_dict.pop("_type_", "Undefined") + cue_id = cues_dict.pop("id") try: cue = CueFactory.create_cue(cue_type, cue_id=cue_id) cue.update_properties(cues_dict) self.__cue_model.add(cue) except Exception: - name = cues_dict.get('name', 'No name') - logger.exception(translate( - 'ApplicationError', 'Unable to create the cue "{}"' - .format(name)) + name = cues_dict.get("name", "No name") + logger.exception( + translate( + "ApplicationError", + 'Unable to create the cue "{}"'.format(name), + ) ) MainActionsHandler.set_saved() self.__main_window.update_window_title() except Exception: - logger.exception(translate( - 'ApplicationError', - 'Error while reading the session file "{}"') - .format(session_file) + logger.exception( + translate( + "ApplicationError", + 'Error while reading the session file "{}"', + ).format(session_file) ) self._new_session_dialog() diff --git a/lisp/backend/__init__.py b/lisp/backend/__init__.py index 4d8c468ec..f39fe5a5c 100644 --- a/lisp/backend/__init__.py +++ b/lisp/backend/__init__.py @@ -32,4 +32,3 @@ def get_backend(): :rtype: lisp.backends.base.backend.Backend """ return __backend - diff --git a/lisp/backend/audio_utils.py b/lisp/backend/audio_utils.py index e1e768fa1..0f8fb0c3e 100644 --- a/lisp/backend/audio_utils.py +++ b/lisp/backend/audio_utils.py @@ -26,7 +26,7 @@ # Decibel value to be considered -inf MIN_VOLUME_DB = -144 # Linear value of MIN_VOLUME_DB -MIN_VOLUME = 6.30957344480193e-08 +MIN_VOLUME = 6.309_573_444_801_93e-08 # Maximum linear value for the volume, equals to 1000% MAX_VOLUME = 10 # Decibel value of MAX_VOLUME @@ -52,7 +52,7 @@ def fader_to_slider(value): Note:: If converting back to an integer scale use `round()` instead of `int()` """ - return (value / 3.16227766) ** (1 / 3.7) + return (value / 3.162_277_66) ** (1 / 3.7) def slider_to_fader(value): @@ -72,14 +72,14 @@ def slider_to_fader(value): elif value < 0.0: value = 0 - return 3.16227766 * (value ** 3.7) + return 3.162_277_66 * (value ** 3.7) def python_duration(path, sound_module): """Returns audio-file duration using the given standard library module.""" duration = 0 try: - with sound_module.open(path, 'r') as file: + with sound_module.open(path, "r") as file: frames = file.getnframes() rate = file.getframerate() duration = int(frames / rate * 1000) @@ -89,10 +89,10 @@ def python_duration(path, sound_module): def uri_duration(uri): """Return the audio-file duration, using the given uri""" - protocol, path = uri.split('://') + protocol, path = uri.split("://") path = urllib.parse.unquote(path) - if protocol == 'file': + if protocol == "file": for mod in [wave, aifc, sunau]: duration = python_duration(path, mod) if duration > 0: diff --git a/lisp/backend/media.py b/lisp/backend/media.py index 39574d64a..3b9dbde38 100644 --- a/lisp/backend/media.py +++ b/lisp/backend/media.py @@ -27,6 +27,7 @@ class MediaState(Enum): """Identify the current media state""" + Null = 0 Playing = 1 Paused = 2 diff --git a/lisp/backend/media_element.py b/lisp/backend/media_element.py index 77c31ae63..0d3b34c4c 100644 --- a/lisp/backend/media_element.py +++ b/lisp/backend/media_element.py @@ -24,6 +24,7 @@ class ElementType(Enum): """The type of the media-element""" + Input = 0 Output = 1 Plugin = 2 @@ -31,6 +32,7 @@ class ElementType(Enum): class MediaType(Enum): """The media-type that the element handle (Audio/Video)""" + Audio = 0 Video = 1 AudioAndVideo = 2 @@ -44,4 +46,4 @@ class MediaElement(HasProperties): ElementType = None MediaType = None - Name = 'Undefined' + Name = "Undefined" diff --git a/lisp/core/action.py b/lisp/core/action.py index 7aa6f35bd..0150d9648 100644 --- a/lisp/core/action.py +++ b/lisp/core/action.py @@ -58,4 +58,4 @@ def log(self): :rtype: str """ - return '' + return "" diff --git a/lisp/core/actions_handler.py b/lisp/core/actions_handler.py index 975dd6c26..e657f634a 100644 --- a/lisp/core/actions_handler.py +++ b/lisp/core/actions_handler.py @@ -29,9 +29,10 @@ class ActionsHandler: """Provide a classic undo/redo mechanism based on stacks.""" - DO_ACTION_STR = '{}' - UNDO_ACTION_STR = translate('Actions', 'Undo: {}') - REDO_ACTION_STR = translate('Actions', 'Redo: {}') + + DO_ACTION_STR = "{}" + UNDO_ACTION_STR = translate("Actions", "Undo: {}") + REDO_ACTION_STR = translate("Actions", "Redo: {}") def __init__(self, stack_size=-1): super().__init__() diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index 460c4f4ee..eb7d23ea1 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -45,13 +45,12 @@ class ConfDictError(Exception): class ConfDict: """Allow to access nested-dictionaries values using "paths".""" - def __init__(self, root=None, sep='.'): + def __init__(self, root=None, sep="."): if not sep: - raise ValueError('ConfDict separator cannot be empty') + raise ValueError("ConfDict separator cannot be empty") if not isinstance(sep, str): raise TypeError( - 'ConfDict separator must be a str, not {}'.format( - typename(sep)) + "ConfDict separator must be a str, not {}".format(typename(sep)) ) self._sep = sep @@ -62,8 +61,7 @@ def __init__(self, root=None, sep='.'): self._root = root else: raise TypeError( - 'ConfDict root must be a dict, not {}'.format( - typename(root)) + "ConfDict root must be a dict, not {}".format(typename(root)) ) def get(self, path, default=_UNSET): @@ -74,13 +72,13 @@ def get(self, path, default=_UNSET): if default is not _UNSET: logger.warning( translate( - 'ConfigurationWarning', - 'Invalid path "{}", return default.' + "ConfigurationWarning", + 'Invalid path "{}", return default.', ).format(path) ) return default - raise ConfDictError('invalid path') + raise ConfDictError("invalid path") def set(self, path, value): try: @@ -91,14 +89,14 @@ def set(self, path, value): return False except (KeyError, TypeError): - raise ConfDictError('invalid path') + raise ConfDictError("invalid path") def pop(self, path): try: node, key = self.__traverse(self.sp(path), self._root) return node.pop(key) except (KeyError, TypeError): - raise ConfDictError('invalid path') + raise ConfDictError("invalid path") def update(self, new_conf): """Update the ConfDict using the given dictionary. @@ -218,12 +216,12 @@ def read(self): self._root = self._read_json(self.user_path) def write(self): - with open(self.user_path, 'w') as f: + with open(self.user_path, "w") as f: json.dump(self._root, f, indent=True) logger.debug( translate( - 'ConfigurationDebug', 'Configuration written at {}' + "ConfigurationDebug", "Configuration written at {}" ).format(self.user_path) ) @@ -232,11 +230,11 @@ def _check_file(self): if path.exists(self.user_path): # Read default configuration default = self._read_json(self.default_path) - default = default.get('_version_', object()) + default = default.get("_version_", object()) # Read user configuration user = self._read_json(self.user_path) - user = user.get('_version_', object()) + user = user.get("_version_", object()) # if the user and default version are the same we are good if user == default: @@ -246,13 +244,13 @@ def _check_file(self): copyfile(self.default_path, self.user_path) logger.info( translate( - 'ConfigurationInfo', 'New configuration installed at {}' + "ConfigurationInfo", "New configuration installed at {}" ).format(self.user_path) ) @staticmethod def _read_json(path): - with open(path, 'r') as f: + with open(path, "r") as f: return json.load(f) diff --git a/lisp/core/decorators.py b/lisp/core/decorators.py index 8555403b8..bba36c884 100644 --- a/lisp/core/decorators.py +++ b/lisp/core/decorators.py @@ -74,8 +74,9 @@ def locked_function(target=None, *, lock=None, blocking=True, timeout=-1): # If called with (keywords) arguments if target is None: - return partial(locked_function, lock=lock, blocking=blocking, - timeout=timeout) + return partial( + locked_function, lock=lock, blocking=blocking, timeout=timeout + ) if lock is None: target.__lock__ = RLock() @@ -113,7 +114,7 @@ def locked_method(target=None, *, blocking=True, timeout=-1): return partial(locked_method, blocking=blocking, timeout=timeout) # generate a lock_name like "__method_name_lock__" - lock_name = '__' + target.__name__ + '_lock__' + lock_name = "__" + target.__name__ + "_lock__" target.__meta_lock__ = Lock() @wraps(target) @@ -154,7 +155,7 @@ def wrapped(*args, **kwargs): try: return target(*args, **kwargs) except Exception: - logging.warning('Exception suppressed.', exc_info=True) + logging.warning("Exception suppressed.", exc_info=True) return wrapped diff --git a/lisp/core/dicttree.py b/lisp/core/dicttree.py index 0f4a0815e..0d9c694a9 100644 --- a/lisp/core/dicttree.py +++ b/lisp/core/dicttree.py @@ -29,7 +29,7 @@ class DictTreeError(Exception): class DictNode: - Sep = '.' + Sep = "." def __init__(self, value=None, parent=None): self.parent = parent @@ -45,17 +45,18 @@ def children(self): def add_child(self, node, name): if not isinstance(node, DictNode): raise TypeError( - 'DictNode children must be a DictNode, not {}'.format( - typename(node)) + "DictNode children must be a DictNode, not {}".format( + typename(node) + ) ) if not isinstance(name, str): raise TypeError( - 'DictNode name must be a str, not {}'.format( - typename(node)) + "DictNode name must be a str, not {}".format(typename(node)) ) if self.Sep in name: raise DictTreeError( - 'DictNode name cannot contains the path separator') + "DictNode name cannot contains the path separator" + ) # Set node name and parent node.name = name @@ -77,7 +78,7 @@ def get(self, path, default=_UNSET): if default is not _UNSET: return default - raise DictTreeError('invalid path') + raise DictTreeError("invalid path") def set(self, path, value): if isinstance(path, str): @@ -93,7 +94,7 @@ def set(self, path, value): else: self._children[child_key].value = value except (KeyError, TypeError): - raise DictTreeError('invalid path') + raise DictTreeError("invalid path") def pop(self, path): if isinstance(path, str): @@ -106,7 +107,7 @@ def pop(self, path): else: self._children.pop(child_key) except (KeyError, TypeError): - raise DictTreeError('Invalid path') + raise DictTreeError("Invalid path") def path(self): if self.parent is not None: @@ -116,7 +117,7 @@ def path(self): else: return self.name - return '' + return "" @classmethod def jp(cls, *paths): diff --git a/lisp/core/fader.py b/lisp/core/fader.py index c46a084c6..1e35727d8 100644 --- a/lisp/core/fader.py +++ b/lisp/core/fader.py @@ -99,8 +99,9 @@ def fade(self, duration, to_value, fade_type): if not isinstance(fade_type, (FadeInType, FadeOutType)): raise AttributeError( - 'fade_type must be one of FadeInType or FadeOutType members,' - 'not {}'.format(typename(fade_type))) + "fade_type must be one of FadeInType or FadeOutType members," + "not {}".format(typename(fade_type)) + ) try: self._time = 0 @@ -114,11 +115,15 @@ def fade(self, duration, to_value, fade_type): return while self._time <= duration and not self._running.is_set(): - rsetattr(self._target, - self._attribute, - functor(ntime(self._time, begin, duration), - value_diff, - base_value)) + rsetattr( + self._target, + self._attribute, + functor( + ntime(self._time, begin, duration), + value_diff, + base_value, + ), + ) self._time += 1 self._running.wait(0.01) diff --git a/lisp/core/has_properties.py b/lisp/core/has_properties.py index 22f36ab7f..fa304826e 100644 --- a/lisp/core/has_properties.py +++ b/lisp/core/has_properties.py @@ -166,10 +166,7 @@ def class_defaults(cls, filter=None): for name in filter(cls.__pro__.copy()) } else: - return { - name: getattr(cls, name).default - for name in cls.__pro__ - } + return {name: getattr(cls, name).default for name in cls.__pro__} def properties(self, defaults=True, filter=None): """ diff --git a/lisp/core/loading.py b/lisp/core/loading.py index e8322e744..a3d1581df 100644 --- a/lisp/core/loading.py +++ b/lisp/core/loading.py @@ -50,7 +50,7 @@ def load_modules(self): for entry in scandir(self.pkg_path): # Exclude __init__, __pycache__ and likely - if re.match('^__.*', entry.name): + if re.match("^__.*", entry.name): continue mod_name = entry.name @@ -59,14 +59,14 @@ def load_modules(self): mod_name, ext = os.path.splitext(entry.name) # Exclude all non-python files - if not re.match('.py[cod]?', ext): + if not re.match(".py[cod]?", ext): continue # Exclude excluded ¯\_(°^°)_/¯ if mod_name in self.excluded: continue - mod_path = self.pkg + '.' + mod_name + mod_path = self.pkg + "." + mod_name try: # Import module @@ -74,10 +74,10 @@ def load_modules(self): except Exception: logger.warning( translate( - 'ModulesLoaderWarning', - 'Cannot load python module: "{0}"' + "ModulesLoaderWarning", + 'Cannot load python module: "{0}"', ).format(mod_name), - exc_info=True + exc_info=True, ) @@ -109,7 +109,7 @@ class ClassLoader: """ - def __init__(self, pkg, pkg_path, pre=('',), suf=('',), exclude=()): + def __init__(self, pkg, pkg_path, pre=("",), suf=("",), exclude=()): """ :param pkg: dotted name of the package :param pkg_path: path of the package to scan @@ -132,7 +132,7 @@ def load_classes(self): for mod_name, module in self._mods_loader: # Load classes from imported module for prefix, suffix in zip(self.prefixes, self.suffixes): - cls_name = 'undefined' + cls_name = "undefined" try: cls_name = module_to_class_name(mod_name, prefix, suffix) if hasattr(module, cls_name): @@ -141,18 +141,18 @@ def load_classes(self): except Exception: logger.warning( translate( - 'ClassLoaderWarning', - 'Cannot load python class: "{0}"' + "ClassLoaderWarning", + 'Cannot load python class: "{0}"', ).format(cls_name), - exc_info=True + exc_info=True, ) -def load_classes(pkg, pkg_path, pre=('',), suf=('',), exclude=()): +def load_classes(pkg, pkg_path, pre=("",), suf=("",), exclude=()): return ClassLoader(pkg, pkg_path, pre, suf, exclude) -def module_to_class_name(mod_name, pre='', suf=''): +def module_to_class_name(mod_name, pre="", suf=""): """Return the supposed class name from loaded module. Substitutions: @@ -165,10 +165,10 @@ def module_to_class_name(mod_name, pre='', suf=''): """ # Capitalize the first letter of each word - base_name = ''.join(word.title() for word in mod_name.split('_')) + base_name = "".join(word.title() for word in mod_name.split("_")) # Add prefix and suffix to the base name return pre + base_name + suf def import_module(module_path): - return __import__(module_path, globals(), locals(), ['*']) + return __import__(module_path, globals(), locals(), ["*"]) diff --git a/lisp/core/memento_model.py b/lisp/core/memento_model.py index c941487ce..3a8a9c415 100644 --- a/lisp/core/memento_model.py +++ b/lisp/core/memento_model.py @@ -18,8 +18,11 @@ # along with Linux Show Player. If not, see . from lisp.core.actions_handler import MainActionsHandler -from lisp.core.memento_model_actions import AddItemAction, RemoveItemAction, \ - MoveItemAction +from lisp.core.memento_model_actions import ( + AddItemAction, + RemoveItemAction, + MoveItemAction, +) from lisp.core.proxy_model import ReadOnlyProxyModel @@ -76,4 +79,5 @@ def __init__(self, model_adapter, handler=None): def _item_moved(self, old_index, new_index): if not self._locked: self._handler.do_action( - self._move_action(self, self.model, old_index, new_index)) + self._move_action(self, self.model, old_index, new_index) + ) diff --git a/lisp/core/memento_model_actions.py b/lisp/core/memento_model_actions.py index 7de28b8d9..0c0b4b7fc 100644 --- a/lisp/core/memento_model_actions.py +++ b/lisp/core/memento_model_actions.py @@ -25,7 +25,7 @@ class MementoAction(Action): """Actions created by the MementoModel to register model changes.""" - __slots__ = ('_m_model', '_model') + __slots__ = ("_m_model", "_model") def __init__(self, m_model, model): super().__init__() @@ -60,7 +60,7 @@ def __redo__(self): class AddItemAction(MementoAction): - __slots__ = '_item' + __slots__ = "_item" def __init__(self, m_model, model, item): super().__init__(m_model, model) @@ -75,7 +75,7 @@ def __redo__(self): class RemoveItemAction(MementoAction): - __slots__ = '_item' + __slots__ = "_item" def __init__(self, m_model, model, item): super().__init__(m_model, model) @@ -90,7 +90,7 @@ def __redo__(self): class MoveItemAction(MementoAction): - __slots__ = ('_old_index', '_new_index') + __slots__ = ("_old_index", "_new_index") def __init__(self, m_model, model_adapter, old_index, new_index): super().__init__(m_model, model_adapter) diff --git a/lisp/core/plugin.py b/lisp/core/plugin.py index 422974f06..f2f07a3a0 100644 --- a/lisp/core/plugin.py +++ b/lisp/core/plugin.py @@ -25,11 +25,11 @@ class Plugin: """Base class for plugins.""" - Name = 'Plugin' + Name = "Plugin" Depends = () OptDepends = () - Authors = ('None',) - Description = 'No Description' + Authors = ("None",) + Description = "No Description" Config = DummyConfiguration() def __init__(self, app): diff --git a/lisp/core/properties.py b/lisp/core/properties.py index 8b8a85b8b..d46ade624 100644 --- a/lisp/core/properties.py +++ b/lisp/core/properties.py @@ -106,7 +106,8 @@ class InstanceProperty: To be used an InstanceProperty should be used in combination of an HasInstanceProperties object. """ - __slots__ = ('value', 'default') + + __slots__ = ("value", "default") def __init__(self, default=None): self.value = default diff --git a/lisp/core/proxy_model.py b/lisp/core/proxy_model.py index 0a90038c9..0f720763d 100644 --- a/lisp/core/proxy_model.py +++ b/lisp/core/proxy_model.py @@ -28,8 +28,9 @@ def __init__(self, model): if not isinstance(model, Model): raise TypeError( - 'ProxyModel model must be a Model object, not {0}'.format( - typename(model)) + "ProxyModel model must be a Model object, not {0}".format( + typename(model) + ) ) self._model = model @@ -86,10 +87,10 @@ def __contains__(self, item): class ReadOnlyProxyModel(ProxyModel): def add(self, item): - raise ModelException('cannot add items into a read-only model') + raise ModelException("cannot add items into a read-only model") def remove(self, item): - raise ModelException('cannot remove items from a read-only model') + raise ModelException("cannot remove items from a read-only model") def reset(self): - raise ModelException('cannot reset read-only model') + raise ModelException("cannot reset read-only model") diff --git a/lisp/core/session.py b/lisp/core/session.py index 41e02228f..b1f1e2cd1 100644 --- a/lisp/core/session.py +++ b/lisp/core/session.py @@ -25,10 +25,10 @@ class Session(HasInstanceProperties): - layout_type = Property(default='') + layout_type = Property(default="") layout = Property(default={}) - session_file = Property(default='') + session_file = Property(default="") def __init__(self, layout): super().__init__() @@ -44,14 +44,14 @@ def name(self): if self.session_file: return os.path.splitext(os.path.basename(self.session_file))[0] else: - return 'Untitled' + return "Untitled" def path(self): """Return the current session-file path.""" if self.session_file: return os.path.dirname(self.session_file) else: - return os.path.expanduser('~') + return os.path.expanduser("~") def abs_path(self, rel_path): """Return an absolute version of the given path.""" diff --git a/lisp/core/signal.py b/lisp/core/signal.py index aa110a76c..9b81b4bc3 100644 --- a/lisp/core/signal.py +++ b/lisp/core/signal.py @@ -31,7 +31,7 @@ from lisp.core.decorators import async_function from lisp.core.util import weak_call_proxy -__all__ = ['Signal', 'Connection'] +__all__ = ["Signal", "Connection"] logger = logging.getLogger(__name__) @@ -59,7 +59,7 @@ def __init__(self, slot_callable, callback=None): elif callable(slot_callable): self._reference = weakref.ref(slot_callable, self._expired) else: - raise TypeError('slot must be callable') + raise TypeError("slot must be callable") self._callback = callback self._slot_id = slot_id(slot_callable) @@ -103,8 +103,9 @@ def __init__(self, *args, **kwargs): self._invoker.customEvent = self._custom_event def call(self, *args, **kwargs): - QApplication.instance().sendEvent(self._invoker, - self._event(*args, **kwargs)) + QApplication.instance().sendEvent( + self._invoker, self._event(*args, **kwargs) + ) def _event(self, *args, **kwargs): return QSlotEvent(self._reference, *args, **kwargs) @@ -117,8 +118,9 @@ class QtQueuedSlot(QtSlot): """Qt queued (safe) slot, execute the call inside the qt-event-loop.""" def call(self, *args, **kwargs): - QApplication.instance().postEvent(self._invoker, - self._event(*args, **kwargs)) + QApplication.instance().postEvent( + self._invoker, self._event(*args, **kwargs) + ) class QSlotEvent(QEvent): @@ -133,6 +135,7 @@ def __init__(self, reference, *args, **kwargs): class Connection(Enum): """Available connection modes.""" + Direct = Slot Async = AsyncSlot QtDirect = QtSlot @@ -177,7 +180,7 @@ def connect(self, slot_callable, mode=Connection.Direct): :raise ValueError: if mode not in Connection enum """ if mode not in Connection: - raise ValueError('invalid mode value: {0}'.format(mode)) + raise ValueError("invalid mode value: {0}".format(mode)) with self.__lock: sid = slot_id(slot_callable) @@ -187,7 +190,7 @@ def connect(self, slot_callable, mode=Connection.Direct): # to avoid cyclic references. self.__slots[sid] = mode.new_slot( slot_callable, - weak_call_proxy(weakref.WeakMethod(self.__remove_slot)) + weak_call_proxy(weakref.WeakMethod(self.__remove_slot)), ) def disconnect(self, slot=None): diff --git a/lisp/core/util.py b/lisp/core/util.py index b0c04c91d..eb3d64175 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -33,9 +33,11 @@ def dict_merge(dct, merge_dct): :type merge_dct: Mapping """ for key, value in merge_dct.items(): - if (key in dct and - isinstance(dct[key], MutableMapping) and - isinstance(value, Mapping)): + if ( + key in dct + and isinstance(dct[key], MutableMapping) + and isinstance(value, Mapping) + ): dict_merge(dct[key], value) else: dct[key] = value @@ -68,11 +70,14 @@ def dict_merge_diff(dct, cmp_dct): return diff -def find_packages(path='.'): +def find_packages(path="."): """List the python packages in the given directory.""" - return [d for d in listdir(path) if isdir(join(path, d)) and - exists(join(path, d, '__init__.py'))] + return [ + d + for d in listdir(path) + if isdir(join(path, d)) and exists(join(path, d, "__init__.py")) + ] def time_tuple(millis): @@ -101,19 +106,19 @@ def strtime(time, accurate=False): # Cast time to int to avoid formatting problems time = time_tuple(int(time)) if time[0] > 0: - return '{:02}:{:02}:{:02}'.format(*time[:-1]) + return "{:02}:{:02}:{:02}".format(*time[:-1]) elif accurate: - return '{:02}:{:02}.{}0'.format(time[1], time[2], time[3] // 100) + return "{:02}:{:02}.{}0".format(time[1], time[2], time[3] // 100) else: - return '{:02}:{:02}.00'.format(*time[1:3]) + return "{:02}:{:02}.00".format(*time[1:3]) -def compose_url(protocol, host, port, path='/'): +def compose_url(protocol, host, port, path="/"): """Compose a URL.""" - if not path.startswith('/'): - path = '/' + path + if not path.startswith("/"): + path = "/" + path - return '{}://{}:{}{}'.format(protocol, host, port, path) + return "{}://{}:{}{}".format(protocol, host, port, path) def greatest_common_superclass(instances): @@ -135,10 +140,10 @@ def get_lan_ip(): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: # Doesn't have to be reachable - s.connect(('10.10.10.10', 0)) + s.connect(("10.10.10.10", 0)) ip = s.getsockname()[0] except OSError: - ip = '127.0.0.1' + ip = "127.0.0.1" finally: s.close() @@ -172,7 +177,7 @@ def natural_keys(text): l.sort(key=natural_keys) # sorts in human order ['something1', 'something4', 'something17'] """ - return [int(c) if c.isdigit() else c for c in re.split('([0-9]+)', text)] + return [int(c) if c.isdigit() else c for c in re.split("([0-9]+)", text)] def rhasattr(obj, attr): @@ -186,7 +191,7 @@ class A: pass a.b.c = 42 hasattr(a, 'b.c') # True """ - return functools.reduce(hasattr, attr.split('.'), obj) + return functools.reduce(hasattr, attr.split("."), obj) def rsetattr(obj, attr, value): @@ -203,7 +208,7 @@ class A: pass rsetattr(a, 'b.c', 42) a.b.c # 42 """ - pre, _, post = attr.rpartition('.') + pre, _, post = attr.rpartition(".") setattr(rgetattr(obj, pre) if pre else obj, post, value) @@ -226,10 +231,11 @@ class A: pass if default is rgetattr_sentinel: _getattr = getattr else: + def _getattr(obj, name): return getattr(obj, name, default) - return functools.reduce(_getattr, attr.split('.'), obj) + return functools.reduce(_getattr, attr.split("."), obj) def filter_live_properties(properties): @@ -239,7 +245,7 @@ def filter_live_properties(properties): :type properties: set :return: """ - return set(p for p in properties if not p.startswith('live_')) + return set(p for p in properties if not p.startswith("live_")) class EqEnum(Enum): diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index f6b1012ea..fe75a5405 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -49,28 +49,28 @@ class CueState: class CueAction(EqEnum): - Default = 'Default' - FadeIn = 'FadeIn' - FadeOut = 'FadeOut' - FadeInStart = 'FadeInStart' - FadeInResume = 'FadeInResume' - FadeOutPause = 'FadeOutPause' - FadeOutStop = 'FadeOutStop' - FadeOutInterrupt = 'FadeOutInterrupt' - Interrupt = 'Interrupt' - Start = 'Start' - Resume = 'Resume' - Pause = 'Pause' - Stop = 'Stop' - DoNothing = 'DoNothing' + Default = "Default" + FadeIn = "FadeIn" + FadeOut = "FadeOut" + FadeInStart = "FadeInStart" + FadeInResume = "FadeInResume" + FadeOutPause = "FadeOutPause" + FadeOutStop = "FadeOutStop" + FadeOutInterrupt = "FadeOutInterrupt" + Interrupt = "Interrupt" + Start = "Start" + Resume = "Resume" + Pause = "Pause" + Stop = "Stop" + DoNothing = "DoNothing" class CueNextAction(EqEnum): - DoNothing = 'DoNothing' - TriggerAfterWait = 'TriggerAfterWait' - TriggerAfterEnd = 'TriggerAfterEnd' - SelectAfterWait = 'SelectAfterWait' - SelectAfterEnd = 'SelectAfterEnd' + DoNothing = "DoNothing" + TriggerAfterWait = "TriggerAfterWait" + TriggerAfterEnd = "TriggerAfterEnd" + SelectAfterWait = "SelectAfterWait" + SelectAfterEnd = "SelectAfterEnd" class Cue(HasProperties): @@ -111,14 +111,15 @@ class Cue(HasProperties): If 'next_action' is AutoFollow or DoNothing, the postwait is not performed. """ - Name = 'Cue' + + Name = "Cue" _type_ = WriteOnceProperty() id = WriteOnceProperty() - name = Property(default='Untitled') + name = Property(default="Untitled") index = Property(default=-1) - description = Property(default='') - stylesheet = Property(default='') + description = Property(default="") + stylesheet = Property(default="") duration = Property(default=0) pre_wait = Property(default=0) post_wait = Property(default=0) @@ -161,15 +162,15 @@ def __init__(self, id=None): self.fadeout_end = Signal() # Status signals - self.interrupted = Signal() # self - self.started = Signal() # self - self.stopped = Signal() # self - self.paused = Signal() # self - self.error = Signal() # self - self.next = Signal() # self - self.end = Signal() # self + self.interrupted = Signal() # self + self.started = Signal() # self + self.stopped = Signal() # self + self.paused = Signal() # self + self.error = Signal() # self + self.next = Signal() # self + self.end = Signal() # self - self.changed('next_action').connect(self.__next_action_changed) + self.changed("next_action").connect(self.__next_action_changed) def execute(self, action=CueAction.Default): """Execute the specified action, if supported. @@ -212,15 +213,17 @@ def execute(self, action=CueAction.Default): elif action is CueAction.FadeInResume: self.resume(fade=self.fadein_duration > 0) elif action is CueAction.FadeOut: - duration = AppConfig().get('cue.fadeAction', 0) + duration = AppConfig().get("cue.fadeAction", 0) fade = AppConfig().get( - 'cue.fadeActionType', FadeOutType.Linear.name) + "cue.fadeActionType", FadeOutType.Linear.name + ) self.fadeout(duration, FadeOutType[fade]) elif action is CueAction.FadeIn: - duration = AppConfig().get('cue.fadeAction', 0) + duration = AppConfig().get("cue.fadeAction", 0) fade = AppConfig().get( - 'cue.fadeActionType', FadeInType.Linear.name) + "cue.fadeActionType", FadeInType.Linear.name + ) self.fadein(duration, FadeInType[fade]) @@ -240,8 +243,9 @@ def start(self, fade=False): state = self._state # PreWait - if self.pre_wait and state & (CueState.IsStopped | - CueState.PreWait_Pause): + if self.pre_wait and state & ( + CueState.IsStopped | CueState.PreWait_Pause + ): self._state = CueState.PreWait # Start the wait, the lock is released during the wait and # re-acquired after @@ -252,9 +256,9 @@ def start(self, fade=False): return # Cue-Start (still locked), the __start__ function should not block - if state & (CueState.IsStopped | - CueState.Pause | - CueState.PreWait_Pause): + if state & ( + CueState.IsStopped | CueState.Pause | CueState.PreWait_Pause + ): running = self.__start__(fade) self._state = CueState.Running @@ -264,11 +268,15 @@ def start(self, fade=False): self._ended() # PostWait (still locked) - if state & (CueState.IsStopped | - CueState.PreWait_Pause | - CueState.PostWait_Pause): - if (self.next_action == CueNextAction.TriggerAfterWait or - self.next_action == CueNextAction.SelectAfterWait): + if state & ( + CueState.IsStopped + | CueState.PreWait_Pause + | CueState.PostWait_Pause + ): + if ( + self.next_action == CueNextAction.TriggerAfterWait + or self.next_action == CueNextAction.SelectAfterWait + ): self._state |= CueState.PostWait if self._postwait.wait(self.post_wait, lock=self._st_lock): @@ -327,9 +335,8 @@ def stop(self, fade=False): # Stop PostWait if self._state & (CueState.PostWait | CueState.PostWait_Pause): # Remove PostWait or PostWait_Pause state - self._state = ( - (self._state ^ CueState.PostWait) & - (self._state ^ CueState.PostWait_Pause) + self._state = (self._state ^ CueState.PostWait) & ( + self._state ^ CueState.PostWait_Pause ) self._postwait.stop() @@ -342,9 +349,8 @@ def stop(self, fade=False): return # Remove Running or Pause state - self._state = ( - (self._state ^ CueState.Running) & - (self._state ^ CueState.Pause) + self._state = (self._state ^ CueState.Running) & ( + self._state ^ CueState.Pause ) self._state |= CueState.Stop self.stopped.emit(self) @@ -438,9 +444,8 @@ def interrupt(self, fade=False): # Stop PostWait if self._state & (CueState.PostWait | CueState.PostWait_Pause): # Remove PostWait or PostWait_Pause state - self._state = ( - (self._state ^ CueState.PostWait) & - (self._state ^ CueState.PostWait_Pause) + self._state = (self._state ^ CueState.PostWait) & ( + self._state ^ CueState.PostWait_Pause ) self._postwait.stop() @@ -449,9 +454,8 @@ def interrupt(self, fade=False): self.__interrupt__(fade) # Remove Running or Pause state - self._state = ( - (self._state ^ CueState.Running) & - (self._state ^ CueState.Pause) + self._state = (self._state ^ CueState.Running) & ( + self._state ^ CueState.Pause ) self._state |= CueState.Stop self.interrupted.emit(self) @@ -503,9 +507,9 @@ def _error(self): locked = self._st_lock.acquire(blocking=False) self._state = ( - (self._state ^ CueState.Running) & - (self._state ^ CueState.Pause) & - (self._state ^ CueState.Stop) + (self._state ^ CueState.Running) + & (self._state ^ CueState.Pause) + & (self._state ^ CueState.Stop) ) | CueState.Error self.error.emit(self) @@ -536,6 +540,8 @@ def state(self): def __next_action_changed(self, next_action): self.end.disconnect(self.next.emit) - if (next_action == CueNextAction.TriggerAfterEnd or - next_action == CueNextAction.SelectAfterEnd): + if ( + next_action == CueNextAction.TriggerAfterEnd + or next_action == CueNextAction.SelectAfterEnd + ): self.end.connect(self.next.emit) diff --git a/lisp/cues/cue_actions.py b/lisp/cues/cue_actions.py index bc6671b23..6de146538 100644 --- a/lisp/cues/cue_actions.py +++ b/lisp/cues/cue_actions.py @@ -25,7 +25,7 @@ class UpdateCueAction(Action): - __slots__ = ('__cue', '__new', '__old') + __slots__ = ("__cue", "__new", "__old") def __init__(self, properties, cue): self.__cue = cue @@ -45,13 +45,14 @@ def redo(self): self.do() def log(self): - return translate('CueActionLog', - 'Cue settings changed: "{}"').format(self.__cue.name) + return translate("CueActionLog", 'Cue settings changed: "{}"').format( + self.__cue.name + ) class UpdateCuesAction(Action): - __slots__ = ('__cues', '__new', '__old') + __slots__ = ("__cues", "__new", "__old") def __init__(self, properties, cues): self.__cues = cues @@ -73,4 +74,4 @@ def redo(self): self.do() def log(self): - return translate('CueActionLog', 'Cues settings changed.') + return translate("CueActionLog", "Cues settings changed.") diff --git a/lisp/cues/cue_factory.py b/lisp/cues/cue_factory.py index d0b015342..63792525a 100644 --- a/lisp/cues/cue_factory.py +++ b/lisp/cues/cue_factory.py @@ -77,7 +77,8 @@ def create_cue(cls, cue_type, cue_id=None, **kwargs): if not callable(factory): raise Exception( - 'Cue not available or badly registered: {}'.format(cue_type)) + "Cue not available or badly registered: {}".format(cue_type) + ) return factory(id=cue_id, **kwargs) @@ -89,7 +90,7 @@ def clone_cue(cls, cue): :rtype: lisp.cues.cue.Cue """ properties = deepcopy(cue.properties()) - properties.pop('id') + properties.pop("id") cue = cls.create_cue(typename(cue)) cue.update_properties(properties) diff --git a/lisp/cues/cue_memento_model.py b/lisp/cues/cue_memento_model.py index da7b39710..5119a940c 100644 --- a/lisp/cues/cue_memento_model.py +++ b/lisp/cues/cue_memento_model.py @@ -18,8 +18,11 @@ # along with Linux Show Player. If not, see . from lisp.core.memento_model import MementoModelAdapter -from lisp.core.memento_model_actions import AddItemAction, MoveItemAction, \ - RemoveItemAction +from lisp.core.memento_model_actions import ( + AddItemAction, + MoveItemAction, + RemoveItemAction, +) class CueMementoAdapter(MementoModelAdapter): @@ -32,6 +35,7 @@ def __init__(self, model_adapter, handler=None): # TODO: keep only the cue-properties for the cue-removed/added action ?? + class CueAddAction(AddItemAction): def log(self): return 'Add cue "{}"'.format(self._item.name) @@ -45,4 +49,5 @@ def log(self): class CueMoveAction(MoveItemAction): def log(self): return 'Move cue from "{}" to "{}"'.format( - self._old_index + 1, self._new_index + 1) + self._old_index + 1, self._new_index + 1 + ) diff --git a/lisp/cues/cue_model.py b/lisp/cues/cue_model.py index 521ab7f2d..6968ab057 100644 --- a/lisp/cues/cue_model.py +++ b/lisp/cues/cue_model.py @@ -34,7 +34,7 @@ def __init__(self): def add(self, cue): if cue.id in self.__cues: - raise ValueError('the cue is already in the layout') + raise ValueError("the cue is already in the layout") self.__cues[cue.id] = cue self.item_added.emit(cue) diff --git a/lisp/cues/cue_time.py b/lisp/cues/cue_time.py index 6364882bb..9feb78e85 100644 --- a/lisp/cues/cue_time.py +++ b/lisp/cues/cue_time.py @@ -50,6 +50,7 @@ class CueTime(metaclass=MetaCueTime): .. note:: The notify signal is emitted only when the cue is running. """ + _Clock = Clock_100 def __init__(self, cue): @@ -58,7 +59,7 @@ def __init__(self, cue): self._clock = self._Clock self._active = False self._cue = cue - self._cue.changed('duration').connect(self.__init) + self._cue.changed("duration").connect(self.__init) self.__init() diff --git a/lisp/cues/media_cue.py b/lisp/cues/media_cue.py index 4cd5969a7..cafd125c6 100644 --- a/lisp/cues/media_cue.py +++ b/lisp/cues/media_cue.py @@ -31,7 +31,7 @@ class MediaCue(Cue): - Name = QT_TRANSLATE_NOOP('CueName', 'Media Cue') + Name = QT_TRANSLATE_NOOP("CueName", "Media Cue") media = Property() default_start_action = Property(default=CueAction.FadeInStart.value) @@ -39,18 +39,24 @@ class MediaCue(Cue): CueActions = ( CueAction.Default, - CueAction.Start, CueAction.FadeInStart, - CueAction.Resume, CueAction.FadeInResume, - CueAction.Stop, CueAction.FadeOutStop, - CueAction.Pause, CueAction.FadeOutPause, - CueAction.FadeOut, CueAction.FadeIn, - CueAction.Interrupt, CueAction.FadeOutInterrupt + CueAction.Start, + CueAction.FadeInStart, + CueAction.Resume, + CueAction.FadeInResume, + CueAction.Stop, + CueAction.FadeOutStop, + CueAction.Pause, + CueAction.FadeOutPause, + CueAction.FadeOut, + CueAction.FadeIn, + CueAction.Interrupt, + CueAction.FadeOutInterrupt, ) def __init__(self, media, id=None): super().__init__(id=id) self.media = media - self.media.changed('duration').connect(self._duration_change) + self.media.changed("duration").connect(self._duration_change) self.media.elements_changed.connect(self.__elements_changed) self.media.error.connect(self._on_error) self.media.eos.connect(self._on_eos) @@ -58,12 +64,12 @@ def __init__(self, media, id=None): self.__in_fadein = False self.__in_fadeout = False - self.__volume = self.media.element('Volume') - self.__fader = Fader(self.__volume, 'live_volume') + self.__volume = self.media.element("Volume") + self.__fader = Fader(self.__volume, "live_volume") self.__fade_lock = Lock() def __elements_changed(self): - self.__volume = self.media.element('Volume') + self.__volume = self.media.element("Volume") self.__fader.target = self.__volume def __start__(self, fade=False): @@ -209,14 +215,15 @@ def _on_start_fade(self): self.__fadein( self.fadein_duration, self.__volume.volume, - FadeInType[self.fadein_type] + FadeInType[self.fadein_type], ) def _on_stop_fade(self, interrupt=False): if interrupt: - duration = AppConfig().get('cue.interruptFade', 0) + duration = AppConfig().get("cue.interruptFade", 0) fade_type = AppConfig().get( - 'cue.interruptFadeType', FadeOutType.Linear.name) + "cue.interruptFadeType", FadeOutType.Linear.name + ) else: duration = self.fadeout_duration fade_type = self.fadeout_type diff --git a/lisp/layout/cue_layout.py b/lisp/layout/cue_layout.py index 288b8cdc9..2d31d033a 100644 --- a/lisp/layout/cue_layout.py +++ b/lisp/layout/cue_layout.py @@ -32,11 +32,11 @@ class CueLayout(HasProperties): # Layout name - NAME = 'Base' + NAME = "Base" # Layout short description - DESCRIPTION = 'No description' + DESCRIPTION = "No description" # Layout details (some useful info) - DETAILS = '' + DETAILS = "" CuesMenu = CueContextMenu() @@ -47,11 +47,11 @@ def __init__(self, application): super().__init__() self.app = application - self.cue_executed = Signal() # After a cue is executed - self.all_executed = Signal() # After execute_all is called + self.cue_executed = Signal() # After a cue is executed + self.all_executed = Signal() # After execute_all is called # TODO: self.standby_changed = Signal() - self.key_pressed = Signal() # After a key is pressed + self.key_pressed = Signal() # After a key is pressed @property def cue_model(self): @@ -149,25 +149,25 @@ def execute_all(self, action, quiet=False): self.all_executed.emit(action) def stop_all(self): - if self.app.conf.get('layout.stopAllFade', False): + if self.app.conf.get("layout.stopAllFade", False): self.execute_all(CueAction.FadeOutStop) else: self.execute_all(CueAction.Stop) def interrupt_all(self): - if self.app.conf.get('layout.interruptAllFade', False): + if self.app.conf.get("layout.interruptAllFade", False): self.execute_all(CueAction.FadeOutInterrupt) else: self.execute_all(CueAction.Interrupt) def pause_all(self): - if self.app.conf.get('layout.pauseAllFade', False): + if self.app.conf.get("layout.pauseAllFade", False): self.execute_all(CueAction.FadeOutPause) else: self.execute_all(CueAction.Pause) def resume_all(self): - if self.app.conf.get('layout.resumeAllFade', True): + if self.app.conf.get("layout.resumeAllFade", True): self.execute_all(CueAction.FadeInResume) else: self.execute_all(CueAction.Resume) @@ -192,7 +192,8 @@ def edit_cues(self, cues): if cues: # Use the greatest common superclass between the selected cues dialog = CueSettingsDialog( - greatest_common_superclass(cues), parent=self.app.window) + greatest_common_superclass(cues), parent=self.app.window + ) def on_apply(settings): action = UpdateCuesAction(settings, cues) diff --git a/lisp/layout/cue_menu.py b/lisp/layout/cue_menu.py index 244183bce..0f5e4eb82 100644 --- a/lisp/layout/cue_menu.py +++ b/lisp/layout/cue_menu.py @@ -28,7 +28,7 @@ MENU_PRIORITY_CUE = 0 MENU_PRIORITY_LAYOUT = 100 MENU_PRIORITY_PLUGIN = 1000 -MENU_PRIORITY_NONE = float('inf') +MENU_PRIORITY_NONE = float("inf") class MenuAction: @@ -54,8 +54,9 @@ def __init__(self, s_text, s_action, m_text=None, m_action=None, **kwargs): self._m_action = m_action def show(self, cues): - return not (len(cues) > 1 and (self._m_text is None or - self._m_action is None)) + return not ( + len(cues) > 1 and (self._m_text is None or self._m_action is None) + ) def text(self, cues): if len(cues) == 1: @@ -71,8 +72,13 @@ def action(self, cues): class MenuActionsGroup: - def __init__(self, separators=True, submenu=False, text='', - priority=MENU_PRIORITY_NONE): + def __init__( + self, + separators=True, + submenu=False, + text="", + priority=MENU_PRIORITY_NONE, + ): """ :param separators: if True a separator is put before and after the group :param submenu: if True the actions are put in a submenu @@ -101,11 +107,11 @@ def create_qmenu(actions, cues, menu): menu.addSeparator() if action.submenu: - menu.addMenu(create_qmenu( - action.actions, - cues, - QMenu(action.text, parent=menu) - )) + menu.addMenu( + create_qmenu( + action.actions, cues, QMenu(action.text, parent=menu) + ) + ) else: create_qmenu(action.actions, cues, menu) @@ -121,7 +127,6 @@ def create_qmenu(actions, cues, menu): class CueContextMenu(ClassBasedRegistry): - def add(self, item, ref_class=Cue): """ :type item: typing.Union[MenuAction, MenuActionGroup] @@ -131,8 +136,9 @@ def add(self, item, ref_class=Cue): if not issubclass(ref_class, Cue): raise TypeError( - 'ref_class must be Cue or a subclass, not {0}'.format( - ref_class.__name__) + "ref_class must be Cue or a subclass, not {0}".format( + ref_class.__name__ + ) ) super().add(item, ref_class) diff --git a/lisp/main.py b/lisp/main.py index 352bc26c3..bbe3ef9fd 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -28,8 +28,14 @@ from PyQt5.QtCore import QLocale, QLibraryInfo from PyQt5.QtWidgets import QApplication -from lisp import USER_DIR, DEFAULT_APP_CONFIG, USER_APP_CONFIG, plugins, \ - I18N_PATH, LOGS_DIR +from lisp import ( + USER_DIR, + DEFAULT_APP_CONFIG, + USER_APP_CONFIG, + plugins, + I18N_PATH, + LOGS_DIR, +) from lisp.application import Application from lisp.core.configuration import JSONFileConfiguration from lisp.ui import themes @@ -39,12 +45,23 @@ def main(): # Parse the command-line arguments - parser = argparse.ArgumentParser(description='Linux Show Player') - parser.add_argument('-f', '--file', default='', nargs='?', const='', - help='Session file path') - parser.add_argument('-l', '--log', choices=['debug', 'info', 'warning'], - default='warning', help='Log level') - parser.add_argument('--locale', default='', help='Force specified locale') + parser = argparse.ArgumentParser(description="Linux Show Player") + parser.add_argument( + "-f", + "--file", + default="", + nargs="?", + const="", + help="Session file path", + ) + parser.add_argument( + "-l", + "--log", + choices=["debug", "info", "warning"], + default="warning", + help="Log level", + ) + parser.add_argument("--locale", default="", help="Force specified locale") args = parser.parse_args() @@ -52,20 +69,21 @@ def main(): os.makedirs(USER_DIR, exist_ok=True) # Get logging level for the console - if args.log == 'debug': + if args.log == "debug": console_log_level = logging.DEBUG # If something bad happen at low-level (e.g. segfault) print the stack import faulthandler + faulthandler.enable() - elif args.log == 'info': + elif args.log == "info": console_log_level = logging.INFO else: console_log_level = logging.WARNING # Setup the root logger default_formatter = logging.Formatter( - '%(asctime)s.%(msecs)03d\t%(name)s\t%(levelname)s\t%(message)s', - datefmt='%Y-%m-%d %H:%M:%S' + "%(asctime)s.%(msecs)03d\t%(name)s\t%(levelname)s\t%(message)s", + datefmt="%Y-%m-%d %H:%M:%S", ) root_logger = logging.getLogger() root_logger.setLevel(logging.DEBUG) @@ -80,7 +98,10 @@ def main(): os.makedirs(LOGS_DIR, exist_ok=True) # Create the file handler file_handler = RotatingFileHandler( - os.path.join(LOGS_DIR, 'lisp.log'), maxBytes=10*(2**20), backupCount=5) + os.path.join(LOGS_DIR, "lisp.log"), + maxBytes=10 * (2 ** 20), + backupCount=5, + ) file_handler.setFormatter(default_formatter) root_logger.addHandler(file_handler) @@ -89,7 +110,7 @@ def main(): # Create the QApplication qt_app = QApplication(sys.argv) - qt_app.setApplicationName('Linux Show Player') + qt_app.setApplicationName("Linux Show Player") qt_app.setQuitOnLastWindowClosed(True) # Get/Set the locale @@ -97,29 +118,30 @@ def main(): if locale: QLocale().setDefault(QLocale(locale)) - logging.info('Using {} locale'.format(QLocale().name())) + logging.info("Using {} locale".format(QLocale().name())) # Qt platform translation install_translation( - 'qt', tr_path=QLibraryInfo.location(QLibraryInfo.TranslationsPath)) + "qt", tr_path=QLibraryInfo.location(QLibraryInfo.TranslationsPath) + ) # Main app translations - install_translation('lisp', tr_path=I18N_PATH) + install_translation("lisp", tr_path=I18N_PATH) # Set UI theme try: - theme_name = app_conf['theme.theme'] + theme_name = app_conf["theme.theme"] themes.get_theme(theme_name).apply(qt_app) logging.info('Using "{}" theme'.format(theme_name)) except Exception: - logging.exception('Unable to load theme.') + logging.exception("Unable to load theme.") # Set LiSP icon theme (not the Qt one) try: - icon_theme = app_conf['theme.icons'] + icon_theme = app_conf["theme.icons"] IconTheme.set_theme_name(icon_theme) logging.info('Using "{}" icon theme'.format(icon_theme)) except Exception: - logging.exception('Unable to load icon theme.') + logging.exception("Unable to load icon theme.") # Initialize the application lisp_app = Application(app_conf) @@ -144,5 +166,5 @@ def handle_quit_signal(*_): sys.exit(exit_code) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index 05b391a63..db70e7906 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -29,7 +29,7 @@ PLUGINS = {} LOADED = {} -FALLBACK_CONFIG_PATH = path.join(path.dirname(__file__), 'default.json') +FALLBACK_CONFIG_PATH = path.join(path.dirname(__file__), "default.json") logger = logging.getLogger(__name__) @@ -45,26 +45,26 @@ def load_plugins(application): PLUGINS[name] = plugin mod_path = path.dirname(inspect.getfile(plugin)) - mod_name = plugin.__module__.split('.')[-1] + mod_name = plugin.__module__.split(".")[-1] - if path.exists(path.join(mod_path, 'default.json')): - default_config_path = path.join(mod_path, 'default.json') + if path.exists(path.join(mod_path, "default.json")): + default_config_path = path.join(mod_path, "default.json") else: default_config_path = FALLBACK_CONFIG_PATH # Load plugin configuration config = JSONFileConfiguration( - path.join(USER_DIR, mod_name + '.json'), - default_config_path + path.join(USER_DIR, mod_name + ".json"), default_config_path ) plugin.Config = config # Load plugin translations install_translation(mod_name) - install_translation(mod_name, tr_path=path.join(mod_path, 'i18n')) + install_translation(mod_name, tr_path=path.join(mod_path, "i18n")) except Exception: logger.exception( - translate('PluginsError', 'Failed to load "{}"').format(name)) + translate("PluginsError", 'Failed to load "{}"').format(name) + ) __init_plugins(application) @@ -86,9 +86,10 @@ def __init_plugins(application): # We've go through all the not loaded plugins and weren't able # to resolve their dependencies, which means there are cyclic or # missing/disabled dependencies - logger.warning(translate( - 'PluginsWarning', 'Cannot satisfy dependencies for: {}') - .format(', '.join(pending)) + logger.warning( + translate( + "PluginsWarning", "Cannot satisfy dependencies for: {}" + ).format(", ".join(pending)) ) return @@ -121,19 +122,27 @@ def __load_plugins(plugins, application, optionals=True): # Try to load the plugin, if enabled try: - if plugin.Config.get('_enabled_', False): + if plugin.Config.get("_enabled_", False): # Create an instance of the plugin and save it LOADED[name] = plugin(application) - logger.info(translate( - 'PluginsInfo', 'Plugin loaded: "{}"').format(name)) + logger.info( + translate("PluginsInfo", 'Plugin loaded: "{}"').format( + name + ) + ) else: - logger.debug(translate( - 'PluginsDebug', - 'Plugin disabled in configuration: "{}"').format(name) + logger.debug( + translate( + "PluginsDebug", + 'Plugin disabled in configuration: "{}"', + ).format(name) ) except Exception: - logger.exception(translate( - 'PluginsError', 'Failed to load plugin: "{}"'.format(name)) + logger.exception( + translate( + "PluginsError", + 'Failed to load plugin: "{}"'.format(name), + ) ) return resolved @@ -144,12 +153,16 @@ def finalize_plugins(): for plugin in LOADED: try: LOADED[plugin].finalize() - logger.info(translate( - 'PluginsInfo', 'Plugin terminated: "{}"').format(plugin)) + logger.info( + translate("PluginsInfo", 'Plugin terminated: "{}"').format( + plugin + ) + ) except Exception: - logger.exception(translate( - 'PluginsError', - 'Failed to terminate plugin: "{}"').format(plugin) + logger.exception( + translate( + "PluginsError", 'Failed to terminate plugin: "{}"' + ).format(plugin) ) @@ -161,7 +174,8 @@ def get_plugin(plugin_name): if is_loaded(plugin_name): return LOADED[plugin_name] else: - raise PluginNotLoadedError(translate( - 'PluginsError', - 'the requested plugin is not loaded: {}').format(plugin_name) + raise PluginNotLoadedError( + translate( + "PluginsError", "the requested plugin is not loaded: {}" + ).format(plugin_name) ) diff --git a/lisp/plugins/action_cues/__init__.py b/lisp/plugins/action_cues/__init__.py index 5f3103b05..125147ba8 100644 --- a/lisp/plugins/action_cues/__init__.py +++ b/lisp/plugins/action_cues/__init__.py @@ -29,9 +29,9 @@ class ActionCues(Plugin): - Name = 'Action Cues' - Authors = ('Francesco Ceruti',) - Description = 'Provide a collection of cues for different purposes' + Name = "Action Cues" + Authors = ("Francesco Ceruti",) + Description = "Provide a collection of cues for different purposes" def __init__(self, app): super().__init__(app) @@ -42,13 +42,16 @@ def __init__(self, app): # Register the menu action for adding the action-cue self.app.window.register_cue_menu_action( - translate('CueName', cue.Name), + translate("CueName", cue.Name), self._new_cue_factory(cue), - 'Action cues' + "Action cues", ) - logger.debug(translate( - 'ActionCuesDebug', 'Registered cue: "{}"').format(name)) + logger.debug( + translate("ActionCuesDebug", 'Registered cue: "{}"').format( + name + ) + ) def _new_cue_factory(self, cue_class): def cue_factory(): @@ -56,9 +59,10 @@ def cue_factory(): cue = CueFactory.create_cue(cue_class.__name__) self.app.cue_model.add(cue) except Exception: - logger.exception(translate( - 'ActionsCuesError', - 'Cannot create cue {}').format(cue_class.__name__) + logger.exception( + translate( + "ActionsCuesError", "Cannot create cue {}" + ).format(cue_class.__name__) ) return cue_factory diff --git a/lisp/plugins/action_cues/collection_cue.py b/lisp/plugins/action_cues/collection_cue.py index a9196cbae..8b50ca629 100644 --- a/lisp/plugins/action_cues/collection_cue.py +++ b/lisp/plugins/action_cues/collection_cue.py @@ -18,8 +18,15 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QVBoxLayout, QSizePolicy, QDialogButtonBox, \ - QDialog, QAbstractItemView, QHeaderView, QTableView +from PyQt5.QtWidgets import ( + QVBoxLayout, + QSizePolicy, + QDialogButtonBox, + QDialog, + QAbstractItemView, + QHeaderView, + QTableView, +) from lisp.application import Application from lisp.core.properties import Property @@ -33,13 +40,13 @@ class CollectionCue(Cue): - Name = QT_TRANSLATE_NOOP('CueName', 'Collection Cue') + Name = QT_TRANSLATE_NOOP("CueName", "Collection Cue") targets = Property(default=[]) def __init__(self, **kwargs): super().__init__(**kwargs) - self.name = translate('CueName', self.Name) + self.name = translate("CueName", self.Name) def __start__(self, fade=False): for target_id, action in self.targets: @@ -51,7 +58,7 @@ def __start__(self, fade=False): class CollectionCueSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Edit Collection') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Edit Collection") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -59,7 +66,8 @@ def __init__(self, **kwargs): self.cue_dialog = CueSelectDialog( cues=Application().cue_model, - selection_mode=QAbstractItemView.ExtendedSelection) + selection_mode=QAbstractItemView.ExtendedSelection, + ) self.collectionModel = CollectionModel() self.collectionView = CollectionView(self.cue_dialog, parent=self) @@ -69,20 +77,23 @@ def __init__(self, **kwargs): # Buttons self.dialogButtons = QDialogButtonBox(self) - self.dialogButtons.setSizePolicy(QSizePolicy.Minimum, - QSizePolicy.Minimum) + self.dialogButtons.setSizePolicy( + QSizePolicy.Minimum, QSizePolicy.Minimum + ) self.layout().addWidget(self.dialogButtons) self.addButton = self.dialogButtons.addButton( - translate('CollectionCue', 'Add'), QDialogButtonBox.ActionRole) + translate("CollectionCue", "Add"), QDialogButtonBox.ActionRole + ) self.addButton.clicked.connect(self._add_dialog) self.delButton = self.dialogButtons.addButton( - translate('CollectionCue', 'Remove'), QDialogButtonBox.ActionRole) + translate("CollectionCue", "Remove"), QDialogButtonBox.ActionRole + ) self.delButton.clicked.connect(self._remove_selected) def loadSettings(self, settings): - for target_id, action in settings.get('targets', []): + for target_id, action in settings.get("targets", []): target = Application().cue_model.get(target_id) if target is not None: self._add_cue(target, CueAction(action)) @@ -92,7 +103,7 @@ def getSettings(self): for target_id, action in self.collectionModel.rows: targets.append((target_id, action.value)) - return {'targets': targets} + return {"targets": targets} def _add_cue(self, cue, action): self.collectionModel.appendRow(cue.__class__, cue.id, action) @@ -128,10 +139,7 @@ def __init__(self, cue_select, **kwargs): self.verticalHeader().setDefaultSectionSize(26) self.verticalHeader().setHighlightSections(False) - self.delegates = [ - CueSelectionDelegate(cue_select), - CueActionDelegate() - ] + self.delegates = [CueSelectionDelegate(cue_select), CueActionDelegate()] for column, delegate in enumerate(self.delegates): self.setItemDelegateForColumn(column, delegate) @@ -140,8 +148,12 @@ def __init__(self, cue_select, **kwargs): class CollectionModel(SimpleCueListModel): def __init__(self): # NOTE: The model does fixed-indices operations based on this list - super().__init__([translate('CollectionCue', 'Cue'), - translate('CollectionCue', 'Action')]) + super().__init__( + [ + translate("CollectionCue", "Cue"), + translate("CollectionCue", "Action"), + ] + ) def setData(self, index, value, role=Qt.DisplayRole): result = super().setData(index, value, role) @@ -149,9 +161,11 @@ def setData(self, index, value, role=Qt.DisplayRole): if result and role == CueClassRole: if self.rows[index.row()][1] not in value.CueActions: self.rows[index.row()][1] = value.CueActions[0] - self.dataChanged.emit(self.index(index.row(), 1), - self.index(index.row(), 1), - [Qt.DisplayRole, Qt.EditRole]) + self.dataChanged.emit( + self.index(index.row(), 1), + self.index(index.row(), 1), + [Qt.DisplayRole, Qt.EditRole], + ) return result diff --git a/lisp/plugins/action_cues/command_cue.py b/lisp/plugins/action_cues/command_cue.py index ee0b49cd7..ce542be39 100644 --- a/lisp/plugins/action_cues/command_cue.py +++ b/lisp/plugins/action_cues/command_cue.py @@ -39,17 +39,17 @@ class CommandCue(Cue): Implemented using :class:`subprocess.Popen` with *shell=True* """ - Name = QT_TRANSLATE_NOOP('CueName', 'Command Cue') + Name = QT_TRANSLATE_NOOP("CueName", "Command Cue") CueActions = (CueAction.Default, CueAction.Start, CueAction.Stop) - command = Property(default='') + command = Property(default="") no_output = Property(default=True) no_error = Property(default=True) kill = Property(default=False) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.name = translate('CueName', self.Name) + self.name = translate("CueName", self.Name) self.__process = None self.__stopped = False @@ -66,8 +66,9 @@ def __exec_command(self): # If no_output is True, discard all the outputs std = subprocess.DEVNULL if self.no_output else None # Execute the command - self.__process = subprocess.Popen(self.command, shell=True, - stdout=std, stderr=std) + self.__process = subprocess.Popen( + self.command, shell=True, stdout=std, stderr=std + ) rcode = self.__process.wait() if rcode == 0 or rcode == -9 or self.no_error: @@ -78,8 +79,8 @@ def __exec_command(self): # If an error occurs and not in no-error mode logger.error( translate( - 'CommandCue', - 'Command cue ended with an error status. Exit code: {}' + "CommandCue", + "Command cue ended with an error status. Exit code: {}", ).format(rcode) ) self._error() @@ -101,7 +102,7 @@ def __stop__(self, fade=False): class CommandCueSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Command') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Command") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -127,15 +128,19 @@ def __init__(self, *args, **kwargs): self.retranslateUi() def retranslateUi(self): - self.group.setTitle(translate('CommandCue', 'Command')) + self.group.setTitle(translate("CommandCue", "Command")) self.commandLineEdit.setPlaceholderText( - translate('CommandCue', 'Command to execute, as in a shell')) + translate("CommandCue", "Command to execute, as in a shell") + ) self.noOutputCheckBox.setText( - translate('CommandCue', 'Discard command output')) + translate("CommandCue", "Discard command output") + ) self.noErrorCheckBox.setText( - translate('CommandCue', 'Ignore command errors')) + translate("CommandCue", "Ignore command errors") + ) self.killCheckBox.setText( - translate('CommandCue', 'Kill instead of terminate')) + translate("CommandCue", "Kill instead of terminate") + ) def enableCheck(self, enabled): self.group.setCheckable(enabled) @@ -154,23 +159,23 @@ def enableCheck(self, enabled): self.killCheckBox.setCheckState(Qt.PartiallyChecked) def loadSettings(self, settings): - self.commandLineEdit.setText(settings.get('command', '')) - self.noOutputCheckBox.setChecked(settings.get('no_output', True)) - self.noErrorCheckBox.setChecked(settings.get('no_error', True)) - self.killCheckBox.setChecked(settings.get('kill', False)) + self.commandLineEdit.setText(settings.get("command", "")) + self.noOutputCheckBox.setChecked(settings.get("no_output", True)) + self.noErrorCheckBox.setChecked(settings.get("no_error", True)) + self.killCheckBox.setChecked(settings.get("kill", False)) def getSettings(self): settings = {} if not (self.group.isCheckable() and not self.group.isChecked()): if self.commandLineEdit.text().strip(): - settings['command'] = self.commandLineEdit.text() + settings["command"] = self.commandLineEdit.text() if self.noOutputCheckBox.checkState() != Qt.PartiallyChecked: - settings['no_output'] = self.noOutputCheckBox.isChecked() + settings["no_output"] = self.noOutputCheckBox.isChecked() if self.noErrorCheckBox.checkState() != Qt.PartiallyChecked: - settings['no_error'] = self.noErrorCheckBox.isChecked() + settings["no_error"] = self.noErrorCheckBox.isChecked() if self.killCheckBox.checkState() != Qt.PartiallyChecked: - settings['kill'] = self.killCheckBox.isChecked() + settings["kill"] = self.killCheckBox.isChecked() return settings diff --git a/lisp/plugins/action_cues/index_action_cue.py b/lisp/plugins/action_cues/index_action_cue.py index f6875bbb3..a069d1d90 100644 --- a/lisp/plugins/action_cues/index_action_cue.py +++ b/lisp/plugins/action_cues/index_action_cue.py @@ -18,8 +18,15 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QCheckBox, QGroupBox, QLabel, QSpinBox, \ - QGridLayout, QVBoxLayout, QLineEdit +from PyQt5.QtWidgets import ( + QCheckBox, + QGroupBox, + QLabel, + QSpinBox, + QGridLayout, + QVBoxLayout, + QLineEdit, +) from lisp.application import Application from lisp.core.properties import Property @@ -31,7 +38,7 @@ class IndexActionCue(Cue): - Name = QT_TRANSLATE_NOOP('CueName', 'Index Action') + Name = QT_TRANSLATE_NOOP("CueName", "Index Action") target_index = Property(default=0) relative = Property(default=True) @@ -39,7 +46,7 @@ class IndexActionCue(Cue): def __init__(self, **kwargs): super().__init__(**kwargs) - self.name = translate('CueName', self.Name) + self.name = translate("CueName", self.Name) def __start__(self, fade=False): if self.relative: @@ -56,9 +63,9 @@ def __start__(self, fade=False): class IndexActionCueSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Action Settings') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Action Settings") - DEFAULT_SUGGESTION = translate('IndexActionCue', 'No suggestion') + DEFAULT_SUGGESTION = translate("IndexActionCue", "No suggestion") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -92,7 +99,8 @@ def __init__(self, **kwargs): self.actionCombo = CueActionComboBox( self._target_class.CueActions, mode=CueActionComboBox.Mode.Value, - parent=self.actionGroup) + parent=self.actionGroup, + ) self.actionGroup.layout().addWidget(self.actionCombo) self.suggestionGroup = QGroupBox(self) @@ -102,7 +110,8 @@ def __init__(self, **kwargs): self.suggestionPreview = QLineEdit(self.suggestionGroup) self.suggestionPreview.setAlignment(Qt.AlignCenter) self.suggestionPreview.setText( - IndexActionCueSettings.DEFAULT_SUGGESTION) + IndexActionCueSettings.DEFAULT_SUGGESTION + ) self.suggestionGroup.layout().addWidget(self.suggestionPreview) self.actionCombo.currentTextChanged.connect(self._update_suggestion) @@ -110,15 +119,18 @@ def __init__(self, **kwargs): self.retranslateUi() def retranslateUi(self): - self.indexGroup.setTitle(translate('IndexActionCue', 'Index')) + self.indexGroup.setTitle(translate("IndexActionCue", "Index")) self.relativeCheck.setText( - translate('IndexActionCue', 'Use a relative index')) + translate("IndexActionCue", "Use a relative index") + ) self.targetIndexLabel.setText( - translate('IndexActionCue', 'Target index')) - self.actionGroup.setTitle(translate('IndexActionCue', 'Action')) + translate("IndexActionCue", "Target index") + ) + self.actionGroup.setTitle(translate("IndexActionCue", "Action")) self.suggestionGroup.setTitle( - translate('IndexActionCue', 'Suggested cue name')) + translate("IndexActionCue", "Suggested cue name") + ) def enableCheck(self, enabled): self.indexGroup.setChecked(enabled) @@ -132,20 +144,20 @@ def getSettings(self): checkable = self.actionGroup.isCheckable() if not (checkable and not self.indexGroup.isChecked()): - conf['relative'] = self.relativeCheck.isChecked() - conf['target_index'] = self.targetIndexSpin.value() + conf["relative"] = self.relativeCheck.isChecked() + conf["target_index"] = self.targetIndexSpin.value() if not (checkable and not self.actionGroup.isChecked()): - conf['action'] = self.actionCombo.currentData() + conf["action"] = self.actionCombo.currentData() return conf def loadSettings(self, settings): - self._cue_index = settings.get('index', -1) + self._cue_index = settings.get("index", -1) - self.relativeCheck.setChecked(settings.get('relative', True)) - self.targetIndexSpin.setValue(settings.get('target_index', 0)) + self.relativeCheck.setChecked(settings.get("relative", True)) + self.targetIndexSpin.setValue(settings.get("target_index", 0)) self._target_changed() # Ensure that the correct options are displayed - self.actionCombo.setCurrentItem(settings.get('action', '')) + self.actionCombo.setCurrentItem(settings.get("action", "")) def _target_changed(self): target = self._current_target() @@ -166,7 +178,7 @@ def _update_suggestion(self): if target is not None: # This require unicode support by the used font, but hey, it's 2017 - suggestion = self.actionCombo.currentText() + ' ➜ ' + target.name + suggestion = self.actionCombo.currentText() + " ➜ " + target.name else: suggestion = IndexActionCueSettings.DEFAULT_SUGGESTION @@ -180,8 +192,7 @@ def _relative_changed(self): else: if self._cue_index >= 0: self.targetIndexSpin.setRange( - -self._cue_index, - max_index - self._cue_index + -self._cue_index, max_index - self._cue_index ) else: self.targetIndexSpin.setRange(-max_index, max_index) diff --git a/lisp/plugins/action_cues/midi_cue.py b/lisp/plugins/action_cues/midi_cue.py index d9fd04aa4..f1cd0c805 100644 --- a/lisp/plugins/action_cues/midi_cue.py +++ b/lisp/plugins/action_cues/midi_cue.py @@ -20,8 +20,15 @@ import logging from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, QLabel, \ - QComboBox, QSpinBox, QFrame +from PyQt5.QtWidgets import ( + QGroupBox, + QVBoxLayout, + QGridLayout, + QLabel, + QComboBox, + QSpinBox, + QFrame, +) from lisp.core.properties import Property from lisp.cues.cue import Cue @@ -35,14 +42,14 @@ class MidiCue(Cue): - Name = QT_TRANSLATE_NOOP('CueName', 'MIDI Cue') + Name = QT_TRANSLATE_NOOP("CueName", "MIDI Cue") - message = Property(default='') + message = Property(default="") def __init__(self, **kwargs): super().__init__(**kwargs) - self.name = translate('CueName', self.Name) - self.__midi = get_plugin('Midi') + self.name = translate("CueName", self.Name) + self.__midi = get_plugin("Midi") def __start__(self, fade=False): if self.message: @@ -52,28 +59,32 @@ def __start__(self, fade=False): class MidiCueSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'MIDI Settings') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "MIDI Settings") MSGS_ATTRIBUTES = { - 'note_on': ['channel', 'note', 'velocity'], - 'note_off': ['channel', 'note', 'velocity'], - 'control_change': ['channel', 'control', 'value'], - 'program_change': ['channel', 'program', None], - 'polytouch': ['channel', 'note', 'value'], - 'pitchwheel': ['channel', 'pitch', None], - 'song_select': ['song', None, None], - 'songpos': ['pos', None, None], - 'start': [None] * 3, - 'stop': [None] * 3, - 'continue': [None] * 3, + "note_on": ["channel", "note", "velocity"], + "note_off": ["channel", "note", "velocity"], + "control_change": ["channel", "control", "value"], + "program_change": ["channel", "program", None], + "polytouch": ["channel", "note", "value"], + "pitchwheel": ["channel", "pitch", None], + "song_select": ["song", None, None], + "songpos": ["pos", None, None], + "start": [None] * 3, + "stop": [None] * 3, + "continue": [None] * 3, } ATTRIBUTES_RANGE = { - 'channel': (1, 16, -1), 'note': (0, 127, 0), - 'velocity': (0, 127, 0), 'control': (0, 127, 0), - 'program': (0, 127, 0), 'value': (0, 127, 0), - 'song': (0, 127, 0), 'pitch': (-8192, 8191, 0), - 'pos': (0, 16383, 0) + "channel": (1, 16, -1), + "note": (0, 127, 0), + "velocity": (0, 127, 0), + "control": (0, 127, 0), + "program": (0, 127, 0), + "value": (0, 127, 0), + "song": (0, 127, 0), + "pitch": (-8192, 8191, 0), + "pos": (0, 16383, 0), } def __init__(self, **kwargs): @@ -114,14 +125,14 @@ def __init__(self, **kwargs): self.retranslateUi() def retranslateUi(self): - self.msgGroup.setTitle(translate('MIDICue', 'MIDI Message')) - self.msgTypeLabel.setText(translate('MIDICue', 'Message type')) + self.msgGroup.setTitle(translate("MIDICue", "MIDI Message")) + self.msgTypeLabel.setText(translate("MIDICue", "Message type")) def __type_changed(self, msg_type): for label, spin, attr_name in self.__attributes(msg_type): if attr_name is None: label.setEnabled(False) - label.setText('') + label.setText("") spin.setEnabled(False) else: label.setEnabled(True) @@ -129,31 +140,33 @@ def __type_changed(self, msg_type): spin.setEnabled(True) spin.setRange( - *self.ATTRIBUTES_RANGE.get(attr_name, (0, 0, 0))[0:2]) + *self.ATTRIBUTES_RANGE.get(attr_name, (0, 0, 0))[0:2] + ) def getSettings(self): msg_type = self.msgTypeCombo.currentText() - msg_dict = {'type': msg_type} + msg_dict = {"type": msg_type} for label, spin, attr_name in self.__attributes(msg_type): if spin.isEnabled(): offset = self.ATTRIBUTES_RANGE.get(attr_name, (0, 0, 0))[2] msg_dict[attr_name] = spin.value() + offset - return {'message': dict_msg_to_str(msg_dict)} + return {"message": dict_msg_to_str(msg_dict)} def __attributes(self, msg_type): - for (label, spin), attr in zip(self._data_widgets, - self.MSGS_ATTRIBUTES[msg_type]): + for (label, spin), attr in zip( + self._data_widgets, self.MSGS_ATTRIBUTES[msg_type] + ): yield label, spin, attr def loadSettings(self, settings): - str_msg = settings.get('message', '') + str_msg = settings.get("message", "") if str_msg: dict_msg = str_msg_to_dict(str_msg) - self.msgTypeCombo.setCurrentText(dict_msg['type']) + self.msgTypeCombo.setCurrentText(dict_msg["type"]) - for label, spin, attr_name in self.__attributes(dict_msg['type']): + for label, spin, attr_name in self.__attributes(dict_msg["type"]): offset = self.ATTRIBUTES_RANGE.get(attr_name, (0, 0, 0))[2] spin.setValue(dict_msg.get(label.text().lower(), 0) - offset) diff --git a/lisp/plugins/action_cues/osc_cue.py b/lisp/plugins/action_cues/osc_cue.py index 4f6156f2c..a16f7201e 100644 --- a/lisp/plugins/action_cues/osc_cue.py +++ b/lisp/plugins/action_cues/osc_cue.py @@ -22,8 +22,19 @@ from PyQt5 import QtCore from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QGridLayout, QLineEdit, \ - QTableView, QTableWidget, QHeaderView, QPushButton, QLabel, QDoubleSpinBox, QMessageBox +from PyQt5.QtWidgets import ( + QGroupBox, + QVBoxLayout, + QGridLayout, + QLineEdit, + QTableView, + QTableWidget, + QHeaderView, + QPushButton, + QLabel, + QDoubleSpinBox, + QMessageBox, +) from lisp.core.decorators import async_function from lisp.core.fade_functions import FadeInType, FadeOutType @@ -55,7 +66,7 @@ def test_path(path): if isinstance(path, str): - if len(path) > 1 and path[0] is '/': + if len(path) > 1 and path[0] is "/": return True return False @@ -70,22 +81,26 @@ def type_can_fade(t): class OscCue(Cue): - Name = QT_TRANSLATE_NOOP('CueName', 'OSC Cue') + Name = QT_TRANSLATE_NOOP("CueName", "OSC Cue") - CueActions = (CueAction.Default, CueAction.Start, CueAction.Stop, - CueAction.Pause) + CueActions = ( + CueAction.Default, + CueAction.Start, + CueAction.Stop, + CueAction.Pause, + ) - path = Property(default='') + path = Property(default="") args = Property(default=[]) fade_type = Property(default=FadeInType.Linear.name) def __init__(self, **kwargs): super().__init__(**kwargs) - self.name = translate('CueName', self.Name) + self.name = translate("CueName", self.Name) - self.__osc = get_plugin('Osc') + self.__osc = get_plugin("Osc") - self.__fader = Fader(self, '_position') + self.__fader = Fader(self, "_position") self.__value = 0 self.__fadein = True @@ -101,7 +116,9 @@ def __set_position(self, value): for row in self.__arg_list: if row[COL_DIFF_VAL] > 0: - args.append(row[COL_BASE_VAL] + row[COL_DIFF_VAL] * self.__value) + args.append( + row[COL_BASE_VAL] + row[COL_DIFF_VAL] * self.__value + ) else: args.append(row[COL_BASE_VAL]) @@ -119,17 +136,20 @@ def __init_arguments(self): self.__arg_list = [] try: for arg in self.args: - if 'end' in arg and arg['fade']: + if "end" in arg and arg["fade"]: self.__has_fade = True - diff_value = arg['end'] - arg['start'] - self.__arg_list.append([arg['type'], arg['start'], diff_value]) + diff_value = arg["end"] - arg["start"] + self.__arg_list.append( + [arg["type"], arg["start"], diff_value] + ) else: - self.__arg_list.append([arg['type'], - arg['start'], - 0]) + self.__arg_list.append([arg["type"], arg["start"], 0]) except KeyError: - logger.error(translate( - 'OscCueError', 'Could not parse argument list, nothing sent')) + logger.error( + translate( + "OscCueError", "Could not parse argument list, nothing sent" + ) + ) return False # set fade type, based on the first argument, which will have a fade @@ -168,8 +188,11 @@ def __start__(self, fade=False): else: self._position = 1 else: - logger.error(translate( - 'OscCueError', 'Error while parsing arguments, nothing sent')) + logger.error( + translate( + "OscCueError", "Error while parsing arguments, nothing sent" + ) + ) return False @@ -188,7 +211,8 @@ def __fade(self, fade_type): try: self.__fader.prepare() ended = self.__fader.fade( - round(self.duration / 1000, 2), 1, fade_type) + round(self.duration / 1000, 2), 1, fade_type + ) # to avoid approximation problems self._position = 1 @@ -196,7 +220,8 @@ def __fade(self, fade_type): self._ended() except Exception: logger.exception( - translate('OscCueError', 'Error during cue execution.')) + translate("OscCueError", "Error during cue execution.") + ) self._error() def current_time(self): @@ -204,7 +229,7 @@ def current_time(self): class OscCueSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('Cue Name', 'OSC Settings') + Name = QT_TRANSLATE_NOOP("Cue Name", "OSC Settings") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -221,11 +246,14 @@ def __init__(self, **kwargs): self.pathEdit = QLineEdit() self.oscGroup.layout().addWidget(self.pathEdit, 1, 0, 1, 2) - self.oscModel = SimpleTableModel([ - translate('Osc Cue', 'Type'), - translate('Osc Cue', 'Argument'), - translate('Osc Cue', 'FadeTo'), - translate('Osc Cue', 'Fade')]) + self.oscModel = SimpleTableModel( + [ + translate("Osc Cue", "Type"), + translate("Osc Cue", "Argument"), + translate("Osc Cue", "FadeTo"), + translate("Osc Cue", "Fade"), + ] + ) self.oscModel.dataChanged.connect(self.__argument_changed) @@ -268,14 +296,16 @@ def __init__(self, **kwargs): self.retranslateUi() def retranslateUi(self): - self.oscGroup.setTitle(translate('OscCue', 'OSC Message')) - self.addButton.setText(translate('OscCue', 'Add')) - self.removeButton.setText(translate('OscCue', 'Remove')) - self.testButton.setText(translate('OscCue', 'Test')) - self.pathLabel.setText(translate('OscCue', 'OSC Path: (example: "/path/to/something")')) - self.fadeGroup.setTitle(translate('OscCue', 'Fade')) - self.fadeLabel.setText(translate('OscCue', 'Time (sec)')) - self.fadeCurveLabel.setText(translate('OscCue', 'Curve')) + self.oscGroup.setTitle(translate("OscCue", "OSC Message")) + self.addButton.setText(translate("OscCue", "Add")) + self.removeButton.setText(translate("OscCue", "Remove")) + self.testButton.setText(translate("OscCue", "Test")) + self.pathLabel.setText( + translate("OscCue", 'OSC Path: (example: "/path/to/something")') + ) + self.fadeGroup.setTitle(translate("OscCue", "Fade")) + self.fadeLabel.setText(translate("OscCue", "Time (sec)")) + self.fadeCurveLabel.setText(translate("OscCue", "Curve")) def enableCheck(self, enabled): self.oscGroup.setCheckable(enabled) @@ -290,57 +320,62 @@ def getSettings(self): if not (checkable and not self.oscGroup.isChecked()): if not test_path(self.pathEdit.text()): - logger.error(translate( - 'OscCueError', - 'Error parsing OSC path, message will be unable to send') + logger.error( + translate( + "OscCueError", + "Error parsing OSC path, message will be unable to send", + ) ) if not (checkable and not self.oscGroup.isChecked()): try: - conf['path'] = self.pathEdit.text() + conf["path"] = self.pathEdit.text() args_list = [] for row in self.oscModel.rows: - arg = {'type': row[COL_TYPE], - 'start': row[COL_START_VAL]} + arg = {"type": row[COL_TYPE], "start": row[COL_START_VAL]} if row[COL_END_VAL] and row[COL_DO_FADE] is True: - arg['end'] = row[COL_END_VAL] + arg["end"] = row[COL_END_VAL] - arg['fade'] = row[COL_DO_FADE] + arg["fade"] = row[COL_DO_FADE] args_list.append(arg) - conf['args'] = args_list + conf["args"] = args_list except ValueError: - logger.error(translate( - 'OscCueError', - 'Error parsing OSC arguments, ' - 'message will be unable to send') + logger.error( + translate( + "OscCueError", + "Error parsing OSC arguments, " + "message will be unable to send", + ) ) if not (checkable and not self.fadeGroup.isCheckable()): - conf['duration'] = self.fadeSpin.value() * 1000 - conf['fade_type'] = self.fadeCurveCombo.currentType() + conf["duration"] = self.fadeSpin.value() * 1000 + conf["fade_type"] = self.fadeCurveCombo.currentType() return conf def loadSettings(self, settings): - if 'path' in settings: - path = settings.get('path', '') + if "path" in settings: + path = settings.get("path", "") self.pathEdit.setText(path) - if 'args' in settings: + if "args" in settings: - args = settings.get('args', '') + args = settings.get("args", "") for arg in args: - self.oscModel.appendRow(arg['type'], - arg['start'], - arg['end'] if 'end' in arg else None, - arg['fade']) + self.oscModel.appendRow( + arg["type"], + arg["start"], + arg["end"] if "end" in arg else None, + arg["fade"], + ) - self.fadeSpin.setValue(settings.get('duration', 0) / 1000) - self.fadeCurveCombo.setCurrentType(settings.get('fade_type', '')) + self.fadeSpin.setValue(settings.get("duration", 0) / 1000) + self.fadeCurveCombo.setCurrentType(settings.get("fade_type", "")) def __new_argument(self): - self.oscModel.appendRow(OscMessageType.Int.value, 0, '', False) + self.oscModel.appendRow(OscMessageType.Int.value, 0, "", False) def __remove_argument(self): if self.oscModel.rowCount(): @@ -353,9 +388,9 @@ def __test_message(self): if not test_path(path): QDetailedMessageBox.dwarning( - 'Warning', - 'No valid path for OSC message - nothing sent', - "Path should start with a '/' followed by a name." + "Warning", + "No valid path for OSC message - nothing sent", + "Path should start with a '/' followed by a name.", ) return @@ -370,7 +405,8 @@ def __test_message(self): self.__osc.server.send(self.path, *args) except ValueError: QMessageBox.critical( - self, 'Error', 'Error on parsing argument list - nothing sent') + self, "Error", "Error on parsing argument list - nothing sent" + ) def __argument_changed(self, index_topleft, index_bottomright, roles): if not (Qt.EditRole in roles): @@ -389,22 +425,22 @@ def __argument_changed(self, index_topleft, index_bottomright, roles): delegate_start = self.oscView.itemDelegate(index_start) delegate_end = self.oscView.itemDelegate(index_end) - if osc_type == 'Integer': + if osc_type == "Integer": delegate_start.updateEditor(OscMessageType.Int) delegate_end.updateEditor(OscMessageType.Int) model_row[COL_START_VAL] = 0 - elif osc_type == 'Float': + elif osc_type == "Float": delegate_start.updateEditor(OscMessageType.Float) delegate_end.updateEditor(OscMessageType.Float) model_row[COL_START_VAL] = 0 - elif osc_type == 'Bool': + elif osc_type == "Bool": delegate_start.updateEditor(OscMessageType.Bool) delegate_end.updateEditor() model_row[COL_START_VAL] = True else: delegate_start.updateEditor(OscMessageType.String) delegate_end.updateEditor() - model_row[COL_START_VAL] = 'None' + model_row[COL_START_VAL] = "None" model_row[COL_END_VAL] = None model_row[COL_DO_FADE] = False @@ -425,10 +461,11 @@ def __init__(self, **kwargs): self.delegates = [ ComboBoxDelegate( options=[i.value for i in OscMessageType], - tr_context='OscMessageType'), + tr_context="OscMessageType", + ), OscArgumentDelegate(), OscArgumentDelegate(), - CheckBoxDelegate() + CheckBoxDelegate(), ] self.setSelectionBehavior(QTableWidget.SelectRows) diff --git a/lisp/plugins/action_cues/seek_cue.py b/lisp/plugins/action_cues/seek_cue.py index af9c13814..37cb4e6c9 100644 --- a/lisp/plugins/action_cues/seek_cue.py +++ b/lisp/plugins/action_cues/seek_cue.py @@ -19,8 +19,14 @@ from PyQt5 import QtCore from PyQt5.QtCore import QTime, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QPushButton, QLabel, \ - QHBoxLayout, QTimeEdit +from PyQt5.QtWidgets import ( + QVBoxLayout, + QGroupBox, + QPushButton, + QLabel, + QHBoxLayout, + QTimeEdit, +) from lisp.application import Application from lisp.core.properties import Property @@ -33,14 +39,14 @@ class SeekCue(Cue): - Name = QT_TRANSLATE_NOOP('CueName', 'Seek Cue') + Name = QT_TRANSLATE_NOOP("CueName", "Seek Cue") target_id = Property() time = Property(default=-1) def __init__(self, **kwargs): super().__init__(**kwargs) - self.name = translate('CueName', self.Name) + self.name = translate("CueName", self.Name) def __start__(self, fade=False): cue = Application().cue_model.get(self.target_id) @@ -51,7 +57,7 @@ def __start__(self, fade=False): class SeekCueSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Seek Settings') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Seek Settings") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -61,7 +67,8 @@ def __init__(self, **kwargs): self.cue_id = -1 self.cueDialog = CueSelectDialog( - cues=Application().cue_model.filter(MediaCue), parent=self) + cues=Application().cue_model.filter(MediaCue), parent=self + ) self.cueGroup = QGroupBox(self) self.cueGroup.setLayout(QVBoxLayout()) @@ -80,7 +87,7 @@ def __init__(self, **kwargs): self.layout().addWidget(self.seekGroup) self.seekEdit = QTimeEdit(self.seekGroup) - self.seekEdit.setDisplayFormat('HH.mm.ss.zzz') + self.seekEdit.setDisplayFormat("HH.mm.ss.zzz") self.seekGroup.layout().addWidget(self.seekEdit) self.seekLabel = QLabel(self.seekGroup) @@ -90,11 +97,11 @@ def __init__(self, **kwargs): self.retranslateUi() def retranslateUi(self): - self.cueGroup.setTitle(translate('SeekCue', 'Cue')) - self.cueButton.setText(translate('SeekCue', 'Click to select')) - self.cueLabel.setText(translate('SeekCue', 'Not selected')) - self.seekGroup.setTitle(translate('SeekCue', 'Seek')) - self.seekLabel.setText(translate('SeekCue', 'Time to reach')) + self.cueGroup.setTitle(translate("SeekCue", "Cue")) + self.cueButton.setText(translate("SeekCue", "Click to select")) + self.cueLabel.setText(translate("SeekCue", "Not selected")) + self.seekGroup.setTitle(translate("SeekCue", "Seek")) + self.seekLabel.setText(translate("SeekCue", "Time to reach")) def select_cue(self): if self.cueDialog.exec_() == self.cueDialog.Accepted: @@ -103,7 +110,8 @@ def select_cue(self): if cue is not None: self.cue_id = cue.id self.seekEdit.setMaximumTime( - QTime.fromMSecsSinceStartOfDay(cue.media.duration)) + QTime.fromMSecsSinceStartOfDay(cue.media.duration) + ) self.cueLabel.setText(cue.name) def enableCheck(self, enabled): @@ -114,20 +122,24 @@ def enableCheck(self, enabled): self.seekGroup.setChecked(False) def getSettings(self): - return {'target_id': self.cue_id, - 'time': self.seekEdit.time().msecsSinceStartOfDay()} + return { + "target_id": self.cue_id, + "time": self.seekEdit.time().msecsSinceStartOfDay(), + } def loadSettings(self, settings): if settings is not None: - cue = Application().cue_model.get(settings.get('target_id')) + cue = Application().cue_model.get(settings.get("target_id")) if cue is not None: - self.cue_id = settings['target_id'] + self.cue_id = settings["target_id"] self.seekEdit.setMaximumTime( - QTime.fromMSecsSinceStartOfDay(cue.media.duration)) + QTime.fromMSecsSinceStartOfDay(cue.media.duration) + ) self.cueLabel.setText(cue.name) self.seekEdit.setTime( - QTime.fromMSecsSinceStartOfDay(settings.get('time', 0))) + QTime.fromMSecsSinceStartOfDay(settings.get("time", 0)) + ) CueSettingsRegistry().add(SeekCueSettings, SeekCue) diff --git a/lisp/plugins/action_cues/stop_all.py b/lisp/plugins/action_cues/stop_all.py index 0f72a8db8..128560fc2 100644 --- a/lisp/plugins/action_cues/stop_all.py +++ b/lisp/plugins/action_cues/stop_all.py @@ -29,24 +29,28 @@ class StopAll(Cue): - Name = QT_TRANSLATE_NOOP('CueName', 'Stop-All') + Name = QT_TRANSLATE_NOOP("CueName", "Stop-All") action = Property(default=CueAction.Stop.value) def __init__(self, **kwargs): super().__init__(**kwargs) - self.name = translate('CueName', self.Name) + self.name = translate("CueName", self.Name) def __start__(self, fade=False): Application().layout.execute_all(action=self.action, quiet=True) class StopAllSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Stop Settings') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Stop Settings") SupportedActions = [ - CueAction.Stop, CueAction.FadeOutStop, CueAction.Pause, - CueAction.FadeOutPause, CueAction.Interrupt, CueAction.FadeOutInterrupt + CueAction.Stop, + CueAction.FadeOutStop, + CueAction.Pause, + CueAction.FadeOutPause, + CueAction.Interrupt, + CueAction.FadeOutInterrupt, ] def __init__(self, **kwargs): @@ -61,13 +65,14 @@ def __init__(self, **kwargs): self.actionCombo = QComboBox(self.group) for action in StopAllSettings.SupportedActions: self.actionCombo.addItem( - translate('CueAction', action.name), action.value) + translate("CueAction", action.name), action.value + ) self.group.layout().addWidget(self.actionCombo) self.retranslateUi() def retranslateUi(self): - self.group.setTitle(translate('StopAll', 'Stop Action')) + self.group.setTitle(translate("StopAll", "Stop Action")) def enableCheck(self, enabled): self.group.setCheckable(enabled) @@ -77,13 +82,14 @@ def getSettings(self): conf = {} if not (self.group.isCheckable() and not self.group.isChecked()): - conf['action'] = self.actionCombo.currentData() + conf["action"] = self.actionCombo.currentData() return conf def loadSettings(self, settings): self.actionCombo.setCurrentText( - translate('CueAction', settings.get('action', ''))) + translate("CueAction", settings.get("action", "")) + ) CueSettingsRegistry().add(StopAllSettings, StopAll) diff --git a/lisp/plugins/action_cues/volume_control.py b/lisp/plugins/action_cues/volume_control.py index f0aff5eae..4224836e0 100644 --- a/lisp/plugins/action_cues/volume_control.py +++ b/lisp/plugins/action_cues/volume_control.py @@ -21,12 +21,22 @@ from PyQt5 import QtCore from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QVBoxLayout, QLabel, QHBoxLayout, QGroupBox, \ - QPushButton, QDoubleSpinBox +from PyQt5.QtWidgets import ( + QVBoxLayout, + QLabel, + QHBoxLayout, + QGroupBox, + QPushButton, + QDoubleSpinBox, +) from lisp.application import Application -from lisp.backend.audio_utils import MIN_VOLUME_DB, MAX_VOLUME_DB, \ - linear_to_db, db_to_linear +from lisp.backend.audio_utils import ( + MIN_VOLUME_DB, + MAX_VOLUME_DB, + linear_to_db, + db_to_linear, +) from lisp.core.decorators import async_function from lisp.core.fade_functions import FadeInType, FadeOutType from lisp.core.fader import Fader @@ -43,27 +53,32 @@ class VolumeControl(Cue): - Name = QT_TRANSLATE_NOOP('CueName', 'Volume Control') + Name = QT_TRANSLATE_NOOP("CueName", "Volume Control") target_id = Property() fade_type = Property(default=FadeInType.Linear.name) - volume = Property(default=.0) + volume = Property(default=0.0) - CueActions = (CueAction.Default, CueAction.Start, CueAction.Stop, - CueAction.Pause, CueAction.Interrupt) + CueActions = ( + CueAction.Default, + CueAction.Start, + CueAction.Stop, + CueAction.Pause, + CueAction.Interrupt, + ) def __init__(self, **kwargs): super().__init__(**kwargs) - self.name = translate('CueName', self.Name) + self.name = translate("CueName", self.Name) - self.__fader = Fader(None, 'live_volume') + self.__fader = Fader(None, "live_volume") self.__init_fader() def __init_fader(self): cue = Application().cue_model.get(self.target_id) if isinstance(cue, MediaCue): - volume = cue.media.element('Volume') + volume = cue.media.element("Volume") if volume is not None: if volume is not self.__fader.target: self.__fader.target = volume @@ -104,7 +119,8 @@ def __fade(self, fade_type): try: self.__fader.prepare() ended = self.__fader.fade( - round(self.duration / 1000, 2), self.volume, fade_type) + round(self.duration / 1000, 2), self.volume, fade_type + ) if ended: # to avoid approximation problems @@ -112,7 +128,8 @@ def __fade(self, fade_type): self._ended() except Exception: logger.exception( - translate('VolumeControlError', 'Error during cue execution.')) + translate("VolumeControlError", "Error during cue execution.") + ) self._error() def current_time(self): @@ -120,7 +137,7 @@ def current_time(self): class VolumeSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Volume Settings') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Volume Settings") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -139,7 +156,7 @@ def __init__(self, **kwargs): self.cueLabel = QLabel(self.cueGroup) self.cueLabel.setAlignment(QtCore.Qt.AlignCenter) - self.cueLabel.setStyleSheet('font-weight: bold;') + self.cueLabel.setStyleSheet("font-weight: bold;") self.cueGroup.layout().addWidget(self.cueLabel) self.cueButton = QPushButton(self.cueGroup) @@ -155,7 +172,7 @@ def __init__(self, **kwargs): self.volumeEdit.setMaximum(100) self.volumeGroup.layout().addWidget(self.volumeEdit) - self.percentLabel = QLabel('%', self.volumeGroup) + self.percentLabel = QLabel("%", self.volumeGroup) self.volumeGroup.layout().addWidget(self.percentLabel) self.volumeDbEdit = QDoubleSpinBox(self.volumeGroup) @@ -163,7 +180,7 @@ def __init__(self, **kwargs): self.volumeDbEdit.setValue(MIN_VOLUME_DB) self.volumeGroup.layout().addWidget(self.volumeDbEdit) - self.dbLabel = QLabel('dB', self.volumeGroup) + self.dbLabel = QLabel("dB", self.volumeGroup) self.volumeGroup.layout().addWidget(self.dbLabel) self.volumeEdit.valueChanged.connect(self.__volume_change) @@ -180,11 +197,11 @@ def __init__(self, **kwargs): self.retranslateUi() def retranslateUi(self): - self.cueGroup.setTitle(translate('VolumeControl', 'Cue')) - self.cueButton.setText(translate('VolumeControl', 'Click to select')) - self.cueLabel.setText(translate('VolumeControl', 'Not selected')) - self.volumeGroup.setTitle(translate('VolumeControl', 'Volume to reach')) - self.fadeGroup.setTitle(translate('VolumeControl', 'Fade')) + self.cueGroup.setTitle(translate("VolumeControl", "Cue")) + self.cueButton.setText(translate("VolumeControl", "Click to select")) + self.cueLabel.setText(translate("VolumeControl", "Not selected")) + self.volumeGroup.setTitle(translate("VolumeControl", "Volume to reach")) + self.fadeGroup.setTitle(translate("VolumeControl", "Fade")) def select_cue(self): if self.cueDialog.exec_() == self.cueDialog.Accepted: @@ -209,24 +226,24 @@ def getSettings(self): checkable = self.cueGroup.isCheckable() if not (checkable and not self.cueGroup.isChecked()): - conf['target_id'] = self.cue_id + conf["target_id"] = self.cue_id if not (checkable and not self.volumeGroup.isCheckable()): - conf['volume'] = self.volumeEdit.value() / 100 + conf["volume"] = self.volumeEdit.value() / 100 if not (checkable and not self.fadeGroup.isCheckable()): - conf['duration'] = self.fadeEdit.duration() * 1000 - conf['fade_type'] = self.fadeEdit.fadeType() + conf["duration"] = self.fadeEdit.duration() * 1000 + conf["fade_type"] = self.fadeEdit.fadeType() return conf def loadSettings(self, settings): - cue = Application().cue_model.get(settings.get('target_id', '')) + cue = Application().cue_model.get(settings.get("target_id", "")) if cue is not None: - self.cue_id = settings['target_id'] + self.cue_id = settings["target_id"] self.cueLabel.setText(cue.name) - self.volumeEdit.setValue(settings.get('volume', 0) * 100) - self.fadeEdit.setDuration(settings.get('duration', 0) / 1000) - self.fadeEdit.setFadeType(settings.get('fade_type', '')) + self.volumeEdit.setValue(settings.get("volume", 0) * 100) + self.fadeEdit.setDuration(settings.get("duration", 0) / 1000) + self.fadeEdit.setFadeType(settings.get("fade_type", "")) def __volume_change(self, value): if not self.__v_edit_flag: diff --git a/lisp/plugins/cart_layout/__init__.py b/lisp/plugins/cart_layout/__init__.py index aeafe7c98..e29154335 100644 --- a/lisp/plugins/cart_layout/__init__.py +++ b/lisp/plugins/cart_layout/__init__.py @@ -6,9 +6,9 @@ class CartLayout(Plugin): - Name = 'Cart Layout' - Description = 'Provide a layout that organizes cues in grid-like pages' - Authors = ('Francesco Ceruti', ) + Name = "Cart Layout" + Description = "Provide a layout that organizes cues in grid-like pages" + Authors = ("Francesco Ceruti",) def __init__(self, app): super().__init__(app) @@ -16,4 +16,5 @@ def __init__(self, app): _CartLayout.Config = CartLayout.Config register_layout(_CartLayout) AppConfigurationDialog.registerSettingsPage( - 'layouts.cart_layout', CartLayoutSettings, CartLayout.Config) + "layouts.cart_layout", CartLayoutSettings, CartLayout.Config + ) diff --git a/lisp/plugins/cart_layout/cue_widget.py b/lisp/plugins/cart_layout/cue_widget.py index 4495dfb96..5325d974e 100644 --- a/lisp/plugins/cart_layout/cue_widget.py +++ b/lisp/plugins/cart_layout/cue_widget.py @@ -19,8 +19,15 @@ from PyQt5.QtCore import Qt, QMimeData, pyqtSignal, QPoint from PyQt5.QtGui import QColor, QDrag -from PyQt5.QtWidgets import QProgressBar, QLCDNumber, QLabel, QHBoxLayout, \ - QWidget, QSizePolicy, QVBoxLayout +from PyQt5.QtWidgets import ( + QProgressBar, + QLCDNumber, + QLabel, + QHBoxLayout, + QWidget, + QSizePolicy, + QVBoxLayout, +) from lisp.backend.audio_utils import slider_to_fader, fader_to_slider from lisp.core.signal import Connection @@ -66,7 +73,7 @@ def __init__(self, cue, **kwargs): self.layout().addLayout(self.hLayout, 4) self.nameButton = QClickLabel(self) - self.nameButton.setObjectName('ButtonCueWidget') + self.nameButton.setObjectName("ButtonCueWidget") self.nameButton.setWordWrap(True) self.nameButton.setAlignment(Qt.AlignCenter) self.nameButton.setFocusPolicy(Qt.NoFocus) @@ -75,9 +82,10 @@ def __init__(self, cue, **kwargs): self.hLayout.addWidget(self.nameButton, 5) self.statusIcon = QLabel(self.nameButton) - self.statusIcon.setStyleSheet('background-color: transparent') + self.statusIcon.setStyleSheet("background-color: transparent") self.statusIcon.setPixmap( - IconTheme.get('led-off').pixmap(CueWidget.ICON_SIZE)) + IconTheme.get("led-off").pixmap(CueWidget.ICON_SIZE) + ) self.seekSlider = QClickSlider(self.nameButton) self.seekSlider.setOrientation(Qt.Horizontal) @@ -85,13 +93,14 @@ def __init__(self, cue, **kwargs): self.seekSlider.setVisible(False) self.volumeSlider = QClickSlider(self.nameButton) - self.volumeSlider.setObjectName('VolumeSlider') + self.volumeSlider.setObjectName("VolumeSlider") self.volumeSlider.setOrientation(Qt.Vertical) self.volumeSlider.setFocusPolicy(Qt.NoFocus) self.volumeSlider.setRange(0, CueWidget.SLIDER_RANGE) self.volumeSlider.setPageStep(10) self.volumeSlider.valueChanged.connect( - self._changeVolume, Qt.DirectConnection) + self._changeVolume, Qt.DirectConnection + ) self.volumeSlider.setVisible(False) self.dbMeter = QDbMeter(self) @@ -103,10 +112,10 @@ def __init__(self, cue, **kwargs): self.timeBar.setLayout(QHBoxLayout()) self.timeBar.layout().setContentsMargins(0, 0, 0, 0) self.timeDisplay = QLCDNumber(self.timeBar) - self.timeDisplay.setStyleSheet('background-color: transparent') + self.timeDisplay.setStyleSheet("background-color: transparent") self.timeDisplay.setSegmentStyle(QLCDNumber.Flat) self.timeDisplay.setDigitCount(8) - self.timeDisplay.display('00:00:00') + self.timeDisplay.display("00:00:00") self.timeBar.layout().addWidget(self.timeDisplay) self.timeBar.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.timeBar.setVisible(False) @@ -125,7 +134,7 @@ def selected(self): def selected(self, value): self._selected = value # Show the selection via stylesheet/qproperties - self.nameButton.setProperty('selected', self.selected) + self.nameButton.setProperty("selected", self.selected) self.nameButton.style().unpolish(self.nameButton) self.nameButton.style().polish(self.nameButton) @@ -133,9 +142,10 @@ def contextMenuEvent(self, event): self.contextMenuRequested.emit(event.globalPos()) def mouseMoveEvent(self, event): - if (event.buttons() == Qt.LeftButton and - (event.modifiers() == Qt.ControlModifier or - event.modifiers() == Qt.ShiftModifier)): + if event.buttons() == Qt.LeftButton and ( + event.modifiers() == Qt.ControlModifier + or event.modifiers() == Qt.ShiftModifier + ): mime_data = QMimeData() mime_data.setText(CartPageWidget.DRAG_MAGIC) @@ -173,7 +183,7 @@ def showDBMeters(self, visible): self._dBMeterElement = None if visible: - self._dBMeterElement = self._cue.media.element('DbMeter') + self._dBMeterElement = self._cue.media.element("DbMeter") if self._dBMeterElement is not None: self._dBMeterElement.level_ready.connect(self.dbMeter.plot) @@ -190,18 +200,18 @@ def showVolumeSlider(self, visible): self._showVolume = visible if self._volumeElement is not None: - self._volumeElement.changed('volume').disconnect( - self.resetVolume) + self._volumeElement.changed("volume").disconnect( + self.resetVolume + ) self._volumeElement = None if visible: self.volumeSlider.setEnabled(self._cue.state & CueState.Running) - self._volumeElement = self._cue.media.element('Volume') + self._volumeElement = self._cue.media.element("Volume") if self._volumeElement is not None: self.resetVolume() - self._volumeElement.changed('volume').connect( - self.resetVolume, - Connection.QtQueued + self._volumeElement.changed("volume").connect( + self.resetVolume, Connection.QtQueued ) self.hLayout.insertWidget(1, self.volumeSlider, 1) @@ -214,21 +224,27 @@ def showVolumeSlider(self, visible): def resetVolume(self): if self._volumeElement is not None: - self.volumeSlider.setValue(round(fader_to_slider( - self._volumeElement.volume) * CueWidget.SLIDER_RANGE)) + self.volumeSlider.setValue( + round( + fader_to_slider(self._volumeElement.volume) + * CueWidget.SLIDER_RANGE + ) + ) def _setCue(self, cue): self._cue = cue # Cue properties changes - self._cue.changed('name').connect( - self._updateName, Connection.QtQueued) - self._cue.changed('stylesheet').connect( - self._updateStyle, Connection.QtQueued) - self._cue.changed('duration').connect( - self._updateDuration, Connection.QtQueued) - self._cue.changed('description').connect( - self._updateDescription, Connection.QtQueued) + self._cue.changed("name").connect(self._updateName, Connection.QtQueued) + self._cue.changed("stylesheet").connect( + self._updateStyle, Connection.QtQueued + ) + self._cue.changed("duration").connect( + self._updateDuration, Connection.QtQueued + ) + self._cue.changed("description").connect( + self._updateDescription, Connection.QtQueued + ) # FadeOut start/end self._cue.fadein_start.connect(self._enterFadein, Connection.QtQueued) @@ -249,7 +265,8 @@ def _setCue(self, cue): # Media cues features dBMeter and seekSlider if isinstance(cue, MediaCue): self._cue.media.elements_changed.connect( - self._mediaUpdated, Connection.QtQueued) + self._mediaUpdated, Connection.QtQueued + ) self._cue.paused.connect(self.dbMeter.reset, Connection.QtQueued) self._cue.stopped.connect(self.dbMeter.reset, Connection.QtQueued) @@ -278,11 +295,14 @@ def _updateDescription(self, description): def _changeVolume(self, new_volume): self._volumeElement.live_volume = slider_to_fader( - new_volume / CueWidget.SLIDER_RANGE) + new_volume / CueWidget.SLIDER_RANGE + ) def _clicked(self, event): - if not (self.seekSlider.geometry().contains(event.pos()) and - self.seekSlider.isVisible()): + if not ( + self.seekSlider.geometry().contains(event.pos()) + and self.seekSlider.isVisible() + ): if event.button() != Qt.RightButton: if event.modifiers() == Qt.ShiftModifier: self.editRequested.emit(self._cue) @@ -310,24 +330,28 @@ def _exitFade(self): def _statusStopped(self): self.statusIcon.setPixmap( - IconTheme.get('led-off').pixmap(CueWidget.ICON_SIZE)) + IconTheme.get("led-off").pixmap(CueWidget.ICON_SIZE) + ) self.volumeSlider.setEnabled(False) self._updateTime(0, True) self.resetVolume() def _statusPlaying(self): self.statusIcon.setPixmap( - IconTheme.get('led-running').pixmap(CueWidget.ICON_SIZE)) + IconTheme.get("led-running").pixmap(CueWidget.ICON_SIZE) + ) self.volumeSlider.setEnabled(True) def _statusPaused(self): self.statusIcon.setPixmap( - IconTheme.get('led-pause').pixmap(CueWidget.ICON_SIZE)) + IconTheme.get("led-pause").pixmap(CueWidget.ICON_SIZE) + ) self.volumeSlider.setEnabled(False) def _statusError(self): self.statusIcon.setPixmap( - IconTheme.get('led-error').pixmap(CueWidget.ICON_SIZE)) + IconTheme.get("led-error").pixmap(CueWidget.ICON_SIZE) + ) self.volumeSlider.setEnabled(False) self.resetVolume() @@ -365,7 +389,8 @@ def _updateTime(self, time, ignore_visibility=False): # Show the time in the widget self.timeDisplay.display( - strtime(time, accurate=self._accurateTiming)) + strtime(time, accurate=self._accurateTiming) + ) def resizeEvent(self, event): self.update() @@ -381,4 +406,5 @@ def update(self): self.seekSlider.setGeometry(4, s_ypos, s_width, s_height) self.statusIcon.setGeometry( - 4, 4, CueWidget.ICON_SIZE, CueWidget.ICON_SIZE) + 4, 4, CueWidget.ICON_SIZE, CueWidget.ICON_SIZE + ) diff --git a/lisp/plugins/cart_layout/layout.py b/lisp/plugins/cart_layout/layout.py index 50a572e84..77d085c31 100644 --- a/lisp/plugins/cart_layout/layout.py +++ b/lisp/plugins/cart_layout/layout.py @@ -28,7 +28,12 @@ from lisp.cues.cue_memento_model import CueMementoAdapter from lisp.cues.media_cue import MediaCue from lisp.layout.cue_layout import CueLayout -from lisp.layout.cue_menu import SimpleMenuAction, MENU_PRIORITY_CUE, MenuActionsGroup, MENU_PRIORITY_LAYOUT +from lisp.layout.cue_menu import ( + SimpleMenuAction, + MENU_PRIORITY_CUE, + MenuActionsGroup, + MENU_PRIORITY_LAYOUT, +) from lisp.plugins.cart_layout.cue_widget import CueWidget from lisp.plugins.cart_layout.model import CueCartModel from lisp.plugins.cart_layout.page_widget import CartPageWidget @@ -37,17 +42,20 @@ class CartLayout(CueLayout): - NAME = QT_TRANSLATE_NOOP('LayoutName', 'Cart Layout') + NAME = QT_TRANSLATE_NOOP("LayoutName", "Cart Layout") DESCRIPTION = translate( - 'LayoutDescription', 'Organize cues in grid like pages') + "LayoutDescription", "Organize cues in grid like pages" + ) DETAILS = [ - QT_TRANSLATE_NOOP('LayoutDetails', 'Click a cue to run it'), - QT_TRANSLATE_NOOP('LayoutDetails', 'SHIFT + Click to edit a cue'), - QT_TRANSLATE_NOOP('LayoutDetails', 'CTRL + Click to select a cue'), + QT_TRANSLATE_NOOP("LayoutDetails", "Click a cue to run it"), + QT_TRANSLATE_NOOP("LayoutDetails", "SHIFT + Click to edit a cue"), + QT_TRANSLATE_NOOP("LayoutDetails", "CTRL + Click to select a cue"), QT_TRANSLATE_NOOP( - 'LayoutDetails', 'To copy cues drag them while pressing CTRL'), + "LayoutDetails", "To copy cues drag them while pressing CTRL" + ), QT_TRANSLATE_NOOP( - 'LayoutDetails', 'To move cues drag them while pressing SHIFT') + "LayoutDetails", "To move cues drag them while pressing SHIFT" + ), ] Config = DummyConfiguration() @@ -62,20 +70,23 @@ class CartLayout(CueLayout): def __init__(self, application): super().__init__(application) - self.__columns = CartLayout.Config['grid.columns'] - self.__rows = CartLayout.Config['grid.rows'] + self.__columns = CartLayout.Config["grid.columns"] + self.__rows = CartLayout.Config["grid.rows"] self._cart_model = CueCartModel( - self.cue_model, self.__rows, self.__columns) + self.cue_model, self.__rows, self.__columns + ) # TODO: move this logic in CartTabWidget ? self._cart_model.item_added.connect( - self.__cue_added, Connection.QtQueued) + self.__cue_added, Connection.QtQueued + ) self._cart_model.item_removed.connect( - self.__cue_removed, Connection.QtQueued) + self.__cue_removed, Connection.QtQueued + ) self._cart_model.item_moved.connect( - self.__cue_moved, Connection.QtQueued) - self._cart_model.model_reset.connect( - self.__model_reset) + self.__cue_moved, Connection.QtQueued + ) + self._cart_model.model_reset.connect(self.__model_reset) self._memento_model = CueMementoAdapter(self._cart_model) self._cart_view = CartTabWidget() @@ -116,7 +127,9 @@ def __init__(self, application): self.show_volume_action = QAction(parent=layout_menu) self.show_volume_action.setCheckable(True) - self.show_volume_action.triggered.connect(self._set_volume_controls_visible) + self.show_volume_action.triggered.connect( + self._set_volume_controls_visible + ) layout_menu.addAction(self.show_volume_action) self.show_accurate_action = QAction(parent=layout_menu) @@ -124,53 +137,52 @@ def __init__(self, application): self.show_accurate_action.triggered.connect(self._set_accurate_time) layout_menu.addAction(self.show_accurate_action) - self._set_countdown_mode(CartLayout.Config['countdownMode']) - self._set_dbmeters_visible(CartLayout.Config['show.dBMeters']) - self._set_accurate_time(CartLayout.Config['show.accurateTime']) - self._set_seek_bars_visible(CartLayout.Config['show.seekSliders']) + self._set_countdown_mode(CartLayout.Config["countdownMode"]) + self._set_dbmeters_visible(CartLayout.Config["show.dBMeters"]) + self._set_accurate_time(CartLayout.Config["show.accurateTime"]) + self._set_seek_bars_visible(CartLayout.Config["show.seekSliders"]) self._set_volume_controls_visible( - CartLayout.Config['show.volumeControls']) + CartLayout.Config["show.volumeControls"] + ) # Context menu actions self._edit_actions_group = MenuActionsGroup(priority=MENU_PRIORITY_CUE) self._edit_actions_group.add( SimpleMenuAction( - translate('ListLayout', 'Edit cue'), + translate("ListLayout", "Edit cue"), self.edit_cue, - translate('ListLayout', 'Edit selected cues'), + translate("ListLayout", "Edit selected cues"), self.edit_cues, ), SimpleMenuAction( - translate('ListLayout', 'Remove cue'), + translate("ListLayout", "Remove cue"), self.cue_model.remove, - translate('ListLayout', 'Remove selected cues'), - self._remove_cues - ) + translate("ListLayout", "Remove selected cues"), + self._remove_cues, + ), ) self.CuesMenu.add(self._edit_actions_group) self._media_actions_group = MenuActionsGroup( - priority=MENU_PRIORITY_CUE + 1) + priority=MENU_PRIORITY_CUE + 1 + ) self._media_actions_group.add( SimpleMenuAction( - translate('CartLayout', 'Play'), - lambda cue: cue.start() + translate("CartLayout", "Play"), lambda cue: cue.start() ), SimpleMenuAction( - translate('CartLayout', 'Pause'), - lambda cue: cue.pause() + translate("CartLayout", "Pause"), lambda cue: cue.pause() ), SimpleMenuAction( - translate('CartLayout', 'Stop'), - lambda cue: cue.stop() - ) + translate("CartLayout", "Stop"), lambda cue: cue.stop() + ), ) self.CuesMenu.add(self._media_actions_group, MediaCue) self._reset_volume_action = SimpleMenuAction( - translate('CartLayout', 'Reset volume'), + translate("CartLayout", "Reset volume"), self._reset_cue_volume, - priority=MENU_PRIORITY_LAYOUT + priority=MENU_PRIORITY_LAYOUT, ) self.CuesMenu.add(self._reset_volume_action, MediaCue) @@ -178,17 +190,22 @@ def __init__(self, application): self.add_page() def retranslate(self): - self.new_page_action.setText(translate('CartLayout', 'Add page')) - self.new_pages_action.setText(translate('CartLayout', 'Add pages')) + self.new_page_action.setText(translate("CartLayout", "Add page")) + self.new_pages_action.setText(translate("CartLayout", "Add pages")) self.rm_current_page_action.setText( - translate('CartLayout', 'Remove current page')) - self.countdown_mode_action.setText(translate('CartLayout', 'Countdown mode')) - self.show_seek_action.setText(translate('CartLayout', 'Show seek-bars')) + translate("CartLayout", "Remove current page") + ) + self.countdown_mode_action.setText( + translate("CartLayout", "Countdown mode") + ) + self.show_seek_action.setText(translate("CartLayout", "Show seek-bars")) self.show_dbmeter_action.setText( - translate('CartLayout', 'Show dB-meters')) - self.show_volume_action.setText(translate('CartLayout', 'Show volume')) + translate("CartLayout", "Show dB-meters") + ) + self.show_volume_action.setText(translate("CartLayout", "Show volume")) self.show_accurate_action.setText( - translate('CartLayout', 'Show accurate time')) + translate("CartLayout", "Show accurate time") + ) def view(self): return self._cart_view @@ -223,9 +240,11 @@ def invert_selection(self): def add_pages(self): pages, accepted = QInputDialog.getInt( self._cart_view, - translate('CartLayout', 'Add pages'), - translate('CartLayout', 'Number of Pages:'), - value=1, min=1, max=10 + translate("CartLayout", "Add pages"), + translate("CartLayout", "Number of Pages:"), + value=1, + min=1, + max=10, ) if accepted: @@ -239,7 +258,8 @@ def add_page(self): page.copyWidgetRequested.connect(self._copy_widget) self._cart_view.addTab( - page, 'Page {}'.format(self._cart_view.count() + 1)) + page, "Page {}".format(self._cart_view.count() + 1) + ) def remove_current_page(self): if self._cart_view.count(): @@ -260,7 +280,7 @@ def remove_page(self, index): page.deleteLater() # Rename every successive tab accordingly - text = translate('CartLayout', 'Page {number}') + text = translate("CartLayout", "Page {number}") for n in range(index, self._cart_view.count()): self._cart_view.setTabText(n, text.format(number=n + 1)) @@ -345,7 +365,7 @@ def to_1d_index(self, index): page *= self.__rows * self.__columns row *= self.__columns return page + row + column - except(TypeError, ValueError): + except (TypeError, ValueError): return -1 def finalize(self): @@ -371,12 +391,14 @@ def _page(self, index): def _move_widget(self, widget, to_row, to_column): new_index = self.to_1d_index( - (self._cart_view.currentIndex(), to_row, to_column)) + (self._cart_view.currentIndex(), to_row, to_column) + ) self._cart_model.move(widget.cue.index, new_index) def _copy_widget(self, widget, to_row, to_column): new_index = self.to_1d_index( - (self._cart_view.currentIndex(), to_row, to_column)) + (self._cart_view.currentIndex(), to_row, to_column) + ) new_cue = CueFactory.clone_cue(widget.cue) self._cart_model.insert(new_cue, new_index) @@ -455,11 +477,13 @@ def __init__(self, *args): super().__init__(*args) self.setIcon(self.Question) - self.setWindowTitle(translate('CartLayout', 'Warning')) + self.setWindowTitle(translate("CartLayout", "Warning")) self.setText( - translate('CartLayout', 'Every cue in the page will be lost.')) + translate("CartLayout", "Every cue in the page will be lost.") + ) self.setInformativeText( - translate('CartLayout', 'Are you sure to continue?')) + translate("CartLayout", "Are you sure to continue?") + ) self.setStandardButtons(QMessageBox.Yes | QMessageBox.No) self.setDefaultButton(QMessageBox.No) diff --git a/lisp/plugins/cart_layout/model.py b/lisp/plugins/cart_layout/model.py index 7fbfd8a3c..a7b96e505 100644 --- a/lisp/plugins/cart_layout/model.py +++ b/lisp/plugins/cart_layout/model.py @@ -69,7 +69,7 @@ def item(self, index): try: return self.__cues[index] except KeyError: - raise IndexError('index out of range') + raise IndexError("index out of range") def insert(self, item, index): index = self.flat(index) @@ -84,7 +84,7 @@ def pop(self, index): try: cue = self.__cues[index] except KeyError: - raise IndexError('index out of range') + raise IndexError("index out of range") self.model.remove(cue) return cue @@ -98,7 +98,7 @@ def move(self, old_index, new_index): self.__cues[new_index].index = new_index self.item_moved.emit(old_index, new_index) else: - raise ModelException('index already used {}'.format(new_index)) + raise ModelException("index already used {}".format(new_index)) def page_edges(self, page): start = self.flat((page, 0, 0)) diff --git a/lisp/plugins/cart_layout/page_widget.py b/lisp/plugins/cart_layout/page_widget.py index e317c563d..bf09e4521 100644 --- a/lisp/plugins/cart_layout/page_widget.py +++ b/lisp/plugins/cart_layout/page_widget.py @@ -31,7 +31,7 @@ class CartPageWidget(QWidget): moveWidgetRequested = pyqtSignal(object, int, int) copyWidgetRequested = pyqtSignal(object, int, int) - DRAG_MAGIC = 'LiSP_Drag&Drop' + DRAG_MAGIC = "LiSP_Drag&Drop" def __init__(self, rows, columns, *args): super().__init__(*args) @@ -64,7 +64,7 @@ def addWidget(self, widget, row, column): self.layout().addWidget(widget, row, column) widget.show() else: - raise IndexError('cell {} already used'.format((row, column))) + raise IndexError("cell {} already used".format((row, column))) def takeWidget(self, row, column): self._checkIndex(row, column) @@ -74,7 +74,7 @@ def takeWidget(self, row, column): self.layout().removeWidget(widget) return widget else: - raise IndexError('cell {} is empty'.format((row, column))) + raise IndexError("cell {} is empty".format((row, column))) def moveWidget(self, o_row, o_column, n_row, n_column): widget = self.takeWidget(o_row, o_column) @@ -132,11 +132,14 @@ def widgetAt(self, pos): def _checkIndex(self, row, column): if not isinstance(row, int): raise TypeError( - 'rows index must be integers, not {}'.format(typename(row))) + "rows index must be integers, not {}".format(typename(row)) + ) if not isinstance(column, int): raise TypeError( - 'columns index must be integers, not {}'.format( - typename(column))) + "columns index must be integers, not {}".format( + typename(column) + ) + ) if not 0 <= row < self.__rows or not 0 <= column < self.__columns: - raise IndexError('index out of bound {}'.format((row, column))) + raise IndexError("index out of bound {}".format((row, column))) diff --git a/lisp/plugins/cart_layout/settings.py b/lisp/plugins/cart_layout/settings.py index 695162376..848d5f37f 100644 --- a/lisp/plugins/cart_layout/settings.py +++ b/lisp/plugins/cart_layout/settings.py @@ -18,15 +18,21 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QGridLayout, \ - QSpinBox, QLabel +from PyQt5.QtWidgets import ( + QGroupBox, + QVBoxLayout, + QCheckBox, + QGridLayout, + QSpinBox, + QLabel, +) from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate class CartLayoutSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Cart Layout') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Cart Layout") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -77,36 +83,37 @@ def __init__(self, **kwargs): def retranslateUi(self): self.behaviorsGroup.setTitle( - translate('CartLayout', 'Default behaviors')) - self.countdownMode.setText(translate('CartLayout', 'Countdown mode')) - self.showSeek.setText(translate('CartLayout', 'Show seek-bars')) - self.showDbMeters.setText(translate('CartLayout', 'Show dB-meters')) - self.showAccurate.setText(translate('CartLayout', 'Show accurate time')) - self.showVolume.setText(translate('CartLayout', 'Show volume')) - self.gridSizeGroup.setTitle(translate('CartLayout', 'Grid size')) - self.columnsLabel.setText(translate('CartLayout', 'Number of columns')) - self.rowsLabel.setText(translate('CartLayout', 'Number of rows')) + translate("CartLayout", "Default behaviors") + ) + self.countdownMode.setText(translate("CartLayout", "Countdown mode")) + self.showSeek.setText(translate("CartLayout", "Show seek-bars")) + self.showDbMeters.setText(translate("CartLayout", "Show dB-meters")) + self.showAccurate.setText(translate("CartLayout", "Show accurate time")) + self.showVolume.setText(translate("CartLayout", "Show volume")) + self.gridSizeGroup.setTitle(translate("CartLayout", "Grid size")) + self.columnsLabel.setText(translate("CartLayout", "Number of columns")) + self.rowsLabel.setText(translate("CartLayout", "Number of rows")) def loadSettings(self, settings): - self.columnsSpin.setValue(settings['grid']['columns']) - self.rowsSpin.setValue(settings['grid']['rows']) - self.showSeek.setChecked(settings['show']['seekSliders']) - self.showDbMeters.setChecked(settings['show']['dBMeters']) - self.showAccurate.setChecked(settings['show']['accurateTime']) - self.showVolume.setChecked(settings['show']['volumeControls']) - self.countdownMode.setChecked(settings['countdownMode']) + self.columnsSpin.setValue(settings["grid"]["columns"]) + self.rowsSpin.setValue(settings["grid"]["rows"]) + self.showSeek.setChecked(settings["show"]["seekSliders"]) + self.showDbMeters.setChecked(settings["show"]["dBMeters"]) + self.showAccurate.setChecked(settings["show"]["accurateTime"]) + self.showVolume.setChecked(settings["show"]["volumeControls"]) + self.countdownMode.setChecked(settings["countdownMode"]) def getSettings(self): return { - 'grid': { - 'columns': self.columnsSpin.value(), - 'rows': self.rowsSpin.value() + "grid": { + "columns": self.columnsSpin.value(), + "rows": self.rowsSpin.value(), }, - 'show': { - 'dBMeters': self.showDbMeters.isChecked(), - 'seekSliders': self.showSeek.isChecked(), - 'accurateTime': self.showAccurate.isChecked(), - 'volumeControls': self.showVolume.isChecked() + "show": { + "dBMeters": self.showDbMeters.isChecked(), + "seekSliders": self.showSeek.isChecked(), + "accurateTime": self.showAccurate.isChecked(), + "volumeControls": self.showVolume.isChecked(), }, - 'countdownMode': self.countdownMode.isChecked() + "countdownMode": self.countdownMode.isChecked(), } diff --git a/lisp/plugins/cart_layout/tab_widget.py b/lisp/plugins/cart_layout/tab_widget.py index 28b021eec..9430ac754 100644 --- a/lisp/plugins/cart_layout/tab_widget.py +++ b/lisp/plugins/cart_layout/tab_widget.py @@ -25,7 +25,7 @@ class CartTabWidget(QTabWidget): - DRAG_MAGIC = 'LiSP_Drag&Drop' + DRAG_MAGIC = "LiSP_Drag&Drop" keyPressed = pyqtSignal(QKeyEvent) @@ -33,7 +33,7 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.setTabBar(QEditableTabBar(self)) self.tabBar().setDrawBase(False) - self.tabBar().setObjectName('CartTabBar') + self.tabBar().setObjectName("CartTabBar") self.setFocusPolicy(Qt.StrongFocus) self.setAcceptDrops(True) diff --git a/lisp/plugins/controller/__init__.py b/lisp/plugins/controller/__init__.py index d1ee4f421..f41157fef 100644 --- a/lisp/plugins/controller/__init__.py +++ b/lisp/plugins/controller/__init__.py @@ -1 +1 @@ -from .controller import Controller \ No newline at end of file +from .controller import Controller diff --git a/lisp/plugins/controller/common.py b/lisp/plugins/controller/common.py index a509e985f..fa70e8050 100644 --- a/lisp/plugins/controller/common.py +++ b/lisp/plugins/controller/common.py @@ -24,35 +24,45 @@ class LayoutAction(Enum): - Go = 'Go' - Reset = 'Reset' + Go = "Go" + Reset = "Reset" - StopAll = 'StopAll' - PauseAll = 'PauseAll' - ResumeAll = 'ResumeAll' - InterruptAll = 'InterrupAll' - FadeOutAll = 'FadeOutAll' - FadeInAll = 'FadeInAll' + StopAll = "StopAll" + PauseAll = "PauseAll" + ResumeAll = "ResumeAll" + InterruptAll = "InterrupAll" + FadeOutAll = "FadeOutAll" + FadeInAll = "FadeInAll" - StandbyForward = 'StandbyForward' - StandbyBack = 'StandbyBack' + StandbyForward = "StandbyForward" + StandbyBack = "StandbyBack" LayoutActionsStrings = { - LayoutAction.Go: QT_TRANSLATE_NOOP('GlobalAction', 'Go'), - LayoutAction.Reset: QT_TRANSLATE_NOOP('GlobalAction', 'Reset'), - LayoutAction.StopAll: QT_TRANSLATE_NOOP('GlobalAction', 'Stop all cues'), - LayoutAction.PauseAll: QT_TRANSLATE_NOOP('GlobalAction', 'Pause all cues'), - LayoutAction.ResumeAll: QT_TRANSLATE_NOOP('GlobalAction', 'Resume all cues'), - LayoutAction.InterruptAll: QT_TRANSLATE_NOOP('GlobalAction', 'Interrupt all cues'), - LayoutAction.FadeOutAll: QT_TRANSLATE_NOOP('GlobalAction', 'Fade-out all cues'), - LayoutAction.FadeInAll: QT_TRANSLATE_NOOP('GlobalAction', 'Fade-in all cues'), - LayoutAction.StandbyForward: QT_TRANSLATE_NOOP('GlobalAction', 'Move standby forward'), - LayoutAction.StandbyBack: QT_TRANSLATE_NOOP('GlobalAction', 'Move standby back'), + LayoutAction.Go: QT_TRANSLATE_NOOP("GlobalAction", "Go"), + LayoutAction.Reset: QT_TRANSLATE_NOOP("GlobalAction", "Reset"), + LayoutAction.StopAll: QT_TRANSLATE_NOOP("GlobalAction", "Stop all cues"), + LayoutAction.PauseAll: QT_TRANSLATE_NOOP("GlobalAction", "Pause all cues"), + LayoutAction.ResumeAll: QT_TRANSLATE_NOOP( + "GlobalAction", "Resume all cues" + ), + LayoutAction.InterruptAll: QT_TRANSLATE_NOOP( + "GlobalAction", "Interrupt all cues" + ), + LayoutAction.FadeOutAll: QT_TRANSLATE_NOOP( + "GlobalAction", "Fade-out all cues" + ), + LayoutAction.FadeInAll: QT_TRANSLATE_NOOP( + "GlobalAction", "Fade-in all cues" + ), + LayoutAction.StandbyForward: QT_TRANSLATE_NOOP( + "GlobalAction", "Move standby forward" + ), + LayoutAction.StandbyBack: QT_TRANSLATE_NOOP( + "GlobalAction", "Move standby back" + ), } def tr_layout_action(action): - return translate( - 'CueAction', LayoutActionsStrings.get(action, action.name) - ) + return translate("CueAction", LayoutActionsStrings.get(action, action.name)) diff --git a/lisp/plugins/controller/controller.py b/lisp/plugins/controller/controller.py index b2a7bf84e..4a664936f 100644 --- a/lisp/plugins/controller/controller.py +++ b/lisp/plugins/controller/controller.py @@ -24,7 +24,10 @@ from lisp.cues.cue import Cue, CueAction from lisp.plugins.controller import protocols from lisp.plugins.controller.common import LayoutAction -from lisp.plugins.controller.controller_settings import CueControllerSettingsPage, ControllerLayoutConfiguration +from lisp.plugins.controller.controller_settings import ( + CueControllerSettingsPage, + ControllerLayoutConfiguration, +) from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.ui_utils import translate @@ -33,11 +36,12 @@ class Controller(Plugin): - Name = 'Controller' - Authors = ('Francesco Ceruti', 'Thomas Achtner') - OptDepends = ('Midi', 'Osc') - Description = 'Allow to control cues via external commands with multiple ' \ - 'protocols' + Name = "Controller" + Authors = ("Francesco Ceruti", "Thomas Achtner") + OptDepends = ("Midi", "Osc") + Description = ( + "Allow to control cues via external commands with multiple " "protocols" + ) def __init__(self, app): super().__init__(app) @@ -59,7 +63,10 @@ def __init__(self, app): # Register the settings widget AppConfigurationDialog.registerSettingsPage( - 'plugins.controller', ControllerLayoutConfiguration, Controller.Config) + "plugins.controller", + ControllerLayoutConfiguration, + Controller.Config, + ) # Register settings-page CueSettingsRegistry().add(CueControllerSettingsPage) @@ -84,13 +91,14 @@ def global_changed(self, *args): # Here we just rebuild everything which is not the best solution # but is the easiest self.__global_map = {} - for protocol in Controller.Config['protocols'].values(): + for protocol in Controller.Config["protocols"].values(): for key, action in protocol: self.__global_map.setdefault(key, set()).add( - LayoutAction(action)) + LayoutAction(action) + ) def cue_changed(self, cue, property_name, value): - if property_name == 'controller': + if property_name == "controller": self.delete_from_cue_map(cue) for protocol in self.__protocols: @@ -99,12 +107,12 @@ def cue_changed(self, cue, property_name, value): actions_map.setdefault(cue, set()).add(CueAction(action)) def delete_from_cue_map(self, cue): - for actions_map in self.__cue_map: + for actions_map in self.__cue_map.values(): actions_map.pop(cue) def perform_cue_action(self, key): - for actions_map in self.__cue_map.get(key, {}): - for cue, action in actions_map: + for cue, actions in self.__cue_map.get(key, {}).items(): + for action in actions: cue.execute(action) def perform_session_action(self, key): @@ -128,16 +136,18 @@ def perform_session_action(self, key): self.app.layout.fadeout_all() elif action is LayoutAction.StandbyForward: self.app.layout.set_standby_index( - self.app.layout.standby_index() + 1) + self.app.layout.standby_index() + 1 + ) elif action is LayoutAction.StandbyBack: self.app.layout.set_standby_index( - self.app.layout.standby_index() - 1) + self.app.layout.standby_index() - 1 + ) else: self.app.finalize() def __cue_added(self, cue): cue.property_changed.connect(self.cue_changed) - self.cue_changed(cue, 'controller', cue.controller) + self.cue_changed(cue, "controller", cue.controller) def __cue_removed(self, cue): cue.property_changed.disconnect(self.cue_changed) @@ -156,6 +166,6 @@ def __load_protocols(self): except Exception: logger.exception( translate( - 'Controller', 'Cannot load controller protocol: "{}"' + "Controller", 'Cannot load controller protocol: "{}"' ).format(protocol_class.__name__) ) diff --git a/lisp/plugins/controller/controller_settings.py b/lisp/plugins/controller/controller_settings.py index 119dde09a..077f729ab 100644 --- a/lisp/plugins/controller/controller_settings.py +++ b/lisp/plugins/controller/controller_settings.py @@ -24,7 +24,7 @@ class CueControllerSettingsPage(SettingsPagesTabWidget, CuePageMixin): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Cue Control') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Cue Control") def __init__(self, cueType, **kwargs): super().__init__(cueType=cueType, **kwargs) @@ -33,14 +33,14 @@ def __init__(self, cueType, **kwargs): self.addPage(page(cueType, parent=self)) def getSettings(self): - return {'controller': super().getSettings()} + return {"controller": super().getSettings()} def loadSettings(self, settings): - super().loadSettings(settings.get('controller', {})) + super().loadSettings(settings.get("controller", {})) class ControllerLayoutConfiguration(SettingsPagesTabWidget): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Layout Controls') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Layout Controls") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -49,7 +49,7 @@ def __init__(self, **kwargs): self.addPage(page(parent=self)) def loadSettings(self, settings): - super().loadSettings(settings['protocols']) + super().loadSettings(settings["protocols"]) def getSettings(self): - return {'protocols': super().getSettings()} + return {"protocols": super().getSettings()} diff --git a/lisp/plugins/controller/protocols/keyboard.py b/lisp/plugins/controller/protocols/keyboard.py index 68837f418..74ea8694f 100644 --- a/lisp/plugins/controller/protocols/keyboard.py +++ b/lisp/plugins/controller/protocols/keyboard.py @@ -18,21 +18,30 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QGridLayout, QTableView, QHeaderView, \ - QPushButton, QVBoxLayout +from PyQt5.QtWidgets import ( + QGroupBox, + QGridLayout, + QTableView, + QHeaderView, + QPushButton, + QVBoxLayout, +) from lisp.application import Application from lisp.plugins.controller.common import LayoutAction, tr_layout_action from lisp.plugins.controller.protocol import Protocol -from lisp.ui.qdelegates import LineEditDelegate, CueActionDelegate,\ - EnumComboBoxDelegate +from lisp.ui.qdelegates import ( + LineEditDelegate, + CueActionDelegate, + EnumComboBoxDelegate, +) from lisp.ui.qmodels import SimpleTableModel from lisp.ui.settings.pages import SettingsPage, CuePageMixin from lisp.ui.ui_utils import translate class KeyboardSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Keyboard Shortcuts') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Keyboard Shortcuts") def __init__(self, actionDelegate, **kwargs): super().__init__(**kwargs) @@ -43,9 +52,12 @@ def __init__(self, actionDelegate, **kwargs): self.keyGroup.setLayout(QGridLayout()) self.layout().addWidget(self.keyGroup) - self.keyboardModel = SimpleTableModel([ - translate('ControllerKeySettings', 'Key'), - translate('ControllerKeySettings', 'Action')]) + self.keyboardModel = SimpleTableModel( + [ + translate("ControllerKeySettings", "Key"), + translate("ControllerKeySettings", "Action"), + ] + ) self.keyboardView = KeyboardView(actionDelegate, parent=self.keyGroup) self.keyboardView.setModel(self.keyboardModel) @@ -63,28 +75,23 @@ def __init__(self, actionDelegate, **kwargs): self._defaultAction = None def retranslateUi(self): - self.keyGroup.setTitle(translate('ControllerKeySettings', 'Shortcuts')) - self.addButton.setText(translate('ControllerSettings', 'Add')) - self.removeButton.setText(translate('ControllerSettings', 'Remove')) + self.keyGroup.setTitle(translate("ControllerKeySettings", "Shortcuts")) + self.addButton.setText(translate("ControllerSettings", "Add")) + self.removeButton.setText(translate("ControllerSettings", "Remove")) def enableCheck(self, enabled): self.keyGroup.setCheckable(enabled) self.keyGroup.setChecked(False) def getSettings(self): - settings = {} - - if not (self.keyGroup.isCheckable() and not self.keyGroup.isChecked()): - settings['keyboard'] = self.keyboardModel.rows - - return settings + return {"keyboard": self.keyboardModel.rows} def loadSettings(self, settings): - for key, action in settings.get('keyboard', []): + for key, action in settings.get("keyboard", []): self.keyboardModel.appendRow(key, action) def _addEmpty(self): - self.keyboardModel.appendRow('', self._defaultAction) + self.keyboardModel.appendRow("", self._defaultAction) def _removeCurrent(self): self.keyboardModel.removeRow(self.keyboardView.currentIndex().row()) @@ -94,10 +101,10 @@ class KeyboardCueSettings(KeyboardSettings, CuePageMixin): def __init__(self, cueType, **kwargs): super().__init__( actionDelegate=CueActionDelegate( - cue_class=cueType, - mode=CueActionDelegate.Mode.Name), + cue_class=cueType, mode=CueActionDelegate.Mode.Name + ), cueType=cueType, - **kwargs + **kwargs, ) self._defaultAction = self.cueType.CueActions[0].name @@ -108,15 +115,14 @@ def __init__(self, **kwargs): actionDelegate=EnumComboBoxDelegate( LayoutAction, mode=EnumComboBoxDelegate.Mode.Name, - trItem=tr_layout_action + trItem=tr_layout_action, ), - **kwargs + **kwargs, ) self._defaultAction = LayoutAction.Go.name class KeyboardView(QTableView): - def __init__(self, actionDelegate, **kwargs): super().__init__(**kwargs) @@ -150,5 +156,5 @@ def reset(self): Application().layout.key_pressed.disconnect(self.__key_pressed) def __key_pressed(self, key_event): - if not key_event.isAutoRepeat() and key_event.text() != '': + if not key_event.isAutoRepeat() and key_event.text() != "": self.protocol_event.emit(key_event.text()) diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index d234eaccb..590b34dc7 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -18,21 +18,34 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QPushButton, QComboBox, QVBoxLayout, \ - QMessageBox, QTableView, QTableWidget, QHeaderView, QGridLayout +from PyQt5.QtWidgets import ( + QGroupBox, + QPushButton, + QComboBox, + QVBoxLayout, + QMessageBox, + QTableView, + QTableWidget, + QHeaderView, + QGridLayout, +) from lisp.plugins import get_plugin, PluginNotLoadedError from lisp.plugins.controller.common import LayoutAction, tr_layout_action from lisp.plugins.controller.protocol import Protocol -from lisp.ui.qdelegates import ComboBoxDelegate, SpinBoxDelegate, \ - CueActionDelegate, EnumComboBoxDelegate +from lisp.ui.qdelegates import ( + ComboBoxDelegate, + SpinBoxDelegate, + CueActionDelegate, + EnumComboBoxDelegate, +) from lisp.ui.qmodels import SimpleTableModel from lisp.ui.settings.pages import CuePageMixin, SettingsPage from lisp.ui.ui_utils import translate class MidiSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'MIDI Controls') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "MIDI Controls") def __init__(self, actionDelegate, **kwargs): super().__init__(**kwargs) @@ -40,16 +53,19 @@ def __init__(self, actionDelegate, **kwargs): self.layout().setAlignment(Qt.AlignTop) self.midiGroup = QGroupBox(self) - self.midiGroup.setTitle(translate('ControllerMidiSettings', 'MIDI')) + self.midiGroup.setTitle(translate("ControllerMidiSettings", "MIDI")) # self.midiGroup.setEnabled(check_module('midi')) self.midiGroup.setLayout(QGridLayout()) self.layout().addWidget(self.midiGroup) - self.midiModel = SimpleTableModel([ - translate('ControllerMidiSettings', 'Type'), - translate('ControllerMidiSettings', 'Channel'), - translate('ControllerMidiSettings', 'Note'), - translate('ControllerMidiSettings', 'Action')]) + self.midiModel = SimpleTableModel( + [ + translate("ControllerMidiSettings", "Type"), + translate("ControllerMidiSettings", "Channel"), + translate("ControllerMidiSettings", "Note"), + translate("ControllerMidiSettings", "Action"), + ] + ) self.midiView = MidiView(actionDelegate, parent=self.midiGroup) self.midiView.setModel(self.midiModel) @@ -69,59 +85,55 @@ def __init__(self, actionDelegate, **kwargs): self.msgTypeCombo = QComboBox(self.midiGroup) self.msgTypeCombo.addItem( - translate('ControllerMidiSettings', 'Filter "note on"')) - self.msgTypeCombo.setItemData(0, 'note_on', Qt.UserRole) + translate("ControllerMidiSettings", 'Filter "note on"') + ) + self.msgTypeCombo.setItemData(0, "note_on", Qt.UserRole) self.msgTypeCombo.addItem( - translate('ControllerMidiSettings', 'Filter "note off"')) - self.msgTypeCombo.setItemData(1, 'note_off', Qt.UserRole) + translate("ControllerMidiSettings", 'Filter "note off"') + ) + self.msgTypeCombo.setItemData(1, "note_off", Qt.UserRole) self.midiGroup.layout().addWidget(self.msgTypeCombo, 2, 1) self.retranslateUi() self._defaultAction = None try: - self.__midi = get_plugin('Midi') + self.__midi = get_plugin("Midi") except PluginNotLoadedError: self.setEnabled(False) def retranslateUi(self): - self.addButton.setText(translate('ControllerSettings', 'Add')) - self.removeButton.setText(translate('ControllerSettings', 'Remove')) - self.midiCapture.setText(translate('ControllerMidiSettings', 'Capture')) + self.addButton.setText(translate("ControllerSettings", "Add")) + self.removeButton.setText(translate("ControllerSettings", "Remove")) + self.midiCapture.setText(translate("ControllerMidiSettings", "Capture")) def enableCheck(self, enabled): self.midiGroup.setCheckable(enabled) self.midiGroup.setChecked(False) def getSettings(self): - settings = {} - checkable = self.midiGroup.isCheckable() - - if not (checkable and not self.midiGroup.isChecked()): - messages = [] + entries = [] + for row in self.midiModel.rows: + message = Midi.str_from_values(row[0], row[1] - 1, row[2]) + entries.append((message, row[-1])) - for row in self.midiModel.rows: - message = Midi.str_from_values(row[0], row[1]-1, row[2]) - messages.append((message, row[-1])) - - if messages: - settings['midi'] = messages - - return settings + return {"midi": entries} def loadSettings(self, settings): - if 'midi' in settings: - for options in settings['midi']: - m_type, channel, note = Midi.from_string(options[0]) - self.midiModel.appendRow(m_type, channel+1, note, options[1]) + if "midi" in settings: + for entries in settings["midi"]: + m_type, channel, note = Midi.from_string(entries[0]) + self.midiModel.appendRow(m_type, channel + 1, note, entries[1]) def capture_message(self): handler = self.__midi.input handler.alternate_mode = True handler.new_message_alt.connect(self.__add_message) - QMessageBox.information(self, '', translate( - 'ControllerMidiSettings', 'Listening MIDI messages ...') + QMessageBox.information( + self, + "", + translate("ControllerMidiSettings", "Listening MIDI messages ..."), ) handler.new_message_alt.disconnect(self.__add_message) @@ -130,7 +142,8 @@ def capture_message(self): def __add_message(self, msg): if self.msgTypeCombo.currentData(Qt.UserRole) == msg.type: self.midiModel.appendRow( - msg.type, msg.channel+1, msg.note, self._defaultAction) + msg.type, msg.channel + 1, msg.note, self._defaultAction + ) def __new_message(self): message_type = self.msgTypeCombo.currentData(Qt.UserRole) @@ -144,10 +157,10 @@ class MidiCueSettings(MidiSettings, CuePageMixin): def __init__(self, cueType, **kwargs): super().__init__( actionDelegate=CueActionDelegate( - cue_class=cueType, - mode=CueActionDelegate.Mode.Name), + cue_class=cueType, mode=CueActionDelegate.Mode.Name + ), cueType=cueType, - **kwargs + **kwargs, ) self._defaultAction = self.cueType.CueActions[0].name @@ -158,9 +171,9 @@ def __init__(self, **kwargs): actionDelegate=EnumComboBoxDelegate( LayoutAction, mode=EnumComboBoxDelegate.Mode.Name, - trItem=tr_layout_action + trItem=tr_layout_action, ), - **kwargs + **kwargs, ) self._defaultAction = LayoutAction.Go.name @@ -170,10 +183,10 @@ def __init__(self, actionDelegate, **kwargs): super().__init__(**kwargs) self.delegates = [ - ComboBoxDelegate(options=['note_on', 'note_off']), + ComboBoxDelegate(options=["note_on", "note_off"]), SpinBoxDelegate(minimum=1, maximum=16), SpinBoxDelegate(minimum=0, maximum=127), - actionDelegate + actionDelegate, ] self.setSelectionBehavior(QTableWidget.SelectRows) @@ -200,10 +213,10 @@ class Midi(Protocol): def __init__(self): super().__init__() # Install callback for new MIDI messages - get_plugin('Midi').input.new_message.connect(self.__new_message) + get_plugin("Midi").input.new_message.connect(self.__new_message) def __new_message(self, message): - if message.type == 'note_on' or message.type == 'note_off': + if message.type == "note_on" or message.type == "note_off": self.protocol_event.emit(Midi.str_from_message(message)) @staticmethod @@ -212,9 +225,9 @@ def str_from_message(message): @staticmethod def str_from_values(m_type, channel, note): - return '{} {} {}'.format(m_type, channel, note) + return "{} {} {}".format(m_type, channel, note) @staticmethod def from_string(message_str): m_type, channel, note = message_str.split() - return m_type, int(channel), int(note) \ No newline at end of file + return m_type, int(channel), int(note) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index ffe92bef0..3e7b93065 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -21,16 +21,32 @@ import ast from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QPushButton, QVBoxLayout, \ - QTableView, QTableWidget, QHeaderView, QGridLayout, QLabel, \ - QDialog, QDialogButtonBox, QLineEdit, QMessageBox +from PyQt5.QtWidgets import ( + QGroupBox, + QPushButton, + QVBoxLayout, + QTableView, + QTableWidget, + QHeaderView, + QGridLayout, + QLabel, + QDialog, + QDialogButtonBox, + QLineEdit, + QMessageBox, +) from lisp.plugins import get_plugin, PluginNotLoadedError from lisp.plugins.controller.common import LayoutAction, tr_layout_action from lisp.plugins.controller.protocol import Protocol from lisp.plugins.osc.osc_delegate import OscArgumentDelegate from lisp.plugins.osc.osc_server import OscMessageType -from lisp.ui.qdelegates import ComboBoxDelegate, LineEditDelegate, CueActionDelegate, EnumComboBoxDelegate +from lisp.ui.qdelegates import ( + ComboBoxDelegate, + LineEditDelegate, + CueActionDelegate, + EnumComboBoxDelegate, +) from lisp.ui.qmodels import SimpleTableModel from lisp.ui.settings.pages import SettingsPage, CuePageMixin from lisp.ui.ui_utils import translate @@ -56,9 +72,9 @@ def __init__(self, **kwargs): self.pathEdit = QLineEdit() self.groupBox.layout().addWidget(self.pathEdit, 1, 0, 1, 2) - self.model = SimpleTableModel([ - translate('Osc Cue', 'Type'), - translate('Osc Cue', 'Argument')]) + self.model = SimpleTableModel( + [translate("Osc Cue", "Type"), translate("Osc Cue", "Argument")] + ) self.model.dataChanged.connect(self.__argument_changed) @@ -112,21 +128,25 @@ def __argument_changed(self, index_topleft, index_bottomright, roles): osc_type = model_row[0] if curr_col == 0: - if osc_type == 'Integer' or osc_type == 'Float': + if osc_type == "Integer" or osc_type == "Float": model_row[1] = 0 - elif osc_type == 'Bool': + elif osc_type == "Bool": model_row[1] = True else: - model_row[1] = '' + model_row[1] = "" def retranslateUi(self): self.groupBox.setTitle( - translate('ControllerOscSettings', 'OSC Message')) + translate("ControllerOscSettings", "OSC Message") + ) self.pathLabel.setText( - translate('ControllerOscSettings', - 'OSC Path: (example: "/path/to/something")')) - self.addButton.setText(translate('OscCue', 'Add')) - self.removeButton.setText(translate('OscCue', 'Remove')) + translate( + "ControllerOscSettings", + 'OSC Path: (example: "/path/to/something")', + ) + ) + self.addButton.setText(translate("OscCue", "Add")) + self.removeButton.setText(translate("OscCue", "Remove")) class OscArgumentView(QTableView): @@ -136,8 +156,9 @@ def __init__(self, **kwargs): self.delegates = [ ComboBoxDelegate( options=[i.value for i in OscMessageType], - tr_context='OscMessageType'), - OscArgumentDelegate() + tr_context="OscMessageType", + ), + OscArgumentDelegate(), ] self.setSelectionBehavior(QTableWidget.SelectRows) @@ -158,7 +179,7 @@ def __init__(self, **kwargs): class OscSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'OSC Controls') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "OSC Controls") def __init__(self, actionDelegate, **kwargs): super().__init__(**kwargs) @@ -166,15 +187,18 @@ def __init__(self, actionDelegate, **kwargs): self.layout().setAlignment(Qt.AlignTop) self.oscGroup = QGroupBox(self) - self.oscGroup.setTitle(translate('ControllerOscSettings', 'OSC')) + self.oscGroup.setTitle(translate("ControllerOscSettings", "OSC")) self.oscGroup.setLayout(QGridLayout()) self.layout().addWidget(self.oscGroup) - self.oscModel = SimpleTableModel([ - translate('ControllerOscSettings', 'Path'), - translate('ControllerOscSettings', 'Types'), - translate('ControllerOscSettings', 'Arguments'), - translate('ControllerOscSettings', 'Actions')]) + self.oscModel = SimpleTableModel( + [ + translate("ControllerOscSettings", "Path"), + translate("ControllerOscSettings", "Types"), + translate("ControllerOscSettings", "Arguments"), + translate("ControllerOscSettings", "Actions"), + ] + ) self.OscView = OscView(actionDelegate, parent=self.oscGroup) self.OscView.setModel(self.oscModel) @@ -197,90 +221,84 @@ def __init__(self, actionDelegate, **kwargs): self.captureDialog.setMaximumSize(self.captureDialog.size()) self.captureDialog.setMinimumSize(self.captureDialog.size()) self.captureDialog.setWindowTitle( - translate('ControllerOscSettings', 'OSC Capture')) + translate("ControllerOscSettings", "OSC Capture") + ) self.captureDialog.setModal(True) - self.captureLabel = QLabel('Waiting for message:') + self.captureLabel = QLabel("Waiting for message:") self.captureLabel.setAlignment(Qt.AlignCenter) self.captureDialog.setLayout(QVBoxLayout()) self.captureDialog.layout().addWidget(self.captureLabel) self.buttonBox = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, - Qt.Horizontal, self.captureDialog) + Qt.Horizontal, + self.captureDialog, + ) self.buttonBox.accepted.connect(self.captureDialog.accept) self.buttonBox.rejected.connect(self.captureDialog.reject) self.captureDialog.layout().addWidget(self.buttonBox) - self.capturedMessage = {'path': None, 'types': None, 'args': None} + self.capturedMessage = {"path": None, "types": None, "args": None} self.retranslateUi() self._defaultAction = None try: - self.__osc = get_plugin('Osc') + self.__osc = get_plugin("Osc") except PluginNotLoadedError: self.setEnabled(False) def retranslateUi(self): - self.addButton.setText(translate('ControllerOscSettings', 'Add')) - self.removeButton.setText(translate('ControllerOscSettings', 'Remove')) - self.oscCapture.setText(translate('ControllerOscSettings', 'Capture')) + self.addButton.setText(translate("ControllerOscSettings", "Add")) + self.removeButton.setText(translate("ControllerOscSettings", "Remove")) + self.oscCapture.setText(translate("ControllerOscSettings", "Capture")) def enableCheck(self, enabled): self.oscGroup.setCheckable(enabled) self.oscGroup.setChecked(False) def getSettings(self): - settings = {} - - messages = [] - + entries = [] for row in self.oscModel.rows: - key = Osc.key_from_values(row[0], row[1], row[2]) - messages.append((key, row[-1])) - - if messages: - settings['osc'] = messages + message = Osc.key_from_values(row[0], row[1], row[2]) + entries.append((message, row[-1])) - return settings + return {"osc": entries} def loadSettings(self, settings): - if 'osc' in settings: - for options in settings['osc']: + if "osc" in settings: + for options in settings["osc"]: key = Osc.message_from_key(options[0]) self.oscModel.appendRow( - key[0], - key[1], - '{}'.format(key[2:])[1:-1], - options[1] + key[0], key[1], "{}".format(key[2:])[1:-1], options[1] ) def capture_message(self): self.__osc.server.new_message.connect(self.__show_message) result = self.captureDialog.exec() - if result == QDialog.Accepted and self.capturedMessage['path']: - args = '{}'.format(self.capturedMessage['args'])[1:-1] + if result == QDialog.Accepted and self.capturedMessage["path"]: + args = "{}".format(self.capturedMessage["args"])[1:-1] self.oscModel.appendRow( - self.capturedMessage['path'], - self.capturedMessage['types'], + self.capturedMessage["path"], + self.capturedMessage["types"], args, - self._defaultAction + self._defaultAction, ) self.__osc.server.new_message.disconnect(self.__show_message) - self.captureLabel.setText('Waiting for message:') + self.captureLabel.setText("Waiting for message:") def __show_message(self, path, args, types): - self.capturedMessage['path'] = path - self.capturedMessage['types'] = types - self.capturedMessage['args'] = args + self.capturedMessage["path"] = path + self.capturedMessage["types"] = types + self.capturedMessage["args"] = args self.captureLabel.setText( 'OSC: "{0}" "{1}" {2}'.format( - self.capturedMessage['path'], - self.capturedMessage['types'], - self.capturedMessage['args'] + self.capturedMessage["path"], + self.capturedMessage["types"], + self.capturedMessage["args"], ) ) @@ -288,37 +306,35 @@ def __new_message(self): dialog = OscMessageDialog(parent=self) if dialog.exec_() == dialog.Accepted: path = dialog.pathEdit.text() - if len(path) < 2 or path[0] is not '/': + if len(path) < 2 or path[0] is not "/": QMessageBox.warning( - self, 'Warning', - 'Osc path seems not valid, \ndo not forget to edit the ' - 'path later.', + self, + "Warning", + "Osc path seems not valid, \ndo not forget to edit the " + "path later.", ) - types = '' + types = "" arguments = [] for row in dialog.model.rows: - if row[0] == 'Bool': + if row[0] == "Bool": if row[1] is True: - types += 'T' + types += "T" if row[1] is True: - types += 'F' + types += "F" else: - if row[0] == 'Integer': - types += 'i' - elif row[0] == 'Float': - types += 'f' - elif row[0] == 'String': - types += 's' + if row[0] == "Integer": + types += "i" + elif row[0] == "Float": + types += "f" + elif row[0] == "String": + types += "s" else: - raise TypeError('Unsupported Osc Type') + raise TypeError("Unsupported Osc Type") arguments.append(row[1]) self.oscModel.appendRow( - path, - types, - '{}'.format(arguments)[1:-1], - self._defaultAction + path, types, "{}".format(arguments)[1:-1], self._defaultAction ) def __remove_message(self): @@ -330,10 +346,10 @@ class OscCueSettings(OscSettings, CuePageMixin): def __init__(self, cueType, **kwargs): super().__init__( actionDelegate=CueActionDelegate( - cue_class=cueType, - mode=CueActionDelegate.Mode.Name), + cue_class=cueType, mode=CueActionDelegate.Mode.Name + ), cueType=cueType, - **kwargs + **kwargs, ) self._defaultAction = self.cueType.CueActions[0].name @@ -344,9 +360,9 @@ def __init__(self, **kwargs): actionDelegate=EnumComboBoxDelegate( LayoutAction, mode=EnumComboBoxDelegate.Mode.Name, - trItem=tr_layout_action + trItem=tr_layout_action, ), - **kwargs + **kwargs, ) self._defaultAction = LayoutAction.Go.name @@ -359,7 +375,7 @@ def __init__(self, actionDelegate, **kwargs): LineEditDelegate(), LineEditDelegate(), LineEditDelegate(), - actionDelegate + actionDelegate, ] self.setSelectionBehavior(QTableWidget.SelectRows) @@ -386,7 +402,7 @@ class Osc(Protocol): def __init__(self): super().__init__() - osc = get_plugin('Osc') + osc = get_plugin("Osc") osc.server.new_message.connect(self.__new_message) def __new_message(self, path, args, types, *_, **__): @@ -396,7 +412,7 @@ def __new_message(self, path, args, types, *_, **__): @staticmethod def key_from_message(path, types, args): key = [path, types, *args] - return 'OSC{}'.format(key) + return "OSC{}".format(key) @staticmethod def key_from_values(path, types, args): diff --git a/lisp/plugins/gst_backend/elements/alsa_sink.py b/lisp/plugins/gst_backend/elements/alsa_sink.py index ee0926a5a..347879cbd 100644 --- a/lisp/plugins/gst_backend/elements/alsa_sink.py +++ b/lisp/plugins/gst_backend/elements/alsa_sink.py @@ -27,22 +27,22 @@ class AlsaSink(GstMediaElement): ElementType = ElementType.Output MediaType = MediaType.Audio - Name = QT_TRANSLATE_NOOP('MediaElementName', 'ALSA Out') + Name = QT_TRANSLATE_NOOP("MediaElementName", "ALSA Out") - device = GstProperty('alsa_sink', 'device', default='') + device = GstProperty("alsa_sink", "device", default="") def __init__(self, pipeline): super().__init__(pipeline) - self.audio_resample = Gst.ElementFactory.make('audioresample', None) - self.alsa_sink = Gst.ElementFactory.make('alsasink', 'sink') + self.audio_resample = Gst.ElementFactory.make("audioresample", None) + self.alsa_sink = Gst.ElementFactory.make("alsasink", "sink") self.pipeline.add(self.audio_resample) self.pipeline.add(self.alsa_sink) self.audio_resample.link(self.alsa_sink) - self.changed('device').connect(self._update_device) + self.changed("device").connect(self._update_device) def sink(self): return self.audio_resample @@ -54,8 +54,8 @@ def _update_device(self, new_device): self.alsa_sink.set_state(Gst.State.NULL) # Create new element and add it to the pipeline - self.alsa_sink = Gst.ElementFactory.make('alsasink', 'sink') - self.alsa_sink.set_property('device', new_device) + self.alsa_sink = Gst.ElementFactory.make("alsasink", "sink") + self.alsa_sink.set_property("device", new_device) self.pipeline.add(self.alsa_sink) self.audio_resample.link(self.alsa_sink) diff --git a/lisp/plugins/gst_backend/elements/audio_dynamic.py b/lisp/plugins/gst_backend/elements/audio_dynamic.py index e1bb44e99..cac6a2ade 100644 --- a/lisp/plugins/gst_backend/elements/audio_dynamic.py +++ b/lisp/plugins/gst_backend/elements/audio_dynamic.py @@ -29,31 +29,30 @@ class AudioDynamic(GstMediaElement): ElementType = ElementType.Plugin MediaType = MediaType.Audio - Name = QT_TRANSLATE_NOOP('MediaElementName', 'Compressor/Expander') + Name = QT_TRANSLATE_NOOP("MediaElementName", "Compressor/Expander") class Mode(Enum): - Compressor = 'compressor' - Expander = 'expander' + Compressor = "compressor" + Expander = "expander" class Characteristics(Enum): - HardKnee = 'hard-knee' - SoftKnee = 'soft-knee' + HardKnee = "hard-knee" + SoftKnee = "soft-knee" - mode = GstProperty( - 'audio_dynamic', 'mode', default=Mode.Compressor.value) - ratio = GstProperty('audio_dynamic', 'ratio', default=1) - threshold = GstProperty('audio_dynamic', 'threshold', default=0) + mode = GstProperty("audio_dynamic", "mode", default=Mode.Compressor.value) + ratio = GstProperty("audio_dynamic", "ratio", default=1) + threshold = GstProperty("audio_dynamic", "threshold", default=0) characteristics = GstProperty( - 'audio_dynamic', - 'characteristics', - default=Characteristics.HardKnee.value + "audio_dynamic", + "characteristics", + default=Characteristics.HardKnee.value, ) def __init__(self, pipeline): super().__init__(pipeline) - self.audio_dynamic = Gst.ElementFactory.make('audiodynamic', None) - self.audio_converter = Gst.ElementFactory.make('audioconvert', None) + self.audio_dynamic = Gst.ElementFactory.make("audiodynamic", None) + self.audio_converter = Gst.ElementFactory.make("audioconvert", None) self.pipeline.add(self.audio_dynamic) self.pipeline.add(self.audio_converter) diff --git a/lisp/plugins/gst_backend/elements/audio_pan.py b/lisp/plugins/gst_backend/elements/audio_pan.py index e6dcd960e..0602eda7b 100644 --- a/lisp/plugins/gst_backend/elements/audio_pan.py +++ b/lisp/plugins/gst_backend/elements/audio_pan.py @@ -27,9 +27,9 @@ class AudioPan(GstMediaElement): ElementType = ElementType.Plugin MediaType = MediaType.Audio - Name = QT_TRANSLATE_NOOP('MediaElementName', 'Audio Pan') + Name = QT_TRANSLATE_NOOP("MediaElementName", "Audio Pan") - pan = GstProperty('panorama', 'panorama', default=.0) + pan = GstProperty("panorama", "panorama", default=0.0) def __init__(self, pipeline): super().__init__(pipeline) diff --git a/lisp/plugins/gst_backend/elements/auto_sink.py b/lisp/plugins/gst_backend/elements/auto_sink.py index e2a58b97c..81a455d54 100644 --- a/lisp/plugins/gst_backend/elements/auto_sink.py +++ b/lisp/plugins/gst_backend/elements/auto_sink.py @@ -27,12 +27,12 @@ class AutoSink(GstMediaElement): ElementType = ElementType.Output MediaType = MediaType.Audio - Name = QT_TRANSLATE_NOOP('MediaElementName', 'System Out') + Name = QT_TRANSLATE_NOOP("MediaElementName", "System Out") def __init__(self, pipeline): super().__init__(pipeline) - self.auto_sink = Gst.ElementFactory.make('autoaudiosink', 'sink') + self.auto_sink = Gst.ElementFactory.make("autoaudiosink", "sink") self.pipeline.add(self.auto_sink) def sink(self): diff --git a/lisp/plugins/gst_backend/elements/auto_src.py b/lisp/plugins/gst_backend/elements/auto_src.py index 8775c43bd..8b79f233b 100644 --- a/lisp/plugins/gst_backend/elements/auto_src.py +++ b/lisp/plugins/gst_backend/elements/auto_src.py @@ -26,7 +26,7 @@ class AutoSrc(GstSrcElement): MediaType = MediaType.Audio - Name = QT_TRANSLATE_NOOP('MediaElementName', 'System Input') + Name = QT_TRANSLATE_NOOP("MediaElementName", "System Input") def __init__(self, pipeline): super().__init__(pipeline) diff --git a/lisp/plugins/gst_backend/elements/db_meter.py b/lisp/plugins/gst_backend/elements/db_meter.py index 741c4b2b1..d02c2fff2 100644 --- a/lisp/plugins/gst_backend/elements/db_meter.py +++ b/lisp/plugins/gst_backend/elements/db_meter.py @@ -28,23 +28,23 @@ class DbMeter(GstMediaElement): ElementType = ElementType.Plugin MediaType = MediaType.Audio - Name = QT_TRANSLATE_NOOP('MediaElementName', 'dB Meter') + Name = QT_TRANSLATE_NOOP("MediaElementName", "dB Meter") - interval = GstProperty('level', 'interval', default=50 * Gst.MSECOND) - peak_ttl = GstProperty('level', 'peak-ttl', default=Gst.SECOND) - peak_falloff = GstProperty('level', 'peak-falloff', default=20) + interval = GstProperty("level", "interval", default=50 * Gst.MSECOND) + peak_ttl = GstProperty("level", "peak-ttl", default=Gst.SECOND) + peak_falloff = GstProperty("level", "peak-falloff", default=20) def __init__(self, pipeline): super().__init__(pipeline) self.level_ready = Signal() self.pipeline = pipeline - self.level = Gst.ElementFactory.make('level', None) - self.level.set_property('post-messages', True) - self.level.set_property('interval', 50 * Gst.MSECOND) - self.level.set_property('peak-ttl', Gst.SECOND) - self.level.set_property('peak-falloff', 20) - self.audio_convert = Gst.ElementFactory.make('audioconvert', None) + self.level = Gst.ElementFactory.make("level", None) + self.level.set_property("post-messages", True) + self.level.set_property("interval", 50 * Gst.MSECOND) + self.level.set_property("peak-ttl", Gst.SECOND) + self.level.set_property("peak-falloff", 20) + self.audio_convert = Gst.ElementFactory.make("audioconvert", None) self.pipeline.add(self.level) self.pipeline.add(self.audio_convert) @@ -53,7 +53,7 @@ def __init__(self, pipeline): bus = self.pipeline.get_bus() bus.add_signal_watch() - self._handler = bus.connect('message::element', self.__on_message) + self._handler = bus.connect("message::element", self.__on_message) def dispose(self): bus = self.pipeline.get_bus() @@ -69,9 +69,9 @@ def src(self): def __on_message(self, bus, message): if message.src == self.level: structure = message.get_structure() - if structure is not None and structure.has_name('level'): + if structure is not None and structure.has_name("level"): self.level_ready.emit( - structure.get_value('peak'), - structure.get_value('rms'), - structure.get_value('decay') + structure.get_value("peak"), + structure.get_value("rms"), + structure.get_value("decay"), ) diff --git a/lisp/plugins/gst_backend/elements/equalizer10.py b/lisp/plugins/gst_backend/elements/equalizer10.py index ab9fe20c5..4165bb5b3 100644 --- a/lisp/plugins/gst_backend/elements/equalizer10.py +++ b/lisp/plugins/gst_backend/elements/equalizer10.py @@ -27,18 +27,18 @@ class Equalizer10(GstMediaElement): ElementType = ElementType.Plugin MediaType = MediaType.Audio - Name = QT_TRANSLATE_NOOP('MediaElementName', '10 Bands Equalizer') - - band0 = GstProperty('equalizer', 'band0', default=0) - band1 = GstProperty('equalizer', 'band1', default=0) - band2 = GstProperty('equalizer', 'band2', default=0) - band3 = GstProperty('equalizer', 'band3', default=0) - band4 = GstProperty('equalizer', 'band4', default=0) - band5 = GstProperty('equalizer', 'band5', default=0) - band6 = GstProperty('equalizer', 'band6', default=0) - band7 = GstProperty('equalizer', 'band7', default=0) - band8 = GstProperty('equalizer', 'band8', default=0) - band9 = GstProperty('equalizer', 'band9', default=0) + Name = QT_TRANSLATE_NOOP("MediaElementName", "10 Bands Equalizer") + + band0 = GstProperty("equalizer", "band0", default=0) + band1 = GstProperty("equalizer", "band1", default=0) + band2 = GstProperty("equalizer", "band2", default=0) + band3 = GstProperty("equalizer", "band3", default=0) + band4 = GstProperty("equalizer", "band4", default=0) + band5 = GstProperty("equalizer", "band5", default=0) + band6 = GstProperty("equalizer", "band6", default=0) + band7 = GstProperty("equalizer", "band7", default=0) + band8 = GstProperty("equalizer", "band8", default=0) + band9 = GstProperty("equalizer", "band9", default=0) def __init__(self, pipeline): super().__init__(pipeline) diff --git a/lisp/plugins/gst_backend/elements/jack_sink.py b/lisp/plugins/gst_backend/elements/jack_sink.py index 3ff62dec8..4e80be9ea 100644 --- a/lisp/plugins/gst_backend/elements/jack_sink.py +++ b/lisp/plugins/gst_backend/elements/jack_sink.py @@ -34,10 +34,10 @@ class JackSink(GstMediaElement): ElementType = ElementType.Output MediaType = MediaType.Audio - Name = QT_TRANSLATE_NOOP('MediaElementName', 'JACK Out') + Name = QT_TRANSLATE_NOOP("MediaElementName", "JACK Out") - CLIENT_NAME = 'linux-show-player' - CONNECT_MODE = 'none' + CLIENT_NAME = "linux-show-player" + CONNECT_MODE = "none" _ControlClient = None _clients = [] @@ -49,16 +49,17 @@ def __init__(self, pipeline): if JackSink._ControlClient is None: JackSink._ControlClient = jack.Client( - 'LinuxShowPlayer_Control', no_start_server=True) + "LinuxShowPlayer_Control", no_start_server=True + ) self.pipeline = pipeline - self.audio_resample = Gst.ElementFactory.make('audioresample') - self.jack_sink = Gst.ElementFactory.make('jackaudiosink', 'sink') + self.audio_resample = Gst.ElementFactory.make("audioresample") + self.jack_sink = Gst.ElementFactory.make("jackaudiosink", "sink") self._client_id = JackSink.__register_client_id() - self._client_name = JackSink.CLIENT_NAME + '-' + str(self._client_id) - self.jack_sink.set_property('client-name', self._client_name) - self.jack_sink.set_property('connect', JackSink.CONNECT_MODE) + self._client_name = JackSink.CLIENT_NAME + "-" + str(self._client_id) + self.jack_sink.set_property("client-name", self._client_name) + self.jack_sink.set_property("connect", JackSink.CONNECT_MODE) self.pipeline.add(self.audio_resample) self.pipeline.add(self.jack_sink) @@ -66,11 +67,11 @@ def __init__(self, pipeline): self.audio_resample.link(self.jack_sink) self.connections = self.default_connections(JackSink._ControlClient) - self.changed('connections').connect(self.__prepare_connections) + self.changed("connections").connect(self.__prepare_connections) bus = self.pipeline.get_bus() bus.add_signal_watch() - self._handler = bus.connect('message', self.__on_message) + self._handler = bus.connect("message", self.__on_message) def sink(self): return self.audio_resample @@ -92,7 +93,8 @@ def default_connections(cls, client): if isinstance(client, jack.Client): # Search for default input ports input_ports = client.get_ports( - name_pattern='^system:', is_audio=True, is_input=True) + name_pattern="^system:", is_audio=True, is_input=True + ) for n, port in enumerate(input_ports): if n < len(connections): connections[n].append(port.name) @@ -102,8 +104,10 @@ def default_connections(cls, client): return connections def __prepare_connections(self, value): - if (self.pipeline.current_state == Gst.State.PLAYING or - self.pipeline.current_state == Gst.State.PAUSED): + if ( + self.pipeline.current_state == Gst.State.PLAYING + or self.pipeline.current_state == Gst.State.PAUSED + ): self.__jack_connect() @classmethod @@ -121,16 +125,19 @@ def __register_client_id(cls): def __jack_connect(self): out_ports = JackSink._ControlClient.get_ports( - name_pattern='^' + self._client_name + ':.+', is_audio=True) + name_pattern="^" + self._client_name + ":.+", is_audio=True + ) for port in out_ports: for conn_port in JackSink._ControlClient.get_all_connections(port): try: JackSink._ControlClient.disconnect(port, conn_port) except jack.JackError: - logger.exception(translate( - 'JackSinkError', - 'An error occurred while disconnection Jack ports') + logger.exception( + translate( + "JackSinkError", + "An error occurred while disconnection Jack ports", + ) ) for output, in_ports in enumerate(self.connections): @@ -138,10 +145,12 @@ def __jack_connect(self): if output < len(out_ports): try: JackSink._ControlClient.connect( - out_ports[output], input_name) + out_ports[output], input_name + ) except jack.JackError: logger.exception( - 'An error occurred while connecting Jack ports') + "An error occurred while connecting Jack ports" + ) else: break diff --git a/lisp/plugins/gst_backend/elements/pitch.py b/lisp/plugins/gst_backend/elements/pitch.py index bbbe465bc..a04796e05 100644 --- a/lisp/plugins/gst_backend/elements/pitch.py +++ b/lisp/plugins/gst_backend/elements/pitch.py @@ -27,15 +27,15 @@ class Pitch(GstMediaElement): ElementType = ElementType.Plugin MediaType = MediaType.Audio - Name = QT_TRANSLATE_NOOP('MediaElementName', 'Pitch') + Name = QT_TRANSLATE_NOOP("MediaElementName", "Pitch") - pitch = GstProperty('gst_pitch', 'pitch', default=1.0) + pitch = GstProperty("gst_pitch", "pitch", default=1.0) def __init__(self, pipeline): super().__init__(pipeline) - self.gst_pitch = Gst.ElementFactory.make('pitch', None) - self.audio_converter = Gst.ElementFactory.make('audioconvert', None) + self.gst_pitch = Gst.ElementFactory.make("pitch", None) + self.audio_converter = Gst.ElementFactory.make("audioconvert", None) self.pipeline.add(self.gst_pitch) self.pipeline.add(self.audio_converter) diff --git a/lisp/plugins/gst_backend/elements/preset_src.py b/lisp/plugins/gst_backend/elements/preset_src.py index ecd7e60e1..345c1a49c 100644 --- a/lisp/plugins/gst_backend/elements/preset_src.py +++ b/lisp/plugins/gst_backend/elements/preset_src.py @@ -29,38 +29,60 @@ class PresetSrc(GstSrcElement): MediaType = MediaType.Audio - Name = QT_TRANSLATE_NOOP('MediaElementName', 'Preset Input') + Name = QT_TRANSLATE_NOOP("MediaElementName", "Preset Input") FREQ = 8000 SILENCE = lambda t: 0 PRESETS = { - 'The 42 melody': lambda t: t * (42 & t >> 10), - 'Mission': lambda t: (~t >> 2) * ((127 & t * (7 & t >> 10)) < (245 & t * (2 + (5 & t >> 14)))), - "80's": lambda t: (t << 3) * [8 / 9, 1, 9 / 8, 6 / 5, 4 / 3, 3 / 2, 0][[0xd2d2c8, 0xce4088, 0xca32c8, 0x8e4009][t >> 14 & 3] >> (0x3dbe4688 >> (18 if (t >> 10 & 15) > 9 else t >> 10 & 15) * 3 & 7) * 3 & 7], - 'Game 1': lambda t: (t * (0xCA98 >> (t >> 9 & 14) & 15) | t >> 8), - 'Game 2': lambda t: t * 5 & (t >> 7) | t * 3 & (t * 4 >> 10) - int(math.cos(t >> 3)) * 10 | t >> 5 & int(math.sin(t >> 4)) | t >> 4, - 'Club': lambda t: (((t * (t ^ t % 255) | (t >> 4)) >> 1) if (t & 4096) else (t >> 3) | (t << 2 if (t & 8192) else t)) + t * (((t >> 9) ^ ((t >> 9) - 1) ^ 1) % 13), - 'Laser 1': lambda t: t * (t >> 5 | t >> 15) & 80 & t * 4 >> 9, - 'Laser 2': lambda t: (t * (t >> 5 | t >> 23) & 43 & t >> 9) ^ (t & t >> 20 | t >> 9), - 'Generator': lambda t: (t * (t >> 22 | t >> 3) & 43 & t >> 8) ^ (t & t >> 12 | t >> 4) + "The 42 melody": lambda t: t * (42 & t >> 10), + "Mission": lambda t: (~t >> 2) + * ((127 & t * (7 & t >> 10)) < (245 & t * (2 + (5 & t >> 14)))), + "80's": lambda t: (t << 3) + * [8 / 9, 1, 9 / 8, 6 / 5, 4 / 3, 3 / 2, 0][ + [0xD2D2C8, 0xCE4088, 0xCA32C8, 0x8E4009][t >> 14 & 3] + >> ( + 0x3DBE4688 >> (18 if (t >> 10 & 15) > 9 else t >> 10 & 15) * 3 + & 7 + ) + * 3 + & 7 + ], + "Game 1": lambda t: (t * (0xCA98 >> (t >> 9 & 14) & 15) | t >> 8), + "Game 2": lambda t: t * 5 & (t >> 7) + | t * 3 & (t * 4 >> 10) - int(math.cos(t >> 3)) * 10 + | t >> 5 & int(math.sin(t >> 4)) + | t >> 4, + "Club": lambda t: ( + ((t * (t ^ t % 255) | (t >> 4)) >> 1) + if (t & 4096) + else (t >> 3) | (t << 2 if (t & 8192) else t) + ) + + t * (((t >> 9) ^ ((t >> 9) - 1) ^ 1) % 13), + "Laser 1": lambda t: t * (t >> 5 | t >> 15) & 80 & t * 4 >> 9, + "Laser 2": lambda t: (t * (t >> 5 | t >> 23) & 43 & t >> 9) + ^ (t & t >> 20 | t >> 9), + "Generator": lambda t: (t * (t >> 22 | t >> 3) & 43 & t >> 8) + ^ (t & t >> 12 | t >> 4), } - preset = Property(default='The 42 melody') + preset = Property(default="The 42 melody") def __init__(self, pipeline): super().__init__(pipeline) self.n_sample = 0 - self.caps = 'audio/x-raw,format=U8,channels=1,layout=interleaved,' \ - 'rate=' + str(PresetSrc.FREQ) + self.caps = ( + "audio/x-raw,format=U8,channels=1,layout=interleaved," + "rate=" + str(PresetSrc.FREQ) + ) - self.app_src = Gst.ElementFactory.make('appsrc', 'appsrc') - self.app_src.set_property('stream-type', GstApp.AppStreamType.SEEKABLE) - self.app_src.set_property('format', Gst.Format.TIME) - self.app_src.set_property('caps', Gst.Caps.from_string(self.caps)) - self.app_src.connect('need-data', self.generate_samples) - self.app_src.connect('seek-data', self.seek) + self.app_src = Gst.ElementFactory.make("appsrc", "appsrc") + self.app_src.set_property("stream-type", GstApp.AppStreamType.SEEKABLE) + self.app_src.set_property("format", Gst.Format.TIME) + self.app_src.set_property("caps", Gst.Caps.from_string(self.caps)) + self.app_src.connect("need-data", self.generate_samples) + self.app_src.connect("seek-data", self.seek) - self.audio_converter = Gst.ElementFactory.make('audioconvert', None) + self.audio_converter = Gst.ElementFactory.make("audioconvert", None) self.pipeline.add(self.app_src) self.pipeline.add(self.audio_converter) @@ -77,7 +99,7 @@ def generate_samples(self, src, need_bytes): remaining = int(self.duration / 1000 * PresetSrc.FREQ - self.n_sample) if remaining <= 0: self.n_sample = 0 - src.emit('end-of-stream') + src.emit("end-of-stream") else: if need_bytes > remaining: need_bytes = remaining @@ -90,7 +112,7 @@ def generate_samples(self, src, need_bytes): self.n_sample += 1 buffer = Gst.Buffer.new_wrapped(bytes(sample)) - src.emit('push-buffer', buffer) + src.emit("push-buffer", buffer) def seek(self, src, time): self.n_sample = int(abs(time / Gst.SECOND) * PresetSrc.FREQ) diff --git a/lisp/plugins/gst_backend/elements/pulse_sink.py b/lisp/plugins/gst_backend/elements/pulse_sink.py index be0c32626..519a3645e 100644 --- a/lisp/plugins/gst_backend/elements/pulse_sink.py +++ b/lisp/plugins/gst_backend/elements/pulse_sink.py @@ -27,13 +27,13 @@ class PulseSink(GstMediaElement): ElementType = ElementType.Output MediaType = MediaType.Audio - Name = QT_TRANSLATE_NOOP('MediaElementName', 'PulseAudio Out') + Name = QT_TRANSLATE_NOOP("MediaElementName", "PulseAudio Out") def __init__(self, pipeline): super().__init__(pipeline) - self.pulse_sink = Gst.ElementFactory.make('pulsesink', 'sink') - self.pulse_sink.set_property('client-name', 'Linux Show Player') + self.pulse_sink = Gst.ElementFactory.make("pulsesink", "sink") + self.pulse_sink.set_property("client-name", "Linux Show Player") self.pipeline.add(self.pulse_sink) diff --git a/lisp/plugins/gst_backend/elements/speed.py b/lisp/plugins/gst_backend/elements/speed.py index 473189c3e..8931ac927 100644 --- a/lisp/plugins/gst_backend/elements/speed.py +++ b/lisp/plugins/gst_backend/elements/speed.py @@ -28,7 +28,7 @@ class Speed(GstMediaElement): ElementType = ElementType.Plugin MediaType = MediaType.Audio - Name = QT_TRANSLATE_NOOP('MediaElementName', 'Speed') + Name = QT_TRANSLATE_NOOP("MediaElementName", "Speed") speed = Property(default=1.0) @@ -49,7 +49,7 @@ def __init__(self, pipeline): self._handler = bus.connect("message", self.__on_message) self._old_speed = self.speed - self.changed('speed').connect(self.__prepare_speed) + self.changed("speed").connect(self.__prepare_speed) def __prepare_speed(self, value): if self._old_speed != value: @@ -70,9 +70,11 @@ def dispose(self): bus.disconnect(self._handler) def __on_message(self, bus, message): - if (message.type == Gst.MessageType.STATE_CHANGED and - message.src == self.scale_tempo and - message.parse_state_changed()[1] == Gst.State.PLAYING): + if ( + message.type == Gst.MessageType.STATE_CHANGED + and message.src == self.scale_tempo + and message.parse_state_changed()[1] == Gst.State.PLAYING + ): self.__change_speed() def __change_speed(self): @@ -83,5 +85,5 @@ def __change_speed(self): Gst.SeekType.SET, self.scale_tempo.query_position(Gst.Format.TIME)[1], Gst.SeekType.NONE, - 0 + 0, ) diff --git a/lisp/plugins/gst_backend/elements/uri_input.py b/lisp/plugins/gst_backend/elements/uri_input.py index 606920a7c..7525671e9 100644 --- a/lisp/plugins/gst_backend/elements/uri_input.py +++ b/lisp/plugins/gst_backend/elements/uri_input.py @@ -38,27 +38,27 @@ def abs_path(path_): def uri_split(uri): try: - scheme, path = uri.split('://') + scheme, path = uri.split("://") except ValueError: - scheme = path = '' + scheme = path = "" return scheme, path def uri_adapter(uri): scheme, path = uri_split(uri) - return scheme + '://' + urlquote(abs_path(path)) + return scheme + "://" + urlquote(abs_path(path)) class UriInput(GstSrcElement): MediaType = MediaType.Audio - Name = QT_TRANSLATE_NOOP('MediaElementName', 'URI Input') + Name = QT_TRANSLATE_NOOP("MediaElementName", "URI Input") _mtime = Property(default=-1) - uri = GstProperty('decoder', 'uri', default='', adapter=uri_adapter) - download = GstProperty('decoder', 'download', default=False) - buffer_size = GstProperty('decoder', 'buffer-size', default=-1) - use_buffering = GstProperty('decoder', 'use-buffering', default=False) + uri = GstProperty("decoder", "uri", default="", adapter=uri_adapter) + download = GstProperty("decoder", "download", default=False) + buffer_size = GstProperty("decoder", "buffer-size", default=-1) + use_buffering = GstProperty("decoder", "use-buffering", default=False) def __init__(self, pipeline): super().__init__(pipeline) @@ -70,9 +70,10 @@ def __init__(self, pipeline): self.pipeline.add(self.decoder) self.pipeline.add(self.audio_convert) - self.changed('uri').connect(self.__uri_changed) - Application().session.changed('session_file').connect( - self.__session_moved) + self.changed("uri").connect(self.__uri_changed) + Application().session.changed("session_file").connect( + self.__session_moved + ) def input_uri(self): return self.uri @@ -92,7 +93,7 @@ def __uri_changed(self, uri): # If the uri is a file update the current mtime scheme, path_ = uri_split(uri) - if scheme == 'file': + if scheme == "file": path_ = abs_path(path_) if path.exists(path_): self._mtime = path.getmtime(path_) @@ -106,10 +107,10 @@ def __uri_changed(self, uri): @async_in_pool(pool=ThreadPoolExecutor(1)) def __duration(self): scheme, path = uri_split(self.uri) - self.duration = gst_uri_duration(scheme + '://' + abs_path(path)) + self.duration = gst_uri_duration(scheme + "://" + abs_path(path)) def __session_moved(self, _): - scheme, path_ = uri_split(self.decoder.get_property('uri')) - if scheme == 'file': + scheme, path_ = uri_split(self.decoder.get_property("uri")) + if scheme == "file": path_ = urlunquote(path_) - self.uri = 'file://' + Application().session.rel_path(path_) + self.uri = "file://" + Application().session.rel_path(path_) diff --git a/lisp/plugins/gst_backend/elements/user_element.py b/lisp/plugins/gst_backend/elements/user_element.py index 0ed4d72b9..bf96c08f7 100644 --- a/lisp/plugins/gst_backend/elements/user_element.py +++ b/lisp/plugins/gst_backend/elements/user_element.py @@ -28,9 +28,9 @@ class UserElement(GstMediaElement): ElementType = ElementType.Plugin MediaType = MediaType.Audio - Name = QT_TRANSLATE_NOOP('MediaElementName', 'Custom Element') + Name = QT_TRANSLATE_NOOP("MediaElementName", "Custom Element") - bin = Property(default='') + bin = Property(default="") def __init__(self, pipeline): super().__init__(pipeline) @@ -50,7 +50,7 @@ def __init__(self, pipeline): self.gst_bin.link(self.audio_convert_src) self._old_bin = self.gst_bin - self.changed('bin').connect(self.__prepare_bin) + self.changed("bin").connect(self.__prepare_bin) def sink(self): return self.audio_convert_sink @@ -59,7 +59,7 @@ def src(self): return self.audio_convert_src def __prepare_bin(self, value): - if value != '' and value != self._old_bin: + if value != "" and value != self._old_bin: self._old_bin = value # If in playing we need to restart the pipeline after unblocking diff --git a/lisp/plugins/gst_backend/elements/volume.py b/lisp/plugins/gst_backend/elements/volume.py index 92bb627eb..48bdf0335 100644 --- a/lisp/plugins/gst_backend/elements/volume.py +++ b/lisp/plugins/gst_backend/elements/volume.py @@ -21,21 +21,25 @@ from lisp.backend.media_element import ElementType, MediaType from lisp.plugins.gst_backend.gi_repository import Gst -from lisp.plugins.gst_backend.gst_element import GstMediaElement, GstProperty, \ - GstLiveProperty +from lisp.plugins.gst_backend.gst_element import ( + GstMediaElement, + GstProperty, + GstLiveProperty, +) class Volume(GstMediaElement): ElementType = ElementType.Plugin MediaType = MediaType.Audio - Name = QT_TRANSLATE_NOOP('MediaElementName', 'Volume') + Name = QT_TRANSLATE_NOOP("MediaElementName", "Volume") - mute = GstProperty('gst_volume', 'mute', default=False) - volume = GstProperty('gst_volume', 'volume', default=1.0) - normal_volume = GstProperty('gst_normal_volume', 'volume', default=1.0) + mute = GstProperty("gst_volume", "mute", default=False) + volume = GstProperty("gst_volume", "volume", default=1.0) + normal_volume = GstProperty("gst_normal_volume", "volume", default=1.0) live_volume = GstLiveProperty( - 'gst_volume', 'volume', type=float, range=(0, 10)) + "gst_volume", "volume", type=float, range=(0, 10) + ) def __init__(self, pipeline): super().__init__(pipeline) diff --git a/lisp/plugins/gst_backend/gi_repository.py b/lisp/plugins/gst_backend/gi_repository.py index f293f0899..12844c979 100644 --- a/lisp/plugins/gst_backend/gi_repository.py +++ b/lisp/plugins/gst_backend/gi_repository.py @@ -3,13 +3,15 @@ # "Solution" for https://bugzilla.gnome.org/show_bug.cgi?id=736260 import sys + sys.modules["gi.overrides.Gst"] = None sys.modules["gi.overrides.GstPbutils"] = None import gi -gi.require_version('Gst', '1.0') -gi.require_version('GstPbutils', '1.0') -gi.require_version('GstApp', '1.0') + +gi.require_version("Gst", "1.0") +gi.require_version("GstPbutils", "1.0") +gi.require_version("GstApp", "1.0") # noinspection PyUnresolvedReferences from gi.repository import Gst, GstPbutils, GObject, GstApp diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index 3ba7e0316..c9876e8ed 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -30,12 +30,18 @@ from lisp.cues.media_cue import MediaCue from lisp.plugins.gst_backend import elements, settings from lisp.plugins.gst_backend.gi_repository import Gst -from lisp.plugins.gst_backend.gst_media_cue import GstCueFactory, \ - UriAudioCueFactory +from lisp.plugins.gst_backend.gst_media_cue import ( + GstCueFactory, + UriAudioCueFactory, +) from lisp.plugins.gst_backend.gst_media_settings import GstMediaSettings from lisp.plugins.gst_backend.gst_settings import GstSettings -from lisp.plugins.gst_backend.gst_utils import gst_parse_tags_list, \ - gst_uri_metadata, gst_mime_types, gst_uri_duration +from lisp.plugins.gst_backend.gst_utils import ( + gst_parse_tags_list, + gst_uri_metadata, + gst_mime_types, + gst_uri_duration, +) from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.ui_utils import translate, qfile_filters @@ -43,10 +49,11 @@ class GstBackend(Plugin, BaseBackend): - Name = 'GStreamer Backend' - Authors = ('Francesco Ceruti', ) - Description = 'Provide audio playback capabilities via the GStreamer ' \ - 'framework' + Name = "GStreamer Backend" + Authors = ("Francesco Ceruti",) + Description = ( + "Provide audio playback capabilities via the GStreamer " "framework" + ) def __init__(self, app): super().__init__(app) @@ -56,19 +63,20 @@ def __init__(self, app): # Register GStreamer settings widgets AppConfigurationDialog.registerSettingsPage( - 'plugins.gst', GstSettings, GstBackend.Config) + "plugins.gst", GstSettings, GstBackend.Config + ) # Add MediaCue settings widget to the CueLayout CueSettingsRegistry().add(GstMediaSettings, MediaCue) # Register GstMediaCue factory - CueFactory.register_factory('GstMediaCue', GstCueFactory(tuple())) + CueFactory.register_factory("GstMediaCue", GstCueFactory(tuple())) # Add Menu entry self.app.window.register_cue_menu_action( - translate('GstBackend', 'Audio cue (from file)'), + translate("GstBackend", "Audio cue (from file)"), self._add_uri_audio_cue, - category='Media cues', - shortcut='CTRL+M' + category="Media cues", + shortcut="CTRL+M", ) # Load elements and their settings-widgets @@ -89,10 +97,10 @@ def uri_tags(self, uri): @memoize def supported_extensions(self): - extensions = {'audio': [], 'video': []} + extensions = {"audio": [], "video": []} for gst_mime, gst_extensions in gst_mime_types(): - for mime in ['audio', 'video']: + for mime in ["audio", "video"]: if gst_mime.startswith(mime): extensions[mime].extend(gst_extensions) @@ -100,28 +108,28 @@ def supported_extensions(self): def _add_uri_audio_cue(self): """Add audio MediaCue(s) form user-selected files""" - dir = GstBackend.Config.get('mediaLookupDir', '') + dir = GstBackend.Config.get("mediaLookupDir", "") if not os.path.exists(dir): dir = self.app.session.path() files, _ = QFileDialog.getOpenFileNames( self.app.window, - translate('GstBackend', 'Select media files'), + translate("GstBackend", "Select media files"), dir, - qfile_filters(self.supported_extensions(), anyfile=True) + qfile_filters(self.supported_extensions(), anyfile=True), ) if files: - GstBackend.Config['mediaLookupDir'] = os.path.dirname(files[0]) + GstBackend.Config["mediaLookupDir"] = os.path.dirname(files[0]) GstBackend.Config.write() QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) # Create media cues, and add them to the Application cue_model - factory = UriAudioCueFactory(GstBackend.Config['pipeline']) + factory = UriAudioCueFactory(GstBackend.Config["pipeline"]) for file in files: file = self.app.session.rel_path(file) - cue = factory(uri='file://' + file) + cue = factory(uri="file://" + file) # Use the filename without extension as cue name cue.name = os.path.splitext(os.path.basename(file))[0] self.app.cue_model.add(cue) diff --git a/lisp/plugins/gst_backend/gst_element.py b/lisp/plugins/gst_backend/gst_element.py index 1ff0bad09..2bba68280 100644 --- a/lisp/plugins/gst_backend/gst_element.py +++ b/lisp/plugins/gst_backend/gst_element.py @@ -24,9 +24,9 @@ class GstProperty(Property): - - def __init__(self, element_name, property_name, default=None, adapter=None, - **meta): + def __init__( + self, element_name, property_name, default=None, adapter=None, **meta + ): super().__init__(default=default, **meta) self.element_name = element_name self.property_name = property_name @@ -44,7 +44,6 @@ def __set__(self, instance, value): class GstLiveProperty(Property): - def __init__(self, element_name, property_name, adapter=None, **meta): super().__init__(**meta) self.element_name = element_name @@ -122,7 +121,6 @@ def input_uri(self): class GstMediaElements(HasInstanceProperties): - def __init__(self): super().__init__() self.elements = [] diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index bb1869b04..7102a3aaa 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -16,6 +16,7 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +from time import perf_counter import logging import weakref @@ -35,7 +36,7 @@ Gst.State.NULL: MediaState.Null, Gst.State.READY: MediaState.Ready, Gst.State.PAUSED: MediaState.Paused, - Gst.State.PLAYING: MediaState.Playing + Gst.State.PLAYING: MediaState.Playing, } @@ -54,6 +55,7 @@ def media_finalizer(pipeline, message_handler, media_elements): class GstError(Exception): """Used to wrap GStreamer debug messages for the logging system.""" + pass @@ -72,8 +74,8 @@ def __init__(self): self.__loop = 0 # current number of loops left to do self.__current_pipe = None # A copy of the pipe property - self.changed('loop').connect(self.__on_loops_changed) - self.changed('pipe').connect(self.__on_pipe_changed) + self.changed("loop").connect(self.__on_loops_changed) + self.changed("pipe").connect(self.__on_pipe_changed) @Media.state.getter def state(self): @@ -81,7 +83,8 @@ def state(self): return MediaState.Null return GST_TO_MEDIA_STATE.get( - self.__pipeline.get_state(Gst.MSECOND)[1], MediaState.Null) + self.__pipeline.get_state(Gst.MSECOND)[1], MediaState.Null + ) def current_time(self): if self.__pipeline is not None: @@ -100,12 +103,16 @@ def play(self): for element in self.elements: element.play() + if self.state != MediaState.Paused: + self.__pipeline.set_state(Gst.State.PAUSED) + self.__pipeline.get_state(Gst.SECOND) + self.__seek(self.start_time) + else: + self.__seek(self.current_time()) + self.__pipeline.set_state(Gst.State.PLAYING) self.__pipeline.get_state(Gst.SECOND) - if self.start_time > 0 or self.stop_time > 0: - self.seek(self.start_time) - self.played.emit(self) def pause(self): @@ -117,7 +124,9 @@ def pause(self): self.__pipeline.set_state(Gst.State.PAUSED) self.__pipeline.get_state(Gst.SECOND) - # FIXME: the pipeline is not flushed + + # Flush the pipeline + self.__seek(self.current_time()) self.paused.emit(self) @@ -149,7 +158,7 @@ def input_uri(self): def update_properties(self, properties): # In order to update the other properties we need the pipeline first - pipe = properties.pop('pipe', ()) + pipe = properties.pop("pipe", ()) if pipe: self.pipe = pipe @@ -179,11 +188,12 @@ def __seek(self, position): result = self.__pipeline.seek( rate if rate > 0 else 1, Gst.Format.TIME, - Gst.SeekFlags.FLUSH, + Gst.SeekFlags.FLUSH | Gst.SeekFlags.SKIP, Gst.SeekType.SET, position * Gst.MSECOND, stop_type, - self.stop_time * Gst.MSECOND) + self.stop_time * Gst.MSECOND, + ) return result @@ -213,7 +223,8 @@ def __init_pipeline(self): bus.add_signal_watch() # Use a weakref or GStreamer will hold a reference of the callback handler = bus.connect( - 'message', weak_call_proxy(weakref.WeakMethod(self.__on_message))) + "message", weak_call_proxy(weakref.WeakMethod(self.__on_message)) + ) # Create all the new elements all_elements = gst_elements.all_elements() @@ -223,28 +234,29 @@ def __init_pipeline(self): except KeyError: logger.warning( translate( - 'GstMediaWarning', 'Invalid pipeline element: "{}"' + "GstMediaWarning", 'Invalid pipeline element: "{}"' ).format(element) ) except Exception: logger.warning( translate( - 'GstMediaError', 'Cannot create pipeline element: "{}"' + "GstMediaError", 'Cannot create pipeline element: "{}"' ).format(element), - exc_info=True + exc_info=True, ) # Reload the elements properties self.elements.update_properties(elements_properties) # The source element should provide the duration - self.elements[0].changed('duration').connect(self.__duration_changed) + self.elements[0].changed("duration").connect(self.__duration_changed) self.duration = self.elements[0].duration # Create a new finalizer object to free the pipeline when the media # is dereferenced self.__finalizer = weakref.finalize( - self, media_finalizer, self.__pipeline, handler, self.elements) + self, media_finalizer, self.__pipeline, handler, self.elements + ) # Set the pipeline to READY self.__pipeline.set_state(Gst.State.READY) @@ -256,8 +268,8 @@ def __on_message(self, bus, message): if message.src == self.__pipeline: if message.type == Gst.MessageType.EOS: if self.__loop != 0: - # If we still have loops to do then seek to begin - # FIXME: this is not seamless + # If we still have loops to do then seek to start + # FIXME: this is not 100% seamless self.__loop -= 1 self.seek(self.start_time) else: @@ -273,7 +285,8 @@ def __on_message(self, bus, message): if message.type == Gst.MessageType.ERROR: error, debug = message.parse_error() logger.error( - 'GStreamer: {}'.format(error.message), exc_info=GstError(debug)) + "GStreamer: {}".format(error.message), exc_info=GstError(debug) + ) # Set the pipeline to NULL self.__pipeline.set_state(Gst.State.NULL) diff --git a/lisp/plugins/gst_backend/gst_media_cue.py b/lisp/plugins/gst_backend/gst_media_cue.py index 324286c58..e49874616 100644 --- a/lisp/plugins/gst_backend/gst_media_cue.py +++ b/lisp/plugins/gst_backend/gst_media_cue.py @@ -33,10 +33,9 @@ def __init__(self, media, id=None, pipeline=None): class GstCueFactory: - def __init__(self, base_pipeline): self.base_pipeline = base_pipeline - self.input = '' + self.input = "" def __call__(self, id=None): return GstMediaCue(GstMedia(), id=id, pipeline=self.pipeline()) @@ -47,10 +46,9 @@ def pipeline(self): class UriAudioCueFactory(GstCueFactory): - def __init__(self, base_pipeline): super().__init__(base_pipeline) - self.input = 'UriInput' + self.input = "UriInput" def __call__(self, id=None, uri=None): cue = super().__call__(id=id) @@ -67,4 +65,4 @@ def __call__(self, id=None, uri=None): class CaptureAudioCueFactory(GstCueFactory): def __init__(self, base_pipeline): super().__init__(base_pipeline) - self.input = 'AutoSrc' + self.input = "AutoSrc" diff --git a/lisp/plugins/gst_backend/gst_media_settings.py b/lisp/plugins/gst_backend/gst_media_settings.py index 919f55ed1..c0e025cea 100644 --- a/lisp/plugins/gst_backend/gst_media_settings.py +++ b/lisp/plugins/gst_backend/gst_media_settings.py @@ -20,8 +20,13 @@ from copy import deepcopy from PyQt5.QtCore import QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGridLayout, QListWidget, QPushButton, \ - QListWidgetItem, QSizePolicy +from PyQt5.QtWidgets import ( + QGridLayout, + QListWidget, + QPushButton, + QListWidgetItem, + QSizePolicy, +) from lisp.plugins.gst_backend.gst_pipe_edit import GstPipeEditDialog from lisp.plugins.gst_backend.settings import pages_by_element @@ -30,7 +35,7 @@ class GstMediaSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Media Settings') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Media Settings") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -45,7 +50,8 @@ def __init__(self, **kwargs): self.layout().addWidget(self.listWidget, 0, 0) self.pipeButton = QPushButton( - translate('GstMediaSettings', 'Change Pipeline'), self) + translate("GstMediaSettings", "Change Pipeline"), self + ) self.layout().addWidget(self.pipeButton, 1, 0) self.layout().setColumnStretch(0, 2) @@ -55,43 +61,45 @@ def __init__(self, **kwargs): self.pipeButton.clicked.connect(self.__edit_pipe) def loadSettings(self, settings): - settings = settings.get('media', {}) + settings = settings.get("media", {}) # Create a local copy of the configuration self._settings = deepcopy(settings) # Create the widgets pages = pages_by_element() - for element in settings.get('pipe', ()): + for element in settings.get("pipe", ()): page = pages.get(element) if page is not None and issubclass(page, SettingsPage): page = page(parent=self) page.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) page.loadSettings( - settings.get('elements', {}).get( - element, page.ELEMENT.class_defaults())) + settings.get("elements", {}).get( + element, page.ELEMENT.class_defaults() + ) + ) page.setVisible(False) self._pages.append(page) - item = QListWidgetItem(translate('MediaElementName', page.Name)) + item = QListWidgetItem(translate("MediaElementName", page.Name)) self.listWidget.addItem(item) self.listWidget.setCurrentRow(0) def getSettings(self): - settings = {'elements': {}} + settings = {"elements": {}} for page in self._pages: page_settings = page.getSettings() if page_settings: - settings['elements'][page.ELEMENT.__name__] = page_settings + settings["elements"][page.ELEMENT.__name__] = page_settings # The pipeline is returned only if check is disabled if not self._check: - settings['pipe'] = self._settings['pipe'] + settings["pipe"] = self._settings["pipe"] - return {'media': settings} + return {"media": settings} def enableCheck(self, enabled): self._check = enabled @@ -112,10 +120,10 @@ def __change_page(self, current, previous): def __edit_pipe(self): # Backup the settings - self._settings = self.getSettings()['media'] + self._settings = self.getSettings()["media"] # Show the dialog - dialog = GstPipeEditDialog(self._settings.get('pipe', ()), parent=self) + dialog = GstPipeEditDialog(self._settings.get("pipe", ()), parent=self) if dialog.exec_() == dialog.Accepted: # Reset the view @@ -127,7 +135,7 @@ def __edit_pipe(self): self._pages.clear() # Reload with the new pipeline - self._settings['pipe'] = dialog.get_pipe() + self._settings["pipe"] = dialog.get_pipe() - self.loadSettings({'media': self._settings}) + self.loadSettings({"media": self._settings}) self.enableCheck(self._check) diff --git a/lisp/plugins/gst_backend/gst_pipe_edit.py b/lisp/plugins/gst_backend/gst_pipe_edit.py index bf8890f66..bb581962e 100644 --- a/lisp/plugins/gst_backend/gst_pipe_edit.py +++ b/lisp/plugins/gst_backend/gst_pipe_edit.py @@ -18,9 +18,18 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QDialog, QGridLayout, QComboBox, QListWidget, \ - QAbstractItemView, QVBoxLayout, QPushButton, QDialogButtonBox, QWidget, \ - QListWidgetItem +from PyQt5.QtWidgets import ( + QDialog, + QGridLayout, + QComboBox, + QListWidget, + QAbstractItemView, + QVBoxLayout, + QPushButton, + QDialogButtonBox, + QWidget, + QListWidgetItem, +) from lisp.plugins.gst_backend import elements from lisp.ui.icons import IconTheme @@ -61,13 +70,13 @@ def __init__(self, pipe, app_mode=False, **kwargs): self.layout().setAlignment(self.buttonsLayout, Qt.AlignHCenter) self.addButton = QPushButton(self) - self.addButton.setIcon(IconTheme.get('go-previous')) + self.addButton.setIcon(IconTheme.get("go-previous")) self.addButton.clicked.connect(self.__add_plugin) self.buttonsLayout.addWidget(self.addButton) self.buttonsLayout.setAlignment(self.addButton, Qt.AlignHCenter) self.delButton = QPushButton(self) - self.delButton.setIcon(IconTheme.get('go-next')) + self.delButton.setIcon(IconTheme.get("go-next")) self.delButton.clicked.connect(self.__remove_plugin) self.buttonsLayout.addWidget(self.delButton) self.buttonsLayout.setAlignment(self.delButton, Qt.AlignHCenter) @@ -79,10 +88,12 @@ def set_pipe(self, pipe): if pipe: if not self._app_mode: self.inputBox.setCurrentText( - translate('MediaElementName', elements.input_name(pipe[0]))) + translate("MediaElementName", elements.input_name(pipe[0])) + ) self.outputBox.setCurrentText( - translate('MediaElementName', elements.output_name(pipe[-1]))) + translate("MediaElementName", elements.output_name(pipe[-1])) + ) self.__init_current_plugins(pipe) self.__init_available_plugins(pipe) @@ -101,7 +112,7 @@ def __init_inputs(self): else: inputs_by_name = {} for key, input in elements.inputs().items(): - inputs_by_name[translate('MediaElementName', input.Name)] = key + inputs_by_name[translate("MediaElementName", input.Name)] = key for name in sorted(inputs_by_name): self.inputBox.addItem(name, inputs_by_name[name]) @@ -111,7 +122,7 @@ def __init_inputs(self): def __init_outputs(self): outputs_by_name = {} for key, output in elements.outputs().items(): - outputs_by_name[translate('MediaElementName', output.Name)] = key + outputs_by_name[translate("MediaElementName", output.Name)] = key for name in sorted(outputs_by_name): self.outputBox.addItem(name, outputs_by_name[name]) @@ -126,7 +137,8 @@ def __init_current_plugins(self, pipe): start = 0 if self._app_mode else 1 for plugin in pipe[start:-1]: item = QListWidgetItem( - translate('MediaElementName', elements.plugin_name(plugin))) + translate("MediaElementName", elements.plugin_name(plugin)) + ) item.setData(Qt.UserRole, plugin) self.currentList.addItem(item) @@ -136,7 +148,8 @@ def __init_available_plugins(self, pipe): for plugin in elements.plugins(): if plugin not in pipe: item = QListWidgetItem( - translate('MediaElementName', elements.plugin_name(plugin))) + translate("MediaElementName", elements.plugin_name(plugin)) + ) item.setData(Qt.UserRole, plugin) self.availableList.addItem(item) @@ -152,7 +165,7 @@ def __remove_plugin(self): class GstPipeEditDialog(QDialog): def __init__(self, pipe, app_mode=False, **kwargs): super().__init__(**kwargs) - self.setWindowTitle(translate('GstPipelineEdit', 'Edit Pipeline')) + self.setWindowTitle(translate("GstPipelineEdit", "Edit Pipeline")) self.setWindowModality(Qt.ApplicationModal) self.setMaximumSize(500, 400) self.setMinimumSize(500, 400) @@ -165,8 +178,9 @@ def __init__(self, pipe, app_mode=False, **kwargs): # Confirm/Cancel buttons self.dialogButtons = QDialogButtonBox(self) - self.dialogButtons.setStandardButtons(QDialogButtonBox.Cancel | - QDialogButtonBox.Ok) + self.dialogButtons.setStandardButtons( + QDialogButtonBox.Cancel | QDialogButtonBox.Ok + ) self.layout().addWidget(self.dialogButtons) self.dialogButtons.accepted.connect(self.accept) diff --git a/lisp/plugins/gst_backend/gst_settings.py b/lisp/plugins/gst_backend/gst_settings.py index 98ddfbbae..22b1616b3 100644 --- a/lisp/plugins/gst_backend/gst_settings.py +++ b/lisp/plugins/gst_backend/gst_settings.py @@ -26,7 +26,7 @@ class GstSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'GStreamer') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "GStreamer") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -37,17 +37,16 @@ def __init__(self, **kwargs): self.pipeGroup.setLayout(QVBoxLayout()) self.layout().addWidget(self.pipeGroup) - self.pipeEdit = GstPipeEdit('', app_mode=True) + self.pipeEdit = GstPipeEdit("", app_mode=True) self.pipeGroup.layout().addWidget(self.pipeEdit) self.retranslateUi() def retranslateUi(self): - self.pipeGroup.setTitle(translate('GstSettings', 'Pipeline')) + self.pipeGroup.setTitle(translate("GstSettings", "Pipeline")) def loadSettings(self, settings): - self.pipeEdit.set_pipe(settings['pipeline']) + self.pipeEdit.set_pipe(settings["pipeline"]) def getSettings(self): - return {'pipeline': list(self.pipeEdit.get_pipe())} - + return {"pipeline": list(self.pipeEdit.get_pipe())} diff --git a/lisp/plugins/gst_backend/settings/__init__.py b/lisp/plugins/gst_backend/settings/__init__.py index a70562a76..ccd9e91e6 100644 --- a/lisp/plugins/gst_backend/settings/__init__.py +++ b/lisp/plugins/gst_backend/settings/__init__.py @@ -26,9 +26,9 @@ def load(): - for _, page in load_classes(__package__, - dirname(__file__), - suf=('Settings', )): + for _, page in load_classes( + __package__, dirname(__file__), suf=("Settings",) + ): __PAGES.add(page) diff --git a/lisp/plugins/gst_backend/settings/alsa_sink.py b/lisp/plugins/gst_backend/settings/alsa_sink.py index 8a28d3dd5..83321b1ab 100644 --- a/lisp/plugins/gst_backend/settings/alsa_sink.py +++ b/lisp/plugins/gst_backend/settings/alsa_sink.py @@ -19,8 +19,13 @@ from PyQt5 import QtCore from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QComboBox, QLabel, \ - QVBoxLayout +from PyQt5.QtWidgets import ( + QGroupBox, + QHBoxLayout, + QComboBox, + QLabel, + QVBoxLayout, +) from lisp.plugins.gst_backend.elements.alsa_sink import AlsaSink from lisp.ui.settings.pages import SettingsPage @@ -37,24 +42,28 @@ def __init__(self, **kwargs): self.layout().setAlignment(Qt.AlignTop) self.devices = self._discover_pcm_devices() - self.devices['default'] = 'default' + self.devices["default"] = "default" self.deviceGroup = QGroupBox(self) - self.deviceGroup.setTitle(translate('AlsaSinkSettings', 'ALSA device')) + self.deviceGroup.setTitle(translate("AlsaSinkSettings", "ALSA device")) self.deviceGroup.setGeometry(0, 0, self.width(), 100) self.deviceGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.deviceGroup) self.device = QComboBox(self.deviceGroup) self.device.addItems(self.devices.keys()) - self.device.setCurrentText('default') + self.device.setCurrentText("default") self.device.setToolTip( - translate('AlsaSinkSettings', 'ALSA devices, as defined in an ' - 'asound configuration file')) + translate( + "AlsaSinkSettings", + "ALSA devices, as defined in an " "asound configuration file", + ) + ) self.deviceGroup.layout().addWidget(self.device) - self.label = QLabel(translate('AlsaSinkSettings', 'ALSA device'), - self.deviceGroup) + self.label = QLabel( + translate("AlsaSinkSettings", "ALSA device"), self.deviceGroup + ) self.label.setAlignment(QtCore.Qt.AlignCenter) self.deviceGroup.layout().addWidget(self.label) @@ -63,7 +72,7 @@ def enableCheck(self, enabled): self.deviceGroup.setChecked(False) def loadSettings(self, settings): - device = settings.get('device', 'default') + device = settings.get("device", "default") for name, dev_name in self.devices.items(): if device == dev_name: @@ -71,19 +80,20 @@ def loadSettings(self, settings): break def getSettings(self): - if not (self.deviceGroup.isCheckable() and - not self.deviceGroup.isChecked()): - return {'device': self.devices[self.device.currentText()]} + if not ( + self.deviceGroup.isCheckable() and not self.deviceGroup.isChecked() + ): + return {"device": self.devices[self.device.currentText()]} return {} def _discover_pcm_devices(self): devices = {} - with open('/proc/asound/pcm', mode='r') as f: + with open("/proc/asound/pcm", mode="r") as f: for dev in f.readlines(): - dev_name = dev[7:dev.find(':', 7)].strip() - dev_code = 'hw:' + dev[:5].replace('-', ',') + dev_name = dev[7 : dev.find(":", 7)].strip() + dev_code = "hw:" + dev[:5].replace("-", ",") devices[dev_name] = dev_code return devices diff --git a/lisp/plugins/gst_backend/settings/audio_dynamic.py b/lisp/plugins/gst_backend/settings/audio_dynamic.py index 2c2e88cc6..756c1d527 100644 --- a/lisp/plugins/gst_backend/settings/audio_dynamic.py +++ b/lisp/plugins/gst_backend/settings/audio_dynamic.py @@ -21,14 +21,20 @@ from PyQt5 import QtCore from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QGroupBox, QGridLayout, QComboBox, QDoubleSpinBox, \ - QLabel, QVBoxLayout +from PyQt5.QtWidgets import ( + QGroupBox, + QGridLayout, + QComboBox, + QDoubleSpinBox, + QLabel, + QVBoxLayout, +) from lisp.plugins.gst_backend.elements.audio_dynamic import AudioDynamic from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate -MIN_dB = 0.000000312 # -100dB +MIN_dB = 0.000_000_312 # -100dB class AudioDynamicSettings(SettingsPage): @@ -48,9 +54,11 @@ def __init__(self, **kwargs): # AudioDynamic mode self.modeComboBox = QComboBox(self.groupBox) self.modeComboBox.addItem( - translate('AudioDynamicSettings', 'Compressor'), 'compressor') + translate("AudioDynamicSettings", "Compressor"), "compressor" + ) self.modeComboBox.addItem( - translate('AudioDynamicSettings', 'Expander'), 'expander') + translate("AudioDynamicSettings", "Expander"), "expander" + ) self.groupBox.layout().addWidget(self.modeComboBox, 0, 0, 1, 1) self.modeLabel = QLabel(self.groupBox) @@ -60,9 +68,11 @@ def __init__(self, **kwargs): # AudioDynamic characteristic self.chComboBox = QComboBox(self.groupBox) self.chComboBox.addItem( - translate('AudioDynamicSettings', 'Soft Knee'), 'soft-knee') + translate("AudioDynamicSettings", "Soft Knee"), "soft-knee" + ) self.chComboBox.addItem( - translate('AudioDynamicSettings', 'Hard Knee'), 'hard-knee') + translate("AudioDynamicSettings", "Hard Knee"), "hard-knee" + ) self.groupBox.layout().addWidget(self.chComboBox, 1, 0, 1, 1) self.chLabel = QLabel(self.groupBox) @@ -92,12 +102,14 @@ def __init__(self, **kwargs): def retranslateUi(self): self.groupBox.setTitle( - translate('AudioDynamicSettings', 'Compressor/Expander')) - self.modeLabel.setText(translate('AudioDynamicSettings', 'Type')) - self.chLabel.setText(translate('AudioDynamicSettings', 'Curve Shape')) - self.ratioLabel.setText(translate('AudioDynamicSettings', 'Ratio')) + translate("AudioDynamicSettings", "Compressor/Expander") + ) + self.modeLabel.setText(translate("AudioDynamicSettings", "Type")) + self.chLabel.setText(translate("AudioDynamicSettings", "Curve Shape")) + self.ratioLabel.setText(translate("AudioDynamicSettings", "Ratio")) self.thresholdLabel.setText( - translate('AudioDynamicSettings', 'Threshold (dB)')) + translate("AudioDynamicSettings", "Threshold (dB)") + ) def enableCheck(self, enabled): self.groupBox.setCheckable(enabled) @@ -107,25 +119,31 @@ def getSettings(self): settings = {} if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): - settings['ratio'] = self.ratioSpin.value() - settings['threshold'] = math.pow(10, - self.thresholdSpin.value() / 20) + settings["ratio"] = self.ratioSpin.value() + settings["threshold"] = math.pow( + 10, self.thresholdSpin.value() / 20 + ) - settings['mode'] = self.modeComboBox.currentData() - settings['characteristics'] = self.chComboBox.currentData() + settings["mode"] = self.modeComboBox.currentData() + settings["characteristics"] = self.chComboBox.currentData() return settings def loadSettings(self, settings): self.modeComboBox.setCurrentText( - translate('AudioDynamicSettings', - settings.get('mode', 'compressor'))) + translate( + "AudioDynamicSettings", settings.get("mode", "compressor") + ) + ) self.chComboBox.setCurrentText( - translate('AudioDynamicSettings', - settings.get('characteristics', 'soft-knee'))) + translate( + "AudioDynamicSettings", + settings.get("characteristics", "soft-knee"), + ) + ) - if settings.get('threshold', 0) == 0: - settings['threshold'] = MIN_dB + if settings.get("threshold", 0) == 0: + settings["threshold"] = MIN_dB - self.thresholdSpin.setValue(20 * math.log10(settings['threshold'])) - self.ratioSpin.setValue(settings.get('ratio', 1)) + self.thresholdSpin.setValue(20 * math.log10(settings["threshold"])) + self.ratioSpin.setValue(settings.get("ratio", 1)) diff --git a/lisp/plugins/gst_backend/settings/audio_pan.py b/lisp/plugins/gst_backend/settings/audio_pan.py index 176fefca9..87222dd70 100644 --- a/lisp/plugins/gst_backend/settings/audio_pan.py +++ b/lisp/plugins/gst_backend/settings/audio_pan.py @@ -57,8 +57,8 @@ def __init__(self, **kwargs): self.retransaleUi() def retransaleUi(self): - self.panBox.setTitle(translate('AudioPanSettings', 'Audio Pan')) - self.panLabel.setText(translate('AudioPanSettings', 'Center')) + self.panBox.setTitle(translate("AudioPanSettings", "Audio Pan")) + self.panLabel.setText(translate("AudioPanSettings", "Center")) def enableCheck(self, enabled): self.panBox.setCheckable(enabled) @@ -66,19 +66,19 @@ def enableCheck(self, enabled): def getSettings(self): if not (self.panBox.isCheckable() and not self.panBox.isChecked()): - return {'pan': self.panSlider.value() / 10} + return {"pan": self.panSlider.value() / 10} return {} def loadSettings(self, settings): - self.panSlider.setValue(settings.get('pan', 0.5) * 10) + self.panSlider.setValue(settings.get("pan", 0.5) * 10) def pan_changed(self, value): if value < 0: - position = translate('AudioPanSettings', 'Left') + position = translate("AudioPanSettings", "Left") elif value > 0: - position = translate('AudioPanSettings', 'Right') + position = translate("AudioPanSettings", "Right") else: - position = translate('AudioPanSettings', 'Center') + position = translate("AudioPanSettings", "Center") - self.panLabel.setText('{0} - {1}'.format(value, position)) + self.panLabel.setText("{0} - {1}".format(value, position)) diff --git a/lisp/plugins/gst_backend/settings/db_meter.py b/lisp/plugins/gst_backend/settings/db_meter.py index 6a5a4e2eb..c38b210cd 100644 --- a/lisp/plugins/gst_backend/settings/db_meter.py +++ b/lisp/plugins/gst_backend/settings/db_meter.py @@ -19,8 +19,13 @@ from PyQt5 import QtCore from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QGroupBox, QGridLayout, QSpinBox, QLabel, \ - QVBoxLayout +from PyQt5.QtWidgets import ( + QGroupBox, + QGridLayout, + QSpinBox, + QLabel, + QVBoxLayout, +) from lisp.plugins.gst_backend.elements.db_meter import DbMeter from lisp.plugins.gst_backend.gi_repository import Gst @@ -78,24 +83,26 @@ def __init__(self, **kwargs): self.retranslateUi() def retranslateUi(self): - self.groupBox.setTitle(translate('DbMeterSettings', 'DbMeter settings')) + self.groupBox.setTitle(translate("DbMeterSettings", "DbMeter settings")) self.intervalLabel.setText( - translate('DbMeterSettings', 'Time between levels (ms)')) - self.ttlLabel.setText(translate('DbMeterSettings', 'Peak ttl (ms)')) + translate("DbMeterSettings", "Time between levels (ms)") + ) + self.ttlLabel.setText(translate("DbMeterSettings", "Peak ttl (ms)")) self.falloffLabel.setText( - translate('DbMeterSettings', 'Peak falloff (dB/sec)')) + translate("DbMeterSettings", "Peak falloff (dB/sec)") + ) def loadSettings(self, settings): - self.intervalSpin.setValue(settings.get('interval', 50) / Gst.MSECOND) - self.ttlSpin.setValue(settings.get('peak_ttl', 500) / Gst.MSECOND) - self.falloffSpin.setValue(settings.get('peak_falloff', 20)) + self.intervalSpin.setValue(settings.get("interval", 50) / Gst.MSECOND) + self.ttlSpin.setValue(settings.get("peak_ttl", 500) / Gst.MSECOND) + self.falloffSpin.setValue(settings.get("peak_falloff", 20)) def getSettings(self): settings = {} if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): - settings['interval'] = self.intervalSpin.value() * Gst.MSECOND - settings['peak_ttl'] = self.ttlSpin.value() * Gst.MSECOND - settings['peak_falloff'] = self.falloffSpin.value() + settings["interval"] = self.intervalSpin.value() * Gst.MSECOND + settings["peak_ttl"] = self.ttlSpin.value() * Gst.MSECOND + settings["peak_falloff"] = self.falloffSpin.value() return settings diff --git a/lisp/plugins/gst_backend/settings/equalizer10.py b/lisp/plugins/gst_backend/settings/equalizer10.py index 130c6babf..bcd1830f5 100644 --- a/lisp/plugins/gst_backend/settings/equalizer10.py +++ b/lisp/plugins/gst_backend/settings/equalizer10.py @@ -31,8 +31,18 @@ class Equalizer10Settings(SettingsPage): ELEMENT = Equalizer10 Name = ELEMENT.Name - FREQ = ['30', '60', '120', '240', '475', '950', '1900', '3800', '7525', - '15K'] + FREQ = [ + "30", + "60", + "120", + "240", + "475", + "950", + "1900", + "3800", + "7525", + "15K", + ] def __init__(self, **kwargs): super().__init__(**kwargs) @@ -42,7 +52,8 @@ def __init__(self, **kwargs): self.groupBox = QGroupBox(self) self.groupBox.resize(self.size()) self.groupBox.setTitle( - translate('Equalizer10Settings', '10 Bands Equalizer (IIR)')) + translate("Equalizer10Settings", "10 Bands Equalizer (IIR)") + ) self.groupBox.setLayout(QGridLayout()) self.groupBox.layout().setVerticalSpacing(0) self.layout().addWidget(self.groupBox) @@ -51,7 +62,7 @@ def __init__(self, **kwargs): for n in range(10): label = QLabel(self.groupBox) - label.setMinimumWidth(QFontMetrics(label.font()).width('000')) + label.setMinimumWidth(QFontMetrics(label.font()).width("000")) label.setAlignment(QtCore.Qt.AlignCenter) label.setNum(0) self.groupBox.layout().addWidget(label, 0, n) @@ -64,10 +75,10 @@ def __init__(self, **kwargs): slider.valueChanged.connect(label.setNum) self.groupBox.layout().addWidget(slider, 1, n) self.groupBox.layout().setAlignment(slider, QtCore.Qt.AlignHCenter) - self.sliders['band' + str(n)] = slider + self.sliders["band" + str(n)] = slider fLabel = QLabel(self.groupBox) - fLabel.setStyleSheet('font-size: 8pt;') + fLabel.setStyleSheet("font-size: 8pt;") fLabel.setAlignment(QtCore.Qt.AlignCenter) fLabel.setText(self.FREQ[n]) self.groupBox.layout().addWidget(fLabel, 2, n) diff --git a/lisp/plugins/gst_backend/settings/jack_sink.py b/lisp/plugins/gst_backend/settings/jack_sink.py index e1c9cdf79..acc5d6fbb 100644 --- a/lisp/plugins/gst_backend/settings/jack_sink.py +++ b/lisp/plugins/gst_backend/settings/jack_sink.py @@ -21,9 +21,18 @@ import logging from PyQt5.QtCore import Qt from PyQt5.QtGui import QPainter, QPolygon, QPainterPath -from PyQt5.QtWidgets import QGroupBox, QWidget, \ - QHBoxLayout, QTreeWidget, QTreeWidgetItem, QGridLayout, QDialog, \ - QDialogButtonBox, QPushButton, QVBoxLayout +from PyQt5.QtWidgets import ( + QGroupBox, + QWidget, + QHBoxLayout, + QTreeWidget, + QTreeWidgetItem, + QGridLayout, + QDialog, + QDialogButtonBox, + QPushButton, + QVBoxLayout, +) from lisp.plugins.gst_backend.elements.jack_sink import JackSink from lisp.ui.settings.pages import SettingsPage @@ -53,12 +62,14 @@ def __init__(self, **kwargs): self.__jack_client = None try: self.__jack_client = jack.Client( - 'LinuxShowPlayer_SettingsControl', no_start_server=True) + "LinuxShowPlayer_SettingsControl", no_start_server=True + ) except jack.JackError: # Disable the widget self.setEnabled(False) logger.error( - 'Cannot connect with a running Jack server.', exc_info=True) + "Cannot connect with a running Jack server.", exc_info=True + ) # if __jack_client is None this will return a default value self.connections = JackSink.default_connections(self.__jack_client) @@ -66,9 +77,10 @@ def __init__(self, **kwargs): self.retranlsateUi() def retranlsateUi(self): - self.jackGroup.setTitle(translate('JackSinkSettings', 'Connections')) + self.jackGroup.setTitle(translate("JackSinkSettings", "Connections")) self.connectionsEdit.setText( - translate('JackSinkSettings', 'Edit connections')) + translate("JackSinkSettings", "Edit connections") + ) def closeEvent(self, event): if self.__jack_client is not None: @@ -78,14 +90,15 @@ def closeEvent(self, event): def getSettings(self): settings = {} - if not (self.jackGroup.isCheckable() and - not self.jackGroup.isChecked()): - settings['connections'] = self.connections + if not ( + self.jackGroup.isCheckable() and not self.jackGroup.isChecked() + ): + settings["connections"] = self.connections return settings def loadSettings(self, settings): - connections = settings.get('connections', []) + connections = settings.get("connections", []) if connections: self.connections = connections.copy() @@ -118,7 +131,7 @@ def add_port(self, port_name): class PortItem(QTreeWidgetItem): def __init__(self, port_name): - super().__init__([port_name[:port_name.index(':')]]) + super().__init__([port_name[: port_name.index(":")]]) self.name = port_name @@ -147,8 +160,10 @@ def paintEvent(self, QPaintEvent): painter.setRenderHint(QPainter.Antialiasing) for output, out_conn in enumerate(self.connections): - y1 = int(self.item_y(self._output_widget.topLevelItem(output)) + ( - yo - yc)) + y1 = int( + self.item_y(self._output_widget.topLevelItem(output)) + + (yo - yc) + ) for client in range(self._input_widget.topLevelItemCount()): client = self._input_widget.topLevelItem(client) @@ -156,8 +171,9 @@ def paintEvent(self, QPaintEvent): for port in client.ports: if port in self.connections[output]: y2 = int(self.item_y(client.ports[port]) + (yi - yc)) - self.draw_connection_line(painter, x1, y1, x2, y2, h1, - h2) + self.draw_connection_line( + painter, x1, y1, x2, y2, h1, h2 + ) painter.end() @@ -174,10 +190,9 @@ def draw_connection_line(painter, x1, y1, x2, y2, h1, h2): # Setup control points spline = QPolygon(4) cp = int((x2 - x1 - 8) * 0.4) - spline.setPoints(x1 + 4, y1, - x1 + 4 + cp, y1, - x2 - 4 - cp, y2, - x2 - 4, y2) + spline.setPoints( + x1 + 4, y1, x1 + 4 + cp, y1, x2 - 4 - cp, y2, x2 - 4, y2 + ) # The connection line path = QPainterPath() path.moveTo(spline.at(0)) @@ -214,18 +229,20 @@ def __init__(self, jack_client, parent=None, **kwargs): self.output_widget = QTreeWidget(self) self.input_widget = QTreeWidget(self) - self.connections_widget = ConnectionsWidget(self.output_widget, - self.input_widget, - parent=self) + self.connections_widget = ConnectionsWidget( + self.output_widget, self.input_widget, parent=self + ) self.output_widget.itemExpanded.connect(self.connections_widget.update) self.output_widget.itemCollapsed.connect(self.connections_widget.update) self.input_widget.itemExpanded.connect(self.connections_widget.update) self.input_widget.itemCollapsed.connect(self.connections_widget.update) self.input_widget.itemSelectionChanged.connect( - self.__input_selection_changed) + self.__input_selection_changed + ) self.output_widget.itemSelectionChanged.connect( - self.__output_selection_changed) + self.__output_selection_changed + ) self.layout().addWidget(self.output_widget, 0, 0) self.layout().addWidget(self.connections_widget, 0, 1) @@ -241,7 +258,8 @@ def __init__(self, jack_client, parent=None, **kwargs): self.layout().addWidget(self.connectButton, 1, 1) self.dialogButtons = QDialogButtonBox( - QDialogButtonBox.Cancel | QDialogButtonBox.Ok) + QDialogButtonBox.Cancel | QDialogButtonBox.Ok + ) self.dialogButtons.accepted.connect(self.accept) self.dialogButtons.rejected.connect(self.reject) self.layout().addWidget(self.dialogButtons, 2, 0, 1, 3) @@ -257,10 +275,12 @@ def __init__(self, jack_client, parent=None, **kwargs): def retranslateUi(self): self.output_widget.setHeaderLabels( - [translate('JackSinkSettings', 'Output ports')]) + [translate("JackSinkSettings", "Output ports")] + ) self.input_widget.setHeaderLabels( - [translate('JackSinkSettings', 'Input ports')]) - self.connectButton.setText(translate('JackSinkSettings', 'Connect')) + [translate("JackSinkSettings", "Input ports")] + ) + self.connectButton.setText(translate("JackSinkSettings", "Connect")) def set_connections(self, connections): self.connections = connections @@ -273,12 +293,13 @@ def update_graph(self): self.output_widget.clear() for port in range(8): self.output_widget.addTopLevelItem( - QTreeWidgetItem(['output_' + str(port)])) + QTreeWidgetItem(["output_" + str(port)]) + ) self.input_widget.clear() clients = {} for port in input_ports: - client_name = port.name[:port.name.index(':')] + client_name = port.name[: port.name.index(":")] if client_name not in clients: clients[client_name] = ClientItem(client_name) @@ -311,11 +332,13 @@ def __check_selection(self): if self.__selected_in.name in self.connections[output]: self.connectButton.setText( - translate('JackSinkSettings', 'Disconnect')) + translate("JackSinkSettings", "Disconnect") + ) self.connectButton.clicked.connect(self.__disconnect_selected) else: self.connectButton.setText( - translate('JackSinkSettings', 'Connect')) + translate("JackSinkSettings", "Connect") + ) self.connectButton.clicked.connect(self.__connect_selected) else: self.connectButton.setEnabled(False) diff --git a/lisp/plugins/gst_backend/settings/pitch.py b/lisp/plugins/gst_backend/settings/pitch.py index 88e14f7e4..f76953a9c 100644 --- a/lisp/plugins/gst_backend/settings/pitch.py +++ b/lisp/plugins/gst_backend/settings/pitch.py @@ -63,7 +63,7 @@ def __init__(self, **kwargs): self.retranslateUi() def retranslateUi(self): - self.groupBox.setTitle(translate('PitchSettings', 'Pitch')) + self.groupBox.setTitle(translate("PitchSettings", "Pitch")) self.pitch_changed(0) def enableCheck(self, enabled): @@ -72,14 +72,16 @@ def enableCheck(self, enabled): def getSettings(self): if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): - return {'pitch': math.pow(2, self.pitchSlider.value() / 12)} + return {"pitch": math.pow(2, self.pitchSlider.value() / 12)} return {} def loadSettings(self, settings): self.pitchSlider.setValue( - round(12 * math.log(settings.get('pitch', 1), 2))) + round(12 * math.log(settings.get("pitch", 1), 2)) + ) def pitch_changed(self, value): self.pitchLabel.setText( - translate('PitchSettings', '{0:+} semitones').format(value)) + translate("PitchSettings", "{0:+} semitones").format(value) + ) diff --git a/lisp/plugins/gst_backend/settings/preset_src.py b/lisp/plugins/gst_backend/settings/preset_src.py index 1501698be..4dff4535e 100644 --- a/lisp/plugins/gst_backend/settings/preset_src.py +++ b/lisp/plugins/gst_backend/settings/preset_src.py @@ -35,7 +35,7 @@ def __init__(self, **kwargs): self.layout().setAlignment(Qt.AlignTop) self.functionGroup = QGroupBox(self) - self.functionGroup.setTitle(translate('PresetSrcSettings', 'Presets')) + self.functionGroup.setTitle(translate("PresetSrcSettings", "Presets")) self.functionGroup.setLayout(QVBoxLayout()) self.layout().addWidget(self.functionGroup) @@ -46,7 +46,7 @@ def __init__(self, **kwargs): self.functionCombo.addItem(function) self.functionDuration = QTimeEdit(self.functionGroup) - self.functionDuration.setDisplayFormat('HH.mm.ss.zzz') + self.functionDuration.setDisplayFormat("HH.mm.ss.zzz") self.functionGroup.layout().addWidget(self.functionDuration) def enableCheck(self, enabled): @@ -54,14 +54,19 @@ def enableCheck(self, enabled): self.functionGroup.setChecked(False) def getSettings(self): - if not (self.functionGroup.isCheckable() and not self.functionGroup.isChecked()): - return {'preset': self.functionCombo.currentText(), - 'duration': self.functionDuration.time(). - msecsSinceStartOfDay()} + if not ( + self.functionGroup.isCheckable() + and not self.functionGroup.isChecked() + ): + return { + "preset": self.functionCombo.currentText(), + "duration": self.functionDuration.time().msecsSinceStartOfDay(), + } return {} def loadSettings(self, settings): - self.functionCombo.setCurrentText(settings.get('preset', '')) + self.functionCombo.setCurrentText(settings.get("preset", "")) self.functionDuration.setTime( - QTime.fromMSecsSinceStartOfDay(settings.get('duration', 0))) + QTime.fromMSecsSinceStartOfDay(settings.get("duration", 0)) + ) diff --git a/lisp/plugins/gst_backend/settings/speed.py b/lisp/plugins/gst_backend/settings/speed.py index 8c040ed18..e3a6e3599 100644 --- a/lisp/plugins/gst_backend/settings/speed.py +++ b/lisp/plugins/gst_backend/settings/speed.py @@ -61,8 +61,8 @@ def __init__(self, **kwargs): self.retranslateUi() def retranslateUi(self): - self.groupBox.setTitle(translate('SpeedSettings', 'Speed')) - self.speedLabel.setText('1.0') + self.groupBox.setTitle(translate("SpeedSettings", "Speed")) + self.speedLabel.setText("1.0") def enableCheck(self, enabled): self.groupBox.setCheckable(enabled) @@ -70,12 +70,12 @@ def enableCheck(self, enabled): def getSettings(self): if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): - return {'speed': self.speedSlider.value() / 100} + return {"speed": self.speedSlider.value() / 100} return {} def loadSettings(self, settings): - self.speedSlider.setValue(settings.get('speed', 1) * 100) + self.speedSlider.setValue(settings.get("speed", 1) * 100) def speedChanged(self, value): self.speedLabel.setText(str(value / 100.0)) diff --git a/lisp/plugins/gst_backend/settings/uri_input.py b/lisp/plugins/gst_backend/settings/uri_input.py index 34552e429..163d3866c 100644 --- a/lisp/plugins/gst_backend/settings/uri_input.py +++ b/lisp/plugins/gst_backend/settings/uri_input.py @@ -18,8 +18,18 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import QStandardPaths, Qt -from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QPushButton, QLineEdit, \ - QGridLayout, QCheckBox, QSpinBox, QLabel, QFileDialog, QVBoxLayout +from PyQt5.QtWidgets import ( + QGroupBox, + QHBoxLayout, + QPushButton, + QLineEdit, + QGridLayout, + QCheckBox, + QSpinBox, + QLabel, + QFileDialog, + QVBoxLayout, +) from lisp.plugins.gst_backend.elements.uri_input import UriInput from lisp.ui.settings.pages import SettingsPage @@ -42,7 +52,7 @@ def __init__(self, **kwargs): self.buttonFindFile = QPushButton(self.fileGroup) self.fileGroup.layout().addWidget(self.buttonFindFile) - self.filePath = QLineEdit('file://', self.fileGroup) + self.filePath = QLineEdit("file://", self.fileGroup) self.fileGroup.layout().addWidget(self.filePath) self.bufferingGroup = QGroupBox(self) @@ -56,7 +66,7 @@ def __init__(self, **kwargs): self.bufferingGroup.layout().addWidget(self.download, 1, 0, 1, 2) self.bufferSize = QSpinBox(self.bufferingGroup) - self.bufferSize.setRange(-1, 2147483647) + self.bufferSize.setRange(-1, 2_147_483_647) self.bufferSize.setValue(-1) self.bufferingGroup.layout().addWidget(self.bufferSize, 2, 0) @@ -69,15 +79,18 @@ def __init__(self, **kwargs): self.retranslateUi() def retranslateUi(self): - self.fileGroup.setTitle(translate('UriInputSettings', 'Source')) - self.buttonFindFile.setText(translate('UriInputSettings', 'Find File')) - self.bufferingGroup.setTitle(translate('UriInputSettings', 'Buffering')) + self.fileGroup.setTitle(translate("UriInputSettings", "Source")) + self.buttonFindFile.setText(translate("UriInputSettings", "Find File")) + self.bufferingGroup.setTitle(translate("UriInputSettings", "Buffering")) self.useBuffering.setText( - translate('UriInputSettings', 'Use Buffering')) - self.download.setText(translate('UriInputSettings', - 'Attempt download on network streams')) + translate("UriInputSettings", "Use Buffering") + ) + self.download.setText( + translate("UriInputSettings", "Attempt download on network streams") + ) self.bufferSizeLabel.setText( - translate('UriInputSettings', 'Buffer size (-1 default value)')) + translate("UriInputSettings", "Buffer size (-1 default value)") + ) def getSettings(self): settings = {} @@ -85,19 +98,19 @@ def getSettings(self): checkable = self.fileGroup.isCheckable() if not (checkable and not self.fileGroup.isChecked()): - settings['uri'] = self.filePath.text() + settings["uri"] = self.filePath.text() if not (checkable and not self.bufferingGroup.isChecked()): - settings['use_buffering'] = self.useBuffering.isChecked() - settings['download'] = self.download.isChecked() - settings['buffer_size'] = self.bufferSize.value() + settings["use_buffering"] = self.useBuffering.isChecked() + settings["download"] = self.download.isChecked() + settings["buffer_size"] = self.bufferSize.value() return settings def loadSettings(self, settings): - self.filePath.setText(settings.get('uri', '')) - self.useBuffering.setChecked(settings.get('use_buffering', False)) - self.download.setChecked(settings.get('download', False)) - self.bufferSize.setValue(settings.get('buffer_size', -1)) + self.filePath.setText(settings.get("uri", "")) + self.useBuffering.setChecked(settings.get("use_buffering", False)) + self.download.setChecked(settings.get("download", False)) + self.bufferSize.setValue(settings.get("buffer_size", -1)) def enableCheck(self, enabled): self.fileGroup.setCheckable(enabled) @@ -106,8 +119,11 @@ def enableCheck(self, enabled): def select_file(self): path = QStandardPaths.writableLocation(QStandardPaths.MusicLocation) file, ok = QFileDialog.getOpenFileName( - self, translate('UriInputSettings', 'Choose file'), - path, translate('UriInputSettings', 'All files') + ' (*)') + self, + translate("UriInputSettings", "Choose file"), + path, + translate("UriInputSettings", "All files") + " (*)", + ) if ok: - self.filePath.setText('file://' + file) + self.filePath.setText("file://" + file) diff --git a/lisp/plugins/gst_backend/settings/user_element.py b/lisp/plugins/gst_backend/settings/user_element.py index 9757c963c..3ffb53406 100644 --- a/lisp/plugins/gst_backend/settings/user_element.py +++ b/lisp/plugins/gst_backend/settings/user_element.py @@ -45,26 +45,28 @@ def __init__(self, **kwargs): self.warning = QLabel(self.groupBox) self.warning.setAlignment(QtCore.Qt.AlignCenter) - self.warning.setStyleSheet('color: #FF2222; font-weight: bold') + self.warning.setStyleSheet("color: #FF2222; font-weight: bold") self.groupBox.layout().addWidget(self.warning) self.retranslateUi() def retranslateUi(self): self.groupBox.setTitle( - translate('UserElementSettings', 'User defined elements')) + translate("UserElementSettings", "User defined elements") + ) self.warning.setText( - translate('UserElementSettings', 'Only for advanced user!')) + translate("UserElementSettings", "Only for advanced user!") + ) def enableCheck(self, enabled): self.groupBox.setCheckable(enabled) self.groupBox.setChecked(False) def loadSettings(self, settings): - self.textEdit.setPlainText(settings.get('bin', '')) + self.textEdit.setPlainText(settings.get("bin", "")) def getSettings(self): if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): - return {'bin': self.textEdit.toPlainText().strip()} + return {"bin": self.textEdit.toPlainText().strip()} return {} diff --git a/lisp/plugins/gst_backend/settings/volume.py b/lisp/plugins/gst_backend/settings/volume.py index c7840d399..0041eefab 100644 --- a/lisp/plugins/gst_backend/settings/volume.py +++ b/lisp/plugins/gst_backend/settings/volume.py @@ -18,8 +18,14 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QSlider, QLabel, QCheckBox, \ - QVBoxLayout +from PyQt5.QtWidgets import ( + QGroupBox, + QHBoxLayout, + QSlider, + QLabel, + QCheckBox, + QVBoxLayout, +) from lisp.backend.audio_utils import db_to_linear, linear_to_db from lisp.plugins.gst_backend.elements.volume import Volume @@ -76,12 +82,13 @@ def __init__(self, **kwargs): self.retranslateUi() def retranslateUi(self): - self.volumeBox.setTitle(translate('VolumeSettings', 'Volume')) - self.volumeLabel.setText('0.0 dB') + self.volumeBox.setTitle(translate("VolumeSettings", "Volume")) + self.volumeLabel.setText("0.0 dB") self.normalBox.setTitle( - translate('VolumeSettings', 'Normalized volume')) - self.normalLabel.setText('0.0 dB') - self.normalReset.setText(translate('VolumeSettings', 'Reset')) + translate("VolumeSettings", "Normalized volume") + ) + self.normalLabel.setText("0.0 dB") + self.normalReset.setText(translate("VolumeSettings", "Reset")) def enableCheck(self, enabled): for box in [self.normalBox, self.volumeBox]: @@ -93,24 +100,25 @@ def getSettings(self): checkable = self.volumeBox.isCheckable() if not (checkable and not self.volumeBox.isChecked()): - settings['volume'] = db_to_linear(self.volume.value() / 10) - settings['mute'] = self.muteButton.isMute() + settings["volume"] = db_to_linear(self.volume.value() / 10) + settings["mute"] = self.muteButton.isMute() if not (checkable and not self.normalBox.isChecked()): if self.normalReset.isChecked(): - settings['normal_volume'] = 1 + settings["normal_volume"] = 1 # If the apply button is pressed, show the correct value - self.normalLabel.setText('0 dB') + self.normalLabel.setText("0 dB") else: - settings['normal_volume'] = self.normal + settings["normal_volume"] = self.normal return settings def loadSettings(self, settings): - self.volume.setValue(linear_to_db(settings.get('volume', 1)) * 10) - self.muteButton.setMute(settings.get('mute', False)) - self.normal = settings.get('normal_volume', 1) + self.volume.setValue(linear_to_db(settings.get("volume", 1)) * 10) + self.muteButton.setMute(settings.get("mute", False)) + self.normal = settings.get("normal_volume", 1) self.normalLabel.setText( - str(round(linear_to_db(self.normal), 3)) + ' dB') + str(round(linear_to_db(self.normal), 3)) + " dB" + ) def volume_changed(self, value): - self.volumeLabel.setText(str(value / 10.0) + ' dB') + self.volumeLabel.setText(str(value / 10.0) + " dB") diff --git a/lisp/plugins/list_layout/__init__.py b/lisp/plugins/list_layout/__init__.py index aa70469d2..93c6b2d07 100644 --- a/lisp/plugins/list_layout/__init__.py +++ b/lisp/plugins/list_layout/__init__.py @@ -6,9 +6,9 @@ class ListLayout(Plugin): - Name = 'List Layout' - Description = 'Provide a layout that organize the cues in a list' - Authors = ('Francesco Ceruti', ) + Name = "List Layout" + Description = "Provide a layout that organize the cues in a list" + Authors = ("Francesco Ceruti",) def __init__(self, app): super().__init__(app) @@ -16,4 +16,5 @@ def __init__(self, app): _ListLayout.Config = ListLayout.Config register_layout(_ListLayout) AppConfigurationDialog.registerSettingsPage( - 'layouts.list_layout', ListLayoutSettings, ListLayout.Config) + "layouts.list_layout", ListLayoutSettings, ListLayout.Config + ) diff --git a/lisp/plugins/list_layout/control_buttons.py b/lisp/plugins/list_layout/control_buttons.py index 374bcd34f..8151e90f6 100644 --- a/lisp/plugins/list_layout/control_buttons.py +++ b/lisp/plugins/list_layout/control_buttons.py @@ -33,37 +33,38 @@ def __init__(self, *args): self.layout().setSpacing(5) # Row 0 - self.pauseButton = self.newButton(IconTheme.get('cue-pause')) + self.pauseButton = self.newButton(IconTheme.get("cue-pause")) self.layout().addWidget(self.pauseButton, 0, 0) - self.stopButton = self.newButton(IconTheme.get('cue-stop')) + self.stopButton = self.newButton(IconTheme.get("cue-stop")) self.layout().addWidget(self.stopButton, 0, 1) - self.interruptButton = self.newButton(IconTheme.get('cue-interrupt')) + self.interruptButton = self.newButton(IconTheme.get("cue-interrupt")) self.layout().addWidget(self.interruptButton, 0, 2) # Row 1 - self.resumeButton = self.newButton(IconTheme.get('cue-start')) + self.resumeButton = self.newButton(IconTheme.get("cue-start")) self.layout().addWidget(self.resumeButton, 1, 0) - self.fadeOutButton = self.newButton(IconTheme.get('fadeout-generic')) + self.fadeOutButton = self.newButton(IconTheme.get("fadeout-generic")) self.layout().addWidget(self.fadeOutButton, 1, 1) - self.fadeInButton = self.newButton(IconTheme.get('fadein-generic')) + self.fadeInButton = self.newButton(IconTheme.get("fadein-generic")) self.layout().addWidget(self.fadeInButton, 1, 2) self.retranslateUi() def retranslateUi(self): # Row 0 - self.pauseButton.setToolTip(translate('ListLayout', 'Pause all')) - self.stopButton.setToolTip(translate('ListLayout', 'Stop all')) + self.pauseButton.setToolTip(translate("ListLayout", "Pause all")) + self.stopButton.setToolTip(translate("ListLayout", "Stop all")) self.interruptButton.setToolTip( - translate('ListLayout', 'Interrupt all')) + translate("ListLayout", "Interrupt all") + ) # Row 1 - self.resumeButton.setToolTip(translate('ListLayout', 'Resume all')) - self.fadeOutButton.setToolTip(translate('ListLayout', 'Fade-Out all')) - self.fadeInButton.setToolTip(translate('ListLayout', 'Fade-In all')) + self.resumeButton.setToolTip(translate("ListLayout", "Resume all")) + self.fadeOutButton.setToolTip(translate("ListLayout", "Fade-Out all")) + self.fadeInButton.setToolTip(translate("ListLayout", "Fade-In all")) def newButton(self, icon): button = QIconPushButton(self) @@ -82,24 +83,24 @@ def __init__(self, **kwargs): self.layout().setSpacing(2) # Start/Pause - self.pauseButton = self.newButton(IconTheme.get('cue-pause')) + self.pauseButton = self.newButton(IconTheme.get("cue-pause")) self.layout().addWidget(self.pauseButton, 0, 0, 2, 1) - self.startButton = self.newButton(IconTheme.get('cue-start')) + self.startButton = self.newButton(IconTheme.get("cue-start")) self.startButton.hide() # Row 0 - self.stopButton = self.newButton(IconTheme.get('cue-stop')) + self.stopButton = self.newButton(IconTheme.get("cue-stop")) self.layout().addWidget(self.stopButton, 0, 1) - self.interruptButton = self.newButton(IconTheme.get('cue-interrupt')) + self.interruptButton = self.newButton(IconTheme.get("cue-interrupt")) self.layout().addWidget(self.interruptButton, 0, 2) # Row 1 - self.fadeOutButton = self.newButton(IconTheme.get('fadeout-generic')) + self.fadeOutButton = self.newButton(IconTheme.get("fadeout-generic")) self.layout().addWidget(self.fadeOutButton, 1, 1) - self.fadeInButton = self.newButton(IconTheme.get('fadein-generic')) + self.fadeInButton = self.newButton(IconTheme.get("fadein-generic")) self.layout().addWidget(self.fadeInButton, 1, 2) self.layout().setColumnStretch(0, 3) diff --git a/lisp/plugins/list_layout/info_panel.py b/lisp/plugins/list_layout/info_panel.py index 9ba78e1c8..d638015a1 100644 --- a/lisp/plugins/list_layout/info_panel.py +++ b/lisp/plugins/list_layout/info_panel.py @@ -39,7 +39,7 @@ def __init__(self, *args): # cue description self.cueDescription = QTextEdit(self) - self.cueDescription.setObjectName('InfoPanelDescription') + self.cueDescription.setObjectName("InfoPanelDescription") self.cueDescription.setFocusPolicy(Qt.NoFocus) self.cueDescription.setReadOnly(True) self.layout().addWidget(self.cueDescription) @@ -48,9 +48,11 @@ def __init__(self, *args): def retranslateUi(self): self.cueName.setPlaceholderText( - translate('ListLayoutInfoPanel', 'Cue name')) + translate("ListLayoutInfoPanel", "Cue name") + ) self.cueDescription.setPlaceholderText( - translate('ListLayoutInfoPanel', 'Cue description')) + translate("ListLayoutInfoPanel", "Cue description") + ) @property def cue(self): @@ -59,14 +61,14 @@ def cue(self): @cue.setter def cue(self, item): if self._cue is not None: - self._cue.changed('name').disconnect(self._name_changed) - self._cue.changed('description').disconnect(self._desc_changed) + self._cue.changed("name").disconnect(self._name_changed) + self._cue.changed("description").disconnect(self._desc_changed) self._cue = item if self._cue is not None: - self._cue.changed('name').connect(self._name_changed) - self._cue.changed('description').connect(self._desc_changed) + self._cue.changed("name").connect(self._name_changed) + self._cue.changed("description").connect(self._desc_changed) self._name_changed(self._cue.name) self._desc_changed(self._cue.description) @@ -75,7 +77,7 @@ def cue(self, item): self.cueDescription.clear() def _name_changed(self, name): - self.cueName.setText(str(self.cue.index + 1) + ' → ' + name) + self.cueName.setText(str(self.cue.index + 1) + " → " + name) def _desc_changed(self, description): self.cueDescription.setText(description) diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index 691c64416..b99b65e92 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -27,7 +27,11 @@ from lisp.cues.cue import Cue, CueAction, CueNextAction from lisp.cues.cue_memento_model import CueMementoAdapter from lisp.layout.cue_layout import CueLayout -from lisp.layout.cue_menu import SimpleMenuAction, MENU_PRIORITY_CUE, MenuActionsGroup +from lisp.layout.cue_menu import ( + SimpleMenuAction, + MENU_PRIORITY_CUE, + MenuActionsGroup, +) from lisp.plugins.list_layout.list_view import CueListView from lisp.plugins.list_layout.models import CueListModel, RunningCueModel from lisp.plugins.list_layout.view import ListLayoutView @@ -35,16 +39,18 @@ class ListLayout(CueLayout): - NAME = QT_TRANSLATE_NOOP('LayoutName', 'List Layout') + NAME = QT_TRANSLATE_NOOP("LayoutName", "List Layout") DESCRIPTION = QT_TRANSLATE_NOOP( - 'LayoutDescription', 'Organize the cues in a list') + "LayoutDescription", "Organize the cues in a list" + ) DETAILS = [ QT_TRANSLATE_NOOP( - 'LayoutDetails', 'SHIFT + Space or Double-Click to edit a cue'), + "LayoutDetails", "SHIFT + Space or Double-Click to edit a cue" + ), QT_TRANSLATE_NOOP( - 'LayoutDetails', 'To copy cues drag them while pressing CTRL'), - QT_TRANSLATE_NOOP( - 'LayoutDetails', 'To move cues drag them') + "LayoutDetails", "To copy cues drag them while pressing CTRL" + ), + QT_TRANSLATE_NOOP("LayoutDetails", "To move cues drag them"), ] Config = DummyConfiguration() @@ -63,16 +69,21 @@ def __init__(self, application): self._running_model = RunningCueModel(self.cue_model) self._view = ListLayoutView( - self._list_model, self._running_model, self.Config) + self._list_model, self._running_model, self.Config + ) # GO button self._view.goButton.clicked.connect(self.__go_slot) # Global actions self._view.controlButtons.stopButton.clicked.connect(self.stop_all) self._view.controlButtons.pauseButton.clicked.connect(self.pause_all) self._view.controlButtons.fadeInButton.clicked.connect(self.fadein_all) - self._view.controlButtons.fadeOutButton.clicked.connect(self.fadeout_all) + self._view.controlButtons.fadeOutButton.clicked.connect( + self.fadeout_all + ) self._view.controlButtons.resumeButton.clicked.connect(self.resume_all) - self._view.controlButtons.interruptButton.clicked.connect(self.interrupt_all) + self._view.controlButtons.interruptButton.clicked.connect( + self.interrupt_all + ) # Cue list self._view.listView.itemDoubleClicked.connect(self._double_clicked) self._view.listView.contextMenuInvoked.connect(self._context_invoked) @@ -113,28 +124,29 @@ def __init__(self, application): # Load settings self._go_key_sequence = QKeySequence( - ListLayout.Config['goKey'], QKeySequence.NativeText) - self._set_seeksliders_visible(ListLayout.Config['show.seekSliders']) - self._set_running_visible(ListLayout.Config['show.playingCues']) - self._set_accurate_time(ListLayout.Config['show.accurateTime']) - self._set_dbmeters_visible(ListLayout.Config['show.dBMeters']) - self._set_selection_mode(ListLayout.Config['selectionMode']) - self._set_auto_continue(ListLayout.Config['autoContinue']) + ListLayout.Config["goKey"], QKeySequence.NativeText + ) + self._set_seeksliders_visible(ListLayout.Config["show.seekSliders"]) + self._set_running_visible(ListLayout.Config["show.playingCues"]) + self._set_accurate_time(ListLayout.Config["show.accurateTime"]) + self._set_dbmeters_visible(ListLayout.Config["show.dBMeters"]) + self._set_selection_mode(ListLayout.Config["selectionMode"]) + self._set_auto_continue(ListLayout.Config["autoContinue"]) # Context menu actions self._edit_actions_group = MenuActionsGroup(priority=MENU_PRIORITY_CUE) self._edit_actions_group.add( SimpleMenuAction( - translate('ListLayout', 'Edit cue'), + translate("ListLayout", "Edit cue"), self.edit_cue, - translate('ListLayout', 'Edit selected cues'), + translate("ListLayout", "Edit selected cues"), self.edit_cues, ), SimpleMenuAction( - translate('ListLayout', 'Remove cue'), + translate("ListLayout", "Remove cue"), self.cue_model.remove, - translate('ListLayout', 'Remove selected cues'), - self._remove_cues + translate("ListLayout", "Remove selected cues"), + self._remove_cues, ), ) @@ -144,16 +156,21 @@ def __init__(self, application): def retranslate(self): self.show_running_action.setText( - translate('ListLayout', 'Show playing cues')) + translate("ListLayout", "Show playing cues") + ) self.show_dbmeter_action.setText( - translate('ListLayout', 'Show dB-meters')) - self.show_seek_action.setText(translate('ListLayout', 'Show seek-bars')) + translate("ListLayout", "Show dB-meters") + ) + self.show_seek_action.setText(translate("ListLayout", "Show seek-bars")) self.show_accurate_action.setText( - translate('ListLayout', 'Show accurate time')) + translate("ListLayout", "Show accurate time") + ) self.auto_continue_action.setText( - translate('ListLayout', 'Auto-select next cue')) + translate("ListLayout", "Auto-select next cue") + ) self.selection_mode_action.setText( - translate('ListLayout', 'Selection mode')) + translate("ListLayout", "Selection mode") + ) def cues(self, cue_type=Cue): yield from self._list_model @@ -182,7 +199,8 @@ def cue_at(self, index): def selected_cues(self, cue_type=Cue): for item in self._view.listView.selectedItems(): yield self._list_model.item( - self._view.listView.indexOfTopLevelItem(item)) + self._view.listView.indexOfTopLevelItem(item) + ) def finalize(self): # Clean layout menu @@ -316,7 +334,7 @@ def _context_invoked(self, event): self.show_context_menu(event.globalPos()) def __go_slot(self): - action = CueAction(ListLayout.Config.get('goAction')) + action = CueAction(ListLayout.Config.get("goAction")) self.go(action=action) def __cue_added(self, cue): @@ -327,8 +345,10 @@ def __cue_next(self, cue): next_index = cue.index + 1 if next_index < len(self._list_model): action = CueNextAction(cue.next_action) - if (action == CueNextAction.SelectAfterEnd or - action == CueNextAction.SelectAfterWait): + if ( + action == CueNextAction.SelectAfterEnd + or action == CueNextAction.SelectAfterWait + ): self.set_standby_index(next_index) else: next_cue = self._list_model.item(next_index) @@ -336,5 +356,5 @@ def __cue_next(self, cue): if self.auto_continue and next_cue is self.standby_cue(): self.set_standby_index(next_index + 1) - except(IndexError, KeyError): + except (IndexError, KeyError): pass diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py index e09ab5867..47285e593 100644 --- a/lisp/plugins/list_layout/list_view.py +++ b/lisp/plugins/list_layout/list_view.py @@ -17,15 +17,27 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import pyqtSignal, Qt, QDataStream, QIODevice, \ - QT_TRANSLATE_NOOP +from PyQt5.QtCore import ( + pyqtSignal, + Qt, + QDataStream, + QIODevice, + QT_TRANSLATE_NOOP, +) from PyQt5.QtGui import QKeyEvent, QContextMenuEvent, QBrush, QColor from PyQt5.QtWidgets import QTreeWidget, QHeaderView, QTreeWidgetItem from lisp.core.signal import Connection from lisp.cues.cue_factory import CueFactory -from lisp.plugins.list_layout.list_widgets import CueStatusIcons, NameWidget, \ - PreWaitWidget, CueTimeWidget, NextActionIcon, PostWaitWidget, IndexWidget +from lisp.plugins.list_layout.list_widgets import ( + CueStatusIcons, + NameWidget, + PreWaitWidget, + CueTimeWidget, + NextActionIcon, + PostWaitWidget, + IndexWidget, +) from lisp.ui.ui_utils import translate @@ -39,7 +51,7 @@ def __init__(self, name, widget, resize=None, width=None, visible=True): @property def name(self): - return translate('ListLayoutHeader', self.baseName) + return translate("ListLayoutHeader", self.baseName) class CueTreeWidgetItem(QTreeWidgetItem): @@ -58,26 +70,23 @@ class CueListView(QTreeWidget): # TODO: add ability to show/hide # TODO: implement columns (cue-type / target / etc..) COLUMNS = [ - ListColumn('', CueStatusIcons, QHeaderView.Fixed, width=45), - ListColumn('#', IndexWidget, QHeaderView.ResizeToContents), + ListColumn("", CueStatusIcons, QHeaderView.Fixed, width=45), + ListColumn("#", IndexWidget, QHeaderView.ResizeToContents), ListColumn( - QT_TRANSLATE_NOOP('ListLayoutHeader', 'Cue'), + QT_TRANSLATE_NOOP("ListLayoutHeader", "Cue"), NameWidget, - QHeaderView.Stretch + QHeaderView.Stretch, ), ListColumn( - QT_TRANSLATE_NOOP('ListLayoutHeader', 'Pre wait'), - PreWaitWidget + QT_TRANSLATE_NOOP("ListLayoutHeader", "Pre wait"), PreWaitWidget ), ListColumn( - QT_TRANSLATE_NOOP('ListLayoutHeader', 'Action'), - CueTimeWidget + QT_TRANSLATE_NOOP("ListLayoutHeader", "Action"), CueTimeWidget ), ListColumn( - QT_TRANSLATE_NOOP('ListLayoutHeader', 'Post wait'), - PostWaitWidget + QT_TRANSLATE_NOOP("ListLayoutHeader", "Post wait"), PostWaitWidget ), - ListColumn('', NextActionIcon, QHeaderView.Fixed, width=18) + ListColumn("", NextActionIcon, QHeaderView.Fixed, width=18), ] ITEM_DEFAULT_BG = QBrush(Qt.transparent) @@ -124,7 +133,7 @@ def __init__(self, listModel, parent=None): def dropEvent(self, event): # Decode mimedata information about the drag&drop event, since only # internal movement are allowed we assume the data format is correct - data = event.mimeData().data('application/x-qabstractitemmodeldatalist') + data = event.mimeData().data("application/x-qabstractitemmodeldatalist") stream = QDataStream(data, QIODevice.ReadOnly) # Get the starting-item row @@ -180,8 +189,10 @@ def keyPressEvent(self, event): super().keyPressEvent(event) def mousePressEvent(self, event): - if (not event.buttons() & Qt.RightButton or - not self.selectionMode() == QTreeWidget.NoSelection): + if ( + not event.buttons() & Qt.RightButton + or not self.selectionMode() == QTreeWidget.NoSelection + ): super().mousePressEvent(event) def standbyIndex(self): diff --git a/lisp/plugins/list_layout/list_widgets.py b/lisp/plugins/list_layout/list_widgets.py index f5536283f..de488e735 100644 --- a/lisp/plugins/list_layout/list_widgets.py +++ b/lisp/plugins/list_layout/list_widgets.py @@ -35,7 +35,7 @@ def __init__(self, item, *args, **kwargs): self.setAttribute(Qt.WA_TranslucentBackground) self.setAlignment(Qt.AlignCenter) - item.cue.changed('index').connect(self.__update, Connection.QtQueued) + item.cue.changed("index").connect(self.__update, Connection.QtQueued) self.__update(item.cue.index) def __update(self, newIndex): @@ -47,7 +47,7 @@ def __init__(self, item, *args, **kwargs): super().__init__(*args, **kwargs) self.setAttribute(Qt.WA_TranslucentBackground) - item.cue.changed('name').connect(self.__update, Connection.QtQueued) + item.cue.changed("name").connect(self.__update, Connection.QtQueued) self.__update(item.cue.name) def __update(self, newName): @@ -77,13 +77,13 @@ def _standbyChange(self): self.update() def _start(self): - self.setPixmap(IconTheme.get('led-running').pixmap(self._size())) + self.setPixmap(IconTheme.get("led-running").pixmap(self._size())) def _pause(self): - self.setPixmap(IconTheme.get('led-pause').pixmap(self._size())) + self.setPixmap(IconTheme.get("led-pause").pixmap(self._size())) def _error(self): - self.setPixmap(IconTheme.get('led-error').pixmap(self._size())) + self.setPixmap(IconTheme.get("led-error").pixmap(self._size())) def _stop(self): self.setPixmap(None) @@ -122,16 +122,17 @@ def paintEvent(self, event): QRect( indicator_width + CueStatusIcons.MARGIN, CueStatusIcons.MARGIN, - status_size, status_size + status_size, + status_size, ), - self._statusPixmap + self._statusPixmap, ) qp.end() class NextActionIcon(QLabel): - STYLESHEET = 'background: transparent;' + STYLESHEET = "background: transparent;" SIZE = 16 def __init__(self, item, *args): @@ -139,20 +140,25 @@ def __init__(self, item, *args): self.setStyleSheet(self.STYLESHEET) self.setAlignment(Qt.AlignCenter) - item.cue.changed('next_action').connect( - self.__update, Connection.QtQueued) + item.cue.changed("next_action").connect( + self.__update, Connection.QtQueued + ) self.__update(item.cue.next_action) def __update(self, next_action): next_action = CueNextAction(next_action) - pixmap = IconTheme.get('').pixmap(self.SIZE) - - if (next_action == CueNextAction.TriggerAfterWait or - next_action == CueNextAction.TriggerAfterEnd): - pixmap = IconTheme.get('cue-trigger-next').pixmap(self.SIZE) - elif (next_action == CueNextAction.SelectAfterWait or - next_action == CueNextAction.SelectAfterEnd): - pixmap = IconTheme.get('cue-select-next').pixmap(self.SIZE) + pixmap = IconTheme.get("").pixmap(self.SIZE) + + if ( + next_action == CueNextAction.TriggerAfterWait + or next_action == CueNextAction.TriggerAfterEnd + ): + pixmap = IconTheme.get("cue-trigger-next").pixmap(self.SIZE) + elif ( + next_action == CueNextAction.SelectAfterWait + or next_action == CueNextAction.SelectAfterEnd + ): + pixmap = IconTheme.get("cue-select-next").pixmap(self.SIZE) self.setToolTip(tr_next_action(next_action)) @@ -160,13 +166,12 @@ def __update(self, next_action): class TimeWidget(QProgressBar): - def __init__(self, item, *args): super().__init__(*args) - self.setObjectName('ListTimeWidget') + self.setObjectName("ListTimeWidget") self.setValue(0) self.setTextVisible(True) - font = QFont('Monospace') + font = QFont("Monospace") font.setStyleHint(QFont.Monospace) self.setFont(font) @@ -190,28 +195,27 @@ def _update_duration(self, duration): self.setTextVisible(False) def _update_style(self, state): - self.setProperty('state', state) + self.setProperty("state", state) self.style().unpolish(self) self.style().polish(self) def _running(self): - self._update_style('running') + self._update_style("running") def _pause(self): - self._update_style('pause') + self._update_style("pause") self._update_time(self.value()) def _stop(self): - self._update_style('stop') + self._update_style("stop") self.setValue(self.minimum()) def _error(self): - self._update_style('error') + self._update_style("error") self.setValue(self.minimum()) class CueTimeWidget(TimeWidget): - def __init__(self, *args): super().__init__(*args) @@ -221,8 +225,9 @@ def __init__(self, *args): self.cue.paused.connect(self._pause, Connection.QtQueued) self.cue.error.connect(self._error, Connection.QtQueued) self.cue.end.connect(self._stop, Connection.QtQueued) - self.cue.changed('duration').connect( - self._update_duration, Connection.QtQueued) + self.cue.changed("duration").connect( + self._update_duration, Connection.QtQueued + ) self.cue_time = CueTime(self.cue) self.cue_time.notify.connect(self._update_time, Connection.QtQueued) @@ -242,7 +247,6 @@ def _stop(self): class PreWaitWidget(TimeWidget): - def __init__(self, *args): super().__init__(*args) self.show_zero_duration = True @@ -251,8 +255,9 @@ def __init__(self, *args): self.cue.prewait_stopped.connect(self._stop, Connection.QtQueued) self.cue.prewait_paused.connect(self._pause, Connection.QtQueued) self.cue.prewait_ended.connect(self._stop, Connection.QtQueued) - self.cue.changed('pre_wait').connect( - self._update_duration, Connection.QtQueued) + self.cue.changed("pre_wait").connect( + self._update_duration, Connection.QtQueued + ) self._update_duration(self.cue.pre_wait) @@ -269,13 +274,13 @@ def _stop(self): class PostWaitWidget(TimeWidget): - def __init__(self, *args): super().__init__(*args) self.show_zero_duration = True - self.cue.changed('next_action').connect( - self._next_action_changed, Connection.QtQueued) + self.cue.changed("next_action").connect( + self._next_action_changed, Connection.QtQueued + ) self.wait_time = CueWaitTime(self.cue, mode=CueWaitTime.Mode.Post) self.cue_time = CueTime(self.cue) @@ -283,8 +288,10 @@ def __init__(self, *args): self._next_action_changed(self.cue.next_action) def _update_duration(self, duration): - if (self.cue.next_action == CueNextAction.TriggerAfterWait or - self.cue.next_action == CueNextAction.SelectAfterWait): + if ( + self.cue.next_action == CueNextAction.TriggerAfterWait + or self.cue.next_action == CueNextAction.SelectAfterWait + ): # The wait time is in seconds, we need milliseconds duration *= 1000 @@ -306,19 +313,22 @@ def _next_action_changed(self, next_action): self.cue_time.notify.disconnect(self._update_time) self.wait_time.notify.disconnect(self._update_time) - self.cue.changed('post_wait').disconnect(self._update_duration) - self.cue.changed('duration').disconnect(self._update_duration) + self.cue.changed("post_wait").disconnect(self._update_duration) + self.cue.changed("duration").disconnect(self._update_duration) - if (next_action == CueNextAction.TriggerAfterEnd or - next_action == CueNextAction.SelectAfterEnd): + if ( + next_action == CueNextAction.TriggerAfterEnd + or next_action == CueNextAction.SelectAfterEnd + ): self.cue.interrupted.connect(self._stop, Connection.QtQueued) self.cue.started.connect(self._running, Connection.QtQueued) self.cue.stopped.connect(self._stop, Connection.QtQueued) self.cue.paused.connect(self._pause, Connection.QtQueued) self.cue.error.connect(self._stop, Connection.QtQueued) self.cue.end.connect(self._stop, Connection.QtQueued) - self.cue.changed('duration').connect( - self._update_duration, Connection.QtQueued) + self.cue.changed("duration").connect( + self._update_duration, Connection.QtQueued + ) self.cue_time.notify.connect(self._update_time, Connection.QtQueued) self._update_duration(self.cue.duration) @@ -327,18 +337,22 @@ def _next_action_changed(self, next_action): self.cue.postwait_stopped.connect(self._stop, Connection.QtQueued) self.cue.postwait_paused.connect(self._pause, Connection.QtQueued) self.cue.postwait_ended.connect(self._stop, Connection.QtQueued) - self.cue.changed('post_wait').connect( - self._update_duration, Connection.QtQueued) + self.cue.changed("post_wait").connect( + self._update_duration, Connection.QtQueued + ) self.wait_time.notify.connect( - self._update_time, Connection.QtQueued) + self._update_time, Connection.QtQueued + ) self._update_duration(self.cue.post_wait) def _stop(self): super()._stop() - if (self.cue.next_action == CueNextAction.TriggerAfterEnd or - self.cue.next_action == CueNextAction.SelectAfterEnd): + if ( + self.cue.next_action == CueNextAction.TriggerAfterEnd + or self.cue.next_action == CueNextAction.SelectAfterEnd + ): self._update_duration(self.cue.duration) else: self._update_duration(self.cue.post_wait) diff --git a/lisp/plugins/list_layout/models.py b/lisp/plugins/list_layout/models.py index ce8a6b9d1..8dc1f96ea 100644 --- a/lisp/plugins/list_layout/models.py +++ b/lisp/plugins/list_layout/models.py @@ -22,7 +22,6 @@ class CueListModel(ModelAdapter): - def __init__(self, model): super().__init__(model) self.__cues = [] @@ -62,8 +61,9 @@ def _model_reset(self): self.model_reset.emit() def _item_added(self, item): - if (not isinstance(item.index, int) or - not 0 <= item.index <= len(self.__cues)): + if not isinstance(item.index, int) or not 0 <= item.index <= len( + self.__cues + ): item.index = len(self.__cues) self.__cues.insert(item.index, item) @@ -91,7 +91,6 @@ def __iter__(self): class RunningCueModel(ReadOnlyProxyModel): - def __init__(self, model): super().__init__(model) self.__playing = [] diff --git a/lisp/plugins/list_layout/playing_view.py b/lisp/plugins/list_layout/playing_view.py index ee6fcbe28..50fcd9882 100644 --- a/lisp/plugins/list_layout/playing_view.py +++ b/lisp/plugins/list_layout/playing_view.py @@ -25,7 +25,6 @@ class RunningCuesListWidget(QListWidget): - def __init__(self, running_model, config, **kwargs): super().__init__(**kwargs) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) @@ -37,11 +36,14 @@ def __init__(self, running_model, config, **kwargs): self._running_cues = {} self._running_model = running_model self._running_model.item_added.connect( - self._item_added, Connection.QtQueued) + self._item_added, Connection.QtQueued + ) self._running_model.item_removed.connect( - self._item_removed, Connection.QtQueued) + self._item_removed, Connection.QtQueued + ) self._running_model.model_reset.connect( - self._model_reset, Connection.QtQueued) + self._model_reset, Connection.QtQueued + ) self.__dbmeter_visible = False self.__seek_visible = False diff --git a/lisp/plugins/list_layout/playing_widgets.py b/lisp/plugins/list_layout/playing_widgets.py index 9a932247d..3bf03b466 100644 --- a/lisp/plugins/list_layout/playing_widgets.py +++ b/lisp/plugins/list_layout/playing_widgets.py @@ -19,8 +19,14 @@ from PyQt5.QtCore import Qt from PyQt5.QtGui import QColor -from PyQt5.QtWidgets import QWidget, QGridLayout, QLabel, QSizePolicy, \ - QLCDNumber, QHBoxLayout +from PyQt5.QtWidgets import ( + QWidget, + QGridLayout, + QLabel, + QSizePolicy, + QLCDNumber, + QHBoxLayout, +) from lisp.cues.cue import CueAction from lisp.core.signal import Connection @@ -93,7 +99,7 @@ def __init__(self, cue, config, **kwargs): self.controlButtons.interruptButton.setEnabled(False) self.timeDisplay = QLCDNumber(self.gridLayoutWidget) - self.timeDisplay.setStyleSheet('background-color: transparent') + self.timeDisplay.setStyleSheet("background-color: transparent") self.timeDisplay.setSegmentStyle(QLCDNumber.Flat) self.timeDisplay.setDigitCount(8) self.timeDisplay.display(strtime(cue.duration)) @@ -104,7 +110,7 @@ def __init__(self, cue, config, **kwargs): self.gridLayout.setColumnStretch(0, 7) self.gridLayout.setColumnStretch(1, 5) - cue.changed('name').connect(self.name_changed, Connection.QtQueued) + cue.changed("name").connect(self.name_changed, Connection.QtQueued) cue.started.connect(self.controlButtons.pauseMode, Connection.QtQueued) cue.paused.connect(self.controlButtons.startMode, Connection.QtQueued) @@ -144,23 +150,20 @@ def _time_updated(self, time): def _update_timers(self, time): self.timeDisplay.display( - strtime(self.cue.duration - time, accurate=self._accurate_time)) + strtime(self.cue.duration - time, accurate=self._accurate_time) + ) def _pause(self): - self.cue.pause( - fade=self._config.get('pauseCueFade', True)) + self.cue.pause(fade=self._config.get("pauseCueFade", True)) def _resume(self): - self.cue.resume( - fade=self._config.get('resumeCueFade', True)) + self.cue.resume(fade=self._config.get("resumeCueFade", True)) def _stop(self): - self.cue.stop( - fade=self._config.get('stopCueFade', True)) + self.cue.stop(fade=self._config.get("stopCueFade", True)) def _interrupt(self): - self.cue.interrupt( - fade=self._config.get('interruptCueFade', True)) + self.cue.interrupt(fade=self._config.get("interruptCueFade", True)) def _fadeout(self): self.cue.execute(CueAction.FadeOut) @@ -187,8 +190,9 @@ def __init__(self, cue, config, **kwargs): self.dbmeter = QDbMeter(self.gridLayoutWidget) self.dbmeter.setVisible(False) - cue.changed('duration').connect(self._update_duration, - Connection.QtQueued) + cue.changed("duration").connect( + self._update_duration, Connection.QtQueued + ) def set_seek_visible(self, visible): if visible and not self.seekSlider.isVisible(): @@ -206,7 +210,7 @@ def set_dbmeter_visible(self, visible): self._dbmeter_element = None if visible: - self._dbmeter_element = self.cue.media.element('DbMeter') + self._dbmeter_element = self.cue.media.element("DbMeter") if self._dbmeter_element is not None: self._dbmeter_element.level_ready.connect(self.dbmeter.plot) diff --git a/lisp/plugins/list_layout/settings.py b/lisp/plugins/list_layout/settings.py index 7ac854f64..3c81b9f64 100644 --- a/lisp/plugins/list_layout/settings.py +++ b/lisp/plugins/list_layout/settings.py @@ -19,8 +19,14 @@ from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtGui import QKeySequence -from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QLabel, \ - QKeySequenceEdit, QGridLayout +from PyQt5.QtWidgets import ( + QGroupBox, + QVBoxLayout, + QCheckBox, + QLabel, + QKeySequenceEdit, + QGridLayout, +) from lisp.cues.cue import CueAction from lisp.ui.settings.pages import SettingsPage @@ -29,7 +35,7 @@ class ListLayoutSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'List Layout') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "List Layout") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -72,7 +78,7 @@ def __init__(self, **kwargs): self.goLayout.addWidget(self.goActionLabel, 1, 0) self.goActionCombo = CueActionComboBox( actions=(CueAction.Default, CueAction.Start, CueAction.FadeInStart), - mode=CueActionComboBox.Mode.Value + mode=CueActionComboBox.Mode.Value, ) self.goLayout.addWidget(self.goActionCombo, 1, 1) @@ -94,59 +100,62 @@ def __init__(self, **kwargs): def retranslateUi(self): self.behaviorsGroup.setTitle( - translate('ListLayout', 'Default behaviors')) - self.showPlaying.setText(translate('ListLayout', 'Show playing cues')) - self.showDbMeters.setText(translate('ListLayout', 'Show dB-meters')) - self.showAccurate.setText(translate('ListLayout', 'Show accurate time')) - self.showSeek.setText(translate('ListLayout', 'Show seek-bars')) - self.autoNext.setText(translate('ListLayout', 'Auto-select next cue')) + translate("ListLayout", "Default behaviors") + ) + self.showPlaying.setText(translate("ListLayout", "Show playing cues")) + self.showDbMeters.setText(translate("ListLayout", "Show dB-meters")) + self.showAccurate.setText(translate("ListLayout", "Show accurate time")) + self.showSeek.setText(translate("ListLayout", "Show seek-bars")) + self.autoNext.setText(translate("ListLayout", "Auto-select next cue")) self.selectionMode.setText( - translate('ListLayout', 'Enable selection mode')) + translate("ListLayout", "Enable selection mode") + ) - self.goKeyLabel.setText(translate('ListLayout', 'Go key:')) - self.goActionLabel.setText(translate('ListLayout', 'Go action')) + self.goKeyLabel.setText(translate("ListLayout", "Go key:")) + self.goActionLabel.setText(translate("ListLayout", "Go action")) self.useFadeGroup.setTitle( - translate('ListLayout', 'Use fade (buttons)')) - self.stopCueFade.setText(translate('ListLayout', 'Stop Cue')) - self.pauseCueFade.setText(translate('ListLayout', 'Pause Cue')) - self.resumeCueFade.setText(translate('ListLayout', 'Resume Cue')) - self.interruptCueFade.setText(translate('ListLayout', 'Interrupt Cue')) + translate("ListLayout", "Use fade (buttons)") + ) + self.stopCueFade.setText(translate("ListLayout", "Stop Cue")) + self.pauseCueFade.setText(translate("ListLayout", "Pause Cue")) + self.resumeCueFade.setText(translate("ListLayout", "Resume Cue")) + self.interruptCueFade.setText(translate("ListLayout", "Interrupt Cue")) def loadSettings(self, settings): - self.showPlaying.setChecked(settings['show']['playingCues']) - self.showDbMeters.setChecked(settings['show']['dBMeters']) - self.showAccurate.setChecked(settings['show']['accurateTime']) - self.showSeek.setChecked(settings['show']['seekSliders']) - self.autoNext.setChecked(settings['autoContinue']) - self.selectionMode.setChecked(settings['selectionMode']) + self.showPlaying.setChecked(settings["show"]["playingCues"]) + self.showDbMeters.setChecked(settings["show"]["dBMeters"]) + self.showAccurate.setChecked(settings["show"]["accurateTime"]) + self.showSeek.setChecked(settings["show"]["seekSliders"]) + self.autoNext.setChecked(settings["autoContinue"]) + self.selectionMode.setChecked(settings["selectionMode"]) self.goKeyEdit.setKeySequence( - QKeySequence(settings['goKey'], QKeySequence.NativeText)) - self.goActionCombo.setCurrentItem(settings['goAction']) + QKeySequence(settings["goKey"], QKeySequence.NativeText) + ) + self.goActionCombo.setCurrentItem(settings["goAction"]) - self.stopCueFade.setChecked(settings['stopCueFade']) - self.pauseCueFade.setChecked(settings['pauseCueFade']) - self.resumeCueFade.setChecked(settings['resumeCueFade']) - self.interruptCueFade.setChecked(settings['interruptCueFade']) + self.stopCueFade.setChecked(settings["stopCueFade"]) + self.pauseCueFade.setChecked(settings["pauseCueFade"]) + self.resumeCueFade.setChecked(settings["resumeCueFade"]) + self.interruptCueFade.setChecked(settings["interruptCueFade"]) def getSettings(self): return { - 'show': { - 'accurateTime': self.showAccurate.isChecked(), - 'playingCues': self.showPlaying.isChecked(), - 'dBMeters': self.showDbMeters.isChecked(), - 'seekBars': self.showSeek.isChecked() + "show": { + "accurateTime": self.showAccurate.isChecked(), + "playingCues": self.showPlaying.isChecked(), + "dBMeters": self.showDbMeters.isChecked(), + "seekBars": self.showSeek.isChecked(), }, - 'autoContinue': self.autoNext.isChecked(), - 'selectionMode': self.selectionMode.isChecked(), - - 'goKey': self.goKeyEdit.keySequence().toString( - QKeySequence.NativeText), - 'goAction': self.goActionCombo.currentItem(), - - 'stopCueFade': self.stopCueFade.isChecked(), - 'pauseCueFade': self.pauseCueFade.isChecked(), - 'resumeCueFade': self.resumeCueFade.isChecked(), - 'interruptCueFade': self.interruptCueFade.isChecked() + "autoContinue": self.autoNext.isChecked(), + "selectionMode": self.selectionMode.isChecked(), + "goKey": self.goKeyEdit.keySequence().toString( + QKeySequence.NativeText + ), + "goAction": self.goActionCombo.currentItem(), + "stopCueFade": self.stopCueFade.isChecked(), + "pauseCueFade": self.pauseCueFade.isChecked(), + "resumeCueFade": self.resumeCueFade.isChecked(), + "interruptCueFade": self.interruptCueFade.isChecked(), } diff --git a/lisp/plugins/list_layout/view.py b/lisp/plugins/list_layout/view.py index a2bf00675..f2b6c2c0b 100644 --- a/lisp/plugins/list_layout/view.py +++ b/lisp/plugins/list_layout/view.py @@ -35,12 +35,12 @@ def __init__(self, listModel, runModel, config, *args): self.listModel = listModel # GO-BUTTON (top-left) - self.goButton = QPushButton('GO', self) + self.goButton = QPushButton("GO", self) self.goButton.setFocusPolicy(Qt.NoFocus) self.goButton.setFixedWidth(120) self.goButton.setFixedHeight(100) self.goButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) - self.goButton.setStyleSheet('font-size: 48pt;') + self.goButton.setStyleSheet("font-size: 48pt;") self.layout().addWidget(self.goButton, 0, 0) # INFO PANEL (top-center) diff --git a/lisp/plugins/media_info/__init__.py b/lisp/plugins/media_info/__init__.py index 1b7aaada3..0202f7204 100644 --- a/lisp/plugins/media_info/__init__.py +++ b/lisp/plugins/media_info/__init__.py @@ -1 +1 @@ -from .media_info import MediaInfo \ No newline at end of file +from .media_info import MediaInfo diff --git a/lisp/plugins/media_info/media_info.py b/lisp/plugins/media_info/media_info.py index 8c2748472..6d22794fd 100644 --- a/lisp/plugins/media_info/media_info.py +++ b/lisp/plugins/media_info/media_info.py @@ -20,34 +20,45 @@ from urllib.request import unquote from PyQt5 import QtCore -from PyQt5.QtWidgets import QMessageBox, QDialog, QVBoxLayout, \ - QTreeWidget, QAbstractItemView, QDialogButtonBox, QTreeWidgetItem, \ - QHeaderView +from PyQt5.QtWidgets import ( + QMessageBox, + QDialog, + QVBoxLayout, + QTreeWidget, + QAbstractItemView, + QDialogButtonBox, + QTreeWidgetItem, + QHeaderView, +) from lisp.core.plugin import Plugin from lisp.cues.media_cue import MediaCue from lisp.layout.cue_layout import CueLayout -from lisp.layout.cue_menu import MenuActionsGroup, SimpleMenuAction, MENU_PRIORITY_PLUGIN -from lisp.plugins.gst_backend.gst_utils import gst_uri_metadata, \ - gst_parse_tags_list +from lisp.layout.cue_menu import ( + MenuActionsGroup, + SimpleMenuAction, + MENU_PRIORITY_PLUGIN, +) +from lisp.plugins.gst_backend.gst_utils import ( + gst_uri_metadata, + gst_parse_tags_list, +) from lisp.ui.ui_utils import translate class MediaInfo(Plugin): - Name = 'Media-Cue information dialog' - Authors = ('Francesco Ceruti', ) - Description = 'Provide a function to show information on media-cues source' + Name = "Media-Cue information dialog" + Authors = ("Francesco Ceruti",) + Description = "Provide a function to show information on media-cues source" def __init__(self, app): super().__init__(app) - self.cue_action_group = MenuActionsGroup( - priority=MENU_PRIORITY_PLUGIN) + self.cue_action_group = MenuActionsGroup(priority=MENU_PRIORITY_PLUGIN) self.cue_action_group.add( SimpleMenuAction( - translate('MediaInfo', 'Media Info'), - self._show_info, + translate("MediaInfo", "Media Info"), self._show_info ) ) @@ -59,34 +70,38 @@ def _show_info(self, cue): if media_uri is None: QMessageBox.warning( self.app.window, - translate('MediaInfo', 'Warning'), - translate('MediaInfo', 'No info to display') + translate("MediaInfo", "Warning"), + translate("MediaInfo", "No info to display"), ) else: - if media_uri.startswith('file://'): - media_uri = 'file://' + self.app.session.abs_path(media_uri[7:]) + if media_uri.startswith("file://"): + media_uri = "file://" + self.app.session.abs_path(media_uri[7:]) gst_info = gst_uri_metadata(media_uri) - info = {'URI': unquote(gst_info.get_uri())} + info = {"URI": unquote(gst_info.get_uri())} # Audio streams info for stream in gst_info.get_audio_streams(): name = stream.get_stream_type_nick().capitalize() info[name] = { - 'Bitrate': str(stream.get_bitrate() // 1000) + ' Kb/s', - 'Channels': str(stream.get_channels()), - 'Sample rate': str(stream.get_sample_rate()) + ' Hz', - 'Sample size': str(stream.get_depth()) + ' bit' + "Bitrate": str(stream.get_bitrate() // 1000) + " Kb/s", + "Channels": str(stream.get_channels()), + "Sample rate": str(stream.get_sample_rate()) + " Hz", + "Sample size": str(stream.get_depth()) + " bit", } # Video streams info for stream in gst_info.get_video_streams(): name = stream.get_stream_type_nick().capitalize() info[name] = { - 'Height': str(stream.get_height()) + ' px', - 'Width': str(stream.get_width()) + ' px', - 'Framerate': str(round(stream.get_framerate_num() / - stream.get_framerate_denom())) + "Height": str(stream.get_height()) + " px", + "Width": str(stream.get_width()) + " px", + "Framerate": str( + round( + stream.get_framerate_num() + / stream.get_framerate_denom() + ) + ), } # Tags @@ -102,7 +117,7 @@ def _show_info(self, cue): tags[name.capitalize()] = tag_txt if tags: - info['Tags'] = tags + info["Tags"] = tags # Show the dialog dialog = InfoDialog(self.app.window, info, cue.name) @@ -114,7 +129,8 @@ def __init__(self, parent, info, title): super().__init__(parent) self.setWindowTitle( - translate('MediaInfo', 'Media Info') + ' - ' + title) + translate("MediaInfo", "Media Info") + " - " + title + ) self.setWindowModality(QtCore.Qt.ApplicationModal) self.setMinimumSize(600, 300) self.resize(600, 300) @@ -123,13 +139,15 @@ def __init__(self, parent, info, title): self.infoTree = QTreeWidget(self) self.infoTree.setColumnCount(2) self.infoTree.setHeaderLabels( - [translate('MediaInfo', 'Info'), translate('MediaInfo', 'Value')]) + [translate("MediaInfo", "Info"), translate("MediaInfo", "Value")] + ) self.infoTree.setAlternatingRowColors(True) self.infoTree.setSelectionMode(QAbstractItemView.NoSelection) self.infoTree.setEditTriggers(QAbstractItemView.NoEditTriggers) self.infoTree.header().setStretchLastSection(False) self.infoTree.header().setSectionResizeMode( - QHeaderView.ResizeToContents) + QHeaderView.ResizeToContents + ) self.layout().addWidget(self.infoTree) self.buttonBox = QDialogButtonBox(self) diff --git a/lisp/plugins/midi/__init__.py b/lisp/plugins/midi/__init__.py index 4b38fbddb..18cbe0d7a 100644 --- a/lisp/plugins/midi/__init__.py +++ b/lisp/plugins/midi/__init__.py @@ -1 +1 @@ -from .midi import Midi \ No newline at end of file +from .midi import Midi diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index 8d522da99..a28a66d8e 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -29,37 +29,38 @@ class Midi(Plugin): """Provide MIDI I/O functionality""" - Name = 'MIDI' - Authors = ('Francesco Ceruti', ) - Description = 'Provide MIDI I/O functionality' + Name = "MIDI" + Authors = ("Francesco Ceruti",) + Description = "Provide MIDI I/O functionality" def __init__(self, app): super().__init__(app) # Register the settings widget AppConfigurationDialog.registerSettingsPage( - 'plugins.midi', MIDISettings, Midi.Config) + "plugins.midi", MIDISettings, Midi.Config + ) # Load the backend and set it as current mido backend - self.backend = mido.Backend(Midi.Config['backend'], load=True) + self.backend = mido.Backend(Midi.Config["backend"], load=True) mido.set_backend(self.backend) # Create default I/O and open the ports/devices - self.__input = MIDIInput( - self._input_name(Midi.Config['inputDevice'])) + self.__input = MIDIInput(self._input_name(Midi.Config["inputDevice"])) self.__input.open() self.__output = MIDIOutput( - self._output_name(Midi.Config['outputDevice'])) + self._output_name(Midi.Config["outputDevice"]) + ) self.__output.open() Midi.Config.changed.connect(self.__config_change) Midi.Config.updated.connect(self.__config_update) def __config_change(self, key, value): - if key == 'inputDevice': + if key == "inputDevice": self.__input.change_port(self._input_name(value)) - elif key == 'outputDevice': + elif key == "outputDevice": self.__output.change_port(self._output_name(value)) def __config_update(self, diff): diff --git a/lisp/plugins/midi/midi_input.py b/lisp/plugins/midi/midi_input.py index 4f96e5bab..568ab1a85 100644 --- a/lisp/plugins/midi/midi_input.py +++ b/lisp/plugins/midi/midi_input.py @@ -32,7 +32,8 @@ def __init__(self, port_name=None): def open(self): self._port = mido_backend().open_input( - name=self._port_name, callback=self.__new_message) + name=self._port_name, callback=self.__new_message + ) def __new_message(self, message): if self.alternate_mode: diff --git a/lisp/plugins/midi/midi_settings.py b/lisp/plugins/midi/midi_settings.py index d7040ca6c..500d45bc6 100644 --- a/lisp/plugins/midi/midi_settings.py +++ b/lisp/plugins/midi/midi_settings.py @@ -18,8 +18,13 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QComboBox, QGridLayout, \ - QLabel +from PyQt5.QtWidgets import ( + QGroupBox, + QVBoxLayout, + QComboBox, + QGridLayout, + QLabel, +) from lisp.plugins.midi.midi_utils import mido_backend from lisp.ui.settings.pages import SettingsPage @@ -27,7 +32,7 @@ class MIDISettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'MIDI settings') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "MIDI settings") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -36,18 +41,21 @@ def __init__(self, **kwargs): self.midiGroup = QGroupBox(self) self.midiGroup.setTitle( - translate('MIDISettings', 'MIDI default devices')) + translate("MIDISettings", "MIDI default devices") + ) self.midiGroup.setLayout(QGridLayout()) self.layout().addWidget(self.midiGroup) self.inputLabel = QLabel( - translate('MIDISettings', 'Input'), self.midiGroup) + translate("MIDISettings", "Input"), self.midiGroup + ) self.midiGroup.layout().addWidget(self.inputLabel, 0, 0) self.inputCombo = QComboBox(self.midiGroup) self.midiGroup.layout().addWidget(self.inputCombo, 0, 1) self.outputLabel = QLabel( - translate('MIDISettings', 'Output'), self.midiGroup) + translate("MIDISettings", "Output"), self.midiGroup + ) self.midiGroup.layout().addWidget(self.outputLabel, 1, 0) self.outputCombo = QComboBox(self.midiGroup) self.midiGroup.layout().addWidget(self.outputCombo, 1, 1) @@ -62,10 +70,10 @@ def __init__(self, **kwargs): def loadSettings(self, settings): # TODO: instead of forcing 'Default' add a warning label and keep the invalid device as an option - self.inputCombo.setCurrentText('Default') - self.inputCombo.setCurrentText(settings['inputDevice']) - self.outputCombo.setCurrentText('Default') - self.outputCombo.setCurrentText(settings['outputDevice']) + self.inputCombo.setCurrentText("Default") + self.inputCombo.setCurrentText(settings["inputDevice"]) + self.outputCombo.setCurrentText("Default") + self.outputCombo.setCurrentText(settings["outputDevice"]) def getSettings(self): if self.isEnabled(): @@ -73,8 +81,8 @@ def getSettings(self): output = self.outputCombo.currentText() return { - 'inputDevice': '' if input == 'Default' else input, - 'outputDevice': '' if output == 'Default' else output + "inputDevice": "" if input == "Default" else input, + "outputDevice": "" if output == "Default" else output, } return {} @@ -83,9 +91,9 @@ def _loadDevices(self): backend = mido_backend() self.inputCombo.clear() - self.inputCombo.addItems(['Default']) + self.inputCombo.addItems(["Default"]) self.inputCombo.addItems(backend.get_input_names()) self.outputCombo.clear() - self.outputCombo.addItems(['Default']) + self.outputCombo.addItems(["Default"]) self.outputCombo.addItems(backend.get_output_names()) diff --git a/lisp/plugins/midi/midi_utils.py b/lisp/plugins/midi/midi_utils.py index 866edcc0d..d9d9a7849 100644 --- a/lisp/plugins/midi/midi_utils.py +++ b/lisp/plugins/midi/midi_utils.py @@ -19,6 +19,7 @@ import mido + def str_msg_to_dict(str_message): return mido.parse_string(str_message).dict() @@ -32,11 +33,10 @@ def mido_backend(): """Return the current backend object, or None""" backend = None - if hasattr(mido, 'backend'): + if hasattr(mido, "backend"): backend = mido.backend if backend is None: - raise RuntimeError('MIDI backend not loaded') + raise RuntimeError("MIDI backend not loaded") return backend - diff --git a/lisp/plugins/network/__init__.py b/lisp/plugins/network/__init__.py index 8779e25f1..28d2c30b5 100644 --- a/lisp/plugins/network/__init__.py +++ b/lisp/plugins/network/__init__.py @@ -1 +1 @@ -from .network import Network \ No newline at end of file +from .network import Network diff --git a/lisp/plugins/network/api/__init__.py b/lisp/plugins/network/api/__init__.py index 2b02cb086..a5bd1cb90 100644 --- a/lisp/plugins/network/api/__init__.py +++ b/lisp/plugins/network/api/__init__.py @@ -9,10 +9,10 @@ def route_all(app, api): for name, module in ModulesLoader(__package__, path.dirname(__file__)): - for endpoint in getattr(module, '__endpoints__', ()): + for endpoint in getattr(module, "__endpoints__", ()): api.add_route(endpoint.UriTemplate, endpoint(app)) logger.debug( - translate( - 'NetworkApiDebug','New end-point: {}' - ).format(endpoint.UriTemplate) + translate("NetworkApiDebug", "New end-point: {}").format( + endpoint.UriTemplate + ) ) diff --git a/lisp/plugins/network/api/cues.py b/lisp/plugins/network/api/cues.py index 85b62b462..2d1f62163 100644 --- a/lisp/plugins/network/api/cues.py +++ b/lisp/plugins/network/api/cues.py @@ -35,17 +35,15 @@ def resolve_cue(app, cue_id): class CuesListEndPoint(EndPoint): - UriTemplate = '/cues' + UriTemplate = "/cues" def on_get(self, request, response): response.status = falcon.HTTP_OK - response.body = json.dumps({ - 'cues': tuple(self.app.cue_model.keys()) - }) + response.body = json.dumps({"cues": tuple(self.app.cue_model.keys())}) class CueEndPoint(EndPoint): - UriTemplate = '/cues/{cue_id}' + UriTemplate = "/cues/{cue_id}" def on_get(self, request, response, cue_id): cue = resolve_cue(self.app, cue_id) @@ -55,36 +53,41 @@ def on_get(self, request, response, cue_id): class CueActionEndPoint(EndPoint): - UriTemplate = '/cues/{cue_id}/action' + UriTemplate = "/cues/{cue_id}/action" def on_post(self, request, response, cue_id): cue = resolve_cue(self.app, cue_id) try: data = json.load(request.stream) - action = CueAction(data['action']) + action = CueAction(data["action"]) cue.execute(action=action) response.status = falcon.HTTP_CREATED - except(KeyError, json.JSONDecodeError): + except (KeyError, json.JSONDecodeError): response.status = falcon.HTTP_BAD_REQUEST class CueStateEndPoint(EndPoint): - UriTemplate = '/cues/{cue_id}/state' + UriTemplate = "/cues/{cue_id}/state" def on_get(self, request, response, cue_id): cue = resolve_cue(self.app, cue_id) response.status = falcon.HTTP_OK - response.body = json.dumps({ - 'state': cue.state, - 'current_time': cue.current_time(), - 'prewait_time': cue.prewait_time(), - 'postwait_time': cue.postwait_time() - }) + response.body = json.dumps( + { + "state": cue.state, + "current_time": cue.current_time(), + "prewait_time": cue.prewait_time(), + "postwait_time": cue.postwait_time(), + } + ) __endpoints__ = ( - CueEndPoint, CueActionEndPoint, CuesListEndPoint, CueStateEndPoint + CueEndPoint, + CueActionEndPoint, + CuesListEndPoint, + CueStateEndPoint, ) diff --git a/lisp/plugins/network/api/layout.py b/lisp/plugins/network/api/layout.py index f3e5c759f..a419f071a 100644 --- a/lisp/plugins/network/api/layout.py +++ b/lisp/plugins/network/api/layout.py @@ -26,17 +26,17 @@ class LayoutActionEndPoint(EndPoint): - UriTemplate = '/layout/action' + UriTemplate = "/layout/action" def on_post(self, req, resp): try: data = json.load(req.stream) - action = CueAction(data['action']) + action = CueAction(data["action"]) self.app.layout.execute_all(action, quiet=True) resp.status = falcon.HTTP_CREATED - except(KeyError, json.JSONDecodeError): + except (KeyError, json.JSONDecodeError): resp.status = falcon.HTTP_BAD_REQUEST -__endpoints__ = (LayoutActionEndPoint, ) +__endpoints__ = (LayoutActionEndPoint,) diff --git a/lisp/plugins/network/discovery.py b/lisp/plugins/network/discovery.py index 26043c669..3fe4f21bb 100644 --- a/lisp/plugins/network/discovery.py +++ b/lisp/plugins/network/discovery.py @@ -34,7 +34,7 @@ class Announcer(Thread): def __init__(self, ip, port, magic): super().__init__(daemon=True) self.server = socketserver.UDPServer((ip, port), AnnouncerUDPHandler) - self.server.bytes_magic = bytes(magic, 'utf-8') + self.server.bytes_magic = bytes(magic, "utf-8") def run(self): with self.server: @@ -46,7 +46,6 @@ def stop(self): class AnnouncerUDPHandler(socketserver.BaseRequestHandler): - def handle(self): data = self.request[0].strip() socket = self.request[1] @@ -62,11 +61,13 @@ class Discoverer(Thread): of attempts to find active (announced) host on the network. """ - def __init__(self, port, magic, max_attempts=3, timeout=0.5, target=''): + def __init__( + self, port, magic, max_attempts=3, timeout=0.5, target="" + ): super().__init__(daemon=True) self.address = (target, port) - self.bytes_magic = bytes(magic, 'utf-8') + self.bytes_magic = bytes(magic, "utf-8") self.max_attempts = max_attempts self.timeout = timeout diff --git a/lisp/plugins/network/discovery_dialogs.py b/lisp/plugins/network/discovery_dialogs.py index 03cda7e4e..86d082e64 100644 --- a/lisp/plugins/network/discovery_dialogs.py +++ b/lisp/plugins/network/discovery_dialogs.py @@ -18,8 +18,16 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QSize -from PyQt5.QtWidgets import QVBoxLayout, QListWidget, QDialogButtonBox, \ - QDialog, QHBoxLayout, QPushButton, QInputDialog, QListWidgetItem +from PyQt5.QtWidgets import ( + QVBoxLayout, + QListWidget, + QDialogButtonBox, + QDialog, + QHBoxLayout, + QPushButton, + QInputDialog, + QListWidgetItem, +) from lisp.core.signal import Connection from lisp.plugins.network.discovery import Discoverer @@ -58,11 +66,12 @@ def __init__(self, port, magic, **kwargs): self._discoverer = Discoverer(port, magic, max_attempts=10) self._discoverer.discovered.connect( - self._host_discovered, Connection.QtQueued) + self._host_discovered, Connection.QtQueued + ) self._discoverer.ended.connect(self._search_ended, Connection.QtQueued) def retranslateUi(self): - self.setWindowTitle(translate('NetworkDiscovery', 'Host discovery')) + self.setWindowTitle(translate("NetworkDiscovery", "Host discovery")) def accept(self): self._discoverer.stop() @@ -139,22 +148,26 @@ def __init__(self, hosts, discovery_port, discovery_magic, **kwargs): self.retranslateUi() def retranslateUi(self): - self.setWindowTitle( - translate('NetworkDiscovery', 'Manage hosts')) + self.setWindowTitle(translate("NetworkDiscovery", "Manage hosts")) self.discoverHostsButton.setText( - translate('NetworkDiscovery', 'Discover hosts')) + translate("NetworkDiscovery", "Discover hosts") + ) self.addHostButton.setText( - translate('NetworkDiscovery', 'Manually add a host')) + translate("NetworkDiscovery", "Manually add a host") + ) self.removeHostButton.setText( - translate('NetworkDiscovery', 'Remove selected host')) + translate("NetworkDiscovery", "Remove selected host") + ) self.removeAllButton.setText( - translate('NetworkDiscovery', 'Remove all host')) + translate("NetworkDiscovery", "Remove all host") + ) def addHostDialog(self): host, ok = QInputDialog.getText( self, - translate('NetworkDiscovery', 'Address'), - translate('NetworkDiscovery', 'Host IP')) + translate("NetworkDiscovery", "Address"), + translate("NetworkDiscovery", "Host IP"), + ) if ok: self.addHost(host) @@ -165,7 +178,8 @@ def addHost(self, host): def discoverHosts(self): dialog = HostDiscoveryDialog( - self.discovery_port, self.discovery_magic, parent=self) + self.discovery_port, self.discovery_magic, parent=self + ) if dialog.exec_() == dialog.Accepted: for host in dialog.hosts(): diff --git a/lisp/plugins/network/endpoint.py b/lisp/plugins/network/endpoint.py index 57d6d910a..5ce0fcfa3 100644 --- a/lisp/plugins/network/endpoint.py +++ b/lisp/plugins/network/endpoint.py @@ -19,7 +19,7 @@ class EndPoint: - UriTemplate = '' + UriTemplate = "" def __init__(self, application): """ diff --git a/lisp/plugins/network/network.py b/lisp/plugins/network/network.py index 63d068ab0..5cfbb7cc2 100644 --- a/lisp/plugins/network/network.py +++ b/lisp/plugins/network/network.py @@ -26,9 +26,9 @@ class Network(Plugin): - Name = 'Network' - Description = 'Allow the application to be controlled via network.' - Authors = ('Francesco Ceruti', ) + Name = "Network" + Description = "Allow the application to be controlled via network." + Authors = ("Francesco Ceruti",) def __init__(self, app): super().__init__(app) @@ -40,17 +40,15 @@ def __init__(self, app): # WSGI Server self.server = APIServerThread( - Network.Config['host'], - Network.Config['port'], - self.api + Network.Config["host"], Network.Config["port"], self.api ) self.server.start() # Announcer self.announcer = Announcer( - Network.Config['host'], - Network.Config['discovery.port'], - Network.Config['discovery.magic'] + Network.Config["host"], + Network.Config["discovery.port"], + Network.Config["discovery.magic"], ) self.announcer.start() diff --git a/lisp/plugins/network/server.py b/lisp/plugins/network/server.py index 46cdd888d..12c57e627 100644 --- a/lisp/plugins/network/server.py +++ b/lisp/plugins/network/server.py @@ -30,14 +30,15 @@ class APIServerThread(Thread): def __init__(self, host, port, api): super().__init__(daemon=True) self.wsgi_server = make_server( - host, port, api, handler_class=APIRequestHandler) + host, port, api, handler_class=APIRequestHandler + ) def run(self): try: logger.info( translate( - 'ApiServerInfo', - 'Start serving network API at: http://{}:{}/' + "ApiServerInfo", + "Start serving network API at: http://{}:{}/", ).format( self.wsgi_server.server_address[0], self.wsgi_server.server_address[1], @@ -46,10 +47,13 @@ def run(self): self.wsgi_server.serve_forever() - logger.info(translate('APIServerInfo', 'Stop serving network API')) + logger.info(translate("APIServerInfo", "Stop serving network API")) except Exception: - logger.exception(translate( - 'ApiServerError', 'Network API server stopped working.')) + logger.exception( + translate( + "ApiServerError", "Network API server stopped working." + ) + ) def stop(self): self.wsgi_server.shutdown() diff --git a/lisp/plugins/osc/osc.py b/lisp/plugins/osc/osc.py index b0a9a4995..aa1668121 100644 --- a/lisp/plugins/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -29,22 +29,21 @@ class Osc(Plugin): """Provide OSC I/O functionality""" - Name = 'OSC' - Authors = ('Thomas Achtner', ) - Description = 'Provide OSC I/O functionality' + Name = "OSC" + Authors = ("Thomas Achtner",) + Description = "Provide OSC I/O functionality" def __init__(self, app): super().__init__(app) # Register the settings widget AppConfigurationDialog.registerSettingsPage( - 'plugins.osc', OscSettings, Osc.Config) + "plugins.osc", OscSettings, Osc.Config + ) # Create a server instance self.__server = OscServer( - Osc.Config['hostname'], - Osc.Config['inPort'], - Osc.Config['outPort'] + Osc.Config["hostname"], Osc.Config["inPort"], Osc.Config["outPort"] ) self.__server.start() @@ -59,11 +58,11 @@ def terminate(self): self.__server.stop() def __config_change(self, key, value): - if key == 'hostname': + if key == "hostname": self.__server.hostname = value - elif key == 'inPort': + elif key == "inPort": self.__server.in_port = value - elif key == 'outPort': + elif key == "outPort": self.__server.out_port = value def __config_update(self, diff): diff --git a/lisp/plugins/osc/osc_delegate.py b/lisp/plugins/osc/osc_delegate.py index 0608d0dcd..a57b90dc2 100644 --- a/lisp/plugins/osc/osc_delegate.py +++ b/lisp/plugins/osc/osc_delegate.py @@ -18,15 +18,20 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QStyledItemDelegate, QSpinBox, QDoubleSpinBox, \ - QCheckBox, QLineEdit +from PyQt5.QtWidgets import ( + QStyledItemDelegate, + QSpinBox, + QDoubleSpinBox, + QCheckBox, + QLineEdit, +) from lisp.plugins.osc.osc_server import OscMessageType class OscArgumentDelegate(QStyledItemDelegate): - MIN = -1000000 - MAX = 1000000 + MIN = -1_000_000 + MAX = 1_000_000 DECIMALS = 4 def __init__(self, tag=OscMessageType.Int, **kwargs): diff --git a/lisp/plugins/osc/osc_server.py b/lisp/plugins/osc/osc_server.py index f5d45d1de..6a69d7188 100644 --- a/lisp/plugins/osc/osc_server.py +++ b/lisp/plugins/osc/osc_server.py @@ -31,10 +31,10 @@ class OscMessageType(Enum): - Int = 'Integer' - Float = 'Float' - Bool = 'Bool' - String = 'String' + Int = "Integer" + Float = "Float" + Bool = "Bool" + String = "String" class OscServer: @@ -93,13 +93,14 @@ def start(self): self.__running = True logger.info( - translate( - 'OscServerInfo', 'OSC server started at {}' - ).format(self.__srv.url) + translate("OscServerInfo", "OSC server started at {}").format( + self.__srv.url + ) ) except ServerError: logger.exception( - translate('OscServerError', 'Cannot start OSC sever')) + translate("OscServerError", "Cannot start OSC sever") + ) def stop(self): if self.__srv is not None: @@ -109,7 +110,7 @@ def stop(self): self.__running = False self.__srv.free() - logger.info(translate('OscServerInfo', 'OSC server stopped')) + logger.info(translate("OscServerInfo", "OSC server stopped")) def send(self, path, *args): with self.__lock: @@ -119,7 +120,6 @@ def send(self, path, *args): def __log_message(self, path, args, types, src, user_data): logger.debug( translate( - 'OscServerDebug', - 'Message from {} -> path: "{}" args: {}' + "OscServerDebug", 'Message from {} -> path: "{}" args: {}' ).format(src.get_url(), path, args) ) diff --git a/lisp/plugins/osc/osc_settings.py b/lisp/plugins/osc/osc_settings.py index fd33af2e0..d42bddf6f 100644 --- a/lisp/plugins/osc/osc_settings.py +++ b/lisp/plugins/osc/osc_settings.py @@ -19,15 +19,21 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt -from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel, \ - QHBoxLayout, QSpinBox, QLineEdit +from PyQt5.QtWidgets import ( + QVBoxLayout, + QGroupBox, + QLabel, + QHBoxLayout, + QSpinBox, + QLineEdit, +) from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate class OscSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'OSC settings') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "OSC settings") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -36,14 +42,14 @@ def __init__(self, **kwargs): self.groupBox = QGroupBox(self) self.groupBox.setLayout(QVBoxLayout()) - self.groupBox.setTitle(translate('OscSettings', 'OSC Settings')) + self.groupBox.setTitle(translate("OscSettings", "OSC Settings")) self.layout().addWidget(self.groupBox) hbox = QHBoxLayout() self.inportBox = QSpinBox(self) self.inportBox.setMinimum(1000) self.inportBox.setMaximum(99999) - label = QLabel(translate('OscSettings', 'Input Port:')) + label = QLabel(translate("OscSettings", "Input Port:")) hbox.layout().addWidget(label) hbox.layout().addWidget(self.inportBox) self.groupBox.layout().addLayout(hbox) @@ -52,28 +58,28 @@ def __init__(self, **kwargs): self.outportBox = QSpinBox(self) self.outportBox.setMinimum(1000) self.outportBox.setMaximum(99999) - label = QLabel(translate('OscSettings', 'Output Port:')) + label = QLabel(translate("OscSettings", "Output Port:")) hbox.layout().addWidget(label) hbox.layout().addWidget(self.outportBox) self.groupBox.layout().addLayout(hbox) hbox = QHBoxLayout() self.hostnameEdit = QLineEdit() - self.hostnameEdit.setText('localhost') + self.hostnameEdit.setText("localhost") self.hostnameEdit.setMaximumWidth(200) - label = QLabel(translate('OscSettings', 'Hostname:')) + label = QLabel(translate("OscSettings", "Hostname:")) hbox.layout().addWidget(label) hbox.layout().addWidget(self.hostnameEdit) self.groupBox.layout().addLayout(hbox) def getSettings(self): return { - 'inPort': self.inportBox.value(), - 'outPort': self.outportBox.value(), - 'hostname': self.hostnameEdit.text() + "inPort": self.inportBox.value(), + "outPort": self.outportBox.value(), + "hostname": self.hostnameEdit.text(), } def loadSettings(self, settings): - self.inportBox.setValue(settings['inPort']) - self.outportBox.setValue(settings['outPort']) - self.hostnameEdit.setText(settings['hostname']) + self.inportBox.setValue(settings["inPort"]) + self.outportBox.setValue(settings["outPort"]) + self.hostnameEdit.setText(settings["hostname"]) diff --git a/lisp/plugins/presets/__init__.py b/lisp/plugins/presets/__init__.py index bc440914d..c139eac0b 100644 --- a/lisp/plugins/presets/__init__.py +++ b/lisp/plugins/presets/__init__.py @@ -1 +1 @@ -from .presets import Presets \ No newline at end of file +from .presets import Presets diff --git a/lisp/plugins/presets/lib.py b/lisp/plugins/presets/lib.py index 635559b83..ce742b517 100644 --- a/lisp/plugins/presets/lib.py +++ b/lisp/plugins/presets/lib.py @@ -30,7 +30,7 @@ except ImportError: from scandir import scandir -PRESETS_DIR = os.path.join(USER_DIR, 'presets') +PRESETS_DIR = os.path.join(USER_DIR, "presets") def preset_path(name): @@ -72,8 +72,7 @@ def load_on_cue(preset_name, cue): :param cue: The cue on which load the preset :type cue: lisp.cue.Cue """ - MainActionsHandler.do_action( - UpdateCueAction(load_preset(preset_name), cue)) + MainActionsHandler.do_action(UpdateCueAction(load_preset(preset_name), cue)) def load_on_cues(preset_name, cues): @@ -86,7 +85,8 @@ def load_on_cues(preset_name, cues): :type cues: typing.Iterable[lisp.cue.Cue] """ MainActionsHandler.do_action( - UpdateCuesAction(load_preset(preset_name), cues)) + UpdateCuesAction(load_preset(preset_name), cues) + ) def load_preset(name): @@ -98,7 +98,7 @@ def load_preset(name): """ path = preset_path(name) - with open(path, mode='r') as in_file: + with open(path, mode="r") as in_file: return json.load(in_file) @@ -114,7 +114,7 @@ def write_preset(name, preset): """ path = preset_path(name) - with open(path, mode='w') as out_file: + with open(path, mode="w") as out_file: json.dump(preset, out_file) @@ -163,10 +163,10 @@ def export_presets(names, archive): """ if names: try: - with ZipFile(archive, mode='w') as archive: + with ZipFile(archive, mode="w") as archive: for name in names: archive.write(preset_path(name), name) - except(OSError, BadZipFile) as e: + except (OSError, BadZipFile) as e: raise PresetExportError(str(e)) @@ -183,7 +183,7 @@ def import_presets(archive, overwrite=True): for member in archive.namelist(): if not (preset_exists(member) and not overwrite): archive.extract(member, path=PRESETS_DIR) - except(OSError, BadZipFile) as e: + except (OSError, BadZipFile) as e: raise PresetImportError(str(e)) @@ -199,7 +199,7 @@ def import_has_conflicts(archive): for member in archive.namelist(): if preset_exists(member): return True - except(OSError, BadZipFile) as e: + except (OSError, BadZipFile) as e: raise PresetImportError(str(e)) return False @@ -218,7 +218,7 @@ def import_conflicts(archive): for member in archive.namelist(): if preset_exists(member): conflicts.append(member) - except(OSError, BadZipFile) as e: + except (OSError, BadZipFile) as e: raise PresetImportError(str(e)) return conflicts diff --git a/lisp/plugins/presets/presets.py b/lisp/plugins/presets/presets.py index 65b862a47..a55defda2 100644 --- a/lisp/plugins/presets/presets.py +++ b/lisp/plugins/presets/presets.py @@ -23,20 +23,35 @@ from lisp.core.plugin import Plugin from lisp.layout.cue_layout import CueLayout -from lisp.layout.cue_menu import MenuActionsGroup, SimpleMenuAction, MENU_PRIORITY_PLUGIN -from lisp.plugins.presets.lib import PRESETS_DIR, load_on_cue, preset_exists, load_on_cues -from lisp.plugins.presets.presets_ui import select_preset_dialog, \ - PresetsDialog, save_preset_dialog, check_override_dialog, write_preset, \ - load_preset_error, write_preset_error +from lisp.layout.cue_menu import ( + MenuActionsGroup, + SimpleMenuAction, + MENU_PRIORITY_PLUGIN, +) +from lisp.plugins.presets.lib import ( + PRESETS_DIR, + load_on_cue, + preset_exists, + load_on_cues, +) +from lisp.plugins.presets.presets_ui import ( + select_preset_dialog, + PresetsDialog, + save_preset_dialog, + check_override_dialog, + write_preset, + load_preset_error, + write_preset_error, +) from lisp.ui.ui_utils import translate # TODO: use logging to report errors class Presets(Plugin): - Name = 'Preset' - Authors = ('Francesco Ceruti', ) - Description = 'Allow to save, edit, import and export cue presets' + Name = "Preset" + Authors = ("Francesco Ceruti",) + Description = "Allow to save, edit, import and export cue presets" def __init__(self, app): super().__init__(app) @@ -47,27 +62,26 @@ def __init__(self, app): # Entry in mainWindow menu self.manageAction = QAction(self.app.window) self.manageAction.triggered.connect(self.__edit_presets) - self.manageAction.setText(translate('Presets', 'Presets')) + self.manageAction.setText(translate("Presets", "Presets")) self.app.window.menuTools.addAction(self.manageAction) # Cue menu (context-action) self.cueActionsGroup = MenuActionsGroup( submenu=True, - text=translate('Presets', 'Presets'), + text=translate("Presets", "Presets"), priority=MENU_PRIORITY_PLUGIN, ) self.cueActionsGroup.add( SimpleMenuAction( - translate('Presets', 'Load on cue'), + translate("Presets", "Load on cue"), self.__load_on_cue, - translate('Presets', 'Load on selected cues'), - self.__load_on_cues + translate("Presets", "Load on selected cues"), + self.__load_on_cues, ), SimpleMenuAction( - translate('Presets', 'Save as preset'), - self.__create_from_cue - ) + translate("Presets", "Save as preset"), self.__create_from_cue + ), ) CueLayout.CuesMenu.add(self.cueActionsGroup) @@ -100,8 +114,8 @@ def __create_from_cue(self, cue): preset = cue.properties(defaults=False) # Discard id and index - preset.pop('id') - preset.pop('index') + preset.pop("id") + preset.pop("index") try: write_preset(name, preset) diff --git a/lisp/plugins/presets/presets_ui.py b/lisp/plugins/presets/presets_ui.py index 59cf8da6b..fb852f3ee 100644 --- a/lisp/plugins/presets/presets_ui.py +++ b/lisp/plugins/presets/presets_ui.py @@ -18,17 +18,40 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QComboBox, QDialog, QInputDialog, QMessageBox, \ - QListWidget, QPushButton, QVBoxLayout, QGridLayout, QDialogButtonBox, \ - QWidget, QLabel, QLineEdit, QFileDialog, QHBoxLayout +from PyQt5.QtWidgets import ( + QComboBox, + QDialog, + QInputDialog, + QMessageBox, + QListWidget, + QPushButton, + QVBoxLayout, + QGridLayout, + QDialogButtonBox, + QWidget, + QLabel, + QLineEdit, + QFileDialog, + QHBoxLayout, +) from lisp.core.util import natural_keys from lisp.cues.cue import Cue from lisp.cues.cue_factory import CueFactory -from lisp.plugins.presets.lib import preset_exists, export_presets, \ - import_presets, import_has_conflicts, PresetExportError, \ - PresetImportError, scan_presets, delete_preset, write_preset, \ - rename_preset, load_preset, load_on_cues +from lisp.plugins.presets.lib import ( + preset_exists, + export_presets, + import_presets, + import_has_conflicts, + PresetExportError, + PresetImportError, + scan_presets, + delete_preset, + write_preset, + rename_preset, + load_preset, + load_on_cues, +) from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.cue_settings import CueSettingsDialog, CueSettingsRegistry from lisp.ui.ui_utils import translate @@ -37,50 +60,45 @@ def preset_error(exception, text, parent=None): QDetailedMessageBox.dcritical( - translate('Presets', 'Presets'), - text, - str(exception), - parent=parent, + translate("Presets", "Presets"), text, str(exception), parent=parent ) def scan_presets_error(exception, parent=None): preset_error( - exception, - translate('Presets', 'Cannot scan presets'), - parent=parent + exception, translate("Presets", "Cannot scan presets"), parent=parent ) def delete_preset_error(exception, name, parent=None): preset_error( exception, - translate('Presets', 'Error while deleting preset "{}"').format(name), - parent=parent + translate("Presets", 'Error while deleting preset "{}"').format(name), + parent=parent, ) def load_preset_error(exception, name, parent=None): preset_error( exception, - translate('Presets', 'Cannot load preset "{}"').format(name), - parent=parent + translate("Presets", 'Cannot load preset "{}"').format(name), + parent=parent, ) def write_preset_error(exception, name, parent=None): preset_error( exception, - translate('Presets', 'Cannot save preset "{}"').format(name), - parent=parent + translate("Presets", 'Cannot save preset "{}"').format(name), + parent=parent, ) def rename_preset_error(exception, name, parent=None): preset_error( exception, - translate('Presets', 'Cannot rename preset "{}"').format(name), - parent=parent + translate("Presets", 'Cannot rename preset "{}"').format(name), + parent=parent, ) @@ -90,9 +108,7 @@ def select_preset_dialog(): if presets: item, confirm = QInputDialog.getItem( - MainWindow(), - translate('Presets', 'Select Preset'), '', - presets + MainWindow(), translate("Presets", "Select Preset"), "", presets ) if confirm: @@ -104,23 +120,23 @@ def select_preset_dialog(): def check_override_dialog(preset_name): answer = QMessageBox.question( MainWindow(), - translate('Presets', 'Presets'), + translate("Presets", "Presets"), translate( - 'Presets', 'Preset "{}" already exists, overwrite?' - .format(preset_name) + "Presets", + 'Preset "{}" already exists, overwrite?'.format(preset_name), ), - buttons=QMessageBox.Yes | QMessageBox.Cancel + buttons=QMessageBox.Yes | QMessageBox.Cancel, ) return answer == QMessageBox.Yes -def save_preset_dialog(base_name=''): +def save_preset_dialog(base_name=""): name, confirm = QInputDialog.getText( MainWindow(), - translate('Presets', 'Presets'), - translate('Presets', 'Preset name'), - text=base_name + translate("Presets", "Presets"), + translate("Presets", "Preset name"), + text=base_name, ) if confirm: @@ -131,7 +147,7 @@ class PresetsDialog(QDialog): def __init__(self, app, **kwargs): super().__init__(**kwargs) self.app = app - + self.resize(500, 400) self.setMaximumSize(self.size()) self.setMinimumSize(self.size()) @@ -209,14 +225,18 @@ def __init__(self, app, **kwargs): self.__selection_changed() def retranslateUi(self): - self.addPresetButton.setText(translate('Presets', 'Add')) - self.renamePresetButton.setText(translate('Presets', 'Rename')) - self.editPresetButton.setText(translate('Presets', 'Edit')) - self.removePresetButton.setText(translate('Presets', 'Remove')) - self.cueFromSelectedButton.setText(translate('Preset', 'Create Cue')) - self.loadOnSelectedButton.setText(translate('Preset', 'Load on selected Cues')) - self.exportSelectedButton.setText(translate('Presets', 'Export selected')) - self.importButton.setText(translate('Presets', 'Import')) + self.addPresetButton.setText(translate("Presets", "Add")) + self.renamePresetButton.setText(translate("Presets", "Rename")) + self.editPresetButton.setText(translate("Presets", "Edit")) + self.removePresetButton.setText(translate("Presets", "Remove")) + self.cueFromSelectedButton.setText(translate("Preset", "Create Cue")) + self.loadOnSelectedButton.setText( + translate("Preset", "Load on selected Cues") + ) + self.exportSelectedButton.setText( + translate("Presets", "Export selected") + ) + self.importButton.setText(translate("Presets", "Import")) def __populate(self): self.presetsList.clear() @@ -242,7 +262,7 @@ def __add_preset(self): exists = preset_exists(preset_name) if not (exists and not check_override_dialog(preset_name)): try: - write_preset(preset_name, {'_type_': cue_type}) + write_preset(preset_name, {"_type_": cue_type}) if not exists: self.presetsList.addItem(preset_name) except OSError as e: @@ -256,8 +276,8 @@ def __rename_preset(self): if preset_exists(new_name): QMessageBox.warning( self, - translate('Presets', 'Warning'), - translate('Presets', 'The same name is already used!') + translate("Presets", "Warning"), + translate("Presets", "The same name is already used!"), ) else: try: @@ -273,7 +293,7 @@ def __edit_preset(self): preset = load_preset(item.text()) if preset is not None: try: - cue_class = CueFactory.create_cue(preset.get('_type_')) + cue_class = CueFactory.create_cue(preset.get("_type_")) cue_class = cue_class.__class__ except Exception: cue_class = Cue @@ -293,17 +313,19 @@ def __cue_from_preset(self, preset_name): try: preset = load_preset(preset_name) if preset is not None: - if CueFactory.has_factory(preset.get('_type_')): - cue = CueFactory.create_cue(preset['_type_']) + if CueFactory.has_factory(preset.get("_type_")): + cue = CueFactory.create_cue(preset["_type_"]) cue.update_properties(preset) self.app.cue_model.add(cue) else: QMessageBox.warning( self, - translate('Presets', 'Warning'), - translate('Presets', 'Cannot create a cue from this ' - 'preset: {}').format(preset_name) + translate("Presets", "Warning"), + translate( + "Presets", + "Cannot create a cue from this " "preset: {}", + ).format(preset_name), ) except OSError as e: load_preset_error(e, preset_name, parent=self) @@ -326,35 +348,36 @@ def __load_on_selected(self): def __export_presets(self): names = [item.text() for item in self.presetsList.selectedItems()] archive, _ = QFileDialog.getSaveFileName( - self, - directory='archive.presets', - filter='*.presets' + self, directory="archive.presets", filter="*.presets" ) - if archive != '': - if not archive.endswith('.presets'): - archive += '.presets' + if archive != "": + if not archive.endswith(".presets"): + archive += ".presets" try: export_presets(names, archive) except PresetExportError as e: QDetailedMessageBox.dcritical( - translate('Presets', 'Presets'), - translate('Presets', 'Cannot export correctly.'), + translate("Presets", "Presets"), + translate("Presets", "Cannot export correctly."), str(e), - parent=self + parent=self, ) def __import_presets(self): - archive, _ = QFileDialog.getOpenFileName(self, filter='*.presets') - if archive != '': + archive, _ = QFileDialog.getOpenFileName(self, filter="*.presets") + if archive != "": try: if import_has_conflicts(archive): answer = QMessageBox.question( self, - translate('Presets', 'Presets'), - translate('Presets', 'Some presets already exists, ' - 'overwrite?'), - buttons=QMessageBox.Yes | QMessageBox.Cancel) + translate("Presets", "Presets"), + translate( + "Presets", + "Some presets already exists, " "overwrite?", + ), + buttons=QMessageBox.Yes | QMessageBox.Cancel, + ) if answer != QMessageBox.Yes: return @@ -363,10 +386,10 @@ def __import_presets(self): self.__populate() except PresetImportError as e: QDetailedMessageBox.dcritical( - translate('Presets', 'Presets'), - translate('Presets', 'Cannot import correctly.'), + translate("Presets", "Presets"), + translate("Presets", "Cannot import correctly."), str(e), - parent=self + parent=self, ) def __selection_changed(self): @@ -400,13 +423,15 @@ def __init__(self, **kwargs): self.typeComboBox = QComboBox(self) for cue_class in CueSettingsRegistry().ref_classes(): - self.typeComboBox.addItem(translate('CueName', cue_class.Name), - cue_class.__name__) + self.typeComboBox.addItem( + translate("CueName", cue_class.Name), cue_class.__name__ + ) self.layout().addWidget(self.typeComboBox, 1, 1) self.dialogButtons = QDialogButtonBox(self) - self.dialogButtons.setStandardButtons(QDialogButtonBox.Ok | - QDialogButtonBox.Cancel) + self.dialogButtons.setStandardButtons( + QDialogButtonBox.Ok | QDialogButtonBox.Cancel + ) self.dialogButtons.accepted.connect(self.accept) self.dialogButtons.rejected.connect(self.reject) self.layout().addWidget(self.dialogButtons, 2, 0, 1, 2) @@ -417,8 +442,8 @@ def __init__(self, **kwargs): self.retranslateUi() def retranslateUi(self): - self.nameLabel.setText(translate('Presets', 'Preset name')) - self.typeLabel.setText(translate('Presets', 'Cue type')) + self.nameLabel.setText(translate("Presets", "Preset name")) + self.typeLabel.setText(translate("Presets", "Cue type")) def get_name(self): return self.nameLineEdit.text() diff --git a/lisp/plugins/rename_cues/__init__.py b/lisp/plugins/rename_cues/__init__.py index af50fdb6c..fd5fe466c 100644 --- a/lisp/plugins/rename_cues/__init__.py +++ b/lisp/plugins/rename_cues/__init__.py @@ -1 +1 @@ -from .rename_cues import RenameCues \ No newline at end of file +from .rename_cues import RenameCues diff --git a/lisp/plugins/rename_cues/rename_action.py b/lisp/plugins/rename_cues/rename_action.py index 76eebda51..ee58a3f34 100644 --- a/lisp/plugins/rename_cues/rename_action.py +++ b/lisp/plugins/rename_cues/rename_action.py @@ -31,7 +31,7 @@ def __init__(self, app, new_cue_list): self.app = app for renamed_cue in new_cue_list: - self.names[renamed_cue['id']] = renamed_cue['cue_preview'] + self.names[renamed_cue["id"]] = renamed_cue["cue_preview"] def do(self): """Use stored name and exchange with current names""" diff --git a/lisp/plugins/rename_cues/rename_cues.py b/lisp/plugins/rename_cues/rename_cues.py index 4b4b04422..399a16966 100644 --- a/lisp/plugins/rename_cues/rename_cues.py +++ b/lisp/plugins/rename_cues/rename_cues.py @@ -28,16 +28,17 @@ class RenameCues(Plugin): - Name = 'RenameCues' - Authors = ('Aurelien Cibrario',) - Description = 'Provide a dialog for batch renaming of cues' + Name = "RenameCues" + Authors = ("Aurelien Cibrario",) + Description = "Provide a dialog for batch renaming of cues" def __init__(self, app): super().__init__(app) # Entry in mainWindow menu self.menuAction = QAction( - translate('RenameCues', 'Rename Cues'), self.app.window) + translate("RenameCues", "Rename Cues"), self.app.window + ) self.menuAction.triggered.connect(self.rename) self.app.window.menuTools.addAction(self.menuAction) diff --git a/lisp/plugins/rename_cues/rename_ui.py b/lisp/plugins/rename_cues/rename_ui.py index 32a3cd07d..5187bc102 100644 --- a/lisp/plugins/rename_cues/rename_ui.py +++ b/lisp/plugins/rename_cues/rename_ui.py @@ -22,9 +22,18 @@ import re from PyQt5.QtCore import Qt, QSize -from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QGridLayout, QLineEdit, \ - QTreeWidget, QAbstractItemView, QTreeWidgetItem, QPushButton, QSpacerItem, \ - QMessageBox +from PyQt5.QtWidgets import ( + QDialog, + QDialogButtonBox, + QGridLayout, + QLineEdit, + QTreeWidget, + QAbstractItemView, + QTreeWidgetItem, + QPushButton, + QSpacerItem, + QMessageBox, +) from lisp.ui.icons import IconTheme from lisp.ui.ui_utils import translate @@ -36,7 +45,7 @@ class RenameUi(QDialog): def __init__(self, parent=None, selected_cues=()): super().__init__(parent) - self.setWindowTitle(translate('RenameCues', 'Rename cues')) + self.setWindowTitle(translate("RenameCues", "Rename cues")) self.setWindowModality(Qt.ApplicationModal) self.resize(650, 350) @@ -45,45 +54,53 @@ def __init__(self, parent=None, selected_cues=()): # Preview List self.previewList = QTreeWidget() self.previewList.setColumnCount(2) - self.previewList.setHeaderLabels([ - translate('RenameCues', 'Current'), - translate('RenameCues', 'Preview') - ]) + self.previewList.setHeaderLabels( + [ + translate("RenameCues", "Current"), + translate("RenameCues", "Preview"), + ] + ) self.previewList.resizeColumnToContents(0) self.previewList.resizeColumnToContents(1) self.previewList.setColumnWidth(0, 300) self.previewList.setSelectionMode(QAbstractItemView.ExtendedSelection) - self.previewList.itemSelectionChanged.connect(self.onPreviewListItemSelectionChanged) + self.previewList.itemSelectionChanged.connect( + self.onPreviewListItemSelectionChanged + ) self.layout().addWidget(self.previewList, 0, 0, 3, 5) # Options buttons self.capitalizeButton = QPushButton() - self.capitalizeButton.setText(translate('RenameCues', 'Capitalize')) + self.capitalizeButton.setText(translate("RenameCues", "Capitalize")) self.capitalizeButton.clicked.connect(self.onCapitalizeButtonClicked) self.layout().addWidget(self.capitalizeButton, 3, 0) self.lowerButton = QPushButton() - self.lowerButton.setText(translate('RenameCues', 'Lowercase')) + self.lowerButton.setText(translate("RenameCues", "Lowercase")) self.lowerButton.clicked.connect(self.onLowerButtonClicked) self.layout().addWidget(self.lowerButton, 4, 0) self.upperButton = QPushButton() - self.upperButton.setText(translate('RenameCues', 'Uppercase')) + self.upperButton.setText(translate("RenameCues", "Uppercase")) self.upperButton.clicked.connect(self.onUpperButtonClicked) self.layout().addWidget(self.upperButton, 5, 0) self.removeNumButton = QPushButton() - self.removeNumButton.setText(translate('RenameCues', 'Remove Numbers')) + self.removeNumButton.setText(translate("RenameCues", "Remove Numbers")) self.removeNumButton.clicked.connect(self.onRemoveNumButtonClicked) self.layout().addWidget(self.removeNumButton, 3, 1) self.addNumberingButton = QPushButton() - self.addNumberingButton.setText(translate('RenameCues', 'Add numbering')) - self.addNumberingButton.clicked.connect(self.onAddNumberingButtonClicked) + self.addNumberingButton.setText( + translate("RenameCues", "Add numbering") + ) + self.addNumberingButton.clicked.connect( + self.onAddNumberingButtonClicked + ) self.layout().addWidget(self.addNumberingButton, 4, 1) self.resetButton = QPushButton() - self.resetButton.setText(translate('RenameCues', 'Reset')) + self.resetButton.setText(translate("RenameCues", "Reset")) self.resetButton.clicked.connect(self.onResetButtonClicked) self.layout().addWidget(self.resetButton, 5, 1) @@ -93,26 +110,33 @@ def __init__(self, parent=None, selected_cues=()): # Regex lines self.outRegexLine = QLineEdit() self.outRegexLine.setPlaceholderText( - translate('RenameCues', 'Rename all cue. () in regex below usable with $0, $1 ...')) + translate( + "RenameCues", + "Rename all cue. () in regex below usable with $0, $1 ...", + ) + ) self.outRegexLine.textChanged.connect(self.onOutRegexChanged) self.layout().addWidget(self.outRegexLine, 3, 3) self.regexLine = QLineEdit() - self.regexLine.setPlaceholderText(translate('RenameCues', 'Type your regex here: ')) + self.regexLine.setPlaceholderText( + translate("RenameCues", "Type your regex here: ") + ) self.regexLine.textChanged.connect(self.onRegexLineChanged) self.layout().addWidget(self.regexLine, 4, 3) # Help button self.helpButton = QPushButton() - self.helpButton.setIcon(IconTheme.get('help-about')) + self.helpButton.setIcon(IconTheme.get("help-about")) self.helpButton.setIconSize(QSize(32, 32)) self.layout().addWidget(self.helpButton, 3, 4, 2, 1) self.helpButton.clicked.connect(self.onHelpButtonClicked) # OK / Cancel buttons self.dialogButtons = QDialogButtonBox() - self.dialogButtons.setStandardButtons(QDialogButtonBox.Ok | - QDialogButtonBox.Cancel) + self.dialogButtons.setStandardButtons( + QDialogButtonBox.Ok | QDialogButtonBox.Cancel + ) self.layout().addWidget(self.dialogButtons, 6, 3) self.dialogButtons.accepted.connect(self.accept) @@ -121,81 +145,85 @@ def __init__(self, parent=None, selected_cues=()): # This list store infos on cues with dicts self.cues_list = [] for cue in selected_cues: - self.cues_list.append({ - 'cue_name': cue.name, - 'cue_preview': cue.name, - 'selected': True, - 'regex_groups': [], - 'id': cue.id - }) + self.cues_list.append( + { + "cue_name": cue.name, + "cue_preview": cue.name, + "selected": True, + "regex_groups": [], + "id": cue.id, + } + ) # Populate Preview list for cue_to_rename in self.cues_list: item = QTreeWidgetItem(self.previewList) - item.setText(0, cue_to_rename['cue_name']) - item.setText(1, cue_to_rename['cue_preview']) + item.setText(0, cue_to_rename["cue_name"]) + item.setText(1, cue_to_rename["cue_preview"]) self.previewList.selectAll() def update_preview_list(self): for i in range(self.previewList.topLevelItemCount()): item = self.previewList.topLevelItem(i) - item.setText(1, self.cues_list[i]['cue_preview']) + item.setText(1, self.cues_list[i]["cue_preview"]) def onPreviewListItemSelectionChanged(self): for i in range(self.previewList.topLevelItemCount()): item = self.previewList.topLevelItem(i) if item.isSelected(): - self.cues_list[i]['selected'] = True + self.cues_list[i]["selected"] = True else: - self.cues_list[i]['selected'] = False + self.cues_list[i]["selected"] = False def onCapitalizeButtonClicked(self): for cue in self.cues_list: - if cue['selected']: - cue['cue_preview'] = cue['cue_preview'].title() + if cue["selected"]: + cue["cue_preview"] = cue["cue_preview"].title() self.update_preview_list() def onLowerButtonClicked(self): for cue in self.cues_list: - if cue['selected']: - cue['cue_preview'] = cue['cue_preview'].lower() + if cue["selected"]: + cue["cue_preview"] = cue["cue_preview"].lower() self.update_preview_list() def onUpperButtonClicked(self): for cue in self.cues_list: - if cue['selected']: - cue['cue_preview'] = cue['cue_preview'].upper() + if cue["selected"]: + cue["cue_preview"] = cue["cue_preview"].upper() self.update_preview_list() def onRemoveNumButtonClicked(self): - regex = re.compile('^[^a-zA-Z]+(.+)') + regex = re.compile("^[^a-zA-Z]+(.+)") for cue in self.cues_list: - if cue['selected']: - match = regex.search(cue['cue_preview']) + if cue["selected"]: + match = regex.search(cue["cue_preview"]) if match is not None: - cue['cue_preview'] = match.group(1) + cue["cue_preview"] = match.group(1) self.update_preview_list() def onAddNumberingButtonClicked(self): # Extract selected rows - cues_to_modify = [cue for cue in self.cues_list if cue['selected']] + cues_to_modify = [cue for cue in self.cues_list if cue["selected"]] # Count selected rows cues_nbr = len(cues_to_modify) # Calculate number of digits in order to add appropriate number of 0's digit_nbr = len(str(cues_nbr)) for i, cue in enumerate(cues_to_modify): - if cue['selected']: - cue['cue_preview'] = f"{i+1:0{digit_nbr}.0f} - {cue['cue_preview']}" + if cue["selected"]: + cue[ + "cue_preview" + ] = f"{i+1:0{digit_nbr}.0f} - {cue['cue_preview']}" self.update_preview_list() def onResetButtonClicked(self): for cue in self.cues_list: - cue['cue_preview'] = cue['cue_name'] - cue['selected'] = True + cue["cue_preview"] = cue["cue_name"] + cue["selected"] = True self.update_preview_list() self.previewList.selectAll() @@ -203,23 +231,25 @@ def onResetButtonClicked(self): def onHelpButtonClicked(self): msg = QMessageBox() msg.setIcon(QMessageBox.Information) - msg.setWindowTitle(translate('RenameCues', 'Regex help')) - msg.setText(translate( - 'RenameCues', - 'You can use Regexes to rename your cues.\n\n' - 'Insert expressions captured with regexes in the ' - 'line below with $0 for the first parenthesis, $1 for' - 'the second, etc...\n' - 'In the second line, you can use standard Python Regexes ' - 'to match expressions in the original cues names. Use ' - 'parenthesis to capture parts of the matched expression.\n\n' - 'Exemple: \n^[a-z]([0-9]+) will find a lower case character' - '([a-z]), followed by one or more number.\n' - 'Only the numbers are between parenthesis and will be usable with ' - '$0 in the first line.\n\n' - 'For more information about Regexes, consult python documentation ' - 'at: https://docs.python.org/3/howto/regex.html#regex-howto' - )) + msg.setWindowTitle(translate("RenameCues", "Regex help")) + msg.setText( + translate( + "RenameCues", + "You can use Regexes to rename your cues.\n\n" + "Insert expressions captured with regexes in the " + "line below with $0 for the first parenthesis, $1 for" + "the second, etc...\n" + "In the second line, you can use standard Python Regexes " + "to match expressions in the original cues names. Use " + "parenthesis to capture parts of the matched expression.\n\n" + "Exemple: \n^[a-z]([0-9]+) will find a lower case character" + "([a-z]), followed by one or more number.\n" + "Only the numbers are between parenthesis and will be usable with " + "$0 in the first line.\n\n" + "For more information about Regexes, consult python documentation " + "at: https://docs.python.org/3/howto/regex.html#regex-howto", + ) + ) msg.exec_() def onRegexLineChanged(self): @@ -228,42 +258,41 @@ def onRegexLineChanged(self): regex = re.compile(pattern) except re.error: logger.debug( - translate( - 'RenameUiDebug', 'Regex error: Invalid pattern'), - exc_info=True + translate("RenameUiDebug", "Regex error: Invalid pattern"), + exc_info=True, ) else: for cue in self.cues_list: - result = regex.search(cue['cue_name']) + result = regex.search(cue["cue_name"]) if result: - cue['regex_groups'] = result.groups() + cue["regex_groups"] = result.groups() self.onOutRegexChanged() def onOutRegexChanged(self): out_pattern = self.outRegexLine.text() - if out_pattern == '': + if out_pattern == "": for cue in self.cues_list: - if cue['selected']: - cue['cue_preview'] = cue['cue_name'] + if cue["selected"]: + cue["cue_preview"] = cue["cue_name"] else: for cue in self.cues_list: out_string = out_pattern - for n in range(len(cue['regex_groups'])): - pattern = rf'\${n}' + for n in range(len(cue["regex_groups"])): + pattern = rf"\${n}" try: out_string = re.sub( - pattern, - cue['regex_groups'][n], - out_string + pattern, cue["regex_groups"][n], out_string ) except IndexError: - logger.debug(translate( - 'RenameUiDebug', - 'Regex error: catch with () before display with $n') + logger.debug( + translate( + "RenameUiDebug", + "Regex error: catch with () before display with $n", + ) ) - if cue['selected']: - cue['cue_preview'] = out_string + if cue["selected"]: + cue["cue_preview"] = out_string self.update_preview_list() diff --git a/lisp/plugins/replay_gain/__init__.py b/lisp/plugins/replay_gain/__init__.py index a004cda8b..cab1455ec 100644 --- a/lisp/plugins/replay_gain/__init__.py +++ b/lisp/plugins/replay_gain/__init__.py @@ -1 +1 @@ -from .replay_gain import ReplayGain \ No newline at end of file +from .replay_gain import ReplayGain diff --git a/lisp/plugins/replay_gain/gain_ui.py b/lisp/plugins/replay_gain/gain_ui.py index c2a71f896..dad6eac86 100644 --- a/lisp/plugins/replay_gain/gain_ui.py +++ b/lisp/plugins/replay_gain/gain_ui.py @@ -20,8 +20,18 @@ from os import cpu_count from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QRadioButton, QSpinBox, \ - QCheckBox, QFrame, QLabel, QGridLayout, QButtonGroup, QProgressDialog +from PyQt5.QtWidgets import ( + QDialog, + QDialogButtonBox, + QRadioButton, + QSpinBox, + QCheckBox, + QFrame, + QLabel, + QGridLayout, + QButtonGroup, + QProgressDialog, +) from lisp.ui.ui_utils import translate @@ -81,8 +91,9 @@ def __init__(self, parent=None): self.layout().addWidget(self.cpuSpinBox, 4, 1) self.dialogButtons = QDialogButtonBox(self) - self.dialogButtons.setStandardButtons(QDialogButtonBox.Ok | - QDialogButtonBox.Cancel) + self.dialogButtons.setStandardButtons( + QDialogButtonBox.Ok | QDialogButtonBox.Cancel + ) self.layout().addWidget(self.dialogButtons, 5, 0, 1, 2) self.normalizeRadio.toggled.connect(self.normalizeSpinBox.setEnabled) @@ -99,14 +110,18 @@ def __init__(self, parent=None): def retranslateUi(self): self.setWindowTitle( - translate('ReplayGain', 'ReplayGain / Normalization')) - self.cpuLabel.setText(translate('ReplayGain', 'Threads number')) + translate("ReplayGain", "ReplayGain / Normalization") + ) + self.cpuLabel.setText(translate("ReplayGain", "Threads number")) self.selectionMode.setText( - translate('ReplayGain', 'Apply only to selected media')) + translate("ReplayGain", "Apply only to selected media") + ) self.gainRadio.setText( - translate('ReplayGain', 'ReplayGain to (dB SPL)')) + translate("ReplayGain", "ReplayGain to (dB SPL)") + ) self.normalizeRadio.setText( - translate('ReplayGain', 'Normalize to (dB)')) + translate("ReplayGain", "Normalize to (dB)") + ) def mode(self): return 0 if self.gainRadio.isChecked() else 1 @@ -129,13 +144,13 @@ def __init__(self, maximum, parent=None): super().__init__(parent) self.setWindowModality(Qt.ApplicationModal) - self.setWindowTitle(translate('ReplayGain', 'Processing files ...')) + self.setWindowTitle(translate("ReplayGain", "Processing files ...")) self.setMaximumSize(320, 110) self.setMinimumSize(320, 110) self.resize(320, 110) self.setMaximum(maximum) - self.setLabelText('0 / {0}'.format(maximum)) + self.setLabelText("0 / {0}".format(maximum)) def on_progress(self, value): if value == -1: @@ -144,4 +159,4 @@ def on_progress(self, value): self.deleteLater() else: self.setValue(self.value() + value) - self.setLabelText('{0} / {1}'.format(self.value(), self.maximum())) + self.setLabelText("{0} / {1}".format(self.value(), self.maximum())) diff --git a/lisp/plugins/replay_gain/replay_gain.py b/lisp/plugins/replay_gain/replay_gain.py index 576a9fe8a..0564f48c1 100644 --- a/lisp/plugins/replay_gain/replay_gain.py +++ b/lisp/plugins/replay_gain/replay_gain.py @@ -24,7 +24,7 @@ import gi -gi.require_version('Gst', '1.0') +gi.require_version("Gst", "1.0") from gi.repository import Gst from PyQt5.QtWidgets import QMenu, QAction, QDialog @@ -40,9 +40,9 @@ class ReplayGain(Plugin): - Name = 'ReplayGain / Normalization' - Authors = ('Francesco Ceruti',) - Description = 'Allow to normalize cues volume' + Name = "ReplayGain / Normalization" + Authors = ("Francesco Ceruti",) + Description = "Allow to normalize cues volume" RESET_VALUE = 1.0 @@ -51,24 +51,24 @@ def __init__(self, app): self._gain_thread = None # Entry in mainWindow menu - self.menu = QMenu( - translate('ReplayGain', 'ReplayGain / Normalization')) + self.menu = QMenu(translate("ReplayGain", "ReplayGain / Normalization")) self.menu_action = self.app.window.menuTools.addMenu(self.menu) self.actionGain = QAction(self.app.window) self.actionGain.triggered.connect(self.gain) - self.actionGain.setText(translate('ReplayGain', 'Calculate')) + self.actionGain.setText(translate("ReplayGain", "Calculate")) self.menu.addAction(self.actionGain) self.actionReset = QAction(self.app.window) self.actionReset.triggered.connect(self._reset_all) - self.actionReset.setText(translate('ReplayGain', 'Reset all')) + self.actionReset.setText(translate("ReplayGain", "Reset all")) self.menu.addAction(self.actionReset) self.actionResetSelected = QAction(self.app.window) self.actionResetSelected.triggered.connect(self._reset_selected) - self.actionResetSelected.setText(translate('ReplayGain', - 'Reset selected')) + self.actionResetSelected.setText( + translate("ReplayGain", "Reset selected") + ) self.menu.addAction(self.actionResetSelected) def gain(self): @@ -89,8 +89,8 @@ def gain(self): uri = media.input_uri() if uri is not None: - if uri[:7] == 'file://': - uri = 'file://' + self.app.session.abs_path(uri[7:]) + if uri[:7] == "file://": + uri = "file://" + self.app.session.abs_path(uri[7:]) if uri not in files: files[uri] = [media] @@ -103,13 +103,14 @@ def gain(self): gainUi.threads(), gainUi.mode(), gainUi.ref_level(), - gainUi.norm_level() + gainUi.norm_level(), ) # Progress dialog self._progress = GainProgressDialog(len(files)) self._gain_thread.on_progress.connect( - self._progress.on_progress, mode=Connection.QtQueued) + self._progress.on_progress, mode=Connection.QtQueued + ) self._progress.show() self._gain_thread.start() @@ -137,7 +138,7 @@ def _reset(self, cues): class GainAction(Action): - __slots__ = ('__media_list', '__new_volumes', '__old_volumes') + __slots__ = ("__media_list", "__new_volumes", "__old_volumes") def __init__(self): self.__media_list = [] @@ -145,7 +146,7 @@ def __init__(self): self.__old_volumes = [] def add_media(self, media, new_volume): - volume = media.element('Volume') + volume = media.element("Volume") if volume is not None: self.__media_list.append(media) self.__new_volumes.append(new_volume) @@ -153,13 +154,13 @@ def add_media(self, media, new_volume): def do(self): for n, media in enumerate(self.__media_list): - volume = media.element('Volume') + volume = media.element("Volume") if volume is not None: volume.normal_volume = self.__new_volumes[n] def undo(self): for n, media in enumerate(self.__media_list): - volume = media.element('Volume') + volume = media.element("Volume") if volume is not None: volume.normal_volume = self.__old_volumes[n] @@ -167,7 +168,7 @@ def redo(self): self.do() def log(self): - return 'Replay gain volume adjusted.' + return "Replay gain volume adjusted." class GainMainThread(Thread): @@ -207,10 +208,12 @@ def run(self): try: self._post_process(future.result()) except Exception: - logger.exception(translate( - 'ReplayGainError', - 'An error occurred while processing gain results.' - )) + logger.exception( + translate( + "ReplayGainError", + "An error occurred while processing gain results.", + ) + ) finally: self.on_progress.emit(1) else: @@ -219,8 +222,9 @@ def run(self): if self._running: MainActionsHandler.do_action(self._action) else: - logger.info(translate( - 'ReplayGainInfo', 'Gain processing stopped by user.')) + logger.info( + translate("ReplayGainInfo", "Gain processing stopped by user.") + ) self.on_progress.emit(-1) self.on_progress.disconnect() @@ -238,16 +242,24 @@ def _post_process(self, gain): for media in self.files[gain.uri]: self._action.add_media(media, volume) - logger.debug(translate( - 'ReplayGainDebug', 'Applied gain for: {}').format(gain.uri)) + logger.debug( + translate("ReplayGainDebug", "Applied gain for: {}").format( + gain.uri + ) + ) else: - logger.debug(translate( - 'ReplayGainDebug', 'Discarded gain for: {}').format(gain.uri)) + logger.debug( + translate("ReplayGainDebug", "Discarded gain for: {}").format( + gain.uri + ) + ) class GstGain: - PIPELINE = 'uridecodebin uri="{0}" ! audioconvert ! rganalysis ' \ - 'reference-level={1} ! fakesink' + PIPELINE = ( + 'uridecodebin uri="{0}" ! audioconvert ! rganalysis ' + "reference-level={1} ! fakesink" + ) def __init__(self, uri, ref_level): self.__lock = Lock() @@ -264,19 +276,21 @@ def __init__(self, uri, ref_level): # Create a pipeline with a fake audio output and get the gain levels def gain(self): self.gain_pipe = Gst.parse_launch( - GstGain.PIPELINE.format(self.uri, self.ref_level)) + GstGain.PIPELINE.format(self.uri, self.ref_level) + ) gain_bus = self.gain_pipe.get_bus() gain_bus.add_signal_watch() # Connect only the messages we want - gain_bus.connect('message::eos', self._on_message) - gain_bus.connect('message::tag', self._on_message) - gain_bus.connect('message::error', self._on_message) + gain_bus.connect("message::eos", self._on_message) + gain_bus.connect("message::tag", self._on_message) + gain_bus.connect("message::error", self._on_message) self.gain_pipe.set_state(Gst.State.PLAYING) - logger.info(translate( - 'ReplayGainInfo', - 'Started gain calculation for: {}').format(self.uri) + logger.info( + translate( + "ReplayGainInfo", "Started gain calculation for: {}" + ).format(self.uri) ) # Block here until EOS @@ -308,9 +322,10 @@ def _on_message(self, bus, message): self.gain_value = tag[1] self.peak_value = peak[1] - logger.info(translate( - 'ReplayGainInfo', - 'Gain calculated for: {}').format(self.uri) + logger.info( + translate( + "ReplayGainInfo", "Gain calculated for: {}" + ).format(self.uri) ) self.completed = True self.__release() @@ -319,12 +334,15 @@ def _on_message(self, bus, message): self.gain_pipe.set_state(Gst.State.NULL) logger.error( - 'GStreamer: {}'.format(error.message), exc_info=error) + "GStreamer: {}".format(error.message), exc_info=error + ) self.__release() except Exception: - logger.exception(translate( - 'ReplayGainError', - 'An error occurred during gain calculation.') + logger.exception( + translate( + "ReplayGainError", + "An error occurred during gain calculation.", + ) ) self.__release() diff --git a/lisp/plugins/synchronizer/__init__.py b/lisp/plugins/synchronizer/__init__.py index 4d36b89e4..d91bf1922 100644 --- a/lisp/plugins/synchronizer/__init__.py +++ b/lisp/plugins/synchronizer/__init__.py @@ -1 +1 @@ -from .synchronizer import Synchronizer \ No newline at end of file +from .synchronizer import Synchronizer diff --git a/lisp/plugins/synchronizer/synchronizer.py b/lisp/plugins/synchronizer/synchronizer.py index 66c754b6e..4707cc358 100644 --- a/lisp/plugins/synchronizer/synchronizer.py +++ b/lisp/plugins/synchronizer/synchronizer.py @@ -32,70 +32,71 @@ class Synchronizer(Plugin): - Name = 'Synchronizer' - Authors = ('Francesco Ceruti',) - OptDepends = ('Network',) - Description = 'Keep multiple sessions, on a network, synchronized' + Name = "Synchronizer" + Authors = ("Francesco Ceruti",) + OptDepends = ("Network",) + Description = "Keep multiple sessions, on a network, synchronized" def __init__(self, app): super().__init__(app) self.peers = [] self.app.session_created.connect(self.session_init) - self.syncMenu = QMenu(translate('Synchronizer', 'Synchronization')) + self.syncMenu = QMenu(translate("Synchronizer", "Synchronization")) self.menu_action = self.app.window.menuTools.addMenu(self.syncMenu) self.addPeerAction = QAction( - translate('Synchronizer', 'Manage connected peers'), - self.app.window + translate("Synchronizer", "Manage connected peers"), self.app.window ) self.addPeerAction.triggered.connect(self.manage_peers) self.syncMenu.addAction(self.addPeerAction) self.showIpAction = QAction( - translate('Synchronizer', 'Show your IP'), self.app.window) + translate("Synchronizer", "Show your IP"), self.app.window + ) self.showIpAction.triggered.connect(self.show_ip) self.syncMenu.addAction(self.showIpAction) def session_init(self): self.app.layout.cue_executed.connect( - self._cue_executes, mode=Connection.Async) + self._cue_executes, mode=Connection.Async + ) self.app.layout.all_executed.connect( - self._all_executed, mode=Connection.Async) + self._all_executed, mode=Connection.Async + ) def manage_peers(self): manager = HostManagementDialog( self.peers, - Synchronizer.Config['discovery.port'], - Synchronizer.Config['discovery.magic'], - parent=self.app.window + Synchronizer.Config["discovery.port"], + Synchronizer.Config["discovery.magic"], + parent=self.app.window, ) manager.exec_() self.peers = manager.hosts() def show_ip(self): - ip = translate('Synchronizer', 'Your IP is:') + ' ' + str(get_lan_ip()) - QMessageBox.information(self.app.window, ' ', ip) + ip = translate("Synchronizer", "Your IP is:") + " " + str(get_lan_ip()) + QMessageBox.information(self.app.window, " ", ip) def _url(self, host, path): return compose_url( - Synchronizer.Config['protocol'], + Synchronizer.Config["protocol"], host, - Synchronizer.Config['port'], - path + Synchronizer.Config["port"], + path, ) def _cue_executes(self, cue): for peer in self.peers: requests.post( - self._url(peer, '/cues/{}/action'.format(cue.id)), - json={'action': CueAction.Default.value} + self._url(peer, "/cues/{}/action".format(cue.id)), + json={"action": CueAction.Default.value}, ) def _all_executed(self, action): for peer in self.peers: requests.post( - self._url(peer, '/layout/action'), - json={'action': action.value} + self._url(peer, "/layout/action"), json={"action": action.value} ) diff --git a/lisp/plugins/timecode/cue_tracker.py b/lisp/plugins/timecode/cue_tracker.py index a458b7827..8247e2bdc 100644 --- a/lisp/plugins/timecode/cue_tracker.py +++ b/lisp/plugins/timecode/cue_tracker.py @@ -95,8 +95,8 @@ def track(self, cue): # Setup new cue and options self.__cue = cue self.__cue_time = HRCueTime(cue) - self.__replace_hours = cue.timecode['replace_hours'] - self.__track = cue.timecode['track'] if self.__replace_hours else -1 + self.__replace_hours = cue.timecode["replace_hours"] + self.__track = cue.timecode["track"] if self.__replace_hours else -1 # Send a "starting" time self.send(self.__cue.current_time()) @@ -117,9 +117,11 @@ def send(self, time): if self.__lock.acquire(blocking=False): try: if not self.__protocol.send(self.format, time, self.__track): - logger.warning(translate( - 'TimecodeWarning', - 'Cannot send timecode, untracking cue') + logger.warning( + translate( + "TimecodeWarning", + "Cannot send timecode, untracking cue", + ) ) self.untrack() except Exception: diff --git a/lisp/plugins/timecode/protocol.py b/lisp/plugins/timecode/protocol.py index af6c3b347..b07c1fc01 100644 --- a/lisp/plugins/timecode/protocol.py +++ b/lisp/plugins/timecode/protocol.py @@ -21,7 +21,8 @@ class TimecodeProtocol: """Base class for timecode protocols""" - Name = 'None' + + Name = "None" def send(self, fmt, time, track=-1): """Send timecode, returns success for error handling""" diff --git a/lisp/plugins/timecode/protocols/artnet.py b/lisp/plugins/timecode/protocols/artnet.py index 7d55bc179..b79055390 100644 --- a/lisp/plugins/timecode/protocols/artnet.py +++ b/lisp/plugins/timecode/protocols/artnet.py @@ -30,14 +30,14 @@ ARTNET_FORMATS = { TcFormat.FILM: OlaClient.TIMECODE_FILM, TcFormat.EBU: OlaClient.TIMECODE_EBU, - TcFormat.SMPTE: OlaClient.TIMECODE_SMPTE + TcFormat.SMPTE: OlaClient.TIMECODE_SMPTE, } logger = logging.getLogger(__name__) class Artnet(TimecodeProtocol): - Name = 'ArtNet' + Name = "ArtNet" def __init__(self): super().__init__() @@ -60,16 +60,20 @@ def send(self, fmt, time, track=-1): try: self.__client.SendTimeCode( - ARTNET_FORMATS[fmt], hours, minutes, seconds, frame) + ARTNET_FORMATS[fmt], hours, minutes, seconds, frame + ) except OLADNotRunningException: - logger.error(translate( - 'TimecodeError', - 'Cannot send timecode. \nOLA daemon has stopped.') + logger.error( + translate( + "TimecodeError", + "Cannot send timecode. \nOLA daemon has stopped.", + ) ) return False except Exception: logger.exception( - translate('TimecodeError', 'Cannot send timecode.')) + translate("TimecodeError", "Cannot send timecode.") + ) return False self.__last_time = time diff --git a/lisp/plugins/timecode/protocols/midi.py b/lisp/plugins/timecode/protocols/midi.py index 672daf830..38e0feead 100644 --- a/lisp/plugins/timecode/protocols/midi.py +++ b/lisp/plugins/timecode/protocols/midi.py @@ -25,11 +25,7 @@ from lisp.plugins.timecode.cue_tracker import TcFormat from lisp.plugins.timecode.protocol import TimecodeProtocol -MIDI_FORMATS = { - TcFormat.FILM: 0, - TcFormat.EBU: 1, - TcFormat.SMPTE: 3 -} +MIDI_FORMATS = {TcFormat.FILM: 0, TcFormat.EBU: 1, TcFormat.SMPTE: 3} FRAME_VALUES = [ @@ -40,18 +36,18 @@ lambda h, m, s, f, r: m & 15, lambda h, m, s, f, r: (m >> 4) & 3, lambda h, m, s, f, r: h & 15, - lambda h, m, s, f, r: (h >> 4 & 1) + (r << 1) + lambda h, m, s, f, r: (h >> 4 & 1) + (r << 1), ] class Midi(TimecodeProtocol): - Name = 'MIDI' + Name = "MIDI" def __init__(self): super().__init__() self.__last_time = -1 self.__last_frame = -1 - self.__midi = get_plugin('Midi') + self.__midi = get_plugin("Midi") def __send_full(self, fmt, hours, minutes, seconds, frame): """Sends fullframe timecode message. @@ -59,10 +55,17 @@ def __send_full(self, fmt, hours, minutes, seconds, frame): Used in case timecode is non continuous (repositioning, rewind). """ message = Message( - 'sysex', data=[ - 0x7f, 0x7f, 0x01, 0x01, - (MIDI_FORMATS[fmt] << 5) + hours, minutes, seconds, frame - ] + "sysex", + data=[ + 0x7F, + 0x7F, + 0x01, + 0x01, + (MIDI_FORMATS[fmt] << 5) + hours, + minutes, + seconds, + frame, + ], ) if self.__midi is not None: @@ -70,10 +73,11 @@ def __send_full(self, fmt, hours, minutes, seconds, frame): def __send_quarter(self, frame_type, fmt, hours, minutes, seconds, frame): """Send quarterframe midi message.""" - messsage = Message('quarter_frame') + messsage = Message("quarter_frame") messsage.frame_type = frame_type messsage.frame_value = FRAME_VALUES[frame_type]( - hours, minutes, seconds, frame, MIDI_FORMATS[fmt]) + hours, minutes, seconds, frame, MIDI_FORMATS[fmt] + ) if self.__midi is not None: self.__midi.send(messsage) @@ -106,7 +110,8 @@ def send(self, format, time, track=-1): frame_type = 0 self.__send_quarter( - frame_type, format, hours, minutes, seconds, frame) + frame_type, format, hours, minutes, seconds, frame + ) self.__last_frame = frame_type diff --git a/lisp/plugins/timecode/settings.py b/lisp/plugins/timecode/settings.py index 22ec179da..97337547b 100644 --- a/lisp/plugins/timecode/settings.py +++ b/lisp/plugins/timecode/settings.py @@ -20,8 +20,15 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt -from PyQt5.QtWidgets import QGridLayout, QVBoxLayout, QGroupBox, \ - QLabel, QCheckBox, QSpinBox, QComboBox +from PyQt5.QtWidgets import ( + QGridLayout, + QVBoxLayout, + QGroupBox, + QLabel, + QCheckBox, + QSpinBox, + QComboBox, +) from lisp.plugins.timecode import protocols from lisp.plugins.timecode.cue_tracker import TcFormat @@ -30,7 +37,7 @@ class TimecodeSettings(CueSettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Timecode') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Timecode") def __init__(self, cueType, **kwargs): super().__init__(cueType, **kwargs) @@ -65,40 +72,42 @@ def __init__(self, cueType, **kwargs): self.warnLabel = QLabel(self) self.warnLabel.setAlignment(Qt.AlignCenter) - self.warnLabel.setStyleSheet('color: #FFA500; font-weight: bold') + self.warnLabel.setStyleSheet("color: #FFA500; font-weight: bold") self.layout().addWidget(self.warnLabel) self.retranslateUi() def retranslateUi(self): - self.groupBox.setTitle('Timecode') + self.groupBox.setTitle("Timecode") self.useHoursCheck.setText( - translate('TimecodeSettings', - 'Replace HOURS by a static track number')) + translate( + "TimecodeSettings", "Replace HOURS by a static track number" + ) + ) self.enableTimecodeCheck.setText( - translate('TimecodeSettings', 'Enable Timecode')) - self.trackLabel.setText( - translate('TimecodeSettings', 'Track number')) + translate("TimecodeSettings", "Enable Timecode") + ) + self.trackLabel.setText(translate("TimecodeSettings", "Track number")) def getSettings(self): return { - 'timecode': { - 'enabled': self.enableTimecodeCheck.isChecked(), - 'replace_hours': self.useHoursCheck.isChecked(), - 'track': self.trackSpin.value() + "timecode": { + "enabled": self.enableTimecodeCheck.isChecked(), + "replace_hours": self.useHoursCheck.isChecked(), + "track": self.trackSpin.value(), } } def loadSettings(self, settings): - settings = settings.get('timecode', {}) + settings = settings.get("timecode", {}) - self.enableTimecodeCheck.setChecked(settings.get('enabled', False)) - self.useHoursCheck.setChecked(settings.get('replace_hours', False)) - self.trackSpin.setValue(settings.get('track', 0)) + self.enableTimecodeCheck.setChecked(settings.get("enabled", False)) + self.useHoursCheck.setChecked(settings.get("replace_hours", False)) + self.trackSpin.setValue(settings.get("track", 0)) class TimecodeAppSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Timecode Settings') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Timecode Settings") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -129,18 +138,21 @@ def __init__(self, **kwargs): def retranslateUi(self): self.groupBox.setTitle( - translate('TimecodeSettings', 'Timecode Settings')) + translate("TimecodeSettings", "Timecode Settings") + ) self.formatLabel.setText( - translate('TimecodeSettings', 'Timecode Format:')) + translate("TimecodeSettings", "Timecode Format:") + ) self.protocolLabel.setText( - translate('TimecodeSettings', 'Timecode Protocol:')) + translate("TimecodeSettings", "Timecode Protocol:") + ) def getSettings(self): return { - 'format': self.formatBox.currentText(), - 'protocol': self.protocolCombo.currentText() + "format": self.formatBox.currentText(), + "protocol": self.protocolCombo.currentText(), } def loadSettings(self, settings): - self.formatBox.setCurrentText(settings['format']) - self.protocolCombo.setCurrentText(settings['protocol']) + self.formatBox.setCurrentText(settings["format"]) + self.protocolCombo.setCurrentText(settings["protocol"]) diff --git a/lisp/plugins/timecode/timecode.py b/lisp/plugins/timecode/timecode.py index c1bb6f609..11e2a6d0e 100644 --- a/lisp/plugins/timecode/timecode.py +++ b/lisp/plugins/timecode/timecode.py @@ -28,8 +28,7 @@ from lisp.plugins.timecode import protocols from lisp.plugins.timecode.cue_tracker import TimecodeCueTracker, TcFormat from lisp.plugins.timecode.protocol import TimecodeProtocol -from lisp.plugins.timecode.settings import TimecodeAppSettings, \ - TimecodeSettings +from lisp.plugins.timecode.settings import TimecodeAppSettings, TimecodeSettings from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.ui_utils import translate @@ -38,36 +37,33 @@ class Timecode(Plugin): - Name = 'Timecode' - Authors = ('Thomas Achtner',) - OptDepends = ('Midi',) - Description = 'Provide timecode via multiple protocols' + Name = "Timecode" + Authors = ("Thomas Achtner",) + OptDepends = ("Midi",) + Description = "Provide timecode via multiple protocols" def __init__(self, app): super().__init__(app) # Register a new Cue property to store settings Cue.timecode = Property( - default={ - 'enabled': False, - 'replace_hours': False, - 'track': 0 - } + default={"enabled": False, "replace_hours": False, "track": 0} ) # Register cue-settings-page CueSettingsRegistry().add(TimecodeSettings, MediaCue) # Register the settings widget AppConfigurationDialog.registerSettingsPage( - 'plugins.timecode', TimecodeAppSettings, Timecode.Config) + "plugins.timecode", TimecodeAppSettings, Timecode.Config + ) # Load available protocols protocols.load_protocols() # Create the cue tracker object self.__cue_tracker = TimecodeCueTracker( - self.__get_protocol(Timecode.Config['protocol']), - TcFormat[Timecode.Config['format']] + self.__get_protocol(Timecode.Config["protocol"]), + TcFormat[Timecode.Config["format"]], ) # Cues with timecode-tracking enabled @@ -87,9 +83,9 @@ def finalize(self): self.__cue_tracker.finalize() def __config_change(self, key, value): - if key == 'protocol': + if key == "protocol": self.__cue_tracker.protocol = self.__get_protocol(value) - elif key == 'format': + elif key == "format": self.__cue_tracker.format = value def __config_update(self, diff): @@ -102,9 +98,9 @@ def __get_protocol(self, protocol_name): except Exception: logger.error( translate( - 'Timecode', 'Cannot load timecode protocol: "{}"' + "Timecode", 'Cannot load timecode protocol: "{}"' ).format(protocol_name), - exc_info=True + exc_info=True, ) # Use a dummy protocol in case of failure return TimecodeProtocol() @@ -114,17 +110,18 @@ def __session_finalize(self): self.__cues.clear() def __cue_changed(self, cue, property_name, value): - if property_name == 'timecode': - if value.get('enabled', False): + if property_name == "timecode": + if value.get("enabled", False): cue.started.connect( - self.__cue_tracker.track, Connection.QtQueued) + self.__cue_tracker.track, Connection.QtQueued + ) else: self.__disable_on_cue(cue) def __cue_added(self, cue): cue.property_changed.connect(self.__cue_changed) # Check for current cue settings - self.__cue_changed(cue, 'timecode', cue.timecode) + self.__cue_changed(cue, "timecode", cue.timecode) def __cue_removed(self, cue): cue.property_changed.disconnect(self.__cue_changed) diff --git a/lisp/plugins/triggers/__init__.py b/lisp/plugins/triggers/__init__.py index f4f67c52c..10a5a0943 100644 --- a/lisp/plugins/triggers/__init__.py +++ b/lisp/plugins/triggers/__init__.py @@ -1 +1 @@ -from .triggers import Triggers \ No newline at end of file +from .triggers import Triggers diff --git a/lisp/plugins/triggers/triggers.py b/lisp/plugins/triggers/triggers.py index 52a51d35d..be0ad02e9 100644 --- a/lisp/plugins/triggers/triggers.py +++ b/lisp/plugins/triggers/triggers.py @@ -27,9 +27,9 @@ class Triggers(Plugin): - Name = 'Triggers' - Authors = ('Francesco Ceruti', ) - Description = 'Allow cues to react to other-cues state changes' + Name = "Triggers" + Authors = ("Francesco Ceruti",) + Description = "Allow cues to react to other-cues state changes" def __init__(self, app): super().__init__(app) @@ -53,15 +53,17 @@ def session_reset(self): self.__handlers.clear() def __cue_changed(self, cue, property_name, value): - if property_name == 'triggers': + if property_name == "triggers": if cue.id in self.__handlers: self.__handlers[cue.id].triggers = cue.triggers else: - self.__handlers[cue.id] = CueHandler(self.app, cue, cue.triggers) + self.__handlers[cue.id] = CueHandler( + self.app, cue, cue.triggers + ) def __cue_added(self, cue): cue.property_changed.connect(self.__cue_changed) - self.__cue_changed(cue, 'triggers', cue.triggers) + self.__cue_changed(cue, "triggers", cue.triggers) def __cue_removed(self, cue): cue.property_changed.disconnect(self.__cue_changed) diff --git a/lisp/plugins/triggers/triggers_handler.py b/lisp/plugins/triggers/triggers_handler.py index 76bcc2b13..0482ac5b4 100644 --- a/lisp/plugins/triggers/triggers_handler.py +++ b/lisp/plugins/triggers/triggers_handler.py @@ -26,10 +26,10 @@ class CueTriggers(Enum): - Started = QT_TRANSLATE_NOOP('CueTriggers', 'Started') - Paused = QT_TRANSLATE_NOOP('CueTriggers', 'Paused') - Stopped = QT_TRANSLATE_NOOP('CueTriggers', 'Stopped') - Ended = QT_TRANSLATE_NOOP('CueTriggers', 'Ended') + Started = QT_TRANSLATE_NOOP("CueTriggers", "Started") + Paused = QT_TRANSLATE_NOOP("CueTriggers", "Paused") + Stopped = QT_TRANSLATE_NOOP("CueTriggers", "Stopped") + Ended = QT_TRANSLATE_NOOP("CueTriggers", "Ended") class CueHandler: diff --git a/lisp/plugins/triggers/triggers_settings.py b/lisp/plugins/triggers/triggers_settings.py index 956a76531..aa7dcd857 100644 --- a/lisp/plugins/triggers/triggers_settings.py +++ b/lisp/plugins/triggers/triggers_settings.py @@ -18,22 +18,30 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt -from PyQt5.QtWidgets import QVBoxLayout, QDialogButtonBox, QSizePolicy, \ - QHeaderView, QTableView +from PyQt5.QtWidgets import ( + QVBoxLayout, + QDialogButtonBox, + QSizePolicy, + QHeaderView, + QTableView, +) from lisp.application import Application from lisp.cues.cue import CueAction from lisp.plugins.triggers.triggers_handler import CueTriggers from lisp.ui.cuelistdialog import CueSelectDialog -from lisp.ui.qdelegates import ComboBoxDelegate, CueActionDelegate, \ - CueSelectionDelegate +from lisp.ui.qdelegates import ( + ComboBoxDelegate, + CueActionDelegate, + CueSelectionDelegate, +) from lisp.ui.qmodels import CueClassRole, SimpleCueListModel from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate class TriggersSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Triggers') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Triggers") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -49,43 +57,48 @@ def __init__(self, **kwargs): self.layout().addWidget(self.triggersView) self.dialogButtons = QDialogButtonBox(self) - self.dialogButtons.setSizePolicy(QSizePolicy.Minimum, - QSizePolicy.Minimum) + self.dialogButtons.setSizePolicy( + QSizePolicy.Minimum, QSizePolicy.Minimum + ) self.layout().addWidget(self.dialogButtons) self.addButton = self.dialogButtons.addButton( - translate('TriggersSettings', 'Add'), QDialogButtonBox.ActionRole) + translate("TriggersSettings", "Add"), QDialogButtonBox.ActionRole + ) self.addButton.clicked.connect(self._add_trigger) self.delButton = self.dialogButtons.addButton( - translate('TriggersSettings', 'Remove'), - QDialogButtonBox.ActionRole) + translate("TriggersSettings", "Remove"), QDialogButtonBox.ActionRole + ) self.delButton.clicked.connect(self._remove_trigger) def _add_trigger(self): if self.cue_select.exec(): cue = self.cue_select.selected_cue() if cue is not None: - self.triggersModel.appendRow(cue.__class__, - CueTriggers.Started.value, - cue.id, - cue.CueActions[0]) + self.triggersModel.appendRow( + cue.__class__, + CueTriggers.Started.value, + cue.id, + cue.CueActions[0], + ) def _remove_trigger(self): self.triggersModel.removeRow(self.triggersView.currentIndex().row()) def loadSettings(self, settings): # Remove the edited cue from the list of possible targets - edited_cue = Application().cue_model.get(settings.get('id')) + edited_cue = Application().cue_model.get(settings.get("id")) if edited_cue: self.cue_select.remove_cue(edited_cue) - for trigger, targets in settings.get('triggers', {}).items(): + for trigger, targets in settings.get("triggers", {}).items(): for target, action in targets: target = Application().cue_model.get(target) if target is not None: - self.triggersModel.appendRow(target.__class__, trigger, - target.id, CueAction(action)) + self.triggersModel.appendRow( + target.__class__, trigger, target.id, CueAction(action) + ) def getSettings(self): triggers = {} @@ -99,7 +112,7 @@ def getSettings(self): if (target, action) not in triggers[trigger]: triggers[trigger].append((target, action)) - return {'triggers': triggers} + return {"triggers": triggers} class TriggersView(QTableView): @@ -120,10 +133,11 @@ def __init__(self, cue_select, **kwargs): self.verticalHeader().setHighlightSections(False) self.delegates = [ - ComboBoxDelegate(options=[e.value for e in CueTriggers], - tr_context='CueTriggers'), + ComboBoxDelegate( + options=[e.value for e in CueTriggers], tr_context="CueTriggers" + ), CueSelectionDelegate(cue_select), - CueActionDelegate() + CueActionDelegate(), ] for column, delegate in enumerate(self.delegates): @@ -133,9 +147,13 @@ def __init__(self, cue_select, **kwargs): class TriggersModel(SimpleCueListModel): def __init__(self): # NOTE: The model does fixed-indices operations based on this list - super().__init__([translate('TriggersSettings', 'Trigger'), - translate('TriggersSettings', 'Cue'), - translate('TriggersSettings', 'Action')]) + super().__init__( + [ + translate("TriggersSettings", "Trigger"), + translate("TriggersSettings", "Cue"), + translate("TriggersSettings", "Action"), + ] + ) self.rows_cc = [] @@ -145,8 +163,10 @@ def setData(self, index, value, role=Qt.DisplayRole): if result and role == CueClassRole: if self.rows[index.row()][2] not in value.CueActions: self.rows[index.row()][2] = value.CueActions[0] - self.dataChanged.emit(self.index(index.row(), 2), - self.index(index.row(), 2), - [Qt.DisplayRole, Qt.EditRole]) + self.dataChanged.emit( + self.index(index.row(), 2), + self.index(index.row(), 2), + [Qt.DisplayRole, Qt.EditRole], + ) return result diff --git a/lisp/ui/about.py b/lisp/ui/about.py index d9596b774..1fb10e385 100644 --- a/lisp/ui/about.py +++ b/lisp/ui/about.py @@ -21,8 +21,15 @@ from PyQt5 import QtCore from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QWidget, QTabWidget, \ - QTextBrowser, QDialogButtonBox +from PyQt5.QtWidgets import ( + QDialog, + QGridLayout, + QLabel, + QWidget, + QTabWidget, + QTextBrowser, + QDialogButtonBox, +) import lisp from lisp.ui.icons import IconTheme @@ -30,7 +37,7 @@ class About(QDialog): - LICENSE = ''' + LICENSE = """

Linux Show Player is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published by
@@ -42,39 +49,45 @@ class About(QDialog): MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

- ''' + """ DESCRIPTION = QT_TRANSLATE_NOOP( - 'AboutDialog', - 'Linux Show Player is a cue-player designed for stage productions.' + "AboutDialog", + "Linux Show Player is a cue-player designed for stage productions.", + ) + WEB_SITE = "http://linux-show-player.sourceforge.net" + DISCUSSION = "https://gitter.im/linux-show-player/linux-show-player" + SOURCE_CODE = "https://github.com/FrancescoCeruti/linux-show-player" + + CONTRIBUTORS = OrderedDict( + { + QT_TRANSLATE_NOOP("About", "Authors"): [ + ("Francesco Ceruti", "ceppofrancy@gmail.com") + ], + QT_TRANSLATE_NOOP("About", "Contributors"): [ + ("Yinameah", "https://github.com/Yinameah"), + ("nodiscc", "https://github.com/nodiscc"), + ("Thomas Achtner", "https://github.com/offtools"), + ], + QT_TRANSLATE_NOOP("About", "Translators"): [ + ("fri", "https://www.transifex.com/user/profile/fri", "Czech"), + ("Olivier Humbert", "https://github.com/trebmuh", "French"), + ( + "aroomthedoomed", + "https://github.com/aroomthedoomed", + "French", + ), + ("Luis García-Tornel", "tornel@gmail.com", "Spanish"), + ("miharix", "https://github.com/miharix", "Slovenian"), + ], + } ) - WEB_SITE = 'http://linux-show-player.sourceforge.net' - DISCUSSION = 'https://gitter.im/linux-show-player/linux-show-player' - SOURCE_CODE = 'https://github.com/FrancescoCeruti/linux-show-player' - - CONTRIBUTORS = OrderedDict({ - QT_TRANSLATE_NOOP('About', 'Authors'): [ - ('Francesco Ceruti', 'ceppofrancy@gmail.com') - ], - QT_TRANSLATE_NOOP('About', 'Contributors'): [ - ('Yinameah', 'https://github.com/Yinameah'), - ('nodiscc', 'https://github.com/nodiscc'), - ('Thomas Achtner', 'https://github.com/offtools') - ], - QT_TRANSLATE_NOOP('About', 'Translators'): [ - ('fri', 'https://www.transifex.com/user/profile/fri', 'Czech'), - ('Olivier Humbert', 'https://github.com/trebmuh', 'French'), - ('aroomthedoomed', 'https://github.com/aroomthedoomed', 'French'), - ('Luis García-Tornel', 'tornel@gmail.com', 'Spanish'), - ('miharix', 'https://github.com/miharix', 'Slovenian'), - ], - }) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setWindowModality(QtCore.Qt.ApplicationModal) - self.setWindowTitle(translate('About', 'About Linux Show Player')) + self.setWindowTitle(translate("About", "About Linux Show Player")) self.setMaximumSize(500, 420) self.setMinimumSize(500, 420) self.resize(500, 420) @@ -83,14 +96,16 @@ def __init__(self, *args, **kwargs): self.iconLabel = QLabel(self) self.iconLabel.setPixmap( - IconTheme.get('linux-show-player').pixmap(100, 100)) + IconTheme.get("linux-show-player").pixmap(100, 100) + ) self.layout().addWidget(self.iconLabel, 0, 0) self.shortInfo = QLabel(self) self.shortInfo.setAlignment(Qt.AlignCenter) self.shortInfo.setText( - '

Linux Show Player {0}

Copyright © Francesco Ceruti' - .format(str(lisp.__version__)) + "

Linux Show Player {0}

Copyright © Francesco Ceruti".format( + str(lisp.__version__) + ) ) self.layout().addWidget(self.shortInfo, 0, 1) @@ -102,29 +117,35 @@ def __init__(self, *args, **kwargs): self.info = QTextBrowser(self) self.info.setOpenExternalLinks(True) - self.info.setHtml(''' + self.info.setHtml( + """

{0}

{2}
{4}
- {6}
'''.format( - translate('AboutDialog', self.DESCRIPTION), - self.WEB_SITE, translate('AboutDialog', 'Web site'), - self.DISCUSSION, translate('AboutDialog', 'Discussion'), - self.SOURCE_CODE, translate('AboutDialog', 'Source code')) + {6}
""".format( + translate("AboutDialog", self.DESCRIPTION), + self.WEB_SITE, + translate("AboutDialog", "Web site"), + self.DISCUSSION, + translate("AboutDialog", "Discussion"), + self.SOURCE_CODE, + translate("AboutDialog", "Source code"), + ) ) - self.tabWidget.addTab(self.info, translate('AboutDialog', 'Info')) + self.tabWidget.addTab(self.info, translate("AboutDialog", "Info")) self.license = QTextBrowser(self) self.license.setOpenExternalLinks(True) self.license.setHtml(self.LICENSE) - self.tabWidget.addTab(self.license, translate('AboutDialog', 'License')) + self.tabWidget.addTab(self.license, translate("AboutDialog", "License")) self.contributors = QTextBrowser(self) self.contributors.setOpenExternalLinks(True) self.contributors.setHtml(self.__contributors()) - self.tabWidget.addTab(self.contributors, - translate('AboutDialog', 'Contributors')) + self.tabWidget.addTab( + self.contributors, translate("AboutDialog", "Contributors") + ) # Ok button self.buttons = QDialogButtonBox(QDialogButtonBox.Ok) @@ -142,25 +163,26 @@ def __init__(self, *args, **kwargs): self.buttons.setFocus() def __contributors(self): - text = '' + text = "" for section, people in self.CONTRIBUTORS.items(): - text += '{0}:
'.format( - translate('About', section) + text += "{0}:
".format( + translate("About", section) ) for person in people: text += person[0] - if '://' in person[1]: + if "://" in person[1]: text += ' - {1}'.format( - person[1], person[1][person[1].index('://')+3:]) + person[1], person[1][person[1].index("://") + 3 :] + ) elif person[1]: text += ' - {0}'.format(person[1]) if len(person) >= 3: - text += ' ({})'.format(person[2]) + text += " ({})".format(person[2]) - text += '
' + text += "
" - text += '
' + text += "
" return text diff --git a/lisp/ui/cuelistdialog.py b/lisp/ui/cuelistdialog.py index 3d98d8798..70b187e41 100644 --- a/lisp/ui/cuelistdialog.py +++ b/lisp/ui/cuelistdialog.py @@ -18,13 +18,24 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QDialog, QTreeWidget, QHeaderView, QVBoxLayout, \ - QDialogButtonBox, QTreeWidgetItem +from PyQt5.QtWidgets import ( + QDialog, + QTreeWidget, + QHeaderView, + QVBoxLayout, + QDialogButtonBox, + QTreeWidgetItem, +) class CueSelectDialog(QDialog): - def __init__(self, cues=None, properties=('index', 'name'), - selection_mode=QTreeWidget.SingleSelection, **kwargs): + def __init__( + self, + cues=None, + properties=("index", "name"), + selection_mode=QTreeWidget.SingleSelection, + **kwargs, + ): super().__init__(**kwargs) self.setMinimumSize(600, 400) @@ -63,7 +74,7 @@ def add_cue(self, cue): item.setTextAlignment(0, Qt.AlignCenter) for n, prop in enumerate(self._properties): - item.setData(n, Qt.DisplayRole, getattr(cue, prop, 'Undefined')) + item.setData(n, Qt.DisplayRole, getattr(cue, prop, "Undefined")) self._cues[cue] = item item.setData(0, Qt.UserRole, cue) diff --git a/lisp/ui/icons/__init__.py b/lisp/ui/icons/__init__.py index e6ee40019..75b7c562f 100644 --- a/lisp/ui/icons/__init__.py +++ b/lisp/ui/icons/__init__.py @@ -32,13 +32,16 @@ def icon_themes_names(): for entry in scandir(path.dirname(__file__)): - if (entry.is_dir() and entry.name != ICON_THEME_COMMON - and entry.name[0] != '_'): + if ( + entry.is_dir() + and entry.name != ICON_THEME_COMMON + and entry.name[0] != "_" + ): yield entry.name class IconTheme: - _SEARCH_PATTERN = '{}/**/{}.*' + _SEARCH_PATTERN = "{}/**/{}.*" _BLANK_ICON = QIcon() _GlobalCache = {} _GlobalTheme = None diff --git a/lisp/ui/layoutselect.py b/lisp/ui/layoutselect.py index 8ef1cc57c..9544788d2 100644 --- a/lisp/ui/layoutselect.py +++ b/lisp/ui/layoutselect.py @@ -20,21 +20,27 @@ import os from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QDialog, QComboBox, QPushButton, QFrame,\ - QTextBrowser, QFileDialog, QGridLayout +from PyQt5.QtWidgets import ( + QDialog, + QComboBox, + QPushButton, + QFrame, + QTextBrowser, + QFileDialog, + QGridLayout, +) from lisp import layout from lisp.ui.ui_utils import translate class LayoutSelect(QDialog): - def __init__(self, **kwargs): super().__init__(**kwargs) - self.filepath = '' + self.filepath = "" self.setWindowModality(Qt.WindowModal) - self.setWindowTitle(translate('LayoutSelect', 'Layout selection')) + self.setWindowTitle(translate("LayoutSelect", "Layout selection")) self.setMaximumSize(675, 300) self.setMinimumSize(675, 300) self.resize(675, 300) @@ -47,11 +53,11 @@ def __init__(self, **kwargs): self.layout().addWidget(self.layoutCombo, 0, 0) self.confirmButton = QPushButton(self) - self.confirmButton.setText(translate('LayoutSelect', 'Select layout')) + self.confirmButton.setText(translate("LayoutSelect", "Select layout")) self.layout().addWidget(self.confirmButton, 0, 1) self.fileButton = QPushButton(self) - self.fileButton.setText(translate('LayoutSelect', 'Open file')) + self.fileButton.setText(translate("LayoutSelect", "Open file")) self.layout().addWidget(self.fileButton, 0, 2) self.layout().setColumnStretch(0, 3) @@ -70,7 +76,7 @@ def __init__(self, **kwargs): self.layoutCombo.addItem(layout_class.NAME, layout_class) if self.layoutCombo.count() == 0: - raise Exception('No layout installed!') + raise Exception("No layout installed!") self.confirmButton.clicked.connect(self.accept) self.fileButton.clicked.connect(self.open_file) @@ -81,21 +87,22 @@ def selected(self): def show_description(self): layout = self.layoutCombo.currentData() - details = '
    ' + details = "
      " for detail in layout.DETAILS: - details += '
    • ' + translate('LayoutDetails', detail) - details += '
    ' + details += "
  • " + translate("LayoutDetails", detail) + details += "
" self.description.setHtml( - '

{}

{}

{}'.format( + "

{}

{}

{}".format( layout.NAME, - translate('LayoutDescription', layout.DESCRIPTION), - details + translate("LayoutDescription", layout.DESCRIPTION), + details, ) ) def open_file(self): path, _ = QFileDialog.getOpenFileName( - self, filter='*.lsp', directory=os.getenv('HOME')) + self, filter="*.lsp", directory=os.getenv("HOME") + ) self.filepath = path self.accept() diff --git a/lisp/ui/logging/common.py b/lisp/ui/logging/common.py index 4b48b8ff5..aa1b21737 100644 --- a/lisp/ui/logging/common.py +++ b/lisp/ui/logging/common.py @@ -20,37 +20,37 @@ from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP LOG_LEVELS = { - 'DEBUG': QT_TRANSLATE_NOOP('Logging', 'Debug'), - 'INFO': QT_TRANSLATE_NOOP('Logging', 'Info'), - 'WARNING': QT_TRANSLATE_NOOP('Logging', 'Warning'), - 'ERROR': QT_TRANSLATE_NOOP('Logging', 'Error'), - 'CRITICAL': QT_TRANSLATE_NOOP('Logging', 'Critical') + "DEBUG": QT_TRANSLATE_NOOP("Logging", "Debug"), + "INFO": QT_TRANSLATE_NOOP("Logging", "Info"), + "WARNING": QT_TRANSLATE_NOOP("Logging", "Warning"), + "ERROR": QT_TRANSLATE_NOOP("Logging", "Error"), + "CRITICAL": QT_TRANSLATE_NOOP("Logging", "Critical"), } LOG_ICONS_NAMES = { - 'DEBUG': 'dialog-question', - 'INFO': 'dialog-information', - 'WARNING': 'dialog-warning', - 'ERROR': 'dialog-error', - 'CRITICAL': 'dialog-error' + "DEBUG": "dialog-question", + "INFO": "dialog-information", + "WARNING": "dialog-warning", + "ERROR": "dialog-error", + "CRITICAL": "dialog-error", } LOG_ATTRIBUTES = { - 'asctime': QT_TRANSLATE_NOOP('Logging', 'Time'), - 'msecs': QT_TRANSLATE_NOOP('Logging', 'Milliseconds'), - 'name': QT_TRANSLATE_NOOP('Logging', 'Logger name'), - 'levelname': QT_TRANSLATE_NOOP('Logging', 'Level'), - 'message': QT_TRANSLATE_NOOP('Logging', 'Message'), - 'funcName': QT_TRANSLATE_NOOP('Logging', 'Function'), - 'pathname': QT_TRANSLATE_NOOP('Logging', 'Path name'), - 'filename': QT_TRANSLATE_NOOP('Logging', 'File name'), - 'lineno': QT_TRANSLATE_NOOP('Logging', 'Line no.'), - 'module': QT_TRANSLATE_NOOP('Logging', 'Module'), - 'process': QT_TRANSLATE_NOOP('Logging', 'Process ID'), - 'processName': QT_TRANSLATE_NOOP('Logging', 'Process name'), - 'thread': QT_TRANSLATE_NOOP('Logging', 'Thread ID'), - 'threadName': QT_TRANSLATE_NOOP('Logging', 'Thread name'), - 'exc_info': QT_TRANSLATE_NOOP('Logging', 'Exception info') + "asctime": QT_TRANSLATE_NOOP("Logging", "Time"), + "msecs": QT_TRANSLATE_NOOP("Logging", "Milliseconds"), + "name": QT_TRANSLATE_NOOP("Logging", "Logger name"), + "levelname": QT_TRANSLATE_NOOP("Logging", "Level"), + "message": QT_TRANSLATE_NOOP("Logging", "Message"), + "funcName": QT_TRANSLATE_NOOP("Logging", "Function"), + "pathname": QT_TRANSLATE_NOOP("Logging", "Path name"), + "filename": QT_TRANSLATE_NOOP("Logging", "File name"), + "lineno": QT_TRANSLATE_NOOP("Logging", "Line no."), + "module": QT_TRANSLATE_NOOP("Logging", "Module"), + "process": QT_TRANSLATE_NOOP("Logging", "Process ID"), + "processName": QT_TRANSLATE_NOOP("Logging", "Process name"), + "thread": QT_TRANSLATE_NOOP("Logging", "Thread ID"), + "threadName": QT_TRANSLATE_NOOP("Logging", "Thread name"), + "exc_info": QT_TRANSLATE_NOOP("Logging", "Exception info"), } LogRecordRole = Qt.UserRole diff --git a/lisp/ui/logging/details.py b/lisp/ui/logging/details.py index a143c0ae4..6c7cbfe03 100644 --- a/lisp/ui/logging/details.py +++ b/lisp/ui/logging/details.py @@ -32,23 +32,22 @@ def __init__(self, *args): self.setLineWrapMode(self.NoWrap) self.setReadOnly(True) - font = QFont('Monospace') + font = QFont("Monospace") font.setStyleHint(QFont.Monospace) self.setFont(font) def setLogRecord(self, record): self._record = record - text = '' + text = "" for attr, attr_text in LOG_ATTRIBUTES.items(): - text += '⇨{}:\n {}'.format( - attr_text, self.formatAttribute(attr)) + text += "⇨{}:\n {}".format(attr_text, self.formatAttribute(attr)) self.setText(text) def formatAttribute(self, attribute): value = getattr(self._record, attribute, None) - if attribute == 'exc_info': + if attribute == "exc_info": if value is not None: - return ' '.join(traceback.format_exception(*value)) + return " ".join(traceback.format_exception(*value)) - return str(value) + '\n' + return str(value) + "\n" diff --git a/lisp/ui/logging/dialog.py b/lisp/ui/logging/dialog.py index 30b7b0505..f05e0dd75 100644 --- a/lisp/ui/logging/dialog.py +++ b/lisp/ui/logging/dialog.py @@ -20,8 +20,15 @@ import logging from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QDialogButtonBox, \ - QSizePolicy, QPushButton, QTextEdit +from PyQt5.QtWidgets import ( + QDialog, + QGridLayout, + QLabel, + QDialogButtonBox, + QSizePolicy, + QPushButton, + QTextEdit, +) from lisp.ui.logging.common import LOG_LEVELS, LOG_ICONS_NAMES from lisp.ui.icons import IconTheme @@ -71,16 +78,17 @@ def __init__(self, **kwargs): self.okButton.clicked.connect(self.nextRecord) self.dismissButton = QPushButton(self) - self.dismissButton.setText(translate('Logging', 'Dismiss all')) + self.dismissButton.setText(translate("Logging", "Dismiss all")) self.dismissButton.clicked.connect(self.dismissAll) self.dismissButton.hide() self.buttonBox.addButton(self.dismissButton, QDialogButtonBox.ResetRole) self.detailsButton = QPushButton(self) self.detailsButton.setCheckable(True) - self.detailsButton.setText(translate('Logging', 'Show details')) + self.detailsButton.setText(translate("Logging", "Show details")) self.buttonBox.addButton( - self.detailsButton, QDialogButtonBox.ActionRole) + self.detailsButton, QDialogButtonBox.ActionRole + ) self.detailsText = QTextEdit(self) self.detailsText.setReadOnly(True) @@ -100,23 +108,26 @@ def updateDisplayed(self): self.messageLabel.setText(record.message) self.iconLabel.setPixmap( - IconTheme.get(LOG_ICONS_NAMES[record.levelname]).pixmap(32)) + IconTheme.get(LOG_ICONS_NAMES[record.levelname]).pixmap(32) + ) self.setWindowTitle( - '{} ({})'.format( - translate('Logging', LOG_LEVELS[record.levelname]), - len(self._records) + "{} ({})".format( + translate("Logging", LOG_LEVELS[record.levelname]), + len(self._records), ) ) # Get record details (exception traceback) - details = '' + details = "" if record.exc_info is not None: details = self._formatter.formatException(record.exc_info) self.detailsText.setText(details) - width = self.detailsText.document().idealWidth() +\ - self.detailsText.contentsMargins().left() * 2 + width = ( + self.detailsText.document().idealWidth() + + self.detailsText.contentsMargins().left() * 2 + ) self.detailsText.setFixedWidth(width if width < 800 else 800) self.detailsButton.setVisible(bool(details)) diff --git a/lisp/ui/logging/models.py b/lisp/ui/logging/models.py index 57fe3c531..8b9f41fde 100644 --- a/lisp/ui/logging/models.py +++ b/lisp/ui/logging/models.py @@ -19,16 +19,24 @@ from collections import deque -from PyQt5.QtCore import QModelIndex, Qt, QSortFilterProxyModel, \ - QAbstractTableModel +from PyQt5.QtCore import ( + QModelIndex, + Qt, + QSortFilterProxyModel, + QAbstractTableModel, +) from PyQt5.QtGui import QFont, QColor from lisp.core.util import typename -from lisp.ui.logging.common import LogRecordRole, LOG_ATTRIBUTES, LogAttributeRole +from lisp.ui.logging.common import ( + LogRecordRole, + LOG_ATTRIBUTES, + LogAttributeRole, +) class LogRecordModel(QAbstractTableModel): - Font = QFont('Monospace') + Font = QFont("Monospace") Font.setStyleHint(QFont.Monospace) def __init__(self, columns, bg, fg, limit=0, **kwargs): @@ -129,8 +137,9 @@ def setSourceModel(self, source_model): super().setSourceModel(source_model) else: raise TypeError( - 'LogRecordFilterModel source must be LogRecordModel, not {}' - .format(typename(source_model)) + "LogRecordFilterModel source must be LogRecordModel, not {}".format( + typename(source_model) + ) ) def filterAcceptsRow(self, source_row, source_parent): @@ -143,18 +152,17 @@ def filterAcceptsRow(self, source_row, source_parent): def log_model_factory(config): # Get logging configuration - limit = config.get('logging.limit', 0) + limit = config.get("logging.limit", 0) levels_background = { level: QColor(*color) - for level, color - in config.get('logging.backgrounds', {}).items() + for level, color in config.get("logging.backgrounds", {}).items() } levels_foreground = { level: QColor(*color) - for level, color - in config.get('logging.foregrounds', {}).items() + for level, color in config.get("logging.foregrounds", {}).items() } # Return a new model to store logging records return LogRecordModel( - LOG_ATTRIBUTES, levels_background, levels_foreground, limit=limit) + LOG_ATTRIBUTES, levels_background, levels_foreground, limit=limit + ) diff --git a/lisp/ui/logging/status.py b/lisp/ui/logging/status.py index 2b454999e..5d2c37bc5 100644 --- a/lisp/ui/logging/status.py +++ b/lisp/ui/logging/status.py @@ -54,18 +54,19 @@ def __init__(self, log_model, icons_size=16, **kwargs): self.layout().addWidget(self.line) self.iconWidget = QWidget(self) - self.iconWidget.setToolTip('Errors/Warnings') + self.iconWidget.setToolTip("Errors/Warnings") self.iconWidget.setLayout(QHBoxLayout()) self.iconWidget.layout().setContentsMargins(0, 0, 0, 0) self.iconWidget.layout().setSpacing(5) self.layout().addWidget(self.iconWidget) - self.errorsCount = QLabel('0', self.iconWidget) + self.errorsCount = QLabel("0", self.iconWidget) self.iconWidget.layout().addWidget(self.errorsCount) self.errorIcon = QLabel(self.iconWidget) self.errorIcon.setPixmap( - IconTheme.get('dialog-error').pixmap(icons_size)) + IconTheme.get("dialog-error").pixmap(icons_size) + ) self.iconWidget.layout().addWidget(self.errorIcon) def mouseDoubleClickEvent(self, e): @@ -77,7 +78,7 @@ def _new_rows(self, parent, start, end): if record.levelno >= logging.INFO: # Display only the fist line of text - self.messageLabel.setText(record.message.split('\n')[0]) + self.messageLabel.setText(record.message.split("\n")[0]) for n in range(start, end + 1): level = self._log_model.record(n).levelno diff --git a/lisp/ui/logging/viewer.py b/lisp/ui/logging/viewer.py index 5e447ac0e..0d2326256 100644 --- a/lisp/ui/logging/viewer.py +++ b/lisp/ui/logging/viewer.py @@ -18,12 +18,25 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QAction, QToolBar, QMainWindow, QStatusBar, \ - QLabel, QTableView, QVBoxLayout, QWidget, QSplitter +from PyQt5.QtWidgets import ( + QAction, + QToolBar, + QMainWindow, + QStatusBar, + QLabel, + QTableView, + QVBoxLayout, + QWidget, + QSplitter, +) from lisp.ui.icons import IconTheme -from lisp.ui.logging.common import LOG_LEVELS, LogAttributeRole, \ - LOG_ICONS_NAMES, LogRecordRole +from lisp.ui.logging.common import ( + LOG_LEVELS, + LogAttributeRole, + LOG_ICONS_NAMES, + LogRecordRole, +) from lisp.ui.logging.details import LogDetails from lisp.ui.logging.models import LogRecordFilterModel from lisp.ui.ui_utils import translate @@ -42,7 +55,8 @@ def __init__(self, log_model, config, **kwargs): """ super().__init__(**kwargs) self.setWindowTitle( - translate('Logging', 'Linux Show Player - Log Viewer')) + translate("Logging", "Linux Show Player - Log Viewer") + ) self.resize(800, 600) self.setCentralWidget(QSplitter()) self.centralWidget().setOrientation(Qt.Vertical) @@ -58,15 +72,12 @@ def __init__(self, log_model, config, **kwargs): self.addToolBar(self.optionsToolBar) # Create level-toggle actions self.levelsActions = self._createActions( - self.optionsToolBar, - LOG_LEVELS, - LOG_ICONS_NAMES, - self._toggleLevel + self.optionsToolBar, LOG_LEVELS, LOG_ICONS_NAMES, self._toggleLevel ) # Setup level filters and columns - visible_levels = config.get('logging.viewer.visibleLevels', ()) - visible_columns = config.get('logging.viewer.visibleColumns', ()) + visible_levels = config.get("logging.viewer.visibleLevels", ()) + visible_columns = config.get("logging.viewer.visibleColumns", ()) for level in visible_levels: self.levelsActions[level].setChecked(True) @@ -92,7 +103,8 @@ def __init__(self, log_model, config, **kwargs): # Setup visible columns for n in range(self.filterModel.columnCount()): column = self.filterModel.headerData( - n, Qt.Horizontal, LogAttributeRole) + n, Qt.Horizontal, LogAttributeRole + ) self.logView.setColumnHidden(n, not column in visible_columns) @@ -102,20 +114,23 @@ def __init__(self, log_model, config, **kwargs): self.filterModel.modelReset.connect(self._rowsChanged) self.logView.selectionModel().selectionChanged.connect( - self._selectionChanged) + self._selectionChanged + ) def _selectionChanged(self, selection): if selection.indexes(): self.detailsView.setLogRecord( - self.filterModel.data(selection.indexes()[0], LogRecordRole)) + self.filterModel.data(selection.indexes()[0], LogRecordRole) + ) else: self.detailsView.setLogRecord(None) def _rowsChanged(self): self.statusLabel.setText( - 'Showing {} of {} records'.format( + "Showing {} of {} records".format( self.filterModel.rowCount(), - self.filterModel.sourceModel().rowCount()) + self.filterModel.sourceModel().rowCount(), + ) ) self.logView.resizeColumnsToContents() # QT Misbehavior: we need to reset the flag @@ -132,8 +147,7 @@ def _createActions(self, menu, actions, icons, trigger_slot): menu_actions = {} for key, name in actions.items(): action = QAction( - IconTheme.get(icons.get(key)), - translate('Logging', name) + IconTheme.get(icons.get(key)), translate("Logging", name) ) action.setCheckable(True) action.triggered.connect(self._actionSlot(trigger_slot, key)) diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index e30b51f4c..d68c96e4e 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -23,8 +23,18 @@ from PyQt5 import QtCore from PyQt5.QtCore import pyqtSignal from PyQt5.QtGui import QKeySequence -from PyQt5.QtWidgets import QMainWindow, QStatusBar, QMenuBar, QMenu, QAction, \ - qApp, QFileDialog, QMessageBox, QVBoxLayout, QWidget +from PyQt5.QtWidgets import ( + QMainWindow, + QStatusBar, + QMenuBar, + QMenu, + QAction, + qApp, + QFileDialog, + QMessageBox, + QVBoxLayout, + QWidget, +) from lisp.core.actions_handler import MainActionsHandler from lisp.core.singleton import QSingleton @@ -44,7 +54,7 @@ class MainWindow(QMainWindow, metaclass=QSingleton): save_session = pyqtSignal(str) open_session = pyqtSignal(str) - def __init__(self, conf, title='Linux Show Player', **kwargs): + def __init__(self, conf, title="Linux Show Player", **kwargs): super().__init__(**kwargs) self.setMinimumSize(500, 400) self.setCentralWidget(QWidget()) @@ -168,7 +178,8 @@ def __init__(self, conf, title='Linux Show Player', **kwargs): # Logging dialogs for errors self.logDialogs = LogDialogs( - self.logModel, level=logging.ERROR, parent=self) + self.logModel, level=logging.ERROR, parent=self + ) # Set component text self.retranslateUi() @@ -176,50 +187,54 @@ def __init__(self, conf, title='Linux Show Player', **kwargs): def retranslateUi(self): self.setWindowTitle(self._title) # menuFile - self.menuFile.setTitle(translate('MainWindow', '&File')) - self.newSessionAction.setText(translate('MainWindow', 'New session')) + self.menuFile.setTitle(translate("MainWindow", "&File")) + self.newSessionAction.setText(translate("MainWindow", "New session")) self.newSessionAction.setShortcut(QKeySequence.New) - self.openSessionAction.setText(translate('MainWindow', 'Open')) + self.openSessionAction.setText(translate("MainWindow", "Open")) self.openSessionAction.setShortcut(QKeySequence.Open) - self.saveSessionAction.setText(translate('MainWindow', 'Save session')) + self.saveSessionAction.setText(translate("MainWindow", "Save session")) self.saveSessionAction.setShortcut(QKeySequence.Save) - self.editPreferences.setText(translate('MainWindow', 'Preferences')) + self.editPreferences.setText(translate("MainWindow", "Preferences")) self.editPreferences.setShortcut(QKeySequence.Preferences) - self.saveSessionWithName.setText(translate('MainWindow', 'Save as')) + self.saveSessionWithName.setText(translate("MainWindow", "Save as")) self.saveSessionWithName.setShortcut(QKeySequence.SaveAs) - self.fullScreenAction.setText(translate('MainWindow', 'Full Screen')) + self.fullScreenAction.setText(translate("MainWindow", "Full Screen")) self.fullScreenAction.setShortcut(QKeySequence.FullScreen) - self.exitAction.setText(translate('MainWindow', 'Exit')) + self.exitAction.setText(translate("MainWindow", "Exit")) # menuEdit - self.menuEdit.setTitle(translate('MainWindow', '&Edit')) - self.actionUndo.setText(translate('MainWindow', 'Undo')) + self.menuEdit.setTitle(translate("MainWindow", "&Edit")) + self.actionUndo.setText(translate("MainWindow", "Undo")) self.actionUndo.setShortcut(QKeySequence.Undo) - self.actionRedo.setText(translate('MainWindow', 'Redo')) + self.actionRedo.setText(translate("MainWindow", "Redo")) self.actionRedo.setShortcut(QKeySequence.Redo) - self.selectAll.setText(translate('MainWindow', 'Select all')) + self.selectAll.setText(translate("MainWindow", "Select all")) self.selectAllMedia.setText( - translate('MainWindow', 'Select all media cues')) + translate("MainWindow", "Select all media cues") + ) self.selectAll.setShortcut(QKeySequence.SelectAll) - self.deselectAll.setText(translate('MainWindow', 'Deselect all')) - self.deselectAll.setShortcut(translate('MainWindow', 'CTRL+SHIFT+A')) + self.deselectAll.setText(translate("MainWindow", "Deselect all")) + self.deselectAll.setShortcut(translate("MainWindow", "CTRL+SHIFT+A")) self.invertSelection.setText( - translate('MainWindow', 'Invert selection')) - self.invertSelection.setShortcut(translate('MainWindow', 'CTRL+I')) - self.multiEdit.setText(translate('MainWindow', 'Edit selected')) - self.multiEdit.setShortcut(translate('MainWindow', 'CTRL+SHIFT+E')) + translate("MainWindow", "Invert selection") + ) + self.invertSelection.setShortcut(translate("MainWindow", "CTRL+I")) + self.multiEdit.setText(translate("MainWindow", "Edit selected")) + self.multiEdit.setShortcut(translate("MainWindow", "CTRL+SHIFT+E")) # menuLayout - self.menuLayout.setTitle(translate('MainWindow', '&Layout')) + self.menuLayout.setTitle(translate("MainWindow", "&Layout")) # menuTools - self.menuTools.setTitle(translate('MainWindow', '&Tools')) - self.multiEdit.setText(translate('MainWindow', 'Edit selection')) + self.menuTools.setTitle(translate("MainWindow", "&Tools")) + self.multiEdit.setText(translate("MainWindow", "Edit selection")) # menuAbout - self.menuAbout.setTitle(translate('MainWindow', '&About')) - self.actionAbout.setText(translate('MainWindow', 'About')) - self.actionAbout_Qt.setText(translate('MainWindow', 'About Qt')) + self.menuAbout.setTitle(translate("MainWindow", "&About")) + self.actionAbout.setText(translate("MainWindow", "About")) + self.actionAbout_Qt.setText(translate("MainWindow", "About Qt")) def set_session(self, session): if self.session is not None: - self.centralWidget().layout().removeWidget(self.session.layout.view()) + self.centralWidget().layout().removeWidget( + self.session.layout.view() + ) # Remove ownership, this allow the widget to be deleted self.session.layout.view().setParent(None) @@ -231,8 +246,9 @@ def closeEvent(self, event): self._exit() event.ignore() - def register_cue_menu_action(self, name, function, category='', - shortcut=''): + def register_cue_menu_action( + self, name, function, category="", shortcut="" + ): """Register a new-cue choice for the edit-menu param name: The name for the MenuAction @@ -242,12 +258,12 @@ def register_cue_menu_action(self, name, function, category='', """ action = QAction(self) - action.setText(translate('MainWindow', name)) + action.setText(translate("MainWindow", name)) action.triggered.connect(function) - if shortcut != '': - action.setShortcut(translate('MainWindow', shortcut)) + if shortcut != "": + action.setShortcut(translate("MainWindow", shortcut)) - if category != '': + if category != "": if category not in self._cue_add_menu: menu = QMenu(category, self) self._cue_add_menu[category] = menu @@ -258,9 +274,9 @@ def register_cue_menu_action(self, name, function, category='', self.menuEdit.insertAction(self.cueSeparator, action) def update_window_title(self): - tile = self._title + ' - ' + self.session.name() + tile = self._title + " - " + self.session.name() if not MainActionsHandler.is_saved(): - tile = '*' + tile + tile = "*" + tile self.setWindowTitle(tile) @@ -274,18 +290,19 @@ def _action_redone(self, action): self.update_window_title() def _save(self): - if self.session.session_file == '': + if self.session.session_file == "": self._save_with_name() else: self.save_session.emit(self.session.session_file) def _save_with_name(self): filename, ok = QFileDialog.getSaveFileName( - parent=self, filter='*.lsp', directory=self.session.path()) + parent=self, filter="*.lsp", directory=self.session.path() + ) if ok: - if not filename.endswith('.lsp'): - filename += '.lsp' + if not filename.endswith(".lsp"): + filename += ".lsp" self.save_session.emit(filename) @@ -295,8 +312,9 @@ def _show_preferences(self): def _load_from_file(self): if self._check_saved(): - path, _ = QFileDialog.getOpenFileName(self, filter='*.lsp', - directory=os.getenv('HOME')) + path, _ = QFileDialog.getOpenFileName( + self, filter="*.lsp", directory=os.getenv("HOME") + ) if os.path.exists(path): self.open_session.emit(path) @@ -309,13 +327,16 @@ def _check_saved(self): if not MainActionsHandler.is_saved(): msgBox = QMessageBox(self) msgBox.setIcon(QMessageBox.Warning) - msgBox.setWindowTitle(translate('MainWindow', 'Close session')) + msgBox.setWindowTitle(translate("MainWindow", "Close session")) msgBox.setText( - translate('MainWindow', 'The current session is not saved.')) + translate("MainWindow", "The current session is not saved.") + ) msgBox.setInformativeText( - translate('MainWindow', 'Discard the changes?')) - msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | - QMessageBox.Cancel) + translate("MainWindow", "Discard the changes?") + ) + msgBox.setStandardButtons( + QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel + ) msgBox.setDefaultButton(QMessageBox.Save) result = msgBox.exec_() diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index b8f06dec6..a63d6043a 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -18,8 +18,15 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QEvent -from PyQt5.QtWidgets import QStyledItemDelegate, QComboBox, QSpinBox, \ - QLineEdit, QStyle, QDialog, QCheckBox +from PyQt5.QtWidgets import ( + QStyledItemDelegate, + QComboBox, + QSpinBox, + QLineEdit, + QStyle, + QDialog, + QCheckBox, +) from lisp.application import Application from lisp.cues.cue import CueAction @@ -32,15 +39,16 @@ class LabelDelegate(QStyledItemDelegate): def _text(self, painter, option, index): - return '' + return "" def paint(self, painter, option, index): # Add 4px of left an right padding option.rect.adjust(4, 0, 4, 0) text = self._text(painter, option, index) - text = option.fontMetrics.elidedText(text, Qt.ElideRight, - option.rect.width()) + text = option.fontMetrics.elidedText( + text, Qt.ElideRight, option.rect.width() + ) painter.save() @@ -181,10 +189,7 @@ def paint(self, painter, option, index): def createEditor(self, parent, option, index): editor = QEnumComboBox( - self.enum, - mode=self.mode, - trItem=self.trItem, - parent=parent + self.enum, mode=self.mode, trItem=self.trItem, parent=parent ) editor.setFrame(False) @@ -220,9 +225,7 @@ def createEditor(self, parent, option, index): self.cue_class = index.data(CueClassRole) editor = CueActionComboBox( - self.cue_class.CueActions, - mode=self.mode, - parent=parent + self.cue_class.CueActions, mode=self.mode, parent=parent ) editor.setFrame(False) @@ -237,9 +240,9 @@ def __init__(self, cue_select_dialog, **kwargs): def _text(self, painter, option, index): cue = Application().cue_model.get(index.data()) if cue is not None: - return '{} | {}'.format(cue.index, cue.name) + return "{} | {}".format(cue.index, cue.name) - return 'UNDEF' + return "UNDEF" def editorEvent(self, event, model, option, index): if event.type() == QEvent.MouseButtonDblClick: diff --git a/lisp/ui/qmodels.py b/lisp/ui/qmodels.py index 35bac3aae..b617192bb 100644 --- a/lisp/ui/qmodels.py +++ b/lisp/ui/qmodels.py @@ -80,9 +80,11 @@ def setData(self, index, value, role=Qt.DisplayRole): if index.isValid(): if role == Qt.DisplayRole or role == Qt.EditRole: self.rows[index.row()][index.column()] = value - self.dataChanged.emit(self.index(index.row(), 0), - self.index(index.row(), index.column()), - [Qt.DisplayRole, Qt.EditRole]) + self.dataChanged.emit( + self.index(index.row(), 0), + self.index(index.row(), index.column()), + [Qt.DisplayRole, Qt.EditRole], + ) return True return False diff --git a/lisp/ui/settings/app_configuration.py b/lisp/ui/settings/app_configuration.py index cb91637e3..11493186b 100644 --- a/lisp/ui/settings/app_configuration.py +++ b/lisp/ui/settings/app_configuration.py @@ -30,7 +30,7 @@ logger = logging.getLogger(__name__) -PageEntry = namedtuple('PageEntry', ('page', 'config')) +PageEntry = namedtuple("PageEntry", ("page", "config")) class AppConfigurationDialog(QDialog): @@ -38,7 +38,7 @@ class AppConfigurationDialog(QDialog): def __init__(self, **kwargs): super().__init__(**kwargs) - self.setWindowTitle(translate('AppConfiguration', 'LiSP preferences')) + self.setWindowTitle(translate("AppConfiguration", "LiSP preferences")) self.setWindowModality(QtCore.Qt.WindowModal) self.setMaximumSize(800, 510) self.setMinimumSize(800, 510) @@ -56,18 +56,21 @@ def __init__(self, **kwargs): self.dialogButtons = QDialogButtonBox(self) self.dialogButtons.setStandardButtons( - QDialogButtonBox.Cancel | - QDialogButtonBox.Apply | - QDialogButtonBox.Ok + QDialogButtonBox.Cancel + | QDialogButtonBox.Apply + | QDialogButtonBox.Ok ) self.layout().addWidget(self.dialogButtons) self.dialogButtons.button(QDialogButtonBox.Cancel).clicked.connect( - self.reject) + self.reject + ) self.dialogButtons.button(QDialogButtonBox.Apply).clicked.connect( - self.applySettings) + self.applySettings + ) self.dialogButtons.button(QDialogButtonBox.Ok).clicked.connect( - self.__onOk) + self.__onOk + ) def applySettings(self): for conf, pages in self._confsMap.items(): @@ -98,16 +101,16 @@ def _populateModel(self, m_parent, r_parent): self._confsMap.setdefault(config, []).append(page_instance) except Exception: if not isinstance(page_class, type): - page_name = 'InvalidPage' + page_name = "InvalidPage" else: - page_name = getattr(page_class, 'Name', page_class.__name__) + page_name = getattr(page_class, "Name", page_class.__name__) logger.warning( translate( - 'AppConfigurationWarning', - 'Cannot load configuration page: "{}" ({})' + "AppConfigurationWarning", + 'Cannot load configuration page: "{}" ({})', ).format(page_name, r_parent.path()), - exc_info=True + exc_info=True, ) else: for r_node in r_parent.children: @@ -126,7 +129,8 @@ def registerSettingsPage(path, page, config): :type config: lisp.core.configuration.Configuration """ AppConfigurationDialog.PagesRegistry.set( - path, PageEntry(page=page, config=config)) + path, PageEntry(page=page, config=config) + ) @staticmethod def unregisterSettingsPage(path): diff --git a/lisp/ui/settings/app_pages/cue.py b/lisp/ui/settings/app_pages/cue.py index 2817e2ff0..e0d638302 100644 --- a/lisp/ui/settings/app_pages/cue.py +++ b/lisp/ui/settings/app_pages/cue.py @@ -26,7 +26,7 @@ class CueAppSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Cue Settings') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Cue Settings") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -52,22 +52,22 @@ def __init__(self, **kwargs): self.retranslateUi() def retranslateUi(self): - self.interruptGroup.setTitle(translate('CueSettings', 'Interrupt fade')) - self.actionGroup.setTitle(translate('CueSettings', 'Fade actions')) + self.interruptGroup.setTitle(translate("CueSettings", "Interrupt fade")) + self.actionGroup.setTitle(translate("CueSettings", "Fade actions")) def applySettings(self): return { - 'cue': { - 'interruptFade': self.interruptFadeEdit.duration(), - 'interruptFadeType': self.interruptFadeEdit.fadeType(), - 'fadeAction': self.fadeActionEdit.duration(), - 'fadeActionType': self.fadeActionEdit.fadeType() + "cue": { + "interruptFade": self.interruptFadeEdit.duration(), + "interruptFadeType": self.interruptFadeEdit.fadeType(), + "fadeAction": self.fadeActionEdit.duration(), + "fadeActionType": self.fadeActionEdit.fadeType(), } } def loadSettings(self, settings): - self.interruptFadeEdit.setDuration(settings['cue']['interruptFade']) - self.interruptFadeEdit.setFadeType(settings['cue']['interruptFadeType']) + self.interruptFadeEdit.setDuration(settings["cue"]["interruptFade"]) + self.interruptFadeEdit.setFadeType(settings["cue"]["interruptFadeType"]) - self.fadeActionEdit.setDuration(settings['cue']['fadeAction']) - self.fadeActionEdit.setFadeType(settings['cue']['fadeActionType']) + self.fadeActionEdit.setDuration(settings["cue"]["fadeAction"]) + self.fadeActionEdit.setFadeType(settings["cue"]["fadeActionType"]) diff --git a/lisp/ui/settings/app_pages/general.py b/lisp/ui/settings/app_pages/general.py index f3c76e943..819b8a09a 100644 --- a/lisp/ui/settings/app_pages/general.py +++ b/lisp/ui/settings/app_pages/general.py @@ -18,8 +18,14 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QCheckBox, QComboBox, \ - QGridLayout, QLabel +from PyQt5.QtWidgets import ( + QGroupBox, + QVBoxLayout, + QCheckBox, + QComboBox, + QGridLayout, + QLabel, +) from lisp import layout from lisp.ui.icons import icon_themes_names @@ -29,7 +35,7 @@ class AppGeneral(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'General') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "General") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -50,7 +56,8 @@ def __init__(self, **kwargs): self.layoutGroup.layout().addWidget(self.layoutCombo) self.startupDialogCheck.stateChanged.connect( - self.layoutCombo.setDisabled) + self.layoutCombo.setDisabled + ) # Application theme self.themeGroup = QGroupBox(self) @@ -75,39 +82,41 @@ def __init__(self, **kwargs): def retranslateUi(self): self.layoutGroup.setTitle( - translate('AppGeneralSettings', 'Default layout')) + translate("AppGeneralSettings", "Default layout") + ) self.startupDialogCheck.setText( - translate('AppGeneralSettings', 'Enable startup layout selector')) + translate("AppGeneralSettings", "Enable startup layout selector") + ) self.themeGroup.setTitle( - translate('AppGeneralSettings', 'Application themes')) - self.themeLabel.setText( - translate('AppGeneralSettings', 'UI theme')) - self.iconsLabel.setText( - translate('AppGeneralSettings', 'Icons theme')) + translate("AppGeneralSettings", "Application themes") + ) + self.themeLabel.setText(translate("AppGeneralSettings", "UI theme")) + self.iconsLabel.setText(translate("AppGeneralSettings", "Icons theme")) def getSettings(self): settings = { - 'theme': { - 'theme': self.themeCombo.currentText(), - 'icons': self.iconsCombo.currentText() + "theme": { + "theme": self.themeCombo.currentText(), + "icons": self.iconsCombo.currentText(), }, - 'layout': {} + "layout": {}, } if self.startupDialogCheck.isChecked(): - settings['layout']['default'] = 'NoDefault' + settings["layout"]["default"] = "NoDefault" else: - settings['layout']['default'] = self.layoutCombo.currentData() + settings["layout"]["default"] = self.layoutCombo.currentData() return settings def loadSettings(self, settings): - layout_name = settings['layout']['default'] - if layout_name.lower() == 'nodefault': + layout_name = settings["layout"]["default"] + if layout_name.lower() == "nodefault": self.startupDialogCheck.setChecked(True) else: self.layoutCombo.setCurrentIndex( - self.layoutCombo.findData(layout_name)) + self.layoutCombo.findData(layout_name) + ) - self.themeCombo.setCurrentText(settings['theme']['theme']) - self.iconsCombo.setCurrentText(settings['theme']['icons']) + self.themeCombo.setCurrentText(settings["theme"]["theme"]) + self.iconsCombo.setCurrentText(settings["theme"]["icons"]) diff --git a/lisp/ui/settings/app_pages/layouts.py b/lisp/ui/settings/app_pages/layouts.py index c69a512f4..f3ae9949d 100644 --- a/lisp/ui/settings/app_pages/layouts.py +++ b/lisp/ui/settings/app_pages/layouts.py @@ -25,7 +25,7 @@ class LayoutsSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Layouts') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Layouts") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -52,24 +52,25 @@ def __init__(self, **kwargs): def retranslateUi(self): self.useFadeGroup.setTitle( - translate('ListLayout', 'Use fade (global actions)')) - self.stopAllFade.setText(translate('ListLayout', 'Stop All')) - self.pauseAllFade.setText(translate('ListLayout', 'Pause All')) - self.resumeAllFade.setText(translate('ListLayout', 'Resume All')) - self.interruptAllFade.setText(translate('ListLayout', 'Interrupt All')) + translate("ListLayout", "Use fade (global actions)") + ) + self.stopAllFade.setText(translate("ListLayout", "Stop All")) + self.pauseAllFade.setText(translate("ListLayout", "Pause All")) + self.resumeAllFade.setText(translate("ListLayout", "Resume All")) + self.interruptAllFade.setText(translate("ListLayout", "Interrupt All")) def loadSettings(self, settings): - self.stopAllFade.setChecked(settings['layout']['stopAllFade']) - self.pauseAllFade.setChecked(settings['layout']['pauseAllFade']) - self.resumeAllFade.setChecked(settings['layout']['resumeAllFade']) - self.interruptAllFade.setChecked(settings['layout']['interruptAllFade']) + self.stopAllFade.setChecked(settings["layout"]["stopAllFade"]) + self.pauseAllFade.setChecked(settings["layout"]["pauseAllFade"]) + self.resumeAllFade.setChecked(settings["layout"]["resumeAllFade"]) + self.interruptAllFade.setChecked(settings["layout"]["interruptAllFade"]) def getSettings(self): return { - 'layout': { - 'stopAllFade': self.stopAllFade.isChecked(), - 'pauseAllFade': self.pauseAllFade.isChecked(), - 'resumeAllFade': self.resumeAllFade.isChecked(), - 'interruptAllFade': self.interruptAllFade.isChecked() + "layout": { + "stopAllFade": self.stopAllFade.isChecked(), + "pauseAllFade": self.pauseAllFade.isChecked(), + "resumeAllFade": self.resumeAllFade.isChecked(), + "interruptAllFade": self.interruptAllFade.isChecked(), } } diff --git a/lisp/ui/settings/app_pages/plugins.py b/lisp/ui/settings/app_pages/plugins.py index a108523d4..7ab41c5d9 100644 --- a/lisp/ui/settings/app_pages/plugins.py +++ b/lisp/ui/settings/app_pages/plugins.py @@ -18,8 +18,12 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt, QSize -from PyQt5.QtWidgets import QListWidget, QListWidgetItem, QTextBrowser, \ - QVBoxLayout +from PyQt5.QtWidgets import ( + QListWidget, + QListWidgetItem, + QTextBrowser, + QVBoxLayout, +) from lisp import plugins from lisp.ui.icons import IconTheme @@ -28,7 +32,7 @@ # TODO: add Enable/Disable options for plugins class PluginsSettings(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Plugins') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Plugins") def __init__(self, **kwargs): super().__init__(**kwargs) @@ -46,11 +50,11 @@ def __init__(self, **kwargs): for name, plugin in plugins.PLUGINS.items(): item = QListWidgetItem(plugin.Name) if plugins.is_loaded(name): - item.setIcon(IconTheme.get('led-running')) - elif not plugin.Config['_enabled_']: - item.setIcon(IconTheme.get('led-pause')) + item.setIcon(IconTheme.get("led-running")) + elif not plugin.Config["_enabled_"]: + item.setIcon(IconTheme.get("led-pause")) else: - item.setIcon(IconTheme.get('led-error')) + item.setIcon(IconTheme.get("led-error")) item.setData(Qt.UserRole, plugin) @@ -71,11 +75,12 @@ def __selection_changed(self): if item is not None: plugin = item.data(Qt.UserRole) - html = 'Description: {}'.format(plugin.Description) - html += '

' - html += 'Authors: {}'.format(', '.join(plugin.Authors)) + html = "Description: {}".format(plugin.Description) + html += "

" + html += "Authors: {}".format(", ".join(plugin.Authors)) self.pluginDescription.setHtml(html) else: self.pluginDescription.setHtml( - 'Description:

Authors: ') + "Description:

Authors: " + ) diff --git a/lisp/ui/settings/cue_pages/cue_appearance.py b/lisp/ui/settings/cue_pages/cue_appearance.py index f5d1dc88f..ef75deede 100644 --- a/lisp/ui/settings/cue_pages/cue_appearance.py +++ b/lisp/ui/settings/cue_pages/cue_appearance.py @@ -18,8 +18,15 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QHBoxLayout, QTextEdit, \ - QSpinBox, QLabel, QLineEdit +from PyQt5.QtWidgets import ( + QVBoxLayout, + QGroupBox, + QHBoxLayout, + QTextEdit, + QSpinBox, + QLabel, + QLineEdit, +) from lisp.ui.settings.pages import SettingsPage from lisp.ui.widgets import QColorButton @@ -27,7 +34,7 @@ class Appearance(SettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Appearance') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Appearance") def __init__(self, **kwargs): super().__init__() @@ -71,8 +78,11 @@ def __init__(self, **kwargs): # Warning self.warning = QLabel(self) - self.warning.setText(translate('CueAppearanceSettings', - 'The appearance depends on the layout')) + self.warning.setText( + translate( + "CueAppearanceSettings", "The appearance depends on the layout" + ) + ) self.warning.setAlignment(Qt.AlignCenter) self.warning.setStyleSheet("color: #FFA500; font-weight: bold") self.layout().addWidget(self.warning) @@ -81,17 +91,22 @@ def __init__(self, **kwargs): def retranslateUi(self): self.cueNameGroup.setTitle( - translate('CueAppearanceSettings', 'Cue name')) - self.cueNameEdit.setText(translate('CueAppearanceSettings', 'NoName')) + translate("CueAppearanceSettings", "Cue name") + ) + self.cueNameEdit.setText(translate("CueAppearanceSettings", "NoName")) self.cueDescriptionGroup.setTitle( - translate('CueAppearanceSettings', 'Description/Note')) + translate("CueAppearanceSettings", "Description/Note") + ) self.fontSizeGroup.setTitle( - translate('CueAppearanceSettings', 'Set Font Size')) - self.colorGroup.setTitle(translate('CueAppearanceSettings', 'Color')) + translate("CueAppearanceSettings", "Set Font Size") + ) + self.colorGroup.setTitle(translate("CueAppearanceSettings", "Color")) self.colorBButton.setText( - translate('CueAppearanceSettings', 'Select background color')) + translate("CueAppearanceSettings", "Select background color") + ) self.colorFButton.setText( - translate('CueAppearanceSettings', 'Select font color')) + translate("CueAppearanceSettings", "Select font color") + ) def enableCheck(self, enabled): self.cueNameGroup.setCheckable(enabled) @@ -112,45 +127,45 @@ def getSettings(self): checkable = self.cueNameGroup.isCheckable() if not (checkable and not self.cueNameGroup.isChecked()): - settings['name'] = self.cueNameEdit.text() + settings["name"] = self.cueNameEdit.text() if not (checkable and not self.cueDescriptionGroup.isChecked()): - settings['description'] = self.cueDescriptionEdit.toPlainText() + settings["description"] = self.cueDescriptionEdit.toPlainText() if not (checkable and not self.colorGroup.isChecked()): if self.colorBButton.color() is not None: - style['background'] = self.colorBButton.color() + style["background"] = self.colorBButton.color() if self.colorFButton.color() is not None: - style['color'] = self.colorFButton.color() + style["color"] = self.colorFButton.color() if not (checkable and not self.fontSizeGroup.isChecked()): - style['font-size'] = str(self.fontSizeSpin.value()) + 'pt' + style["font-size"] = str(self.fontSizeSpin.value()) + "pt" if style: - settings['stylesheet'] = dict_to_css(style) + settings["stylesheet"] = dict_to_css(style) return settings def loadSettings(self, settings): - if 'name' in settings: - self.cueNameEdit.setText(settings['name']) - if 'description' in settings: - self.cueDescriptionEdit.setText(settings['description']) - if 'stylesheet' in settings: - settings = css_to_dict(settings['stylesheet']) - if 'background' in settings: - self.colorBButton.setColor(settings['background']) - if 'color' in settings: - self.colorFButton.setColor(settings['color']) - if 'font-size' in settings: + if "name" in settings: + self.cueNameEdit.setText(settings["name"]) + if "description" in settings: + self.cueDescriptionEdit.setText(settings["description"]) + if "stylesheet" in settings: + settings = css_to_dict(settings["stylesheet"]) + if "background" in settings: + self.colorBButton.setColor(settings["background"]) + if "color" in settings: + self.colorFButton.setColor(settings["color"]) + if "font-size" in settings: # [:-2] for removing "pt" - self.fontSizeSpin.setValue(int(settings['font-size'][:-2])) + self.fontSizeSpin.setValue(int(settings["font-size"][:-2])) def css_to_dict(css): dict = {} css = css.strip() - for attribute in css.split(';'): + for attribute in css.split(";"): try: - name, value = attribute.split(':') + name, value = attribute.split(":") dict[name.strip()] = value.strip() except Exception: pass @@ -159,8 +174,8 @@ def css_to_dict(css): def dict_to_css(css_dict): - css = '' + css = "" for name in css_dict: - css += name + ':' + str(css_dict[name]) + ';' + css += name + ":" + str(css_dict[name]) + ";" return css diff --git a/lisp/ui/settings/cue_pages/cue_general.py b/lisp/ui/settings/cue_pages/cue_general.py index 5abb5644e..f05edcbe2 100644 --- a/lisp/ui/settings/cue_pages/cue_general.py +++ b/lisp/ui/settings/cue_pages/cue_general.py @@ -18,18 +18,31 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QLabel, QVBoxLayout, \ - QDoubleSpinBox +from PyQt5.QtWidgets import ( + QGroupBox, + QHBoxLayout, + QLabel, + QVBoxLayout, + QDoubleSpinBox, +) from lisp.cues.cue import CueAction -from lisp.ui.settings.pages import CueSettingsPage, SettingsPagesTabWidget, CuePageMixin +from lisp.ui.settings.pages import ( + CueSettingsPage, + SettingsPagesTabWidget, + CuePageMixin, +) from lisp.ui.ui_utils import translate -from lisp.ui.widgets import FadeComboBox, CueActionComboBox, \ - CueNextActionComboBox, FadeEdit +from lisp.ui.widgets import ( + FadeComboBox, + CueActionComboBox, + CueNextActionComboBox, + FadeEdit, +) class CueGeneralSettingsPage(SettingsPagesTabWidget, CuePageMixin): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Cue') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Cue") def __init__(self, cueType, **kwargs): super().__init__(cueType=cueType, **kwargs) @@ -39,7 +52,7 @@ def __init__(self, cueType, **kwargs): class CueBehavioursPage(CueSettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Behaviours') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Behaviours") def __init__(self, cueType, **kwargs): super().__init__(cueType, **kwargs) @@ -51,14 +64,11 @@ def __init__(self, cueType, **kwargs): self.layout().addWidget(self.startActionGroup) self.startActionCombo = CueActionComboBox( - { - CueAction.Start, - CueAction.FadeInStart - }.intersection(self.cueType.CueActions).union({ - CueAction.DoNothing - }), + {CueAction.Start, CueAction.FadeInStart} + .intersection(self.cueType.CueActions) + .union({CueAction.DoNothing}), mode=CueActionComboBox.Mode.Value, - parent=self.startActionGroup + parent=self.startActionGroup, ) self.startActionCombo.setEnabled(self.startActionCombo.count() > 1) self.startActionGroup.layout().addWidget(self.startActionCombo) @@ -77,12 +87,12 @@ def __init__(self, cueType, **kwargs): CueAction.Stop, CueAction.Pause, CueAction.FadeOutStop, - CueAction.FadeOutPause - }.intersection(self.cueType.CueActions).union({ - CueAction.DoNothing - }), + CueAction.FadeOutPause, + } + .intersection(self.cueType.CueActions) + .union({CueAction.DoNothing}), mode=CueActionComboBox.Mode.Value, - parent=self.stopActionGroup + parent=self.stopActionGroup, ) self.stopActionCombo.setEnabled(self.stopActionCombo.count() > 1) self.stopActionGroup.layout().addWidget(self.stopActionCombo) @@ -93,24 +103,24 @@ def __init__(self, cueType, **kwargs): self.layout().addSpacing(150) self.setEnabled( - self.stopActionCombo.isEnabled() and - self.startActionCombo.isEnabled() + self.stopActionCombo.isEnabled() + and self.startActionCombo.isEnabled() ) self.retranslateUi() def retranslateUi(self): # Start-Action - self.startActionGroup.setTitle( - translate('CueSettings', 'Start action')) + self.startActionGroup.setTitle(translate("CueSettings", "Start action")) self.startActionLabel.setText( - translate('CueSettings', 'Default action to start the cue')) + translate("CueSettings", "Default action to start the cue") + ) # Stop-Action - self.stopActionGroup.setTitle( - translate('CueSettings', 'Stop action')) + self.stopActionGroup.setTitle(translate("CueSettings", "Stop action")) self.stopActionLabel.setText( - translate('CueSettings', 'Default action to stop the cue')) + translate("CueSettings", "Default action to stop the cue") + ) def enableCheck(self, enabled): self.startActionGroup.setCheckable(enabled) @@ -122,24 +132,30 @@ def getSettings(self): settings = {} checkable = self.startActionGroup.isCheckable() - if ((not checkable or self.startActionGroup.isChecked()) and - self.startActionCombo.isEnabled()): - settings['default_start_action'] = self.startActionCombo.currentItem() - if ((not checkable or self.stopActionGroup.isChecked()) and - self.stopActionCombo.isEnabled()): - settings['default_stop_action'] = self.stopActionCombo.currentItem() + if ( + not checkable or self.startActionGroup.isChecked() + ) and self.startActionCombo.isEnabled(): + settings[ + "default_start_action" + ] = self.startActionCombo.currentItem() + if ( + not checkable or self.stopActionGroup.isChecked() + ) and self.stopActionCombo.isEnabled(): + settings["default_stop_action"] = self.stopActionCombo.currentItem() return settings def loadSettings(self, settings): self.startActionCombo.setCurrentItem( - settings.get('default_start_action', '')) + settings.get("default_start_action", "") + ) self.stopActionCombo.setCurrentItem( - settings.get('default_stop_action', '')) + settings.get("default_stop_action", "") + ) class CueWaitsPage(CueSettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Pre/Post Wait') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Pre/Post Wait") def __init__(self, cueType, **kwargs): super().__init__(cueType=cueType, **kwargs) @@ -177,22 +193,25 @@ def __init__(self, cueType, **kwargs): self.layout().addWidget(self.nextActionGroup) self.nextActionCombo = CueNextActionComboBox( - parent=self.nextActionGroup) + parent=self.nextActionGroup + ) self.nextActionGroup.layout().addWidget(self.nextActionCombo) self.retranslateUi() def retranslateUi(self): # PreWait - self.preWaitGroup.setTitle(translate('CueSettings', 'Pre wait')) + self.preWaitGroup.setTitle(translate("CueSettings", "Pre wait")) self.preWaitLabel.setText( - translate('CueSettings', 'Wait before cue execution')) + translate("CueSettings", "Wait before cue execution") + ) # PostWait - self.postWaitGroup.setTitle(translate('CueSettings', 'Post wait')) + self.postWaitGroup.setTitle(translate("CueSettings", "Post wait")) self.postWaitLabel.setText( - translate('CueSettings', 'Wait after cue execution')) + translate("CueSettings", "Wait after cue execution") + ) # NextAction - self.nextActionGroup.setTitle(translate('CueSettings', 'Next action')) + self.nextActionGroup.setTitle(translate("CueSettings", "Next action")) def enableCheck(self, enabled): self.preWaitGroup.setCheckable(enabled) @@ -203,26 +222,26 @@ def enableCheck(self, enabled): self.nextActionGroup.setChecked(False) def loadSettings(self, settings): - self.preWaitSpin.setValue(settings.get('pre_wait', 0)) - self.postWaitSpin.setValue(settings.get('post_wait', 0)) - self.nextActionCombo.setCurrentAction(settings.get('next_action', '')) + self.preWaitSpin.setValue(settings.get("pre_wait", 0)) + self.postWaitSpin.setValue(settings.get("post_wait", 0)) + self.nextActionCombo.setCurrentAction(settings.get("next_action", "")) def getSettings(self): settings = {} checkable = self.preWaitGroup.isCheckable() if not checkable or self.preWaitGroup.isChecked(): - settings['pre_wait'] = self.preWaitSpin.value() + settings["pre_wait"] = self.preWaitSpin.value() if not checkable or self.postWaitGroup.isChecked(): - settings['post_wait'] = self.postWaitSpin.value() + settings["post_wait"] = self.postWaitSpin.value() if not checkable or self.nextActionGroup.isChecked(): - settings['next_action'] = self.nextActionCombo.currentData() + settings["next_action"] = self.nextActionCombo.currentData() return settings class CueFadePage(CueSettingsPage): - Name = QT_TRANSLATE_NOOP('SettingsPageName', 'Fade In/Out') + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Fade In/Out") def __init__(self, cueType, **kwargs): super().__init__(cueType, **kwargs) @@ -230,41 +249,41 @@ def __init__(self, cueType, **kwargs): # FadeIn self.fadeInGroup = QGroupBox(self) - self.fadeInGroup.setEnabled( - CueAction.FadeInStart in cueType.CueActions - ) + self.fadeInGroup.setEnabled(CueAction.FadeInStart in cueType.CueActions) self.fadeInGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.fadeInGroup) - self.fadeInEdit = FadeEdit(self.fadeInGroup, - mode=FadeComboBox.Mode.FadeIn) + self.fadeInEdit = FadeEdit( + self.fadeInGroup, mode=FadeComboBox.Mode.FadeIn + ) self.fadeInGroup.layout().addWidget(self.fadeInEdit) # FadeOut self.fadeOutGroup = QGroupBox(self) self.fadeOutGroup.setEnabled( - CueAction.FadeOutPause in cueType.CueActions or - CueAction.FadeOutStop in cueType.CueActions + CueAction.FadeOutPause in cueType.CueActions + or CueAction.FadeOutStop in cueType.CueActions ) self.fadeOutGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.fadeOutGroup) self.fadeOutEdit = FadeEdit( - self.fadeOutGroup, mode=FadeComboBox.Mode.FadeOut) + self.fadeOutGroup, mode=FadeComboBox.Mode.FadeOut + ) self.fadeOutGroup.layout().addWidget(self.fadeOutEdit) self.retranslateUi() def retranslateUi(self): # FadeIn/Out - self.fadeInGroup.setTitle(translate('FadeSettings', 'Fade In')) - self.fadeOutGroup.setTitle(translate('FadeSettings', 'Fade Out')) + self.fadeInGroup.setTitle(translate("FadeSettings", "Fade In")) + self.fadeOutGroup.setTitle(translate("FadeSettings", "Fade Out")) def loadSettings(self, settings): - self.fadeInEdit.setFadeType(settings.get('fadein_type', '')) - self.fadeInEdit.setDuration(settings.get('fadein_duration', 0)) - self.fadeOutEdit.setFadeType(settings.get('fadeout_type', '')) - self.fadeOutEdit.setDuration(settings.get('fadeout_duration', 0)) + self.fadeInEdit.setFadeType(settings.get("fadein_type", "")) + self.fadeInEdit.setDuration(settings.get("fadein_duration", 0)) + self.fadeOutEdit.setFadeType(settings.get("fadeout_type", "")) + self.fadeOutEdit.setDuration(settings.get("fadeout_duration", 0)) def enableCheck(self, enabled): self.fadeInGroup.setCheckable(enabled) @@ -277,10 +296,10 @@ def getSettings(self): checkable = self.fadeInGroup.isCheckable() if not checkable or self.fadeInGroup.isChecked(): - settings['fadein_type'] = self.fadeInEdit.fadeType() - settings['fadein_duration'] = self.fadeInEdit.duration() + settings["fadein_type"] = self.fadeInEdit.fadeType() + settings["fadein_duration"] = self.fadeInEdit.duration() if not checkable or self.fadeInGroup.isChecked(): - settings['fadeout_type'] = self.fadeOutEdit.fadeType() - settings['fadeout_duration'] = self.fadeOutEdit.duration() + settings["fadeout_type"] = self.fadeOutEdit.fadeType() + settings["fadeout_duration"] = self.fadeOutEdit.duration() return settings diff --git a/lisp/ui/settings/cue_pages/media_cue.py b/lisp/ui/settings/cue_pages/media_cue.py index 691d64fe5..dd6b6decf 100644 --- a/lisp/ui/settings/cue_pages/media_cue.py +++ b/lisp/ui/settings/cue_pages/media_cue.py @@ -18,15 +18,21 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import QTime, Qt -from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QTimeEdit, QLabel, \ - QSpinBox, QVBoxLayout +from PyQt5.QtWidgets import ( + QGroupBox, + QHBoxLayout, + QTimeEdit, + QLabel, + QSpinBox, + QVBoxLayout, +) from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate class MediaCueSettings(SettingsPage): - Name = 'Media-Cue' + Name = "Media-Cue" def __init__(self, **kwargs): super().__init__(**kwargs) @@ -38,7 +44,7 @@ def __init__(self, **kwargs): self.layout().addWidget(self.startGroup) self.startEdit = QTimeEdit(self.startGroup) - self.startEdit.setDisplayFormat('HH.mm.ss.zzz') + self.startEdit.setDisplayFormat("HH.mm.ss.zzz") self.startGroup.layout().addWidget(self.startEdit) self.startLabel = QLabel(self.startGroup) @@ -51,7 +57,7 @@ def __init__(self, **kwargs): self.layout().addWidget(self.stopGroup) self.stopEdit = QTimeEdit(self.stopGroup) - self.stopEdit.setDisplayFormat('HH.mm.ss.zzz') + self.stopEdit.setDisplayFormat("HH.mm.ss.zzz") self.stopGroup.layout().addWidget(self.stopEdit) self.stopLabel = QLabel(self.stopGroup) @@ -64,7 +70,7 @@ def __init__(self, **kwargs): self.layout().addWidget(self.loopGroup) self.spinLoop = QSpinBox(self.loopGroup) - self.spinLoop.setRange(-1, 1000000) + self.spinLoop.setRange(-1, 1_000_000) self.loopGroup.layout().addWidget(self.spinLoop) self.loopLabel = QLabel(self.loopGroup) @@ -74,16 +80,21 @@ def __init__(self, **kwargs): self.retranslateUi() def retranslateUi(self): - self.startGroup.setTitle(translate('MediaCueSettings', 'Start time')) + self.startGroup.setTitle(translate("MediaCueSettings", "Start time")) self.stopLabel.setText( - translate('MediaCueSettings', 'Stop position of the media')) - self.stopGroup.setTitle(translate('MediaCueSettings', 'Stop time')) + translate("MediaCueSettings", "Stop position of the media") + ) + self.stopGroup.setTitle(translate("MediaCueSettings", "Stop time")) self.startLabel.setText( - translate('MediaCueSettings', 'Start position of the media')) - self.loopGroup.setTitle(translate('MediaCueSettings', 'Loop')) + translate("MediaCueSettings", "Start position of the media") + ) + self.loopGroup.setTitle(translate("MediaCueSettings", "Loop")) self.loopLabel.setText( - translate('MediaCueSettings', 'Repetition after first play ' - '(-1 = infinite)')) + translate( + "MediaCueSettings", + "Repetition after first play " "(-1 = infinite)", + ) + ) def getSettings(self): settings = {} @@ -91,14 +102,14 @@ def getSettings(self): if not (checkable and not self.startGroup.isChecked()): time = self.startEdit.time().msecsSinceStartOfDay() - settings['start_time'] = time + settings["start_time"] = time if not (checkable and not self.stopGroup.isChecked()): time = self.stopEdit.time().msecsSinceStartOfDay() - settings['stop_time'] = time + settings["stop_time"] = time if not (checkable and not self.loopGroup.isChecked()): - settings['loop'] = self.spinLoop.value() + settings["loop"] = self.spinLoop.value() - return {'media': settings} + return {"media": settings} def enableCheck(self, enabled): self.startGroup.setCheckable(enabled) @@ -111,18 +122,18 @@ def enableCheck(self, enabled): self.loopGroup.setChecked(False) def loadSettings(self, settings): - settings = settings.get('media', {}) + settings = settings.get("media", {}) - if 'loop' in settings: - self.spinLoop.setValue(settings['loop']) - if 'start_time' in settings: - time = self._to_qtime(settings['start_time']) + if "loop" in settings: + self.spinLoop.setValue(settings["loop"]) + if "start_time" in settings: + time = self._to_qtime(settings["start_time"]) self.startEdit.setTime(time) - if 'stop_time' in settings: - time = self._to_qtime(settings['stop_time']) + if "stop_time" in settings: + time = self._to_qtime(settings["stop_time"]) self.stopEdit.setTime(time) - time = self._to_qtime(settings.get('duration', 0)) + time = self._to_qtime(settings.get("duration", 0)) self.startEdit.setMaximumTime(time) self.stopEdit.setMaximumTime(time) diff --git a/lisp/ui/settings/cue_settings.py b/lisp/ui/settings/cue_settings.py index 2a2dea4a5..5701da120 100644 --- a/lisp/ui/settings/cue_settings.py +++ b/lisp/ui/settings/cue_settings.py @@ -53,7 +53,7 @@ def __init__(self, cue, **kwargs): self.setMinimumSize(640, 510) self.resize(640, 510) self.setLayout(QVBoxLayout()) - #self.layout().setContentsMargins(5, 5, 5, 10) + # self.layout().setContentsMargins(5, 5, 5, 10) if isinstance(cue, type): if issubclass(cue, Cue): @@ -61,8 +61,8 @@ def __init__(self, cue, **kwargs): cue_class = cue else: raise TypeError( - 'invalid cue type, must be a Cue subclass or a Cue object, ' - 'not {}'.format(cue.__name__) + "invalid cue type, must be a Cue subclass or a Cue object, " + "not {}".format(cue.__name__) ) elif isinstance(cue, Cue): self.setWindowTitle(cue.name) @@ -70,8 +70,8 @@ def __init__(self, cue, **kwargs): cue_class = cue.__class__ else: raise TypeError( - 'invalid cue type, must be a Cue subclass or a Cue object, ' - 'not {}'.format(typename(cue)) + "invalid cue type, must be a Cue subclass or a Cue object, " + "not {}".format(typename(cue)) ) self.mainPage = SettingsPagesTabWidget(parent=self) @@ -80,7 +80,7 @@ def __init__(self, cue, **kwargs): def sk(widget): # Sort-Key function - return translate('SettingsPageName', widget.Name) + return translate("SettingsPageName", widget.Name) for page in sorted(CueSettingsRegistry().filter(cue_class), key=sk): if issubclass(page, CuePageMixin): @@ -94,18 +94,21 @@ def sk(widget): self.dialogButtons = QDialogButtonBox(self) self.dialogButtons.setStandardButtons( - QDialogButtonBox.Cancel | - QDialogButtonBox.Apply | - QDialogButtonBox.Ok + QDialogButtonBox.Cancel + | QDialogButtonBox.Apply + | QDialogButtonBox.Ok ) self.layout().addWidget(self.dialogButtons) self.dialogButtons.button(QDialogButtonBox.Cancel).clicked.connect( - self.reject) + self.reject + ) self.dialogButtons.button(QDialogButtonBox.Apply).clicked.connect( - self.__onApply) + self.__onApply + ) self.dialogButtons.button(QDialogButtonBox.Ok).clicked.connect( - self.__onOk) + self.__onOk + ) def loadSettings(self, settings): self.mainPage.loadSettings(settings) diff --git a/lisp/ui/settings/pages.py b/lisp/ui/settings/pages.py index 36cc1030d..4c8f1b24e 100644 --- a/lisp/ui/settings/pages.py +++ b/lisp/ui/settings/pages.py @@ -26,7 +26,6 @@ class SettingsPage(QWidget): - def loadSettings(self, settings): """Load existing settings value into the widget @@ -85,7 +84,7 @@ def page(self, index): def addPage(self, page): self._pages.append(page) - self.tabWidget.addTab(page, translate('SettingsPageName', page.Name)) + self.tabWidget.addTab(page, translate("SettingsPageName", page.Name)) def removePage(self, index): self.tabWidget.removeTab(index) diff --git a/lisp/ui/themes/dark/__init__.py b/lisp/ui/themes/dark/__init__.py index 6d84ea625..17e8eb885 100644 --- a/lisp/ui/themes/dark/__init__.py +++ b/lisp/ui/themes/dark/__init__.py @@ -1 +1 @@ -from .dark import Dark \ No newline at end of file +from .dark import Dark diff --git a/lisp/ui/themes/dark/assetes.py b/lisp/ui/themes/dark/assetes.py index ff16f4996..d552717e8 100644 --- a/lisp/ui/themes/dark/assetes.py +++ b/lisp/ui/themes/dark/assetes.py @@ -1149,10 +1149,17 @@ \x00\x00\x04\xb4\x00\x00\x00\x00\x00\x01\x00\x00\x29\x10\ " + def qInitResources(): - QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qRegisterResourceData( + 0x01, qt_resource_struct, qt_resource_name, qt_resource_data + ) + def qCleanupResources(): - QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qUnregisterResourceData( + 0x01, qt_resource_struct, qt_resource_name, qt_resource_data + ) + qInitResources() diff --git a/lisp/ui/themes/dark/dark.py b/lisp/ui/themes/dark/dark.py index e8a1ec87d..764939f07 100644 --- a/lisp/ui/themes/dark/dark.py +++ b/lisp/ui/themes/dark/dark.py @@ -26,10 +26,10 @@ class Dark: - QssPath = os.path.join(os.path.dirname(__file__), 'theme.qss') + QssPath = os.path.join(os.path.dirname(__file__), "theme.qss") def apply(self, qt_app): - with open(Dark.QssPath, mode='r', encoding='utf-8') as f: + with open(Dark.QssPath, mode="r", encoding="utf-8") as f: qt_app.setStyleSheet(f.read()) # Change link color diff --git a/lisp/ui/ui_utils.py b/lisp/ui/ui_utils.py index 9713f0b45..693def951 100644 --- a/lisp/ui/ui_utils.py +++ b/lisp/ui/ui_utils.py @@ -76,17 +76,18 @@ def qfile_filters(extensions, allexts=True, anyfile=True): filters = [] for key in extensions: - filters.append(key.title() + ' (' + ' *.'.join(extensions[key]) + ')') + filters.append(key.title() + " (" + " *.".join(extensions[key]) + ")") filters.sort() if allexts: - filters.insert(0, 'All supported (' + - ' *.'.join(chain(*extensions.values())) + ')') + filters.insert( + 0, "All supported (" + " *.".join(chain(*extensions.values())) + ")" + ) if anyfile: - filters.append('Any file (*)') + filters.append("Any file (*)") - return ';;'.join(filters) + return ";;".join(filters) # Keep a reference of translators objects @@ -97,14 +98,14 @@ def install_translation(name, tr_path=I18N_PATH): tr_file = path.join(tr_path, name) translator = QTranslator() - translator.load(QLocale(), tr_file, '_') + translator.load(QLocale(), tr_file, "_") if QApplication.installTranslator(translator): # Keep a reference, QApplication does not _TRANSLATORS.append(translator) - logger.debug('Installed translation: {}'.format(tr_file)) + logger.debug("Installed translation: {}".format(tr_file)) else: - logger.debug('No translation at: {}'.format(tr_file)) + logger.debug("No translation at: {}".format(tr_file)) def translate(context, text, disambiguation=None, n=-1): @@ -123,9 +124,12 @@ def tr_sorted(context, iterable, key=None, reverse=False): The sorting is done using translated versions of the iterable values. """ if key is not None: + def tr_key(item): translate(context, key(item)) + else: + def tr_key(item): translate(context, item) diff --git a/lisp/ui/widgets/cue_actions.py b/lisp/ui/widgets/cue_actions.py index fe9890985..6babf246a 100644 --- a/lisp/ui/widgets/cue_actions.py +++ b/lisp/ui/widgets/cue_actions.py @@ -24,18 +24,19 @@ from lisp.ui.widgets.qenumcombobox import QEnumComboBox CueActionsStrings = { - CueAction.Default: QT_TRANSLATE_NOOP('CueAction', 'Default'), - CueAction.FadeInStart: QT_TRANSLATE_NOOP('CueAction', 'Faded Start'), - CueAction.FadeInResume: QT_TRANSLATE_NOOP('CueAction', 'Faded Resume'), - CueAction.FadeOutPause: QT_TRANSLATE_NOOP('CueAction', 'Faded Pause'), - CueAction.FadeOutStop: QT_TRANSLATE_NOOP('CueAction', 'Faded Stop'), - CueAction.FadeOutInterrupt: - QT_TRANSLATE_NOOP('CueAction', 'Faded Interrupt'), - CueAction.Start: QT_TRANSLATE_NOOP('CueAction', 'Start'), - CueAction.Resume: QT_TRANSLATE_NOOP('CueAction', 'Resume'), - CueAction.Pause: QT_TRANSLATE_NOOP('CueAction', 'Pause'), - CueAction.Stop: QT_TRANSLATE_NOOP('CueAction', 'Stop'), - CueAction.DoNothing: QT_TRANSLATE_NOOP('CueAction', 'Do Nothing'), + CueAction.Default: QT_TRANSLATE_NOOP("CueAction", "Default"), + CueAction.FadeInStart: QT_TRANSLATE_NOOP("CueAction", "Faded Start"), + CueAction.FadeInResume: QT_TRANSLATE_NOOP("CueAction", "Faded Resume"), + CueAction.FadeOutPause: QT_TRANSLATE_NOOP("CueAction", "Faded Pause"), + CueAction.FadeOutStop: QT_TRANSLATE_NOOP("CueAction", "Faded Stop"), + CueAction.FadeOutInterrupt: QT_TRANSLATE_NOOP( + "CueAction", "Faded Interrupt" + ), + CueAction.Start: QT_TRANSLATE_NOOP("CueAction", "Start"), + CueAction.Resume: QT_TRANSLATE_NOOP("CueAction", "Resume"), + CueAction.Pause: QT_TRANSLATE_NOOP("CueAction", "Pause"), + CueAction.Stop: QT_TRANSLATE_NOOP("CueAction", "Stop"), + CueAction.DoNothing: QT_TRANSLATE_NOOP("CueAction", "Do Nothing"), } @@ -47,8 +48,7 @@ def tr_action(action): :return: translated UI friendly string to indicate the action :rtype: str """ - return translate( - 'CueAction', CueActionsStrings.get(action, action.name)) + return translate("CueAction", CueActionsStrings.get(action, action.name)) class CueActionComboBox(QEnumComboBox): diff --git a/lisp/ui/widgets/cue_next_actions.py b/lisp/ui/widgets/cue_next_actions.py index 0eebcc281..f464e9612 100644 --- a/lisp/ui/widgets/cue_next_actions.py +++ b/lisp/ui/widgets/cue_next_actions.py @@ -25,16 +25,19 @@ CueNextActionsStrings = { - CueNextAction.DoNothing: QT_TRANSLATE_NOOP( - 'CueNextAction', 'Do Nothing'), + CueNextAction.DoNothing: QT_TRANSLATE_NOOP("CueNextAction", "Do Nothing"), CueNextAction.TriggerAfterEnd: QT_TRANSLATE_NOOP( - 'CueNextAction', 'Trigger after the end'), + "CueNextAction", "Trigger after the end" + ), CueNextAction.TriggerAfterWait: QT_TRANSLATE_NOOP( - 'CueNextAction', 'Trigger after post wait'), + "CueNextAction", "Trigger after post wait" + ), CueNextAction.SelectAfterEnd: QT_TRANSLATE_NOOP( - 'CueNextAction', 'Select after the end'), + "CueNextAction", "Select after the end" + ), CueNextAction.SelectAfterWait: QT_TRANSLATE_NOOP( - 'CueNextAction', 'Select after post wait') + "CueNextAction", "Select after post wait" + ), } @@ -47,7 +50,8 @@ def tr_next_action(action): :rtype: str """ return translate( - 'CueNextAction', CueNextActionsStrings.get(action, action.name)) + "CueNextAction", CueNextActionsStrings.get(action, action.name) + ) class CueNextActionComboBox(QComboBox): diff --git a/lisp/ui/widgets/fades.py b/lisp/ui/widgets/fades.py index 3041cf5b6..d690381f2 100644 --- a/lisp/ui/widgets/fades.py +++ b/lisp/ui/widgets/fades.py @@ -20,28 +20,34 @@ from enum import Enum from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt -from PyQt5.QtWidgets import QComboBox, QStyledItemDelegate, QWidget, \ - QGridLayout, QDoubleSpinBox, QLabel +from PyQt5.QtWidgets import ( + QComboBox, + QStyledItemDelegate, + QWidget, + QGridLayout, + QDoubleSpinBox, + QLabel, +) from lisp.ui.icons import IconTheme from lisp.ui.ui_utils import translate -QT_TRANSLATE_NOOP('Fade', 'Linear') -QT_TRANSLATE_NOOP('Fade', 'Quadratic') -QT_TRANSLATE_NOOP('Fade', 'Quadratic2') +QT_TRANSLATE_NOOP("Fade", "Linear") +QT_TRANSLATE_NOOP("Fade", "Quadratic") +QT_TRANSLATE_NOOP("Fade", "Quadratic2") class FadeComboBox(QComboBox): FadeOutIcons = { - 'Linear': 'fadeout-linear', - 'Quadratic': 'fadeout-quadratic', - 'Quadratic2': 'fadeout-quadratic2' + "Linear": "fadeout-linear", + "Quadratic": "fadeout-quadratic", + "Quadratic2": "fadeout-quadratic2", } FadeInIcons = { - 'Linear': 'fadein-linear', - 'Quadratic': 'fadein-quadratic', - 'Quadratic2': 'fadein-quadratic2' + "Linear": "fadein-linear", + "Quadratic": "fadein-quadratic", + "Quadratic2": "fadein-quadratic2", } class Mode(Enum): @@ -58,17 +64,16 @@ def __init__(self, *args, mode=Mode.FadeOut, **kwargs): items = self.FadeOutIcons for key in sorted(items.keys()): - self.addItem(IconTheme.get(items[key]), translate('Fade', key), key) + self.addItem(IconTheme.get(items[key]), translate("Fade", key), key) def setCurrentType(self, type): - self.setCurrentText(translate('Fade', type)) + self.setCurrentText(translate("Fade", type)) def currentType(self): return self.currentData() class FadeEdit(QWidget): - def __init__(self, *args, mode=FadeComboBox.Mode.FadeOut, **kwargs): super().__init__(*args, **kwargs) self.setLayout(QGridLayout()) @@ -91,8 +96,8 @@ def __init__(self, *args, mode=FadeComboBox.Mode.FadeOut, **kwargs): self.retranslateUi() def retranslateUi(self): - self.fadeDurationLabel.setText(translate('FadeEdit', 'Duration (sec)')) - self.fadeTypeLabel.setText(translate('FadeEdit', 'Curve')) + self.fadeDurationLabel.setText(translate("FadeEdit", "Duration (sec)")) + self.fadeTypeLabel.setText(translate("FadeEdit", "Curve")) def duration(self): return self.fadeDurationSpin.value() diff --git a/lisp/ui/widgets/pagestreewidget.py b/lisp/ui/widgets/pagestreewidget.py index b9bcf3a64..d4f46b165 100644 --- a/lisp/ui/widgets/pagestreewidget.py +++ b/lisp/ui/widgets/pagestreewidget.py @@ -42,14 +42,16 @@ def __init__(self, navModel, **kwargs): self.layout().addWidget(self._currentWidget, 0, 1) self.navWidget.selectionModel().selectionChanged.connect( - self._changePage) + self._changePage + ) self._resetStretch() def selectFirst(self): if self.navModel.rowCount(): self.navWidget.setCurrentIndex( - self.navModel.index(0, 0, QModelIndex())) + self.navModel.index(0, 0, QModelIndex()) + ) def currentWidget(self): return self._currentWidget @@ -64,7 +66,8 @@ def _changePage(self, selected): self._currentWidget.hide() self._currentWidget = selected.indexes()[0].internalPointer().page self._currentWidget.setSizePolicy( - QSizePolicy.Ignored, QSizePolicy.Ignored) + QSizePolicy.Ignored, QSizePolicy.Ignored + ) self._currentWidget.show() self.layout().addWidget(self._currentWidget, 0, 1) self._resetStretch() @@ -75,6 +78,7 @@ class PageNode: :type parent: PageNode :type _children: list[PageNode] """ + def __init__(self, page): self.parent = None self.page = page @@ -189,4 +193,4 @@ def removePage(self, row, parent=QModelIndex()): if row < parentNode.childCount(): self.beginRemoveRows(parent, row, row) parentNode.removeChild(row) - self.endRemoveRows() \ No newline at end of file + self.endRemoveRows() diff --git a/lisp/ui/widgets/qclickslider.py b/lisp/ui/widgets/qclickslider.py index 1cf560bef..6853960d2 100644 --- a/lisp/ui/widgets/qclickslider.py +++ b/lisp/ui/widgets/qclickslider.py @@ -53,5 +53,6 @@ def _control_rect(self): opt = QStyleOptionSlider() self.initStyleOption(opt) - return self.style().subControlRect(QStyle.CC_Slider, opt, - QStyle.SC_SliderHandle) + return self.style().subControlRect( + QStyle.CC_Slider, opt, QStyle.SC_SliderHandle + ) diff --git a/lisp/ui/widgets/qcolorbutton.py b/lisp/ui/widgets/qcolorbutton.py index a5ac56645..9203e38f7 100644 --- a/lisp/ui/widgets/qcolorbutton.py +++ b/lisp/ui/widgets/qcolorbutton.py @@ -37,7 +37,7 @@ def __init__(self, *args): super().__init__(*args) self._color = None - self.setToolTip(translate('QColorButton', 'Right click to reset')) + self.setToolTip(translate("QColorButton", "Right click to reset")) self.pressed.connect(self.onColorPicker) def setColor(self, color): @@ -47,9 +47,10 @@ def setColor(self, color): if self._color is not None: self.setStyleSheet( - 'QColorButton {{ background-color: {0}; }}'.format(self._color)) + "QColorButton {{ background-color: {0}; }}".format(self._color) + ) else: - self.setStyleSheet('') + self.setStyleSheet("") def color(self): return self._color diff --git a/lisp/ui/widgets/qdbmeter.py b/lisp/ui/widgets/qdbmeter.py index 43d8ca80f..53414608a 100644 --- a/lisp/ui/widgets/qdbmeter.py +++ b/lisp/ui/widgets/qdbmeter.py @@ -23,7 +23,6 @@ class QDbMeter(QWidget): - def __init__(self, parent, min=-60, max=0, clip=0): super().__init__(parent) self.db_min = min @@ -32,12 +31,12 @@ def __init__(self, parent, min=-60, max=0, clip=0): db_range = abs(self.db_min - self.db_max) yellow = abs(self.db_min + 20) / db_range # -20 db - red = abs(self.db_min) / db_range # 0 db + red = abs(self.db_min) / db_range # 0 db self.grad = QLinearGradient() - self.grad.setColorAt(0, QColor(0, 255, 0)) # Green - self.grad.setColorAt(yellow, QColor(255, 255, 0)) # Yellow - self.grad.setColorAt(red, QColor(255, 0, 0)) # Red + self.grad.setColorAt(0, QColor(0, 255, 0)) # Green + self.grad.setColorAt(yellow, QColor(255, 255, 0)) # Yellow + self.grad.setColorAt(red, QColor(255, 0, 0)) # Red self.reset() @@ -58,8 +57,8 @@ def plot(self, peaks, rms, decPeak): def paintEvent(self, e): if not self.visibleRegion().isEmpty(): # Stretch factor - mul = (self.height() - 4) - mul /= (self.db_max - self.db_min) + mul = self.height() - 4 + mul /= self.db_max - self.db_min peaks = [] for n, peak in enumerate(self.peaks): @@ -103,8 +102,9 @@ def paintEvent(self, e): for n, (peak, rms, dPeak) in enumerate(zip(peaks, rmss, dPeaks)): # Maximum 'peak-rect' size - maxRect = QtCore.QRect(xpos, self.height() - 2, xdim - 2, - 2 - self.height()) + maxRect = QtCore.QRect( + xpos, self.height() - 2, xdim - 2, 2 - self.height() + ) # Set QLinearGradient start and final-stop position self.grad.setStart(maxRect.topLeft()) @@ -121,8 +121,9 @@ def paintEvent(self, e): qp.fillRect(rect, self.grad) # Draw decay peak - decRect = QtCore.QRect(xpos, (self.height() - 3) - dPeak, - xdim - 2, 2) + decRect = QtCore.QRect( + xpos, (self.height() - 3) - dPeak, xdim - 2, 2 + ) qp.fillRect(decRect, self.grad) # Draw Borders diff --git a/lisp/ui/widgets/qeditabletabbar.py b/lisp/ui/widgets/qeditabletabbar.py index 9bd12eee2..8118533bb 100644 --- a/lisp/ui/widgets/qeditabletabbar.py +++ b/lisp/ui/widgets/qeditabletabbar.py @@ -35,13 +35,11 @@ def __init__(self, *args): self._editor.installEventFilter(self) def eventFilter(self, widget, event): - clickOutside = ( - event.type() == QEvent.MouseButtonPress and - not self._editor.geometry().contains(event.globalPos()) + clickOutside = event.type() == QEvent.MouseButtonPress and not self._editor.geometry().contains( + event.globalPos() ) escKey = ( - event.type() == QEvent.KeyPress and - event.key() == Qt.Key_Escape + event.type() == QEvent.KeyPress and event.key() == Qt.Key_Escape ) if clickOutside or escKey: diff --git a/lisp/ui/widgets/qenumcombobox.py b/lisp/ui/widgets/qenumcombobox.py index b4119bc46..b7e7b7784 100644 --- a/lisp/ui/widgets/qenumcombobox.py +++ b/lisp/ui/widgets/qenumcombobox.py @@ -40,5 +40,5 @@ def setCurrentItem(self, item): item = self.enum[item] self.setCurrentText(self.trItem(item)) - except(ValueError, KeyError): + except (ValueError, KeyError): pass diff --git a/lisp/ui/widgets/qmessagebox.py b/lisp/ui/widgets/qmessagebox.py index 802af012a..54897a1c9 100644 --- a/lisp/ui/widgets/qmessagebox.py +++ b/lisp/ui/widgets/qmessagebox.py @@ -26,29 +26,23 @@ class QDetailedMessageBox(QMessageBox): @staticmethod def dcritical(title, text, detailed_text, parent=None): """MessageBox with "Critical" icon""" - QDetailedMessageBox.dgeneric(title, - text, - detailed_text, - QMessageBox.Critical, - parent) + QDetailedMessageBox.dgeneric( + title, text, detailed_text, QMessageBox.Critical, parent + ) @staticmethod def dwarning(title, text, detailed_text, parent=None): """MessageBox with "Warning" icon""" - QDetailedMessageBox.dgeneric(title, - text, - detailed_text, - QMessageBox.Warning, - parent) + QDetailedMessageBox.dgeneric( + title, text, detailed_text, QMessageBox.Warning, parent + ) @staticmethod def dinformation(title, text, detailed_text, parent=None): """MessageBox with "Information" icon""" - QDetailedMessageBox.dgeneric(title, - text, - detailed_text, - QMessageBox.Information, - parent) + QDetailedMessageBox.dgeneric( + title, text, detailed_text, QMessageBox.Information, parent + ) @staticmethod def dgeneric(title, text, detail_text, icon, parent=None): @@ -59,7 +53,7 @@ def dgeneric(title, text, detail_text, icon, parent=None): messageBox.setWindowTitle(title) messageBox.setText(text) # Show the detail text only if not an empty string - if detail_text.strip() != '': + if detail_text.strip() != "": messageBox.setDetailedText(detail_text) messageBox.addButton(QMessageBox.Ok) messageBox.setDefaultButton(QMessageBox.Ok) diff --git a/lisp/ui/widgets/qmutebutton.py b/lisp/ui/widgets/qmutebutton.py index 4194c4d48..051ab6a41 100644 --- a/lisp/ui/widgets/qmutebutton.py +++ b/lisp/ui/widgets/qmutebutton.py @@ -40,6 +40,6 @@ def __init__(self, *args): def onToggle(self): if self.isChecked(): - self.setIcon(IconTheme.get('audio-volume-muted')) + self.setIcon(IconTheme.get("audio-volume-muted")) else: - self.setIcon(IconTheme.get('audio-volume-high')) + self.setIcon(IconTheme.get("audio-volume-high")) diff --git a/lisp/ui/widgets/qprogresswheel.py b/lisp/ui/widgets/qprogresswheel.py index 43d7fcff8..ca642b7cf 100644 --- a/lisp/ui/widgets/qprogresswheel.py +++ b/lisp/ui/widgets/qprogresswheel.py @@ -27,7 +27,6 @@ class QProgressWheel(QWidget): - def __init__(self, *args): super().__init__(*args) self._angle = 0 @@ -99,7 +98,7 @@ def paintEvent(self, event): innerRadius = (width - 1) * 0.5 * 0.38 capsuleHeight = outerRadius - innerRadius - capsuleWidth = capsuleHeight * (.23 if width > 32 else .35) + capsuleWidth = capsuleHeight * (0.23 if width > 32 else 0.35) capsuleRadius = capsuleWidth / 2 # Use the pen (text) color to draw the "capsules" @@ -115,8 +114,11 @@ def paintEvent(self, event): painter.drawRoundedRect( QRectF( - capsuleWidth * -0.5, (innerRadius + capsuleHeight) * -1, - capsuleWidth, capsuleHeight + capsuleWidth * -0.5, + (innerRadius + capsuleHeight) * -1, + capsuleWidth, + capsuleHeight, ), - capsuleRadius, capsuleRadius + capsuleRadius, + capsuleRadius, ) diff --git a/lisp/ui/widgets/qsteptimeedit.py b/lisp/ui/widgets/qsteptimeedit.py index 37f1e5b2c..56da6bdfe 100644 --- a/lisp/ui/widgets/qsteptimeedit.py +++ b/lisp/ui/widgets/qsteptimeedit.py @@ -21,7 +21,6 @@ class QStepTimeEdit(QTimeEdit): - def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) @@ -29,9 +28,9 @@ def __init__(self, parent=None, **kwargs): def setSectionStep(self, section, step): if not isinstance(section, QTimeEdit.Section): - raise AttributeError('Not a QTimeEdit.Section') + raise AttributeError("Not a QTimeEdit.Section") if step <= 0: - raise AttributeError('Step value must be >= 0') + raise AttributeError("Step value must be >= 0") self._sections_steps[section] = step diff --git a/lisp/ui/widgets/qstyledslider.py b/lisp/ui/widgets/qstyledslider.py index 3224fda4c..8f00b6f3b 100644 --- a/lisp/ui/widgets/qstyledslider.py +++ b/lisp/ui/widgets/qstyledslider.py @@ -35,8 +35,9 @@ def paintEvent(self, event): self.initStyleOption(option) tick = 5 - handle = self.style().subControlRect(QStyle.CC_Slider, option, - QStyle.SC_SliderHandle) + handle = self.style().subControlRect( + QStyle.CC_Slider, option, QStyle.SC_SliderHandle + ) # Draw tick marks # Do this manually because they are very badly behaved with stylesheets @@ -56,17 +57,22 @@ def paintEvent(self, event): if self.tickPosition() != QSlider.NoTicks: for i in range(self.minimum(), self.maximum() + 1, interval): y = 0 - x = round((i - self.minimum()) / - slide_range * - no_handle_size + - handle_half_size - ) - 1 + x = ( + round( + (i - self.minimum()) / slide_range * no_handle_size + + handle_half_size + ) + - 1 + ) if self.orientation() == Qt.Vertical: x, y = y, x # QSlider.TicksAbove == QSlider.TicksLeft - if self.tickPosition() == QSlider.TicksBothSides or self.tickPosition() == QSlider.TicksAbove: + if ( + self.tickPosition() == QSlider.TicksBothSides + or self.tickPosition() == QSlider.TicksAbove + ): if self.orientation() == Qt.Horizontal: y = self.rect().top() painter.drawLine(x, y, x, y + tick) @@ -75,7 +81,10 @@ def paintEvent(self, event): painter.drawLine(x, y, x + tick, y) # QSlider.TicksBelow == QSlider.TicksRight - if self.tickPosition() == QSlider.TicksBothSides or self.tickPosition() == QSlider.TicksBelow: + if ( + self.tickPosition() == QSlider.TicksBothSides + or self.tickPosition() == QSlider.TicksBelow + ): if self.orientation() == Qt.Horizontal: y = self.rect().bottom() painter.drawLine(x, y, x, y - tick) diff --git a/lisp/ui/widgets/qvertiacallabel.py b/lisp/ui/widgets/qvertiacallabel.py index 2010fb81b..08af8c493 100644 --- a/lisp/ui/widgets/qvertiacallabel.py +++ b/lisp/ui/widgets/qvertiacallabel.py @@ -23,7 +23,6 @@ class QVerticalLabel(QLabel): - def paintEvent(self, event): painter = QPainter(self) painter.rotate(-90) @@ -35,7 +34,7 @@ def paintEvent(self, event): self.setMaximumWidth(hint.height()) self.setMinimumWidth(0) - self.setMaximumHeight(16777215) + self.setMaximumHeight(16_777_215) self.setMinimumHeight(hint.width()) def sizeHint(self): diff --git a/plugins_utils.py b/plugins_utils.py index 6374e0595..34934b2ee 100755 --- a/plugins_utils.py +++ b/plugins_utils.py @@ -26,56 +26,51 @@ from lisp.core.loading import module_to_class_name parser = argparse.ArgumentParser() -parser.add_argument('name', help='Name of the new plugin') +parser.add_argument("name", help="Name of the new plugin") args = parser.parse_args() -PLUGINS_DIRECTORY = 'lisp/plugins' +PLUGINS_DIRECTORY = "lisp/plugins" def create_plugin(name: str): - print('>>> CREATE NEW PLUGIN: {}'.format(name)) + print(">>> CREATE NEW PLUGIN: {}".format(name)) if not name.isidentifier(): - print('Invalid plugin name!', file=sys.stderr) + print("Invalid plugin name!", file=sys.stderr) sys.exit(-1) plugin_path = os.path.join(PLUGINS_DIRECTORY, name) class_name = module_to_class_name(name) os.makedirs(plugin_path) - print('>>> DIRECTORY {} CREATED'.format(plugin_path)) - - print('>>> CREATE DEFAULT SETTINGS FILE') - copyfile(os.path.join(PLUGINS_DIRECTORY, 'default.json'), - os.path.join(plugin_path, 'default.json')) - - print('>>> CREATE DEFAULT INIT FILE') - with open(os.path.join(plugin_path, '__init__.py'), 'w') as init_file: - init_file.write('\n'.join([ - '# Auto-generated __init__.py for plugin', - '', - 'from .{} import {}'.format(name, class_name), - '' - ])) - - print('>>> CREATE DEFAULT MAIN PLUGIN FILE') - with open(os.path.join(plugin_path, name + '.py'), 'w') as main_plugin: - main_plugin.write('\n'.join([ - '# Auto-generated plugin', - '', - 'from lisp.core.plugin import Plugin', - '', - '', - 'class {}(Plugin):'.format(class_name), - '', - ' Name = "{}"'.format(class_name), - ' Authors = ("Nobody", )', - ' Description = "No Description"', - '', - ])) - - print('>>> DONE') + print(">>> DIRECTORY {} CREATED".format(plugin_path)) + + print(">>> CREATE DEFAULT SETTINGS FILE") + copyfile( + os.path.join(PLUGINS_DIRECTORY, "default.json"), + os.path.join(plugin_path, "default.json"), + ) + + print(">>> CREATE DEFAULT INIT FILE") + with open(os.path.join(plugin_path, "__init__.py"), "w") as init_file: + init_file.write( + "# Auto-generated __init__.py for plugin\n" + "from .{} import {}\n".format(name, class_name) + ) + + print(">>> CREATE DEFAULT MAIN PLUGIN FILE") + with open(os.path.join(plugin_path, name + ".py"), "w") as main_plugin: + main_plugin.write( + "# Auto-generated plugin\n" + "from lisp.core.plugin import Plugin\n\n\n" + "class {0}(Plugin):\n" + ' Name = "{0}"\n' + ' Authors = ("Nobody", )\n' + ' Description = "No Description"\n'.format(class_name) + ) + + print(">>> DONE") try: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..60c0f5b34 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,12 @@ +[tool.black] +line-length = 80 +py36 = true +exclude = ''' +/( + \.git + | \.github + | \.tx + | dist + | docs +)/ +''' \ No newline at end of file diff --git a/setup.py b/setup.py index 85aa70e40..1b3d4e9e9 100644 --- a/setup.py +++ b/setup.py @@ -7,45 +7,45 @@ import lisp -def find_files(directory, regex='.*', rel=''): +def find_files(directory, regex=".*", rel=""): """Search for files that match `regex` in `directory`.""" paths = [] for path, directories, file_names in os.walk(directory): for filename in file_names: file_path = os.path.join(path, filename) if re.match(regex, file_path): - paths.append(file_path[len(rel):]) + paths.append(file_path[len(rel) :]) return paths # List the directories with icons to be installed -lisp_icons = find_files('lisp/ui/icons', rel='lisp/ui/icons/') +lisp_icons = find_files("lisp/ui/icons", rel="lisp/ui/icons/") # Setup function setup( - name='linux-show-player', + name="linux-show-player", author=lisp.__author__, author_email=lisp.__email__, version=lisp.__version__, license=lisp.__license__, url=lisp.__email__, - description='Cue player for live shows', + description="Cue player for live shows", install_requires=[ - 'PyQt5', - 'PyGobject', - 'sortedcontainers', - 'mido', - 'python-rtmidi', - 'JACK-Client', - 'pyliblo', - 'falcon', - 'requests', + "PyQt5", + "PyGobject", + "sortedcontainers", + "mido", + "python-rtmidi", + "JACK-Client", + "pyliblo", + "falcon", + "requests", 'scandir;python_version<"3.5"', ], packages=find_packages(), package_data={ - '': ['i18n/*.qm', '*.qss', '*.json'], - 'lisp.ui.icons': lisp_icons, + "": ["i18n/*.qm", "*.qss", "*.json"], + "lisp.ui.icons": lisp_icons, }, - scripts=['linux-show-player'] + scripts=["linux-show-player"], ) From a2134a3ee8b8fa9ae1ec02387b11a081997e5432 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 27 Oct 2018 15:36:50 +0200 Subject: [PATCH 135/333] Added adjustable wait between one "GO" and another in ListLayout Update: small us adjustment Fix: Cue settings in application preferences are now applied --- Pipfile.lock | 8 ++++---- lisp/plugins/cart_layout/settings.py | 22 ++++++++++------------ lisp/plugins/list_layout/default.json | 3 ++- lisp/plugins/list_layout/layout.py | 12 +++++++++--- lisp/plugins/list_layout/settings.py | 18 ++++++++++++++---- lisp/ui/settings/app_pages/cue.py | 2 +- lisp/ui/settings/app_pages/general.py | 11 ++++++----- lisp/ui/widgets/fades.py | 21 ++++++++++----------- 8 files changed, 56 insertions(+), 41 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index a42ed4ea6..e0e8cb222 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -124,7 +124,7 @@ }, "pyqt5": { "hashes": [ - "sha256:0e1b099e036ea5e851fb4475b714b2229947dc848022c77420e47f55b642b299", + "sha256:517e4339135c4874b799af0d484bc2e8c27b54850113a68eec40a0b56534f450", "sha256:ac1eb5a114b6e7788e8be378be41c5e54b17d5158994504e85e43b5fca006a39", "sha256:d2309296a5a79d0a1c0e6c387c30f0398b65523a6dcc8a19cc172e46b949e00d", "sha256:e85936bae1581bcb908847d2038e5b34237a5e6acc03130099a78930770e7ead" @@ -242,10 +242,10 @@ }, "graphviz": { "hashes": [ - "sha256:310bacfb969f0ac7c872610500e017c3e82b24a8abd33d289e99af162de30cb8", - "sha256:865afa6ab9775cf29db03abd8e571a164042c726c35a1b3c1e2b8c4c645e2993" + "sha256:0e1744a45b0d707bc44f99c7b8e5f25dc22cf96b6aaf2432ac308ed9822a9cb6", + "sha256:d311be4fddfe832a56986ac5e1d6e8715d7fcb0208560da79d1bb0f72abef41f" ], - "version": "==0.9" + "version": "==0.10.1" }, "objgraph": { "hashes": [ diff --git a/lisp/plugins/cart_layout/settings.py b/lisp/plugins/cart_layout/settings.py index 848d5f37f..c76f4c401 100644 --- a/lisp/plugins/cart_layout/settings.py +++ b/lisp/plugins/cart_layout/settings.py @@ -62,22 +62,20 @@ def __init__(self, **kwargs): self.gridSizeGroup.setLayout(QGridLayout()) self.layout().addWidget(self.gridSizeGroup) + self.columnsLabel = QLabel(self.gridSizeGroup) + self.gridSizeGroup.layout().addWidget(self.columnsLabel, 0, 0) self.columnsSpin = QSpinBox(self.gridSizeGroup) self.columnsSpin.setRange(1, 16) - self.gridSizeGroup.layout().addWidget(self.columnsSpin, 0, 0) - - self.columnsLabel = QLabel(self.gridSizeGroup) - self.gridSizeGroup.layout().addWidget(self.columnsLabel, 0, 1) + self.gridSizeGroup.layout().addWidget(self.columnsSpin, 0, 1) + self.rowsLabel = QLabel(self.gridSizeGroup) + self.gridSizeGroup.layout().addWidget(self.rowsLabel, 1, 0) self.rowsSpin = QSpinBox(self.gridSizeGroup) self.rowsSpin.setRange(1, 16) - self.gridSizeGroup.layout().addWidget(self.rowsSpin, 1, 0) - - self.rowsLabel = QLabel(self.gridSizeGroup) - self.gridSizeGroup.layout().addWidget(self.rowsLabel, 1, 1) + self.gridSizeGroup.layout().addWidget(self.rowsSpin, 1, 1) - self.gridSizeGroup.layout().setColumnStretch(0, 5) - self.gridSizeGroup.layout().setColumnStretch(1, 3) + self.gridSizeGroup.layout().setColumnStretch(0, 1) + self.gridSizeGroup.layout().setColumnStretch(1, 1) self.retranslateUi() @@ -91,8 +89,8 @@ def retranslateUi(self): self.showAccurate.setText(translate("CartLayout", "Show accurate time")) self.showVolume.setText(translate("CartLayout", "Show volume")) self.gridSizeGroup.setTitle(translate("CartLayout", "Grid size")) - self.columnsLabel.setText(translate("CartLayout", "Number of columns")) - self.rowsLabel.setText(translate("CartLayout", "Number of rows")) + self.columnsLabel.setText(translate("CartLayout", "Number of columns:")) + self.rowsLabel.setText(translate("CartLayout", "Number of rows:")) def loadSettings(self, settings): self.columnsSpin.setValue(settings["grid"]["columns"]) diff --git a/lisp/plugins/list_layout/default.json b/lisp/plugins/list_layout/default.json index bdbfba2fa..cd0477cca 100644 --- a/lisp/plugins/list_layout/default.json +++ b/lisp/plugins/list_layout/default.json @@ -1,5 +1,5 @@ { - "_version_": "1.3", + "_version_": "1.4", "_enabled_": true, "show": { "dBMeters": true, @@ -11,6 +11,7 @@ "autoContinue": true, "goKey": "Space", "goAction": "Default", + "goDelay": 100, "stopCueFade": true, "pauseCueFade": true, "resumeCueFade": true, diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index b99b65e92..09d937d73 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP +from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP, QTimer from PyQt5.QtGui import QKeySequence from PyQt5.QtWidgets import QAction @@ -67,6 +67,8 @@ def __init__(self, application): self._list_model.item_added.connect(self.__cue_added) self._memento_model = CueMementoAdapter(self._list_model) self._running_model = RunningCueModel(self.cue_model) + self._go_timer = QTimer() + self._go_timer.setSingleShot(True) self._view = ListLayoutView( self._list_model, self._running_model, self.Config @@ -334,8 +336,12 @@ def _context_invoked(self, event): self.show_context_menu(event.globalPos()) def __go_slot(self): - action = CueAction(ListLayout.Config.get("goAction")) - self.go(action=action) + if not self._go_timer.isActive(): + action = CueAction(ListLayout.Config.get("goAction")) + self.go(action=action) + + self._go_timer.setInterval(ListLayout.Config.get("goDelay")) + self._go_timer.start() def __cue_added(self, cue): cue.next.connect(self.__cue_next, Connection.QtQueued) diff --git a/lisp/plugins/list_layout/settings.py b/lisp/plugins/list_layout/settings.py index 3c81b9f64..2d2e202c4 100644 --- a/lisp/plugins/list_layout/settings.py +++ b/lisp/plugins/list_layout/settings.py @@ -26,6 +26,7 @@ QLabel, QKeySequenceEdit, QGridLayout, + QSpinBox, ) from lisp.cues.cue import CueAction @@ -65,8 +66,8 @@ def __init__(self, **kwargs): self.behaviorsGroup.layout().addWidget(self.selectionMode) self.goLayout = QGridLayout() - self.goLayout.setColumnStretch(0, 2) - self.goLayout.setColumnStretch(1, 5) + self.goLayout.setColumnStretch(0, 1) + self.goLayout.setColumnStretch(1, 1) self.behaviorsGroup.layout().addLayout(self.goLayout) self.goKeyLabel = QLabel(self.behaviorsGroup) @@ -82,6 +83,12 @@ def __init__(self, **kwargs): ) self.goLayout.addWidget(self.goActionCombo, 1, 1) + self.goDelayLabel = QLabel(self.behaviorsGroup) + self.goLayout.addWidget(self.goDelayLabel, 2, 0) + self.goDelaySpin = QSpinBox(self.behaviorsGroup) + self.goDelaySpin.setMaximum(10000) + self.goLayout.addWidget(self.goDelaySpin, 2, 1) + self.useFadeGroup = QGroupBox(self) self.useFadeGroup.setLayout(QGridLayout()) self.layout().addWidget(self.useFadeGroup) @@ -111,8 +118,9 @@ def retranslateUi(self): translate("ListLayout", "Enable selection mode") ) - self.goKeyLabel.setText(translate("ListLayout", "Go key:")) - self.goActionLabel.setText(translate("ListLayout", "Go action")) + self.goKeyLabel.setText(translate("ListLayout", "GO key:")) + self.goActionLabel.setText(translate("ListLayout", "GO action:")) + self.goDelayLabel.setText(translate("ListLayout", "GO delay (ms):")) self.useFadeGroup.setTitle( translate("ListLayout", "Use fade (buttons)") @@ -134,6 +142,7 @@ def loadSettings(self, settings): QKeySequence(settings["goKey"], QKeySequence.NativeText) ) self.goActionCombo.setCurrentItem(settings["goAction"]) + self.goDelaySpin.setValue(settings["goDelay"]) self.stopCueFade.setChecked(settings["stopCueFade"]) self.pauseCueFade.setChecked(settings["pauseCueFade"]) @@ -154,6 +163,7 @@ def getSettings(self): QKeySequence.NativeText ), "goAction": self.goActionCombo.currentItem(), + "goDelay": self.goDelaySpin.value(), "stopCueFade": self.stopCueFade.isChecked(), "pauseCueFade": self.pauseCueFade.isChecked(), "resumeCueFade": self.resumeCueFade.isChecked(), diff --git a/lisp/ui/settings/app_pages/cue.py b/lisp/ui/settings/app_pages/cue.py index e0d638302..7c9f81715 100644 --- a/lisp/ui/settings/app_pages/cue.py +++ b/lisp/ui/settings/app_pages/cue.py @@ -55,7 +55,7 @@ def retranslateUi(self): self.interruptGroup.setTitle(translate("CueSettings", "Interrupt fade")) self.actionGroup.setTitle(translate("CueSettings", "Fade actions")) - def applySettings(self): + def getSettings(self): return { "cue": { "interruptFade": self.interruptFadeEdit.duration(), diff --git a/lisp/ui/settings/app_pages/general.py b/lisp/ui/settings/app_pages/general.py index 819b8a09a..72fb80fd9 100644 --- a/lisp/ui/settings/app_pages/general.py +++ b/lisp/ui/settings/app_pages/general.py @@ -62,22 +62,23 @@ def __init__(self, **kwargs): # Application theme self.themeGroup = QGroupBox(self) self.themeGroup.setLayout(QGridLayout()) - self.layout().addWidget(self.themeGroup) + self.layout().addWidget(self.themeGroup) self.themeLabel = QLabel(self.themeGroup) self.themeGroup.layout().addWidget(self.themeLabel, 0, 0) - self.themeCombo = QComboBox(self.themeGroup) self.themeCombo.addItems(themes_names()) self.themeGroup.layout().addWidget(self.themeCombo, 0, 1) self.iconsLabel = QLabel(self.themeGroup) self.themeGroup.layout().addWidget(self.iconsLabel, 1, 0) - self.iconsCombo = QComboBox(self.themeGroup) self.iconsCombo.addItems(icon_themes_names()) self.themeGroup.layout().addWidget(self.iconsCombo, 1, 1) + self.themeGroup.layout().setColumnStretch(0, 1) + self.themeGroup.layout().setColumnStretch(1, 1) + self.retranslateUi() def retranslateUi(self): @@ -90,8 +91,8 @@ def retranslateUi(self): self.themeGroup.setTitle( translate("AppGeneralSettings", "Application themes") ) - self.themeLabel.setText(translate("AppGeneralSettings", "UI theme")) - self.iconsLabel.setText(translate("AppGeneralSettings", "Icons theme")) + self.themeLabel.setText(translate("AppGeneralSettings", "UI theme:")) + self.iconsLabel.setText(translate("AppGeneralSettings", "Icons theme:")) def getSettings(self): settings = { diff --git a/lisp/ui/widgets/fades.py b/lisp/ui/widgets/fades.py index d690381f2..23aad7434 100644 --- a/lisp/ui/widgets/fades.py +++ b/lisp/ui/widgets/fades.py @@ -78,26 +78,25 @@ def __init__(self, *args, mode=FadeComboBox.Mode.FadeOut, **kwargs): super().__init__(*args, **kwargs) self.setLayout(QGridLayout()) + self.fadeDurationLabel = QLabel(self) + self.layout().addWidget(self.fadeDurationLabel, 0, 0) self.fadeDurationSpin = QDoubleSpinBox(self) self.fadeDurationSpin.setRange(0, 3600) - self.layout().addWidget(self.fadeDurationSpin, 0, 0) - - self.fadeDurationLabel = QLabel(self) - self.fadeDurationLabel.setAlignment(Qt.AlignCenter) - self.layout().addWidget(self.fadeDurationLabel, 0, 1) + self.layout().addWidget(self.fadeDurationSpin, 0, 1) + self.fadeTypeLabel = QLabel(self) + self.layout().addWidget(self.fadeTypeLabel, 1, 0) self.fadeTypeCombo = FadeComboBox(self, mode=mode) - self.layout().addWidget(self.fadeTypeCombo, 1, 0) + self.layout().addWidget(self.fadeTypeCombo, 1, 1) - self.fadeTypeLabel = QLabel(self) - self.fadeTypeLabel.setAlignment(Qt.AlignCenter) - self.layout().addWidget(self.fadeTypeLabel, 1, 1) + self.layout().setColumnStretch(0, 1) + self.layout().setColumnStretch(1, 1) self.retranslateUi() def retranslateUi(self): - self.fadeDurationLabel.setText(translate("FadeEdit", "Duration (sec)")) - self.fadeTypeLabel.setText(translate("FadeEdit", "Curve")) + self.fadeDurationLabel.setText(translate("FadeEdit", "Duration (sec):")) + self.fadeTypeLabel.setText(translate("FadeEdit", "Curve:")) def duration(self): return self.fadeDurationSpin.value() From 699761548945eb7ad3b243f1cd38c5cc598ef47d Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 27 Oct 2018 16:30:19 +0200 Subject: [PATCH 136/333] Added "Clone" option in ListLayout --- lisp/plugins/list_layout/layout.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index 09d937d73..c299a194d 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -25,6 +25,7 @@ from lisp.core.properties import ProxyProperty from lisp.core.signal import Connection from lisp.cues.cue import Cue, CueAction, CueNextAction +from lisp.cues.cue_factory import CueFactory from lisp.cues.cue_memento_model import CueMementoAdapter from lisp.layout.cue_layout import CueLayout from lisp.layout.cue_menu import ( @@ -141,13 +142,19 @@ def __init__(self, application): SimpleMenuAction( translate("ListLayout", "Edit cue"), self.edit_cue, - translate("ListLayout", "Edit selected cues"), + translate("ListLayout", "Edit selected"), self.edit_cues, ), + SimpleMenuAction( + translate("ListLayout", "Clone cue"), + self._clone_cue, + translate("ListLayout", "Clone selected"), + self._clone_cues, + ), SimpleMenuAction( translate("ListLayout", "Remove cue"), self.cue_model.remove, - translate("ListLayout", "Remove selected cues"), + translate("ListLayout", "Remove selected"), self._remove_cues, ), ) @@ -335,6 +342,15 @@ def _context_invoked(self, event): else: self.show_context_menu(event.globalPos()) + def _clone_cue(self, cue): + self._clone_cues((cue, )) + + def _clone_cues(self, cues): + for pos, cue in enumerate(cues, cues[-1].index + 1): + clone = CueFactory.clone_cue(cue) + clone.name = "Copy of {}".format(clone.name) + self._list_model.insert(clone, pos) + def __go_slot(self): if not self._go_timer.isActive(): action = CueAction(ListLayout.Config.get("goAction")) @@ -352,8 +368,8 @@ def __cue_next(self, cue): if next_index < len(self._list_model): action = CueNextAction(cue.next_action) if ( - action == CueNextAction.SelectAfterEnd - or action == CueNextAction.SelectAfterWait + action == CueNextAction.SelectAfterEnd + or action == CueNextAction.SelectAfterWait ): self.set_standby_index(next_index) else: From 689bc07db2e5e017b9497ad3f89e3b67c4653c70 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 27 Oct 2018 17:26:50 +0200 Subject: [PATCH 137/333] New cues are inserted after the current selection, when allowed by the layout (#110) --- lisp/plugins/action_cues/__init__.py | 6 ++++++ lisp/plugins/gst_backend/gst_backend.py | 12 +++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lisp/plugins/action_cues/__init__.py b/lisp/plugins/action_cues/__init__.py index 125147ba8..2c072400f 100644 --- a/lisp/plugins/action_cues/__init__.py +++ b/lisp/plugins/action_cues/__init__.py @@ -57,6 +57,12 @@ def _new_cue_factory(self, cue_class): def cue_factory(): try: cue = CueFactory.create_cue(cue_class.__name__) + + # Get the (last) index of the current selection + layout_selection = list(self.app.layout.selected_cues()) + if layout_selection: + cue.index = layout_selection[-1].index + 1 + self.app.cue_model.add(cue) except Exception: logger.exception( diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index c9876e8ed..3dab1c53c 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -127,11 +127,21 @@ def _add_uri_audio_cue(self): # Create media cues, and add them to the Application cue_model factory = UriAudioCueFactory(GstBackend.Config["pipeline"]) - for file in files: + # Get the (last) index of the current selection + start_index = -1 + layout_selection = list(self.app.layout.selected_cues()) + if layout_selection: + start_index = layout_selection[-1].index + 1 + + for index, file in enumerate(files, start_index): file = self.app.session.rel_path(file) cue = factory(uri="file://" + file) # Use the filename without extension as cue name cue.name = os.path.splitext(os.path.basename(file))[0] + # Set the index (if something is selected) + if start_index != -1: + cue.index = index + self.app.cue_model.add(cue) QApplication.restoreOverrideCursor() From 26bdee474748bd9c3e80ecdadee8dcaefcb2d076 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 2 Nov 2018 15:23:10 +0100 Subject: [PATCH 138/333] Update flatpak build scripts --- Pipfile | 26 ++- Pipfile.lock | 96 +++++--- README.md | 1 + dist/Flatpak/README | 62 ++++++ dist/Flatpak/TODO | 5 - dist/Flatpak/pipenv_flatpak.py | 210 ++++++++++++++++++ ...uti.LinuxShowPlayer.json => template.json} | 182 ++------------- dist/Flatpak/travis_flatpak.py | 88 ++++---- dist/travis_bintray.py | 19 -- lisp/core/loading.py | 9 +- lisp/plugins/list_layout/layout.py | 6 +- lisp/plugins/presets/lib.py | 7 +- lisp/ui/icons/__init__.py | 11 +- setup.py | 12 - 14 files changed, 418 insertions(+), 316 deletions(-) create mode 100644 dist/Flatpak/README delete mode 100644 dist/Flatpak/TODO create mode 100644 dist/Flatpak/pipenv_flatpak.py rename dist/Flatpak/{com.github.FrancescoCeruti.LinuxShowPlayer.json => template.json} (51%) diff --git a/Pipfile b/Pipfile index 513ecf7bc..c61f92776 100644 --- a/Pipfile +++ b/Pipfile @@ -4,17 +4,21 @@ verify_ssl = true name = "pypi" [packages] -sortedcontainers = "*" -mido = "*" -python-rtmidi = "*" -JACK-Client = "*" -pyliblo = "*" -"PyQt5" = "*" -PyGObject = "*" -falcon = "*" -requests = "*" +Cython = "==0.29.*" +falcon = "==1.4.*" +JACK-Client = "==0.4.*" +mido = "==1.2.*" +PyGObject = "==3.30.*" +pyliblo = "==0.10.*" +PyQt5 = "==5.11.*" +python-rtmidi = "==1.1.*" +requests = "==2.20.*" +sortedcontainers = "==2.0.*" [dev-packages] -Cython = "*" -pycallgraph = "*" objgraph = "*" +pycallgraph = "*" +html5lib = "*" + +[requires] +python_version = ">= 3.5" diff --git a/Pipfile.lock b/Pipfile.lock index e0e8cb222..beb3ffdf6 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,10 +1,12 @@ { "_meta": { "hash": { - "sha256": "c1700f263a741ff95932519e9a58f1dbcc400242fcd26fa20f8b6d2e90fbd9af" + "sha256": "dedb111fc520e13dd00a16ff9f2a4c7ea81295e94b1e17fc83efe726ea5b072d" }, "pipfile-spec": 6, - "requires": {}, + "requires": { + "python_version": ">= 3.5" + }, "sources": [ { "name": "pypi", @@ -65,6 +67,40 @@ ], "version": "==3.0.4" }, + "cython": { + "hashes": [ + "sha256:019008a69e6b7c102f2ed3d733a288d1784363802b437dd2b91e6256b12746da", + "sha256:1441fe19c56c90b8c2159d7b861c31a134d543ef7886fd82a5d267f9f11f35ac", + "sha256:1d1a5e9d6ed415e75a676b72200ad67082242ec4d2d76eb7446da255ae72d3f7", + "sha256:339f5b985de3662b1d6c69991ab46fdbdc736feb4ac903ef6b8c00e14d87f4d8", + "sha256:35bdf3f48535891fee2eaade70e91d5b2cc1ee9fc2a551847c7ec18bce55a92c", + "sha256:3d0afba0aec878639608f013045697fb0969ff60b3aea2daec771ea8d01ad112", + "sha256:42c53786806e24569571a7a24ebe78ec6b364fe53e79a3f27eddd573cacd398f", + "sha256:48b919da89614d201e72fbd8247b5ae8881e296cf968feb5595a015a14c67f1f", + "sha256:49906e008eeb91912654a36c200566392bd448b87a529086694053a280f8af2d", + "sha256:49fc01a7c9c4e3c1784e9a15d162c2cac3990fcc28728227a6f8f0837aabda7c", + "sha256:501b671b639b9ca17ad303f8807deb1d0ff754d1dab106f2607d14b53cb0ff0b", + "sha256:5574574142364804423ab4428bd331a05c65f7ecfd31ac97c936f0c720fe6a53", + "sha256:6092239a772b3c6604be9e94b9ab4f0dacb7452e8ad299fd97eae0611355b679", + "sha256:71ff5c7632501c4f60edb8a24fd0a772e04c5bdca2856d978d04271b63666ef7", + "sha256:7dcf2ad14e25b05eda8bdd104f8c03a642a384aeefd25a5b51deac0826e646fa", + "sha256:8ca3a99f5a7443a6a8f83a5d8fcc11854b44e6907e92ba8640d8a8f7b9085e21", + "sha256:927da3b5710fb705aab173ad630b45a4a04c78e63dcd89411a065b2fe60e4770", + "sha256:94916d1ede67682638d3cc0feb10648ff14dc51fb7a7f147f4fedce78eaaea97", + "sha256:a3e5e5ca325527d312cdb12a4dab8b0459c458cad1c738c6f019d0d8d147081c", + "sha256:a7716a98f0b9b8f61ddb2bae7997daf546ac8fc594be6ba397f4bde7d76bfc62", + "sha256:acf10d1054de92af8d5bfc6620bb79b85f04c98214b4da7db77525bfa9fc2a89", + "sha256:de46ffb67e723975f5acab101c5235747af1e84fbbc89bf3533e2ea93fb26947", + "sha256:df428969154a9a4cd9748c7e6efd18432111fbea3d700f7376046c38c5e27081", + "sha256:f5ebf24b599caf466f9da8c4115398d663b2567b89e92f58a835e9da4f74669f", + "sha256:f79e45d5c122c4fb1fd54029bf1d475cecc05f4ed5b68136b0d6ec268bae68b6", + "sha256:f7a43097d143bd7846ffba6d2d8cd1cc97f233318dbd0f50a235ea01297a096b", + "sha256:fceb8271bc2fd3477094ca157c824e8ea840a7b393e89e766eea9a3b9ce7e0c6", + "sha256:ff919ceb40259f5332db43803aa6c22ff487e86036ce3921ae04b9185efc99a4" + ], + "index": "pypi", + "version": "==0.29" + }, "falcon": { "hashes": [ "sha256:0a66b33458fab9c1e400a9be1a68056abda178eb02a8cb4b8f795e9df20b053b", @@ -206,40 +242,6 @@ } }, "develop": { - "cython": { - "hashes": [ - "sha256:019008a69e6b7c102f2ed3d733a288d1784363802b437dd2b91e6256b12746da", - "sha256:1441fe19c56c90b8c2159d7b861c31a134d543ef7886fd82a5d267f9f11f35ac", - "sha256:1d1a5e9d6ed415e75a676b72200ad67082242ec4d2d76eb7446da255ae72d3f7", - "sha256:339f5b985de3662b1d6c69991ab46fdbdc736feb4ac903ef6b8c00e14d87f4d8", - "sha256:35bdf3f48535891fee2eaade70e91d5b2cc1ee9fc2a551847c7ec18bce55a92c", - "sha256:3d0afba0aec878639608f013045697fb0969ff60b3aea2daec771ea8d01ad112", - "sha256:42c53786806e24569571a7a24ebe78ec6b364fe53e79a3f27eddd573cacd398f", - "sha256:48b919da89614d201e72fbd8247b5ae8881e296cf968feb5595a015a14c67f1f", - "sha256:49906e008eeb91912654a36c200566392bd448b87a529086694053a280f8af2d", - "sha256:49fc01a7c9c4e3c1784e9a15d162c2cac3990fcc28728227a6f8f0837aabda7c", - "sha256:501b671b639b9ca17ad303f8807deb1d0ff754d1dab106f2607d14b53cb0ff0b", - "sha256:5574574142364804423ab4428bd331a05c65f7ecfd31ac97c936f0c720fe6a53", - "sha256:6092239a772b3c6604be9e94b9ab4f0dacb7452e8ad299fd97eae0611355b679", - "sha256:71ff5c7632501c4f60edb8a24fd0a772e04c5bdca2856d978d04271b63666ef7", - "sha256:7dcf2ad14e25b05eda8bdd104f8c03a642a384aeefd25a5b51deac0826e646fa", - "sha256:8ca3a99f5a7443a6a8f83a5d8fcc11854b44e6907e92ba8640d8a8f7b9085e21", - "sha256:927da3b5710fb705aab173ad630b45a4a04c78e63dcd89411a065b2fe60e4770", - "sha256:94916d1ede67682638d3cc0feb10648ff14dc51fb7a7f147f4fedce78eaaea97", - "sha256:a3e5e5ca325527d312cdb12a4dab8b0459c458cad1c738c6f019d0d8d147081c", - "sha256:a7716a98f0b9b8f61ddb2bae7997daf546ac8fc594be6ba397f4bde7d76bfc62", - "sha256:acf10d1054de92af8d5bfc6620bb79b85f04c98214b4da7db77525bfa9fc2a89", - "sha256:de46ffb67e723975f5acab101c5235747af1e84fbbc89bf3533e2ea93fb26947", - "sha256:df428969154a9a4cd9748c7e6efd18432111fbea3d700f7376046c38c5e27081", - "sha256:f5ebf24b599caf466f9da8c4115398d663b2567b89e92f58a835e9da4f74669f", - "sha256:f79e45d5c122c4fb1fd54029bf1d475cecc05f4ed5b68136b0d6ec268bae68b6", - "sha256:f7a43097d143bd7846ffba6d2d8cd1cc97f233318dbd0f50a235ea01297a096b", - "sha256:fceb8271bc2fd3477094ca157c824e8ea840a7b393e89e766eea9a3b9ce7e0c6", - "sha256:ff919ceb40259f5332db43803aa6c22ff487e86036ce3921ae04b9185efc99a4" - ], - "index": "pypi", - "version": "==0.29" - }, "graphviz": { "hashes": [ "sha256:0e1744a45b0d707bc44f99c7b8e5f25dc22cf96b6aaf2432ac308ed9822a9cb6", @@ -247,6 +249,14 @@ ], "version": "==0.10.1" }, + "html5lib": { + "hashes": [ + "sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", + "sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736" + ], + "index": "pypi", + "version": "==1.0.1" + }, "objgraph": { "hashes": [ "sha256:4a0c2c6268e10a9e8176ae054ff3faac9a432087801e1f95c3ebbe52550295a0", @@ -261,6 +271,20 @@ ], "index": "pypi", "version": "==1.0.1" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "version": "==1.11.0" + }, + "webencodings": { + "hashes": [ + "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", + "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" + ], + "version": "==0.5.1" } } } diff --git a/README.md b/README.md index c52e63bfd..db6d3af7d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Code Health Gitter Code style: black +Say Thanks!

--- diff --git a/dist/Flatpak/README b/dist/Flatpak/README new file mode 100644 index 000000000..fdfcdf19c --- /dev/null +++ b/dist/Flatpak/README @@ -0,0 +1,62 @@ +------ INTRO ------ + +(╯°□°)╯︵ ┻━┻ + +The scripts/files in this folder allow to build a Flatpak bundle of +Linux Show Player using Travis-CI, in LiSP case, the build is run in a custom +docker image, the travis VM or containers (Ubuntu 12.04 or 14.04) don't have +support for flatpak. + +- build.sh: this bash script invoke the needed flatpak commands to build the + application from a manifest file. + +- functions.sh: include some handy logging for travis + +- pipenv_flatpak.py: starting from the project Pipfile.lock generate the + appropriate modules to install the python requirements + +- template.json: the base manifest file, contains all the metadata, and the + non-python packages (some exception here) + +- travis_flatpak.py: starting from "template.json" outputs the complete manifest + + +------ DETAILS ------ + +- some parts are hardcoded, because I'm lazy and this scripts are designed with + LiSP in mind + +- CPython is already present in the runtime, but we prefer a newer version, + so we include a custom one. + This version is build on a different Travis-CI repository and downloaded + from bintray, to optimize the building process + +- non-used features of the various packages should be disabled when possible + +- gstreamer-plugins-good is already included in the base runtime, but with some + needed feature disabled, so we re-built it + +- the python "protobuf" package is specified directly in the template, this for + a few reasons: + - It's needed to "build" the OLA python package, which, can only be build by + with all the OLA framework (--enable-python-libs) + - It's not specified in the Pipfile, for the reason above, without a complete + OLA installation you cannot have the python package, so we don't need it + +- protobuf requires "six" and "setuptools", we install the first, but we + consider the second already shipped with the cpython packages + +- for python we favor pre-build "wheel" releases, which are faster to install + +- the PyQt5 package from PyPi already bundle the correct Qt5 binaries + + +------ NOTES ------ + +- pipenv_flatpak.py is quite generic, it could also be used in other places + +- The PyQt5 wheels from pypi are "all-inclusive" and quite big + +- If we need custom build for "pyqt5" and "sip" the best approach is probably to + create an ad-hoc repository to produce a wheel file like the ones distributed + from pypi diff --git a/dist/Flatpak/TODO b/dist/Flatpak/TODO deleted file mode 100644 index 740fcc79e..000000000 --- a/dist/Flatpak/TODO +++ /dev/null @@ -1,5 +0,0 @@ -- If we need custom build for "pyqt5" and "sip" the best approach is probably to - create an ad-hoc repository to produce a wheel file like the ones distributed from pypi -- We should avoid using pip, see the "upgrade-pip" module - -(╯°□°)╯︵ ┻━┻ \ No newline at end of file diff --git a/dist/Flatpak/pipenv_flatpak.py b/dist/Flatpak/pipenv_flatpak.py new file mode 100644 index 000000000..e5bc66b33 --- /dev/null +++ b/dist/Flatpak/pipenv_flatpak.py @@ -0,0 +1,210 @@ +import html5lib +import json +import os +import requests +import urllib.parse +from concurrent.futures import ThreadPoolExecutor, wait + +# From pip project (https://github.com/pypa/pip) +BZ2_EXTENSIONS = (".tar.bz2", ".tbz") +XZ_EXTENSIONS = (".tar.xz", ".txz", ".tlz", ".tar.lz", ".tar.lzma") +ZIP_EXTENSIONS = (".zip", ".whl") +TAR_EXTENSIONS = (".tar.gz", ".tgz", ".tar") +ARCHIVE_EXTENSIONS = ( + ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS +) + +PLATFORMS_LINUX_x86_64 = ("linux_x86_64", "manylinux1_x86_64") +PYPI_URL = "https://pypi.python.org/simple/" + + +def _flatpak_module_template(): + return { + "name": "", + "buildsystem": "simple", + "build-commands": ["pip3 install --no-deps --prefix=${FLATPAK_DEST} "], + "sources": [{"type": "file", "url": "", "sha256": ""}], + } + + +def _is_like_archive(filename): + """Return whether the filename looks like an archive. + + From pip project (https://github.com/pypa/pip) + """ + for ext in ARCHIVE_EXTENSIONS: + if filename.endswith(ext): + return True, ext + + return False, None + + +def _is_wheel(filename): + if filename.endswith(".whl"): + return True + + return False + + +def _wheel_versions(py_version): + return { + "py" + py_version.replace(".", ""), + "py" + py_version[0], + "cp" + py_version.replace(".", ""), + "any", + } + + +def _wheel_tags(filename): + """Get wheel tags form the filename, see pep-0425 + + Returns: + A tuple (version, set(python-tags), platform-tag) + """ + parts = filename[:-4].split("-") + return parts[1], set(parts[-3].split(".")), parts[-1] + + +def _find_candidate_downloads(package, whl_platforms, whl_versions): + for filename, url in package["candidates"]: + if _is_wheel(filename): + if package["wheel"] is not None: + # Take the first matching wheel, ignore others + continue + + version, python_tags, platform_tag = _wheel_tags(filename) + if version != package["version"]: + # print(' discard version {}'.format(version)) + continue + if platform_tag not in whl_platforms: + # print(' discard platform {}'.format(platform_tag)) + continue + if not python_tags.intersection(whl_versions): + # print(' discard python-version {}'.format(python_tags)) + continue + + url, fragment = urllib.parse.urldefrag(url) + if not fragment.startswith("sha256="): + continue + + package["wheel"] = (url, fragment[7:]) + if package["source"] is not None: + break + else: + is_archive, ext = _is_like_archive(filename) + if is_archive: + version = filename[: -(len(ext))].split("-")[-1] + if version != package["version"]: + # print(' discard version {}'.format(version)) + continue + + url, fragment = urllib.parse.urldefrag(url) + if not fragment.startswith("sha256="): + continue + + package["source"] = (url, fragment[7:]) + if package["wheel"] is not None: + break + + +def get_locked_packages(lock_file): + with open(lock_file, mode="r") as lf: + lock_content = json.load(lf) + + # Get the required packages + packages = [] + for name, info in lock_content.get("default", {}).items(): + packages.append( + { + "name": name, + "version": info["version"][2:], + "candidates": [], + "wheel": None, + "source": None, + } + ) + + return packages + + +def fetch_downloads_candidates(url_template, packages): + session = requests.session() + + def fetch(package): + print(" Download candidates for {}".format(package["name"])) + + # GET the page from the mirror + url = url_template.format(package["name"]) + resp = session.get(url) + + if resp.status_code != 200: + print( + " Cannot fetch candidates: error {}".format(resp.status_code) + ) + + # Parse HTML content + html = html5lib.parse(resp.content, namespaceHTMLElements=False) + + # Iterate all the provided downloads + for link in html.findall(".//a"): + package["candidates"].append((link.text, link.attrib["href"])) + + with ThreadPoolExecutor(max_workers=5) as executor: + wait(tuple(executor.submit(fetch, package) for package in packages)) + + session.close() + + +def filter_candidates(packages, whl_platforms, whl_versions): + for package in packages: + _find_candidate_downloads(package, whl_platforms, whl_versions) + # Cleanup + package.pop("candidates") + + +def generate(lock_file, py_version, platforms, base_url): + # Read the Pipfile.lock + print("=> Reading Pipfile.lock ...") + packages = get_locked_packages(lock_file) + print("=> Found {} required packages".format(len(packages))) + print("=> Fetching packages info from {}".format(base_url)) + fetch_downloads_candidates(base_url + "{}/", packages) + print("=> Filtering packages downloads candidates ...") + filter_candidates(packages, platforms, _wheel_versions(py_version)) + + # Insert python-packages modules + for package in packages: + if package["wheel"] is not None: + source = package["wheel"] + elif package["source"] is not None: + source = package["source"] + else: + print(" Skip: {}".format(package["name"])) + continue + + print(" Selected: {}".format(source[0])) + + module = _flatpak_module_template() + module["name"] = package["name"] + module["build-commands"][0] += os.path.basename( + urllib.parse.urlsplit(source[0]).path + ) + module["sources"][0]["url"] = source[0] + module["sources"][0]["sha256"] = source[1] + + yield module + + +if __name__ == "__main__": + import pprint + import platform + + for module in tuple( + generate( + "../../Pipfile.lock", + ".".join(platform.python_version_tuple[:2]), + PLATFORMS_LINUX_x86_64, + PYPI_URL, + ) + ): + pprint.pprint(module, indent=4, width=1000) diff --git a/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json b/dist/Flatpak/template.json similarity index 51% rename from dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json rename to dist/Flatpak/template.json index 10e23a220..b6bf211a9 100644 --- a/dist/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json +++ b/dist/Flatpak/template.json @@ -31,25 +31,11 @@ "sources": [ { "type": "archive", - "url": "https://dl.bintray.com/francescoceruti/flatpak-builds/py3.6.5-freedesktop1.6.tar.gz", + "url": "https://dl.bintray.com/francescoceruti/flatpak-builds/py3.7.1-freedesktop1.6.tar.gz", "sha256": "d55bea2a7e62f90df90c2a8069d5efbd7a1bb6698b0293db5947f874eaa243be" } ] }, - { - "name": "upgrade-pip", - "buildsystem": "simple", - "build-commands": [ - "python3 setup.py install --prefix=/app" - ], - "sources": [ - { - "type": "archive", - "url": "https://files.pythonhosted.org/packages/ae/e8/2340d46ecadb1692a1e455f13f75e596d4eab3d11a57446f08259dee8f02/pip-10.0.1.tar.gz", - "sha256": "f2bd08e0cd1b06e10218feaf6fef299f473ba706582eb3bd9d52203fdbd7ee68" - } - ] - }, { "name": "Jack", "buildsystem": "simple", @@ -127,55 +113,31 @@ "sources": [ { "type": "archive", - "url": "https://github.com/google/protobuf/releases/download/v3.1.0/protobuf-cpp-3.1.0.tar.gz", - "sha1": "b7b7275405ac18784965b02bea7d62f836873564" + "url": "https://github.com/protocolbuffers/protobuf/releases/download/v3.6.1/protobuf-cpp-3.6.1.tar.gz", + "sha256": "b3732e471a9bb7950f090fd0457ebd2536a9ba0891b7f3785919c654fe2a2529" } ] }, { - "name": "sip", + "name": "python-google-protobuf", "buildsystem": "simple", "build-commands": [ - "pip3 install --prefix=${FLATPAK_DEST} sip-4.19.8-cp36-cp36m-manylinux1_x86_64.whl" + "pip3 install --no-deps --prefix=${FLATPAK_DEST} six-1.11.0-py2.py3-none-any.whl", + "pip3 install --no-deps --prefix=${FLATPAK_DEST} protobuf-3.6.1-cp37-cp37m-manylinux1_x86_64.whl" ], "sources": [ { - "type": "file", - "url": "https://files.pythonhosted.org/packages/8a/ea/d317ce5696dda4df7c156cd60447cda22833b38106c98250eae1451f03ec/sip-4.19.8-cp36-cp36m-manylinux1_x86_64.whl", - "sha256": "cf98150a99e43fda7ae22abe655b6f202e491d6291486548daa56cb15a2fcf85" - } - ], - "cleanup": [ - "/bin", - "/include" - ] - }, - { - "name": "PyQt5", - "buildsystem": "simple", - "build-commands": [ - "pip3 install --prefix=${FLATPAK_DEST} PyQt5-5.10.1-5.10.1-cp35.cp36.cp37.cp38-abi3-manylinux1_x86_64.whl" - ], - "sources": [ + "type": "archive", + "url": "https://files.pythonhosted.org/packages/67/4b/141a581104b1f6397bfa78ac9d43d8ad29a7ca43ea90a2d863fe3056e86a/six-1.11.0-py2.py3-none-any.whl", + "sha256": "832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + }, { - "type": "file", - "url": "https://files.pythonhosted.org/packages/e4/15/4e2e49f64884edbab6f833c6fd3add24d7938f2429aec1f2883e645d4d8f/PyQt5-5.10.1-5.10.1-cp35.cp36.cp37.cp38-abi3-manylinux1_x86_64.whl", - "sha256": "1e652910bd1ffd23a3a48c510ecad23a57a853ed26b782cd54b16658e6f271ac" + "type": "archive", + "url": "https://files.pythonhosted.org/packages/3a/30/289ead101f94998d88e8961a3548aea29417ae0057be23972483cddebf4f/protobuf-3.6.1-cp37-cp37m-manylinux1_x86_64.whl", + "sha256": "4b92e235a3afd42e7493b281c8b80c0c65cbef45de30f43d571d1ee40a1f77ef" } ] }, - { - "name": "python-google-protobuf", - "buildsystem": "simple", - "build-options": { - "build-args": [ - "--share=network" - ] - }, - "build-commands": [ - "pip3 install --prefix=${FLATPAK_DEST} protobuf==3.1.0.post1" - ] - }, { "name": "ola", "build-options": { @@ -206,127 +168,11 @@ } ] }, - { - "name": "cython", - "buildsystem": "simple", - "build-options": { - "build-args": [ - "--share=network" - ] - }, - "build-commands": [ - "pip3 install --prefix=${FLATPAK_DEST} cython" - ] - }, - { - "name": "PyGobject", - "buildsystem": "simple", - "build-options": { - "build-args": [ - "--share=network" - ] - }, - "build-commands": [ - "pip3 install --prefix=${FLATPAK_DEST} PyGobject" - ] - }, - { - "name": "sortedcontainers", - "buildsystem": "simple", - "build-options": { - "build-args": [ - "--share=network" - ] - }, - "build-commands": [ - "pip3 install --prefix=${FLATPAK_DEST} sortedcontainers" - ] - }, - { - "name": "mido", - "buildsystem": "simple", - "build-options": { - "build-args": [ - "--share=network" - ] - }, - "build-commands": [ - "pip3 install --prefix=${FLATPAK_DEST} mido" - ], - "cleanup": [ - "/bin" - ] - }, - { - "name": "python-rtmidi", - "buildsystem": "simple", - "build-options": { - "build-args": [ - "--share=network" - ] - }, - "build-commands": [ - "pip3 install --prefix=${FLATPAK_DEST} python-rtmidi" - ] - }, - { - "name": "JACK-Client", - "buildsystem": "simple", - "build-options": { - "build-args": [ - "--share=network" - ] - }, - "build-commands": [ - "pip3 install --prefix=${FLATPAK_DEST} JACK-Client" - ] - }, - { - "name": "falcon", - "buildsystem": "simple", - "build-options": { - "build-args": [ - "--share=network" - ] - }, - "build-commands": [ - "pip3 install --prefix=${FLATPAK_DEST} falcon" - ] - }, - { - "name": "pyliblo", - "buildsystem": "simple", - "build-options": { - "build-args": [ - "--share=network" - ] - }, - "build-commands": [ - "pip3 install --prefix=${FLATPAK_DEST} pyliblo" - ] - }, - { - "name": "requests", - "buildsystem": "simple", - "build-options": { - "build-args": [ - "--share=network" - ] - }, - "build-commands": [ - "pip3 install --prefix=${FLATPAK_DEST} requests" - ] - }, { "name": "linux-show-player", "buildsystem": "simple", - "build-options": { - "build-args": [ - "--share=network" - ] - }, "build-commands": [ - "pip3 install --prefix=${FLATPAK_DEST} .", + "pip3 install --no-deps --prefix=${FLATPAK_DEST} .", "mkdir -p /app/share/applications/", "cp dist/linuxshowplayer.desktop /app/share/applications/", "mkdir -p /app/share/mime/packages/", diff --git a/dist/Flatpak/travis_flatpak.py b/dist/Flatpak/travis_flatpak.py index e75a1b616..09709ed67 100644 --- a/dist/Flatpak/travis_flatpak.py +++ b/dist/Flatpak/travis_flatpak.py @@ -1,52 +1,58 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2018 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -import os import json +import urllib.parse +import os + +import pipenv_flatpak DIR = os.path.dirname(__file__) -APP_ID = "com.github.FrancescoCeruti.LinuxShowPlayer" -MANIFEST = os.path.join(DIR, APP_ID + '.json') -BRANCH = os.environ['TRAVIS_BRANCH'] -COMMIT = os.environ['TRAVIS_COMMIT'] +BRANCH = os.environ["TRAVIS_BRANCH"] +COMMIT = os.environ["TRAVIS_COMMIT"] -LiSP_MODULE = 'linux-show-player' +APP_ID = "com.github.FrancescoCeruti.LinuxShowPlayer" +APP_MODULE = "linux-show-player" +TEMPLATE = os.path.join(DIR, "template.json") +DESTINATION = os.path.join(DIR, APP_ID + ".json") -# Load the manifest (as dictionary) -with open(MANIFEST, mode='r') as template: - manifest = json.load(template) +LOCKFILE = "../../Pipfile.lock" -# Patch top-Level attributes -manifest['branch'] = BRANCH -if BRANCH != 'master': - manifest['desktop-file-name-suffix'] = ' ({})'.format(BRANCH) -# Patch modules attributes -source = {} -for module in reversed(manifest['modules']): - if module['name'] == LiSP_MODULE: - source = module['sources'][0] - break +print(">>> Generating flatpak manifest ....\n") +with open(TEMPLATE, mode="r") as f: + manifest = json.load(f) -source['branch'] = BRANCH -source['commit'] = COMMIT +# Patch top-Level attributes +manifest["branch"] = BRANCH +if BRANCH != "master": + manifest["desktop-file-name-suffix"] = " ({})".format(BRANCH) + +py_version = None +app_index = None + +# Get python version "major.minor" and patch the app-module to use the correct +# branch +for index, module in enumerate(manifest["modules"]): + if module["name"] == "cpython": + path = urllib.parse.urlsplit(module["sources"][0]["url"]).path + file = os.path.basename(path) + py_version = file[2:5] + elif module["name"] == APP_MODULE: + module["sources"][0]["branch"] = BRANCH + module["sources"][0]["commit"] = COMMIT + app_index = index + +# Generate python-modules from Pipfile.lock, insert them before the app-module +for num, py_module in enumerate( + pipenv_flatpak.generate( + LOCKFILE, + py_version, + pipenv_flatpak.PLATFORMS_LINUX_x86_64, + pipenv_flatpak.PYPI_URL, + ) +): + manifest["modules"].insert((app_index - 1) + num, py_module) # Save the patched manifest -with open(MANIFEST, mode='w') as out: +with open(DESTINATION, mode="w") as out: json.dump(manifest, out, indent=4) + +print("\n>>> Done!") diff --git a/dist/travis_bintray.py b/dist/travis_bintray.py index 9aa7a7c31..44b59ec8a 100644 --- a/dist/travis_bintray.py +++ b/dist/travis_bintray.py @@ -1,22 +1,3 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2018 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - import os import datetime import json diff --git a/lisp/core/loading.py b/lisp/core/loading.py index a3d1581df..f4a9ade3d 100644 --- a/lisp/core/loading.py +++ b/lisp/core/loading.py @@ -18,16 +18,11 @@ # along with Linux Show Player. If not, see . import logging -import os.path +import os import re from lisp.ui.ui_utils import translate -try: - from os import scandir -except ImportError: - from scandir import scandir - logger = logging.getLogger(__name__) @@ -47,7 +42,7 @@ def __iter__(self): def load_modules(self): """Generate lists of tuples (class-name, class-object).""" - for entry in scandir(self.pkg_path): + for entry in os.scandir(self.pkg_path): # Exclude __init__, __pycache__ and likely if re.match("^__.*", entry.name): diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index c299a194d..a4ab7ebe3 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -343,7 +343,7 @@ def _context_invoked(self, event): self.show_context_menu(event.globalPos()) def _clone_cue(self, cue): - self._clone_cues((cue, )) + self._clone_cues((cue,)) def _clone_cues(self, cues): for pos, cue in enumerate(cues, cues[-1].index + 1): @@ -368,8 +368,8 @@ def __cue_next(self, cue): if next_index < len(self._list_model): action = CueNextAction(cue.next_action) if ( - action == CueNextAction.SelectAfterEnd - or action == CueNextAction.SelectAfterWait + action == CueNextAction.SelectAfterEnd + or action == CueNextAction.SelectAfterWait ): self.set_standby_index(next_index) else: diff --git a/lisp/plugins/presets/lib.py b/lisp/plugins/presets/lib.py index ce742b517..68698b8f8 100644 --- a/lisp/plugins/presets/lib.py +++ b/lisp/plugins/presets/lib.py @@ -25,11 +25,6 @@ from lisp.core.actions_handler import MainActionsHandler from lisp.cues.cue_actions import UpdateCueAction, UpdateCuesAction -try: - from os import scandir -except ImportError: - from scandir import scandir - PRESETS_DIR = os.path.join(USER_DIR, "presets") @@ -47,7 +42,7 @@ def scan_presets(): Every time this function is called a search in `PRESETS_DIR` is performed. """ - for entry in scandir(PRESETS_DIR): + for entry in os.scandir(PRESETS_DIR): if entry.is_file(): yield entry.name diff --git a/lisp/ui/icons/__init__.py b/lisp/ui/icons/__init__.py index 75b7c562f..0ee44cd26 100644 --- a/lisp/ui/icons/__init__.py +++ b/lisp/ui/icons/__init__.py @@ -18,12 +18,7 @@ # along with Linux Show Player. If not, see . import glob -from os import path - -try: - from os import scandir -except ImportError: - from scandir import scandir +import os from PyQt5.QtGui import QIcon @@ -31,7 +26,7 @@ def icon_themes_names(): - for entry in scandir(path.dirname(__file__)): + for entry in os.scandir(os.path.dirname(__file__)): if ( entry.is_dir() and entry.name != ICON_THEME_COMMON @@ -47,7 +42,7 @@ class IconTheme: _GlobalTheme = None def __init__(self, *names): - self._lookup_dirs = [path.join(ICON_THEMES_DIR, d) for d in names] + self._lookup_dirs = [os.path.join(ICON_THEMES_DIR, d) for d in names] def __iter__(self): yield from self._lookup_dirs diff --git a/setup.py b/setup.py index 1b3d4e9e9..1d2fcd438 100644 --- a/setup.py +++ b/setup.py @@ -30,18 +30,6 @@ def find_files(directory, regex=".*", rel=""): license=lisp.__license__, url=lisp.__email__, description="Cue player for live shows", - install_requires=[ - "PyQt5", - "PyGobject", - "sortedcontainers", - "mido", - "python-rtmidi", - "JACK-Client", - "pyliblo", - "falcon", - "requests", - 'scandir;python_version<"3.5"', - ], packages=find_packages(), package_data={ "": ["i18n/*.qm", "*.qss", "*.json"], From 530ce735bd0f04a2d4b4c2b15f6dbcc1e02db821 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 2 Nov 2018 15:38:59 +0100 Subject: [PATCH 139/333] Update travis.yml --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index ddc37d722..9e52cb9d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,10 @@ branches: before_install: - docker pull francescoceruti/flatpack +install: + # Required by travis_flatpak.py + - pip install html5lib + script: # Enter the flatpak buid directory - cd dist/Flatpak From d0db7c25aa46ff12dc2036dfbd4a922948979e30 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 2 Nov 2018 15:42:55 +0100 Subject: [PATCH 140/333] Fix travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9e52cb9d4..d18e7a07e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ before_install: install: # Required by travis_flatpak.py - - pip install html5lib + - pip3 install --user html5lib script: # Enter the flatpak buid directory From 285a5fe8fa4f43a4b3c751b512dbb9f9b9c6a8e2 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 2 Nov 2018 16:01:26 +0100 Subject: [PATCH 141/333] Fix travis.yml (again) --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d18e7a07e..ee5820082 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,8 @@ -language: generic sudo: required +language: python + +python: + - "3.7" services: - docker From 4dbb7978a19233e826062fbadf29d2df9a35e1ed Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 2 Nov 2018 16:04:51 +0100 Subject: [PATCH 142/333] maybe this time will work --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ee5820082..7d229ba70 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ sudo: required language: python python: - - "3.7" + - "3.6" services: - docker From 28b73f304e5be737541efc4d31da019894b77a55 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 2 Nov 2018 16:07:38 +0100 Subject: [PATCH 143/333] uff --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7d229ba70..fd9fa1e45 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ before_install: install: # Required by travis_flatpak.py - - pip3 install --user html5lib + - pip3 install html5lib script: # Enter the flatpak buid directory From 9d4bc55a4b7475079e275465e85d325f267e7005 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 2 Nov 2018 16:11:01 +0100 Subject: [PATCH 144/333] =?UTF-8?q?(=E2=95=AF=C2=B0=E2=96=A1=C2=B0?= =?UTF-8?q?=EF=BC=89=E2=95=AF=EF=B8=B5=20=E2=94=BB=E2=94=81=E2=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fd9fa1e45..2aece95c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ before_install: install: # Required by travis_flatpak.py - - pip3 install html5lib + - pip3 install requests html5lib script: # Enter the flatpak buid directory From 5a25d6229123cee2773a017fadc4e7d57b597953 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 2 Nov 2018 16:20:51 +0100 Subject: [PATCH 145/333] Fixes for flatpak build --- dist/Flatpak/pipenv_flatpak.py | 5 ++--- dist/Flatpak/template.json | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/dist/Flatpak/pipenv_flatpak.py b/dist/Flatpak/pipenv_flatpak.py index e5bc66b33..8834e8152 100644 --- a/dist/Flatpak/pipenv_flatpak.py +++ b/dist/Flatpak/pipenv_flatpak.py @@ -14,7 +14,7 @@ ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS ) -PLATFORMS_LINUX_x86_64 = ("linux_x86_64", "manylinux1_x86_64") +PLATFORMS_LINUX_x86_64 = ("linux_x86_64", "manylinux1_x86_64", "any") PYPI_URL = "https://pypi.python.org/simple/" @@ -50,8 +50,7 @@ def _wheel_versions(py_version): return { "py" + py_version.replace(".", ""), "py" + py_version[0], - "cp" + py_version.replace(".", ""), - "any", + "cp" + py_version.replace(".", "") } diff --git a/dist/Flatpak/template.json b/dist/Flatpak/template.json index b6bf211a9..c8e93c31a 100644 --- a/dist/Flatpak/template.json +++ b/dist/Flatpak/template.json @@ -32,7 +32,7 @@ { "type": "archive", "url": "https://dl.bintray.com/francescoceruti/flatpak-builds/py3.7.1-freedesktop1.6.tar.gz", - "sha256": "d55bea2a7e62f90df90c2a8069d5efbd7a1bb6698b0293db5947f874eaa243be" + "sha256": "d9eb119c421fee85380ef90b9d1551b564740b01d705425b79d31a71b0587531" } ] }, From eda34dbda79b0014ce3757a149ad9f40a8bb815e Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 2 Nov 2018 16:45:31 +0100 Subject: [PATCH 146/333] Fixes for flatpak build --- dist/Flatpak/template.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/Flatpak/template.json b/dist/Flatpak/template.json index c8e93c31a..d6e324e99 100644 --- a/dist/Flatpak/template.json +++ b/dist/Flatpak/template.json @@ -127,12 +127,12 @@ ], "sources": [ { - "type": "archive", + "type": "file", "url": "https://files.pythonhosted.org/packages/67/4b/141a581104b1f6397bfa78ac9d43d8ad29a7ca43ea90a2d863fe3056e86a/six-1.11.0-py2.py3-none-any.whl", "sha256": "832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" }, { - "type": "archive", + "type": "file", "url": "https://files.pythonhosted.org/packages/3a/30/289ead101f94998d88e8961a3548aea29417ae0057be23972483cddebf4f/protobuf-3.6.1-cp37-cp37m-manylinux1_x86_64.whl", "sha256": "4b92e235a3afd42e7493b281c8b80c0c65cbef45de30f43d571d1ee40a1f77ef" } From 60f9f50870c92c4a4771b0d35167a4a6bffb3072 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 2 Nov 2018 18:14:44 +0100 Subject: [PATCH 147/333] Fixes for flatpak build (revert protbuf to 3.1.0, update OLA) --- dist/Flatpak/README | 3 +++ dist/Flatpak/template.json | 17 ++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/dist/Flatpak/README b/dist/Flatpak/README index fdfcdf19c..5934b1eca 100644 --- a/dist/Flatpak/README +++ b/dist/Flatpak/README @@ -53,6 +53,9 @@ support for flatpak. ------ NOTES ------ +- until OLA 0.11 we must use protobuf < 3.2, or it will not build, + if needed we may patch it, for now it's fine + - pipenv_flatpak.py is quite generic, it could also be used in other places - The PyQt5 wheels from pypi are "all-inclusive" and quite big diff --git a/dist/Flatpak/template.json b/dist/Flatpak/template.json index d6e324e99..4578da158 100644 --- a/dist/Flatpak/template.json +++ b/dist/Flatpak/template.json @@ -113,8 +113,8 @@ "sources": [ { "type": "archive", - "url": "https://github.com/protocolbuffers/protobuf/releases/download/v3.6.1/protobuf-cpp-3.6.1.tar.gz", - "sha256": "b3732e471a9bb7950f090fd0457ebd2536a9ba0891b7f3785919c654fe2a2529" + "url": "https://github.com/protocolbuffers/protobuf/releases/download/v3.1.0/protobuf-cpp-3.1.0.tar.gz", + "sha256": "51ceea9957c875bdedeb1f64396b5b0f3864fe830eed6a2d9c066448373ea2d6" } ] }, @@ -123,7 +123,7 @@ "buildsystem": "simple", "build-commands": [ "pip3 install --no-deps --prefix=${FLATPAK_DEST} six-1.11.0-py2.py3-none-any.whl", - "pip3 install --no-deps --prefix=${FLATPAK_DEST} protobuf-3.6.1-cp37-cp37m-manylinux1_x86_64.whl" + "pip3 install --no-deps --prefix=${FLATPAK_DEST} protobuf-3.1.0.post1-py2.py3-none-any.whl" ], "sources": [ { @@ -133,8 +133,8 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/3a/30/289ead101f94998d88e8961a3548aea29417ae0057be23972483cddebf4f/protobuf-3.6.1-cp37-cp37m-manylinux1_x86_64.whl", - "sha256": "4b92e235a3afd42e7493b281c8b80c0c65cbef45de30f43d571d1ee40a1f77ef" + "url": "https://files.pythonhosted.org/packages/a5/bb/11821bdc46cb9aad8e18618715e5e93eef44abb642ed862c4b080c474183/protobuf-3.1.0.post1-py2.py3-none-any.whl", + "sha256": "42315e73409eaefdcc11e216695ff21f87dc483ad0595c57999baddf7f841180" } ] }, @@ -154,10 +154,9 @@ ], "sources": [ { - "type": "git", - "url": "https://github.com/OpenLightingProject/ola.git", - "tag": "0.10.6", - "commit": "6e57342c414a72cdd721e8df5bc7967e17459647" + "type": "archive", + "url": "https://github.com/OpenLightingProject/ola/releases/download/0.10.7/ola-0.10.7.tar.gz", + "sha256": "8a65242d95e0622a3553df498e0db323a13e99eeb1accc63a8a2ca8913ab31a0" }, { "type": "script", From bcfc55e147109bd116267cd0a08731270f1ab24e Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 2 Nov 2018 20:33:22 +0100 Subject: [PATCH 148/333] =?UTF-8?q?(=E2=95=AF=C2=B0=E2=96=A1=C2=B0?= =?UTF-8?q?=EF=BC=89=E2=95=AF=EF=B8=B5=20=E2=94=BB=E2=94=81=E2=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/Flatpak/template.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dist/Flatpak/template.json b/dist/Flatpak/template.json index 4578da158..00047fd83 100644 --- a/dist/Flatpak/template.json +++ b/dist/Flatpak/template.json @@ -170,6 +170,11 @@ { "name": "linux-show-player", "buildsystem": "simple", + "build-options": { + "build-args": [ + "--share=network" + ] + }, "build-commands": [ "pip3 install --no-deps --prefix=${FLATPAK_DEST} .", "mkdir -p /app/share/applications/", From 510d0796ad12bab04ff0d69850cf8578c9df347a Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 27 Dec 2018 14:58:32 +0100 Subject: [PATCH 149/333] Created separate widget for midi messages selection, minor updates Update: some improvement to setup.py Update: dependencies versions --- .gitignore | 1 - .travis.yml | 2 +- Pipfile | 25 ++-- Pipfile.lock | 136 ++++++++----------- dist/Flatpak/README | 6 +- dist/Flatpak/pipenv_flatpak.py | 11 +- lisp/__init__.py | 2 +- lisp/plugins/action_cues/midi_cue.py | 123 ++--------------- lisp/plugins/midi/widgets.py | 190 +++++++++++++++++++++++++++ setup.py | 40 +++--- 10 files changed, 301 insertions(+), 235 deletions(-) create mode 100644 lisp/plugins/midi/widgets.py diff --git a/.gitignore b/.gitignore index 5ca7f7e99..7ad858049 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,3 @@ eggs/ # QT project files, generated only for pylupdate (qt i18n) *.pro -/doc/ diff --git a/.travis.yml b/.travis.yml index 2aece95c5..2dc7acdbf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ before_install: install: # Required by travis_flatpak.py - - pip3 install requests html5lib + - travis_retry pip3 install requests html5lib script: # Enter the flatpak buid directory diff --git a/Pipfile b/Pipfile index c61f92776..879b0e4fd 100644 --- a/Pipfile +++ b/Pipfile @@ -4,21 +4,16 @@ verify_ssl = true name = "pypi" [packages] -Cython = "==0.29.*" -falcon = "==1.4.*" -JACK-Client = "==0.4.*" -mido = "==1.2.*" -PyGObject = "==3.30.*" -pyliblo = "==0.10.*" -PyQt5 = "==5.11.*" -python-rtmidi = "==1.1.*" -requests = "==2.20.*" -sortedcontainers = "==2.0.*" +Cython = "~=0.29" +falcon = "~=1.4" +JACK-Client = "~=0.4" +mido = "~=1.2" +PyGObject = "~=3.30" +pyliblo = "~=0.10" +PyQt5 = "~=5.6" +python-rtmidi = "~=1.1" +requests = "~=2.20" +sortedcontainers = "~=2.0" [dev-packages] -objgraph = "*" -pycallgraph = "*" html5lib = "*" - -[requires] -python_version = ">= 3.5" diff --git a/Pipfile.lock b/Pipfile.lock index beb3ffdf6..7eebfe50a 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,12 +1,10 @@ { "_meta": { "hash": { - "sha256": "dedb111fc520e13dd00a16ff9f2a4c7ea81295e94b1e17fc83efe726ea5b072d" + "sha256": "4cf40d7ac39723118c6c58be7041619cbb24d2799c3714661e534fc1fb88880c" }, "pipfile-spec": 6, - "requires": { - "python_version": ">= 3.5" - }, + "requires": {}, "sources": [ { "name": "pypi", @@ -18,10 +16,10 @@ "default": { "certifi": { "hashes": [ - "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", - "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" + "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", + "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" ], - "version": "==2018.10.15" + "version": "==2018.11.29" }, "cffi": { "hashes": [ @@ -69,37 +67,37 @@ }, "cython": { "hashes": [ - "sha256:019008a69e6b7c102f2ed3d733a288d1784363802b437dd2b91e6256b12746da", - "sha256:1441fe19c56c90b8c2159d7b861c31a134d543ef7886fd82a5d267f9f11f35ac", - "sha256:1d1a5e9d6ed415e75a676b72200ad67082242ec4d2d76eb7446da255ae72d3f7", - "sha256:339f5b985de3662b1d6c69991ab46fdbdc736feb4ac903ef6b8c00e14d87f4d8", - "sha256:35bdf3f48535891fee2eaade70e91d5b2cc1ee9fc2a551847c7ec18bce55a92c", - "sha256:3d0afba0aec878639608f013045697fb0969ff60b3aea2daec771ea8d01ad112", - "sha256:42c53786806e24569571a7a24ebe78ec6b364fe53e79a3f27eddd573cacd398f", - "sha256:48b919da89614d201e72fbd8247b5ae8881e296cf968feb5595a015a14c67f1f", - "sha256:49906e008eeb91912654a36c200566392bd448b87a529086694053a280f8af2d", - "sha256:49fc01a7c9c4e3c1784e9a15d162c2cac3990fcc28728227a6f8f0837aabda7c", - "sha256:501b671b639b9ca17ad303f8807deb1d0ff754d1dab106f2607d14b53cb0ff0b", - "sha256:5574574142364804423ab4428bd331a05c65f7ecfd31ac97c936f0c720fe6a53", - "sha256:6092239a772b3c6604be9e94b9ab4f0dacb7452e8ad299fd97eae0611355b679", - "sha256:71ff5c7632501c4f60edb8a24fd0a772e04c5bdca2856d978d04271b63666ef7", - "sha256:7dcf2ad14e25b05eda8bdd104f8c03a642a384aeefd25a5b51deac0826e646fa", - "sha256:8ca3a99f5a7443a6a8f83a5d8fcc11854b44e6907e92ba8640d8a8f7b9085e21", - "sha256:927da3b5710fb705aab173ad630b45a4a04c78e63dcd89411a065b2fe60e4770", - "sha256:94916d1ede67682638d3cc0feb10648ff14dc51fb7a7f147f4fedce78eaaea97", - "sha256:a3e5e5ca325527d312cdb12a4dab8b0459c458cad1c738c6f019d0d8d147081c", - "sha256:a7716a98f0b9b8f61ddb2bae7997daf546ac8fc594be6ba397f4bde7d76bfc62", - "sha256:acf10d1054de92af8d5bfc6620bb79b85f04c98214b4da7db77525bfa9fc2a89", - "sha256:de46ffb67e723975f5acab101c5235747af1e84fbbc89bf3533e2ea93fb26947", - "sha256:df428969154a9a4cd9748c7e6efd18432111fbea3d700f7376046c38c5e27081", - "sha256:f5ebf24b599caf466f9da8c4115398d663b2567b89e92f58a835e9da4f74669f", - "sha256:f79e45d5c122c4fb1fd54029bf1d475cecc05f4ed5b68136b0d6ec268bae68b6", - "sha256:f7a43097d143bd7846ffba6d2d8cd1cc97f233318dbd0f50a235ea01297a096b", - "sha256:fceb8271bc2fd3477094ca157c824e8ea840a7b393e89e766eea9a3b9ce7e0c6", - "sha256:ff919ceb40259f5332db43803aa6c22ff487e86036ce3921ae04b9185efc99a4" + "sha256:004c181b75f926f48dc0570372ca2cfb06a1b3210cb647185977ce9fde98b66e", + "sha256:085d596c016130f5b1e2fe72446e3e63bfcf67535e7ff6772eaa05c5d2ad6fd5", + "sha256:1014758344717844a05882c92ebd76d8fab15b0a8e9b42b378a99a6c5299ab3b", + "sha256:12c007d3704ca9840734748fd6c052960be67562ff15609c3b85d1ca638289d2", + "sha256:1a20f575197e814453f2814829715fcb21436075e298d883a34c7ffe4d567a1d", + "sha256:1b6f201228368ec9b307261b46512f3605f84d4994bb6eb78cdab71455810424", + "sha256:2ac187ff998a95abb7fae452b5178f91e1a713698c9ced89836c94e6b1d3f41e", + "sha256:3585fbe18d8666d91ecb3b3366ca6e9ea49001cd0a7c38a226cececb7852aa0d", + "sha256:3669dfe4117ee8825a48cf527cb4ac15a39a0142fcb72ecedfd75fe6545b2cda", + "sha256:382c1e0f8f8be36e9057264677fd60b669a41c5810113694cbbb4060ee0cefc0", + "sha256:44bb606d8c60d8acaa7f72b3bbc2ebd9816785124c58d33c057ca326f1185dae", + "sha256:6f1a5344ff1f0f44023c41d4b0e52215b490658b42e017520cb89a56250ecbca", + "sha256:7a29f9d780ac497dcd76ce814a9d170575bedddeb89ecc25fe738abef4c87172", + "sha256:8022a5b83ef442584f2efd941fe8678de1c67e28bf81e6671b20627ec8a79387", + "sha256:998af90092cd1231990eb878e2c71ed92716d6a758aa03a2e6673e077a7dd072", + "sha256:9e60b83afe8914ab6607f7150fd282d1cb0531a45cf98d2a40159f976ae4cd7a", + "sha256:a6581d3dda15adea19ac70b89211aadbf21f45c7f3ee3bc8e1536e5437c9faf9", + "sha256:af515924b8aebb729f631590eb43300ce948fa67d3885fdae9238717c0a68821", + "sha256:b49ea3bb357bc34eaa7b461633ce7c2d79186fe681297115ff9e2b8f5ceba2fd", + "sha256:bc524cc603f0aa23af00111ddd1aa0aad12d629f5a9a5207f425a1af66393094", + "sha256:ca7daccffb14896767b20d69bfc8de9e41e9589b9377110292c3af8460ef9c2b", + "sha256:cdfb68eb11c6c4e90e34cf54ffd678a7813782fae980d648db6185e6b0c8a0ba", + "sha256:d21fb6e7a3831f1f8346275839d46ed1eb2abd350fc81bad2fdf208cc9e4f998", + "sha256:e17104c6871e7c0eee4de12717892a1083bd3b8b1da0ec103fa464b1c6c80964", + "sha256:e7f71b489959d478cff72d8dcab1531efd43299341e3f8b85ab980beec452ded", + "sha256:e8420326e4b40bcbb06f070efb218ca2ca21827891b7c69d4cc4802b3ce1afc9", + "sha256:eec1b890cf5c16cb656a7062769ac276c0fccf898ce215ff8ef75eac740063f7", + "sha256:f04e21ba7c117b20f57b0af2d4c8ed760495e5bb3f21b0352dbcfe5d2221678b" ], "index": "pypi", - "version": "==0.29" + "version": "==0.29.2" }, "falcon": { "hashes": [ @@ -111,10 +109,10 @@ }, "idna": { "hashes": [ - "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", - "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" ], - "version": "==2.7" + "version": "==2.8" }, "jack-client": { "hashes": [ @@ -134,9 +132,9 @@ }, "pycairo": { "hashes": [ - "sha256:0f0a35ec923d87bc495f6753b1e540fd046d95db56a35250c44089fbce03b698" + "sha256:abd42a4c9c2069febb4c38fe74bfc4b4a9d3a89fea3bc2e4ba7baff7a20f783f" ], - "version": "==1.17.1" + "version": "==1.18.0" }, "pycparser": { "hashes": [ @@ -146,10 +144,10 @@ }, "pygobject": { "hashes": [ - "sha256:5e642a76cfddd3e488a32bcdf7cef189e29226522be8726bc0e050dd53fa2d1c" + "sha256:2d4423cbf169d50a3165f2dde21478a00215c7fb3f68d9d236af588c670980bb" ], "index": "pypi", - "version": "==3.30.1" + "version": "==3.30.4" }, "pyliblo": { "hashes": [ @@ -212,43 +210,36 @@ }, "requests": { "hashes": [ - "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c", - "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279" + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" ], "index": "pypi", - "version": "==2.20.0" + "version": "==2.21.0" }, "six": { "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" ], - "version": "==1.11.0" + "version": "==1.12.0" }, "sortedcontainers": { "hashes": [ - "sha256:220bb2e3e1886297fd7cdd6d164cb5cf237be1cfae1a3a3e526d149c52816682", - "sha256:b74f2756fb5e23512572cc76f0fe0832fd86310f77dfee54335a35fb33f6b950" + "sha256:974e9a32f56b17c1bac2aebd9dcf197f3eb9cd30553c5852a3187ad162e1a03a", + "sha256:d9e96492dd51fae31e60837736b38fe42a187b5404c16606ff7ee7cd582d4c60" ], "index": "pypi", - "version": "==2.0.5" + "version": "==2.1.0" }, "urllib3": { "hashes": [ - "sha256:41c3db2fc01e5b907288010dec72f9d0a74e37d6994e6eb56849f59fea2265ae", - "sha256:8819bba37a02d143296a4d032373c4dd4aca11f6d4c9973335ca75f9c8475f59" + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" ], - "version": "==1.24" + "version": "==1.24.1" } }, "develop": { - "graphviz": { - "hashes": [ - "sha256:0e1744a45b0d707bc44f99c7b8e5f25dc22cf96b6aaf2432ac308ed9822a9cb6", - "sha256:d311be4fddfe832a56986ac5e1d6e8715d7fcb0208560da79d1bb0f72abef41f" - ], - "version": "==0.10.1" - }, "html5lib": { "hashes": [ "sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", @@ -257,27 +248,12 @@ "index": "pypi", "version": "==1.0.1" }, - "objgraph": { - "hashes": [ - "sha256:4a0c2c6268e10a9e8176ae054ff3faac9a432087801e1f95c3ebbe52550295a0", - "sha256:901dac7a4d73dd6c0e9697cf32676687f4e51d21b343543f795ec10461eb200e" - ], - "index": "pypi", - "version": "==3.4.0" - }, - "pycallgraph": { - "hashes": [ - "sha256:b1262b0f9831da889c6e9a9a82a22267df497013cd2f5b36c39359a607c91e71" - ], - "index": "pypi", - "version": "==1.0.1" - }, "six": { "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" ], - "version": "==1.11.0" + "version": "==1.12.0" }, "webencodings": { "hashes": [ diff --git a/dist/Flatpak/README b/dist/Flatpak/README index 5934b1eca..dfcb7274e 100644 --- a/dist/Flatpak/README +++ b/dist/Flatpak/README @@ -38,12 +38,12 @@ support for flatpak. - the python "protobuf" package is specified directly in the template, this for a few reasons: - - It's needed to "build" the OLA python package, which, can only be build by + - It's needed to "build" the OLA python package, which, can only be build with all the OLA framework (--enable-python-libs) - It's not specified in the Pipfile, for the reason above, without a complete - OLA installation you cannot have the python package, so we don't need it + OLA installation you cannot have the python package -- protobuf requires "six" and "setuptools", we install the first, but we +- protobuf requires "six" and "setuptools", we install the only first, we consider the second already shipped with the cpython packages - for python we favor pre-build "wheel" releases, which are faster to install diff --git a/dist/Flatpak/pipenv_flatpak.py b/dist/Flatpak/pipenv_flatpak.py index 8834e8152..00073248a 100644 --- a/dist/Flatpak/pipenv_flatpak.py +++ b/dist/Flatpak/pipenv_flatpak.py @@ -28,10 +28,7 @@ def _flatpak_module_template(): def _is_like_archive(filename): - """Return whether the filename looks like an archive. - - From pip project (https://github.com/pypa/pip) - """ + """Return whether the filename looks like an archive.""" for ext in ARCHIVE_EXTENSIONS: if filename.endswith(ext): return True, ext @@ -50,12 +47,12 @@ def _wheel_versions(py_version): return { "py" + py_version.replace(".", ""), "py" + py_version[0], - "cp" + py_version.replace(".", "") + "cp" + py_version.replace(".", ""), } def _wheel_tags(filename): - """Get wheel tags form the filename, see pep-0425 + """Get wheel tags from the filename, see pep-0425 Returns: A tuple (version, set(python-tags), platform-tag) @@ -148,7 +145,7 @@ def fetch(package): for link in html.findall(".//a"): package["candidates"].append((link.text, link.attrib["href"])) - with ThreadPoolExecutor(max_workers=5) as executor: + with ThreadPoolExecutor(max_workers=10) as executor: wait(tuple(executor.submit(fetch, package) for package in packages)) session.close() diff --git a/lisp/__init__.py b/lisp/__init__.py index 4410f9bf7..bedf4cb32 100644 --- a/lisp/__init__.py +++ b/lisp/__init__.py @@ -23,7 +23,7 @@ __email__ = "ceppofrancy@gmail.com" __url__ = "https://github.com/FrancescoCeruti/linux-show-player" __license__ = "GPLv3" -__version__ = "0.6dev" +__version__ = "0.6.0.dev0" # Application wide "constants" APP_DIR = path.dirname(__file__) diff --git a/lisp/plugins/action_cues/midi_cue.py b/lisp/plugins/action_cues/midi_cue.py index f1cd0c805..0ed4a7636 100644 --- a/lisp/plugins/action_cues/midi_cue.py +++ b/lisp/plugins/action_cues/midi_cue.py @@ -18,22 +18,14 @@ # along with Linux Show Player. If not, see . import logging - -from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import ( - QGroupBox, - QVBoxLayout, - QGridLayout, - QLabel, - QComboBox, - QSpinBox, - QFrame, -) +from PyQt5.QtCore import QT_TRANSLATE_NOOP +from PyQt5.QtWidgets import QVBoxLayout from lisp.core.properties import Property from lisp.cues.cue import Cue from lisp.plugins import get_plugin from lisp.plugins.midi.midi_utils import str_msg_to_dict, dict_msg_to_str +from lisp.plugins.midi.widgets import MIDIMessageEdit from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate @@ -61,114 +53,21 @@ def __start__(self, fade=False): class MidiCueSettings(SettingsPage): Name = QT_TRANSLATE_NOOP("SettingsPageName", "MIDI Settings") - MSGS_ATTRIBUTES = { - "note_on": ["channel", "note", "velocity"], - "note_off": ["channel", "note", "velocity"], - "control_change": ["channel", "control", "value"], - "program_change": ["channel", "program", None], - "polytouch": ["channel", "note", "value"], - "pitchwheel": ["channel", "pitch", None], - "song_select": ["song", None, None], - "songpos": ["pos", None, None], - "start": [None] * 3, - "stop": [None] * 3, - "continue": [None] * 3, - } - - ATTRIBUTES_RANGE = { - "channel": (1, 16, -1), - "note": (0, 127, 0), - "velocity": (0, 127, 0), - "control": (0, 127, 0), - "program": (0, 127, 0), - "value": (0, 127, 0), - "song": (0, 127, 0), - "pitch": (-8192, 8191, 0), - "pos": (0, 16383, 0), - } - def __init__(self, **kwargs): super().__init__(**kwargs) self.setLayout(QVBoxLayout()) - self.layout().setAlignment(Qt.AlignTop) - - self.msgGroup = QGroupBox(self) - self.msgGroup.setLayout(QGridLayout()) - self.layout().addWidget(self.msgGroup) - - # Message type - self.msgTypeLabel = QLabel(self.msgGroup) - self.msgGroup.layout().addWidget(self.msgTypeLabel, 0, 0) - self.msgTypeCombo = QComboBox(self.msgGroup) - self.msgTypeCombo.addItems(sorted(self.MSGS_ATTRIBUTES.keys())) - self.msgTypeCombo.currentTextChanged.connect(self.__type_changed) - self.msgGroup.layout().addWidget(self.msgTypeCombo, 0, 1) - - line = QFrame(self.msgGroup) - line.setFrameShape(QFrame.HLine) - line.setFrameShadow(QFrame.Sunken) - self.msgGroup.layout().addWidget(line, 1, 0, 1, 2) - - # Data widgets - self._data_widgets = [] - for n in range(2, 5): - dataLabel = QLabel(self.msgGroup) - dataSpin = QSpinBox(self.msgGroup) - - self.msgGroup.layout().addWidget(dataLabel, n, 0) - self.msgGroup.layout().addWidget(dataSpin, n, 1) - - self._data_widgets.append((dataLabel, dataSpin)) - - self.__type_changed(self.msgTypeCombo.currentText()) - - self.retranslateUi() - - def retranslateUi(self): - self.msgGroup.setTitle(translate("MIDICue", "MIDI Message")) - self.msgTypeLabel.setText(translate("MIDICue", "Message type")) - - def __type_changed(self, msg_type): - for label, spin, attr_name in self.__attributes(msg_type): - if attr_name is None: - label.setEnabled(False) - label.setText("") - spin.setEnabled(False) - else: - label.setEnabled(True) - label.setText(attr_name.title()) - - spin.setEnabled(True) - spin.setRange( - *self.ATTRIBUTES_RANGE.get(attr_name, (0, 0, 0))[0:2] - ) - - def getSettings(self): - msg_type = self.msgTypeCombo.currentText() - msg_dict = {"type": msg_type} - - for label, spin, attr_name in self.__attributes(msg_type): - if spin.isEnabled(): - offset = self.ATTRIBUTES_RANGE.get(attr_name, (0, 0, 0))[2] - msg_dict[attr_name] = spin.value() + offset + self.layout().setContentsMargins(0, 0, 0, 0) - return {"message": dict_msg_to_str(msg_dict)} + self.midiEdit = MIDIMessageEdit(parent=self) + self.layout().addWidget(self.midiEdit) - def __attributes(self, msg_type): - for (label, spin), attr in zip( - self._data_widgets, self.MSGS_ATTRIBUTES[msg_type] - ): - yield label, spin, attr + def getSettings(self): + return {"message": dict_msg_to_str(self.midiEdit.getMessageDict())} def loadSettings(self, settings): - str_msg = settings.get("message", "") - if str_msg: - dict_msg = str_msg_to_dict(str_msg) - self.msgTypeCombo.setCurrentText(dict_msg["type"]) - - for label, spin, attr_name in self.__attributes(dict_msg["type"]): - offset = self.ATTRIBUTES_RANGE.get(attr_name, (0, 0, 0))[2] - spin.setValue(dict_msg.get(label.text().lower(), 0) - offset) + message = settings.get("message", "") + if message: + self.midiEdit.setMessageDict(str_msg_to_dict(message)) CueSettingsRegistry().add(MidiCueSettings, MidiCue) diff --git a/lisp/plugins/midi/widgets.py b/lisp/plugins/midi/widgets.py new file mode 100644 index 000000000..f0bb6bef6 --- /dev/null +++ b/lisp/plugins/midi/widgets.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP +from PyQt5.QtWidgets import ( + QGroupBox, + QVBoxLayout, + QGridLayout, + QLabel, + QComboBox, + QSpinBox, + QFrame, + QWidget, +) + +from lisp.ui.ui_utils import translate + + +class MIDIMessageEdit(QWidget): + """ + To reference naming and values see: + https://github.com/mido/mido/blob/df6d05a6abcf6139ca31715dd3ed5450b2d98e96/mido/messages/specs.py + https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message + """ + + MESSAGES = { + "note_on": ["channel", "note", "velocity"], + "note_off": ["channel", "note", "velocity"], + "polytouch": ["channel", "note", "value"], + "control_change": ["channel", "control", "value"], + "program_change": ["channel", "program", None], + "aftertouch": ["channel", "value", None], + "pitchwheel": ["channel", "pitch", None], + "song_select": ["song", None, None], + "songpos": ["pos", None, None], + "start": [None] * 3, + "stop": [None] * 3, + "continue": [None] * 3, + } + + ATTRIBUTES = { + "channel": (1, 16, -1), + "note": (0, 127, 0), + "velocity": (0, 127, 0), + "control": (0, 127, 0), + "program": (0, 127, 0), + "value": (0, 127, 0), + "song": (0, 127, 0), + "pitch": (-8192, 8191, 0), + "pos": (0, 16383, 0), + } + + MESSAGES_UI = { + "note_on": QT_TRANSLATE_NOOP("MIDIMessageType", "Note ON"), + "note_off": QT_TRANSLATE_NOOP("MIDIMessageType", "Note OFF"), + "polytouch": QT_TRANSLATE_NOOP( + "MIDIMessageType", "Polyphonic After-touch" + ), + "control_change": QT_TRANSLATE_NOOP( + "MIDIMessageType", "Control/Mode Change" + ), + "program_change": QT_TRANSLATE_NOOP( + "MIDIMessageType", "Program Change" + ), + "aftertouch": QT_TRANSLATE_NOOP( + "MIDIMessageType", "Channel After-touch" + ), + "pitchwheel": QT_TRANSLATE_NOOP("MIDIMessageType", "Pitch Bend Change"), + "song_select": QT_TRANSLATE_NOOP("MIDIMessageType", "Song Select"), + "songpos": QT_TRANSLATE_NOOP("MIDIMessageType", "Song Position"), + "start": QT_TRANSLATE_NOOP("MIDIMessageType", "Start"), + "stop": QT_TRANSLATE_NOOP("MIDIMessageType", "Stop"), + "continue": QT_TRANSLATE_NOOP("MIDIMessageType", "Continue"), + } + + ATTRIBUTES_UI = { + "channel": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Channel"), + "note": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Note"), + "velocity": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Velocity"), + "control": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Control"), + "program": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Program"), + "value": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Value"), + "song": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Song"), + "pitch": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Pitch"), + "pos": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Position"), + } + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.setLayout(QVBoxLayout()) + self.layout().setAlignment(Qt.AlignTop) + + self.msgGroup = QGroupBox(self) + self.msgGroup.setLayout(QGridLayout()) + self.layout().addWidget(self.msgGroup) + + # Message type + self.msgTypeLabel = QLabel(self.msgGroup) + self.msgGroup.layout().addWidget(self.msgTypeLabel, 0, 0) + self.msgTypeCombo = QComboBox(self.msgGroup) + for msgType in MIDIMessageEdit.MESSAGES.keys(): + self.msgTypeCombo.addItem( + translate( + "MIDIMessageType", MIDIMessageEdit.MESSAGES_UI[msgType] + ), + msgType, + ) + self.msgTypeCombo.currentIndexChanged.connect(self._typeChanged) + self.msgGroup.layout().addWidget(self.msgTypeCombo, 0, 1) + + line = QFrame(self.msgGroup) + line.setFrameShape(QFrame.HLine) + line.setFrameShadow(QFrame.Sunken) + self.msgGroup.layout().addWidget(line, 1, 0, 1, 2) + + # Data widgets + self._dataWidgets = [] + for n in range(2, 5): + dataLabel = QLabel(self.msgGroup) + dataSpin = QSpinBox(self.msgGroup) + + self.msgGroup.layout().addWidget(dataLabel, n, 0) + self.msgGroup.layout().addWidget(dataSpin, n, 1) + + self._dataWidgets.append((dataSpin, dataLabel)) + + self._typeChanged() + self.retranslateUi() + + def retranslateUi(self): + self.msgGroup.setTitle(translate("MIDICue", "MIDI Message")) + self.msgTypeLabel.setText(translate("MIDICue", "Message type")) + + def getMessageDict(self): + msgType = self.msgTypeCombo.currentData() + msgDict = {"type": msgType} + + for attr, spin, label in self._currentValues(msgType): + if spin.isEnabled(): + offset = MIDIMessageEdit.ATTRIBUTES[attr][2] + msgDict[attr] = spin.value() + offset + + return msgDict + + def setMessageDict(self, dictMsg): + self.msgTypeCombo.setCurrentIndex( + self.msgTypeCombo.findData(dictMsg["type"]) + ) + + for attr, spin, label in self._currentValues(dictMsg["type"]): + min_, _, offset = MIDIMessageEdit.ATTRIBUTES.get(attr, (0, 0, 0)) + spin.setValue(dictMsg.get(attr, min_) - offset) + + def _currentValues(self, msgType): + for attr, (spin, label) in zip( + MIDIMessageEdit.MESSAGES[msgType], self._dataWidgets + ): + yield attr, spin, label + + def _typeChanged(self): + msgType = self.msgTypeCombo.currentData() + for attr, spin, label in self._currentValues(msgType): + if attr is None: + label.setEnabled(False) + label.setText("") + + spin.setEnabled(False) + else: + label.setEnabled(True) + label.setText(MIDIMessageEdit.ATTRIBUTES_UI[attr]) + + min_, max_, _ = MIDIMessageEdit.ATTRIBUTES[attr] + spin.setRange(min_, max_) + spin.setEnabled(True) diff --git a/setup.py b/setup.py index 1d2fcd438..4f8926eec 100644 --- a/setup.py +++ b/setup.py @@ -1,25 +1,33 @@ #!/usr/bin/env python3 import os -import re +from pathlib import Path from setuptools import find_packages, setup import lisp -def find_files(directory, regex=".*", rel=""): - """Search for files that match `regex` in `directory`.""" - paths = [] - for path, directories, file_names in os.walk(directory): - for filename in file_names: - file_path = os.path.join(path, filename) - if re.match(regex, file_path): - paths.append(file_path[len(rel) :]) - return paths +def long_description(): + readme = Path(__file__).parent / "README.md" + with open(readme, encoding="utf8") as readme_file: + return readme_file.read() -# List the directories with icons to be installed -lisp_icons = find_files("lisp/ui/icons", rel="lisp/ui/icons/") +def package_data_dirs(package_path): + """Fetch all "data" directories in the given package""" + dirs = [] + for dirpath, dirnames, filenames in os.walk(package_path): + # Exclude: empty, python-cache and python-modules + if ( + filenames + and "__pycache__" not in dirpath + and "__init__.py" not in filenames + ): + rel_dirpath = str(Path(dirpath).relative_to(package_path)) + dirs.append(rel_dirpath + "/*") + + return dirs + # Setup function setup( @@ -30,10 +38,12 @@ def find_files(directory, regex=".*", rel=""): license=lisp.__license__, url=lisp.__email__, description="Cue player for live shows", + long_description=long_description(), + long_description_content_type="text/markdown", packages=find_packages(), package_data={ - "": ["i18n/*.qm", "*.qss", "*.json"], - "lisp.ui.icons": lisp_icons, + "": ["i18n/qm/*", "*.qss", "*.json"], + "lisp.ui.icons": package_data_dirs("lisp/ui/icons"), }, - scripts=["linux-show-player"], + entry_points={"console_scripts": ["linux-show-player=lisp.main:main"]}, ) From 414ad56b222b06f88027ac59b27a6264187ee7ad Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 5 Jan 2019 10:55:29 +0100 Subject: [PATCH 150/333] Updated file headers Update: removed encoding string in .py files, not necessary Update: updated copyright notices Update: reorganization of the midi plugin package --- docs/user/source/conf.py | 66 ++++++---- i18n_update.py | 3 +- lisp/__init__.py | 4 +- lisp/application.py | 4 +- lisp/backend/__init__.py | 4 +- lisp/backend/audio_utils.py | 4 +- lisp/backend/backend.py | 4 +- lisp/backend/media.py | 4 +- lisp/backend/media_element.py | 4 +- lisp/core/action.py | 4 +- lisp/core/actions_handler.py | 4 +- lisp/core/class_based_registry.py | 4 +- lisp/core/clock.py | 4 +- lisp/core/configuration.py | 4 +- lisp/core/decorators.py | 4 +- lisp/core/dicttree.py | 4 +- lisp/core/fade_functions.py | 7 +- lisp/core/fader.py | 4 +- lisp/core/has_properties.py | 4 +- lisp/core/loading.py | 4 +- lisp/core/memento_model.py | 4 +- lisp/core/memento_model_actions.py | 4 +- lisp/core/model.py | 4 +- lisp/core/model_adapter.py | 4 +- lisp/core/plugin.py | 4 +- lisp/core/properties.py | 4 +- lisp/core/proxy_model.py | 4 +- lisp/core/qmeta.py | 4 +- lisp/core/rwait.py | 4 +- lisp/core/session.py | 4 +- lisp/core/signal.py | 4 +- lisp/core/singleton.py | 4 +- lisp/core/util.py | 4 +- lisp/cues/cue.py | 4 +- lisp/cues/cue_actions.py | 4 +- lisp/cues/cue_factory.py | 4 +- lisp/cues/cue_memento_model.py | 4 +- lisp/cues/cue_model.py | 4 +- lisp/cues/cue_time.py | 4 +- lisp/cues/media_cue.py | 4 +- lisp/layout/cue_layout.py | 4 +- lisp/layout/cue_menu.py | 4 +- lisp/main.py | 4 +- lisp/plugins/__init__.py | 4 +- lisp/plugins/action_cues/__init__.py | 4 +- lisp/plugins/action_cues/collection_cue.py | 4 +- lisp/plugins/action_cues/command_cue.py | 4 +- lisp/plugins/action_cues/index_action_cue.py | 4 +- lisp/plugins/action_cues/midi_cue.py | 4 +- lisp/plugins/action_cues/osc_cue.py | 6 +- lisp/plugins/action_cues/seek_cue.py | 4 +- lisp/plugins/action_cues/stop_all.py | 4 +- lisp/plugins/action_cues/volume_control.py | 4 +- lisp/plugins/cart_layout/cue_widget.py | 4 +- lisp/plugins/cart_layout/layout.py | 4 +- lisp/plugins/cart_layout/model.py | 4 +- lisp/plugins/cart_layout/page_widget.py | 4 +- lisp/plugins/cart_layout/settings.py | 4 +- lisp/plugins/cart_layout/tab_widget.py | 4 +- lisp/plugins/controller/common.py | 4 +- lisp/plugins/controller/controller.py | 4 +- .../plugins/controller/controller_settings.py | 4 +- lisp/plugins/controller/protocol.py | 4 +- lisp/plugins/controller/protocols/__init__.py | 4 +- lisp/plugins/controller/protocols/keyboard.py | 4 +- lisp/plugins/controller/protocols/midi.py | 4 +- lisp/plugins/controller/protocols/osc.py | 6 +- lisp/plugins/gst_backend/elements/__init__.py | 4 +- .../plugins/gst_backend/elements/alsa_sink.py | 4 +- .../gst_backend/elements/audio_dynamic.py | 4 +- .../plugins/gst_backend/elements/audio_pan.py | 4 +- .../plugins/gst_backend/elements/auto_sink.py | 4 +- lisp/plugins/gst_backend/elements/auto_src.py | 4 +- lisp/plugins/gst_backend/elements/db_meter.py | 4 +- .../gst_backend/elements/equalizer10.py | 4 +- .../plugins/gst_backend/elements/jack_sink.py | 4 +- lisp/plugins/gst_backend/elements/pitch.py | 4 +- .../gst_backend/elements/preset_src.py | 4 +- .../gst_backend/elements/pulse_sink.py | 4 +- lisp/plugins/gst_backend/elements/speed.py | 4 +- .../plugins/gst_backend/elements/uri_input.py | 4 +- .../gst_backend/elements/user_element.py | 4 +- lisp/plugins/gst_backend/elements/volume.py | 4 +- lisp/plugins/gst_backend/gst_backend.py | 4 +- lisp/plugins/gst_backend/gst_element.py | 4 +- lisp/plugins/gst_backend/gst_media.py | 4 +- lisp/plugins/gst_backend/gst_media_cue.py | 4 +- .../plugins/gst_backend/gst_media_settings.py | 4 +- lisp/plugins/gst_backend/gst_pipe_edit.py | 4 +- lisp/plugins/gst_backend/gst_settings.py | 4 +- lisp/plugins/gst_backend/gst_utils.py | 4 +- lisp/plugins/gst_backend/settings/__init__.py | 4 +- .../plugins/gst_backend/settings/alsa_sink.py | 4 +- .../gst_backend/settings/audio_dynamic.py | 4 +- .../plugins/gst_backend/settings/audio_pan.py | 4 +- lisp/plugins/gst_backend/settings/db_meter.py | 4 +- .../gst_backend/settings/equalizer10.py | 4 +- .../plugins/gst_backend/settings/jack_sink.py | 4 +- lisp/plugins/gst_backend/settings/pitch.py | 4 +- .../gst_backend/settings/preset_src.py | 4 +- lisp/plugins/gst_backend/settings/speed.py | 4 +- .../plugins/gst_backend/settings/uri_input.py | 4 +- .../gst_backend/settings/user_element.py | 4 +- lisp/plugins/gst_backend/settings/volume.py | 4 +- lisp/plugins/list_layout/control_buttons.py | 4 +- lisp/plugins/list_layout/info_panel.py | 4 +- lisp/plugins/list_layout/layout.py | 4 +- lisp/plugins/list_layout/list_view.py | 4 +- lisp/plugins/list_layout/list_widgets.py | 4 +- lisp/plugins/list_layout/models.py | 4 +- lisp/plugins/list_layout/playing_view.py | 4 +- lisp/plugins/list_layout/playing_widgets.py | 4 +- lisp/plugins/list_layout/settings.py | 4 +- lisp/plugins/list_layout/view.py | 4 +- lisp/plugins/media_info/media_info.py | 4 +- lisp/plugins/midi/midi.py | 7 +- lisp/plugins/midi/midi_input.py | 42 ------- .../midi/{midi_common.py => midi_io.py} | 46 ++++++- lisp/plugins/midi/midi_output.py | 37 ------ lisp/plugins/midi/midi_settings.py | 4 +- lisp/plugins/midi/midi_utils.py | 62 +++++++++- lisp/plugins/midi/widgets.py | 116 ++++++------------ lisp/plugins/network/api/cues.py | 4 +- lisp/plugins/network/api/layout.py | 4 +- lisp/plugins/network/discovery.py | 4 +- lisp/plugins/network/discovery_dialogs.py | 4 +- lisp/plugins/network/endpoint.py | 4 +- lisp/plugins/network/network.py | 4 +- lisp/plugins/network/server.py | 4 +- lisp/plugins/osc/osc.py | 6 +- lisp/plugins/osc/osc_delegate.py | 4 +- lisp/plugins/osc/osc_server.py | 6 +- lisp/plugins/osc/osc_settings.py | 6 +- lisp/plugins/presets/lib.py | 4 +- lisp/plugins/presets/presets.py | 4 +- lisp/plugins/presets/presets_ui.py | 4 +- lisp/plugins/rename_cues/rename_action.py | 4 +- lisp/plugins/rename_cues/rename_cues.py | 4 +- lisp/plugins/rename_cues/rename_ui.py | 4 +- lisp/plugins/replay_gain/gain_ui.py | 4 +- lisp/plugins/replay_gain/replay_gain.py | 4 +- lisp/plugins/synchronizer/synchronizer.py | 4 +- lisp/plugins/timecode/__init__.py | 4 +- lisp/plugins/timecode/cue_tracker.py | 4 +- lisp/plugins/timecode/protocol.py | 4 +- lisp/plugins/timecode/protocols/__init__.py | 4 +- lisp/plugins/timecode/protocols/artnet.py | 4 +- lisp/plugins/timecode/protocols/midi.py | 4 +- lisp/plugins/timecode/settings.py | 4 +- lisp/plugins/timecode/timecode.py | 4 +- lisp/plugins/triggers/triggers.py | 4 +- lisp/plugins/triggers/triggers_handler.py | 4 +- lisp/plugins/triggers/triggers_settings.py | 4 +- lisp/ui/about.py | 4 +- lisp/ui/cuelistdialog.py | 4 +- lisp/ui/icons/__init__.py | 4 +- lisp/ui/layoutselect.py | 4 +- lisp/ui/logging/common.py | 4 +- lisp/ui/logging/details.py | 4 +- lisp/ui/logging/dialog.py | 4 +- lisp/ui/logging/handler.py | 4 +- lisp/ui/logging/models.py | 4 +- lisp/ui/logging/status.py | 4 +- lisp/ui/logging/viewer.py | 4 +- lisp/ui/mainwindow.py | 4 +- lisp/ui/qdelegates.py | 4 +- lisp/ui/qmodels.py | 6 +- lisp/ui/settings/app_configuration.py | 4 +- lisp/ui/settings/app_pages/cue.py | 4 +- lisp/ui/settings/app_pages/general.py | 4 +- lisp/ui/settings/app_pages/layouts.py | 4 +- lisp/ui/settings/app_pages/plugins.py | 4 +- lisp/ui/settings/cue_pages/cue_appearance.py | 4 +- lisp/ui/settings/cue_pages/cue_general.py | 4 +- lisp/ui/settings/cue_pages/media_cue.py | 4 +- lisp/ui/settings/cue_settings.py | 4 +- lisp/ui/settings/pages.py | 4 +- lisp/ui/themes/dark/assetes.py | 4 +- lisp/ui/themes/dark/dark.py | 4 +- lisp/ui/themes/dark/theme.qss | 2 +- lisp/ui/ui_utils.py | 4 +- lisp/ui/widgets/cue_actions.py | 4 +- lisp/ui/widgets/cue_next_actions.py | 4 +- lisp/ui/widgets/fades.py | 4 +- lisp/ui/widgets/pagestreewidget.py | 4 +- lisp/ui/widgets/qclicklabel.py | 4 +- lisp/ui/widgets/qclickslider.py | 4 +- lisp/ui/widgets/qcolorbutton.py | 4 +- lisp/ui/widgets/qdbmeter.py | 4 +- lisp/ui/widgets/qeditabletabbar.py | 4 +- lisp/ui/widgets/qiconpushbutton.py | 4 +- lisp/ui/widgets/qmessagebox.py | 4 +- lisp/ui/widgets/qmutebutton.py | 4 +- lisp/ui/widgets/qprogresswheel.py | 4 +- lisp/ui/widgets/qsteptimeedit.py | 4 +- lisp/ui/widgets/qstyledslider.py | 4 +- lisp/ui/widgets/qvertiacallabel.py | 4 +- plugins_utils.py | 3 +- 198 files changed, 379 insertions(+), 772 deletions(-) delete mode 100644 lisp/plugins/midi/midi_input.py rename lisp/plugins/midi/{midi_common.py => midi_io.py} (54%) delete mode 100644 lisp/plugins/midi/midi_output.py diff --git a/docs/user/source/conf.py b/docs/user/source/conf.py index c25bafdfa..073f4c61c 100644 --- a/docs/user/source/conf.py +++ b/docs/user/source/conf.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# + # Linux Show Player documentation build configuration file, created by # sphinx-quickstart on Thu Feb 23 10:55:18 2017. # @@ -34,30 +33,30 @@ extensions = [] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'Linux Show Player' -copyright = '2017, Francesco Ceruti' -author = 'Francesco Ceruti' +project = "Linux Show Player" +copyright = "2017, Francesco Ceruti" +author = "Francesco Ceruti" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '0.5' +version = "0.5" # The full version, including alpha/beta/rc tags. -release = '0.5' +release = "0.5" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -72,7 +71,7 @@ exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # -- Options for HTML output ---------------------------------------------- @@ -92,29 +91,34 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = 'LinuxShowPlayerdoc' +htmlhelp_basename = "LinuxShowPlayerdoc" # -- Options for LaTeX output --------------------------------------------- -latex_toplevel_sectioning = 'section' +latex_toplevel_sectioning = "section" latex_elements = { - 'preamble': '''\ + "preamble": """\ \\let\\oldsection\\section -\\renewcommand\\section{\\clearpage\\oldsection}''', - 'figure_align': 'H' +\\renewcommand\\section{\\clearpage\\oldsection}""", + "figure_align": "H", } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'LinuxShowPlayer.tex', 'Linux Show Player User Documentation', - 'Francesco Ceruti', 'article'), + ( + master_doc, + "LinuxShowPlayer.tex", + "Linux Show Player User Documentation", + "Francesco Ceruti", + "article", + ) ] @@ -123,8 +127,13 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'linuxshowplayer', 'Linux Show Player Documentation', - [author], 1) + ( + master_doc, + "linuxshowplayer", + "Linux Show Player Documentation", + [author], + 1, + ) ] @@ -134,10 +143,13 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'LinuxShowPlayer', 'Linux Show Player Documentation', - author, 'LinuxShowPlayer', 'Cue player for stage productions', - 'Miscellaneous'), + ( + master_doc, + "LinuxShowPlayer", + "Linux Show Player Documentation", + author, + "LinuxShowPlayer", + "Cue player for stage productions", + "Miscellaneous", + ) ] - - - diff --git a/i18n_update.py b/i18n_update.py index ece220dba..fc501affc 100755 --- a/i18n_update.py +++ b/i18n_update.py @@ -1,9 +1,8 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/__init__.py b/lisp/__init__.py index bedf4cb32..aed4a6d44 100644 --- a/lisp/__init__.py +++ b/lisp/__init__.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/application.py b/lisp/application.py index babb50d47..261aa668c 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/backend/__init__.py b/lisp/backend/__init__.py index f39fe5a5c..7867a91e5 100644 --- a/lisp/backend/__init__.py +++ b/lisp/backend/__init__.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/backend/audio_utils.py b/lisp/backend/audio_utils.py index 0f8fb0c3e..c60f348f9 100644 --- a/lisp/backend/audio_utils.py +++ b/lisp/backend/audio_utils.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/backend/backend.py b/lisp/backend/backend.py index 170ab2836..6758dbfce 100644 --- a/lisp/backend/backend.py +++ b/lisp/backend/backend.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/backend/media.py b/lisp/backend/media.py index 3b9dbde38..d7800823e 100644 --- a/lisp/backend/media.py +++ b/lisp/backend/media.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/backend/media_element.py b/lisp/backend/media_element.py index 0d3b34c4c..1d410c354 100644 --- a/lisp/backend/media_element.py +++ b/lisp/backend/media_element.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/action.py b/lisp/core/action.py index 0150d9648..87bf4d57f 100644 --- a/lisp/core/action.py +++ b/lisp/core/action.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/actions_handler.py b/lisp/core/actions_handler.py index e657f634a..d67b2d351 100644 --- a/lisp/core/actions_handler.py +++ b/lisp/core/actions_handler.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/class_based_registry.py b/lisp/core/class_based_registry.py index cbd315226..b0a90ddf7 100644 --- a/lisp/core/class_based_registry.py +++ b/lisp/core/class_based_registry.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/clock.py b/lisp/core/clock.py index 66f85b4bb..896a51274 100644 --- a/lisp/core/clock.py +++ b/lisp/core/clock.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index eb7d23ea1..25f5e873c 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/decorators.py b/lisp/core/decorators.py index bba36c884..dc2d5a0c4 100644 --- a/lisp/core/decorators.py +++ b/lisp/core/decorators.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/dicttree.py b/lisp/core/dicttree.py index 0d9c694a9..77aade6c7 100644 --- a/lisp/core/dicttree.py +++ b/lisp/core/dicttree.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/fade_functions.py b/lisp/core/fade_functions.py index d2ce44f27..64b6d3fa6 100644 --- a/lisp/core/fade_functions.py +++ b/lisp/core/fade_functions.py @@ -1,10 +1,7 @@ -# -*- coding: utf-8 -*- -# # Python porting of qtractor fade functions +## This file is part of Linux Show Player # -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/fader.py b/lisp/core/fader.py index 1e35727d8..a96975bb8 100644 --- a/lisp/core/fader.py +++ b/lisp/core/fader.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/has_properties.py b/lisp/core/has_properties.py index fa304826e..beeca7ba7 100644 --- a/lisp/core/has_properties.py +++ b/lisp/core/has_properties.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/loading.py b/lisp/core/loading.py index f4a9ade3d..f968761bc 100644 --- a/lisp/core/loading.py +++ b/lisp/core/loading.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/memento_model.py b/lisp/core/memento_model.py index 3a8a9c415..5d0517f14 100644 --- a/lisp/core/memento_model.py +++ b/lisp/core/memento_model.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/memento_model_actions.py b/lisp/core/memento_model_actions.py index 0c0b4b7fc..6cfe198dd 100644 --- a/lisp/core/memento_model_actions.py +++ b/lisp/core/memento_model_actions.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/model.py b/lisp/core/model.py index a4b647588..8bafd1190 100644 --- a/lisp/core/model.py +++ b/lisp/core/model.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/model_adapter.py b/lisp/core/model_adapter.py index e31247871..c3be4dec8 100644 --- a/lisp/core/model_adapter.py +++ b/lisp/core/model_adapter.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/plugin.py b/lisp/core/plugin.py index f2f07a3a0..0d1b5129e 100644 --- a/lisp/core/plugin.py +++ b/lisp/core/plugin.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/properties.py b/lisp/core/properties.py index d46ade624..cf1b23fdd 100644 --- a/lisp/core/properties.py +++ b/lisp/core/properties.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/proxy_model.py b/lisp/core/proxy_model.py index 0f720763d..cb54347d1 100644 --- a/lisp/core/proxy_model.py +++ b/lisp/core/proxy_model.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/qmeta.py b/lisp/core/qmeta.py index 7dd007a6f..c0dc72935 100644 --- a/lisp/core/qmeta.py +++ b/lisp/core/qmeta.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/rwait.py b/lisp/core/rwait.py index 4ab4b5eb9..b2ef15f2e 100644 --- a/lisp/core/rwait.py +++ b/lisp/core/rwait.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/session.py b/lisp/core/session.py index b1f1e2cd1..a0dce988f 100644 --- a/lisp/core/session.py +++ b/lisp/core/session.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/signal.py b/lisp/core/signal.py index 9b81b4bc3..9743404ef 100644 --- a/lisp/core/signal.py +++ b/lisp/core/signal.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/singleton.py b/lisp/core/singleton.py index 1a09cc2b8..e51fd1260 100644 --- a/lisp/core/singleton.py +++ b/lisp/core/singleton.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/core/util.py b/lisp/core/util.py index eb3d64175..8f6baaf11 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index fe75a5405..9eaec3634 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/cues/cue_actions.py b/lisp/cues/cue_actions.py index 6de146538..a51bca1f3 100644 --- a/lisp/cues/cue_actions.py +++ b/lisp/cues/cue_actions.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/cues/cue_factory.py b/lisp/cues/cue_factory.py index 63792525a..71e57f387 100644 --- a/lisp/cues/cue_factory.py +++ b/lisp/cues/cue_factory.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/cues/cue_memento_model.py b/lisp/cues/cue_memento_model.py index 5119a940c..43cc8914a 100644 --- a/lisp/cues/cue_memento_model.py +++ b/lisp/cues/cue_memento_model.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/cues/cue_model.py b/lisp/cues/cue_model.py index 6968ab057..f5337f773 100644 --- a/lisp/cues/cue_model.py +++ b/lisp/cues/cue_model.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/cues/cue_time.py b/lisp/cues/cue_time.py index 9feb78e85..1e121b17f 100644 --- a/lisp/cues/cue_time.py +++ b/lisp/cues/cue_time.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/cues/media_cue.py b/lisp/cues/media_cue.py index cafd125c6..b64654ff8 100644 --- a/lisp/cues/media_cue.py +++ b/lisp/cues/media_cue.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/layout/cue_layout.py b/lisp/layout/cue_layout.py index 2d31d033a..abb5f9eef 100644 --- a/lisp/layout/cue_layout.py +++ b/lisp/layout/cue_layout.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/layout/cue_menu.py b/lisp/layout/cue_menu.py index 0f5e4eb82..daecd251a 100644 --- a/lisp/layout/cue_menu.py +++ b/lisp/layout/cue_menu.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/main.py b/lisp/main.py index bbe3ef9fd..cee2d3019 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index db70e7906..b9e1d8a8a 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/action_cues/__init__.py b/lisp/plugins/action_cues/__init__.py index 2c072400f..680207d9f 100644 --- a/lisp/plugins/action_cues/__init__.py +++ b/lisp/plugins/action_cues/__init__.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/action_cues/collection_cue.py b/lisp/plugins/action_cues/collection_cue.py index 8b50ca629..6e6ca058c 100644 --- a/lisp/plugins/action_cues/collection_cue.py +++ b/lisp/plugins/action_cues/collection_cue.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/action_cues/command_cue.py b/lisp/plugins/action_cues/command_cue.py index ce542be39..d8456139b 100644 --- a/lisp/plugins/action_cues/command_cue.py +++ b/lisp/plugins/action_cues/command_cue.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/action_cues/index_action_cue.py b/lisp/plugins/action_cues/index_action_cue.py index a069d1d90..3e5cba6cc 100644 --- a/lisp/plugins/action_cues/index_action_cue.py +++ b/lisp/plugins/action_cues/index_action_cue.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/action_cues/midi_cue.py b/lisp/plugins/action_cues/midi_cue.py index 0ed4a7636..7ca18ce74 100644 --- a/lisp/plugins/action_cues/midi_cue.py +++ b/lisp/plugins/action_cues/midi_cue.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/action_cues/osc_cue.py b/lisp/plugins/action_cues/osc_cue.py index a16f7201e..5804d98cc 100644 --- a/lisp/plugins/action_cues/osc_cue.py +++ b/lisp/plugins/action_cues/osc_cue.py @@ -1,9 +1,7 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti -# Copyright 2012-2016 Thomas Achtner +# Copyright 2018 Francesco Ceruti +# Copyright 2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/action_cues/seek_cue.py b/lisp/plugins/action_cues/seek_cue.py index 37cb4e6c9..1af9f575f 100644 --- a/lisp/plugins/action_cues/seek_cue.py +++ b/lisp/plugins/action_cues/seek_cue.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/action_cues/stop_all.py b/lisp/plugins/action_cues/stop_all.py index 128560fc2..009264432 100644 --- a/lisp/plugins/action_cues/stop_all.py +++ b/lisp/plugins/action_cues/stop_all.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/action_cues/volume_control.py b/lisp/plugins/action_cues/volume_control.py index 4224836e0..792b23086 100644 --- a/lisp/plugins/action_cues/volume_control.py +++ b/lisp/plugins/action_cues/volume_control.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/cart_layout/cue_widget.py b/lisp/plugins/cart_layout/cue_widget.py index 5325d974e..1f1dbf69c 100644 --- a/lisp/plugins/cart_layout/cue_widget.py +++ b/lisp/plugins/cart_layout/cue_widget.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/cart_layout/layout.py b/lisp/plugins/cart_layout/layout.py index 77d085c31..37632dd89 100644 --- a/lisp/plugins/cart_layout/layout.py +++ b/lisp/plugins/cart_layout/layout.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/cart_layout/model.py b/lisp/plugins/cart_layout/model.py index a7b96e505..4cb4aefb1 100644 --- a/lisp/plugins/cart_layout/model.py +++ b/lisp/plugins/cart_layout/model.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/cart_layout/page_widget.py b/lisp/plugins/cart_layout/page_widget.py index bf09e4521..72f7d6358 100644 --- a/lisp/plugins/cart_layout/page_widget.py +++ b/lisp/plugins/cart_layout/page_widget.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/cart_layout/settings.py b/lisp/plugins/cart_layout/settings.py index c76f4c401..c029c6b21 100644 --- a/lisp/plugins/cart_layout/settings.py +++ b/lisp/plugins/cart_layout/settings.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/cart_layout/tab_widget.py b/lisp/plugins/cart_layout/tab_widget.py index 9430ac754..b706aa239 100644 --- a/lisp/plugins/cart_layout/tab_widget.py +++ b/lisp/plugins/cart_layout/tab_widget.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/controller/common.py b/lisp/plugins/controller/common.py index fa70e8050..4fb8e7100 100644 --- a/lisp/plugins/controller/common.py +++ b/lisp/plugins/controller/common.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/controller/controller.py b/lisp/plugins/controller/controller.py index 4a664936f..95a6b98f6 100644 --- a/lisp/plugins/controller/controller.py +++ b/lisp/plugins/controller/controller.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/controller/controller_settings.py b/lisp/plugins/controller/controller_settings.py index 077f729ab..16be655e4 100644 --- a/lisp/plugins/controller/controller_settings.py +++ b/lisp/plugins/controller/controller_settings.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/controller/protocol.py b/lisp/plugins/controller/protocol.py index 16275a8b0..40760838d 100644 --- a/lisp/plugins/controller/protocol.py +++ b/lisp/plugins/controller/protocol.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/controller/protocols/__init__.py b/lisp/plugins/controller/protocols/__init__.py index 99a44dd56..4bffcd0cb 100644 --- a/lisp/plugins/controller/protocols/__init__.py +++ b/lisp/plugins/controller/protocols/__init__.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/controller/protocols/keyboard.py b/lisp/plugins/controller/protocols/keyboard.py index 74ea8694f..c996ef1e9 100644 --- a/lisp/plugins/controller/protocols/keyboard.py +++ b/lisp/plugins/controller/protocols/keyboard.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index 590b34dc7..52cb28a65 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 3e7b93065..7411cdd43 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -1,9 +1,7 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti -# Copyright 2012-2016 Thomas Achtner +# Copyright 2018 Francesco Ceruti +# Copyright 2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/__init__.py b/lisp/plugins/gst_backend/elements/__init__.py index e2747cf2d..5fe33156c 100644 --- a/lisp/plugins/gst_backend/elements/__init__.py +++ b/lisp/plugins/gst_backend/elements/__init__.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/alsa_sink.py b/lisp/plugins/gst_backend/elements/alsa_sink.py index 347879cbd..05964a798 100644 --- a/lisp/plugins/gst_backend/elements/alsa_sink.py +++ b/lisp/plugins/gst_backend/elements/alsa_sink.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/audio_dynamic.py b/lisp/plugins/gst_backend/elements/audio_dynamic.py index cac6a2ade..7dbaba0ae 100644 --- a/lisp/plugins/gst_backend/elements/audio_dynamic.py +++ b/lisp/plugins/gst_backend/elements/audio_dynamic.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/audio_pan.py b/lisp/plugins/gst_backend/elements/audio_pan.py index 0602eda7b..66a3f3315 100644 --- a/lisp/plugins/gst_backend/elements/audio_pan.py +++ b/lisp/plugins/gst_backend/elements/audio_pan.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/auto_sink.py b/lisp/plugins/gst_backend/elements/auto_sink.py index 81a455d54..27b3fb35b 100644 --- a/lisp/plugins/gst_backend/elements/auto_sink.py +++ b/lisp/plugins/gst_backend/elements/auto_sink.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/auto_src.py b/lisp/plugins/gst_backend/elements/auto_src.py index 8b79f233b..88b7473a1 100644 --- a/lisp/plugins/gst_backend/elements/auto_src.py +++ b/lisp/plugins/gst_backend/elements/auto_src.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/db_meter.py b/lisp/plugins/gst_backend/elements/db_meter.py index d02c2fff2..bbab82262 100644 --- a/lisp/plugins/gst_backend/elements/db_meter.py +++ b/lisp/plugins/gst_backend/elements/db_meter.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/equalizer10.py b/lisp/plugins/gst_backend/elements/equalizer10.py index 4165bb5b3..7bc593b09 100644 --- a/lisp/plugins/gst_backend/elements/equalizer10.py +++ b/lisp/plugins/gst_backend/elements/equalizer10.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/jack_sink.py b/lisp/plugins/gst_backend/elements/jack_sink.py index 4e80be9ea..33a605cfb 100644 --- a/lisp/plugins/gst_backend/elements/jack_sink.py +++ b/lisp/plugins/gst_backend/elements/jack_sink.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/pitch.py b/lisp/plugins/gst_backend/elements/pitch.py index a04796e05..09d94fed5 100644 --- a/lisp/plugins/gst_backend/elements/pitch.py +++ b/lisp/plugins/gst_backend/elements/pitch.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/preset_src.py b/lisp/plugins/gst_backend/elements/preset_src.py index 345c1a49c..107d9aa54 100644 --- a/lisp/plugins/gst_backend/elements/preset_src.py +++ b/lisp/plugins/gst_backend/elements/preset_src.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/pulse_sink.py b/lisp/plugins/gst_backend/elements/pulse_sink.py index 519a3645e..acaf3ecec 100644 --- a/lisp/plugins/gst_backend/elements/pulse_sink.py +++ b/lisp/plugins/gst_backend/elements/pulse_sink.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/speed.py b/lisp/plugins/gst_backend/elements/speed.py index 8931ac927..815511d19 100644 --- a/lisp/plugins/gst_backend/elements/speed.py +++ b/lisp/plugins/gst_backend/elements/speed.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/uri_input.py b/lisp/plugins/gst_backend/elements/uri_input.py index 7525671e9..b7d293fba 100644 --- a/lisp/plugins/gst_backend/elements/uri_input.py +++ b/lisp/plugins/gst_backend/elements/uri_input.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/user_element.py b/lisp/plugins/gst_backend/elements/user_element.py index bf96c08f7..d869a7301 100644 --- a/lisp/plugins/gst_backend/elements/user_element.py +++ b/lisp/plugins/gst_backend/elements/user_element.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/elements/volume.py b/lisp/plugins/gst_backend/elements/volume.py index 48bdf0335..755d9efe4 100644 --- a/lisp/plugins/gst_backend/elements/volume.py +++ b/lisp/plugins/gst_backend/elements/volume.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index 3dab1c53c..813d8cb4c 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/gst_element.py b/lisp/plugins/gst_backend/gst_element.py index 2bba68280..5c80f06cc 100644 --- a/lisp/plugins/gst_backend/gst_element.py +++ b/lisp/plugins/gst_backend/gst_element.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index 7102a3aaa..832a2ef9e 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/gst_media_cue.py b/lisp/plugins/gst_backend/gst_media_cue.py index e49874616..0bcb70d2c 100644 --- a/lisp/plugins/gst_backend/gst_media_cue.py +++ b/lisp/plugins/gst_backend/gst_media_cue.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/gst_media_settings.py b/lisp/plugins/gst_backend/gst_media_settings.py index c0e025cea..1121c7cee 100644 --- a/lisp/plugins/gst_backend/gst_media_settings.py +++ b/lisp/plugins/gst_backend/gst_media_settings.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/gst_pipe_edit.py b/lisp/plugins/gst_backend/gst_pipe_edit.py index bb581962e..81c08cea3 100644 --- a/lisp/plugins/gst_backend/gst_pipe_edit.py +++ b/lisp/plugins/gst_backend/gst_pipe_edit.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/gst_settings.py b/lisp/plugins/gst_backend/gst_settings.py index 22b1616b3..412f43cb8 100644 --- a/lisp/plugins/gst_backend/gst_settings.py +++ b/lisp/plugins/gst_backend/gst_settings.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/gst_utils.py b/lisp/plugins/gst_backend/gst_utils.py index 1fcde6fc3..4618adf8c 100644 --- a/lisp/plugins/gst_backend/gst_utils.py +++ b/lisp/plugins/gst_backend/gst_utils.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/settings/__init__.py b/lisp/plugins/gst_backend/settings/__init__.py index ccd9e91e6..19bdae0ca 100644 --- a/lisp/plugins/gst_backend/settings/__init__.py +++ b/lisp/plugins/gst_backend/settings/__init__.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/settings/alsa_sink.py b/lisp/plugins/gst_backend/settings/alsa_sink.py index 83321b1ab..c156f3c5a 100644 --- a/lisp/plugins/gst_backend/settings/alsa_sink.py +++ b/lisp/plugins/gst_backend/settings/alsa_sink.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/settings/audio_dynamic.py b/lisp/plugins/gst_backend/settings/audio_dynamic.py index 756c1d527..b1e2cd3ac 100644 --- a/lisp/plugins/gst_backend/settings/audio_dynamic.py +++ b/lisp/plugins/gst_backend/settings/audio_dynamic.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/settings/audio_pan.py b/lisp/plugins/gst_backend/settings/audio_pan.py index 87222dd70..7d5b2ab6f 100644 --- a/lisp/plugins/gst_backend/settings/audio_pan.py +++ b/lisp/plugins/gst_backend/settings/audio_pan.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/settings/db_meter.py b/lisp/plugins/gst_backend/settings/db_meter.py index c38b210cd..5822c8b02 100644 --- a/lisp/plugins/gst_backend/settings/db_meter.py +++ b/lisp/plugins/gst_backend/settings/db_meter.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/settings/equalizer10.py b/lisp/plugins/gst_backend/settings/equalizer10.py index bcd1830f5..2e7f5ad51 100644 --- a/lisp/plugins/gst_backend/settings/equalizer10.py +++ b/lisp/plugins/gst_backend/settings/equalizer10.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/settings/jack_sink.py b/lisp/plugins/gst_backend/settings/jack_sink.py index acc5d6fbb..8f0ad22bc 100644 --- a/lisp/plugins/gst_backend/settings/jack_sink.py +++ b/lisp/plugins/gst_backend/settings/jack_sink.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/settings/pitch.py b/lisp/plugins/gst_backend/settings/pitch.py index f76953a9c..7db4e9394 100644 --- a/lisp/plugins/gst_backend/settings/pitch.py +++ b/lisp/plugins/gst_backend/settings/pitch.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/settings/preset_src.py b/lisp/plugins/gst_backend/settings/preset_src.py index 4dff4535e..7f69335f6 100644 --- a/lisp/plugins/gst_backend/settings/preset_src.py +++ b/lisp/plugins/gst_backend/settings/preset_src.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/settings/speed.py b/lisp/plugins/gst_backend/settings/speed.py index e3a6e3599..9f0bf15f5 100644 --- a/lisp/plugins/gst_backend/settings/speed.py +++ b/lisp/plugins/gst_backend/settings/speed.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/settings/uri_input.py b/lisp/plugins/gst_backend/settings/uri_input.py index 163d3866c..1a1076254 100644 --- a/lisp/plugins/gst_backend/settings/uri_input.py +++ b/lisp/plugins/gst_backend/settings/uri_input.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/settings/user_element.py b/lisp/plugins/gst_backend/settings/user_element.py index 3ffb53406..c564be018 100644 --- a/lisp/plugins/gst_backend/settings/user_element.py +++ b/lisp/plugins/gst_backend/settings/user_element.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/gst_backend/settings/volume.py b/lisp/plugins/gst_backend/settings/volume.py index 0041eefab..7547ba3c9 100644 --- a/lisp/plugins/gst_backend/settings/volume.py +++ b/lisp/plugins/gst_backend/settings/volume.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/list_layout/control_buttons.py b/lisp/plugins/list_layout/control_buttons.py index 8151e90f6..dc77b6e67 100644 --- a/lisp/plugins/list_layout/control_buttons.py +++ b/lisp/plugins/list_layout/control_buttons.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/list_layout/info_panel.py b/lisp/plugins/list_layout/info_panel.py index d638015a1..a71239874 100644 --- a/lisp/plugins/list_layout/info_panel.py +++ b/lisp/plugins/list_layout/info_panel.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index a4ab7ebe3..ba9985bc4 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py index 47285e593..4d66970b6 100644 --- a/lisp/plugins/list_layout/list_view.py +++ b/lisp/plugins/list_layout/list_view.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/list_layout/list_widgets.py b/lisp/plugins/list_layout/list_widgets.py index de488e735..57803e0ea 100644 --- a/lisp/plugins/list_layout/list_widgets.py +++ b/lisp/plugins/list_layout/list_widgets.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/list_layout/models.py b/lisp/plugins/list_layout/models.py index 8dc1f96ea..96f88e7df 100644 --- a/lisp/plugins/list_layout/models.py +++ b/lisp/plugins/list_layout/models.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/list_layout/playing_view.py b/lisp/plugins/list_layout/playing_view.py index 50fcd9882..323eb5528 100644 --- a/lisp/plugins/list_layout/playing_view.py +++ b/lisp/plugins/list_layout/playing_view.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/list_layout/playing_widgets.py b/lisp/plugins/list_layout/playing_widgets.py index 3bf03b466..eb5de2ce2 100644 --- a/lisp/plugins/list_layout/playing_widgets.py +++ b/lisp/plugins/list_layout/playing_widgets.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/list_layout/settings.py b/lisp/plugins/list_layout/settings.py index 2d2e202c4..2672a1ef2 100644 --- a/lisp/plugins/list_layout/settings.py +++ b/lisp/plugins/list_layout/settings.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/list_layout/view.py b/lisp/plugins/list_layout/view.py index f2b6c2c0b..65f326ed7 100644 --- a/lisp/plugins/list_layout/view.py +++ b/lisp/plugins/list_layout/view.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/media_info/media_info.py b/lisp/plugins/media_info/media_info.py index 6d22794fd..809475f7e 100644 --- a/lisp/plugins/media_info/media_info.py +++ b/lisp/plugins/media_info/media_info.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index a28a66d8e..3f34bc05e 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2019 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,8 +18,7 @@ import mido from lisp.core.plugin import Plugin -from lisp.plugins.midi.midi_input import MIDIInput -from lisp.plugins.midi.midi_output import MIDIOutput +from lisp.plugins.midi.midi_io import MIDIOutput, MIDIInput from lisp.plugins.midi.midi_settings import MIDISettings from lisp.ui.settings.app_configuration import AppConfigurationDialog diff --git a/lisp/plugins/midi/midi_input.py b/lisp/plugins/midi/midi_input.py deleted file mode 100644 index 568ab1a85..000000000 --- a/lisp/plugins/midi/midi_input.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from lisp.core.signal import Signal -from lisp.plugins.midi.midi_common import MIDICommon -from lisp.plugins.midi.midi_utils import mido_backend - - -class MIDIInput(MIDICommon): - def __init__(self, port_name=None): - super().__init__(port_name=port_name) - - self.alternate_mode = False - self.new_message = Signal() - self.new_message_alt = Signal() - - def open(self): - self._port = mido_backend().open_input( - name=self._port_name, callback=self.__new_message - ) - - def __new_message(self, message): - if self.alternate_mode: - self.new_message_alt.emit(message) - else: - self.new_message.emit(message) diff --git a/lisp/plugins/midi/midi_common.py b/lisp/plugins/midi/midi_io.py similarity index 54% rename from lisp/plugins/midi/midi_common.py rename to lisp/plugins/midi/midi_io.py index bd8a89b6d..cecea8fd2 100644 --- a/lisp/plugins/midi/midi_common.py +++ b/lisp/plugins/midi/midi_io.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2019 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,10 +15,14 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from abc import abstractmethod +import mido +from abc import ABC, abstractmethod + +from lisp.core.signal import Signal +from lisp.plugins.midi.midi_utils import mido_backend -class MIDICommon: +class MIDIBase(ABC): def __init__(self, port_name=None): """ :param port_name: the port name @@ -48,3 +50,37 @@ def is_open(self): return not self._port.closed return False + + +class MIDIOutput(MIDIBase): + def __init__(self, port_name=None): + super().__init__(port_name=port_name) + + def send_from_str(self, str_message): + self.send(mido.parse_string(str_message)) + + def send(self, message): + self._port.send(message) + + def open(self): + self._port = mido_backend().open_output(self._port_name) + + +class MIDIInput(MIDIBase): + def __init__(self, port_name=None): + super().__init__(port_name=port_name) + + self.alternate_mode = False + self.new_message = Signal() + self.new_message_alt = Signal() + + def open(self): + self._port = mido_backend().open_input( + name=self._port_name, callback=self.__new_message + ) + + def __new_message(self, message): + if self.alternate_mode: + self.new_message_alt.emit(message) + else: + self.new_message.emit(message) diff --git a/lisp/plugins/midi/midi_output.py b/lisp/plugins/midi/midi_output.py deleted file mode 100644 index 43278fc87..000000000 --- a/lisp/plugins/midi/midi_output.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of Linux Show Player -# -# Copyright 2012-2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -import mido - -from lisp.plugins.midi.midi_common import MIDICommon -from lisp.plugins.midi.midi_utils import mido_backend - - -class MIDIOutput(MIDICommon): - def __init__(self, port_name=None): - super().__init__(port_name=port_name) - - def send_from_str(self, str_message): - self.send(mido.parse_string(str_message)) - - def send(self, message): - self._port.send(message) - - def open(self): - self._port = mido_backend().open_output(self._port_name) diff --git a/lisp/plugins/midi/midi_settings.py b/lisp/plugins/midi/midi_settings.py index 500d45bc6..50fbbc7bd 100644 --- a/lisp/plugins/midi/midi_settings.py +++ b/lisp/plugins/midi/midi_settings.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/midi/midi_utils.py b/lisp/plugins/midi/midi_utils.py index d9d9a7849..75203f916 100644 --- a/lisp/plugins/midi/midi_utils.py +++ b/lisp/plugins/midi/midi_utils.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2019 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,6 +16,64 @@ # along with Linux Show Player. If not, see . import mido +from PyQt5.QtCore import QT_TRANSLATE_NOOP + + +MIDI_MSGS_SPEC = { + "note_on": ["channel", "note", "velocity"], + "note_off": ["channel", "note", "velocity"], + "polytouch": ["channel", "note", "value"], + "control_change": ["channel", "control", "value"], + "program_change": ["channel", "program", None], + "aftertouch": ["channel", "value", None], + "pitchwheel": ["channel", "pitch", None], + "song_select": ["song", None, None], + "songpos": ["pos", None, None], + "start": [None] * 3, + "stop": [None] * 3, + "continue": [None] * 3, +} + +MIDI_ATTRS_SPEC = { + "channel": (1, 16, -1), + "note": (0, 127, 0), + "velocity": (0, 127, 0), + "control": (0, 127, 0), + "program": (0, 127, 0), + "value": (0, 127, 0), + "song": (0, 127, 0), + "pitch": (-8192, 8191, 0), + "pos": (0, 16383, 0), +} + +MIDI_MSGS_NAME = { + "note_on": QT_TRANSLATE_NOOP("MIDIMessageType", "Note ON"), + "note_off": QT_TRANSLATE_NOOP("MIDIMessageType", "Note OFF"), + "polytouch": QT_TRANSLATE_NOOP("MIDIMessageType", "Polyphonic After-touch"), + "control_change": QT_TRANSLATE_NOOP( + "MIDIMessageType", "Control/Mode Change" + ), + "program_change": QT_TRANSLATE_NOOP("MIDIMessageType", "Program Change"), + "aftertouch": QT_TRANSLATE_NOOP("MIDIMessageType", "Channel After-touch"), + "pitchwheel": QT_TRANSLATE_NOOP("MIDIMessageType", "Pitch Bend Change"), + "song_select": QT_TRANSLATE_NOOP("MIDIMessageType", "Song Select"), + "songpos": QT_TRANSLATE_NOOP("MIDIMessageType", "Song Position"), + "start": QT_TRANSLATE_NOOP("MIDIMessageType", "Start"), + "stop": QT_TRANSLATE_NOOP("MIDIMessageType", "Stop"), + "continue": QT_TRANSLATE_NOOP("MIDIMessageType", "Continue"), +} + +MIDI_ATTRS_NAME = { + "channel": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Channel"), + "note": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Note"), + "velocity": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Velocity"), + "control": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Control"), + "program": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Program"), + "value": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Value"), + "song": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Song"), + "pitch": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Pitch"), + "pos": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Position"), +} def str_msg_to_dict(str_message): diff --git a/lisp/plugins/midi/widgets.py b/lisp/plugins/midi/widgets.py index f0bb6bef6..01f0f8d7b 100644 --- a/lisp/plugins/midi/widgets.py +++ b/lisp/plugins/midi/widgets.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2019 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP +from PyQt5.QtCore import Qt from PyQt5.QtWidgets import ( QGroupBox, QVBoxLayout, @@ -27,8 +25,16 @@ QSpinBox, QFrame, QWidget, + QDialog, + QDialogButtonBox, ) +from lisp.plugins.midi.midi_utils import ( + MIDI_MSGS_SPEC, + MIDI_ATTRS_SPEC, + MIDI_MSGS_NAME, + MIDI_ATTRS_NAME, +) from lisp.ui.ui_utils import translate @@ -39,68 +45,6 @@ class MIDIMessageEdit(QWidget): https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message """ - MESSAGES = { - "note_on": ["channel", "note", "velocity"], - "note_off": ["channel", "note", "velocity"], - "polytouch": ["channel", "note", "value"], - "control_change": ["channel", "control", "value"], - "program_change": ["channel", "program", None], - "aftertouch": ["channel", "value", None], - "pitchwheel": ["channel", "pitch", None], - "song_select": ["song", None, None], - "songpos": ["pos", None, None], - "start": [None] * 3, - "stop": [None] * 3, - "continue": [None] * 3, - } - - ATTRIBUTES = { - "channel": (1, 16, -1), - "note": (0, 127, 0), - "velocity": (0, 127, 0), - "control": (0, 127, 0), - "program": (0, 127, 0), - "value": (0, 127, 0), - "song": (0, 127, 0), - "pitch": (-8192, 8191, 0), - "pos": (0, 16383, 0), - } - - MESSAGES_UI = { - "note_on": QT_TRANSLATE_NOOP("MIDIMessageType", "Note ON"), - "note_off": QT_TRANSLATE_NOOP("MIDIMessageType", "Note OFF"), - "polytouch": QT_TRANSLATE_NOOP( - "MIDIMessageType", "Polyphonic After-touch" - ), - "control_change": QT_TRANSLATE_NOOP( - "MIDIMessageType", "Control/Mode Change" - ), - "program_change": QT_TRANSLATE_NOOP( - "MIDIMessageType", "Program Change" - ), - "aftertouch": QT_TRANSLATE_NOOP( - "MIDIMessageType", "Channel After-touch" - ), - "pitchwheel": QT_TRANSLATE_NOOP("MIDIMessageType", "Pitch Bend Change"), - "song_select": QT_TRANSLATE_NOOP("MIDIMessageType", "Song Select"), - "songpos": QT_TRANSLATE_NOOP("MIDIMessageType", "Song Position"), - "start": QT_TRANSLATE_NOOP("MIDIMessageType", "Start"), - "stop": QT_TRANSLATE_NOOP("MIDIMessageType", "Stop"), - "continue": QT_TRANSLATE_NOOP("MIDIMessageType", "Continue"), - } - - ATTRIBUTES_UI = { - "channel": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Channel"), - "note": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Note"), - "velocity": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Velocity"), - "control": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Control"), - "program": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Program"), - "value": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Value"), - "song": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Song"), - "pitch": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Pitch"), - "pos": QT_TRANSLATE_NOOP("MIDIMessageAttr", "Position"), - } - def __init__(self, **kwargs): super().__init__(**kwargs) self.setLayout(QVBoxLayout()) @@ -114,12 +58,9 @@ def __init__(self, **kwargs): self.msgTypeLabel = QLabel(self.msgGroup) self.msgGroup.layout().addWidget(self.msgTypeLabel, 0, 0) self.msgTypeCombo = QComboBox(self.msgGroup) - for msgType in MIDIMessageEdit.MESSAGES.keys(): + for msgType in MIDI_MSGS_SPEC.keys(): self.msgTypeCombo.addItem( - translate( - "MIDIMessageType", MIDIMessageEdit.MESSAGES_UI[msgType] - ), - msgType, + translate("MIDIMessageType", MIDI_MSGS_NAME[msgType]), msgType ) self.msgTypeCombo.currentIndexChanged.connect(self._typeChanged) self.msgGroup.layout().addWidget(self.msgTypeCombo, 0, 1) @@ -153,7 +94,7 @@ def getMessageDict(self): for attr, spin, label in self._currentValues(msgType): if spin.isEnabled(): - offset = MIDIMessageEdit.ATTRIBUTES[attr][2] + offset = MIDI_ATTRS_SPEC[attr][2] msgDict[attr] = spin.value() + offset return msgDict @@ -164,12 +105,12 @@ def setMessageDict(self, dictMsg): ) for attr, spin, label in self._currentValues(dictMsg["type"]): - min_, _, offset = MIDIMessageEdit.ATTRIBUTES.get(attr, (0, 0, 0)) + min_, _, offset = MIDI_ATTRS_SPEC.get(attr, (0, 0, 0)) spin.setValue(dictMsg.get(attr, min_) - offset) def _currentValues(self, msgType): for attr, (spin, label) in zip( - MIDIMessageEdit.MESSAGES[msgType], self._dataWidgets + MIDI_MSGS_SPEC[msgType], self._dataWidgets ): yield attr, spin, label @@ -183,8 +124,31 @@ def _typeChanged(self): spin.setEnabled(False) else: label.setEnabled(True) - label.setText(MIDIMessageEdit.ATTRIBUTES_UI[attr]) + label.setText(MIDI_ATTRS_NAME[attr]) - min_, max_, _ = MIDIMessageEdit.ATTRIBUTES[attr] + min_, max_, _ = MIDI_ATTRS_SPEC[attr] spin.setRange(min_, max_) spin.setEnabled(True) + + +class MIDIMessageEditDialog(QDialog): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.setLayout(QVBoxLayout()) + self.layout().setContentsMargins(0, 0, 0, 0) + + self.editor = MIDIMessageEdit() + self.layout().addWidget(self.editor) + + self.buttons = QDialogButtonBox( + QDialogButtonBox.Ok | QDialogButtonBox.Cancel + ) + self.buttons.accepted.connect(self.accept) + self.buttons.rejected.connect(self.reject) + self.layout().addWidget(self.editor) + + def getMessageDict(self): + return self.editor.getMessageDict() + + def setMessageDict(self, dictMsg): + self.editor.setMessageDict(dictMsg) diff --git a/lisp/plugins/network/api/cues.py b/lisp/plugins/network/api/cues.py index 2d1f62163..b2bdfe16b 100644 --- a/lisp/plugins/network/api/cues.py +++ b/lisp/plugins/network/api/cues.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/network/api/layout.py b/lisp/plugins/network/api/layout.py index a419f071a..f6f2b6b34 100644 --- a/lisp/plugins/network/api/layout.py +++ b/lisp/plugins/network/api/layout.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/network/discovery.py b/lisp/plugins/network/discovery.py index 3fe4f21bb..0c6ca82e7 100644 --- a/lisp/plugins/network/discovery.py +++ b/lisp/plugins/network/discovery.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/network/discovery_dialogs.py b/lisp/plugins/network/discovery_dialogs.py index 86d082e64..610f7ffa1 100644 --- a/lisp/plugins/network/discovery_dialogs.py +++ b/lisp/plugins/network/discovery_dialogs.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/network/endpoint.py b/lisp/plugins/network/endpoint.py index 5ce0fcfa3..78e1c7186 100644 --- a/lisp/plugins/network/endpoint.py +++ b/lisp/plugins/network/endpoint.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/network/network.py b/lisp/plugins/network/network.py index 5cfbb7cc2..a8670b735 100644 --- a/lisp/plugins/network/network.py +++ b/lisp/plugins/network/network.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/network/server.py b/lisp/plugins/network/server.py index 12c57e627..e20e138dd 100644 --- a/lisp/plugins/network/server.py +++ b/lisp/plugins/network/server.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/osc/osc.py b/lisp/plugins/osc/osc.py index aa1668121..0d6426de9 100644 --- a/lisp/plugins/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -1,9 +1,7 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti -# Copyright 2012-2016 Thomas Achtner +# Copyright 2017 Francesco Ceruti +# Copyright 2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/osc/osc_delegate.py b/lisp/plugins/osc/osc_delegate.py index a57b90dc2..0a359a2e0 100644 --- a/lisp/plugins/osc/osc_delegate.py +++ b/lisp/plugins/osc/osc_delegate.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/osc/osc_server.py b/lisp/plugins/osc/osc_server.py index 6a69d7188..c02f1d66f 100644 --- a/lisp/plugins/osc/osc_server.py +++ b/lisp/plugins/osc/osc_server.py @@ -1,9 +1,7 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti -# Copyright 2012-2016 Thomas Achtner +# Copyright 2018 Francesco Ceruti +# Copyright 2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/osc/osc_settings.py b/lisp/plugins/osc/osc_settings.py index d42bddf6f..5847dec54 100644 --- a/lisp/plugins/osc/osc_settings.py +++ b/lisp/plugins/osc/osc_settings.py @@ -1,9 +1,7 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti -# Copyright 2012-2016 Thomas Achtner +# Copyright 2018 Francesco Ceruti +# Copyright 2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/presets/lib.py b/lisp/plugins/presets/lib.py index 68698b8f8..c04fa8f06 100644 --- a/lisp/plugins/presets/lib.py +++ b/lisp/plugins/presets/lib.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/presets/presets.py b/lisp/plugins/presets/presets.py index a55defda2..d5a7850ba 100644 --- a/lisp/plugins/presets/presets.py +++ b/lisp/plugins/presets/presets.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/presets/presets_ui.py b/lisp/plugins/presets/presets_ui.py index fb852f3ee..e9770d7ab 100644 --- a/lisp/plugins/presets/presets_ui.py +++ b/lisp/plugins/presets/presets_ui.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/rename_cues/rename_action.py b/lisp/plugins/rename_cues/rename_action.py index ee58a3f34..88589aaa2 100644 --- a/lisp/plugins/rename_cues/rename_action.py +++ b/lisp/plugins/rename_cues/rename_action.py @@ -1,9 +1,7 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # # Copyright 2016-2017 Aurelien Cibrario -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/rename_cues/rename_cues.py b/lisp/plugins/rename_cues/rename_cues.py index 399a16966..f236a1caf 100644 --- a/lisp/plugins/rename_cues/rename_cues.py +++ b/lisp/plugins/rename_cues/rename_cues.py @@ -1,9 +1,7 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # # Copyright 2016-2017 Aurelien Cibrario -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/rename_cues/rename_ui.py b/lisp/plugins/rename_cues/rename_ui.py index 5187bc102..efa140e39 100644 --- a/lisp/plugins/rename_cues/rename_ui.py +++ b/lisp/plugins/rename_cues/rename_ui.py @@ -1,9 +1,7 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # # Copyright 2016-2017 Aurelien Cibrario -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/replay_gain/gain_ui.py b/lisp/plugins/replay_gain/gain_ui.py index dad6eac86..1c076f564 100644 --- a/lisp/plugins/replay_gain/gain_ui.py +++ b/lisp/plugins/replay_gain/gain_ui.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/replay_gain/replay_gain.py b/lisp/plugins/replay_gain/replay_gain.py index 0564f48c1..ec7dab291 100644 --- a/lisp/plugins/replay_gain/replay_gain.py +++ b/lisp/plugins/replay_gain/replay_gain.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/synchronizer/synchronizer.py b/lisp/plugins/synchronizer/synchronizer.py index 4707cc358..b795ff0ea 100644 --- a/lisp/plugins/synchronizer/synchronizer.py +++ b/lisp/plugins/synchronizer/synchronizer.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/timecode/__init__.py b/lisp/plugins/timecode/__init__.py index 28e5d0b1c..035ccc944 100644 --- a/lisp/plugins/timecode/__init__.py +++ b/lisp/plugins/timecode/__init__.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # Copyright 2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify diff --git a/lisp/plugins/timecode/cue_tracker.py b/lisp/plugins/timecode/cue_tracker.py index 8247e2bdc..a4c53d4f4 100644 --- a/lisp/plugins/timecode/cue_tracker.py +++ b/lisp/plugins/timecode/cue_tracker.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # Copyright 2016-2017 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify diff --git a/lisp/plugins/timecode/protocol.py b/lisp/plugins/timecode/protocol.py index b07c1fc01..b55be4515 100644 --- a/lisp/plugins/timecode/protocol.py +++ b/lisp/plugins/timecode/protocol.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # Copyright 2016-2017 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify diff --git a/lisp/plugins/timecode/protocols/__init__.py b/lisp/plugins/timecode/protocols/__init__.py index c8b620bc4..ff05b45d9 100644 --- a/lisp/plugins/timecode/protocols/__init__.py +++ b/lisp/plugins/timecode/protocols/__init__.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/timecode/protocols/artnet.py b/lisp/plugins/timecode/protocols/artnet.py index b79055390..f1bb0f788 100644 --- a/lisp/plugins/timecode/protocols/artnet.py +++ b/lisp/plugins/timecode/protocols/artnet.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # Copyright 2016-2017 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify diff --git a/lisp/plugins/timecode/protocols/midi.py b/lisp/plugins/timecode/protocols/midi.py index 38e0feead..9fc769505 100644 --- a/lisp/plugins/timecode/protocols/midi.py +++ b/lisp/plugins/timecode/protocols/midi.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # Copyright 2016-2017 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify diff --git a/lisp/plugins/timecode/settings.py b/lisp/plugins/timecode/settings.py index 97337547b..836eddac7 100644 --- a/lisp/plugins/timecode/settings.py +++ b/lisp/plugins/timecode/settings.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # Copyright 2016-2017 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify diff --git a/lisp/plugins/timecode/timecode.py b/lisp/plugins/timecode/timecode.py index 11e2a6d0e..77728ca0d 100644 --- a/lisp/plugins/timecode/timecode.py +++ b/lisp/plugins/timecode/timecode.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # Copyright 2016-2017 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify diff --git a/lisp/plugins/triggers/triggers.py b/lisp/plugins/triggers/triggers.py index be0ad02e9..a667cb424 100644 --- a/lisp/plugins/triggers/triggers.py +++ b/lisp/plugins/triggers/triggers.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/triggers/triggers_handler.py b/lisp/plugins/triggers/triggers_handler.py index 0482ac5b4..d69398c2c 100644 --- a/lisp/plugins/triggers/triggers_handler.py +++ b/lisp/plugins/triggers/triggers_handler.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/plugins/triggers/triggers_settings.py b/lisp/plugins/triggers/triggers_settings.py index aa7dcd857..554e9e6b3 100644 --- a/lisp/plugins/triggers/triggers_settings.py +++ b/lisp/plugins/triggers/triggers_settings.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/about.py b/lisp/ui/about.py index 1fb10e385..7b370d2ed 100644 --- a/lisp/ui/about.py +++ b/lisp/ui/about.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/cuelistdialog.py b/lisp/ui/cuelistdialog.py index 70b187e41..9afd49338 100644 --- a/lisp/ui/cuelistdialog.py +++ b/lisp/ui/cuelistdialog.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/icons/__init__.py b/lisp/ui/icons/__init__.py index 0ee44cd26..a00684e0d 100644 --- a/lisp/ui/icons/__init__.py +++ b/lisp/ui/icons/__init__.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/layoutselect.py b/lisp/ui/layoutselect.py index 9544788d2..8b20dd734 100644 --- a/lisp/ui/layoutselect.py +++ b/lisp/ui/layoutselect.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/logging/common.py b/lisp/ui/logging/common.py index aa1b21737..eb68f9539 100644 --- a/lisp/ui/logging/common.py +++ b/lisp/ui/logging/common.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/logging/details.py b/lisp/ui/logging/details.py index 6c7cbfe03..715e28663 100644 --- a/lisp/ui/logging/details.py +++ b/lisp/ui/logging/details.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/logging/dialog.py b/lisp/ui/logging/dialog.py index f05e0dd75..29342b79d 100644 --- a/lisp/ui/logging/dialog.py +++ b/lisp/ui/logging/dialog.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/logging/handler.py b/lisp/ui/logging/handler.py index e26ad113d..a844ad6ca 100644 --- a/lisp/ui/logging/handler.py +++ b/lisp/ui/logging/handler.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/logging/models.py b/lisp/ui/logging/models.py index 8b9f41fde..f38b73516 100644 --- a/lisp/ui/logging/models.py +++ b/lisp/ui/logging/models.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/logging/status.py b/lisp/ui/logging/status.py index 5d2c37bc5..fabbc1586 100644 --- a/lisp/ui/logging/status.py +++ b/lisp/ui/logging/status.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/logging/viewer.py b/lisp/ui/logging/viewer.py index 0d2326256..df2dde86a 100644 --- a/lisp/ui/logging/viewer.py +++ b/lisp/ui/logging/viewer.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index d68c96e4e..cae170c4f 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index a63d6043a..174296f70 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/qmodels.py b/lisp/ui/qmodels.py index b617192bb..cb252bbe5 100644 --- a/lisp/ui/qmodels.py +++ b/lisp/ui/qmodels.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,7 +19,7 @@ from lisp.cues.cue import Cue -# Application defended data-roles +# Application defined data-roles CueClassRole = Qt.UserRole + 1 diff --git a/lisp/ui/settings/app_configuration.py b/lisp/ui/settings/app_configuration.py index 11493186b..18c66ed10 100644 --- a/lisp/ui/settings/app_configuration.py +++ b/lisp/ui/settings/app_configuration.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/settings/app_pages/cue.py b/lisp/ui/settings/app_pages/cue.py index 7c9f81715..6b830b1cc 100644 --- a/lisp/ui/settings/app_pages/cue.py +++ b/lisp/ui/settings/app_pages/cue.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/settings/app_pages/general.py b/lisp/ui/settings/app_pages/general.py index 72fb80fd9..67822ba23 100644 --- a/lisp/ui/settings/app_pages/general.py +++ b/lisp/ui/settings/app_pages/general.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/settings/app_pages/layouts.py b/lisp/ui/settings/app_pages/layouts.py index f3ae9949d..853344023 100644 --- a/lisp/ui/settings/app_pages/layouts.py +++ b/lisp/ui/settings/app_pages/layouts.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/settings/app_pages/plugins.py b/lisp/ui/settings/app_pages/plugins.py index 7ab41c5d9..fb8a54c19 100644 --- a/lisp/ui/settings/app_pages/plugins.py +++ b/lisp/ui/settings/app_pages/plugins.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/settings/cue_pages/cue_appearance.py b/lisp/ui/settings/cue_pages/cue_appearance.py index ef75deede..07fb9f339 100644 --- a/lisp/ui/settings/cue_pages/cue_appearance.py +++ b/lisp/ui/settings/cue_pages/cue_appearance.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/settings/cue_pages/cue_general.py b/lisp/ui/settings/cue_pages/cue_general.py index f05edcbe2..9643e80c8 100644 --- a/lisp/ui/settings/cue_pages/cue_general.py +++ b/lisp/ui/settings/cue_pages/cue_general.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/settings/cue_pages/media_cue.py b/lisp/ui/settings/cue_pages/media_cue.py index dd6b6decf..fcfdb6138 100644 --- a/lisp/ui/settings/cue_pages/media_cue.py +++ b/lisp/ui/settings/cue_pages/media_cue.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/settings/cue_settings.py b/lisp/ui/settings/cue_settings.py index 5701da120..4489cbe07 100644 --- a/lisp/ui/settings/cue_settings.py +++ b/lisp/ui/settings/cue_settings.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/settings/pages.py b/lisp/ui/settings/pages.py index 4c8f1b24e..edf3f627f 100644 --- a/lisp/ui/settings/pages.py +++ b/lisp/ui/settings/pages.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/themes/dark/assetes.py b/lisp/ui/themes/dark/assetes.py index d552717e8..6d380be7a 100644 --- a/lisp/ui/themes/dark/assetes.py +++ b/lisp/ui/themes/dark/assetes.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- - -# Resource object code + Resource object code # # Created by: The Resource Compiler for PyQt5 (Qt v5.5.1) # diff --git a/lisp/ui/themes/dark/dark.py b/lisp/ui/themes/dark/dark.py index 764939f07..6427b3067 100644 --- a/lisp/ui/themes/dark/dark.py +++ b/lisp/ui/themes/dark/dark.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/themes/dark/theme.qss b/lisp/ui/themes/dark/theme.qss index 18396375a..81035aca4 100644 --- a/lisp/ui/themes/dark/theme.qss +++ b/lisp/ui/themes/dark/theme.qss @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 Francesco Ceruti + * Copyright 2018 Francesco Ceruti * * This file is part of LiSP (Linux Show Player). * diff --git a/lisp/ui/ui_utils.py b/lisp/ui/ui_utils.py index 693def951..d2c0b7cbb 100644 --- a/lisp/ui/ui_utils.py +++ b/lisp/ui/ui_utils.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/widgets/cue_actions.py b/lisp/ui/widgets/cue_actions.py index 6babf246a..8e2850a38 100644 --- a/lisp/ui/widgets/cue_actions.py +++ b/lisp/ui/widgets/cue_actions.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/widgets/cue_next_actions.py b/lisp/ui/widgets/cue_next_actions.py index f464e9612..5eecc0832 100644 --- a/lisp/ui/widgets/cue_next_actions.py +++ b/lisp/ui/widgets/cue_next_actions.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/widgets/fades.py b/lisp/ui/widgets/fades.py index 23aad7434..31443a69a 100644 --- a/lisp/ui/widgets/fades.py +++ b/lisp/ui/widgets/fades.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/widgets/pagestreewidget.py b/lisp/ui/widgets/pagestreewidget.py index d4f46b165..e4339a1b7 100644 --- a/lisp/ui/widgets/pagestreewidget.py +++ b/lisp/ui/widgets/pagestreewidget.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/widgets/qclicklabel.py b/lisp/ui/widgets/qclicklabel.py index 76cb80797..f10a25618 100644 --- a/lisp/ui/widgets/qclicklabel.py +++ b/lisp/ui/widgets/qclicklabel.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/widgets/qclickslider.py b/lisp/ui/widgets/qclickslider.py index 6853960d2..adbbf51fd 100644 --- a/lisp/ui/widgets/qclickslider.py +++ b/lisp/ui/widgets/qclickslider.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/widgets/qcolorbutton.py b/lisp/ui/widgets/qcolorbutton.py index 9203e38f7..bfed84a76 100644 --- a/lisp/ui/widgets/qcolorbutton.py +++ b/lisp/ui/widgets/qcolorbutton.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/widgets/qdbmeter.py b/lisp/ui/widgets/qdbmeter.py index 53414608a..f96a22220 100644 --- a/lisp/ui/widgets/qdbmeter.py +++ b/lisp/ui/widgets/qdbmeter.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/widgets/qeditabletabbar.py b/lisp/ui/widgets/qeditabletabbar.py index 8118533bb..4ea48c37a 100644 --- a/lisp/ui/widgets/qeditabletabbar.py +++ b/lisp/ui/widgets/qeditabletabbar.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/widgets/qiconpushbutton.py b/lisp/ui/widgets/qiconpushbutton.py index 8a56a2900..e2c1b9294 100644 --- a/lisp/ui/widgets/qiconpushbutton.py +++ b/lisp/ui/widgets/qiconpushbutton.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2017 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/widgets/qmessagebox.py b/lisp/ui/widgets/qmessagebox.py index 54897a1c9..c1e525113 100644 --- a/lisp/ui/widgets/qmessagebox.py +++ b/lisp/ui/widgets/qmessagebox.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/widgets/qmutebutton.py b/lisp/ui/widgets/qmutebutton.py index 051ab6a41..10741ba98 100644 --- a/lisp/ui/widgets/qmutebutton.py +++ b/lisp/ui/widgets/qmutebutton.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/widgets/qprogresswheel.py b/lisp/ui/widgets/qprogresswheel.py index ca642b7cf..cd49eed36 100644 --- a/lisp/ui/widgets/qprogresswheel.py +++ b/lisp/ui/widgets/qprogresswheel.py @@ -1,11 +1,9 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # # This file contains a PyQt5 version of QProgressIndicator.cpp from # https://github.com/mojocorp/QProgressIndicator # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/widgets/qsteptimeedit.py b/lisp/ui/widgets/qsteptimeedit.py index 56da6bdfe..63619067c 100644 --- a/lisp/ui/widgets/qsteptimeedit.py +++ b/lisp/ui/widgets/qsteptimeedit.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/widgets/qstyledslider.py b/lisp/ui/widgets/qstyledslider.py index 8f00b6f3b..585f27852 100644 --- a/lisp/ui/widgets/qstyledslider.py +++ b/lisp/ui/widgets/qstyledslider.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/lisp/ui/widgets/qvertiacallabel.py b/lisp/ui/widgets/qvertiacallabel.py index 08af8c493..bbf84914c 100644 --- a/lisp/ui/widgets/qvertiacallabel.py +++ b/lisp/ui/widgets/qvertiacallabel.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2016 Francesco Ceruti +# Copyright 2016 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/plugins_utils.py b/plugins_utils.py index 34934b2ee..1b1c1ab30 100755 --- a/plugins_utils.py +++ b/plugins_utils.py @@ -1,9 +1,8 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # This file is part of Linux Show Player # -# Copyright 2012-2017 Francesco Ceruti +# Copyright 2018 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From 24d70cac93841056ef4e57b23b0de757dfd10866 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 5 Jan 2019 10:56:31 +0100 Subject: [PATCH 151/333] New Crowdin translations (#123) --- lisp/i18n/ts/cs_CZ/action_cues.ts | 399 +++++--- lisp/i18n/ts/cs_CZ/cart_layout.ts | 176 ++++ lisp/i18n/ts/cs_CZ/controller.ts | 221 +++-- lisp/i18n/ts/cs_CZ/gst_backend.ts | 470 +++++----- lisp/i18n/ts/cs_CZ/lisp.ts | 1383 ++++++++++++++++------------ lisp/i18n/ts/cs_CZ/list_layout.ts | 189 ++++ lisp/i18n/ts/cs_CZ/media_info.ts | 45 +- lisp/i18n/ts/cs_CZ/midi.ts | 38 +- lisp/i18n/ts/cs_CZ/network.ts | 47 + lisp/i18n/ts/cs_CZ/osc.ts | 35 + lisp/i18n/ts/cs_CZ/presets.ts | 180 ++-- lisp/i18n/ts/cs_CZ/rename_cues.ts | 95 ++ lisp/i18n/ts/cs_CZ/replay_gain.ts | 64 +- lisp/i18n/ts/cs_CZ/synchronizer.ts | 104 ++- lisp/i18n/ts/cs_CZ/timecode.ts | 129 ++- lisp/i18n/ts/cs_CZ/triggers.ts | 78 +- lisp/i18n/ts/de_DE/action_cues.ts | 345 +++++++ lisp/i18n/ts/de_DE/cart_layout.ts | 176 ++++ lisp/i18n/ts/de_DE/controller.ts | 198 ++++ lisp/i18n/ts/de_DE/gst_backend.ts | 388 ++++++++ lisp/i18n/ts/de_DE/lisp.ts | 1180 ++++++++++++++++++++++++ lisp/i18n/ts/de_DE/list_layout.ts | 189 ++++ lisp/i18n/ts/de_DE/media_info.ts | 37 + lisp/i18n/ts/de_DE/midi.ts | 30 + lisp/i18n/ts/de_DE/network.ts | 47 + lisp/i18n/ts/de_DE/osc.ts | 35 + lisp/i18n/ts/de_DE/presets.ts | 150 +++ lisp/i18n/ts/de_DE/rename_cues.ts | 95 ++ lisp/i18n/ts/de_DE/replay_gain.ts | 52 ++ lisp/i18n/ts/de_DE/synchronizer.ts | 85 ++ lisp/i18n/ts/de_DE/timecode.ts | 110 +++ lisp/i18n/ts/de_DE/triggers.ts | 63 ++ lisp/i18n/ts/es_ES/action_cues.ts | 399 +++++--- lisp/i18n/ts/es_ES/cart_layout.ts | 176 ++++ lisp/i18n/ts/es_ES/controller.ts | 221 +++-- lisp/i18n/ts/es_ES/gst_backend.ts | 470 +++++----- lisp/i18n/ts/es_ES/lisp.ts | 1383 ++++++++++++++++------------ lisp/i18n/ts/es_ES/list_layout.ts | 189 ++++ lisp/i18n/ts/es_ES/media_info.ts | 45 +- lisp/i18n/ts/es_ES/midi.ts | 38 +- lisp/i18n/ts/es_ES/network.ts | 47 + lisp/i18n/ts/es_ES/osc.ts | 35 + lisp/i18n/ts/es_ES/presets.ts | 180 ++-- lisp/i18n/ts/es_ES/rename_cues.ts | 95 ++ lisp/i18n/ts/es_ES/replay_gain.ts | 64 +- lisp/i18n/ts/es_ES/synchronizer.ts | 104 ++- lisp/i18n/ts/es_ES/timecode.ts | 129 ++- lisp/i18n/ts/es_ES/triggers.ts | 78 +- lisp/i18n/ts/fr_FR/action_cues.ts | 399 +++++--- lisp/i18n/ts/fr_FR/cart_layout.ts | 176 ++++ lisp/i18n/ts/fr_FR/controller.ts | 221 +++-- lisp/i18n/ts/fr_FR/gst_backend.ts | 470 +++++----- lisp/i18n/ts/fr_FR/lisp.ts | 1383 ++++++++++++++++------------ lisp/i18n/ts/fr_FR/list_layout.ts | 189 ++++ lisp/i18n/ts/fr_FR/media_info.ts | 45 +- lisp/i18n/ts/fr_FR/midi.ts | 38 +- lisp/i18n/ts/fr_FR/network.ts | 47 + lisp/i18n/ts/fr_FR/osc.ts | 35 + lisp/i18n/ts/fr_FR/presets.ts | 180 ++-- lisp/i18n/ts/fr_FR/rename_cues.ts | 95 ++ lisp/i18n/ts/fr_FR/replay_gain.ts | 64 +- lisp/i18n/ts/fr_FR/synchronizer.ts | 104 ++- lisp/i18n/ts/fr_FR/timecode.ts | 129 ++- lisp/i18n/ts/fr_FR/triggers.ts | 78 +- lisp/i18n/ts/it_IT/action_cues.ts | 399 +++++--- lisp/i18n/ts/it_IT/cart_layout.ts | 176 ++++ lisp/i18n/ts/it_IT/controller.ts | 221 +++-- lisp/i18n/ts/it_IT/gst_backend.ts | 470 +++++----- lisp/i18n/ts/it_IT/lisp.ts | 1383 ++++++++++++++++------------ lisp/i18n/ts/it_IT/list_layout.ts | 189 ++++ lisp/i18n/ts/it_IT/media_info.ts | 45 +- lisp/i18n/ts/it_IT/midi.ts | 38 +- lisp/i18n/ts/it_IT/network.ts | 47 + lisp/i18n/ts/it_IT/osc.ts | 35 + lisp/i18n/ts/it_IT/presets.ts | 180 ++-- lisp/i18n/ts/it_IT/rename_cues.ts | 95 ++ lisp/i18n/ts/it_IT/replay_gain.ts | 64 +- lisp/i18n/ts/it_IT/synchronizer.ts | 104 ++- lisp/i18n/ts/it_IT/timecode.ts | 129 ++- lisp/i18n/ts/it_IT/triggers.ts | 78 +- lisp/i18n/ts/nl_BE/action_cues.ts | 345 +++++++ lisp/i18n/ts/nl_BE/cart_layout.ts | 176 ++++ lisp/i18n/ts/nl_BE/controller.ts | 198 ++++ lisp/i18n/ts/nl_BE/gst_backend.ts | 388 ++++++++ lisp/i18n/ts/nl_BE/lisp.ts | 1180 ++++++++++++++++++++++++ lisp/i18n/ts/nl_BE/list_layout.ts | 189 ++++ lisp/i18n/ts/nl_BE/media_info.ts | 37 + lisp/i18n/ts/nl_BE/midi.ts | 30 + lisp/i18n/ts/nl_BE/network.ts | 47 + lisp/i18n/ts/nl_BE/osc.ts | 35 + lisp/i18n/ts/nl_BE/presets.ts | 150 +++ lisp/i18n/ts/nl_BE/rename_cues.ts | 95 ++ lisp/i18n/ts/nl_BE/replay_gain.ts | 52 ++ lisp/i18n/ts/nl_BE/synchronizer.ts | 85 ++ lisp/i18n/ts/nl_BE/timecode.ts | 110 +++ lisp/i18n/ts/nl_BE/triggers.ts | 63 ++ lisp/i18n/ts/nl_NL/action_cues.ts | 345 +++++++ lisp/i18n/ts/nl_NL/cart_layout.ts | 176 ++++ lisp/i18n/ts/nl_NL/controller.ts | 198 ++++ lisp/i18n/ts/nl_NL/gst_backend.ts | 388 ++++++++ lisp/i18n/ts/nl_NL/lisp.ts | 1180 ++++++++++++++++++++++++ lisp/i18n/ts/nl_NL/list_layout.ts | 189 ++++ lisp/i18n/ts/nl_NL/media_info.ts | 37 + lisp/i18n/ts/nl_NL/midi.ts | 30 + lisp/i18n/ts/nl_NL/network.ts | 47 + lisp/i18n/ts/nl_NL/osc.ts | 35 + lisp/i18n/ts/nl_NL/presets.ts | 150 +++ lisp/i18n/ts/nl_NL/rename_cues.ts | 95 ++ lisp/i18n/ts/nl_NL/replay_gain.ts | 52 ++ lisp/i18n/ts/nl_NL/synchronizer.ts | 85 ++ lisp/i18n/ts/nl_NL/timecode.ts | 110 +++ lisp/i18n/ts/nl_NL/triggers.ts | 63 ++ lisp/i18n/ts/sl_SI/action_cues.ts | 399 +++++--- lisp/i18n/ts/sl_SI/cart_layout.ts | 176 ++++ lisp/i18n/ts/sl_SI/controller.ts | 221 +++-- lisp/i18n/ts/sl_SI/gst_backend.ts | 470 +++++----- lisp/i18n/ts/sl_SI/lisp.ts | 1383 ++++++++++++++++------------ lisp/i18n/ts/sl_SI/list_layout.ts | 189 ++++ lisp/i18n/ts/sl_SI/media_info.ts | 45 +- lisp/i18n/ts/sl_SI/midi.ts | 38 +- lisp/i18n/ts/sl_SI/network.ts | 47 + lisp/i18n/ts/sl_SI/osc.ts | 35 + lisp/i18n/ts/sl_SI/presets.ts | 180 ++-- lisp/i18n/ts/sl_SI/rename_cues.ts | 95 ++ lisp/i18n/ts/sl_SI/replay_gain.ts | 64 +- lisp/i18n/ts/sl_SI/synchronizer.ts | 104 ++- lisp/i18n/ts/sl_SI/timecode.ts | 129 ++- lisp/i18n/ts/sl_SI/triggers.ts | 78 +- 128 files changed, 21300 insertions(+), 6505 deletions(-) create mode 100644 lisp/i18n/ts/cs_CZ/cart_layout.ts create mode 100644 lisp/i18n/ts/cs_CZ/list_layout.ts create mode 100644 lisp/i18n/ts/cs_CZ/network.ts create mode 100644 lisp/i18n/ts/cs_CZ/osc.ts create mode 100644 lisp/i18n/ts/cs_CZ/rename_cues.ts create mode 100644 lisp/i18n/ts/de_DE/action_cues.ts create mode 100644 lisp/i18n/ts/de_DE/cart_layout.ts create mode 100644 lisp/i18n/ts/de_DE/controller.ts create mode 100644 lisp/i18n/ts/de_DE/gst_backend.ts create mode 100644 lisp/i18n/ts/de_DE/lisp.ts create mode 100644 lisp/i18n/ts/de_DE/list_layout.ts create mode 100644 lisp/i18n/ts/de_DE/media_info.ts create mode 100644 lisp/i18n/ts/de_DE/midi.ts create mode 100644 lisp/i18n/ts/de_DE/network.ts create mode 100644 lisp/i18n/ts/de_DE/osc.ts create mode 100644 lisp/i18n/ts/de_DE/presets.ts create mode 100644 lisp/i18n/ts/de_DE/rename_cues.ts create mode 100644 lisp/i18n/ts/de_DE/replay_gain.ts create mode 100644 lisp/i18n/ts/de_DE/synchronizer.ts create mode 100644 lisp/i18n/ts/de_DE/timecode.ts create mode 100644 lisp/i18n/ts/de_DE/triggers.ts create mode 100644 lisp/i18n/ts/es_ES/cart_layout.ts create mode 100644 lisp/i18n/ts/es_ES/list_layout.ts create mode 100644 lisp/i18n/ts/es_ES/network.ts create mode 100644 lisp/i18n/ts/es_ES/osc.ts create mode 100644 lisp/i18n/ts/es_ES/rename_cues.ts create mode 100644 lisp/i18n/ts/fr_FR/cart_layout.ts create mode 100644 lisp/i18n/ts/fr_FR/list_layout.ts create mode 100644 lisp/i18n/ts/fr_FR/network.ts create mode 100644 lisp/i18n/ts/fr_FR/osc.ts create mode 100644 lisp/i18n/ts/fr_FR/rename_cues.ts create mode 100644 lisp/i18n/ts/it_IT/cart_layout.ts create mode 100644 lisp/i18n/ts/it_IT/list_layout.ts create mode 100644 lisp/i18n/ts/it_IT/network.ts create mode 100644 lisp/i18n/ts/it_IT/osc.ts create mode 100644 lisp/i18n/ts/it_IT/rename_cues.ts create mode 100644 lisp/i18n/ts/nl_BE/action_cues.ts create mode 100644 lisp/i18n/ts/nl_BE/cart_layout.ts create mode 100644 lisp/i18n/ts/nl_BE/controller.ts create mode 100644 lisp/i18n/ts/nl_BE/gst_backend.ts create mode 100644 lisp/i18n/ts/nl_BE/lisp.ts create mode 100644 lisp/i18n/ts/nl_BE/list_layout.ts create mode 100644 lisp/i18n/ts/nl_BE/media_info.ts create mode 100644 lisp/i18n/ts/nl_BE/midi.ts create mode 100644 lisp/i18n/ts/nl_BE/network.ts create mode 100644 lisp/i18n/ts/nl_BE/osc.ts create mode 100644 lisp/i18n/ts/nl_BE/presets.ts create mode 100644 lisp/i18n/ts/nl_BE/rename_cues.ts create mode 100644 lisp/i18n/ts/nl_BE/replay_gain.ts create mode 100644 lisp/i18n/ts/nl_BE/synchronizer.ts create mode 100644 lisp/i18n/ts/nl_BE/timecode.ts create mode 100644 lisp/i18n/ts/nl_BE/triggers.ts create mode 100644 lisp/i18n/ts/nl_NL/action_cues.ts create mode 100644 lisp/i18n/ts/nl_NL/cart_layout.ts create mode 100644 lisp/i18n/ts/nl_NL/controller.ts create mode 100644 lisp/i18n/ts/nl_NL/gst_backend.ts create mode 100644 lisp/i18n/ts/nl_NL/lisp.ts create mode 100644 lisp/i18n/ts/nl_NL/list_layout.ts create mode 100644 lisp/i18n/ts/nl_NL/media_info.ts create mode 100644 lisp/i18n/ts/nl_NL/midi.ts create mode 100644 lisp/i18n/ts/nl_NL/network.ts create mode 100644 lisp/i18n/ts/nl_NL/osc.ts create mode 100644 lisp/i18n/ts/nl_NL/presets.ts create mode 100644 lisp/i18n/ts/nl_NL/rename_cues.ts create mode 100644 lisp/i18n/ts/nl_NL/replay_gain.ts create mode 100644 lisp/i18n/ts/nl_NL/synchronizer.ts create mode 100644 lisp/i18n/ts/nl_NL/timecode.ts create mode 100644 lisp/i18n/ts/nl_NL/triggers.ts create mode 100644 lisp/i18n/ts/sl_SI/cart_layout.ts create mode 100644 lisp/i18n/ts/sl_SI/list_layout.ts create mode 100644 lisp/i18n/ts/sl_SI/network.ts create mode 100644 lisp/i18n/ts/sl_SI/osc.ts create mode 100644 lisp/i18n/ts/sl_SI/rename_cues.ts diff --git a/lisp/i18n/ts/cs_CZ/action_cues.ts b/lisp/i18n/ts/cs_CZ/action_cues.ts index 78d22733d..a2f8a0827 100644 --- a/lisp/i18n/ts/cs_CZ/action_cues.ts +++ b/lisp/i18n/ts/cs_CZ/action_cues.ts @@ -1,244 +1,345 @@ - - + + + + CollectionCue - - Add - Přidat + + Add + Přidat - - Remove - Odstranit + + Remove + Odstranit - - Cue - Narážka + + Cue + Narážka - - Action - Činnost + + Action + Činnost - - + + CommandCue - - Process ended with an error status. - Postup skončil s chybovým stavem. + + Process ended with an error status. + Postup skončil s chybovým stavem. - - Exit code: - Kód ukončení: + + Exit code: + Kód ukončení: - - Command - Příkaz + + Command + Příkaz - - Command to execute, as in a shell - Příkaz k provedení, jako v terminálu + + Command to execute, as in a shell + Příkaz k provedení, jako v terminálu - - Discard command output - Zahodit výstup příkazu + + Discard command output + Zahodit výstup příkazu - - Ignore command errors - Přehlížet chyby příkazu + + Ignore command errors + Přehlížet chyby příkazu - - Kill instead of terminate - Zabít namísto ukončit + + Kill instead of terminate + Zabít namísto ukončit - - + + + Command cue ended with an error status. Exit code: {} + Command cue ended with an error status. Exit code: {} + + + + Cue Name + + + OSC Settings + OSC Settings + + + CueName - - Command Cue - Narážka příkazu + + Command Cue + Narážka příkazu - - MIDI Cue - Narážka MIDI + + MIDI Cue + Narážka MIDI - - Volume Control - Ovládání hlasitosti + + Volume Control + Ovládání hlasitosti - - Seek Cue - Narážka prohledávání + + Seek Cue + Narážka prohledávání - - Collection Cue - Narážka sbírky + + Collection Cue + Narážka sbírky - - Stop-All - Zastavit vše + + Stop-All + Zastavit vše - - Index Action - Činnost indexu + + Index Action + Činnost indexu - - + + + OSC Cue + OSC Cue + + + IndexActionCue - - Index - Index + + Index + Index + + + + Use a relative index + Použít poměrný index + + + + Target index + Cílový index - - Use a relative index - Použít poměrný index + + Action + Činnost - - Target index - Cílový index + + No suggestion + No suggestion - - Action - Činnost + + Suggested cue name + Suggested cue name - - + + MIDICue - - MIDI Message - Zpráva MIDI + + MIDI Message + Zpráva MIDI + + + + Message type + Typ zprávy + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OscCue + + + Error during cue execution. + Error during cue execution. + + + + OSC Message + OSC Message + + + + Add + Add + + + + Remove + Remove + + + + Test + Test + + + + OSC Path: (example: "/path/to/something") + OSC Path: (example: "/path/to/something") + + + + Fade + Fade + + + + Time (sec) + Time (sec) - - Message type - Typ zprávy + + Curve + Curve - - + + SeekCue - - Cue - Narážka + + Cue + Narážka - - Click to select - Klepnout pro vybrání + + Click to select + Klepnout pro vybrání - - Not selected - Nevybráno + + Not selected + Nevybráno - - Seek - Hledat + + Seek + Hledat - - Time to reach - Čas k dosažení + + Time to reach + Čas k dosažení - - + + SettingsPageName - - Command - Příkaz + + Command + Příkaz - - MIDI Settings - Nastavení MIDI + + MIDI Settings + Nastavení MIDI - - Volume Settings - Nastavení hlasitosti + + Volume Settings + Nastavení hlasitosti - - Seek Settings - Nastavení prohledávání + + Seek Settings + Nastavení prohledávání - - Edit Collection - Upravit sbírku + + Edit Collection + Upravit sbírku - - Action Settings - Nastavení činnosti + + Action Settings + Nastavení činnosti - - Stop Settings - Nastavení zastavení + + Stop Settings + Nastavení zastavení - - + + StopAll - - Stop Action - Činnost zastavení + + Stop Action + Činnost zastavení - - + + VolumeControl - - Error during cue execution - Chyba během provádění narážky + + Error during cue execution + Chyba během provádění narážky - - Cue - Narážka + + Cue + Narážka - - Click to select - Klepnout pro vybrání + + Click to select + Klepnout pro vybrání - - Not selected - Nevybráno + + Not selected + Nevybráno - - Volume to reach - Hlasitost k dosažení + + Volume to reach + Hlasitost k dosažení - - Fade - Prolínat + + Fade + Prolínat - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/cs_CZ/cart_layout.ts b/lisp/i18n/ts/cs_CZ/cart_layout.ts new file mode 100644 index 000000000..0f5e79c01 --- /dev/null +++ b/lisp/i18n/ts/cs_CZ/cart_layout.ts @@ -0,0 +1,176 @@ + + + + + CartLayout + + + Default behaviors + Default behaviors + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show volume + Show volume + + + + Automatically add new page + Automatically add new page + + + + Grid size + Grid size + + + + Number of columns + Number of columns + + + + Number of rows + Number of rows + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Number of Pages: + Number of Pages: + + + + Page {number} + Page {number} + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + LayoutDescription + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + LayoutDetails + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them while pressing SHIFT + To move cues drag them while pressing SHIFT + + + + ListLayout + + + Edit cue + Edit cue + + + + Edit selected cues + Edit selected cues + + + + Remove cue + Remove cue + + + + Remove selected cues + Remove selected cues + + + diff --git a/lisp/i18n/ts/cs_CZ/controller.ts b/lisp/i18n/ts/cs_CZ/controller.ts index 4ceaca1fe..031e5d7fd 100644 --- a/lisp/i18n/ts/cs_CZ/controller.ts +++ b/lisp/i18n/ts/cs_CZ/controller.ts @@ -1,99 +1,198 @@ - - + + + + + Controller + + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + ControllerKeySettings - - Key - Klávesa + + Key + Klávesa - - Action - Činnost + + Action + Činnost - - Shortcuts - Klávesové zkratky + + Shortcuts + Klávesové zkratky - - + + ControllerMidiSettings - - MIDI - MIDI + + MIDI + MIDI - - Type - Typ + + Type + Typ - - Channel - Kanál + + Channel + Kanál - - Note - Nota + + Note + Nota - - Action - Činnost + + Action + Činnost - - Filter "note on" - Filtr "nota zapnuta" + + Filter "note on" + Filtr "nota zapnuta" - - Filter "note off" - Filtr (nota vypnuta" + + Filter "note off" + Filtr (nota vypnuta" - - Capture - Zachytávání + + Capture + Zachytávání - - Listening MIDI messages ... - Naslouchá se zprávám MIDI... + + Listening MIDI messages ... + Naslouchá se zprávám MIDI... - - + + + ControllerOscSettings + + + OSC Message + OSC Message + + + + OSC Path: (example: "/path/to/something") + OSC Path: (example: "/path/to/something") + + + + OSC + OSC + + + + Path + Path + + + + Types + Types + + + + Arguments + Arguments + + + + Actions + Actions + + + + OSC Capture + OSC Capture + + + + Add + Add + + + + Remove + Remove + + + + Capture + Capture + + + ControllerSettings - - Add - Přidat + + Add + Přidat + + + + Remove + Odstranit + + + Osc Cue - - Remove - Odstranit + + Type + Type - - + + + Argument + Argument + + + + OscCue + + + Add + Add + + + + Remove + Remove + + + SettingsPageName - - Cue Control - Ovládání narážky + + Cue Control + Ovládání narážky + + + + MIDI Controls + Ovládání MIDI - - MIDI Controls - Ovládání MIDI + + Keyboard Shortcuts + Klávesové zkratky - - Keyboard Shortcuts - Klávesové zkratky + + OSC Controls + OSC Controls - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/cs_CZ/gst_backend.ts b/lisp/i18n/ts/cs_CZ/gst_backend.ts index 2066d434b..087934f5a 100644 --- a/lisp/i18n/ts/cs_CZ/gst_backend.ts +++ b/lisp/i18n/ts/cs_CZ/gst_backend.ts @@ -1,368 +1,388 @@ - - + + + + AlsaSinkSettings - - ALSA device - Zařízení ALSA + + ALSA device + Zařízení ALSA - - ALSA devices, as defined in an asound configuration file - Zařízení ALSA, jak jsou stanovena v souboru s nastavením asound + + ALSA devices, as defined in an asound configuration file + Zařízení ALSA, jak jsou stanovena v souboru s nastavením asound - - + + AudioDynamicSettings - - Compressor - Stlačovač + + Compressor + Stlačovač - - Expander - Rozpínač + + Expander + Rozpínač - - Soft Knee - Měkké koleno + + Soft Knee + Měkké koleno - - Hard Knee - Tvrdé koleno + + Hard Knee + Tvrdé koleno - - Compressor/Expander - Stlačovač/Rozpínač + + Compressor/Expander + Stlačovač/Rozpínač - - Type - Typ + + Type + Typ - - Curve Shape - Tvar křivky + + Curve Shape + Tvar křivky - - Ratio - Poměr + + Ratio + Poměr - - Threshold (dB) - Práh (dB) + + Threshold (dB) + Práh (dB) - - + + AudioPanSettings - - Audio Pan - Vyvážení zvuku + + Audio Pan + Vyvážení zvuku - - Center - Na střed + + Center + Na střed - - Left - Vlevo + + Left + Vlevo - - Right - Vpravo + + Right + Vpravo - - + + DbMeterSettings - - DbMeter settings - Nastavení měřidla decibelů + + DbMeter settings + Nastavení měřidla decibelů - - Time between levels (ms) - Čas mezi hladinami (ms) + + Time between levels (ms) + Čas mezi hladinami (ms) - - Peak ttl (ms) - Ttl vrcholu (ms) + + Peak ttl (ms) + Ttl vrcholu (ms) - - Peak falloff (dB/sec) - Opadávání vrcholu (dB/s) + + Peak falloff (dB/sec) + Opadávání vrcholu (dB/s) - - + + Equalizer10Settings - - 10 Bands Equalizer (IIR) - Desetipásmový ekvalizér (IIR) + + 10 Bands Equalizer (IIR) + Desetipásmový ekvalizér (IIR) - - + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + GstMediaSettings - - Change Pipeline - Změnit komunikační spojení + + Change Pipeline + Změnit komunikační spojení - - + + GstPipelineEdit - - Edit Pipeline - Upravit komunikační spojení + + Edit Pipeline + Upravit komunikační spojení - - + + GstSettings - - Pipeline - Komunikační spojení + + Pipeline + Komunikační spojení - - + + JackSinkSettings - - Connections - Spojení + + Connections + Spojení - - Edit connections - Upravit spojení + + Edit connections + Upravit spojení - - Output ports - Výstupní přípojky + + Output ports + Výstupní přípojky - - Input ports - Vstupní přípojky + + Input ports + Vstupní přípojky - - Connect - Spojit + + Connect + Spojit - - Disconnect - Odpojit + + Disconnect + Odpojit - - + + MediaElementName - - Compressor/Expander - Stlačovač/Rozpínač + + Compressor/Expander + Stlačovač/Rozpínač - - Audio Pan - Vyvážení zvuku + + Audio Pan + Vyvážení zvuku - - PulseAudio Out - Výstup PulseAudio + + PulseAudio Out + Výstup PulseAudio - - Volume - Hlasitost + + Volume + Hlasitost - - dB Meter - Měřidlo decibelů + + dB Meter + Měřidlo decibelů - - System Input - Vstup systému + + System Input + Vstup systému - - ALSA Out - Výstup ALSA + + ALSA Out + Výstup ALSA - - JACK Out - Výstup JACK + + JACK Out + Výstup JACK - - Custom Element - Vlastní prvek + + Custom Element + Vlastní prvek - - System Out - Výstup systému + + System Out + Výstup systému - - Pitch - Výška tónu + + Pitch + Výška tónu - - URI Input - Vstup URI + + URI Input + Vstup URI - - 10 Bands Equalizer - Desetipásmový ekvalizér + + 10 Bands Equalizer + Desetipásmový ekvalizér - - Speed - Rychlost + + Speed + Rychlost - - Preset Input - Vstup přednastavení + + Preset Input + Vstup přednastavení - - + + PitchSettings - - Pitch - Výška tónu + + Pitch + Výška tónu - - {0:+} semitones - Půltóny {0:+} + + {0:+} semitones + Půltóny {0:+} - - + + PresetSrcSettings - - Presets - Přednastavení + + Presets + Přednastavení - - + + SettingsPageName - - GStreamer settings - Nastavení pro GStreamer + + GStreamer settings + Nastavení pro GStreamer + + + + Media Settings + Nastavení médií - - Media Settings - Nastavení médií + + GStreamer + GStreamer - - + + SpeedSettings - - Speed - Rychlost + + Speed + Rychlost - - + + UriInputSettings - - Source - Zdroj + + Source + Zdroj - - Find File - Najít soubor + + Find File + Najít soubor - - Buffering - Ukládání do vyrovnávací paměti + + Buffering + Ukládání do vyrovnávací paměti - - Use Buffering - Použít ukládání do vyrovnávací paměti + + Use Buffering + Použít ukládání do vyrovnávací paměti - - Attempt download on network streams - Pokusit se o stažení na síťových proudech + + Attempt download on network streams + Pokusit se o stažení na síťových proudech - - Buffer size (-1 default value) - Velikost vyrovnávací paměti (-1 výchozí hodnota) + + Buffer size (-1 default value) + Velikost vyrovnávací paměti (-1 výchozí hodnota) - - Choose file - Vybrat soubor + + Choose file + Vybrat soubor - - All files - Všechny soubory + + All files + Všechny soubory - - + + UserElementSettings - - User defined elements - Uživatelem stanovené prvky + + User defined elements + Uživatelem stanovené prvky - - Only for advanced user! - Jen pro pokročilého uživatele! + + Only for advanced user! + Jen pro pokročilého uživatele! - - + + VolumeSettings - - Volume - Hlasitost + + Volume + Hlasitost - - Normalized volume - Normalizovaná hlasitost + + Normalized volume + Normalizovaná hlasitost - - Reset - Nastavit znovu + + Reset + Nastavit znovu - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/cs_CZ/lisp.ts b/lisp/i18n/ts/cs_CZ/lisp.ts index 4230fb794..c7056ba0a 100644 --- a/lisp/i18n/ts/cs_CZ/lisp.ts +++ b/lisp/i18n/ts/cs_CZ/lisp.ts @@ -1,947 +1,1180 @@ - - + + + + About - - Authors - Autoři + + Authors + Autoři - - Contributors - Přispěvatelé + + Contributors + Přispěvatelé - - Translators - Překladatelé + + Translators + Překladatelé - - About Linux Show Player - O programu Linux Show Player + + About Linux Show Player + O programu Linux Show Player - - + + AboutDialog - - Linux Show Player is a cue-player designed for stage productions. - Linux Show Player je přehrávač záznamů s narážkami navržený pro výrobu scén. + + Linux Show Player is a cue-player designed for stage productions. + Linux Show Player je přehrávač záznamů s narážkami navržený pro výrobu scén. - - Web site - Stránky + + Web site + Stránky - - Users group - Uživatelská skupina + + Users group + Uživatelská skupina - - Source code - Zdrojový kód + + Source code + Zdrojový kód - - Info - Informace + + Info + Informace - - License - Licence + + License + Licence - - Contributors - Přispěvatelé + + Contributors + Přispěvatelé - - - AppGeneralSettings - - Startup layout - Rozvržení při spuštění + + Discussion + Discussion + + + Actions - - Use startup dialog - Použít dialog při spuštění + + Undo: {} + Undo: {} - - Application theme - Motiv programu + + Redo: {} + Redo: {} - - + + AppConfiguration - - LiSP preferences - Nastavení LiSP + + LiSP preferences + Nastavení LiSP + + + + AppGeneralSettings + + + Startup layout + Rozvržení při spuštění + + + + Use startup dialog + Použít dialog při spuštění + + + + Application theme + Motiv programu + + + + Default layout + Default layout + + + + Enable startup layout selector + Enable startup layout selector - - + + + Application themes + Application themes + + + + UI theme + UI theme + + + + Icons theme + Icons theme + + + CartLayout - - Add page - Přidat stranu + + Add page + Přidat stranu - - Add pages - Přidat strany + + Add pages + Přidat strany - - Remove current page - Odstranit nynější stranu + + Remove current page + Odstranit nynější stranu - - Countdown mode - Režim odpočítávání + + Countdown mode + Režim odpočítávání - - Show seek-bars - Ukázat prohledávací pruhy + + Show seek-bars + Ukázat prohledávací pruhy - - Show dB-meters - Ukázat měřidla decibelů + + Show dB-meters + Ukázat měřidla decibelů - - Show volume - Ukázat hlasitost + + Show volume + Ukázat hlasitost - - Show accurate time - Ukázat přesný čas + + Show accurate time + Ukázat přesný čas - - Edit cue - Upravit narážku + + Edit cue + Upravit narážku - - Remove - Odstranit + + Remove + Odstranit - - Select - Vybrat + + Select + Vybrat - - Play - Přehrát + + Play + Přehrát - - Pause - Pozastavit + + Pause + Pozastavit - - Stop - Zastavit + + Stop + Zastavit - - Reset volume - Nastavit znovu hlasitost + + Reset volume + Nastavit znovu hlasitost - - Number of Pages: - Počet stran: + + Number of Pages: + Počet stran: - - Warning - Varování + + Warning + Varování - - Every cue in the page will be lost. - Každá narážka na straně bude ztracena. + + Every cue in the page will be lost. + Každá narážka na straně bude ztracena. - - Are you sure to continue? - Jste si jistý, že chcete pokračovat? + + Are you sure to continue? + Jste si jistý, že chcete pokračovat? - - Page - Strana + + Page + Strana - - Default behaviors - Výchozí chování + + Default behaviors + Výchozí chování - - Automatically add new page - Automaticky přidat novou stranu + + Automatically add new page + Automaticky přidat novou stranu - - Grid size - Velikost mřížky + + Grid size + Velikost mřížky - - Number of columns - Počet sloupců + + Number of columns + Počet sloupců - - Number of rows - Počet řádků + + Number of rows + Počet řádků - - + + CueAction - - Default - Výchozí + + Default + Výchozí + + + + Pause + Pozastavit + + + + Start + Spustit + + + + Stop + Zastavit + + + + FadeInStart + Začátek postupného zesílení + + + + FadeOutStop + Konec postupného zeslabení + + + + FadeOutPause + Pozastavení postupného zeslabení + + + + Faded Start + Faded Start - - Pause - Pozastavit + + Faded Resume + Faded Resume - - Start - Spustit + + Faded Pause + Faded Pause - - Stop - Zastavit + + Faded Stop + Faded Stop - - FadeInStart - Začátek postupného zesílení + + Faded Interrupt + Faded Interrupt - - FadeOutStop - Konec postupného zeslabení + + Resume + Resume - - FadeOutPause - Pozastavení postupného zeslabení + + Do Nothing + Do Nothing - - + + CueActionLog - - Cue settings changed: "{}" - Nastavení narážky změněna: "{}" + + Cue settings changed: "{}" + Nastavení narážky změněna: "{}" - - Cues settings changed. - Nastavení narážky změněna. + + Cues settings changed. + Nastavení narážky změněna. - - + + CueAppearanceSettings - - The appearance depends on the layout - Vzhled závisí na rozvržení + + The appearance depends on the layout + Vzhled závisí na rozvržení - - Cue name - Název narážky + + Cue name + Název narážky - - NoName - Bez názvu + + NoName + Bez názvu - - Description/Note - Popis/Poznámka + + Description/Note + Popis/Poznámka - - Set Font Size - Nastavit velikost písma + + Set Font Size + Nastavit velikost písma - - Color - Barva + + Color + Barva - - Select background color - Vybrat barvu pozadí + + Select background color + Vybrat barvu pozadí - - Select font color - Vybrat barvu písma + + Select font color + Vybrat barvu písma - - + + CueName - - Media Cue - Narážka v záznamu + + Media Cue + Narážka v záznamu - - + + + CueNextAction + + + Do Nothing + Do Nothing + + + + Auto Follow + Auto Follow + + + + Auto Next + Auto Next + + + CueSettings - - Pre wait - Čekání před + + Pre wait + Čekání před - - Wait before cue execution - Čekat před provedením narážky + + Wait before cue execution + Čekat před provedením narážky - - Post wait - Čekání po + + Post wait + Čekání po - - Wait after cue execution - Čekat po provedení narážky + + Wait after cue execution + Čekat po provedení narážky - - Next action - Další činnost + + Next action + Další činnost - - Interrupt Fade - Prolínání přerušení + + Interrupt Fade + Prolínání přerušení - - Fade Action - Činnost prolínání + + Fade Action + Činnost prolínání - - Behaviours - Chování + + Behaviours + Chování - - Pre/Post Wait - Čekání před/po + + Pre/Post Wait + Čekání před/po - - Fade In/Out - Postupné zesílení/zeslabení signálu + + Fade In/Out + Postupné zesílení/zeslabení signálu - - Start action - Činnost spuštění + + Start action + Činnost spuštění - - Default action to start the cue - Výchozí činnost pro spuštění narážky + + Default action to start the cue + Výchozí činnost pro spuštění narážky - - Stop action - Činnost zastavení + + Stop action + Činnost zastavení - - Default action to stop the cue - Výchozí činnost pro zastavení narážky + + Default action to stop the cue + Výchozí činnost pro zastavení narážky - - + + + Interrupt fade + Interrupt fade + + + + Fade actions + Fade actions + + + Fade - - Linear - Lineární + + Linear + Lineární - - Quadratic - Kvadratický + + Quadratic + Kvadratický - - Quadratic2 - Kvadratický 2 + + Quadratic2 + Kvadratický 2 - - + + FadeEdit - - Duration (sec) - Doba trvání (s) + + Duration (sec) + Doba trvání (s) - - Curve - Křivka + + Curve + Křivka - - + + FadeSettings - - Fade In - Postupné zesílení signálu + + Fade In + Postupné zesílení signálu - - Fade Out - Postupné zeslabení signálu + + Fade Out + Postupné zeslabení signálu - - + + LayoutDescription - - Organize cues in grid like pages - Uspořádat narážky v mřížce jako strany + + Organize cues in grid like pages + Uspořádat narážky v mřížce jako strany - - Organize the cues in a list - Uspořádat narážky v seznamu + + Organize the cues in a list + Uspořádat narážky v seznamu - - + + LayoutDetails - - Click a cue to run it - Klepnout na narážku pro její spuštění + + Click a cue to run it + Klepnout na narážku pro její spuštění - - SHIFT + Click to edit a cue - Shift + klepnutí pro upravení narážky + + SHIFT + Click to edit a cue + Shift + klepnutí pro upravení narážky - - CTRL + Click to select a cue - Ctrl + klepnutí pro vybrání narážky + + CTRL + Click to select a cue + Ctrl + klepnutí pro vybrání narážky - - SHIFT + Space or Double-Click to edit a cue - Shift + mezerník nebo dvojité klepnutí pro upravení narážky + + SHIFT + Space or Double-Click to edit a cue + Shift + mezerník nebo dvojité klepnutí pro upravení narážky - - To copy cues drag them while pressing CTRL - Pro zkopírování narážek je táhněte za současného držení klávesy Ctrl + + To copy cues drag them while pressing CTRL + Pro zkopírování narážek je táhněte za současného držení klávesy Ctrl - - To copy cues drag them while pressing SHIFT - Pro zkopírování narážek je táhněte za současného držení klávesy Shift + + To copy cues drag them while pressing SHIFT + Pro zkopírování narážek je táhněte za současného držení klávesy Shift - - CTRL + Left Click to select cues - Ctrl + klepnutí levým tlačítkem myši pro vybrání narážky + + CTRL + Left Click to select cues + Ctrl + klepnutí levým tlačítkem myši pro vybrání narážky - - To move cues drag them - Pro přesunutí narážek je táhněte + + To move cues drag them + Pro přesunutí narážek je táhněte - - + + LayoutSelect - - Layout selection - Výběr rozvržení + + Layout selection + Výběr rozvržení - - Select layout - Vybrat rozvržení + + Select layout + Vybrat rozvržení - - Open file - Otevřít soubor + + Open file + Otevřít soubor - - + + ListLayout - - Show playing cues - Ukázat přehrávané narážky + + Show playing cues + Ukázat přehrávané narážky + + + + Show dB-meters + Ukázat měřidla decibelů + + + + Show seek-bars + Ukázat prohledávací pruhy - - Show dB-meters - Ukázat měřidla decibelů + + Show accurate time + Ukázat přesný čas - - Show seek-bars - Ukázat prohledávací pruhy + + Auto-select next cue + Vybrat další narážku automaticky - - Show accurate time - Ukázat přesný čas + + Edit cue + Upravit narážku - - Auto-select next cue - Vybrat další narážku automaticky + + Remove + Odstranit - - Edit cue - Upravit narážku + + Select + Vybrat - - Remove - Odstranit + + Stop all + Zastavit vše - - Select - Vybrat + + Pause all + Pozastavit vše - - Stop all - Zastavit vše + + Restart all + Spustit vše znovu - - Pause all - Pozastavit vše + + Stop + Zastavit - - Restart all - Spustit vše znovu + + Restart + Nastavit znovu na výchozí - - Stop - Zastavit + + Default behaviors + Výchozí chování - - Restart - Nastavit znovu na výchozí + + At list end: + Na konci seznamu: - - Default behaviors - Výchozí chování + + Go key: + Klávesa pro přechod: - - At list end: - Na konci seznamu: + + Interrupt all + Přerušit vše - - Go key: - Klávesa pro přechod: + + Fade-Out all + Postupně zeslabit vše - - Interrupt all - Přerušit vše + + Fade-In all + Postupně zesílit vše - - Fade-Out all - Postupně zeslabit vše + + Use fade + Použít prolínání - - Fade-In all - Postupně zesílit vše + + Stop Cue + Narážka pro zastavení - - Use fade - Použít prolínání + + Pause Cue + Narážka pro pozastavení - - Stop Cue - Narážka pro zastavení + + Restart Cue + Narážka pro opětovné spuštění - - Pause Cue - Narážka pro pozastavení + + Interrupt Cue + Narážka pro přerušení - - Restart Cue - Narážka pro opětovné spuštění + + Stop All + Zastavit vše - - Interrupt Cue - Narážka pro přerušení + + Pause All + Pozastavit vše - - Stop All - Zastavit vše + + Restart All + Spustit vše znovu - - Pause All - Pozastavit vše + + Interrupt All + Přerušit vše - - Restart All - Spustit vše znovu + + Use fade (global actions) + Use fade (global actions) - - Interrupt All - Přerušit vše + + Resume All + Resume All - - + + ListLayoutHeader - - Cue - Narážka + + Cue + Narážka - - Pre wait - Čekání před + + Pre wait + Čekání před - - Action - Činnost + + Action + Činnost - - Post wait - Čekání po + + Post wait + Čekání po - - + + ListLayoutInfoPanel - - Cue name - Název narážky + + Cue name + Název narážky - - Cue description - Popis narážky + + Cue description + Popis narážky - - + + Logging - - Information - Informace + + Information + Informace - - Debug - Ladění + + Debug + Ladění - - Warning - Varování + + Warning + Varování - - Error - Chyba + + Error + Chyba - - Details: - Podrobnosti: + + Details: + Podrobnosti: - - + + + Dismiss all + Dismiss all + + + + Show details + Show details + + + + Linux Show Player - Log Viewer + Linux Show Player - Log Viewer + + + + Info + Info + + + + Critical + Critical + + + + Time + Time + + + + Milliseconds + Milliseconds + + + + Logger name + Logger name + + + + Level + Level + + + + Message + Message + + + + Function + Function + + + + Path name + Path name + + + + File name + File name + + + + Line no. + Line no. + + + + Module + Module + + + + Process ID + Process ID + + + + Process name + Process name + + + + Thread ID + Thread ID + + + + Thread name + Thread name + + + MainWindow - - &File - &Soubor + + &File + &Soubor - - New session - Nové sezení + + New session + Nové sezení - - Open - Otevřít + + Open + Otevřít - - Save session - Uložit sezení + + Save session + Uložit sezení - - Preferences - Nastavení + + Preferences + Nastavení - - Save as - Uložit jako + + Save as + Uložit jako - - Full Screen - Na celou obrazovku + + Full Screen + Na celou obrazovku - - Exit - Ukončit + + Exit + Ukončit - - &Edit - Úp&ravy + + &Edit + Úp&ravy - - Undo - Zpět + + Undo + Zpět - - Redo - Znovu + + Redo + Znovu - - Select all - Vybrat vše + + Select all + Vybrat vše - - Select all media cues - Vybrat všechny narážky v záznamu + + Select all media cues + Vybrat všechny narážky v záznamu - - Deselect all - Odznačit všechny narážky + + Deselect all + Odznačit všechny narážky - - CTRL+SHIFT+A - Ctrl+Shift+A + + CTRL+SHIFT+A + Ctrl+Shift+A - - Invert selection - Obrátit výběr + + Invert selection + Obrátit výběr - - CTRL+I - CTRL+I + + CTRL+I + CTRL+I - - Edit selected - Upravit vybrané + + Edit selected + Upravit vybrané - - CTRL+SHIFT+E - Ctrl+Shift+E + + CTRL+SHIFT+E + Ctrl+Shift+E - - &Layout - &Rozvržení + + &Layout + &Rozvržení - - &Tools - &Nástroje + + &Tools + &Nástroje - - Edit selection - Upravit výběr + + Edit selection + Upravit výběr - - &About - &O programu + + &About + &O programu - - About - O programu + + About + O programu - - About Qt - O Qt + + About Qt + O Qt - - Undone: - Vráceno zpět: + + Undone: + Vráceno zpět: - - Redone: - Uděláno znovu: + + Redone: + Uděláno znovu: - - Close session - Zavřít sezení + + Close session + Zavřít sezení - - The current session is not saved. - Nynější sezení není uloženo. + + The current session is not saved. + Nynější sezení není uloženo. - - Discard the changes? - Zahodit změny? + + Discard the changes? + Zahodit změny? - - + + MediaCueMenus - - Audio cue (from file) - Zvuková narážka (ze souboru) + + Audio cue (from file) + Zvuková narážka (ze souboru) - - Select media files - Vybrat soubory se záznamy + + Select media files + Vybrat soubory se záznamy - - + + MediaCueSettings - - Start time - Čas spuštění + + Start time + Čas spuštění - - Stop position of the media - Poloha zastavení záznamu + + Stop position of the media + Poloha zastavení záznamu - - Stop time - Čas zastavení + + Stop time + Čas zastavení - - Start position of the media - Poloha spuštění záznamu + + Start position of the media + Poloha spuštění záznamu - - Loop - Smyčka + + Loop + Smyčka - - Repetition after first play (-1 = infinite) - Opakování po prvním přehrání (-1 = po nekonečnou dobu) + + Repetition after first play (-1 = infinite) + Opakování po prvním přehrání (-1 = po nekonečnou dobu) - - + + QColorButton - - Right click to reset - Klepnutí pravým tlačítkem myši pro nastavení na výchozí hodnotu + + Right click to reset + Klepnutí pravým tlačítkem myši pro nastavení na výchozí hodnotu - - + + SettingsPageName - - Appearance - Vzhled + + Appearance + Vzhled + + + + General + Obecné + + + + Cue + Narážka + + + + Cue Settings + Nastavení narážky + + + + Plugins + Plugins - - General - Obecné + + Behaviours + Behaviours - - Cue - Narážka + + Pre/Post Wait + Pre/Post Wait - - Cue Settings - Nastavení narážky + + Fade In/Out + Fade In/Out - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/cs_CZ/list_layout.ts b/lisp/i18n/ts/cs_CZ/list_layout.ts new file mode 100644 index 000000000..f68a072d5 --- /dev/null +++ b/lisp/i18n/ts/cs_CZ/list_layout.ts @@ -0,0 +1,189 @@ + + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them + To move cues drag them + + + + ListLayout + + + Default behaviors + Default behaviors + + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show seek-bars + Show seek-bars + + + + Auto-select next cue + Auto-select next cue + + + + Enable selection mode + Enable selection mode + + + + Go key: + Go key: + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Resume Cue + Resume Cue + + + + Interrupt Cue + Interrupt Cue + + + + Edit cue + Edit cue + + + + Edit selected cues + Edit selected cues + + + + Remove cue + Remove cue + + + + Remove selected cues + Remove selected cues + + + + Selection mode + Selection mode + + + + Pause all + Pause all + + + + Stop all + Stop all + + + + Interrupt all + Interrupt all + + + + Resume all + Resume all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + diff --git a/lisp/i18n/ts/cs_CZ/media_info.ts b/lisp/i18n/ts/cs_CZ/media_info.ts index ad9af4154..d58415ce5 100644 --- a/lisp/i18n/ts/cs_CZ/media_info.ts +++ b/lisp/i18n/ts/cs_CZ/media_info.ts @@ -1,30 +1,37 @@ - - + + + + MediaInfo - - Media Info - Údaje o záznamu + + Media Info + Údaje o záznamu - - Error - Chyba + + Error + Chyba - - No info to display - Žádné údaje k zobrazení + + No info to display + Žádné údaje k zobrazení - - Info - Informace + + Info + Informace - - Value - Hodnota + + Value + Hodnota - - \ No newline at end of file + + + Warning + Warning + + + diff --git a/lisp/i18n/ts/cs_CZ/midi.ts b/lisp/i18n/ts/cs_CZ/midi.ts index a3f8bfad4..58fd6437d 100644 --- a/lisp/i18n/ts/cs_CZ/midi.ts +++ b/lisp/i18n/ts/cs_CZ/midi.ts @@ -1,28 +1,30 @@ - - + + + + MIDISettings - - MIDI default devices - Výchozí zařízení MIDI + + MIDI default devices + Výchozí zařízení MIDI - - Input - Vstup + + Input + Vstup - - Output - Výstup + + Output + Výstup - - + + SettingsPageName - - MIDI settings - Nastavení MIDI + + MIDI settings + Nastavení MIDI - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/cs_CZ/network.ts b/lisp/i18n/ts/cs_CZ/network.ts new file mode 100644 index 000000000..49cee197d --- /dev/null +++ b/lisp/i18n/ts/cs_CZ/network.ts @@ -0,0 +1,47 @@ + + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + diff --git a/lisp/i18n/ts/cs_CZ/osc.ts b/lisp/i18n/ts/cs_CZ/osc.ts new file mode 100644 index 000000000..6a9eb64d3 --- /dev/null +++ b/lisp/i18n/ts/cs_CZ/osc.ts @@ -0,0 +1,35 @@ + + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + SettingsPageName + + + OSC settings + OSC settings + + + diff --git a/lisp/i18n/ts/cs_CZ/presets.ts b/lisp/i18n/ts/cs_CZ/presets.ts index 7fa6973ed..dac0d3b03 100644 --- a/lisp/i18n/ts/cs_CZ/presets.ts +++ b/lisp/i18n/ts/cs_CZ/presets.ts @@ -1,138 +1,150 @@ - - + + + + Preset - - Create Cue - Vytvořit narážku + + Create Cue + Vytvořit narážku - - Load on selected Cues - Nahrát na vybraných narážkách + + Load on selected Cues + Nahrát na vybraných narážkách - - + + Presets - - Presets - Přednastavení + + Presets + Přednastavení - - Load preset - Nahrát přednastavení + + Load preset + Nahrát přednastavení - - Save as preset - Uložit jako přednastavení + + Save as preset + Uložit jako přednastavení - - Cannot scan presets - Nelze prohledat přednastavení + + Cannot scan presets + Nelze prohledat přednastavení - - Error while deleting preset "{}" - Chyba při mazání přednastavení "{}" + + Error while deleting preset "{}" + Chyba při mazání přednastavení "{}" - - Cannot load preset "{}" - Nelze nahrát přednastavení "{}" + + Cannot load preset "{}" + Nelze nahrát přednastavení "{}" - - Cannot save preset "{}" - Nelze uložit přednastavení "{}" + + Cannot save preset "{}" + Nelze uložit přednastavení "{}" - - Cannot rename preset "{}" - Nelze přejmenovat přednastavení "{}" + + Cannot rename preset "{}" + Nelze přejmenovat přednastavení "{}" - - Select Preset - Vybrat přednastavení + + Select Preset + Vybrat přednastavení - - Preset already exists, overwrite? - Přednastavení již existuje. Přepsat? + + Preset already exists, overwrite? + Přednastavení již existuje. Přepsat? - - Preset name - Název přednastavení + + Preset name + Název přednastavení - - Add - Přidat + + Add + Přidat - - Rename - Přejmenovat + + Rename + Přejmenovat - - Edit - Upravit + + Edit + Upravit - - Remove - Odstranit + + Remove + Odstranit - - Export selected - Vybráno vyvedení + + Export selected + Vybráno vyvedení - - Import - Vyvedení + + Import + Vyvedení - - Warning - Varování + + Warning + Varování - - The same name is already used! - Stejný název se již používá! + + The same name is already used! + Stejný název se již používá! - - Cannot create a cue from this preset: {} - Nelze vytvořit narážku z tohoto přednastavení: "{}" + + Cannot create a cue from this preset: {} + Nelze vytvořit narážku z tohoto přednastavení: "{}" - - Cannot export correctly. - Nelze vyvést správně. + + Cannot export correctly. + Nelze vyvést správně. - - Some presets already exists, overwrite? - Některá přednastavení již existují. Přepsat? + + Some presets already exists, overwrite? + Některá přednastavení již existují. Přepsat? - - Cannot import correctly. - Nelze zavést správně. + + Cannot import correctly. + Nelze zavést správně. - - Cue type - Typ narážky + + Cue type + Typ narážky - - \ No newline at end of file + + + Load on cue + Load on cue + + + + Load on selected cues + Load on selected cues + + + diff --git a/lisp/i18n/ts/cs_CZ/rename_cues.ts b/lisp/i18n/ts/cs_CZ/rename_cues.ts new file mode 100644 index 000000000..91f0053a9 --- /dev/null +++ b/lisp/i18n/ts/cs_CZ/rename_cues.ts @@ -0,0 +1,95 @@ + + + + + RenameCues + + + Rename Cues + Rename Cues + + + + Rename cues + Rename cues + + + + Current + Current + + + + Preview + Preview + + + + Capitalize + Capitalize + + + + Lowercase + Lowercase + + + + Uppercase + Uppercase + + + + Remove Numbers + Remove Numbers + + + + Add numbering + Add numbering + + + + Reset + Reset + + + + Rename all cue. () in regex below usable with $0, $1 ... + Rename all cue. () in regex below usable with $0, $1 ... + + + + Type your regex here: + Type your regex here: + + + + Regex help + Regex help + + + + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple : +^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple : +^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation + + + diff --git a/lisp/i18n/ts/cs_CZ/replay_gain.ts b/lisp/i18n/ts/cs_CZ/replay_gain.ts index 8dc65a3c7..e49bfc3e9 100644 --- a/lisp/i18n/ts/cs_CZ/replay_gain.ts +++ b/lisp/i18n/ts/cs_CZ/replay_gain.ts @@ -1,50 +1,52 @@ - - + + + + ReplayGain - - ReplayGain / Normalization - Vyrovnání hlasitosti/Normalizace + + ReplayGain / Normalization + Vyrovnání hlasitosti/Normalizace - - Calculate - Spočítat + + Calculate + Spočítat - - Reset all - Nastavit vše znovu + + Reset all + Nastavit vše znovu - - Reset selected - Nastavit vybrané znovu + + Reset selected + Nastavit vybrané znovu - - Threads number - Počet vláken + + Threads number + Počet vláken - - Apply only to selected media - Použít pouze na vybrané záznamy + + Apply only to selected media + Použít pouze na vybrané záznamy - - ReplayGain to (dB SPL) - Vyrovnat hlasitost na (dB SPL) + + ReplayGain to (dB SPL) + Vyrovnat hlasitost na (dB SPL) - - Normalize to (dB) - Normalizovat na (dB SPL) + + Normalize to (dB) + Normalizovat na (dB SPL) - - Processing files ... - Zpracovávají se soubory... + + Processing files ... + Zpracovávají se soubory... - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/cs_CZ/synchronizer.ts b/lisp/i18n/ts/cs_CZ/synchronizer.ts index 1bf0ea0bc..3c022b78f 100644 --- a/lisp/i18n/ts/cs_CZ/synchronizer.ts +++ b/lisp/i18n/ts/cs_CZ/synchronizer.ts @@ -1,83 +1,85 @@ - - + + + + SyncPeerDialog - - Manage connected peers - Spravovat spojené protějšky + + Manage connected peers + Spravovat spojené protějšky - - Discover peers - Objevit protějšky + + Discover peers + Objevit protějšky - - Manually add a peer - Přidat protějšek ručně + + Manually add a peer + Přidat protějšek ručně - - Remove selected peer - Odstranit vybraný protějšek + + Remove selected peer + Odstranit vybraný protějšek - - Remove all peers - Odstranit všechny protějšky + + Remove all peers + Odstranit všechny protějšky - - Address - Adresa + + Address + Adresa - - Peer IP - Adresa protějšku + + Peer IP + Adresa protějšku - - Error - Chyba + + Error + Chyba - - Already connected - Již připojen + + Already connected + Již připojen - - Cannot add peer - Nelze přidat protějšek + + Cannot add peer + Nelze přidat protějšek - - + + Synchronizer - - Synchronization - Seřízení + + Synchronization + Seřízení - - Manage connected peers - Spravovat spojené protějšky + + Manage connected peers + Spravovat spojené protějšky - - Show your IP - Ukázat vaši adresu (IP) + + Show your IP + Ukázat vaši adresu (IP) - - Your IP is: - Vaše adresu (IP): + + Your IP is: + Vaše adresu (IP): - - Discovering peers ... - Objevují se protějšci... + + Discovering peers ... + Objevují se protějšci... - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/cs_CZ/timecode.ts b/lisp/i18n/ts/cs_CZ/timecode.ts index 63e8a1752..d17c94612 100644 --- a/lisp/i18n/ts/cs_CZ/timecode.ts +++ b/lisp/i18n/ts/cs_CZ/timecode.ts @@ -1,81 +1,110 @@ - - + + + + SettingsPageName - - Timecode Settings - Nastavení časového kódu + + Timecode Settings + Nastavení časového kódu - - Timecode - Časový kód + + Timecode + Časový kód - - + + Timecode - - Cannot send timecode. - Nelze poslat časový kód + + Cannot send timecode. + Nelze poslat časový kód - - OLA has stopped. - OLA zastavil. + + OLA has stopped. + OLA zastavil. - - + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + Cannot send timecode. +OLA has stopped. + Cannot send timecode. +OLA has stopped. + + + TimecodeSettings - - OLA Timecode Settings - Nastavení časového kódu OLA + + OLA Timecode Settings + Nastavení časového kódu OLA + + + + Enable Plugin + Povolit přídavný modul + + + + High-Resolution Timecode + Časový kód pro velké rozlišení + + + + Timecode Format: + Formát časového kódu: - - Enable Plugin - Povolit přídavný modul + + OLA status + Stav OLA - - High-Resolution Timecode - Časový kód pro velké rozlišení + + OLA is not running - start the OLA daemon. + OLA neběží - spustit démona OLA. - - Timecode Format: - Formát časového kódu: + + Replace HOURS by a static track number + Nahradit HODINY stálým číslem stopy - - OLA status - Stav OLA + + Enable ArtNet Timecode + Povolit časový kód ArtNet - - OLA is not running - start the OLA daemon. - OLA neběží - spustit démona OLA. + + Track number + Číslo stopy - - Replace HOURS by a static track number - Nahradit HODINY stálým číslem stopy + + To send ArtNet Timecode you need to setup a running OLA session! + Pro poslání časového kódu ArtNet je potřeba zřídit běžící sezení OLA! - - Enable ArtNet Timecode - Povolit časový kód ArtNet + + Enable Timecode + Enable Timecode - - Track number - Číslo stopy + + Timecode Settings + Timecode Settings - - To send ArtNet Timecode you need to setup a running OLA session! - Pro poslání časového kódu ArtNet je potřeba zřídit běžící sezení OLA! + + Timecode Protocol: + Timecode Protocol: - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/cs_CZ/triggers.ts b/lisp/i18n/ts/cs_CZ/triggers.ts index 449b1a5c4..4969c0f96 100644 --- a/lisp/i18n/ts/cs_CZ/triggers.ts +++ b/lisp/i18n/ts/cs_CZ/triggers.ts @@ -1,61 +1,63 @@ - - + + + + CueTriggers - - Started - Spuštěno + + Started + Spuštěno - - Paused - Pozastaveno + + Paused + Pozastaveno - - Stopped - Zastaveno + + Stopped + Zastaveno - - Ended - Skončeno + + Ended + Skončeno - - + + SettingsPageName - - Triggers - Spouštěče + + Triggers + Spouštěče - - + + TriggersSettings - - Add - Přidat + + Add + Přidat - - Remove - Odstranit + + Remove + Odstranit - - Trigger - Spouštěč + + Trigger + Spouštěč - - Cue - Narážka + + Cue + Narážka - - Action - Činnost + + Action + Činnost - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/de_DE/action_cues.ts b/lisp/i18n/ts/de_DE/action_cues.ts new file mode 100644 index 000000000..8e963cc3f --- /dev/null +++ b/lisp/i18n/ts/de_DE/action_cues.ts @@ -0,0 +1,345 @@ + + + + + CollectionCue + + + Add + Add + + + + Remove + Remove + + + + Cue + Cue + + + + Action + Action + + + + CommandCue + + + Process ended with an error status. + Process ended with an error status. + + + + Exit code: + Exit code: + + + + Command + Command + + + + Command to execute, as in a shell + Command to execute, as in a shell + + + + Discard command output + Discard command output + + + + Ignore command errors + Ignore command errors + + + + Kill instead of terminate + Kill instead of terminate + + + + Command cue ended with an error status. Exit code: {} + Command cue ended with an error status. Exit code: {} + + + + Cue Name + + + OSC Settings + OSC Settings + + + + CueName + + + Command Cue + Command Cue + + + + MIDI Cue + MIDI Cue + + + + Volume Control + Volume Control + + + + Seek Cue + Seek Cue + + + + Collection Cue + Collection Cue + + + + Stop-All + Stop-All + + + + Index Action + Index Action + + + + OSC Cue + OSC Cue + + + + IndexActionCue + + + Index + Index + + + + Use a relative index + Use a relative index + + + + Target index + Target index + + + + Action + Action + + + + No suggestion + No suggestion + + + + Suggested cue name + Suggested cue name + + + + MIDICue + + + MIDI Message + MIDI Message + + + + Message type + Message type + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OscCue + + + Error during cue execution. + Error during cue execution. + + + + OSC Message + OSC Message + + + + Add + Add + + + + Remove + Remove + + + + Test + Test + + + + OSC Path: (example: "/path/to/something") + OSC Path: (example: "/path/to/something") + + + + Fade + Fade + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + + + SeekCue + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Seek + Seek + + + + Time to reach + Time to reach + + + + SettingsPageName + + + Command + Command + + + + MIDI Settings + MIDI Settings + + + + Volume Settings + Volume Settings + + + + Seek Settings + Seek Settings + + + + Edit Collection + Edit Collection + + + + Action Settings + Action Settings + + + + Stop Settings + Stop Settings + + + + StopAll + + + Stop Action + Stop Action + + + + VolumeControl + + + Error during cue execution + Error during cue execution + + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Volume to reach + Volume to reach + + + + Fade + Fade + + + diff --git a/lisp/i18n/ts/de_DE/cart_layout.ts b/lisp/i18n/ts/de_DE/cart_layout.ts new file mode 100644 index 000000000..8d20cca42 --- /dev/null +++ b/lisp/i18n/ts/de_DE/cart_layout.ts @@ -0,0 +1,176 @@ + + + + + CartLayout + + + Default behaviors + Default behaviors + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show volume + Show volume + + + + Automatically add new page + Automatically add new page + + + + Grid size + Grid size + + + + Number of columns + Number of columns + + + + Number of rows + Number of rows + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Number of Pages: + Number of Pages: + + + + Page {number} + Page {number} + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + LayoutDescription + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + LayoutDetails + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them while pressing SHIFT + To move cues drag them while pressing SHIFT + + + + ListLayout + + + Edit cue + Edit cue + + + + Edit selected cues + Edit selected cues + + + + Remove cue + Remove cue + + + + Remove selected cues + Remove selected cues + + + diff --git a/lisp/i18n/ts/de_DE/controller.ts b/lisp/i18n/ts/de_DE/controller.ts new file mode 100644 index 000000000..fa1edf877 --- /dev/null +++ b/lisp/i18n/ts/de_DE/controller.ts @@ -0,0 +1,198 @@ + + + + + Controller + + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + + ControllerKeySettings + + + Key + Key + + + + Action + Action + + + + Shortcuts + Shortcuts + + + + ControllerMidiSettings + + + MIDI + MIDI + + + + Type + Type + + + + Channel + Channel + + + + Note + Note + + + + Action + Action + + + + Filter "note on" + Filter "note on" + + + + Filter "note off" + Filter "note off" + + + + Capture + Capture + + + + Listening MIDI messages ... + Listening MIDI messages ... + + + + ControllerOscSettings + + + OSC Message + OSC Message + + + + OSC Path: (example: "/path/to/something") + OSC Path: (example: "/path/to/something") + + + + OSC + OSC + + + + Path + Path + + + + Types + Types + + + + Arguments + Arguments + + + + Actions + Actions + + + + OSC Capture + OSC Capture + + + + Add + Add + + + + Remove + Remove + + + + Capture + Capture + + + + ControllerSettings + + + Add + Add + + + + Remove + Remove + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + OscCue + + + Add + Add + + + + Remove + Remove + + + + SettingsPageName + + + Cue Control + Cue Control + + + + MIDI Controls + MIDI Controls + + + + Keyboard Shortcuts + Keyboard Shortcuts + + + + OSC Controls + OSC Controls + + + diff --git a/lisp/i18n/ts/de_DE/gst_backend.ts b/lisp/i18n/ts/de_DE/gst_backend.ts new file mode 100644 index 000000000..7f991ff9d --- /dev/null +++ b/lisp/i18n/ts/de_DE/gst_backend.ts @@ -0,0 +1,388 @@ + + + + + AlsaSinkSettings + + + ALSA device + ALSA device + + + + ALSA devices, as defined in an asound configuration file + ALSA devices, as defined in an asound configuration file + + + + AudioDynamicSettings + + + Compressor + Compressor + + + + Expander + Expander + + + + Soft Knee + Soft Knee + + + + Hard Knee + Hard Knee + + + + Compressor/Expander + Compressor/Expander + + + + Type + Type + + + + Curve Shape + Curve Shape + + + + Ratio + Ratio + + + + Threshold (dB) + Threshold (dB) + + + + AudioPanSettings + + + Audio Pan + Audio Pan + + + + Center + Center + + + + Left + Left + + + + Right + Right + + + + DbMeterSettings + + + DbMeter settings + DbMeter settings + + + + Time between levels (ms) + Time between levels (ms) + + + + Peak ttl (ms) + Peak ttl (ms) + + + + Peak falloff (dB/sec) + Peak falloff (dB/sec) + + + + Equalizer10Settings + + + 10 Bands Equalizer (IIR) + 10 Bands Equalizer (IIR) + + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + GstMediaSettings + + + Change Pipeline + Change Pipeline + + + + GstPipelineEdit + + + Edit Pipeline + Edit Pipeline + + + + GstSettings + + + Pipeline + Pipeline + + + + JackSinkSettings + + + Connections + Connections + + + + Edit connections + Edit connections + + + + Output ports + Output ports + + + + Input ports + Input ports + + + + Connect + Connect + + + + Disconnect + Disconnect + + + + MediaElementName + + + Compressor/Expander + Compressor/Expander + + + + Audio Pan + Audio Pan + + + + PulseAudio Out + PulseAudio Out + + + + Volume + Volume + + + + dB Meter + dB Meter + + + + System Input + System Input + + + + ALSA Out + ALSA Out + + + + JACK Out + JACK Out + + + + Custom Element + Custom Element + + + + System Out + System Out + + + + Pitch + Pitch + + + + URI Input + URI Input + + + + 10 Bands Equalizer + 10 Bands Equalizer + + + + Speed + Speed + + + + Preset Input + Preset Input + + + + PitchSettings + + + Pitch + Pitch + + + + {0:+} semitones + {0:+} semitones + + + + PresetSrcSettings + + + Presets + Presets + + + + SettingsPageName + + + GStreamer settings + GStreamer settings + + + + Media Settings + Media Settings + + + + GStreamer + GStreamer + + + + SpeedSettings + + + Speed + Speed + + + + UriInputSettings + + + Source + Source + + + + Find File + Find File + + + + Buffering + Buffering + + + + Use Buffering + Use Buffering + + + + Attempt download on network streams + Attempt download on network streams + + + + Buffer size (-1 default value) + Buffer size (-1 default value) + + + + Choose file + Choose file + + + + All files + All files + + + + UserElementSettings + + + User defined elements + User defined elements + + + + Only for advanced user! + Only for advanced user! + + + + VolumeSettings + + + Volume + Volume + + + + Normalized volume + Normalized volume + + + + Reset + Reset + + + diff --git a/lisp/i18n/ts/de_DE/lisp.ts b/lisp/i18n/ts/de_DE/lisp.ts new file mode 100644 index 000000000..8ceecacfa --- /dev/null +++ b/lisp/i18n/ts/de_DE/lisp.ts @@ -0,0 +1,1180 @@ + + + + + About + + + Authors + Authors + + + + Contributors + Contributors + + + + Translators + Translators + + + + About Linux Show Player + About Linux Show Player + + + + AboutDialog + + + Linux Show Player is a cue-player designed for stage productions. + Linux Show Player is a cue-player designed for stage productions. + + + + Web site + Web site + + + + Users group + Users group + + + + Source code + Source code + + + + Info + Info + + + + License + License + + + + Contributors + Contributors + + + + Discussion + Discussion + + + + Actions + + + Undo: {} + Undo: {} + + + + Redo: {} + Redo: {} + + + + AppConfiguration + + + LiSP preferences + LiSP preferences + + + + AppGeneralSettings + + + Startup layout + Startup layout + + + + Use startup dialog + Use startup dialog + + + + Application theme + Application theme + + + + Default layout + Default layout + + + + Enable startup layout selector + Enable startup layout selector + + + + Application themes + Application themes + + + + UI theme + UI theme + + + + Icons theme + Icons theme + + + + CartLayout + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show volume + Show volume + + + + Show accurate time + Show accurate time + + + + Edit cue + Edit cue + + + + Remove + Remove + + + + Select + Select + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Number of Pages: + Number of Pages: + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + Page + Page + + + + Default behaviors + Default behaviors + + + + Automatically add new page + Automatically add new page + + + + Grid size + Grid size + + + + Number of columns + Number of columns + + + + Number of rows + Number of rows + + + + CueAction + + + Default + Default + + + + Pause + Pause + + + + Start + Start + + + + Stop + Stop + + + + FadeInStart + FadeInStart + + + + FadeOutStop + FadeOutStop + + + + FadeOutPause + FadeOutPause + + + + Faded Start + Faded Start + + + + Faded Resume + Faded Resume + + + + Faded Pause + Faded Pause + + + + Faded Stop + Faded Stop + + + + Faded Interrupt + Faded Interrupt + + + + Resume + Resume + + + + Do Nothing + Do Nothing + + + + CueActionLog + + + Cue settings changed: "{}" + Cue settings changed: "{}" + + + + Cues settings changed. + Cues settings changed. + + + + CueAppearanceSettings + + + The appearance depends on the layout + The appearance depends on the layout + + + + Cue name + Cue name + + + + NoName + NoName + + + + Description/Note + Description/Note + + + + Set Font Size + Set Font Size + + + + Color + Color + + + + Select background color + Select background color + + + + Select font color + Select font color + + + + CueName + + + Media Cue + Media Cue + + + + CueNextAction + + + Do Nothing + Do Nothing + + + + Auto Follow + Auto Follow + + + + Auto Next + Auto Next + + + + CueSettings + + + Pre wait + Pre wait + + + + Wait before cue execution + Wait before cue execution + + + + Post wait + Post wait + + + + Wait after cue execution + Wait after cue execution + + + + Next action + Next action + + + + Interrupt Fade + Interrupt Fade + + + + Fade Action + Fade Action + + + + Behaviours + Behaviours + + + + Pre/Post Wait + Pre/Post Wait + + + + Fade In/Out + Fade In/Out + + + + Start action + Start action + + + + Default action to start the cue + Default action to start the cue + + + + Stop action + Stop action + + + + Default action to stop the cue + Default action to stop the cue + + + + Interrupt fade + Interrupt fade + + + + Fade actions + Fade actions + + + + Fade + + + Linear + Linear + + + + Quadratic + Quadratic + + + + Quadratic2 + Quadratic2 + + + + FadeEdit + + + Duration (sec) + Duration (sec) + + + + Curve + Curve + + + + FadeSettings + + + Fade In + Fade In + + + + Fade Out + Fade Out + + + + LayoutDescription + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + Organize the cues in a list + Organize the cues in a list + + + + LayoutDetails + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To copy cues drag them while pressing SHIFT + To copy cues drag them while pressing SHIFT + + + + CTRL + Left Click to select cues + CTRL + Left Click to select cues + + + + To move cues drag them + To move cues drag them + + + + LayoutSelect + + + Layout selection + Layout selection + + + + Select layout + Select layout + + + + Open file + Open file + + + + ListLayout + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show seek-bars + Show seek-bars + + + + Show accurate time + Show accurate time + + + + Auto-select next cue + Auto-select next cue + + + + Edit cue + Edit cue + + + + Remove + Remove + + + + Select + Select + + + + Stop all + Stop all + + + + Pause all + Pause all + + + + Restart all + Restart all + + + + Stop + Stop + + + + Restart + Restart + + + + Default behaviors + Default behaviors + + + + At list end: + At list end: + + + + Go key: + Go key: + + + + Interrupt all + Interrupt all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + Use fade + Use fade + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Restart Cue + Restart Cue + + + + Interrupt Cue + Interrupt Cue + + + + Stop All + Stop All + + + + Pause All + Pause All + + + + Restart All + Restart All + + + + Interrupt All + Interrupt All + + + + Use fade (global actions) + Use fade (global actions) + + + + Resume All + Resume All + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + + Logging + + + Information + Information + + + + Debug + Debug + + + + Warning + Warning + + + + Error + Error + + + + Details: + Details: + + + + Dismiss all + Dismiss all + + + + Show details + Show details + + + + Linux Show Player - Log Viewer + Linux Show Player - Log Viewer + + + + Info + Info + + + + Critical + Critical + + + + Time + Time + + + + Milliseconds + Milliseconds + + + + Logger name + Logger name + + + + Level + Level + + + + Message + Message + + + + Function + Function + + + + Path name + Path name + + + + File name + File name + + + + Line no. + Line no. + + + + Module + Module + + + + Process ID + Process ID + + + + Process name + Process name + + + + Thread ID + Thread ID + + + + Thread name + Thread name + + + + MainWindow + + + &File + &File + + + + New session + New session + + + + Open + Open + + + + Save session + Save session + + + + Preferences + Preferences + + + + Save as + Save as + + + + Full Screen + Full Screen + + + + Exit + Exit + + + + &Edit + &Edit + + + + Undo + Undo + + + + Redo + Redo + + + + Select all + Select all + + + + Select all media cues + Select all media cues + + + + Deselect all + Deselect all + + + + CTRL+SHIFT+A + CTRL+SHIFT+A + + + + Invert selection + Invert selection + + + + CTRL+I + CTRL+I + + + + Edit selected + Edit selected + + + + CTRL+SHIFT+E + CTRL+SHIFT+E + + + + &Layout + &Layout + + + + &Tools + &Tools + + + + Edit selection + Edit selection + + + + &About + &About + + + + About + About + + + + About Qt + About Qt + + + + Undone: + Undone: + + + + Redone: + Redone: + + + + Close session + Close session + + + + The current session is not saved. + The current session is not saved. + + + + Discard the changes? + Discard the changes? + + + + MediaCueMenus + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + MediaCueSettings + + + Start time + Start time + + + + Stop position of the media + Stop position of the media + + + + Stop time + Stop time + + + + Start position of the media + Start position of the media + + + + Loop + Loop + + + + Repetition after first play (-1 = infinite) + Repetition after first play (-1 = infinite) + + + + QColorButton + + + Right click to reset + Right click to reset + + + + SettingsPageName + + + Appearance + Appearance + + + + General + General + + + + Cue + Cue + + + + Cue Settings + Cue Settings + + + + Plugins + Plugins + + + + Behaviours + Behaviours + + + + Pre/Post Wait + Pre/Post Wait + + + + Fade In/Out + Fade In/Out + + + diff --git a/lisp/i18n/ts/de_DE/list_layout.ts b/lisp/i18n/ts/de_DE/list_layout.ts new file mode 100644 index 000000000..d1b2d7ad6 --- /dev/null +++ b/lisp/i18n/ts/de_DE/list_layout.ts @@ -0,0 +1,189 @@ + + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them + To move cues drag them + + + + ListLayout + + + Default behaviors + Default behaviors + + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show seek-bars + Show seek-bars + + + + Auto-select next cue + Auto-select next cue + + + + Enable selection mode + Enable selection mode + + + + Go key: + Go key: + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Resume Cue + Resume Cue + + + + Interrupt Cue + Interrupt Cue + + + + Edit cue + Edit cue + + + + Edit selected cues + Edit selected cues + + + + Remove cue + Remove cue + + + + Remove selected cues + Remove selected cues + + + + Selection mode + Selection mode + + + + Pause all + Pause all + + + + Stop all + Stop all + + + + Interrupt all + Interrupt all + + + + Resume all + Resume all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + diff --git a/lisp/i18n/ts/de_DE/media_info.ts b/lisp/i18n/ts/de_DE/media_info.ts new file mode 100644 index 000000000..7b1f2de15 --- /dev/null +++ b/lisp/i18n/ts/de_DE/media_info.ts @@ -0,0 +1,37 @@ + + + + + MediaInfo + + + Media Info + Media Info + + + + Error + Error + + + + No info to display + No info to display + + + + Info + Info + + + + Value + Value + + + + Warning + Warning + + + diff --git a/lisp/i18n/ts/de_DE/midi.ts b/lisp/i18n/ts/de_DE/midi.ts new file mode 100644 index 000000000..0082ceb85 --- /dev/null +++ b/lisp/i18n/ts/de_DE/midi.ts @@ -0,0 +1,30 @@ + + + + + MIDISettings + + + MIDI default devices + MIDI default devices + + + + Input + Input + + + + Output + Output + + + + SettingsPageName + + + MIDI settings + MIDI settings + + + diff --git a/lisp/i18n/ts/de_DE/network.ts b/lisp/i18n/ts/de_DE/network.ts new file mode 100644 index 000000000..d9b2ba83d --- /dev/null +++ b/lisp/i18n/ts/de_DE/network.ts @@ -0,0 +1,47 @@ + + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + diff --git a/lisp/i18n/ts/de_DE/osc.ts b/lisp/i18n/ts/de_DE/osc.ts new file mode 100644 index 000000000..9eafde67a --- /dev/null +++ b/lisp/i18n/ts/de_DE/osc.ts @@ -0,0 +1,35 @@ + + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + SettingsPageName + + + OSC settings + OSC settings + + + diff --git a/lisp/i18n/ts/de_DE/presets.ts b/lisp/i18n/ts/de_DE/presets.ts new file mode 100644 index 000000000..09261a2b3 --- /dev/null +++ b/lisp/i18n/ts/de_DE/presets.ts @@ -0,0 +1,150 @@ + + + + + Preset + + + Create Cue + Create Cue + + + + Load on selected Cues + Load on selected Cues + + + + Presets + + + Presets + Presets + + + + Load preset + Load preset + + + + Save as preset + Save as preset + + + + Cannot scan presets + Cannot scan presets + + + + Error while deleting preset "{}" + Error while deleting preset "{}" + + + + Cannot load preset "{}" + Cannot load preset "{}" + + + + Cannot save preset "{}" + Cannot save preset "{}" + + + + Cannot rename preset "{}" + Cannot rename preset "{}" + + + + Select Preset + Select Preset + + + + Preset already exists, overwrite? + Preset already exists, overwrite? + + + + Preset name + Preset name + + + + Add + Add + + + + Rename + Rename + + + + Edit + Edit + + + + Remove + Remove + + + + Export selected + Export selected + + + + Import + Import + + + + Warning + Warning + + + + The same name is already used! + The same name is already used! + + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + + + + Cannot export correctly. + Cannot export correctly. + + + + Some presets already exists, overwrite? + Some presets already exists, overwrite? + + + + Cannot import correctly. + Cannot import correctly. + + + + Cue type + Cue type + + + + Load on cue + Load on cue + + + + Load on selected cues + Load on selected cues + + + diff --git a/lisp/i18n/ts/de_DE/rename_cues.ts b/lisp/i18n/ts/de_DE/rename_cues.ts new file mode 100644 index 000000000..03684c10d --- /dev/null +++ b/lisp/i18n/ts/de_DE/rename_cues.ts @@ -0,0 +1,95 @@ + + + + + RenameCues + + + Rename Cues + Rename Cues + + + + Rename cues + Rename cues + + + + Current + Current + + + + Preview + Preview + + + + Capitalize + Capitalize + + + + Lowercase + Lowercase + + + + Uppercase + Uppercase + + + + Remove Numbers + Remove Numbers + + + + Add numbering + Add numbering + + + + Reset + Reset + + + + Rename all cue. () in regex below usable with $0, $1 ... + Rename all cue. () in regex below usable with $0, $1 ... + + + + Type your regex here: + Type your regex here: + + + + Regex help + Regex help + + + + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple : +^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple : +^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation + + + diff --git a/lisp/i18n/ts/de_DE/replay_gain.ts b/lisp/i18n/ts/de_DE/replay_gain.ts new file mode 100644 index 000000000..aced4861d --- /dev/null +++ b/lisp/i18n/ts/de_DE/replay_gain.ts @@ -0,0 +1,52 @@ + + + + + ReplayGain + + + ReplayGain / Normalization + ReplayGain / Normalization + + + + Calculate + Calculate + + + + Reset all + Reset all + + + + Reset selected + Reset selected + + + + Threads number + Threads number + + + + Apply only to selected media + Apply only to selected media + + + + ReplayGain to (dB SPL) + ReplayGain to (dB SPL) + + + + Normalize to (dB) + Normalize to (dB) + + + + Processing files ... + Processing files ... + + + diff --git a/lisp/i18n/ts/de_DE/synchronizer.ts b/lisp/i18n/ts/de_DE/synchronizer.ts new file mode 100644 index 000000000..e5d4b5cc8 --- /dev/null +++ b/lisp/i18n/ts/de_DE/synchronizer.ts @@ -0,0 +1,85 @@ + + + + + SyncPeerDialog + + + Manage connected peers + Manage connected peers + + + + Discover peers + Discover peers + + + + Manually add a peer + Manually add a peer + + + + Remove selected peer + Remove selected peer + + + + Remove all peers + Remove all peers + + + + Address + Address + + + + Peer IP + Peer IP + + + + Error + Error + + + + Already connected + Already connected + + + + Cannot add peer + Cannot add peer + + + + Synchronizer + + + Synchronization + Synchronization + + + + Manage connected peers + Manage connected peers + + + + Show your IP + Show your IP + + + + Your IP is: + Your IP is: + + + + Discovering peers ... + Discovering peers ... + + + diff --git a/lisp/i18n/ts/de_DE/timecode.ts b/lisp/i18n/ts/de_DE/timecode.ts new file mode 100644 index 000000000..d24a06292 --- /dev/null +++ b/lisp/i18n/ts/de_DE/timecode.ts @@ -0,0 +1,110 @@ + + + + + SettingsPageName + + + Timecode Settings + Timecode Settings + + + + Timecode + Timecode + + + + Timecode + + + Cannot send timecode. + Cannot send timecode. + + + + OLA has stopped. + OLA has stopped. + + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + Cannot send timecode. +OLA has stopped. + Cannot send timecode. +OLA has stopped. + + + + TimecodeSettings + + + OLA Timecode Settings + OLA Timecode Settings + + + + Enable Plugin + Enable Plugin + + + + High-Resolution Timecode + High-Resolution Timecode + + + + Timecode Format: + Timecode Format: + + + + OLA status + OLA status + + + + OLA is not running - start the OLA daemon. + OLA is not running - start the OLA daemon. + + + + Replace HOURS by a static track number + Replace HOURS by a static track number + + + + Enable ArtNet Timecode + Enable ArtNet Timecode + + + + Track number + Track number + + + + To send ArtNet Timecode you need to setup a running OLA session! + To send ArtNet Timecode you need to setup a running OLA session! + + + + Enable Timecode + Enable Timecode + + + + Timecode Settings + Timecode Settings + + + + Timecode Protocol: + Timecode Protocol: + + + diff --git a/lisp/i18n/ts/de_DE/triggers.ts b/lisp/i18n/ts/de_DE/triggers.ts new file mode 100644 index 000000000..6053b4d9a --- /dev/null +++ b/lisp/i18n/ts/de_DE/triggers.ts @@ -0,0 +1,63 @@ + + + + + CueTriggers + + + Started + Started + + + + Paused + Paused + + + + Stopped + Stopped + + + + Ended + Ended + + + + SettingsPageName + + + Triggers + Triggers + + + + TriggersSettings + + + Add + Add + + + + Remove + Remove + + + + Trigger + Trigger + + + + Cue + Cue + + + + Action + Action + + + diff --git a/lisp/i18n/ts/es_ES/action_cues.ts b/lisp/i18n/ts/es_ES/action_cues.ts index 3596ccec2..3a2a70af3 100644 --- a/lisp/i18n/ts/es_ES/action_cues.ts +++ b/lisp/i18n/ts/es_ES/action_cues.ts @@ -1,244 +1,345 @@ - - + + + + CollectionCue - - Add - Añadir + + Add + Añadir - - Remove - Eliminar + + Remove + Eliminar - - Cue - Cue + + Cue + Cue - - Action - Acción + + Action + Acción - - + + CommandCue - - Process ended with an error status. - El proceso terminó con un estado de error + + Process ended with an error status. + El proceso terminó con un estado de error. - - Exit code: - Código de salida: + + Exit code: + Código de salida: - - Command - Comando + + Command + Comando - - Command to execute, as in a shell - Comando a ejecutar, como en una línea de comandos + + Command to execute, as in a shell + Comando a ejecutar, como en una línea de comando - - Discard command output - Descartar salida de comando + + Discard command output + Descartar salida de comando - - Ignore command errors - Ignorar errores de comandos + + Ignore command errors + Ignorar errores de comando - - Kill instead of terminate - Matar en vez de terminar + + Kill instead of terminate + Matar en vez de terminar - - + + + Command cue ended with an error status. Exit code: {} + Cue de comando terminó con un estado de error. Código de Salida: {} + + + + Cue Name + + + OSC Settings + Configuración de OSC + + + CueName - - Command Cue - Cue de comando + + Command Cue + Cue de comando - - MIDI Cue - Cue de MIDI + + MIDI Cue + Cue de MIDI - - Volume Control - Control de volumen + + Volume Control + Control de volumen - - Seek Cue - Cue de búsqueda + + Seek Cue + Cue de búsqueda - - Collection Cue - Cue de Colección + + Collection Cue + Cue de Colección - - Stop-All - Deterner Todo + + Stop-All + Detener Todo - - Index Action - Índice de Acciones + + Index Action + Índice de Acciones - - + + + OSC Cue + Cue OSC + + + IndexActionCue - - Index - Índice + + Index + Índice + + + + Use a relative index + Usar índice relativo + + + + Target index + Índice de Target - - Use a relative index - Usar índice relativo + + Action + Acción - - Target index - Índice de Target + + No suggestion + No hay sugerencias - - Action - Acción + + Suggested cue name + Nombre sugerido del Cue - - + + MIDICue - - MIDI Message - Mensaje MIDI + + MIDI Message + Mensaje MIDI + + + + Message type + Tipo de mensaje + + + + Osc Cue + + + Type + Tipo + + + + Argument + Argumento + + + + FadeTo + Fundido a + + + + Fade + Fundido + + + + OscCue + + + Error during cue execution. + Error durante la ejecución del cue. + + + + OSC Message + Mensaje OSC + + + + Add + Añadir + + + + Remove + Eliminar + + + + Test + Probar + + + + OSC Path: (example: "/path/to/something") + Dirección OSC: (ejemplo: "/path/to/something") + + + + Fade + Fundido + + + + Time (sec) + Tiempo (seg) - - Message type - Tipo de mensaje + + Curve + Curva - - + + SeekCue - - Cue - Cue + + Cue + Cue - - Click to select - Click para seleccionar + + Click to select + Click para seleccionar - - Not selected - No seleccionado + + Not selected + No seleccionado - - Seek - Búsqueda + + Seek + Búsqueda - - Time to reach - Tiempo a alcanzar + + Time to reach + Tiempo a alcanzar - - + + SettingsPageName - - Command - Comando + + Command + Comando - - MIDI Settings - Ajustes MIDI + + MIDI Settings + Ajustes MIDI - - Volume Settings - Ajustes de volumen + + Volume Settings + Ajustes de volumen - - Seek Settings - Ajustes de búsqueda + + Seek Settings + Ajustes de búsqueda - - Edit Collection - Editar Colección + + Edit Collection + Editar Colección - - Action Settings - Ajustes de Acción + + Action Settings + Ajustes de Acción - - Stop Settings - Ajustes de Detención + + Stop Settings + Ajustes de Detención - - + + StopAll - - Stop Action - Detener Acción + + Stop Action + Detener Acción - - + + VolumeControl - - Error during cue execution - Error durante la ejecución del cue + + Error during cue execution + Error durante la ejecución del cue - - Cue - Cue + + Cue + Cue - - Click to select - Click para seleccionar + + Click to select + Click para seleccionar - - Not selected - No seleccionado + + Not selected + No seleccionado - - Volume to reach - Volumen a alcanzar + + Volume to reach + Volumen a alcanzar - - Fade - Fundido + + Fade + Fundido - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/es_ES/cart_layout.ts b/lisp/i18n/ts/es_ES/cart_layout.ts new file mode 100644 index 000000000..e3a75b446 --- /dev/null +++ b/lisp/i18n/ts/es_ES/cart_layout.ts @@ -0,0 +1,176 @@ + + + + + CartLayout + + + Default behaviors + Comportamiento por defecto + + + + Countdown mode + Modo de cuenta regresiva + + + + Show seek-bars + Mostrar barra de búsqueda + + + + Show dB-meters + Mostrar medidores de dB + + + + Show accurate time + Mostrar tiempo preciso + + + + Show volume + Mostrar volumen + + + + Automatically add new page + Añadir página automáticamente + + + + Grid size + Tamaño de cuadrícula + + + + Number of columns + Número de columnas + + + + Number of rows + Número de filas + + + + Play + Reproducir + + + + Pause + Pausa + + + + Stop + Detener + + + + Reset volume + Reestablecer volumen + + + + Add page + Añadir página + + + + Add pages + Añadir páginas + + + + Remove current page + Eliminar página actual + + + + Number of Pages: + Número de páginas: + + + + Page {number} + Página {number} + + + + Warning + Advertencia + + + + Every cue in the page will be lost. + Todos los cues en la página se perderán. + + + + Are you sure to continue? + ¿Está seguro de continuar? + + + + LayoutDescription + + + Organize cues in grid like pages + Organizar cues en cuadrícula como páginas + + + + LayoutDetails + + + Click a cue to run it + Hacer click en un cue para ejecutarlo + + + + SHIFT + Click to edit a cue + SHIFT + Click para editar un cue + + + + CTRL + Click to select a cue + CTRL + Click para seleccionar un cue + + + + To copy cues drag them while pressing CTRL + Para copiar cues, arrástrelos mientras presiona CTRL + + + + To move cues drag them while pressing SHIFT + Para mover Cues arrástrelos mientras presiona SHIFT + + + + ListLayout + + + Edit cue + Editar cue + + + + Edit selected cues + Editar los cues seleccionados + + + + Remove cue + Eliminar cue + + + + Remove selected cues + Eliminar los cues seleccionados + + + diff --git a/lisp/i18n/ts/es_ES/controller.ts b/lisp/i18n/ts/es_ES/controller.ts index 5d1277fd6..31449211a 100644 --- a/lisp/i18n/ts/es_ES/controller.ts +++ b/lisp/i18n/ts/es_ES/controller.ts @@ -1,99 +1,198 @@ - - + + + + + Controller + + + Cannot load controller protocol: "{}" + No se puede cargar el protocolo de control: "{}" + + + ControllerKeySettings - - Key - Tecla + + Key + Tecla - - Action - Acción + + Action + Acción - - Shortcuts - Atajos de teclado + + Shortcuts + Atajos de teclado - - + + ControllerMidiSettings - - MIDI - MIDI + + MIDI + MIDI - - Type - Tipo + + Type + Tipo - - Channel - Canal + + Channel + Canal - - Note - Nota + + Note + Nota - - Action - Acción + + Action + Acción - - Filter "note on" - Filtrar "Nota ON" + + Filter "note on" + Filtrar "Nota ON" - - Filter "note off" - Filtrar "Nota OFF" + + Filter "note off" + Filtrar "Nota OFF" - - Capture - Captura + + Capture + Captura - - Listening MIDI messages ... - Escuchando mensajes MIDI ... + + Listening MIDI messages ... + Escuchando mensajes MIDI ... - - + + + ControllerOscSettings + + + OSC Message + Mensaje OSC + + + + OSC Path: (example: "/path/to/something") + Dirección OSC: (ejemplo: "/path/to/something") + + + + OSC + OSC + + + + Path + Ruta + + + + Types + Tipos + + + + Arguments + Argumentos + + + + Actions + Acciones + + + + OSC Capture + Captura OSC + + + + Add + Añadir + + + + Remove + Eliminar + + + + Capture + Capturar + + + ControllerSettings - - Add - Añadir + + Add + Añadir + + + + Remove + Eliminar + + + Osc Cue - - Remove - Eliminar + + Type + Tipo - - + + + Argument + Argumento + + + + OscCue + + + Add + Añadir + + + + Remove + Eliminar + + + SettingsPageName - - Cue Control - Control de Cue + + Cue Control + Control de Cue + + + + MIDI Controls + Controles MIDI - - MIDI Controls - Controles MIDI + + Keyboard Shortcuts + Atajos de teclado - - Keyboard Shortcuts - Atajos de teclado + + OSC Controls + Controles OSC - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/es_ES/gst_backend.ts b/lisp/i18n/ts/es_ES/gst_backend.ts index 775c7cd49..84a56db2a 100644 --- a/lisp/i18n/ts/es_ES/gst_backend.ts +++ b/lisp/i18n/ts/es_ES/gst_backend.ts @@ -1,368 +1,388 @@ - - + + + + AlsaSinkSettings - - ALSA device - Dispositivo ALSA + + ALSA device + Dispositivo ALSA - - ALSA devices, as defined in an asound configuration file - Dispositivos ALSA, según están definidos en un archivo de configuración asound + + ALSA devices, as defined in an asound configuration file + Dispositivos ALSA, según están definidos en un archivo de configuración asound - - + + AudioDynamicSettings - - Compressor - Compresor + + Compressor + Compresor - - Expander - Expansor + + Expander + Expansor - - Soft Knee - Suave + + Soft Knee + Suave - - Hard Knee - Duro + + Hard Knee + Duro - - Compressor/Expander - Compresor/Expansor + + Compressor/Expander + Compresor/Expansor - - Type - Tipo + + Type + Tipo - - Curve Shape - Forma de la curva + + Curve Shape + Forma de la curva - - Ratio - Proporción + + Ratio + Proporción - - Threshold (dB) - Umbral (dB) + + Threshold (dB) + Umbral (dB) - - + + AudioPanSettings - - Audio Pan - Paneo de audio + + Audio Pan + Paneo de audio - - Center - Centro + + Center + Centro - - Left - Izquierda + + Left + Izquierda - - Right - Derecha + + Right + Derecha - - + + DbMeterSettings - - DbMeter settings - Ajustes del medidor dB + + DbMeter settings + Ajustes del medidor dB - - Time between levels (ms) - Intérvalo entre niveles (ms) + + Time between levels (ms) + Intérvalo entre niveles (ms) - - Peak ttl (ms) - Picos ttl (ms) + + Peak ttl (ms) + Picos ttl (ms) - - Peak falloff (dB/sec) - Decaimiento de picos (dB/seg.) + + Peak falloff (dB/sec) + Decaimiento de picos (dB/seg.) - - + + Equalizer10Settings - - 10 Bands Equalizer (IIR) - Equalizador de 10 bandas (IIR) + + 10 Bands Equalizer (IIR) + Equalizador de 10 bandas (IIR) - - + + + GstBackend + + + Audio cue (from file) + Cue de audio (desde archivo) + + + + Select media files + Seleccionar archivos de medios + + + GstMediaSettings - - Change Pipeline - Cambiar Pipeline + + Change Pipeline + Cambiar Pipeline - - + + GstPipelineEdit - - Edit Pipeline - Editar Pipeline + + Edit Pipeline + Editar Pipeline - - + + GstSettings - - Pipeline - Pipeline + + Pipeline + Pipeline - - + + JackSinkSettings - - Connections - Conexiones + + Connections + Conexiones - - Edit connections - Editar conexiones + + Edit connections + Editar conexiones - - Output ports - Puertos de salida + + Output ports + Puertos de salida - - Input ports - Puertos de entrada + + Input ports + Puertos de entrada - - Connect - Conectar + + Connect + Conectar - - Disconnect - Desconectar + + Disconnect + Desconectar - - + + MediaElementName - - Compressor/Expander - Compresor/Expansor + + Compressor/Expander + Compresor/Expansor - - Audio Pan - Paneo de audio + + Audio Pan + Paneo de audio - - PulseAudio Out - Salida de PulseAudio + + PulseAudio Out + Salida de PulseAudio - - Volume - Volumen + + Volume + Volumen - - dB Meter - Medidor de dB + + dB Meter + Medidor de dB - - System Input - Entrada de sistema + + System Input + Entrada de sistema - - ALSA Out - Salida ALSA + + ALSA Out + Salida ALSA - - JACK Out - Salida JACK + + JACK Out + Salida JACK - - Custom Element - Elemento personalizado + + Custom Element + Elemento personalizado - - System Out - Salida de sistema + + System Out + Salida de sistema - - Pitch - Tono + + Pitch + Tono - - URI Input - Entrada URI + + URI Input + Entrada URI - - 10 Bands Equalizer - Ecualizador de 10 bandas + + 10 Bands Equalizer + Ecualizador de 10 bandas - - Speed - Velocidad + + Speed + Velocidad - - Preset Input - Preset de entradas + + Preset Input + Preset de entradas - - + + PitchSettings - - Pitch - Tono + + Pitch + Tono - - {0:+} semitones - {0:+} semitonos + + {0:+} semitones + {0:+} semitonos - - + + PresetSrcSettings - - Presets - Presets + + Presets + Preajustes - - + + SettingsPageName - - GStreamer settings - Ajustes de GStreamer + + GStreamer settings + Ajustes de GStreamer + + + + Media Settings + Ajustes de Media - - Media Settings - Ajustes de Media + + GStreamer + GStreamer - - + + SpeedSettings - - Speed - Velocidad + + Speed + Velocidad - - + + UriInputSettings - - Source - Origen + + Source + Origen - - Find File - Encontrar archivo + + Find File + Encontrar archivo - - Buffering - Buffering + + Buffering + Pre-Carga - - Use Buffering - Usar Buffering + + Use Buffering + Usar Buffering - - Attempt download on network streams - Intentar descargar en streams de red + + Attempt download on network streams + Intentar descargar en streams de red - - Buffer size (-1 default value) - Tamaño del buffer (-1 valor por defecto) + + Buffer size (-1 default value) + Tamaño del buffer (-1 valor por defecto) - - Choose file - Elegir archivo + + Choose file + Elegir archivo - - All files - Todos los archivos + + All files + Todos los archivos - - + + UserElementSettings - - User defined elements - Elementos definidos por el usuario + + User defined elements + Elementos definidos por el usuario - - Only for advanced user! - ¡Sólo para usuarios avanzados! + + Only for advanced user! + ¡Sólo para usuarios avanzados! - - + + VolumeSettings - - Volume - Volumen + + Volume + Volumen - - Normalized volume - Volumen normalizado + + Normalized volume + Volumen normalizado - - Reset - Reestablecer + + Reset + Reestablecer - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/es_ES/lisp.ts b/lisp/i18n/ts/es_ES/lisp.ts index 2bc224dbc..cdeafc254 100644 --- a/lisp/i18n/ts/es_ES/lisp.ts +++ b/lisp/i18n/ts/es_ES/lisp.ts @@ -1,947 +1,1180 @@ - - + + + + About - - Authors - Autores + + Authors + Autores - - Contributors - Contribuidores + + Contributors + Contribuidores - - Translators - Traductores + + Translators + Traductores - - About Linux Show Player - Acerca de Linux Show Player + + About Linux Show Player + Acerca de Linux Show Player - - + + AboutDialog - - Linux Show Player is a cue-player designed for stage productions. - Linux Show Player es un reproductor de Cues diseñado para producciones escénicas + + Linux Show Player is a cue-player designed for stage productions. + Linux Show Player es un reproductor de Cues diseñado para producciones escénicas. - - Web site - Sitio Web + + Web site + Sitio Web - - Users group - Grupo de usuarios + + Users group + Grupo de usuarios - - Source code - Código fuente + + Source code + Código fuente - - Info - Información + + Info + Información - - License - Licencia + + License + Licencia - - Contributors - Contibuidores + + Contributors + Contibuidores - - - AppGeneralSettings - - Startup layout - Layout de inicio + + Discussion + Discusión + + + Actions - - Use startup dialog - Usar diálogo de inicio + + Undo: {} + Deshacer: {} - - Application theme - Tema de la aplicación + + Redo: {} + Rehacer: {} - - + + AppConfiguration - - LiSP preferences - Preferencias de LiSP + + LiSP preferences + Preferencias de LiSP + + + + AppGeneralSettings + + + Startup layout + Layout de inicio + + + + Use startup dialog + Usar diálogo de inicio + + + + Application theme + Tema de la aplicación + + + + Default layout + Diseño por defecto + + + + Enable startup layout selector + Activar a selector de diseño de inicio - - + + + Application themes + Temas de la Aplicación + + + + UI theme + Tema de interfaz de usuario + + + + Icons theme + Tema de iconos + + + CartLayout - - Add page - Añadir página + + Add page + Añadir página - - Add pages - Añadir páginas + + Add pages + Añadir páginas - - Remove current page - Eliminar página actual + + Remove current page + Eliminar página actual - - Countdown mode - Modo de cuenta regresiva + + Countdown mode + Modo de cuenta regresiva - - Show seek-bars - Mostrar barra de búsqueda + + Show seek-bars + Mostrar barra de búsqueda - - Show dB-meters - Mostrar medidores de dB + + Show dB-meters + Mostrar medidores de dB - - Show volume - Mostrar volumen + + Show volume + Mostrar volumen - - Show accurate time - Mostrar tiempo preciso + + Show accurate time + Mostrar tiempo preciso - - Edit cue - Editar cue + + Edit cue + Editar cue - - Remove - Eliminar + + Remove + Eliminar - - Select - Seleccionar + + Select + Seleccionar - - Play - Reproducir + + Play + Reproducir - - Pause - Pausa + + Pause + Pausa - - Stop - Detener + + Stop + Detener - - Reset volume - Reestablecer volumen + + Reset volume + Reestablecer volumen - - Number of Pages: - Número de páginas: + + Number of Pages: + Número de páginas: - - Warning - Advertencia + + Warning + Advertencia - - Every cue in the page will be lost. - Todos los cues en la página se perderán + + Every cue in the page will be lost. + Todos los cues en la página se perderán. - - Are you sure to continue? - ¿Está seguro de continuar? + + Are you sure to continue? + ¿Está seguro de continuar? - - Page - Página + + Page + Página - - Default behaviors - Comportamientos por defecto + + Default behaviors + Comportamientos por defecto - - Automatically add new page - Añadir página automáticamente + + Automatically add new page + Añadir página automáticamente - - Grid size - Tamaño de cuadrícula + + Grid size + Tamaño de cuadrícula - - Number of columns - Número de columnas + + Number of columns + Número de columnas - - Number of rows - Número de filas + + Number of rows + Número de filas - - + + CueAction - - Default - Por defecto + + Default + Por defecto + + + + Pause + Pausa + + + + Start + Inicio + + + + Stop + Detener + + + + FadeInStart + Inicio Fundido de entrada + + + + FadeOutStop + Detener fundido de salida + + + + FadeOutPause + Pausar fundido de salida + + + + Faded Start + Iniciar con fundido - - Pause - Pausa + + Faded Resume + Reanudar con fundido - - Start - Inicio + + Faded Pause + Pausar con fundido - - Stop - Detener + + Faded Stop + Detener con fundido - - FadeInStart - + + Faded Interrupt + Interrupción con fundido - - FadeOutStop - + + Resume + Reanudar - - FadeOutPause - + + Do Nothing + No hacer nada - - + + CueActionLog - - Cue settings changed: "{}" - Ajustes de Cue cambiados: "{}" + + Cue settings changed: "{}" + Ajustes de Cue cambiados: "{}" - - Cues settings changed. - Ajustes de Cue han cambiado + + Cues settings changed. + Ajustes de Cue han cambiado. - - + + CueAppearanceSettings - - The appearance depends on the layout - La apariencia depende del Layout + + The appearance depends on the layout + La apariencia depende del Layout - - Cue name - Nombre del Cue + + Cue name + Nombre del Cue - - NoName - Sin nombre + + NoName + Sin nombre - - Description/Note - Descripción/Notas + + Description/Note + Descripción/Notas - - Set Font Size - Establecer el tamaño de la fuente + + Set Font Size + Establecer el tamaño de la fuente - - Color - Color + + Color + Color - - Select background color - Seleccionar el color de fondo + + Select background color + Seleccionar el color de fondo - - Select font color - Seleccionar el color de la fuente + + Select font color + Seleccionar el color de la fuente - - + + CueName - - Media Cue - Cue de Media + + Media Cue + Cue de Media - - + + + CueNextAction + + + Do Nothing + No hacer nada + + + + Auto Follow + Auto seguir + + + + Auto Next + Auto Siguiente + + + CueSettings - - Pre wait - Pre wait + + Pre wait + Pre Espera - - Wait before cue execution - Wait antes de la ejecución del cue + + Wait before cue execution + Wait antes de la ejecución del cue - - Post wait - Post wait + + Post wait + Post Espera - - Wait after cue execution - Wait después de la ejecución del cue + + Wait after cue execution + Wait después de la ejecución del cue - - Next action - Siguiente acción + + Next action + Siguiente acción - - Interrupt Fade - Interrumpir Fundido + + Interrupt Fade + Interrumpir Fundido - - Fade Action - Acción de Fundido + + Fade Action + Acción de Fundido - - Behaviours - Comportamientos + + Behaviours + Comportamientos - - Pre/Post Wait - Pre/Post Wait + + Pre/Post Wait + Pre/Post Espera - - Fade In/Out - Fundido de Ingreso/Salida + + Fade In/Out + Fundido de Ingreso/Salida - - Start action - Iniciar Acción + + Start action + Iniciar Acción - - Default action to start the cue - Acción predeterminada para iniciar el Cue + + Default action to start the cue + Acción predeterminada para iniciar el Cue - - Stop action - Detener Acción + + Stop action + Detener Acción - - Default action to stop the cue - Acción predeterminada para detener el Cue + + Default action to stop the cue + Acción predeterminada para detener el Cue - - + + + Interrupt fade + Interrumpir Fundido + + + + Fade actions + Acciones de Fundido + + + Fade - - Linear - Linear + + Linear + Lineal - - Quadratic - Cuadrático + + Quadratic + Cuadrático - - Quadratic2 - Cuadrático2 + + Quadratic2 + Cuadrático2 - - + + FadeEdit - - Duration (sec) - Duración (seg.) + + Duration (sec) + Duración (seg.) - - Curve - Curva + + Curve + Curva - - + + FadeSettings - - Fade In - Fundido de ingreso + + Fade In + Fundido de ingreso - - Fade Out - Fundido de salida + + Fade Out + Fundido de salida - - + + LayoutDescription - - Organize cues in grid like pages - Organizar cues en cuadrícula como páginas + + Organize cues in grid like pages + Organizar cues en cuadrícula como páginas - - Organize the cues in a list - Organizar cues en una lista + + Organize the cues in a list + Organizar cues en una lista - - + + LayoutDetails - - Click a cue to run it - Hacer click en un cue para ejecutarlo + + Click a cue to run it + Hacer click en un cue para ejecutarlo - - SHIFT + Click to edit a cue - SHIFT + Click para editar un cue + + SHIFT + Click to edit a cue + SHIFT + Click para editar un cue - - CTRL + Click to select a cue - CTRL + Click para seleccionar un cue + + CTRL + Click to select a cue + CTRL + Click para seleccionar un cue - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Barra espaciadora o Doble click para editar un cue + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Barra espaciadora o Doble click para editar un cue - - To copy cues drag them while pressing CTRL - Para copiar cues arrástrelos mientras presiona CTRL + + To copy cues drag them while pressing CTRL + Para copiar cues arrástrelos mientras presiona CTRL - - To copy cues drag them while pressing SHIFT - Para copiar Cues arrástrelos mientras presiona SHIFT + + To copy cues drag them while pressing SHIFT + Para copiar Cues arrástrelos mientras presiona SHIFT - - CTRL + Left Click to select cues - CTRL + Click Izquierdo para seleccionar Cues + + CTRL + Left Click to select cues + CTRL + Click Izquierdo para seleccionar Cues - - To move cues drag them - Para mover Cues arrástrelos + + To move cues drag them + Para mover Cues arrástrelos - - + + LayoutSelect - - Layout selection - Selección de Layout + + Layout selection + Selección de Layout - - Select layout - Seleccionar Layout + + Select layout + Seleccionar Layout - - Open file - Abrir archivo + + Open file + Abrir archivo - - + + ListLayout - - Show playing cues - Mostrar los cues en ejecución + + Show playing cues + Mostrar los cues en ejecución + + + + Show dB-meters + Mostrar medidor de dB + + + + Show seek-bars + Mostrar barras de búsqueda - - Show dB-meters - Mostrar medidor de dB + + Show accurate time + Mostrar tiempo preciso - - Show seek-bars - Mostrar barras de búsqueda + + Auto-select next cue + Seleccionar automáticamente el siguiente cue - - Show accurate time - Mostrar tiempo preciso + + Edit cue + Editar cue - - Auto-select next cue - Seleccionar automáticamente el siguiente cue + + Remove + Eliminar - - Edit cue - Editar cue + + Select + Seleccionar - - Remove - Eliminar + + Stop all + Detener todo - - Select - Seleccionar + + Pause all + Pausar todo - - Stop all - Detener todo + + Restart all + Reiniciar todo - - Pause all - Pausar todo + + Stop + Detener - - Restart all - Reiniciar todo + + Restart + Reiniciar - - Stop - Detener + + Default behaviors + Comportamiento por defecto - - Restart - Reiniciar + + At list end: + Al final de la lista: - - Default behaviors - Comportamiento por defecto + + Go key: + Tecla de Go: - - At list end: - Al final de la lista: + + Interrupt all + Interrumpir todo - - Go key: - Tecla de GO + + Fade-Out all + Fundido de Salida para Todo - - Interrupt all - Interrumpir todo + + Fade-In all + Fundido de Entrada para Todo - - Fade-Out all - Fundido de Salida para Todo + + Use fade + Usar fundido - - Fade-In all - Fundido de Entrada para Todo + + Stop Cue + Detener Cue - - Use fade - Usar fundido + + Pause Cue + Pausar Cue - - Stop Cue - Detener Cue + + Restart Cue + Reiniciar Cue - - Pause Cue - Pausar Cue + + Interrupt Cue + Interrumpir Cue - - Restart Cue - Reiniciar Cue + + Stop All + Detener Todo - - Interrupt Cue - Interrumpir Cue + + Pause All + Pausar Todo - - Stop All - Detener Todo + + Restart All + Reiniciar Todo - - Pause All - Pausar Todo + + Interrupt All + Interrumpir todo - - Restart All - Reiniciar Todo + + Use fade (global actions) + Usar Fundido (acciones globales) - - Interrupt All - Interrumpir todo + + Resume All + Reanudar todo - - + + ListLayoutHeader - - Cue - Cue + + Cue + Cue - - Pre wait - Pre wait + + Pre wait + Pre espera - - Action - Acción + + Action + Acción - - Post wait - Post wait + + Post wait + Post espera - - + + ListLayoutInfoPanel - - Cue name - Nombre del Cue + + Cue name + Nombre del Cue - - Cue description - Descripción del cue + + Cue description + Descripción del cue - - + + Logging - - Information - Información + + Information + Información - - Debug - Depurar + + Debug + Depurar - - Warning - Advertencia + + Warning + Advertencia - - Error - Error + + Error + Error - - Details: - Detalles: + + Details: + Detalles: - - + + + Dismiss all + Descartar todo + + + + Show details + Mostrar detalles + + + + Linux Show Player - Log Viewer + Linux Show Player - Visor de registro + + + + Info + Información + + + + Critical + Crítico + + + + Time + Tiempo + + + + Milliseconds + Milisegundos + + + + Logger name + Número de Logger + + + + Level + Nivel + + + + Message + Mensaje + + + + Function + Función + + + + Path name + Nombre de la ruta + + + + File name + Nombre del archivo + + + + Line no. + Línea no. + + + + Module + Módulo + + + + Process ID + ID del proceso + + + + Process name + Nombre del proceso + + + + Thread ID + ID del hilo + + + + Thread name + Nombre del hilo + + + MainWindow - - &File - &Archivo + + &File + &Archivo - - New session - Nueva sesión + + New session + Nueva sesión - - Open - Abrir + + Open + Abrir - - Save session - Guardar sesión + + Save session + Guardar sesión - - Preferences - Preferencias + + Preferences + Preferencias - - Save as - Guardar como + + Save as + Guardar como - - Full Screen - Pantalla completa + + Full Screen + Pantalla completa - - Exit - Salir + + Exit + Salir - - &Edit - &Editar + + &Edit + &Editar - - Undo - Deshacer + + Undo + Deshacer - - Redo - Rehacer + + Redo + Rehacer - - Select all - Seleccionar todo + + Select all + Seleccionar todo - - Select all media cues - Seleccionar todos los Media cues + + Select all media cues + Seleccionar todos los Media cues - - Deselect all - Deseleccionar todo + + Deselect all + Deseleccionar todo - - CTRL+SHIFT+A - CTRL+SHIFT+A + + CTRL+SHIFT+A + CTRL + SHIFT + A - - Invert selection - Invertir selección + + Invert selection + Invertir selección - - CTRL+I - CTRL+I + + CTRL+I + CTRL + I - - Edit selected - Editar seleccionado + + Edit selected + Editar seleccionado - - CTRL+SHIFT+E - CTRL+SHIFT+E + + CTRL+SHIFT+E + CTRL + SHIFT + E - - &Layout - &Layout + + &Layout + &Layout - - &Tools - &Herramientas + + &Tools + &Herramientas - - Edit selection - Editar selección + + Edit selection + Editar selección - - &About - &Acerca + + &About + &Acerca - - About - Acerca + + About + Acerca - - About Qt - Acerca de QT + + About Qt + Acerca de Qt - - Undone: - Deshacer + + Undone: + Deshacer: - - Redone: - Rehacer + + Redone: + Rehacer: - - Close session - Cerrar sesión + + Close session + Cerrar sesión - - The current session is not saved. - La sección actual no se ha guardado + + The current session is not saved. + La sección actual no se ha guardado. - - Discard the changes? - ¿Descartar cambios? + + Discard the changes? + ¿Descartar cambios? - - + + MediaCueMenus - - Audio cue (from file) - Cue de audio (desde archivo) + + Audio cue (from file) + Cue de audio (desde archivo) - - Select media files - Seleccionar archivos de medios + + Select media files + Seleccionar archivos de medios - - + + MediaCueSettings - - Start time - Tiempo de inicio + + Start time + Tiempo de inicio - - Stop position of the media - Posición de detención del media + + Stop position of the media + Posición de detención del media - - Stop time - Tiempo de detención + + Stop time + Tiempo de detención - - Start position of the media - Posición de inicio del media + + Start position of the media + Posición de inicio del media - - Loop - Bucle + + Loop + Bucle - - Repetition after first play (-1 = infinite) - Repeticiones despues de la primera ejecución (-1 = infinito) + + Repetition after first play (-1 = infinite) + Repeticiones después de la primera ejecución (-1 = infinito) - - + + QColorButton - - Right click to reset - Click derecho para reiniciar + + Right click to reset + Click derecho para reiniciar - - + + SettingsPageName - - Appearance - Apariencia + + Appearance + Apariencia + + + + General + General + + + + Cue + Cue + + + + Cue Settings + Ajustes de Cue + + + + Plugins + Plugins - - General - General + + Behaviours + Comportamientos - - Cue - Cue + + Pre/Post Wait + Pre/Post Espera - - Cue Settings - Ajustes de Cue + + Fade In/Out + Fundido de Ingreso/Salida - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/es_ES/list_layout.ts b/lisp/i18n/ts/es_ES/list_layout.ts new file mode 100644 index 000000000..56a0186fe --- /dev/null +++ b/lisp/i18n/ts/es_ES/list_layout.ts @@ -0,0 +1,189 @@ + + + + + LayoutDescription + + + Organize the cues in a list + Organizar cues en una lista + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Barra espaciadora o Doble click para editar un cue + + + + To copy cues drag them while pressing CTRL + Para copiar cues, arrástrelos mientras presiona CTRL + + + + To move cues drag them + Para mover Cues arrástrelos + + + + ListLayout + + + Default behaviors + Comportamientos por defecto + + + + Show playing cues + Mostrar los cues en ejecución + + + + Show dB-meters + Mostrar medidores de dB + + + + Show accurate time + Mostrar tiempo preciso + + + + Show seek-bars + Mostrar barras de búsqueda + + + + Auto-select next cue + Seleccionar automáticamente el siguiente cue + + + + Enable selection mode + Habilitar modo de selección + + + + Go key: + Tecla de Go: + + + + Use fade (buttons) + Usar fundido (botones) + + + + Stop Cue + Detener Cue + + + + Pause Cue + Pausar Cue + + + + Resume Cue + Reanudar Cue + + + + Interrupt Cue + Interrumpir Cue + + + + Edit cue + Editar cue + + + + Edit selected cues + Editar los cues seleccionados + + + + Remove cue + Eliminar cue + + + + Remove selected cues + Eliminar los cues seleccionados + + + + Selection mode + Modo de selección + + + + Pause all + Pausar todo + + + + Stop all + Detener todo + + + + Interrupt all + Interrumpir todo + + + + Resume all + Continuar todas + + + + Fade-Out all + Fundido de Salida para Todo + + + + Fade-In all + Fundido de Entrada para Todo + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre Espera + + + + Action + Acción + + + + Post wait + Post Espera + + + + ListLayoutInfoPanel + + + Cue name + Nombre del Cue + + + + Cue description + Descripción del cue + + + diff --git a/lisp/i18n/ts/es_ES/media_info.ts b/lisp/i18n/ts/es_ES/media_info.ts index 86257290a..e77575f4e 100644 --- a/lisp/i18n/ts/es_ES/media_info.ts +++ b/lisp/i18n/ts/es_ES/media_info.ts @@ -1,30 +1,37 @@ - - + + + + MediaInfo - - Media Info - Información del Media + + Media Info + Información del Media - - Error - Error + + Error + Error - - No info to display - Ninguna información para mostrar + + No info to display + Ninguna información para mostrar - - Info - Información + + Info + Información - - Value - Valor + + Value + Valor - - \ No newline at end of file + + + Warning + Advertencia + + + diff --git a/lisp/i18n/ts/es_ES/midi.ts b/lisp/i18n/ts/es_ES/midi.ts index e34f88be3..efa916b11 100644 --- a/lisp/i18n/ts/es_ES/midi.ts +++ b/lisp/i18n/ts/es_ES/midi.ts @@ -1,28 +1,30 @@ - - + + + + MIDISettings - - MIDI default devices - Dispositivos MIDI por defecto + + MIDI default devices + Dispositivos MIDI por defecto - - Input - Entrada + + Input + Entrada - - Output - Salida + + Output + Salida - - + + SettingsPageName - - MIDI settings - Ajustes MIDI + + MIDI settings + Ajustes MIDI - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/es_ES/network.ts b/lisp/i18n/ts/es_ES/network.ts new file mode 100644 index 000000000..7e796fdb6 --- /dev/null +++ b/lisp/i18n/ts/es_ES/network.ts @@ -0,0 +1,47 @@ + + + + + NetworkDiscovery + + + Host discovery + Descubrimiento de host + + + + Manage hosts + Administrar hosts + + + + Discover hosts + Descubrir hosts + + + + Manually add a host + Añadir un host manualmente + + + + Remove selected host + Remover el host seleccionado + + + + Remove all host + Eliminar todos los hosts + + + + Address + Dirección + + + + Host IP + IP del host + + + diff --git a/lisp/i18n/ts/es_ES/osc.ts b/lisp/i18n/ts/es_ES/osc.ts new file mode 100644 index 000000000..2d2fb3f8e --- /dev/null +++ b/lisp/i18n/ts/es_ES/osc.ts @@ -0,0 +1,35 @@ + + + + + OscSettings + + + OSC Settings + Configuración de OSC + + + + Input Port: + Puerto de entrada: + + + + Output Port: + Puerto de salida: + + + + Hostname: + Nombre de host: + + + + SettingsPageName + + + OSC settings + Configuración de OSC + + + diff --git a/lisp/i18n/ts/es_ES/presets.ts b/lisp/i18n/ts/es_ES/presets.ts index 2e4160225..78bd5579f 100644 --- a/lisp/i18n/ts/es_ES/presets.ts +++ b/lisp/i18n/ts/es_ES/presets.ts @@ -1,138 +1,150 @@ - - + + + + Preset - - Create Cue - Crear Cue + + Create Cue + Crear Cue - - Load on selected Cues - Abrir en Cues seleccionadas + + Load on selected Cues + Abrir en Cues seleccionadas - - + + Presets - - Presets - Presets + + Presets + Preajustes - - Load preset - Cargar Preset + + Load preset + Cargar Preset - - Save as preset - Guardar como Preset + + Save as preset + Guardar como Preset - - Cannot scan presets - No se pueden escanear Presets + + Cannot scan presets + No se pueden escanear Presets - - Error while deleting preset "{}" - Error al borrar Preset "{}" + + Error while deleting preset "{}" + Error al borrar Preset "{}" - - Cannot load preset "{}" - No se puede cargar Preset "{}" + + Cannot load preset "{}" + No se puede cargar Preset "{}" - - Cannot save preset "{}" - No se puede guardar Preset "{}" + + Cannot save preset "{}" + No se puede guardar Preset "{}" - - Cannot rename preset "{}" - No se puede cambiar el nombre del Preset "{}" + + Cannot rename preset "{}" + No se puede cambiar el nombre del Preset "{}" - - Select Preset - Seleccionar Preset + + Select Preset + Seleccionar Preset - - Preset already exists, overwrite? - El Preset ya existe, ¿desea sobreescribirlo? + + Preset already exists, overwrite? + El Preset ya existe, ¿desea sobreescribirlo? - - Preset name - Nombre del Preset + + Preset name + Nombre del Preset - - Add - Añadir + + Add + Añadir - - Rename - Cambiar nombre + + Rename + Cambiar nombre - - Edit - Editar + + Edit + Editar - - Remove - Eliminar + + Remove + Eliminar - - Export selected - Exportar seleccionados + + Export selected + Exportar seleccionados - - Import - Importar + + Import + Importar - - Warning - Advertencia + + Warning + Advertencia - - The same name is already used! - ¡El mismo nombre ya está siendo usado! + + The same name is already used! + ¡El mismo nombre ya está siendo usado! - - Cannot create a cue from this preset: {} - No se puede crear un Cue desde este Preset: {} + + Cannot create a cue from this preset: {} + No se puede crear un Cue desde este Preset: {} - - Cannot export correctly. - No se puede exportar correctamente. + + Cannot export correctly. + No se puede exportar correctamente. - - Some presets already exists, overwrite? - Algunos Presets ya existen, ¿desea sobreescribirlos? + + Some presets already exists, overwrite? + Algunos Presets ya existen, ¿desea sobreescribirlos? - - Cannot import correctly. - No se puede importar correctamente. + + Cannot import correctly. + No se puede importar correctamente. - - Cue type - Tipo de Cue + + Cue type + Tipo de Cue - - \ No newline at end of file + + + Load on cue + Cargar en cue + + + + Load on selected cues + Cargar en cues seleccionados + + + diff --git a/lisp/i18n/ts/es_ES/rename_cues.ts b/lisp/i18n/ts/es_ES/rename_cues.ts new file mode 100644 index 000000000..ac6d1718f --- /dev/null +++ b/lisp/i18n/ts/es_ES/rename_cues.ts @@ -0,0 +1,95 @@ + + + + + RenameCues + + + Rename Cues + Renombrar cues + + + + Rename cues + Renombrar cues + + + + Current + Actual + + + + Preview + Vista previa + + + + Capitalize + En mayúsculas + + + + Lowercase + Minúsculas + + + + Uppercase + Mayúsculas + + + + Remove Numbers + Eliminar números + + + + Add numbering + Añadir numeración + + + + Reset + Reestablecer + + + + Rename all cue. () in regex below usable with $0, $1 ... + Cambie el nombre todos cue. () en regex abajo con $0, $1... + + + + Type your regex here: + Escribir tu regex aquí: + + + + Regex help + Ayuda de regex + + + + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple : +^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation + Puede utilizar Regexes para renombar cues. + +Insertar expresiones capturadas con regexes en la línea abajo con $0 para el primer paréntesis, $1 para el segundo, etc.... +En la segunda línea, puede usar Regexes Python estándard para igualar expresiones en los nombres originales de los cues. Use paréntesis para capturar partes de una expresión igualada. + +Ejemplo: +^[a-z]([0-9]+) encontrará un carácter en minúsculas ([a-z]), seguido por uno o más números. +Solo los números están entre paréntesis y serán usados con $0 en la primera línea. + +Para mas información acerca de Regexes, consultar la documentación de Python + + + diff --git a/lisp/i18n/ts/es_ES/replay_gain.ts b/lisp/i18n/ts/es_ES/replay_gain.ts index 86f5db96a..860473050 100644 --- a/lisp/i18n/ts/es_ES/replay_gain.ts +++ b/lisp/i18n/ts/es_ES/replay_gain.ts @@ -1,50 +1,52 @@ - - + + + + ReplayGain - - ReplayGain / Normalization - ReplayGain / Normalización + + ReplayGain / Normalization + ReplayGain / Normalización - - Calculate - Calcular + + Calculate + Calcular - - Reset all - Reestablecer todo + + Reset all + Reestablecer todo - - Reset selected - Reestablecer seleccionados + + Reset selected + Reestablecer seleccionados - - Threads number - Número de procesos + + Threads number + Número de procesos - - Apply only to selected media - Aplicar solamente a los media seleccionados + + Apply only to selected media + Aplicar solamente a los media seleccionados - - ReplayGain to (dB SPL) - ReplayGain a (dB SPL) + + ReplayGain to (dB SPL) + ReplayGain a (dB SPL) - - Normalize to (dB) - Normalizar a (dB) + + Normalize to (dB) + Normalizar a (dB) - - Processing files ... - Procesando archivos ... + + Processing files ... + Procesando archivos ... - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/es_ES/synchronizer.ts b/lisp/i18n/ts/es_ES/synchronizer.ts index c0ffad302..8082c153c 100644 --- a/lisp/i18n/ts/es_ES/synchronizer.ts +++ b/lisp/i18n/ts/es_ES/synchronizer.ts @@ -1,83 +1,85 @@ - - + + + + SyncPeerDialog - - Manage connected peers - Gestionar peers conectados + + Manage connected peers + Gestionar peers conectados - - Discover peers - Descubrir peers + + Discover peers + Descubrir peers - - Manually add a peer - Añadir un peer manualmente + + Manually add a peer + Añadir un peer manualmente - - Remove selected peer - Eliminar el peer seleccionado + + Remove selected peer + Eliminar el peer seleccionado - - Remove all peers - Eliminar todos los peers + + Remove all peers + Eliminar todos los peers - - Address - Dirección + + Address + Dirección - - Peer IP - IP del peer + + Peer IP + IP del peer - - Error - Error + + Error + Error - - Already connected - Ya está conectado + + Already connected + Ya está conectado - - Cannot add peer - Imposible añadir peer + + Cannot add peer + Imposible añadir peer - - + + Synchronizer - - Synchronization - Sincronización + + Synchronization + Sincronización - - Manage connected peers - Gestionar peers conectados + + Manage connected peers + Gestionar peers conectados - - Show your IP - Mostrar su IP + + Show your IP + Mostrar su IP - - Your IP is: - Su IP es: + + Your IP is: + Su IP es: - - Discovering peers ... - Descubriendo peers ... + + Discovering peers ... + Descubriendo peers ... - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/es_ES/timecode.ts b/lisp/i18n/ts/es_ES/timecode.ts index d6b2993a8..bdc33a856 100644 --- a/lisp/i18n/ts/es_ES/timecode.ts +++ b/lisp/i18n/ts/es_ES/timecode.ts @@ -1,81 +1,110 @@ - - + + + + SettingsPageName - - Timecode Settings - Ajustes de Código de Tiemo + + Timecode Settings + Ajustes de Código de Tiempo - - Timecode - Código de Tiempo + + Timecode + Código de Tiempo - - + + Timecode - - Cannot send timecode. - No se puede mandar Código de Tiempo + + Cannot send timecode. + No se puede mandar Código de Tiempo. - - OLA has stopped. - OLA se ha detenido + + OLA has stopped. + OLA se ha detenido. - - + + + Cannot load timecode protocol: "{}" + No se puede cargar el Código de Tiempo: "{}" + + + + Cannot send timecode. +OLA has stopped. + No se puede enviar Código de Tiempo. +OLA se ha detenido. + + + TimecodeSettings - - OLA Timecode Settings - Ajustes de Código de Tiempo OLA + + OLA Timecode Settings + Ajustes de Código de Tiempo OLA + + + + Enable Plugin + Activar Plugin + + + + High-Resolution Timecode + Código de Tiempo de Alta Resolución + + + + Timecode Format: + Formato del Código de Tiempo: - - Enable Plugin - Activar Plugin + + OLA status + Estado de OLA - - High-Resolution Timecode - Código de Tiempo de Alta Resolución + + OLA is not running - start the OLA daemon. + OLA no se está ejecutando - iniciar el daemon OLA. - - Timecode Format: - Formato del Código de Tiempò + + Replace HOURS by a static track number + Reemplazar HORAS por un número de track estático - - OLA status - Estado de OLA + + Enable ArtNet Timecode + Activar Código de Tiempo ArtNet - - OLA is not running - start the OLA daemon. - OLA no se está ejecutanto - iniciar el daemon OLA + + Track number + Número de Track - - Replace HOURS by a static track number - Reemplazar HORAS por un número de track estático + + To send ArtNet Timecode you need to setup a running OLA session! + ¡Para mandar Código de Tiempo ArtNet necesita configurar una sesión OLA en ejecución! - - Enable ArtNet Timecode - Activar Código de Tiempo ArtNet + + Enable Timecode + Habilitar Código de Tiempo - - Track number - Número de Track + + Timecode Settings + Ajustes de Código de Tiempo - - To send ArtNet Timecode you need to setup a running OLA session! - ¡Para mandar Código de Tiempo ArtNet necesita configurar una sesión OLA en ejecución! + + Timecode Protocol: + Protocolo de Código de Tiempo: - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/es_ES/triggers.ts b/lisp/i18n/ts/es_ES/triggers.ts index 397ba1295..b4ed81282 100644 --- a/lisp/i18n/ts/es_ES/triggers.ts +++ b/lisp/i18n/ts/es_ES/triggers.ts @@ -1,61 +1,63 @@ - - + + + + CueTriggers - - Started - Iniciado + + Started + Iniciado - - Paused - Pausado + + Paused + Pausado - - Stopped - Detenido + + Stopped + Detenido - - Ended - Terminado + + Ended + Terminado - - + + SettingsPageName - - Triggers - Disparadores + + Triggers + Disparadores - - + + TriggersSettings - - Add - Añadir + + Add + Añadir - - Remove - Eliminar + + Remove + Eliminar - - Trigger - Disparador + + Trigger + Disparador - - Cue - Cue + + Cue + Cue - - Action - Acción + + Action + Acción - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/fr_FR/action_cues.ts b/lisp/i18n/ts/fr_FR/action_cues.ts index dd3051f3c..c1411454b 100644 --- a/lisp/i18n/ts/fr_FR/action_cues.ts +++ b/lisp/i18n/ts/fr_FR/action_cues.ts @@ -1,244 +1,345 @@ - - + + + + CollectionCue - - Add - Ajouter + + Add + Ajouter - - Remove - Retirer + + Remove + Retirer - - Cue - Cue + + Cue + Cue - - Action - Action + + Action + Action - - + + CommandCue - - Process ended with an error status. - Le process s'est terminé par une erreur + + Process ended with an error status. + Le process s'est terminé par une erreur - - Exit code: - Code de sortie : + + Exit code: + Code de sortie : - - Command - Commande + + Command + Commande - - Command to execute, as in a shell - Commande à exécuter, comme dans un terminal + + Command to execute, as in a shell + Commande à exécuter, comme dans un terminal - - Discard command output - Abandonner la sortie de commande + + Discard command output + Abandonner la sortie de commande - - Ignore command errors - Ignorer les erreurs de commande + + Ignore command errors + Ignorer les erreurs de commande - - Kill instead of terminate - Killer au lieu de terminer + + Kill instead of terminate + Killer au lieu de terminer - - + + + Command cue ended with an error status. Exit code: {} + Command cue ended with an error status. Exit code: {} + + + + Cue Name + + + OSC Settings + OSC Settings + + + CueName - - Command Cue - Cue de commande + + Command Cue + Cue de commande - - MIDI Cue - Cue MIDI + + MIDI Cue + Cue MIDI - - Volume Control - Contrôle du Volume + + Volume Control + Contrôle du Volume - - Seek Cue - Recherche de Cue + + Seek Cue + Recherche de Cue - - Collection Cue - Collection de cue + + Collection Cue + Collection de cue - - Stop-All - Tout arrêter + + Stop-All + Tout arrêter - - Index Action - Index d'action + + Index Action + Index d'action - - + + + OSC Cue + OSC Cue + + + IndexActionCue - - Index - Index + + Index + Index + + + + Use a relative index + Utiliser un index relatif + + + + Target index + Index cible - - Use a relative index - Utiliser un index relatif + + Action + Action - - Target index - Index cible + + No suggestion + No suggestion - - Action - Action + + Suggested cue name + Suggested cue name - - + + MIDICue - - MIDI Message - Message MIDI + + MIDI Message + Message MIDI + + + + Message type + Type de message + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OscCue + + + Error during cue execution. + Error during cue execution. + + + + OSC Message + OSC Message + + + + Add + Add + + + + Remove + Remove + + + + Test + Test + + + + OSC Path: (example: "/path/to/something") + OSC Path: (example: "/path/to/something") + + + + Fade + Fade + + + + Time (sec) + Time (sec) - - Message type - Type de message + + Curve + Curve - - + + SeekCue - - Cue - Cue + + Cue + Cue - - Click to select - Cliquer pour sélectionner + + Click to select + Cliquer pour sélectionner - - Not selected - Non sélectionné + + Not selected + Non sélectionné - - Seek - Recherche + + Seek + Recherche - - Time to reach - Temps à atteindre + + Time to reach + Temps à atteindre - - + + SettingsPageName - - Command - Commande + + Command + Commande - - MIDI Settings - Préférences MIDI + + MIDI Settings + Préférences MIDI - - Volume Settings - Préférences de volume + + Volume Settings + Préférences de volume - - Seek Settings - Préférences de recherche + + Seek Settings + Préférences de recherche - - Edit Collection - Editer la collection + + Edit Collection + Editer la collection - - Action Settings - Paramètres d'action + + Action Settings + Paramètres d'action - - Stop Settings - Paramètres d'arrêt + + Stop Settings + Paramètres d'arrêt - - + + StopAll - - Stop Action - Action d'arrêt + + Stop Action + Action d'arrêt - - + + VolumeControl - - Error during cue execution - Erreur lors de l'exécution de la cue + + Error during cue execution + Erreur lors de l'exécution de la cue - - Cue - Cue + + Cue + Cue - - Click to select - Cliquer pour sélectionner + + Click to select + Cliquer pour sélectionner - - Not selected - Non sélectionné + + Not selected + Non sélectionné - - Volume to reach - Volume à atteindre + + Volume to reach + Volume à atteindre - - Fade - Fondu + + Fade + Fondu - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/fr_FR/cart_layout.ts b/lisp/i18n/ts/fr_FR/cart_layout.ts new file mode 100644 index 000000000..690241600 --- /dev/null +++ b/lisp/i18n/ts/fr_FR/cart_layout.ts @@ -0,0 +1,176 @@ + + + + + CartLayout + + + Default behaviors + Default behaviors + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show volume + Show volume + + + + Automatically add new page + Automatically add new page + + + + Grid size + Grid size + + + + Number of columns + Number of columns + + + + Number of rows + Number of rows + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Number of Pages: + Number of Pages: + + + + Page {number} + Page {number} + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + LayoutDescription + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + LayoutDetails + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them while pressing SHIFT + To move cues drag them while pressing SHIFT + + + + ListLayout + + + Edit cue + Edit cue + + + + Edit selected cues + Edit selected cues + + + + Remove cue + Remove cue + + + + Remove selected cues + Remove selected cues + + + diff --git a/lisp/i18n/ts/fr_FR/controller.ts b/lisp/i18n/ts/fr_FR/controller.ts index 211a244ed..019b8bf8e 100644 --- a/lisp/i18n/ts/fr_FR/controller.ts +++ b/lisp/i18n/ts/fr_FR/controller.ts @@ -1,99 +1,198 @@ - - + + + + + Controller + + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + ControllerKeySettings - - Key - Touche + + Key + Touche - - Action - Action + + Action + Action - - Shortcuts - Raccourcis + + Shortcuts + Raccourcis - - + + ControllerMidiSettings - - MIDI - MIDI + + MIDI + MIDI - - Type - Type + + Type + Type - - Channel - Canal + + Channel + Canal - - Note - Note + + Note + Note - - Action - Action + + Action + Action - - Filter "note on" - Filtrer les "note on" + + Filter "note on" + Filtrer les "note on" - - Filter "note off" - Filtrer les "note off" + + Filter "note off" + Filtrer les "note off" - - Capture - Capture + + Capture + Capture - - Listening MIDI messages ... - Écoute des messages MIDI ... + + Listening MIDI messages ... + Écoute des messages MIDI ... - - + + + ControllerOscSettings + + + OSC Message + OSC Message + + + + OSC Path: (example: "/path/to/something") + OSC Path: (example: "/path/to/something") + + + + OSC + OSC + + + + Path + Path + + + + Types + Types + + + + Arguments + Arguments + + + + Actions + Actions + + + + OSC Capture + OSC Capture + + + + Add + Add + + + + Remove + Remove + + + + Capture + Capture + + + ControllerSettings - - Add - Ajouter + + Add + Ajouter + + + + Remove + Retirer + + + Osc Cue - - Remove - Retirer + + Type + Type - - + + + Argument + Argument + + + + OscCue + + + Add + Add + + + + Remove + Remove + + + SettingsPageName - - Cue Control - Contrôle de la cue + + Cue Control + Contrôle de la cue + + + + MIDI Controls + Contrôles MIDI - - MIDI Controls - Contrôles MIDI + + Keyboard Shortcuts + Raccourcis-clavier - - Keyboard Shortcuts - Raccourcis-clavier + + OSC Controls + OSC Controls - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/fr_FR/gst_backend.ts b/lisp/i18n/ts/fr_FR/gst_backend.ts index f70ef4a2a..4eaff592b 100644 --- a/lisp/i18n/ts/fr_FR/gst_backend.ts +++ b/lisp/i18n/ts/fr_FR/gst_backend.ts @@ -1,368 +1,388 @@ - - + + + + AlsaSinkSettings - - ALSA device - Périphérique ALSA + + ALSA device + Périphérique ALSA - - ALSA devices, as defined in an asound configuration file - Périphériques ALSA, comme définis dans le fichier de configuration asound + + ALSA devices, as defined in an asound configuration file + Périphériques ALSA, comme définis dans le fichier de configuration asound - - + + AudioDynamicSettings - - Compressor - Compresseur + + Compressor + Compresseur - - Expander - Expandeur + + Expander + Expandeur - - Soft Knee - Coude léger + + Soft Knee + Coude léger - - Hard Knee - Coude dur + + Hard Knee + Coude dur - - Compressor/Expander - Compresseur / expandeur + + Compressor/Expander + Compresseur / expandeur - - Type - Type + + Type + Type - - Curve Shape - Forme de la courbe + + Curve Shape + Forme de la courbe - - Ratio - Ratio + + Ratio + Ratio - - Threshold (dB) - Seuil (dB) + + Threshold (dB) + Seuil (dB) - - + + AudioPanSettings - - Audio Pan - Panoramique audio + + Audio Pan + Panoramique audio - - Center - Centre + + Center + Centre - - Left - Gauche + + Left + Gauche - - Right - Droit + + Right + Droit - - + + DbMeterSettings - - DbMeter settings - Préférences du mesureur de dB + + DbMeter settings + Préférences du mesureur de dB - - Time between levels (ms) - Temps entre les niveaux (ms) + + Time between levels (ms) + Temps entre les niveaux (ms) - - Peak ttl (ms) - Crête ttl (ms) + + Peak ttl (ms) + Crête ttl (ms) - - Peak falloff (dB/sec) - Crête falloff (dB/sec) + + Peak falloff (dB/sec) + Crête falloff (dB/sec) - - + + Equalizer10Settings - - 10 Bands Equalizer (IIR) - Égaliseur 10 bandes (IIR) + + 10 Bands Equalizer (IIR) + Égaliseur 10 bandes (IIR) - - + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + GstMediaSettings - - Change Pipeline - Changer le bitoduc + + Change Pipeline + Changer le bitoduc - - + + GstPipelineEdit - - Edit Pipeline - Éditer le bitoduc + + Edit Pipeline + Éditer le bitoduc - - + + GstSettings - - Pipeline - Bitoduc + + Pipeline + Bitoduc - - + + JackSinkSettings - - Connections - Connexions + + Connections + Connexions - - Edit connections - Éditer les connexions + + Edit connections + Éditer les connexions - - Output ports - Ports de sortie + + Output ports + Ports de sortie - - Input ports - Ports d'entrée + + Input ports + Ports d'entrée - - Connect - Connecter + + Connect + Connecter - - Disconnect - Déconnecter + + Disconnect + Déconnecter - - + + MediaElementName - - Compressor/Expander - Compresseur / expandeur + + Compressor/Expander + Compresseur / expandeur - - Audio Pan - Panoramique audio + + Audio Pan + Panoramique audio - - PulseAudio Out - Sortie PulseAudio + + PulseAudio Out + Sortie PulseAudio - - Volume - Volume + + Volume + Volume - - dB Meter - Mesureur de dB + + dB Meter + Mesureur de dB - - System Input - Entrée système + + System Input + Entrée système - - ALSA Out - Sortie ALSA + + ALSA Out + Sortie ALSA - - JACK Out - Sortie JACK + + JACK Out + Sortie JACK - - Custom Element - Élément personnalisé + + Custom Element + Élément personnalisé - - System Out - Sortie système + + System Out + Sortie système - - Pitch - Hauteur de note + + Pitch + Hauteur de note - - URI Input - Entrée URI + + URI Input + Entrée URI - - 10 Bands Equalizer - Égaliseur 10 bandes + + 10 Bands Equalizer + Égaliseur 10 bandes - - Speed - Vitesse + + Speed + Vitesse - - Preset Input - Entrée pré-sélectionnée + + Preset Input + Entrée pré-sélectionnée - - + + PitchSettings - - Pitch - Hauteur de note + + Pitch + Hauteur de note - - {0:+} semitones - {0:+} demi-tons + + {0:+} semitones + {0:+} demi-tons - - + + PresetSrcSettings - - Presets - Pré-sélections + + Presets + Pré-sélections - - + + SettingsPageName - - GStreamer settings - Préférences GStreamer + + GStreamer settings + Préférences GStreamer + + + + Media Settings + Préférences des médias - - Media Settings - Préférences des médias + + GStreamer + GStreamer - - + + SpeedSettings - - Speed - Vitesse + + Speed + Vitesse - - + + UriInputSettings - - Source - Source + + Source + Source - - Find File - Trouver le fichier + + Find File + Trouver le fichier - - Buffering - Tampon + + Buffering + Tampon - - Use Buffering - Utiliser le tampon + + Use Buffering + Utiliser le tampon - - Attempt download on network streams - Essayer de télécharger en flux de réseau + + Attempt download on network streams + Essayer de télécharger en flux de réseau - - Buffer size (-1 default value) - Taille du tampon (valeur par défaut -1) + + Buffer size (-1 default value) + Taille du tampon (valeur par défaut -1) - - Choose file - Choisir un fichier + + Choose file + Choisir un fichier - - All files - Tous les fichiers + + All files + Tous les fichiers - - + + UserElementSettings - - User defined elements - Éléments spécifiés par l'utilisateur + + User defined elements + Éléments spécifiés par l'utilisateur - - Only for advanced user! - Seulement pour utilisateurs avancés ! + + Only for advanced user! + Seulement pour utilisateurs avancés ! - - + + VolumeSettings - - Volume - Volume + + Volume + Volume - - Normalized volume - Volume normalisé + + Normalized volume + Volume normalisé - - Reset - Réinitialiser + + Reset + Réinitialiser - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/fr_FR/lisp.ts b/lisp/i18n/ts/fr_FR/lisp.ts index b04353aee..37989e4af 100644 --- a/lisp/i18n/ts/fr_FR/lisp.ts +++ b/lisp/i18n/ts/fr_FR/lisp.ts @@ -1,947 +1,1180 @@ - - + + + + About - - Authors - Auteurs + + Authors + Auteurs - - Contributors - Contributeurs + + Contributors + Contributeurs - - Translators - Traducteurs + + Translators + Traducteurs - - About Linux Show Player - À propos de Linux Show Player + + About Linux Show Player + À propos de Linux Show Player - - + + AboutDialog - - Linux Show Player is a cue-player designed for stage productions. - Linux Show Player est un logiciel de lecture de cue pensé pour le théâtre ou l'événementiel. + + Linux Show Player is a cue-player designed for stage productions. + Linux Show Player est un logiciel de lecture de cue pensé pour le théâtre ou l'événementiel. - - Web site - Site web + + Web site + Site web - - Users group - Groupe d'utilisateurs + + Users group + Groupe d'utilisateurs - - Source code - Code source + + Source code + Code source - - Info - Info + + Info + Info - - License - Licence + + License + Licence - - Contributors - Contributeurs + + Contributors + Contributeurs - - - AppGeneralSettings - - Startup layout - État de démarrage + + Discussion + Discussion + + + Actions - - Use startup dialog - Utilisez le dialogue du démarrage + + Undo: {} + Undo: {} - - Application theme - Thème de l'application + + Redo: {} + Redo: {} - - + + AppConfiguration - - LiSP preferences - Préférences de LiSP + + LiSP preferences + Préférences de LiSP + + + + AppGeneralSettings + + + Startup layout + État de démarrage + + + + Use startup dialog + Utilisez le dialogue du démarrage + + + + Application theme + Thème de l'application + + + + Default layout + Default layout + + + + Enable startup layout selector + Enable startup layout selector - - + + + Application themes + Application themes + + + + UI theme + UI theme + + + + Icons theme + Icons theme + + + CartLayout - - Add page - Ajouter une page + + Add page + Ajouter une page - - Add pages - Ajouter des pages + + Add pages + Ajouter des pages - - Remove current page - Retirer la page actuelle + + Remove current page + Retirer la page actuelle - - Countdown mode - Compte à rebours + + Countdown mode + Compte à rebours - - Show seek-bars - Montrer le barre de recherche + + Show seek-bars + Montrer le barre de recherche - - Show dB-meters - Afficher le mesureur de dB + + Show dB-meters + Afficher le mesureur de dB - - Show volume - Afficher le volume + + Show volume + Afficher le volume - - Show accurate time - Afficher le temps exact + + Show accurate time + Afficher le temps exact - - Edit cue - Éditer la cue + + Edit cue + Éditer la cue - - Remove - Retirer + + Remove + Retirer - - Select - Sélectionner + + Select + Sélectionner - - Play - Lecture + + Play + Lecture - - Pause - Pause + + Pause + Pause - - Stop - Stop + + Stop + Stop - - Reset volume - Réinitialisation du volume + + Reset volume + Réinitialisation du volume - - Number of Pages: - Nombre de pages : + + Number of Pages: + Nombre de pages : - - Warning - Attention + + Warning + Attention - - Every cue in the page will be lost. - Toutes les cues dans la pages seront perdues + + Every cue in the page will be lost. + Toutes les cues dans la pages seront perdues - - Are you sure to continue? - Voulez-vous vraiment continuer ? + + Are you sure to continue? + Voulez-vous vraiment continuer ? - - Page - Page + + Page + Page - - Default behaviors - Comportements par défaut + + Default behaviors + Comportements par défaut - - Automatically add new page - Ajouter une page automatiquement + + Automatically add new page + Ajouter une page automatiquement - - Grid size - Taille de la grille + + Grid size + Taille de la grille - - Number of columns - Nombre de colonnes + + Number of columns + Nombre de colonnes - - Number of rows - Nombre de lignes + + Number of rows + Nombre de lignes - - + + CueAction - - Default - Défaut + + Default + Défaut + + + + Pause + Pause + + + + Start + Démarrer + + + + Stop + Stop + + + + FadeInStart + DémarrageDuFonduÀlOuverture + + + + FadeOutStop + ArrêtduFonduÀlaFermeture + + + + FadeOutPause + PauseduFonduÀlaFermeture + + + + Faded Start + Faded Start - - Pause - Pause + + Faded Resume + Faded Resume - - Start - Démarrer + + Faded Pause + Faded Pause - - Stop - Stop + + Faded Stop + Faded Stop - - FadeInStart - DémarrageDuFonduÀlOuverture + + Faded Interrupt + Faded Interrupt - - FadeOutStop - ArrêtduFonduÀlaFermeture + + Resume + Resume - - FadeOutPause - PauseduFonduÀlaFermeture + + Do Nothing + Do Nothing - - + + CueActionLog - - Cue settings changed: "{}" - Paramètres de cue modifiés : "{}" + + Cue settings changed: "{}" + Paramètres de cue modifiés : "{}" - - Cues settings changed. - Paramètres de cues modifiés. + + Cues settings changed. + Paramètres de cues modifiés. - - + + CueAppearanceSettings - - The appearance depends on the layout - L'apparence dépend de l'agencement + + The appearance depends on the layout + L'apparence dépend de l'agencement - - Cue name - Nom de la cue + + Cue name + Nom de la cue - - NoName - Pas de nom + + NoName + Pas de nom - - Description/Note - Description / note + + Description/Note + Description / note - - Set Font Size - Définir la taille de la police + + Set Font Size + Définir la taille de la police - - Color - Couleur + + Color + Couleur - - Select background color - Sélectionner la couleur de fond + + Select background color + Sélectionner la couleur de fond - - Select font color - Sélectionner la couleur de la police + + Select font color + Sélectionner la couleur de la police - - + + CueName - - Media Cue - Cue de média + + Media Cue + Cue de média - - + + + CueNextAction + + + Do Nothing + Do Nothing + + + + Auto Follow + Auto Follow + + + + Auto Next + Auto Next + + + CueSettings - - Pre wait - Pré-attente + + Pre wait + Pré-attente - - Wait before cue execution - Attente avant l'exécution de la cue + + Wait before cue execution + Attente avant l'exécution de la cue - - Post wait - Post-attente + + Post wait + Post-attente - - Wait after cue execution - Attente après l'exécution de la cue + + Wait after cue execution + Attente après l'exécution de la cue - - Next action - Prochaine action + + Next action + Prochaine action - - Interrupt Fade - Interrompre le fondu + + Interrupt Fade + Interrompre le fondu - - Fade Action - Action de fondu + + Fade Action + Action de fondu - - Behaviours - Comportement + + Behaviours + Comportement - - Pre/Post Wait - Attente pré/post + + Pre/Post Wait + Attente pré/post - - Fade In/Out - Fondu à l'ouverture/fermeture + + Fade In/Out + Fondu à l'ouverture/fermeture - - Start action - Lancer l'action + + Start action + Lancer l'action - - Default action to start the cue - Action par défaut pour démarrer le cue + + Default action to start the cue + Action par défaut pour démarrer le cue - - Stop action - Arrêter l'action + + Stop action + Arrêter l'action - - Default action to stop the cue - Action par défaut pour arrêter le cue + + Default action to stop the cue + Action par défaut pour arrêter le cue - - + + + Interrupt fade + Interrupt fade + + + + Fade actions + Fade actions + + + Fade - - Linear - Linéaire + + Linear + Linéaire - - Quadratic - Du second degré + + Quadratic + Du second degré - - Quadratic2 - Du second degré 2 + + Quadratic2 + Du second degré 2 - - + + FadeEdit - - Duration (sec) - Durée (sec) + + Duration (sec) + Durée (sec) - - Curve - Courbe + + Curve + Courbe - - + + FadeSettings - - Fade In - Fondu à l'ouverture + + Fade In + Fondu à l'ouverture - - Fade Out - Fondu à la fermeture + + Fade Out + Fondu à la fermeture - - + + LayoutDescription - - Organize cues in grid like pages - Organiser les cues en grille comme les pages + + Organize cues in grid like pages + Organiser les cues en grille comme les pages - - Organize the cues in a list - Organiser les cues dans une liste + + Organize the cues in a list + Organiser les cues dans une liste - - + + LayoutDetails - - Click a cue to run it - Cliquer sur une cue pour l'exécuter + + Click a cue to run it + Cliquer sur une cue pour l'exécuter - - SHIFT + Click to edit a cue - MAJ + clic pour éditer une cue + + SHIFT + Click to edit a cue + MAJ + clic pour éditer une cue - - CTRL + Click to select a cue - CTRL + clic pour sélectionner une cue + + CTRL + Click to select a cue + CTRL + clic pour sélectionner une cue - - SHIFT + Space or Double-Click to edit a cue - MAJ + Espace ou double-clic pour éditer une cue + + SHIFT + Space or Double-Click to edit a cue + MAJ + Espace ou double-clic pour éditer une cue - - To copy cues drag them while pressing CTRL - Pour copier les cues, glissez-les en pressant CTRL + + To copy cues drag them while pressing CTRL + Pour copier les cues, glissez-les en pressant CTRL - - To copy cues drag them while pressing SHIFT - Pour copier les cues, glissez-les en pressant SHIFT + + To copy cues drag them while pressing SHIFT + Pour copier les cues, glissez-les en pressant SHIFT - - CTRL + Left Click to select cues - CTRL + clic-gauche pour sélectionner les cues + + CTRL + Left Click to select cues + CTRL + clic-gauche pour sélectionner les cues - - To move cues drag them - Pour déplacer des cues, déplacez-les + + To move cues drag them + Pour déplacer des cues, déplacez-les - - + + LayoutSelect - - Layout selection - Sélection de l'agencement + + Layout selection + Sélection de l'agencement - - Select layout - Sélectionner l'agencement + + Select layout + Sélectionner l'agencement - - Open file - Ouvrir un fichier + + Open file + Ouvrir un fichier - - + + ListLayout - - Show playing cues - Montrer les cues en cours + + Show playing cues + Montrer les cues en cours + + + + Show dB-meters + Afficher le mesureur de dB + + + + Show seek-bars + Afficher la barre de recherche - - Show dB-meters - Afficher le mesureur de dB + + Show accurate time + Afficher le temps exact - - Show seek-bars - Afficher la barre de recherche + + Auto-select next cue + Auto-sélectionner la prochaine cue - - Show accurate time - Afficher le temps exact + + Edit cue + Éditer la cue - - Auto-select next cue - Auto-sélectionner la prochaine cue + + Remove + Retirer - - Edit cue - Éditer la cue + + Select + Sélectionner - - Remove - Retirer + + Stop all + Tout stopper - - Select - Sélectionner + + Pause all + Tout mettre en pause - - Stop all - Tout stopper + + Restart all + Tout redémarrer - - Pause all - Tout mettre en pause + + Stop + Stop - - Restart all - Tout redémarrer + + Restart + Redémarrer - - Stop - Stop + + Default behaviors + Comportements par défaut - - Restart - Redémarrer + + At list end: + À la fin de la liste : - - Default behaviors - Comportements par défaut + + Go key: + Touche pour lancer : - - At list end: - À la fin de la liste : + + Interrupt all + Tout interrompre - - Go key: - Touche pour lancer : + + Fade-Out all + Tout fondre à la fermeture - - Interrupt all - Tout interrompre + + Fade-In all + Tout fondre à l'ouverture - - Fade-Out all - Tout fondre à la fermeture + + Use fade + Utiliser le fondu - - Fade-In all - Tout fondre à l'ouverture + + Stop Cue + Arrêter la cue - - Use fade - Utiliser le fondu + + Pause Cue + Mettre la cue en pause - - Stop Cue - Arrêter la cue + + Restart Cue + Redémarrer la cue - - Pause Cue - Mettre la cue en pause + + Interrupt Cue + Interrompre la cue - - Restart Cue - Redémarrer la cue + + Stop All + Tout arrêter - - Interrupt Cue - Interrompre la cue + + Pause All + Tout mettre en pause - - Stop All - Tout arrêter + + Restart All + Tout redémarrer - - Pause All - Tout mettre en pause + + Interrupt All + Tout interrompre - - Restart All - Tout redémarrer + + Use fade (global actions) + Use fade (global actions) - - Interrupt All - Tout interrompre + + Resume All + Resume All - - + + ListLayoutHeader - - Cue - Cue + + Cue + Cue - - Pre wait - Pré-attente + + Pre wait + Pré-attente - - Action - Action + + Action + Action - - Post wait - Post-attente + + Post wait + Post-attente - - + + ListLayoutInfoPanel - - Cue name - Nom de la cue + + Cue name + Nom de la cue - - Cue description - Description de la cue + + Cue description + Description de la cue - - + + Logging - - Information - Informations + + Information + Informations - - Debug - Déboguage + + Debug + Déboguage - - Warning - Attention + + Warning + Attention - - Error - Erreur + + Error + Erreur - - Details: - Détails : + + Details: + Détails : - - + + + Dismiss all + Dismiss all + + + + Show details + Show details + + + + Linux Show Player - Log Viewer + Linux Show Player - Log Viewer + + + + Info + Info + + + + Critical + Critical + + + + Time + Time + + + + Milliseconds + Milliseconds + + + + Logger name + Logger name + + + + Level + Level + + + + Message + Message + + + + Function + Function + + + + Path name + Path name + + + + File name + File name + + + + Line no. + Line no. + + + + Module + Module + + + + Process ID + Process ID + + + + Process name + Process name + + + + Thread ID + Thread ID + + + + Thread name + Thread name + + + MainWindow - - &File - &Fichier + + &File + &Fichier - - New session - Nouvelle session + + New session + Nouvelle session - - Open - Ouvrir + + Open + Ouvrir - - Save session - Sauvegarder la session + + Save session + Sauvegarder la session - - Preferences - Préférences + + Preferences + Préférences - - Save as - Sauvegarder sous + + Save as + Sauvegarder sous - - Full Screen - Plein écran + + Full Screen + Plein écran - - Exit - Quitter + + Exit + Quitter - - &Edit - Édit&er + + &Edit + Édit&er - - Undo - Annuler + + Undo + Annuler - - Redo - Refaire + + Redo + Refaire - - Select all - Tout sélectionner + + Select all + Tout sélectionner - - Select all media cues - Sélectionner toutes les cues média + + Select all media cues + Sélectionner toutes les cues média - - Deselect all - Tout désélectionner + + Deselect all + Tout désélectionner - - CTRL+SHIFT+A - CTRL+MAJ+A + + CTRL+SHIFT+A + CTRL+MAJ+A - - Invert selection - Inverser la sélection + + Invert selection + Inverser la sélection - - CTRL+I - CTRL+I + + CTRL+I + CTRL+I - - Edit selected - Éditer la sélection + + Edit selected + Éditer la sélection - - CTRL+SHIFT+E - CTRL+MAJ+E + + CTRL+SHIFT+E + CTRL+MAJ+E - - &Layout - É&tat + + &Layout + É&tat - - &Tools - Ou&tils + + &Tools + Ou&tils - - Edit selection - Éditer la sélection + + Edit selection + Éditer la sélection - - &About - À &propos + + &About + À &propos - - About - À propos + + About + À propos - - About Qt - À propos de Qt + + About Qt + À propos de Qt - - Undone: - Défaire : + + Undone: + Défaire : - - Redone: - Refaire : + + Redone: + Refaire : - - Close session - Fermer la session + + Close session + Fermer la session - - The current session is not saved. - La session actuelle n'est pas sauvegardée + + The current session is not saved. + La session actuelle n'est pas sauvegardée - - Discard the changes? - Annuler les changements ? + + Discard the changes? + Annuler les changements ? - - + + MediaCueMenus - - Audio cue (from file) - Cue audio (depuis un fichier) + + Audio cue (from file) + Cue audio (depuis un fichier) - - Select media files - Sélectionner des fichiers médias + + Select media files + Sélectionner des fichiers médias - - + + MediaCueSettings - - Start time - Temps de départ + + Start time + Temps de départ - - Stop position of the media - Position de fin du média + + Stop position of the media + Position de fin du média - - Stop time - Temps de fin + + Stop time + Temps de fin - - Start position of the media - Position de démarrage du média + + Start position of the media + Position de démarrage du média - - Loop - Boucle + + Loop + Boucle - - Repetition after first play (-1 = infinite) - Répétition après la première lecture (-1 = infinie) + + Repetition after first play (-1 = infinite) + Répétition après la première lecture (-1 = infinie) - - + + QColorButton - - Right click to reset - Clic-droit pour réinitialiser + + Right click to reset + Clic-droit pour réinitialiser - - + + SettingsPageName - - Appearance - Apparence + + Appearance + Apparence + + + + General + Générale + + + + Cue + Cue + + + + Cue Settings + Paramètres de cue + + + + Plugins + Plugins - - General - Générale + + Behaviours + Behaviours - - Cue - Cue + + Pre/Post Wait + Pre/Post Wait - - Cue Settings - Paramètres de cue + + Fade In/Out + Fade In/Out - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/fr_FR/list_layout.ts b/lisp/i18n/ts/fr_FR/list_layout.ts new file mode 100644 index 000000000..f433cd424 --- /dev/null +++ b/lisp/i18n/ts/fr_FR/list_layout.ts @@ -0,0 +1,189 @@ + + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them + To move cues drag them + + + + ListLayout + + + Default behaviors + Default behaviors + + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show seek-bars + Show seek-bars + + + + Auto-select next cue + Auto-select next cue + + + + Enable selection mode + Enable selection mode + + + + Go key: + Go key: + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Resume Cue + Resume Cue + + + + Interrupt Cue + Interrupt Cue + + + + Edit cue + Edit cue + + + + Edit selected cues + Edit selected cues + + + + Remove cue + Remove cue + + + + Remove selected cues + Remove selected cues + + + + Selection mode + Selection mode + + + + Pause all + Pause all + + + + Stop all + Stop all + + + + Interrupt all + Interrupt all + + + + Resume all + Resume all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + diff --git a/lisp/i18n/ts/fr_FR/media_info.ts b/lisp/i18n/ts/fr_FR/media_info.ts index af4460ca6..0f2ea0812 100644 --- a/lisp/i18n/ts/fr_FR/media_info.ts +++ b/lisp/i18n/ts/fr_FR/media_info.ts @@ -1,30 +1,37 @@ - - + + + + MediaInfo - - Media Info - Info sur le média + + Media Info + Info sur le média - - Error - Erreur + + Error + Erreur - - No info to display - Pas d'info à afficher + + No info to display + Pas d'info à afficher - - Info - Info + + Info + Info - - Value - Valeur + + Value + Valeur - - \ No newline at end of file + + + Warning + Warning + + + diff --git a/lisp/i18n/ts/fr_FR/midi.ts b/lisp/i18n/ts/fr_FR/midi.ts index 494e3b037..ebbe17626 100644 --- a/lisp/i18n/ts/fr_FR/midi.ts +++ b/lisp/i18n/ts/fr_FR/midi.ts @@ -1,28 +1,30 @@ - - + + + + MIDISettings - - MIDI default devices - Périphériques MIDI par défaut + + MIDI default devices + Périphériques MIDI par défaut - - Input - Entrée + + Input + Entrée - - Output - Sortie + + Output + Sortie - - + + SettingsPageName - - MIDI settings - Préférences MIDI + + MIDI settings + Préférences MIDI - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/fr_FR/network.ts b/lisp/i18n/ts/fr_FR/network.ts new file mode 100644 index 000000000..c9e4cea54 --- /dev/null +++ b/lisp/i18n/ts/fr_FR/network.ts @@ -0,0 +1,47 @@ + + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + diff --git a/lisp/i18n/ts/fr_FR/osc.ts b/lisp/i18n/ts/fr_FR/osc.ts new file mode 100644 index 000000000..b57db305f --- /dev/null +++ b/lisp/i18n/ts/fr_FR/osc.ts @@ -0,0 +1,35 @@ + + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + SettingsPageName + + + OSC settings + OSC settings + + + diff --git a/lisp/i18n/ts/fr_FR/presets.ts b/lisp/i18n/ts/fr_FR/presets.ts index a731dd4b3..617197d86 100644 --- a/lisp/i18n/ts/fr_FR/presets.ts +++ b/lisp/i18n/ts/fr_FR/presets.ts @@ -1,138 +1,150 @@ - - + + + + Preset - - Create Cue - Créer un cue + + Create Cue + Créer un cue - - Load on selected Cues - Charger les cues sélectionnés + + Load on selected Cues + Charger les cues sélectionnés - - + + Presets - - Presets - Pré-réglages + + Presets + Pré-réglages - - Load preset - Charger un pré-réglage + + Load preset + Charger un pré-réglage - - Save as preset - Sauvegarder un pré-réglage sous + + Save as preset + Sauvegarder un pré-réglage sous - - Cannot scan presets - Impossible d'examiner les pré-réglages + + Cannot scan presets + Impossible d'examiner les pré-réglages - - Error while deleting preset "{}" - Erreur lors de la suppression du pré-réglage "{}" + + Error while deleting preset "{}" + Erreur lors de la suppression du pré-réglage "{}" - - Cannot load preset "{}" - Impossible de charger le pré-réglage "{}" + + Cannot load preset "{}" + Impossible de charger le pré-réglage "{}" - - Cannot save preset "{}" - Impossible de sauvegarder le pré-réglage "{}" + + Cannot save preset "{}" + Impossible de sauvegarder le pré-réglage "{}" - - Cannot rename preset "{}" - Impossible de renommer le pré-réglage "{}" + + Cannot rename preset "{}" + Impossible de renommer le pré-réglage "{}" - - Select Preset - Sélectionner le pré-réglage + + Select Preset + Sélectionner le pré-réglage - - Preset already exists, overwrite? - Le pré-réglage existe déjà, l'écraser ? + + Preset already exists, overwrite? + Le pré-réglage existe déjà, l'écraser ? - - Preset name - Nom du pré-réglage + + Preset name + Nom du pré-réglage - - Add - Ajouter + + Add + Ajouter - - Rename - Renommer + + Rename + Renommer - - Edit - Éditer + + Edit + Éditer - - Remove - Supprimer + + Remove + Supprimer - - Export selected - Exporter le sélectionné + + Export selected + Exporter le sélectionné - - Import - Importer + + Import + Importer - - Warning - Attention + + Warning + Attention - - The same name is already used! - Le même nom est déjà utilisé ! + + The same name is already used! + Le même nom est déjà utilisé ! - - Cannot create a cue from this preset: {} - Impossible de créer un cue depuis ce pré-réglage : {} + + Cannot create a cue from this preset: {} + Impossible de créer un cue depuis ce pré-réglage : {} - - Cannot export correctly. - Impossible d'exporter correctement + + Cannot export correctly. + Impossible d'exporter correctement - - Some presets already exists, overwrite? - Certains pré-réglages existent déjà, les écraser ? + + Some presets already exists, overwrite? + Certains pré-réglages existent déjà, les écraser ? - - Cannot import correctly. - Impossible d'importer correctement. + + Cannot import correctly. + Impossible d'importer correctement. - - Cue type - Type de cue + + Cue type + Type de cue - - \ No newline at end of file + + + Load on cue + Load on cue + + + + Load on selected cues + Load on selected cues + + + diff --git a/lisp/i18n/ts/fr_FR/rename_cues.ts b/lisp/i18n/ts/fr_FR/rename_cues.ts new file mode 100644 index 000000000..3970fd565 --- /dev/null +++ b/lisp/i18n/ts/fr_FR/rename_cues.ts @@ -0,0 +1,95 @@ + + + + + RenameCues + + + Rename Cues + Rename Cues + + + + Rename cues + Rename cues + + + + Current + Current + + + + Preview + Preview + + + + Capitalize + Capitalize + + + + Lowercase + Lowercase + + + + Uppercase + Uppercase + + + + Remove Numbers + Remove Numbers + + + + Add numbering + Add numbering + + + + Reset + Reset + + + + Rename all cue. () in regex below usable with $0, $1 ... + Rename all cue. () in regex below usable with $0, $1 ... + + + + Type your regex here: + Type your regex here: + + + + Regex help + Regex help + + + + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple : +^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple : +^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation + + + diff --git a/lisp/i18n/ts/fr_FR/replay_gain.ts b/lisp/i18n/ts/fr_FR/replay_gain.ts index 10d0f9780..b02df1b64 100644 --- a/lisp/i18n/ts/fr_FR/replay_gain.ts +++ b/lisp/i18n/ts/fr_FR/replay_gain.ts @@ -1,50 +1,52 @@ - - + + + + ReplayGain - - ReplayGain / Normalization - Gain de lecture / normalisation + + ReplayGain / Normalization + Gain de lecture / normalisation - - Calculate - Calculer + + Calculate + Calculer - - Reset all - Tout réinitialiser + + Reset all + Tout réinitialiser - - Reset selected - Réinitialiser la séléction + + Reset selected + Réinitialiser la séléction - - Threads number - Nombre de fils + + Threads number + Nombre de fils - - Apply only to selected media - Ne s'applique seulement au média sélectionné + + Apply only to selected media + Ne s'applique seulement au média sélectionné - - ReplayGain to (dB SPL) - Gain de lecture à (dB SPL) + + ReplayGain to (dB SPL) + Gain de lecture à (dB SPL) - - Normalize to (dB) - Normaliser à (dB) + + Normalize to (dB) + Normaliser à (dB) - - Processing files ... - Fichier en cours de traitement ... + + Processing files ... + Fichier en cours de traitement ... - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/fr_FR/synchronizer.ts b/lisp/i18n/ts/fr_FR/synchronizer.ts index 8e368f515..d9a97659c 100644 --- a/lisp/i18n/ts/fr_FR/synchronizer.ts +++ b/lisp/i18n/ts/fr_FR/synchronizer.ts @@ -1,83 +1,85 @@ - - + + + + SyncPeerDialog - - Manage connected peers - Gestion des paires connectées + + Manage connected peers + Gestion des paires connectées - - Discover peers - Découvrir les paires + + Discover peers + Découvrir les paires - - Manually add a peer - Ajouter manuelement une paire + + Manually add a peer + Ajouter manuelement une paire - - Remove selected peer - Retirer la paire séléctionnée + + Remove selected peer + Retirer la paire séléctionnée - - Remove all peers - Retirer toutes les paires + + Remove all peers + Retirer toutes les paires - - Address - Adresses + + Address + Adresses - - Peer IP - IP de la paire + + Peer IP + IP de la paire - - Error - Erreur + + Error + Erreur - - Already connected - Déjà connecté + + Already connected + Déjà connecté - - Cannot add peer - Impossible d'ajouter une paire + + Cannot add peer + Impossible d'ajouter une paire - - + + Synchronizer - - Synchronization - Synchronisation + + Synchronization + Synchronisation - - Manage connected peers - Gérer les paiers connectées + + Manage connected peers + Gérer les paiers connectées - - Show your IP - Afficher votre IP + + Show your IP + Afficher votre IP - - Your IP is: - Votre IP est : + + Your IP is: + Votre IP est : - - Discovering peers ... - Découverte des paires ... + + Discovering peers ... + Découverte des paires ... - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/fr_FR/timecode.ts b/lisp/i18n/ts/fr_FR/timecode.ts index 04bc167ac..24c34b05d 100644 --- a/lisp/i18n/ts/fr_FR/timecode.ts +++ b/lisp/i18n/ts/fr_FR/timecode.ts @@ -1,81 +1,110 @@ - - + + + + SettingsPageName - - Timecode Settings - Paramètres de code temporel + + Timecode Settings + Paramètres de code temporel - - Timecode - Code temporel + + Timecode + Code temporel - - + + Timecode - - Cannot send timecode. - Impossible d'envoyer un code temporel. + + Cannot send timecode. + Impossible d'envoyer un code temporel. - - OLA has stopped. - OLA s'est arrêté + + OLA has stopped. + OLA s'est arrêté - - + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + Cannot send timecode. +OLA has stopped. + Cannot send timecode. +OLA has stopped. + + + TimecodeSettings - - OLA Timecode Settings - Paramètres du code temporel OLA + + OLA Timecode Settings + Paramètres du code temporel OLA + + + + Enable Plugin + Activer le greffon + + + + High-Resolution Timecode + Code temporel de haute-résolution + + + + Timecode Format: + Format de code temporel - - Enable Plugin - Activer le greffon + + OLA status + Status OLA - - High-Resolution Timecode - Code temporel de haute-résolution + + OLA is not running - start the OLA daemon. + OLA n'est pas démarré - démarrez le démon OLA - - Timecode Format: - Format de code temporel + + Replace HOURS by a static track number + Remplace HOURS par un nombre de piste statique - - OLA status - Status OLA + + Enable ArtNet Timecode + Active le code temporel ArtNet - - OLA is not running - start the OLA daemon. - OLA n'est pas démarré - démarrez le démon OLA + + Track number + Numéro de piste - - Replace HOURS by a static track number - Remplace HOURS par un nombre de piste statique + + To send ArtNet Timecode you need to setup a running OLA session! + Pour envoyer un code temporel ArtNet, vous devez paramétrer et lancer une session OLA ! - - Enable ArtNet Timecode - Active le code temporel ArtNet + + Enable Timecode + Enable Timecode - - Track number - Numéro de piste + + Timecode Settings + Timecode Settings - - To send ArtNet Timecode you need to setup a running OLA session! - Pour envoyer un code temporel ArtNet, vous devez paramétrer et lancer une session OLA ! + + Timecode Protocol: + Timecode Protocol: - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/fr_FR/triggers.ts b/lisp/i18n/ts/fr_FR/triggers.ts index bd0614e90..74633a109 100644 --- a/lisp/i18n/ts/fr_FR/triggers.ts +++ b/lisp/i18n/ts/fr_FR/triggers.ts @@ -1,61 +1,63 @@ - - + + + + CueTriggers - - Started - Démarré + + Started + Démarré - - Paused - En pause + + Paused + En pause - - Stopped - Arrêté + + Stopped + Arrêté - - Ended - Fini + + Ended + Fini - - + + SettingsPageName - - Triggers - Déclencheurs + + Triggers + Déclencheurs - - + + TriggersSettings - - Add - Ajouter + + Add + Ajouter - - Remove - Retirer + + Remove + Retirer - - Trigger - Déclencheur + + Trigger + Déclencheur - - Cue - Cue + + Cue + Cue - - Action - Action + + Action + Action - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/it_IT/action_cues.ts b/lisp/i18n/ts/it_IT/action_cues.ts index d182e89e6..a586f940d 100644 --- a/lisp/i18n/ts/it_IT/action_cues.ts +++ b/lisp/i18n/ts/it_IT/action_cues.ts @@ -1,244 +1,345 @@ - - + + + + CollectionCue - - Add - Aggiungi + + Add + Aggiungi - - Remove - Rimuovi + + Remove + Rimuovi - - Cue - Cue + + Cue + Cue - - Action - Azione + + Action + Azione - - + + CommandCue - - Process ended with an error status. - Processo terminato con uno stato errore. + + Process ended with an error status. + Processo terminato con uno stato errore. - - Exit code: - Codice di uscita: + + Exit code: + Codice di uscita: - - Command - Comando + + Command + Comando - - Command to execute, as in a shell - Comando da eseguire, come in una shell + + Command to execute, as in a shell + Comando da eseguire, come in una shell - - Discard command output - Scarta l'output del comando + + Discard command output + Scarta l'output del comando - - Ignore command errors - Ignora errori + + Ignore command errors + Ignora errori - - Kill instead of terminate - Uccidi invece di terminare + + Kill instead of terminate + Uccidi invece di terminare - - + + + Command cue ended with an error status. Exit code: {} + Cue comando terminata con un codice di errore. Codice di uscita: {} + + + + Cue Name + + + OSC Settings + Impostazioni OSC + + + CueName - - Command Cue - Cue Comando + + Command Cue + Cue Comando - - MIDI Cue - Cue MIDI + + MIDI Cue + Cue MIDI - - Volume Control - Controllo Volume + + Volume Control + Controllo Volume - - Seek Cue - Seek Cue + + Seek Cue + Cue di Riposizionamento - - Collection Cue - Collezione + + Collection Cue + Collezione - - Stop-All - Ferma Tutto + + Stop-All + Ferma Tutto - - Index Action - Azione Posizionale + + Index Action + Azione Posizionale - - + + + OSC Cue + Cue OSC + + + IndexActionCue - - Index - Posizione + + Index + Posizione + + + + Use a relative index + Usa una posizione relativa + + + + Target index + Posizione bersaglio - - Use a relative index - Usa una posizione relativa + + Action + Azione - - Target index - Posizione bersaglio + + No suggestion + Nessun suggerimento - - Action - Azione + + Suggested cue name + Nome suggerito per la cue - - + + MIDICue - - MIDI Message - Messaggio Midi + + MIDI Message + Messaggio Midi + + + + Message type + Tipo di messaggio + + + + Osc Cue + + + Type + Tipo + + + + Argument + Argomento + + + + FadeTo + Dissolvi a + + + + Fade + Dissolvenza + + + + OscCue + + + Error during cue execution. + Errore durante l'esecuzione della cue. + + + + OSC Message + Messaggio OSC + + + + Add + Aggiungi + + + + Remove + Rimuovi + + + + Test + Testa + + + + OSC Path: (example: "/path/to/something") + Percorso OSC: (esempio: "/percorso/per/qualcosa") + + + + Fade + Dissolvenza + + + + Time (sec) + Tempo (sec) - - Message type - Tipo di messaggio + + Curve + Curva - - + + SeekCue - - Cue - Cue + + Cue + Cue - - Click to select - Clicca per selezionare + + Click to select + Clicca per selezionare - - Not selected - Non slezionata + + Not selected + Non selezionata - - Seek - Posizionamento + + Seek + Posizionamento - - Time to reach - Posizione da raggiungere + + Time to reach + Posizione da raggiungere - - + + SettingsPageName - - Command - Comando + + Command + Comando - - MIDI Settings - Impostazioni MIDI + + MIDI Settings + Impostazioni MIDI - - Volume Settings - Impostazioni Volume + + Volume Settings + Impostazioni Volume - - Seek Settings - Impostazioni Posizione + + Seek Settings + Impostazioni Posizione - - Edit Collection - Modifica Collezione + + Edit Collection + Modifica Collezione - - Action Settings - Impostazioni Azione + + Action Settings + Impostazioni Azione - - Stop Settings - Impostazioni di Arresto + + Stop Settings + Impostazioni di Arresto - - + + StopAll - - Stop Action - Azione di arresto + + Stop Action + Azione di arresto - - + + VolumeControl - - Error during cue execution - Errore durante l'esecuzione della cue + + Error during cue execution + Errore durante l'esecuzione della cue - - Cue - Cue + + Cue + Cue - - Click to select - Clicca per selezionare + + Click to select + Clicca per selezionare - - Not selected - Non slezionata + + Not selected + Non selezionata - - Volume to reach - Volume da raggiungere + + Volume to reach + Volume da raggiungere - - Fade - Dissolvenza + + Fade + Dissolvenza - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/it_IT/cart_layout.ts b/lisp/i18n/ts/it_IT/cart_layout.ts new file mode 100644 index 000000000..9cc6c32df --- /dev/null +++ b/lisp/i18n/ts/it_IT/cart_layout.ts @@ -0,0 +1,176 @@ + + + + + CartLayout + + + Default behaviors + Comportamenti predefiniti + + + + Countdown mode + Modalità countdown + + + + Show seek-bars + Mostra barre di avanzamento + + + + Show dB-meters + Mostra indicatori dB + + + + Show accurate time + Mostra tempo accurato + + + + Show volume + Mostra volume + + + + Automatically add new page + Aggiungi automaticamente nuove pagine + + + + Grid size + Dimensione griglia + + + + Number of columns + Numero di colonne + + + + Number of rows + Numero di righe + + + + Play + Riproduci + + + + Pause + Pausa + + + + Stop + Ferma + + + + Reset volume + Resetta volume + + + + Add page + Aggiungi pagina + + + + Add pages + Aggiungi pagine + + + + Remove current page + Rimuovi pagina corrente + + + + Number of Pages: + Numero di Pagine: + + + + Page {number} + Pagina {number} + + + + Warning + Attenzione + + + + Every cue in the page will be lost. + Ogni cue nella pagina sarà persa. + + + + Are you sure to continue? + Sei sicuro di voler continuare? + + + + LayoutDescription + + + Organize cues in grid like pages + Organizza le cue in pagine simili a tabelle + + + + LayoutDetails + + + Click a cue to run it + Clicca una cue per eseguirla + + + + SHIFT + Click to edit a cue + SHIFT + Click per modificare una cue + + + + CTRL + Click to select a cue + CTRL + Click per selezionare una cue + + + + To copy cues drag them while pressing CTRL + Per copiare le cue trascinale mentre premi CTRL + + + + To move cues drag them while pressing SHIFT + Per muovere le cue trascinale mentre premi SHIFT + + + + ListLayout + + + Edit cue + Modifica Cue + + + + Edit selected cues + Modifica cue selezionate + + + + Remove cue + Rimuovi cue + + + + Remove selected cues + Rimuovi cue selezionate + + + diff --git a/lisp/i18n/ts/it_IT/controller.ts b/lisp/i18n/ts/it_IT/controller.ts index a2c97d4d9..3a47e7cbd 100644 --- a/lisp/i18n/ts/it_IT/controller.ts +++ b/lisp/i18n/ts/it_IT/controller.ts @@ -1,99 +1,198 @@ - - + + + + + Controller + + + Cannot load controller protocol: "{}" + Impossibile caricare il protocollo: "{}" + + + ControllerKeySettings - - Key - Tasto + + Key + Tasto - - Action - Azione + + Action + Azione - - Shortcuts - Scorciatoie + + Shortcuts + Scorciatoie - - + + ControllerMidiSettings - - MIDI - MIDI + + MIDI + MIDI - - Type - Tipo + + Type + Tipo - - Channel - Canale + + Channel + Canale - - Note - Nota + + Note + Nota - - Action - Azione + + Action + Azione - - Filter "note on" - Filtro "note on" + + Filter "note on" + Filtro "note on" - - Filter "note off" - Filtro "note off" + + Filter "note off" + Filtro "note off" - - Capture - Cattura + + Capture + Cattura - - Listening MIDI messages ... - Ascoltando messaggi MIDI ... + + Listening MIDI messages ... + Ascoltando messaggi MIDI ... - - + + + ControllerOscSettings + + + OSC Message + Messaggio OSC + + + + OSC Path: (example: "/path/to/something") + Percorso OSC: (esempio: "/percorso/per/qualcosa") + + + + OSC + OSC + + + + Path + Percorso + + + + Types + Tipi + + + + Arguments + Argomenti + + + + Actions + Azioni + + + + OSC Capture + Acquisisci OSC + + + + Add + Aggiungi + + + + Remove + Rimuovi + + + + Capture + Acquisisci + + + ControllerSettings - - Add - Aggiungi + + Add + Aggiungi + + + + Remove + Rimuovi + + + Osc Cue - - Remove - Rimuovi + + Type + Tipo - - + + + Argument + Argomento + + + + OscCue + + + Add + Aggiungi + + + + Remove + Rimuovi + + + SettingsPageName - - Cue Control - Controllo Cue + + Cue Control + Controllo Cue + + + + MIDI Controls + Controlli MIDI - - MIDI Controls - Controlli MIDI + + Keyboard Shortcuts + Scorciatoie Tastiera - - Keyboard Shortcuts - Scorciatoie Tastiera + + OSC Controls + Controlli OSC - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/it_IT/gst_backend.ts b/lisp/i18n/ts/it_IT/gst_backend.ts index 5e084829e..0c5951e0c 100644 --- a/lisp/i18n/ts/it_IT/gst_backend.ts +++ b/lisp/i18n/ts/it_IT/gst_backend.ts @@ -1,368 +1,388 @@ - - + + + + AlsaSinkSettings - - ALSA device - Dispositivo ALSA + + ALSA device + Dispositivo ALSA - - ALSA devices, as defined in an asound configuration file - Dispositivi ALSA, come definiti nel file di configurazione asound + + ALSA devices, as defined in an asound configuration file + Dispositivi ALSA, come definiti nel file di configurazione asound - - + + AudioDynamicSettings - - Compressor - Compressore + + Compressor + Compressore - - Expander - Espansore + + Expander + Espansore - - Soft Knee - Morbida + + Soft Knee + Morbida - - Hard Knee - Dura + + Hard Knee + Dura - - Compressor/Expander - Compressore/Espansore + + Compressor/Expander + Compressore/Espansore - - Type - Tipo + + Type + Tipo - - Curve Shape - Forma curva + + Curve Shape + Forma curva - - Ratio - Rapporto + + Ratio + Rapporto - - Threshold (dB) - Soglia (dB) + + Threshold (dB) + Soglia (dB) - - + + AudioPanSettings - - Audio Pan - Bilanciamento + + Audio Pan + Bilanciamento - - Center - Centro + + Center + Centro - - Left - Sinistra + + Left + Sinistra - - Right - Destra + + Right + Destra - - + + DbMeterSettings - - DbMeter settings - Impostazioni indicatore dB + + DbMeter settings + Impostazioni indicatore dB - - Time between levels (ms) - Intervallo tra due livelli (ms) + + Time between levels (ms) + Intervallo tra due livelli (ms) - - Peak ttl (ms) - Durata picchi (ms) + + Peak ttl (ms) + Durata picchi (ms) - - Peak falloff (dB/sec) - Decadimento picchi (dB/sec) + + Peak falloff (dB/sec) + Decadimento picchi (dB/sec) - - + + Equalizer10Settings - - 10 Bands Equalizer (IIR) - Equalizzatore 10 bande (IIR) + + 10 Bands Equalizer (IIR) + Equalizzatore 10 bande (IIR) - - + + + GstBackend + + + Audio cue (from file) + Cue audio (da file) + + + + Select media files + Seleziona file multimediali + + + GstMediaSettings - - Change Pipeline - Modifica Pipeline + + Change Pipeline + Modifica Pipeline - - + + GstPipelineEdit - - Edit Pipeline - Modifica Pipeline + + Edit Pipeline + Modifica Pipeline - - + + GstSettings - - Pipeline - Pipeline + + Pipeline + Pipeline - - + + JackSinkSettings - - Connections - Connessioni + + Connections + Connessioni - - Edit connections - Modifica connessioni + + Edit connections + Modifica connessioni - - Output ports - Uscite + + Output ports + Uscite - - Input ports - Ingressi + + Input ports + Ingressi - - Connect - Connetti + + Connect + Connetti - - Disconnect - Disconnetti + + Disconnect + Disconnetti - - + + MediaElementName - - Compressor/Expander - Compressore/Espansore + + Compressor/Expander + Compressore/Espansore - - Audio Pan - Audio Pan + + Audio Pan + Bilanciamento - - PulseAudio Out - Uscita PulseAudio + + PulseAudio Out + Uscita PulseAudio - - Volume - Volume + + Volume + Volume - - dB Meter - Indicatore dB + + dB Meter + Indicatore dB - - System Input - Ingresso di Sistema + + System Input + Ingresso di Sistema - - ALSA Out - Uscita ALSA + + ALSA Out + Uscita ALSA - - JACK Out - Uscita JACK + + JACK Out + Uscita JACK - - Custom Element - Elemento Personalizzato + + Custom Element + Elemento Personalizzato - - System Out - Uscita di Sistema + + System Out + Uscita di Sistema - - Pitch - Tonalità + + Pitch + Tonalità - - URI Input - Ingresso URI + + URI Input + Ingresso URI - - 10 Bands Equalizer - Equalizzatore 10 bande + + 10 Bands Equalizer + Equalizzatore 10 bande - - Speed - Velocità + + Speed + Velocità - - Preset Input - Ingresso Preset + + Preset Input + Ingresso Preset - - + + PitchSettings - - Pitch - Tonalità + + Pitch + Tonalità - - {0:+} semitones - {0:+} semitoni + + {0:+} semitones + {0:+} semitoni - - + + PresetSrcSettings - - Presets - Preset + + Presets + Preset - - + + SettingsPageName - - GStreamer settings - Impostazioni GStreamer + + GStreamer settings + Impostazioni GStreamer + + + + Media Settings + Impostazioni Media - - Media Settings - Impostazioni Media + + GStreamer + GStreamer - - + + SpeedSettings - - Speed - Velocità + + Speed + Velocità - - + + UriInputSettings - - Source - Sorgente + + Source + Sorgente - - Find File - Seleziona file + + Find File + Seleziona file - - Buffering - Buffering + + Buffering + Pre-Caricamento - - Use Buffering - Usare buffering + + Use Buffering + Usare buffering - - Attempt download on network streams - Prova a scaricare con flussi di rete + + Attempt download on network streams + Prova a scaricare con flussi di rete - - Buffer size (-1 default value) - Dimensione buffer (-1 valore predefinito) + + Buffer size (-1 default value) + Dimensione buffer (-1 valore predefinito) - - Choose file - Seleziona file + + Choose file + Seleziona file - - All files - Tutti i file + + All files + Tutti i file - - + + UserElementSettings - - User defined elements - Elementi definiti dall'utente + + User defined elements + Elementi definiti dall'utente - - Only for advanced user! - Solo per utenti avanzati! + + Only for advanced user! + Solo per utenti avanzati! - - + + VolumeSettings - - Volume - Volume + + Volume + Volume - - Normalized volume - Volume normalizzato + + Normalized volume + Volume normalizzato - - Reset - Azzera + + Reset + Azzera - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/it_IT/lisp.ts b/lisp/i18n/ts/it_IT/lisp.ts index d1861dd4a..ce78b9081 100644 --- a/lisp/i18n/ts/it_IT/lisp.ts +++ b/lisp/i18n/ts/it_IT/lisp.ts @@ -1,947 +1,1180 @@ - - + + + + About - - Authors - Autori + + Authors + Autori - - Contributors - Contributori + + Contributors + Contributori - - Translators - Traduttori + + Translators + Traduttori - - About Linux Show Player - Riguardo Linux Show Player + + About Linux Show Player + Riguardo Linux Show Player - - + + AboutDialog - - Linux Show Player is a cue-player designed for stage productions. - Linux Show Player è un cue-player pensato per produzioni teatrali. + + Linux Show Player is a cue-player designed for stage productions. + Linux Show Player è un cue-player pensato per produzioni teatrali. - - Web site - Sito web + + Web site + Sito web - - Users group - Gruppo di discussione + + Users group + Gruppo di discussione - - Source code - Codice sorgente + + Source code + Codice sorgente - - Info - Informazioni + + Info + Informazioni - - License - Licenza + + License + Licenza - - Contributors - Contributori + + Contributors + Contributori - - - AppGeneralSettings - - Startup layout - Layout iniziale + + Discussion + Discussione + + + Actions - - Use startup dialog - Usa layout iniziale + + Undo: {} + Annulla: {} - - Application theme - Stile applicazione + + Redo: {} + Ripeti: {} - - + + AppConfiguration - - LiSP preferences - Preferenze di LiSP + + LiSP preferences + Preferenze di LiSP + + + + AppGeneralSettings + + + Startup layout + Layout iniziale + + + + Use startup dialog + Usa layout iniziale + + + + Application theme + Stile applicazione + + + + Default layout + Layout Predefinito + + + + Enable startup layout selector + Attivare il selettore di layout all'avvio - - + + + Application themes + Temi dell'applicazione + + + + UI theme + Tema dell'interfaccia utente + + + + Icons theme + Tema dell'icone + + + CartLayout - - Add page - Aggungi pagina + + Add page + Aggiungi pagina - - Add pages - Aggiungi pagine + + Add pages + Aggiungi pagine - - Remove current page - Rimuovi pagina corrente + + Remove current page + Rimuovi pagina corrente - - Countdown mode - Modalità countdown + + Countdown mode + Modalità countdown - - Show seek-bars - Mostra barre di avanzamento + + Show seek-bars + Mostra barre di avanzamento - - Show dB-meters - Mostra indicatori dB + + Show dB-meters + Mostra indicatori dB - - Show volume - Mostra volume + + Show volume + Mostra volume - - Show accurate time - Mostra tempo accurato + + Show accurate time + Mostra tempo accurato - - Edit cue - Modifica Cue + + Edit cue + Modifica Cue - - Remove - Rimuovi + + Remove + Rimuovi - - Select - Seleziona + + Select + Seleziona - - Play - Riproduci + + Play + Riproduci - - Pause - Pausa + + Pause + Pausa - - Stop - Ferma + + Stop + Ferma - - Reset volume - Resetta volume + + Reset volume + Resetta volume - - Number of Pages: - Numero di Pagine: + + Number of Pages: + Numero di Pagine: - - Warning - Attenzione + + Warning + Attenzione - - Every cue in the page will be lost. - Ogni cue nella pagina sarà persa. + + Every cue in the page will be lost. + Ogni cue nella pagina sarà persa. - - Are you sure to continue? - Sei sicuro di voler continuare? + + Are you sure to continue? + Sei sicuro di voler continuare? - - Page - Pagina + + Page + Pagina - - Default behaviors - Comportamenti predefiniti + + Default behaviors + Comportamenti predefiniti - - Automatically add new page - Aggiungi automaticamente nuove pagine + + Automatically add new page + Aggiungi automaticamente nuove pagine - - Grid size - Dimensione griglia + + Grid size + Dimensione griglia - - Number of columns - Numero di colonne + + Number of columns + Numero di colonne - - Number of rows - Numero di righe + + Number of rows + Numero di righe - - + + CueAction - - Default - Automatica + + Default + Automatica + + + + Pause + Pausa + + + + Start + Avvia + + + + Stop + Ferma + + + + FadeInStart + Avvio con Dissolvenza + + + + FadeOutStop + Ferma con Dissolvenza + + + + FadeOutPause + Pausa con Dissolvenza + + + + Faded Start + Avvia con dissolvenza - - Pause - Pausa + + Faded Resume + Riprendi con dissolvenza - - Start - Avvia + + Faded Pause + Pausa con dissolvenza - - Stop - Ferma + + Faded Stop + Ferma con dissolvenza - - FadeInStart - Avvio con Dissolvenza + + Faded Interrupt + Interrompi con dissolvenza - - FadeOutStop - Ferma con Dissolvenza + + Resume + Riprendi - - FadeOutPause - Pausa con Dissolvenza + + Do Nothing + Non fare nulla - - + + CueActionLog - - Cue settings changed: "{}" - Impostazioni cue cambiate "{}" + + Cue settings changed: "{}" + Impostazioni cue cambiate "{}" - - Cues settings changed. - Impostazioni di più cue cambiate. + + Cues settings changed. + Impostazioni di più cue cambiate. - - + + CueAppearanceSettings - - The appearance depends on the layout - L'aspetto dipende dal layout + + The appearance depends on the layout + L'aspetto dipende dal layout - - Cue name - Nome della cue + + Cue name + Nome della cue - - NoName - NoName + + NoName + NoName - - Description/Note - Descrizione/Note + + Description/Note + Descrizione/Note - - Set Font Size - Dimensione carattere + + Set Font Size + Dimensione carattere - - Color - Colore + + Color + Colore - - Select background color - Selezione colore di sfondo + + Select background color + Selezione colore di sfondo - - Select font color - Seleziona colore del carattere + + Select font color + Seleziona colore del carattere - - + + CueName - - Media Cue - Cue Multimediale + + Media Cue + Cue Multimediale - - + + + CueNextAction + + + Do Nothing + Non fare nulla + + + + Auto Follow + Auto Follow + + + + Auto Next + Auto Next + + + CueSettings - - Pre wait - Pre wait + + Pre wait + Pre wait - - Wait before cue execution - Attesa prima dell'esecuzione della cue + + Wait before cue execution + Attesa prima dell'esecuzione della cue - - Post wait - Post wait + + Post wait + Post wait - - Wait after cue execution - Attesa dopo l'esecuzione della cue + + Wait after cue execution + Attesa dopo l'esecuzione della cue - - Next action - Azione successiva + + Next action + Azione successiva - - Interrupt Fade - Dissolvenza Interruzioni + + Interrupt Fade + Dissolvenza Interruzioni - - Fade Action - Operazioni di Dissolvenza + + Fade Action + Operazioni di Dissolvenza - - Behaviours - Comportamenti + + Behaviours + Comportamenti - - Pre/Post Wait - Attesa Prima/Dopo + + Pre/Post Wait + Attesa Prima/Dopo - - Fade In/Out - Dissolvenza Ingresso/Uscita + + Fade In/Out + Dissolvenza Ingresso/Uscita - - Start action - Azione d'avvio + + Start action + Azione d'avvio - - Default action to start the cue - Azione standard per avviare la cue + + Default action to start the cue + Azione standard per avviare la cue - - Stop action - Azione di arresto + + Stop action + Azione di arresto - - Default action to stop the cue - Azione standard per arrestare la cue + + Default action to stop the cue + Azione standard per arrestare la cue - - + + + Interrupt fade + Interrupt fade + + + + Fade actions + Fade actions + + + Fade - - Linear - Lineare + + Linear + Lineare - - Quadratic - Quadratica + + Quadratic + Quadratica - - Quadratic2 - Quadratica2 + + Quadratic2 + Quadratica2 - - + + FadeEdit - - Duration (sec) - Durata (sec) + + Duration (sec) + Durata (sec) - - Curve - Curva + + Curve + Curva - - + + FadeSettings - - Fade In - Dissolvenza Ingresso + + Fade In + Dissolvenza Ingresso - - Fade Out - Dissolvenza Uscita + + Fade Out + Dissolvenza Uscita - - + + LayoutDescription - - Organize cues in grid like pages - Organizza le cue in pagine simili a tabelle + + Organize cues in grid like pages + Organizza le cue in pagine simili a tabelle - - Organize the cues in a list - Organizza le cue in una lista + + Organize the cues in a list + Organizza le cue in una lista - - + + LayoutDetails - - Click a cue to run it - Clicca una cue per eseguirla + + Click a cue to run it + Clicca una cue per eseguirla - - SHIFT + Click to edit a cue - SHIFT + Click per modificare una cue + + SHIFT + Click to edit a cue + SHIFT + Click per modificare una cue - - CTRL + Click to select a cue - CTRL + Click per selezionare una cue + + CTRL + Click to select a cue + CTRL + Click per selezionare una cue - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Spazio o Doppio Click per modifcare una cue + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Spazio o Doppio Click per modificare una cue - - To copy cues drag them while pressing CTRL - Per copiare le cue trascinale mentre premi CTRL + + To copy cues drag them while pressing CTRL + Per copiare le cue trascinale mentre premi CTRL - - To copy cues drag them while pressing SHIFT - Per copiare le cue trascinale mentre premi SHIFT + + To copy cues drag them while pressing SHIFT + Per copiare le cue trascinale mentre premi SHIFT - - CTRL + Left Click to select cues - CTRL + Click Sinistro per selezionare una cue + + CTRL + Left Click to select cues + CTRL + Click Sinistro per selezionare una cue - - To move cues drag them - Per spostare una cue trascinala + + To move cues drag them + Per spostare una cue trascinala - - + + LayoutSelect - - Layout selection - Selezione del layout + + Layout selection + Selezione del layout - - Select layout - Seleziona layout + + Select layout + Seleziona layout - - Open file - Apri file + + Open file + Apri file - - + + ListLayout - - Show playing cues - Mostra cue in riproduzione + + Show playing cues + Mostra cue in riproduzione + + + + Show dB-meters + Mostra indicatori dB + + + + Show seek-bars + Mostra barre di avanzamento - - Show dB-meters - Mostra indicatori dB + + Show accurate time + Mostra tempo accurato - - Show seek-bars - Mostra barre di avanzamento + + Auto-select next cue + Auto-seleziona prossima cue - - Show accurate time - Mostra tempo accurato + + Edit cue + Modifica Cue - - Auto-select next cue - Auto-seleziona prossima cue + + Remove + Rimuovi - - Edit cue - Modifica Cue + + Select + Seleziona - - Remove - Rimuovi + + Stop all + Ferma tutte - - Select - Seleziona + + Pause all + Sospendi tutte - - Stop all - Ferma tutte + + Restart all + Riavvia tutte - - Pause all - Sospendi tutte + + Stop + Ferma - - Restart all - Riavvia tutte + + Restart + Riavvia - - Stop - Ferma + + Default behaviors + Comportamenti predefiniti - - Restart - Riavvia + + At list end: + A fine lista: - - Default behaviors - Comportamenti predefiniti + + Go key: + Tasto "Go": - - At list end: - A fine lista: + + Interrupt all + Interrompiti tutte - - Go key: - Tasto "Go": + + Fade-Out all + Sfuma tutte in uscita - - Interrupt all - Interrompiti tutte + + Fade-In all + Sfuma tutte in ingresso - - Fade-Out all - Sfuma tutte in uscita + + Use fade + Usa dissolvenza - - Fade-In all - Sfuma tutte in ingresso + + Stop Cue + Ferma Cue - - Use fade - Usa dissolvenza + + Pause Cue + Pausa Cue - - Stop Cue - Ferma Cue + + Restart Cue + Riavvia Cue - - Pause Cue - Pausa Cue + + Interrupt Cue + Interrompi Cue - - Restart Cue - Riavvia Cue + + Stop All + Ferma Tutte - - Interrupt Cue - Interrompi Cue + + Pause All + Pausa Tutte - - Stop All - Ferma Tutte + + Restart All + Riavvia Tutte - - Pause All - Pausa Tutte + + Interrupt All + Interrompi Tutte - - Restart All - Riavvia Tutte + + Use fade (global actions) + Use fade (global actions) - - Interrupt All - Interrompi Tutte + + Resume All + Resume All - - + + ListLayoutHeader - - Cue - Cue + + Cue + Cue - - Pre wait - Pre wait + + Pre wait + Pre wait - - Action - Azione + + Action + Azione - - Post wait - Post wait + + Post wait + Post wait - - + + ListLayoutInfoPanel - - Cue name - Nome della cue + + Cue name + Nome della cue - - Cue description - Descrizone della cue + + Cue description + Descrizione della cue - - + + Logging - - Information - Informazione + + Information + Informazione - - Debug - Debug + + Debug + Debug - - Warning - Avviso + + Warning + Avviso - - Error - Errore + + Error + Errore - - Details: - Dettagli: + + Details: + Dettagli: - - + + + Dismiss all + Dismiss all + + + + Show details + Show details + + + + Linux Show Player - Log Viewer + Linux Show Player - Log Viewer + + + + Info + Info + + + + Critical + Critical + + + + Time + Time + + + + Milliseconds + Milliseconds + + + + Logger name + Logger name + + + + Level + Level + + + + Message + Message + + + + Function + Function + + + + Path name + Path name + + + + File name + File name + + + + Line no. + Line no. + + + + Module + Module + + + + Process ID + Process ID + + + + Process name + Process name + + + + Thread ID + Thread ID + + + + Thread name + Thread name + + + MainWindow - - &File - &File + + &File + &File - - New session - Nuova sessione + + New session + Nuova sessione - - Open - Apri + + Open + Apri - - Save session - Salva sessione + + Save session + Salva sessione - - Preferences - Preferenze + + Preferences + Preferenze - - Save as - Salva come + + Save as + Salva come - - Full Screen - Schermo Intero + + Full Screen + Schermo Intero - - Exit - Esci + + Exit + Esci - - &Edit - &Modifica + + &Edit + &Modifica - - Undo - Annulla + + Undo + Annulla - - Redo - Ripeti + + Redo + Ripeti - - Select all - Seleziona tutti + + Select all + Seleziona tutti - - Select all media cues - Seleziona tutte le media-cue + + Select all media cues + Seleziona tutte le media-cue - - Deselect all - Deseleziona tutti + + Deselect all + Deseleziona tutti - - CTRL+SHIFT+A - CTRL+SHIFT+A + + CTRL+SHIFT+A + CTRL+SHIFT+A - - Invert selection - Inverti selezione + + Invert selection + Inverti selezione - - CTRL+I - CTRL+I + + CTRL+I + CTRL+I - - Edit selected - Modifica selezionati + + Edit selected + Modifica selezionati - - CTRL+SHIFT+E - CTRL+SHIFT+E + + CTRL+SHIFT+E + CTRL+SHIFT+E - - &Layout - &Layout + + &Layout + &Layout - - &Tools - &Strumenti + + &Tools + &Strumenti - - Edit selection - Modifica slezionati + + Edit selection + Modifica selezionati - - &About - &About + + &About + &About - - About - Informazioni + + About + Informazioni - - About Qt - Informazioni su Qt + + About Qt + Informazioni su Qt - - Undone: - Annullato: + + Undone: + Annullato: - - Redone: - Ripetuto: + + Redone: + Ripetuto: - - Close session - Chiudi sessione + + Close session + Chiudi sessione - - The current session is not saved. - La sessione corrente non è salvata. + + The current session is not saved. + La sessione corrente non è salvata. - - Discard the changes? - Scartare la modifiche? + + Discard the changes? + Scartare la modifiche? - - + + MediaCueMenus - - Audio cue (from file) - Cue audio (da file) + + Audio cue (from file) + Cue audio (da file) - - Select media files - Seleziona file multimediali + + Select media files + Seleziona file multimediali - - + + MediaCueSettings - - Start time - Posizione iniziale + + Start time + Posizione iniziale - - Stop position of the media - Posizione in cui la traccia si deve fermare + + Stop position of the media + Posizione in cui la traccia si deve fermare - - Stop time - Posizione finale + + Stop time + Posizione finale - - Start position of the media - Posizione da cui la traccia deve partire + + Start position of the media + Posizione da cui la traccia deve partire - - Loop - Loop + + Loop + Loop - - Repetition after first play (-1 = infinite) - Ripetizioni dopo la prima riproduzione (-1 = infinite) + + Repetition after first play (-1 = infinite) + Ripetizioni dopo la prima riproduzione (-1 = infinite) - - + + QColorButton - - Right click to reset - Clicca con il pulsante destro per resettare + + Right click to reset + Clicca con il pulsante destro per resettare - - + + SettingsPageName - - Appearance - Aspetto + + Appearance + Aspetto + + + + General + Generale + + + + Cue + Cue + + + + Cue Settings + Impostazioni Cue + + + + Plugins + Plugins - - General - Generale + + Behaviours + Behaviours - - Cue - Cue + + Pre/Post Wait + Pre/Post Wait - - Cue Settings - Impostazioni Cue + + Fade In/Out + Fade In/Out - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/it_IT/list_layout.ts b/lisp/i18n/ts/it_IT/list_layout.ts new file mode 100644 index 000000000..f7cf6801b --- /dev/null +++ b/lisp/i18n/ts/it_IT/list_layout.ts @@ -0,0 +1,189 @@ + + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them + To move cues drag them + + + + ListLayout + + + Default behaviors + Default behaviors + + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show seek-bars + Show seek-bars + + + + Auto-select next cue + Auto-select next cue + + + + Enable selection mode + Enable selection mode + + + + Go key: + Go key: + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Resume Cue + Resume Cue + + + + Interrupt Cue + Interrupt Cue + + + + Edit cue + Edit cue + + + + Edit selected cues + Edit selected cues + + + + Remove cue + Remove cue + + + + Remove selected cues + Remove selected cues + + + + Selection mode + Selection mode + + + + Pause all + Pause all + + + + Stop all + Stop all + + + + Interrupt all + Interrupt all + + + + Resume all + Resume all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + diff --git a/lisp/i18n/ts/it_IT/media_info.ts b/lisp/i18n/ts/it_IT/media_info.ts index 43edd463b..75c4dc63b 100644 --- a/lisp/i18n/ts/it_IT/media_info.ts +++ b/lisp/i18n/ts/it_IT/media_info.ts @@ -1,30 +1,37 @@ - - + + + + MediaInfo - - Media Info - Informazioni Media + + Media Info + Informazioni Media - - Error - Errore + + Error + Errore - - No info to display - Nessuna informazione da mostrare + + No info to display + Nessuna informazione da mostrare - - Info - Informazione + + Info + Informazione - - Value - Valore + + Value + Valore - - \ No newline at end of file + + + Warning + Warning + + + diff --git a/lisp/i18n/ts/it_IT/midi.ts b/lisp/i18n/ts/it_IT/midi.ts index c2ccfa63f..8e280b534 100644 --- a/lisp/i18n/ts/it_IT/midi.ts +++ b/lisp/i18n/ts/it_IT/midi.ts @@ -1,28 +1,30 @@ - - + + + + MIDISettings - - MIDI default devices - Dispositivi MIDI predefiniti + + MIDI default devices + Dispositivi MIDI predefiniti - - Input - Ingresso + + Input + Ingresso - - Output - Uscita + + Output + Uscita - - + + SettingsPageName - - MIDI settings - Impostazioni MIDI + + MIDI settings + Impostazioni MIDI - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/it_IT/network.ts b/lisp/i18n/ts/it_IT/network.ts new file mode 100644 index 000000000..df29dedd4 --- /dev/null +++ b/lisp/i18n/ts/it_IT/network.ts @@ -0,0 +1,47 @@ + + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + diff --git a/lisp/i18n/ts/it_IT/osc.ts b/lisp/i18n/ts/it_IT/osc.ts new file mode 100644 index 000000000..d925c26e3 --- /dev/null +++ b/lisp/i18n/ts/it_IT/osc.ts @@ -0,0 +1,35 @@ + + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + SettingsPageName + + + OSC settings + OSC settings + + + diff --git a/lisp/i18n/ts/it_IT/presets.ts b/lisp/i18n/ts/it_IT/presets.ts index 40e90b082..790c789ba 100644 --- a/lisp/i18n/ts/it_IT/presets.ts +++ b/lisp/i18n/ts/it_IT/presets.ts @@ -1,138 +1,150 @@ - - + + + + Preset - - Create Cue - Crea Cue + + Create Cue + Crea Cue - - Load on selected Cues - Carica sulle cue selezionate + + Load on selected Cues + Carica sulle cue selezionate - - + + Presets - - Presets - Preset + + Presets + Preset - - Load preset - Carica preset + + Load preset + Carica preset - - Save as preset - Salva come preset + + Save as preset + Salva come preset - - Cannot scan presets - Impossibile cercare i preset + + Cannot scan presets + Impossibile cercare i preset - - Error while deleting preset "{}" - Errore durate l'eliminazione del preset "{}" + + Error while deleting preset "{}" + Errore durate l'eliminazione del preset "{}"; - - Cannot load preset "{}" - Impossible caricare il preset "{}" + + Cannot load preset "{}" + Impossible caricare il preset "{}" - - Cannot save preset "{}" - Impossibile salvare il preset "{}" + + Cannot save preset "{}" + Impossibile salvare il preset "{}" - - Cannot rename preset "{}" - Impossible rinominare il preset "{}" + + Cannot rename preset "{}" + Impossible rinominare il preset "{}" - - Select Preset - Seleziona Preset + + Select Preset + Seleziona Preset - - Preset already exists, overwrite? - Preset esistente, sovrascrivere? + + Preset already exists, overwrite? + Preset esistente, sovrascrivere? - - Preset name - Nome preset + + Preset name + Nome preset - - Add - Aggiungi + + Add + Aggiungi - - Rename - Rinomina + + Rename + Rinomina - - Edit - Modifica + + Edit + Modifica - - Remove - Rimuovi + + Remove + Rimuovi - - Export selected - Esporta selezionati + + Export selected + Esporta selezionati - - Import - Importa + + Import + Importa - - Warning - Avviso + + Warning + Avviso - - The same name is already used! - Lo stesso nome è già in uso! + + The same name is already used! + Lo stesso nome è già in uso! - - Cannot create a cue from this preset: {} - Impossibile creare cue da questo preset: {} + + Cannot create a cue from this preset: {} + Impossibile creare cue da questo preset: {} - - Cannot export correctly. - Impossibile esportare correttamente + + Cannot export correctly. + Impossibile esportare correttamente. - - Some presets already exists, overwrite? - Alcuni preset esistono già, sovrascriverli? + + Some presets already exists, overwrite? + Alcuni preset esistono già, sovrascriverli? - - Cannot import correctly. - Impossible importare correttamente. + + Cannot import correctly. + Impossible importare correttamente. - - Cue type - Tipo di cue + + Cue type + Tipo di cue - - \ No newline at end of file + + + Load on cue + Load on cue + + + + Load on selected cues + Load on selected cues + + + diff --git a/lisp/i18n/ts/it_IT/rename_cues.ts b/lisp/i18n/ts/it_IT/rename_cues.ts new file mode 100644 index 000000000..68bbe03a5 --- /dev/null +++ b/lisp/i18n/ts/it_IT/rename_cues.ts @@ -0,0 +1,95 @@ + + + + + RenameCues + + + Rename Cues + Rename Cues + + + + Rename cues + Rename cues + + + + Current + Current + + + + Preview + Preview + + + + Capitalize + Capitalize + + + + Lowercase + Lowercase + + + + Uppercase + Uppercase + + + + Remove Numbers + Remove Numbers + + + + Add numbering + Add numbering + + + + Reset + Reset + + + + Rename all cue. () in regex below usable with $0, $1 ... + Rename all cue. () in regex below usable with $0, $1 ... + + + + Type your regex here: + Type your regex here: + + + + Regex help + Regex help + + + + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple : +^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple : +^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation + + + diff --git a/lisp/i18n/ts/it_IT/replay_gain.ts b/lisp/i18n/ts/it_IT/replay_gain.ts index 328b8669b..1b391c402 100644 --- a/lisp/i18n/ts/it_IT/replay_gain.ts +++ b/lisp/i18n/ts/it_IT/replay_gain.ts @@ -1,50 +1,52 @@ - - + + + + ReplayGain - - ReplayGain / Normalization - ReplayGain / Normalizzazione + + ReplayGain / Normalization + ReplayGain / Normalizzazione - - Calculate - Calcola + + Calculate + Calcola - - Reset all - Resetta tutto + + Reset all + Resetta tutto - - Reset selected - Resetta selezionati + + Reset selected + Resetta selezionati - - Threads number - Numero di processi + + Threads number + Numero di processi - - Apply only to selected media - Applica solo alle traccie selezionate + + Apply only to selected media + Applica solo alle traccie selezionate - - ReplayGain to (dB SPL) - ReplayGain a (dB SPL) + + ReplayGain to (dB SPL) + ReplayGain a (dB SPL) - - Normalize to (dB) - Normalizza a (dB) + + Normalize to (dB) + Normalizza a (dB) - - Processing files ... - Processando ... + + Processing files ... + Processando ... - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/it_IT/synchronizer.ts b/lisp/i18n/ts/it_IT/synchronizer.ts index 146a02c49..32d96b177 100644 --- a/lisp/i18n/ts/it_IT/synchronizer.ts +++ b/lisp/i18n/ts/it_IT/synchronizer.ts @@ -1,83 +1,85 @@ - - + + + + SyncPeerDialog - - Manage connected peers - Gestisci peer connessi + + Manage connected peers + Gestisci peer connessi - - Discover peers - Ricerca peer + + Discover peers + Ricerca peer - - Manually add a peer - Aggiungi peer manualmente + + Manually add a peer + Aggiungi peer manualmente - - Remove selected peer - Rimuovi peer selezionato + + Remove selected peer + Rimuovi peer selezionato - - Remove all peers - Rimuovi tutti i peer + + Remove all peers + Rimuovi tutti i peer - - Address - Indirizzo + + Address + Indirizzo - - Peer IP - IP del peer + + Peer IP + IP del peer - - Error - Error + + Error + Error - - Already connected - Già connesso + + Already connected + Già connesso - - Cannot add peer - Impossibile aggiungere il peer + + Cannot add peer + Impossibile aggiungere il peer - - + + Synchronizer - - Synchronization - Sincronizzazione + + Synchronization + Sincronizzazione - - Manage connected peers - Gestisci peer connessi + + Manage connected peers + Gestisci peer connessi - - Show your IP - Visualizza IP + + Show your IP + Visualizza IP - - Your IP is: - Il tuo IP è: + + Your IP is: + Il tuo IP è: - - Discovering peers ... - Ricerca peer in corso ... + + Discovering peers ... + Ricerca peer in corso ... - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/it_IT/timecode.ts b/lisp/i18n/ts/it_IT/timecode.ts index facdbf6b4..5566e8d0f 100644 --- a/lisp/i18n/ts/it_IT/timecode.ts +++ b/lisp/i18n/ts/it_IT/timecode.ts @@ -1,81 +1,110 @@ - - + + + + SettingsPageName - - Timecode Settings - Impostazioni Timecode + + Timecode Settings + Impostazioni Timecode - - Timecode - Timecode + + Timecode + Timecode - - + + Timecode - - Cannot send timecode. - Impossibile inviare il timecode. + + Cannot send timecode. + Impossibile inviare il timecode. - - OLA has stopped. - OLA si è fermato. + + OLA has stopped. + OLA si è fermato. - - + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + Cannot send timecode. +OLA has stopped. + Cannot send timecode. +OLA has stopped. + + + TimecodeSettings - - OLA Timecode Settings - Impostazioni Timecode OLA + + OLA Timecode Settings + Impostazioni Timecode OLA + + + + Enable Plugin + Attiva Plugin + + + + High-Resolution Timecode + Timecode ad alta risoluzione + + + + Timecode Format: + Formato Timecode: - - Enable Plugin - Attiva Plugin + + OLA status + Stato OLA - - High-Resolution Timecode - Timecode ad alta risoluzione. + + OLA is not running - start the OLA daemon. + OLA non è in esecuzione - avvia il demone OLA. - - Timecode Format: - Formato Timecode: + + Replace HOURS by a static track number + Sostituire ORE con un numero di traccia statico - - OLA status - Stato OLA + + Enable ArtNet Timecode + Attiva ArtNet Timecode - - OLA is not running - start the OLA daemon. - OLA non è in esecuzione - avvia il demone OLA + + Track number + Numero di traccia - - Replace HOURS by a static track number - Sostiture "ORE" con un numero di traccia statico + + To send ArtNet Timecode you need to setup a running OLA session! + Per utilizzare il Timecode ArtNet devi avviare una sessione OLA! - - Enable ArtNet Timecode - Attiva ArtNet Timecode + + Enable Timecode + Enable Timecode - - Track number - Numero di traccia + + Timecode Settings + Timecode Settings - - To send ArtNet Timecode you need to setup a running OLA session! - Per utilizzare il Timecode ArtNet devi avviare una sessione OLA! + + Timecode Protocol: + Timecode Protocol: - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/it_IT/triggers.ts b/lisp/i18n/ts/it_IT/triggers.ts index 94493cdc3..0d3a4673f 100644 --- a/lisp/i18n/ts/it_IT/triggers.ts +++ b/lisp/i18n/ts/it_IT/triggers.ts @@ -1,61 +1,63 @@ - - + + + + CueTriggers - - Started - Avviata + + Started + Avviata - - Paused - Sospesa + + Paused + Sospesa - - Stopped - Fermata + + Stopped + Fermata - - Ended - Finita + + Ended + Finita - - + + SettingsPageName - - Triggers - Triggers + + Triggers + Triggers - - + + TriggersSettings - - Add - Aggiungi + + Add + Aggiungi - - Remove - Rimuovi + + Remove + Rimuovi - - Trigger - Evento + + Trigger + Evento - - Cue - Cue + + Cue + Cue - - Action - Azione + + Action + Azione - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/nl_BE/action_cues.ts b/lisp/i18n/ts/nl_BE/action_cues.ts new file mode 100644 index 000000000..346a9c81f --- /dev/null +++ b/lisp/i18n/ts/nl_BE/action_cues.ts @@ -0,0 +1,345 @@ + + + + + CollectionCue + + + Add + Add + + + + Remove + Remove + + + + Cue + Cue + + + + Action + Action + + + + CommandCue + + + Process ended with an error status. + Process ended with an error status. + + + + Exit code: + Exit code: + + + + Command + Command + + + + Command to execute, as in a shell + Command to execute, as in a shell + + + + Discard command output + Discard command output + + + + Ignore command errors + Ignore command errors + + + + Kill instead of terminate + Kill instead of terminate + + + + Command cue ended with an error status. Exit code: {} + Command cue ended with an error status. Exit code: {} + + + + Cue Name + + + OSC Settings + OSC Settings + + + + CueName + + + Command Cue + Command Cue + + + + MIDI Cue + MIDI Cue + + + + Volume Control + Volume Control + + + + Seek Cue + Seek Cue + + + + Collection Cue + Collection Cue + + + + Stop-All + Stop-All + + + + Index Action + Index Action + + + + OSC Cue + OSC Cue + + + + IndexActionCue + + + Index + Index + + + + Use a relative index + Use a relative index + + + + Target index + Target index + + + + Action + Action + + + + No suggestion + No suggestion + + + + Suggested cue name + Suggested cue name + + + + MIDICue + + + MIDI Message + MIDI Message + + + + Message type + Message type + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OscCue + + + Error during cue execution. + Error during cue execution. + + + + OSC Message + OSC Message + + + + Add + Add + + + + Remove + Remove + + + + Test + Test + + + + OSC Path: (example: "/path/to/something") + OSC Path: (example: "/path/to/something") + + + + Fade + Fade + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + + + SeekCue + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Seek + Seek + + + + Time to reach + Time to reach + + + + SettingsPageName + + + Command + Command + + + + MIDI Settings + MIDI Settings + + + + Volume Settings + Volume Settings + + + + Seek Settings + Seek Settings + + + + Edit Collection + Edit Collection + + + + Action Settings + Action Settings + + + + Stop Settings + Stop Settings + + + + StopAll + + + Stop Action + Stop Action + + + + VolumeControl + + + Error during cue execution + Error during cue execution + + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Volume to reach + Volume to reach + + + + Fade + Fade + + + diff --git a/lisp/i18n/ts/nl_BE/cart_layout.ts b/lisp/i18n/ts/nl_BE/cart_layout.ts new file mode 100644 index 000000000..c2b04113e --- /dev/null +++ b/lisp/i18n/ts/nl_BE/cart_layout.ts @@ -0,0 +1,176 @@ + + + + + CartLayout + + + Default behaviors + Default behaviors + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show volume + Show volume + + + + Automatically add new page + Automatically add new page + + + + Grid size + Grid size + + + + Number of columns + Number of columns + + + + Number of rows + Number of rows + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Number of Pages: + Number of Pages: + + + + Page {number} + Page {number} + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + LayoutDescription + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + LayoutDetails + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them while pressing SHIFT + To move cues drag them while pressing SHIFT + + + + ListLayout + + + Edit cue + Edit cue + + + + Edit selected cues + Edit selected cues + + + + Remove cue + Remove cue + + + + Remove selected cues + Remove selected cues + + + diff --git a/lisp/i18n/ts/nl_BE/controller.ts b/lisp/i18n/ts/nl_BE/controller.ts new file mode 100644 index 000000000..f2cbdcfef --- /dev/null +++ b/lisp/i18n/ts/nl_BE/controller.ts @@ -0,0 +1,198 @@ + + + + + Controller + + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + + ControllerKeySettings + + + Key + Key + + + + Action + Action + + + + Shortcuts + Shortcuts + + + + ControllerMidiSettings + + + MIDI + MIDI + + + + Type + Type + + + + Channel + Channel + + + + Note + Note + + + + Action + Action + + + + Filter "note on" + Filter "note on" + + + + Filter "note off" + Filter "note off" + + + + Capture + Capture + + + + Listening MIDI messages ... + Listening MIDI messages ... + + + + ControllerOscSettings + + + OSC Message + OSC Message + + + + OSC Path: (example: "/path/to/something") + OSC Path: (example: "/path/to/something") + + + + OSC + OSC + + + + Path + Path + + + + Types + Types + + + + Arguments + Arguments + + + + Actions + Actions + + + + OSC Capture + OSC Capture + + + + Add + Add + + + + Remove + Remove + + + + Capture + Capture + + + + ControllerSettings + + + Add + Add + + + + Remove + Remove + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + OscCue + + + Add + Add + + + + Remove + Remove + + + + SettingsPageName + + + Cue Control + Cue Control + + + + MIDI Controls + MIDI Controls + + + + Keyboard Shortcuts + Keyboard Shortcuts + + + + OSC Controls + OSC Controls + + + diff --git a/lisp/i18n/ts/nl_BE/gst_backend.ts b/lisp/i18n/ts/nl_BE/gst_backend.ts new file mode 100644 index 000000000..dfa534cbe --- /dev/null +++ b/lisp/i18n/ts/nl_BE/gst_backend.ts @@ -0,0 +1,388 @@ + + + + + AlsaSinkSettings + + + ALSA device + ALSA device + + + + ALSA devices, as defined in an asound configuration file + ALSA devices, as defined in an asound configuration file + + + + AudioDynamicSettings + + + Compressor + Compressor + + + + Expander + Expander + + + + Soft Knee + Soft Knee + + + + Hard Knee + Hard Knee + + + + Compressor/Expander + Compressor/Expander + + + + Type + Type + + + + Curve Shape + Curve Shape + + + + Ratio + Ratio + + + + Threshold (dB) + Threshold (dB) + + + + AudioPanSettings + + + Audio Pan + Audio Pan + + + + Center + Center + + + + Left + Left + + + + Right + Right + + + + DbMeterSettings + + + DbMeter settings + DbMeter settings + + + + Time between levels (ms) + Time between levels (ms) + + + + Peak ttl (ms) + Peak ttl (ms) + + + + Peak falloff (dB/sec) + Peak falloff (dB/sec) + + + + Equalizer10Settings + + + 10 Bands Equalizer (IIR) + 10 Bands Equalizer (IIR) + + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + GstMediaSettings + + + Change Pipeline + Change Pipeline + + + + GstPipelineEdit + + + Edit Pipeline + Edit Pipeline + + + + GstSettings + + + Pipeline + Pipeline + + + + JackSinkSettings + + + Connections + Connections + + + + Edit connections + Edit connections + + + + Output ports + Output ports + + + + Input ports + Input ports + + + + Connect + Connect + + + + Disconnect + Disconnect + + + + MediaElementName + + + Compressor/Expander + Compressor/Expander + + + + Audio Pan + Audio Pan + + + + PulseAudio Out + PulseAudio Out + + + + Volume + Volume + + + + dB Meter + dB Meter + + + + System Input + System Input + + + + ALSA Out + ALSA Out + + + + JACK Out + JACK Out + + + + Custom Element + Custom Element + + + + System Out + System Out + + + + Pitch + Pitch + + + + URI Input + URI Input + + + + 10 Bands Equalizer + 10 Bands Equalizer + + + + Speed + Speed + + + + Preset Input + Preset Input + + + + PitchSettings + + + Pitch + Pitch + + + + {0:+} semitones + {0:+} semitones + + + + PresetSrcSettings + + + Presets + Presets + + + + SettingsPageName + + + GStreamer settings + GStreamer settings + + + + Media Settings + Media Settings + + + + GStreamer + GStreamer + + + + SpeedSettings + + + Speed + Speed + + + + UriInputSettings + + + Source + Source + + + + Find File + Find File + + + + Buffering + Buffering + + + + Use Buffering + Use Buffering + + + + Attempt download on network streams + Attempt download on network streams + + + + Buffer size (-1 default value) + Buffer size (-1 default value) + + + + Choose file + Choose file + + + + All files + All files + + + + UserElementSettings + + + User defined elements + User defined elements + + + + Only for advanced user! + Only for advanced user! + + + + VolumeSettings + + + Volume + Volume + + + + Normalized volume + Normalized volume + + + + Reset + Reset + + + diff --git a/lisp/i18n/ts/nl_BE/lisp.ts b/lisp/i18n/ts/nl_BE/lisp.ts new file mode 100644 index 000000000..4129770cc --- /dev/null +++ b/lisp/i18n/ts/nl_BE/lisp.ts @@ -0,0 +1,1180 @@ + + + + + About + + + Authors + Authors + + + + Contributors + Contributors + + + + Translators + Translators + + + + About Linux Show Player + About Linux Show Player + + + + AboutDialog + + + Linux Show Player is a cue-player designed for stage productions. + Linux Show Player is a cue-player designed for stage productions. + + + + Web site + Web site + + + + Users group + Users group + + + + Source code + Source code + + + + Info + Info + + + + License + License + + + + Contributors + Contributors + + + + Discussion + Discussion + + + + Actions + + + Undo: {} + Undo: {} + + + + Redo: {} + Redo: {} + + + + AppConfiguration + + + LiSP preferences + LiSP preferences + + + + AppGeneralSettings + + + Startup layout + Startup layout + + + + Use startup dialog + Use startup dialog + + + + Application theme + Application theme + + + + Default layout + Default layout + + + + Enable startup layout selector + Enable startup layout selector + + + + Application themes + Application themes + + + + UI theme + UI theme + + + + Icons theme + Icons theme + + + + CartLayout + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show volume + Show volume + + + + Show accurate time + Show accurate time + + + + Edit cue + Edit cue + + + + Remove + Remove + + + + Select + Select + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Number of Pages: + Number of Pages: + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + Page + Page + + + + Default behaviors + Default behaviors + + + + Automatically add new page + Automatically add new page + + + + Grid size + Grid size + + + + Number of columns + Number of columns + + + + Number of rows + Number of rows + + + + CueAction + + + Default + Default + + + + Pause + Pause + + + + Start + Start + + + + Stop + Stop + + + + FadeInStart + FadeInStart + + + + FadeOutStop + FadeOutStop + + + + FadeOutPause + FadeOutPause + + + + Faded Start + Faded Start + + + + Faded Resume + Faded Resume + + + + Faded Pause + Faded Pause + + + + Faded Stop + Faded Stop + + + + Faded Interrupt + Faded Interrupt + + + + Resume + Resume + + + + Do Nothing + Do Nothing + + + + CueActionLog + + + Cue settings changed: "{}" + Cue settings changed: "{}" + + + + Cues settings changed. + Cues settings changed. + + + + CueAppearanceSettings + + + The appearance depends on the layout + The appearance depends on the layout + + + + Cue name + Cue name + + + + NoName + NoName + + + + Description/Note + Description/Note + + + + Set Font Size + Set Font Size + + + + Color + Color + + + + Select background color + Select background color + + + + Select font color + Select font color + + + + CueName + + + Media Cue + Media Cue + + + + CueNextAction + + + Do Nothing + Do Nothing + + + + Auto Follow + Auto Follow + + + + Auto Next + Auto Next + + + + CueSettings + + + Pre wait + Pre wait + + + + Wait before cue execution + Wait before cue execution + + + + Post wait + Post wait + + + + Wait after cue execution + Wait after cue execution + + + + Next action + Next action + + + + Interrupt Fade + Interrupt Fade + + + + Fade Action + Fade Action + + + + Behaviours + Behaviours + + + + Pre/Post Wait + Pre/Post Wait + + + + Fade In/Out + Fade In/Out + + + + Start action + Start action + + + + Default action to start the cue + Default action to start the cue + + + + Stop action + Stop action + + + + Default action to stop the cue + Default action to stop the cue + + + + Interrupt fade + Interrupt fade + + + + Fade actions + Fade actions + + + + Fade + + + Linear + Linear + + + + Quadratic + Quadratic + + + + Quadratic2 + Quadratic2 + + + + FadeEdit + + + Duration (sec) + Duration (sec) + + + + Curve + Curve + + + + FadeSettings + + + Fade In + Fade In + + + + Fade Out + Fade Out + + + + LayoutDescription + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + Organize the cues in a list + Organize the cues in a list + + + + LayoutDetails + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To copy cues drag them while pressing SHIFT + To copy cues drag them while pressing SHIFT + + + + CTRL + Left Click to select cues + CTRL + Left Click to select cues + + + + To move cues drag them + To move cues drag them + + + + LayoutSelect + + + Layout selection + Layout selection + + + + Select layout + Select layout + + + + Open file + Open file + + + + ListLayout + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show seek-bars + Show seek-bars + + + + Show accurate time + Show accurate time + + + + Auto-select next cue + Auto-select next cue + + + + Edit cue + Edit cue + + + + Remove + Remove + + + + Select + Select + + + + Stop all + Stop all + + + + Pause all + Pause all + + + + Restart all + Restart all + + + + Stop + Stop + + + + Restart + Restart + + + + Default behaviors + Default behaviors + + + + At list end: + At list end: + + + + Go key: + Go key: + + + + Interrupt all + Interrupt all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + Use fade + Use fade + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Restart Cue + Restart Cue + + + + Interrupt Cue + Interrupt Cue + + + + Stop All + Stop All + + + + Pause All + Pause All + + + + Restart All + Restart All + + + + Interrupt All + Interrupt All + + + + Use fade (global actions) + Use fade (global actions) + + + + Resume All + Resume All + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + + Logging + + + Information + Information + + + + Debug + Debug + + + + Warning + Warning + + + + Error + Error + + + + Details: + Details: + + + + Dismiss all + Dismiss all + + + + Show details + Show details + + + + Linux Show Player - Log Viewer + Linux Show Player - Log Viewer + + + + Info + Info + + + + Critical + Critical + + + + Time + Time + + + + Milliseconds + Milliseconds + + + + Logger name + Logger name + + + + Level + Level + + + + Message + Message + + + + Function + Function + + + + Path name + Path name + + + + File name + File name + + + + Line no. + Line no. + + + + Module + Module + + + + Process ID + Process ID + + + + Process name + Process name + + + + Thread ID + Thread ID + + + + Thread name + Thread name + + + + MainWindow + + + &File + &File + + + + New session + New session + + + + Open + Open + + + + Save session + Save session + + + + Preferences + Preferences + + + + Save as + Save as + + + + Full Screen + Full Screen + + + + Exit + Exit + + + + &Edit + &Edit + + + + Undo + Undo + + + + Redo + Redo + + + + Select all + Select all + + + + Select all media cues + Select all media cues + + + + Deselect all + Deselect all + + + + CTRL+SHIFT+A + CTRL+SHIFT+A + + + + Invert selection + Invert selection + + + + CTRL+I + CTRL+I + + + + Edit selected + Edit selected + + + + CTRL+SHIFT+E + CTRL+SHIFT+E + + + + &Layout + &Layout + + + + &Tools + &Tools + + + + Edit selection + Edit selection + + + + &About + &About + + + + About + About + + + + About Qt + About Qt + + + + Undone: + Undone: + + + + Redone: + Redone: + + + + Close session + Close session + + + + The current session is not saved. + The current session is not saved. + + + + Discard the changes? + Discard the changes? + + + + MediaCueMenus + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + MediaCueSettings + + + Start time + Start time + + + + Stop position of the media + Stop position of the media + + + + Stop time + Stop time + + + + Start position of the media + Start position of the media + + + + Loop + Loop + + + + Repetition after first play (-1 = infinite) + Repetition after first play (-1 = infinite) + + + + QColorButton + + + Right click to reset + Right click to reset + + + + SettingsPageName + + + Appearance + Appearance + + + + General + General + + + + Cue + Cue + + + + Cue Settings + Cue Settings + + + + Plugins + Plugins + + + + Behaviours + Behaviours + + + + Pre/Post Wait + Pre/Post Wait + + + + Fade In/Out + Fade In/Out + + + diff --git a/lisp/i18n/ts/nl_BE/list_layout.ts b/lisp/i18n/ts/nl_BE/list_layout.ts new file mode 100644 index 000000000..454cae5fc --- /dev/null +++ b/lisp/i18n/ts/nl_BE/list_layout.ts @@ -0,0 +1,189 @@ + + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them + To move cues drag them + + + + ListLayout + + + Default behaviors + Default behaviors + + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show seek-bars + Show seek-bars + + + + Auto-select next cue + Auto-select next cue + + + + Enable selection mode + Enable selection mode + + + + Go key: + Go key: + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Resume Cue + Resume Cue + + + + Interrupt Cue + Interrupt Cue + + + + Edit cue + Edit cue + + + + Edit selected cues + Edit selected cues + + + + Remove cue + Remove cue + + + + Remove selected cues + Remove selected cues + + + + Selection mode + Selection mode + + + + Pause all + Pause all + + + + Stop all + Stop all + + + + Interrupt all + Interrupt all + + + + Resume all + Resume all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + diff --git a/lisp/i18n/ts/nl_BE/media_info.ts b/lisp/i18n/ts/nl_BE/media_info.ts new file mode 100644 index 000000000..e91c05015 --- /dev/null +++ b/lisp/i18n/ts/nl_BE/media_info.ts @@ -0,0 +1,37 @@ + + + + + MediaInfo + + + Media Info + Media Info + + + + Error + Error + + + + No info to display + No info to display + + + + Info + Info + + + + Value + Value + + + + Warning + Warning + + + diff --git a/lisp/i18n/ts/nl_BE/midi.ts b/lisp/i18n/ts/nl_BE/midi.ts new file mode 100644 index 000000000..7b442fabf --- /dev/null +++ b/lisp/i18n/ts/nl_BE/midi.ts @@ -0,0 +1,30 @@ + + + + + MIDISettings + + + MIDI default devices + MIDI default devices + + + + Input + Input + + + + Output + Output + + + + SettingsPageName + + + MIDI settings + MIDI settings + + + diff --git a/lisp/i18n/ts/nl_BE/network.ts b/lisp/i18n/ts/nl_BE/network.ts new file mode 100644 index 000000000..d05fba554 --- /dev/null +++ b/lisp/i18n/ts/nl_BE/network.ts @@ -0,0 +1,47 @@ + + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + diff --git a/lisp/i18n/ts/nl_BE/osc.ts b/lisp/i18n/ts/nl_BE/osc.ts new file mode 100644 index 000000000..3d16b4883 --- /dev/null +++ b/lisp/i18n/ts/nl_BE/osc.ts @@ -0,0 +1,35 @@ + + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + SettingsPageName + + + OSC settings + OSC settings + + + diff --git a/lisp/i18n/ts/nl_BE/presets.ts b/lisp/i18n/ts/nl_BE/presets.ts new file mode 100644 index 000000000..1c7f716f2 --- /dev/null +++ b/lisp/i18n/ts/nl_BE/presets.ts @@ -0,0 +1,150 @@ + + + + + Preset + + + Create Cue + Create Cue + + + + Load on selected Cues + Load on selected Cues + + + + Presets + + + Presets + Presets + + + + Load preset + Load preset + + + + Save as preset + Save as preset + + + + Cannot scan presets + Cannot scan presets + + + + Error while deleting preset "{}" + Error while deleting preset "{}" + + + + Cannot load preset "{}" + Cannot load preset "{}" + + + + Cannot save preset "{}" + Cannot save preset "{}" + + + + Cannot rename preset "{}" + Cannot rename preset "{}" + + + + Select Preset + Select Preset + + + + Preset already exists, overwrite? + Preset already exists, overwrite? + + + + Preset name + Preset name + + + + Add + Add + + + + Rename + Rename + + + + Edit + Edit + + + + Remove + Remove + + + + Export selected + Export selected + + + + Import + Import + + + + Warning + Warning + + + + The same name is already used! + The same name is already used! + + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + + + + Cannot export correctly. + Cannot export correctly. + + + + Some presets already exists, overwrite? + Some presets already exists, overwrite? + + + + Cannot import correctly. + Cannot import correctly. + + + + Cue type + Cue type + + + + Load on cue + Load on cue + + + + Load on selected cues + Load on selected cues + + + diff --git a/lisp/i18n/ts/nl_BE/rename_cues.ts b/lisp/i18n/ts/nl_BE/rename_cues.ts new file mode 100644 index 000000000..5bdbaa77c --- /dev/null +++ b/lisp/i18n/ts/nl_BE/rename_cues.ts @@ -0,0 +1,95 @@ + + + + + RenameCues + + + Rename Cues + Rename Cues + + + + Rename cues + Rename cues + + + + Current + Current + + + + Preview + Preview + + + + Capitalize + Capitalize + + + + Lowercase + Lowercase + + + + Uppercase + Uppercase + + + + Remove Numbers + Remove Numbers + + + + Add numbering + Add numbering + + + + Reset + Reset + + + + Rename all cue. () in regex below usable with $0, $1 ... + Rename all cue. () in regex below usable with $0, $1 ... + + + + Type your regex here: + Type your regex here: + + + + Regex help + Regex help + + + + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple : +^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple : +^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation + + + diff --git a/lisp/i18n/ts/nl_BE/replay_gain.ts b/lisp/i18n/ts/nl_BE/replay_gain.ts new file mode 100644 index 000000000..3a405f69e --- /dev/null +++ b/lisp/i18n/ts/nl_BE/replay_gain.ts @@ -0,0 +1,52 @@ + + + + + ReplayGain + + + ReplayGain / Normalization + ReplayGain / Normalization + + + + Calculate + Calculate + + + + Reset all + Reset all + + + + Reset selected + Reset selected + + + + Threads number + Threads number + + + + Apply only to selected media + Apply only to selected media + + + + ReplayGain to (dB SPL) + ReplayGain to (dB SPL) + + + + Normalize to (dB) + Normalize to (dB) + + + + Processing files ... + Processing files ... + + + diff --git a/lisp/i18n/ts/nl_BE/synchronizer.ts b/lisp/i18n/ts/nl_BE/synchronizer.ts new file mode 100644 index 000000000..a8b218e05 --- /dev/null +++ b/lisp/i18n/ts/nl_BE/synchronizer.ts @@ -0,0 +1,85 @@ + + + + + SyncPeerDialog + + + Manage connected peers + Manage connected peers + + + + Discover peers + Discover peers + + + + Manually add a peer + Manually add a peer + + + + Remove selected peer + Remove selected peer + + + + Remove all peers + Remove all peers + + + + Address + Address + + + + Peer IP + Peer IP + + + + Error + Error + + + + Already connected + Already connected + + + + Cannot add peer + Cannot add peer + + + + Synchronizer + + + Synchronization + Synchronization + + + + Manage connected peers + Manage connected peers + + + + Show your IP + Show your IP + + + + Your IP is: + Your IP is: + + + + Discovering peers ... + Discovering peers ... + + + diff --git a/lisp/i18n/ts/nl_BE/timecode.ts b/lisp/i18n/ts/nl_BE/timecode.ts new file mode 100644 index 000000000..1306f5c48 --- /dev/null +++ b/lisp/i18n/ts/nl_BE/timecode.ts @@ -0,0 +1,110 @@ + + + + + SettingsPageName + + + Timecode Settings + Timecode Settings + + + + Timecode + Timecode + + + + Timecode + + + Cannot send timecode. + Cannot send timecode. + + + + OLA has stopped. + OLA has stopped. + + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + Cannot send timecode. +OLA has stopped. + Cannot send timecode. +OLA has stopped. + + + + TimecodeSettings + + + OLA Timecode Settings + OLA Timecode Settings + + + + Enable Plugin + Enable Plugin + + + + High-Resolution Timecode + High-Resolution Timecode + + + + Timecode Format: + Timecode Format: + + + + OLA status + OLA status + + + + OLA is not running - start the OLA daemon. + OLA is not running - start the OLA daemon. + + + + Replace HOURS by a static track number + Replace HOURS by a static track number + + + + Enable ArtNet Timecode + Enable ArtNet Timecode + + + + Track number + Track number + + + + To send ArtNet Timecode you need to setup a running OLA session! + To send ArtNet Timecode you need to setup a running OLA session! + + + + Enable Timecode + Enable Timecode + + + + Timecode Settings + Timecode Settings + + + + Timecode Protocol: + Timecode Protocol: + + + diff --git a/lisp/i18n/ts/nl_BE/triggers.ts b/lisp/i18n/ts/nl_BE/triggers.ts new file mode 100644 index 000000000..1d67b7dfa --- /dev/null +++ b/lisp/i18n/ts/nl_BE/triggers.ts @@ -0,0 +1,63 @@ + + + + + CueTriggers + + + Started + Started + + + + Paused + Paused + + + + Stopped + Stopped + + + + Ended + Ended + + + + SettingsPageName + + + Triggers + Triggers + + + + TriggersSettings + + + Add + Add + + + + Remove + Remove + + + + Trigger + Trigger + + + + Cue + Cue + + + + Action + Action + + + diff --git a/lisp/i18n/ts/nl_NL/action_cues.ts b/lisp/i18n/ts/nl_NL/action_cues.ts new file mode 100644 index 000000000..668de4e1d --- /dev/null +++ b/lisp/i18n/ts/nl_NL/action_cues.ts @@ -0,0 +1,345 @@ + + + + + CollectionCue + + + Add + Add + + + + Remove + Remove + + + + Cue + Cue + + + + Action + Action + + + + CommandCue + + + Process ended with an error status. + Process ended with an error status. + + + + Exit code: + Exit code: + + + + Command + Command + + + + Command to execute, as in a shell + Command to execute, as in a shell + + + + Discard command output + Discard command output + + + + Ignore command errors + Ignore command errors + + + + Kill instead of terminate + Kill instead of terminate + + + + Command cue ended with an error status. Exit code: {} + Command cue ended with an error status. Exit code: {} + + + + Cue Name + + + OSC Settings + OSC Settings + + + + CueName + + + Command Cue + Command Cue + + + + MIDI Cue + MIDI Cue + + + + Volume Control + Volume Control + + + + Seek Cue + Seek Cue + + + + Collection Cue + Collection Cue + + + + Stop-All + Stop-All + + + + Index Action + Index Action + + + + OSC Cue + OSC Cue + + + + IndexActionCue + + + Index + Index + + + + Use a relative index + Use a relative index + + + + Target index + Target index + + + + Action + Action + + + + No suggestion + No suggestion + + + + Suggested cue name + Suggested cue name + + + + MIDICue + + + MIDI Message + MIDI Message + + + + Message type + Message type + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OscCue + + + Error during cue execution. + Error during cue execution. + + + + OSC Message + OSC Message + + + + Add + Add + + + + Remove + Remove + + + + Test + Test + + + + OSC Path: (example: "/path/to/something") + OSC Path: (example: "/path/to/something") + + + + Fade + Fade + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + + + SeekCue + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Seek + Seek + + + + Time to reach + Time to reach + + + + SettingsPageName + + + Command + Command + + + + MIDI Settings + MIDI Settings + + + + Volume Settings + Volume Settings + + + + Seek Settings + Seek Settings + + + + Edit Collection + Edit Collection + + + + Action Settings + Action Settings + + + + Stop Settings + Stop Settings + + + + StopAll + + + Stop Action + Stop Action + + + + VolumeControl + + + Error during cue execution + Error during cue execution + + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Volume to reach + Volume to reach + + + + Fade + Fade + + + diff --git a/lisp/i18n/ts/nl_NL/cart_layout.ts b/lisp/i18n/ts/nl_NL/cart_layout.ts new file mode 100644 index 000000000..f52c8d203 --- /dev/null +++ b/lisp/i18n/ts/nl_NL/cart_layout.ts @@ -0,0 +1,176 @@ + + + + + CartLayout + + + Default behaviors + Default behaviors + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show volume + Show volume + + + + Automatically add new page + Automatically add new page + + + + Grid size + Grid size + + + + Number of columns + Number of columns + + + + Number of rows + Number of rows + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Number of Pages: + Number of Pages: + + + + Page {number} + Page {number} + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + LayoutDescription + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + LayoutDetails + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them while pressing SHIFT + To move cues drag them while pressing SHIFT + + + + ListLayout + + + Edit cue + Edit cue + + + + Edit selected cues + Edit selected cues + + + + Remove cue + Remove cue + + + + Remove selected cues + Remove selected cues + + + diff --git a/lisp/i18n/ts/nl_NL/controller.ts b/lisp/i18n/ts/nl_NL/controller.ts new file mode 100644 index 000000000..56da86ec0 --- /dev/null +++ b/lisp/i18n/ts/nl_NL/controller.ts @@ -0,0 +1,198 @@ + + + + + Controller + + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + + ControllerKeySettings + + + Key + Key + + + + Action + Action + + + + Shortcuts + Shortcuts + + + + ControllerMidiSettings + + + MIDI + MIDI + + + + Type + Type + + + + Channel + Channel + + + + Note + Note + + + + Action + Action + + + + Filter "note on" + Filter "note on" + + + + Filter "note off" + Filter "note off" + + + + Capture + Capture + + + + Listening MIDI messages ... + Listening MIDI messages ... + + + + ControllerOscSettings + + + OSC Message + OSC Message + + + + OSC Path: (example: "/path/to/something") + OSC Path: (example: "/path/to/something") + + + + OSC + OSC + + + + Path + Path + + + + Types + Types + + + + Arguments + Arguments + + + + Actions + Actions + + + + OSC Capture + OSC Capture + + + + Add + Add + + + + Remove + Remove + + + + Capture + Capture + + + + ControllerSettings + + + Add + Add + + + + Remove + Remove + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + OscCue + + + Add + Add + + + + Remove + Remove + + + + SettingsPageName + + + Cue Control + Cue Control + + + + MIDI Controls + MIDI Controls + + + + Keyboard Shortcuts + Keyboard Shortcuts + + + + OSC Controls + OSC Controls + + + diff --git a/lisp/i18n/ts/nl_NL/gst_backend.ts b/lisp/i18n/ts/nl_NL/gst_backend.ts new file mode 100644 index 000000000..9f4949abc --- /dev/null +++ b/lisp/i18n/ts/nl_NL/gst_backend.ts @@ -0,0 +1,388 @@ + + + + + AlsaSinkSettings + + + ALSA device + ALSA device + + + + ALSA devices, as defined in an asound configuration file + ALSA devices, as defined in an asound configuration file + + + + AudioDynamicSettings + + + Compressor + Compressor + + + + Expander + Expander + + + + Soft Knee + Soft Knee + + + + Hard Knee + Hard Knee + + + + Compressor/Expander + Compressor/Expander + + + + Type + Type + + + + Curve Shape + Curve Shape + + + + Ratio + Ratio + + + + Threshold (dB) + Threshold (dB) + + + + AudioPanSettings + + + Audio Pan + Audio Pan + + + + Center + Center + + + + Left + Left + + + + Right + Right + + + + DbMeterSettings + + + DbMeter settings + DbMeter settings + + + + Time between levels (ms) + Time between levels (ms) + + + + Peak ttl (ms) + Peak ttl (ms) + + + + Peak falloff (dB/sec) + Peak falloff (dB/sec) + + + + Equalizer10Settings + + + 10 Bands Equalizer (IIR) + 10 Bands Equalizer (IIR) + + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + GstMediaSettings + + + Change Pipeline + Change Pipeline + + + + GstPipelineEdit + + + Edit Pipeline + Edit Pipeline + + + + GstSettings + + + Pipeline + Pipeline + + + + JackSinkSettings + + + Connections + Connections + + + + Edit connections + Edit connections + + + + Output ports + Output ports + + + + Input ports + Input ports + + + + Connect + Connect + + + + Disconnect + Disconnect + + + + MediaElementName + + + Compressor/Expander + Compressor/Expander + + + + Audio Pan + Audio Pan + + + + PulseAudio Out + PulseAudio Out + + + + Volume + Volume + + + + dB Meter + dB Meter + + + + System Input + System Input + + + + ALSA Out + ALSA Out + + + + JACK Out + JACK Out + + + + Custom Element + Custom Element + + + + System Out + System Out + + + + Pitch + Pitch + + + + URI Input + URI Input + + + + 10 Bands Equalizer + 10 Bands Equalizer + + + + Speed + Speed + + + + Preset Input + Preset Input + + + + PitchSettings + + + Pitch + Pitch + + + + {0:+} semitones + {0:+} semitones + + + + PresetSrcSettings + + + Presets + Presets + + + + SettingsPageName + + + GStreamer settings + GStreamer settings + + + + Media Settings + Media Settings + + + + GStreamer + GStreamer + + + + SpeedSettings + + + Speed + Speed + + + + UriInputSettings + + + Source + Source + + + + Find File + Find File + + + + Buffering + Buffering + + + + Use Buffering + Use Buffering + + + + Attempt download on network streams + Attempt download on network streams + + + + Buffer size (-1 default value) + Buffer size (-1 default value) + + + + Choose file + Choose file + + + + All files + All files + + + + UserElementSettings + + + User defined elements + User defined elements + + + + Only for advanced user! + Only for advanced user! + + + + VolumeSettings + + + Volume + Volume + + + + Normalized volume + Normalized volume + + + + Reset + Reset + + + diff --git a/lisp/i18n/ts/nl_NL/lisp.ts b/lisp/i18n/ts/nl_NL/lisp.ts new file mode 100644 index 000000000..6961717a1 --- /dev/null +++ b/lisp/i18n/ts/nl_NL/lisp.ts @@ -0,0 +1,1180 @@ + + + + + About + + + Authors + Authors + + + + Contributors + Contributors + + + + Translators + Translators + + + + About Linux Show Player + About Linux Show Player + + + + AboutDialog + + + Linux Show Player is a cue-player designed for stage productions. + Linux Show Player is a cue-player designed for stage productions. + + + + Web site + Web site + + + + Users group + Users group + + + + Source code + Source code + + + + Info + Info + + + + License + License + + + + Contributors + Contributors + + + + Discussion + Discussion + + + + Actions + + + Undo: {} + Undo: {} + + + + Redo: {} + Redo: {} + + + + AppConfiguration + + + LiSP preferences + LiSP preferences + + + + AppGeneralSettings + + + Startup layout + Startup layout + + + + Use startup dialog + Use startup dialog + + + + Application theme + Application theme + + + + Default layout + Default layout + + + + Enable startup layout selector + Enable startup layout selector + + + + Application themes + Application themes + + + + UI theme + UI theme + + + + Icons theme + Icons theme + + + + CartLayout + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show volume + Show volume + + + + Show accurate time + Show accurate time + + + + Edit cue + Edit cue + + + + Remove + Remove + + + + Select + Select + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Number of Pages: + Number of Pages: + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + Page + Page + + + + Default behaviors + Default behaviors + + + + Automatically add new page + Automatically add new page + + + + Grid size + Grid size + + + + Number of columns + Number of columns + + + + Number of rows + Number of rows + + + + CueAction + + + Default + Default + + + + Pause + Pause + + + + Start + Start + + + + Stop + Stop + + + + FadeInStart + FadeInStart + + + + FadeOutStop + FadeOutStop + + + + FadeOutPause + FadeOutPause + + + + Faded Start + Faded Start + + + + Faded Resume + Faded Resume + + + + Faded Pause + Faded Pause + + + + Faded Stop + Faded Stop + + + + Faded Interrupt + Faded Interrupt + + + + Resume + Resume + + + + Do Nothing + Do Nothing + + + + CueActionLog + + + Cue settings changed: "{}" + Cue settings changed: "{}" + + + + Cues settings changed. + Cues settings changed. + + + + CueAppearanceSettings + + + The appearance depends on the layout + The appearance depends on the layout + + + + Cue name + Cue name + + + + NoName + NoName + + + + Description/Note + Description/Note + + + + Set Font Size + Set Font Size + + + + Color + Color + + + + Select background color + Select background color + + + + Select font color + Select font color + + + + CueName + + + Media Cue + Media Cue + + + + CueNextAction + + + Do Nothing + Do Nothing + + + + Auto Follow + Auto Follow + + + + Auto Next + Auto Next + + + + CueSettings + + + Pre wait + Pre wait + + + + Wait before cue execution + Wait before cue execution + + + + Post wait + Post wait + + + + Wait after cue execution + Wait after cue execution + + + + Next action + Next action + + + + Interrupt Fade + Interrupt Fade + + + + Fade Action + Fade Action + + + + Behaviours + Behaviours + + + + Pre/Post Wait + Pre/Post Wait + + + + Fade In/Out + Fade In/Out + + + + Start action + Start action + + + + Default action to start the cue + Default action to start the cue + + + + Stop action + Stop action + + + + Default action to stop the cue + Default action to stop the cue + + + + Interrupt fade + Interrupt fade + + + + Fade actions + Fade actions + + + + Fade + + + Linear + Linear + + + + Quadratic + Quadratic + + + + Quadratic2 + Quadratic2 + + + + FadeEdit + + + Duration (sec) + Duration (sec) + + + + Curve + Curve + + + + FadeSettings + + + Fade In + Fade In + + + + Fade Out + Fade Out + + + + LayoutDescription + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + Organize the cues in a list + Organize the cues in a list + + + + LayoutDetails + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To copy cues drag them while pressing SHIFT + To copy cues drag them while pressing SHIFT + + + + CTRL + Left Click to select cues + CTRL + Left Click to select cues + + + + To move cues drag them + To move cues drag them + + + + LayoutSelect + + + Layout selection + Layout selection + + + + Select layout + Select layout + + + + Open file + Open file + + + + ListLayout + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show seek-bars + Show seek-bars + + + + Show accurate time + Show accurate time + + + + Auto-select next cue + Auto-select next cue + + + + Edit cue + Edit cue + + + + Remove + Remove + + + + Select + Select + + + + Stop all + Stop all + + + + Pause all + Pause all + + + + Restart all + Restart all + + + + Stop + Stop + + + + Restart + Restart + + + + Default behaviors + Default behaviors + + + + At list end: + At list end: + + + + Go key: + Go key: + + + + Interrupt all + Interrupt all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + Use fade + Use fade + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Restart Cue + Restart Cue + + + + Interrupt Cue + Interrupt Cue + + + + Stop All + Stop All + + + + Pause All + Pause All + + + + Restart All + Restart All + + + + Interrupt All + Interrupt All + + + + Use fade (global actions) + Use fade (global actions) + + + + Resume All + Resume All + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + + Logging + + + Information + Information + + + + Debug + Debug + + + + Warning + Warning + + + + Error + Error + + + + Details: + Details: + + + + Dismiss all + Dismiss all + + + + Show details + Show details + + + + Linux Show Player - Log Viewer + Linux Show Player - Log Viewer + + + + Info + Info + + + + Critical + Critical + + + + Time + Time + + + + Milliseconds + Milliseconds + + + + Logger name + Logger name + + + + Level + Level + + + + Message + Message + + + + Function + Function + + + + Path name + Path name + + + + File name + File name + + + + Line no. + Line no. + + + + Module + Module + + + + Process ID + Process ID + + + + Process name + Process name + + + + Thread ID + Thread ID + + + + Thread name + Thread name + + + + MainWindow + + + &File + &File + + + + New session + New session + + + + Open + Open + + + + Save session + Save session + + + + Preferences + Preferences + + + + Save as + Save as + + + + Full Screen + Full Screen + + + + Exit + Exit + + + + &Edit + &Edit + + + + Undo + Undo + + + + Redo + Redo + + + + Select all + Select all + + + + Select all media cues + Select all media cues + + + + Deselect all + Deselect all + + + + CTRL+SHIFT+A + CTRL+SHIFT+A + + + + Invert selection + Invert selection + + + + CTRL+I + CTRL+I + + + + Edit selected + Edit selected + + + + CTRL+SHIFT+E + CTRL+SHIFT+E + + + + &Layout + &Layout + + + + &Tools + &Tools + + + + Edit selection + Edit selection + + + + &About + &About + + + + About + About + + + + About Qt + About Qt + + + + Undone: + Undone: + + + + Redone: + Redone: + + + + Close session + Close session + + + + The current session is not saved. + The current session is not saved. + + + + Discard the changes? + Discard the changes? + + + + MediaCueMenus + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + MediaCueSettings + + + Start time + Start time + + + + Stop position of the media + Stop position of the media + + + + Stop time + Stop time + + + + Start position of the media + Start position of the media + + + + Loop + Loop + + + + Repetition after first play (-1 = infinite) + Repetition after first play (-1 = infinite) + + + + QColorButton + + + Right click to reset + Right click to reset + + + + SettingsPageName + + + Appearance + Appearance + + + + General + General + + + + Cue + Cue + + + + Cue Settings + Cue Settings + + + + Plugins + Plugins + + + + Behaviours + Behaviours + + + + Pre/Post Wait + Pre/Post Wait + + + + Fade In/Out + Fade In/Out + + + diff --git a/lisp/i18n/ts/nl_NL/list_layout.ts b/lisp/i18n/ts/nl_NL/list_layout.ts new file mode 100644 index 000000000..7eadaab5d --- /dev/null +++ b/lisp/i18n/ts/nl_NL/list_layout.ts @@ -0,0 +1,189 @@ + + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them + To move cues drag them + + + + ListLayout + + + Default behaviors + Default behaviors + + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show seek-bars + Show seek-bars + + + + Auto-select next cue + Auto-select next cue + + + + Enable selection mode + Enable selection mode + + + + Go key: + Go key: + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Resume Cue + Resume Cue + + + + Interrupt Cue + Interrupt Cue + + + + Edit cue + Edit cue + + + + Edit selected cues + Edit selected cues + + + + Remove cue + Remove cue + + + + Remove selected cues + Remove selected cues + + + + Selection mode + Selection mode + + + + Pause all + Pause all + + + + Stop all + Stop all + + + + Interrupt all + Interrupt all + + + + Resume all + Resume all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + diff --git a/lisp/i18n/ts/nl_NL/media_info.ts b/lisp/i18n/ts/nl_NL/media_info.ts new file mode 100644 index 000000000..40b3c02b3 --- /dev/null +++ b/lisp/i18n/ts/nl_NL/media_info.ts @@ -0,0 +1,37 @@ + + + + + MediaInfo + + + Media Info + Media Info + + + + Error + Error + + + + No info to display + No info to display + + + + Info + Info + + + + Value + Value + + + + Warning + Warning + + + diff --git a/lisp/i18n/ts/nl_NL/midi.ts b/lisp/i18n/ts/nl_NL/midi.ts new file mode 100644 index 000000000..229c5f84f --- /dev/null +++ b/lisp/i18n/ts/nl_NL/midi.ts @@ -0,0 +1,30 @@ + + + + + MIDISettings + + + MIDI default devices + MIDI default devices + + + + Input + Input + + + + Output + Output + + + + SettingsPageName + + + MIDI settings + MIDI settings + + + diff --git a/lisp/i18n/ts/nl_NL/network.ts b/lisp/i18n/ts/nl_NL/network.ts new file mode 100644 index 000000000..bdf4f09f4 --- /dev/null +++ b/lisp/i18n/ts/nl_NL/network.ts @@ -0,0 +1,47 @@ + + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + diff --git a/lisp/i18n/ts/nl_NL/osc.ts b/lisp/i18n/ts/nl_NL/osc.ts new file mode 100644 index 000000000..a3d32f704 --- /dev/null +++ b/lisp/i18n/ts/nl_NL/osc.ts @@ -0,0 +1,35 @@ + + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + SettingsPageName + + + OSC settings + OSC settings + + + diff --git a/lisp/i18n/ts/nl_NL/presets.ts b/lisp/i18n/ts/nl_NL/presets.ts new file mode 100644 index 000000000..ebcc7e510 --- /dev/null +++ b/lisp/i18n/ts/nl_NL/presets.ts @@ -0,0 +1,150 @@ + + + + + Preset + + + Create Cue + Create Cue + + + + Load on selected Cues + Load on selected Cues + + + + Presets + + + Presets + Presets + + + + Load preset + Load preset + + + + Save as preset + Save as preset + + + + Cannot scan presets + Cannot scan presets + + + + Error while deleting preset "{}" + Error while deleting preset "{}" + + + + Cannot load preset "{}" + Cannot load preset "{}" + + + + Cannot save preset "{}" + Cannot save preset "{}" + + + + Cannot rename preset "{}" + Cannot rename preset "{}" + + + + Select Preset + Select Preset + + + + Preset already exists, overwrite? + Preset already exists, overwrite? + + + + Preset name + Preset name + + + + Add + Add + + + + Rename + Rename + + + + Edit + Edit + + + + Remove + Remove + + + + Export selected + Export selected + + + + Import + Import + + + + Warning + Warning + + + + The same name is already used! + The same name is already used! + + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + + + + Cannot export correctly. + Cannot export correctly. + + + + Some presets already exists, overwrite? + Some presets already exists, overwrite? + + + + Cannot import correctly. + Cannot import correctly. + + + + Cue type + Cue type + + + + Load on cue + Load on cue + + + + Load on selected cues + Load on selected cues + + + diff --git a/lisp/i18n/ts/nl_NL/rename_cues.ts b/lisp/i18n/ts/nl_NL/rename_cues.ts new file mode 100644 index 000000000..d447a5f19 --- /dev/null +++ b/lisp/i18n/ts/nl_NL/rename_cues.ts @@ -0,0 +1,95 @@ + + + + + RenameCues + + + Rename Cues + Rename Cues + + + + Rename cues + Rename cues + + + + Current + Current + + + + Preview + Preview + + + + Capitalize + Capitalize + + + + Lowercase + Lowercase + + + + Uppercase + Uppercase + + + + Remove Numbers + Remove Numbers + + + + Add numbering + Add numbering + + + + Reset + Reset + + + + Rename all cue. () in regex below usable with $0, $1 ... + Rename all cue. () in regex below usable with $0, $1 ... + + + + Type your regex here: + Type your regex here: + + + + Regex help + Regex help + + + + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple : +^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple : +^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation + + + diff --git a/lisp/i18n/ts/nl_NL/replay_gain.ts b/lisp/i18n/ts/nl_NL/replay_gain.ts new file mode 100644 index 000000000..0c0153352 --- /dev/null +++ b/lisp/i18n/ts/nl_NL/replay_gain.ts @@ -0,0 +1,52 @@ + + + + + ReplayGain + + + ReplayGain / Normalization + ReplayGain / Normalization + + + + Calculate + Calculate + + + + Reset all + Reset all + + + + Reset selected + Reset selected + + + + Threads number + Threads number + + + + Apply only to selected media + Apply only to selected media + + + + ReplayGain to (dB SPL) + ReplayGain to (dB SPL) + + + + Normalize to (dB) + Normalize to (dB) + + + + Processing files ... + Processing files ... + + + diff --git a/lisp/i18n/ts/nl_NL/synchronizer.ts b/lisp/i18n/ts/nl_NL/synchronizer.ts new file mode 100644 index 000000000..bb31ac524 --- /dev/null +++ b/lisp/i18n/ts/nl_NL/synchronizer.ts @@ -0,0 +1,85 @@ + + + + + SyncPeerDialog + + + Manage connected peers + Manage connected peers + + + + Discover peers + Discover peers + + + + Manually add a peer + Manually add a peer + + + + Remove selected peer + Remove selected peer + + + + Remove all peers + Remove all peers + + + + Address + Address + + + + Peer IP + Peer IP + + + + Error + Error + + + + Already connected + Already connected + + + + Cannot add peer + Cannot add peer + + + + Synchronizer + + + Synchronization + Synchronization + + + + Manage connected peers + Manage connected peers + + + + Show your IP + Show your IP + + + + Your IP is: + Your IP is: + + + + Discovering peers ... + Discovering peers ... + + + diff --git a/lisp/i18n/ts/nl_NL/timecode.ts b/lisp/i18n/ts/nl_NL/timecode.ts new file mode 100644 index 000000000..fbceb7550 --- /dev/null +++ b/lisp/i18n/ts/nl_NL/timecode.ts @@ -0,0 +1,110 @@ + + + + + SettingsPageName + + + Timecode Settings + Timecode Settings + + + + Timecode + Timecode + + + + Timecode + + + Cannot send timecode. + Cannot send timecode. + + + + OLA has stopped. + OLA has stopped. + + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + Cannot send timecode. +OLA has stopped. + Cannot send timecode. +OLA has stopped. + + + + TimecodeSettings + + + OLA Timecode Settings + OLA Timecode Settings + + + + Enable Plugin + Enable Plugin + + + + High-Resolution Timecode + High-Resolution Timecode + + + + Timecode Format: + Timecode Format: + + + + OLA status + OLA status + + + + OLA is not running - start the OLA daemon. + OLA is not running - start the OLA daemon. + + + + Replace HOURS by a static track number + Replace HOURS by a static track number + + + + Enable ArtNet Timecode + Enable ArtNet Timecode + + + + Track number + Track number + + + + To send ArtNet Timecode you need to setup a running OLA session! + To send ArtNet Timecode you need to setup a running OLA session! + + + + Enable Timecode + Enable Timecode + + + + Timecode Settings + Timecode Settings + + + + Timecode Protocol: + Timecode Protocol: + + + diff --git a/lisp/i18n/ts/nl_NL/triggers.ts b/lisp/i18n/ts/nl_NL/triggers.ts new file mode 100644 index 000000000..55fc89bb3 --- /dev/null +++ b/lisp/i18n/ts/nl_NL/triggers.ts @@ -0,0 +1,63 @@ + + + + + CueTriggers + + + Started + Started + + + + Paused + Paused + + + + Stopped + Stopped + + + + Ended + Ended + + + + SettingsPageName + + + Triggers + Triggers + + + + TriggersSettings + + + Add + Add + + + + Remove + Remove + + + + Trigger + Trigger + + + + Cue + Cue + + + + Action + Action + + + diff --git a/lisp/i18n/ts/sl_SI/action_cues.ts b/lisp/i18n/ts/sl_SI/action_cues.ts index 66523b37f..e5caf2963 100644 --- a/lisp/i18n/ts/sl_SI/action_cues.ts +++ b/lisp/i18n/ts/sl_SI/action_cues.ts @@ -1,244 +1,345 @@ - - + + + + CollectionCue - - Add - Dodaj + + Add + Dodaj - - Remove - Odstrani + + Remove + Odstrani - - Cue - Vrsta + + Cue + Vrsta - - Action - Akcija + + Action + Akcija - - + + CommandCue - - Process ended with an error status. - Procesiranje se je končalo z napako. + + Process ended with an error status. + Procesiranje se je končalo z napako. - - Exit code: - Izhodni ukaz: + + Exit code: + Izhodni ukaz: - - Command - Ukaz + + Command + Ukaz - - Command to execute, as in a shell - Ukaz za izvršitev, kot v ukazni lupini + + Command to execute, as in a shell + Ukaz za izvršitev, kot v ukazni lupini - - Discard command output - Zavrži izhod ukaza + + Discard command output + Zavrži izhod ukaza - - Ignore command errors - Ignoriraj napake ukaza + + Ignore command errors + Ignoriraj napake ukaza - - Kill instead of terminate - Kill namesto terminate + + Kill instead of terminate + Kill namesto terminate - - + + + Command cue ended with an error status. Exit code: {} + Command cue ended with an error status. Exit code: {} + + + + Cue Name + + + OSC Settings + OSC Settings + + + CueName - - Command Cue - Ukazna vrsta + + Command Cue + Ukazna vrsta - - MIDI Cue - MIDI vrsta + + MIDI Cue + MIDI vrsta - - Volume Control - Krmiljenje glasnosti + + Volume Control + Krmiljenje glasnosti - - Seek Cue - Vrsta iskanj + + Seek Cue + Vrsta iskanj - - Collection Cue - Zbirke v vrsti + + Collection Cue + Zbirke v vrsti - - Stop-All - Ustavi vse + + Stop-All + Ustavi vse - - Index Action - Akcija indeksa + + Index Action + Akcija indeksa - - + + + OSC Cue + OSC Cue + + + IndexActionCue - - Index - Indeks + + Index + Indeks + + + + Use a relative index + Uporabi relativni indeks + + + + Target index + Ciljni indeks - - Use a relative index - Uporabi relativni indeks + + Action + Akcija - - Target index - Ciljni indeks + + No suggestion + No suggestion - - Action - Akcija + + Suggested cue name + Suggested cue name - - + + MIDICue - - MIDI Message - MIDI sporočilo + + MIDI Message + MIDI sporočilo + + + + Message type + Tip sporočila + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OscCue + + + Error during cue execution. + Error during cue execution. + + + + OSC Message + OSC Message + + + + Add + Add + + + + Remove + Remove + + + + Test + Test + + + + OSC Path: (example: "/path/to/something") + OSC Path: (example: "/path/to/something") + + + + Fade + Fade + + + + Time (sec) + Time (sec) - - Message type - Tip sporočila + + Curve + Curve - - + + SeekCue - - Cue - Vrsta + + Cue + Vrsta - - Click to select - Klikni za izbor + + Click to select + Klikni za izbor - - Not selected - Ni izbrano + + Not selected + Ni izbrano - - Seek - Iskanje + + Seek + Iskanje - - Time to reach - Čas za dosego + + Time to reach + Čas za dosego - - + + SettingsPageName - - Command - Ukaz + + Command + Ukaz - - MIDI Settings - MIDI nastavitve + + MIDI Settings + MIDI nastavitve - - Volume Settings - Nastavitve glasnosti + + Volume Settings + Nastavitve glasnosti - - Seek Settings - Nastavitve iskanja + + Seek Settings + Nastavitve iskanja - - Edit Collection - Uredi zbirko + + Edit Collection + Uredi zbirko - - Action Settings - Nastavitve akcije + + Action Settings + Nastavitve akcije - - Stop Settings - Nastavitve ustavitve + + Stop Settings + Nastavitve ustavitve - - + + StopAll - - Stop Action - Akcija ustavitve + + Stop Action + Akcija ustavitve - - + + VolumeControl - - Error during cue execution - Napaka med izvajanjem vrste + + Error during cue execution + Napaka med izvajanjem vrste - - Cue - Vrsta + + Cue + Vrsta - - Click to select - Klikni za izbor + + Click to select + Klikni za izbor - - Not selected - Ni izbrano + + Not selected + Ni izbrano - - Volume to reach - Glasnost za dosego + + Volume to reach + Glasnost za dosego - - Fade - Pojenjanje + + Fade + Pojenjanje - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/sl_SI/cart_layout.ts b/lisp/i18n/ts/sl_SI/cart_layout.ts new file mode 100644 index 000000000..ce87c97a0 --- /dev/null +++ b/lisp/i18n/ts/sl_SI/cart_layout.ts @@ -0,0 +1,176 @@ + + + + + CartLayout + + + Default behaviors + Default behaviors + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show volume + Show volume + + + + Automatically add new page + Automatically add new page + + + + Grid size + Grid size + + + + Number of columns + Number of columns + + + + Number of rows + Number of rows + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Number of Pages: + Number of Pages: + + + + Page {number} + Page {number} + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + LayoutDescription + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + LayoutDetails + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them while pressing SHIFT + To move cues drag them while pressing SHIFT + + + + ListLayout + + + Edit cue + Edit cue + + + + Edit selected cues + Edit selected cues + + + + Remove cue + Remove cue + + + + Remove selected cues + Remove selected cues + + + diff --git a/lisp/i18n/ts/sl_SI/controller.ts b/lisp/i18n/ts/sl_SI/controller.ts index 9b0282b16..ecc2cd873 100644 --- a/lisp/i18n/ts/sl_SI/controller.ts +++ b/lisp/i18n/ts/sl_SI/controller.ts @@ -1,99 +1,198 @@ - - + + + + + Controller + + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + ControllerKeySettings - - Key - Ključ + + Key + Ključ - - Action - Akcija + + Action + Akcija - - Shortcuts - Bližnjica + + Shortcuts + Bližnjica - - + + ControllerMidiSettings - - MIDI - MIDI + + MIDI + MIDI - - Type - Tip + + Type + Tip - - Channel - Kanal + + Channel + Kanal - - Note - Beležka + + Note + Beležka - - Action - Akcija + + Action + Akcija - - Filter "note on" - Filter "Beležka aktivna" + + Filter "note on" + Filter "Beležka aktivna" - - Filter "note off" - Filter "Beležka neaktivna" + + Filter "note off" + Filter "Beležka neaktivna" - - Capture - Zajemi + + Capture + Zajemi - - Listening MIDI messages ... - Poslušam MIDI sporočila ... + + Listening MIDI messages ... + Poslušam MIDI sporočila ... - - + + + ControllerOscSettings + + + OSC Message + OSC Message + + + + OSC Path: (example: "/path/to/something") + OSC Path: (example: "/path/to/something") + + + + OSC + OSC + + + + Path + Path + + + + Types + Types + + + + Arguments + Arguments + + + + Actions + Actions + + + + OSC Capture + OSC Capture + + + + Add + Add + + + + Remove + Remove + + + + Capture + Capture + + + ControllerSettings - - Add - Dodaj + + Add + Dodaj + + + + Remove + Odstrani + + + Osc Cue - - Remove - Odstrani + + Type + Type - - + + + Argument + Argument + + + + OscCue + + + Add + Add + + + + Remove + Remove + + + SettingsPageName - - Cue Control - Krmiljenje vrste + + Cue Control + Krmiljenje vrste + + + + MIDI Controls + Krmiljenje MIDI - - MIDI Controls - Krmiljenje MIDI + + Keyboard Shortcuts + Bližnjice na tipkovnici - - Keyboard Shortcuts - Bližnjice na tipkovnici + + OSC Controls + OSC Controls - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/sl_SI/gst_backend.ts b/lisp/i18n/ts/sl_SI/gst_backend.ts index 80c011b81..1939a626b 100644 --- a/lisp/i18n/ts/sl_SI/gst_backend.ts +++ b/lisp/i18n/ts/sl_SI/gst_backend.ts @@ -1,368 +1,388 @@ - - + + + + AlsaSinkSettings - - ALSA device - ALSA naprava + + ALSA device + ALSA naprava - - ALSA devices, as defined in an asound configuration file - ALSA naprava, kot definirana v asound konfiguracijski datoteki + + ALSA devices, as defined in an asound configuration file + ALSA naprava, kot definirana v asound konfiguracijski datoteki - - + + AudioDynamicSettings - - Compressor - Kompresor + + Compressor + Kompresor - - Expander - Razširjevalec + + Expander + Razširjevalec - - Soft Knee - Mehko koleno + + Soft Knee + Mehko koleno - - Hard Knee - Trdo koleno + + Hard Knee + Trdo koleno - - Compressor/Expander - Kompresor/Razširjevalec + + Compressor/Expander + Kompresor/Razširjevalec - - Type - Tip + + Type + Tip - - Curve Shape - Oblika krivulje + + Curve Shape + Oblika krivulje - - Ratio - Razmerje + + Ratio + Razmerje - - Threshold (dB) - Prag (dB) + + Threshold (dB) + Prag (dB) - - + + AudioPanSettings - - Audio Pan - Avdio nagib + + Audio Pan + Avdio nagib - - Center - Sredina + + Center + Sredina - - Left - Levo + + Left + Levo - - Right - Desno + + Right + Desno - - + + DbMeterSettings - - DbMeter settings - Nastavitve Db Metra + + DbMeter settings + Nastavitve Db Metra - - Time between levels (ms) - Čas med nivoji (ms) + + Time between levels (ms) + Čas med nivoji (ms) - - Peak ttl (ms) - Vrhovni TTL (ms) + + Peak ttl (ms) + Vrhovni TTL (ms) - - Peak falloff (dB/sec) - Vrhovni padec (dB/s) + + Peak falloff (dB/sec) + Vrhovni padec (dB/s) - - + + Equalizer10Settings - - 10 Bands Equalizer (IIR) - 10 kanalni Izenačevalnik (IIR) + + 10 Bands Equalizer (IIR) + 10 kanalni Izenačevalnik (IIR) - - + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + GstMediaSettings - - Change Pipeline - Spremeni cevovod + + Change Pipeline + Spremeni cevovod - - + + GstPipelineEdit - - Edit Pipeline - Uredi cevovod + + Edit Pipeline + Uredi cevovod - - + + GstSettings - - Pipeline - Cevovod + + Pipeline + Cevovod - - + + JackSinkSettings - - Connections - Povezave + + Connections + Povezave - - Edit connections - Uredi povezave + + Edit connections + Uredi povezave - - Output ports - Izhodna vrata + + Output ports + Izhodna vrata - - Input ports - Vhodna vrata + + Input ports + Vhodna vrata - - Connect - Poveži + + Connect + Poveži - - Disconnect - Prekini + + Disconnect + Prekini - - + + MediaElementName - - Compressor/Expander - Kompresor/Razširjevalec + + Compressor/Expander + Kompresor/Razširjevalec - - Audio Pan - Avdio nagib + + Audio Pan + Avdio nagib - - PulseAudio Out - PulseAudio izhod + + PulseAudio Out + PulseAudio izhod - - Volume - Glasnost + + Volume + Glasnost - - dB Meter - dB meter + + dB Meter + dB meter - - System Input - Sistemski vhod + + System Input + Sistemski vhod - - ALSA Out - ALSA izhod + + ALSA Out + ALSA izhod - - JACK Out - JACK izhod + + JACK Out + JACK izhod - - Custom Element - Element po meri + + Custom Element + Element po meri - - System Out - Sistemski izhod + + System Out + Sistemski izhod - - Pitch - Višina + + Pitch + Višina - - URI Input - URI vhod + + URI Input + URI vhod - - 10 Bands Equalizer - 10 kanalni Izenačevalnik + + 10 Bands Equalizer + 10 kanalni Izenačevalnik - - Speed - Hitrost + + Speed + Hitrost - - Preset Input - Pred-nastavljeni vhod + + Preset Input + Pred-nastavljeni vhod - - + + PitchSettings - - Pitch - Višina + + Pitch + Višina - - {0:+} semitones - {0:+} poltoni + + {0:+} semitones + {0:+} poltoni - - + + PresetSrcSettings - - Presets - Pred-nastavitve + + Presets + Pred-nastavitve - - + + SettingsPageName - - GStreamer settings - GStreamer nastavitve + + GStreamer settings + GStreamer nastavitve + + + + Media Settings + Nastavitve medija - - Media Settings - Nastavitve medija + + GStreamer + GStreamer - - + + SpeedSettings - - Speed - Hitrost + + Speed + Hitrost - - + + UriInputSettings - - Source - Izvor + + Source + Izvor - - Find File - Najdi datoteko + + Find File + Najdi datoteko - - Buffering - Pred-pomnjenje + + Buffering + Pred-pomnjenje - - Use Buffering - Uporabi pred-pomnjenje + + Use Buffering + Uporabi pred-pomnjenje - - Attempt download on network streams - Poizkusi prenesti omrežni tok + + Attempt download on network streams + Poizkusi prenesti omrežni tok - - Buffer size (-1 default value) - Velikost pred-pomnilnika (-1 privzeta vrednost) + + Buffer size (-1 default value) + Velikost pred-pomnilnika (-1 privzeta vrednost) - - Choose file - Izberi datoteko + + Choose file + Izberi datoteko - - All files - Vse datoteke + + All files + Vse datoteke - - + + UserElementSettings - - User defined elements - Uporabniško definirani elementi + + User defined elements + Uporabniško definirani elementi - - Only for advanced user! - Le za napredne uporabnike! + + Only for advanced user! + Le za napredne uporabnike! - - + + VolumeSettings - - Volume - Glasnost + + Volume + Glasnost - - Normalized volume - Normalizirana glasnost + + Normalized volume + Normalizirana glasnost - - Reset - Ponastavi + + Reset + Ponastavi - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/sl_SI/lisp.ts b/lisp/i18n/ts/sl_SI/lisp.ts index f883d87df..8f2b57d0f 100644 --- a/lisp/i18n/ts/sl_SI/lisp.ts +++ b/lisp/i18n/ts/sl_SI/lisp.ts @@ -1,947 +1,1180 @@ - - + + + + About - - Authors - Avtorji + + Authors + Avtorji - - Contributors - Prispevkarji + + Contributors + Prispevkarji - - Translators - Prevajalci + + Translators + Prevajalci - - About Linux Show Player - Opis Linux Show Player-ja + + About Linux Show Player + Opis Linux Show Player-ja - - + + AboutDialog - - Linux Show Player is a cue-player designed for stage productions. - Linux Show Player je predvajalnik vrst, prilagojen za odrsko/gledališčno rabo + + Linux Show Player is a cue-player designed for stage productions. + Linux Show Player je predvajalnik vrst, prilagojen za odrsko/gledališčno rabo - - Web site - Spletna stran + + Web site + Spletna stran - - Users group - Skupina uporabnikov + + Users group + Skupina uporabnikov - - Source code - Izvorna koda + + Source code + Izvorna koda - - Info - Podrobnosti + + Info + Podrobnosti - - License - Licenca + + License + Licenca - - Contributors - Prispevkarji + + Contributors + Prispevkarji - - - AppGeneralSettings - - Startup layout - Začetna razporeditev + + Discussion + Discussion + + + Actions - - Use startup dialog - Uporabi začetni dialog + + Undo: {} + Undo: {} - - Application theme - Tema aplikacije + + Redo: {} + Redo: {} - - + + AppConfiguration - - LiSP preferences - LiSP nastavitve + + LiSP preferences + LiSP nastavitve + + + + AppGeneralSettings + + + Startup layout + Začetna razporeditev + + + + Use startup dialog + Uporabi začetni dialog + + + + Application theme + Tema aplikacije + + + + Default layout + Default layout + + + + Enable startup layout selector + Enable startup layout selector - - + + + Application themes + Application themes + + + + UI theme + UI theme + + + + Icons theme + Icons theme + + + CartLayout - - Add page - Dodaj stran + + Add page + Dodaj stran - - Add pages - Dodaj strani + + Add pages + Dodaj strani - - Remove current page - Odstrani trenutno stran + + Remove current page + Odstrani trenutno stran - - Countdown mode - Odštevalni način + + Countdown mode + Odštevalni način - - Show seek-bars - Prikaži iskalne stolpiče + + Show seek-bars + Prikaži iskalne stolpiče - - Show dB-meters - Prikaži dB metre + + Show dB-meters + Prikaži dB metre - - Show volume - Prikaži glasnost + + Show volume + Prikaži glasnost - - Show accurate time - Prikaži točen čas + + Show accurate time + Prikaži točen čas - - Edit cue - Uredi vrsto + + Edit cue + Uredi vrsto - - Remove - Odstrani + + Remove + Odstrani - - Select - Izberi + + Select + Izberi - - Play - Predvajaj + + Play + Predvajaj - - Pause - Premor + + Pause + Premor - - Stop - Ustavi + + Stop + Ustavi - - Reset volume - Ponastavi glasnost + + Reset volume + Ponastavi glasnost - - Number of Pages: - Število strani: + + Number of Pages: + Število strani: - - Warning - Opozorilo + + Warning + Opozorilo - - Every cue in the page will be lost. - Vse vrste na strani bodo izgubljene + + Every cue in the page will be lost. + Vse vrste na strani bodo izgubljene - - Are you sure to continue? - Ste prepričani v nadaljevanje? + + Are you sure to continue? + Ste prepričani v nadaljevanje? - - Page - Stran + + Page + Stran - - Default behaviors - Privzeto obnašanje + + Default behaviors + Privzeto obnašanje - - Automatically add new page - Samodejno dodaj novo stran + + Automatically add new page + Samodejno dodaj novo stran - - Grid size - Velikost mreže + + Grid size + Velikost mreže - - Number of columns - Število stolpcev + + Number of columns + Število stolpcev - - Number of rows - Število vrstic + + Number of rows + Število vrstic - - + + CueAction - - Default - Privzeto + + Default + Privzeto + + + + Pause + Premor + + + + Start + Zagon + + + + Stop + Ustavi + + + + FadeInStart + Ojačevanje ob začetku + + + + FadeOutStop + Pojemanje ob ustavitvi + + + + FadeOutPause + Pojemanje ob premoru + + + + Faded Start + Faded Start - - Pause - Premor + + Faded Resume + Faded Resume - - Start - Zagon + + Faded Pause + Faded Pause - - Stop - Ustavi + + Faded Stop + Faded Stop - - FadeInStart - Ojačevanje ob začetku + + Faded Interrupt + Faded Interrupt - - FadeOutStop - Pojemanje ob ustavitvi + + Resume + Resume - - FadeOutPause - Pojemanje ob premoru + + Do Nothing + Do Nothing - - + + CueActionLog - - Cue settings changed: "{}" - Nastavitev vrste spremenjene: "{}" + + Cue settings changed: "{}" + Nastavitev vrste spremenjene: "{}" - - Cues settings changed. - Nastavitev vrst spremenjene. + + Cues settings changed. + Nastavitev vrst spremenjene. - - + + CueAppearanceSettings - - The appearance depends on the layout - Prikaz je odvisen od uporabljene razporeditve + + The appearance depends on the layout + Prikaz je odvisen od uporabljene razporeditve - - Cue name - Ime vrste + + Cue name + Ime vrste - - NoName - Brez imena + + NoName + Brez imena - - Description/Note - Opis/Zapiski + + Description/Note + Opis/Zapiski - - Set Font Size - Nastavi velikost pisave + + Set Font Size + Nastavi velikost pisave - - Color - Barva + + Color + Barva - - Select background color - Izberi barvo ozadja + + Select background color + Izberi barvo ozadja - - Select font color - Izberi barvo pisave + + Select font color + Izberi barvo pisave - - + + CueName - - Media Cue - Medijska vrsta + + Media Cue + Medijska vrsta - - + + + CueNextAction + + + Do Nothing + Do Nothing + + + + Auto Follow + Auto Follow + + + + Auto Next + Auto Next + + + CueSettings - - Pre wait - Pred čakanje + + Pre wait + Pred čakanje - - Wait before cue execution - Počakaj preden izvedeš vrsto + + Wait before cue execution + Počakaj preden izvedeš vrsto - - Post wait - Po čakanje + + Post wait + Po čakanje - - Wait after cue execution - Počakaj po izvedbi vrste + + Wait after cue execution + Počakaj po izvedbi vrste - - Next action - Naslednja akcija + + Next action + Naslednja akcija - - Interrupt Fade - Prekini prehod + + Interrupt Fade + Prekini prehod - - Fade Action - Akcija prehoda + + Fade Action + Akcija prehoda - - Behaviours - Obnašanja + + Behaviours + Obnašanja - - Pre/Post Wait - Pred/Po čakanje + + Pre/Post Wait + Pred/Po čakanje - - Fade In/Out - Prehod v/iz + + Fade In/Out + Prehod v/iz - - Start action - Začetna akcija + + Start action + Začetna akcija - - Default action to start the cue - Privzeta akcija za zagon vrste + + Default action to start the cue + Privzeta akcija za zagon vrste - - Stop action - Ustavi akcijo + + Stop action + Ustavi akcijo - - Default action to stop the cue - Privzeta akcija ob ustavitvi vrste + + Default action to stop the cue + Privzeta akcija ob ustavitvi vrste - - + + + Interrupt fade + Interrupt fade + + + + Fade actions + Fade actions + + + Fade - - Linear - Linearno + + Linear + Linearno - - Quadratic - Kvadratno + + Quadratic + Kvadratno - - Quadratic2 - Kvadratno2 + + Quadratic2 + Kvadratno2 - - + + FadeEdit - - Duration (sec) - Trajanje (s) + + Duration (sec) + Trajanje (s) - - Curve - Krivulja + + Curve + Krivulja - - + + FadeSettings - - Fade In - Ojačevanje + + Fade In + Ojačevanje - - Fade Out - Pojemanje + + Fade Out + Pojemanje - - + + LayoutDescription - - Organize cues in grid like pages - Razporedi vrste v strani podobni mreži + + Organize cues in grid like pages + Razporedi vrste v strani podobni mreži - - Organize the cues in a list - Razporedi vrste v seznam + + Organize the cues in a list + Razporedi vrste v seznam - - + + LayoutDetails - - Click a cue to run it - Klik na vrsto za zagon + + Click a cue to run it + Klik na vrsto za zagon - - SHIFT + Click to edit a cue - SHIFT + Klik, za ureditev vrste + + SHIFT + Click to edit a cue + SHIFT + Klik, za ureditev vrste - - CTRL + Click to select a cue - CTRL + Klik, za izbor vrste + + CTRL + Click to select a cue + CTRL + Klik, za izbor vrste - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Preslednica ali dvoklik za ureditev vrste + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Preslednica ali dvoklik za ureditev vrste - - To copy cues drag them while pressing CTRL - Za kopiranje vrst, med vlečenjem pridržite SHIFT + + To copy cues drag them while pressing CTRL + Za kopiranje vrst, med vlečenjem pridržite SHIFT - - To copy cues drag them while pressing SHIFT - Za kopiranje vrst, med vlečenjem pridržite SHIFT + + To copy cues drag them while pressing SHIFT + Za kopiranje vrst, med vlečenjem pridržite SHIFT - - CTRL + Left Click to select cues - CTRL + Levi klik, za izbor vrst + + CTRL + Left Click to select cues + CTRL + Levi klik, za izbor vrst - - To move cues drag them - Za premikanje vrst jih povlecite + + To move cues drag them + Za premikanje vrst jih povlecite - - + + LayoutSelect - - Layout selection - Izbor razporeditve + + Layout selection + Izbor razporeditve - - Select layout - Izberi razporeditev + + Select layout + Izberi razporeditev - - Open file - Odpri datoteko + + Open file + Odpri datoteko - - + + ListLayout - - Show playing cues - Prikaži predvajane vrste + + Show playing cues + Prikaži predvajane vrste + + + + Show dB-meters + Prikaži dB metre + + + + Show seek-bars + Prikaži iskalne stolpiče - - Show dB-meters - Prikaži dB metre + + Show accurate time + Prikaži točen čas - - Show seek-bars - Prikaži iskalne stolpiče + + Auto-select next cue + Samodejno izberi naslednjo vrsto - - Show accurate time - Prikaži točen čas + + Edit cue + Uredi vrsto - - Auto-select next cue - Samodejno izberi naslednjo vrsto + + Remove + Odstrani - - Edit cue - Uredi vrsto + + Select + Izberi - - Remove - Odstrani + + Stop all + Ustavi vse - - Select - Izberi + + Pause all + V premor vse - - Stop all - Ustavi vse + + Restart all + Ponovno zaženi vse - - Pause all - V premor vse + + Stop + Ustavi - - Restart all - Ponovno zaženi vse + + Restart + Ponovno zaženi - - Stop - Ustavi + + Default behaviors + Privzeto obnašanje - - Restart - Ponovno zaženi + + At list end: + Ob koncu seznama: - - Default behaviors - Privzeto obnašanje + + Go key: + Sprožilni ključ: - - At list end: - Ob koncu seznama: + + Interrupt all + Prekini vse - - Go key: - Sprožilni ključ: + + Fade-Out all + Pojemanje vse - - Interrupt all - Prekini vse + + Fade-In all + Ojačevanje vse - - Fade-Out all - Pojemanje vse + + Use fade + Uporabi prehod - - Fade-In all - Ojačevanje vse + + Stop Cue + Ustavi vrsto - - Use fade - Uporabi prehod + + Pause Cue + Premor vrste - - Stop Cue - Ustavi vrsto + + Restart Cue + Ponovno zaženi vrsto - - Pause Cue - Premor vrste + + Interrupt Cue + Prekini vrsto - - Restart Cue - Ponovno zaženi vrsto + + Stop All + Ustavi vse - - Interrupt Cue - Prekini vrsto + + Pause All + V premor vse - - Stop All - Ustavi vse + + Restart All + Ponovno zaženi vse - - Pause All - V premor vse + + Interrupt All + Prekini vse - - Restart All - Ponovno zaženi vse + + Use fade (global actions) + Use fade (global actions) - - Interrupt All - Prekini vse + + Resume All + Resume All - - + + ListLayoutHeader - - Cue - Vrsta + + Cue + Vrsta - - Pre wait - Pred čakanje + + Pre wait + Pred čakanje - - Action - Akcija + + Action + Akcija - - Post wait - Po čakanje + + Post wait + Po čakanje - - + + ListLayoutInfoPanel - - Cue name - Ime vrste + + Cue name + Ime vrste - - Cue description - Opis vrste + + Cue description + Opis vrste - - + + Logging - - Information - Informacije + + Information + Informacije - - Debug - Razhroščevanje + + Debug + Razhroščevanje - - Warning - Opozorilo + + Warning + Opozorilo - - Error - Napaka + + Error + Napaka - - Details: - Podrobnosti: + + Details: + Podrobnosti: - - + + + Dismiss all + Dismiss all + + + + Show details + Show details + + + + Linux Show Player - Log Viewer + Linux Show Player - Log Viewer + + + + Info + Info + + + + Critical + Critical + + + + Time + Time + + + + Milliseconds + Milliseconds + + + + Logger name + Logger name + + + + Level + Level + + + + Message + Message + + + + Function + Function + + + + Path name + Path name + + + + File name + File name + + + + Line no. + Line no. + + + + Module + Module + + + + Process ID + Process ID + + + + Process name + Process name + + + + Thread ID + Thread ID + + + + Thread name + Thread name + + + MainWindow - - &File - &Datoteka + + &File + &Datoteka - - New session - Nova seja + + New session + Nova seja - - Open - Odpri + + Open + Odpri - - Save session - Shrani sejo + + Save session + Shrani sejo - - Preferences - Nastavitve + + Preferences + Nastavitve - - Save as - Shrani kot + + Save as + Shrani kot - - Full Screen - Polni zaslon + + Full Screen + Polni zaslon - - Exit - Izhod + + Exit + Izhod - - &Edit - &Uredi + + &Edit + &Uredi - - Undo - Razveljavi + + Undo + Razveljavi - - Redo - Obnovi + + Redo + Obnovi - - Select all - Izberi vse + + Select all + Izberi vse - - Select all media cues - Izberi vse medijske vrste + + Select all media cues + Izberi vse medijske vrste - - Deselect all - Od izberi vse + + Deselect all + Od izberi vse - - CTRL+SHIFT+A - CTRL+SHIFT+A + + CTRL+SHIFT+A + CTRL+SHIFT+A - - Invert selection - Invertiraj izbor + + Invert selection + Invertiraj izbor - - CTRL+I - CTRL+I + + CTRL+I + CTRL+I - - Edit selected - Uredi izbrano + + Edit selected + Uredi izbrano - - CTRL+SHIFT+E - CTRL+SHIFT+E + + CTRL+SHIFT+E + CTRL+SHIFT+E - - &Layout - &Razporeditev + + &Layout + &Razporeditev - - &Tools - &Orodja + + &Tools + &Orodja - - Edit selection - Uredi izbor + + Edit selection + Uredi izbor - - &About - &Opis + + &About + &Opis - - About - Opis + + About + Opis - - About Qt - Opis Qt + + About Qt + Opis Qt - - Undone: - Razveljavljeno: + + Undone: + Razveljavljeno: - - Redone: - Uveljavljeno: + + Redone: + Uveljavljeno: - - Close session - Zapri sejo + + Close session + Zapri sejo - - The current session is not saved. - Trenutna seja ni shranjena. + + The current session is not saved. + Trenutna seja ni shranjena. - - Discard the changes? - Zavržem spremembe? + + Discard the changes? + Zavržem spremembe? - - + + MediaCueMenus - - Audio cue (from file) - Avdio vrsta (iz datoteke) + + Audio cue (from file) + Avdio vrsta (iz datoteke) - - Select media files - Izberi medijske datoteke + + Select media files + Izberi medijske datoteke - - + + MediaCueSettings - - Start time - Čas začetka + + Start time + Čas začetka - - Stop position of the media - Zaustavitvena pozicija medija + + Stop position of the media + Zaustavitvena pozicija medija - - Stop time - Čas zaustavitve + + Stop time + Čas zaustavitve - - Start position of the media - Začetna pozicija medija + + Start position of the media + Začetna pozicija medija - - Loop - Ponavljaj + + Loop + Ponavljaj - - Repetition after first play (-1 = infinite) - Ponovitev po prvem predvajanju (-1 = neskončno) + + Repetition after first play (-1 = infinite) + Ponovitev po prvem predvajanju (-1 = neskončno) - - + + QColorButton - - Right click to reset - Desno klik za ponastavitev + + Right click to reset + Desno klik za ponastavitev - - + + SettingsPageName - - Appearance - Prikaz + + Appearance + Prikaz + + + + General + Splošno + + + + Cue + Vrsta + + + + Cue Settings + Nastavitve vrste + + + + Plugins + Plugins - - General - Splošno + + Behaviours + Behaviours - - Cue - Vrsta + + Pre/Post Wait + Pre/Post Wait - - Cue Settings - Nastavitve vrste + + Fade In/Out + Fade In/Out - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/sl_SI/list_layout.ts b/lisp/i18n/ts/sl_SI/list_layout.ts new file mode 100644 index 000000000..9ccb31406 --- /dev/null +++ b/lisp/i18n/ts/sl_SI/list_layout.ts @@ -0,0 +1,189 @@ + + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them + To move cues drag them + + + + ListLayout + + + Default behaviors + Default behaviors + + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show seek-bars + Show seek-bars + + + + Auto-select next cue + Auto-select next cue + + + + Enable selection mode + Enable selection mode + + + + Go key: + Go key: + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Resume Cue + Resume Cue + + + + Interrupt Cue + Interrupt Cue + + + + Edit cue + Edit cue + + + + Edit selected cues + Edit selected cues + + + + Remove cue + Remove cue + + + + Remove selected cues + Remove selected cues + + + + Selection mode + Selection mode + + + + Pause all + Pause all + + + + Stop all + Stop all + + + + Interrupt all + Interrupt all + + + + Resume all + Resume all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + diff --git a/lisp/i18n/ts/sl_SI/media_info.ts b/lisp/i18n/ts/sl_SI/media_info.ts index 44b679ec8..ed55bbf65 100644 --- a/lisp/i18n/ts/sl_SI/media_info.ts +++ b/lisp/i18n/ts/sl_SI/media_info.ts @@ -1,30 +1,37 @@ - - + + + + MediaInfo - - Media Info - Podrobnosti o mediju + + Media Info + Podrobnosti o mediju - - Error - Napaka + + Error + Napaka - - No info to display - Brez podrobnosti za prikaz + + No info to display + Brez podrobnosti za prikaz - - Info - Podrobnosti + + Info + Podrobnosti - - Value - Vrednost + + Value + Vrednost - - \ No newline at end of file + + + Warning + Warning + + + diff --git a/lisp/i18n/ts/sl_SI/midi.ts b/lisp/i18n/ts/sl_SI/midi.ts index caf61d2ec..c58884133 100644 --- a/lisp/i18n/ts/sl_SI/midi.ts +++ b/lisp/i18n/ts/sl_SI/midi.ts @@ -1,28 +1,30 @@ - - + + + + MIDISettings - - MIDI default devices - Privzete MIDI naprave + + MIDI default devices + Privzete MIDI naprave - - Input - Vhod + + Input + Vhod - - Output - Izhod + + Output + Izhod - - + + SettingsPageName - - MIDI settings - MIDI nastavitve + + MIDI settings + MIDI nastavitve - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/sl_SI/network.ts b/lisp/i18n/ts/sl_SI/network.ts new file mode 100644 index 000000000..654f28be8 --- /dev/null +++ b/lisp/i18n/ts/sl_SI/network.ts @@ -0,0 +1,47 @@ + + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + diff --git a/lisp/i18n/ts/sl_SI/osc.ts b/lisp/i18n/ts/sl_SI/osc.ts new file mode 100644 index 000000000..cd68690ee --- /dev/null +++ b/lisp/i18n/ts/sl_SI/osc.ts @@ -0,0 +1,35 @@ + + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + SettingsPageName + + + OSC settings + OSC settings + + + diff --git a/lisp/i18n/ts/sl_SI/presets.ts b/lisp/i18n/ts/sl_SI/presets.ts index 7d3f3d715..d7c6827ba 100644 --- a/lisp/i18n/ts/sl_SI/presets.ts +++ b/lisp/i18n/ts/sl_SI/presets.ts @@ -1,138 +1,150 @@ - - + + + + Preset - - Create Cue - Ustvari vrsto + + Create Cue + Ustvari vrsto - - Load on selected Cues - Naloži izbrane vrste + + Load on selected Cues + Naloži izbrane vrste - - + + Presets - - Presets - Pred-nastavitve + + Presets + Pred-nastavitve - - Load preset - Naloži pred-nastavitve + + Load preset + Naloži pred-nastavitve - - Save as preset - Shrani kot pred-nastavitev + + Save as preset + Shrani kot pred-nastavitev - - Cannot scan presets - Ne morem prebrat pred-nastavitve + + Cannot scan presets + Ne morem prebrat pred-nastavitve - - Error while deleting preset "{}" - Napaka med brisanjem pred-nastavitve "{}" + + Error while deleting preset "{}" + Napaka med brisanjem pred-nastavitve "{}" - - Cannot load preset "{}" - Ne morem naložit pred-nastavitve "{}" + + Cannot load preset "{}" + Ne morem naložit pred-nastavitve "{}" - - Cannot save preset "{}" - Ne morem shranit pred-nastavitev "{}" + + Cannot save preset "{}" + Ne morem shranit pred-nastavitev "{}" - - Cannot rename preset "{}" - Ne morem preimenovat pred-nastavitve "{}" + + Cannot rename preset "{}" + Ne morem preimenovat pred-nastavitve "{}" - - Select Preset - Izberi pred-nastavitve + + Select Preset + Izberi pred-nastavitve - - Preset already exists, overwrite? - Pred-nastavitev že obstaja, prepišem? + + Preset already exists, overwrite? + Pred-nastavitev že obstaja, prepišem? - - Preset name - Ime pred-nastavitve + + Preset name + Ime pred-nastavitve - - Add - Dodaj + + Add + Dodaj - - Rename - Preimenuj + + Rename + Preimenuj - - Edit - Uredi + + Edit + Uredi - - Remove - Odstrani + + Remove + Odstrani - - Export selected - Izvozi izbrano + + Export selected + Izvozi izbrano - - Import - Uvozi + + Import + Uvozi - - Warning - Opozorilo + + Warning + Opozorilo - - The same name is already used! - Ime je že v uporabi! + + The same name is already used! + Ime je že v uporabi! - - Cannot create a cue from this preset: {} - Ne morem ustvarit vrste iz te pred-nastavitve: "{}" + + Cannot create a cue from this preset: {} + Ne morem ustvarit vrste iz te pred-nastavitve: "{}" - - Cannot export correctly. - Ne morem pravilno izvozit. + + Cannot export correctly. + Ne morem pravilno izvozit. - - Some presets already exists, overwrite? - Nekatere pred-nastavitve že obstajajo, prepišem? + + Some presets already exists, overwrite? + Nekatere pred-nastavitve že obstajajo, prepišem? - - Cannot import correctly. - Ne morem pravilno uvozit. + + Cannot import correctly. + Ne morem pravilno uvozit. - - Cue type - Tip vrste + + Cue type + Tip vrste - - \ No newline at end of file + + + Load on cue + Load on cue + + + + Load on selected cues + Load on selected cues + + + diff --git a/lisp/i18n/ts/sl_SI/rename_cues.ts b/lisp/i18n/ts/sl_SI/rename_cues.ts new file mode 100644 index 000000000..95a17826a --- /dev/null +++ b/lisp/i18n/ts/sl_SI/rename_cues.ts @@ -0,0 +1,95 @@ + + + + + RenameCues + + + Rename Cues + Rename Cues + + + + Rename cues + Rename cues + + + + Current + Current + + + + Preview + Preview + + + + Capitalize + Capitalize + + + + Lowercase + Lowercase + + + + Uppercase + Uppercase + + + + Remove Numbers + Remove Numbers + + + + Add numbering + Add numbering + + + + Reset + Reset + + + + Rename all cue. () in regex below usable with $0, $1 ... + Rename all cue. () in regex below usable with $0, $1 ... + + + + Type your regex here: + Type your regex here: + + + + Regex help + Regex help + + + + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple : +^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple : +^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation + + + diff --git a/lisp/i18n/ts/sl_SI/replay_gain.ts b/lisp/i18n/ts/sl_SI/replay_gain.ts index e612fb476..ba7b8201d 100644 --- a/lisp/i18n/ts/sl_SI/replay_gain.ts +++ b/lisp/i18n/ts/sl_SI/replay_gain.ts @@ -1,50 +1,52 @@ - - + + + + ReplayGain - - ReplayGain / Normalization - Ojačanje predvajanja / Normalizacija + + ReplayGain / Normalization + Ojačanje predvajanja / Normalizacija - - Calculate - Izračunaj + + Calculate + Izračunaj - - Reset all - Ponastavi vse + + Reset all + Ponastavi vse - - Reset selected - Ponastavi izbrano + + Reset selected + Ponastavi izbrano - - Threads number - Število niti + + Threads number + Število niti - - Apply only to selected media - Uveljavi samo na izbranih medijih + + Apply only to selected media + Uveljavi samo na izbranih medijih - - ReplayGain to (dB SPL) - Ojačaj predvajanje na (dB SPL) + + ReplayGain to (dB SPL) + Ojačaj predvajanje na (dB SPL) - - Normalize to (dB) - Normaliziraj na (dB) + + Normalize to (dB) + Normaliziraj na (dB) - - Processing files ... - Procesiram datoteke ... + + Processing files ... + Procesiram datoteke ... - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/sl_SI/synchronizer.ts b/lisp/i18n/ts/sl_SI/synchronizer.ts index 023260542..a44724669 100644 --- a/lisp/i18n/ts/sl_SI/synchronizer.ts +++ b/lisp/i18n/ts/sl_SI/synchronizer.ts @@ -1,83 +1,85 @@ - - + + + + SyncPeerDialog - - Manage connected peers - Upravljaj povezane soležnike + + Manage connected peers + Upravljaj povezane soležnike - - Discover peers - Odkrij soležnike + + Discover peers + Odkrij soležnike - - Manually add a peer - Ročno dodaj soležnik + + Manually add a peer + Ročno dodaj soležnik - - Remove selected peer - Odstrani izbrani soležnik + + Remove selected peer + Odstrani izbrani soležnik - - Remove all peers - Odstrani vse soležnike + + Remove all peers + Odstrani vse soležnike - - Address - Naslov + + Address + Naslov - - Peer IP - IP soležnika + + Peer IP + IP soležnika - - Error - Napaka + + Error + Napaka - - Already connected - Že povezan + + Already connected + Že povezan - - Cannot add peer - Ne morem dodat soležnika + + Cannot add peer + Ne morem dodat soležnika - - + + Synchronizer - - Synchronization - Sinhronizacija + + Synchronization + Sinhronizacija - - Manage connected peers - Upravljaj povezane soležnike + + Manage connected peers + Upravljaj povezane soležnike - - Show your IP - Prikaži tvoj IP + + Show your IP + Prikaži tvoj IP - - Your IP is: - Tvoj IP je: + + Your IP is: + Tvoj IP je: - - Discovering peers ... - Odkrivam soležnike ... + + Discovering peers ... + Odkrivam soležnike ... - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/sl_SI/timecode.ts b/lisp/i18n/ts/sl_SI/timecode.ts index c0005b184..90924f5c4 100644 --- a/lisp/i18n/ts/sl_SI/timecode.ts +++ b/lisp/i18n/ts/sl_SI/timecode.ts @@ -1,81 +1,110 @@ - - + + + + SettingsPageName - - Timecode Settings - Nastavitve časovnega zapisa + + Timecode Settings + Nastavitve časovnega zapisa - - Timecode - Časovni zapis + + Timecode + Časovni zapis - - + + Timecode - - Cannot send timecode. - Ne morem poslat časovnega zapisa + + Cannot send timecode. + Ne morem poslat časovnega zapisa - - OLA has stopped. - OLA se je zaustavil + + OLA has stopped. + OLA se je zaustavil - - + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + Cannot send timecode. +OLA has stopped. + Cannot send timecode. +OLA has stopped. + + + TimecodeSettings - - OLA Timecode Settings - Nastavitve OLA časovnega zapisa + + OLA Timecode Settings + Nastavitve OLA časovnega zapisa + + + + Enable Plugin + Omogoči vtičnik + + + + High-Resolution Timecode + Zelo podobni časovni zapis + + + + Timecode Format: + Format časovnega zapisa: - - Enable Plugin - Omogoči vtičnik + + OLA status + OLA stanje - - High-Resolution Timecode - Zelo podobni časovni zapis + + OLA is not running - start the OLA daemon. + OLA ni zagnan - zaženite OLA daemon. - - Timecode Format: - Format časovnega zapisa: + + Replace HOURS by a static track number + Zamenjaj URE s statično številko sledi - - OLA status - OLA stanje + + Enable ArtNet Timecode + Omogoči ArtNet časovni zapis - - OLA is not running - start the OLA daemon. - OLA ni zagnan - zaženite OLA daemon. + + Track number + Številka sledi - - Replace HOURS by a static track number - Zamenjaj URE s statično številko sledi + + To send ArtNet Timecode you need to setup a running OLA session! + Za pošiljanje ArtNet časovnega zapisa, rabite nastavit aktivno OLA sejo! - - Enable ArtNet Timecode - Omogoči ArtNet časovni zapis + + Enable Timecode + Enable Timecode - - Track number - Številka sledi + + Timecode Settings + Timecode Settings - - To send ArtNet Timecode you need to setup a running OLA session! - Za pošiljanje ArtNet časovnega zapisa, rabite nastavit aktivno OLA sejo! + + Timecode Protocol: + Timecode Protocol: - - \ No newline at end of file + + diff --git a/lisp/i18n/ts/sl_SI/triggers.ts b/lisp/i18n/ts/sl_SI/triggers.ts index 532e72b53..3a119e3f3 100644 --- a/lisp/i18n/ts/sl_SI/triggers.ts +++ b/lisp/i18n/ts/sl_SI/triggers.ts @@ -1,61 +1,63 @@ - - + + + + CueTriggers - - Started - Zagnan + + Started + Zagnan - - Paused - V premoru + + Paused + V premoru - - Stopped - Ustavljen + + Stopped + Ustavljen - - Ended - Končan + + Ended + Končan - - + + SettingsPageName - - Triggers - Prožilci + + Triggers + Prožilci - - + + TriggersSettings - - Add - Dodaj + + Add + Dodaj - - Remove - Odstrani + + Remove + Odstrani - - Trigger - Prožilec + + Trigger + Prožilec - - Cue - Vrsta + + Cue + Vrsta - - Action - Akcija + + Action + Akcija - - \ No newline at end of file + + From 231dffa03805cd40ef3117e19d6ad719f0907974 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 5 Jan 2019 12:11:28 +0100 Subject: [PATCH 152/333] update source translation files --- .tx/config | 63 --- i18n_update.py | 4 +- lisp/i18n/ts/en/action_cues.ts | 188 ++++---- lisp/i18n/ts/en/cart_layout.ts | 99 ++-- lisp/i18n/ts/en/controller.ts | 129 ++++-- lisp/i18n/ts/en/gst_backend.ts | 154 ++++--- lisp/i18n/ts/en/lisp.ts | 791 +++++++++----------------------- lisp/i18n/ts/en/list_layout.ts | 128 ++++-- lisp/i18n/ts/en/media_info.ts | 15 +- lisp/i18n/ts/en/midi.ts | 132 +++++- lisp/i18n/ts/en/network.ts | 40 +- lisp/i18n/ts/en/osc.ts | 39 +- lisp/i18n/ts/en/presets.ts | 68 +-- lisp/i18n/ts/en/rename_cues.ts | 45 +- lisp/i18n/ts/en/replay_gain.ts | 49 +- lisp/i18n/ts/en/synchronizer.ts | 64 +-- lisp/i18n/ts/en/timecode.ts | 73 +-- lisp/i18n/ts/en/triggers.ts | 20 +- 18 files changed, 928 insertions(+), 1173 deletions(-) delete mode 100644 .tx/config diff --git a/.tx/config b/.tx/config deleted file mode 100644 index f04beecbc..000000000 --- a/.tx/config +++ /dev/null @@ -1,63 +0,0 @@ -[main] -host = https://www.transifex.com - -[linux-show-player.lisp-application] -file_filter = lisp/i18n/lisp_.ts -source_lang = en -type = QT - -[linux-show-player.lisp-module-action-cues] -file_filter = lisp/modules/action_cues/i18n/action_cues_.ts -source_lang = en -type = QT - -[linux-show-player.lisp-module-controller] -file_filter = lisp/plugins/controller/i18n/controller_.ts -source_lang = en -type = QT - -[linux-show-player.lisp-module-gstreamer-backend] -file_filter = lisp/modules/gst_backend/i18n/gst_backend_.ts -source_lang = en -type = QT - -[linux-show-player.lisp-module-media-info] -file_filter = lisp/modules/media_info/i18n/media_info_.ts -source_lang = en -type = QT - -[linux-show-player.lisp-module-midi] -file_filter = lisp/modules/midi/i18n/midi_.ts -source_lang = en -type = QT - -[linux-show-player.lisp-module-presets] -file_filter = lisp/modules/presets/i18n/presets_.ts -source_lang = en -type = QT - -[linux-show-player.lisp-module-replaygain] -file_filter = lisp/modules/replay_gain/i18n/replay_gain_.ts -source_lang = en -type = QT - -[linux-show-player.lisp-module-synchronizer] -file_filter = lisp/plugins/synchronizer/i18n/synchronizer_.ts -source_lang = en -type = QT - -[linux-show-player.lisp-module-triggers] -file_filter = lisp/plugins/triggers/i18n/triggers_.ts -source_lang = en -type = QT - -[linux-show-player.lisp-module-timecode] -file_filter = lisp/plugins/timecode/i18n/timecode_.ts -source_lang = en -type = QT - -[linux-show-player.lisp-module-uri-changer] -file_filter = lisp/modules/uri_changer/i18n/uri_changer_.ts -source_lang = en -type = QT - diff --git a/i18n_update.py b/i18n_update.py index fc501affc..4ee439345 100755 --- a/i18n_update.py +++ b/i18n_update.py @@ -2,7 +2,7 @@ # # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2019 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -56,6 +56,8 @@ def existing_locales(): # Locales of which generate translations files LOCALES = args.locales if not LOCALES: + LOCALES = ["en"] +elif LOCALES == "*": LOCALES = list(existing_locales()) print(">>> UPDATE EXISTING:", ", ".join(LOCALES)) diff --git a/lisp/i18n/ts/en/action_cues.ts b/lisp/i18n/ts/en/action_cues.ts index 1ec356d88..0fbd13d00 100644 --- a/lisp/i18n/ts/en/action_cues.ts +++ b/lisp/i18n/ts/en/action_cues.ts @@ -1,24 +1,40 @@ + + ActionCuesDebug + + + Registered cue: "{}" + + + + + ActionsCuesError + + + Cannot create cue {} + + + CollectionCue - + Add Add - + Remove Remove - + Cue Cue - + Action Action @@ -26,50 +42,35 @@ CommandCue - - Process ended with an error status. - Process ended with an error status. - - - - Exit code: - Exit code: - - - + Command Command - + Command to execute, as in a shell Command to execute, as in a shell - + Discard command output Discard command output - + Ignore command errors Ignore command errors - + Kill instead of terminate Kill instead of terminate - - - Command cue ended with an error status. Exit code: {} - - Cue Name - + OSC Settings @@ -77,42 +78,42 @@ CueName - + Command Cue Command Cue - + MIDI Cue MIDI Cue - + Volume Control Volume Control - + Seek Cue Seek Cue - + Collection Cue Collection Cue - + Stop-All Stop-All - + Index Action Index Action - + OSC Cue @@ -120,68 +121,55 @@ IndexActionCue - + Index Index - + Use a relative index Use a relative index - + Target index Target index - + Action Action - + No suggestion - + Suggested cue name - - MIDICue - - - MIDI Message - MIDI Message - - - - Message type - Message type - - Osc Cue - + Type - + Argument - + FadeTo - + Fade Fade @@ -189,75 +177,88 @@ OscCue - - Error during cue execution. - - - - + OSC Message - + Add Add - + Remove Remove - + Test - + OSC Path: (example: "/path/to/something") - + Fade Fade - + Time (sec) - + Curve + + OscCueError + + + Could not parse argument list, nothing sent + + + + + Error while parsing arguments, nothing sent + + + + + Error during cue execution. + + + SeekCue - + Cue Cue - + Click to select Click to select - + Not selected Not selected - + Seek Seek - + Time to reach Time to reach @@ -265,37 +266,37 @@ SettingsPageName - + Command Command - + MIDI Settings MIDI Settings - + Volume Settings Volume Settings - + Seek Settings Seek Settings - + Edit Collection Edit Collection - + Action Settings Action Settings - + Stop Settings Stop Settings @@ -303,7 +304,7 @@ StopAll - + Stop Action Stop Action @@ -311,34 +312,37 @@ VolumeControl - - Error during cue execution - Error during cue execution - - - + Cue Cue - + Click to select Click to select - + Not selected Not selected - + Volume to reach Volume to reach - + Fade Fade + + VolumeControlError + + + Error during cue execution. + + + diff --git a/lisp/i18n/ts/en/cart_layout.ts b/lisp/i18n/ts/en/cart_layout.ts index ea0c937ac..c90ce02c0 100644 --- a/lisp/i18n/ts/en/cart_layout.ts +++ b/lisp/i18n/ts/en/cart_layout.ts @@ -3,120 +3,115 @@ CartLayout - + Default behaviors - + Countdown mode - + Show seek-bars - + Show dB-meters - + Show accurate time - + Show volume - - Automatically add new page - - - - + Grid size - - Number of columns - - - - - Number of rows - - - - + Play - + Pause - + Stop - + Reset volume - + Add page - + Add pages - + Remove current page - + Number of Pages: - + Page {number} - + Warning - + Every cue in the page will be lost. - + Are you sure to continue? + + + Number of columns: + + + + + Number of rows: + + LayoutDescription - + Organize cues in grid like pages @@ -124,52 +119,68 @@ LayoutDetails - + Click a cue to run it - + SHIFT + Click to edit a cue - + CTRL + Click to select a cue - + To copy cues drag them while pressing CTRL - + To move cues drag them while pressing SHIFT + + LayoutName + + + Cart Layout + + + ListLayout - + Edit cue - + Edit selected cues - + Remove cue - + Remove selected cues + + SettingsPageName + + + Cart Layout + + + diff --git a/lisp/i18n/ts/en/controller.ts b/lisp/i18n/ts/en/controller.ts index a5a4ff5cf..787a54f70 100644 --- a/lisp/i18n/ts/en/controller.ts +++ b/lisp/i18n/ts/en/controller.ts @@ -3,7 +3,7 @@ Controller - + Cannot load controller protocol: "{}" @@ -11,17 +11,17 @@ ControllerKeySettings - + Key Key - + Action Action - + Shortcuts Shortcuts @@ -29,47 +29,47 @@ ControllerMidiSettings - + MIDI MIDI - + Type Type - + Channel Channel - + Note Note - + Action Action - + Filter "note on" Filter "note on" - + Filter "note off" Filter "note off" - + Capture Capture - + Listening MIDI messages ... Listening MIDI messages ... @@ -77,57 +77,52 @@ ControllerOscSettings - + OSC Message - - OSC Path: (example: "/path/to/something") - - - - + OSC - + Path - + Types - + Arguments - + Actions - + OSC Capture - + Add Add - + Remove Remove - + Capture Capture @@ -135,25 +130,78 @@ ControllerSettings - + Add Add - + Remove Remove + + GlobalAction + + + Go + + + + + Reset + + + + + Stop all cues + + + + + Pause all cues + + + + + Resume all cues + + + + + Interrupt all cues + + + + + Fade-out all cues + + + + + Fade-in all cues + + + + + Move standby forward + + + + + Move standby back + + + Osc Cue - + Type Type - + Argument @@ -161,12 +209,12 @@ OscCue - + Add Add - + Remove Remove @@ -174,24 +222,29 @@ SettingsPageName - + Cue Control Cue Control - + MIDI Controls MIDI Controls - + Keyboard Shortcuts Keyboard Shortcuts - + OSC Controls + + + Layout Controls + + diff --git a/lisp/i18n/ts/en/gst_backend.ts b/lisp/i18n/ts/en/gst_backend.ts index afcdc40f3..bf8b41740 100644 --- a/lisp/i18n/ts/en/gst_backend.ts +++ b/lisp/i18n/ts/en/gst_backend.ts @@ -3,60 +3,55 @@ AlsaSinkSettings - + ALSA device ALSA device - - - ALSA devices, as defined in an asound configuration file - ALSA devices, as defined in an asound configuration file - AudioDynamicSettings - + Compressor Compressor - + Expander Expander - + Soft Knee Soft Knee - + Hard Knee Hard Knee - + Compressor/Expander Compressor/Expander - + Type Type - + Curve Shape Curve Shape - + Ratio Ratio - + Threshold (dB) Threshold (dB) @@ -64,22 +59,22 @@ AudioPanSettings - + Audio Pan Audio Pan - + Center Center - + Left Left - + Right Right @@ -87,22 +82,22 @@ DbMeterSettings - + DbMeter settings DbMeter settings - + Time between levels (ms) Time between levels (ms) - + Peak ttl (ms) Peak ttl (ms) - + Peak falloff (dB/sec) Peak falloff (dB/sec) @@ -110,7 +105,7 @@ Equalizer10Settings - + 10 Bands Equalizer (IIR) 10 Bands Equalizer (IIR) @@ -118,28 +113,44 @@ GstBackend - + Audio cue (from file) - + Select media files + + GstMediaError + + + Cannot create pipeline element: "{}" + + + GstMediaSettings - + Change Pipeline Change Pipeline + + GstMediaWarning + + + Invalid pipeline element: "{}" + + + GstPipelineEdit - + Edit Pipeline Edit Pipeline @@ -147,7 +158,7 @@ GstSettings - + Pipeline Pipeline @@ -155,32 +166,32 @@ JackSinkSettings - + Connections Connections - + Edit connections Edit connections - + Output ports Output ports - + Input ports Input ports - + Connect Connect - + Disconnect Disconnect @@ -188,77 +199,77 @@ MediaElementName - + Compressor/Expander Compressor/Expander - + Audio Pan Audio Pan - + PulseAudio Out PulseAudio Out - + Volume Volume - + dB Meter dB Meter - + System Input System Input - + ALSA Out ALSA Out - + JACK Out JACK Out - + Custom Element Custom Element - + System Out System Out - + Pitch Pitch - + URI Input URI Input - + 10 Bands Equalizer 10 Bands Equalizer - + Speed Speed - + Preset Input Preset Input @@ -266,12 +277,12 @@ PitchSettings - + Pitch Pitch - + {0:+} semitones {0:+} semitones @@ -279,7 +290,7 @@ PresetSrcSettings - + Presets Presets @@ -287,17 +298,12 @@ SettingsPageName - - GStreamer settings - GStreamer settings - - - + Media Settings Media Settings - + GStreamer @@ -305,7 +311,7 @@ SpeedSettings - + Speed Speed @@ -313,42 +319,42 @@ UriInputSettings - + Source Source - + Find File Find File - + Buffering Buffering - + Use Buffering Use Buffering - + Attempt download on network streams Attempt download on network streams - + Buffer size (-1 default value) Buffer size (-1 default value) - + Choose file Choose file - + All files All files @@ -356,12 +362,12 @@ UserElementSettings - + User defined elements User defined elements - + Only for advanced user! Only for advanced user! @@ -369,17 +375,17 @@ VolumeSettings - + Volume Volume - + Normalized volume Normalized volume - + Reset Reset diff --git a/lisp/i18n/ts/en/lisp.ts b/lisp/i18n/ts/en/lisp.ts index 68d66ead0..1613e4b23 100644 --- a/lisp/i18n/ts/en/lisp.ts +++ b/lisp/i18n/ts/en/lisp.ts @@ -3,22 +3,22 @@ About - + Authors Authors - + Contributors Contributors - + Translators Translators - + About Linux Show Player About Linux Show Player @@ -26,42 +26,32 @@ AboutDialog - - Linux Show Player is a cue-player designed for stage productions. - Linux Show Player is a cue-player designed for stage productions. - - - + Web site Web site - - Users group - Users group - - - + Source code Source code - + Info Info - + License License - + Contributors Contributors - + Discussion @@ -69,12 +59,12 @@ Actions - + Undo: {} - + Redo: {} @@ -82,7 +72,7 @@ AppConfiguration - + LiSP preferences LiSP preferences @@ -90,243 +80,109 @@ AppGeneralSettings - - Startup layout - Startup layout - - - - Use startup dialog - Use startup dialog - - - - Application theme - Application theme - - - + Default layout - + Enable startup layout selector - + Application themes - - UI theme + + UI theme: - - Icons theme + + Icons theme: - CartLayout - - - Add page - Add page - - - - Add pages - Add pages - - - - Remove current page - Remove current page - - - - Countdown mode - Countdown mode - - - - Show seek-bars - Show seek-bars - - - - Show dB-meters - Show dB-meters - + ApplicationError - - Show volume - Show volume - - - - Show accurate time - Show accurate time - - - - Edit cue - Edit cue - - - - Remove - Remove - - - - Select - Select - - - - Play - Play - - - - Pause - Pause - - - - Stop - Stop - - - - Reset volume - Reset volume - - - - Number of Pages: - Number of Pages: - - - - Warning - Warning - - - - Every cue in the page will be lost. - Every cue in the page will be lost. - - - - Are you sure to continue? - Are you sure to continue? - - - - Page - Page - - - - Default behaviors - Default behaviors - - - - Automatically add new page - Automatically add new page - - - - Grid size - Grid size + + Startup error + + + + ConfigurationDebug - - Number of columns - Number of columns + + Configuration written at {} + + + + ConfigurationInfo - - Number of rows - Number of rows + + New configuration installed at {} + CueAction - + Default Default - + Pause Pause - + Start Start - + Stop Stop - - FadeInStart - FadeInStart - - - - FadeOutStop - FadeOutStop - - - - FadeOutPause - FadeOutPause - - - + Faded Start - + Faded Resume - + Faded Pause - + Faded Stop - + Faded Interrupt - + Resume - + Do Nothing @@ -334,12 +190,12 @@ CueActionLog - + Cue settings changed: "{}" Cue settings changed: "{}" - + Cues settings changed. Cues settings changed. @@ -347,42 +203,42 @@ CueAppearanceSettings - + The appearance depends on the layout The appearance depends on the layout - + Cue name Cue name - + NoName NoName - + Description/Note Description/Note - + Set Font Size Set Font Size - + Color Color - + Select background color Select background color - + Select font color Select font color @@ -390,7 +246,7 @@ CueName - + Media Cue Media Cue @@ -398,100 +254,85 @@ CueNextAction - + Do Nothing + + + Trigger after the end + + - Auto Follow + Trigger after post wait + + + + + Select after the end - - Auto Next + + Select after post wait CueSettings - + Pre wait Pre wait - + Wait before cue execution Wait before cue execution - + Post wait Post wait - + Wait after cue execution Wait after cue execution - + Next action Next action - - Interrupt Fade - Interrupt Fade - - - - Fade Action - Fade Action - - - - Behaviours - Behaviours - - - - Pre/Post Wait - Pre/Post Wait - - - - Fade In/Out - Fade In/Out - - - + Start action Start action - + Default action to start the cue Default action to start the cue - + Stop action Stop action - + Default action to stop the cue Default action to stop the cue - + Interrupt fade - + Fade actions @@ -499,17 +340,17 @@ Fade - + Linear Linear - + Quadratic Quadratic - + Quadratic2 Quadratic2 @@ -517,99 +358,43 @@ FadeEdit - - Duration (sec) - Duration (sec) + + Duration (sec): + - - Curve - Curve + + Curve: + FadeSettings - + Fade In Fade In - + Fade Out Fade Out - - LayoutDescription - - - Organize cues in grid like pages - Organize cues in grid like pages - - - - Organize the cues in a list - Organize the cues in a list - - - - LayoutDetails - - - Click a cue to run it - Click a cue to run it - - - - SHIFT + Click to edit a cue - SHIFT + Click to edit a cue - - - - CTRL + Click to select a cue - CTRL + Click to select a cue - - - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Space or Double-Click to edit a cue - - - - To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL - - - - To copy cues drag them while pressing SHIFT - To copy cues drag them while pressing SHIFT - - - - CTRL + Left Click to select cues - CTRL + Left Click to select cues - - - - To move cues drag them - To move cues drag them - - LayoutSelect - + Layout selection Layout selection - + Select layout Select layout - + Open file Open file @@ -617,518 +402,363 @@ ListLayout - - Show playing cues - Show playing cues - - - - Show dB-meters - Show dB-meters - - - - Show seek-bars - Show seek-bars - - - - Show accurate time - Show accurate time - - - - Auto-select next cue - Auto-select next cue - - - - Edit cue - Edit cue - - - - Remove - Remove - - - - Select - Select - - - - Stop all - Stop all - - - - Pause all - Pause all - - - - Restart all - Restart all - - - - Stop - Stop - - - - Restart - Restart - - - - Default behaviors - Default behaviors - - - - At list end: - At list end: - - - - Go key: - Go key: - - - - Interrupt all - Interrupt all - - - - Fade-Out all - Fade-Out all - - - - Fade-In all - Fade-In all - - - - Use fade - Use fade - - - - Stop Cue - Stop Cue - - - - Pause Cue - Pause Cue - - - - Restart Cue - Restart Cue - - - - Interrupt Cue - Interrupt Cue - - - + Stop All Stop All - + Pause All Pause All - - Restart All - Restart All - - - + Interrupt All Interrupt All - + Use fade (global actions) - + Resume All - - ListLayoutHeader - - - Cue - Cue - - - - Pre wait - Pre wait - - - - Action - Action - - - - Post wait - Post wait - - - - ListLayoutInfoPanel - - - Cue name - Cue name - - - - Cue description - Cue description - - Logging - - Information - Information - - - + Debug Debug - + Warning Warning - + Error Error - - Details: - Details: - - - + Dismiss all - + Show details - + Linux Show Player - Log Viewer - + Info Info - + Critical - + Time - + Milliseconds - + Logger name - + Level - + Message - + Function - + Path name - + File name - + Line no. - + Module - + Process ID - + Process name - + Thread ID - + Thread name + + + Exception info + + MainWindow - + &File &File - + New session New session - + Open Open - + Save session Save session - + Preferences Preferences - + Save as Save as - + Full Screen Full Screen - + Exit Exit - + &Edit &Edit - + Undo Undo - + Redo Redo - + Select all Select all - + Select all media cues Select all media cues - + Deselect all Deselect all - + CTRL+SHIFT+A CTRL+SHIFT+A - + Invert selection Invert selection - + CTRL+I CTRL+I - + Edit selected Edit selected - + CTRL+SHIFT+E CTRL+SHIFT+E - + &Layout &Layout - + &Tools &Tools - + Edit selection Edit selection - + &About &About - + About About - + About Qt About Qt - - Undone: - Undone: - - - - Redone: - Redone: - - - + Close session Close session - + The current session is not saved. The current session is not saved. - + Discard the changes? Discard the changes? - - MediaCueMenus - - - Audio cue (from file) - Audio cue (from file) - - - - Select media files - Select media files - - MediaCueSettings - + Start time Start time - + Stop position of the media Stop position of the media - + Stop time Stop time - + Start position of the media Start position of the media - + Loop Loop + + + PluginsError - - Repetition after first play (-1 = infinite) - Repetition after first play (-1 = infinite) + + Failed to load "{}" + + + + + Failed to terminate plugin: "{}" + + + + + the requested plugin is not loaded: {} + + + + + PluginsInfo + + + Plugin loaded: "{}" + + + + + Plugin terminated: "{}" + + + + + PluginsWarning + + + Cannot satisfy dependencies for: {} + QColorButton - + Right click to reset Right click to reset @@ -1136,44 +766,49 @@ SettingsPageName - + Appearance Appearance - + General General - + Cue Cue - + Cue Settings Cue Settings - + Plugins - + Behaviours Behaviours - + Pre/Post Wait Pre/Post Wait - + Fade In/Out Fade In/Out + + + Layouts + + diff --git a/lisp/i18n/ts/en/list_layout.ts b/lisp/i18n/ts/en/list_layout.ts index 6cd3c2f17..a5487a89e 100644 --- a/lisp/i18n/ts/en/list_layout.ts +++ b/lisp/i18n/ts/en/list_layout.ts @@ -3,7 +3,7 @@ LayoutDescription - + Organize the cues in a list @@ -11,163 +11,191 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue - + To copy cues drag them while pressing CTRL - + To move cues drag them + + LayoutName + + + List Layout + + + ListLayout - + Default behaviors - + Show playing cues - + Show dB-meters - + Show accurate time - + Show seek-bars - + Auto-select next cue - + Enable selection mode - - Go key: - - - - + Use fade (buttons) - + Stop Cue - + Pause Cue - + Resume Cue - + Interrupt Cue - + Edit cue - - Edit selected cues - - - - + Remove cue - - Remove selected cues - - - - + Selection mode - + Pause all - + Stop all - + Interrupt all - + Resume all - + Fade-Out all - + Fade-In all + + + GO key: + + + + + GO action: + + + + + GO delay (ms): + + + + + Edit selected + + + + + Clone cue + + + + + Clone selected + + + + + Remove selected + + ListLayoutHeader - + Cue - + Pre wait - + Action - + Post wait @@ -175,14 +203,22 @@ ListLayoutInfoPanel - + Cue name - + Cue description + + SettingsPageName + + + List Layout + + + diff --git a/lisp/i18n/ts/en/media_info.ts b/lisp/i18n/ts/en/media_info.ts index 8d5287183..1d514868b 100644 --- a/lisp/i18n/ts/en/media_info.ts +++ b/lisp/i18n/ts/en/media_info.ts @@ -3,32 +3,27 @@ MediaInfo - + Media Info Media Info - - Error - Error - - - + No info to display No info to display - + Info Info - + Value Value - + Warning diff --git a/lisp/i18n/ts/en/midi.ts b/lisp/i18n/ts/en/midi.ts index 4e2aa34f6..aeb6c14fb 100644 --- a/lisp/i18n/ts/en/midi.ts +++ b/lisp/i18n/ts/en/midi.ts @@ -1,19 +1,143 @@ + + MIDICue + + + MIDI Message + + + + + Message type + + + + + MIDIMessageAttr + + + Channel + + + + + Note + + + + + Velocity + + + + + Control + + + + + Program + + + + + Value + + + + + Song + + + + + Pitch + + + + + Position + + + + + MIDIMessageType + + + Note ON + + + + + Note OFF + + + + + Polyphonic After-touch + + + + + Control/Mode Change + + + + + Program Change + + + + + Channel After-touch + + + + + Pitch Bend Change + + + + + Song Select + + + + + Song Position + + + + + Start + + + + + Stop + + + + + Continue + + + MIDISettings - + MIDI default devices MIDI default devices - + Input Input - + Output Output @@ -21,7 +145,7 @@ SettingsPageName - + MIDI settings MIDI settings diff --git a/lisp/i18n/ts/en/network.ts b/lisp/i18n/ts/en/network.ts index bd35353ec..f2829baea 100644 --- a/lisp/i18n/ts/en/network.ts +++ b/lisp/i18n/ts/en/network.ts @@ -1,44 +1,68 @@ + + APIServerInfo + + + Stop serving network API + + + + + ApiServerError + + + Network API server stopped working. + + + + + NetworkApiDebug + + + New end-point: {} + + + NetworkDiscovery - + Host discovery - + Manage hosts - + Discover hosts - + Manually add a host - + Remove selected host - + Remove all host - + Address - + Host IP diff --git a/lisp/i18n/ts/en/osc.ts b/lisp/i18n/ts/en/osc.ts index d3b902b5c..23265b66d 100644 --- a/lisp/i18n/ts/en/osc.ts +++ b/lisp/i18n/ts/en/osc.ts @@ -1,24 +1,53 @@ + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + + + + + OscServerError + + + Cannot start OSC sever + + + + + OscServerInfo + + + OSC server started at {} + + + + + OSC server stopped + + + OscSettings - + OSC Settings - + Input Port: - + Output Port: - + Hostname: @@ -26,7 +55,7 @@ SettingsPageName - + OSC settings diff --git a/lisp/i18n/ts/en/presets.ts b/lisp/i18n/ts/en/presets.ts index 59e613472..348ead47e 100644 --- a/lisp/i18n/ts/en/presets.ts +++ b/lisp/i18n/ts/en/presets.ts @@ -3,12 +3,12 @@ Preset - + Create Cue Create Cue - + Load on selected Cues Load on selected Cues @@ -16,132 +16,112 @@ Presets - + Presets Presets - - Load preset - Load preset - - - + Save as preset Save as preset - + Cannot scan presets Cannot scan presets - + Error while deleting preset "{}" Error while deleting preset "{}" - + Cannot load preset "{}" Cannot load preset "{}" - + Cannot save preset "{}" Cannot save preset "{}" - + Cannot rename preset "{}" Cannot rename preset "{}" - + Select Preset Select Preset - - Preset already exists, overwrite? - Preset already exists, overwrite? - - - + Preset name Preset name - + Add Add - + Rename Rename - + Edit Edit - + Remove Remove - + Export selected Export selected - + Import Import - + Warning Warning - + The same name is already used! The same name is already used! - - Cannot create a cue from this preset: {} - Cannot create a cue from this preset: {} - - - + Cannot export correctly. Cannot export correctly. - - Some presets already exists, overwrite? - Some presets already exists, overwrite? - - - + Cannot import correctly. Cannot import correctly. - + Cue type Cue type - + Load on cue - + Load on selected cues diff --git a/lisp/i18n/ts/en/rename_cues.ts b/lisp/i18n/ts/en/rename_cues.ts index 15fa7321c..3c386eebb 100644 --- a/lisp/i18n/ts/en/rename_cues.ts +++ b/lisp/i18n/ts/en/rename_cues.ts @@ -3,82 +3,71 @@ RenameCues - + Rename Cues - + Rename cues - + Current - + Preview - + Capitalize - + Lowercase - + Uppercase - + Remove Numbers - + Add numbering - + Reset - - Rename all cue. () in regex below usable with $0, $1 ... - - - - + Type your regex here: - + Regex help + + + RenameUiDebug - - You can use Regexes to rename your cues. - -Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... -In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. - -Exemple : -^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. -Only the numbers are between parenthesis and will be usable with $0 in the first line. - -For more information about Regexes, consult python documentation + + Regex error: Invalid pattern diff --git a/lisp/i18n/ts/en/replay_gain.ts b/lisp/i18n/ts/en/replay_gain.ts index 400a0fe36..70614ea78 100644 --- a/lisp/i18n/ts/en/replay_gain.ts +++ b/lisp/i18n/ts/en/replay_gain.ts @@ -3,49 +3,80 @@ ReplayGain - + ReplayGain / Normalization ReplayGain / Normalization - + Calculate Calculate - + Reset all Reset all - + Reset selected Reset selected - + Threads number Threads number - + Apply only to selected media Apply only to selected media - + ReplayGain to (dB SPL) ReplayGain to (dB SPL) - + Normalize to (dB) Normalize to (dB) - + Processing files ... Processing files ... + + ReplayGainDebug + + + Applied gain for: {} + + + + + Discarded gain for: {} + + + + + ReplayGainInfo + + + Gain processing stopped by user. + + + + + Started gain calculation for: {} + + + + + Gain calculated for: {} + + + diff --git a/lisp/i18n/ts/en/synchronizer.ts b/lisp/i18n/ts/en/synchronizer.ts index 098a08f3e..3bea676b1 100644 --- a/lisp/i18n/ts/en/synchronizer.ts +++ b/lisp/i18n/ts/en/synchronizer.ts @@ -1,72 +1,19 @@ - - SyncPeerDialog - - - Manage connected peers - Manage connected peers - - - - Discover peers - Discover peers - - - - Manually add a peer - Manually add a peer - - - - Remove selected peer - Remove selected peer - - - - Remove all peers - Remove all peers - - - - Address - Address - - - - Peer IP - Peer IP - - - - Error - Error - - - - Already connected - Already connected - - - - Cannot add peer - Cannot add peer - - Synchronizer - + Synchronization Synchronization - + Manage connected peers Manage connected peers - + Show your IP Show your IP @@ -75,10 +22,5 @@ Your IP is: Your IP is: - - - Discovering peers ... - Discovering peers ... - diff --git a/lisp/i18n/ts/en/timecode.ts b/lisp/i18n/ts/en/timecode.ts index d4050d4c2..0f5e66419 100644 --- a/lisp/i18n/ts/en/timecode.ts +++ b/lisp/i18n/ts/en/timecode.ts @@ -3,12 +3,12 @@ SettingsPageName - + Timecode Settings Timecode Settings - + Timecode Timecode @@ -16,91 +16,48 @@ Timecode - - Cannot send timecode. - Cannot send timecode. - - - - OLA has stopped. - OLA has stopped. - - - + Cannot load timecode protocol: "{}" + + + TimecodeError - - Cannot send timecode. -OLA has stopped. - + + Cannot send timecode. + Cannot send timecode. TimecodeSettings - - OLA Timecode Settings - OLA Timecode Settings - - - - Enable Plugin - Enable Plugin - - - - High-Resolution Timecode - High-Resolution Timecode - - - + Timecode Format: Timecode Format: - - OLA status - OLA status - - - - OLA is not running - start the OLA daemon. - OLA is not running - start the OLA daemon. - - - + Replace HOURS by a static track number Replace HOURS by a static track number - - Enable ArtNet Timecode - Enable ArtNet Timecode - - - + Track number Track number - - To send ArtNet Timecode you need to setup a running OLA session! - To send ArtNet Timecode you need to setup a running OLA session! - - - + Enable Timecode - + Timecode Settings Timecode Settings - + Timecode Protocol: diff --git a/lisp/i18n/ts/en/triggers.ts b/lisp/i18n/ts/en/triggers.ts index 5f9ccdca5..65b66c922 100644 --- a/lisp/i18n/ts/en/triggers.ts +++ b/lisp/i18n/ts/en/triggers.ts @@ -3,22 +3,22 @@ CueTriggers - + Started Started - + Paused Paused - + Stopped Stopped - + Ended Ended @@ -26,7 +26,7 @@ SettingsPageName - + Triggers Triggers @@ -34,27 +34,27 @@ TriggersSettings - + Add Add - + Remove Remove - + Trigger Trigger - + Cue Cue - + Action Action From cbbf82458b8817228c8ba204afa2214e970233f5 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 5 Jan 2019 12:47:04 +0100 Subject: [PATCH 153/333] Update target translations (#145) --- lisp/i18n/ts/cs_CZ/action_cues.ts | 188 +++---- lisp/i18n/ts/cs_CZ/cart_layout.ts | 99 ++-- lisp/i18n/ts/cs_CZ/controller.ts | 129 +++-- lisp/i18n/ts/cs_CZ/gst_backend.ts | 154 +++--- lisp/i18n/ts/cs_CZ/lisp.ts | 799 ++++++++--------------------- lisp/i18n/ts/cs_CZ/list_layout.ts | 128 +++-- lisp/i18n/ts/cs_CZ/media_info.ts | 15 +- lisp/i18n/ts/cs_CZ/midi.ts | 132 ++++- lisp/i18n/ts/cs_CZ/network.ts | 40 +- lisp/i18n/ts/cs_CZ/osc.ts | 39 +- lisp/i18n/ts/cs_CZ/presets.ts | 68 +-- lisp/i18n/ts/cs_CZ/rename_cues.ts | 56 +- lisp/i18n/ts/cs_CZ/replay_gain.ts | 49 +- lisp/i18n/ts/cs_CZ/synchronizer.ts | 64 +-- lisp/i18n/ts/cs_CZ/timecode.ts | 74 +-- lisp/i18n/ts/cs_CZ/triggers.ts | 20 +- lisp/i18n/ts/de_DE/action_cues.ts | 188 +++---- lisp/i18n/ts/de_DE/cart_layout.ts | 99 ++-- lisp/i18n/ts/de_DE/controller.ts | 129 +++-- lisp/i18n/ts/de_DE/gst_backend.ts | 154 +++--- lisp/i18n/ts/de_DE/lisp.ts | 799 ++++++++--------------------- lisp/i18n/ts/de_DE/list_layout.ts | 128 +++-- lisp/i18n/ts/de_DE/media_info.ts | 15 +- lisp/i18n/ts/de_DE/midi.ts | 132 ++++- lisp/i18n/ts/de_DE/network.ts | 40 +- lisp/i18n/ts/de_DE/osc.ts | 39 +- lisp/i18n/ts/de_DE/presets.ts | 68 +-- lisp/i18n/ts/de_DE/rename_cues.ts | 56 +- lisp/i18n/ts/de_DE/replay_gain.ts | 49 +- lisp/i18n/ts/de_DE/synchronizer.ts | 64 +-- lisp/i18n/ts/de_DE/timecode.ts | 74 +-- lisp/i18n/ts/de_DE/triggers.ts | 20 +- lisp/i18n/ts/es_ES/action_cues.ts | 188 +++---- lisp/i18n/ts/es_ES/cart_layout.ts | 99 ++-- lisp/i18n/ts/es_ES/controller.ts | 129 +++-- lisp/i18n/ts/es_ES/gst_backend.ts | 154 +++--- lisp/i18n/ts/es_ES/lisp.ts | 799 ++++++++--------------------- lisp/i18n/ts/es_ES/list_layout.ts | 128 +++-- lisp/i18n/ts/es_ES/media_info.ts | 15 +- lisp/i18n/ts/es_ES/midi.ts | 132 ++++- lisp/i18n/ts/es_ES/network.ts | 40 +- lisp/i18n/ts/es_ES/osc.ts | 39 +- lisp/i18n/ts/es_ES/presets.ts | 68 +-- lisp/i18n/ts/es_ES/rename_cues.ts | 56 +- lisp/i18n/ts/es_ES/replay_gain.ts | 49 +- lisp/i18n/ts/es_ES/synchronizer.ts | 64 +-- lisp/i18n/ts/es_ES/timecode.ts | 74 +-- lisp/i18n/ts/es_ES/triggers.ts | 20 +- lisp/i18n/ts/fr_FR/action_cues.ts | 188 +++---- lisp/i18n/ts/fr_FR/cart_layout.ts | 99 ++-- lisp/i18n/ts/fr_FR/controller.ts | 129 +++-- lisp/i18n/ts/fr_FR/gst_backend.ts | 154 +++--- lisp/i18n/ts/fr_FR/lisp.ts | 799 ++++++++--------------------- lisp/i18n/ts/fr_FR/list_layout.ts | 128 +++-- lisp/i18n/ts/fr_FR/media_info.ts | 15 +- lisp/i18n/ts/fr_FR/midi.ts | 132 ++++- lisp/i18n/ts/fr_FR/network.ts | 40 +- lisp/i18n/ts/fr_FR/osc.ts | 39 +- lisp/i18n/ts/fr_FR/presets.ts | 68 +-- lisp/i18n/ts/fr_FR/rename_cues.ts | 56 +- lisp/i18n/ts/fr_FR/replay_gain.ts | 49 +- lisp/i18n/ts/fr_FR/synchronizer.ts | 64 +-- lisp/i18n/ts/fr_FR/timecode.ts | 74 +-- lisp/i18n/ts/fr_FR/triggers.ts | 20 +- lisp/i18n/ts/it_IT/action_cues.ts | 188 +++---- lisp/i18n/ts/it_IT/cart_layout.ts | 99 ++-- lisp/i18n/ts/it_IT/controller.ts | 129 +++-- lisp/i18n/ts/it_IT/gst_backend.ts | 154 +++--- lisp/i18n/ts/it_IT/lisp.ts | 799 ++++++++--------------------- lisp/i18n/ts/it_IT/list_layout.ts | 128 +++-- lisp/i18n/ts/it_IT/media_info.ts | 15 +- lisp/i18n/ts/it_IT/midi.ts | 132 ++++- lisp/i18n/ts/it_IT/network.ts | 40 +- lisp/i18n/ts/it_IT/osc.ts | 39 +- lisp/i18n/ts/it_IT/presets.ts | 68 +-- lisp/i18n/ts/it_IT/rename_cues.ts | 56 +- lisp/i18n/ts/it_IT/replay_gain.ts | 49 +- lisp/i18n/ts/it_IT/synchronizer.ts | 64 +-- lisp/i18n/ts/it_IT/timecode.ts | 74 +-- lisp/i18n/ts/it_IT/triggers.ts | 20 +- lisp/i18n/ts/nl_BE/action_cues.ts | 188 +++---- lisp/i18n/ts/nl_BE/cart_layout.ts | 99 ++-- lisp/i18n/ts/nl_BE/controller.ts | 129 +++-- lisp/i18n/ts/nl_BE/gst_backend.ts | 154 +++--- lisp/i18n/ts/nl_BE/lisp.ts | 799 ++++++++--------------------- lisp/i18n/ts/nl_BE/list_layout.ts | 128 +++-- lisp/i18n/ts/nl_BE/media_info.ts | 15 +- lisp/i18n/ts/nl_BE/midi.ts | 132 ++++- lisp/i18n/ts/nl_BE/network.ts | 40 +- lisp/i18n/ts/nl_BE/osc.ts | 39 +- lisp/i18n/ts/nl_BE/presets.ts | 68 +-- lisp/i18n/ts/nl_BE/rename_cues.ts | 56 +- lisp/i18n/ts/nl_BE/replay_gain.ts | 49 +- lisp/i18n/ts/nl_BE/synchronizer.ts | 64 +-- lisp/i18n/ts/nl_BE/timecode.ts | 74 +-- lisp/i18n/ts/nl_BE/triggers.ts | 20 +- lisp/i18n/ts/sl_SI/action_cues.ts | 188 +++---- lisp/i18n/ts/sl_SI/cart_layout.ts | 99 ++-- lisp/i18n/ts/sl_SI/controller.ts | 129 +++-- lisp/i18n/ts/sl_SI/gst_backend.ts | 154 +++--- lisp/i18n/ts/sl_SI/lisp.ts | 799 ++++++++--------------------- lisp/i18n/ts/sl_SI/list_layout.ts | 128 +++-- lisp/i18n/ts/sl_SI/media_info.ts | 15 +- lisp/i18n/ts/sl_SI/midi.ts | 132 ++++- lisp/i18n/ts/sl_SI/network.ts | 40 +- lisp/i18n/ts/sl_SI/osc.ts | 39 +- lisp/i18n/ts/sl_SI/presets.ts | 68 +-- lisp/i18n/ts/sl_SI/rename_cues.ts | 56 +- lisp/i18n/ts/sl_SI/replay_gain.ts | 49 +- lisp/i18n/ts/sl_SI/synchronizer.ts | 64 +-- lisp/i18n/ts/sl_SI/timecode.ts | 74 +-- lisp/i18n/ts/sl_SI/triggers.ts | 20 +- 112 files changed, 6510 insertions(+), 7868 deletions(-) diff --git a/lisp/i18n/ts/cs_CZ/action_cues.ts b/lisp/i18n/ts/cs_CZ/action_cues.ts index a2f8a0827..ae6dec644 100644 --- a/lisp/i18n/ts/cs_CZ/action_cues.ts +++ b/lisp/i18n/ts/cs_CZ/action_cues.ts @@ -1,25 +1,41 @@ + + ActionCuesDebug + + + Registered cue: "{}" + Registered cue: "{}" + + + + ActionsCuesError + + + Cannot create cue {} + Cannot create cue {} + + CollectionCue - + Add Přidat - + Remove Odstranit - + Cue Narážka - + Action Činnost @@ -27,50 +43,35 @@ CommandCue - - Process ended with an error status. - Postup skončil s chybovým stavem. - - - - Exit code: - Kód ukončení: - - - + Command Příkaz - + Command to execute, as in a shell Příkaz k provedení, jako v terminálu - + Discard command output Zahodit výstup příkazu - + Ignore command errors Přehlížet chyby příkazu - + Kill instead of terminate Zabít namísto ukončit - - - Command cue ended with an error status. Exit code: {} - Command cue ended with an error status. Exit code: {} - Cue Name - + OSC Settings OSC Settings @@ -78,42 +79,42 @@ CueName - + Command Cue Narážka příkazu - + MIDI Cue Narážka MIDI - + Volume Control Ovládání hlasitosti - + Seek Cue Narážka prohledávání - + Collection Cue Narážka sbírky - + Stop-All Zastavit vše - + Index Action Činnost indexu - + OSC Cue OSC Cue @@ -121,68 +122,55 @@ IndexActionCue - + Index Index - + Use a relative index Použít poměrný index - + Target index Cílový index - + Action Činnost - + No suggestion No suggestion - + Suggested cue name Suggested cue name - - MIDICue - - - MIDI Message - Zpráva MIDI - - - - Message type - Typ zprávy - - Osc Cue - + Type Type - + Argument Argument - + FadeTo FadeTo - + Fade Fade @@ -190,75 +178,88 @@ OscCue - - Error during cue execution. - Error during cue execution. - - - + OSC Message OSC Message - + Add Add - + Remove Remove - + Test Test - + OSC Path: (example: "/path/to/something") OSC Path: (example: "/path/to/something") - + Fade Fade - + Time (sec) Time (sec) - + Curve Curve + + OscCueError + + + Could not parse argument list, nothing sent + Could not parse argument list, nothing sent + + + + Error while parsing arguments, nothing sent + Error while parsing arguments, nothing sent + + + + Error during cue execution. + Error during cue execution. + + SeekCue - + Cue Narážka - + Click to select Klepnout pro vybrání - + Not selected Nevybráno - + Seek Hledat - + Time to reach Čas k dosažení @@ -266,37 +267,37 @@ SettingsPageName - + Command Příkaz - + MIDI Settings Nastavení MIDI - + Volume Settings Nastavení hlasitosti - + Seek Settings Nastavení prohledávání - + Edit Collection Upravit sbírku - + Action Settings Nastavení činnosti - + Stop Settings Nastavení zastavení @@ -304,7 +305,7 @@ StopAll - + Stop Action Činnost zastavení @@ -312,34 +313,37 @@ VolumeControl - - Error during cue execution - Chyba během provádění narážky - - - + Cue Narážka - + Click to select Klepnout pro vybrání - + Not selected Nevybráno - + Volume to reach Hlasitost k dosažení - + Fade Prolínat + + VolumeControlError + + + Error during cue execution. + Error during cue execution. + + diff --git a/lisp/i18n/ts/cs_CZ/cart_layout.ts b/lisp/i18n/ts/cs_CZ/cart_layout.ts index 0f5e79c01..e6b342c7b 100644 --- a/lisp/i18n/ts/cs_CZ/cart_layout.ts +++ b/lisp/i18n/ts/cs_CZ/cart_layout.ts @@ -4,120 +4,115 @@ CartLayout - + Default behaviors Default behaviors - + Countdown mode Countdown mode - + Show seek-bars Show seek-bars - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show volume Show volume - - Automatically add new page - Automatically add new page - - - + Grid size Grid size - - Number of columns - Number of columns - - - - Number of rows - Number of rows - - - + Play Play - + Pause Pause - + Stop Stop - + Reset volume Reset volume - + Add page Add page - + Add pages Add pages - + Remove current page Remove current page - + Number of Pages: Number of Pages: - + Page {number} Page {number} - + Warning Warning - + Every cue in the page will be lost. Every cue in the page will be lost. - + Are you sure to continue? Are you sure to continue? + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + LayoutDescription - + Organize cues in grid like pages Organize cues in grid like pages @@ -125,52 +120,68 @@ LayoutDetails - + Click a cue to run it Click a cue to run it - + SHIFT + Click to edit a cue SHIFT + Click to edit a cue - + CTRL + Click to select a cue CTRL + Click to select a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them while pressing SHIFT To move cues drag them while pressing SHIFT + + LayoutName + + + Cart Layout + Cart Layout + + ListLayout - + Edit cue Edit cue - + Edit selected cues Edit selected cues - + Remove cue Remove cue - + Remove selected cues Remove selected cues + + SettingsPageName + + + Cart Layout + Cart Layout + + diff --git a/lisp/i18n/ts/cs_CZ/controller.ts b/lisp/i18n/ts/cs_CZ/controller.ts index 031e5d7fd..f76fe0694 100644 --- a/lisp/i18n/ts/cs_CZ/controller.ts +++ b/lisp/i18n/ts/cs_CZ/controller.ts @@ -4,7 +4,7 @@ Controller - + Cannot load controller protocol: "{}" Cannot load controller protocol: "{}" @@ -12,17 +12,17 @@ ControllerKeySettings - + Key Klávesa - + Action Činnost - + Shortcuts Klávesové zkratky @@ -30,47 +30,47 @@ ControllerMidiSettings - + MIDI MIDI - + Type Typ - + Channel Kanál - + Note Nota - + Action Činnost - + Filter "note on" Filtr "nota zapnuta" - + Filter "note off" Filtr (nota vypnuta" - + Capture Zachytávání - + Listening MIDI messages ... Naslouchá se zprávám MIDI... @@ -78,57 +78,52 @@ ControllerOscSettings - + OSC Message OSC Message - - OSC Path: (example: "/path/to/something") - OSC Path: (example: "/path/to/something") - - - + OSC OSC - + Path Path - + Types Types - + Arguments Arguments - + Actions Actions - + OSC Capture OSC Capture - + Add Add - + Remove Remove - + Capture Capture @@ -136,25 +131,78 @@ ControllerSettings - + Add Přidat - + Remove Odstranit + + GlobalAction + + + Go + Go + + + + Reset + Reset + + + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + Osc Cue - + Type Type - + Argument Argument @@ -162,12 +210,12 @@ OscCue - + Add Add - + Remove Remove @@ -175,24 +223,29 @@ SettingsPageName - + Cue Control Ovládání narážky - + MIDI Controls Ovládání MIDI - + Keyboard Shortcuts Klávesové zkratky - + OSC Controls OSC Controls + + + Layout Controls + Layout Controls + diff --git a/lisp/i18n/ts/cs_CZ/gst_backend.ts b/lisp/i18n/ts/cs_CZ/gst_backend.ts index 087934f5a..b6f6caf97 100644 --- a/lisp/i18n/ts/cs_CZ/gst_backend.ts +++ b/lisp/i18n/ts/cs_CZ/gst_backend.ts @@ -4,60 +4,55 @@ AlsaSinkSettings - + ALSA device Zařízení ALSA - - - ALSA devices, as defined in an asound configuration file - Zařízení ALSA, jak jsou stanovena v souboru s nastavením asound - AudioDynamicSettings - + Compressor Stlačovač - + Expander Rozpínač - + Soft Knee Měkké koleno - + Hard Knee Tvrdé koleno - + Compressor/Expander Stlačovač/Rozpínač - + Type Typ - + Curve Shape Tvar křivky - + Ratio Poměr - + Threshold (dB) Práh (dB) @@ -65,22 +60,22 @@ AudioPanSettings - + Audio Pan Vyvážení zvuku - + Center Na střed - + Left Vlevo - + Right Vpravo @@ -88,22 +83,22 @@ DbMeterSettings - + DbMeter settings Nastavení měřidla decibelů - + Time between levels (ms) Čas mezi hladinami (ms) - + Peak ttl (ms) Ttl vrcholu (ms) - + Peak falloff (dB/sec) Opadávání vrcholu (dB/s) @@ -111,7 +106,7 @@ Equalizer10Settings - + 10 Bands Equalizer (IIR) Desetipásmový ekvalizér (IIR) @@ -119,28 +114,44 @@ GstBackend - + Audio cue (from file) Audio cue (from file) - + Select media files Select media files + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + GstMediaSettings - + Change Pipeline Změnit komunikační spojení + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + GstPipelineEdit - + Edit Pipeline Upravit komunikační spojení @@ -148,7 +159,7 @@ GstSettings - + Pipeline Komunikační spojení @@ -156,32 +167,32 @@ JackSinkSettings - + Connections Spojení - + Edit connections Upravit spojení - + Output ports Výstupní přípojky - + Input ports Vstupní přípojky - + Connect Spojit - + Disconnect Odpojit @@ -189,77 +200,77 @@ MediaElementName - + Compressor/Expander Stlačovač/Rozpínač - + Audio Pan Vyvážení zvuku - + PulseAudio Out Výstup PulseAudio - + Volume Hlasitost - + dB Meter Měřidlo decibelů - + System Input Vstup systému - + ALSA Out Výstup ALSA - + JACK Out Výstup JACK - + Custom Element Vlastní prvek - + System Out Výstup systému - + Pitch Výška tónu - + URI Input Vstup URI - + 10 Bands Equalizer Desetipásmový ekvalizér - + Speed Rychlost - + Preset Input Vstup přednastavení @@ -267,12 +278,12 @@ PitchSettings - + Pitch Výška tónu - + {0:+} semitones Půltóny {0:+} @@ -280,7 +291,7 @@ PresetSrcSettings - + Presets Přednastavení @@ -288,17 +299,12 @@ SettingsPageName - - GStreamer settings - Nastavení pro GStreamer - - - + Media Settings Nastavení médií - + GStreamer GStreamer @@ -306,7 +312,7 @@ SpeedSettings - + Speed Rychlost @@ -314,42 +320,42 @@ UriInputSettings - + Source Zdroj - + Find File Najít soubor - + Buffering Ukládání do vyrovnávací paměti - + Use Buffering Použít ukládání do vyrovnávací paměti - + Attempt download on network streams Pokusit se o stažení na síťových proudech - + Buffer size (-1 default value) Velikost vyrovnávací paměti (-1 výchozí hodnota) - + Choose file Vybrat soubor - + All files Všechny soubory @@ -357,12 +363,12 @@ UserElementSettings - + User defined elements Uživatelem stanovené prvky - + Only for advanced user! Jen pro pokročilého uživatele! @@ -370,17 +376,17 @@ VolumeSettings - + Volume Hlasitost - + Normalized volume Normalizovaná hlasitost - + Reset Nastavit znovu diff --git a/lisp/i18n/ts/cs_CZ/lisp.ts b/lisp/i18n/ts/cs_CZ/lisp.ts index c7056ba0a..ce8b3b382 100644 --- a/lisp/i18n/ts/cs_CZ/lisp.ts +++ b/lisp/i18n/ts/cs_CZ/lisp.ts @@ -4,22 +4,22 @@ About - + Authors Autoři - + Contributors Přispěvatelé - + Translators Překladatelé - + About Linux Show Player O programu Linux Show Player @@ -27,42 +27,32 @@ AboutDialog - - Linux Show Player is a cue-player designed for stage productions. - Linux Show Player je přehrávač záznamů s narážkami navržený pro výrobu scén. - - - + Web site Stránky - - Users group - Uživatelská skupina - - - + Source code Zdrojový kód - + Info Informace - + License Licence - + Contributors Přispěvatelé - + Discussion Discussion @@ -70,12 +60,12 @@ Actions - + Undo: {} Undo: {} - + Redo: {} Redo: {} @@ -83,7 +73,7 @@ AppConfiguration - + LiSP preferences Nastavení LiSP @@ -91,243 +81,109 @@ AppGeneralSettings - - Startup layout - Rozvržení při spuštění - - - - Use startup dialog - Použít dialog při spuštění - - - - Application theme - Motiv programu - - - + Default layout Default layout - + Enable startup layout selector Enable startup layout selector - + Application themes Application themes - - UI theme - UI theme + + UI theme: + UI theme: - - Icons theme - Icons theme + + Icons theme: + Icons theme: - CartLayout - - - Add page - Přidat stranu - - - - Add pages - Přidat strany - - - - Remove current page - Odstranit nynější stranu - - - - Countdown mode - Režim odpočítávání - - - - Show seek-bars - Ukázat prohledávací pruhy - - - - Show dB-meters - Ukázat měřidla decibelů - - - - Show volume - Ukázat hlasitost - - - - Show accurate time - Ukázat přesný čas - - - - Edit cue - Upravit narážku - - - - Remove - Odstranit - - - - Select - Vybrat - - - - Play - Přehrát - - - - Pause - Pozastavit - - - - Stop - Zastavit - - - - Reset volume - Nastavit znovu hlasitost - - - - Number of Pages: - Počet stran: - - - - Warning - Varování - - - - Every cue in the page will be lost. - Každá narážka na straně bude ztracena. - - - - Are you sure to continue? - Jste si jistý, že chcete pokračovat? - + ApplicationError - - Page - Strana - - - - Default behaviors - Výchozí chování - - - - Automatically add new page - Automaticky přidat novou stranu - - - - Grid size - Velikost mřížky + + Startup error + Startup error + + + ConfigurationDebug - - Number of columns - Počet sloupců + + Configuration written at {} + Configuration written at {} + + + ConfigurationInfo - - Number of rows - Počet řádků + + New configuration installed at {} + New configuration installed at {} CueAction - + Default Výchozí - + Pause Pozastavit - + Start Spustit - + Stop Zastavit - - FadeInStart - Začátek postupného zesílení - - - - FadeOutStop - Konec postupného zeslabení - - - - FadeOutPause - Pozastavení postupného zeslabení - - - + Faded Start Faded Start - + Faded Resume Faded Resume - + Faded Pause Faded Pause - + Faded Stop Faded Stop - + Faded Interrupt Faded Interrupt - + Resume Resume - + Do Nothing Do Nothing @@ -335,12 +191,12 @@ CueActionLog - + Cue settings changed: "{}" Nastavení narážky změněna: "{}" - + Cues settings changed. Nastavení narážky změněna. @@ -348,42 +204,42 @@ CueAppearanceSettings - + The appearance depends on the layout Vzhled závisí na rozvržení - + Cue name Název narážky - + NoName Bez názvu - + Description/Note Popis/Poznámka - + Set Font Size Nastavit velikost písma - + Color Barva - + Select background color Vybrat barvu pozadí - + Select font color Vybrat barvu písma @@ -391,7 +247,7 @@ CueName - + Media Cue Narážka v záznamu @@ -399,100 +255,85 @@ CueNextAction - + Do Nothing Do Nothing + + + Trigger after the end + Trigger after the end + - Auto Follow - Auto Follow + Trigger after post wait + Trigger after post wait - - Auto Next - Auto Next + + Select after the end + Select after the end + + + + Select after post wait + Select after post wait CueSettings - + Pre wait Čekání před - + Wait before cue execution Čekat před provedením narážky - + Post wait Čekání po - + Wait after cue execution Čekat po provedení narážky - + Next action Další činnost - - Interrupt Fade - Prolínání přerušení - - - - Fade Action - Činnost prolínání - - - - Behaviours - Chování - - - - Pre/Post Wait - Čekání před/po - - - - Fade In/Out - Postupné zesílení/zeslabení signálu - - - + Start action Činnost spuštění - + Default action to start the cue Výchozí činnost pro spuštění narážky - + Stop action Činnost zastavení - + Default action to stop the cue Výchozí činnost pro zastavení narážky - + Interrupt fade Interrupt fade - + Fade actions Fade actions @@ -500,17 +341,17 @@ Fade - + Linear Lineární - + Quadratic Kvadratický - + Quadratic2 Kvadratický 2 @@ -518,99 +359,43 @@ FadeEdit - - Duration (sec) - Doba trvání (s) + + Duration (sec): + Duration (sec): - - Curve - Křivka + + Curve: + Curve: FadeSettings - + Fade In Postupné zesílení signálu - + Fade Out Postupné zeslabení signálu - - LayoutDescription - - - Organize cues in grid like pages - Uspořádat narážky v mřížce jako strany - - - - Organize the cues in a list - Uspořádat narážky v seznamu - - - - LayoutDetails - - - Click a cue to run it - Klepnout na narážku pro její spuštění - - - - SHIFT + Click to edit a cue - Shift + klepnutí pro upravení narážky - - - - CTRL + Click to select a cue - Ctrl + klepnutí pro vybrání narážky - - - - SHIFT + Space or Double-Click to edit a cue - Shift + mezerník nebo dvojité klepnutí pro upravení narážky - - - - To copy cues drag them while pressing CTRL - Pro zkopírování narážek je táhněte za současného držení klávesy Ctrl - - - - To copy cues drag them while pressing SHIFT - Pro zkopírování narážek je táhněte za současného držení klávesy Shift - - - - CTRL + Left Click to select cues - Ctrl + klepnutí levým tlačítkem myši pro vybrání narážky - - - - To move cues drag them - Pro přesunutí narážek je táhněte - - LayoutSelect - + Layout selection Výběr rozvržení - + Select layout Vybrat rozvržení - + Open file Otevřít soubor @@ -618,518 +403,363 @@ ListLayout - - Show playing cues - Ukázat přehrávané narážky - - - - Show dB-meters - Ukázat měřidla decibelů - - - - Show seek-bars - Ukázat prohledávací pruhy - - - - Show accurate time - Ukázat přesný čas - - - - Auto-select next cue - Vybrat další narážku automaticky - - - - Edit cue - Upravit narážku - - - - Remove - Odstranit - - - - Select - Vybrat - - - - Stop all - Zastavit vše - - - - Pause all - Pozastavit vše - - - - Restart all - Spustit vše znovu - - - - Stop - Zastavit - - - - Restart - Nastavit znovu na výchozí - - - - Default behaviors - Výchozí chování - - - - At list end: - Na konci seznamu: - - - - Go key: - Klávesa pro přechod: - - - - Interrupt all - Přerušit vše - - - - Fade-Out all - Postupně zeslabit vše - - - - Fade-In all - Postupně zesílit vše - - - - Use fade - Použít prolínání - - - - Stop Cue - Narážka pro zastavení - - - - Pause Cue - Narážka pro pozastavení - - - - Restart Cue - Narážka pro opětovné spuštění - - - - Interrupt Cue - Narážka pro přerušení - - - + Stop All Zastavit vše - + Pause All Pozastavit vše - - Restart All - Spustit vše znovu - - - + Interrupt All Přerušit vše - + Use fade (global actions) Use fade (global actions) - + Resume All Resume All - - ListLayoutHeader - - - Cue - Narážka - - - - Pre wait - Čekání před - - - - Action - Činnost - - - - Post wait - Čekání po - - - - ListLayoutInfoPanel - - - Cue name - Název narážky - - - - Cue description - Popis narážky - - Logging - - Information - Informace - - - + Debug Ladění - + Warning Varování - + Error Chyba - - Details: - Podrobnosti: - - - + Dismiss all Dismiss all - + Show details Show details - + Linux Show Player - Log Viewer Linux Show Player - Log Viewer - + Info Info - + Critical Critical - + Time Time - + Milliseconds Milliseconds - + Logger name Logger name - + Level Level - + Message Message - + Function Function - + Path name Path name - + File name File name - + Line no. Line no. - + Module Module - + Process ID Process ID - + Process name Process name - + Thread ID Thread ID - + Thread name Thread name + + + Exception info + Exception info + MainWindow - + &File &Soubor - + New session Nové sezení - + Open Otevřít - + Save session Uložit sezení - + Preferences Nastavení - + Save as Uložit jako - + Full Screen Na celou obrazovku - + Exit Ukončit - + &Edit Úp&ravy - + Undo Zpět - + Redo Znovu - + Select all Vybrat vše - + Select all media cues Vybrat všechny narážky v záznamu - + Deselect all Odznačit všechny narážky - + CTRL+SHIFT+A Ctrl+Shift+A - + Invert selection Obrátit výběr - + CTRL+I CTRL+I - + Edit selected Upravit vybrané - + CTRL+SHIFT+E Ctrl+Shift+E - + &Layout &Rozvržení - + &Tools &Nástroje - + Edit selection Upravit výběr - + &About &O programu - + About O programu - + About Qt O Qt - - Undone: - Vráceno zpět: - - - - Redone: - Uděláno znovu: - - - + Close session Zavřít sezení - + The current session is not saved. Nynější sezení není uloženo. - + Discard the changes? Zahodit změny? - - MediaCueMenus - - - Audio cue (from file) - Zvuková narážka (ze souboru) - - - - Select media files - Vybrat soubory se záznamy - - MediaCueSettings - + Start time Čas spuštění - + Stop position of the media Poloha zastavení záznamu - + Stop time Čas zastavení - + Start position of the media Poloha spuštění záznamu - + Loop Smyčka + + + PluginsError + + + Failed to load "{}" + Failed to load "{}" + + + + Failed to terminate plugin: "{}" + Failed to terminate plugin: "{}" + + + + the requested plugin is not loaded: {} + the requested plugin is not loaded: {} + + + + PluginsInfo + + + Plugin loaded: "{}" + Plugin loaded: "{}" + - - Repetition after first play (-1 = infinite) - Opakování po prvním přehrání (-1 = po nekonečnou dobu) + + Plugin terminated: "{}" + Plugin terminated: "{}" + + + + PluginsWarning + + + Cannot satisfy dependencies for: {} + Cannot satisfy dependencies for: {} QColorButton - + Right click to reset Klepnutí pravým tlačítkem myši pro nastavení na výchozí hodnotu @@ -1137,44 +767,49 @@ SettingsPageName - + Appearance Vzhled - + General Obecné - + Cue Narážka - + Cue Settings Nastavení narážky - + Plugins Plugins - + Behaviours Behaviours - + Pre/Post Wait Pre/Post Wait - + Fade In/Out Fade In/Out + + + Layouts + Layouts + diff --git a/lisp/i18n/ts/cs_CZ/list_layout.ts b/lisp/i18n/ts/cs_CZ/list_layout.ts index f68a072d5..d3740e883 100644 --- a/lisp/i18n/ts/cs_CZ/list_layout.ts +++ b/lisp/i18n/ts/cs_CZ/list_layout.ts @@ -4,7 +4,7 @@ LayoutDescription - + Organize the cues in a list Organize the cues in a list @@ -12,163 +12,191 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue SHIFT + Space or Double-Click to edit a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them To move cues drag them + + LayoutName + + + List Layout + List Layout + + ListLayout - + Default behaviors Default behaviors - + Show playing cues Show playing cues - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue - + Enable selection mode Enable selection mode - - Go key: - Go key: - - - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - - Edit selected cues - Edit selected cues - - - + Remove cue Remove cue - - Remove selected cues - Remove selected cues - - - + Selection mode Selection mode - + Pause all Pause all - + Stop all Stop all - + Interrupt all Interrupt all - + Resume all Resume all - + Fade-Out all Fade-Out all - + Fade-In all Fade-In all + + + GO key: + GO key: + + + + GO action: + GO action: + + + + GO delay (ms): + GO delay (ms): + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove selected + Remove selected + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait @@ -176,14 +204,22 @@ ListLayoutInfoPanel - + Cue name Cue name - + Cue description Cue description + + SettingsPageName + + + List Layout + List Layout + + diff --git a/lisp/i18n/ts/cs_CZ/media_info.ts b/lisp/i18n/ts/cs_CZ/media_info.ts index d58415ce5..daae04bec 100644 --- a/lisp/i18n/ts/cs_CZ/media_info.ts +++ b/lisp/i18n/ts/cs_CZ/media_info.ts @@ -4,32 +4,27 @@ MediaInfo - + Media Info Údaje o záznamu - - Error - Chyba - - - + No info to display Žádné údaje k zobrazení - + Info Informace - + Value Hodnota - + Warning Warning diff --git a/lisp/i18n/ts/cs_CZ/midi.ts b/lisp/i18n/ts/cs_CZ/midi.ts index 58fd6437d..23d74e7e7 100644 --- a/lisp/i18n/ts/cs_CZ/midi.ts +++ b/lisp/i18n/ts/cs_CZ/midi.ts @@ -1,20 +1,144 @@ + + MIDICue + + + MIDI Message + MIDI Message + + + + Message type + Message type + + + + MIDIMessageAttr + + + Channel + Channel + + + + Note + Note + + + + Velocity + Velocity + + + + Control + Control + + + + Program + Program + + + + Value + Value + + + + Song + Song + + + + Pitch + Pitch + + + + Position + Position + + + + MIDIMessageType + + + Note ON + Note ON + + + + Note OFF + Note OFF + + + + Polyphonic After-touch + Polyphonic After-touch + + + + Control/Mode Change + Control/Mode Change + + + + Program Change + Program Change + + + + Channel After-touch + Channel After-touch + + + + Pitch Bend Change + Pitch Bend Change + + + + Song Select + Song Select + + + + Song Position + Song Position + + + + Start + Start + + + + Stop + Stop + + + + Continue + Continue + + MIDISettings - + MIDI default devices Výchozí zařízení MIDI - + Input Vstup - + Output Výstup @@ -22,7 +146,7 @@ SettingsPageName - + MIDI settings Nastavení MIDI diff --git a/lisp/i18n/ts/cs_CZ/network.ts b/lisp/i18n/ts/cs_CZ/network.ts index 49cee197d..6a9dc1cb2 100644 --- a/lisp/i18n/ts/cs_CZ/network.ts +++ b/lisp/i18n/ts/cs_CZ/network.ts @@ -1,45 +1,69 @@ + + APIServerInfo + + + Stop serving network API + Stop serving network API + + + + ApiServerError + + + Network API server stopped working. + Network API server stopped working. + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + NetworkDiscovery - + Host discovery Host discovery - + Manage hosts Manage hosts - + Discover hosts Discover hosts - + Manually add a host Manually add a host - + Remove selected host Remove selected host - + Remove all host Remove all host - + Address Address - + Host IP Host IP diff --git a/lisp/i18n/ts/cs_CZ/osc.ts b/lisp/i18n/ts/cs_CZ/osc.ts index 6a9eb64d3..8c4703f23 100644 --- a/lisp/i18n/ts/cs_CZ/osc.ts +++ b/lisp/i18n/ts/cs_CZ/osc.ts @@ -1,25 +1,54 @@ + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + OscSettings - + OSC Settings OSC Settings - + Input Port: Input Port: - + Output Port: Output Port: - + Hostname: Hostname: @@ -27,7 +56,7 @@ SettingsPageName - + OSC settings OSC settings diff --git a/lisp/i18n/ts/cs_CZ/presets.ts b/lisp/i18n/ts/cs_CZ/presets.ts index dac0d3b03..9e8ca26f6 100644 --- a/lisp/i18n/ts/cs_CZ/presets.ts +++ b/lisp/i18n/ts/cs_CZ/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Vytvořit narážku - + Load on selected Cues Nahrát na vybraných narážkách @@ -17,132 +17,112 @@ Presets - + Presets Přednastavení - - Load preset - Nahrát přednastavení - - - + Save as preset Uložit jako přednastavení - + Cannot scan presets Nelze prohledat přednastavení - + Error while deleting preset "{}" Chyba při mazání přednastavení "{}" - + Cannot load preset "{}" Nelze nahrát přednastavení "{}" - + Cannot save preset "{}" Nelze uložit přednastavení "{}" - + Cannot rename preset "{}" Nelze přejmenovat přednastavení "{}" - + Select Preset Vybrat přednastavení - - Preset already exists, overwrite? - Přednastavení již existuje. Přepsat? - - - + Preset name Název přednastavení - + Add Přidat - + Rename Přejmenovat - + Edit Upravit - + Remove Odstranit - + Export selected Vybráno vyvedení - + Import Vyvedení - + Warning Varování - + The same name is already used! Stejný název se již používá! - - Cannot create a cue from this preset: {} - Nelze vytvořit narážku z tohoto přednastavení: "{}" - - - + Cannot export correctly. Nelze vyvést správně. - - Some presets already exists, overwrite? - Některá přednastavení již existují. Přepsat? - - - + Cannot import correctly. Nelze zavést správně. - + Cue type Typ narážky - + Load on cue Load on cue - + Load on selected cues Load on selected cues diff --git a/lisp/i18n/ts/cs_CZ/rename_cues.ts b/lisp/i18n/ts/cs_CZ/rename_cues.ts index 91f0053a9..3deed7d41 100644 --- a/lisp/i18n/ts/cs_CZ/rename_cues.ts +++ b/lisp/i18n/ts/cs_CZ/rename_cues.ts @@ -4,92 +4,72 @@ RenameCues - + Rename Cues Rename Cues - + Rename cues Rename cues - + Current Current - + Preview Preview - + Capitalize Capitalize - + Lowercase Lowercase - + Uppercase Uppercase - + Remove Numbers Remove Numbers - + Add numbering Add numbering - + Reset Reset - - Rename all cue. () in regex below usable with $0, $1 ... - Rename all cue. () in regex below usable with $0, $1 ... - - - + Type your regex here: Type your regex here: - + Regex help Regex help + + + RenameUiDebug - - You can use Regexes to rename your cues. - -Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... -In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. - -Exemple : -^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. -Only the numbers are between parenthesis and will be usable with $0 in the first line. - -For more information about Regexes, consult python documentation - You can use Regexes to rename your cues. - -Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... -In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. - -Exemple : -^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. -Only the numbers are between parenthesis and will be usable with $0 in the first line. - -For more information about Regexes, consult python documentation + + Regex error: Invalid pattern + Regex error: Invalid pattern diff --git a/lisp/i18n/ts/cs_CZ/replay_gain.ts b/lisp/i18n/ts/cs_CZ/replay_gain.ts index e49bfc3e9..fbfbf9a14 100644 --- a/lisp/i18n/ts/cs_CZ/replay_gain.ts +++ b/lisp/i18n/ts/cs_CZ/replay_gain.ts @@ -4,49 +4,80 @@ ReplayGain - + ReplayGain / Normalization Vyrovnání hlasitosti/Normalizace - + Calculate Spočítat - + Reset all Nastavit vše znovu - + Reset selected Nastavit vybrané znovu - + Threads number Počet vláken - + Apply only to selected media Použít pouze na vybrané záznamy - + ReplayGain to (dB SPL) Vyrovnat hlasitost na (dB SPL) - + Normalize to (dB) Normalizovat na (dB SPL) - + Processing files ... Zpracovávají se soubory... + + ReplayGainDebug + + + Applied gain for: {} + Applied gain for: {} + + + + Discarded gain for: {} + Discarded gain for: {} + + + + ReplayGainInfo + + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} + + diff --git a/lisp/i18n/ts/cs_CZ/synchronizer.ts b/lisp/i18n/ts/cs_CZ/synchronizer.ts index 3c022b78f..98c6c523a 100644 --- a/lisp/i18n/ts/cs_CZ/synchronizer.ts +++ b/lisp/i18n/ts/cs_CZ/synchronizer.ts @@ -1,73 +1,20 @@ - - SyncPeerDialog - - - Manage connected peers - Spravovat spojené protějšky - - - - Discover peers - Objevit protějšky - - - - Manually add a peer - Přidat protějšek ručně - - - - Remove selected peer - Odstranit vybraný protějšek - - - - Remove all peers - Odstranit všechny protějšky - - - - Address - Adresa - - - - Peer IP - Adresa protějšku - - - - Error - Chyba - - - - Already connected - Již připojen - - - - Cannot add peer - Nelze přidat protějšek - - Synchronizer - + Synchronization Seřízení - + Manage connected peers Spravovat spojené protějšky - + Show your IP Ukázat vaši adresu (IP) @@ -76,10 +23,5 @@ Your IP is: Vaše adresu (IP): - - - Discovering peers ... - Objevují se protějšci... - diff --git a/lisp/i18n/ts/cs_CZ/timecode.ts b/lisp/i18n/ts/cs_CZ/timecode.ts index d17c94612..41b2979f9 100644 --- a/lisp/i18n/ts/cs_CZ/timecode.ts +++ b/lisp/i18n/ts/cs_CZ/timecode.ts @@ -4,12 +4,12 @@ SettingsPageName - + Timecode Settings Nastavení časového kódu - + Timecode Časový kód @@ -17,92 +17,48 @@ Timecode - - Cannot send timecode. - Nelze poslat časový kód - - - - OLA has stopped. - OLA zastavil. - - - + Cannot load timecode protocol: "{}" Cannot load timecode protocol: "{}" + + + TimecodeError - - Cannot send timecode. -OLA has stopped. - Cannot send timecode. -OLA has stopped. + + Cannot send timecode. + Cannot send timecode. TimecodeSettings - - OLA Timecode Settings - Nastavení časového kódu OLA - - - - Enable Plugin - Povolit přídavný modul - - - - High-Resolution Timecode - Časový kód pro velké rozlišení - - - + Timecode Format: Formát časového kódu: - - OLA status - Stav OLA - - - - OLA is not running - start the OLA daemon. - OLA neběží - spustit démona OLA. - - - + Replace HOURS by a static track number Nahradit HODINY stálým číslem stopy - - Enable ArtNet Timecode - Povolit časový kód ArtNet - - - + Track number Číslo stopy - - To send ArtNet Timecode you need to setup a running OLA session! - Pro poslání časového kódu ArtNet je potřeba zřídit běžící sezení OLA! - - - + Enable Timecode Enable Timecode - + Timecode Settings Timecode Settings - + Timecode Protocol: Timecode Protocol: diff --git a/lisp/i18n/ts/cs_CZ/triggers.ts b/lisp/i18n/ts/cs_CZ/triggers.ts index 4969c0f96..fedc2b20d 100644 --- a/lisp/i18n/ts/cs_CZ/triggers.ts +++ b/lisp/i18n/ts/cs_CZ/triggers.ts @@ -4,22 +4,22 @@ CueTriggers - + Started Spuštěno - + Paused Pozastaveno - + Stopped Zastaveno - + Ended Skončeno @@ -27,7 +27,7 @@ SettingsPageName - + Triggers Spouštěče @@ -35,27 +35,27 @@ TriggersSettings - + Add Přidat - + Remove Odstranit - + Trigger Spouštěč - + Cue Narážka - + Action Činnost diff --git a/lisp/i18n/ts/de_DE/action_cues.ts b/lisp/i18n/ts/de_DE/action_cues.ts index 8e963cc3f..e93fe57f5 100644 --- a/lisp/i18n/ts/de_DE/action_cues.ts +++ b/lisp/i18n/ts/de_DE/action_cues.ts @@ -1,25 +1,41 @@ + + ActionCuesDebug + + + Registered cue: "{}" + Registered cue: "{}" + + + + ActionsCuesError + + + Cannot create cue {} + Cannot create cue {} + + CollectionCue - + Add Add - + Remove Remove - + Cue Cue - + Action Action @@ -27,50 +43,35 @@ CommandCue - - Process ended with an error status. - Process ended with an error status. - - - - Exit code: - Exit code: - - - + Command Command - + Command to execute, as in a shell Command to execute, as in a shell - + Discard command output Discard command output - + Ignore command errors Ignore command errors - + Kill instead of terminate Kill instead of terminate - - - Command cue ended with an error status. Exit code: {} - Command cue ended with an error status. Exit code: {} - Cue Name - + OSC Settings OSC Settings @@ -78,42 +79,42 @@ CueName - + Command Cue Command Cue - + MIDI Cue MIDI Cue - + Volume Control Volume Control - + Seek Cue Seek Cue - + Collection Cue Collection Cue - + Stop-All Stop-All - + Index Action Index Action - + OSC Cue OSC Cue @@ -121,68 +122,55 @@ IndexActionCue - + Index Index - + Use a relative index Use a relative index - + Target index Target index - + Action Action - + No suggestion No suggestion - + Suggested cue name Suggested cue name - - MIDICue - - - MIDI Message - MIDI Message - - - - Message type - Message type - - Osc Cue - + Type Type - + Argument Argument - + FadeTo FadeTo - + Fade Fade @@ -190,75 +178,88 @@ OscCue - - Error during cue execution. - Error during cue execution. - - - + OSC Message OSC Message - + Add Add - + Remove Remove - + Test Test - + OSC Path: (example: "/path/to/something") OSC Path: (example: "/path/to/something") - + Fade Fade - + Time (sec) Time (sec) - + Curve Curve + + OscCueError + + + Could not parse argument list, nothing sent + Could not parse argument list, nothing sent + + + + Error while parsing arguments, nothing sent + Error while parsing arguments, nothing sent + + + + Error during cue execution. + Error during cue execution. + + SeekCue - + Cue Cue - + Click to select Click to select - + Not selected Not selected - + Seek Seek - + Time to reach Time to reach @@ -266,37 +267,37 @@ SettingsPageName - + Command Command - + MIDI Settings MIDI Settings - + Volume Settings Volume Settings - + Seek Settings Seek Settings - + Edit Collection Edit Collection - + Action Settings Action Settings - + Stop Settings Stop Settings @@ -304,7 +305,7 @@ StopAll - + Stop Action Stop Action @@ -312,34 +313,37 @@ VolumeControl - - Error during cue execution - Error during cue execution - - - + Cue Cue - + Click to select Click to select - + Not selected Not selected - + Volume to reach Volume to reach - + Fade Fade + + VolumeControlError + + + Error during cue execution. + Error during cue execution. + + diff --git a/lisp/i18n/ts/de_DE/cart_layout.ts b/lisp/i18n/ts/de_DE/cart_layout.ts index 8d20cca42..624d3d1bf 100644 --- a/lisp/i18n/ts/de_DE/cart_layout.ts +++ b/lisp/i18n/ts/de_DE/cart_layout.ts @@ -4,120 +4,115 @@ CartLayout - + Default behaviors Default behaviors - + Countdown mode Countdown mode - + Show seek-bars Show seek-bars - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show volume Show volume - - Automatically add new page - Automatically add new page - - - + Grid size Grid size - - Number of columns - Number of columns - - - - Number of rows - Number of rows - - - + Play Play - + Pause Pause - + Stop Stop - + Reset volume Reset volume - + Add page Add page - + Add pages Add pages - + Remove current page Remove current page - + Number of Pages: Number of Pages: - + Page {number} Page {number} - + Warning Warning - + Every cue in the page will be lost. Every cue in the page will be lost. - + Are you sure to continue? Are you sure to continue? + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + LayoutDescription - + Organize cues in grid like pages Organize cues in grid like pages @@ -125,52 +120,68 @@ LayoutDetails - + Click a cue to run it Click a cue to run it - + SHIFT + Click to edit a cue SHIFT + Click to edit a cue - + CTRL + Click to select a cue CTRL + Click to select a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them while pressing SHIFT To move cues drag them while pressing SHIFT + + LayoutName + + + Cart Layout + Cart Layout + + ListLayout - + Edit cue Edit cue - + Edit selected cues Edit selected cues - + Remove cue Remove cue - + Remove selected cues Remove selected cues + + SettingsPageName + + + Cart Layout + Cart Layout + + diff --git a/lisp/i18n/ts/de_DE/controller.ts b/lisp/i18n/ts/de_DE/controller.ts index fa1edf877..bdb1b872d 100644 --- a/lisp/i18n/ts/de_DE/controller.ts +++ b/lisp/i18n/ts/de_DE/controller.ts @@ -4,7 +4,7 @@ Controller - + Cannot load controller protocol: "{}" Cannot load controller protocol: "{}" @@ -12,17 +12,17 @@ ControllerKeySettings - + Key Key - + Action Action - + Shortcuts Shortcuts @@ -30,47 +30,47 @@ ControllerMidiSettings - + MIDI MIDI - + Type Type - + Channel Channel - + Note Note - + Action Action - + Filter "note on" Filter "note on" - + Filter "note off" Filter "note off" - + Capture Capture - + Listening MIDI messages ... Listening MIDI messages ... @@ -78,57 +78,52 @@ ControllerOscSettings - + OSC Message OSC Message - - OSC Path: (example: "/path/to/something") - OSC Path: (example: "/path/to/something") - - - + OSC OSC - + Path Path - + Types Types - + Arguments Arguments - + Actions Actions - + OSC Capture OSC Capture - + Add Add - + Remove Remove - + Capture Capture @@ -136,25 +131,78 @@ ControllerSettings - + Add Add - + Remove Remove + + GlobalAction + + + Go + Go + + + + Reset + Reset + + + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + Osc Cue - + Type Type - + Argument Argument @@ -162,12 +210,12 @@ OscCue - + Add Add - + Remove Remove @@ -175,24 +223,29 @@ SettingsPageName - + Cue Control Cue Control - + MIDI Controls MIDI Controls - + Keyboard Shortcuts Keyboard Shortcuts - + OSC Controls OSC Controls + + + Layout Controls + Layout Controls + diff --git a/lisp/i18n/ts/de_DE/gst_backend.ts b/lisp/i18n/ts/de_DE/gst_backend.ts index 7f991ff9d..113560e74 100644 --- a/lisp/i18n/ts/de_DE/gst_backend.ts +++ b/lisp/i18n/ts/de_DE/gst_backend.ts @@ -4,60 +4,55 @@ AlsaSinkSettings - + ALSA device ALSA device - - - ALSA devices, as defined in an asound configuration file - ALSA devices, as defined in an asound configuration file - AudioDynamicSettings - + Compressor Compressor - + Expander Expander - + Soft Knee Soft Knee - + Hard Knee Hard Knee - + Compressor/Expander Compressor/Expander - + Type Type - + Curve Shape Curve Shape - + Ratio Ratio - + Threshold (dB) Threshold (dB) @@ -65,22 +60,22 @@ AudioPanSettings - + Audio Pan Audio Pan - + Center Center - + Left Left - + Right Right @@ -88,22 +83,22 @@ DbMeterSettings - + DbMeter settings DbMeter settings - + Time between levels (ms) Time between levels (ms) - + Peak ttl (ms) Peak ttl (ms) - + Peak falloff (dB/sec) Peak falloff (dB/sec) @@ -111,7 +106,7 @@ Equalizer10Settings - + 10 Bands Equalizer (IIR) 10 Bands Equalizer (IIR) @@ -119,28 +114,44 @@ GstBackend - + Audio cue (from file) Audio cue (from file) - + Select media files Select media files + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + GstMediaSettings - + Change Pipeline Change Pipeline + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + GstPipelineEdit - + Edit Pipeline Edit Pipeline @@ -148,7 +159,7 @@ GstSettings - + Pipeline Pipeline @@ -156,32 +167,32 @@ JackSinkSettings - + Connections Connections - + Edit connections Edit connections - + Output ports Output ports - + Input ports Input ports - + Connect Connect - + Disconnect Disconnect @@ -189,77 +200,77 @@ MediaElementName - + Compressor/Expander Compressor/Expander - + Audio Pan Audio Pan - + PulseAudio Out PulseAudio Out - + Volume Volume - + dB Meter dB Meter - + System Input System Input - + ALSA Out ALSA Out - + JACK Out JACK Out - + Custom Element Custom Element - + System Out System Out - + Pitch Pitch - + URI Input URI Input - + 10 Bands Equalizer 10 Bands Equalizer - + Speed Speed - + Preset Input Preset Input @@ -267,12 +278,12 @@ PitchSettings - + Pitch Pitch - + {0:+} semitones {0:+} semitones @@ -280,7 +291,7 @@ PresetSrcSettings - + Presets Presets @@ -288,17 +299,12 @@ SettingsPageName - - GStreamer settings - GStreamer settings - - - + Media Settings Media Settings - + GStreamer GStreamer @@ -306,7 +312,7 @@ SpeedSettings - + Speed Speed @@ -314,42 +320,42 @@ UriInputSettings - + Source Source - + Find File Find File - + Buffering Buffering - + Use Buffering Use Buffering - + Attempt download on network streams Attempt download on network streams - + Buffer size (-1 default value) Buffer size (-1 default value) - + Choose file Choose file - + All files All files @@ -357,12 +363,12 @@ UserElementSettings - + User defined elements User defined elements - + Only for advanced user! Only for advanced user! @@ -370,17 +376,17 @@ VolumeSettings - + Volume Volume - + Normalized volume Normalized volume - + Reset Reset diff --git a/lisp/i18n/ts/de_DE/lisp.ts b/lisp/i18n/ts/de_DE/lisp.ts index 8ceecacfa..3c379105f 100644 --- a/lisp/i18n/ts/de_DE/lisp.ts +++ b/lisp/i18n/ts/de_DE/lisp.ts @@ -4,22 +4,22 @@ About - + Authors Authors - + Contributors Contributors - + Translators Translators - + About Linux Show Player About Linux Show Player @@ -27,42 +27,32 @@ AboutDialog - - Linux Show Player is a cue-player designed for stage productions. - Linux Show Player is a cue-player designed for stage productions. - - - + Web site Web site - - Users group - Users group - - - + Source code Source code - + Info Info - + License License - + Contributors Contributors - + Discussion Discussion @@ -70,12 +60,12 @@ Actions - + Undo: {} Undo: {} - + Redo: {} Redo: {} @@ -83,7 +73,7 @@ AppConfiguration - + LiSP preferences LiSP preferences @@ -91,243 +81,109 @@ AppGeneralSettings - - Startup layout - Startup layout - - - - Use startup dialog - Use startup dialog - - - - Application theme - Application theme - - - + Default layout Default layout - + Enable startup layout selector Enable startup layout selector - + Application themes Application themes - - UI theme - UI theme + + UI theme: + UI theme: - - Icons theme - Icons theme + + Icons theme: + Icons theme: - CartLayout - - - Add page - Add page - - - - Add pages - Add pages - - - - Remove current page - Remove current page - - - - Countdown mode - Countdown mode - - - - Show seek-bars - Show seek-bars - - - - Show dB-meters - Show dB-meters - - - - Show volume - Show volume - - - - Show accurate time - Show accurate time - - - - Edit cue - Edit cue - - - - Remove - Remove - - - - Select - Select - - - - Play - Play - - - - Pause - Pause - - - - Stop - Stop - - - - Reset volume - Reset volume - - - - Number of Pages: - Number of Pages: - - - - Warning - Warning - - - - Every cue in the page will be lost. - Every cue in the page will be lost. - - - - Are you sure to continue? - Are you sure to continue? - + ApplicationError - - Page - Page - - - - Default behaviors - Default behaviors - - - - Automatically add new page - Automatically add new page - - - - Grid size - Grid size + + Startup error + Startup error + + + ConfigurationDebug - - Number of columns - Number of columns + + Configuration written at {} + Configuration written at {} + + + ConfigurationInfo - - Number of rows - Number of rows + + New configuration installed at {} + New configuration installed at {} CueAction - + Default Default - + Pause Pause - + Start Start - + Stop Stop - - FadeInStart - FadeInStart - - - - FadeOutStop - FadeOutStop - - - - FadeOutPause - FadeOutPause - - - + Faded Start Faded Start - + Faded Resume Faded Resume - + Faded Pause Faded Pause - + Faded Stop Faded Stop - + Faded Interrupt Faded Interrupt - + Resume Resume - + Do Nothing Do Nothing @@ -335,12 +191,12 @@ CueActionLog - + Cue settings changed: "{}" Cue settings changed: "{}" - + Cues settings changed. Cues settings changed. @@ -348,42 +204,42 @@ CueAppearanceSettings - + The appearance depends on the layout The appearance depends on the layout - + Cue name Cue name - + NoName NoName - + Description/Note Description/Note - + Set Font Size Set Font Size - + Color Color - + Select background color Select background color - + Select font color Select font color @@ -391,7 +247,7 @@ CueName - + Media Cue Media Cue @@ -399,100 +255,85 @@ CueNextAction - + Do Nothing Do Nothing + + + Trigger after the end + Trigger after the end + - Auto Follow - Auto Follow + Trigger after post wait + Trigger after post wait - - Auto Next - Auto Next + + Select after the end + Select after the end + + + + Select after post wait + Select after post wait CueSettings - + Pre wait Pre wait - + Wait before cue execution Wait before cue execution - + Post wait Post wait - + Wait after cue execution Wait after cue execution - + Next action Next action - - Interrupt Fade - Interrupt Fade - - - - Fade Action - Fade Action - - - - Behaviours - Behaviours - - - - Pre/Post Wait - Pre/Post Wait - - - - Fade In/Out - Fade In/Out - - - + Start action Start action - + Default action to start the cue Default action to start the cue - + Stop action Stop action - + Default action to stop the cue Default action to stop the cue - + Interrupt fade Interrupt fade - + Fade actions Fade actions @@ -500,17 +341,17 @@ Fade - + Linear Linear - + Quadratic Quadratic - + Quadratic2 Quadratic2 @@ -518,99 +359,43 @@ FadeEdit - - Duration (sec) - Duration (sec) + + Duration (sec): + Duration (sec): - - Curve - Curve + + Curve: + Curve: FadeSettings - + Fade In Fade In - + Fade Out Fade Out - - LayoutDescription - - - Organize cues in grid like pages - Organize cues in grid like pages - - - - Organize the cues in a list - Organize the cues in a list - - - - LayoutDetails - - - Click a cue to run it - Click a cue to run it - - - - SHIFT + Click to edit a cue - SHIFT + Click to edit a cue - - - - CTRL + Click to select a cue - CTRL + Click to select a cue - - - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Space or Double-Click to edit a cue - - - - To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL - - - - To copy cues drag them while pressing SHIFT - To copy cues drag them while pressing SHIFT - - - - CTRL + Left Click to select cues - CTRL + Left Click to select cues - - - - To move cues drag them - To move cues drag them - - LayoutSelect - + Layout selection Layout selection - + Select layout Select layout - + Open file Open file @@ -618,518 +403,363 @@ ListLayout - - Show playing cues - Show playing cues - - - - Show dB-meters - Show dB-meters - - - - Show seek-bars - Show seek-bars - - - - Show accurate time - Show accurate time - - - - Auto-select next cue - Auto-select next cue - - - - Edit cue - Edit cue - - - - Remove - Remove - - - - Select - Select - - - - Stop all - Stop all - - - - Pause all - Pause all - - - - Restart all - Restart all - - - - Stop - Stop - - - - Restart - Restart - - - - Default behaviors - Default behaviors - - - - At list end: - At list end: - - - - Go key: - Go key: - - - - Interrupt all - Interrupt all - - - - Fade-Out all - Fade-Out all - - - - Fade-In all - Fade-In all - - - - Use fade - Use fade - - - - Stop Cue - Stop Cue - - - - Pause Cue - Pause Cue - - - - Restart Cue - Restart Cue - - - - Interrupt Cue - Interrupt Cue - - - + Stop All Stop All - + Pause All Pause All - - Restart All - Restart All - - - + Interrupt All Interrupt All - + Use fade (global actions) Use fade (global actions) - + Resume All Resume All - - ListLayoutHeader - - - Cue - Cue - - - - Pre wait - Pre wait - - - - Action - Action - - - - Post wait - Post wait - - - - ListLayoutInfoPanel - - - Cue name - Cue name - - - - Cue description - Cue description - - Logging - - Information - Information - - - + Debug Debug - + Warning Warning - + Error Error - - Details: - Details: - - - + Dismiss all Dismiss all - + Show details Show details - + Linux Show Player - Log Viewer Linux Show Player - Log Viewer - + Info Info - + Critical Critical - + Time Time - + Milliseconds Milliseconds - + Logger name Logger name - + Level Level - + Message Message - + Function Function - + Path name Path name - + File name File name - + Line no. Line no. - + Module Module - + Process ID Process ID - + Process name Process name - + Thread ID Thread ID - + Thread name Thread name + + + Exception info + Exception info + MainWindow - + &File &File - + New session New session - + Open Open - + Save session Save session - + Preferences Preferences - + Save as Save as - + Full Screen Full Screen - + Exit Exit - + &Edit &Edit - + Undo Undo - + Redo Redo - + Select all Select all - + Select all media cues Select all media cues - + Deselect all Deselect all - + CTRL+SHIFT+A CTRL+SHIFT+A - + Invert selection Invert selection - + CTRL+I CTRL+I - + Edit selected Edit selected - + CTRL+SHIFT+E CTRL+SHIFT+E - + &Layout &Layout - + &Tools &Tools - + Edit selection Edit selection - + &About &About - + About About - + About Qt About Qt - - Undone: - Undone: - - - - Redone: - Redone: - - - + Close session Close session - + The current session is not saved. The current session is not saved. - + Discard the changes? Discard the changes? - - MediaCueMenus - - - Audio cue (from file) - Audio cue (from file) - - - - Select media files - Select media files - - MediaCueSettings - + Start time Start time - + Stop position of the media Stop position of the media - + Stop time Stop time - + Start position of the media Start position of the media - + Loop Loop + + + PluginsError + + + Failed to load "{}" + Failed to load "{}" + + + + Failed to terminate plugin: "{}" + Failed to terminate plugin: "{}" + + + + the requested plugin is not loaded: {} + the requested plugin is not loaded: {} + + + + PluginsInfo + + + Plugin loaded: "{}" + Plugin loaded: "{}" + - - Repetition after first play (-1 = infinite) - Repetition after first play (-1 = infinite) + + Plugin terminated: "{}" + Plugin terminated: "{}" + + + + PluginsWarning + + + Cannot satisfy dependencies for: {} + Cannot satisfy dependencies for: {} QColorButton - + Right click to reset Right click to reset @@ -1137,44 +767,49 @@ SettingsPageName - + Appearance Appearance - + General General - + Cue Cue - + Cue Settings Cue Settings - + Plugins Plugins - + Behaviours Behaviours - + Pre/Post Wait Pre/Post Wait - + Fade In/Out Fade In/Out + + + Layouts + Layouts + diff --git a/lisp/i18n/ts/de_DE/list_layout.ts b/lisp/i18n/ts/de_DE/list_layout.ts index d1b2d7ad6..9012c2cc6 100644 --- a/lisp/i18n/ts/de_DE/list_layout.ts +++ b/lisp/i18n/ts/de_DE/list_layout.ts @@ -4,7 +4,7 @@ LayoutDescription - + Organize the cues in a list Organize the cues in a list @@ -12,163 +12,191 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue SHIFT + Space or Double-Click to edit a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them To move cues drag them + + LayoutName + + + List Layout + List Layout + + ListLayout - + Default behaviors Default behaviors - + Show playing cues Show playing cues - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue - + Enable selection mode Enable selection mode - - Go key: - Go key: - - - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - - Edit selected cues - Edit selected cues - - - + Remove cue Remove cue - - Remove selected cues - Remove selected cues - - - + Selection mode Selection mode - + Pause all Pause all - + Stop all Stop all - + Interrupt all Interrupt all - + Resume all Resume all - + Fade-Out all Fade-Out all - + Fade-In all Fade-In all + + + GO key: + GO key: + + + + GO action: + GO action: + + + + GO delay (ms): + GO delay (ms): + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove selected + Remove selected + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait @@ -176,14 +204,22 @@ ListLayoutInfoPanel - + Cue name Cue name - + Cue description Cue description + + SettingsPageName + + + List Layout + List Layout + + diff --git a/lisp/i18n/ts/de_DE/media_info.ts b/lisp/i18n/ts/de_DE/media_info.ts index 7b1f2de15..65ad21a55 100644 --- a/lisp/i18n/ts/de_DE/media_info.ts +++ b/lisp/i18n/ts/de_DE/media_info.ts @@ -4,32 +4,27 @@ MediaInfo - + Media Info Media Info - - Error - Error - - - + No info to display No info to display - + Info Info - + Value Value - + Warning Warning diff --git a/lisp/i18n/ts/de_DE/midi.ts b/lisp/i18n/ts/de_DE/midi.ts index 0082ceb85..58ac9f26c 100644 --- a/lisp/i18n/ts/de_DE/midi.ts +++ b/lisp/i18n/ts/de_DE/midi.ts @@ -1,20 +1,144 @@ + + MIDICue + + + MIDI Message + MIDI Message + + + + Message type + Message type + + + + MIDIMessageAttr + + + Channel + Channel + + + + Note + Note + + + + Velocity + Velocity + + + + Control + Control + + + + Program + Program + + + + Value + Value + + + + Song + Song + + + + Pitch + Pitch + + + + Position + Position + + + + MIDIMessageType + + + Note ON + Note ON + + + + Note OFF + Note OFF + + + + Polyphonic After-touch + Polyphonic After-touch + + + + Control/Mode Change + Control/Mode Change + + + + Program Change + Program Change + + + + Channel After-touch + Channel After-touch + + + + Pitch Bend Change + Pitch Bend Change + + + + Song Select + Song Select + + + + Song Position + Song Position + + + + Start + Start + + + + Stop + Stop + + + + Continue + Continue + + MIDISettings - + MIDI default devices MIDI default devices - + Input Input - + Output Output @@ -22,7 +146,7 @@ SettingsPageName - + MIDI settings MIDI settings diff --git a/lisp/i18n/ts/de_DE/network.ts b/lisp/i18n/ts/de_DE/network.ts index d9b2ba83d..1b8daf435 100644 --- a/lisp/i18n/ts/de_DE/network.ts +++ b/lisp/i18n/ts/de_DE/network.ts @@ -1,45 +1,69 @@ + + APIServerInfo + + + Stop serving network API + Stop serving network API + + + + ApiServerError + + + Network API server stopped working. + Network API server stopped working. + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + NetworkDiscovery - + Host discovery Host discovery - + Manage hosts Manage hosts - + Discover hosts Discover hosts - + Manually add a host Manually add a host - + Remove selected host Remove selected host - + Remove all host Remove all host - + Address Address - + Host IP Host IP diff --git a/lisp/i18n/ts/de_DE/osc.ts b/lisp/i18n/ts/de_DE/osc.ts index 9eafde67a..f30769d5a 100644 --- a/lisp/i18n/ts/de_DE/osc.ts +++ b/lisp/i18n/ts/de_DE/osc.ts @@ -1,25 +1,54 @@ + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + OscSettings - + OSC Settings OSC Settings - + Input Port: Input Port: - + Output Port: Output Port: - + Hostname: Hostname: @@ -27,7 +56,7 @@ SettingsPageName - + OSC settings OSC settings diff --git a/lisp/i18n/ts/de_DE/presets.ts b/lisp/i18n/ts/de_DE/presets.ts index 09261a2b3..18a7634b5 100644 --- a/lisp/i18n/ts/de_DE/presets.ts +++ b/lisp/i18n/ts/de_DE/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Create Cue - + Load on selected Cues Load on selected Cues @@ -17,132 +17,112 @@ Presets - + Presets Presets - - Load preset - Load preset - - - + Save as preset Save as preset - + Cannot scan presets Cannot scan presets - + Error while deleting preset "{}" Error while deleting preset "{}" - + Cannot load preset "{}" Cannot load preset "{}" - + Cannot save preset "{}" Cannot save preset "{}" - + Cannot rename preset "{}" Cannot rename preset "{}" - + Select Preset Select Preset - - Preset already exists, overwrite? - Preset already exists, overwrite? - - - + Preset name Preset name - + Add Add - + Rename Rename - + Edit Edit - + Remove Remove - + Export selected Export selected - + Import Import - + Warning Warning - + The same name is already used! The same name is already used! - - Cannot create a cue from this preset: {} - Cannot create a cue from this preset: {} - - - + Cannot export correctly. Cannot export correctly. - - Some presets already exists, overwrite? - Some presets already exists, overwrite? - - - + Cannot import correctly. Cannot import correctly. - + Cue type Cue type - + Load on cue Load on cue - + Load on selected cues Load on selected cues diff --git a/lisp/i18n/ts/de_DE/rename_cues.ts b/lisp/i18n/ts/de_DE/rename_cues.ts index 03684c10d..cb57b2b58 100644 --- a/lisp/i18n/ts/de_DE/rename_cues.ts +++ b/lisp/i18n/ts/de_DE/rename_cues.ts @@ -4,92 +4,72 @@ RenameCues - + Rename Cues Rename Cues - + Rename cues Rename cues - + Current Current - + Preview Preview - + Capitalize Capitalize - + Lowercase Lowercase - + Uppercase Uppercase - + Remove Numbers Remove Numbers - + Add numbering Add numbering - + Reset Reset - - Rename all cue. () in regex below usable with $0, $1 ... - Rename all cue. () in regex below usable with $0, $1 ... - - - + Type your regex here: Type your regex here: - + Regex help Regex help + + + RenameUiDebug - - You can use Regexes to rename your cues. - -Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... -In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. - -Exemple : -^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. -Only the numbers are between parenthesis and will be usable with $0 in the first line. - -For more information about Regexes, consult python documentation - You can use Regexes to rename your cues. - -Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... -In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. - -Exemple : -^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. -Only the numbers are between parenthesis and will be usable with $0 in the first line. - -For more information about Regexes, consult python documentation + + Regex error: Invalid pattern + Regex error: Invalid pattern diff --git a/lisp/i18n/ts/de_DE/replay_gain.ts b/lisp/i18n/ts/de_DE/replay_gain.ts index aced4861d..2de3dc767 100644 --- a/lisp/i18n/ts/de_DE/replay_gain.ts +++ b/lisp/i18n/ts/de_DE/replay_gain.ts @@ -4,49 +4,80 @@ ReplayGain - + ReplayGain / Normalization ReplayGain / Normalization - + Calculate Calculate - + Reset all Reset all - + Reset selected Reset selected - + Threads number Threads number - + Apply only to selected media Apply only to selected media - + ReplayGain to (dB SPL) ReplayGain to (dB SPL) - + Normalize to (dB) Normalize to (dB) - + Processing files ... Processing files ... + + ReplayGainDebug + + + Applied gain for: {} + Applied gain for: {} + + + + Discarded gain for: {} + Discarded gain for: {} + + + + ReplayGainInfo + + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} + + diff --git a/lisp/i18n/ts/de_DE/synchronizer.ts b/lisp/i18n/ts/de_DE/synchronizer.ts index e5d4b5cc8..03e21668b 100644 --- a/lisp/i18n/ts/de_DE/synchronizer.ts +++ b/lisp/i18n/ts/de_DE/synchronizer.ts @@ -1,73 +1,20 @@ - - SyncPeerDialog - - - Manage connected peers - Manage connected peers - - - - Discover peers - Discover peers - - - - Manually add a peer - Manually add a peer - - - - Remove selected peer - Remove selected peer - - - - Remove all peers - Remove all peers - - - - Address - Address - - - - Peer IP - Peer IP - - - - Error - Error - - - - Already connected - Already connected - - - - Cannot add peer - Cannot add peer - - Synchronizer - + Synchronization Synchronization - + Manage connected peers Manage connected peers - + Show your IP Show your IP @@ -76,10 +23,5 @@ Your IP is: Your IP is: - - - Discovering peers ... - Discovering peers ... - diff --git a/lisp/i18n/ts/de_DE/timecode.ts b/lisp/i18n/ts/de_DE/timecode.ts index d24a06292..ab03e1bf5 100644 --- a/lisp/i18n/ts/de_DE/timecode.ts +++ b/lisp/i18n/ts/de_DE/timecode.ts @@ -4,12 +4,12 @@ SettingsPageName - + Timecode Settings Timecode Settings - + Timecode Timecode @@ -17,92 +17,48 @@ Timecode - - Cannot send timecode. - Cannot send timecode. - - - - OLA has stopped. - OLA has stopped. - - - + Cannot load timecode protocol: "{}" Cannot load timecode protocol: "{}" + + + TimecodeError - - Cannot send timecode. -OLA has stopped. - Cannot send timecode. -OLA has stopped. + + Cannot send timecode. + Cannot send timecode. TimecodeSettings - - OLA Timecode Settings - OLA Timecode Settings - - - - Enable Plugin - Enable Plugin - - - - High-Resolution Timecode - High-Resolution Timecode - - - + Timecode Format: Timecode Format: - - OLA status - OLA status - - - - OLA is not running - start the OLA daemon. - OLA is not running - start the OLA daemon. - - - + Replace HOURS by a static track number Replace HOURS by a static track number - - Enable ArtNet Timecode - Enable ArtNet Timecode - - - + Track number Track number - - To send ArtNet Timecode you need to setup a running OLA session! - To send ArtNet Timecode you need to setup a running OLA session! - - - + Enable Timecode Enable Timecode - + Timecode Settings Timecode Settings - + Timecode Protocol: Timecode Protocol: diff --git a/lisp/i18n/ts/de_DE/triggers.ts b/lisp/i18n/ts/de_DE/triggers.ts index 6053b4d9a..43694dfcd 100644 --- a/lisp/i18n/ts/de_DE/triggers.ts +++ b/lisp/i18n/ts/de_DE/triggers.ts @@ -4,22 +4,22 @@ CueTriggers - + Started Started - + Paused Paused - + Stopped Stopped - + Ended Ended @@ -27,7 +27,7 @@ SettingsPageName - + Triggers Triggers @@ -35,27 +35,27 @@ TriggersSettings - + Add Add - + Remove Remove - + Trigger Trigger - + Cue Cue - + Action Action diff --git a/lisp/i18n/ts/es_ES/action_cues.ts b/lisp/i18n/ts/es_ES/action_cues.ts index 3a2a70af3..be5a40c53 100644 --- a/lisp/i18n/ts/es_ES/action_cues.ts +++ b/lisp/i18n/ts/es_ES/action_cues.ts @@ -1,25 +1,41 @@ + + ActionCuesDebug + + + Registered cue: "{}" + Registered cue: "{}" + + + + ActionsCuesError + + + Cannot create cue {} + Cannot create cue {} + + CollectionCue - + Add Añadir - + Remove Eliminar - + Cue Cue - + Action Acción @@ -27,50 +43,35 @@ CommandCue - - Process ended with an error status. - El proceso terminó con un estado de error. - - - - Exit code: - Código de salida: - - - + Command Comando - + Command to execute, as in a shell Comando a ejecutar, como en una línea de comando - + Discard command output Descartar salida de comando - + Ignore command errors Ignorar errores de comando - + Kill instead of terminate Matar en vez de terminar - - - Command cue ended with an error status. Exit code: {} - Cue de comando terminó con un estado de error. Código de Salida: {} - Cue Name - + OSC Settings Configuración de OSC @@ -78,42 +79,42 @@ CueName - + Command Cue Cue de comando - + MIDI Cue Cue de MIDI - + Volume Control Control de volumen - + Seek Cue Cue de búsqueda - + Collection Cue Cue de Colección - + Stop-All Detener Todo - + Index Action Índice de Acciones - + OSC Cue Cue OSC @@ -121,68 +122,55 @@ IndexActionCue - + Index Índice - + Use a relative index Usar índice relativo - + Target index Índice de Target - + Action Acción - + No suggestion No hay sugerencias - + Suggested cue name Nombre sugerido del Cue - - MIDICue - - - MIDI Message - Mensaje MIDI - - - - Message type - Tipo de mensaje - - Osc Cue - + Type Tipo - + Argument Argumento - + FadeTo Fundido a - + Fade Fundido @@ -190,75 +178,88 @@ OscCue - - Error during cue execution. - Error durante la ejecución del cue. - - - + OSC Message Mensaje OSC - + Add Añadir - + Remove Eliminar - + Test Probar - + OSC Path: (example: "/path/to/something") Dirección OSC: (ejemplo: "/path/to/something") - + Fade Fundido - + Time (sec) Tiempo (seg) - + Curve Curva + + OscCueError + + + Could not parse argument list, nothing sent + Could not parse argument list, nothing sent + + + + Error while parsing arguments, nothing sent + Error while parsing arguments, nothing sent + + + + Error during cue execution. + Error during cue execution. + + SeekCue - + Cue Cue - + Click to select Click para seleccionar - + Not selected No seleccionado - + Seek Búsqueda - + Time to reach Tiempo a alcanzar @@ -266,37 +267,37 @@ SettingsPageName - + Command Comando - + MIDI Settings Ajustes MIDI - + Volume Settings Ajustes de volumen - + Seek Settings Ajustes de búsqueda - + Edit Collection Editar Colección - + Action Settings Ajustes de Acción - + Stop Settings Ajustes de Detención @@ -304,7 +305,7 @@ StopAll - + Stop Action Detener Acción @@ -312,34 +313,37 @@ VolumeControl - - Error during cue execution - Error durante la ejecución del cue - - - + Cue Cue - + Click to select Click para seleccionar - + Not selected No seleccionado - + Volume to reach Volumen a alcanzar - + Fade Fundido + + VolumeControlError + + + Error during cue execution. + Error during cue execution. + + diff --git a/lisp/i18n/ts/es_ES/cart_layout.ts b/lisp/i18n/ts/es_ES/cart_layout.ts index e3a75b446..3b0ed3d92 100644 --- a/lisp/i18n/ts/es_ES/cart_layout.ts +++ b/lisp/i18n/ts/es_ES/cart_layout.ts @@ -4,120 +4,115 @@ CartLayout - + Default behaviors Comportamiento por defecto - + Countdown mode Modo de cuenta regresiva - + Show seek-bars Mostrar barra de búsqueda - + Show dB-meters Mostrar medidores de dB - + Show accurate time Mostrar tiempo preciso - + Show volume Mostrar volumen - - Automatically add new page - Añadir página automáticamente - - - + Grid size Tamaño de cuadrícula - - Number of columns - Número de columnas - - - - Number of rows - Número de filas - - - + Play Reproducir - + Pause Pausa - + Stop Detener - + Reset volume Reestablecer volumen - + Add page Añadir página - + Add pages Añadir páginas - + Remove current page Eliminar página actual - + Number of Pages: Número de páginas: - + Page {number} Página {number} - + Warning Advertencia - + Every cue in the page will be lost. Todos los cues en la página se perderán. - + Are you sure to continue? ¿Está seguro de continuar? + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + LayoutDescription - + Organize cues in grid like pages Organizar cues en cuadrícula como páginas @@ -125,52 +120,68 @@ LayoutDetails - + Click a cue to run it Hacer click en un cue para ejecutarlo - + SHIFT + Click to edit a cue SHIFT + Click para editar un cue - + CTRL + Click to select a cue CTRL + Click para seleccionar un cue - + To copy cues drag them while pressing CTRL Para copiar cues, arrástrelos mientras presiona CTRL - + To move cues drag them while pressing SHIFT Para mover Cues arrástrelos mientras presiona SHIFT + + LayoutName + + + Cart Layout + Cart Layout + + ListLayout - + Edit cue Editar cue - + Edit selected cues Editar los cues seleccionados - + Remove cue Eliminar cue - + Remove selected cues Eliminar los cues seleccionados + + SettingsPageName + + + Cart Layout + Cart Layout + + diff --git a/lisp/i18n/ts/es_ES/controller.ts b/lisp/i18n/ts/es_ES/controller.ts index 31449211a..fa43810f2 100644 --- a/lisp/i18n/ts/es_ES/controller.ts +++ b/lisp/i18n/ts/es_ES/controller.ts @@ -4,7 +4,7 @@ Controller - + Cannot load controller protocol: "{}" No se puede cargar el protocolo de control: "{}" @@ -12,17 +12,17 @@ ControllerKeySettings - + Key Tecla - + Action Acción - + Shortcuts Atajos de teclado @@ -30,47 +30,47 @@ ControllerMidiSettings - + MIDI MIDI - + Type Tipo - + Channel Canal - + Note Nota - + Action Acción - + Filter "note on" Filtrar "Nota ON" - + Filter "note off" Filtrar "Nota OFF" - + Capture Captura - + Listening MIDI messages ... Escuchando mensajes MIDI ... @@ -78,57 +78,52 @@ ControllerOscSettings - + OSC Message Mensaje OSC - - OSC Path: (example: "/path/to/something") - Dirección OSC: (ejemplo: "/path/to/something") - - - + OSC OSC - + Path Ruta - + Types Tipos - + Arguments Argumentos - + Actions Acciones - + OSC Capture Captura OSC - + Add Añadir - + Remove Eliminar - + Capture Capturar @@ -136,25 +131,78 @@ ControllerSettings - + Add Añadir - + Remove Eliminar + + GlobalAction + + + Go + Go + + + + Reset + Reset + + + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + Osc Cue - + Type Tipo - + Argument Argumento @@ -162,12 +210,12 @@ OscCue - + Add Añadir - + Remove Eliminar @@ -175,24 +223,29 @@ SettingsPageName - + Cue Control Control de Cue - + MIDI Controls Controles MIDI - + Keyboard Shortcuts Atajos de teclado - + OSC Controls Controles OSC + + + Layout Controls + Layout Controls + diff --git a/lisp/i18n/ts/es_ES/gst_backend.ts b/lisp/i18n/ts/es_ES/gst_backend.ts index 84a56db2a..93b776f0d 100644 --- a/lisp/i18n/ts/es_ES/gst_backend.ts +++ b/lisp/i18n/ts/es_ES/gst_backend.ts @@ -4,60 +4,55 @@ AlsaSinkSettings - + ALSA device Dispositivo ALSA - - - ALSA devices, as defined in an asound configuration file - Dispositivos ALSA, según están definidos en un archivo de configuración asound - AudioDynamicSettings - + Compressor Compresor - + Expander Expansor - + Soft Knee Suave - + Hard Knee Duro - + Compressor/Expander Compresor/Expansor - + Type Tipo - + Curve Shape Forma de la curva - + Ratio Proporción - + Threshold (dB) Umbral (dB) @@ -65,22 +60,22 @@ AudioPanSettings - + Audio Pan Paneo de audio - + Center Centro - + Left Izquierda - + Right Derecha @@ -88,22 +83,22 @@ DbMeterSettings - + DbMeter settings Ajustes del medidor dB - + Time between levels (ms) Intérvalo entre niveles (ms) - + Peak ttl (ms) Picos ttl (ms) - + Peak falloff (dB/sec) Decaimiento de picos (dB/seg.) @@ -111,7 +106,7 @@ Equalizer10Settings - + 10 Bands Equalizer (IIR) Equalizador de 10 bandas (IIR) @@ -119,28 +114,44 @@ GstBackend - + Audio cue (from file) Cue de audio (desde archivo) - + Select media files Seleccionar archivos de medios + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + GstMediaSettings - + Change Pipeline Cambiar Pipeline + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + GstPipelineEdit - + Edit Pipeline Editar Pipeline @@ -148,7 +159,7 @@ GstSettings - + Pipeline Pipeline @@ -156,32 +167,32 @@ JackSinkSettings - + Connections Conexiones - + Edit connections Editar conexiones - + Output ports Puertos de salida - + Input ports Puertos de entrada - + Connect Conectar - + Disconnect Desconectar @@ -189,77 +200,77 @@ MediaElementName - + Compressor/Expander Compresor/Expansor - + Audio Pan Paneo de audio - + PulseAudio Out Salida de PulseAudio - + Volume Volumen - + dB Meter Medidor de dB - + System Input Entrada de sistema - + ALSA Out Salida ALSA - + JACK Out Salida JACK - + Custom Element Elemento personalizado - + System Out Salida de sistema - + Pitch Tono - + URI Input Entrada URI - + 10 Bands Equalizer Ecualizador de 10 bandas - + Speed Velocidad - + Preset Input Preset de entradas @@ -267,12 +278,12 @@ PitchSettings - + Pitch Tono - + {0:+} semitones {0:+} semitonos @@ -280,7 +291,7 @@ PresetSrcSettings - + Presets Preajustes @@ -288,17 +299,12 @@ SettingsPageName - - GStreamer settings - Ajustes de GStreamer - - - + Media Settings Ajustes de Media - + GStreamer GStreamer @@ -306,7 +312,7 @@ SpeedSettings - + Speed Velocidad @@ -314,42 +320,42 @@ UriInputSettings - + Source Origen - + Find File Encontrar archivo - + Buffering Pre-Carga - + Use Buffering Usar Buffering - + Attempt download on network streams Intentar descargar en streams de red - + Buffer size (-1 default value) Tamaño del buffer (-1 valor por defecto) - + Choose file Elegir archivo - + All files Todos los archivos @@ -357,12 +363,12 @@ UserElementSettings - + User defined elements Elementos definidos por el usuario - + Only for advanced user! ¡Sólo para usuarios avanzados! @@ -370,17 +376,17 @@ VolumeSettings - + Volume Volumen - + Normalized volume Volumen normalizado - + Reset Reestablecer diff --git a/lisp/i18n/ts/es_ES/lisp.ts b/lisp/i18n/ts/es_ES/lisp.ts index cdeafc254..b5ea42d98 100644 --- a/lisp/i18n/ts/es_ES/lisp.ts +++ b/lisp/i18n/ts/es_ES/lisp.ts @@ -4,22 +4,22 @@ About - + Authors Autores - + Contributors Contribuidores - + Translators Traductores - + About Linux Show Player Acerca de Linux Show Player @@ -27,42 +27,32 @@ AboutDialog - - Linux Show Player is a cue-player designed for stage productions. - Linux Show Player es un reproductor de Cues diseñado para producciones escénicas. - - - + Web site Sitio Web - - Users group - Grupo de usuarios - - - + Source code Código fuente - + Info Información - + License Licencia - + Contributors Contibuidores - + Discussion Discusión @@ -70,12 +60,12 @@ Actions - + Undo: {} Deshacer: {} - + Redo: {} Rehacer: {} @@ -83,7 +73,7 @@ AppConfiguration - + LiSP preferences Preferencias de LiSP @@ -91,243 +81,109 @@ AppGeneralSettings - - Startup layout - Layout de inicio - - - - Use startup dialog - Usar diálogo de inicio - - - - Application theme - Tema de la aplicación - - - + Default layout Diseño por defecto - + Enable startup layout selector Activar a selector de diseño de inicio - + Application themes Temas de la Aplicación - - UI theme - Tema de interfaz de usuario + + UI theme: + UI theme: - - Icons theme - Tema de iconos + + Icons theme: + Icons theme: - CartLayout - - - Add page - Añadir página - - - - Add pages - Añadir páginas - - - - Remove current page - Eliminar página actual - - - - Countdown mode - Modo de cuenta regresiva - - - - Show seek-bars - Mostrar barra de búsqueda - - - - Show dB-meters - Mostrar medidores de dB - - - - Show volume - Mostrar volumen - - - - Show accurate time - Mostrar tiempo preciso - - - - Edit cue - Editar cue - - - - Remove - Eliminar - - - - Select - Seleccionar - - - - Play - Reproducir - - - - Pause - Pausa - - - - Stop - Detener - - - - Reset volume - Reestablecer volumen - - - - Number of Pages: - Número de páginas: - - - - Warning - Advertencia - - - - Every cue in the page will be lost. - Todos los cues en la página se perderán. - - - - Are you sure to continue? - ¿Está seguro de continuar? - + ApplicationError - - Page - Página - - - - Default behaviors - Comportamientos por defecto - - - - Automatically add new page - Añadir página automáticamente - - - - Grid size - Tamaño de cuadrícula + + Startup error + Startup error + + + ConfigurationDebug - - Number of columns - Número de columnas + + Configuration written at {} + Configuration written at {} + + + ConfigurationInfo - - Number of rows - Número de filas + + New configuration installed at {} + New configuration installed at {} CueAction - + Default Por defecto - + Pause Pausa - + Start Inicio - + Stop Detener - - FadeInStart - Inicio Fundido de entrada - - - - FadeOutStop - Detener fundido de salida - - - - FadeOutPause - Pausar fundido de salida - - - + Faded Start Iniciar con fundido - + Faded Resume Reanudar con fundido - + Faded Pause Pausar con fundido - + Faded Stop Detener con fundido - + Faded Interrupt Interrupción con fundido - + Resume Reanudar - + Do Nothing No hacer nada @@ -335,12 +191,12 @@ CueActionLog - + Cue settings changed: "{}" Ajustes de Cue cambiados: "{}" - + Cues settings changed. Ajustes de Cue han cambiado. @@ -348,42 +204,42 @@ CueAppearanceSettings - + The appearance depends on the layout La apariencia depende del Layout - + Cue name Nombre del Cue - + NoName Sin nombre - + Description/Note Descripción/Notas - + Set Font Size Establecer el tamaño de la fuente - + Color Color - + Select background color Seleccionar el color de fondo - + Select font color Seleccionar el color de la fuente @@ -391,7 +247,7 @@ CueName - + Media Cue Cue de Media @@ -399,100 +255,85 @@ CueNextAction - + Do Nothing No hacer nada + + + Trigger after the end + Trigger after the end + - Auto Follow - Auto seguir + Trigger after post wait + Trigger after post wait - - Auto Next - Auto Siguiente + + Select after the end + Select after the end + + + + Select after post wait + Select after post wait CueSettings - + Pre wait Pre Espera - + Wait before cue execution Wait antes de la ejecución del cue - + Post wait Post Espera - + Wait after cue execution Wait después de la ejecución del cue - + Next action Siguiente acción - - Interrupt Fade - Interrumpir Fundido - - - - Fade Action - Acción de Fundido - - - - Behaviours - Comportamientos - - - - Pre/Post Wait - Pre/Post Espera - - - - Fade In/Out - Fundido de Ingreso/Salida - - - + Start action Iniciar Acción - + Default action to start the cue Acción predeterminada para iniciar el Cue - + Stop action Detener Acción - + Default action to stop the cue Acción predeterminada para detener el Cue - + Interrupt fade Interrumpir Fundido - + Fade actions Acciones de Fundido @@ -500,17 +341,17 @@ Fade - + Linear Lineal - + Quadratic Cuadrático - + Quadratic2 Cuadrático2 @@ -518,99 +359,43 @@ FadeEdit - - Duration (sec) - Duración (seg.) + + Duration (sec): + Duration (sec): - - Curve - Curva + + Curve: + Curve: FadeSettings - + Fade In Fundido de ingreso - + Fade Out Fundido de salida - - LayoutDescription - - - Organize cues in grid like pages - Organizar cues en cuadrícula como páginas - - - - Organize the cues in a list - Organizar cues en una lista - - - - LayoutDetails - - - Click a cue to run it - Hacer click en un cue para ejecutarlo - - - - SHIFT + Click to edit a cue - SHIFT + Click para editar un cue - - - - CTRL + Click to select a cue - CTRL + Click para seleccionar un cue - - - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Barra espaciadora o Doble click para editar un cue - - - - To copy cues drag them while pressing CTRL - Para copiar cues arrástrelos mientras presiona CTRL - - - - To copy cues drag them while pressing SHIFT - Para copiar Cues arrástrelos mientras presiona SHIFT - - - - CTRL + Left Click to select cues - CTRL + Click Izquierdo para seleccionar Cues - - - - To move cues drag them - Para mover Cues arrástrelos - - LayoutSelect - + Layout selection Selección de Layout - + Select layout Seleccionar Layout - + Open file Abrir archivo @@ -618,518 +403,363 @@ ListLayout - - Show playing cues - Mostrar los cues en ejecución - - - - Show dB-meters - Mostrar medidor de dB - - - - Show seek-bars - Mostrar barras de búsqueda - - - - Show accurate time - Mostrar tiempo preciso - - - - Auto-select next cue - Seleccionar automáticamente el siguiente cue - - - - Edit cue - Editar cue - - - - Remove - Eliminar - - - - Select - Seleccionar - - - - Stop all - Detener todo - - - - Pause all - Pausar todo - - - - Restart all - Reiniciar todo - - - - Stop - Detener - - - - Restart - Reiniciar - - - - Default behaviors - Comportamiento por defecto - - - - At list end: - Al final de la lista: - - - - Go key: - Tecla de Go: - - - - Interrupt all - Interrumpir todo - - - - Fade-Out all - Fundido de Salida para Todo - - - - Fade-In all - Fundido de Entrada para Todo - - - - Use fade - Usar fundido - - - - Stop Cue - Detener Cue - - - - Pause Cue - Pausar Cue - - - - Restart Cue - Reiniciar Cue - - - - Interrupt Cue - Interrumpir Cue - - - + Stop All Detener Todo - + Pause All Pausar Todo - - Restart All - Reiniciar Todo - - - + Interrupt All Interrumpir todo - + Use fade (global actions) Usar Fundido (acciones globales) - + Resume All Reanudar todo - - ListLayoutHeader - - - Cue - Cue - - - - Pre wait - Pre espera - - - - Action - Acción - - - - Post wait - Post espera - - - - ListLayoutInfoPanel - - - Cue name - Nombre del Cue - - - - Cue description - Descripción del cue - - Logging - - Information - Información - - - + Debug Depurar - + Warning Advertencia - + Error Error - - Details: - Detalles: - - - + Dismiss all Descartar todo - + Show details Mostrar detalles - + Linux Show Player - Log Viewer Linux Show Player - Visor de registro - + Info Información - + Critical Crítico - + Time Tiempo - + Milliseconds Milisegundos - + Logger name Número de Logger - + Level Nivel - + Message Mensaje - + Function Función - + Path name Nombre de la ruta - + File name Nombre del archivo - + Line no. Línea no. - + Module Módulo - + Process ID ID del proceso - + Process name Nombre del proceso - + Thread ID ID del hilo - + Thread name Nombre del hilo + + + Exception info + Exception info + MainWindow - + &File &Archivo - + New session Nueva sesión - + Open Abrir - + Save session Guardar sesión - + Preferences Preferencias - + Save as Guardar como - + Full Screen Pantalla completa - + Exit Salir - + &Edit &Editar - + Undo Deshacer - + Redo Rehacer - + Select all Seleccionar todo - + Select all media cues Seleccionar todos los Media cues - + Deselect all Deseleccionar todo - + CTRL+SHIFT+A CTRL + SHIFT + A - + Invert selection Invertir selección - + CTRL+I CTRL + I - + Edit selected Editar seleccionado - + CTRL+SHIFT+E CTRL + SHIFT + E - + &Layout &Layout - + &Tools &Herramientas - + Edit selection Editar selección - + &About &Acerca - + About Acerca - + About Qt Acerca de Qt - - Undone: - Deshacer: - - - - Redone: - Rehacer: - - - + Close session Cerrar sesión - + The current session is not saved. La sección actual no se ha guardado. - + Discard the changes? ¿Descartar cambios? - - MediaCueMenus - - - Audio cue (from file) - Cue de audio (desde archivo) - - - - Select media files - Seleccionar archivos de medios - - MediaCueSettings - + Start time Tiempo de inicio - + Stop position of the media Posición de detención del media - + Stop time Tiempo de detención - + Start position of the media Posición de inicio del media - + Loop Bucle + + + PluginsError + + + Failed to load "{}" + Failed to load "{}" + + + + Failed to terminate plugin: "{}" + Failed to terminate plugin: "{}" + + + + the requested plugin is not loaded: {} + the requested plugin is not loaded: {} + + + + PluginsInfo + + + Plugin loaded: "{}" + Plugin loaded: "{}" + - - Repetition after first play (-1 = infinite) - Repeticiones después de la primera ejecución (-1 = infinito) + + Plugin terminated: "{}" + Plugin terminated: "{}" + + + + PluginsWarning + + + Cannot satisfy dependencies for: {} + Cannot satisfy dependencies for: {} QColorButton - + Right click to reset Click derecho para reiniciar @@ -1137,44 +767,49 @@ SettingsPageName - + Appearance Apariencia - + General General - + Cue Cue - + Cue Settings Ajustes de Cue - + Plugins Plugins - + Behaviours Comportamientos - + Pre/Post Wait Pre/Post Espera - + Fade In/Out Fundido de Ingreso/Salida + + + Layouts + Layouts + diff --git a/lisp/i18n/ts/es_ES/list_layout.ts b/lisp/i18n/ts/es_ES/list_layout.ts index 56a0186fe..5329c5ce2 100644 --- a/lisp/i18n/ts/es_ES/list_layout.ts +++ b/lisp/i18n/ts/es_ES/list_layout.ts @@ -4,7 +4,7 @@ LayoutDescription - + Organize the cues in a list Organizar cues en una lista @@ -12,163 +12,191 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue SHIFT + Barra espaciadora o Doble click para editar un cue - + To copy cues drag them while pressing CTRL Para copiar cues, arrástrelos mientras presiona CTRL - + To move cues drag them Para mover Cues arrástrelos + + LayoutName + + + List Layout + List Layout + + ListLayout - + Default behaviors Comportamientos por defecto - + Show playing cues Mostrar los cues en ejecución - + Show dB-meters Mostrar medidores de dB - + Show accurate time Mostrar tiempo preciso - + Show seek-bars Mostrar barras de búsqueda - + Auto-select next cue Seleccionar automáticamente el siguiente cue - + Enable selection mode Habilitar modo de selección - - Go key: - Tecla de Go: - - - + Use fade (buttons) Usar fundido (botones) - + Stop Cue Detener Cue - + Pause Cue Pausar Cue - + Resume Cue Reanudar Cue - + Interrupt Cue Interrumpir Cue - + Edit cue Editar cue - - Edit selected cues - Editar los cues seleccionados - - - + Remove cue Eliminar cue - - Remove selected cues - Eliminar los cues seleccionados - - - + Selection mode Modo de selección - + Pause all Pausar todo - + Stop all Detener todo - + Interrupt all Interrumpir todo - + Resume all Continuar todas - + Fade-Out all Fundido de Salida para Todo - + Fade-In all Fundido de Entrada para Todo + + + GO key: + GO key: + + + + GO action: + GO action: + + + + GO delay (ms): + GO delay (ms): + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove selected + Remove selected + ListLayoutHeader - + Cue Cue - + Pre wait Pre Espera - + Action Acción - + Post wait Post Espera @@ -176,14 +204,22 @@ ListLayoutInfoPanel - + Cue name Nombre del Cue - + Cue description Descripción del cue + + SettingsPageName + + + List Layout + List Layout + + diff --git a/lisp/i18n/ts/es_ES/media_info.ts b/lisp/i18n/ts/es_ES/media_info.ts index e77575f4e..4388a1dc5 100644 --- a/lisp/i18n/ts/es_ES/media_info.ts +++ b/lisp/i18n/ts/es_ES/media_info.ts @@ -4,32 +4,27 @@ MediaInfo - + Media Info Información del Media - - Error - Error - - - + No info to display Ninguna información para mostrar - + Info Información - + Value Valor - + Warning Advertencia diff --git a/lisp/i18n/ts/es_ES/midi.ts b/lisp/i18n/ts/es_ES/midi.ts index efa916b11..f7c7eb28d 100644 --- a/lisp/i18n/ts/es_ES/midi.ts +++ b/lisp/i18n/ts/es_ES/midi.ts @@ -1,20 +1,144 @@ + + MIDICue + + + MIDI Message + MIDI Message + + + + Message type + Message type + + + + MIDIMessageAttr + + + Channel + Channel + + + + Note + Note + + + + Velocity + Velocity + + + + Control + Control + + + + Program + Program + + + + Value + Value + + + + Song + Song + + + + Pitch + Pitch + + + + Position + Position + + + + MIDIMessageType + + + Note ON + Note ON + + + + Note OFF + Note OFF + + + + Polyphonic After-touch + Polyphonic After-touch + + + + Control/Mode Change + Control/Mode Change + + + + Program Change + Program Change + + + + Channel After-touch + Channel After-touch + + + + Pitch Bend Change + Pitch Bend Change + + + + Song Select + Song Select + + + + Song Position + Song Position + + + + Start + Start + + + + Stop + Stop + + + + Continue + Continue + + MIDISettings - + MIDI default devices Dispositivos MIDI por defecto - + Input Entrada - + Output Salida @@ -22,7 +146,7 @@ SettingsPageName - + MIDI settings Ajustes MIDI diff --git a/lisp/i18n/ts/es_ES/network.ts b/lisp/i18n/ts/es_ES/network.ts index 7e796fdb6..6430d1cfa 100644 --- a/lisp/i18n/ts/es_ES/network.ts +++ b/lisp/i18n/ts/es_ES/network.ts @@ -1,45 +1,69 @@ + + APIServerInfo + + + Stop serving network API + Stop serving network API + + + + ApiServerError + + + Network API server stopped working. + Network API server stopped working. + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + NetworkDiscovery - + Host discovery Descubrimiento de host - + Manage hosts Administrar hosts - + Discover hosts Descubrir hosts - + Manually add a host Añadir un host manualmente - + Remove selected host Remover el host seleccionado - + Remove all host Eliminar todos los hosts - + Address Dirección - + Host IP IP del host diff --git a/lisp/i18n/ts/es_ES/osc.ts b/lisp/i18n/ts/es_ES/osc.ts index 2d2fb3f8e..760cff170 100644 --- a/lisp/i18n/ts/es_ES/osc.ts +++ b/lisp/i18n/ts/es_ES/osc.ts @@ -1,25 +1,54 @@ + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + OscSettings - + OSC Settings Configuración de OSC - + Input Port: Puerto de entrada: - + Output Port: Puerto de salida: - + Hostname: Nombre de host: @@ -27,7 +56,7 @@ SettingsPageName - + OSC settings Configuración de OSC diff --git a/lisp/i18n/ts/es_ES/presets.ts b/lisp/i18n/ts/es_ES/presets.ts index 78bd5579f..57b1caee4 100644 --- a/lisp/i18n/ts/es_ES/presets.ts +++ b/lisp/i18n/ts/es_ES/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Crear Cue - + Load on selected Cues Abrir en Cues seleccionadas @@ -17,132 +17,112 @@ Presets - + Presets Preajustes - - Load preset - Cargar Preset - - - + Save as preset Guardar como Preset - + Cannot scan presets No se pueden escanear Presets - + Error while deleting preset "{}" Error al borrar Preset "{}" - + Cannot load preset "{}" No se puede cargar Preset "{}" - + Cannot save preset "{}" No se puede guardar Preset "{}" - + Cannot rename preset "{}" No se puede cambiar el nombre del Preset "{}" - + Select Preset Seleccionar Preset - - Preset already exists, overwrite? - El Preset ya existe, ¿desea sobreescribirlo? - - - + Preset name Nombre del Preset - + Add Añadir - + Rename Cambiar nombre - + Edit Editar - + Remove Eliminar - + Export selected Exportar seleccionados - + Import Importar - + Warning Advertencia - + The same name is already used! ¡El mismo nombre ya está siendo usado! - - Cannot create a cue from this preset: {} - No se puede crear un Cue desde este Preset: {} - - - + Cannot export correctly. No se puede exportar correctamente. - - Some presets already exists, overwrite? - Algunos Presets ya existen, ¿desea sobreescribirlos? - - - + Cannot import correctly. No se puede importar correctamente. - + Cue type Tipo de Cue - + Load on cue Cargar en cue - + Load on selected cues Cargar en cues seleccionados diff --git a/lisp/i18n/ts/es_ES/rename_cues.ts b/lisp/i18n/ts/es_ES/rename_cues.ts index ac6d1718f..990d55965 100644 --- a/lisp/i18n/ts/es_ES/rename_cues.ts +++ b/lisp/i18n/ts/es_ES/rename_cues.ts @@ -4,92 +4,72 @@ RenameCues - + Rename Cues Renombrar cues - + Rename cues Renombrar cues - + Current Actual - + Preview Vista previa - + Capitalize En mayúsculas - + Lowercase Minúsculas - + Uppercase Mayúsculas - + Remove Numbers Eliminar números - + Add numbering Añadir numeración - + Reset Reestablecer - - Rename all cue. () in regex below usable with $0, $1 ... - Cambie el nombre todos cue. () en regex abajo con $0, $1... - - - + Type your regex here: Escribir tu regex aquí: - + Regex help Ayuda de regex + + + RenameUiDebug - - You can use Regexes to rename your cues. - -Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... -In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. - -Exemple : -^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. -Only the numbers are between parenthesis and will be usable with $0 in the first line. - -For more information about Regexes, consult python documentation - Puede utilizar Regexes para renombar cues. - -Insertar expresiones capturadas con regexes en la línea abajo con $0 para el primer paréntesis, $1 para el segundo, etc.... -En la segunda línea, puede usar Regexes Python estándard para igualar expresiones en los nombres originales de los cues. Use paréntesis para capturar partes de una expresión igualada. - -Ejemplo: -^[a-z]([0-9]+) encontrará un carácter en minúsculas ([a-z]), seguido por uno o más números. -Solo los números están entre paréntesis y serán usados con $0 en la primera línea. - -Para mas información acerca de Regexes, consultar la documentación de Python + + Regex error: Invalid pattern + Regex error: Invalid pattern diff --git a/lisp/i18n/ts/es_ES/replay_gain.ts b/lisp/i18n/ts/es_ES/replay_gain.ts index 860473050..b69a782ac 100644 --- a/lisp/i18n/ts/es_ES/replay_gain.ts +++ b/lisp/i18n/ts/es_ES/replay_gain.ts @@ -4,49 +4,80 @@ ReplayGain - + ReplayGain / Normalization ReplayGain / Normalización - + Calculate Calcular - + Reset all Reestablecer todo - + Reset selected Reestablecer seleccionados - + Threads number Número de procesos - + Apply only to selected media Aplicar solamente a los media seleccionados - + ReplayGain to (dB SPL) ReplayGain a (dB SPL) - + Normalize to (dB) Normalizar a (dB) - + Processing files ... Procesando archivos ... + + ReplayGainDebug + + + Applied gain for: {} + Applied gain for: {} + + + + Discarded gain for: {} + Discarded gain for: {} + + + + ReplayGainInfo + + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} + + diff --git a/lisp/i18n/ts/es_ES/synchronizer.ts b/lisp/i18n/ts/es_ES/synchronizer.ts index 8082c153c..b8069d261 100644 --- a/lisp/i18n/ts/es_ES/synchronizer.ts +++ b/lisp/i18n/ts/es_ES/synchronizer.ts @@ -1,73 +1,20 @@ - - SyncPeerDialog - - - Manage connected peers - Gestionar peers conectados - - - - Discover peers - Descubrir peers - - - - Manually add a peer - Añadir un peer manualmente - - - - Remove selected peer - Eliminar el peer seleccionado - - - - Remove all peers - Eliminar todos los peers - - - - Address - Dirección - - - - Peer IP - IP del peer - - - - Error - Error - - - - Already connected - Ya está conectado - - - - Cannot add peer - Imposible añadir peer - - Synchronizer - + Synchronization Sincronización - + Manage connected peers Gestionar peers conectados - + Show your IP Mostrar su IP @@ -76,10 +23,5 @@ Your IP is: Su IP es: - - - Discovering peers ... - Descubriendo peers ... - diff --git a/lisp/i18n/ts/es_ES/timecode.ts b/lisp/i18n/ts/es_ES/timecode.ts index bdc33a856..f72a990f8 100644 --- a/lisp/i18n/ts/es_ES/timecode.ts +++ b/lisp/i18n/ts/es_ES/timecode.ts @@ -4,12 +4,12 @@ SettingsPageName - + Timecode Settings Ajustes de Código de Tiempo - + Timecode Código de Tiempo @@ -17,92 +17,48 @@ Timecode - - Cannot send timecode. - No se puede mandar Código de Tiempo. - - - - OLA has stopped. - OLA se ha detenido. - - - + Cannot load timecode protocol: "{}" No se puede cargar el Código de Tiempo: "{}" + + + TimecodeError - - Cannot send timecode. -OLA has stopped. - No se puede enviar Código de Tiempo. -OLA se ha detenido. + + Cannot send timecode. + Cannot send timecode. TimecodeSettings - - OLA Timecode Settings - Ajustes de Código de Tiempo OLA - - - - Enable Plugin - Activar Plugin - - - - High-Resolution Timecode - Código de Tiempo de Alta Resolución - - - + Timecode Format: Formato del Código de Tiempo: - - OLA status - Estado de OLA - - - - OLA is not running - start the OLA daemon. - OLA no se está ejecutando - iniciar el daemon OLA. - - - + Replace HOURS by a static track number Reemplazar HORAS por un número de track estático - - Enable ArtNet Timecode - Activar Código de Tiempo ArtNet - - - + Track number Número de Track - - To send ArtNet Timecode you need to setup a running OLA session! - ¡Para mandar Código de Tiempo ArtNet necesita configurar una sesión OLA en ejecución! - - - + Enable Timecode Habilitar Código de Tiempo - + Timecode Settings Ajustes de Código de Tiempo - + Timecode Protocol: Protocolo de Código de Tiempo: diff --git a/lisp/i18n/ts/es_ES/triggers.ts b/lisp/i18n/ts/es_ES/triggers.ts index b4ed81282..3622d7f3c 100644 --- a/lisp/i18n/ts/es_ES/triggers.ts +++ b/lisp/i18n/ts/es_ES/triggers.ts @@ -4,22 +4,22 @@ CueTriggers - + Started Iniciado - + Paused Pausado - + Stopped Detenido - + Ended Terminado @@ -27,7 +27,7 @@ SettingsPageName - + Triggers Disparadores @@ -35,27 +35,27 @@ TriggersSettings - + Add Añadir - + Remove Eliminar - + Trigger Disparador - + Cue Cue - + Action Acción diff --git a/lisp/i18n/ts/fr_FR/action_cues.ts b/lisp/i18n/ts/fr_FR/action_cues.ts index c1411454b..39ff424bc 100644 --- a/lisp/i18n/ts/fr_FR/action_cues.ts +++ b/lisp/i18n/ts/fr_FR/action_cues.ts @@ -1,25 +1,41 @@ + + ActionCuesDebug + + + Registered cue: "{}" + Registered cue: "{}" + + + + ActionsCuesError + + + Cannot create cue {} + Cannot create cue {} + + CollectionCue - + Add Ajouter - + Remove Retirer - + Cue Cue - + Action Action @@ -27,50 +43,35 @@ CommandCue - - Process ended with an error status. - Le process s'est terminé par une erreur - - - - Exit code: - Code de sortie : - - - + Command Commande - + Command to execute, as in a shell Commande à exécuter, comme dans un terminal - + Discard command output Abandonner la sortie de commande - + Ignore command errors Ignorer les erreurs de commande - + Kill instead of terminate Killer au lieu de terminer - - - Command cue ended with an error status. Exit code: {} - Command cue ended with an error status. Exit code: {} - Cue Name - + OSC Settings OSC Settings @@ -78,42 +79,42 @@ CueName - + Command Cue Cue de commande - + MIDI Cue Cue MIDI - + Volume Control Contrôle du Volume - + Seek Cue Recherche de Cue - + Collection Cue Collection de cue - + Stop-All Tout arrêter - + Index Action Index d'action - + OSC Cue OSC Cue @@ -121,68 +122,55 @@ IndexActionCue - + Index Index - + Use a relative index Utiliser un index relatif - + Target index Index cible - + Action Action - + No suggestion No suggestion - + Suggested cue name Suggested cue name - - MIDICue - - - MIDI Message - Message MIDI - - - - Message type - Type de message - - Osc Cue - + Type Type - + Argument Argument - + FadeTo FadeTo - + Fade Fade @@ -190,75 +178,88 @@ OscCue - - Error during cue execution. - Error during cue execution. - - - + OSC Message OSC Message - + Add Add - + Remove Remove - + Test Test - + OSC Path: (example: "/path/to/something") OSC Path: (example: "/path/to/something") - + Fade Fade - + Time (sec) Time (sec) - + Curve Curve + + OscCueError + + + Could not parse argument list, nothing sent + Could not parse argument list, nothing sent + + + + Error while parsing arguments, nothing sent + Error while parsing arguments, nothing sent + + + + Error during cue execution. + Error during cue execution. + + SeekCue - + Cue Cue - + Click to select Cliquer pour sélectionner - + Not selected Non sélectionné - + Seek Recherche - + Time to reach Temps à atteindre @@ -266,37 +267,37 @@ SettingsPageName - + Command Commande - + MIDI Settings Préférences MIDI - + Volume Settings Préférences de volume - + Seek Settings Préférences de recherche - + Edit Collection Editer la collection - + Action Settings Paramètres d'action - + Stop Settings Paramètres d'arrêt @@ -304,7 +305,7 @@ StopAll - + Stop Action Action d'arrêt @@ -312,34 +313,37 @@ VolumeControl - - Error during cue execution - Erreur lors de l'exécution de la cue - - - + Cue Cue - + Click to select Cliquer pour sélectionner - + Not selected Non sélectionné - + Volume to reach Volume à atteindre - + Fade Fondu + + VolumeControlError + + + Error during cue execution. + Error during cue execution. + + diff --git a/lisp/i18n/ts/fr_FR/cart_layout.ts b/lisp/i18n/ts/fr_FR/cart_layout.ts index 690241600..e57083893 100644 --- a/lisp/i18n/ts/fr_FR/cart_layout.ts +++ b/lisp/i18n/ts/fr_FR/cart_layout.ts @@ -4,120 +4,115 @@ CartLayout - + Default behaviors Default behaviors - + Countdown mode Countdown mode - + Show seek-bars Show seek-bars - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show volume Show volume - - Automatically add new page - Automatically add new page - - - + Grid size Grid size - - Number of columns - Number of columns - - - - Number of rows - Number of rows - - - + Play Play - + Pause Pause - + Stop Stop - + Reset volume Reset volume - + Add page Add page - + Add pages Add pages - + Remove current page Remove current page - + Number of Pages: Number of Pages: - + Page {number} Page {number} - + Warning Warning - + Every cue in the page will be lost. Every cue in the page will be lost. - + Are you sure to continue? Are you sure to continue? + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + LayoutDescription - + Organize cues in grid like pages Organize cues in grid like pages @@ -125,52 +120,68 @@ LayoutDetails - + Click a cue to run it Click a cue to run it - + SHIFT + Click to edit a cue SHIFT + Click to edit a cue - + CTRL + Click to select a cue CTRL + Click to select a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them while pressing SHIFT To move cues drag them while pressing SHIFT + + LayoutName + + + Cart Layout + Cart Layout + + ListLayout - + Edit cue Edit cue - + Edit selected cues Edit selected cues - + Remove cue Remove cue - + Remove selected cues Remove selected cues + + SettingsPageName + + + Cart Layout + Cart Layout + + diff --git a/lisp/i18n/ts/fr_FR/controller.ts b/lisp/i18n/ts/fr_FR/controller.ts index 019b8bf8e..df046de83 100644 --- a/lisp/i18n/ts/fr_FR/controller.ts +++ b/lisp/i18n/ts/fr_FR/controller.ts @@ -4,7 +4,7 @@ Controller - + Cannot load controller protocol: "{}" Cannot load controller protocol: "{}" @@ -12,17 +12,17 @@ ControllerKeySettings - + Key Touche - + Action Action - + Shortcuts Raccourcis @@ -30,47 +30,47 @@ ControllerMidiSettings - + MIDI MIDI - + Type Type - + Channel Canal - + Note Note - + Action Action - + Filter "note on" Filtrer les "note on" - + Filter "note off" Filtrer les "note off" - + Capture Capture - + Listening MIDI messages ... Écoute des messages MIDI ... @@ -78,57 +78,52 @@ ControllerOscSettings - + OSC Message OSC Message - - OSC Path: (example: "/path/to/something") - OSC Path: (example: "/path/to/something") - - - + OSC OSC - + Path Path - + Types Types - + Arguments Arguments - + Actions Actions - + OSC Capture OSC Capture - + Add Add - + Remove Remove - + Capture Capture @@ -136,25 +131,78 @@ ControllerSettings - + Add Ajouter - + Remove Retirer + + GlobalAction + + + Go + Go + + + + Reset + Reset + + + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + Osc Cue - + Type Type - + Argument Argument @@ -162,12 +210,12 @@ OscCue - + Add Add - + Remove Remove @@ -175,24 +223,29 @@ SettingsPageName - + Cue Control Contrôle de la cue - + MIDI Controls Contrôles MIDI - + Keyboard Shortcuts Raccourcis-clavier - + OSC Controls OSC Controls + + + Layout Controls + Layout Controls + diff --git a/lisp/i18n/ts/fr_FR/gst_backend.ts b/lisp/i18n/ts/fr_FR/gst_backend.ts index 4eaff592b..20286761e 100644 --- a/lisp/i18n/ts/fr_FR/gst_backend.ts +++ b/lisp/i18n/ts/fr_FR/gst_backend.ts @@ -4,60 +4,55 @@ AlsaSinkSettings - + ALSA device Périphérique ALSA - - - ALSA devices, as defined in an asound configuration file - Périphériques ALSA, comme définis dans le fichier de configuration asound - AudioDynamicSettings - + Compressor Compresseur - + Expander Expandeur - + Soft Knee Coude léger - + Hard Knee Coude dur - + Compressor/Expander Compresseur / expandeur - + Type Type - + Curve Shape Forme de la courbe - + Ratio Ratio - + Threshold (dB) Seuil (dB) @@ -65,22 +60,22 @@ AudioPanSettings - + Audio Pan Panoramique audio - + Center Centre - + Left Gauche - + Right Droit @@ -88,22 +83,22 @@ DbMeterSettings - + DbMeter settings Préférences du mesureur de dB - + Time between levels (ms) Temps entre les niveaux (ms) - + Peak ttl (ms) Crête ttl (ms) - + Peak falloff (dB/sec) Crête falloff (dB/sec) @@ -111,7 +106,7 @@ Equalizer10Settings - + 10 Bands Equalizer (IIR) Égaliseur 10 bandes (IIR) @@ -119,28 +114,44 @@ GstBackend - + Audio cue (from file) Audio cue (from file) - + Select media files Select media files + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + GstMediaSettings - + Change Pipeline Changer le bitoduc + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + GstPipelineEdit - + Edit Pipeline Éditer le bitoduc @@ -148,7 +159,7 @@ GstSettings - + Pipeline Bitoduc @@ -156,32 +167,32 @@ JackSinkSettings - + Connections Connexions - + Edit connections Éditer les connexions - + Output ports Ports de sortie - + Input ports Ports d'entrée - + Connect Connecter - + Disconnect Déconnecter @@ -189,77 +200,77 @@ MediaElementName - + Compressor/Expander Compresseur / expandeur - + Audio Pan Panoramique audio - + PulseAudio Out Sortie PulseAudio - + Volume Volume - + dB Meter Mesureur de dB - + System Input Entrée système - + ALSA Out Sortie ALSA - + JACK Out Sortie JACK - + Custom Element Élément personnalisé - + System Out Sortie système - + Pitch Hauteur de note - + URI Input Entrée URI - + 10 Bands Equalizer Égaliseur 10 bandes - + Speed Vitesse - + Preset Input Entrée pré-sélectionnée @@ -267,12 +278,12 @@ PitchSettings - + Pitch Hauteur de note - + {0:+} semitones {0:+} demi-tons @@ -280,7 +291,7 @@ PresetSrcSettings - + Presets Pré-sélections @@ -288,17 +299,12 @@ SettingsPageName - - GStreamer settings - Préférences GStreamer - - - + Media Settings Préférences des médias - + GStreamer GStreamer @@ -306,7 +312,7 @@ SpeedSettings - + Speed Vitesse @@ -314,42 +320,42 @@ UriInputSettings - + Source Source - + Find File Trouver le fichier - + Buffering Tampon - + Use Buffering Utiliser le tampon - + Attempt download on network streams Essayer de télécharger en flux de réseau - + Buffer size (-1 default value) Taille du tampon (valeur par défaut -1) - + Choose file Choisir un fichier - + All files Tous les fichiers @@ -357,12 +363,12 @@ UserElementSettings - + User defined elements Éléments spécifiés par l'utilisateur - + Only for advanced user! Seulement pour utilisateurs avancés ! @@ -370,17 +376,17 @@ VolumeSettings - + Volume Volume - + Normalized volume Volume normalisé - + Reset Réinitialiser diff --git a/lisp/i18n/ts/fr_FR/lisp.ts b/lisp/i18n/ts/fr_FR/lisp.ts index 37989e4af..ddc724297 100644 --- a/lisp/i18n/ts/fr_FR/lisp.ts +++ b/lisp/i18n/ts/fr_FR/lisp.ts @@ -4,22 +4,22 @@ About - + Authors Auteurs - + Contributors Contributeurs - + Translators Traducteurs - + About Linux Show Player À propos de Linux Show Player @@ -27,42 +27,32 @@ AboutDialog - - Linux Show Player is a cue-player designed for stage productions. - Linux Show Player est un logiciel de lecture de cue pensé pour le théâtre ou l'événementiel. - - - + Web site Site web - - Users group - Groupe d'utilisateurs - - - + Source code Code source - + Info Info - + License Licence - + Contributors Contributeurs - + Discussion Discussion @@ -70,12 +60,12 @@ Actions - + Undo: {} Undo: {} - + Redo: {} Redo: {} @@ -83,7 +73,7 @@ AppConfiguration - + LiSP preferences Préférences de LiSP @@ -91,243 +81,109 @@ AppGeneralSettings - - Startup layout - État de démarrage - - - - Use startup dialog - Utilisez le dialogue du démarrage - - - - Application theme - Thème de l'application - - - + Default layout Default layout - + Enable startup layout selector Enable startup layout selector - + Application themes Application themes - - UI theme - UI theme + + UI theme: + UI theme: - - Icons theme - Icons theme + + Icons theme: + Icons theme: - CartLayout - - - Add page - Ajouter une page - - - - Add pages - Ajouter des pages - - - - Remove current page - Retirer la page actuelle - - - - Countdown mode - Compte à rebours - - - - Show seek-bars - Montrer le barre de recherche - - - - Show dB-meters - Afficher le mesureur de dB - - - - Show volume - Afficher le volume - - - - Show accurate time - Afficher le temps exact - - - - Edit cue - Éditer la cue - - - - Remove - Retirer - - - - Select - Sélectionner - - - - Play - Lecture - - - - Pause - Pause - - - - Stop - Stop - - - - Reset volume - Réinitialisation du volume - - - - Number of Pages: - Nombre de pages : - - - - Warning - Attention - - - - Every cue in the page will be lost. - Toutes les cues dans la pages seront perdues - - - - Are you sure to continue? - Voulez-vous vraiment continuer ? - + ApplicationError - - Page - Page - - - - Default behaviors - Comportements par défaut - - - - Automatically add new page - Ajouter une page automatiquement - - - - Grid size - Taille de la grille + + Startup error + Startup error + + + ConfigurationDebug - - Number of columns - Nombre de colonnes + + Configuration written at {} + Configuration written at {} + + + ConfigurationInfo - - Number of rows - Nombre de lignes + + New configuration installed at {} + New configuration installed at {} CueAction - + Default Défaut - + Pause Pause - + Start Démarrer - + Stop Stop - - FadeInStart - DémarrageDuFonduÀlOuverture - - - - FadeOutStop - ArrêtduFonduÀlaFermeture - - - - FadeOutPause - PauseduFonduÀlaFermeture - - - + Faded Start Faded Start - + Faded Resume Faded Resume - + Faded Pause Faded Pause - + Faded Stop Faded Stop - + Faded Interrupt Faded Interrupt - + Resume Resume - + Do Nothing Do Nothing @@ -335,12 +191,12 @@ CueActionLog - + Cue settings changed: "{}" Paramètres de cue modifiés : "{}" - + Cues settings changed. Paramètres de cues modifiés. @@ -348,42 +204,42 @@ CueAppearanceSettings - + The appearance depends on the layout L'apparence dépend de l'agencement - + Cue name Nom de la cue - + NoName Pas de nom - + Description/Note Description / note - + Set Font Size Définir la taille de la police - + Color Couleur - + Select background color Sélectionner la couleur de fond - + Select font color Sélectionner la couleur de la police @@ -391,7 +247,7 @@ CueName - + Media Cue Cue de média @@ -399,100 +255,85 @@ CueNextAction - + Do Nothing Do Nothing + + + Trigger after the end + Trigger after the end + - Auto Follow - Auto Follow + Trigger after post wait + Trigger after post wait - - Auto Next - Auto Next + + Select after the end + Select after the end + + + + Select after post wait + Select after post wait CueSettings - + Pre wait Pré-attente - + Wait before cue execution Attente avant l'exécution de la cue - + Post wait Post-attente - + Wait after cue execution Attente après l'exécution de la cue - + Next action Prochaine action - - Interrupt Fade - Interrompre le fondu - - - - Fade Action - Action de fondu - - - - Behaviours - Comportement - - - - Pre/Post Wait - Attente pré/post - - - - Fade In/Out - Fondu à l'ouverture/fermeture - - - + Start action Lancer l'action - + Default action to start the cue Action par défaut pour démarrer le cue - + Stop action Arrêter l'action - + Default action to stop the cue Action par défaut pour arrêter le cue - + Interrupt fade Interrupt fade - + Fade actions Fade actions @@ -500,17 +341,17 @@ Fade - + Linear Linéaire - + Quadratic Du second degré - + Quadratic2 Du second degré 2 @@ -518,99 +359,43 @@ FadeEdit - - Duration (sec) - Durée (sec) + + Duration (sec): + Duration (sec): - - Curve - Courbe + + Curve: + Curve: FadeSettings - + Fade In Fondu à l'ouverture - + Fade Out Fondu à la fermeture - - LayoutDescription - - - Organize cues in grid like pages - Organiser les cues en grille comme les pages - - - - Organize the cues in a list - Organiser les cues dans une liste - - - - LayoutDetails - - - Click a cue to run it - Cliquer sur une cue pour l'exécuter - - - - SHIFT + Click to edit a cue - MAJ + clic pour éditer une cue - - - - CTRL + Click to select a cue - CTRL + clic pour sélectionner une cue - - - - SHIFT + Space or Double-Click to edit a cue - MAJ + Espace ou double-clic pour éditer une cue - - - - To copy cues drag them while pressing CTRL - Pour copier les cues, glissez-les en pressant CTRL - - - - To copy cues drag them while pressing SHIFT - Pour copier les cues, glissez-les en pressant SHIFT - - - - CTRL + Left Click to select cues - CTRL + clic-gauche pour sélectionner les cues - - - - To move cues drag them - Pour déplacer des cues, déplacez-les - - LayoutSelect - + Layout selection Sélection de l'agencement - + Select layout Sélectionner l'agencement - + Open file Ouvrir un fichier @@ -618,518 +403,363 @@ ListLayout - - Show playing cues - Montrer les cues en cours - - - - Show dB-meters - Afficher le mesureur de dB - - - - Show seek-bars - Afficher la barre de recherche - - - - Show accurate time - Afficher le temps exact - - - - Auto-select next cue - Auto-sélectionner la prochaine cue - - - - Edit cue - Éditer la cue - - - - Remove - Retirer - - - - Select - Sélectionner - - - - Stop all - Tout stopper - - - - Pause all - Tout mettre en pause - - - - Restart all - Tout redémarrer - - - - Stop - Stop - - - - Restart - Redémarrer - - - - Default behaviors - Comportements par défaut - - - - At list end: - À la fin de la liste : - - - - Go key: - Touche pour lancer : - - - - Interrupt all - Tout interrompre - - - - Fade-Out all - Tout fondre à la fermeture - - - - Fade-In all - Tout fondre à l'ouverture - - - - Use fade - Utiliser le fondu - - - - Stop Cue - Arrêter la cue - - - - Pause Cue - Mettre la cue en pause - - - - Restart Cue - Redémarrer la cue - - - - Interrupt Cue - Interrompre la cue - - - + Stop All Tout arrêter - + Pause All Tout mettre en pause - - Restart All - Tout redémarrer - - - + Interrupt All Tout interrompre - + Use fade (global actions) Use fade (global actions) - + Resume All Resume All - - ListLayoutHeader - - - Cue - Cue - - - - Pre wait - Pré-attente - - - - Action - Action - - - - Post wait - Post-attente - - - - ListLayoutInfoPanel - - - Cue name - Nom de la cue - - - - Cue description - Description de la cue - - Logging - - Information - Informations - - - + Debug Déboguage - + Warning Attention - + Error Erreur - - Details: - Détails : - - - + Dismiss all Dismiss all - + Show details Show details - + Linux Show Player - Log Viewer Linux Show Player - Log Viewer - + Info Info - + Critical Critical - + Time Time - + Milliseconds Milliseconds - + Logger name Logger name - + Level Level - + Message Message - + Function Function - + Path name Path name - + File name File name - + Line no. Line no. - + Module Module - + Process ID Process ID - + Process name Process name - + Thread ID Thread ID - + Thread name Thread name + + + Exception info + Exception info + MainWindow - + &File &Fichier - + New session Nouvelle session - + Open Ouvrir - + Save session Sauvegarder la session - + Preferences Préférences - + Save as Sauvegarder sous - + Full Screen Plein écran - + Exit Quitter - + &Edit Édit&er - + Undo Annuler - + Redo Refaire - + Select all Tout sélectionner - + Select all media cues Sélectionner toutes les cues média - + Deselect all Tout désélectionner - + CTRL+SHIFT+A CTRL+MAJ+A - + Invert selection Inverser la sélection - + CTRL+I CTRL+I - + Edit selected Éditer la sélection - + CTRL+SHIFT+E CTRL+MAJ+E - + &Layout É&tat - + &Tools Ou&tils - + Edit selection Éditer la sélection - + &About À &propos - + About À propos - + About Qt À propos de Qt - - Undone: - Défaire : - - - - Redone: - Refaire : - - - + Close session Fermer la session - + The current session is not saved. La session actuelle n'est pas sauvegardée - + Discard the changes? Annuler les changements ? - - MediaCueMenus - - - Audio cue (from file) - Cue audio (depuis un fichier) - - - - Select media files - Sélectionner des fichiers médias - - MediaCueSettings - + Start time Temps de départ - + Stop position of the media Position de fin du média - + Stop time Temps de fin - + Start position of the media Position de démarrage du média - + Loop Boucle + + + PluginsError + + + Failed to load "{}" + Failed to load "{}" + + + + Failed to terminate plugin: "{}" + Failed to terminate plugin: "{}" + + + + the requested plugin is not loaded: {} + the requested plugin is not loaded: {} + + + + PluginsInfo + + + Plugin loaded: "{}" + Plugin loaded: "{}" + - - Repetition after first play (-1 = infinite) - Répétition après la première lecture (-1 = infinie) + + Plugin terminated: "{}" + Plugin terminated: "{}" + + + + PluginsWarning + + + Cannot satisfy dependencies for: {} + Cannot satisfy dependencies for: {} QColorButton - + Right click to reset Clic-droit pour réinitialiser @@ -1137,44 +767,49 @@ SettingsPageName - + Appearance Apparence - + General Générale - + Cue Cue - + Cue Settings Paramètres de cue - + Plugins Plugins - + Behaviours Behaviours - + Pre/Post Wait Pre/Post Wait - + Fade In/Out Fade In/Out + + + Layouts + Layouts + diff --git a/lisp/i18n/ts/fr_FR/list_layout.ts b/lisp/i18n/ts/fr_FR/list_layout.ts index f433cd424..3e32454e4 100644 --- a/lisp/i18n/ts/fr_FR/list_layout.ts +++ b/lisp/i18n/ts/fr_FR/list_layout.ts @@ -4,7 +4,7 @@ LayoutDescription - + Organize the cues in a list Organize the cues in a list @@ -12,163 +12,191 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue SHIFT + Space or Double-Click to edit a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them To move cues drag them + + LayoutName + + + List Layout + List Layout + + ListLayout - + Default behaviors Default behaviors - + Show playing cues Show playing cues - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue - + Enable selection mode Enable selection mode - - Go key: - Go key: - - - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - - Edit selected cues - Edit selected cues - - - + Remove cue Remove cue - - Remove selected cues - Remove selected cues - - - + Selection mode Selection mode - + Pause all Pause all - + Stop all Stop all - + Interrupt all Interrupt all - + Resume all Resume all - + Fade-Out all Fade-Out all - + Fade-In all Fade-In all + + + GO key: + GO key: + + + + GO action: + GO action: + + + + GO delay (ms): + GO delay (ms): + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove selected + Remove selected + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait @@ -176,14 +204,22 @@ ListLayoutInfoPanel - + Cue name Cue name - + Cue description Cue description + + SettingsPageName + + + List Layout + List Layout + + diff --git a/lisp/i18n/ts/fr_FR/media_info.ts b/lisp/i18n/ts/fr_FR/media_info.ts index 0f2ea0812..0f030b739 100644 --- a/lisp/i18n/ts/fr_FR/media_info.ts +++ b/lisp/i18n/ts/fr_FR/media_info.ts @@ -4,32 +4,27 @@ MediaInfo - + Media Info Info sur le média - - Error - Erreur - - - + No info to display Pas d'info à afficher - + Info Info - + Value Valeur - + Warning Warning diff --git a/lisp/i18n/ts/fr_FR/midi.ts b/lisp/i18n/ts/fr_FR/midi.ts index ebbe17626..16e45cc72 100644 --- a/lisp/i18n/ts/fr_FR/midi.ts +++ b/lisp/i18n/ts/fr_FR/midi.ts @@ -1,20 +1,144 @@ + + MIDICue + + + MIDI Message + MIDI Message + + + + Message type + Message type + + + + MIDIMessageAttr + + + Channel + Channel + + + + Note + Note + + + + Velocity + Velocity + + + + Control + Control + + + + Program + Program + + + + Value + Value + + + + Song + Song + + + + Pitch + Pitch + + + + Position + Position + + + + MIDIMessageType + + + Note ON + Note ON + + + + Note OFF + Note OFF + + + + Polyphonic After-touch + Polyphonic After-touch + + + + Control/Mode Change + Control/Mode Change + + + + Program Change + Program Change + + + + Channel After-touch + Channel After-touch + + + + Pitch Bend Change + Pitch Bend Change + + + + Song Select + Song Select + + + + Song Position + Song Position + + + + Start + Start + + + + Stop + Stop + + + + Continue + Continue + + MIDISettings - + MIDI default devices Périphériques MIDI par défaut - + Input Entrée - + Output Sortie @@ -22,7 +146,7 @@ SettingsPageName - + MIDI settings Préférences MIDI diff --git a/lisp/i18n/ts/fr_FR/network.ts b/lisp/i18n/ts/fr_FR/network.ts index c9e4cea54..3f8c65471 100644 --- a/lisp/i18n/ts/fr_FR/network.ts +++ b/lisp/i18n/ts/fr_FR/network.ts @@ -1,45 +1,69 @@ + + APIServerInfo + + + Stop serving network API + Stop serving network API + + + + ApiServerError + + + Network API server stopped working. + Network API server stopped working. + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + NetworkDiscovery - + Host discovery Host discovery - + Manage hosts Manage hosts - + Discover hosts Discover hosts - + Manually add a host Manually add a host - + Remove selected host Remove selected host - + Remove all host Remove all host - + Address Address - + Host IP Host IP diff --git a/lisp/i18n/ts/fr_FR/osc.ts b/lisp/i18n/ts/fr_FR/osc.ts index b57db305f..3007228bc 100644 --- a/lisp/i18n/ts/fr_FR/osc.ts +++ b/lisp/i18n/ts/fr_FR/osc.ts @@ -1,25 +1,54 @@ + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + OscSettings - + OSC Settings OSC Settings - + Input Port: Input Port: - + Output Port: Output Port: - + Hostname: Hostname: @@ -27,7 +56,7 @@ SettingsPageName - + OSC settings OSC settings diff --git a/lisp/i18n/ts/fr_FR/presets.ts b/lisp/i18n/ts/fr_FR/presets.ts index 617197d86..05c824068 100644 --- a/lisp/i18n/ts/fr_FR/presets.ts +++ b/lisp/i18n/ts/fr_FR/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Créer un cue - + Load on selected Cues Charger les cues sélectionnés @@ -17,132 +17,112 @@ Presets - + Presets Pré-réglages - - Load preset - Charger un pré-réglage - - - + Save as preset Sauvegarder un pré-réglage sous - + Cannot scan presets Impossible d'examiner les pré-réglages - + Error while deleting preset "{}" Erreur lors de la suppression du pré-réglage "{}" - + Cannot load preset "{}" Impossible de charger le pré-réglage "{}" - + Cannot save preset "{}" Impossible de sauvegarder le pré-réglage "{}" - + Cannot rename preset "{}" Impossible de renommer le pré-réglage "{}" - + Select Preset Sélectionner le pré-réglage - - Preset already exists, overwrite? - Le pré-réglage existe déjà, l'écraser ? - - - + Preset name Nom du pré-réglage - + Add Ajouter - + Rename Renommer - + Edit Éditer - + Remove Supprimer - + Export selected Exporter le sélectionné - + Import Importer - + Warning Attention - + The same name is already used! Le même nom est déjà utilisé ! - - Cannot create a cue from this preset: {} - Impossible de créer un cue depuis ce pré-réglage : {} - - - + Cannot export correctly. Impossible d'exporter correctement - - Some presets already exists, overwrite? - Certains pré-réglages existent déjà, les écraser ? - - - + Cannot import correctly. Impossible d'importer correctement. - + Cue type Type de cue - + Load on cue Load on cue - + Load on selected cues Load on selected cues diff --git a/lisp/i18n/ts/fr_FR/rename_cues.ts b/lisp/i18n/ts/fr_FR/rename_cues.ts index 3970fd565..261646b55 100644 --- a/lisp/i18n/ts/fr_FR/rename_cues.ts +++ b/lisp/i18n/ts/fr_FR/rename_cues.ts @@ -4,92 +4,72 @@ RenameCues - + Rename Cues Rename Cues - + Rename cues Rename cues - + Current Current - + Preview Preview - + Capitalize Capitalize - + Lowercase Lowercase - + Uppercase Uppercase - + Remove Numbers Remove Numbers - + Add numbering Add numbering - + Reset Reset - - Rename all cue. () in regex below usable with $0, $1 ... - Rename all cue. () in regex below usable with $0, $1 ... - - - + Type your regex here: Type your regex here: - + Regex help Regex help + + + RenameUiDebug - - You can use Regexes to rename your cues. - -Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... -In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. - -Exemple : -^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. -Only the numbers are between parenthesis and will be usable with $0 in the first line. - -For more information about Regexes, consult python documentation - You can use Regexes to rename your cues. - -Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... -In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. - -Exemple : -^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. -Only the numbers are between parenthesis and will be usable with $0 in the first line. - -For more information about Regexes, consult python documentation + + Regex error: Invalid pattern + Regex error: Invalid pattern diff --git a/lisp/i18n/ts/fr_FR/replay_gain.ts b/lisp/i18n/ts/fr_FR/replay_gain.ts index b02df1b64..f114701bb 100644 --- a/lisp/i18n/ts/fr_FR/replay_gain.ts +++ b/lisp/i18n/ts/fr_FR/replay_gain.ts @@ -4,49 +4,80 @@ ReplayGain - + ReplayGain / Normalization Gain de lecture / normalisation - + Calculate Calculer - + Reset all Tout réinitialiser - + Reset selected Réinitialiser la séléction - + Threads number Nombre de fils - + Apply only to selected media Ne s'applique seulement au média sélectionné - + ReplayGain to (dB SPL) Gain de lecture à (dB SPL) - + Normalize to (dB) Normaliser à (dB) - + Processing files ... Fichier en cours de traitement ... + + ReplayGainDebug + + + Applied gain for: {} + Applied gain for: {} + + + + Discarded gain for: {} + Discarded gain for: {} + + + + ReplayGainInfo + + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} + + diff --git a/lisp/i18n/ts/fr_FR/synchronizer.ts b/lisp/i18n/ts/fr_FR/synchronizer.ts index d9a97659c..8bf3985a9 100644 --- a/lisp/i18n/ts/fr_FR/synchronizer.ts +++ b/lisp/i18n/ts/fr_FR/synchronizer.ts @@ -1,73 +1,20 @@ - - SyncPeerDialog - - - Manage connected peers - Gestion des paires connectées - - - - Discover peers - Découvrir les paires - - - - Manually add a peer - Ajouter manuelement une paire - - - - Remove selected peer - Retirer la paire séléctionnée - - - - Remove all peers - Retirer toutes les paires - - - - Address - Adresses - - - - Peer IP - IP de la paire - - - - Error - Erreur - - - - Already connected - Déjà connecté - - - - Cannot add peer - Impossible d'ajouter une paire - - Synchronizer - + Synchronization Synchronisation - + Manage connected peers Gérer les paiers connectées - + Show your IP Afficher votre IP @@ -76,10 +23,5 @@ Your IP is: Votre IP est : - - - Discovering peers ... - Découverte des paires ... - diff --git a/lisp/i18n/ts/fr_FR/timecode.ts b/lisp/i18n/ts/fr_FR/timecode.ts index 24c34b05d..ac60b2f1b 100644 --- a/lisp/i18n/ts/fr_FR/timecode.ts +++ b/lisp/i18n/ts/fr_FR/timecode.ts @@ -4,12 +4,12 @@ SettingsPageName - + Timecode Settings Paramètres de code temporel - + Timecode Code temporel @@ -17,92 +17,48 @@ Timecode - - Cannot send timecode. - Impossible d'envoyer un code temporel. - - - - OLA has stopped. - OLA s'est arrêté - - - + Cannot load timecode protocol: "{}" Cannot load timecode protocol: "{}" + + + TimecodeError - - Cannot send timecode. -OLA has stopped. - Cannot send timecode. -OLA has stopped. + + Cannot send timecode. + Cannot send timecode. TimecodeSettings - - OLA Timecode Settings - Paramètres du code temporel OLA - - - - Enable Plugin - Activer le greffon - - - - High-Resolution Timecode - Code temporel de haute-résolution - - - + Timecode Format: Format de code temporel - - OLA status - Status OLA - - - - OLA is not running - start the OLA daemon. - OLA n'est pas démarré - démarrez le démon OLA - - - + Replace HOURS by a static track number Remplace HOURS par un nombre de piste statique - - Enable ArtNet Timecode - Active le code temporel ArtNet - - - + Track number Numéro de piste - - To send ArtNet Timecode you need to setup a running OLA session! - Pour envoyer un code temporel ArtNet, vous devez paramétrer et lancer une session OLA ! - - - + Enable Timecode Enable Timecode - + Timecode Settings Timecode Settings - + Timecode Protocol: Timecode Protocol: diff --git a/lisp/i18n/ts/fr_FR/triggers.ts b/lisp/i18n/ts/fr_FR/triggers.ts index 74633a109..06eab38c6 100644 --- a/lisp/i18n/ts/fr_FR/triggers.ts +++ b/lisp/i18n/ts/fr_FR/triggers.ts @@ -4,22 +4,22 @@ CueTriggers - + Started Démarré - + Paused En pause - + Stopped Arrêté - + Ended Fini @@ -27,7 +27,7 @@ SettingsPageName - + Triggers Déclencheurs @@ -35,27 +35,27 @@ TriggersSettings - + Add Ajouter - + Remove Retirer - + Trigger Déclencheur - + Cue Cue - + Action Action diff --git a/lisp/i18n/ts/it_IT/action_cues.ts b/lisp/i18n/ts/it_IT/action_cues.ts index a586f940d..b5a64ea94 100644 --- a/lisp/i18n/ts/it_IT/action_cues.ts +++ b/lisp/i18n/ts/it_IT/action_cues.ts @@ -1,25 +1,41 @@ + + ActionCuesDebug + + + Registered cue: "{}" + Registered cue: "{}" + + + + ActionsCuesError + + + Cannot create cue {} + Cannot create cue {} + + CollectionCue - + Add Aggiungi - + Remove Rimuovi - + Cue Cue - + Action Azione @@ -27,50 +43,35 @@ CommandCue - - Process ended with an error status. - Processo terminato con uno stato errore. - - - - Exit code: - Codice di uscita: - - - + Command Comando - + Command to execute, as in a shell Comando da eseguire, come in una shell - + Discard command output Scarta l'output del comando - + Ignore command errors Ignora errori - + Kill instead of terminate Uccidi invece di terminare - - - Command cue ended with an error status. Exit code: {} - Cue comando terminata con un codice di errore. Codice di uscita: {} - Cue Name - + OSC Settings Impostazioni OSC @@ -78,42 +79,42 @@ CueName - + Command Cue Cue Comando - + MIDI Cue Cue MIDI - + Volume Control Controllo Volume - + Seek Cue Cue di Riposizionamento - + Collection Cue Collezione - + Stop-All Ferma Tutto - + Index Action Azione Posizionale - + OSC Cue Cue OSC @@ -121,68 +122,55 @@ IndexActionCue - + Index Posizione - + Use a relative index Usa una posizione relativa - + Target index Posizione bersaglio - + Action Azione - + No suggestion Nessun suggerimento - + Suggested cue name Nome suggerito per la cue - - MIDICue - - - MIDI Message - Messaggio Midi - - - - Message type - Tipo di messaggio - - Osc Cue - + Type Tipo - + Argument Argomento - + FadeTo Dissolvi a - + Fade Dissolvenza @@ -190,75 +178,88 @@ OscCue - - Error during cue execution. - Errore durante l'esecuzione della cue. - - - + OSC Message Messaggio OSC - + Add Aggiungi - + Remove Rimuovi - + Test Testa - + OSC Path: (example: "/path/to/something") Percorso OSC: (esempio: "/percorso/per/qualcosa") - + Fade Dissolvenza - + Time (sec) Tempo (sec) - + Curve Curva + + OscCueError + + + Could not parse argument list, nothing sent + Could not parse argument list, nothing sent + + + + Error while parsing arguments, nothing sent + Error while parsing arguments, nothing sent + + + + Error during cue execution. + Error during cue execution. + + SeekCue - + Cue Cue - + Click to select Clicca per selezionare - + Not selected Non selezionata - + Seek Posizionamento - + Time to reach Posizione da raggiungere @@ -266,37 +267,37 @@ SettingsPageName - + Command Comando - + MIDI Settings Impostazioni MIDI - + Volume Settings Impostazioni Volume - + Seek Settings Impostazioni Posizione - + Edit Collection Modifica Collezione - + Action Settings Impostazioni Azione - + Stop Settings Impostazioni di Arresto @@ -304,7 +305,7 @@ StopAll - + Stop Action Azione di arresto @@ -312,34 +313,37 @@ VolumeControl - - Error during cue execution - Errore durante l'esecuzione della cue - - - + Cue Cue - + Click to select Clicca per selezionare - + Not selected Non selezionata - + Volume to reach Volume da raggiungere - + Fade Dissolvenza + + VolumeControlError + + + Error during cue execution. + Error during cue execution. + + diff --git a/lisp/i18n/ts/it_IT/cart_layout.ts b/lisp/i18n/ts/it_IT/cart_layout.ts index 9cc6c32df..e55f4c7db 100644 --- a/lisp/i18n/ts/it_IT/cart_layout.ts +++ b/lisp/i18n/ts/it_IT/cart_layout.ts @@ -4,120 +4,115 @@ CartLayout - + Default behaviors Comportamenti predefiniti - + Countdown mode Modalità countdown - + Show seek-bars Mostra barre di avanzamento - + Show dB-meters Mostra indicatori dB - + Show accurate time Mostra tempo accurato - + Show volume Mostra volume - - Automatically add new page - Aggiungi automaticamente nuove pagine - - - + Grid size Dimensione griglia - - Number of columns - Numero di colonne - - - - Number of rows - Numero di righe - - - + Play Riproduci - + Pause Pausa - + Stop Ferma - + Reset volume Resetta volume - + Add page Aggiungi pagina - + Add pages Aggiungi pagine - + Remove current page Rimuovi pagina corrente - + Number of Pages: Numero di Pagine: - + Page {number} Pagina {number} - + Warning Attenzione - + Every cue in the page will be lost. Ogni cue nella pagina sarà persa. - + Are you sure to continue? Sei sicuro di voler continuare? + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + LayoutDescription - + Organize cues in grid like pages Organizza le cue in pagine simili a tabelle @@ -125,52 +120,68 @@ LayoutDetails - + Click a cue to run it Clicca una cue per eseguirla - + SHIFT + Click to edit a cue SHIFT + Click per modificare una cue - + CTRL + Click to select a cue CTRL + Click per selezionare una cue - + To copy cues drag them while pressing CTRL Per copiare le cue trascinale mentre premi CTRL - + To move cues drag them while pressing SHIFT Per muovere le cue trascinale mentre premi SHIFT + + LayoutName + + + Cart Layout + Cart Layout + + ListLayout - + Edit cue Modifica Cue - + Edit selected cues Modifica cue selezionate - + Remove cue Rimuovi cue - + Remove selected cues Rimuovi cue selezionate + + SettingsPageName + + + Cart Layout + Cart Layout + + diff --git a/lisp/i18n/ts/it_IT/controller.ts b/lisp/i18n/ts/it_IT/controller.ts index 3a47e7cbd..d9f98d912 100644 --- a/lisp/i18n/ts/it_IT/controller.ts +++ b/lisp/i18n/ts/it_IT/controller.ts @@ -4,7 +4,7 @@ Controller - + Cannot load controller protocol: "{}" Impossibile caricare il protocollo: "{}" @@ -12,17 +12,17 @@ ControllerKeySettings - + Key Tasto - + Action Azione - + Shortcuts Scorciatoie @@ -30,47 +30,47 @@ ControllerMidiSettings - + MIDI MIDI - + Type Tipo - + Channel Canale - + Note Nota - + Action Azione - + Filter "note on" Filtro "note on" - + Filter "note off" Filtro "note off" - + Capture Cattura - + Listening MIDI messages ... Ascoltando messaggi MIDI ... @@ -78,57 +78,52 @@ ControllerOscSettings - + OSC Message Messaggio OSC - - OSC Path: (example: "/path/to/something") - Percorso OSC: (esempio: "/percorso/per/qualcosa") - - - + OSC OSC - + Path Percorso - + Types Tipi - + Arguments Argomenti - + Actions Azioni - + OSC Capture Acquisisci OSC - + Add Aggiungi - + Remove Rimuovi - + Capture Acquisisci @@ -136,25 +131,78 @@ ControllerSettings - + Add Aggiungi - + Remove Rimuovi + + GlobalAction + + + Go + Go + + + + Reset + Reset + + + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + Osc Cue - + Type Tipo - + Argument Argomento @@ -162,12 +210,12 @@ OscCue - + Add Aggiungi - + Remove Rimuovi @@ -175,24 +223,29 @@ SettingsPageName - + Cue Control Controllo Cue - + MIDI Controls Controlli MIDI - + Keyboard Shortcuts Scorciatoie Tastiera - + OSC Controls Controlli OSC + + + Layout Controls + Layout Controls + diff --git a/lisp/i18n/ts/it_IT/gst_backend.ts b/lisp/i18n/ts/it_IT/gst_backend.ts index 0c5951e0c..cb0de3f4c 100644 --- a/lisp/i18n/ts/it_IT/gst_backend.ts +++ b/lisp/i18n/ts/it_IT/gst_backend.ts @@ -4,60 +4,55 @@ AlsaSinkSettings - + ALSA device Dispositivo ALSA - - - ALSA devices, as defined in an asound configuration file - Dispositivi ALSA, come definiti nel file di configurazione asound - AudioDynamicSettings - + Compressor Compressore - + Expander Espansore - + Soft Knee Morbida - + Hard Knee Dura - + Compressor/Expander Compressore/Espansore - + Type Tipo - + Curve Shape Forma curva - + Ratio Rapporto - + Threshold (dB) Soglia (dB) @@ -65,22 +60,22 @@ AudioPanSettings - + Audio Pan Bilanciamento - + Center Centro - + Left Sinistra - + Right Destra @@ -88,22 +83,22 @@ DbMeterSettings - + DbMeter settings Impostazioni indicatore dB - + Time between levels (ms) Intervallo tra due livelli (ms) - + Peak ttl (ms) Durata picchi (ms) - + Peak falloff (dB/sec) Decadimento picchi (dB/sec) @@ -111,7 +106,7 @@ Equalizer10Settings - + 10 Bands Equalizer (IIR) Equalizzatore 10 bande (IIR) @@ -119,28 +114,44 @@ GstBackend - + Audio cue (from file) Cue audio (da file) - + Select media files Seleziona file multimediali + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + GstMediaSettings - + Change Pipeline Modifica Pipeline + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + GstPipelineEdit - + Edit Pipeline Modifica Pipeline @@ -148,7 +159,7 @@ GstSettings - + Pipeline Pipeline @@ -156,32 +167,32 @@ JackSinkSettings - + Connections Connessioni - + Edit connections Modifica connessioni - + Output ports Uscite - + Input ports Ingressi - + Connect Connetti - + Disconnect Disconnetti @@ -189,77 +200,77 @@ MediaElementName - + Compressor/Expander Compressore/Espansore - + Audio Pan Bilanciamento - + PulseAudio Out Uscita PulseAudio - + Volume Volume - + dB Meter Indicatore dB - + System Input Ingresso di Sistema - + ALSA Out Uscita ALSA - + JACK Out Uscita JACK - + Custom Element Elemento Personalizzato - + System Out Uscita di Sistema - + Pitch Tonalità - + URI Input Ingresso URI - + 10 Bands Equalizer Equalizzatore 10 bande - + Speed Velocità - + Preset Input Ingresso Preset @@ -267,12 +278,12 @@ PitchSettings - + Pitch Tonalità - + {0:+} semitones {0:+} semitoni @@ -280,7 +291,7 @@ PresetSrcSettings - + Presets Preset @@ -288,17 +299,12 @@ SettingsPageName - - GStreamer settings - Impostazioni GStreamer - - - + Media Settings Impostazioni Media - + GStreamer GStreamer @@ -306,7 +312,7 @@ SpeedSettings - + Speed Velocità @@ -314,42 +320,42 @@ UriInputSettings - + Source Sorgente - + Find File Seleziona file - + Buffering Pre-Caricamento - + Use Buffering Usare buffering - + Attempt download on network streams Prova a scaricare con flussi di rete - + Buffer size (-1 default value) Dimensione buffer (-1 valore predefinito) - + Choose file Seleziona file - + All files Tutti i file @@ -357,12 +363,12 @@ UserElementSettings - + User defined elements Elementi definiti dall'utente - + Only for advanced user! Solo per utenti avanzati! @@ -370,17 +376,17 @@ VolumeSettings - + Volume Volume - + Normalized volume Volume normalizzato - + Reset Azzera diff --git a/lisp/i18n/ts/it_IT/lisp.ts b/lisp/i18n/ts/it_IT/lisp.ts index ce78b9081..6952961c1 100644 --- a/lisp/i18n/ts/it_IT/lisp.ts +++ b/lisp/i18n/ts/it_IT/lisp.ts @@ -4,22 +4,22 @@ About - + Authors Autori - + Contributors Contributori - + Translators Traduttori - + About Linux Show Player Riguardo Linux Show Player @@ -27,42 +27,32 @@ AboutDialog - - Linux Show Player is a cue-player designed for stage productions. - Linux Show Player è un cue-player pensato per produzioni teatrali. - - - + Web site Sito web - - Users group - Gruppo di discussione - - - + Source code Codice sorgente - + Info Informazioni - + License Licenza - + Contributors Contributori - + Discussion Discussione @@ -70,12 +60,12 @@ Actions - + Undo: {} Annulla: {} - + Redo: {} Ripeti: {} @@ -83,7 +73,7 @@ AppConfiguration - + LiSP preferences Preferenze di LiSP @@ -91,243 +81,109 @@ AppGeneralSettings - - Startup layout - Layout iniziale - - - - Use startup dialog - Usa layout iniziale - - - - Application theme - Stile applicazione - - - + Default layout Layout Predefinito - + Enable startup layout selector Attivare il selettore di layout all'avvio - + Application themes Temi dell'applicazione - - UI theme - Tema dell'interfaccia utente + + UI theme: + UI theme: - - Icons theme - Tema dell'icone + + Icons theme: + Icons theme: - CartLayout + ApplicationError - - Add page - Aggiungi pagina - - - - Add pages - Aggiungi pagine - - - - Remove current page - Rimuovi pagina corrente - - - - Countdown mode - Modalità countdown - - - - Show seek-bars - Mostra barre di avanzamento - - - - Show dB-meters - Mostra indicatori dB - - - - Show volume - Mostra volume - - - - Show accurate time - Mostra tempo accurato - - - - Edit cue - Modifica Cue - - - - Remove - Rimuovi - - - - Select - Seleziona - - - - Play - Riproduci - - - - Pause - Pausa - - - - Stop - Ferma - - - - Reset volume - Resetta volume - - - - Number of Pages: - Numero di Pagine: - - - - Warning - Attenzione - - - - Every cue in the page will be lost. - Ogni cue nella pagina sarà persa. - - - - Are you sure to continue? - Sei sicuro di voler continuare? - - - - Page - Pagina - - - - Default behaviors - Comportamenti predefiniti - - - - Automatically add new page - Aggiungi automaticamente nuove pagine - - - - Grid size - Dimensione griglia + + Startup error + Startup error + + + ConfigurationDebug - - Number of columns - Numero di colonne + + Configuration written at {} + Configuration written at {} + + + ConfigurationInfo - - Number of rows - Numero di righe + + New configuration installed at {} + New configuration installed at {} CueAction - + Default Automatica - + Pause Pausa - + Start Avvia - + Stop Ferma - - FadeInStart - Avvio con Dissolvenza - - - - FadeOutStop - Ferma con Dissolvenza - - - - FadeOutPause - Pausa con Dissolvenza - - - + Faded Start Avvia con dissolvenza - + Faded Resume Riprendi con dissolvenza - + Faded Pause Pausa con dissolvenza - + Faded Stop Ferma con dissolvenza - + Faded Interrupt Interrompi con dissolvenza - + Resume Riprendi - + Do Nothing Non fare nulla @@ -335,12 +191,12 @@ CueActionLog - + Cue settings changed: "{}" Impostazioni cue cambiate "{}" - + Cues settings changed. Impostazioni di più cue cambiate. @@ -348,42 +204,42 @@ CueAppearanceSettings - + The appearance depends on the layout L'aspetto dipende dal layout - + Cue name Nome della cue - + NoName NoName - + Description/Note Descrizione/Note - + Set Font Size Dimensione carattere - + Color Colore - + Select background color Selezione colore di sfondo - + Select font color Seleziona colore del carattere @@ -391,7 +247,7 @@ CueName - + Media Cue Cue Multimediale @@ -399,100 +255,85 @@ CueNextAction - + Do Nothing Non fare nulla + + + Trigger after the end + Trigger after the end + - Auto Follow - Auto Follow + Trigger after post wait + Trigger after post wait + + + + Select after the end + Select after the end - - Auto Next - Auto Next + + Select after post wait + Select after post wait CueSettings - + Pre wait Pre wait - + Wait before cue execution Attesa prima dell'esecuzione della cue - + Post wait Post wait - + Wait after cue execution Attesa dopo l'esecuzione della cue - + Next action Azione successiva - - Interrupt Fade - Dissolvenza Interruzioni - - - - Fade Action - Operazioni di Dissolvenza - - - - Behaviours - Comportamenti - - - - Pre/Post Wait - Attesa Prima/Dopo - - - - Fade In/Out - Dissolvenza Ingresso/Uscita - - - + Start action Azione d'avvio - + Default action to start the cue Azione standard per avviare la cue - + Stop action Azione di arresto - + Default action to stop the cue Azione standard per arrestare la cue - + Interrupt fade Interrupt fade - + Fade actions Fade actions @@ -500,17 +341,17 @@ Fade - + Linear Lineare - + Quadratic Quadratica - + Quadratic2 Quadratica2 @@ -518,99 +359,43 @@ FadeEdit - - Duration (sec) - Durata (sec) + + Duration (sec): + Duration (sec): - - Curve - Curva + + Curve: + Curve: FadeSettings - + Fade In Dissolvenza Ingresso - + Fade Out Dissolvenza Uscita - - LayoutDescription - - - Organize cues in grid like pages - Organizza le cue in pagine simili a tabelle - - - - Organize the cues in a list - Organizza le cue in una lista - - - - LayoutDetails - - - Click a cue to run it - Clicca una cue per eseguirla - - - - SHIFT + Click to edit a cue - SHIFT + Click per modificare una cue - - - - CTRL + Click to select a cue - CTRL + Click per selezionare una cue - - - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Spazio o Doppio Click per modificare una cue - - - - To copy cues drag them while pressing CTRL - Per copiare le cue trascinale mentre premi CTRL - - - - To copy cues drag them while pressing SHIFT - Per copiare le cue trascinale mentre premi SHIFT - - - - CTRL + Left Click to select cues - CTRL + Click Sinistro per selezionare una cue - - - - To move cues drag them - Per spostare una cue trascinala - - LayoutSelect - + Layout selection Selezione del layout - + Select layout Seleziona layout - + Open file Apri file @@ -618,518 +403,363 @@ ListLayout - - Show playing cues - Mostra cue in riproduzione - - - - Show dB-meters - Mostra indicatori dB - - - - Show seek-bars - Mostra barre di avanzamento - - - - Show accurate time - Mostra tempo accurato - - - - Auto-select next cue - Auto-seleziona prossima cue - - - - Edit cue - Modifica Cue - - - - Remove - Rimuovi - - - - Select - Seleziona - - - - Stop all - Ferma tutte - - - - Pause all - Sospendi tutte - - - - Restart all - Riavvia tutte - - - - Stop - Ferma - - - - Restart - Riavvia - - - - Default behaviors - Comportamenti predefiniti - - - - At list end: - A fine lista: - - - - Go key: - Tasto "Go": - - - - Interrupt all - Interrompiti tutte - - - - Fade-Out all - Sfuma tutte in uscita - - - - Fade-In all - Sfuma tutte in ingresso - - - - Use fade - Usa dissolvenza - - - - Stop Cue - Ferma Cue - - - - Pause Cue - Pausa Cue - - - - Restart Cue - Riavvia Cue - - - - Interrupt Cue - Interrompi Cue - - - + Stop All Ferma Tutte - + Pause All Pausa Tutte - - Restart All - Riavvia Tutte - - - + Interrupt All Interrompi Tutte - + Use fade (global actions) Use fade (global actions) - + Resume All Resume All - - ListLayoutHeader - - - Cue - Cue - - - - Pre wait - Pre wait - - - - Action - Azione - - - - Post wait - Post wait - - - - ListLayoutInfoPanel - - - Cue name - Nome della cue - - - - Cue description - Descrizione della cue - - Logging - - Information - Informazione - - - + Debug Debug - + Warning Avviso - + Error Errore - - Details: - Dettagli: - - - + Dismiss all Dismiss all - + Show details Show details - + Linux Show Player - Log Viewer Linux Show Player - Log Viewer - + Info Info - + Critical Critical - + Time Time - + Milliseconds Milliseconds - + Logger name Logger name - + Level Level - + Message Message - + Function Function - + Path name Path name - + File name File name - + Line no. Line no. - + Module Module - + Process ID Process ID - + Process name Process name - + Thread ID Thread ID - + Thread name Thread name + + + Exception info + Exception info + MainWindow - + &File &File - + New session Nuova sessione - + Open Apri - + Save session Salva sessione - + Preferences Preferenze - + Save as Salva come - + Full Screen Schermo Intero - + Exit Esci - + &Edit &Modifica - + Undo Annulla - + Redo Ripeti - + Select all Seleziona tutti - + Select all media cues Seleziona tutte le media-cue - + Deselect all Deseleziona tutti - + CTRL+SHIFT+A CTRL+SHIFT+A - + Invert selection Inverti selezione - + CTRL+I CTRL+I - + Edit selected Modifica selezionati - + CTRL+SHIFT+E CTRL+SHIFT+E - + &Layout &Layout - + &Tools &Strumenti - + Edit selection Modifica selezionati - + &About &About - + About Informazioni - + About Qt Informazioni su Qt - - Undone: - Annullato: - - - - Redone: - Ripetuto: - - - + Close session Chiudi sessione - + The current session is not saved. La sessione corrente non è salvata. - + Discard the changes? Scartare la modifiche? - - MediaCueMenus - - - Audio cue (from file) - Cue audio (da file) - - - - Select media files - Seleziona file multimediali - - MediaCueSettings - + Start time Posizione iniziale - + Stop position of the media Posizione in cui la traccia si deve fermare - + Stop time Posizione finale - + Start position of the media Posizione da cui la traccia deve partire - + Loop Loop + + + PluginsError + + + Failed to load "{}" + Failed to load "{}" + + + + Failed to terminate plugin: "{}" + Failed to terminate plugin: "{}" + + + + the requested plugin is not loaded: {} + the requested plugin is not loaded: {} + + + + PluginsInfo + + + Plugin loaded: "{}" + Plugin loaded: "{}" + - - Repetition after first play (-1 = infinite) - Ripetizioni dopo la prima riproduzione (-1 = infinite) + + Plugin terminated: "{}" + Plugin terminated: "{}" + + + + PluginsWarning + + + Cannot satisfy dependencies for: {} + Cannot satisfy dependencies for: {} QColorButton - + Right click to reset Clicca con il pulsante destro per resettare @@ -1137,44 +767,49 @@ SettingsPageName - + Appearance Aspetto - + General Generale - + Cue Cue - + Cue Settings Impostazioni Cue - + Plugins Plugins - + Behaviours Behaviours - + Pre/Post Wait Pre/Post Wait - + Fade In/Out Fade In/Out + + + Layouts + Layouts + diff --git a/lisp/i18n/ts/it_IT/list_layout.ts b/lisp/i18n/ts/it_IT/list_layout.ts index f7cf6801b..a3d5f607a 100644 --- a/lisp/i18n/ts/it_IT/list_layout.ts +++ b/lisp/i18n/ts/it_IT/list_layout.ts @@ -4,7 +4,7 @@ LayoutDescription - + Organize the cues in a list Organize the cues in a list @@ -12,163 +12,191 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue SHIFT + Space or Double-Click to edit a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them To move cues drag them + + LayoutName + + + List Layout + List Layout + + ListLayout - + Default behaviors Default behaviors - + Show playing cues Show playing cues - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue - + Enable selection mode Enable selection mode - - Go key: - Go key: - - - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - - Edit selected cues - Edit selected cues - - - + Remove cue Remove cue - - Remove selected cues - Remove selected cues - - - + Selection mode Selection mode - + Pause all Pause all - + Stop all Stop all - + Interrupt all Interrupt all - + Resume all Resume all - + Fade-Out all Fade-Out all - + Fade-In all Fade-In all + + + GO key: + GO key: + + + + GO action: + GO action: + + + + GO delay (ms): + GO delay (ms): + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove selected + Remove selected + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait @@ -176,14 +204,22 @@ ListLayoutInfoPanel - + Cue name Cue name - + Cue description Cue description + + SettingsPageName + + + List Layout + List Layout + + diff --git a/lisp/i18n/ts/it_IT/media_info.ts b/lisp/i18n/ts/it_IT/media_info.ts index 75c4dc63b..404a699ac 100644 --- a/lisp/i18n/ts/it_IT/media_info.ts +++ b/lisp/i18n/ts/it_IT/media_info.ts @@ -4,32 +4,27 @@ MediaInfo - + Media Info Informazioni Media - - Error - Errore - - - + No info to display Nessuna informazione da mostrare - + Info Informazione - + Value Valore - + Warning Warning diff --git a/lisp/i18n/ts/it_IT/midi.ts b/lisp/i18n/ts/it_IT/midi.ts index 8e280b534..230c4f9dc 100644 --- a/lisp/i18n/ts/it_IT/midi.ts +++ b/lisp/i18n/ts/it_IT/midi.ts @@ -1,20 +1,144 @@ + + MIDICue + + + MIDI Message + MIDI Message + + + + Message type + Message type + + + + MIDIMessageAttr + + + Channel + Channel + + + + Note + Note + + + + Velocity + Velocity + + + + Control + Control + + + + Program + Program + + + + Value + Value + + + + Song + Song + + + + Pitch + Pitch + + + + Position + Position + + + + MIDIMessageType + + + Note ON + Note ON + + + + Note OFF + Note OFF + + + + Polyphonic After-touch + Polyphonic After-touch + + + + Control/Mode Change + Control/Mode Change + + + + Program Change + Program Change + + + + Channel After-touch + Channel After-touch + + + + Pitch Bend Change + Pitch Bend Change + + + + Song Select + Song Select + + + + Song Position + Song Position + + + + Start + Start + + + + Stop + Stop + + + + Continue + Continue + + MIDISettings - + MIDI default devices Dispositivi MIDI predefiniti - + Input Ingresso - + Output Uscita @@ -22,7 +146,7 @@ SettingsPageName - + MIDI settings Impostazioni MIDI diff --git a/lisp/i18n/ts/it_IT/network.ts b/lisp/i18n/ts/it_IT/network.ts index df29dedd4..9f372d313 100644 --- a/lisp/i18n/ts/it_IT/network.ts +++ b/lisp/i18n/ts/it_IT/network.ts @@ -1,45 +1,69 @@ + + APIServerInfo + + + Stop serving network API + Stop serving network API + + + + ApiServerError + + + Network API server stopped working. + Network API server stopped working. + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + NetworkDiscovery - + Host discovery Host discovery - + Manage hosts Manage hosts - + Discover hosts Discover hosts - + Manually add a host Manually add a host - + Remove selected host Remove selected host - + Remove all host Remove all host - + Address Address - + Host IP Host IP diff --git a/lisp/i18n/ts/it_IT/osc.ts b/lisp/i18n/ts/it_IT/osc.ts index d925c26e3..bbe2f58d9 100644 --- a/lisp/i18n/ts/it_IT/osc.ts +++ b/lisp/i18n/ts/it_IT/osc.ts @@ -1,25 +1,54 @@ + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + OscSettings - + OSC Settings OSC Settings - + Input Port: Input Port: - + Output Port: Output Port: - + Hostname: Hostname: @@ -27,7 +56,7 @@ SettingsPageName - + OSC settings OSC settings diff --git a/lisp/i18n/ts/it_IT/presets.ts b/lisp/i18n/ts/it_IT/presets.ts index 790c789ba..22dbb2103 100644 --- a/lisp/i18n/ts/it_IT/presets.ts +++ b/lisp/i18n/ts/it_IT/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Crea Cue - + Load on selected Cues Carica sulle cue selezionate @@ -17,132 +17,112 @@ Presets - + Presets Preset - - Load preset - Carica preset - - - + Save as preset Salva come preset - + Cannot scan presets Impossibile cercare i preset - + Error while deleting preset "{}" Errore durate l'eliminazione del preset "{}"; - + Cannot load preset "{}" Impossible caricare il preset "{}" - + Cannot save preset "{}" Impossibile salvare il preset "{}" - + Cannot rename preset "{}" Impossible rinominare il preset "{}" - + Select Preset Seleziona Preset - - Preset already exists, overwrite? - Preset esistente, sovrascrivere? - - - + Preset name Nome preset - + Add Aggiungi - + Rename Rinomina - + Edit Modifica - + Remove Rimuovi - + Export selected Esporta selezionati - + Import Importa - + Warning Avviso - + The same name is already used! Lo stesso nome è già in uso! - - Cannot create a cue from this preset: {} - Impossibile creare cue da questo preset: {} - - - + Cannot export correctly. Impossibile esportare correttamente. - - Some presets already exists, overwrite? - Alcuni preset esistono già, sovrascriverli? - - - + Cannot import correctly. Impossible importare correttamente. - + Cue type Tipo di cue - + Load on cue Load on cue - + Load on selected cues Load on selected cues diff --git a/lisp/i18n/ts/it_IT/rename_cues.ts b/lisp/i18n/ts/it_IT/rename_cues.ts index 68bbe03a5..c29e7143c 100644 --- a/lisp/i18n/ts/it_IT/rename_cues.ts +++ b/lisp/i18n/ts/it_IT/rename_cues.ts @@ -4,92 +4,72 @@ RenameCues - + Rename Cues Rename Cues - + Rename cues Rename cues - + Current Current - + Preview Preview - + Capitalize Capitalize - + Lowercase Lowercase - + Uppercase Uppercase - + Remove Numbers Remove Numbers - + Add numbering Add numbering - + Reset Reset - - Rename all cue. () in regex below usable with $0, $1 ... - Rename all cue. () in regex below usable with $0, $1 ... - - - + Type your regex here: Type your regex here: - + Regex help Regex help + + + RenameUiDebug - - You can use Regexes to rename your cues. - -Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... -In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. - -Exemple : -^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. -Only the numbers are between parenthesis and will be usable with $0 in the first line. - -For more information about Regexes, consult python documentation - You can use Regexes to rename your cues. - -Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... -In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. - -Exemple : -^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. -Only the numbers are between parenthesis and will be usable with $0 in the first line. - -For more information about Regexes, consult python documentation + + Regex error: Invalid pattern + Regex error: Invalid pattern diff --git a/lisp/i18n/ts/it_IT/replay_gain.ts b/lisp/i18n/ts/it_IT/replay_gain.ts index 1b391c402..4aed1e3e5 100644 --- a/lisp/i18n/ts/it_IT/replay_gain.ts +++ b/lisp/i18n/ts/it_IT/replay_gain.ts @@ -4,49 +4,80 @@ ReplayGain - + ReplayGain / Normalization ReplayGain / Normalizzazione - + Calculate Calcola - + Reset all Resetta tutto - + Reset selected Resetta selezionati - + Threads number Numero di processi - + Apply only to selected media Applica solo alle traccie selezionate - + ReplayGain to (dB SPL) ReplayGain a (dB SPL) - + Normalize to (dB) Normalizza a (dB) - + Processing files ... Processando ... + + ReplayGainDebug + + + Applied gain for: {} + Applied gain for: {} + + + + Discarded gain for: {} + Discarded gain for: {} + + + + ReplayGainInfo + + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} + + diff --git a/lisp/i18n/ts/it_IT/synchronizer.ts b/lisp/i18n/ts/it_IT/synchronizer.ts index 32d96b177..e894d98f0 100644 --- a/lisp/i18n/ts/it_IT/synchronizer.ts +++ b/lisp/i18n/ts/it_IT/synchronizer.ts @@ -1,73 +1,20 @@ - - SyncPeerDialog - - - Manage connected peers - Gestisci peer connessi - - - - Discover peers - Ricerca peer - - - - Manually add a peer - Aggiungi peer manualmente - - - - Remove selected peer - Rimuovi peer selezionato - - - - Remove all peers - Rimuovi tutti i peer - - - - Address - Indirizzo - - - - Peer IP - IP del peer - - - - Error - Error - - - - Already connected - Già connesso - - - - Cannot add peer - Impossibile aggiungere il peer - - Synchronizer - + Synchronization Sincronizzazione - + Manage connected peers Gestisci peer connessi - + Show your IP Visualizza IP @@ -76,10 +23,5 @@ Your IP is: Il tuo IP è: - - - Discovering peers ... - Ricerca peer in corso ... - diff --git a/lisp/i18n/ts/it_IT/timecode.ts b/lisp/i18n/ts/it_IT/timecode.ts index 5566e8d0f..a1d07532c 100644 --- a/lisp/i18n/ts/it_IT/timecode.ts +++ b/lisp/i18n/ts/it_IT/timecode.ts @@ -4,12 +4,12 @@ SettingsPageName - + Timecode Settings Impostazioni Timecode - + Timecode Timecode @@ -17,92 +17,48 @@ Timecode - - Cannot send timecode. - Impossibile inviare il timecode. - - - - OLA has stopped. - OLA si è fermato. - - - + Cannot load timecode protocol: "{}" Cannot load timecode protocol: "{}" + + + TimecodeError - - Cannot send timecode. -OLA has stopped. - Cannot send timecode. -OLA has stopped. + + Cannot send timecode. + Cannot send timecode. TimecodeSettings - - OLA Timecode Settings - Impostazioni Timecode OLA - - - - Enable Plugin - Attiva Plugin - - - - High-Resolution Timecode - Timecode ad alta risoluzione - - - + Timecode Format: Formato Timecode: - - OLA status - Stato OLA - - - - OLA is not running - start the OLA daemon. - OLA non è in esecuzione - avvia il demone OLA. - - - + Replace HOURS by a static track number Sostituire ORE con un numero di traccia statico - - Enable ArtNet Timecode - Attiva ArtNet Timecode - - - + Track number Numero di traccia - - To send ArtNet Timecode you need to setup a running OLA session! - Per utilizzare il Timecode ArtNet devi avviare una sessione OLA! - - - + Enable Timecode Enable Timecode - + Timecode Settings Timecode Settings - + Timecode Protocol: Timecode Protocol: diff --git a/lisp/i18n/ts/it_IT/triggers.ts b/lisp/i18n/ts/it_IT/triggers.ts index 0d3a4673f..4a3bd9a8f 100644 --- a/lisp/i18n/ts/it_IT/triggers.ts +++ b/lisp/i18n/ts/it_IT/triggers.ts @@ -4,22 +4,22 @@ CueTriggers - + Started Avviata - + Paused Sospesa - + Stopped Fermata - + Ended Finita @@ -27,7 +27,7 @@ SettingsPageName - + Triggers Triggers @@ -35,27 +35,27 @@ TriggersSettings - + Add Aggiungi - + Remove Rimuovi - + Trigger Evento - + Cue Cue - + Action Azione diff --git a/lisp/i18n/ts/nl_BE/action_cues.ts b/lisp/i18n/ts/nl_BE/action_cues.ts index 346a9c81f..14e3e1c06 100644 --- a/lisp/i18n/ts/nl_BE/action_cues.ts +++ b/lisp/i18n/ts/nl_BE/action_cues.ts @@ -1,25 +1,41 @@ + + ActionCuesDebug + + + Registered cue: "{}" + Registered cue: "{}" + + + + ActionsCuesError + + + Cannot create cue {} + Cannot create cue {} + + CollectionCue - + Add Add - + Remove Remove - + Cue Cue - + Action Action @@ -27,50 +43,35 @@ CommandCue - - Process ended with an error status. - Process ended with an error status. - - - - Exit code: - Exit code: - - - + Command Command - + Command to execute, as in a shell Command to execute, as in a shell - + Discard command output Discard command output - + Ignore command errors Ignore command errors - + Kill instead of terminate Kill instead of terminate - - - Command cue ended with an error status. Exit code: {} - Command cue ended with an error status. Exit code: {} - Cue Name - + OSC Settings OSC Settings @@ -78,42 +79,42 @@ CueName - + Command Cue Command Cue - + MIDI Cue MIDI Cue - + Volume Control Volume Control - + Seek Cue Seek Cue - + Collection Cue Collection Cue - + Stop-All Stop-All - + Index Action Index Action - + OSC Cue OSC Cue @@ -121,68 +122,55 @@ IndexActionCue - + Index Index - + Use a relative index Use a relative index - + Target index Target index - + Action Action - + No suggestion No suggestion - + Suggested cue name Suggested cue name - - MIDICue - - - MIDI Message - MIDI Message - - - - Message type - Message type - - Osc Cue - + Type Type - + Argument Argument - + FadeTo FadeTo - + Fade Fade @@ -190,75 +178,88 @@ OscCue - - Error during cue execution. - Error during cue execution. - - - + OSC Message OSC Message - + Add Add - + Remove Remove - + Test Test - + OSC Path: (example: "/path/to/something") OSC Path: (example: "/path/to/something") - + Fade Fade - + Time (sec) Time (sec) - + Curve Curve + + OscCueError + + + Could not parse argument list, nothing sent + Could not parse argument list, nothing sent + + + + Error while parsing arguments, nothing sent + Error while parsing arguments, nothing sent + + + + Error during cue execution. + Error during cue execution. + + SeekCue - + Cue Cue - + Click to select Click to select - + Not selected Not selected - + Seek Seek - + Time to reach Time to reach @@ -266,37 +267,37 @@ SettingsPageName - + Command Command - + MIDI Settings MIDI Settings - + Volume Settings Volume Settings - + Seek Settings Seek Settings - + Edit Collection Edit Collection - + Action Settings Action Settings - + Stop Settings Stop Settings @@ -304,7 +305,7 @@ StopAll - + Stop Action Stop Action @@ -312,34 +313,37 @@ VolumeControl - - Error during cue execution - Error during cue execution - - - + Cue Cue - + Click to select Click to select - + Not selected Not selected - + Volume to reach Volume to reach - + Fade Fade + + VolumeControlError + + + Error during cue execution. + Error during cue execution. + + diff --git a/lisp/i18n/ts/nl_BE/cart_layout.ts b/lisp/i18n/ts/nl_BE/cart_layout.ts index c2b04113e..cc0ecb7ec 100644 --- a/lisp/i18n/ts/nl_BE/cart_layout.ts +++ b/lisp/i18n/ts/nl_BE/cart_layout.ts @@ -4,120 +4,115 @@ CartLayout - + Default behaviors Default behaviors - + Countdown mode Countdown mode - + Show seek-bars Show seek-bars - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show volume Show volume - - Automatically add new page - Automatically add new page - - - + Grid size Grid size - - Number of columns - Number of columns - - - - Number of rows - Number of rows - - - + Play Play - + Pause Pause - + Stop Stop - + Reset volume Reset volume - + Add page Add page - + Add pages Add pages - + Remove current page Remove current page - + Number of Pages: Number of Pages: - + Page {number} Page {number} - + Warning Warning - + Every cue in the page will be lost. Every cue in the page will be lost. - + Are you sure to continue? Are you sure to continue? + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + LayoutDescription - + Organize cues in grid like pages Organize cues in grid like pages @@ -125,52 +120,68 @@ LayoutDetails - + Click a cue to run it Click a cue to run it - + SHIFT + Click to edit a cue SHIFT + Click to edit a cue - + CTRL + Click to select a cue CTRL + Click to select a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them while pressing SHIFT To move cues drag them while pressing SHIFT + + LayoutName + + + Cart Layout + Cart Layout + + ListLayout - + Edit cue Edit cue - + Edit selected cues Edit selected cues - + Remove cue Remove cue - + Remove selected cues Remove selected cues + + SettingsPageName + + + Cart Layout + Cart Layout + + diff --git a/lisp/i18n/ts/nl_BE/controller.ts b/lisp/i18n/ts/nl_BE/controller.ts index f2cbdcfef..9fe9989e0 100644 --- a/lisp/i18n/ts/nl_BE/controller.ts +++ b/lisp/i18n/ts/nl_BE/controller.ts @@ -4,7 +4,7 @@ Controller - + Cannot load controller protocol: "{}" Cannot load controller protocol: "{}" @@ -12,17 +12,17 @@ ControllerKeySettings - + Key Key - + Action Action - + Shortcuts Shortcuts @@ -30,47 +30,47 @@ ControllerMidiSettings - + MIDI MIDI - + Type Type - + Channel Channel - + Note Note - + Action Action - + Filter "note on" Filter "note on" - + Filter "note off" Filter "note off" - + Capture Capture - + Listening MIDI messages ... Listening MIDI messages ... @@ -78,57 +78,52 @@ ControllerOscSettings - + OSC Message OSC Message - - OSC Path: (example: "/path/to/something") - OSC Path: (example: "/path/to/something") - - - + OSC OSC - + Path Path - + Types Types - + Arguments Arguments - + Actions Actions - + OSC Capture OSC Capture - + Add Add - + Remove Remove - + Capture Capture @@ -136,25 +131,78 @@ ControllerSettings - + Add Add - + Remove Remove + + GlobalAction + + + Go + Go + + + + Reset + Reset + + + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + Osc Cue - + Type Type - + Argument Argument @@ -162,12 +210,12 @@ OscCue - + Add Add - + Remove Remove @@ -175,24 +223,29 @@ SettingsPageName - + Cue Control Cue Control - + MIDI Controls MIDI Controls - + Keyboard Shortcuts Keyboard Shortcuts - + OSC Controls OSC Controls + + + Layout Controls + Layout Controls + diff --git a/lisp/i18n/ts/nl_BE/gst_backend.ts b/lisp/i18n/ts/nl_BE/gst_backend.ts index dfa534cbe..fc313b84e 100644 --- a/lisp/i18n/ts/nl_BE/gst_backend.ts +++ b/lisp/i18n/ts/nl_BE/gst_backend.ts @@ -4,60 +4,55 @@ AlsaSinkSettings - + ALSA device ALSA device - - - ALSA devices, as defined in an asound configuration file - ALSA devices, as defined in an asound configuration file - AudioDynamicSettings - + Compressor Compressor - + Expander Expander - + Soft Knee Soft Knee - + Hard Knee Hard Knee - + Compressor/Expander Compressor/Expander - + Type Type - + Curve Shape Curve Shape - + Ratio Ratio - + Threshold (dB) Threshold (dB) @@ -65,22 +60,22 @@ AudioPanSettings - + Audio Pan Audio Pan - + Center Center - + Left Left - + Right Right @@ -88,22 +83,22 @@ DbMeterSettings - + DbMeter settings DbMeter settings - + Time between levels (ms) Time between levels (ms) - + Peak ttl (ms) Peak ttl (ms) - + Peak falloff (dB/sec) Peak falloff (dB/sec) @@ -111,7 +106,7 @@ Equalizer10Settings - + 10 Bands Equalizer (IIR) 10 Bands Equalizer (IIR) @@ -119,28 +114,44 @@ GstBackend - + Audio cue (from file) Audio cue (from file) - + Select media files Select media files + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + GstMediaSettings - + Change Pipeline Change Pipeline + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + GstPipelineEdit - + Edit Pipeline Edit Pipeline @@ -148,7 +159,7 @@ GstSettings - + Pipeline Pipeline @@ -156,32 +167,32 @@ JackSinkSettings - + Connections Connections - + Edit connections Edit connections - + Output ports Output ports - + Input ports Input ports - + Connect Connect - + Disconnect Disconnect @@ -189,77 +200,77 @@ MediaElementName - + Compressor/Expander Compressor/Expander - + Audio Pan Audio Pan - + PulseAudio Out PulseAudio Out - + Volume Volume - + dB Meter dB Meter - + System Input System Input - + ALSA Out ALSA Out - + JACK Out JACK Out - + Custom Element Custom Element - + System Out System Out - + Pitch Pitch - + URI Input URI Input - + 10 Bands Equalizer 10 Bands Equalizer - + Speed Speed - + Preset Input Preset Input @@ -267,12 +278,12 @@ PitchSettings - + Pitch Pitch - + {0:+} semitones {0:+} semitones @@ -280,7 +291,7 @@ PresetSrcSettings - + Presets Presets @@ -288,17 +299,12 @@ SettingsPageName - - GStreamer settings - GStreamer settings - - - + Media Settings Media Settings - + GStreamer GStreamer @@ -306,7 +312,7 @@ SpeedSettings - + Speed Speed @@ -314,42 +320,42 @@ UriInputSettings - + Source Source - + Find File Find File - + Buffering Buffering - + Use Buffering Use Buffering - + Attempt download on network streams Attempt download on network streams - + Buffer size (-1 default value) Buffer size (-1 default value) - + Choose file Choose file - + All files All files @@ -357,12 +363,12 @@ UserElementSettings - + User defined elements User defined elements - + Only for advanced user! Only for advanced user! @@ -370,17 +376,17 @@ VolumeSettings - + Volume Volume - + Normalized volume Normalized volume - + Reset Reset diff --git a/lisp/i18n/ts/nl_BE/lisp.ts b/lisp/i18n/ts/nl_BE/lisp.ts index 4129770cc..f9a223380 100644 --- a/lisp/i18n/ts/nl_BE/lisp.ts +++ b/lisp/i18n/ts/nl_BE/lisp.ts @@ -4,22 +4,22 @@ About - + Authors Authors - + Contributors Contributors - + Translators Translators - + About Linux Show Player About Linux Show Player @@ -27,42 +27,32 @@ AboutDialog - - Linux Show Player is a cue-player designed for stage productions. - Linux Show Player is a cue-player designed for stage productions. - - - + Web site Web site - - Users group - Users group - - - + Source code Source code - + Info Info - + License License - + Contributors Contributors - + Discussion Discussion @@ -70,12 +60,12 @@ Actions - + Undo: {} Undo: {} - + Redo: {} Redo: {} @@ -83,7 +73,7 @@ AppConfiguration - + LiSP preferences LiSP preferences @@ -91,243 +81,109 @@ AppGeneralSettings - - Startup layout - Startup layout - - - - Use startup dialog - Use startup dialog - - - - Application theme - Application theme - - - + Default layout Default layout - + Enable startup layout selector Enable startup layout selector - + Application themes Application themes - - UI theme - UI theme + + UI theme: + UI theme: - - Icons theme - Icons theme + + Icons theme: + Icons theme: - CartLayout - - - Add page - Add page - - - - Add pages - Add pages - - - - Remove current page - Remove current page - - - - Countdown mode - Countdown mode - - - - Show seek-bars - Show seek-bars - - - - Show dB-meters - Show dB-meters - - - - Show volume - Show volume - - - - Show accurate time - Show accurate time - - - - Edit cue - Edit cue - - - - Remove - Remove - - - - Select - Select - - - - Play - Play - - - - Pause - Pause - - - - Stop - Stop - - - - Reset volume - Reset volume - - - - Number of Pages: - Number of Pages: - - - - Warning - Warning - - - - Every cue in the page will be lost. - Every cue in the page will be lost. - - - - Are you sure to continue? - Are you sure to continue? - + ApplicationError - - Page - Page - - - - Default behaviors - Default behaviors - - - - Automatically add new page - Automatically add new page - - - - Grid size - Grid size + + Startup error + Startup error + + + ConfigurationDebug - - Number of columns - Number of columns + + Configuration written at {} + Configuration written at {} + + + ConfigurationInfo - - Number of rows - Number of rows + + New configuration installed at {} + New configuration installed at {} CueAction - + Default Default - + Pause Pause - + Start Start - + Stop Stop - - FadeInStart - FadeInStart - - - - FadeOutStop - FadeOutStop - - - - FadeOutPause - FadeOutPause - - - + Faded Start Faded Start - + Faded Resume Faded Resume - + Faded Pause Faded Pause - + Faded Stop Faded Stop - + Faded Interrupt Faded Interrupt - + Resume Resume - + Do Nothing Do Nothing @@ -335,12 +191,12 @@ CueActionLog - + Cue settings changed: "{}" Cue settings changed: "{}" - + Cues settings changed. Cues settings changed. @@ -348,42 +204,42 @@ CueAppearanceSettings - + The appearance depends on the layout The appearance depends on the layout - + Cue name Cue name - + NoName NoName - + Description/Note Description/Note - + Set Font Size Set Font Size - + Color Color - + Select background color Select background color - + Select font color Select font color @@ -391,7 +247,7 @@ CueName - + Media Cue Media Cue @@ -399,100 +255,85 @@ CueNextAction - + Do Nothing Do Nothing + + + Trigger after the end + Trigger after the end + - Auto Follow - Auto Follow + Trigger after post wait + Trigger after post wait - - Auto Next - Auto Next + + Select after the end + Select after the end + + + + Select after post wait + Select after post wait CueSettings - + Pre wait Pre wait - + Wait before cue execution Wait before cue execution - + Post wait Post wait - + Wait after cue execution Wait after cue execution - + Next action Next action - - Interrupt Fade - Interrupt Fade - - - - Fade Action - Fade Action - - - - Behaviours - Behaviours - - - - Pre/Post Wait - Pre/Post Wait - - - - Fade In/Out - Fade In/Out - - - + Start action Start action - + Default action to start the cue Default action to start the cue - + Stop action Stop action - + Default action to stop the cue Default action to stop the cue - + Interrupt fade Interrupt fade - + Fade actions Fade actions @@ -500,17 +341,17 @@ Fade - + Linear Linear - + Quadratic Quadratic - + Quadratic2 Quadratic2 @@ -518,99 +359,43 @@ FadeEdit - - Duration (sec) - Duration (sec) + + Duration (sec): + Duration (sec): - - Curve - Curve + + Curve: + Curve: FadeSettings - + Fade In Fade In - + Fade Out Fade Out - - LayoutDescription - - - Organize cues in grid like pages - Organize cues in grid like pages - - - - Organize the cues in a list - Organize the cues in a list - - - - LayoutDetails - - - Click a cue to run it - Click a cue to run it - - - - SHIFT + Click to edit a cue - SHIFT + Click to edit a cue - - - - CTRL + Click to select a cue - CTRL + Click to select a cue - - - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Space or Double-Click to edit a cue - - - - To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL - - - - To copy cues drag them while pressing SHIFT - To copy cues drag them while pressing SHIFT - - - - CTRL + Left Click to select cues - CTRL + Left Click to select cues - - - - To move cues drag them - To move cues drag them - - LayoutSelect - + Layout selection Layout selection - + Select layout Select layout - + Open file Open file @@ -618,518 +403,363 @@ ListLayout - - Show playing cues - Show playing cues - - - - Show dB-meters - Show dB-meters - - - - Show seek-bars - Show seek-bars - - - - Show accurate time - Show accurate time - - - - Auto-select next cue - Auto-select next cue - - - - Edit cue - Edit cue - - - - Remove - Remove - - - - Select - Select - - - - Stop all - Stop all - - - - Pause all - Pause all - - - - Restart all - Restart all - - - - Stop - Stop - - - - Restart - Restart - - - - Default behaviors - Default behaviors - - - - At list end: - At list end: - - - - Go key: - Go key: - - - - Interrupt all - Interrupt all - - - - Fade-Out all - Fade-Out all - - - - Fade-In all - Fade-In all - - - - Use fade - Use fade - - - - Stop Cue - Stop Cue - - - - Pause Cue - Pause Cue - - - - Restart Cue - Restart Cue - - - - Interrupt Cue - Interrupt Cue - - - + Stop All Stop All - + Pause All Pause All - - Restart All - Restart All - - - + Interrupt All Interrupt All - + Use fade (global actions) Use fade (global actions) - + Resume All Resume All - - ListLayoutHeader - - - Cue - Cue - - - - Pre wait - Pre wait - - - - Action - Action - - - - Post wait - Post wait - - - - ListLayoutInfoPanel - - - Cue name - Cue name - - - - Cue description - Cue description - - Logging - - Information - Information - - - + Debug Debug - + Warning Warning - + Error Error - - Details: - Details: - - - + Dismiss all Dismiss all - + Show details Show details - + Linux Show Player - Log Viewer Linux Show Player - Log Viewer - + Info Info - + Critical Critical - + Time Time - + Milliseconds Milliseconds - + Logger name Logger name - + Level Level - + Message Message - + Function Function - + Path name Path name - + File name File name - + Line no. Line no. - + Module Module - + Process ID Process ID - + Process name Process name - + Thread ID Thread ID - + Thread name Thread name + + + Exception info + Exception info + MainWindow - + &File &File - + New session New session - + Open Open - + Save session Save session - + Preferences Preferences - + Save as Save as - + Full Screen Full Screen - + Exit Exit - + &Edit &Edit - + Undo Undo - + Redo Redo - + Select all Select all - + Select all media cues Select all media cues - + Deselect all Deselect all - + CTRL+SHIFT+A CTRL+SHIFT+A - + Invert selection Invert selection - + CTRL+I CTRL+I - + Edit selected Edit selected - + CTRL+SHIFT+E CTRL+SHIFT+E - + &Layout &Layout - + &Tools &Tools - + Edit selection Edit selection - + &About &About - + About About - + About Qt About Qt - - Undone: - Undone: - - - - Redone: - Redone: - - - + Close session Close session - + The current session is not saved. The current session is not saved. - + Discard the changes? Discard the changes? - - MediaCueMenus - - - Audio cue (from file) - Audio cue (from file) - - - - Select media files - Select media files - - MediaCueSettings - + Start time Start time - + Stop position of the media Stop position of the media - + Stop time Stop time - + Start position of the media Start position of the media - + Loop Loop + + + PluginsError + + + Failed to load "{}" + Failed to load "{}" + + + + Failed to terminate plugin: "{}" + Failed to terminate plugin: "{}" + + + + the requested plugin is not loaded: {} + the requested plugin is not loaded: {} + + + + PluginsInfo + + + Plugin loaded: "{}" + Plugin loaded: "{}" + - - Repetition after first play (-1 = infinite) - Repetition after first play (-1 = infinite) + + Plugin terminated: "{}" + Plugin terminated: "{}" + + + + PluginsWarning + + + Cannot satisfy dependencies for: {} + Cannot satisfy dependencies for: {} QColorButton - + Right click to reset Right click to reset @@ -1137,44 +767,49 @@ SettingsPageName - + Appearance Appearance - + General General - + Cue Cue - + Cue Settings Cue Settings - + Plugins Plugins - + Behaviours Behaviours - + Pre/Post Wait Pre/Post Wait - + Fade In/Out Fade In/Out + + + Layouts + Layouts + diff --git a/lisp/i18n/ts/nl_BE/list_layout.ts b/lisp/i18n/ts/nl_BE/list_layout.ts index 454cae5fc..41b5c6eac 100644 --- a/lisp/i18n/ts/nl_BE/list_layout.ts +++ b/lisp/i18n/ts/nl_BE/list_layout.ts @@ -4,7 +4,7 @@ LayoutDescription - + Organize the cues in a list Organize the cues in a list @@ -12,163 +12,191 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue SHIFT + Space or Double-Click to edit a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them To move cues drag them + + LayoutName + + + List Layout + List Layout + + ListLayout - + Default behaviors Default behaviors - + Show playing cues Show playing cues - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue - + Enable selection mode Enable selection mode - - Go key: - Go key: - - - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - - Edit selected cues - Edit selected cues - - - + Remove cue Remove cue - - Remove selected cues - Remove selected cues - - - + Selection mode Selection mode - + Pause all Pause all - + Stop all Stop all - + Interrupt all Interrupt all - + Resume all Resume all - + Fade-Out all Fade-Out all - + Fade-In all Fade-In all + + + GO key: + GO key: + + + + GO action: + GO action: + + + + GO delay (ms): + GO delay (ms): + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove selected + Remove selected + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait @@ -176,14 +204,22 @@ ListLayoutInfoPanel - + Cue name Cue name - + Cue description Cue description + + SettingsPageName + + + List Layout + List Layout + + diff --git a/lisp/i18n/ts/nl_BE/media_info.ts b/lisp/i18n/ts/nl_BE/media_info.ts index e91c05015..773bb30de 100644 --- a/lisp/i18n/ts/nl_BE/media_info.ts +++ b/lisp/i18n/ts/nl_BE/media_info.ts @@ -4,32 +4,27 @@ MediaInfo - + Media Info Media Info - - Error - Error - - - + No info to display No info to display - + Info Info - + Value Value - + Warning Warning diff --git a/lisp/i18n/ts/nl_BE/midi.ts b/lisp/i18n/ts/nl_BE/midi.ts index 7b442fabf..65c17e530 100644 --- a/lisp/i18n/ts/nl_BE/midi.ts +++ b/lisp/i18n/ts/nl_BE/midi.ts @@ -1,20 +1,144 @@ + + MIDICue + + + MIDI Message + MIDI Message + + + + Message type + Message type + + + + MIDIMessageAttr + + + Channel + Channel + + + + Note + Note + + + + Velocity + Velocity + + + + Control + Control + + + + Program + Program + + + + Value + Value + + + + Song + Song + + + + Pitch + Pitch + + + + Position + Position + + + + MIDIMessageType + + + Note ON + Note ON + + + + Note OFF + Note OFF + + + + Polyphonic After-touch + Polyphonic After-touch + + + + Control/Mode Change + Control/Mode Change + + + + Program Change + Program Change + + + + Channel After-touch + Channel After-touch + + + + Pitch Bend Change + Pitch Bend Change + + + + Song Select + Song Select + + + + Song Position + Song Position + + + + Start + Start + + + + Stop + Stop + + + + Continue + Continue + + MIDISettings - + MIDI default devices MIDI default devices - + Input Input - + Output Output @@ -22,7 +146,7 @@ SettingsPageName - + MIDI settings MIDI settings diff --git a/lisp/i18n/ts/nl_BE/network.ts b/lisp/i18n/ts/nl_BE/network.ts index d05fba554..aab98b429 100644 --- a/lisp/i18n/ts/nl_BE/network.ts +++ b/lisp/i18n/ts/nl_BE/network.ts @@ -1,45 +1,69 @@ + + APIServerInfo + + + Stop serving network API + Stop serving network API + + + + ApiServerError + + + Network API server stopped working. + Network API server stopped working. + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + NetworkDiscovery - + Host discovery Host discovery - + Manage hosts Manage hosts - + Discover hosts Discover hosts - + Manually add a host Manually add a host - + Remove selected host Remove selected host - + Remove all host Remove all host - + Address Address - + Host IP Host IP diff --git a/lisp/i18n/ts/nl_BE/osc.ts b/lisp/i18n/ts/nl_BE/osc.ts index 3d16b4883..47a3b801e 100644 --- a/lisp/i18n/ts/nl_BE/osc.ts +++ b/lisp/i18n/ts/nl_BE/osc.ts @@ -1,25 +1,54 @@ + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + OscSettings - + OSC Settings OSC Settings - + Input Port: Input Port: - + Output Port: Output Port: - + Hostname: Hostname: @@ -27,7 +56,7 @@ SettingsPageName - + OSC settings OSC settings diff --git a/lisp/i18n/ts/nl_BE/presets.ts b/lisp/i18n/ts/nl_BE/presets.ts index 1c7f716f2..c6ce90b18 100644 --- a/lisp/i18n/ts/nl_BE/presets.ts +++ b/lisp/i18n/ts/nl_BE/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Create Cue - + Load on selected Cues Load on selected Cues @@ -17,132 +17,112 @@ Presets - + Presets Presets - - Load preset - Load preset - - - + Save as preset Save as preset - + Cannot scan presets Cannot scan presets - + Error while deleting preset "{}" Error while deleting preset "{}" - + Cannot load preset "{}" Cannot load preset "{}" - + Cannot save preset "{}" Cannot save preset "{}" - + Cannot rename preset "{}" Cannot rename preset "{}" - + Select Preset Select Preset - - Preset already exists, overwrite? - Preset already exists, overwrite? - - - + Preset name Preset name - + Add Add - + Rename Rename - + Edit Edit - + Remove Remove - + Export selected Export selected - + Import Import - + Warning Warning - + The same name is already used! The same name is already used! - - Cannot create a cue from this preset: {} - Cannot create a cue from this preset: {} - - - + Cannot export correctly. Cannot export correctly. - - Some presets already exists, overwrite? - Some presets already exists, overwrite? - - - + Cannot import correctly. Cannot import correctly. - + Cue type Cue type - + Load on cue Load on cue - + Load on selected cues Load on selected cues diff --git a/lisp/i18n/ts/nl_BE/rename_cues.ts b/lisp/i18n/ts/nl_BE/rename_cues.ts index 5bdbaa77c..3555a4276 100644 --- a/lisp/i18n/ts/nl_BE/rename_cues.ts +++ b/lisp/i18n/ts/nl_BE/rename_cues.ts @@ -4,92 +4,72 @@ RenameCues - + Rename Cues Rename Cues - + Rename cues Rename cues - + Current Current - + Preview Preview - + Capitalize Capitalize - + Lowercase Lowercase - + Uppercase Uppercase - + Remove Numbers Remove Numbers - + Add numbering Add numbering - + Reset Reset - - Rename all cue. () in regex below usable with $0, $1 ... - Rename all cue. () in regex below usable with $0, $1 ... - - - + Type your regex here: Type your regex here: - + Regex help Regex help + + + RenameUiDebug - - You can use Regexes to rename your cues. - -Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... -In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. - -Exemple : -^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. -Only the numbers are between parenthesis and will be usable with $0 in the first line. - -For more information about Regexes, consult python documentation - You can use Regexes to rename your cues. - -Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... -In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. - -Exemple : -^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. -Only the numbers are between parenthesis and will be usable with $0 in the first line. - -For more information about Regexes, consult python documentation + + Regex error: Invalid pattern + Regex error: Invalid pattern diff --git a/lisp/i18n/ts/nl_BE/replay_gain.ts b/lisp/i18n/ts/nl_BE/replay_gain.ts index 3a405f69e..3f48cad48 100644 --- a/lisp/i18n/ts/nl_BE/replay_gain.ts +++ b/lisp/i18n/ts/nl_BE/replay_gain.ts @@ -4,49 +4,80 @@ ReplayGain - + ReplayGain / Normalization ReplayGain / Normalization - + Calculate Calculate - + Reset all Reset all - + Reset selected Reset selected - + Threads number Threads number - + Apply only to selected media Apply only to selected media - + ReplayGain to (dB SPL) ReplayGain to (dB SPL) - + Normalize to (dB) Normalize to (dB) - + Processing files ... Processing files ... + + ReplayGainDebug + + + Applied gain for: {} + Applied gain for: {} + + + + Discarded gain for: {} + Discarded gain for: {} + + + + ReplayGainInfo + + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} + + diff --git a/lisp/i18n/ts/nl_BE/synchronizer.ts b/lisp/i18n/ts/nl_BE/synchronizer.ts index a8b218e05..4798ba3ec 100644 --- a/lisp/i18n/ts/nl_BE/synchronizer.ts +++ b/lisp/i18n/ts/nl_BE/synchronizer.ts @@ -1,73 +1,20 @@ - - SyncPeerDialog - - - Manage connected peers - Manage connected peers - - - - Discover peers - Discover peers - - - - Manually add a peer - Manually add a peer - - - - Remove selected peer - Remove selected peer - - - - Remove all peers - Remove all peers - - - - Address - Address - - - - Peer IP - Peer IP - - - - Error - Error - - - - Already connected - Already connected - - - - Cannot add peer - Cannot add peer - - Synchronizer - + Synchronization Synchronization - + Manage connected peers Manage connected peers - + Show your IP Show your IP @@ -76,10 +23,5 @@ Your IP is: Your IP is: - - - Discovering peers ... - Discovering peers ... - diff --git a/lisp/i18n/ts/nl_BE/timecode.ts b/lisp/i18n/ts/nl_BE/timecode.ts index 1306f5c48..bfd0ad96e 100644 --- a/lisp/i18n/ts/nl_BE/timecode.ts +++ b/lisp/i18n/ts/nl_BE/timecode.ts @@ -4,12 +4,12 @@ SettingsPageName - + Timecode Settings Timecode Settings - + Timecode Timecode @@ -17,92 +17,48 @@ Timecode - - Cannot send timecode. - Cannot send timecode. - - - - OLA has stopped. - OLA has stopped. - - - + Cannot load timecode protocol: "{}" Cannot load timecode protocol: "{}" + + + TimecodeError - - Cannot send timecode. -OLA has stopped. - Cannot send timecode. -OLA has stopped. + + Cannot send timecode. + Cannot send timecode. TimecodeSettings - - OLA Timecode Settings - OLA Timecode Settings - - - - Enable Plugin - Enable Plugin - - - - High-Resolution Timecode - High-Resolution Timecode - - - + Timecode Format: Timecode Format: - - OLA status - OLA status - - - - OLA is not running - start the OLA daemon. - OLA is not running - start the OLA daemon. - - - + Replace HOURS by a static track number Replace HOURS by a static track number - - Enable ArtNet Timecode - Enable ArtNet Timecode - - - + Track number Track number - - To send ArtNet Timecode you need to setup a running OLA session! - To send ArtNet Timecode you need to setup a running OLA session! - - - + Enable Timecode Enable Timecode - + Timecode Settings Timecode Settings - + Timecode Protocol: Timecode Protocol: diff --git a/lisp/i18n/ts/nl_BE/triggers.ts b/lisp/i18n/ts/nl_BE/triggers.ts index 1d67b7dfa..1d2add464 100644 --- a/lisp/i18n/ts/nl_BE/triggers.ts +++ b/lisp/i18n/ts/nl_BE/triggers.ts @@ -4,22 +4,22 @@ CueTriggers - + Started Started - + Paused Paused - + Stopped Stopped - + Ended Ended @@ -27,7 +27,7 @@ SettingsPageName - + Triggers Triggers @@ -35,27 +35,27 @@ TriggersSettings - + Add Add - + Remove Remove - + Trigger Trigger - + Cue Cue - + Action Action diff --git a/lisp/i18n/ts/sl_SI/action_cues.ts b/lisp/i18n/ts/sl_SI/action_cues.ts index e5caf2963..34186ae88 100644 --- a/lisp/i18n/ts/sl_SI/action_cues.ts +++ b/lisp/i18n/ts/sl_SI/action_cues.ts @@ -1,25 +1,41 @@ + + ActionCuesDebug + + + Registered cue: "{}" + Registered cue: "{}" + + + + ActionsCuesError + + + Cannot create cue {} + Cannot create cue {} + + CollectionCue - + Add Dodaj - + Remove Odstrani - + Cue Vrsta - + Action Akcija @@ -27,50 +43,35 @@ CommandCue - - Process ended with an error status. - Procesiranje se je končalo z napako. - - - - Exit code: - Izhodni ukaz: - - - + Command Ukaz - + Command to execute, as in a shell Ukaz za izvršitev, kot v ukazni lupini - + Discard command output Zavrži izhod ukaza - + Ignore command errors Ignoriraj napake ukaza - + Kill instead of terminate Kill namesto terminate - - - Command cue ended with an error status. Exit code: {} - Command cue ended with an error status. Exit code: {} - Cue Name - + OSC Settings OSC Settings @@ -78,42 +79,42 @@ CueName - + Command Cue Ukazna vrsta - + MIDI Cue MIDI vrsta - + Volume Control Krmiljenje glasnosti - + Seek Cue Vrsta iskanj - + Collection Cue Zbirke v vrsti - + Stop-All Ustavi vse - + Index Action Akcija indeksa - + OSC Cue OSC Cue @@ -121,68 +122,55 @@ IndexActionCue - + Index Indeks - + Use a relative index Uporabi relativni indeks - + Target index Ciljni indeks - + Action Akcija - + No suggestion No suggestion - + Suggested cue name Suggested cue name - - MIDICue - - - MIDI Message - MIDI sporočilo - - - - Message type - Tip sporočila - - Osc Cue - + Type Type - + Argument Argument - + FadeTo FadeTo - + Fade Fade @@ -190,75 +178,88 @@ OscCue - - Error during cue execution. - Error during cue execution. - - - + OSC Message OSC Message - + Add Add - + Remove Remove - + Test Test - + OSC Path: (example: "/path/to/something") OSC Path: (example: "/path/to/something") - + Fade Fade - + Time (sec) Time (sec) - + Curve Curve + + OscCueError + + + Could not parse argument list, nothing sent + Could not parse argument list, nothing sent + + + + Error while parsing arguments, nothing sent + Error while parsing arguments, nothing sent + + + + Error during cue execution. + Error during cue execution. + + SeekCue - + Cue Vrsta - + Click to select Klikni za izbor - + Not selected Ni izbrano - + Seek Iskanje - + Time to reach Čas za dosego @@ -266,37 +267,37 @@ SettingsPageName - + Command Ukaz - + MIDI Settings MIDI nastavitve - + Volume Settings Nastavitve glasnosti - + Seek Settings Nastavitve iskanja - + Edit Collection Uredi zbirko - + Action Settings Nastavitve akcije - + Stop Settings Nastavitve ustavitve @@ -304,7 +305,7 @@ StopAll - + Stop Action Akcija ustavitve @@ -312,34 +313,37 @@ VolumeControl - - Error during cue execution - Napaka med izvajanjem vrste - - - + Cue Vrsta - + Click to select Klikni za izbor - + Not selected Ni izbrano - + Volume to reach Glasnost za dosego - + Fade Pojenjanje + + VolumeControlError + + + Error during cue execution. + Error during cue execution. + + diff --git a/lisp/i18n/ts/sl_SI/cart_layout.ts b/lisp/i18n/ts/sl_SI/cart_layout.ts index ce87c97a0..23d5dc2a0 100644 --- a/lisp/i18n/ts/sl_SI/cart_layout.ts +++ b/lisp/i18n/ts/sl_SI/cart_layout.ts @@ -4,120 +4,115 @@ CartLayout - + Default behaviors Default behaviors - + Countdown mode Countdown mode - + Show seek-bars Show seek-bars - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show volume Show volume - - Automatically add new page - Automatically add new page - - - + Grid size Grid size - - Number of columns - Number of columns - - - - Number of rows - Number of rows - - - + Play Play - + Pause Pause - + Stop Stop - + Reset volume Reset volume - + Add page Add page - + Add pages Add pages - + Remove current page Remove current page - + Number of Pages: Number of Pages: - + Page {number} Page {number} - + Warning Warning - + Every cue in the page will be lost. Every cue in the page will be lost. - + Are you sure to continue? Are you sure to continue? + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + LayoutDescription - + Organize cues in grid like pages Organize cues in grid like pages @@ -125,52 +120,68 @@ LayoutDetails - + Click a cue to run it Click a cue to run it - + SHIFT + Click to edit a cue SHIFT + Click to edit a cue - + CTRL + Click to select a cue CTRL + Click to select a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them while pressing SHIFT To move cues drag them while pressing SHIFT + + LayoutName + + + Cart Layout + Cart Layout + + ListLayout - + Edit cue Edit cue - + Edit selected cues Edit selected cues - + Remove cue Remove cue - + Remove selected cues Remove selected cues + + SettingsPageName + + + Cart Layout + Cart Layout + + diff --git a/lisp/i18n/ts/sl_SI/controller.ts b/lisp/i18n/ts/sl_SI/controller.ts index ecc2cd873..01efb1c10 100644 --- a/lisp/i18n/ts/sl_SI/controller.ts +++ b/lisp/i18n/ts/sl_SI/controller.ts @@ -4,7 +4,7 @@ Controller - + Cannot load controller protocol: "{}" Cannot load controller protocol: "{}" @@ -12,17 +12,17 @@ ControllerKeySettings - + Key Ključ - + Action Akcija - + Shortcuts Bližnjica @@ -30,47 +30,47 @@ ControllerMidiSettings - + MIDI MIDI - + Type Tip - + Channel Kanal - + Note Beležka - + Action Akcija - + Filter "note on" Filter "Beležka aktivna" - + Filter "note off" Filter "Beležka neaktivna" - + Capture Zajemi - + Listening MIDI messages ... Poslušam MIDI sporočila ... @@ -78,57 +78,52 @@ ControllerOscSettings - + OSC Message OSC Message - - OSC Path: (example: "/path/to/something") - OSC Path: (example: "/path/to/something") - - - + OSC OSC - + Path Path - + Types Types - + Arguments Arguments - + Actions Actions - + OSC Capture OSC Capture - + Add Add - + Remove Remove - + Capture Capture @@ -136,25 +131,78 @@ ControllerSettings - + Add Dodaj - + Remove Odstrani + + GlobalAction + + + Go + Go + + + + Reset + Reset + + + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + Osc Cue - + Type Type - + Argument Argument @@ -162,12 +210,12 @@ OscCue - + Add Add - + Remove Remove @@ -175,24 +223,29 @@ SettingsPageName - + Cue Control Krmiljenje vrste - + MIDI Controls Krmiljenje MIDI - + Keyboard Shortcuts Bližnjice na tipkovnici - + OSC Controls OSC Controls + + + Layout Controls + Layout Controls + diff --git a/lisp/i18n/ts/sl_SI/gst_backend.ts b/lisp/i18n/ts/sl_SI/gst_backend.ts index 1939a626b..31614da54 100644 --- a/lisp/i18n/ts/sl_SI/gst_backend.ts +++ b/lisp/i18n/ts/sl_SI/gst_backend.ts @@ -4,60 +4,55 @@ AlsaSinkSettings - + ALSA device ALSA naprava - - - ALSA devices, as defined in an asound configuration file - ALSA naprava, kot definirana v asound konfiguracijski datoteki - AudioDynamicSettings - + Compressor Kompresor - + Expander Razširjevalec - + Soft Knee Mehko koleno - + Hard Knee Trdo koleno - + Compressor/Expander Kompresor/Razširjevalec - + Type Tip - + Curve Shape Oblika krivulje - + Ratio Razmerje - + Threshold (dB) Prag (dB) @@ -65,22 +60,22 @@ AudioPanSettings - + Audio Pan Avdio nagib - + Center Sredina - + Left Levo - + Right Desno @@ -88,22 +83,22 @@ DbMeterSettings - + DbMeter settings Nastavitve Db Metra - + Time between levels (ms) Čas med nivoji (ms) - + Peak ttl (ms) Vrhovni TTL (ms) - + Peak falloff (dB/sec) Vrhovni padec (dB/s) @@ -111,7 +106,7 @@ Equalizer10Settings - + 10 Bands Equalizer (IIR) 10 kanalni Izenačevalnik (IIR) @@ -119,28 +114,44 @@ GstBackend - + Audio cue (from file) Audio cue (from file) - + Select media files Select media files + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + GstMediaSettings - + Change Pipeline Spremeni cevovod + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + GstPipelineEdit - + Edit Pipeline Uredi cevovod @@ -148,7 +159,7 @@ GstSettings - + Pipeline Cevovod @@ -156,32 +167,32 @@ JackSinkSettings - + Connections Povezave - + Edit connections Uredi povezave - + Output ports Izhodna vrata - + Input ports Vhodna vrata - + Connect Poveži - + Disconnect Prekini @@ -189,77 +200,77 @@ MediaElementName - + Compressor/Expander Kompresor/Razširjevalec - + Audio Pan Avdio nagib - + PulseAudio Out PulseAudio izhod - + Volume Glasnost - + dB Meter dB meter - + System Input Sistemski vhod - + ALSA Out ALSA izhod - + JACK Out JACK izhod - + Custom Element Element po meri - + System Out Sistemski izhod - + Pitch Višina - + URI Input URI vhod - + 10 Bands Equalizer 10 kanalni Izenačevalnik - + Speed Hitrost - + Preset Input Pred-nastavljeni vhod @@ -267,12 +278,12 @@ PitchSettings - + Pitch Višina - + {0:+} semitones {0:+} poltoni @@ -280,7 +291,7 @@ PresetSrcSettings - + Presets Pred-nastavitve @@ -288,17 +299,12 @@ SettingsPageName - - GStreamer settings - GStreamer nastavitve - - - + Media Settings Nastavitve medija - + GStreamer GStreamer @@ -306,7 +312,7 @@ SpeedSettings - + Speed Hitrost @@ -314,42 +320,42 @@ UriInputSettings - + Source Izvor - + Find File Najdi datoteko - + Buffering Pred-pomnjenje - + Use Buffering Uporabi pred-pomnjenje - + Attempt download on network streams Poizkusi prenesti omrežni tok - + Buffer size (-1 default value) Velikost pred-pomnilnika (-1 privzeta vrednost) - + Choose file Izberi datoteko - + All files Vse datoteke @@ -357,12 +363,12 @@ UserElementSettings - + User defined elements Uporabniško definirani elementi - + Only for advanced user! Le za napredne uporabnike! @@ -370,17 +376,17 @@ VolumeSettings - + Volume Glasnost - + Normalized volume Normalizirana glasnost - + Reset Ponastavi diff --git a/lisp/i18n/ts/sl_SI/lisp.ts b/lisp/i18n/ts/sl_SI/lisp.ts index 8f2b57d0f..a93f63dee 100644 --- a/lisp/i18n/ts/sl_SI/lisp.ts +++ b/lisp/i18n/ts/sl_SI/lisp.ts @@ -4,22 +4,22 @@ About - + Authors Avtorji - + Contributors Prispevkarji - + Translators Prevajalci - + About Linux Show Player Opis Linux Show Player-ja @@ -27,42 +27,32 @@ AboutDialog - - Linux Show Player is a cue-player designed for stage productions. - Linux Show Player je predvajalnik vrst, prilagojen za odrsko/gledališčno rabo - - - + Web site Spletna stran - - Users group - Skupina uporabnikov - - - + Source code Izvorna koda - + Info Podrobnosti - + License Licenca - + Contributors Prispevkarji - + Discussion Discussion @@ -70,12 +60,12 @@ Actions - + Undo: {} Undo: {} - + Redo: {} Redo: {} @@ -83,7 +73,7 @@ AppConfiguration - + LiSP preferences LiSP nastavitve @@ -91,243 +81,109 @@ AppGeneralSettings - - Startup layout - Začetna razporeditev - - - - Use startup dialog - Uporabi začetni dialog - - - - Application theme - Tema aplikacije - - - + Default layout Default layout - + Enable startup layout selector Enable startup layout selector - + Application themes Application themes - - UI theme - UI theme + + UI theme: + UI theme: - - Icons theme - Icons theme + + Icons theme: + Icons theme: - CartLayout - - - Add page - Dodaj stran - - - - Add pages - Dodaj strani - - - - Remove current page - Odstrani trenutno stran - - - - Countdown mode - Odštevalni način - - - - Show seek-bars - Prikaži iskalne stolpiče - - - - Show dB-meters - Prikaži dB metre - - - - Show volume - Prikaži glasnost - - - - Show accurate time - Prikaži točen čas - - - - Edit cue - Uredi vrsto - - - - Remove - Odstrani - - - - Select - Izberi - - - - Play - Predvajaj - - - - Pause - Premor - - - - Stop - Ustavi - - - - Reset volume - Ponastavi glasnost - - - - Number of Pages: - Število strani: - - - - Warning - Opozorilo - - - - Every cue in the page will be lost. - Vse vrste na strani bodo izgubljene - - - - Are you sure to continue? - Ste prepričani v nadaljevanje? - + ApplicationError - - Page - Stran - - - - Default behaviors - Privzeto obnašanje - - - - Automatically add new page - Samodejno dodaj novo stran - - - - Grid size - Velikost mreže + + Startup error + Startup error + + + ConfigurationDebug - - Number of columns - Število stolpcev + + Configuration written at {} + Configuration written at {} + + + ConfigurationInfo - - Number of rows - Število vrstic + + New configuration installed at {} + New configuration installed at {} CueAction - + Default Privzeto - + Pause Premor - + Start Zagon - + Stop Ustavi - - FadeInStart - Ojačevanje ob začetku - - - - FadeOutStop - Pojemanje ob ustavitvi - - - - FadeOutPause - Pojemanje ob premoru - - - + Faded Start Faded Start - + Faded Resume Faded Resume - + Faded Pause Faded Pause - + Faded Stop Faded Stop - + Faded Interrupt Faded Interrupt - + Resume Resume - + Do Nothing Do Nothing @@ -335,12 +191,12 @@ CueActionLog - + Cue settings changed: "{}" Nastavitev vrste spremenjene: "{}" - + Cues settings changed. Nastavitev vrst spremenjene. @@ -348,42 +204,42 @@ CueAppearanceSettings - + The appearance depends on the layout Prikaz je odvisen od uporabljene razporeditve - + Cue name Ime vrste - + NoName Brez imena - + Description/Note Opis/Zapiski - + Set Font Size Nastavi velikost pisave - + Color Barva - + Select background color Izberi barvo ozadja - + Select font color Izberi barvo pisave @@ -391,7 +247,7 @@ CueName - + Media Cue Medijska vrsta @@ -399,100 +255,85 @@ CueNextAction - + Do Nothing Do Nothing + + + Trigger after the end + Trigger after the end + - Auto Follow - Auto Follow + Trigger after post wait + Trigger after post wait - - Auto Next - Auto Next + + Select after the end + Select after the end + + + + Select after post wait + Select after post wait CueSettings - + Pre wait Pred čakanje - + Wait before cue execution Počakaj preden izvedeš vrsto - + Post wait Po čakanje - + Wait after cue execution Počakaj po izvedbi vrste - + Next action Naslednja akcija - - Interrupt Fade - Prekini prehod - - - - Fade Action - Akcija prehoda - - - - Behaviours - Obnašanja - - - - Pre/Post Wait - Pred/Po čakanje - - - - Fade In/Out - Prehod v/iz - - - + Start action Začetna akcija - + Default action to start the cue Privzeta akcija za zagon vrste - + Stop action Ustavi akcijo - + Default action to stop the cue Privzeta akcija ob ustavitvi vrste - + Interrupt fade Interrupt fade - + Fade actions Fade actions @@ -500,17 +341,17 @@ Fade - + Linear Linearno - + Quadratic Kvadratno - + Quadratic2 Kvadratno2 @@ -518,99 +359,43 @@ FadeEdit - - Duration (sec) - Trajanje (s) + + Duration (sec): + Duration (sec): - - Curve - Krivulja + + Curve: + Curve: FadeSettings - + Fade In Ojačevanje - + Fade Out Pojemanje - - LayoutDescription - - - Organize cues in grid like pages - Razporedi vrste v strani podobni mreži - - - - Organize the cues in a list - Razporedi vrste v seznam - - - - LayoutDetails - - - Click a cue to run it - Klik na vrsto za zagon - - - - SHIFT + Click to edit a cue - SHIFT + Klik, za ureditev vrste - - - - CTRL + Click to select a cue - CTRL + Klik, za izbor vrste - - - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Preslednica ali dvoklik za ureditev vrste - - - - To copy cues drag them while pressing CTRL - Za kopiranje vrst, med vlečenjem pridržite SHIFT - - - - To copy cues drag them while pressing SHIFT - Za kopiranje vrst, med vlečenjem pridržite SHIFT - - - - CTRL + Left Click to select cues - CTRL + Levi klik, za izbor vrst - - - - To move cues drag them - Za premikanje vrst jih povlecite - - LayoutSelect - + Layout selection Izbor razporeditve - + Select layout Izberi razporeditev - + Open file Odpri datoteko @@ -618,518 +403,363 @@ ListLayout - - Show playing cues - Prikaži predvajane vrste - - - - Show dB-meters - Prikaži dB metre - - - - Show seek-bars - Prikaži iskalne stolpiče - - - - Show accurate time - Prikaži točen čas - - - - Auto-select next cue - Samodejno izberi naslednjo vrsto - - - - Edit cue - Uredi vrsto - - - - Remove - Odstrani - - - - Select - Izberi - - - - Stop all - Ustavi vse - - - - Pause all - V premor vse - - - - Restart all - Ponovno zaženi vse - - - - Stop - Ustavi - - - - Restart - Ponovno zaženi - - - - Default behaviors - Privzeto obnašanje - - - - At list end: - Ob koncu seznama: - - - - Go key: - Sprožilni ključ: - - - - Interrupt all - Prekini vse - - - - Fade-Out all - Pojemanje vse - - - - Fade-In all - Ojačevanje vse - - - - Use fade - Uporabi prehod - - - - Stop Cue - Ustavi vrsto - - - - Pause Cue - Premor vrste - - - - Restart Cue - Ponovno zaženi vrsto - - - - Interrupt Cue - Prekini vrsto - - - + Stop All Ustavi vse - + Pause All V premor vse - - Restart All - Ponovno zaženi vse - - - + Interrupt All Prekini vse - + Use fade (global actions) Use fade (global actions) - + Resume All Resume All - - ListLayoutHeader - - - Cue - Vrsta - - - - Pre wait - Pred čakanje - - - - Action - Akcija - - - - Post wait - Po čakanje - - - - ListLayoutInfoPanel - - - Cue name - Ime vrste - - - - Cue description - Opis vrste - - Logging - - Information - Informacije - - - + Debug Razhroščevanje - + Warning Opozorilo - + Error Napaka - - Details: - Podrobnosti: - - - + Dismiss all Dismiss all - + Show details Show details - + Linux Show Player - Log Viewer Linux Show Player - Log Viewer - + Info Info - + Critical Critical - + Time Time - + Milliseconds Milliseconds - + Logger name Logger name - + Level Level - + Message Message - + Function Function - + Path name Path name - + File name File name - + Line no. Line no. - + Module Module - + Process ID Process ID - + Process name Process name - + Thread ID Thread ID - + Thread name Thread name + + + Exception info + Exception info + MainWindow - + &File &Datoteka - + New session Nova seja - + Open Odpri - + Save session Shrani sejo - + Preferences Nastavitve - + Save as Shrani kot - + Full Screen Polni zaslon - + Exit Izhod - + &Edit &Uredi - + Undo Razveljavi - + Redo Obnovi - + Select all Izberi vse - + Select all media cues Izberi vse medijske vrste - + Deselect all Od izberi vse - + CTRL+SHIFT+A CTRL+SHIFT+A - + Invert selection Invertiraj izbor - + CTRL+I CTRL+I - + Edit selected Uredi izbrano - + CTRL+SHIFT+E CTRL+SHIFT+E - + &Layout &Razporeditev - + &Tools &Orodja - + Edit selection Uredi izbor - + &About &Opis - + About Opis - + About Qt Opis Qt - - Undone: - Razveljavljeno: - - - - Redone: - Uveljavljeno: - - - + Close session Zapri sejo - + The current session is not saved. Trenutna seja ni shranjena. - + Discard the changes? Zavržem spremembe? - - MediaCueMenus - - - Audio cue (from file) - Avdio vrsta (iz datoteke) - - - - Select media files - Izberi medijske datoteke - - MediaCueSettings - + Start time Čas začetka - + Stop position of the media Zaustavitvena pozicija medija - + Stop time Čas zaustavitve - + Start position of the media Začetna pozicija medija - + Loop Ponavljaj + + + PluginsError + + + Failed to load "{}" + Failed to load "{}" + + + + Failed to terminate plugin: "{}" + Failed to terminate plugin: "{}" + + + + the requested plugin is not loaded: {} + the requested plugin is not loaded: {} + + + + PluginsInfo + + + Plugin loaded: "{}" + Plugin loaded: "{}" + - - Repetition after first play (-1 = infinite) - Ponovitev po prvem predvajanju (-1 = neskončno) + + Plugin terminated: "{}" + Plugin terminated: "{}" + + + + PluginsWarning + + + Cannot satisfy dependencies for: {} + Cannot satisfy dependencies for: {} QColorButton - + Right click to reset Desno klik za ponastavitev @@ -1137,44 +767,49 @@ SettingsPageName - + Appearance Prikaz - + General Splošno - + Cue Vrsta - + Cue Settings Nastavitve vrste - + Plugins Plugins - + Behaviours Behaviours - + Pre/Post Wait Pre/Post Wait - + Fade In/Out Fade In/Out + + + Layouts + Layouts + diff --git a/lisp/i18n/ts/sl_SI/list_layout.ts b/lisp/i18n/ts/sl_SI/list_layout.ts index 9ccb31406..056660c50 100644 --- a/lisp/i18n/ts/sl_SI/list_layout.ts +++ b/lisp/i18n/ts/sl_SI/list_layout.ts @@ -4,7 +4,7 @@ LayoutDescription - + Organize the cues in a list Organize the cues in a list @@ -12,163 +12,191 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue SHIFT + Space or Double-Click to edit a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them To move cues drag them + + LayoutName + + + List Layout + List Layout + + ListLayout - + Default behaviors Default behaviors - + Show playing cues Show playing cues - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue - + Enable selection mode Enable selection mode - - Go key: - Go key: - - - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - - Edit selected cues - Edit selected cues - - - + Remove cue Remove cue - - Remove selected cues - Remove selected cues - - - + Selection mode Selection mode - + Pause all Pause all - + Stop all Stop all - + Interrupt all Interrupt all - + Resume all Resume all - + Fade-Out all Fade-Out all - + Fade-In all Fade-In all + + + GO key: + GO key: + + + + GO action: + GO action: + + + + GO delay (ms): + GO delay (ms): + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove selected + Remove selected + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait @@ -176,14 +204,22 @@ ListLayoutInfoPanel - + Cue name Cue name - + Cue description Cue description + + SettingsPageName + + + List Layout + List Layout + + diff --git a/lisp/i18n/ts/sl_SI/media_info.ts b/lisp/i18n/ts/sl_SI/media_info.ts index ed55bbf65..adade229f 100644 --- a/lisp/i18n/ts/sl_SI/media_info.ts +++ b/lisp/i18n/ts/sl_SI/media_info.ts @@ -4,32 +4,27 @@ MediaInfo - + Media Info Podrobnosti o mediju - - Error - Napaka - - - + No info to display Brez podrobnosti za prikaz - + Info Podrobnosti - + Value Vrednost - + Warning Warning diff --git a/lisp/i18n/ts/sl_SI/midi.ts b/lisp/i18n/ts/sl_SI/midi.ts index c58884133..bf0ab8e91 100644 --- a/lisp/i18n/ts/sl_SI/midi.ts +++ b/lisp/i18n/ts/sl_SI/midi.ts @@ -1,20 +1,144 @@ + + MIDICue + + + MIDI Message + MIDI Message + + + + Message type + Message type + + + + MIDIMessageAttr + + + Channel + Channel + + + + Note + Note + + + + Velocity + Velocity + + + + Control + Control + + + + Program + Program + + + + Value + Value + + + + Song + Song + + + + Pitch + Pitch + + + + Position + Position + + + + MIDIMessageType + + + Note ON + Note ON + + + + Note OFF + Note OFF + + + + Polyphonic After-touch + Polyphonic After-touch + + + + Control/Mode Change + Control/Mode Change + + + + Program Change + Program Change + + + + Channel After-touch + Channel After-touch + + + + Pitch Bend Change + Pitch Bend Change + + + + Song Select + Song Select + + + + Song Position + Song Position + + + + Start + Start + + + + Stop + Stop + + + + Continue + Continue + + MIDISettings - + MIDI default devices Privzete MIDI naprave - + Input Vhod - + Output Izhod @@ -22,7 +146,7 @@ SettingsPageName - + MIDI settings MIDI nastavitve diff --git a/lisp/i18n/ts/sl_SI/network.ts b/lisp/i18n/ts/sl_SI/network.ts index 654f28be8..4210f94b8 100644 --- a/lisp/i18n/ts/sl_SI/network.ts +++ b/lisp/i18n/ts/sl_SI/network.ts @@ -1,45 +1,69 @@ + + APIServerInfo + + + Stop serving network API + Stop serving network API + + + + ApiServerError + + + Network API server stopped working. + Network API server stopped working. + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + NetworkDiscovery - + Host discovery Host discovery - + Manage hosts Manage hosts - + Discover hosts Discover hosts - + Manually add a host Manually add a host - + Remove selected host Remove selected host - + Remove all host Remove all host - + Address Address - + Host IP Host IP diff --git a/lisp/i18n/ts/sl_SI/osc.ts b/lisp/i18n/ts/sl_SI/osc.ts index cd68690ee..ec9d19730 100644 --- a/lisp/i18n/ts/sl_SI/osc.ts +++ b/lisp/i18n/ts/sl_SI/osc.ts @@ -1,25 +1,54 @@ + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + OscSettings - + OSC Settings OSC Settings - + Input Port: Input Port: - + Output Port: Output Port: - + Hostname: Hostname: @@ -27,7 +56,7 @@ SettingsPageName - + OSC settings OSC settings diff --git a/lisp/i18n/ts/sl_SI/presets.ts b/lisp/i18n/ts/sl_SI/presets.ts index d7c6827ba..008edbcd9 100644 --- a/lisp/i18n/ts/sl_SI/presets.ts +++ b/lisp/i18n/ts/sl_SI/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Ustvari vrsto - + Load on selected Cues Naloži izbrane vrste @@ -17,132 +17,112 @@ Presets - + Presets Pred-nastavitve - - Load preset - Naloži pred-nastavitve - - - + Save as preset Shrani kot pred-nastavitev - + Cannot scan presets Ne morem prebrat pred-nastavitve - + Error while deleting preset "{}" Napaka med brisanjem pred-nastavitve "{}" - + Cannot load preset "{}" Ne morem naložit pred-nastavitve "{}" - + Cannot save preset "{}" Ne morem shranit pred-nastavitev "{}" - + Cannot rename preset "{}" Ne morem preimenovat pred-nastavitve "{}" - + Select Preset Izberi pred-nastavitve - - Preset already exists, overwrite? - Pred-nastavitev že obstaja, prepišem? - - - + Preset name Ime pred-nastavitve - + Add Dodaj - + Rename Preimenuj - + Edit Uredi - + Remove Odstrani - + Export selected Izvozi izbrano - + Import Uvozi - + Warning Opozorilo - + The same name is already used! Ime je že v uporabi! - - Cannot create a cue from this preset: {} - Ne morem ustvarit vrste iz te pred-nastavitve: "{}" - - - + Cannot export correctly. Ne morem pravilno izvozit. - - Some presets already exists, overwrite? - Nekatere pred-nastavitve že obstajajo, prepišem? - - - + Cannot import correctly. Ne morem pravilno uvozit. - + Cue type Tip vrste - + Load on cue Load on cue - + Load on selected cues Load on selected cues diff --git a/lisp/i18n/ts/sl_SI/rename_cues.ts b/lisp/i18n/ts/sl_SI/rename_cues.ts index 95a17826a..ff72d956c 100644 --- a/lisp/i18n/ts/sl_SI/rename_cues.ts +++ b/lisp/i18n/ts/sl_SI/rename_cues.ts @@ -4,92 +4,72 @@ RenameCues - + Rename Cues Rename Cues - + Rename cues Rename cues - + Current Current - + Preview Preview - + Capitalize Capitalize - + Lowercase Lowercase - + Uppercase Uppercase - + Remove Numbers Remove Numbers - + Add numbering Add numbering - + Reset Reset - - Rename all cue. () in regex below usable with $0, $1 ... - Rename all cue. () in regex below usable with $0, $1 ... - - - + Type your regex here: Type your regex here: - + Regex help Regex help + + + RenameUiDebug - - You can use Regexes to rename your cues. - -Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... -In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. - -Exemple : -^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. -Only the numbers are between parenthesis and will be usable with $0 in the first line. - -For more information about Regexes, consult python documentation - You can use Regexes to rename your cues. - -Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... -In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. - -Exemple : -^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. -Only the numbers are between parenthesis and will be usable with $0 in the first line. - -For more information about Regexes, consult python documentation + + Regex error: Invalid pattern + Regex error: Invalid pattern diff --git a/lisp/i18n/ts/sl_SI/replay_gain.ts b/lisp/i18n/ts/sl_SI/replay_gain.ts index ba7b8201d..aac9bae36 100644 --- a/lisp/i18n/ts/sl_SI/replay_gain.ts +++ b/lisp/i18n/ts/sl_SI/replay_gain.ts @@ -4,49 +4,80 @@ ReplayGain - + ReplayGain / Normalization Ojačanje predvajanja / Normalizacija - + Calculate Izračunaj - + Reset all Ponastavi vse - + Reset selected Ponastavi izbrano - + Threads number Število niti - + Apply only to selected media Uveljavi samo na izbranih medijih - + ReplayGain to (dB SPL) Ojačaj predvajanje na (dB SPL) - + Normalize to (dB) Normaliziraj na (dB) - + Processing files ... Procesiram datoteke ... + + ReplayGainDebug + + + Applied gain for: {} + Applied gain for: {} + + + + Discarded gain for: {} + Discarded gain for: {} + + + + ReplayGainInfo + + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} + + diff --git a/lisp/i18n/ts/sl_SI/synchronizer.ts b/lisp/i18n/ts/sl_SI/synchronizer.ts index a44724669..615f011f5 100644 --- a/lisp/i18n/ts/sl_SI/synchronizer.ts +++ b/lisp/i18n/ts/sl_SI/synchronizer.ts @@ -1,73 +1,20 @@ - - SyncPeerDialog - - - Manage connected peers - Upravljaj povezane soležnike - - - - Discover peers - Odkrij soležnike - - - - Manually add a peer - Ročno dodaj soležnik - - - - Remove selected peer - Odstrani izbrani soležnik - - - - Remove all peers - Odstrani vse soležnike - - - - Address - Naslov - - - - Peer IP - IP soležnika - - - - Error - Napaka - - - - Already connected - Že povezan - - - - Cannot add peer - Ne morem dodat soležnika - - Synchronizer - + Synchronization Sinhronizacija - + Manage connected peers Upravljaj povezane soležnike - + Show your IP Prikaži tvoj IP @@ -76,10 +23,5 @@ Your IP is: Tvoj IP je: - - - Discovering peers ... - Odkrivam soležnike ... - diff --git a/lisp/i18n/ts/sl_SI/timecode.ts b/lisp/i18n/ts/sl_SI/timecode.ts index 90924f5c4..db4888006 100644 --- a/lisp/i18n/ts/sl_SI/timecode.ts +++ b/lisp/i18n/ts/sl_SI/timecode.ts @@ -4,12 +4,12 @@ SettingsPageName - + Timecode Settings Nastavitve časovnega zapisa - + Timecode Časovni zapis @@ -17,92 +17,48 @@ Timecode - - Cannot send timecode. - Ne morem poslat časovnega zapisa - - - - OLA has stopped. - OLA se je zaustavil - - - + Cannot load timecode protocol: "{}" Cannot load timecode protocol: "{}" + + + TimecodeError - - Cannot send timecode. -OLA has stopped. - Cannot send timecode. -OLA has stopped. + + Cannot send timecode. + Cannot send timecode. TimecodeSettings - - OLA Timecode Settings - Nastavitve OLA časovnega zapisa - - - - Enable Plugin - Omogoči vtičnik - - - - High-Resolution Timecode - Zelo podobni časovni zapis - - - + Timecode Format: Format časovnega zapisa: - - OLA status - OLA stanje - - - - OLA is not running - start the OLA daemon. - OLA ni zagnan - zaženite OLA daemon. - - - + Replace HOURS by a static track number Zamenjaj URE s statično številko sledi - - Enable ArtNet Timecode - Omogoči ArtNet časovni zapis - - - + Track number Številka sledi - - To send ArtNet Timecode you need to setup a running OLA session! - Za pošiljanje ArtNet časovnega zapisa, rabite nastavit aktivno OLA sejo! - - - + Enable Timecode Enable Timecode - + Timecode Settings Timecode Settings - + Timecode Protocol: Timecode Protocol: diff --git a/lisp/i18n/ts/sl_SI/triggers.ts b/lisp/i18n/ts/sl_SI/triggers.ts index 3a119e3f3..02f0e5d9b 100644 --- a/lisp/i18n/ts/sl_SI/triggers.ts +++ b/lisp/i18n/ts/sl_SI/triggers.ts @@ -4,22 +4,22 @@ CueTriggers - + Started Zagnan - + Paused V premoru - + Stopped Ustavljen - + Ended Končan @@ -27,7 +27,7 @@ SettingsPageName - + Triggers Prožilci @@ -35,27 +35,27 @@ TriggersSettings - + Add Dodaj - + Remove Odstrani - + Trigger Prožilec - + Cue Vrsta - + Action Akcija From 9846297d673955f87e3c0acb89f650b4b7912bcd Mon Sep 17 00:00:00 2001 From: pade Date: Sat, 19 Jan 2019 15:30:10 +0100 Subject: [PATCH 154/333] Save last session path (#148) --- lisp/application.py | 6 +++++- lisp/ui/layoutselect.py | 10 ++++++++-- lisp/ui/themes/dark/assetes.py | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lisp/application.py b/lisp/application.py index 261aa668c..9d6d60d3d 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -18,7 +18,7 @@ import json import logging from PyQt5.QtWidgets import QDialog, qApp -from os.path import exists +from os.path import exists, dirname from lisp import layout from lisp.core.actions_handler import MainActionsHandler @@ -166,6 +166,10 @@ def _save_to_file(self, session_file): """Save the current session into a file.""" self.session.session_file = session_file + # Save session path + self.conf.set("session.last_path", dirname(session_file)) + self.conf.write() + # Add the cues session_dict = {"cues": []} diff --git a/lisp/ui/layoutselect.py b/lisp/ui/layoutselect.py index 8b20dd734..a7a11e045 100644 --- a/lisp/ui/layoutselect.py +++ b/lisp/ui/layoutselect.py @@ -30,6 +30,7 @@ from lisp import layout from lisp.ui.ui_utils import translate +from lisp.core.configuration import AppConfig class LayoutSelect(QDialog): @@ -99,8 +100,13 @@ def show_description(self): ) def open_file(self): + app_config = AppConfig() + last_session_path = app_config.get("session.last_path", os.getenv("HOME")) path, _ = QFileDialog.getOpenFileName( - self, filter="*.lsp", directory=os.getenv("HOME") + self, filter="*.lsp", directory=last_session_path ) - self.filepath = path + if path != "": + self.filepath = path + app_config.set("session.last_path", os.path.dirname(path)) + app_config.write() self.accept() diff --git a/lisp/ui/themes/dark/assetes.py b/lisp/ui/themes/dark/assetes.py index 6d380be7a..3fcf23e29 100644 --- a/lisp/ui/themes/dark/assetes.py +++ b/lisp/ui/themes/dark/assetes.py @@ -1,4 +1,4 @@ - Resource object code +# Resource object code # # Created by: The Resource Compiler for PyQt5 (Qt v5.5.1) # From c590e32bbfa8ae1cea94b4a02e5bda9bba8e166c Mon Sep 17 00:00:00 2001 From: s0600204 Date: Wed, 28 Nov 2018 21:35:41 +0000 Subject: [PATCH 155/333] Split "Action cues" up & permit cue types to be defined by the plugins they belong to. --- lisp/cues/cue_factory.py | 39 +++++++++++++++++ lisp/plugins/action_cues/__init__.py | 42 ++----------------- lisp/plugins/midi/midi.py | 6 ++- .../plugins/{action_cues => midi}/midi_cue.py | 0 lisp/plugins/misc_cues/__init__.py | 38 +++++++++++++++++ .../{action_cues => misc_cues}/command_cue.py | 0 lisp/plugins/osc/osc.py | 6 +++ lisp/plugins/{action_cues => osc}/osc_cue.py | 0 8 files changed, 91 insertions(+), 40 deletions(-) rename lisp/plugins/{action_cues => midi}/midi_cue.py (100%) create mode 100644 lisp/plugins/misc_cues/__init__.py rename lisp/plugins/{action_cues => misc_cues}/command_cue.py (100%) rename lisp/plugins/{action_cues => osc}/osc_cue.py (100%) diff --git a/lisp/cues/cue_factory.py b/lisp/cues/cue_factory.py index 71e57f387..9803adaa0 100644 --- a/lisp/cues/cue_factory.py +++ b/lisp/cues/cue_factory.py @@ -15,10 +15,13 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging from copy import deepcopy from lisp.core.util import typename +from lisp.ui.ui_utils import translate +logger = logging.getLogger(__name__) class CueFactory: """Provide a generic factory to build different cues types. @@ -30,6 +33,42 @@ class CueFactory: # Register methods + @classmethod + def register_cue_type(cls, app, cue_class, cue_category=None): + cls.register_factory(cue_class.__name__, cue_class) + + def _new_cue_factory(app, cue_class): + def cue_factory(): + try: + cue = CueFactory.create_cue(cue_class.__name__) + + # Get the (last) index of the current selection + layout_selection = list(app.layout.selected_cues()) + if layout_selection: + cue.index = layout_selection[-1].index + 1 + + app.cue_model.add(cue) + except Exception: + logger.exception( + translate("CueFactory", "Cannot create cue {}").format( + cue_class.__name__ + ) + ) + + return cue_factory + + app.window.register_cue_menu_action( + translate("CueName", cue_class.Name), + _new_cue_factory(app, cue_class), + cue_category or translate("CueCategory", "Miscellaneous cues"), + ) + + logger.debug( + translate("CueFactory", 'Registered cue: "{}"').format( + cue_class.__name__ + ) + ) + @classmethod def register_factory(cls, cue_type, factory): """Register a new cue-type in the factory. diff --git a/lisp/plugins/action_cues/__init__.py b/lisp/plugins/action_cues/__init__.py index 680207d9f..b1feb471b 100644 --- a/lisp/plugins/action_cues/__init__.py +++ b/lisp/plugins/action_cues/__init__.py @@ -15,7 +15,6 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import logging from os import path from lisp.core.loading import load_classes @@ -23,50 +22,15 @@ from lisp.cues.cue_factory import CueFactory from lisp.ui.ui_utils import translate -logger = logging.getLogger(__name__) - class ActionCues(Plugin): Name = "Action Cues" Authors = ("Francesco Ceruti",) - Description = "Provide a collection of cues for different purposes" + Description = "A collection of cues that act on other cues" def __init__(self, app): super().__init__(app) + # Register each action cue class with the cue-factory for name, cue in load_classes(__package__, path.dirname(__file__)): - # Register the action-cue in the cue-factory - CueFactory.register_factory(cue.__name__, cue) - - # Register the menu action for adding the action-cue - self.app.window.register_cue_menu_action( - translate("CueName", cue.Name), - self._new_cue_factory(cue), - "Action cues", - ) - - logger.debug( - translate("ActionCuesDebug", 'Registered cue: "{}"').format( - name - ) - ) - - def _new_cue_factory(self, cue_class): - def cue_factory(): - try: - cue = CueFactory.create_cue(cue_class.__name__) - - # Get the (last) index of the current selection - layout_selection = list(self.app.layout.selected_cues()) - if layout_selection: - cue.index = layout_selection[-1].index + 1 - - self.app.cue_model.add(cue) - except Exception: - logger.exception( - translate( - "ActionsCuesError", "Cannot create cue {}" - ).format(cue_class.__name__) - ) - - return cue_factory + CueFactory.register_cue_type(self.app, cue, translate("CueCategory", "Action cues")) diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index 3f34bc05e..fefc21147 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -18,10 +18,12 @@ import mido from lisp.core.plugin import Plugin +from lisp.cues.cue_factory import CueFactory +from lisp.plugins.midi.midi_cue import MidiCue from lisp.plugins.midi.midi_io import MIDIOutput, MIDIInput from lisp.plugins.midi.midi_settings import MIDISettings from lisp.ui.settings.app_configuration import AppConfigurationDialog - +from lisp.ui.ui_utils import translate class Midi(Plugin): """Provide MIDI I/O functionality""" @@ -38,6 +40,8 @@ def __init__(self, app): "plugins.midi", MIDISettings, Midi.Config ) + CueFactory.register_cue_type(self.app, MidiCue, translate("CueCategory", "Protocol cues")) + # Load the backend and set it as current mido backend self.backend = mido.Backend(Midi.Config["backend"], load=True) mido.set_backend(self.backend) diff --git a/lisp/plugins/action_cues/midi_cue.py b/lisp/plugins/midi/midi_cue.py similarity index 100% rename from lisp/plugins/action_cues/midi_cue.py rename to lisp/plugins/midi/midi_cue.py diff --git a/lisp/plugins/misc_cues/__init__.py b/lisp/plugins/misc_cues/__init__.py new file mode 100644 index 000000000..aa9d5fa78 --- /dev/null +++ b/lisp/plugins/misc_cues/__init__.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Linux Show Player +# +# Copyright 2012-2018 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from os import path + +from lisp.core.loading import load_classes +from lisp.core.plugin import Plugin +from lisp.cues.cue_factory import CueFactory +from lisp.ui.ui_utils import translate + + +class MiscCues(Plugin): + Name = "Miscellaneous Cues" + Authors = ("Various",) + Description = "A collection of cues that don't belong to specific plugins" + + def __init__(self, app): + super().__init__(app) + + for name, cue in load_classes(__package__, path.dirname(__file__)): + # Register the action-cue in the cue-factory + CueFactory.register_cue_type(self.app, cue) diff --git a/lisp/plugins/action_cues/command_cue.py b/lisp/plugins/misc_cues/command_cue.py similarity index 100% rename from lisp/plugins/action_cues/command_cue.py rename to lisp/plugins/misc_cues/command_cue.py diff --git a/lisp/plugins/osc/osc.py b/lisp/plugins/osc/osc.py index 0d6426de9..604575407 100644 --- a/lisp/plugins/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -18,9 +18,12 @@ from lisp.core.plugin import Plugin +from lisp.cues.cue_factory import CueFactory +from lisp.plugins.osc.osc_cue import OscCue from lisp.plugins.osc.osc_server import OscServer from lisp.plugins.osc.osc_settings import OscSettings from lisp.ui.settings.app_configuration import AppConfigurationDialog +from lisp.ui.ui_utils import translate # TODO: layout-controls in external plugin @@ -39,6 +42,9 @@ def __init__(self, app): "plugins.osc", OscSettings, Osc.Config ) + # Register the OSC cue type + CueFactory.register_cue_type(self.app, OscCue, translate("CueCategory", "Protocol cues")) + # Create a server instance self.__server = OscServer( Osc.Config["hostname"], Osc.Config["inPort"], Osc.Config["outPort"] diff --git a/lisp/plugins/action_cues/osc_cue.py b/lisp/plugins/osc/osc_cue.py similarity index 100% rename from lisp/plugins/action_cues/osc_cue.py rename to lisp/plugins/osc/osc_cue.py From 3d43004cbe321a89a080a65927c0d1997a50130b Mon Sep 17 00:00:00 2001 From: s0600204 Date: Thu, 6 Dec 2018 17:07:29 +0000 Subject: [PATCH 156/333] Refine cue type registration code a little. --- lisp/cues/cue_factory.py | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/lisp/cues/cue_factory.py b/lisp/cues/cue_factory.py index 9803adaa0..eb90252cb 100644 --- a/lisp/cues/cue_factory.py +++ b/lisp/cues/cue_factory.py @@ -37,29 +37,26 @@ class CueFactory: def register_cue_type(cls, app, cue_class, cue_category=None): cls.register_factory(cue_class.__name__, cue_class) - def _new_cue_factory(app, cue_class): - def cue_factory(): - try: - cue = CueFactory.create_cue(cue_class.__name__) - - # Get the (last) index of the current selection - layout_selection = list(app.layout.selected_cues()) - if layout_selection: - cue.index = layout_selection[-1].index + 1 - - app.cue_model.add(cue) - except Exception: - logger.exception( - translate("CueFactory", "Cannot create cue {}").format( - cue_class.__name__ - ) + def __cue_factory(): + try: + cue = CueFactory.create_cue(cue_class.__name__) + + # Get the (last) index of the current selection + layout_selection = list(app.layout.selected_cues()) + if layout_selection: + cue.index = layout_selection[-1].index + 1 + + app.cue_model.add(cue) + except Exception: + logger.exception( + translate("CueFactory", "Cannot create cue {}").format( + cue_class.__name__ ) - - return cue_factory + ) app.window.register_cue_menu_action( translate("CueName", cue_class.Name), - _new_cue_factory(app, cue_class), + __cue_factory, cue_category or translate("CueCategory", "Miscellaneous cues"), ) From 2a69e3ecc9e7cbf551488ef9375bc28db4f3eecb Mon Sep 17 00:00:00 2001 From: s0600204 Date: Sat, 8 Dec 2018 13:18:55 +0000 Subject: [PATCH 157/333] Address issues raised by the Codacy automated code checker. --- lisp/cues/cue_factory.py | 2 +- lisp/plugins/action_cues/__init__.py | 2 +- lisp/plugins/misc_cues/__init__.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lisp/cues/cue_factory.py b/lisp/cues/cue_factory.py index eb90252cb..3e53ebbb5 100644 --- a/lisp/cues/cue_factory.py +++ b/lisp/cues/cue_factory.py @@ -59,7 +59,7 @@ def __cue_factory(): __cue_factory, cue_category or translate("CueCategory", "Miscellaneous cues"), ) - + logger.debug( translate("CueFactory", 'Registered cue: "{}"').format( cue_class.__name__ diff --git a/lisp/plugins/action_cues/__init__.py b/lisp/plugins/action_cues/__init__.py index b1feb471b..a9216a0d8 100644 --- a/lisp/plugins/action_cues/__init__.py +++ b/lisp/plugins/action_cues/__init__.py @@ -32,5 +32,5 @@ def __init__(self, app): super().__init__(app) # Register each action cue class with the cue-factory - for name, cue in load_classes(__package__, path.dirname(__file__)): + for _, cue in load_classes(__package__, path.dirname(__file__)): CueFactory.register_cue_type(self.app, cue, translate("CueCategory", "Action cues")) diff --git a/lisp/plugins/misc_cues/__init__.py b/lisp/plugins/misc_cues/__init__.py index aa9d5fa78..4dd67dc48 100644 --- a/lisp/plugins/misc_cues/__init__.py +++ b/lisp/plugins/misc_cues/__init__.py @@ -33,6 +33,6 @@ class MiscCues(Plugin): def __init__(self, app): super().__init__(app) - for name, cue in load_classes(__package__, path.dirname(__file__)): - # Register the action-cue in the cue-factory + # Register the action-cue in the cue-factory + for _, cue in load_classes(__package__, path.dirname(__file__)): CueFactory.register_cue_type(self.app, cue) From 1e1e6a1c4ae7832b271f389fbee2cf19d5ed13db Mon Sep 17 00:00:00 2001 From: s0600204 Date: Sat, 19 Jan 2019 19:03:35 +0000 Subject: [PATCH 158/333] Relocate cue registration function to Application --- lisp/application.py | 32 ++++++++++++++++++++++++ lisp/cues/cue_factory.py | 37 ---------------------------- lisp/plugins/action_cues/__init__.py | 3 +-- lisp/plugins/midi/midi.py | 3 +-- lisp/plugins/misc_cues/__init__.py | 4 +-- lisp/plugins/osc/osc.py | 3 +-- 6 files changed, 36 insertions(+), 46 deletions(-) diff --git a/lisp/application.py b/lisp/application.py index 9d6d60d3d..29d1ecef2 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -235,3 +235,35 @@ def _load_from_file(self, session_file): ).format(session_file) ) self._new_session_dialog() + + def register_cue_type(self, cue_class, cue_category=None): + CueFactory.register_factory(cue_class.__name__, cue_class) + + def __cue_factory(): + try: + cue = CueFactory.create_cue(cue_class.__name__) + + # Get the (last) index of the current selection + layout_selection = list(self.layout.selected_cues()) + if layout_selection: + cue.index = layout_selection[-1].index + 1 + + self.cue_model.add(cue) + except Exception: + logger.exception( + translate("ApplicationError", "Cannot create cue {}").format( + cue_class.__name__ + ) + ) + + self.window.register_cue_menu_action( + translate("CueName", cue_class.Name), + __cue_factory, + cue_category or translate("CueCategory", "Miscellaneous cues"), + ) + + logger.debug( + translate("ApplicationDebug", 'Registered cue: "{}"').format( + cue_class.__name__ + ) + ) diff --git a/lisp/cues/cue_factory.py b/lisp/cues/cue_factory.py index 3e53ebbb5..df11caeca 100644 --- a/lisp/cues/cue_factory.py +++ b/lisp/cues/cue_factory.py @@ -15,13 +15,9 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import logging from copy import deepcopy from lisp.core.util import typename -from lisp.ui.ui_utils import translate - -logger = logging.getLogger(__name__) class CueFactory: """Provide a generic factory to build different cues types. @@ -33,39 +29,6 @@ class CueFactory: # Register methods - @classmethod - def register_cue_type(cls, app, cue_class, cue_category=None): - cls.register_factory(cue_class.__name__, cue_class) - - def __cue_factory(): - try: - cue = CueFactory.create_cue(cue_class.__name__) - - # Get the (last) index of the current selection - layout_selection = list(app.layout.selected_cues()) - if layout_selection: - cue.index = layout_selection[-1].index + 1 - - app.cue_model.add(cue) - except Exception: - logger.exception( - translate("CueFactory", "Cannot create cue {}").format( - cue_class.__name__ - ) - ) - - app.window.register_cue_menu_action( - translate("CueName", cue_class.Name), - __cue_factory, - cue_category or translate("CueCategory", "Miscellaneous cues"), - ) - - logger.debug( - translate("CueFactory", 'Registered cue: "{}"').format( - cue_class.__name__ - ) - ) - @classmethod def register_factory(cls, cue_type, factory): """Register a new cue-type in the factory. diff --git a/lisp/plugins/action_cues/__init__.py b/lisp/plugins/action_cues/__init__.py index a9216a0d8..7675acb4d 100644 --- a/lisp/plugins/action_cues/__init__.py +++ b/lisp/plugins/action_cues/__init__.py @@ -19,7 +19,6 @@ from lisp.core.loading import load_classes from lisp.core.plugin import Plugin -from lisp.cues.cue_factory import CueFactory from lisp.ui.ui_utils import translate @@ -33,4 +32,4 @@ def __init__(self, app): # Register each action cue class with the cue-factory for _, cue in load_classes(__package__, path.dirname(__file__)): - CueFactory.register_cue_type(self.app, cue, translate("CueCategory", "Action cues")) + app.register_cue_type(cue, translate("CueCategory", "Action cues")) diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index fefc21147..7bcf85364 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -18,7 +18,6 @@ import mido from lisp.core.plugin import Plugin -from lisp.cues.cue_factory import CueFactory from lisp.plugins.midi.midi_cue import MidiCue from lisp.plugins.midi.midi_io import MIDIOutput, MIDIInput from lisp.plugins.midi.midi_settings import MIDISettings @@ -40,7 +39,7 @@ def __init__(self, app): "plugins.midi", MIDISettings, Midi.Config ) - CueFactory.register_cue_type(self.app, MidiCue, translate("CueCategory", "Protocol cues")) + app.register_cue_type(MidiCue, translate("CueCategory", "Protocol cues")) # Load the backend and set it as current mido backend self.backend = mido.Backend(Midi.Config["backend"], load=True) diff --git a/lisp/plugins/misc_cues/__init__.py b/lisp/plugins/misc_cues/__init__.py index 4dd67dc48..aa53d05fc 100644 --- a/lisp/plugins/misc_cues/__init__.py +++ b/lisp/plugins/misc_cues/__init__.py @@ -21,8 +21,6 @@ from lisp.core.loading import load_classes from lisp.core.plugin import Plugin -from lisp.cues.cue_factory import CueFactory -from lisp.ui.ui_utils import translate class MiscCues(Plugin): @@ -35,4 +33,4 @@ def __init__(self, app): # Register the action-cue in the cue-factory for _, cue in load_classes(__package__, path.dirname(__file__)): - CueFactory.register_cue_type(self.app, cue) + app.register_cue_type(cue) diff --git a/lisp/plugins/osc/osc.py b/lisp/plugins/osc/osc.py index 604575407..46188ffff 100644 --- a/lisp/plugins/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -18,7 +18,6 @@ from lisp.core.plugin import Plugin -from lisp.cues.cue_factory import CueFactory from lisp.plugins.osc.osc_cue import OscCue from lisp.plugins.osc.osc_server import OscServer from lisp.plugins.osc.osc_settings import OscSettings @@ -43,7 +42,7 @@ def __init__(self, app): ) # Register the OSC cue type - CueFactory.register_cue_type(self.app, OscCue, translate("CueCategory", "Protocol cues")) + app.register_cue_type(OscCue, translate("CueCategory", "Protocol cues")) # Create a server instance self.__server = OscServer( From e1b07dfc81db46a6ad86f5f219d1a5c996be4375 Mon Sep 17 00:00:00 2001 From: s0600204 Date: Sat, 19 Jan 2019 19:04:08 +0000 Subject: [PATCH 159/333] Alter names of categories. --- lisp/application.py | 2 +- lisp/plugins/midi/midi.py | 2 +- lisp/plugins/osc/osc.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lisp/application.py b/lisp/application.py index 29d1ecef2..7adedf9e4 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -259,7 +259,7 @@ def __cue_factory(): self.window.register_cue_menu_action( translate("CueName", cue_class.Name), __cue_factory, - cue_category or translate("CueCategory", "Miscellaneous cues"), + cue_category or translate("CueCategory", "Misc cues"), ) logger.debug( diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index 7bcf85364..f954cb208 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -39,7 +39,7 @@ def __init__(self, app): "plugins.midi", MIDISettings, Midi.Config ) - app.register_cue_type(MidiCue, translate("CueCategory", "Protocol cues")) + app.register_cue_type(MidiCue, translate("CueCategory", "Integration cues")) # Load the backend and set it as current mido backend self.backend = mido.Backend(Midi.Config["backend"], load=True) diff --git a/lisp/plugins/osc/osc.py b/lisp/plugins/osc/osc.py index 46188ffff..ab1fd2b29 100644 --- a/lisp/plugins/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -42,7 +42,7 @@ def __init__(self, app): ) # Register the OSC cue type - app.register_cue_type(OscCue, translate("CueCategory", "Protocol cues")) + app.register_cue_type(OscCue, translate("CueCategory", "Integration cues")) # Create a server instance self.__server = OscServer( From 0a05ee8e39859aa714487214f12489e2cecbf4af Mon Sep 17 00:00:00 2001 From: s0600204 Date: Tue, 22 Jan 2019 20:41:14 +0000 Subject: [PATCH 160/333] Recategorise command cue --- .../__init__.py | 17 ++++++++--------- .../command_cue.py | 0 2 files changed, 8 insertions(+), 9 deletions(-) rename lisp/plugins/{misc_cues => system_command_cue}/__init__.py (70%) rename lisp/plugins/{misc_cues => system_command_cue}/command_cue.py (100%) diff --git a/lisp/plugins/misc_cues/__init__.py b/lisp/plugins/system_command_cue/__init__.py similarity index 70% rename from lisp/plugins/misc_cues/__init__.py rename to lisp/plugins/system_command_cue/__init__.py index aa53d05fc..7e8a529a1 100644 --- a/lisp/plugins/misc_cues/__init__.py +++ b/lisp/plugins/system_command_cue/__init__.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -# # This file is part of Linux Show Player # -# Copyright 2012-2018 Francesco Ceruti +# Copyright 2019 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -21,16 +19,17 @@ from lisp.core.loading import load_classes from lisp.core.plugin import Plugin +from lisp.ui.ui_utils import translate -class MiscCues(Plugin): - Name = "Miscellaneous Cues" - Authors = ("Various",) - Description = "A collection of cues that don't belong to specific plugins" +class SystemCommandCue(Plugin): + Name = "Command Cue" + Authors = ("Francesco Ceruti",) + Description = "A cue that allows running an arbitrary shell command." def __init__(self, app): super().__init__(app) - # Register the action-cue in the cue-factory + # Register the cue in the cue-factory for _, cue in load_classes(__package__, path.dirname(__file__)): - app.register_cue_type(cue) + app.register_cue_type(cue, translate("CueCategory", "Integration cues")) diff --git a/lisp/plugins/misc_cues/command_cue.py b/lisp/plugins/system_command_cue/command_cue.py similarity index 100% rename from lisp/plugins/misc_cues/command_cue.py rename to lisp/plugins/system_command_cue/command_cue.py From ebc9c1c357ba4b0cde97e00f338430a13f27298e Mon Sep 17 00:00:00 2001 From: s0600204 <2053873+s0600204@users.noreply.github.com> Date: Wed, 23 Jan 2019 14:48:51 +0000 Subject: [PATCH 161/333] Follow XDG spec when storing user data. (#149) On linux systems, it is recommended to use the XDG specification when storing user configuration, caching, and user-specific application data Thus: * configuration is now stored at $XDG_CONFIG_HOME/linux_show_player (eg. ~/.config/linux_show_player) * presets are now stored under $XDG_DATA_HOME/linux_show_player/presets (eg. ~/.local/share/linux_show_player/presets) * logs are now stored under $XDG_CACHE_HOME/linux_show_player/logs (eg. ~/.cache/linux_show_player/logs) The advantage of using the appdirs python module is (rather than hard-coding): 1) We follow the $XDG_{*}_HOME environment hints, should a user have them set; 2) If the standard ever changes, then we don't have to worry about it; 3) If lisp is ever ported to Windows or OSX, then the module provides suitable paths defined by OSX and Windows development guidelines; --- Pipfile | 1 + lisp/__init__.py | 8 ++++---- lisp/main.py | 12 ++++++------ lisp/plugins/__init__.py | 4 ++-- lisp/plugins/presets/lib.py | 4 ++-- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Pipfile b/Pipfile index 879b0e4fd..d93a9cff1 100644 --- a/Pipfile +++ b/Pipfile @@ -4,6 +4,7 @@ verify_ssl = true name = "pypi" [packages] +appdirs = ">=1.4.1" Cython = "~=0.29" falcon = "~=1.4" JACK-Client = "~=0.4" diff --git a/lisp/__init__.py b/lisp/__init__.py index aed4a6d44..cce10f9df 100644 --- a/lisp/__init__.py +++ b/lisp/__init__.py @@ -16,6 +16,7 @@ # along with Linux Show Player. If not, see . from os import path +from appdirs import AppDirs __author__ = "Francesco Ceruti" __email__ = "ceppofrancy@gmail.com" @@ -26,12 +27,11 @@ # Application wide "constants" APP_DIR = path.dirname(__file__) -USER_DIR = path.join(path.expanduser("~"), ".linux_show_player") - -LOGS_DIR = path.join(USER_DIR, "logs") +# The version passed follows . +USER_DIRS = AppDirs("LinuxShowPlayer", version=".".join(__version__.split(".")[0:2])) DEFAULT_APP_CONFIG = path.join(APP_DIR, "default.json") -USER_APP_CONFIG = path.join(USER_DIR, "lisp.json") +USER_APP_CONFIG = path.join(USER_DIRS.user_config_dir, "lisp.json") I18N_PATH = path.join(APP_DIR, "i18n", "qm") diff --git a/lisp/main.py b/lisp/main.py index cee2d3019..97ab4ce6d 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -27,12 +27,11 @@ from PyQt5.QtWidgets import QApplication from lisp import ( - USER_DIR, + USER_DIRS, DEFAULT_APP_CONFIG, USER_APP_CONFIG, plugins, I18N_PATH, - LOGS_DIR, ) from lisp.application import Application from lisp.core.configuration import JSONFileConfiguration @@ -63,8 +62,9 @@ def main(): args = parser.parse_args() - # Make sure the application user directory exists - os.makedirs(USER_DIR, exist_ok=True) + # Make sure the application user directories exist + os.makedirs(USER_DIRS.user_config_dir, exist_ok=True) + os.makedirs(USER_DIRS.user_data_dir, exist_ok=True) # Get logging level for the console if args.log == "debug": @@ -93,10 +93,10 @@ def main(): root_logger.addHandler(stream_handler) # Make sure the logs directory exists - os.makedirs(LOGS_DIR, exist_ok=True) + os.makedirs(USER_DIRS.user_log_dir, exist_ok=True) # Create the file handler file_handler = RotatingFileHandler( - os.path.join(LOGS_DIR, "lisp.log"), + os.path.join(USER_DIRS.user_log_dir, "lisp.log"), maxBytes=10 * (2 ** 20), backupCount=5, ) diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index b9e1d8a8a..4382004b1 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -19,7 +19,7 @@ import logging from os import path -from lisp import USER_DIR +from lisp import USER_DIRS from lisp.core.configuration import JSONFileConfiguration from lisp.core.loading import load_classes from lisp.ui.ui_utils import install_translation, translate @@ -52,7 +52,7 @@ def load_plugins(application): # Load plugin configuration config = JSONFileConfiguration( - path.join(USER_DIR, mod_name + ".json"), default_config_path + path.join(USER_DIRS.user_config_dir, mod_name + ".json"), default_config_path ) plugin.Config = config diff --git a/lisp/plugins/presets/lib.py b/lisp/plugins/presets/lib.py index c04fa8f06..10acc30c3 100644 --- a/lisp/plugins/presets/lib.py +++ b/lisp/plugins/presets/lib.py @@ -19,11 +19,11 @@ import os from zipfile import ZipFile, BadZipFile -from lisp import USER_DIR +from lisp import USER_DIRS from lisp.core.actions_handler import MainActionsHandler from lisp.cues.cue_actions import UpdateCueAction, UpdateCuesAction -PRESETS_DIR = os.path.join(USER_DIR, "presets") +PRESETS_DIR = os.path.join(USER_DIRS.user_data_dir, "presets") def preset_path(name): From a8c2d613185c035bf40440240451af29e96cd640 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 23 Jan 2019 15:53:24 +0100 Subject: [PATCH 162/333] Various updates and fixes --- Pipfile.lock | 84 ++++++++++++++-------------- lisp/core/configuration.py | 1 - lisp/core/fade_functions.py | 2 +- lisp/cues/cue.py | 2 +- lisp/main.py | 12 ++-- lisp/plugins/list_layout/settings.py | 8 ++- lisp/plugins/osc/osc.py | 1 - lisp/plugins/presets/lib.py | 58 ++++++------------- lisp/plugins/presets/presets.py | 9 ++- lisp/plugins/presets/presets_ui.py | 76 ++++++++++--------------- lisp/ui/layoutselect.py | 4 +- lisp/ui/ui_utils.py | 10 ++-- 12 files changed, 117 insertions(+), 150 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 7eebfe50a..898409973 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -67,37 +67,37 @@ }, "cython": { "hashes": [ - "sha256:004c181b75f926f48dc0570372ca2cfb06a1b3210cb647185977ce9fde98b66e", - "sha256:085d596c016130f5b1e2fe72446e3e63bfcf67535e7ff6772eaa05c5d2ad6fd5", - "sha256:1014758344717844a05882c92ebd76d8fab15b0a8e9b42b378a99a6c5299ab3b", - "sha256:12c007d3704ca9840734748fd6c052960be67562ff15609c3b85d1ca638289d2", - "sha256:1a20f575197e814453f2814829715fcb21436075e298d883a34c7ffe4d567a1d", - "sha256:1b6f201228368ec9b307261b46512f3605f84d4994bb6eb78cdab71455810424", - "sha256:2ac187ff998a95abb7fae452b5178f91e1a713698c9ced89836c94e6b1d3f41e", - "sha256:3585fbe18d8666d91ecb3b3366ca6e9ea49001cd0a7c38a226cececb7852aa0d", - "sha256:3669dfe4117ee8825a48cf527cb4ac15a39a0142fcb72ecedfd75fe6545b2cda", - "sha256:382c1e0f8f8be36e9057264677fd60b669a41c5810113694cbbb4060ee0cefc0", - "sha256:44bb606d8c60d8acaa7f72b3bbc2ebd9816785124c58d33c057ca326f1185dae", - "sha256:6f1a5344ff1f0f44023c41d4b0e52215b490658b42e017520cb89a56250ecbca", - "sha256:7a29f9d780ac497dcd76ce814a9d170575bedddeb89ecc25fe738abef4c87172", - "sha256:8022a5b83ef442584f2efd941fe8678de1c67e28bf81e6671b20627ec8a79387", - "sha256:998af90092cd1231990eb878e2c71ed92716d6a758aa03a2e6673e077a7dd072", - "sha256:9e60b83afe8914ab6607f7150fd282d1cb0531a45cf98d2a40159f976ae4cd7a", - "sha256:a6581d3dda15adea19ac70b89211aadbf21f45c7f3ee3bc8e1536e5437c9faf9", - "sha256:af515924b8aebb729f631590eb43300ce948fa67d3885fdae9238717c0a68821", - "sha256:b49ea3bb357bc34eaa7b461633ce7c2d79186fe681297115ff9e2b8f5ceba2fd", - "sha256:bc524cc603f0aa23af00111ddd1aa0aad12d629f5a9a5207f425a1af66393094", - "sha256:ca7daccffb14896767b20d69bfc8de9e41e9589b9377110292c3af8460ef9c2b", - "sha256:cdfb68eb11c6c4e90e34cf54ffd678a7813782fae980d648db6185e6b0c8a0ba", - "sha256:d21fb6e7a3831f1f8346275839d46ed1eb2abd350fc81bad2fdf208cc9e4f998", - "sha256:e17104c6871e7c0eee4de12717892a1083bd3b8b1da0ec103fa464b1c6c80964", - "sha256:e7f71b489959d478cff72d8dcab1531efd43299341e3f8b85ab980beec452ded", - "sha256:e8420326e4b40bcbb06f070efb218ca2ca21827891b7c69d4cc4802b3ce1afc9", - "sha256:eec1b890cf5c16cb656a7062769ac276c0fccf898ce215ff8ef75eac740063f7", - "sha256:f04e21ba7c117b20f57b0af2d4c8ed760495e5bb3f21b0352dbcfe5d2221678b" + "sha256:1327655db47beb665961d3dc0365e20c9e8e80c234513ab2c7d06ec0dd9d63eb", + "sha256:142400f13102403f43576bb92d808a668e29deda5625388cfa39fe0bcf37b3d1", + "sha256:1b4204715141281a631337378f0c15fe660b35e1b6888ca05f1f3f49df3b97d5", + "sha256:23aabaaf8887e6db99df2145de6742f8c92830134735778bf2ae26338f2b406f", + "sha256:2a724c6f21fdf4e3c1e8c5c862ff87f5420fdaecf53a5a0417915e483d90217f", + "sha256:2c9c8c1c6e8bd3587e5f5db6f865a42195ff2dedcaf5cdb63fdea10c98bd6246", + "sha256:3a1be38b774423605189d60652b3d8a324fc81d213f96569720c8093784245ab", + "sha256:46be5297a76513e4d5d6e746737d4866a762cfe457e57d7c54baa7ef8fea7e9a", + "sha256:48dc2ea4c4d3f34ddcad5bc71b1f1cf49830f868832d3e5df803c811e7395b6e", + "sha256:53f33e04d2ed078ac02841741bcd536b546e1f416608084468ab30a87638a466", + "sha256:57b10588618ca19a4cc870f381aa8805bc5fe0c62d19d7f940232ff8a373887c", + "sha256:6001038341b52301450bb9c62e5d5da825788944572679277e137ffb3596e718", + "sha256:70bef52e735607060f327d729be35c820d9018d260a875e4f98b20ba8c4fff96", + "sha256:7d0f76b251699be8f1f1064dcb12d4b3b2b676ce15ff30c104e0c2091a015142", + "sha256:9440b64c1569c26a184b7c778bb221cf9987c5c8486d32cda02302c66ea78980", + "sha256:956cc97eac6f9d3b16e3b2d2a94c5586af3403ba97945e9d88a4a0f029899646", + "sha256:ae430ad8cce937e07ea566d1d7899eef1fedc8ec512b4d5fa37ebf2c1f879936", + "sha256:bdb575149881978d62167dd8427402a5872a79bd83e9d51219680670e9f80b40", + "sha256:c0ffcddd3dbdf22aae3980931112cc8b2732315a6273988f3205cf5dacf36f45", + "sha256:c133e2efc57426974366ac74f2ef0f1171b860301ac27f72316deacff4ccdc17", + "sha256:c6e9521d0b77eb1da89e8264eb98c8f5cda7c49a49b8128acfd35f0ca50e56d0", + "sha256:c7cac0220ecb733024e8acfcfb6b593a007185690f2ea470d2392b72510b7187", + "sha256:d53483820ac28f2be2ff13eedd56c0f36a4c583727b551d3d468023556e2336a", + "sha256:d60210784186d61e0ec808d5dbee5d661c7457a57f93cb5fdc456394607ce98c", + "sha256:d687fb1cd9df28c1515666174c62e54bd894a6a6d0862f89705063cd47739f83", + "sha256:d926764d9c768a48b0a16a91696aaa25498057e060934f968fa4c5629b942d85", + "sha256:d94a2f4ad74732f58d1c771fc5d90a62c4fe4c98d0adfecbc76cd0d8d14bf044", + "sha256:def76a546eeec059666f5f4117dfdf9c78e50fa1f95bdd23b04618c7adf845cd" ], "index": "pypi", - "version": "==0.29.2" + "version": "==0.29.3" }, "falcon": { "hashes": [ @@ -192,21 +192,21 @@ }, "python-rtmidi": { "hashes": [ - "sha256:0693c98d12bd568f359f8e25816f06bda1e921595d41f13e6af549e68871be0b", - "sha256:1a126193398d88715c1c5b9b2e715399d7b59435c4d21c1541babfd467eb2fa3", - "sha256:558f2d05a8aace53c970bb5bad7f0599eaf178dd1d65db0a3d6e19949623ca31", - "sha256:67e149aaf9392d1d3bf9ad9bd7fb2b0a966a215c3f7560d7dcf8c61c8425dc61", - "sha256:68c3f5f880f0c62c85f7b627539020bd512939e18a234a40c7bba3fc67dd7230", - "sha256:776e0ed1c0c66d3794b380f1e6af8aae9288e6d6ab6b2ef32b88a8f02320c445", - "sha256:7ea0ec498299d9663e9003476e3ad9caecae6acbd5db1453cce967cd18309d0d", - "sha256:82e6fde0d37a25726163245120eebd8873e959ec608c855cb32193c061f192f5", - "sha256:c09dd7a5b24619bc09a89eac2f3c1435cccad75c10eec4b88bfd4be6c09b0e00", - "sha256:cf6b8a742989feda2009260ec6e019d960fe8f9c22831189c8f2d9795faa009e", - "sha256:e5788048b0c73b8b4f76c851aa56c75a649e211906d1a42e9d59ed144388452e", - "sha256:eab13d4d7950646234a4a4526c8b655b136e7722dee8e4f1db9fc4e05dd5a477" + "sha256:024a7a466c5b97f8fbbbcb2a6c968f10f4344f348a318188c3c0a7ae5f3e1b19", + "sha256:136a6ab2ca38ce97324266411c47cb1004fb0a84c35946d52579c1c66acf4aa6", + "sha256:308fc922fd1e0f110bab6091e899e1d5fccbc8889a423de82bbd0999ea2258d0", + "sha256:31a4031f75e38d7257476c8abed1c8466ee04f65e9bd4380c581f5e394c85a0f", + "sha256:542970637ece4183c5160285661ae71246c80fc37e8d69edaee507c768a65af8", + "sha256:78ace337b36e94a2a83bcb4e3cc80d1a45f740ace0978b60d689bdd9c870eaf0", + "sha256:7e82585912e0f68a6561ea95c981cc6669d00744bc9ac48fb094cfa595411d3a", + "sha256:a9b444eb0706eed6de4fb81f2a08df2b7b5b62ee304bcf95d3e65c8cbaf7c789", + "sha256:b0dc5b6a8f0bee92200425056dd2d753042ba5386feb316b0c8d7fb9d4ea19f8", + "sha256:b240d10966debe6fcbe6f5bb5d20ccaad128fc1c5732a88ad8ad82094759ee8c", + "sha256:c4137111ce8cca60c959f00e592087d343c1c8a036b687c72f7e4c33ca982a92", + "sha256:dee4fbe88a5d7b124de3f91831804fb31a92ec13ace6ab192d1ea76143d2a261" ], "index": "pypi", - "version": "==1.1.2" + "version": "==1.2.1" }, "requests": { "hashes": [ diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index 25f5e873c..632b14c39 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -252,7 +252,6 @@ def _read_json(path): return json.load(f) -# TODO: we should remove this in favor of a non-singleton class AppConfig(JSONFileConfiguration, metaclass=ABCSingleton): """Provide access to the application configuration (singleton)""" diff --git a/lisp/core/fade_functions.py b/lisp/core/fade_functions.py index 64b6d3fa6..b2bb973bc 100644 --- a/lisp/core/fade_functions.py +++ b/lisp/core/fade_functions.py @@ -1,5 +1,5 @@ # Python porting of qtractor fade functions -## This file is part of Linux Show Player +# This file is part of Linux Show Player # # Copyright 2016 Francesco Ceruti # diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 9eaec3634..2820ff7a8 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -305,7 +305,7 @@ def __start__(self, fade=False): When called from `Cue.start()`, `_st_lock` is acquired. - If the execution is instantaneous should return False, otherwise + If the execution is instantaneous, should return False, otherwise return True and call the `_ended` function later. :param fade: True if a fade should be performed (when supported) diff --git a/lisp/main.py b/lisp/main.py index cee2d3019..4963ce131 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -116,12 +116,16 @@ def main(): if locale: QLocale().setDefault(QLocale(locale)) - logging.info("Using {} locale".format(QLocale().name())) + logging.info( + 'Using "{}" locale -> {}'.format( + QLocale().name(), QLocale().uiLanguages() + ) + ) # Qt platform translation - install_translation( - "qt", tr_path=QLibraryInfo.location(QLibraryInfo.TranslationsPath) - ) + qt_tr_path = QLibraryInfo.location(QLibraryInfo.TranslationsPath) + # install_translation("qt", tr_path=qt_tr_path) + install_translation("qtbase", tr_path=qt_tr_path) # Main app translations install_translation("lisp", tr_path=I18N_PATH) diff --git a/lisp/plugins/list_layout/settings.py b/lisp/plugins/list_layout/settings.py index 2672a1ef2..d45ee3258 100644 --- a/lisp/plugins/list_layout/settings.py +++ b/lisp/plugins/list_layout/settings.py @@ -116,9 +116,11 @@ def retranslateUi(self): translate("ListLayout", "Enable selection mode") ) - self.goKeyLabel.setText(translate("ListLayout", "GO key:")) - self.goActionLabel.setText(translate("ListLayout", "GO action:")) - self.goDelayLabel.setText(translate("ListLayout", "GO delay (ms):")) + self.goKeyLabel.setText(translate("ListLayout", "GO Key:")) + self.goActionLabel.setText(translate("ListLayout", "GO Action:")) + self.goDelayLabel.setText( + translate("ListLayout", "GO minimum interval (ms):") + ) self.useFadeGroup.setTitle( translate("ListLayout", "Use fade (buttons)") diff --git a/lisp/plugins/osc/osc.py b/lisp/plugins/osc/osc.py index 0d6426de9..92e6a0500 100644 --- a/lisp/plugins/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -23,7 +23,6 @@ from lisp.ui.settings.app_configuration import AppConfigurationDialog -# TODO: layout-controls in external plugin class Osc(Plugin): """Provide OSC I/O functionality""" diff --git a/lisp/plugins/presets/lib.py b/lisp/plugins/presets/lib.py index c04fa8f06..ab18b515e 100644 --- a/lisp/plugins/presets/lib.py +++ b/lisp/plugins/presets/lib.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2016 Francesco Ceruti +# Copyright 2019 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ import json import os -from zipfile import ZipFile, BadZipFile +from zipfile import ZipFile from lisp import USER_DIR from lisp.core.actions_handler import MainActionsHandler @@ -134,18 +134,6 @@ def delete_preset(name): os.remove(path) -class PresetImportError(Exception): - """ - Raised when an error occur during presets import. - """ - - -class PresetExportError(Exception): - """ - Raised when an error occur during presets export. - """ - - def export_presets(names, archive): """Export presets-files into an archive. @@ -155,12 +143,9 @@ def export_presets(names, archive): :type archive: str """ if names: - try: - with ZipFile(archive, mode="w") as archive: - for name in names: - archive.write(preset_path(name), name) - except (OSError, BadZipFile) as e: - raise PresetExportError(str(e)) + with ZipFile(archive, mode="w") as archive: + for name in names: + archive.write(preset_path(name), name) def import_presets(archive, overwrite=True): @@ -171,13 +156,10 @@ def import_presets(archive, overwrite=True): :param overwrite: Overwrite existing files :type overwrite: bool """ - try: - with ZipFile(archive) as archive: - for member in archive.namelist(): - if not (preset_exists(member) and not overwrite): - archive.extract(member, path=PRESETS_DIR) - except (OSError, BadZipFile) as e: - raise PresetImportError(str(e)) + with ZipFile(archive) as archive: + for member in archive.namelist(): + if not (preset_exists(member) and not overwrite): + archive.extract(member, path=PRESETS_DIR) def import_has_conflicts(archive): @@ -187,13 +169,10 @@ def import_has_conflicts(archive): :type archive: str :rtype: bool """ - try: - with ZipFile(archive) as archive: - for member in archive.namelist(): - if preset_exists(member): - return True - except (OSError, BadZipFile) as e: - raise PresetImportError(str(e)) + with ZipFile(archive) as archive: + for member in archive.namelist(): + if preset_exists(member): + return True return False @@ -206,12 +185,9 @@ def import_conflicts(archive): """ conflicts = [] - try: - with ZipFile(archive) as archive: - for member in archive.namelist(): - if preset_exists(member): - conflicts.append(member) - except (OSError, BadZipFile) as e: - raise PresetImportError(str(e)) + with ZipFile(archive) as archive: + for member in archive.namelist(): + if preset_exists(member): + conflicts.append(member) return conflicts diff --git a/lisp/plugins/presets/presets.py b/lisp/plugins/presets/presets.py index d5a7850ba..5de09ea4a 100644 --- a/lisp/plugins/presets/presets.py +++ b/lisp/plugins/presets/presets.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2019 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -44,7 +44,6 @@ from lisp.ui.ui_utils import translate -# TODO: use logging to report errors class Presets(Plugin): Name = "Preset" @@ -94,7 +93,7 @@ def __load_on_cue(self, cue): try: load_on_cue(preset_name, cue) except OSError as e: - load_preset_error(e, preset_name, parent=self.app.window) + load_preset_error(e, preset_name) def __load_on_cues(self, cues): preset_name = select_preset_dialog() @@ -102,7 +101,7 @@ def __load_on_cues(self, cues): try: load_on_cues(preset_name, cues) except OSError as e: - load_preset_error(e, preset_name, parent=self.app.window) + load_preset_error(e, preset_name) def __create_from_cue(self, cue): name = save_preset_dialog(cue.name) @@ -118,4 +117,4 @@ def __create_from_cue(self, cue): try: write_preset(name, preset) except OSError as e: - write_preset_error(e, name, parent=self.app.window) + write_preset_error(e, name) diff --git a/lisp/plugins/presets/presets_ui.py b/lisp/plugins/presets/presets_ui.py index e9770d7ab..c89bbf636 100644 --- a/lisp/plugins/presets/presets_ui.py +++ b/lisp/plugins/presets/presets_ui.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2016 Francesco Ceruti +# Copyright 2019 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -15,6 +15,8 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging + from PyQt5.QtCore import Qt from PyQt5.QtWidgets import ( QComboBox, @@ -32,6 +34,7 @@ QFileDialog, QHBoxLayout, ) +from zipfile import BadZipFile from lisp.core.util import natural_keys from lisp.cues.cue import Cue @@ -41,8 +44,6 @@ export_presets, import_presets, import_has_conflicts, - PresetExportError, - PresetImportError, scan_presets, delete_preset, write_preset, @@ -53,50 +54,41 @@ from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.cue_settings import CueSettingsDialog, CueSettingsRegistry from lisp.ui.ui_utils import translate -from lisp.ui.widgets import QDetailedMessageBox +logger = logging.getLogger(__file__[:-3]) -def preset_error(exception, text, parent=None): - QDetailedMessageBox.dcritical( - translate("Presets", "Presets"), text, str(exception), parent=parent - ) +def preset_error(exception, text): + logger.error(text, exc_info=exception) -def scan_presets_error(exception, parent=None): - preset_error( - exception, translate("Presets", "Cannot scan presets"), parent=parent - ) +def scan_presets_error(exception): + preset_error(exception, translate("Presets", "Cannot scan presets")) -def delete_preset_error(exception, name, parent=None): + +def delete_preset_error(exception, name): preset_error( exception, translate("Presets", 'Error while deleting preset "{}"').format(name), - parent=parent, ) -def load_preset_error(exception, name, parent=None): +def load_preset_error(exception, name): preset_error( - exception, - translate("Presets", 'Cannot load preset "{}"').format(name), - parent=parent, + exception, translate("Presets", 'Cannot load preset "{}"').format(name) ) -def write_preset_error(exception, name, parent=None): +def write_preset_error(exception, name): preset_error( - exception, - translate("Presets", 'Cannot save preset "{}"').format(name), - parent=parent, + exception, translate("Presets", 'Cannot save preset "{}"').format(name) ) -def rename_preset_error(exception, name, parent=None): +def rename_preset_error(exception, name): preset_error( exception, translate("Presets", 'Cannot rename preset "{}"').format(name), - parent=parent, ) @@ -112,7 +104,7 @@ def select_preset_dialog(): if confirm: return item except OSError as e: - scan_presets_error(e, parent=MainWindow()) + scan_presets_error(e) def check_override_dialog(preset_name): @@ -241,7 +233,7 @@ def __populate(self): try: self.presetsList.addItems(scan_presets()) except OSError as e: - scan_presets_error(e, parent=self) + scan_presets_error(e) def __remove_preset(self): for item in self.presetsList.selectedItems(): @@ -249,7 +241,7 @@ def __remove_preset(self): delete_preset(item.text()) self.presetsList.takeItem(self.presetsList.currentRow()) except OSError as e: - delete_preset_error(e, item.text(), parent=self) + delete_preset_error(e, item.text()) def __add_preset(self): dialog = NewPresetDialog(parent=self) @@ -264,7 +256,7 @@ def __add_preset(self): if not exists: self.presetsList.addItem(preset_name) except OSError as e: - write_preset_error(e, preset_name, parent=self) + write_preset_error(e, preset_name) def __rename_preset(self): item = self.presetsList.currentItem() @@ -282,7 +274,7 @@ def __rename_preset(self): rename_preset(item.text(), new_name) item.setText(new_name) except OSError as e: - rename_preset_error(e, item.text(), parent=self) + rename_preset_error(e, item.text()) def __edit_preset(self): item = self.presetsList.currentItem() @@ -303,9 +295,9 @@ def __edit_preset(self): try: write_preset(item.text(), preset) except OSError as e: - write_preset_error(e, item.text(), parent=self) + write_preset_error(e, item.text()) except OSError as e: - load_preset_error(e, item.text(), parent=self) + load_preset_error(e, item.text()) def __cue_from_preset(self, preset_name): try: @@ -326,7 +318,7 @@ def __cue_from_preset(self, preset_name): ).format(preset_name), ) except OSError as e: - load_preset_error(e, preset_name, parent=self) + load_preset_error(e, preset_name) def __cue_from_selected(self): for item in self.presetsList.selectedItems(): @@ -341,7 +333,7 @@ def __load_on_selected(self): if cues: load_on_cues(preset_name, cues) except OSError as e: - load_preset_error(e, preset_name, parent=self) + load_preset_error(e, preset_name) def __export_presets(self): names = [item.text() for item in self.presetsList.selectedItems()] @@ -354,12 +346,9 @@ def __export_presets(self): archive += ".presets" try: export_presets(names, archive) - except PresetExportError as e: - QDetailedMessageBox.dcritical( - translate("Presets", "Presets"), - translate("Presets", "Cannot export correctly."), - str(e), - parent=self, + except (OSError, BadZipFile): + logger.exception( + translate("Presets", "Cannot export correctly.") ) def __import_presets(self): @@ -382,12 +371,9 @@ def __import_presets(self): import_presets(archive) self.__populate() - except PresetImportError as e: - QDetailedMessageBox.dcritical( - translate("Presets", "Presets"), - translate("Presets", "Cannot import correctly."), - str(e), - parent=self, + except (OSError, BadZipFile): + logger.exception( + translate("Presets", "Cannot import correctly.") ) def __selection_changed(self): diff --git a/lisp/ui/layoutselect.py b/lisp/ui/layoutselect.py index a7a11e045..4c77db2a0 100644 --- a/lisp/ui/layoutselect.py +++ b/lisp/ui/layoutselect.py @@ -101,7 +101,9 @@ def show_description(self): def open_file(self): app_config = AppConfig() - last_session_path = app_config.get("session.last_path", os.getenv("HOME")) + last_session_path = app_config.get( + "session.last_path", os.getenv("HOME") + ) path, _ = QFileDialog.getOpenFileName( self, filter="*.lsp", directory=last_session_path ) diff --git a/lisp/ui/ui_utils.py b/lisp/ui/ui_utils.py index d2c0b7cbb..7ad1ddfea 100644 --- a/lisp/ui/ui_utils.py +++ b/lisp/ui/ui_utils.py @@ -93,17 +93,17 @@ def qfile_filters(extensions, allexts=True, anyfile=True): def install_translation(name, tr_path=I18N_PATH): - tr_file = path.join(tr_path, name) - translator = QTranslator() - translator.load(QLocale(), tr_file, "_") + translator.load(QLocale(), name, "_", tr_path) if QApplication.installTranslator(translator): # Keep a reference, QApplication does not _TRANSLATORS.append(translator) - logger.debug("Installed translation: {}".format(tr_file)) + logger.debug( + 'Installed translation for "{}" from {}'.format(name, tr_path) + ) else: - logger.debug("No translation at: {}".format(tr_file)) + logger.debug('No translation "{}" in {}'.format(name, tr_path)) def translate(context, text, disambiguation=None, n=-1): From a4217f8f9a32342b9cdd767c14350107ff527eec Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 26 Jan 2019 12:53:59 +0100 Subject: [PATCH 163/333] Refactored the osc_cue.py module Update: improved checkbox-delegate Update: improved SimpleTableModel --- Pipfile.lock | 10 +- README.md | 8 +- lisp/__init__.py | 4 +- lisp/core/configuration.py | 4 +- lisp/plugins/__init__.py | 3 +- lisp/plugins/action_cues/osc_cue.py | 422 ++++++++++------------- lisp/plugins/controller/protocols/osc.py | 2 +- lisp/plugins/osc/osc_server.py | 8 +- lisp/ui/qdelegates.py | 47 ++- lisp/ui/qmodels.py | 23 +- 10 files changed, 256 insertions(+), 275 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 898409973..7f4a23904 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4cf40d7ac39723118c6c58be7041619cbb24d2799c3714661e534fc1fb88880c" + "sha256": "9ef1a5b405946616a8849996c6a5515f579c7666ccf22475c6ae6a36385a4950" }, "pipfile-spec": 6, "requires": {}, @@ -14,6 +14,14 @@ ] }, "default": { + "appdirs": { + "hashes": [ + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" + ], + "index": "pypi", + "version": "==1.4.3" + }, "certifi": { "hashes": [ "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", diff --git a/README.md b/README.md index db6d3af7d..081b483b1 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ ![Linux Show Player Logo](https://raw.githubusercontent.com/wiki/FrancescoCeruti/linux-show-player/media/site_logo.png) -

Sound player designed for stage productions

+

Cue player designed for stage productions

License: GPL GitHub release Github All Releases -Code Health +Code Health Gitter Code style: black Say Thanks! @@ -37,11 +37,11 @@ Tested on the following systems *(it should work on newer versions)*: ### Usage -Use the installed launcher from the menu (for the package installation), or +Use the installed launcher from the menu, or: $ linux-show-player # Launch the program $ linux-show-player -l [debug/warning/info] # Launch with different log options $ linux-show-player -f # Open a saved session $ linux-show-player --locale # Launch using the given locale -*User documentation downloaded under the GitHub release page or [viewed online](http://linux-show-player-users.readthedocs.io/en/latest/index.html)* \ No newline at end of file +User documentation can be downloaded under the GitHub release page or [viewed online](http://linux-show-player-users.readthedocs.io/en/latest/index.html) \ No newline at end of file diff --git a/lisp/__init__.py b/lisp/__init__.py index cce10f9df..3b717b3a1 100644 --- a/lisp/__init__.py +++ b/lisp/__init__.py @@ -28,7 +28,9 @@ APP_DIR = path.dirname(__file__) # The version passed follows . -USER_DIRS = AppDirs("LinuxShowPlayer", version=".".join(__version__.split(".")[0:2])) +USER_DIRS = AppDirs( + "LinuxShowPlayer", version=".".join(__version__.split(".")[0:2]) +) DEFAULT_APP_CONFIG = path.join(APP_DIR, "default.json") USER_APP_CONFIG = path.join(USER_DIRS.user_config_dir, "lisp.json") diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index 632b14c39..3e1dff21f 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -68,9 +68,9 @@ def get(self, path, default=_UNSET): return node[key] except (KeyError, TypeError): if default is not _UNSET: - logger.warning( + logger.info( translate( - "ConfigurationWarning", + "ConfigurationInfo", 'Invalid path "{}", return default.', ).format(path) ) diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index 4382004b1..ae0807f2b 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -52,7 +52,8 @@ def load_plugins(application): # Load plugin configuration config = JSONFileConfiguration( - path.join(USER_DIRS.user_config_dir, mod_name + ".json"), default_config_path + path.join(USER_DIRS.user_config_dir, mod_name + ".json"), + default_config_path, ) plugin.Config = config diff --git a/lisp/plugins/action_cues/osc_cue.py b/lisp/plugins/action_cues/osc_cue.py index 5804d98cc..abb01f0aa 100644 --- a/lisp/plugins/action_cues/osc_cue.py +++ b/lisp/plugins/action_cues/osc_cue.py @@ -17,7 +17,6 @@ # along with Linux Show Player. If not, see . import logging - from PyQt5 import QtCore from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import ( @@ -31,7 +30,7 @@ QPushButton, QLabel, QDoubleSpinBox, - QMessageBox, + QStyledItemDelegate, ) from lisp.core.decorators import async_function @@ -41,38 +40,20 @@ from lisp.cues.cue import Cue, CueAction from lisp.plugins import get_plugin from lisp.plugins.osc.osc_server import OscMessageType -from lisp.ui.qdelegates import ComboBoxDelegate, CheckBoxDelegate -from lisp.plugins.osc.osc_delegate import OscArgumentDelegate +from lisp.ui.qdelegates import ComboBoxDelegate, BoolCheckBoxDelegate from lisp.ui.qmodels import SimpleTableModel from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate -from lisp.ui.widgets import FadeComboBox, QDetailedMessageBox +from lisp.ui.widgets import FadeComboBox -# TODO: a lot of refactoring +# TODO: some of this should probably be moved into the osc module logger = logging.getLogger(__name__) -COL_TYPE = 0 -COL_START_VAL = 1 -COL_END_VAL = 2 -COL_DO_FADE = 3 - -COL_BASE_VAL = 1 -COL_DIFF_VAL = 2 - - -def test_path(path): - if isinstance(path, str): - if len(path) > 1 and path[0] is "/": - return True - return False - -def type_can_fade(t): - if t == OscMessageType.Int.value: - return True - elif t == OscMessageType.Float.value: +def type_can_fade(osc_type): + if osc_type == OscMessageType.Int or osc_type == OscMessageType.Float: return True else: return False @@ -98,99 +79,68 @@ def __init__(self, **kwargs): self.__osc = get_plugin("Osc") - self.__fader = Fader(self, "_position") - self.__value = 0 + self.__has_fade = False self.__fadein = True + self.__fader = Fader(self, "position") + self.__position = 0 - self.__arg_list = [] - self.__has_fade = False + self.changed("args").connect(self.__on_args_change) - def __get_position(self): - return self.__value + @property + def position(self): + return self.__position - def __set_position(self, value): - self.__value = value + @position.setter + def position(self, value): + self.__position = value args = [] - for row in self.__arg_list: - if row[COL_DIFF_VAL] > 0: - args.append( - row[COL_BASE_VAL] + row[COL_DIFF_VAL] * self.__value - ) - else: - args.append(row[COL_BASE_VAL]) - - self.__osc.server.send(self.path, *args) - - _position = property(__get_position, __set_position) - - def __init_arguments(self): - # check path - if not test_path(self.path): - return False - - # arguments from the cue settings are converted and stored in a new list - # list: [ type, base_value, diff_value ] - self.__arg_list = [] try: for arg in self.args: - if "end" in arg and arg["fade"]: - self.__has_fade = True - diff_value = arg["end"] - arg["start"] - self.__arg_list.append( - [arg["type"], arg["start"], diff_value] - ) + start = arg["start"] + if arg["fade"]: + partial = (arg["end"] - start) * self.__position + args.append(start + partial) else: - self.__arg_list.append([arg["type"], arg["start"], 0]) - except KeyError: - logger.error( + args.append(start) + + self.__osc.server.send(self.path, *args) + except Exception: + self.interrupt() + + logger.exception( translate( - "OscCueError", "Could not parse argument list, nothing sent" + "OscCueError", + "Cannot send OSC message, see error for details", ) ) - return False - - # set fade type, based on the first argument, which will have a fade - if self.__has_fade: - for row in self.__arg_list: - if row[COL_DIFF_VAL] > 0: - self.__fadein = True - break - elif row[COL_DIFF_VAL] < 0: - self.__fadein = False - break - else: - continue - - # always fade from 0 to 1, reset value before start or restart fade - self.__value = 0 + self._error() - return True + def __on_args_change(self, new_args): + # Set fade type, based on the first argument that have a fade + for arg in new_args: + if arg["fade"]: + self.__has_fade = True + self.__fadein = arg["end"] > arg["start"] + break def has_fade(self): - return self.duration > 0 and self.__has_fade + return self.__has_fade and self.duration > 0 def __start__(self, fade=False): - if self.__init_arguments(): - if self.__fader.is_paused(): - self.__fader.resume() - return True + if self.__fader.is_paused(): + self.__fader.resume() + return True - if self.has_fade(): - if not self.__fadein: - self.__fade(FadeOutType[self.fade_type]) - return True - else: - self.__fade(FadeInType[self.fade_type]) - return True + if self.has_fade(): + if not self.__fadein: + self.__fade(FadeOutType[self.fade_type]) + return True else: - self._position = 1 + self.__fade(FadeInType[self.fade_type]) + return True else: - logger.error( - translate( - "OscCueError", "Error while parsing arguments, nothing sent" - ) - ) + self.position = 1 return False @@ -206,26 +156,26 @@ def __pause__(self, fade=False): @async_function def __fade(self, fade_type): - try: - self.__fader.prepare() - ended = self.__fader.fade( - round(self.duration / 1000, 2), 1, fade_type - ) + self.__position = 0 + self.__fader.prepare() + ended = self.__fader.fade(round(self.duration / 1000, 2), 1, fade_type) - # to avoid approximation problems - self._position = 1 - if ended: - self._ended() - except Exception: - logger.exception( - translate("OscCueError", "Error during cue execution.") - ) - self._error() + if ended: + # Avoid approximation problems (if needed) + if self.position != 1: + self.position = 1 + self._ended() def current_time(self): return self.__fader.current_time() +COL_TYPE = 0 +COL_START_VALUE = 1 +COL_END_VALUE = 2 +COL_FADE = 3 + + class OscCueSettings(SettingsPage): Name = QT_TRANSLATE_NOOP("Cue Name", "OSC Settings") @@ -246,15 +196,13 @@ def __init__(self, **kwargs): self.oscModel = SimpleTableModel( [ - translate("Osc Cue", "Type"), - translate("Osc Cue", "Argument"), - translate("Osc Cue", "FadeTo"), - translate("Osc Cue", "Fade"), + translate("OscCue", "Type"), + translate("OscCue", "Value"), + translate("OscCue", "FadeTo"), + translate("OscCue", "Fade"), ] ) - self.oscModel.dataChanged.connect(self.__argument_changed) - self.oscView = OscView(parent=self.oscGroup) self.oscView.setModel(self.oscModel) self.oscGroup.layout().addWidget(self.oscView, 2, 0, 1, 2) @@ -267,10 +215,6 @@ def __init__(self, **kwargs): self.removeButton.clicked.connect(self.__remove_argument) self.oscGroup.layout().addWidget(self.removeButton, 3, 1) - self.testButton = QPushButton(self.oscGroup) - self.testButton.clicked.connect(self.__test_message) - self.oscGroup.layout().addWidget(self.testButton, 4, 0) - # Fade self.fadeGroup = QGroupBox(self) self.fadeGroup.setLayout(QGridLayout()) @@ -291,15 +235,26 @@ def __init__(self, **kwargs): self.fadeCurveLabel.setAlignment(QtCore.Qt.AlignCenter) self.fadeGroup.layout().addWidget(self.fadeCurveLabel, 1, 1) + self.pathEdit.textEdited.connect(self.__fixPath) + self.retranslateUi() + def __fixPath(self, text): + # Enforce ASCII + text = text.encode("ascii", errors="ignore").decode() + # Enforce heading "/" to the path + if text and text[0] != "/": + text = "/" + text + + self.pathEdit.setText(text) + def retranslateUi(self): self.oscGroup.setTitle(translate("OscCue", "OSC Message")) self.addButton.setText(translate("OscCue", "Add")) self.removeButton.setText(translate("OscCue", "Remove")) - self.testButton.setText(translate("OscCue", "Test")) - self.pathLabel.setText( - translate("OscCue", 'OSC Path: (example: "/path/to/something")') + self.pathLabel.setText(translate("OscCue", "OSC Path:")) + self.pathEdit.setPlaceholderText( + translate("OscCue", "/path/to/something") ) self.fadeGroup.setTitle(translate("OscCue", "Fade")) self.fadeLabel.setText(translate("OscCue", "Time (sec)")) @@ -317,140 +272,44 @@ def getSettings(self): checkable = self.oscGroup.isCheckable() if not (checkable and not self.oscGroup.isChecked()): - if not test_path(self.pathEdit.text()): - logger.error( - translate( - "OscCueError", - "Error parsing OSC path, message will be unable to send", - ) - ) - - if not (checkable and not self.oscGroup.isChecked()): - try: - conf["path"] = self.pathEdit.text() - args_list = [] - for row in self.oscModel.rows: - arg = {"type": row[COL_TYPE], "start": row[COL_START_VAL]} - - if row[COL_END_VAL] and row[COL_DO_FADE] is True: - arg["end"] = row[COL_END_VAL] - - arg["fade"] = row[COL_DO_FADE] - args_list.append(arg) - - conf["args"] = args_list - except ValueError: - logger.error( - translate( - "OscCueError", - "Error parsing OSC arguments, " - "message will be unable to send", - ) - ) + conf["path"] = self.pathEdit.text() + conf["args"] = [ + { + "type": row[COL_TYPE], + "start": row[COL_START_VALUE], + "end": row[COL_END_VALUE], + "fade": row[COL_FADE], + } + for row in self.oscModel.rows + ] if not (checkable and not self.fadeGroup.isCheckable()): conf["duration"] = self.fadeSpin.value() * 1000 conf["fade_type"] = self.fadeCurveCombo.currentType() + return conf def loadSettings(self, settings): - if "path" in settings: - path = settings.get("path", "") - self.pathEdit.setText(path) - - if "args" in settings: - - args = settings.get("args", "") - for arg in args: - self.oscModel.appendRow( - arg["type"], - arg["start"], - arg["end"] if "end" in arg else None, - arg["fade"], - ) + self.pathEdit.setText(settings.get("path", "")) + + for arg in settings.get("args", {}): + self.oscModel.appendRow( + arg.get("type", "Integer"), + arg.get("start", 0), + arg.get("end", None), + arg.get("fade", False), + ) self.fadeSpin.setValue(settings.get("duration", 0) / 1000) self.fadeCurveCombo.setCurrentType(settings.get("fade_type", "")) def __new_argument(self): - self.oscModel.appendRow(OscMessageType.Int.value, 0, "", False) + self.oscModel.appendRow(OscMessageType.Int.value, 0, 0, False) def __remove_argument(self): if self.oscModel.rowCount(): self.oscModel.removeRow(self.oscView.currentIndex().row()) - def __test_message(self): - # TODO: check arguments for error - - path = self.pathEdit.text() - - if not test_path(path): - QDetailedMessageBox.dwarning( - "Warning", - "No valid path for OSC message - nothing sent", - "Path should start with a '/' followed by a name.", - ) - return - - try: - args = [] - for row in self.oscModel.rows: - if row[COL_END_VAL] and row[COL_DO_FADE]: - args.append(row[COL_END_VAL]) - else: - args.append(row[COL_START_VAL]) - - self.__osc.server.send(self.path, *args) - except ValueError: - QMessageBox.critical( - self, "Error", "Error on parsing argument list - nothing sent" - ) - - def __argument_changed(self, index_topleft, index_bottomright, roles): - if not (Qt.EditRole in roles): - return - - model = index_bottomright.model() - curr_row = index_topleft.row() - model_row = model.rows[curr_row] - curr_col = index_bottomright.column() - osc_type = model_row[COL_TYPE] - - # Type changed - if curr_col == COL_TYPE: - index_start = model.createIndex(curr_row, COL_START_VAL) - index_end = model.createIndex(curr_row, COL_END_VAL) - delegate_start = self.oscView.itemDelegate(index_start) - delegate_end = self.oscView.itemDelegate(index_end) - - if osc_type == "Integer": - delegate_start.updateEditor(OscMessageType.Int) - delegate_end.updateEditor(OscMessageType.Int) - model_row[COL_START_VAL] = 0 - elif osc_type == "Float": - delegate_start.updateEditor(OscMessageType.Float) - delegate_end.updateEditor(OscMessageType.Float) - model_row[COL_START_VAL] = 0 - elif osc_type == "Bool": - delegate_start.updateEditor(OscMessageType.Bool) - delegate_end.updateEditor() - model_row[COL_START_VAL] = True - else: - delegate_start.updateEditor(OscMessageType.String) - delegate_end.updateEditor() - model_row[COL_START_VAL] = "None" - - model_row[COL_END_VAL] = None - model_row[COL_DO_FADE] = False - - if not type_can_fade(model_row[COL_TYPE]): - model_row[COL_END_VAL] = None - model_row[COL_DO_FADE] = False - - if curr_col == COL_DO_FADE: - if model_row[COL_DO_FADE] is False: - model_row[COL_END_VAL] = None - class OscView(QTableView): def __init__(self, **kwargs): @@ -461,9 +320,9 @@ def __init__(self, **kwargs): options=[i.value for i in OscMessageType], tr_context="OscMessageType", ), - OscArgumentDelegate(), - OscArgumentDelegate(), - CheckBoxDelegate(), + QStyledItemDelegate(), + QStyledItemDelegate(), + BoolCheckBoxDelegate(), ] self.setSelectionBehavior(QTableWidget.SelectRows) @@ -482,5 +341,78 @@ def __init__(self, **kwargs): for column, delegate in enumerate(self.delegates): self.setItemDelegateForColumn(column, delegate) + def setModel(self, model): + if isinstance(model, SimpleTableModel): + if isinstance(self.model(), SimpleTableModel): + self.model().dataChanged.disconnect(self._dataChanged) + + super().setModel(model) + model.dataChanged.connect(self._dataChanged) + + def _toInt(self, modelRow): + start = modelRow[COL_START_VALUE] + end = modelRow[COL_END_VALUE] + + modelRow[COL_END_VALUE] = 0 + modelRow[COL_START_VALUE] = 0 + # We try to covert the value, if possible + try: + modelRow[COL_START_VALUE] = int(start) + modelRow[COL_END_VALUE] = int(end) + except (ValueError, TypeError): + pass + + def _toFloat(self, modelRow): + start = modelRow[COL_START_VALUE] + end = modelRow[COL_END_VALUE] + + modelRow[COL_START_VALUE] = 0.0 + modelRow[COL_END_VALUE] = 0.0 + # We try to covert the value, if possible + # We also take care of "inf" and "-inf" values + try: + start = float(start) + if float("-inf") < start < float("inf"): + modelRow[COL_START_VALUE] = start + + end = float(end) + if float("-inf") < end < float("inf"): + modelRow[COL_END_VALUE] = end + except (ValueError, TypeError): + pass + + def _toBool(self, modelRow): + modelRow[COL_START_VALUE] = True + + def _toString(self, modelRow): + modelRow[COL_START_VALUE] = "text" + + def _dataChanged(self, _, bottom_right, roles): + if Qt.EditRole not in roles: + return + + # NOTE: We assume one item will change at the time + column = bottom_right.column() + modelRow = self.model().rows[bottom_right.row()] + oscType = modelRow[COL_TYPE] + + if column == COL_TYPE: + # Type changed, set a correct value + # The delegate will decide the correct editor to display + if oscType == OscMessageType.Int: + self._toInt(modelRow) + elif oscType == OscMessageType.Float: + self._toFloat(modelRow) + elif oscType == OscMessageType.Bool: + self._toBool(modelRow) + else: + # We assume OscMessageType.String + self._toString(modelRow) + + if not type_can_fade(oscType): + # Cannot fade, keep columns cleans + modelRow[COL_FADE] = False + modelRow[COL_END_VALUE] = None + CueSettingsRegistry().add(OscCueSettings, OscCue) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 7411cdd43..fc1c747cb 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -318,7 +318,7 @@ def __new_message(self): if row[0] == "Bool": if row[1] is True: types += "T" - if row[1] is True: + else: types += "F" else: if row[0] == "Integer": diff --git a/lisp/plugins/osc/osc_server.py b/lisp/plugins/osc/osc_server.py index c02f1d66f..6c59470cc 100644 --- a/lisp/plugins/osc/osc_server.py +++ b/lisp/plugins/osc/osc_server.py @@ -16,19 +16,19 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import logging -import traceback -from enum import Enum from liblo import ServerThread, ServerError + +import logging from threading import Lock from lisp.core.signal import Signal +from lisp.core.util import EqEnum from lisp.ui.ui_utils import translate logger = logging.getLogger(__name__) -class OscMessageType(Enum): +class OscMessageType(EqEnum): Int = "Integer" Float = "Float" Bool = "Bool" diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index 174296f70..e569e6ef4 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import Qt, QEvent +from PyQt5.QtCore import Qt, QEvent, QPoint from PyQt5.QtWidgets import ( QStyledItemDelegate, QComboBox, @@ -24,6 +24,8 @@ QStyle, QDialog, QCheckBox, + QStyleOptionButton, + qApp, ) from lisp.application import Application @@ -122,23 +124,48 @@ def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) -class CheckBoxDelegate(QStyledItemDelegate): +class BoolCheckBoxDelegate(QStyledItemDelegate): def __init__(self, **kwargs): super().__init__(**kwargs) def createEditor(self, parent, option, index): - return QCheckBox(parent) + return None - def setEditorData(self, checkBox, index): + def _checkBoxRect(self, option): + cbRect = option.rect + cbSize = qApp.style().subElementRect( + QStyle.SE_ViewItemCheckIndicator, QStyleOptionButton(), QCheckBox() + ) + + # Center the checkbox (horizontally) + cbRect.moveLeft( + option.rect.left() + (option.rect.width() - cbSize.width()) / 2 + ) + return cbRect + + def editorEvent(self, event, model, option, index): + # If the "checkbox" is left clicked change the current state + if event.type() == QEvent.MouseButtonRelease: + cbRect = self._checkBoxRect(option) + if event.button() == Qt.LeftButton and cbRect.contains(event.pos()): + value = bool(index.model().data(index, Qt.EditRole)) + model.setData(index, not value, Qt.EditRole) + + return True + + return super().editorEvent(event, model, option, index) + + def paint(self, painter, option, index): value = index.model().data(index, Qt.EditRole) - if isinstance(value, bool): - checkBox.setChecked(value) + cbOpt = QStyleOptionButton() - def setModelData(self, checkBox, model, index): - model.setData(index, checkBox.isChecked(), Qt.EditRole) + cbOpt.state = QStyle.State_Enabled + cbOpt.state |= QStyle.State_On if value else QStyle.State_Off + cbOpt.rect = self._checkBoxRect(option) - def updateEditorGeometry(self, editor, option, index): - editor.setGeometry(option.rect) + qApp.style().drawControl( + QStyle.CE_CheckBox, cbOpt, painter, QCheckBox() + ) class LineEditDelegate(QStyledItemDelegate): diff --git a/lisp/ui/qmodels.py b/lisp/ui/qmodels.py index cb252bbe5..ebaca8114 100644 --- a/lisp/ui/qmodels.py +++ b/lisp/ui/qmodels.py @@ -18,6 +18,7 @@ from PyQt5.QtCore import QAbstractTableModel, QModelIndex, Qt from lisp.cues.cue import Cue +from lisp.ui.ui_utils import translate # Application defined data-roles CueClassRole = Qt.UserRole + 1 @@ -69,20 +70,30 @@ def headerData(self, section, orientation, role=Qt.DisplayRole): def data(self, index, role=Qt.DisplayRole): if index.isValid(): - if role == Qt.DisplayRole or role == Qt.EditRole: + if role == Qt.DisplayRole: + value = self.rows[index.row()][index.column()] + if type(value) == bool: + return translate("QComboBox", str(value).title()) + else: + return value + elif role == Qt.EditRole: return self.rows[index.row()][index.column()] elif role == Qt.TextAlignmentRole: return Qt.AlignCenter def setData(self, index, value, role=Qt.DisplayRole): - if index.isValid(): - if role == Qt.DisplayRole or role == Qt.EditRole: - self.rows[index.row()][index.column()] = value + if index.isValid() and (role == Qt.DisplayRole or role == Qt.EditRole): + row = index.row() + col = index.column() + # Update only if value is different + if self.rows[row][col] != value: + self.rows[index.row][col] = value self.dataChanged.emit( - self.index(index.row(), 0), - self.index(index.row(), index.column()), + self.index(index.row, 0), + self.index(index.row, col), [Qt.DisplayRole, Qt.EditRole], ) + return True return False From 8607370013c07551b8e86861900878b97a2e9611 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 26 Jan 2019 15:33:18 +0100 Subject: [PATCH 164/333] Cue registration minor refactoring --- lisp/application.py | 34 +------------ lisp/core/plugin.py | 1 + lisp/cues/cue_factory.py | 1 + lisp/plugins/action_cues/__init__.py | 11 +++-- lisp/plugins/action_cues/collection_cue.py | 1 + .../command_cue.py | 1 + lisp/plugins/action_cues/index_action_cue.py | 1 + lisp/plugins/action_cues/seek_cue.py | 1 + lisp/plugins/action_cues/stop_all.py | 1 + lisp/plugins/action_cues/volume_control.py | 1 + lisp/plugins/gst_backend/gst_backend.py | 3 +- lisp/plugins/midi/midi.py | 11 +++-- lisp/plugins/osc/osc.py | 15 +++--- lisp/plugins/osc/osc_cue.py | 2 +- lisp/plugins/system_command_cue/__init__.py | 35 -------------- lisp/ui/mainwindow.py | 48 +++++++++++++++---- 16 files changed, 72 insertions(+), 95 deletions(-) rename lisp/plugins/{system_command_cue => action_cues}/command_cue.py (99%) delete mode 100644 lisp/plugins/system_command_cue/__init__.py diff --git a/lisp/application.py b/lisp/application.py index 7adedf9e4..daa94b56c 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -33,8 +33,8 @@ from lisp.ui.layoutselect import LayoutSelect from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.app_configuration import AppConfigurationDialog -from lisp.ui.settings.app_pages.general import AppGeneral from lisp.ui.settings.app_pages.cue import CueAppSettings +from lisp.ui.settings.app_pages.general import AppGeneral from lisp.ui.settings.app_pages.layouts import LayoutsSettings from lisp.ui.settings.app_pages.plugins import PluginsSettings from lisp.ui.settings.cue_pages.cue_appearance import Appearance @@ -235,35 +235,3 @@ def _load_from_file(self, session_file): ).format(session_file) ) self._new_session_dialog() - - def register_cue_type(self, cue_class, cue_category=None): - CueFactory.register_factory(cue_class.__name__, cue_class) - - def __cue_factory(): - try: - cue = CueFactory.create_cue(cue_class.__name__) - - # Get the (last) index of the current selection - layout_selection = list(self.layout.selected_cues()) - if layout_selection: - cue.index = layout_selection[-1].index + 1 - - self.cue_model.add(cue) - except Exception: - logger.exception( - translate("ApplicationError", "Cannot create cue {}").format( - cue_class.__name__ - ) - ) - - self.window.register_cue_menu_action( - translate("CueName", cue_class.Name), - __cue_factory, - cue_category or translate("CueCategory", "Misc cues"), - ) - - logger.debug( - translate("ApplicationDebug", 'Registered cue: "{}"').format( - cue_class.__name__ - ) - ) diff --git a/lisp/core/plugin.py b/lisp/core/plugin.py index 0d1b5129e..614d1b1cf 100644 --- a/lisp/core/plugin.py +++ b/lisp/core/plugin.py @@ -31,6 +31,7 @@ class Plugin: Config = DummyConfiguration() def __init__(self, app): + """:type app: lisp.application.Application""" self.__app = app @property diff --git a/lisp/cues/cue_factory.py b/lisp/cues/cue_factory.py index df11caeca..71e57f387 100644 --- a/lisp/cues/cue_factory.py +++ b/lisp/cues/cue_factory.py @@ -19,6 +19,7 @@ from lisp.core.util import typename + class CueFactory: """Provide a generic factory to build different cues types. diff --git a/lisp/plugins/action_cues/__init__.py b/lisp/plugins/action_cues/__init__.py index 7675acb4d..bb0f72651 100644 --- a/lisp/plugins/action_cues/__init__.py +++ b/lisp/plugins/action_cues/__init__.py @@ -19,17 +19,18 @@ from lisp.core.loading import load_classes from lisp.core.plugin import Plugin -from lisp.ui.ui_utils import translate +from lisp.cues.cue_factory import CueFactory class ActionCues(Plugin): Name = "Action Cues" Authors = ("Francesco Ceruti",) - Description = "A collection of cues that act on other cues" + Description = "A collection of cues to extend base functions" def __init__(self, app): super().__init__(app) - # Register each action cue class with the cue-factory - for _, cue in load_classes(__package__, path.dirname(__file__)): - app.register_cue_type(cue, translate("CueCategory", "Action cues")) + # Register all the cue in the plugin + for _, cue_class in load_classes(__package__, path.dirname(__file__)): + CueFactory.register_factory(cue_class.__name__, cue_class) + app.window.register_simple_cue_menu(cue_class, cue_class.Category) diff --git a/lisp/plugins/action_cues/collection_cue.py b/lisp/plugins/action_cues/collection_cue.py index 6e6ca058c..370d67e31 100644 --- a/lisp/plugins/action_cues/collection_cue.py +++ b/lisp/plugins/action_cues/collection_cue.py @@ -39,6 +39,7 @@ class CollectionCue(Cue): Name = QT_TRANSLATE_NOOP("CueName", "Collection Cue") + Category = QT_TRANSLATE_NOOP("CueCategory", "Action cues") targets = Property(default=[]) diff --git a/lisp/plugins/system_command_cue/command_cue.py b/lisp/plugins/action_cues/command_cue.py similarity index 99% rename from lisp/plugins/system_command_cue/command_cue.py rename to lisp/plugins/action_cues/command_cue.py index d8456139b..c9b09134c 100644 --- a/lisp/plugins/system_command_cue/command_cue.py +++ b/lisp/plugins/action_cues/command_cue.py @@ -38,6 +38,7 @@ class CommandCue(Cue): """ Name = QT_TRANSLATE_NOOP("CueName", "Command Cue") + Category = None CueActions = (CueAction.Default, CueAction.Start, CueAction.Stop) command = Property(default="") diff --git a/lisp/plugins/action_cues/index_action_cue.py b/lisp/plugins/action_cues/index_action_cue.py index 3e5cba6cc..8a93132ef 100644 --- a/lisp/plugins/action_cues/index_action_cue.py +++ b/lisp/plugins/action_cues/index_action_cue.py @@ -37,6 +37,7 @@ class IndexActionCue(Cue): Name = QT_TRANSLATE_NOOP("CueName", "Index Action") + Category = QT_TRANSLATE_NOOP("CueCategory", "Action cues") target_index = Property(default=0) relative = Property(default=True) diff --git a/lisp/plugins/action_cues/seek_cue.py b/lisp/plugins/action_cues/seek_cue.py index 1af9f575f..5eb907772 100644 --- a/lisp/plugins/action_cues/seek_cue.py +++ b/lisp/plugins/action_cues/seek_cue.py @@ -38,6 +38,7 @@ class SeekCue(Cue): Name = QT_TRANSLATE_NOOP("CueName", "Seek Cue") + Category = QT_TRANSLATE_NOOP("CueCategory", "Action cues") target_id = Property() time = Property(default=-1) diff --git a/lisp/plugins/action_cues/stop_all.py b/lisp/plugins/action_cues/stop_all.py index 009264432..fbf59d337 100644 --- a/lisp/plugins/action_cues/stop_all.py +++ b/lisp/plugins/action_cues/stop_all.py @@ -28,6 +28,7 @@ class StopAll(Cue): Name = QT_TRANSLATE_NOOP("CueName", "Stop-All") + Category = QT_TRANSLATE_NOOP("CueCategory", "Action cues") action = Property(default=CueAction.Stop.value) diff --git a/lisp/plugins/action_cues/volume_control.py b/lisp/plugins/action_cues/volume_control.py index 792b23086..4979dcf2f 100644 --- a/lisp/plugins/action_cues/volume_control.py +++ b/lisp/plugins/action_cues/volume_control.py @@ -52,6 +52,7 @@ class VolumeControl(Cue): Name = QT_TRANSLATE_NOOP("CueName", "Volume Control") + Category = QT_TRANSLATE_NOOP("CueCategory", "Action cues") target_id = Property() fade_type = Property(default=FadeInType.Linear.name) diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index 813d8cb4c..302a5a4f2 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -68,9 +68,8 @@ def __init__(self, app): # Register GstMediaCue factory CueFactory.register_factory("GstMediaCue", GstCueFactory(tuple())) - # Add Menu entry - self.app.window.register_cue_menu_action( + self.app.window.register_cue_menu( translate("GstBackend", "Audio cue (from file)"), self._add_uri_audio_cue, category="Media cues", diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index f954cb208..a00b514db 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -16,13 +16,15 @@ # along with Linux Show Player. If not, see . import mido +from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.core.plugin import Plugin +from lisp.cues.cue_factory import CueFactory from lisp.plugins.midi.midi_cue import MidiCue from lisp.plugins.midi.midi_io import MIDIOutput, MIDIInput from lisp.plugins.midi.midi_settings import MIDISettings from lisp.ui.settings.app_configuration import AppConfigurationDialog -from lisp.ui.ui_utils import translate + class Midi(Plugin): """Provide MIDI I/O functionality""" @@ -39,8 +41,6 @@ def __init__(self, app): "plugins.midi", MIDISettings, Midi.Config ) - app.register_cue_type(MidiCue, translate("CueCategory", "Integration cues")) - # Load the backend and set it as current mido backend self.backend = mido.Backend(Midi.Config["backend"], load=True) mido.set_backend(self.backend) @@ -57,6 +57,11 @@ def __init__(self, app): Midi.Config.changed.connect(self.__config_change) Midi.Config.updated.connect(self.__config_update) + CueFactory.register_factory(MidiCue.__name__, MidiCue) + app.window.register_simple_cue_menu( + MidiCue, QT_TRANSLATE_NOOP("CueCategory", "Integration cues") + ) + def __config_change(self, key, value): if key == "inputDevice": self.__input.change_port(self._input_name(value)) diff --git a/lisp/plugins/osc/osc.py b/lisp/plugins/osc/osc.py index 779622da2..f9f7fe8ba 100644 --- a/lisp/plugins/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2017 Francesco Ceruti +# Copyright 2019 Francesco Ceruti # Copyright 2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify @@ -15,14 +15,14 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . - +from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.core.plugin import Plugin +from lisp.cues.cue_factory import CueFactory from lisp.plugins.osc.osc_cue import OscCue from lisp.plugins.osc.osc_server import OscServer from lisp.plugins.osc.osc_settings import OscSettings from lisp.ui.settings.app_configuration import AppConfigurationDialog -from lisp.ui.ui_utils import translate class Osc(Plugin): @@ -40,9 +40,6 @@ def __init__(self, app): "plugins.osc", OscSettings, Osc.Config ) - # Register the OSC cue type - app.register_cue_type(OscCue, translate("CueCategory", "Integration cues")) - # Create a server instance self.__server = OscServer( Osc.Config["hostname"], Osc.Config["inPort"], Osc.Config["outPort"] @@ -52,6 +49,12 @@ def __init__(self, app): Osc.Config.changed.connect(self.__config_change) Osc.Config.updated.connect(self.__config_update) + # Register the OSC cue type + CueFactory.register_factory(OscCue.__name__, OscCue) + app.window.register_simple_cue_menu( + OscCue, QT_TRANSLATE_NOOP("CueCategory", "Integration cues") + ) + @property def server(self): return self.__server diff --git a/lisp/plugins/osc/osc_cue.py b/lisp/plugins/osc/osc_cue.py index abb01f0aa..cdb86f39c 100644 --- a/lisp/plugins/osc/osc_cue.py +++ b/lisp/plugins/osc/osc_cue.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2019 Francesco Ceruti # Copyright 2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify diff --git a/lisp/plugins/system_command_cue/__init__.py b/lisp/plugins/system_command_cue/__init__.py deleted file mode 100644 index 7e8a529a1..000000000 --- a/lisp/plugins/system_command_cue/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -# This file is part of Linux Show Player -# -# Copyright 2019 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from os import path - -from lisp.core.loading import load_classes -from lisp.core.plugin import Plugin -from lisp.ui.ui_utils import translate - - -class SystemCommandCue(Plugin): - Name = "Command Cue" - Authors = ("Francesco Ceruti",) - Description = "A cue that allows running an arbitrary shell command." - - def __init__(self, app): - super().__init__(app) - - # Register the cue in the cue-factory - for _, cue in load_classes(__package__, path.dirname(__file__)): - app.register_cue_type(cue, translate("CueCategory", "Integration cues")) diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index cae170c4f..1512c7fc0 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2019 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -17,7 +17,6 @@ import logging import os - from PyQt5 import QtCore from PyQt5.QtCore import pyqtSignal from PyQt5.QtGui import QKeySequence @@ -36,6 +35,7 @@ from lisp.core.actions_handler import MainActionsHandler from lisp.core.singleton import QSingleton +from lisp.cues.cue_factory import CueFactory from lisp.cues.media_cue import MediaCue from lisp.ui.about import About from lisp.ui.logging.dialog import LogDialogs @@ -46,6 +46,8 @@ from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.ui_utils import translate +logger = logging.getLogger(__name__) + class MainWindow(QMainWindow, metaclass=QSingleton): new_session = pyqtSignal() @@ -244,9 +246,7 @@ def closeEvent(self, event): self._exit() event.ignore() - def register_cue_menu_action( - self, name, function, category="", shortcut="" - ): + def register_cue_menu(self, name, function, category="", shortcut=""): """Register a new-cue choice for the edit-menu param name: The name for the MenuAction @@ -256,10 +256,10 @@ def register_cue_menu_action( """ action = QAction(self) - action.setText(translate("MainWindow", name)) + action.setText(translate("CueName", name)) action.triggered.connect(function) if shortcut != "": - action.setShortcut(translate("MainWindow", shortcut)) + action.setShortcut(translate("CueCategory", shortcut)) if category != "": if category not in self._cue_add_menu: @@ -271,6 +271,34 @@ def register_cue_menu_action( else: self.menuEdit.insertAction(self.cueSeparator, action) + logger.debug( + translate("MainWindowDebug", 'Registered cue: "{}"').format(name) + ) + + def register_simple_cue_menu(self, cue_class, cue_category=""): + def menu_function(): + try: + cue = CueFactory.create_cue(cue_class.__name__) + + # Get the (last) index of the current selection + layout_selection = list(self.session.layout.selected_cues()) + if layout_selection: + cue.index = layout_selection[-1].index + 1 + + self.session.cue_model.add(cue) + except Exception: + logger.exception( + translate("MainWindowError", "Cannot create cue {}").format( + cue_class.__name__ + ) + ) + + self.register_cue_menu( + translate("CueName", cue_class.Name), + menu_function, + cue_category or translate("CueCategory", "Misc cues"), + ) + def update_window_title(self): tile = self._title + " - " + self.session.name() if not MainActionsHandler.is_saved(): @@ -278,13 +306,13 @@ def update_window_title(self): self.setWindowTitle(tile) - def _action_done(self, action): + def _action_done(self): self.update_window_title() - def _action_undone(self, action): + def _action_undone(self): self.update_window_title() - def _action_redone(self, action): + def _action_redone(self): self.update_window_title() def _save(self): From 0c5be3c7c9b8cd77d6235bdb85606c6f9e879868 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 29 Jan 2019 15:23:20 +0100 Subject: [PATCH 165/333] Fixes --- lisp/plugins/media_info/media_info.py | 1 + lisp/plugins/midi/midi.py | 10 +++++----- lisp/plugins/network/api/layout.py | 10 +++++++++- lisp/plugins/osc/osc.py | 11 +++++------ lisp/ui/mainwindow.py | 4 +++- lisp/ui/qmodels.py | 6 +++--- 6 files changed, 26 insertions(+), 16 deletions(-) diff --git a/lisp/plugins/media_info/media_info.py b/lisp/plugins/media_info/media_info.py index 809475f7e..031136d92 100644 --- a/lisp/plugins/media_info/media_info.py +++ b/lisp/plugins/media_info/media_info.py @@ -49,6 +49,7 @@ class MediaInfo(Plugin): Name = "Media-Cue information dialog" Authors = ("Francesco Ceruti",) Description = "Provide a function to show information on media-cues source" + Depends = ("GstBackend", ) def __init__(self, app): super().__init__(app) diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index a00b514db..be07054ab 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -40,6 +40,11 @@ def __init__(self, app): AppConfigurationDialog.registerSettingsPage( "plugins.midi", MIDISettings, Midi.Config ) + # Register cue + CueFactory.register_factory(MidiCue.__name__, MidiCue) + app.window.register_simple_cue_menu( + MidiCue, QT_TRANSLATE_NOOP("CueCategory", "Integration cues") + ) # Load the backend and set it as current mido backend self.backend = mido.Backend(Midi.Config["backend"], load=True) @@ -57,11 +62,6 @@ def __init__(self, app): Midi.Config.changed.connect(self.__config_change) Midi.Config.updated.connect(self.__config_update) - CueFactory.register_factory(MidiCue.__name__, MidiCue) - app.window.register_simple_cue_menu( - MidiCue, QT_TRANSLATE_NOOP("CueCategory", "Integration cues") - ) - def __config_change(self, key, value): if key == "inputDevice": self.__input.change_port(self._input_name(value)) diff --git a/lisp/plugins/network/api/layout.py b/lisp/plugins/network/api/layout.py index f6f2b6b34..41305d9a1 100644 --- a/lisp/plugins/network/api/layout.py +++ b/lisp/plugins/network/api/layout.py @@ -37,4 +37,12 @@ def on_post(self, req, resp): resp.status = falcon.HTTP_BAD_REQUEST -__endpoints__ = (LayoutActionEndPoint,) +class GoEndPoint(EndPoint): + UriTemplate = "/layout/go" + + def on_post(self, req, resp): + self.app.layout.go() + resp.status = falcon.HTTP_CREATED + + +__endpoints__ = (LayoutActionEndPoint, GoEndPoint) diff --git a/lisp/plugins/osc/osc.py b/lisp/plugins/osc/osc.py index f9f7fe8ba..7be96ac53 100644 --- a/lisp/plugins/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -39,6 +39,11 @@ def __init__(self, app): AppConfigurationDialog.registerSettingsPage( "plugins.osc", OscSettings, Osc.Config ) + # Register the cue + CueFactory.register_factory(OscCue.__name__, OscCue) + app.window.register_simple_cue_menu( + OscCue, QT_TRANSLATE_NOOP("CueCategory", "Integration cues") + ) # Create a server instance self.__server = OscServer( @@ -49,12 +54,6 @@ def __init__(self, app): Osc.Config.changed.connect(self.__config_change) Osc.Config.updated.connect(self.__config_update) - # Register the OSC cue type - CueFactory.register_factory(OscCue.__name__, OscCue) - app.window.register_simple_cue_menu( - OscCue, QT_TRANSLATE_NOOP("CueCategory", "Integration cues") - ) - @property def server(self): return self.__server diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 1512c7fc0..5093be2be 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -272,7 +272,9 @@ def register_cue_menu(self, name, function, category="", shortcut=""): self.menuEdit.insertAction(self.cueSeparator, action) logger.debug( - translate("MainWindowDebug", 'Registered cue: "{}"').format(name) + translate("MainWindowDebug", 'Registered cue menu: "{}"').format( + name + ) ) def register_simple_cue_menu(self, cue_class, cue_category=""): diff --git a/lisp/ui/qmodels.py b/lisp/ui/qmodels.py index ebaca8114..65cc1a356 100644 --- a/lisp/ui/qmodels.py +++ b/lisp/ui/qmodels.py @@ -87,10 +87,10 @@ def setData(self, index, value, role=Qt.DisplayRole): col = index.column() # Update only if value is different if self.rows[row][col] != value: - self.rows[index.row][col] = value + self.rows[row][col] = value self.dataChanged.emit( - self.index(index.row, 0), - self.index(index.row, col), + self.index(row, 0), + self.index(row, col), [Qt.DisplayRole, Qt.EditRole], ) From 7a1326c62b9068c52dd8fcd3b2aa9b4798bb5923 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 1 Feb 2019 11:29:47 +0100 Subject: [PATCH 166/333] Fix #153 --- lisp/plugins/cart_layout/cue_widget.py | 6 ++++-- lisp/plugins/media_info/media_info.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lisp/plugins/cart_layout/cue_widget.py b/lisp/plugins/cart_layout/cue_widget.py index 1f1dbf69c..f06ed778b 100644 --- a/lisp/plugins/cart_layout/cue_widget.py +++ b/lisp/plugins/cart_layout/cue_widget.py @@ -76,7 +76,9 @@ def __init__(self, cue, **kwargs): self.nameButton.setAlignment(Qt.AlignCenter) self.nameButton.setFocusPolicy(Qt.NoFocus) self.nameButton.clicked.connect(self._clicked) - self.nameButton.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) + self.nameButton.setSizePolicy( + QSizePolicy.Ignored, QSizePolicy.Preferred + ) self.hLayout.addWidget(self.nameButton, 5) self.statusIcon = QLabel(self.nameButton) @@ -115,7 +117,7 @@ def __init__(self, cue, **kwargs): self.timeDisplay.setDigitCount(8) self.timeDisplay.display("00:00:00") self.timeBar.layout().addWidget(self.timeDisplay) - self.timeBar.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) + self.timeBar.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) self.timeBar.setVisible(False) self._setCue(cue) diff --git a/lisp/plugins/media_info/media_info.py b/lisp/plugins/media_info/media_info.py index 031136d92..59f7ced14 100644 --- a/lisp/plugins/media_info/media_info.py +++ b/lisp/plugins/media_info/media_info.py @@ -49,7 +49,7 @@ class MediaInfo(Plugin): Name = "Media-Cue information dialog" Authors = ("Francesco Ceruti",) Description = "Provide a function to show information on media-cues source" - Depends = ("GstBackend", ) + Depends = ("GstBackend",) def __init__(self, app): super().__init__(app) From dc942fb1eb9449a23c4aa445fc7429c6ef02f27a Mon Sep 17 00:00:00 2001 From: Gajenthran <35834540+Gajenthran@users.noreply.github.com> Date: Tue, 19 Feb 2019 21:34:55 +0100 Subject: [PATCH 167/333] Return to player if the user don't save and add shortcut to remove cue (#154) --- lisp/layout/cue_layout.py | 3 +++ lisp/plugins/list_layout/layout.py | 4 ++++ lisp/ui/mainwindow.py | 8 ++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lisp/layout/cue_layout.py b/lisp/layout/cue_layout.py index abb5f9eef..7535f391c 100644 --- a/lisp/layout/cue_layout.py +++ b/lisp/layout/cue_layout.py @@ -218,6 +218,9 @@ def show_cue_context_menu(self, cues, position): def finalize(self): """Destroy all the layout elements""" + def _remove_cue(self, cue): + self.cue_model.remove(cue) + def _remove_cues(self, cues): for cue in cues: self.cue_model.remove(cue) diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index ba9985bc4..346175e8d 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -251,6 +251,10 @@ def _key_pressed(self, event): if QKeySequence(keys) in self._go_key_sequence: event.accept() self.__go_slot() + elif event.key() == Qt.Key_Delete: + cue = self.standby_cue() + if cue is not None: + self._remove_cue(cue) elif event.key() == Qt.Key_Space: if event.modifiers() == Qt.ShiftModifier: event.accept() diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 5093be2be..bac3325b3 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -319,9 +319,10 @@ def _action_redone(self): def _save(self): if self.session.session_file == "": - self._save_with_name() + return self._save_with_name() else: self.save_session.emit(self.session.session_file) + return True def _save_with_name(self): filename, ok = QFileDialog.getSaveFileName( @@ -333,6 +334,9 @@ def _save_with_name(self): filename += ".lsp" self.save_session.emit(filename) + return True + else: + return False def _show_preferences(self): prefUi = AppConfigurationDialog(parent=self) @@ -371,7 +375,7 @@ def _check_saved(self): if result == QMessageBox.Cancel: return False elif result == QMessageBox.Save: - self._save() + return self._save() return True From 77b072450648ff51ca80ccf89750f3ee47ad798b Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 19 Feb 2019 21:57:18 +0100 Subject: [PATCH 168/333] Minor improvements in ListLayout selection --- lisp/plugins/list_layout/layout.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index 346175e8d..5606f61c8 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -121,6 +121,7 @@ def __init__(self, application): self.selection_mode_action = QAction(parent=layout_menu) self.selection_mode_action.setCheckable(True) self.selection_mode_action.triggered.connect(self._set_selection_mode) + self.selection_mode_action.setShortcut(QKeySequence("Ctrl+Alt+S")) layout_menu.addAction(self.selection_mode_action) # Load settings @@ -218,9 +219,10 @@ def finalize(self): del self._edit_actions_group def select_all(self, cue_type=Cue): - for index in range(self._view.listView.topLevelItemCount()): - if isinstance(self._list_model.item(index), cue_type): - self._view.listView.topLevelItem(index).setSelected(True) + if self.selection_mode: + for index in range(self._view.listView.topLevelItemCount()): + if isinstance(self._list_model.item(index), cue_type): + self._view.listView.topLevelItem(index).setSelected(True) def deselect_all(self, cue_type=Cue): for index in range(self._view.listView.topLevelItemCount()): @@ -228,9 +230,10 @@ def deselect_all(self, cue_type=Cue): self._view.listView.topLevelItem(index).setSelected(False) def invert_selection(self): - for index in range(self._view.listView.topLevelItemCount()): - item = self._view.listView.topLevelItem(index) - item.setSelected(not item.isSelected()) + if self.selection_mode: + for index in range(self._view.listView.topLevelItemCount()): + item = self._view.listView.topLevelItem(index) + item.setSelected(not item.isSelected()) def _key_pressed(self, event): event.ignore() @@ -248,13 +251,12 @@ def _key_pressed(self, event): if modifiers & Qt.MetaModifier: keys += Qt.META - if QKeySequence(keys) in self._go_key_sequence: + sequence = QKeySequence(keys) + if sequence in self._go_key_sequence: event.accept() self.__go_slot() - elif event.key() == Qt.Key_Delete: - cue = self.standby_cue() - if cue is not None: - self._remove_cue(cue) + elif sequence == QKeySequence.Delete: + self._remove_cues(self.selected_cues()) elif event.key() == Qt.Key_Space: if event.modifiers() == Qt.ShiftModifier: event.accept() @@ -316,6 +318,10 @@ def _set_selection_mode(self, enable): self.selection_mode_action.setChecked(enable) if enable: self._view.listView.setSelectionMode(CueListView.ExtendedSelection) + + standby = self.standby_index() + if standby >= 0: + self._view.listView.topLevelItem(standby).setSelected(True) else: self.deselect_all() self._view.listView.setSelectionMode(CueListView.NoSelection) From f8b000d83307bdde864c96f76658108f1ffbd9b6 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 25 Feb 2019 00:57:28 +0100 Subject: [PATCH 169/333] Various GUI improvements Update: improved handling of "last used" directory for session files Update: replaced PyQt "exec_()" with "exec()" Update: minor refactoring of ui/mainwindow.py to allow better code reuse and conform to PyQt GUI parts using camelCase naming Fixed: better closing-events handling Fixed: Cue.execute using a strict "is" condition on the CueAction parameter, this broke the StopAll cue, and doesn't make too much sense Various changes here and there --- lisp/__init__.py | 15 +- lisp/application.py | 22 +- lisp/core/session.py | 6 +- lisp/cues/cue.py | 31 +-- lisp/layout/cue_layout.py | 4 +- lisp/main.py | 20 +- lisp/plugins/__init__.py | 4 +- lisp/plugins/action_cues/__init__.py | 2 +- lisp/plugins/action_cues/collection_cue.py | 2 +- lisp/plugins/action_cues/seek_cue.py | 2 +- lisp/plugins/action_cues/stop_all.py | 4 +- lisp/plugins/action_cues/volume_control.py | 2 +- lisp/plugins/cart_layout/layout.py | 2 +- lisp/plugins/controller/protocols/osc.py | 2 +- lisp/plugins/gst_backend/gst_backend.py | 4 +- .../plugins/gst_backend/gst_media_settings.py | 2 +- .../plugins/gst_backend/settings/jack_sink.py | 2 +- lisp/plugins/media_info/media_info.py | 2 +- lisp/plugins/midi/midi.py | 2 +- lisp/plugins/network/discovery_dialogs.py | 4 +- lisp/plugins/osc/osc.py | 2 +- lisp/plugins/presets/lib.py | 4 +- lisp/plugins/presets/presets_ui.py | 4 +- lisp/plugins/rename_cues/rename_cues.py | 2 +- lisp/plugins/rename_cues/rename_ui.py | 2 +- lisp/plugins/replay_gain/replay_gain.py | 2 +- lisp/plugins/synchronizer/synchronizer.py | 2 +- lisp/ui/layoutselect.py | 48 ++-- lisp/ui/mainwindow.py | 253 +++++++++--------- lisp/ui/qdelegates.py | 2 +- lisp/ui/ui_utils.py | 17 +- lisp/ui/widgets/qcolorbutton.py | 2 +- lisp/ui/widgets/qmessagebox.py | 2 +- 33 files changed, 239 insertions(+), 237 deletions(-) diff --git a/lisp/__init__.py b/lisp/__init__.py index 3b717b3a1..be0e215d0 100644 --- a/lisp/__init__.py +++ b/lisp/__init__.py @@ -22,18 +22,19 @@ __email__ = "ceppofrancy@gmail.com" __url__ = "https://github.com/FrancescoCeruti/linux-show-player" __license__ = "GPLv3" -__version__ = "0.6.0.dev0" - -# Application wide "constants" -APP_DIR = path.dirname(__file__) +__version_info__ = (0, 6, 0, "dev0") +__version__ = ".".join(map(str, __version_info__)) # The version passed follows . -USER_DIRS = AppDirs( - "LinuxShowPlayer", version=".".join(__version__.split(".")[0:2]) +app_dirs = AppDirs( + "LinuxShowPlayer", version="{}.{}".format(*__version_info__[0:2]) ) +# Application wide "constants" +APP_DIR = path.dirname(__file__) + DEFAULT_APP_CONFIG = path.join(APP_DIR, "default.json") -USER_APP_CONFIG = path.join(USER_DIRS.user_config_dir, "lisp.json") +USER_APP_CONFIG = path.join(app_dirs.user_config_dir, "lisp.json") I18N_PATH = path.join(APP_DIR, "i18n", "qm") diff --git a/lisp/application.py b/lisp/application.py index daa94b56c..b3a41f342 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -118,27 +118,25 @@ def start(self, session_file=""): def finalize(self): self._delete_session() - self.__main_window = None self.__cue_model = None + self.__main_window = None def _new_session_dialog(self): """Show the layout-selection dialog""" try: # Prompt the user for a new layout - dialog = LayoutSelect(parent=self.window) - if dialog.exec_() == QDialog.Accepted: - # If a valid file is selected load it, otherwise load the layout - if exists(dialog.filepath): - self._load_from_file(dialog.filepath) + dialog = LayoutSelect(self, parent=self.window) + if dialog.exec() == QDialog.Accepted: + # If a file is selected load it, otherwise load the layout + if dialog.sessionPath: + self._load_from_file(dialog.sessionPath) else: self._new_session(dialog.selected()) else: if self.__session is None: # If the user close the dialog, and no layout exists # the application is closed - self.finalize() qApp.quit() - exit(0) except Exception: logger.critical( translate("ApplicationError", "Startup error"), exc_info=True @@ -149,7 +147,7 @@ def _new_session(self, layout): self._delete_session() self.__session = Session(layout(application=self)) - self.__main_window.set_session(self.__session) + self.__main_window.setSession(self.__session) self.session_created.emit(self.__session) @@ -167,7 +165,7 @@ def _save_to_file(self, session_file): self.session.session_file = session_file # Save session path - self.conf.set("session.last_path", dirname(session_file)) + self.conf.set("session.last_dir", dirname(session_file)) self.conf.write() # Add the cues @@ -193,7 +191,7 @@ def _save_to_file(self, session_file): file.write(json.dumps(session_dict, sort_keys=True, indent=4)) MainActionsHandler.set_saved() - self.__main_window.update_window_title() + self.__main_window.updateWindowTitle() def _load_from_file(self, session_file): """ Load a saved session from file """ @@ -226,7 +224,7 @@ def _load_from_file(self, session_file): ) MainActionsHandler.set_saved() - self.__main_window.update_window_title() + self.__main_window.updateWindowTitle() except Exception: logger.exception( translate( diff --git a/lisp/core/session.py b/lisp/core/session.py index a0dce988f..a226bfb24 100644 --- a/lisp/core/session.py +++ b/lisp/core/session.py @@ -44,7 +44,7 @@ def name(self): else: return "Untitled" - def path(self): + def dir(self): """Return the current session-file path.""" if self.session_file: return os.path.dirname(self.session_file) @@ -54,13 +54,13 @@ def path(self): def abs_path(self, rel_path): """Return an absolute version of the given path.""" if not os.path.isabs(rel_path): - return os.path.normpath(os.path.join(self.path(), rel_path)) + return os.path.normpath(os.path.join(self.dir(), rel_path)) return rel_path def rel_path(self, abs_path): """Return a relative (to the session-file) version of the given path.""" - return os.path.relpath(abs_path, start=self.path()) + return os.path.relpath(abs_path, start=self.dir()) def finalize(self): self.layout.finalize() diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 2820ff7a8..b8f5bd264 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -181,43 +181,44 @@ def execute(self, action=CueAction.Default): :param action: the action to be performed :type action: CueAction """ - if action is CueAction.Default: + if action == CueAction.Default: if self._state & CueState.IsRunning: action = CueAction(self.default_stop_action) else: action = CueAction(self.default_start_action) - if action is CueAction.DoNothing: + if action == CueAction.DoNothing: return - if action in self.CueActions: - if action is CueAction.Interrupt: + + if action == self.CueActions: + if action == CueAction.Interrupt: self.interrupt() - elif action is CueAction.FadeOutInterrupt: + elif action == CueAction.FadeOutInterrupt: self.interrupt(fade=True) - elif action is CueAction.Start: + elif action == CueAction.Start: self.start() - elif action is CueAction.FadeInStart: + elif action == CueAction.FadeInStart: self.start(fade=self.fadein_duration > 0) - elif action is CueAction.Stop: + elif action == CueAction.Stop: self.stop() - elif action is CueAction.FadeOutStop: + elif action == CueAction.FadeOutStop: self.stop(fade=self.fadeout_duration > 0) - elif action is CueAction.Pause: + elif action == CueAction.Pause: self.pause() - elif action is CueAction.FadeOutPause: + elif action == CueAction.FadeOutPause: self.pause(fade=self.fadeout_duration > 0) - elif action is CueAction.Resume: + elif action == CueAction.Resume: self.resume() - elif action is CueAction.FadeInResume: + elif action == CueAction.FadeInResume: self.resume(fade=self.fadein_duration > 0) - elif action is CueAction.FadeOut: + elif action == CueAction.FadeOut: duration = AppConfig().get("cue.fadeAction", 0) fade = AppConfig().get( "cue.fadeActionType", FadeOutType.Linear.name ) self.fadeout(duration, FadeOutType[fade]) - elif action is CueAction.FadeIn: + elif action == CueAction.FadeIn: duration = AppConfig().get("cue.fadeAction", 0) fade = AppConfig().get( "cue.fadeActionType", FadeInType.Linear.name diff --git a/lisp/layout/cue_layout.py b/lisp/layout/cue_layout.py index 7535f391c..4a71fb752 100644 --- a/lisp/layout/cue_layout.py +++ b/lisp/layout/cue_layout.py @@ -184,7 +184,7 @@ def on_apply(settings): MainActionsHandler.do_action(action) dialog.onApply.connect(on_apply) - dialog.exec_() + dialog.exec() def edit_cues(self, cues): if cues: @@ -198,7 +198,7 @@ def on_apply(settings): MainActionsHandler.do_action(action) dialog.onApply.connect(on_apply) - dialog.exec_() + dialog.exec() def show_context_menu(self, position): menu = self.app.window.menuEdit diff --git a/lisp/main.py b/lisp/main.py index b412e5af7..b8bf68cfb 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -21,13 +21,14 @@ import sys import signal +from functools import partial from logging.handlers import RotatingFileHandler -from PyQt5.QtCore import QLocale, QLibraryInfo +from PyQt5.QtCore import QLocale, QLibraryInfo, QTimer from PyQt5.QtWidgets import QApplication from lisp import ( - USER_DIRS, + app_dirs, DEFAULT_APP_CONFIG, USER_APP_CONFIG, plugins, @@ -63,8 +64,8 @@ def main(): args = parser.parse_args() # Make sure the application user directories exist - os.makedirs(USER_DIRS.user_config_dir, exist_ok=True) - os.makedirs(USER_DIRS.user_data_dir, exist_ok=True) + os.makedirs(app_dirs.user_config_dir, exist_ok=True) + os.makedirs(app_dirs.user_data_dir, exist_ok=True) # Get logging level for the console if args.log == "debug": @@ -93,10 +94,10 @@ def main(): root_logger.addHandler(stream_handler) # Make sure the logs directory exists - os.makedirs(USER_DIRS.user_log_dir, exist_ok=True) + os.makedirs(app_dirs.user_log_dir, exist_ok=True) # Create the file handler file_handler = RotatingFileHandler( - os.path.join(USER_DIRS.user_log_dir, "lisp.log"), + os.path.join(app_dirs.user_log_dir, "lisp.log"), maxBytes=10 * (2 ** 20), backupCount=5, ) @@ -157,9 +158,10 @@ def handle_quit_signal(*_): signal.signal(signal.SIGINT, handle_quit_signal) with PyQtUnixSignalHandler(): - # Start the application - lisp_app.start(session_file=args.file) - exit_code = qt_app.exec_() # block until exit + # Defer application start when QT main-loop starts + QTimer.singleShot(0, partial(lisp_app.start, session_file=args.file)) + # Start QT main-loop, blocks until exit + exit_code = qt_app.exec() # Finalize all and exit plugins.finalize_plugins() diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index ae0807f2b..afbd762fa 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -19,7 +19,7 @@ import logging from os import path -from lisp import USER_DIRS +from lisp import app_dirs from lisp.core.configuration import JSONFileConfiguration from lisp.core.loading import load_classes from lisp.ui.ui_utils import install_translation, translate @@ -52,7 +52,7 @@ def load_plugins(application): # Load plugin configuration config = JSONFileConfiguration( - path.join(USER_DIRS.user_config_dir, mod_name + ".json"), + path.join(app_dirs.user_config_dir, mod_name + ".json"), default_config_path, ) plugin.Config = config diff --git a/lisp/plugins/action_cues/__init__.py b/lisp/plugins/action_cues/__init__.py index bb0f72651..59e5eaa20 100644 --- a/lisp/plugins/action_cues/__init__.py +++ b/lisp/plugins/action_cues/__init__.py @@ -33,4 +33,4 @@ def __init__(self, app): # Register all the cue in the plugin for _, cue_class in load_classes(__package__, path.dirname(__file__)): CueFactory.register_factory(cue_class.__name__, cue_class) - app.window.register_simple_cue_menu(cue_class, cue_class.Category) + app.window.registerSimpleCueMenu(cue_class, cue_class.Category) diff --git a/lisp/plugins/action_cues/collection_cue.py b/lisp/plugins/action_cues/collection_cue.py index 370d67e31..783e3eff5 100644 --- a/lisp/plugins/action_cues/collection_cue.py +++ b/lisp/plugins/action_cues/collection_cue.py @@ -109,7 +109,7 @@ def _add_cue(self, cue, action): self.cue_dialog.remove_cue(cue) def _add_dialog(self): - if self.cue_dialog.exec_() == QDialog.Accepted: + if self.cue_dialog.exec() == QDialog.Accepted: for target in self.cue_dialog.selected_cues(): self._add_cue(target, target.CueActions[0]) diff --git a/lisp/plugins/action_cues/seek_cue.py b/lisp/plugins/action_cues/seek_cue.py index 5eb907772..3acab539d 100644 --- a/lisp/plugins/action_cues/seek_cue.py +++ b/lisp/plugins/action_cues/seek_cue.py @@ -103,7 +103,7 @@ def retranslateUi(self): self.seekLabel.setText(translate("SeekCue", "Time to reach")) def select_cue(self): - if self.cueDialog.exec_() == self.cueDialog.Accepted: + if self.cueDialog.exec() == self.cueDialog.Accepted: cue = self.cueDialog.selected_cue() if cue is not None: diff --git a/lisp/plugins/action_cues/stop_all.py b/lisp/plugins/action_cues/stop_all.py index fbf59d337..a959b5be2 100644 --- a/lisp/plugins/action_cues/stop_all.py +++ b/lisp/plugins/action_cues/stop_all.py @@ -37,7 +37,9 @@ def __init__(self, **kwargs): self.name = translate("CueName", self.Name) def __start__(self, fade=False): - Application().layout.execute_all(action=self.action, quiet=True) + Application().layout.execute_all( + action=CueAction(self.action), quiet=True + ) class StopAllSettings(SettingsPage): diff --git a/lisp/plugins/action_cues/volume_control.py b/lisp/plugins/action_cues/volume_control.py index 4979dcf2f..9bdcfa08f 100644 --- a/lisp/plugins/action_cues/volume_control.py +++ b/lisp/plugins/action_cues/volume_control.py @@ -203,7 +203,7 @@ def retranslateUi(self): self.fadeGroup.setTitle(translate("VolumeControl", "Fade")) def select_cue(self): - if self.cueDialog.exec_() == self.cueDialog.Accepted: + if self.cueDialog.exec() == self.cueDialog.Accepted: cue = self.cueDialog.selected_cue() if cue is not None: diff --git a/lisp/plugins/cart_layout/layout.py b/lisp/plugins/cart_layout/layout.py index 37632dd89..1708902be 100644 --- a/lisp/plugins/cart_layout/layout.py +++ b/lisp/plugins/cart_layout/layout.py @@ -263,7 +263,7 @@ def remove_current_page(self): if self._cart_view.count(): confirm = RemovePageConfirmBox(self._cart_view) - if confirm.exec_() == QMessageBox.Yes: + if confirm.exec() == QMessageBox.Yes: self.remove_page(self._cart_view.currentIndex()) def remove_page(self, index): diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index fc1c747cb..5854df7ad 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -302,7 +302,7 @@ def __show_message(self, path, args, types): def __new_message(self): dialog = OscMessageDialog(parent=self) - if dialog.exec_() == dialog.Accepted: + if dialog.exec() == dialog.Accepted: path = dialog.pathEdit.text() if len(path) < 2 or path[0] is not "/": QMessageBox.warning( diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index 302a5a4f2..cd3c3d0d7 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -69,7 +69,7 @@ def __init__(self, app): # Register GstMediaCue factory CueFactory.register_factory("GstMediaCue", GstCueFactory(tuple())) # Add Menu entry - self.app.window.register_cue_menu( + self.app.window.registerCueMenu( translate("GstBackend", "Audio cue (from file)"), self._add_uri_audio_cue, category="Media cues", @@ -107,7 +107,7 @@ def _add_uri_audio_cue(self): """Add audio MediaCue(s) form user-selected files""" dir = GstBackend.Config.get("mediaLookupDir", "") if not os.path.exists(dir): - dir = self.app.session.path() + dir = self.app.session.dir() files, _ = QFileDialog.getOpenFileNames( self.app.window, diff --git a/lisp/plugins/gst_backend/gst_media_settings.py b/lisp/plugins/gst_backend/gst_media_settings.py index 1121c7cee..a255d903f 100644 --- a/lisp/plugins/gst_backend/gst_media_settings.py +++ b/lisp/plugins/gst_backend/gst_media_settings.py @@ -123,7 +123,7 @@ def __edit_pipe(self): # Show the dialog dialog = GstPipeEditDialog(self._settings.get("pipe", ()), parent=self) - if dialog.exec_() == dialog.Accepted: + if dialog.exec() == dialog.Accepted: # Reset the view self.listWidget.clear() if self._current_page is not None: diff --git a/lisp/plugins/gst_backend/settings/jack_sink.py b/lisp/plugins/gst_backend/settings/jack_sink.py index 8f0ad22bc..7e6577e0b 100644 --- a/lisp/plugins/gst_backend/settings/jack_sink.py +++ b/lisp/plugins/gst_backend/settings/jack_sink.py @@ -107,7 +107,7 @@ def enableCheck(self, enabled): def __edit_connections(self): dialog = JackConnectionsDialog(self.__jack_client, parent=self) dialog.set_connections(self.connections.copy()) - dialog.exec_() + dialog.exec() if dialog.result() == dialog.Accepted: self.connections = dialog.connections diff --git a/lisp/plugins/media_info/media_info.py b/lisp/plugins/media_info/media_info.py index 59f7ced14..12971ff82 100644 --- a/lisp/plugins/media_info/media_info.py +++ b/lisp/plugins/media_info/media_info.py @@ -120,7 +120,7 @@ def _show_info(self, cue): # Show the dialog dialog = InfoDialog(self.app.window, info, cue.name) - dialog.exec_() + dialog.exec() class InfoDialog(QDialog): diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index be07054ab..79e6b898a 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -42,7 +42,7 @@ def __init__(self, app): ) # Register cue CueFactory.register_factory(MidiCue.__name__, MidiCue) - app.window.register_simple_cue_menu( + app.window.registerSimpleCueMenu( MidiCue, QT_TRANSLATE_NOOP("CueCategory", "Integration cues") ) diff --git a/lisp/plugins/network/discovery_dialogs.py b/lisp/plugins/network/discovery_dialogs.py index 610f7ffa1..74982ef58 100644 --- a/lisp/plugins/network/discovery_dialogs.py +++ b/lisp/plugins/network/discovery_dialogs.py @@ -84,7 +84,7 @@ def exec_(self): self.progressWheel.startAnimation() self._discoverer.start() - return super().exec_() + return super().exec() def hosts(self): return [item.text() for item in self.listWidget.selectedItems()] @@ -179,7 +179,7 @@ def discoverHosts(self): self.discovery_port, self.discovery_magic, parent=self ) - if dialog.exec_() == dialog.Accepted: + if dialog.exec() == dialog.Accepted: for host in dialog.hosts(): self.addHost(host) diff --git a/lisp/plugins/osc/osc.py b/lisp/plugins/osc/osc.py index 7be96ac53..538aa8e76 100644 --- a/lisp/plugins/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -41,7 +41,7 @@ def __init__(self, app): ) # Register the cue CueFactory.register_factory(OscCue.__name__, OscCue) - app.window.register_simple_cue_menu( + app.window.registerSimpleCueMenu( OscCue, QT_TRANSLATE_NOOP("CueCategory", "Integration cues") ) diff --git a/lisp/plugins/presets/lib.py b/lisp/plugins/presets/lib.py index 673bfead4..098a13794 100644 --- a/lisp/plugins/presets/lib.py +++ b/lisp/plugins/presets/lib.py @@ -19,11 +19,11 @@ import os from zipfile import ZipFile -from lisp import USER_DIRS +from lisp import app_dirs from lisp.core.actions_handler import MainActionsHandler from lisp.cues.cue_actions import UpdateCueAction, UpdateCuesAction -PRESETS_DIR = os.path.join(USER_DIRS.user_data_dir, "presets") +PRESETS_DIR = os.path.join(app_dirs.user_data_dir, "presets") def preset_path(name): diff --git a/lisp/plugins/presets/presets_ui.py b/lisp/plugins/presets/presets_ui.py index c89bbf636..fdf605d9f 100644 --- a/lisp/plugins/presets/presets_ui.py +++ b/lisp/plugins/presets/presets_ui.py @@ -245,7 +245,7 @@ def __remove_preset(self): def __add_preset(self): dialog = NewPresetDialog(parent=self) - if dialog.exec_() == QDialog.Accepted: + if dialog.exec() == QDialog.Accepted: preset_name = dialog.get_name() cue_type = dialog.get_type() @@ -290,7 +290,7 @@ def __edit_preset(self): edit_dialog = CueSettingsDialog(cue_class) edit_dialog.loadSettings(preset) - if edit_dialog.exec_() == edit_dialog.Accepted: + if edit_dialog.exec() == edit_dialog.Accepted: preset.update(edit_dialog.getSettings()) try: write_preset(item.text(), preset) diff --git a/lisp/plugins/rename_cues/rename_cues.py b/lisp/plugins/rename_cues/rename_cues.py index f236a1caf..3981717f0 100644 --- a/lisp/plugins/rename_cues/rename_cues.py +++ b/lisp/plugins/rename_cues/rename_cues.py @@ -54,7 +54,7 @@ def rename(self): # Initiate rename windows renameUi = RenameUi(self.app.window, selected_cues) - renameUi.exec_() + renameUi.exec() if renameUi.result() == QDialog.Accepted: MainActionsHandler.do_action( diff --git a/lisp/plugins/rename_cues/rename_ui.py b/lisp/plugins/rename_cues/rename_ui.py index efa140e39..82728eba9 100644 --- a/lisp/plugins/rename_cues/rename_ui.py +++ b/lisp/plugins/rename_cues/rename_ui.py @@ -248,7 +248,7 @@ def onHelpButtonClicked(self): "at: https://docs.python.org/3/howto/regex.html#regex-howto", ) ) - msg.exec_() + msg.exec() def onRegexLineChanged(self): pattern = self.regexLine.text() diff --git a/lisp/plugins/replay_gain/replay_gain.py b/lisp/plugins/replay_gain/replay_gain.py index ec7dab291..1bd177c6a 100644 --- a/lisp/plugins/replay_gain/replay_gain.py +++ b/lisp/plugins/replay_gain/replay_gain.py @@ -71,7 +71,7 @@ def __init__(self, app): def gain(self): gainUi = GainUi(self.app.window) - gainUi.exec_() + gainUi.exec() if gainUi.result() == QDialog.Accepted: if gainUi.only_selected(): diff --git a/lisp/plugins/synchronizer/synchronizer.py b/lisp/plugins/synchronizer/synchronizer.py index b795ff0ea..0b3ee0b39 100644 --- a/lisp/plugins/synchronizer/synchronizer.py +++ b/lisp/plugins/synchronizer/synchronizer.py @@ -70,7 +70,7 @@ def manage_peers(self): Synchronizer.Config["discovery.magic"], parent=self.app.window, ) - manager.exec_() + manager.exec() self.peers = manager.hosts() diff --git a/lisp/ui/layoutselect.py b/lisp/ui/layoutselect.py index 4c77db2a0..ad4007683 100644 --- a/lisp/ui/layoutselect.py +++ b/lisp/ui/layoutselect.py @@ -15,8 +15,6 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import os - from PyQt5.QtCore import Qt from PyQt5.QtWidgets import ( QDialog, @@ -24,21 +22,19 @@ QPushButton, QFrame, QTextBrowser, - QFileDialog, QGridLayout, ) from lisp import layout from lisp.ui.ui_utils import translate -from lisp.core.configuration import AppConfig class LayoutSelect(QDialog): - def __init__(self, **kwargs): + def __init__(self, application, **kwargs): super().__init__(**kwargs) - self.filepath = "" + self.application = application + self.sessionPath = "" - self.setWindowModality(Qt.WindowModal) self.setWindowTitle(translate("LayoutSelect", "Layout selection")) self.setMaximumSize(675, 300) self.setMinimumSize(675, 300) @@ -48,7 +44,7 @@ def __init__(self, **kwargs): self.layout().setContentsMargins(5, 5, 5, 5) self.layoutCombo = QComboBox(self) - self.layoutCombo.currentIndexChanged.connect(self.show_description) + self.layoutCombo.currentIndexChanged.connect(self.updateDescription) self.layout().addWidget(self.layoutCombo, 0, 0) self.confirmButton = QPushButton(self) @@ -71,44 +67,36 @@ def __init__(self, **kwargs): self.description = QTextBrowser(self) self.layout().addWidget(self.description, 2, 0, 1, 3) - for layout_class in layout.get_layouts(): - self.layoutCombo.addItem(layout_class.NAME, layout_class) + for layoutClass in layout.get_layouts(): + self.layoutCombo.addItem(layoutClass.NAME, layoutClass) if self.layoutCombo.count() == 0: raise Exception("No layout installed!") self.confirmButton.clicked.connect(self.accept) - self.fileButton.clicked.connect(self.open_file) + self.fileButton.clicked.connect(self.openSessionFile) def selected(self): return self.layoutCombo.currentData() - def show_description(self): - layout = self.layoutCombo.currentData() + def updateDescription(self): + layoutClass = self.layoutCombo.currentData() details = "

    " - for detail in layout.DETAILS: - details += "
  • " + translate("LayoutDetails", detail) + for detail in layoutClass.DETAILS: + details += "
  • {}
  • ".format(translate("LayoutDetails", detail)) details += "
" self.description.setHtml( "

{}

{}

{}".format( - layout.NAME, - translate("LayoutDescription", layout.DESCRIPTION), + layoutClass.NAME, + translate("LayoutDescription", layoutClass.DESCRIPTION), details, ) ) - def open_file(self): - app_config = AppConfig() - last_session_path = app_config.get( - "session.last_path", os.getenv("HOME") - ) - path, _ = QFileDialog.getOpenFileName( - self, filter="*.lsp", directory=last_session_path - ) - if path != "": - self.filepath = path - app_config.set("session.last_path", os.path.dirname(path)) - app_config.write() - self.accept() + def openSessionFile(self): + path = self.application.window.getOpenSessionFile() + if path is not None: + self.sessionPath = path + self.accepted() diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index bac3325b3..a47918e54 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -32,6 +32,7 @@ QVBoxLayout, QWidget, ) +from functools import partial from lisp.core.actions_handler import MainActionsHandler from lisp.core.singleton import QSingleton @@ -61,7 +62,7 @@ def __init__(self, conf, title="Linux Show Player", **kwargs): self.centralWidget().setLayout(QVBoxLayout()) self.centralWidget().layout().setContentsMargins(5, 5, 5, 5) - self._cue_add_menu = {} + self.__cueSubMenus = {} self._title = title self.conf = conf @@ -71,9 +72,9 @@ def __init__(self, conf, title="Linux Show Player", **kwargs): self.setStatusBar(QStatusBar(self)) # Changes - MainActionsHandler.action_done.connect(self._action_done) - MainActionsHandler.action_undone.connect(self._action_undone) - MainActionsHandler.action_redone.connect(self._action_redone) + MainActionsHandler.action_done.connect(self.updateWindowTitle) + MainActionsHandler.action_undone.connect(self.updateWindowTitle) + MainActionsHandler.action_redone.connect(self.updateWindowTitle) # Menubar self.menubar = QMenuBar(self) @@ -96,20 +97,20 @@ def __init__(self, conf, title="Linux Show Player", **kwargs): # menuFile self.newSessionAction = QAction(self) - self.newSessionAction.triggered.connect(self._new_session) + self.newSessionAction.triggered.connect(self.__newSession) self.openSessionAction = QAction(self) - self.openSessionAction.triggered.connect(self._load_from_file) + self.openSessionAction.triggered.connect(self.__openSession) self.saveSessionAction = QAction(self) - self.saveSessionAction.triggered.connect(self._save) + self.saveSessionAction.triggered.connect(self.__saveSession) self.saveSessionWithName = QAction(self) - self.saveSessionWithName.triggered.connect(self._save_with_name) + self.saveSessionWithName.triggered.connect(self.__saveWithName) self.editPreferences = QAction(self) - self.editPreferences.triggered.connect(self._show_preferences) + self.editPreferences.triggered.connect(self.__onEditPreferences) self.fullScreenAction = QAction(self) - self.fullScreenAction.triggered.connect(self._fullscreen) + self.fullScreenAction.triggered.connect(self.setFullScreen) self.fullScreenAction.setCheckable(True) self.exitAction = QAction(self) - self.exitAction.triggered.connect(self._exit) + self.exitAction.triggered.connect(self.close) self.menuFile.addAction(self.newSessionAction) self.menuFile.addAction(self.openSessionAction) @@ -129,15 +130,15 @@ def __init__(self, conf, title="Linux Show Player", **kwargs): self.actionRedo = QAction(self) self.actionRedo.triggered.connect(MainActionsHandler.redo_action) self.multiEdit = QAction(self) - self.multiEdit.triggered.connect(self._edit_selected_cue) + self.multiEdit.triggered.connect(self.__editSelectedCues) self.selectAll = QAction(self) - self.selectAll.triggered.connect(self._select_all) + self.selectAll.triggered.connect(self.__layoutSelectAll) self.selectAllMedia = QAction(self) - self.selectAllMedia.triggered.connect(self._select_all_media) + self.selectAllMedia.triggered.connect(self.__layoutSelectAllMediaCues) self.deselectAll = QAction(self) - self.deselectAll.triggered.connect(self._deselect_all) + self.deselectAll.triggered.connect(self._layoutDeselectAll) self.invertSelection = QAction(self) - self.invertSelection.triggered.connect(self._invert_selection) + self.invertSelection.triggered.connect(self.__layoutInvertSelection) self.cueSeparator = self.menuEdit.addSeparator() self.menuEdit.addAction(self.actionUndo) @@ -152,7 +153,7 @@ def __init__(self, conf, title="Linux Show Player", **kwargs): # menuAbout self.actionAbout = QAction(self) - self.actionAbout.triggered.connect(self._show_about) + self.actionAbout.triggered.connect(self.__about) self.actionAbout_Qt = QAction(self) self.actionAbout_Qt.triggered.connect(qApp.aboutQt) @@ -230,7 +231,7 @@ def retranslateUi(self): self.actionAbout.setText(translate("MainWindow", "About")) self.actionAbout_Qt.setText(translate("MainWindow", "About Qt")) - def set_session(self, session): + def setSession(self, session): if self.session is not None: self.centralWidget().layout().removeWidget( self.session.layout.view() @@ -243,10 +244,13 @@ def set_session(self, session): self.centralWidget().layout().addWidget(self.session.layout.view()) def closeEvent(self, event): - self._exit() - event.ignore() + if self.__checkSessionSaved(): + qApp.quit() + event.accept() + else: + event.ignore() - def register_cue_menu(self, name, function, category="", shortcut=""): + def registerCueMenu(self, name, function, category="", shortcut=""): """Register a new-cue choice for the edit-menu param name: The name for the MenuAction @@ -261,13 +265,13 @@ def register_cue_menu(self, name, function, category="", shortcut=""): if shortcut != "": action.setShortcut(translate("CueCategory", shortcut)) - if category != "": - if category not in self._cue_add_menu: - menu = QMenu(category, self) - self._cue_add_menu[category] = menu - self.menuEdit.insertMenu(self.cueSeparator, menu) + if category: + if category not in self.__cueSubMenus: + subMenu = QMenu(category, self) + self.__cueSubMenus[category] = subMenu + self.menuEdit.insertMenu(self.cueSeparator, subMenu) - self._cue_add_menu[category].addAction(action) + self.__cueSubMenus[category].addAction(action) else: self.menuEdit.insertAction(self.cueSeparator, action) @@ -277,132 +281,141 @@ def register_cue_menu(self, name, function, category="", shortcut=""): ) ) - def register_simple_cue_menu(self, cue_class, cue_category=""): - def menu_function(): - try: - cue = CueFactory.create_cue(cue_class.__name__) - - # Get the (last) index of the current selection - layout_selection = list(self.session.layout.selected_cues()) - if layout_selection: - cue.index = layout_selection[-1].index + 1 - - self.session.cue_model.add(cue) - except Exception: - logger.exception( - translate("MainWindowError", "Cannot create cue {}").format( - cue_class.__name__ - ) - ) - - self.register_cue_menu( - translate("CueName", cue_class.Name), - menu_function, - cue_category or translate("CueCategory", "Misc cues"), + def registerSimpleCueMenu(self, cueClass, category=""): + self.registerCueMenu( + translate("CueName", cueClass.Name), + partial(self.__simpleCueInsert, cueClass), + category or translate("CueCategory", "Misc cues"), ) - def update_window_title(self): + def updateWindowTitle(self): tile = self._title + " - " + self.session.name() if not MainActionsHandler.is_saved(): tile = "*" + tile self.setWindowTitle(tile) - def _action_done(self): - self.update_window_title() + def getOpenSessionFile(self): + path, _ = QFileDialog.getOpenFileName( + self, + filter="*.lsp", + directory=self.conf.get("session.last_dir", os.getenv("HOME")), + ) - def _action_undone(self): - self.update_window_title() + if os.path.exists(path): + self.conf.set("session.last_dir", os.path.dirname(path)) + self.conf.write() - def _action_redone(self): - self.update_window_title() + return path - def _save(self): - if self.session.session_file == "": - return self._save_with_name() + def getSaveSessionFile(self): + if self.session.session_file: + session_dir = self.session.dir() else: - self.save_session.emit(self.session.session_file) - return True + session_dir = self.conf.get("session.last_dir", os.getenv("HOME")) - def _save_with_name(self): - filename, ok = QFileDialog.getSaveFileName( - parent=self, filter="*.lsp", directory=self.session.path() + filename, accepted = QFileDialog.getSaveFileName( + parent=self, filter="*.lsp", directory=session_dir ) - if ok: + if accepted: if not filename.endswith(".lsp"): filename += ".lsp" - self.save_session.emit(filename) - return True + return filename + + def setFullScreen(self, enable): + if enable: + self.showFullScreen() else: - return False + self.showMaximized() - def _show_preferences(self): - prefUi = AppConfigurationDialog(parent=self) - prefUi.exec_() + def __simpleCueInsert(self, cueClass): + try: + cue = CueFactory.create_cue(cueClass.__name__) - def _load_from_file(self): - if self._check_saved(): - path, _ = QFileDialog.getOpenFileName( - self, filter="*.lsp", directory=os.getenv("HOME") + # Get the (last) index of the current selection + layout_selection = list(self.session.layout.selected_cues()) + if layout_selection: + cue.index = layout_selection[-1].index + 1 + + self.session.cue_model.add(cue) + except Exception: + logger.exception( + translate("MainWindowError", "Cannot create cue {}").format( + cueClass.__name__ + ) ) - if os.path.exists(path): - self.open_session.emit(path) + def __onEditPreferences(self): + prefUi = AppConfigurationDialog(parent=self) + prefUi.exec() - def _new_session(self): - if self._check_saved(): - self.new_session.emit() + def __editSelectedCues(self): + self.session.layout.edit_cues(list(self.session.layout.selected_cues())) - def _check_saved(self): - if not MainActionsHandler.is_saved(): - msgBox = QMessageBox(self) - msgBox.setIcon(QMessageBox.Warning) - msgBox.setWindowTitle(translate("MainWindow", "Close session")) - msgBox.setText( - translate("MainWindow", "The current session is not saved.") - ) - msgBox.setInformativeText( - translate("MainWindow", "Discard the changes?") - ) - msgBox.setStandardButtons( - QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel - ) - msgBox.setDefaultButton(QMessageBox.Save) + def __layoutSelectAll(self): + self.session.layout.select_all() - result = msgBox.exec_() - if result == QMessageBox.Cancel: - return False - elif result == QMessageBox.Save: - return self._save() + def __layoutInvertSelection(self): + self.session.layout.invert_selection() - return True + def _layoutDeselectAll(self): + self.session.layout.deselect_all() - def _fullscreen(self, enable): - if enable: - self.showFullScreen() + def __layoutSelectAllMediaCues(self): + self.session.layout.select_all(cue_class=MediaCue) + + def __saveSession(self): + if self.session.session_file: + self.save_session.emit(self.session.session_file) + return True else: - self.showMaximized() + return self.__saveWithName() - def _show_about(self): - About(self).show() + def __saveWithName(self): + path = self.getSaveSessionFile() - def _exit(self): - if self._check_saved(): - qApp.quit() + if path is not None: + self.save_session.emit(path) + return True - def _edit_selected_cue(self): - self.session.layout.edit_cues(list(self.session.layout.selected_cues())) + return False - def _select_all(self): - self.session.layout.select_all() + def __openSession(self): + path = self.getOpenSessionFile() - def _invert_selection(self): - self.session.layout.invert_selection() + if path is not None: + self.open_session.emit(path) - def _deselect_all(self): - self.session.layout.deselect_all() + def __newSession(self): + if self.__checkSessionSaved(): + self.new_session.emit() - def _select_all_media(self): - self.session.layout.select_all(cue_class=MediaCue) + def __checkSessionSaved(self): + if not MainActionsHandler.is_saved(): + saveMessageBox = QMessageBox( + QMessageBox.Warning, + translate("MainWindow", "Close session"), + translate( + "MainWindow", + "The current session contains changes that have not been saved.", + ), + QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel, + self + ) + saveMessageBox.setInformativeText( + translate("MainWindow", "Do you want to save them now?") + ) + saveMessageBox.setDefaultButton(QMessageBox.Save) + + choice = saveMessageBox.exec() + if choice == QMessageBox.Save: + return self.__saveSession() + elif choice == QMessageBox.Cancel: + return False + + return True + + def __about(self): + About(self).show() diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index e569e6ef4..f6354c741 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -271,7 +271,7 @@ def _text(self, painter, option, index): def editorEvent(self, event, model, option, index): if event.type() == QEvent.MouseButtonDblClick: - if self.cue_select.exec_() == QDialog.Accepted: + if self.cue_select.exec() == QDialog.Accepted: cue = self.cue_select.selected_cue() if cue is not None: model.setData(index, cue.id, Qt.EditRole) diff --git a/lisp/ui/ui_utils.py b/lisp/ui/ui_utils.py index 7ad1ddfea..539a2e832 100644 --- a/lisp/ui/ui_utils.py +++ b/lisp/ui/ui_utils.py @@ -14,16 +14,13 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import fcntl - -import os -import logging +import fcntl from itertools import chain +import logging +import os import signal -from os import path - from PyQt5.QtCore import QTranslator, QLocale, QSocketNotifier from PyQt5.QtWidgets import QApplication, qApp @@ -35,7 +32,7 @@ def adjust_widget_position(widget): """Adjust the widget position to ensure it's in the desktop. - :type widget: PyQt5.QtWidgets.QWidget + :type widget: PyQt5.QtWidgets.QWidget.QWidget """ widget.setGeometry(adjust_position(widget.geometry())) @@ -43,8 +40,8 @@ def adjust_widget_position(widget): def adjust_position(rect): """Adjust the given rect to ensure it's in the desktop space. - :type widget: PyQt5.QtCore.QRect - :return: PyQt5.QtCore.QRect + :type rect: PyQt5.QtCore.QRect.QRect + :return: PyQt5.QtCore.QRect.QRect """ desktop = qApp.desktop().availableGeometry() @@ -103,7 +100,7 @@ def install_translation(name, tr_path=I18N_PATH): 'Installed translation for "{}" from {}'.format(name, tr_path) ) else: - logger.debug('No translation "{}" in {}'.format(name, tr_path)) + logger.debug('No translation for "{}" in {}'.format(name, tr_path)) def translate(context, text, disambiguation=None, n=-1): diff --git a/lisp/ui/widgets/qcolorbutton.py b/lisp/ui/widgets/qcolorbutton.py index bfed84a76..6c323e272 100644 --- a/lisp/ui/widgets/qcolorbutton.py +++ b/lisp/ui/widgets/qcolorbutton.py @@ -58,7 +58,7 @@ def onColorPicker(self): if self._color is not None: dlg.setCurrentColor(QColor(self._color)) - if dlg.exec_() == dlg.Accepted: + if dlg.exec() == dlg.Accepted: self.setColor(dlg.currentColor().name()) def mousePressEvent(self, e): diff --git a/lisp/ui/widgets/qmessagebox.py b/lisp/ui/widgets/qmessagebox.py index c1e525113..478e0560e 100644 --- a/lisp/ui/widgets/qmessagebox.py +++ b/lisp/ui/widgets/qmessagebox.py @@ -56,4 +56,4 @@ def dgeneric(title, text, detail_text, icon, parent=None): messageBox.addButton(QMessageBox.Ok) messageBox.setDefaultButton(QMessageBox.Ok) - messageBox.exec_() + messageBox.exec() From b21a992b2f6530d16c45d6f5ec5f98ae5e325a78 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 26 Feb 2019 22:45:06 +0100 Subject: [PATCH 170/333] Minor fixes and improvements --- lisp/core/actions_handler.py | 10 +++------- lisp/core/decorators.py | 2 +- lisp/core/memento_model.py | 15 ++++++++++----- lisp/core/memento_model_actions.py | 10 ++-------- lisp/core/proxy_model.py | 1 + lisp/cues/cue_model.py | 2 +- lisp/ui/mainwindow.py | 2 +- 7 files changed, 19 insertions(+), 23 deletions(-) diff --git a/lisp/core/actions_handler.py b/lisp/core/actions_handler.py index d67b2d351..97f0eacd8 100644 --- a/lisp/core/actions_handler.py +++ b/lisp/core/actions_handler.py @@ -32,19 +32,15 @@ class ActionsHandler: UNDO_ACTION_STR = translate("Actions", "Undo: {}") REDO_ACTION_STR = translate("Actions", "Redo: {}") - def __init__(self, stack_size=-1): + def __init__(self, stack_size=None): super().__init__() self.action_done = Signal() self.action_undone = Signal() self.action_redone = Signal() - self._undo = deque() - self._redo = deque() - - if stack_size > 0: - self._undo.maxlen = stack_size - self._redo.maxlen = stack_size + self._undo = deque(maxlen=stack_size) + self._redo = deque(maxlen=stack_size) self._saved_action = None diff --git a/lisp/core/decorators.py b/lisp/core/decorators.py index dc2d5a0c4..a70cdc2d6 100644 --- a/lisp/core/decorators.py +++ b/lisp/core/decorators.py @@ -98,7 +98,7 @@ def locked(*args, **kwargs): def locked_method(target=None, *, blocking=True, timeout=-1): - """Decorator. Make a *method* synchronized. + """Decorator. Make a *method* "synchronized". Only one thread at time can access the decorated method. diff --git a/lisp/core/memento_model.py b/lisp/core/memento_model.py index 5d0517f14..666bba686 100644 --- a/lisp/core/memento_model.py +++ b/lisp/core/memento_model.py @@ -15,6 +15,8 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +from contextlib import contextmanager + from lisp.core.actions_handler import MainActionsHandler from lisp.core.memento_model_actions import ( AddItemAction, @@ -55,14 +57,17 @@ def _item_removed(self, item): if not self._locked: self._handler.do_action(self._remove_action(self, self.model, item)) + def _model_reset(self): + """Reset cannot be reverted""" + + @contextmanager def lock(self): self._locked = True - def unlock(self): - self._locked = False - - def _model_reset(self): - """Reset cannot be reverted""" + try: + yield self + finally: + self._locked = False class MementoModelAdapter(MementoModel): diff --git a/lisp/core/memento_model_actions.py b/lisp/core/memento_model_actions.py index 6cfe198dd..afb738864 100644 --- a/lisp/core/memento_model_actions.py +++ b/lisp/core/memento_model_actions.py @@ -34,18 +34,12 @@ def do(self): pass def undo(self): - try: - self._m_model.lock() + with self._m_model.lock(): self.__undo__() - finally: - self._m_model.unlock() def redo(self): - try: - self._m_model.lock() + with self._m_model.lock(): self.__redo__() - finally: - self._m_model.unlock() @abstractmethod def __undo__(self): diff --git a/lisp/core/proxy_model.py b/lisp/core/proxy_model.py index cb54347d1..56b10d725 100644 --- a/lisp/core/proxy_model.py +++ b/lisp/core/proxy_model.py @@ -18,6 +18,7 @@ from abc import abstractmethod from lisp.core.model import Model, ModelException +from lisp.core.util import typename class ABCProxyModel(Model): diff --git a/lisp/cues/cue_model.py b/lisp/cues/cue_model.py index f5337f773..733569654 100644 --- a/lisp/cues/cue_model.py +++ b/lisp/cues/cue_model.py @@ -32,7 +32,7 @@ def __init__(self): def add(self, cue): if cue.id in self.__cues: - raise ValueError("the cue is already in the layout") + raise ValueError("the cue is already in the model") self.__cues[cue.id] = cue self.item_added.emit(cue) diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index a47918e54..112f6fee2 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -402,7 +402,7 @@ def __checkSessionSaved(self): "The current session contains changes that have not been saved.", ), QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel, - self + self, ) saveMessageBox.setInformativeText( translate("MainWindow", "Do you want to save them now?") From fef13d7b0d7b691d075bd462b1ff3bc066518032 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 27 Feb 2019 18:16:08 +0100 Subject: [PATCH 171/333] Various fixes Fix: bug introduced by commit f8b000d83307bdde864c96f76658108f1ffbd9b6 Fix: `MainWindow` size is set to desktop().availableGeometry() on startup Minor changes --- lisp/core/memento_model.py | 2 +- lisp/core/proxy_model.py | 8 ++++---- lisp/cues/cue.py | 2 +- lisp/ui/layoutselect.py | 2 +- lisp/ui/mainwindow.py | 1 + 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lisp/core/memento_model.py b/lisp/core/memento_model.py index 666bba686..338d2fee6 100644 --- a/lisp/core/memento_model.py +++ b/lisp/core/memento_model.py @@ -71,7 +71,7 @@ def lock(self): class MementoModelAdapter(MementoModel): - """Extension of the MementoModel that use a ModelAdapter as a base-model""" + """Extension of the MementoModel that can handle ModelAdapter(s).""" def __init__(self, model_adapter, handler=None): super().__init__(model_adapter, handler) diff --git a/lisp/core/proxy_model.py b/lisp/core/proxy_model.py index 56b10d725..23eb4d4b1 100644 --- a/lisp/core/proxy_model.py +++ b/lisp/core/proxy_model.py @@ -55,13 +55,13 @@ def _item_removed(self, item): class ProxyModel(ABCProxyModel): - """Proxy that wrap a more generic model to extend its functionality. + """Proxy that wrap another model to extend its functionality. - The add, remove, __iter__, __len__ and __contains__ default implementations - use the the model ones. + The default implementations of `add`, `remove`, `__iter__`, `__len__` and + `__contains__` fallback on the wrapped model. .. note: - The base model cannot be changed. + The wrapped model should not be changed. Any ProxyModel could provide it's own methods/signals. """ diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index b8f5bd264..6e9d342c7 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -190,7 +190,7 @@ def execute(self, action=CueAction.Default): if action == CueAction.DoNothing: return - if action == self.CueActions: + if action in self.CueActions: if action == CueAction.Interrupt: self.interrupt() elif action == CueAction.FadeOutInterrupt: diff --git a/lisp/ui/layoutselect.py b/lisp/ui/layoutselect.py index ad4007683..25ae03e42 100644 --- a/lisp/ui/layoutselect.py +++ b/lisp/ui/layoutselect.py @@ -99,4 +99,4 @@ def openSessionFile(self): path = self.application.window.getOpenSessionFile() if path is not None: self.sessionPath = path - self.accepted() + self.accept() diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 112f6fee2..dd0f37f4d 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -58,6 +58,7 @@ class MainWindow(QMainWindow, metaclass=QSingleton): def __init__(self, conf, title="Linux Show Player", **kwargs): super().__init__(**kwargs) self.setMinimumSize(500, 400) + self.setGeometry(qApp.desktop().availableGeometry(self)) self.setCentralWidget(QWidget()) self.centralWidget().setLayout(QVBoxLayout()) self.centralWidget().layout().setContentsMargins(5, 5, 5, 5) From 6190f322d831f10866013f8b4d4dcf8b7ee2b040 Mon Sep 17 00:00:00 2001 From: s0600204 <2053873+s0600204@users.noreply.github.com> Date: Tue, 26 Mar 2019 22:08:54 +0000 Subject: [PATCH 172/333] Correct the indexAt method (#169) It was incorrectly adding an additional six pixels to the height and width to each cue widget when calculating their expected height. This was causing an ever increasing numerical error causing: * The wrong cue to be selected for edits * Application crashes when a cue context menu was requested, but no cue existed at the grid-id returned by `indexAt` Also tidy up the function (floor instead of ceil then - 1) --- lisp/plugins/cart_layout/page_widget.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lisp/plugins/cart_layout/page_widget.py b/lisp/plugins/cart_layout/page_widget.py index 72f7d6358..f63b989e0 100644 --- a/lisp/plugins/cart_layout/page_widget.py +++ b/lisp/plugins/cart_layout/page_widget.py @@ -112,15 +112,14 @@ def dropEvent(self, event): self.copyWidgetRequested.emit(event.source(), row, column) def indexAt(self, pos): - # Margins and spacings are equals - space = self.layout().horizontalSpacing() + # All four margins (left, right, top, bottom) of a cue widget are equal margin = self.layout().contentsMargins().right() - r_size = (self.height() + margin * 2) // self.__rows + space - c_size = (self.width() + margin * 2) // self.__columns + space + r_size = (self.height() + margin * 2) // self.__rows + c_size = (self.width() + margin * 2) // self.__columns - row = math.ceil(pos.y() / r_size) - 1 - column = math.ceil(pos.x() / c_size) - 1 + row = math.floor(pos.y() / r_size) + column = math.floor(pos.x() / c_size) return row, column From 402cfba391e5a619c51e70f85b7ccb03b9583074 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 27 Mar 2019 00:31:42 +0100 Subject: [PATCH 173/333] Fix #167 (Removing a page in Cart Layout has unintended side-effects) --- Pipfile.lock | 172 ++++++++++++++--------------- lisp/plugins/cart_layout/layout.py | 13 +-- lisp/plugins/cart_layout/model.py | 10 +- 3 files changed, 92 insertions(+), 103 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 7f4a23904..28f5b1657 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -24,47 +24,43 @@ }, "certifi": { "hashes": [ - "sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7", - "sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033" + "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", + "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" ], - "version": "==2018.11.29" + "version": "==2019.3.9" }, "cffi": { "hashes": [ - "sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743", - "sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef", - "sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50", - "sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f", - "sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30", - "sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93", - "sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257", - "sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b", - "sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3", - "sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e", - "sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc", - "sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04", - "sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6", - "sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359", - "sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596", - "sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b", - "sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd", - "sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95", - "sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5", - "sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e", - "sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6", - "sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca", - "sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31", - "sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1", - "sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2", - "sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085", - "sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801", - "sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4", - "sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184", - "sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917", - "sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f", - "sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb" + "sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f", + "sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11", + "sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d", + "sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891", + "sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf", + "sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c", + "sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed", + "sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b", + "sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a", + "sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585", + "sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea", + "sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f", + "sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33", + "sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145", + "sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a", + "sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3", + "sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f", + "sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd", + "sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804", + "sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d", + "sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92", + "sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f", + "sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84", + "sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb", + "sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7", + "sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7", + "sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35", + "sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889" ], - "version": "==1.11.5" + "version": "==1.12.2" }, "chardet": { "hashes": [ @@ -75,37 +71,37 @@ }, "cython": { "hashes": [ - "sha256:1327655db47beb665961d3dc0365e20c9e8e80c234513ab2c7d06ec0dd9d63eb", - "sha256:142400f13102403f43576bb92d808a668e29deda5625388cfa39fe0bcf37b3d1", - "sha256:1b4204715141281a631337378f0c15fe660b35e1b6888ca05f1f3f49df3b97d5", - "sha256:23aabaaf8887e6db99df2145de6742f8c92830134735778bf2ae26338f2b406f", - "sha256:2a724c6f21fdf4e3c1e8c5c862ff87f5420fdaecf53a5a0417915e483d90217f", - "sha256:2c9c8c1c6e8bd3587e5f5db6f865a42195ff2dedcaf5cdb63fdea10c98bd6246", - "sha256:3a1be38b774423605189d60652b3d8a324fc81d213f96569720c8093784245ab", - "sha256:46be5297a76513e4d5d6e746737d4866a762cfe457e57d7c54baa7ef8fea7e9a", - "sha256:48dc2ea4c4d3f34ddcad5bc71b1f1cf49830f868832d3e5df803c811e7395b6e", - "sha256:53f33e04d2ed078ac02841741bcd536b546e1f416608084468ab30a87638a466", - "sha256:57b10588618ca19a4cc870f381aa8805bc5fe0c62d19d7f940232ff8a373887c", - "sha256:6001038341b52301450bb9c62e5d5da825788944572679277e137ffb3596e718", - "sha256:70bef52e735607060f327d729be35c820d9018d260a875e4f98b20ba8c4fff96", - "sha256:7d0f76b251699be8f1f1064dcb12d4b3b2b676ce15ff30c104e0c2091a015142", - "sha256:9440b64c1569c26a184b7c778bb221cf9987c5c8486d32cda02302c66ea78980", - "sha256:956cc97eac6f9d3b16e3b2d2a94c5586af3403ba97945e9d88a4a0f029899646", - "sha256:ae430ad8cce937e07ea566d1d7899eef1fedc8ec512b4d5fa37ebf2c1f879936", - "sha256:bdb575149881978d62167dd8427402a5872a79bd83e9d51219680670e9f80b40", - "sha256:c0ffcddd3dbdf22aae3980931112cc8b2732315a6273988f3205cf5dacf36f45", - "sha256:c133e2efc57426974366ac74f2ef0f1171b860301ac27f72316deacff4ccdc17", - "sha256:c6e9521d0b77eb1da89e8264eb98c8f5cda7c49a49b8128acfd35f0ca50e56d0", - "sha256:c7cac0220ecb733024e8acfcfb6b593a007185690f2ea470d2392b72510b7187", - "sha256:d53483820ac28f2be2ff13eedd56c0f36a4c583727b551d3d468023556e2336a", - "sha256:d60210784186d61e0ec808d5dbee5d661c7457a57f93cb5fdc456394607ce98c", - "sha256:d687fb1cd9df28c1515666174c62e54bd894a6a6d0862f89705063cd47739f83", - "sha256:d926764d9c768a48b0a16a91696aaa25498057e060934f968fa4c5629b942d85", - "sha256:d94a2f4ad74732f58d1c771fc5d90a62c4fe4c98d0adfecbc76cd0d8d14bf044", - "sha256:def76a546eeec059666f5f4117dfdf9c78e50fa1f95bdd23b04618c7adf845cd" + "sha256:050b082accb95c0a3a6a9dd1ec6fb0bea5321f5b894c8fd0dc6c3abebb68822b", + "sha256:22e2b55baaa2c7d5aee0376fd1cca39a6036598b9423d8ecd44c514fde1952c3", + "sha256:2be438190b1acce073f3aa4220fdbefb7fc910204dc0a82ef725802195e58dfb", + "sha256:2da461af4845cf858f6ef366e74093917cce731f70e95e03b24e9c8e66d62e6d", + "sha256:46e9d20bd1a7431ac58f95d0b8e5ec14d33363b49bfa1a6245621037fb5bbd49", + "sha256:50a735fccd946217d1dd06a06ac16a582a1a1169c16588e234284b12d7d08d3f", + "sha256:63ddf6ea98dff4dc1d3567412015dbc21dbb728323b2f93ba7bc5994db28bbe2", + "sha256:6976bc08d6a6d1ea53564df6d9ab0bf1fa3e073556e2520931dffa5c3d68b5c3", + "sha256:6c5d33f1b5c864382fbce810a8fd9e015447869ae42e98e6301e977b8165e7ae", + "sha256:6db2fa279e235a9feef96833eb34be2a807add214a253e5e92e99b4b0e6659c2", + "sha256:71e44d9a85dbba538e5c2d0eb8b09b2233b0938010345d5e5bae428b429e6c47", + "sha256:750d9bb4aad23ddd61061d7a4e271bb39df1886c90861faf0b21c9219fd1c2c3", + "sha256:7a262e2e2807e05cacef76c1333bb5b5f46d882d1a6eca1a38c65a0c29df2a64", + "sha256:7f65d40728c6170f7f5a8a9329ba65568069b08581d899c44efb80c7d751524a", + "sha256:8002f39d504120ff0125783dd00464806c6d77519c63f7899c1781d68d7a7887", + "sha256:8cdf96a9ae2201a6423252dfe7a0d7121fdd451f9c81abd276d7112ad563f9fb", + "sha256:a3ea96c1fb1f80b3216f6300067dba85a6c03e4070ef8c4a1a150618d358c265", + "sha256:a8689c538ac7c3162df90f2b141aa9a210798485b8f6f72a31dedab60d3d06f7", + "sha256:aee85f89c22d3d88e958050834c9d16ec6ce9c9b1ce6042ae6e7a2125c2dc3a9", + "sha256:b05ad60e8b8750d3ce254ca3bd45bc222b8c9b7671affbda9bec36a36a24a484", + "sha256:b1ef8bd48a3ff6743c2facb09b666ffa78414cc12f971a21b6f977ffd1968d26", + "sha256:b890e55e7557d1a8a4e4defe082fdbabf241ac4a0fb38608e6ae50644dcc7d76", + "sha256:bddf0db28eca8e650ec120e9d8724646b811c517eae9aa688253abc757547881", + "sha256:be43609c12c85d2e3fefa4c0f7b6197431fdf7ea804e11a419709dc7df46d9ea", + "sha256:be69dc0c85cfc78e808fb22753ab2a99bdbd124702b21141a77da50f2fdae00e", + "sha256:d404cfa5e8595e8c0f81ff19d06a0dcfefcd9a3c8e2bf973985a6cc48731879b", + "sha256:df0696840e4da454475b83bbe0f8f4f8d90b75e68bfff7bfbe5b97cecc50a1df", + "sha256:f7cb71cafe05980404704045110f45c09143ed41dabadb91061c063dd543b924" ], "index": "pypi", - "version": "==0.29.3" + "version": "==0.29.6" }, "falcon": { "hashes": [ @@ -124,11 +120,11 @@ }, "jack-client": { "hashes": [ - "sha256:7b2fe976775e38324412b26a3d7111f641dbdb596cb594b080a39a6e374377fc", - "sha256:8d7d8b4b505528cf29aa2a25793167318857d7c576d6db1a9be0f3e611e4b37a" + "sha256:10078565681fa1fb807fb171b3666c17e7c827d572f0cfb28ed2c425918e5499", + "sha256:36cfa279ec4eb37aa6ecf0feed3e2419c38d9963e61c570d71cac43208402968" ], "index": "pypi", - "version": "==0.4.5" + "version": "==0.4.6" }, "mido": { "hashes": [ @@ -152,10 +148,10 @@ }, "pygobject": { "hashes": [ - "sha256:2d4423cbf169d50a3165f2dde21478a00215c7fb3f68d9d236af588c670980bb" + "sha256:a4d6f6d8e688d28e07119fc83aabc6e7aad02057f48b2194089959d03654f695" ], "index": "pypi", - "version": "==3.30.4" + "version": "==3.32.0" }, "pyliblo": { "hashes": [ @@ -166,30 +162,30 @@ }, "pyqt5": { "hashes": [ - "sha256:517e4339135c4874b799af0d484bc2e8c27b54850113a68eec40a0b56534f450", - "sha256:ac1eb5a114b6e7788e8be378be41c5e54b17d5158994504e85e43b5fca006a39", - "sha256:d2309296a5a79d0a1c0e6c387c30f0398b65523a6dcc8a19cc172e46b949e00d", - "sha256:e85936bae1581bcb908847d2038e5b34237a5e6acc03130099a78930770e7ead" + "sha256:020e8771ad66429587d3db6cceb028be3435e2c65f417dcc8e3b3e644b6ab1d7", + "sha256:399fd9c3b3786210d9d2a86bc73255cc03c997b454480b7c0daf3e1a09e1ab58", + "sha256:c0565737d1d5fd40a7e14b6692bfec1f1e1f0be082ae70d1776c44561980e3c4", + "sha256:d4e88208dfd017636e4b1f407990d0c3b6cf47afed6be4f2fb6ca887ef513e4b" ], "index": "pypi", - "version": "==5.11.3" + "version": "==5.12.1" }, "pyqt5-sip": { "hashes": [ - "sha256:125f77c087572c9272219cda030a63c2f996b8507592b2a54d7ef9b75f9f054d", - "sha256:14c37b06e3fb7c2234cb208fa461ec4e62b4ba6d8b32ca3753c0b2cfd61b00e3", - "sha256:1cb2cf52979f9085fc0eab7e0b2438eb4430d4aea8edec89762527e17317175b", - "sha256:4babef08bccbf223ec34464e1ed0a23caeaeea390ca9a3529227d9a57f0d6ee4", - "sha256:53cb9c1208511cda0b9ed11cffee992a5a2f5d96eb88722569b2ce65ecf6b960", - "sha256:549449d9461d6c665cbe8af4a3808805c5e6e037cd2ce4fd93308d44a049bfac", - "sha256:5f5b3089b200ff33de3f636b398e7199b57a6b5c1bb724bdb884580a072a14b5", - "sha256:a4d9bf6e1fa2dd6e73f1873f1a47cee11a6ba0cf9ba8cf7002b28c76823600d0", - "sha256:a4ee6026216f1fbe25c8847f9e0fbce907df5b908f84816e21af16ec7666e6fe", - "sha256:a91a308a5e0cc99de1e97afd8f09f46dd7ca20cfaa5890ef254113eebaa1adff", - "sha256:b0342540da479d2713edc68fb21f307473f68da896ad5c04215dae97630e0069", - "sha256:f997e21b4e26a3397cb7b255b8d1db5b9772c8e0c94b6d870a5a0ab5c27eacaa" + "sha256:0aed4266e52f4d11947439d9df8c57d4578e92258ad0d70645f9f69b627b35c7", + "sha256:15750a4a3718c68ba662eede7310612aedad70c9727497908bf2eeddd255d16f", + "sha256:2071265b7a603354b624b6cfae4ea032719e341799fb925961fd192f328fd203", + "sha256:31a59f76168df007b480b9382256c20f8898c642e1394df2990559f0f6389f66", + "sha256:39ce455bf5be5c1b20db10d840a536f70c3454be3baa7adca22f67ed90507853", + "sha256:5d8a4b8e75fa6c9f837ea92793f1aeacbf3929f1d11cdf36f9604c1876182aca", + "sha256:6bf9bacac776257390dd3f8a703d12c8228e4eb1aa0b191f1550965f44e4c23a", + "sha256:87a76841e07e6dae5df34dded427708b5ea8d605c017e0bb6e56a7354959271e", + "sha256:9f0ad4198f2657da9d59a393c266959362c517445cace842051e5ad564fa8fb0", + "sha256:cbb0f24e3bfa1403bf3a05f30066d4457509aa411d6c6823d02b0508b787b8ea", + "sha256:d38d379fa4f5c86a8ea92b8fab42748f37de093d21a25c34b2339eb2764ee5d5", + "sha256:f24413d34707525a0fe1448b9574089242b9baa7c604ae5714abef9ad1749839" ], - "version": "==4.19.13" + "version": "==4.19.15" }, "python-mimeparse": { "hashes": [ diff --git a/lisp/plugins/cart_layout/layout.py b/lisp/plugins/cart_layout/layout.py index 1708902be..841395804 100644 --- a/lisp/plugins/cart_layout/layout.py +++ b/lisp/plugins/cart_layout/layout.py @@ -75,15 +75,9 @@ def __init__(self, application): self.cue_model, self.__rows, self.__columns ) # TODO: move this logic in CartTabWidget ? - self._cart_model.item_added.connect( - self.__cue_added, Connection.QtQueued - ) - self._cart_model.item_removed.connect( - self.__cue_removed, Connection.QtQueued - ) - self._cart_model.item_moved.connect( - self.__cue_moved, Connection.QtQueued - ) + self._cart_model.item_added.connect(self.__cue_added) + self._cart_model.item_removed.connect(self.__cue_removed) + self._cart_model.item_moved.connect(self.__cue_moved) self._cart_model.model_reset.connect(self.__model_reset) self._memento_model = CueMementoAdapter(self._cart_model) @@ -278,6 +272,7 @@ def remove_page(self, index): page.deleteLater() # Rename every successive tab accordingly + # TODO: check for "standard" naming using a regex text = translate("CartLayout", "Page {number}") for n in range(index, self._cart_view.count()): self._cart_view.setTabText(n, text.format(number=n + 1)) diff --git a/lisp/plugins/cart_layout/model.py b/lisp/plugins/cart_layout/model.py index 4cb4aefb1..cf5cab5d0 100644 --- a/lisp/plugins/cart_layout/model.py +++ b/lisp/plugins/cart_layout/model.py @@ -100,19 +100,17 @@ def move(self, old_index, new_index): def page_edges(self, page): start = self.flat((page, 0, 0)) - end = self.flat((page, self.__rows, self.__columns)) + end = self.flat((page, self.__rows - 1, self.__columns - 1)) return start, end def remove_page(self, page, lshift=True): start, end = self.page_edges(page) - for index in range(start, end + 1): - cue = self.__cues.get(index) - if cue is not None: - self.remove(cue) + for index in self.__cues.irange(start, end): + self.remove(self.__cues[index]) if lshift: page_size = self.__rows * self.__columns - for index in self.__cues.irange(minimum=end + 1): + for index in self.__cues.irange(end + 1): new_index = index - page_size self.__cues[new_index] = self.__cues.pop(index) self.__cues[new_index].index = new_index From 9ae865cdf492a86a689024ccba42aad381c5202d Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 27 Mar 2019 11:12:18 +0100 Subject: [PATCH 174/333] Fix "select all media-cues" --- lisp/ui/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index dd0f37f4d..2144ebebb 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -365,7 +365,7 @@ def _layoutDeselectAll(self): self.session.layout.deselect_all() def __layoutSelectAllMediaCues(self): - self.session.layout.select_all(cue_class=MediaCue) + self.session.layout.select_all(cue_type=MediaCue) def __saveSession(self): if self.session.session_file: From ae9bb245ed23a32e4e4b654986ffb882f5e215c6 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 7 Apr 2019 17:57:20 +0200 Subject: [PATCH 175/333] Improve building scripts, switch to CirceCI for automate builds Fix: QFileDialog usage has been corrected in a few places --- .circleci/config.yml | 130 ++++++++ .travis.yml | 48 --- dist/AppImage/Recipe | 304 ------------------ dist/Flatpak/README | 65 ---- dist/Flatpak/build.sh | 43 --- dist/Flatpak/functions.sh | 53 --- dist/travis_bintray.py | 72 ----- lisp/plugins/gst_backend/gst_backend.py | 8 +- .../plugins/gst_backend/settings/uri_input.py | 22 +- lisp/ui/mainwindow.py | 22 +- scripts/Flatpak/README | 59 ++++ scripts/Flatpak/build_flatpak.sh | 31 ++ scripts/Flatpak/config.sh | 13 + scripts/Flatpak/functions.sh | 89 +++++ {dist => scripts}/Flatpak/pipenv_flatpak.py | 4 +- .../Flatpak/prepare_flatpak.py | 40 ++- scripts/Flatpak/requirements.txt | 2 + {dist => scripts}/Flatpak/template.json | 169 +++++++--- 18 files changed, 506 insertions(+), 668 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 .travis.yml delete mode 100755 dist/AppImage/Recipe delete mode 100644 dist/Flatpak/README delete mode 100755 dist/Flatpak/build.sh delete mode 100644 dist/Flatpak/functions.sh delete mode 100644 dist/travis_bintray.py create mode 100644 scripts/Flatpak/README create mode 100755 scripts/Flatpak/build_flatpak.sh create mode 100755 scripts/Flatpak/config.sh create mode 100755 scripts/Flatpak/functions.sh rename {dist => scripts}/Flatpak/pipenv_flatpak.py (97%) rename dist/Flatpak/travis_flatpak.py => scripts/Flatpak/prepare_flatpak.py (59%) create mode 100644 scripts/Flatpak/requirements.txt rename {dist => scripts}/Flatpak/template.json (52%) diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..b4d9695e2 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,130 @@ +version: 2 +jobs: + build: + machine: + image: ubuntu-1604:201903-01 + + working_directory: ~/repo + + environment: + BUILD_BRANCH: $CIRCLE_BRANCH + BUILD_COMMIT: $CIRCLE_SHA1 + + steps: + - checkout + + - run: + name: SYSTEM ⟹ Install flatpak & flatpak-builder + command: | + sudo apt-get -qq update + sudo apt-get install -y software-properties-common python3-software-properties + sudo add-apt-repository -y ppa:alexlarsson/flatpak + sudo apt-get -qq update + sudo apt-get install -y ostree flatpak flatpak-builder + + - restore_cache: + name: PYENV ⟹ Restore cache + key: pyenv-python-3.6.8 + + - run: + name: PYENV ⟹ Install python 3.6.8 + command: | + pyenv install --skip-existing 3.6.8 + pyenv global 3.6.8 + + - save_cache: + name: PYENV ⟹ Save cache + key: pyenv-python-3.6.8 + paths: + - ~/.pyenv/versions/3.6.8 + + - run: + name: FLATPAK ⟹ Install flathub Runtime & SDK + working_directory: ~/repo/scripts/Flatpak + command: source functions.sh && flatpak_install_runtime + + - restore_cache: + name: PIP ⟹ Restore cache + keys: + - v3-pip-{{ checksum "scripts/Flatpak/requirements.txt" }} + + - run: + name: FLATPAK ⟹ Generate flatpak manifest + working_directory: ~/repo/scripts/Flatpak + command: | + export BUILD_BRANCH=$CIRCLE_BRANCH + source functions.sh && flatpak_build_manifest + + - save_cache: + name: PIP ⟹ Save cache + key: v3-pip-{{ checksum "scripts/Flatpak/requirements.txt" }} + paths: + - ~/repo/scripts/Flatpak/.venv + + - restore_cache: + name: FLATPAK BUILD ⟹ Restore cache + keys: + - v2-flatpak-{{ checksum "scripts/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json" }} + - v2-flatpak- + + - run: + name: FLATPAK BUILD ⟹ Build flatpak (no exit on error) + working_directory: ~/repo/scripts/Flatpak + command: source functions.sh && flatpak_build_noexit + + - save_cache: + name: FLATPAK BUILD ⟹ Save cache + key: v2-flatpak-{{ checksum "scripts/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json" }} + paths: + - ~/repo/scripts/Flatpak/.flatpak-builder + + - run: + name: FLATPAK BUILD ⟹ Check if build failed + working_directory: ~/repo/scripts/Flatpak + command: source functions.sh && flatpak_build_noexit_check + + - run: + name: FLATPAK ⟹ Bundle flatpak + working_directory: ~/repo/scripts/Flatpak + command: | + export BUILD_BRANCH=$CIRCLE_BRANCH + source functions.sh && flatpak_bundle + + - run: + name: BINTRAY ⟹ Upload bundle + working_directory: ~/repo/scripts/Flatpak + command: | + curl -fL https://getcli.jfrog.io | sh + chmod +x jfrog + + PKG_VERSION=$(date '+%Y%m%d')-$CIRCLE_BRANCH-$CIRCLE_BUILD_NUM + + ./jfrog bt config \ + --interactive=false \ + --user=francescoceruti \ + --key=$BINTRAY_API_KEY \ + --licenses=GPL-3.0 + + ./jfrog bt upload \ + --publish \ + --override \ + out/linux-show-payer.flatpak \ + francescoceruti/LinuxShowPlayer/ci/$PKG_VERSION \ + linux-show-player_$PKG_VERSION.flatpak + + - store_artifacts: + path: ~/repo/scripts/Flatpak/out + destination: build + +workflows: + version: 2 + build: + jobs: + - build: + context: Bintray + filters: + branches: + only: + - master + - develop + - building \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2dc7acdbf..000000000 --- a/.travis.yml +++ /dev/null @@ -1,48 +0,0 @@ -sudo: required -language: python - -python: - - "3.6" - -services: - - docker - -branches: - only: - - develop - - master - -before_install: - - docker pull francescoceruti/flatpack - -install: - # Required by travis_flatpak.py - - travis_retry pip3 install requests html5lib - -script: - # Enter the flatpak buid directory - - cd dist/Flatpak - # Patch the flatpak manifest to work with travis - - python3 travis_flatpak.py - # Run the build in a container, mounting the current directory - # FIXME: if this fails at any stage the cache is not committed by travis - - docker run --privileged -v ${PWD}:/src -i francescoceruti/flatpack src/build.sh $TRAVIS_BRANCH - # Return into the build-dir - - cd $TRAVIS_BUILD_DIR - -cache: - directories: - # Flatpak build cache, avoid rebuilding unchanged modules - - $TRAVIS_BUILD_DIR/dist/Flatpak/.flatpak-builder - -before_deploy: - - python3 dist/travis_bintray.py - -deploy: - provider: bintray - file: dist/bintray.json - user: "francescoceruti" - key: $BINTRAY_API_KEY - skip_cleanup: true # since we are building inside the working-directory - on: - all_branches: true diff --git a/dist/AppImage/Recipe b/dist/AppImage/Recipe deleted file mode 100755 index ea9a48447..000000000 --- a/dist/AppImage/Recipe +++ /dev/null @@ -1,304 +0,0 @@ -#!/usr/bin/env bash - -# TODO: -# - -# FIXME: -# - PyQt5 from pypi is available only for x86_64 - -# Cleanup the console -echo -en "\ec" -# Exit on error -set -e -# Enable verbose shell output -set -x -# Allow to use ldconfig under Debian Jessie -export PATH="/sbin:$PATH" - -######################################################################## -# Create the AppDir -######################################################################## - -APP="LinuxShowPlayer" -LOWERAPP=${APP,,} - -if [ -d $APP/$APP.AppDir/ ]; then - rm -rf $APP/$APP.AppDir/ -fi - -mkdir -p $APP/$APP.AppDir/ -cd $APP/ - -######################################################################## -# Get Build dependencies (and virtualenv) -######################################################################## - -sudo apt-get update -qq || true -sudo apt-get -y install build-essential python3-dev libffi-dev libportmidi-dev libasound2-dev \ - libjack-jackd2-dev bison flex libcppunit-dev bison flex uuid-dev libprotobuf-dev protobuf-compiler \ - libprotoc-dev libncurses5-dev autoconf libtool rsync wget python3-pip virtualenv - -# If the python3 version is < 3.5 -if [ `python3 -c 'import sys; print(sys.version_info.major >= 3 and sys.version_info.minor >= 5)'` = "False" ]; -then - # Install and initialize pyenv - sudo apt-get -y install libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev curl git llvm libncurses5-dev libncursesw5-dev xz-utils - curl -L https://raw.githubusercontent.com/yyuu/pyenv-installer/master/bin/pyenv-installer | bash - export PYENV_ROOT="$HOME/.pyenv" - export PATH="$PYENV_ROOT/bin:$PATH" - eval "$(pyenv init -)" - #eval "$(pyenv virtualenv-init -)" - #pyenv install --list - pyenv install -s 3.5.3 - pyenv global 3.5.3 -fi - -# Install some via pip -pip3 install --user -U protobuf pip setuptools virtualenv - -######################################################################## -# Source some helper functions -######################################################################## - -wget "https://github.com/probonopd/AppImages/raw/master/functions.sh" -. ./functions.sh - -######################################################################## -# Get the last version of the app from GitHub -######################################################################## - -REPO="FrancescoCeruti/linux-show-player" -if [ -z "$1" ]; then - VERSION="master" -elif [ "$1" = "latest" ]; then - VERSION=$(wget -q "https://api.github.com/repos/$REPO/releases/latest" -O - | grep -E "tag_name" | cut -d'"' -f4) -else - VERSION="$1" -fi -DLD="https://github.com/$REPO/archive/"$VERSION".tar.gz" -ARCHIVE="linux-show-player-"$VERSION".tar.gz" - -# Download in the "$APP" directory -wget $DLD -O $ARCHIVE - -######################################################################## -# Get the dependencies -######################################################################## - -# Get OLA -OLA_VERSION="0.10.3" -OLA_FILE="ola-"$OLA_VERSION -wget -nc -c "https://github.com/OpenLightingProject/ola/releases/download/"$OLA_VERSION"/"$OLA_FILE".tar.gz" - -# We need to re-compile PyGObject aganist our python version (may become unecessary in future) -# Get PyGObject -apt-get source python3-gi -# To be able to compile it we need to install some build-dependecies -sudo apt-get -y build-dep python3-gi - -# Get .deb(s) -mkdir -p debs -cd debs -apt-get download libglib2.0-0 libgstreamer1.0-0 gstreamer1.0-plugins-base libgirepository-1.0-1 \ - gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly gstreamer1.0-plugins-bad gstreamer1.0-alsa \ - gstreamer1.0-pulseaudio gstreamer1.0-libav gstreamer1.0-fluendo-mp3 gir1.2-gstreamer-1.0 \ - gir1.2-gst-plugins-base-1.0 gir1.2-glib-2.0 libportmidi0 alsa-utils libpulse0 libffi6 -cd .. - -######################################################################## -# Create a virtualenv inside the AppDir -######################################################################## - -PYVERSION=`python3 -V | cut -d' ' -f2 | cut -d'.' -f1,2` -SYS_PYLIB=`python3 -c 'import os; print(os.path.dirname(os.__file__))'` -APP_PYLIB='usr/lib/python'$PYVERSION - -cd $APP.AppDir/ - -# Create the virtualenv in "usr" with python3, coping the files -virtualenv -p python3 --always-copy usr -# Copy all the python stdlib files skipped by virtualenv (why??) -rsync -tr $SYS_PYLIB/* $APP_PYLIB/ --exclude="*-packages/" -# For some reason using "--always-copy" even "python"/"python3" are not linked but copied -# fix that and save ~10MB of space -cd usr/bin -rm python3 python -ln -s python$PYVERSION python -ln -s python$PYVERSION python3 -cd ../.. - -# Create "sitecustomize.py" to load "site-packages" -cat > $APP_PYLIB"/sitecustomize.py" < usr/bin/linux-show-player <" -f 2 | xargs) ./usr/lib/ - -# move libpulsecommon-.so to the parent directory -mv usr/lib/x86_64-linux-gnu/pulseaudio/* usr/lib/x86_64-linux-gnu/ -rm -rf usr/lib/x86_64-linux-gnu/pulseaudio/ - -# make sure that GI_TYPELIB_PATH point to the right directory (some distro put gir files in different locations) -mv usr/lib/x86_64-linux-gnu/girepository-1.0/* usr/lib/girepository-1.0/ || true -rm -rf usr/lib/x86_64-linux-gnu/girepository-1.0 || true - -# Remove some "garbage" -rm -rf ./home || true -rm -rf ./var || true -rm -rf ./etc || true -rm -rf usr/lib/udev/ || true -rm -rf usr/lib/systemd/ || true -rm -rf usr/lib/x86_64-linux-gnu/mesa/ || true -rm -rf usr/lib/x86_64-linux-gnu/mesa-egl/ || true -rm -rf usr/lib/x86_64-linux-gnu/gio/ || true -rm -rf usr/share/alsa || true -rm -rf usr/share/doc || true -rm -rf usr/share/lintian || true -rm -rf usr/share/man || true -rm -rf usr/share/python-wheels || true -rm -rf usr/lib/python-wheels || true -rm -rf usr/share/sounds || true - -######################################################################## -# Finalize the AppDir -######################################################################## - -cd .. - -# Extract the archive -ARCHIVE_CONT="linux-show-player-content" -mkdir -p $ARCHIVE_CONT -tar zxf $ARCHIVE -C $ARCHIVE_CONT --strip-components 1 - -# Copy icons/mime -MIME_DIR="$APP.AppDir/usr/share/mime" -DESKTOP_DIR="$APP.AppDir/usr/share/applications" -ICONS_DIR="$APP.AppDir/usr/share/icons/default/512x512/apps" -MIME_ICONS_DIR="$APP.AppDir/usr/share/icons/default/512x512/mimetypes" - -mkdir -p $MIME_DIR -mkdir -p $ICONS_DIR -mkdir -p $DESKTOP_DIR -mkdir -p $MIME_ICONS_DIR - -cp $ARCHIVE_CONT/dist/linuxshowplayer.xml $MIME_DIR -cp $ARCHIVE_CONT/dist/linuxshowplayer.png $ICONS_DIR -cp $ARCHIVE_CONT/dist/linuxshowplayer.desktop $DESKTOP_DIR -cp $ARCHIVE_CONT/dist/application-x-linuxshowplayer.png $MIME_ICONS_DIR - -cd $APP.AppDir/ - -# Apprun & desktop-integration -get_apprun - -get_icon -get_desktop -get_desktopintegration $LOWERAPP - -######################################################################## -# Package the AppDir as an AppImage -######################################################################## - -# At this point, you should be able to run the application by executing -# ./AppRun - -cd .. - -generate_type2_appimage - -# To check libraries opened from the base system -#strace -e open -f ./AppRun 2>&1 | grep '("/usr/lib/' | grep so diff --git a/dist/Flatpak/README b/dist/Flatpak/README deleted file mode 100644 index dfcb7274e..000000000 --- a/dist/Flatpak/README +++ /dev/null @@ -1,65 +0,0 @@ ------- INTRO ------ - -(╯°□°)╯︵ ┻━┻ - -The scripts/files in this folder allow to build a Flatpak bundle of -Linux Show Player using Travis-CI, in LiSP case, the build is run in a custom -docker image, the travis VM or containers (Ubuntu 12.04 or 14.04) don't have -support for flatpak. - -- build.sh: this bash script invoke the needed flatpak commands to build the - application from a manifest file. - -- functions.sh: include some handy logging for travis - -- pipenv_flatpak.py: starting from the project Pipfile.lock generate the - appropriate modules to install the python requirements - -- template.json: the base manifest file, contains all the metadata, and the - non-python packages (some exception here) - -- travis_flatpak.py: starting from "template.json" outputs the complete manifest - - ------- DETAILS ------ - -- some parts are hardcoded, because I'm lazy and this scripts are designed with - LiSP in mind - -- CPython is already present in the runtime, but we prefer a newer version, - so we include a custom one. - This version is build on a different Travis-CI repository and downloaded - from bintray, to optimize the building process - -- non-used features of the various packages should be disabled when possible - -- gstreamer-plugins-good is already included in the base runtime, but with some - needed feature disabled, so we re-built it - -- the python "protobuf" package is specified directly in the template, this for - a few reasons: - - It's needed to "build" the OLA python package, which, can only be build - with all the OLA framework (--enable-python-libs) - - It's not specified in the Pipfile, for the reason above, without a complete - OLA installation you cannot have the python package - -- protobuf requires "six" and "setuptools", we install the only first, we - consider the second already shipped with the cpython packages - -- for python we favor pre-build "wheel" releases, which are faster to install - -- the PyQt5 package from PyPi already bundle the correct Qt5 binaries - - ------- NOTES ------ - -- until OLA 0.11 we must use protobuf < 3.2, or it will not build, - if needed we may patch it, for now it's fine - -- pipenv_flatpak.py is quite generic, it could also be used in other places - -- The PyQt5 wheels from pypi are "all-inclusive" and quite big - -- If we need custom build for "pyqt5" and "sip" the best approach is probably to - create an ad-hoc repository to produce a wheel file like the ones distributed - from pypi diff --git a/dist/Flatpak/build.sh b/dist/Flatpak/build.sh deleted file mode 100755 index d60752ea8..000000000 --- a/dist/Flatpak/build.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -# Make sure we are in the same directory of this file -cd "${0%/*}" -# Include some travis utility function -source "functions.sh" - -BRANCH="$1" - -set -e - -# Prepare the repository -ostree init --mode=archive-z2 --repo=repo - -# Install runtime and sdk -travis_fold start "flatpak_install_deps" - travis_time_start - flatpak --user remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo - flatpak --user install flathub org.gnome.Platform//3.28 org.gnome.Sdk//3.28 - travis_time_finish -travis_fold end "flatpak_install_deps" -echo -e \\n - -# Build -travis_fold start "flatpak_build" - travis_time_start - flatpak-builder --verbose --force-clean --ccache --repo=repo \ - build com.github.FrancescoCeruti.LinuxShowPlayer.json - travis_time_finish -travis_fold end "flatpak_build" -echo -e \\n - -# Bundle the build -travis_fold start "flatpak_bundle" - travis_time_start - mkdir -p out - flatpak build-bundle --verbose repo out/LinuxShowPlayer.flatpak \ - com.github.FrancescoCeruti.LinuxShowPlayer $BRANCH - travis_time_finish -travis_fold end "flatpak_update_repo" -echo -e \\n - -set +e \ No newline at end of file diff --git a/dist/Flatpak/functions.sh b/dist/Flatpak/functions.sh deleted file mode 100644 index 9b9663511..000000000 --- a/dist/Flatpak/functions.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash -# -# Derived from: https://github.com/travis-ci/travis-build/blob/e12e2fc01fd96cae8671b1955397813023953743/lib/travis/build/templates/header.sh -# -# MIT LICENSE -# -# Copyright (c) 2016 Travis CI GmbH -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -ANSI_CLEAR="\033[0K" - -travis_time_start() { - travis_timer_id=$(printf %08x $(( RANDOM * RANDOM ))) - travis_start_time=$(travis_nanoseconds) - echo -en "travis_time:start:$travis_timer_id\r${ANSI_CLEAR}" -} - -travis_time_finish() { - local result=$? - travis_end_time=$(travis_nanoseconds) - local duration=$(($travis_end_time-$travis_start_time)) - echo -en "\ntravis_time:end:$travis_timer_id:start=$travis_start_time,finish=$travis_end_time,duration=$duration\r${ANSI_CLEAR}" - return $result -} - -travis_nanoseconds() { - local cmd="date" - local format="+%s%N" - $cmd -u $format -} - -travis_fold() { - local action=$1 - local name=$2 - echo -en "travis_fold:${action}:${name}\r${ANSI_CLEAR}" -} \ No newline at end of file diff --git a/dist/travis_bintray.py b/dist/travis_bintray.py deleted file mode 100644 index 44b59ec8a..000000000 --- a/dist/travis_bintray.py +++ /dev/null @@ -1,72 +0,0 @@ -import os -import datetime -import json - -TEMPLATE = { - "package": { - "subject": "francescoceruti", - "repo": "LinuxShowPlayer", - "name": "", - "desc": "Automatic builds from travis-ci", - "website_url": "https://github.com/FrancescoCeruti/linux-show-player/issues", - "vcs_url": "https://github.com/FrancescoCeruti/linux-show-player.git", - "issue_tracker_url": "https://github.com/FrancescoCeruti/linux-show-player/issues", - "licenses": ["GPL-3.0"], - }, - - "version": { - "name": "", - "released": "", - "desc": "" - }, - - "files": [ - ], - - "publish": True -} - -DIR = os.path.dirname(__file__) -TODAY = datetime.datetime.now().strftime('%Y-%m-%d') -DESC_FILE = 'bintray.json' -DESC_PATH = os.path.join(DIR, DESC_FILE) - -BRANCH = os.environ['TRAVIS_BRANCH'] -COMMIT = os.environ['TRAVIS_COMMIT'] -COMMIT_MSG = os.environ['TRAVIS_COMMIT_MESSAGE'] -TAG = os.environ.get('TRAVIS_TAG', '') - -VERSION = datetime.datetime.now().strftime('%Y.%m.%d_%H:%M') -VERSION += '_{}'.format(TAG if TAG else COMMIT[:7]) - -print('Creating "{}" ...'.format(DESC_FILE)) - -# Package -TEMPLATE['package']['name'] = BRANCH -print('>>> Package name: {}'.format(BRANCH)) - -# Version -TEMPLATE['version']['name'] = VERSION -print('>>> Version name: {}'.format(VERSION)) - -TEMPLATE['version']['released'] = TODAY -print('>>> Version date: {}'.format(TODAY)) - -TEMPLATE['version']['desc'] = COMMIT_MSG - -if TAG: - TEMPLATE['version']['vcs_tag'] = TAG - -# Files -TEMPLATE['files'].append( - { - "includePattern": "dist/Flatpak/out/LinuxShowPlayer.flatpak", - "uploadPattern": '/{0}/LinuxShowPlayer.flatpak'.format(VERSION), - } -) - -# Save the bintray description -with open(DESC_PATH, mode='w') as out: - json.dump(TEMPLATE, out, indent=4) - -print('{} created.'.format(DESC_FILE)) diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index cd3c3d0d7..4d7c7c1b5 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -105,14 +105,14 @@ def supported_extensions(self): def _add_uri_audio_cue(self): """Add audio MediaCue(s) form user-selected files""" - dir = GstBackend.Config.get("mediaLookupDir", "") - if not os.path.exists(dir): - dir = self.app.session.dir() + directory = GstBackend.Config.get("mediaLookupDir", "") + if not os.path.exists(directory): + directory = self.app.session.dir() files, _ = QFileDialog.getOpenFileNames( self.app.window, translate("GstBackend", "Select media files"), - dir, + directory, qfile_filters(self.supported_extensions(), anyfile=True), ) if files: diff --git a/lisp/plugins/gst_backend/settings/uri_input.py b/lisp/plugins/gst_backend/settings/uri_input.py index 1a1076254..f71b2f943 100644 --- a/lisp/plugins/gst_backend/settings/uri_input.py +++ b/lisp/plugins/gst_backend/settings/uri_input.py @@ -14,6 +14,7 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import os from PyQt5.QtCore import QStandardPaths, Qt from PyQt5.QtWidgets import ( @@ -29,6 +30,8 @@ QVBoxLayout, ) +from lisp.application import Application +from lisp.plugins.gst_backend import GstBackend from lisp.plugins.gst_backend.elements.uri_input import UriInput from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate @@ -115,13 +118,22 @@ def enableCheck(self, enabled): self.fileGroup.setChecked(False) def select_file(self): - path = QStandardPaths.writableLocation(QStandardPaths.MusicLocation) - file, ok = QFileDialog.getOpenFileName( + directory = "" + current = self.filePath.text() + + if current.startswith("file://"): + directory = Application().session.abs_path(current[7:]) + if not os.path.exists(directory): + directory = GstBackend.Config.get("mediaLookupDir", "") + if not os.path.exists(directory): + directory = self.app.session.dir() + + path, _ = QFileDialog.getOpenFileName( self, translate("UriInputSettings", "Choose file"), - path, + directory, translate("UriInputSettings", "All files") + " (*)", ) - if ok: - self.filePath.setText("file://" + file) + if os.path.exists(path): + self.filePath.setText("file://" + path) diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 2144ebebb..e6d6ec6cc 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -311,19 +311,19 @@ def getOpenSessionFile(self): def getSaveSessionFile(self): if self.session.session_file: - session_dir = self.session.dir() + directory = self.session.dir() else: - session_dir = self.conf.get("session.last_dir", os.getenv("HOME")) + directory = self.conf.get("session.last_dir", os.getenv("HOME")) - filename, accepted = QFileDialog.getSaveFileName( - parent=self, filter="*.lsp", directory=session_dir + path, _ = QFileDialog.getSaveFileName( + parent=self, filter="*.lsp", directory=directory ) - if accepted: - if not filename.endswith(".lsp"): - filename += ".lsp" + if path: + if not path.endswith(".lsp"): + path += ".lsp" - return filename + return path def setFullScreen(self, enable): if enable: @@ -336,9 +336,9 @@ def __simpleCueInsert(self, cueClass): cue = CueFactory.create_cue(cueClass.__name__) # Get the (last) index of the current selection - layout_selection = list(self.session.layout.selected_cues()) - if layout_selection: - cue.index = layout_selection[-1].index + 1 + layoutSelection = list(self.session.layout.selected_cues()) + if layoutSelection: + cue.index = layoutSelection[-1].index + 1 self.session.cue_model.add(cue) except Exception: diff --git a/scripts/Flatpak/README b/scripts/Flatpak/README new file mode 100644 index 000000000..68c8a8b63 --- /dev/null +++ b/scripts/Flatpak/README @@ -0,0 +1,59 @@ +====== INTRO ====== + +(╯°□°)╯︵ ┻━┻ + +The scripts/files in this folder allow to build a Flatpak package of Linux Show Player. + + * build_flatpak.sh: Combine the commands from "functions.sh" to run a complete build + + * config.sh: Some environment configuration + + * functions.sh: A collection of commands to build the flatpak + + * pipenv_flatpak.py: Starting from the project Pipfile.lock generate the + appropriate modules to install the python requirements + + * prepare_flatpak.py: Starting from "template.json" outputs the complete + manifest. Uses "pipenv_flatpak.py" internally. + + * template.json: The base manifest file, contains all the metadata, and the + non-python packages (some exception here) + + * requirements.txt: Python packages required for the build + +====== REQUIREMENTS ====== + + * ostree + * flatpak >= 1.0 + * flatpak-builder + * Python >= 3.6 + * (python) see "requirements.txt" + * BUILD_BRANCH to be set. + +====== DETAILS ====== + +=== Qt5 + PyQt5 === + +Instead of using the pypi package we build our own Qt binaries, this allow to +remove unused modules, and reduce (a lot) the size of the build, at the cost +of a longer build time. + +=== OLA === + +Until version 0.11 we must use protobuf < 3.2, or it will not build, +if needed we may patch it, for now it's fine. + +=== protobuf === + +It's needed to "build" the OLA python package, which, can only be build +with all the OLA framework (--enable-python-libs) + +Requires "six" and "setuptools", we install only the first, we +consider the second already shipped with the python version + +====== NOTES ====== + +Some parts are hardcoded, because I'm lazy and this scripts are designed with +LiSP in mind. + +Non-used features of the various packages should be disabled when possible. diff --git a/scripts/Flatpak/build_flatpak.sh b/scripts/Flatpak/build_flatpak.sh new file mode 100755 index 000000000..53118dc7f --- /dev/null +++ b/scripts/Flatpak/build_flatpak.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +# Exit script if a statement returns a non-true return value. +set -o errexit +# Use the error status of the first failure, rather than that of the last item in a pipeline. +set -o pipefail + +echo -e "\n\n" +echo "================= build_flatpak.sh =================" +echo -e "\n" + +# Make sure we are in the same directory of this file +cd "${0%/*}" + +# Load Environment variables +source functions.sh + +# Print relevant variables +echo "<<< FLATPAK_INSTALL = "$FLATPAK_INSTALL +echo "<<< FLATPAK_PY_VERSION = "$FLATPAK_PY_VERSION +echo "<<< FLATPAK_APP_ID = " $FLATPAK_APP_ID +echo "<<< FLATPAK_APP_MODULE = " $FLATPAK_APP_MODULE + +flatpak_install_runtime +flatpak_build_manifest +flatpak_build +flatpak_bundle + +echo -e "\n" +echo "================= build_flatpak.sh =================" +echo -e "\n\n" \ No newline at end of file diff --git a/scripts/Flatpak/config.sh b/scripts/Flatpak/config.sh new file mode 100755 index 000000000..cde52ef3e --- /dev/null +++ b/scripts/Flatpak/config.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +export FLATPAK_RUNTIME="org.gnome.Platform" +export FLATPAK_RUNTIME_VERSION="3.32" +export FLATPAK_SDK="org.gnome.Sdk" + +export FLATPAK_INSTALL="$FLATPAK_RUNTIME//$FLATPAK_RUNTIME_VERSION $FLATPAK_SDK//$FLATPAK_RUNTIME_VERSION" + +export FLATPAK_PY_VERSION="3.7" +export FLATPAK_PY_IGNORE_PACKAGES="setuptools six pygobject pycairo pyqt5 sip" + +export FLATPAK_APP_ID="com.github.FrancescoCeruti.LinuxShowPlayer" +export FLATPAK_APP_MODULE="linux-show-player" \ No newline at end of file diff --git a/scripts/Flatpak/functions.sh b/scripts/Flatpak/functions.sh new file mode 100755 index 000000000..48e279ef7 --- /dev/null +++ b/scripts/Flatpak/functions.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +source config.sh + +function flatpak_build_manifest() { + # Create virtual-environment + python3 -m venv ./venv + + # Install requirements + source ./venv/bin/activate + pip3 install --upgrade -r requirements.txt + + # Build manifest + python3 prepare_flatpak.py + deactivate +} + +function flatpak_install_runtime() { + echo -e "\n" + echo "###################################################" + echo "# Install flatpak (flathub) runtime and sdk #" + echo "###################################################" + echo -e "\n" + + flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo + flatpak install --user --noninteractive --assumeyes flathub $FLATPAK_INSTALL +} + +function flatpak_build() { + echo -e "\n" + echo "###########################" + echo "# Build the flatpak #" + echo "###########################" + echo -e "\n" + + # Prepare the repository + ostree init --mode=archive-z2 --repo=repo + # Build the flatpak + flatpak-builder --verbose --force-clean --ccache --repo=repo build $FLATPAK_APP_ID.json +} + + +function flatpak_build_noexit() { + if [[ ${-/e} != $- ]] ; then set +e ; USE_E=1 ; else USE_E=0 ; fi + + flatpak_build + echo "$?" > "FB_EXIT_CODE" + + if [[ $USE_E -eq 1 ]] ; then set -e ; fi +} + +function flatpak_build_noexit_check() { + if [[ -f "FB_EXIT_CODE" ]] ; then + if [[ $(cat "FB_EXIT_CODE") -ne 0 ]] ; then + >&2 echo "Error: flatpak-builder exit with non-zero" + exit $(cat "FB_EXIT_CODE") + fi + fi +} + +function flatpak_bundle() { + echo -e "\n" + echo "###############################" + echo "# Create flatpak bundle #" + echo "###############################" + echo -e "\n" + + mkdir -p out + # Create the bundle (without blocking the script) + flatpak build-bundle repo out/linux-show-payer.flatpak $FLATPAK_APP_ID $BUILD_BRANCH & + # Print elapsed time + watch_process $! +} + +function watch_process() { + elapsed="00:00" + # If this command is killed, kill the watched process + trap "kill $1 2> /dev/null" EXIT + # While the process is running print the elapsed time every second + while kill -0 $1 2> /dev/null; do + elapsed=$(echo -en $(ps -o etime= -p "$1")) + echo -ne "[ $elapsed ] Building ...\r" + sleep 1 + done + # Print a "completed message" + echo "Completed in $elapsed " + # Disable the trap on a normal exit. + trap - EXIT +} diff --git a/dist/Flatpak/pipenv_flatpak.py b/scripts/Flatpak/pipenv_flatpak.py similarity index 97% rename from dist/Flatpak/pipenv_flatpak.py rename to scripts/Flatpak/pipenv_flatpak.py index 00073248a..a3dc323ec 100644 --- a/dist/Flatpak/pipenv_flatpak.py +++ b/scripts/Flatpak/pipenv_flatpak.py @@ -22,7 +22,9 @@ def _flatpak_module_template(): return { "name": "", "buildsystem": "simple", - "build-commands": ["pip3 install --no-deps --prefix=${FLATPAK_DEST} "], + "build-commands": [ + "pip3 --verbose install --no-index --no-deps --ignore-installed --no-build-isolation --prefix=${FLATPAK_DEST} " + ], "sources": [{"type": "file", "url": "", "sha256": ""}], } diff --git a/dist/Flatpak/travis_flatpak.py b/scripts/Flatpak/prepare_flatpak.py similarity index 59% rename from dist/Flatpak/travis_flatpak.py rename to scripts/Flatpak/prepare_flatpak.py index 09709ed67..d7abc59e8 100644 --- a/dist/Flatpak/travis_flatpak.py +++ b/scripts/Flatpak/prepare_flatpak.py @@ -1,21 +1,21 @@ import json -import urllib.parse import os import pipenv_flatpak -DIR = os.path.dirname(__file__) -BRANCH = os.environ["TRAVIS_BRANCH"] -COMMIT = os.environ["TRAVIS_COMMIT"] +BRANCH = os.environ["BUILD_BRANCH"] +APP_ID = os.environ["FLATPAK_APP_ID"] +PY_VERSION = os.environ["FLATPAK_PY_VERSION"] +APP_MODULE = os.environ["FLATPAK_APP_MODULE"] +IGNORE_PACKAGES = [ + p.lower() for p in os.environ.get("FLATPAK_PY_IGNORE_PACKAGES", "").split() +] -APP_ID = "com.github.FrancescoCeruti.LinuxShowPlayer" -APP_MODULE = "linux-show-player" +DIR = os.path.dirname(__file__) TEMPLATE = os.path.join(DIR, "template.json") DESTINATION = os.path.join(DIR, APP_ID + ".json") - LOCKFILE = "../../Pipfile.lock" - print(">>> Generating flatpak manifest ....\n") with open(TEMPLATE, mode="r") as f: manifest = json.load(f) @@ -25,31 +25,27 @@ if BRANCH != "master": manifest["desktop-file-name-suffix"] = " ({})".format(BRANCH) -py_version = None -app_index = None - -# Get python version "major.minor" and patch the app-module to use the correct -# branch +# Patch the app-module to use the correct branch +app_index = 0 for index, module in enumerate(manifest["modules"]): - if module["name"] == "cpython": - path = urllib.parse.urlsplit(module["sources"][0]["url"]).path - file = os.path.basename(path) - py_version = file[2:5] - elif module["name"] == APP_MODULE: - module["sources"][0]["branch"] = BRANCH - module["sources"][0]["commit"] = COMMIT + if module["name"] == APP_MODULE: app_index = index + module["sources"][0]["branch"] = BRANCH + break # Generate python-modules from Pipfile.lock, insert them before the app-module for num, py_module in enumerate( pipenv_flatpak.generate( LOCKFILE, - py_version, + PY_VERSION, pipenv_flatpak.PLATFORMS_LINUX_x86_64, pipenv_flatpak.PYPI_URL, ) ): - manifest["modules"].insert((app_index - 1) + num, py_module) + if py_module["name"].lower() not in IGNORE_PACKAGES: + manifest["modules"].insert((app_index - 1) + num, py_module) + else: + print("=> Package ignored: {}".format(py_module["name"])) # Save the patched manifest with open(DESTINATION, mode="w") as out: diff --git a/scripts/Flatpak/requirements.txt b/scripts/Flatpak/requirements.txt new file mode 100644 index 000000000..6504ea5a2 --- /dev/null +++ b/scripts/Flatpak/requirements.txt @@ -0,0 +1,2 @@ +requests +html5lib \ No newline at end of file diff --git a/dist/Flatpak/template.json b/scripts/Flatpak/template.json similarity index 52% rename from dist/Flatpak/template.json rename to scripts/Flatpak/template.json index 00047fd83..8bf11084f 100644 --- a/dist/Flatpak/template.json +++ b/scripts/Flatpak/template.json @@ -1,7 +1,7 @@ { "app-id": "com.github.FrancescoCeruti.LinuxShowPlayer", "runtime": "org.gnome.Platform", - "runtime-version": "3.28", + "runtime-version": "3.32", "sdk": "org.gnome.Sdk", "command": "linux-show-player", "build-options": { @@ -16,24 +16,116 @@ "--device=all" ], "cleanup-commands": [ - "pip3 uninstall --yes cython || true" + "pip3 uninstall --yes cython || true", + "pip3 uninstall --yes wheel || true" ], "rename-appdata-file": "linuxshowplayer.appdata.xml", - "rename-desktop-file": "linuxshowplayer.desktop", - "rename-icon": "linuxshowplayer", + "rename-desktop-file": "linuxshowplayer.desktop", + "rename-icon": "linuxshowplayer", "modules": [ { - "name": "cpython", - "buildsystem": "simple", - "build-commands": [ - "cp -r bin/ include/ lib/ share/ /app" + "name": "Qt5", + "config-opts": [ + "-confirm-license", "-opensource", + "-shared", + "-nomake", "examples", + "-nomake", "tests", + "-skip", "qt3d", + "-skip", "qtconnectivity", + "-skip", "qtcharts", + "-skip", "qtdatavis3d", + "-skip", "qtdeclarative", + "-skip", "qtmultimedia", + "-skip", "qtquickcontrols", + "-skip", "qtquickcontrols2", + "-skip", "qtsensors", + "-skip", "qtscript", + "-skip", "qtserialbus", + "-skip", "qtspeech", + "-skip", "qttools", + "-skip", "qtwebchannel", + "-skip", "qtwebengine", + "-skip", "qtwebglplugin", + "-skip", "qtwebsockets", + "-skip", "qtwebview", + "-gtk" + ], + "sources": [ + { + "type": "archive", + "url": "http://master.qt.io/archive/qt/5.12/5.12.2/single/qt-everywhere-src-5.12.2.tar.xz", + "sha256": "59b8cb4e728450b21224dcaaa40eb25bafc5196b6988f2225c394c6b7f881ff5" + } + ] + }, + { + "name": "PyQt5", + "config-opts": [ + "--assume-shared", + "--concatenate", + "--confirm-license", + "--no-designer-plugin", + "--no-dist-info", + "--no-docstrings", + "--no-qml-plugin", + "--no-qsci-api", + "--no-stubs", + "--no-tools", + "QMAKE_CFLAGS_RELEASE='-I/usr/include/python3.7m/'", + "QMAKE_CXXFLAGS_RELEASE='-I/usr/include/python3.7m/'" ], "sources": [ { "type": "archive", - "url": "https://dl.bintray.com/francescoceruti/flatpak-builds/py3.7.1-freedesktop1.6.tar.gz", - "sha256": "d9eb119c421fee85380ef90b9d1551b564740b01d705425b79d31a71b0587531" + "url": "https://www.riverbankcomputing.com/static/Downloads/PyQt5/5.12.1/PyQt5_gpl-5.12.1.tar.gz", + "sha256": "3718ce847d824090fd5f95ff3f13847ee75c2507368d4cbaeb48338f506e59bf" + }, + { + "type": "script", + "commands": [ + "processed=`sed -e 's|prefix|sysroot|' <<< $@`", + "python3 configure.py $processed" + ], + "dest-filename": "configure" + } + ], + "modules": [ + { + "name": "SIP", + "config-opts": [ + "--no-dist-info", + "--no-stubs", + "--bindir=/app/bin", + "--destdir=/app/lib/python3.7/site-packages", + "--incdir=/app/include", + "--pyidir=/app/lib/python3.7/site-packages", + "--sipdir=/app/share/sip", + "--sip-module PyQt5.sip" + ], + "sources": [ + { + "type": "archive", + "url": "https://www.riverbankcomputing.com/static/Downloads/sip/4.19.15/sip-4.19.15.tar.gz", + "sha256": "2b5c0b2c0266b467b365c21376d50dde61a3236722ab87ff1e8dacec283eb610" + }, + { + "type": "script", + "commands": [ + "processed=`sed -e 's|--prefix=/app||' <<< $@`", + "python3 configure.py $processed" + ], + "dest-filename": "configure" + } + ], + "cleanup": [ + "/bin", + "/include" + ] } + ], + "cleanup": [ + "/bin", + "/include" ] }, { @@ -55,26 +147,6 @@ "/bin" ] }, - { - "name": "gstreamer-plugins-good", - "config-opts": [ - "--enable-silent-rules", - "--enable-experimental", - "--disable-monoscope", - "--disable-aalib", - "--enable-cairo", - "--disable-libcaca", - "--disable-gtk-doc-html", - "--with-default-visualizer=autoaudiosink" - ], - "sources": [ - { - "type": "archive", - "url": "http://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-1.10.5.tar.xz", - "sha256": "be053f6ed716eeb517cec148cec637cdce571c6e04d5c21409e2876fb76c7639" - } - ] - }, { "name": "RtMidi", "config-opts": [ @@ -119,18 +191,26 @@ ] }, { - "name": "python-google-protobuf", + "name": "python-six", "buildsystem": "simple", "build-commands": [ - "pip3 install --no-deps --prefix=${FLATPAK_DEST} six-1.11.0-py2.py3-none-any.whl", - "pip3 install --no-deps --prefix=${FLATPAK_DEST} protobuf-3.1.0.post1-py2.py3-none-any.whl" + "pip3 install --no-index --no-deps --prefix=${FLATPAK_DEST} six-1.11.0-py2.py3-none-any.whl" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/67/4b/141a581104b1f6397bfa78ac9d43d8ad29a7ca43ea90a2d863fe3056e86a/six-1.11.0-py2.py3-none-any.whl", "sha256": "832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" - }, + } + ] + }, + { + "name": "python-google-protobuf", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --no-deps --prefix=${FLATPAK_DEST} protobuf-3.1.0.post1-py2.py3-none-any.whl" + ], + "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/a5/bb/11821bdc46cb9aad8e18618715e5e93eef44abb642ed862c4b080c474183/protobuf-3.1.0.post1-py2.py3-none-any.whl", @@ -167,16 +247,25 @@ } ] }, + { + "name": "python-wheel", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --no-deps --prefix=${FLATPAK_DEST} wheel-0.33.1-py2.py3-none-any.whl" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/96/ba/a4702cbb6a3a485239fbe9525443446203f00771af9ac000fa3ef2788201/wheel-0.33.1-py2.py3-none-any.whl", + "sha256": "8eb4a788b3aec8abf5ff68d4165441bc57420c9f64ca5f471f58c3969fe08668" + } + ] + }, { "name": "linux-show-player", "buildsystem": "simple", - "build-options": { - "build-args": [ - "--share=network" - ] - }, "build-commands": [ - "pip3 install --no-deps --prefix=${FLATPAK_DEST} .", + "pip3 install --no-index --no-deps --no-build-isolation --prefix=${FLATPAK_DEST} .", "mkdir -p /app/share/applications/", "cp dist/linuxshowplayer.desktop /app/share/applications/", "mkdir -p /app/share/mime/packages/", From d9e1351e692f9328dbffff0868ef78e4c4aaf2fc Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 11 Apr 2019 00:45:02 +0200 Subject: [PATCH 176/333] Improved translations Merged #165 Update: refactored ts/qm file generation Update: refactored locale-select widget Update: improved translations searching Various changes --- i18n_update.py | 196 +- lisp/__init__.py | 3 +- lisp/default.json | 3 +- lisp/i18n/qm/action_cues_cs.qm | Bin 3563 -> 0 bytes lisp/i18n/qm/action_cues_en.qm | Bin 3367 -> 0 bytes lisp/i18n/qm/action_cues_es.qm | Bin 3655 -> 0 bytes lisp/i18n/qm/action_cues_fr.qm | Bin 3699 -> 0 bytes lisp/i18n/qm/action_cues_it.qm | Bin 3677 -> 0 bytes lisp/i18n/qm/action_cues_sl_SI.qm | Bin 3502 -> 0 bytes lisp/i18n/qm/base_cs_CZ.qm | Bin 0 -> 42965 bytes lisp/i18n/qm/base_de_DE.qm | Bin 0 -> 41365 bytes lisp/i18n/qm/base_en.qm | Bin 0 -> 21618 bytes lisp/i18n/qm/base_es_ES.qm | Bin 0 -> 44747 bytes lisp/i18n/qm/base_fr_FR.qm | Bin 0 -> 43571 bytes lisp/i18n/qm/base_it_IT.qm | Bin 0 -> 43603 bytes lisp/i18n/qm/base_nl_BE.qm | Bin 0 -> 41365 bytes lisp/i18n/qm/base_nl_NL.qm | Bin 0 -> 42813 bytes lisp/i18n/qm/base_sl_SI.qm | Bin 0 -> 42518 bytes lisp/i18n/qm/controller_cs.qm | Bin 1424 -> 0 bytes lisp/i18n/qm/controller_en.qm | Bin 1354 -> 0 bytes lisp/i18n/qm/controller_es.qm | Bin 1392 -> 0 bytes lisp/i18n/qm/controller_fr.qm | Bin 1406 -> 0 bytes lisp/i18n/qm/controller_it.qm | Bin 1384 -> 0 bytes lisp/i18n/qm/controller_sl_SI.qm | Bin 1429 -> 0 bytes lisp/i18n/qm/gst_backend_cs.qm | Bin 5777 -> 0 bytes lisp/i18n/qm/gst_backend_en.qm | Bin 5277 -> 0 bytes lisp/i18n/qm/gst_backend_es.qm | Bin 5693 -> 0 bytes lisp/i18n/qm/gst_backend_fr.qm | Bin 5785 -> 0 bytes lisp/i18n/qm/gst_backend_it.qm | Bin 5601 -> 0 bytes lisp/i18n/qm/gst_backend_sl_SI.qm | Bin 5558 -> 0 bytes lisp/i18n/qm/lisp_cs.qm | Bin 15682 -> 0 bytes lisp/i18n/qm/lisp_en.qm | Bin 14056 -> 0 bytes lisp/i18n/qm/lisp_es.qm | Bin 15372 -> 0 bytes lisp/i18n/qm/lisp_fr.qm | Bin 15994 -> 0 bytes lisp/i18n/qm/lisp_it.qm | Bin 15466 -> 0 bytes lisp/i18n/qm/lisp_sl_SI.qm | Bin 14915 -> 0 bytes lisp/i18n/qm/media_info_cs.qm | Bin 387 -> 0 bytes lisp/i18n/qm/media_info_en.qm | Bin 349 -> 0 bytes lisp/i18n/qm/media_info_es.qm | Bin 413 -> 0 bytes lisp/i18n/qm/media_info_fr.qm | Bin 373 -> 0 bytes lisp/i18n/qm/media_info_it.qm | Bin 413 -> 0 bytes lisp/i18n/qm/media_info_sl_SI.qm | Bin 414 -> 0 bytes lisp/i18n/qm/midi_cs.qm | Bin 341 -> 0 bytes lisp/i18n/qm/midi_en.qm | Bin 333 -> 0 bytes lisp/i18n/qm/midi_es.qm | Bin 353 -> 0 bytes lisp/i18n/qm/midi_fr.qm | Bin 359 -> 0 bytes lisp/i18n/qm/midi_it.qm | Bin 363 -> 0 bytes lisp/i18n/qm/midi_sl_SI.qm | Bin 342 -> 0 bytes lisp/i18n/qm/presets_cs.qm | Bin 2587 -> 0 bytes lisp/i18n/qm/presets_en.qm | Bin 2329 -> 0 bytes lisp/i18n/qm/presets_es.qm | Bin 2637 -> 0 bytes lisp/i18n/qm/presets_fr.qm | Bin 2797 -> 0 bytes lisp/i18n/qm/presets_it.qm | Bin 2551 -> 0 bytes lisp/i18n/qm/presets_sl_SI.qm | Bin 2620 -> 0 bytes lisp/i18n/qm/replay_gain_cs.qm | Bin 953 -> 0 bytes lisp/i18n/qm/replay_gain_en.qm | Bin 861 -> 0 bytes lisp/i18n/qm/replay_gain_es.qm | Bin 941 -> 0 bytes lisp/i18n/qm/replay_gain_fr.qm | Bin 979 -> 0 bytes lisp/i18n/qm/replay_gain_it.qm | Bin 893 -> 0 bytes lisp/i18n/qm/replay_gain_sl_SI.qm | Bin 936 -> 0 bytes lisp/i18n/qm/synchronizer_cs.qm | Bin 1455 -> 0 bytes lisp/i18n/qm/synchronizer_en.qm | Bin 1337 -> 0 bytes lisp/i18n/qm/synchronizer_es.qm | Bin 1425 -> 0 bytes lisp/i18n/qm/synchronizer_fr.qm | Bin 1495 -> 0 bytes lisp/i18n/qm/synchronizer_it.qm | Bin 1407 -> 0 bytes lisp/i18n/qm/synchronizer_sl_SI.qm | Bin 1416 -> 0 bytes lisp/i18n/qm/timecode_cs.qm | Bin 1673 -> 0 bytes lisp/i18n/qm/timecode_en.qm | Bin 1619 -> 0 bytes lisp/i18n/qm/timecode_es.qm | Bin 1859 -> 0 bytes lisp/i18n/qm/timecode_fr.qm | Bin 1835 -> 0 bytes lisp/i18n/qm/timecode_it.qm | Bin 1705 -> 0 bytes lisp/i18n/qm/timecode_sl_SI.qm | Bin 1750 -> 0 bytes lisp/i18n/qm/triggers_cs.qm | Bin 689 -> 0 bytes lisp/i18n/qm/triggers_en.qm | Bin 637 -> 0 bytes lisp/i18n/qm/triggers_es.qm | Bin 675 -> 0 bytes lisp/i18n/qm/triggers_fr.qm | Bin 663 -> 0 bytes lisp/i18n/qm/triggers_it.qm | Bin 651 -> 0 bytes lisp/i18n/qm/triggers_sl_SI.qm | Bin 668 -> 0 bytes lisp/i18n/ts/en/action_cues.ts | 163 +- lisp/i18n/ts/en/cart_layout.ts | 42 +- lisp/i18n/ts/en/controller.ts | 26 +- lisp/i18n/ts/en/gst_backend.ts | 6 +- lisp/i18n/ts/en/lisp.ts | 2834 +++++++++++++++++++++---- lisp/i18n/ts/en/list_layout.ts | 54 +- lisp/i18n/ts/en/media_info.ts | 10 +- lisp/i18n/ts/en/midi.ts | 21 + lisp/i18n/ts/en/osc.ts | 82 + lisp/i18n/ts/en/presets.ts | 46 +- lisp/main.py | 17 +- lisp/plugins/__init__.py | 3 +- lisp/ui/settings/app_pages/general.py | 23 +- lisp/ui/settings/app_pages/layouts.py | 24 +- lisp/ui/themes/dark/theme.qss | 34 +- lisp/ui/ui_utils.py | 11 +- lisp/ui/widgets/__init__.py | 1 + lisp/ui/widgets/locales.py | 50 + 96 files changed, 2901 insertions(+), 748 deletions(-) delete mode 100644 lisp/i18n/qm/action_cues_cs.qm delete mode 100644 lisp/i18n/qm/action_cues_en.qm delete mode 100644 lisp/i18n/qm/action_cues_es.qm delete mode 100644 lisp/i18n/qm/action_cues_fr.qm delete mode 100644 lisp/i18n/qm/action_cues_it.qm delete mode 100644 lisp/i18n/qm/action_cues_sl_SI.qm create mode 100644 lisp/i18n/qm/base_cs_CZ.qm create mode 100644 lisp/i18n/qm/base_de_DE.qm create mode 100644 lisp/i18n/qm/base_en.qm create mode 100644 lisp/i18n/qm/base_es_ES.qm create mode 100644 lisp/i18n/qm/base_fr_FR.qm create mode 100644 lisp/i18n/qm/base_it_IT.qm create mode 100644 lisp/i18n/qm/base_nl_BE.qm create mode 100644 lisp/i18n/qm/base_nl_NL.qm create mode 100644 lisp/i18n/qm/base_sl_SI.qm delete mode 100644 lisp/i18n/qm/controller_cs.qm delete mode 100644 lisp/i18n/qm/controller_en.qm delete mode 100644 lisp/i18n/qm/controller_es.qm delete mode 100644 lisp/i18n/qm/controller_fr.qm delete mode 100644 lisp/i18n/qm/controller_it.qm delete mode 100644 lisp/i18n/qm/controller_sl_SI.qm delete mode 100644 lisp/i18n/qm/gst_backend_cs.qm delete mode 100644 lisp/i18n/qm/gst_backend_en.qm delete mode 100644 lisp/i18n/qm/gst_backend_es.qm delete mode 100644 lisp/i18n/qm/gst_backend_fr.qm delete mode 100644 lisp/i18n/qm/gst_backend_it.qm delete mode 100644 lisp/i18n/qm/gst_backend_sl_SI.qm delete mode 100644 lisp/i18n/qm/lisp_cs.qm delete mode 100644 lisp/i18n/qm/lisp_en.qm delete mode 100644 lisp/i18n/qm/lisp_es.qm delete mode 100644 lisp/i18n/qm/lisp_fr.qm delete mode 100644 lisp/i18n/qm/lisp_it.qm delete mode 100644 lisp/i18n/qm/lisp_sl_SI.qm delete mode 100644 lisp/i18n/qm/media_info_cs.qm delete mode 100644 lisp/i18n/qm/media_info_en.qm delete mode 100644 lisp/i18n/qm/media_info_es.qm delete mode 100644 lisp/i18n/qm/media_info_fr.qm delete mode 100644 lisp/i18n/qm/media_info_it.qm delete mode 100644 lisp/i18n/qm/media_info_sl_SI.qm delete mode 100644 lisp/i18n/qm/midi_cs.qm delete mode 100644 lisp/i18n/qm/midi_en.qm delete mode 100644 lisp/i18n/qm/midi_es.qm delete mode 100644 lisp/i18n/qm/midi_fr.qm delete mode 100644 lisp/i18n/qm/midi_it.qm delete mode 100644 lisp/i18n/qm/midi_sl_SI.qm delete mode 100644 lisp/i18n/qm/presets_cs.qm delete mode 100644 lisp/i18n/qm/presets_en.qm delete mode 100644 lisp/i18n/qm/presets_es.qm delete mode 100644 lisp/i18n/qm/presets_fr.qm delete mode 100644 lisp/i18n/qm/presets_it.qm delete mode 100644 lisp/i18n/qm/presets_sl_SI.qm delete mode 100644 lisp/i18n/qm/replay_gain_cs.qm delete mode 100644 lisp/i18n/qm/replay_gain_en.qm delete mode 100644 lisp/i18n/qm/replay_gain_es.qm delete mode 100644 lisp/i18n/qm/replay_gain_fr.qm delete mode 100644 lisp/i18n/qm/replay_gain_it.qm delete mode 100644 lisp/i18n/qm/replay_gain_sl_SI.qm delete mode 100644 lisp/i18n/qm/synchronizer_cs.qm delete mode 100644 lisp/i18n/qm/synchronizer_en.qm delete mode 100644 lisp/i18n/qm/synchronizer_es.qm delete mode 100644 lisp/i18n/qm/synchronizer_fr.qm delete mode 100644 lisp/i18n/qm/synchronizer_it.qm delete mode 100644 lisp/i18n/qm/synchronizer_sl_SI.qm delete mode 100644 lisp/i18n/qm/timecode_cs.qm delete mode 100644 lisp/i18n/qm/timecode_en.qm delete mode 100644 lisp/i18n/qm/timecode_es.qm delete mode 100644 lisp/i18n/qm/timecode_fr.qm delete mode 100644 lisp/i18n/qm/timecode_it.qm delete mode 100644 lisp/i18n/qm/timecode_sl_SI.qm delete mode 100644 lisp/i18n/qm/triggers_cs.qm delete mode 100644 lisp/i18n/qm/triggers_en.qm delete mode 100644 lisp/i18n/qm/triggers_es.qm delete mode 100644 lisp/i18n/qm/triggers_fr.qm delete mode 100644 lisp/i18n/qm/triggers_it.qm delete mode 100644 lisp/i18n/qm/triggers_sl_SI.qm create mode 100644 lisp/ui/widgets/locales.py diff --git a/i18n_update.py b/i18n_update.py index 4ee439345..9cb338e8b 100755 --- a/i18n_update.py +++ b/i18n_update.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# # This file is part of Linux Show Player # # Copyright 2019 Francesco Ceruti @@ -17,114 +16,131 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import argparse import os -import re import subprocess import sys +from argparse import ArgumentParser +from pathlib import Path +from typing import Iterable -try: - from os import scandir, getcwd -except ImportError: - from scandir import scandir - - -parser = argparse.ArgumentParser(description="i18n utility for LiSP") -parser.add_argument( - "locales", nargs="*", help="Locales of witch generate translations" -) -parser.add_argument( - "-n", "--noobsolete", help="Discard obsolete strings", action="store_true" -) -parser.add_argument("-q", "--qm", help="Release .qm files", action="store_true") - +TS_DIR = Path("lisp/i18n/ts") +QM_DIR = Path("lisp/i18n/qm") +PYLUPDATE = "pylupdate5" +LRELEASE = "lrelease" -args = parser.parse_args() -# pylupdate command with arguments -PYLUPDATE_CMD = ["pylupdate5"] -if args.noobsolete: - PYLUPDATE_CMD.append("-noobsolete") +def dirs_path(path: str): + for entry in os.scandir(path): + if entry.is_dir(): + yield Path(entry.path) def existing_locales(): - for entry in scandir("lisp/i18n/ts/"): + for entry in os.scandir(TS_DIR): if entry.is_dir(): yield entry.name -# Locales of which generate translations files -LOCALES = args.locales -if not LOCALES: - LOCALES = ["en"] -elif LOCALES == "*": - LOCALES = list(existing_locales()) - print(">>> UPDATE EXISTING:", ", ".join(LOCALES)) - +def source_files(root: Path, extensions: Iterable[str] = ("py",)): + for ext in extensions: + yield from root.glob("**/*.{}".format(ext)) + + +def module_sources(module_path: Path, extensions: Iterable[str] = ("py",)): + return module_path.stem, tuple(source_files(module_path, extensions)) + + +def modules_sources( + modules_path: Iterable[Path], extensions: Iterable[str] = ("py",) +): + for module_path in modules_path: + if not module_path.match("__*__"): + yield module_sources(module_path, extensions) + + +def lupdate( + modules_path: Iterable[Path], + locales: Iterable[str], + options: Iterable[str] = (), + extensions: Iterable[str] = ("py",), +): + ts_files = dict(modules_sources(modules_path, extensions)) + for locale in locales: + locale_path = TS_DIR.joinpath(locale) + locale_path.mkdir(exist_ok=True) + for module_name, sources in ts_files.items(): + subprocess.run( + ( + PYLUPDATE, + *options, + *sources, + "-ts", + locale_path.joinpath(module_name + ".ts"), + ), + stdout=sys.stdout, + stderr=sys.stderr, + ) + + +def lrelease(locales: Iterable[str], options: Iterable[str] = ()): + for locale in locales: + qm_file = QM_DIR.joinpath("base_{}.qm".format(locale)) + locale_path = TS_DIR.joinpath(locale) + ts_files = source_files(locale_path, extensions=("ts",)) + + subprocess.run( + (LRELEASE, *options, *ts_files, "-qm", qm_file), + stdout=sys.stdout, + stderr=sys.stderr, + ) -def search_files(root, exclude=(), extensions=()): - exc_regex = "^(" + "|".join(exclude) + ").*" if exclude else "$^" - ext_regex = ".*\.(" + "|".join(extensions) + ")$" if extensions else ".*" - for path, directories, filenames in os.walk(root): - if re.match(exc_regex, path): - continue +if __name__ == "__main__": + parser = ArgumentParser(description="i18n utility for LiSP") + parser.add_argument( + "locales", + nargs="*", + help="Locales to translate, ignore if --all is present", + ) + parser.add_argument( + "-a", "--all", help="Use existing locales", action="store_true" + ) + parser.add_argument( + "-n", + "--noobsolete", + help="If --qm, discard obsolete strings", + action="store_true", + ) + parser.add_argument( + "-q", "--qm", help="Release .qm files", action="store_true" + ) + parser.add_argument( + "-t", "--ts", help="Update .ts files", action="store_true" + ) + args = parser.parse_args() - path = os.path.relpath(path, root) + # Decide what to do + do_release = args.qm + do_update = args.ts or not do_release - for filename in filenames: - if re.match(ext_regex, filename): - if path: - filename = os.path.join(path, filename) - yield filename + # Decide locales to use + if args.all: + locales = list(existing_locales()) + print(">>> USING LOCALES: ", ", ".join(locales)) + else: + locales = args.locales or ("en",) + if do_update: + print(">>> UPDATING ...") -def create_pro_file(root, exclude=(), extensions=("py",)): - base_name = os.path.basename(os.path.normpath(root)) - back = "../" * (len(root.split("/")) - 1) + options = [] + if args.noobsolete: + options.append("-noobsolete") - translations = "TRANSLATIONS = " - for locale in LOCALES: - translations += os.path.join( - back, "i18n/ts/", locale, base_name + ".ts " + lupdate( + list(dirs_path("lisp/plugins")) + [Path("lisp")], locales, options ) - files = "SOURCES = " + " ".join(search_files(root, exclude, extensions)) - - with open(os.path.join(root, base_name + ".pro"), mode="w") as pro_file: - pro_file.write(translations) - pro_file.write(os.linesep) - pro_file.write(files) - - -def generate_for_submodules(path, qm=False): - modules = [entry.path for entry in scandir(path) if entry.is_dir()] - for module in modules: - if "__pycache__" not in module: - create_pro_file(module) - p_file = os.path.join(module, os.path.basename(module) + ".pro") - if qm: - subprocess.run( - ["lrelease", p_file], stdout=sys.stdout, stderr=sys.stderr - ) - else: - subprocess.run( - PYLUPDATE_CMD + [p_file], - stdout=sys.stdout, - stderr=sys.stderr, - ) - - -print(">>> UPDATE TRANSLATIONS FOR APPLICATION") -create_pro_file("lisp", exclude=("lisp/plugins/",)) -if args.qm: - subprocess.run( - ["lrelease", "lisp/lisp.pro"], stdout=sys.stdout, stderr=sys.stderr - ) -else: - subprocess.run( - PYLUPDATE_CMD + ["lisp/lisp.pro"], stdout=sys.stdout, stderr=sys.stderr - ) - -print(">>> UPDATE TRANSLATIONS FOR PLUGINS") -generate_for_submodules("lisp/plugins", qm=args.qm) + if do_release: + print(">>> RELEASING ...") + lrelease(locales) diff --git a/lisp/__init__.py b/lisp/__init__.py index be0e215d0..2b6d3f6de 100644 --- a/lisp/__init__.py +++ b/lisp/__init__.py @@ -32,11 +32,10 @@ # Application wide "constants" APP_DIR = path.dirname(__file__) +I18N_DIR = path.join(APP_DIR, "i18n", "qm") DEFAULT_APP_CONFIG = path.join(APP_DIR, "default.json") USER_APP_CONFIG = path.join(app_dirs.user_config_dir, "lisp.json") -I18N_PATH = path.join(APP_DIR, "i18n", "qm") - ICON_THEMES_DIR = path.join(APP_DIR, "ui", "icons") ICON_THEME_COMMON = "lisp" diff --git a/lisp/default.json b/lisp/default.json index e9cd69fe8..9b140edae 100644 --- a/lisp/default.json +++ b/lisp/default.json @@ -1,5 +1,5 @@ { - "_version_": "0.6dev.12", + "_version_": "0.6dev.13", "cue": { "fadeAction": 3, "fadeActionType": "Linear", @@ -10,6 +10,7 @@ "theme": "Dark", "icons": "numix" }, + "locale": "", "session": { "minSave": true }, diff --git a/lisp/i18n/qm/action_cues_cs.qm b/lisp/i18n/qm/action_cues_cs.qm deleted file mode 100644 index 29ca1623c66130821f24b0dfd60e50b728bb7efc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3563 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 QV#j&kW$p#AW+0XN7qP6eHvj+t diff --git a/lisp/i18n/qm/action_cues_es.qm b/lisp/i18n/qm/action_cues_es.qm deleted file mode 100644 index 2238b217ae53b44cac5abd883df04eb9294581f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3655 zcmb_eTWl0n82-228+&P+AOxF+9H1&jAZiRz;!A0Z6w-oF2=T!<%pA94XJ_hMD#gf~ zi3vRLfQgAnyud3lQ4$g)JSlhqHJbQfH1SQN#*q4=665!u*_oZ)oe^S8)1E!u^MC*M z-@bDmzi6!akChRsW#E z>z9a1f6?Au*N6&V($eu;SZ8v_@_!TMclKR+V<%B{k@~uU#z@6$|;?$LC?c zlo!Lth$`pv5C4dB#o7GPcb|qo3fIEvLhhjrusd5gvExJh`(&~91MhDYzZrQM=ie*- zbm&c@z7?gnfA+8*D1GqJS48E-a%uP!cs&-bm7C?-7M#x)%DeAh1-s>n@n{}+*TS{@ zS;hSwarR%Ty!iQRuz#TP^|xoRzptvVJb`$Is+afN1N$4*FLa#iyHb0n@ILZ%rhfQI z=JQnj_?~n4{?x#y7jd3$4KDq%5^)_IeChfqcw7kA+GZ2UxY3UjH5wxwKPD}cjg){C z|BmUt>DVQFtdBdECDnF3kh#d_CTV2A%8hOM#|$IGph)8skZd0XQpV9Cnj{OjP5j#M z(Z(pq#Lg1&lT90HbtCBVltiUG@C+TWi z3|pN%V*0=|eVYN}y7u8dhr*L7Sk&C`Q4 z4G&RbBzy{5gpZ@K4`2E2ZKu0;o0f(2d%n~R;XEsR={8MU^Hqm+7h<1kOF;`N3SC9P zSxTZWW6u#q4Z=}-iW?d-<`ZWk&gO=_uA@uO6Vf)MAzm>3IicA?1uZ;Z^8;^7TI?d6 zsu-CEz=F@-aUN*TuxO253G0m33{bCH_a0p|vQeiAiqHq$*2<*N(LXApE6;UX)EJHD6RZ&4a;$$`xN9o(eIVLT-lS zB{n#2nfg3m0-oesl~Y*aW{k*XU6mb&*3yh(pXMvssI?i#Z#k0c2JAo?qU{;uk!MCd z)YhxKNi=boJC2qwBEFn#C~h*&`mBk@MwqVD^tlwMF_bPqvUt+k7eb^L$+yU0*xzEm z^nKHAsB2&-+U!MvV;Q@XH$b|jfV^{;Mi)u8lnzcBxcb_9%AuuWuEEQyGsf;Vt*bVT zLdizDw+AV_oU_!euGH=JxEnKY`OU#~u~FL2cVV5C?+z+4nt`0IgM%#{yglQQSe+wn zPV1PeVwt#Hcn`T&kJtZiZtEfKmU=7jtm#cTI)Rpp-b8#|2H5c&apL)5+4&T06$0OI z>G@*e2WtC8Jv_-zBw++DMnvd=Y&|lgM|>~U0onnCP@I#Rk>8;i;Ul}(=0EFBO8ldA ZFnK8|Ey>oCs6~>B(gpeoCb2J<`v-z^^I8A^ diff --git a/lisp/i18n/qm/action_cues_fr.qm b/lisp/i18n/qm/action_cues_fr.qm deleted file mode 100644 index 2284d3199f7cde8ef1648d6cb7eb63343cec58a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3699 zcmb_eTWl0n82-228+++CB8a3R2dE+jQ3xi67-KE16ev_$2)-DH*+V-rJG0Kr))pgg zCMNJiFd^RJt*`2XMiN9H41yPei6lN4jlNMe;e{uo#P2_6cV;iMlo+$wo^!VU`Ty^~ zegAp%Y1J`rsr+Vu(6X}xAn={0|hk{1_<3L7c) z`Z=P^KQyuH5>e_3&7b%i>z>5%83MA>86%<9v?^{`&^cV!EkA)hQ|cW+#Qd)IU2N0QKYNw3+f zIqwGi?ENzL;*~SF|6uOwPjBJ8C2uZ234i+Y=l9)%``_iiHzC(^q3~YnW5nrn@$gfO z&n}+Ww}Ahj>HGF9yL?#7fkxLFj0#N)N zG6TzXGT0V}UE7xGI9!tnzSD<0&q2P1dy>5HR_{=`+~rD|h9NE^QRwO-+DH4Ll@WO@ z+SB`G#hvYTs~-kjs^DXgL**t@nPIn5F&u>~SKl{0IoN)S1pSJyq&ZqdTfh**kG;4v}Bn)3zjxdBjBW;_5(2nYH#P@LB z4`c-&L>X3W{3gf;4o}5LBjLXdZ0)|U++q2q;gyA{V<6mGP^|@VMuiFm0ta>aZ>yp0 zp;P&hIV*t1vfM7>G(ARRXjDhRYyg*l&0+OaQfv6jXKkk&_!VQ*j_b(~yYxKQ^W!XT zqumI-jgPixAT1m&3uoSGJE~agNrGJH&)vH%+eS+KKpJJ?9ua}`Dwbmes@B?*GeLV) zjH>X#Mc~;p_Qj_SY9Cit|@(ANT)2z;sq<15r!j_ z+rkfwpyqFm^M0Mmoc2C!WmsuMx4G~7qHY%T7R+GXYgBY=uYoSeH{FhJs(hoc$%DIv3zU=(Mq` zH1Sg3LD#NTq!@Ocz;hK5v}jwBnfp`#b^IEHmWx6k0!}G8uAI0VE2A3#YNBTjGxYFw zVao8PWq|%bwz?_VN(bOIx1fba&`qeBkr|N-kt>1uoH>jokAw#9Jm5>Pp(kx4ux910 z4XvVaDB+GXz%g8>?d4HWaIZrSBD-7@qAIzl`bY_5LnK%8U+1~!vvBYlGyFN1>*1CMf@uch>NO-8_spt_)u4kn*7;H z3m=B4i;4l4wlH|hEb%x8KQ?IvH{M2u?q34;Yz$wP>U_C0=7HunEy`3+pmwZz=JDq7 z!)MUCdgA`y9bVMn>s+r)bnEjN(VZyD=;g#O8-4;tFDP5RJ)$S8ddw@wndkJgksq^B zZ>}EU$CN17JhYN9YqI`4Z9Mur;pS*mF%AY=nC|4LQPvOgPWk`LP%-`wHV@Dxd8bh* P&CJuL^cD1IPjBL1*k1p) diff --git a/lisp/i18n/qm/action_cues_it.qm b/lisp/i18n/qm/action_cues_it.qm deleted file mode 100644 index 6ab877b46e56e1c7b3599ef15bfaf5bfcac4ae7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3677 zcmb_eTWB0r82&fAn_c&k1ltA;c{ojLixPuM5h;RQx2dKzZA{YACvm!Sx*0M%6Xp_= zO8X{MTk)lW;014eD=Ju_&=#MxwWTO1_#g^`h@v2f6+|t5|C!yH+1=SxM3&^tnfWi@ zfBVjP@SJ|zm0wQRes5vCIvu`M&e7J_@;K`@X4QpZ`96`BENo?`Be;yhBtx znh_(%hze&i_x%95{!He`dr!b01#5OkHg)eNqFYX7Pwx5%uTS-tzUBR|{a=ng19`ju z$Ahl}@Acf9Ke$d~W0vzW*Jph3om!1mx3K^Ly^xfcNhg^anDqcO_W! zX9~`5h%^0s;ptCbLOg#IzWC}j*!#0sTXzy@E)*~By94X-;%7C;^}SGfD|;4xpDQ1F zobl`^pV)UA-{%Leo`XEy7+n2%9paiDJbG<2?5zfC=^-St^j#VfC92R7>%M|MjL8p$NK|ZvwhI;9WuJ=&trz*rF7in1QAv!>_u-$;T zP0KOs{j;)RFZb4}LcT#7FzIB6m~vIS(aFxX@f-SV9x-f;3GIyc5g)GzT9V z{CNt4Nn7F3$8JgeXKd`Jg`_ji;hlzC(;QvYf_R1PdrQ8T|CH zDEHgLV2YC~h6kIv+%A%AjnEVlWdW+wOr3)#-bHP(VXAJ~jtr4W$FUtZ$=WvB4-AYV zz{IIu2F5r}4|@lN=5L&?sHbkSPlmh283-*9il3>+A>LbbDRfYVg^CGNO22>#-Kt| zr6~lqA|jS(12Pl>nMQN$q^VFZKRvZ`DiQn;x?keAsNlzmB0nePVxrD4?Sclk0?*;R zni7;H@;2w$OLtd-ace^uq{u=cxpA(To^czxCZOe0PHMnh-Zpxnn zDpf(ITC{U(S9&9dStZZik)*k$TNA(!sWiAqLmi=j%)=Cv<~64-Je_@xX)hvRUU55of&L@>du^}jMyeC6!$$X*axS>B zqaMgGwl0wOBz978{4tZq4|pE3s6)&11_lD*J5sAHbQ#`)nQy4_1Vq_l5I$m(?iz!^ zY=}AOd4^S2$HGu+GlK>Q32vYVvBgjLWX&yTFYPWCISwr+*i+vfNhHaiO*D~saEn_Hh)!+J1I6_lH&o$D91QJLBFUsP$W`X@2$ zV3T2Qj(jZMKd4}UmWQ(&ZBe0(9Zx)E zDE{Pu;MNW>F(D3k1Upui@V~-uPQDN>Do-xYMnS2b(lVm$?Z#}{sD^y B_{jhO diff --git a/lisp/i18n/qm/action_cues_sl_SI.qm b/lisp/i18n/qm/action_cues_sl_SI.qm deleted file mode 100644 index 1a081b143f4aaa354bbae32cf9b345dec542e269..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3502 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 Y_{Iyy4BOethrS0W1?uYl;j8!kUqYjBzyJUM literal 0 HcmV?d00001 diff --git a/lisp/i18n/qm/base_de_DE.qm b/lisp/i18n/qm/base_de_DE.qm new file mode 100644 index 0000000000000000000000000000000000000000..b4eb4c6709230a7194bca29a68dfe01965c3f746 GIT binary patch literal 41365 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 OvQ9GBgLHje-TwhmjLfY7 literal 0 HcmV?d00001 diff --git a/lisp/i18n/qm/base_es_ES.qm b/lisp/i18n/qm/base_es_ES.qm new file mode 100644 index 0000000000000000000000000000000000000000..7499459b7598697193ef69a0311e3ece553c30da GIT binary patch literal 44747 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 literal 0 HcmV?d00001 diff --git a/lisp/i18n/qm/base_it_IT.qm b/lisp/i18n/qm/base_it_IT.qm new file mode 100644 index 0000000000000000000000000000000000000000..44b431cbd3c134dfb5052b6e9c1060b4fcf4a003 GIT binary patch literal 43603 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 E0Bm|#^8f$< literal 0 HcmV?d00001 diff --git a/lisp/i18n/qm/base_nl_BE.qm b/lisp/i18n/qm/base_nl_BE.qm new file mode 100644 index 0000000000000000000000000000000000000000..b4eb4c6709230a7194bca29a68dfe01965c3f746 GIT binary patch literal 41365 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 n!V-|3a7}Cb0 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 QhAxAO-_Ny0^E0v79|9gS0RR91 diff --git a/lisp/i18n/qm/controller_es.qm b/lisp/i18n/qm/controller_es.qm deleted file mode 100644 index ffa2f29f740bf55772d6d61bbe6b168268117d84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1392 zcmb7EJBSlO82)p~CYOg24-~-&<6>nITZ==;?Oh~j;^Tsy;+maHR(5B??nKCSYT*fD zCxX}rS_+mHCt4^zz|Ph}Z0tl1I}5*A_BM&9F0u@hnSZ|T|Nd7NpE>*Ae|vEE!=c)X zOAnuaS|Xwiq7#pZvNqA#cQ`MSaqt$=+$I_4A7kGl<0XgvNN)Y|ou}qZ^I9ASGxC8#M=3B9E*05yg(QW<$N>q%O-zNeOz4WM0~x5^NQEh+sd4O^#L;#gcP5bCWI@jm z8b%aM1yiu>p&A8T`~DZc6yXIpyhM4y8rVW`FAaN`nn=C}-|&pZ$fOQ3qcgUlY>haJ-D@e;Z2@PH|BmhRXvY6%SQjw+m!%yzY-VczO#HjZ YnKns8?uwcsRvG;V>}1=3__<8x7nWK$8~^|S diff --git a/lisp/i18n/qm/controller_fr.qm b/lisp/i18n/qm/controller_fr.qm deleted file mode 100644 index 89e4d26d9f03e83bd6d8cab1322585cee5bf9b6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1406 zcma)6J%|%Q82xgapUWj4@lX+ru!gJTjIF{o$sUoQ37QLbitFwqS=r2n-Pwa&sl}Zj zb~d7gprv4GapDzPoM0)~TxDZtqljSPo6WjO?#Gp7c6NT=``$P6zFmH8p8W9b$)k^F zif?Z{d$U_5qD`Vp&xsU9bZrOYWlEjCPc*Ylsp~J$|D@D5)|7H`{nbsZQ`m2%?p42F zw2^vHdyn;}>GgNuOZe%>KQwIfvm0BOr(fBx?cbQcICs8K#J<+6d>|cA%g^23|sl+(mDZ7ZGGvdyXp%UwAPzvD#0JwKpu&ni4-x8Z-=$x=-$e z{EVg#gzqvL`(OCE0MEf;N6Ir=Pv@N3ao8oQA!8e{5gARzHzN;yi8#(gV5Fk&;3-Ay zM{AZXn5&k!Bbe%RI;FTcXa6$}pNLYJj>2PuT%eusB1f{#FqMALdpMwOIMbjyt)Qt? zmQR$}sVkNz7>BRaMt!BO_L=ADUFNCfayc%=SqfPrwxr~Uhc!p%Jcc!Ks`0Qk4PlzG z+mr3#AQm={BD)iKT^^Ae#Z)S$DMK!deY(e^E$o-Y8Tcf^rCY4;tOa2YgD%3JsOZ3A z-vfK8ByUDqO~>1S?Z;WpHUKj0RIa;xc~qF diff --git a/lisp/i18n/qm/controller_it.qm b/lisp/i18n/qm/controller_it.qm deleted file mode 100644 index bdd88728f80f2b1566d1ad3f9cc577757ba465a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1384 zcmah}J%|%Q82xhjyIi8gfQM*=HI-GgRJbO$Mbot1@P;oH4rH}QU!VKX~F?|tw4_I+7;V;}qc>*?b!r^?$m zp1u9HOhlVR7oHO(+eBABVZB7@llO_{UQzn$3(P+#{f^YF~`BH@VGc#2+Rzh^=V|C03X~ z*0ctC=rKR`LHI&|7vONFv}esVO9pm4@LmrK~=KDZ&utK`}zH$(n8ZK}(S@|MTQ30=jUdr1dFWW|g^eJqWxX^f>J3_wbIP z99YY@l?ke-vGV4S8RO8tjN&BtWz@fAxh&}F(|flk*e2bMW}Ru75_D;~P c%e#b5nI){$p}-nrt-=4mZnjONFqcUD1BIbC^Z)<= diff --git a/lisp/i18n/qm/controller_sl_SI.qm b/lisp/i18n/qm/controller_sl_SI.qm deleted file mode 100644 index fbfdef85408eb309ad6d1266814b485ca13dd9c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1429 zcmah}O=}ZT6ur}YwE2=4Pz0;I1lO^kxR7p=lt`?p)dm-W;+T0&Uo)8(<|EK9T#uI&oQCUG;$HO! zcAJTZ^$$3ImRx_2e40!?-mc-Ale+&JaoSG(YW+d{O!{lZoGWg!~t? zotJlU{!JkGGX_MKD%7Pm30ft63IR&0r_y$Xv?lTQNKIONB1v*kjHl@+p@(9c)Sx*$ z4DmirZIW8fZ-6SbtbRdp$1e%Tv6}?JKSOAayhEZsa>WZTU_Ocf*l&| zeh6Mbf1|)jYN3*T2x_hMBSC4jLz02^tOmeX#V{0EA$07BTk~`o2nUX^fwT5FHhGoz zq71J_j-&SjL{&;2$jH<@&V~|{ocykcMZARty~q8TFYg;(l}!x; z^DWzlb)|1yHQ3>B74TiSEp^*qBNKiw$;mOIhjt1i=X|3KHTz$T{rNUfN~);s6eo60 H#p3?}uhv29 diff --git a/lisp/i18n/qm/gst_backend_cs.qm b/lisp/i18n/qm/gst_backend_cs.qm deleted file mode 100644 index 6901b436c2d5ab015e6b9be9244b647e8223d80f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5777 zcmb_gTWlOx8U8o%uJ`VZ?X)I|!bwO|S7~z*DQcsL%HE`@?Od$gI8DMune3d6$2&W- z?96Pjqf{ygR46Y974Z72@BjY$f9Bx#^pVAPFF*bBZChSB_v{aUJx&z)Cq;K%CFm9~hjQ#no=LmoL zhqhsjEcS1_is#vP`k%~g$Ms$PU%C7{Tt5`Qd-xYbv9HFDZvHW@zZjQuz+X2Le?o&E z$>-wVU;GlD-e<%5JOozT#QgqYBdL|Xw`X+dP(_a&} zQp2zAgZ?k1rZZRY{N2>sQy%dCmHO?oL)ibv^i2LGqUa~nUmu3PiAU3Ky!H>G+<($H zUjHc3z$+Q~=|6zauQET4;r+Ol`HMFJ{B7A!6%u&wOtu{P9Q2v<*W^UjT!dZ&PWIx} z2Z8(T>=Q440cM!YzWvmP;GY`_QwSn2=>wrIO5DmNQ@#ZEvOIqX% zU8e<-WMLIFMn|bgV{tZU4F6MOM~h=ZmkpyN`*EEcGaapHSe2r5UBkNIL>TGu?nrbm zQ0GXaidr4A$-^&)T->v8)ljlE+-s=&{QNxb*PHJNyrnhY_usuvRlLdf_+5+ifb9dz z*gE$L%>nbWVM$#WmeBCeu{}!{CEF?+7reUW8nz|MhS@{4L72lO6NbttvRDIg$-G^y z)urRub+*m$n5P@|p?OQI8l{kZcB}-UJyZbT0@Yx5Xt)e^s13X4@Mmk9rOQ?D@1T>g zo(7u(!` z?8#Ype%3a1F{+Q>)0$<)!8Nv6WLmw{J~?1iP<6ZTIyZm1VcPv^2KVU^OkY zDOxZt%=Xx|Uvcz{LMzDy`(6dJ&iMxobWkZFxX)uXBW+5#L+6i4C?cF@QDS-n&6^=x z?4=W6rvYh+ZHvU?qSpZ561>c@%Eig|^&S+Mw!b${ORXZxnrYhQGP`__BTLHd9R&$^ zoXbWh@M;J+qkvu46r)wAJK*j}8oV6Fr?;d7bO;h0{C9y^gOwa$a-qHnnG*5GrN+dw1!NePoKc#<-@-C$jqKYi z#!6Z{RmqVBcR3SlMVQ zs%ZteFp!_stP4_18#QU7=&Kk#;<(48ZfGGnAETKdMn8z8%wgSdLvjWnrzMvks;;<` zrrwtV@fHFzPXRGRI*lIUs}0J5(*1ecvSi6mtx2s^S<#!b!M?D$Qq7%cfsQg&m)$22 zU@PS&4;c>HXm8P5SJ=6;=W@!PO}O5C1j-AS2@jn7V5p%)TyHWA^}GqI=DDI~*WDia zZ3o`XH6o@@c|1;j?I%02vqFm+J=}m{D=5UXw1eVL@J;$}HqG_b=D&KCkNzA)>nhJp8lVkZQ z(bH%q#TC7Q_cc;TXYisEM zg|Du0Ee|l##d*h-)zviPAZ_O-4>6s*GRAGu`dG))>sFXbg3cO*9?hF~<+wdGNSM zd+7k}qDz4>1}^SBuZV3)E`y+waG(g4p2yF4n_mqum`xKOQ03N=MYm zsg6kI0Y@%73Je}8x^;zn!`-fQnz}r5sCB{rs9Zdb!HB&cK1qfjk)cFXf0bux3 z3@5x$X5)O*mYp29!mzCC5sBwK$uR>|AZ2DQ5sFh_hmVZ8NY0uobo(*Ov^8Deplo4? zwCfe&D7*Jyb`F__2_39>f@3Z`2G1*54d~6Yw+040d-F8TGat{IJW1Y1`_$V$Lp~t$ zTRfX{owQl6Wpn1%aj#tF2*89XM(^9l=Y7pHUD41?PpWA%*U@+fF`86BaS5=kV}K6$ zH4zQWl<>vlJBW&uj*_?-&g00!Mgjn`nngEWa~vS7Ma zQQ&d3!YbfIV1UOp6(53_AAkB_Lv#E|+7_sI=LyT47iGIHG`*o&C43X|99iGSjvE4# zdUJRPrPzL&Spmu4o>Y3NaP%8Ce`sKG;jbK#hSJgR77mc75W&N%cQfW)568d#xp{Yu{J4XvVcCc+aVXUze R-oEuoz{MhwTYc;L{tJZ2614yT diff --git a/lisp/i18n/qm/gst_backend_en.qm b/lisp/i18n/qm/gst_backend_en.qm deleted file mode 100644 index e62a9026306c44026ea01d000f5f7a8f2fb7b279..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5277 zcmb_gTWlOx8UCGk*L(M7?F5sW=p=;Jty5x)0wsz-_QrM{=d$wJB{B-?Waq4R@a)WX zE>7K$hZayvTZyM4B+^Qi3YB=~0SKrS1wx}zcnGLSEdp_QC=xsriAq%k-+yLzcE{OW z8;M-@%$b?<{kQM@|9|G>5A?B{|Gf6>uMUsD{Ll+Od3%;<;5~{>-Xa=|Q|uK-+?RlCmy!JDq!bj-B2miu*pQg2q?-QlV!I(QnU%2rS zQT`<|CqGFP|J#7O{320O?7ia;E>Yskd+&dK5V+R|%dbOz=BYvbD<8xAQ-jymehT@& zj$K_oisz?e-~HvYxZjBV?j&$BgaU;#c?L%>y(;XQ+%{ zK{`p=B!em>6YR`5{-w zOKAA#+PGoE!?r}#F#8xYN<|2(!zc%;y7+686FMYSwCfE=x~}c8KSpPK z-LOw>Sz6twM0|2*HyAojbMy$H*>3iC8+fD*e0=VahGyw<2MGrtf&H@y9Dx|tfZFKr zO}gkin^Kf(T0@!JTi1L~trELar9clrxTC_i1k+@P8p#JVM;A+$+(C&HEw@4)dONnv z5$o^h0!+6-3=dAYO?KI?db^N4PAhO<5bqk$Ol8RgerM_KP@CLJ4Uwp>cBp!n+TRR1!fQ{rsD$`w1iD?o_Pb#X3l4b=Pk%@;%gmf8DLc;%Egj(^Uw7h^# zc?Gh4SpqpgvI|3DgJ)#b>n(?4#oM#iu_U%^tk?Ri5p;?!(OC#m+QB|oUGwI)faQk5 z^QSJIl~7o?%_PH$3^#8^?7as*lJL?7(5e_s{ADgwF%P15TuG^PL~(yfY8#@enWkN> zav+bpvZ8|7Rm8y#5gt@G6?7f2ZDM2Ss1zakva*XsZ%Vd}8<_3gR!)UOv=Ee&TayS}3J_ePw4h#%d|CJ;4o;Rsj{67;VQrO(dH!R>8su{=$u@5lDjDG*6u|G zY8`Pkl}uRMO~~hcAUnFNL$iLh%F#n#B_{5j=JTrNo1WOzOkb)VE8o=_g@||+#E_-Q z02JqO6}_(Oxg+7Djjgy9h9tyD7i+fd%J$*7D@uXp6@l3GeARgtPtF@SYR|V0*In0V zz#$thaggdel0(_{o#qFIuFDQ0C-IixezqNhs1vfQ(0&(=_&CZFGiVEFRmB+o456!N zS*(`g6<0=%^Wo4zA3ATDTcT<^Len=jtAcL~zAK%>?D7%FcA6zJLg9Owiu0c4^FDvo zV6w9RxH%3BZU;^GGg$Z?Lk1m*B4$r9N9dfgq^=kj@RCJr;_miLyziXt)Ky=vi_I`8 m=YsqkQJznE`~?JiEA&EanM*3$+-=2e-p? zT->Bl9#T{h3gWF14-q0#fu}x|2cV!-+}cK^4<$u?ND&Bl07MZAH-Ren{xiEX9(#9f zA>!=Lo|!q{`Ty^~o^$j&`tI9*zVZCe_V2y*xtl+Dcao^*pOiRohp6{6lz2rFZGVmi zMoJjJMCUSJCCV-Y*ZjM5{<;4UZM#M1U;i=D;EQzr{lDV9%XDGo+eEpK2iN`+^w_K4 zA{t1OdElc&eak)W+zUkM+uQH^rHgBE`{A$k0{08Ol{c_{{(7(e`4188c&7Kpg&zVh zowzbLg69K?Z@%_C?!T4z#Zlno9}ljBe@Z_1Dd42OpM3KX$T{#<@|{02-reLM-uVXM zU*F(0XH!=- z=#hCT^}X9)#PgZ-r;kiy{f}j|e?NltL`Kg37U*Bkyqwgb?`v6l-^10-_UwBTyxj_} znS6HWm2t>*E?dsO0J-;M@6P&|w%-T0nd#P z=^%c)Bx#BMMHYTbj6C|a`mJMBgA5ivx{`^X^IN|9*2tkMEr(L_`vP}tJmq;yc+VjX zcMQ7MoZ;dr&(=d-4zJP0Jt#C6desa|>cX&uhX1bZTe_&)R?S%Q9nCXrOVkXri{b-R z!ZLLb=_rM4a>C^@CA;2mr0d!aGdDEh>xMmb$ zXcRPa}hEZ7N--88&G@w}emUTH{ za(acw<4M5aC^TKW(C&GoF2$nso|4iMrd*Y#+kMTWNCNBzyqW=*(AHRiR~)A?d@tfY z5;495_GTdI+!_UUkB^B-Br;b_Kj~|x@iYuJIx{n;bR7)ujgQ4FCg8i8ibMlfwmg&B zQ2KExi6Xndw5(Z6Qk0E`G?DO?cOG}W(^5CI80J3c!@lHgf|#Gy4KIe*54;vWKU8(G z*iocZ>s^*5i@7l|imD=TfL8-iLWk}v*_I`%L7<$}sw*36@lZJTA{5~Y31f%zv(5P^ zZLXrBEygYt46btcc2Z=f3>RgxyCB2sxD3j*L|NM?R#EZUgzPTc2srhjlLKaga11J1 z%hA~dqqLHpu^PT78n)wg(Ra85rVBS<$6kw=JL`K}fEv8KH>=Hb#T8l=9YT zQ`V83&uDe2JZu*VStuhyV156VQauku?hNriRg<>D{C^smVq5Hk;FI$fXZu!W=2&-- zQ`pI4JcnEsO1|sab3_7~bBZBDQm@z?>gkI|#g+kdkT^V!>T{Rq_M*+Zf zg?lc}=TUQ=s5GS1JBu{}`vrZ53eZqn-hKo%9qnmIcdl~D^<;fBU5DH1+8#2~gzT!} zt_@Nypi<3*a-|pMW;PS50G4d927|QR~{LwHT&o?iu4gWKv%F9dkD8boLk|_YuvB@^4O6BSI|h+4bQfu%BF4L z3NfFK3Qu3)y=vNy;CID|?}J9$ws?Y$!^+&CN;n*cf3z1GI9~A_iCh?O@QN)3%i59* zQza+IjUA*2Nsqj*pv)s$D2nRVAO^X$fXJ)RXaT6SJ)&|&Ypcr<#hp=nn;UVLo-j?d z5e-V%f@7#05JNoC?EZr65%fbG`dq|$w4}aU+iH%u7NM?XZRB&~{dYt9b7sPZ=7rxitC0WK_AbD z0znI8ujv-|P5QMOTO56&7=3u0ck!BUdSX>GeW`lJVq1y^uuY2==N54(jH`42iW3cG z?Y8sxHcQYF;)4Z@nJYxfmX>YXmC?aLTP$vltY&}3rU(qy9x;8)z_G-!)OEB<*p7_LM-IGe<4%YL zYJm&sWP1{86LCdYz zpDVT8iwoI0(Lxb+@-pG+kPLb*0H-8KDX#KQnfZEp6^ItGj)hqx2 diff --git a/lisp/i18n/qm/gst_backend_fr.qm b/lisp/i18n/qm/gst_backend_fr.qm deleted file mode 100644 index 29f25810d3f2e65f97f7f122e1c67dd1dd8da39e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5785 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 diff --git a/lisp/i18n/qm/gst_backend_it.qm b/lisp/i18n/qm/gst_backend_it.qm deleted file mode 100644 index 1bef5edf2b49eeb61af04333a0a4f22ad579327b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5601 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 Oc<9dft~>jCdjAK+_rRL~ diff --git a/lisp/i18n/qm/lisp_cs.qm b/lisp/i18n/qm/lisp_cs.qm deleted file mode 100644 index 18e03842312364ffb9b7e28565b0487f5b645dfb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15682 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 Y&N3-%{vD71xtn3ab%DTdf;GYa1;%t&t^fc4 diff --git a/lisp/i18n/qm/lisp_en.qm b/lisp/i18n/qm/lisp_en.qm deleted file mode 100644 index 304381649a133bc9725efc7fbed9a086ad300b91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14056 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 KaIX5gy8j34#(wMo diff --git a/lisp/i18n/qm/lisp_es.qm b/lisp/i18n/qm/lisp_es.qm deleted file mode 100644 index 44dccaeb1cb6a69ba50098cd220f8e71225b1c5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15372 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 diff --git a/lisp/i18n/qm/lisp_fr.qm b/lisp/i18n/qm/lisp_fr.qm deleted file mode 100644 index c2b961eaff6202d85683538103cc866c6f0f271c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15994 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 diff --git a/lisp/i18n/qm/lisp_it.qm b/lisp/i18n/qm/lisp_it.qm deleted file mode 100644 index 433628e806da4de5c86f5df2c4a534095cf0466b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15466 zcmcIr3ve9eeg7ogQ+Ik=mS2ikWLcIhO9o>*!2t(ZvTQk$EbC-6C5&Npw|Bbgw0qgR zlWbEmZ4y#w9U4Mih9>1vlj2ZX2<6p;l7^U>#1oz-Ax;BBrkyEmAvBZ6429DE{@=H+ z+uN1pOc;&4(rN$i_y7BR#gnPkXWsehiRUhE`}Xzs{?lu_84Kna+y5A28y^tQ9qlae zLV&Tpb1WEmfw8JF7Tj??V_Q$MVESps`hUiPnIAG1t!F{~7Ch%z@J}CNY|DNY{9C*i zkBDd8`{LO(&Vr}$_e)i_>07|NsgG@XaRZ)7w)Zbi!Yunk?$4`LXmEu|V*X){8f6iFl3v6!sb;d4vlg;;ihOzEL zY~kf?;NuiKbr0w?+{^y++Rrm~(I=}0UrI8zCR~-Ae2KAbg{rSSmx7#ItA5b_AY;;- zRp)L7e@&mQu6i-f*u_^>Z~t-ubnmMkJb5SNf2I1GQ->IPHe&jIFz> z`iUOkyX;HV-%4Kwd7iEQ-lr7CTCT1BZ|IDL9uiM!Z}q#+LawS?sy`Th4E(%av!)vQ zY&~2vxCTOB`qP@BbH8A$_i;Rbjpw^H`Gy9zu6J4PcPuYMcyUllpn1--@Ji|7qM1b^QU&(2pPi)V|_ z-)`~jULASj)^^6i6Ol8gV8`|MMb7-@EEJ_ge(>+W*E|+^{ts6&*4Y+${?_C8`;Q~f z|2>|a*GBuI8^F)W=&ntZc%MZRU0Yy>v(cFl6VdXLR8i1@`EV z9$x_dEssX;nufmnzY=}w>^S`H3(;>+!#^V5jlR$qgkJt7`p%hG84L8(W|ELM@YCAN zAEm%|SUflWxVEtCyCC~w?d@~mqxrVlr;bCexK{hiHGpr9#6l{b;U@8vz8-71y#y}L z#(F~k2EV*5_VKzEfd8_1Rz+ibzlOip?2gS0LcXpWVlxLIch{4#nI!%Gi`e4O3HU)m zJi8x_eX-`(u?+82G#>iPJ@D&>_~y?6Z^uXCoB!-B z#L-Lf%@5((p~NSD5AtvMd3^d)@UzAT~e)8ul5tr-Z_kZ;3cyD9;RQL?^+#5grfP(ls9DnVZ8sLrB9r_OJ7c-t4 zw$|M;u?qUxE}lKl)!lpD5aj()-QS$}3G^=2J#s7LX#8m1>7#c;|4QBI?_B|Z`m4IP zZ`%gFAFE&0_9x)~M19XIufuNp>vw$c6k~1wQUAuP#~|mO_5btM=U}Is8)``po4?SI zdFLkR|CNThhhW!B{-EK3%N_&0V+~I{vK4XE-SEc$;qPrbvLJE}9J6g~l4V(jm6*(Q zmSP%{*eE_x@yQYVoM5x~Y!N?AupHhw$`qzY0LVi4UNtbIm5S16RxKTo60_Q(G?9~! zDtZ`y3-Z5J5s^3>0Jmf>=)W+Pxc&E=d26UQI`iDJtf7*W$2 z30whN&()rUdR4&5ww6Y-NkuIvo#2R4G}^YHFe1Q^AW0crE9E`eYebA>4b3frsTqr*$h0ye6|zNdZaM*1 z1Z0j2C3CJwpGk+~*nBh%BTqW)e&HkfmBM$!xM?U+CeZp%9UX#QlQ8}k(EenMMg~|oD(;eU0`0}43LA7t5IN? z!Y$xuasmpuBz~sUClWBFWQBdg5_`>nu1GMHR4Czxq6Sx2Vf2#n2`}FF8{#{8m$^e| zjIt9^A!nNh4WdcvrnIlI1^%AH9c_4nHfYP~ECZ_heCsI_tnzZ)4N7MNd?5WFb>BTSEbo z#!Hr3{yG&7`{m$-ToTpVZI))Q0lxy76+{!O2SlsC83^i#l+ zHg_Su0;7!xGYUP&Xv4*%se-IR4m?BEg*%7DI@k^DM%G~&crA=7AuaGuRZ7mvYDP)z zlsay>v4ad0w`@LYOjNs>CwqRyUQt1m3V_)n+*X8F47J84{1AvPeiLs)m{_?`kdYr4K|N5CRy|@GP{16 z7ZaMA_*6}mT6UO< z6bXZpiqSGzh~m5#SV|LawF*}=dsfnr%~p(BmZgvlxn!h?U+ms;$a!LjpnxrB+^=M= zCgCMH4cFj9v2sL7mZ;i!nsn4fn+7kTU%M<{I1g($4>Jn(mppw)OEV$1x>T=uma^;+ z!m1R#12G;T59jH#g&l-0Xm>5j3ZFbIkm$jZoWlH)O_oEodO@+R7F?|bx3wIwmhUxm z_=p`S!NHO)rpgGhMVk)J+G@C@Iag89?MAF92(})JO#KiISG1k%E-1+!B2|t;l+lWO zRhe!qlwo2435Ia-#=ERgh%JW~K3Y_|lNIMhGBQ;qva6A3b>uvn!>G%mS&S+QMUqib z@Hwr}9a}N{M(dS%eA7yWDiY13GFo8a!gg0B9ndqfie`Wuw?ONh3~gO<**Pr0X;(rr zHi^ZfqmARb-??nwXsssrro!ON)$1(!M%m*$$Y7wrvzia-eQ=Q}jQgVytQX&dT+k$& z!_Q_JCPyfn;-N@Vo5@Uh+uJrcH90EvN`qKt&+%f+Cw*~BKuV}Hl~9zkIkC;YW(h86 z_f94wam9w}x8qAP_!?Ex#R}%gW_v~(g?g=JDZv?OvJowH@ZCvR0OKVK=7A@ih$ z{zNaSk|;JF%Gie{cpobCFbv~wlzAjt%G|UE6@99|N!BIyjSNpYZGr|5dDiTXJeWvV zFp=PaA@aBc;xx8UuBY*ge)IAio5}OB*lV(w$jdn4!P(J}R+`BveU=-Ym*vAK(=-TB zJQHKo)|%~_Gs?wYNhA=8CXoZo9l3y@Qljv-+07LA@EEKxxTjC(3 zFisk}p#GYQw74BWiWOy@{&kEzmeKi;(>J0@IPdZjeA9XG(t6R|i}3cN550&V(pKaY zS_p{a_zoOfYgnb(2QA^fnlz^z^)$Jd-pt{O_E41TBw#c|wnVG5hpYi_;oA5lScz*8 zxa_!a;K4BOrKCDZgwO39*pJuCT0MYEm_T`r3O=_5xQj7{M} zErvK6@Z#UV;lulK4s=|;D?rR)&d!-(9I?;=yO+w>@bN@E~MLBM>sqUTgizASCOinmvQU}Y~qJI zbj5~R3#4dq66K=tVVp#{NGy4x;I0|1_K}mPdq((n!8eGV2QB4(DFA((vYcwALkeIY zZ)~?N!BBHuF?(8G9ihuFX=8KcmYZ(FSql#fBxG;uaJ$8<;`gq03?B`30r46}B4a*4%`wDmku+ zHhyq%K?f|pxN148bjvVfF%uk8W=j0{GK_Pm3}VXY4M{3-<}EH!*cCZ4r0{u_m*g_2 zINO6Ji`p#A3^s{9x-Jfq%3k)nKxhNTNEJHWz!7V&GH~)NvGK#!)}rM+k9ED?1%Mf_ zhStD(J=DdoHS(e|CTG>@tcoi+h6cKLo^ni<7Fe*0mPKArwX6$~&M&$--X1pk=vo(z zdb~Y~`fOZYvmnA@THP&H6GhhM7IWV8x zdIv59#U4)-d%RH4#mre4w!~KrB(=GF=K*gasvE>rB|7)T=qtR|ZS)Q!VN^~iW`edY z632eb*clmJtIMdH;R|A+K)X0|#;3O5UO3QJ1b2fRck5b)vW29qr^I;-KbbCwYjrO4 zqW(KVfZN;6x%1*#9YuyQc`7L{O;&^z$63Brb7HJ_VDwRG1bK +nvbdyaSqD7Cdh z9d!SLwlyPYZ$;a0r;*rj3HQztNnKI+Mz+$11Vy(n=wcqV_U?((&SH%tDz3JfI`NT! z(!98hNVfBHuGl#4A1>iq5u`Xi(c5~UIo`_(Ef@^QUCQpDT3|7p!8os*y5*1aZka{{ zr=I~}K%5du?{osu`72{<<0co7pjzieRf8BWMpqsX55jx23M z`l&R|F)X3ecA#-Pygdoop;~L~Im~X#6-ggk5{+1T(7#maDc)q>RG)>9GzD#m4ow=Jg2%ufi=&riBU#|SBC+$9ZCY*6Olb^Od9J=&O}mc&wiho|vOq+~R&YXr7$G#jQ9 zW}~edx0?s02u!EpD{nCx7}dnZv__M!vXh~lBed)*GnA2>=Z~ZVo*2L?{p>Sj;V<3r zyG!xB65q5ZA|dI_fik46z1~S&yC`P)5tulmOlNiM=4dRFy8AAbuEeoVI;-OU18|*s z^FDzbL@NfHZ;Ak6G4g$qLNw4~n@T6%6d9tqwl8jr9PDg4*NLOwyoUdgfQ(DGbm=UJ z>z`cDn;*KDU&)VZAN@rnwKxS8iJHvk6m%`08!u2! z`u|OpatceA%NXV8&nKdJ-XTtfD92Dnp@<`|qtKwtVqUwa&2{Ao7S$rk(PHFqir6xl z&CFsMYaXKER6*gLW&9w&p|-nJENUw6*(v2Xt`^y*vTv_(>zffocb%;ZJ-#!i=iAgb p_}vjKpIzy`on+$hC*O$8CvDoOr6@N-*!W*B-mo{rovW*={vT0FqdNcq diff --git a/lisp/i18n/qm/lisp_sl_SI.qm b/lisp/i18n/qm/lisp_sl_SI.qm deleted file mode 100644 index 24a3417ea134ff239db23bc3d7c1f8fea7d4988e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14915 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 ul6-}f%;JKa#7Zn~)D0zNsmhiJp0B`HUdN z91NZec?@X``EaEyAUP~5MHqY;QW;VhG8qyX6fksgfsIwbp;v{$59k~PpkAGNop@1O=Xl5nc1wwxL3YkDhD3s(Yq+}Ks_wgDXQ35au%!aR9jtY(UKFT2z!@#13R}`lhC2CVJ+j2y{ytP%N1t186qF144fJ3YkEMDU{?Zq+}Ks_ogDXQ35at7MDhE)2fenaRU5kqHi`aoIPT$m&%tX(;w0uU8 zatQ`chCGHeu<~4nM20GcOdvZCNh=FTEf$?BICN$*C@}akq%x!cO-h72fD3G`0uHkr z82o@bih-~cXm=vegiP$N0?MR-<#U0qEM_PHdNL8kqe6c93YkC$E0p9bq+}Ks_ogC9d8Lje$HGbC~V1sK?XnANqYD8Gmu$l~-(P0393%uCB> z1SuC|2w=!(NMR@fl1U7CK(d&jgdr2Ikp-j@i#`qP`WO^|CMYoEGNb~{%w))7D21EE z1-4iLhj}IpPCz%LGE@O|7hrP{P`nE0DUfUt&}5+J7^>iA3;E?MWC9(kP?E2Zl384k olURwx%>oQzU^`M^{)Fpi4NJ@^O-0kv0SYZe#{Y^;|5cb-0BVF&SO5S3 diff --git a/lisp/i18n/qm/midi_cs.qm b/lisp/i18n/qm/midi_cs.qm deleted file mode 100644 index 198dd5240890a1524d1a299e2accca124f411b8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 341 zcmcE7ks@*G{hX<16=n7(EZlq7iGhJZfgvFL2#{uA&Djp3`?=39UAQ7 z0|TP~NQ8?ajG>sJgrSt7fCI>7U;|=S&%A=t5_TYq$Jf)vGdQ)RBr`9)m=UC23#k4t zLo!1KLq0&s8>Li3&8vpkeHt8mzbN%*Z~w} LV`Ti#!o&gqn8QpP diff --git a/lisp/i18n/qm/midi_en.qm b/lisp/i18n/qm/midi_en.qm deleted file mode 100644 index 19f905911319d1cd7fc51b544eb4b01e1052124a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 333 zcmcE7ks@*G{hX<16=n7(EZlq7iGhJZfgvFL2#{uA&Djp3d%4dpU9!? z0|O%uNQ8^QlOd0xfT5J3gagQBU;|=S&%A=t5_TYq$Jf)vGdQ)RBr`9)m=UC2gTWW5 z!v%;H7*ZHg8PXUMfqHWoN}&8QhD?TJpm;IdG!c*)3Mr{+iKRIuK)Nh5IkgzO;XDlf zAcvqiht0pV1gCqXkla%Yw6_EZGl4!zM{)r#*agKB0~y8A_q{2fenaRJ@X1mOW1)d9$!xv&*0ROlFYpHVn&c2 zD+U*aOon2H0)~7bnF-XL$xsGnD=_#ncmicTfi3{a76IuLhE#?$AWjCV%ZHmU0y0%0 zB{eOvG^Yeemt`iW7Gt-Lhani~xE!D*$PQ%lFD=38LJ0;(hAf6spzBJ2HWve30d*JL geqONs#ZYg7EEa(IF(5HL)h{tOm9YaT%EZVB0L8sZ;{X5v diff --git a/lisp/i18n/qm/midi_fr.qm b/lisp/i18n/qm/midi_fr.qm deleted file mode 100644 index cdf263f3d3e37de98c2ed804e2c3bdfee14b6223..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 359 zcmcE7ks@*G{hX<16=n7(EZlq7iGhJZfgvFL2#{uA&Djp37jmCnzzn2~c$0#af%KJ3 z1_nkokO&WhD?=Vb2}2RXONLYqpa26K5VLyb6_l2+gXDcZT|9$ROG+~H(u)~EI;a+`X2=H`R06aR;XpS3(h{651o|HApER)RQi1+UMs*$B9lT(7 c6hplSa)1ELuK|haseXyMsf-;!Q6^?a0L|%7NdN!< diff --git a/lisp/i18n/qm/midi_it.qm b/lisp/i18n/qm/midi_it.qm deleted file mode 100644 index 93fedfc459e76c58288dbe6ac44bc02244f20b0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 363 zcmcE7ks@*G{hX<16=n7(EZlq7iGhJZfgvFL2#{uA&Djp37jvIozzn3#c$0#af%LUZ z1_nkgkca?-Cqo`XIztgdDnl_4=5qiA8Q6fB)ibZ4w1geV;_>x#@eEEaDap)BFJ=Vk zvS4sw$ONh`V8{oOnG7XBTn5An48B0~T!2^sCh($ diff --git a/lisp/i18n/qm/midi_sl_SI.qm b/lisp/i18n/qm/midi_sl_SI.qm deleted file mode 100644 index da547e70ffa6aa9e5e6dadae8be99718d18541b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 342 zcmcE7ks@*G{hX<16=n7(EZlq7iGhJZfgvFL2#{uA&Djp3d%4dpU)5SA5wWK67FTI!%q*{w1fT4&X zlc9{EiXoMu1c((Fd>K3$T!2`CA&()Ep#UhK2$WBSnG7@Pge?4D|}gasimX0us|x{StFi89RWY PoQjP96`B64FtY#vZum*j diff --git a/lisp/i18n/qm/presets_cs.qm b/lisp/i18n/qm/presets_cs.qm deleted file mode 100644 index a244fa405255f31321e54e9de3ca70e32a27e342..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2587 zcmb7GOK1~882-~_n`j#q#ka_`_$Vql2!d~IL$!jBVtw9Rvs1fmvb*eV(x#zY$Ukx$X4@8WqW`e=SLQDP|iJM(}j z{zZ!wt`li{v{h?dqMC==P)!=~t=jN`S46d=+Hl)8?2Bmc*B`^a=kd)qfv?%|;Txav z`_;sek!#4~B!;&q5%(c+=aYsf&AWOZzejJ?^}Ym7EK$GyIqIv~U%&OuJ;bH!pLoB3 zJCfYB>iOYJWAxE~Q9JLxDwxR967F?Y+x`{>84Pn1^P-NBg4BR$66pOvYJvV+lo>)-sjjZlG?6|sb!0)SAR9RB$&&%jm zsXM8w3bibw6OgNe(M?rYem_$As`GipkWgC)2t`P!dkoZHV3PMM*CE{hQLCZo`sDWMaKI~&-b=4yTILE*Yx?mP!Su@)0ni`LbbzF zDe6iogxW%2t7h&<0hH+%3SLadaEPMO;MSQ+$J*mOhbBTX%Ss9V@eq{^anlyM6||o! z|85MjN789VBT$$NI)v`^@}&@}sY&uGxI3%5nL1A;t;t$C|<=59LnM*uix3AoMuIIz|nu_q8XwIP-*c uOaI1QTPAPb<3>Yh7(%t;501Dkq~$22_-Wm>;2#wsm&Ko*NaRnfCiWL@4lQ5+ diff --git a/lisp/i18n/qm/presets_en.qm b/lisp/i18n/qm/presets_en.qm deleted file mode 100644 index 5ecca7b0da83015c14a11e4b634a19f3e08e1d71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2329 zcmbtVOK4PA82*zxFU?F8v}z@Uli(w$gd!AtVv`|NA!48zt)b(HVKE*GcEb`Z1#X ziu1bi15xfpHZ%VPQRb)Y_5nfEThFfamSKM|yZY%peDBMyp7MjQZcrZNfQ&%eps8|r)|Ul$6=&r-&+AuHf=jM?wBa6_qzGmnj3Ns0D*+JTsTe5z-hh#`a34%Y zuE(N~NgnWu;+_VUN#q=j0oAis&Y0)vGx?ZZ8u}@KyPyq0u0U1kio!K-5DC@`_!3bS z{}j!U4>uXr%xUegqC?m((Xyo*DZ619DOQO?cUh&**#!|*nVRz@tC@pFT?X z#Ay*s5x8Ox^*^0!ouGgkG-^4wH97fWEfS!Pgg}0R#^^gad_?ws9Dem(9HWUi0>ls( zx-)N#hIQU(*haCn8AYJ09OsD*FD(zJg4mxR&`AkFQ8fiyo8-eLiQu7Ik$91^9~P1)UG?f&1?^j%imu<5z!{;tm62++856?&xBp~eE#!c?PX zwAf`+pb1FECuM7qnecp-O8h4{r+ZL$BBKSP2Q66!>sY)BC?U-zLP*z|O$ZSQwlL=h zoOzJ5^26CRt1$@fPCX>Xh4p;HL@a{Vj7SAhq?7HYw`RGMrms_{w{*;@j?S`neP$-b z-I_8UrzxY@cv3JFH!ypxh6Zw^6|N_W!m|(i$n=*$W`(W>z8|whL-F`xZh*n!H5gy? zrIPztG|$C?@D)FnqTiCBZ)(Is_BA235>m$fUB>MuY&A~Gph(kjxEcpxIdapy3K#$7 zbkZZ7{zqp9XvT`qO^#s8)jy*Z=eCAyigP{xpi#@AKSu{zFjwBV49dhqNVXHpMDGZk@9 lwc4yMxi^$bLqDAZn}F1f+$~Q2oGU^oxy#@ZF5ljsp10Sb<2wKV diff --git a/lisp/i18n/qm/presets_es.qm b/lisp/i18n/qm/presets_es.qm deleted file mode 100644 index 959f1a6ca4e66994abec6a6ec2c3eb99c162f807..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2637 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 diff --git a/lisp/i18n/qm/presets_sl_SI.qm b/lisp/i18n/qm/presets_sl_SI.qm deleted file mode 100644 index 9f6a6e1f040fe8e5db39da3b2d975150a825680d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2620 zcmbVO&ud&&6h4!gmzhp8MpCr3gmNdgI$+GAR4BC&(==*}+R(IgrSx{*t+{b#-f-WW zNs|zvyB4w#vA8LMST};`!h%20cA*DLpcMhDw zx?hV=y$5{fYH{QGANc>%(#zN0MxHCBjl&hh-7bCbyMsIJcjvsCp40eoJP*LV zbe4Pys7sOzX%cuc_KjEKgu}SXvqs54iP?@S@~e5Hd==z4yhg7eut8q#O&hFfl`a54 zQj1g{m`cDNdC8Nm@LIxG4OI`6%dq}`am#d^JaU1vltkudT$k4s!~DK2;35j(L`^I= zcxvLgv#Fz?zYIKci2@ySRNKb5^fbzqHcFy@ZTV1i1S=+Jjn-)*DWWzjTP-gT^^g)N z>K!F68m}pWMeU2WrRIoOTTfMbXh5Z$Kg`%b1zuEL7f-9Uim4>;o+z)n)b!MG&%yi3(?OqGXVJ~=vYZ@{g*l9+z^rGpo91?R)MJAc5;7?!*48IdEuJ2bJWQq` z0A;CULI0m39*c@FswJB$vy4F;__P?wpH-B_fx-@K&4Dkw*~y>Vl7n#mvXghIH<7!d zj{Ul9Mg7`&+%#unbOwrSqvF^fdH^L%un1P#)*UuQmEkTtl`iQ-$%Q#r2LrcM0$>XP;Ye^Pi|WtNQ&)O4zhCqO1|=Y97SrQrEX zf$huasbAEw3{wCP(m+XO5YM2hq<1%(!5E8?+8jZi&=5*qQ~$w_v!tCQKVy9sU%J&IRL zJqTV3J&NE(y@)pvp$88hJp@mJBH|yQZzg2bW+m**e3_m1Yrb8570kc?{^ZfY;=-Go zd#^v$09^HcE^Y(fz0%I-hpbV0D$j7r%DeX~oSz^0xa)I&Zsz5?eeOQ1yj}asxmA5} z@RoepS~BmLBbG>WsLExj z+dVB)RdntgHYhPBxr9bYDG?>|d@gti0cxz8v{uJ*jytyz#T}``fwBshg4%Ks{sr8i zE^D{#kcSTa4pj;yE|$ba)N(2^`1Y4`oM^;RTO~QAs@MT|YlG>l9U@#;UsqueHG$Cv-VEKmMLIto)X z8}-6KPgB43lydAlXY46Yx+rZQ+3V}&ZYrB9QK^tx7l}Vxgf-EhhcRbInB$%-0-m>@ q0eexoKI-!thPcbhrROT~vl9@y`|^L|a0rhWtJp3q_d diff --git a/lisp/i18n/qm/replay_gain_en.qm b/lisp/i18n/qm/replay_gain_en.qm deleted file mode 100644 index 43d745f193cbccd0055853432a4e335a85ba9e95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 861 zcmZ`%F-ROi6#aW*;$0yqA%t+n7!XbfD})p&Qp6KH3z6frO6A<{R|jTikDa~ZEK-KF z31=bL2zDl|+DN)21g=P7X=N)|m_B)Pr|ad|FwFmdcIUtU{+s{$G<@>&dVjY!Q~tVf zaCT88qGj)9<{gpuw(#llBhFO#EN9{DSoeJq{>%7z*N6Wzaq{C3f1AZ`Z?51grQ_Zn z^1hdTExo|`NWuIb!_8N;KrJjy9TF6ixs6i9f-Eu4OaY~^A-+U?uSiZHnI2%57g{ad z5wU?uV_{iyka8#@4wZa}3#A6PwCvQRGM+0rO^YM}8&DfoI_M0nxF~hd)-vV1%xAO; zS_w$Fd__ATBarW?RzR;xa~aaqYMew;s~vW%Dq(dl53xdZ;Kks=Xx0ML&=$}(5xK@K zV&AO{_fb8G1GZL~EwQOITjBeD9`s9U0Kh3Lp=}M;feV_)9&N*kpg_Z9WuD%PCg#Om zFEUlJ$t(8!KgEW%5wwPGSB5ltJXB;Ll31&+pW`Z ze=HPootQuh3rnk1f?#o-m2dXQ=Fk-xW`_BH=Kc5H{Dq_N#>vmuFHfhZK0bc) z;ady9syCS41-xgq{j)uuQG2IubK9#QJa2G69{YOWGkP9bfFO;=PEo2V=;45#6#Y9v5Z;E8E_cp zBSu4aZ=BG$#5uK;B(^Vc#3hd<(^dz$4yCI@qb~D8v!fHM`YXyro+ESx%LK6m>=iW9 z5~{JRY|yhR*H!Oku+G9M>ABYiJKUWdM{|~>T9_-5uCB*vS6Q>89jDo_HCKh`qfLaE zOf-cmigVH&V#?SSYiv8IjQDPp=-1k59Ox`F(NlS1tj@&u{i*`@&>^&QU!ji`u7*Vf z^PGQ+aSjgUB4D`X}HEwrtJwegm~i$z=cl diff --git a/lisp/i18n/qm/replay_gain_fr.qm b/lisp/i18n/qm/replay_gain_fr.qm deleted file mode 100644 index b4c4b44be7f286504e98bbe83a8e4c18aa950615..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 979 zcmah{F>4e-7=3p~G120eMrL=<-MJzbmcdp% z3n5Lw(pqgqD-k6{3QIw-IuI=U1HRe4+Y4bumYLaa=9~Av@6EgUF1YyR_x_8+R`bKX zgZJOt0Pa-&v>pSMLG{_sr;Jg3Ew6ETUK{MyxqhAcHt=|UdHU_=S3Lb#|8(mYSKD}V z_>$|H#?i_Rpw`ly9@Kc_F4mzSkm5Qddi)GD9-=?mRs1vj4>=-OW=ie|#zO?ml-b>3 zmgDnWBW7NN;WWWzQ2z#p)~rDay88c?YT0< z+gKrvj~&iAmJ>m6CuVI483b{ie3p@9mSr@N$^*ZG^Fk+yeU+vKBBUCc=t&G45De9Y?2x4JrWh+?t5BTkd+dFmSu`}=OoB6#r-^|t5>Df;|pB^49Exx<` z?CrM>fa}q(r3XNCtMT~zBgSaFkQX>TY#!{jIKMgZ^&n<`cIMT`=gj7<57&Nhj@vJf zp78(Y_Lt4ejNfaZIp5@t8(2q9GtfvNA>d#cS$dMQfuiS8FiL2;iXF!?Gh%F!G3TLX zh{H*Ki|cuAhQekXycJFvm3WnkzDl(`#Rcu2%8eXuNu5Py6LZ+8iX~NPS%X>|(j)^T zy$bC*j~%Wr(69DtHZ!7Ewk@!N6dkPAq|-aL=u4wVs_bYb?W~4~=g=k3@|cP;IFd4Q zN-5&fYTA6au!(X`XZOTiZIlyn9EX5c&?Bl;S7L}Qj+#h;HGZMy$9hh*T$o5D*2MS} z(rcTA3SRn8!HFgUyFBIgls0FkN*s$LcDvg_ffg!!dHbGLLOlkRZ)`@a(wI<# z7yluI-jc3qbH}vt8ww$pDdm8#Kvj1qcw?H4<$v7d6!7N#Ldn#LY|y`>iob#Te2$GM F`VC$HzHa~k diff --git a/lisp/i18n/qm/replay_gain_sl_SI.qm b/lisp/i18n/qm/replay_gain_sl_SI.qm deleted file mode 100644 index 555373223569272828481d527175b11420918f08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 936 zcmZvaze^lJ6vyA*<%fGh21E}{dgpE@aW=C%%g)|`LQ+cF z1T6w-0+y+xjo66jSp7*727^JVX>K6SkD z_2_4lh?cy|`g<0LLf8^kFA7A(VlZD@y+qLg! zpOODtJAeKJ>q|*govUI;lU`Ae1SyJ$Ba+0(Ac{#NN)R#Rwy8}S;so&~GBI|9h$Fim z0fz2iOg&9gwZ(LTSFV{6N_27&vRH(Y-^CBLHIXX5wa8`S+2UjL0%(A}QR)`Oq7vpm z`hTY<6=7?q+M5ZI)dlJ{UmXE9fDTUc$4usP%62GjyaQwI&2 z|I8T75Os3obL8J*FbeEb(jT@m-4VvfF zr(pBe_gRdaZE{GnzVov5z1py?XQ3IeHR(6!YoPm9VJJy4}qU^z66Gr5WE9H{>$VnK!mAWL|>yrj|&xM=tHi~n! omt8@c?NuTpuOTltGr>c{l5X4<*-gj?TwTBJUDYdB)74x50JAp7x&QzG diff --git a/lisp/i18n/qm/synchronizer_cs.qm b/lisp/i18n/qm/synchronizer_cs.qm deleted file mode 100644 index 4d4b2678c1228ad3edb277d8be23df51e8d4b7a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1455 zcmcIkKWGzC9R4o3B*mm@wJL>nI5SzP!9S3ph!A@Y75@Z-g>JsyJ(6qUyKr}jUIvlk z;NT#Fh~VNVE)EVIDlSffTUWt9Q^C-oi;L**F3BbJa@iy#FYoTX@ArM*?|bjUmOlGw z@8z@4C+0uge)ay#B>?wu^~E|MosaFQaTaGAKfa1r8f*!h=ZCH&xVii`#aJ{%6as&`O+Js=?}JMe=sd;tCAuJ`^;h zH5h2X#0FuA0O4@4ZgHl&ie?JIHHYg_Sc|w$JL_oEZy!$wgX7JJnF&Xl!-mA@eJ#MB zT4q9+j>2?ZX%M+6^c-$r-RIUKo?*-y+yFDH8n$M(xaA{8ia&&9RN)frz_7$+%Q7QL z6j1i1FtkWct0&GL*oNJZ*kNMFuQ9I@A`1C^?3Vr=Dl{CpqrE~lulsQx>(7~?)HNx69fG#I`96?njr8WdPvF|#yf{q%M?bNTmC8hkFg3D%i|Vm_(sRHdo(G<)Fgw1vETw}((EKwP{KkW zC{wPQcNN!c_EQjso~Gl$DX|^@4iM-Bo2LevnPZq@v=2F~`eeNOV-}~mxwIg@&^YP1P zU(c66-hKVy`wb$xPd8uOBT8LLAKMwk+r5}+Sr5v~MAjYa=#PzgtJd#tzYu@SJg*h; zeJ*PqE@A$s?0fqyQM!_Q^T_Pe@Tvv-A$eB5fP&#!XwC!*+e z#1MFDjQxyPsaSEPE+d2lwV9y4;L8`x%j zlJAVRv2M5Ty3u2l#rrtq{oJ{BuPbzPUjBP4jHXzm2DM=M0fdH# zov7W1w{5o3QU@$lox$FXLMniSsN4a{w7SIk>03OhY5%nQ~=2@m09I+gkhUz;MF diff --git a/lisp/i18n/qm/synchronizer_es.qm b/lisp/i18n/qm/synchronizer_es.qm deleted file mode 100644 index 9d6382b35aba84807c4036e7e7655154e529e5c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1425 zcmcIkO=uHA82$3oq?np$t3@Qp*o#nVQ0XaDA-Yux7J@m|lhfTHUD(W&-HnIH? z7C+p1`TpxwBDzmEpKlT+E+_Y`6nfR|Q+w$L#YLj@yY%jXg?%gOU!Q-V|2_4rlE=Kv zq+cyy|Ch|3^@b>UJ^T8xUZ?4|)egQN=Gxz{5T$PCI_IBY|GA^~+z#e{7A{(sux-c1 zX?O`l14oO5B z%pq8ldtS?^$|veN56`N=c7Wo=F(Rz2t{k>j9p}ph22aSgBOfy3ObtxaBM9wuNu*-+wEn;ZUb(-B=K!Ub{6Lpm3PBJ)%BS+fw&f zOEvpW55p9xW*oN7rI8r70r&5Jrx0h#Ls!Ys+0O#2M*&3itu9AYavNP1%ni!JryoJh HWFqkw2fH}V diff --git a/lisp/i18n/qm/synchronizer_fr.qm b/lisp/i18n/qm/synchronizer_fr.qm deleted file mode 100644 index 171e069c5e2864abd95b56a6eeaad16428f503a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1495 zcmbVLJ!n%=82$2JNK9I)A`;5Aiy{~>gF`5V=qplC3=Jr5-sasVPhRdT?*n|GErnZxqsmi>TeyZ%s<9?E_2byqin|_ ziYz3F zG?v0!gE|4mT+w!wG~1%$Xss#@Wllm{qE)KXCU97XMeAe(O+edOl3;PD31kJO0&mPD zP%Tjj?EzoriqzU^2x*#P6LKf9o%6M`>L#^-z$n6J^agPU+(;XiS8-a(^>HVflA;wr zv7JqF16i>Z*L5a}DbQ8aduY${ZRDo;JkX$j8Znt`0)~^US2?Edh-iW1r<1O)%fno*Jwxghcod>apky*o3FQ$L2s56L`b`=OueO@1hg z+!yD3QV4Q{6?Ao?mr6@lyKRYFF30|*_{P>;M_XIU_3!H?#H<=cPzVVKcFye%xq;2hxtp9Og@x4> z79xlUHd-k*78X}1_JW-V7J?{c1zSo&s?+co4il?=b6&Uzw_kb zhZ76$ZajVaNSrAHU=Nd+JfG zfc3_7_Qgr;|1|x^c}0|7&b_>6-qXzM`YxXD~0aQo)g-t2aepaS|;!Kr0XwKz1x*Y9$DC zBJ~-VW^C#}KnMvkCdX{lNEsD2f0ZlV60whyWmTv);@T)l8xCueVid%Ph-wa0 zUGDom>mr}%<2<@nhinfh`}HGd62n&ONqsnF`_P6KO`Fz=4(1(e4*S=dNZp}^XrnC* z0w-H=b_{wL7RU{)T!FAl8(a>;IJ8HH6ru2KWG6aMNlFrEB%B?zGytW>ylh5P%dx$T zN!1z@!^-8d@oLtbw)H?O*%yI5YyUkq;}pl~8YG9qU<`f+=4hbBk>uIty1vDFy1Uoa zaTrETTe)kGg~HtYRd{4}oU~$GCGSYB_OHAI0VZ1}8OFmw;(pRnBr1P>pohtDX&hq< Iex_5YzcZ9GcmMzZ diff --git a/lisp/i18n/qm/synchronizer_sl_SI.qm b/lisp/i18n/qm/synchronizer_sl_SI.qm deleted file mode 100644 index 515323117f22aef0411317389d8478e05c7301e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1416 zcmcIkKWGzC82>K0CY7X#(powMxrl>^4Yr6#kwWww3jPU=4m$aI_ed_iych1$XaaU{ za&QnqL~zhW5EloB4vM?rrf!1h;vjVB;#Pm}&R$~vIY}t*>&tt;@Av0>IkRQYz2AQF z@IyZL?#8pXU(OQIExPb{jYwOFZJTkdJY3^X^*gy^MEY}m>z9dhr}Up6zhnJ6@u--= z{`G|Z;xNvCPP{Q+5yeg>U*1#S)8y;Y4*qYYI^ULw;>)R>`TIDZowBAMVE$~i5U6hlBCw8f=J*mS+s4B`+gF2rrZ`*iF40%m@6xqx;3NSCQb%5)GbsU zV2J4UfuieiX15JX3c;-=w@2ZeKzD^x2aboTj@m|T1FwnNN9_{VHc5~}>%^iS*>g+? z*)*7K8x4>f)xn_4KwAg8L$x7@JVMNvj&I2p_X50;(nG@PB{L5Z5*KHxx8f$)i19Ne2UM^r)~MxjtpUZvGrt9nv69qt7$ z?Z1xBV7o&IRy`HWHXfadrHNoRQOeaQvk~gu8HdUtZhI$99jz_213lHk1GW diff --git a/lisp/i18n/qm/timecode_cs.qm b/lisp/i18n/qm/timecode_cs.qm deleted file mode 100644 index 1a7a9830a6953dd8e845f642ff650accb2687f9a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1673 zcmb7EJ7^S96upzrZVa2Sius@-FO5Yo35Zz46j?t@gs=(8Dx}icoyYE&**D9~n?M$^ zmLMttI}0sr1hMsliddXn*iKo`!`|tPe zejGaT`pScspU)6cl}_L9!jlRMr1te{xtX%`va**y|-}xVd`7y4bETntX%tv^UmJxs|yE|z|)ntE4=7hJc&hmo=a(ql0R#fc+RYFLj@S6 zoDLxIULsC{Hi;QManKDaBX~V91##&8QD_#0{W}IHhiZt=K$eSy9U#aCq(9T|J8KHz zO6GH6FbOv4Jrf=bN6H&akrSGhe#ZDz|nCbAo0yk1l?+ zTO|jO40$-sp%@v5?>{fhg2P$HlQ}L~M2_YpT0}(YoR2qyQPo%g_mDO7llF5qNA*Cc5jVr`=m)ux-emAG2zN?p{v9J6_Bj#nMC$l27)#kqM_ zSZ1cq!7eiCnZ-pWf=YpVtrHriEC}-ue!Zz7n&twH$DlDYEbDp|Q;xR|zk((R4H*rE z^}PlV>fF(PSY3tg;ZTuiAb3ElTdOxy$YJD*qV|T5TWS>PQCIiRP@Mi`-VFy%Q!BG& zH(-KuOd@qb233$58AXfBeIHFS)Vkb$bY&=*nyAW{jdG>(dx~-c!!KbNzdG7Fegm0Z Bh7te( diff --git a/lisp/i18n/qm/timecode_en.qm b/lisp/i18n/qm/timecode_en.qm deleted file mode 100644 index 101f6c52c34adcc53a1f99ac9f9a1d7444b15ccc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1619 zcma)6J!lj`82v6WKZYbkF?xu|#KIz&#KJ26o+06ikc7L7r8svx>@B&SU3O-R0}D$L zu?VKK&_WO!TTw)em5SCjc4{lZUVJl`*_bmMWx3m(zxTcGeeatK&%NUN9}hM@PMmms zyeZ8|MiF!8sk6l|Q>V4Axa{VoqFAhBW@DpP(@L~bu zp6i3}W`=O;^1*X6pNI;Nhdvj-;k`Bdbo3tfZw!C;-(daz$eo+NFy0!i-8Ji%0tDIY z#mpGhD4>u8H7FvF#2_ZAAHTs`5Q;|Ri2<{%DzpxyuU5D(mUt)%=J2WR!^EfxD2*o| zNj|9z?vR6H4k{malIDoxyaXi;Oa)8{a?IlXL6)X7{P8(1Wu%!B(qnp?`m~9WC1N=< zTBb!R0jr4^%A#w;iSY=;*g+gyUM#UDS4`|Y&73e+4>xckS$bO0{ zAgYkNK{FMLkW4=9Vma`eQx&12R;q(Y{s-~NOzbX~Axxf8#*OSwhTUdyv%AEpWibL< zPG_0~Nf`@ujL49#F_K2LOpT0{nzm-n4jxtnI>CsrV}eP;CQ!^+D@mn953ngXf+w2k zreJ2-<06ctwa3_=6pv&SI|MYS?R3VF!2qI8ResqPZLwmMilbAF*UA80#cUPdb!y|E z`!fu0{L|=3Ws3CjGp)Hy}4i%o=yu)WTdauqRxfV)vBx=rA!Keo5pO=X$;|9#+ diff --git a/lisp/i18n/qm/timecode_es.qm b/lisp/i18n/qm/timecode_es.qm deleted file mode 100644 index a6ee3f443351be892ef91c58c9511e5ec66b4233..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1859 zcmb7EJxo(k6g~whNPwUTN(>>F#5kBB3xh#oEQHE{(3I-T>wDMNCw=!defODQ92|`? z4kT_4va*=CXf(zcCowv^IGFe|n%GGP<9BZR9<5M*h8FHm&-Zh_d#0Z|!&^TeJ^U~> z@_J?C)#n)^s?deUL-)4%6zg&Nd<15`y-q)~S>wb~N zx?`*7-9#_We;l2h_(+uc-TP_y8}={vZ4NxZ{bzmO-8V#Ch5mcD|KfaNpl~1OsXHD( zo_6AfpjoPsLms&lVC9g+cL8fjK6nK^xI}4udJ3K|?ZA=USPU*ptv%(2Im?xcmM>FW zY?3l`gCYt^N#J%F?2t8ydP5V9Ew{pDa@N-=gXv#0||_N(EYk zQaM65gq~ykM7B|QnwDq*Dh1TBQ6=EiO=HChwmD6f4K6KYM9B(8sDnyHI=d9Sh+;PE z^62KlC<877X&MGd(n)tt%qgo_mLgNti&Bf&O5h;21WMs@(_s0<%nP*F+Q{zlnc zIdVPKzFKy_7&TmC@@0@`*YXe$qKk}b1GUzKJ3Q~XrSZHBgL0(3Kpj93J9*gfA>SZk zTAD~6;-LyU|XMSq}7VrLszV`agGRoseQvSt(n#6!S}0^UI>RCM?6wvxTl&_Nq{kUzFAMRL@h^5aH=iZvYQEwtKJHRH0u< zED|q8HmLfRo@x)aNgI?iUU1A0HIQ(>>2+hJK+`t9cqTuBeZlT^IOM`r_Ad69(ReI{ zIKyMYDjrrmnwpc?cy2V7$h%Ys>cj890V39dNGK^W75RdWDzH9|o(YKzLp;#2_I8Y7 YRPAhgA#Qs@p|7xSEAL_;ccfDP0L9pyo&W#< diff --git a/lisp/i18n/qm/timecode_fr.qm b/lisp/i18n/qm/timecode_fr.qm deleted file mode 100644 index 0b8395c79b7734daf932041eedda204b3efacdcf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1835 zcmb7EL1+^}6#X$at)(qhtQL_n1yK;2B8W&WVp^<~mbN6R2l3!0nI?;y*|<9^RK&B0 zcqn)g4|?^W2QQ+Ch$kz06TEm+F?#SIUd8ukyW2*(wjPq*`7`_8`|tnxqfaXR%Rlbk zeml7L#fAINKaLSmgO1Gnr&ej@(8 z{pkeOTb4Us4|ih!{k9{+?}$>rIzRM(MtrL4LC-Cmf86z@`jV*aSoe*qzp;O~r+5?l zscSY!?xb;I4~>&c6_O-KlC5 z6vG^)IkNDX1%R&rVgMhIA#iy^itQ1Fh~SgCrs>RzOdlLJmB=yjD|7%)MVG<0h&K=X z0oH;N`6(MG**9(}Iw4nx$#V0o6BrKC%@icaOf|y%{6+&Q9k< z&GLn>-9|%J)&Uy@P#GWD5X}_`6G@gUaNfiD8JyNJ(XLa%U4gXOiq)8;D~Lo5%yC$cNUNrUtjYETH>ejVh5ILl&{vkHg|11#+Z9XJU1gfA zcU_@7sQ{lX`~oAbm7rI?(Z&lf~#QCLQqT^8E2$`^$S>LuwV%goXQ zs6Asm-_FfWq1ZPu;d*sTk0et(M7Oz6&Ds9%&}q5FbO;DMvcjUURGtOg6``9vQk!sz zT@2YcCH7yL3PgT~j`G;$p}3}taC$KI_GHlwN8De-LM*z0P*P&{@c|t)V0zSQrZuGR i<01|wTQY#L6d17{=p)zTg2xiNiW?U3I>zpnbm|Y!gPI8d diff --git a/lisp/i18n/qm/timecode_it.qm b/lisp/i18n/qm/timecode_it.qm deleted file mode 100644 index 97837f2b7d914783acfd866ffbbf79fb84108e48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1705 zcma)6O=uHA82!@PA4^-?LN$oUGzSmGwjiFOLZh@)C~Zii2M^LoGNy~0*|0lXXz}2& zig+k^v!GWmUPTcR=|M%$UK9^1-aP18eBUIyCb3IpnOVO1dEeiAbM{Fk{pQF0d+#QW zy;yzt{Ns5ds?(VV~W&5U%f{zZxeNY>V3BT3ft%U9>4twd#CT| zJnXKm{?{`DxOMB`>6!OLsojAO>91HH9egx=7w2~dzg1si|MSr88^5uCXt;O>`>CrA zQE$2%Cq}494%J9enLMhH>c=AWV)PfCnksu0)yKzHlrfI326C;%Xfi z12QCO6Hp#$%pwtXG=dcoWHSD23w{995{8RV1^$}WF-!8Bwu@DRm6lX!I!`%~ljBD`V16KmyYrz~@Ud)L#8Hm7m^}4F;0e2Em z#)h?y>jIV!YYBh`R#N2;&uLk5mB{&KNf{9p%+@I~qT<=8k9N4pqe>xFxk_;bhMJ&x zH>^5387tcv76O$Z2G1_S<}`dD2dD*miF&SDDwAGts%ujP6?krF98dp83a46Hy$_WT zfNfHo+Bi2NDoi`AqSdx?U5T5lEnORJW=aWkCcr4SB_?RfroY~#9Mg~K2DN||On9bb zdrXrYlM@IgogZoq?T9HbMfygVH6?hvB2~@PmNp}M20BJ7w&KjYP6q>w06Y^0$<15^ zKtr4kfWoF&nB)bTqYOY<^_+KTXN^A#$hQtFRssf=#JMsoZhk}REH&JXdvKzl>aHv+ zv9Nrpup&wuLfYt@vM|0ZUl%&8m6V?xYKG?F^smnTkq2dCXK}T}yyw@X$tJ_zq`b|C zGvt_Ej7@y)4@ZX*k!8D5xzqgIum~a+G#(zVTcaw>9DX#~_&^hJ(6dFa@%(hiT5Ncs s&`RN5;sYkEqjV8vY?DeA|B3Rgn5=5+2w6?KR8w(LzF}|6*U31AT#$tEpZ@>4w_q}hcg#1=ZUCBQx694rS{LcgG3!aI#QFvM5&SVkxQFcrSyx<*Vw+;`Q+VCtZ#Kb zpT)fGao3yi9_-)io*4f?WPI!SIQRwYQ<+D7w{iYy=BxDz`@ec`Ui*#x<9&r&*f*}& zMCo%WoH%M^h*Llwt&>YOF}jL*4Wpn7+my!BRj{j~XjKq5#KTEs?F{5`!a6uMkI{=8s5CEz&&YfD>rsvVJR}LaaobotRDk;(U&+@qh)=tJQ?r zBIFr_R0J&YAd=EO2QpGq+fAW$RSwU%yyys)^W}n&ED})b7EK~CHH+arkm~@n0j~;Q ziL>u*1YUAN%XX9FP0(fN;DAJ3e-oo(-9$}wZ=L?&oNcX*=0)H+p|m}B9|Tn4l~*Id z8zzg3vwyn{r~*YIHCrxPZIW&-#`xPK7N~MOj7IUm490cHq4uS=M`Rfjr<6%O?2W}D zpHLat0aNz#L)S$|u~GPi`;y5u!PK_NMb&e)dHP$Xbtq1&E{aQVW(?7zVj!XngXAL4 zqlg^Ysm#k5mBD3&@-%~QpdY1nWD}IyP&1*M+@XptzG^%{CfPCcNM6(&UJ`6>aV5XZ ziW`jUOzaYqJ}*_68&-?LPfqI;%_6jq#P-$EG#Nw3O0ikbuW~t=401L)4{G2Hk8-V!GhfFQr}0%{IJ{ zxkBK!QU_#MgRqf(bh$+kpqaACq=$^vrUI%Pd2azTg@2am2Cm8Otg)SK+dh_R{{zOY Bgg5{I diff --git a/lisp/i18n/qm/triggers_cs.qm b/lisp/i18n/qm/triggers_cs.qm deleted file mode 100644 index 421f5f49dcfc98d22c12779f49cb54dd9f742c2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 689 zcmcE7ks@*G{hX<16=n7(EZlq7iGhJ3fPuk%2?GOTBm;xjRv_KW;N!IgD9*rAwIL43 zS76OJ3F4cw_P8Ad@(*+Ty{Qc3Kjd05>j{u9DK?XJ;W_8U=Nljq~vbdc~Q$va})6-LniWxzg#DEUUXQ*OG zWGH4R0g`2CTG;{;ON(*n1zA|YP|BFdP{JsUVj+8QNn%k64sAjVQ8=vwYRoSvz^M^r zUp~x6kT>C8q&Q(y*wHS#b70HZ6DAqD7cgf?c!6mXCVVAaFN;0IJ)#PE=@4j3tkaLbs15rEq! zAqIb-Eufev0_x5K+KI4_EhsfNzYLFNNOT|t58P6As1vbU+yM+RHb%z(EKDo_4Th3j diff --git a/lisp/i18n/qm/triggers_en.qm b/lisp/i18n/qm/triggers_en.qm deleted file mode 100644 index 72c0fd2ebb665ff922a42519882c46aa078bfa99..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 637 zcmcE7ks@*G{hX<16=n7(EZlq7iGhJ3fPuk%2?GP8Jp+T+Rv?|r;N!IgD9*rAwIL43 z=U~k^3F7Os_P8Ad@@I4Wy{Qc3pXORI>j{uf-d;0t_KQTQV8afiM-wDu#QA z1Lg;ip9F$aOG+~H(u)HU(^LHtb5o%nb7V*cIx&+WpCJ#fkj{v~=Gs;Uq8D-Z-<<@M|CGtVz+}V>6cJ(w zVMt{tV#sC4WXNMkWJqDi=Ku;bumLfvYhFrf3OkU+?Od7~Qk0pVo?2AQ2-3y}bW$Qi zDMK-u7Pf%I(qbHX1QmKA$nFDb%P%Ovp-qCp z1*p83p#W%C5yUx1gDmiWagz82PCGa`X%P3g1yPZ;0Sas$ibf( z^57cT9Ft2j^Yg&kU}nIy!?k?`+nLEw1lPvwm;w$=0jzr17@UFOnTk*i407By2r#%Z md+0~k_FjEn$-Gm)qO diff --git a/lisp/i18n/qm/triggers_fr.qm b/lisp/i18n/qm/triggers_fr.qm deleted file mode 100644 index e0207974c675d95b1ec1edff21f17a130f0c707c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 663 zcmaKou}Z^G6o&scHVLhiOwz$Y5!`$Omss0E5h<;+7?MkR#Uv%U=^(f}2!bGpxCjn{ zi-U@bI60{J01n-{)Ws(dJP9UJXv!s=oO{oA{&W7-N2~JoeR2A+IRCtJ`7~Gq&;lA) zK&}Ba?8}+h%@{i;6YWsB{567zh!7rI6e*!XSex zV;v(n#Q1bZDWi0R;foAt7J6L%1u>3zyCqfNb|(flzDxy>A}KLwndY)m?&@;BUM=|l1=FXes#RSA%* diff --git a/lisp/i18n/qm/triggers_it.qm b/lisp/i18n/qm/triggers_it.qm deleted file mode 100644 index 40b1b204b9eb1c56593713b642587f32462461de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 651 zcmaKou}i~16vn@_HZ5%^8AV*gzrfukmR2YtrR^*RbHp2xD@od+{sRtzAh;A4!J%#r zDlX#Wpdii;UBunLK=4bjDPkkXyLay$zx(ce>vzuL^XJ*=%gXY@?)m*>13(Mdya2Qs z(7a(jz*h5`eJl*Gc32yQ{inEID;zZ*SzngE-d0(^mdB$HW>LN!#CcRXo}BRhT>`nK zJUa#&knkB1>=GN$8HKv<2&c$XrPdeSK)S97LM;ZW=s+Pv4}$v)(OY&u%w#MKxFqaf zGR0102T>-}phN)gFQj_C49G;6h=j`(!(lp7X*xwbmDv$dBz-q**{;~OJ&}xNz`~HQ zq8}m!b!#t@%1^NA8&f>NOk%N~M4upYismgRG0~jM>8O#I3FT>Zwn`OkDtIJR>}0B; ew}qz$|9}SS1O&epL8Vyn=S%Yi59v`()4l;rcZ-Pt diff --git a/lisp/i18n/qm/triggers_sl_SI.qm b/lisp/i18n/qm/triggers_sl_SI.qm deleted file mode 100644 index 34b1451b838c210a17456ddcbeb65b90b913affa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 668 zcmaKoJxjw-6o%jQBdu+P(8!nAcp1=Z?#FeNjoSwI|zc{B3%Rr z7r{ZrMVuTI9333GIk@=?q@EjWTERGklXJq8_ha(T9C`k1pSr*hxA-r6)dXo6JnmTY9?5Rt2{#CR{}VSyV8F1_T)DgbFkROf4Br zH-yVzog^&Szl0KH&k$Y+lEoTacm%YtgBl6;9TMm?8X=%Yg$X#yJ~)9@@W5fRmqljN z%FOe)ZMh}GVoOGy`2jP?V_QO`Xat~|uX^0EeXRG79`0i)%upL9nXLs-G;jJPY9U00 zT>1g~1l1j`p<$U6Bq3yr1G&@{R(D$eqJxz%R0GMxkGa2P52&XhUFqtPZY~=81~B=L A(*OVf diff --git a/lisp/i18n/ts/en/action_cues.ts b/lisp/i18n/ts/en/action_cues.ts index 0fbd13d00..fd0e909a3 100644 --- a/lisp/i18n/ts/en/action_cues.ts +++ b/lisp/i18n/ts/en/action_cues.ts @@ -1,40 +1,24 @@ - - ActionCuesDebug - - - Registered cue: "{}" - - - - - ActionsCuesError - - - Cannot create cue {} - - - CollectionCue - + Add Add - + Remove Remove - + Cue Cue - + Action Action @@ -42,36 +26,36 @@ CommandCue - + Command Command - + Command to execute, as in a shell Command to execute, as in a shell - + Discard command output Discard command output - + Ignore command errors Ignore command errors - + Kill instead of terminate Kill instead of terminate - Cue Name + CueCategory - - OSC Settings + + Action cues @@ -85,7 +69,7 @@ MIDI Cue - MIDI Cue + MIDI Cue @@ -112,153 +96,90 @@ Index Action Index Action - - - OSC Cue - - IndexActionCue - + Index Index - + Use a relative index Use a relative index - + Target index Target index - + Action Action - + No suggestion - + Suggested cue name Osc Cue - - - Type - - - - - Argument - - - - - FadeTo - - Fade - Fade + Fade OscCue - - - OSC Message - - Add - Add + Add Remove - Remove - - - - Test - - - - - OSC Path: (example: "/path/to/something") - + Remove Fade - Fade - - - - Time (sec) - - - - - Curve - - - - - OscCueError - - - Could not parse argument list, nothing sent - - - - - Error while parsing arguments, nothing sent - - - - - Error during cue execution. - + Fade SeekCue - + Cue Cue - + Click to select Click to select - + Not selected Not selected - + Seek Seek - + Time to reach Time to reach @@ -266,37 +187,37 @@ SettingsPageName - + Command Command MIDI Settings - MIDI Settings + MIDI Settings - + Volume Settings Volume Settings - + Seek Settings Seek Settings - + Edit Collection Edit Collection - + Action Settings Action Settings - + Stop Settings Stop Settings @@ -304,7 +225,7 @@ StopAll - + Stop Action Stop Action @@ -312,27 +233,27 @@ VolumeControl - + Cue Cue - + Click to select Click to select - + Not selected Not selected - + Volume to reach Volume to reach - + Fade Fade @@ -340,7 +261,7 @@ VolumeControlError - + Error during cue execution. diff --git a/lisp/i18n/ts/en/cart_layout.ts b/lisp/i18n/ts/en/cart_layout.ts index c90ce02c0..7f33f80d8 100644 --- a/lisp/i18n/ts/en/cart_layout.ts +++ b/lisp/i18n/ts/en/cart_layout.ts @@ -8,27 +8,27 @@ - + Countdown mode - + Show seek-bars - + Show dB-meters - + Show accurate time - + Show volume @@ -38,62 +38,62 @@ - + Play - + Pause - + Stop - + Reset volume - + Add page - + Add pages - + Remove current page - + Number of Pages: - + Page {number} - + Warning - + Every cue in the page will be lost. - + Are you sure to continue? @@ -155,22 +155,22 @@ ListLayout - + Edit cue - + Edit selected cues - + Remove cue - + Remove selected cues diff --git a/lisp/i18n/ts/en/controller.ts b/lisp/i18n/ts/en/controller.ts index 787a54f70..da98848cc 100644 --- a/lisp/i18n/ts/en/controller.ts +++ b/lisp/i18n/ts/en/controller.ts @@ -77,52 +77,52 @@ ControllerOscSettings - + OSC Message - + OSC - + Path - + Types - + Arguments - + Actions - + OSC Capture - + Add Add - + Remove Remove - + Capture Capture @@ -209,12 +209,12 @@ OscCue - + Add Add - + Remove Remove @@ -237,7 +237,7 @@ Keyboard Shortcuts - + OSC Controls diff --git a/lisp/i18n/ts/en/gst_backend.ts b/lisp/i18n/ts/en/gst_backend.ts index bf8b41740..c3d143bc9 100644 --- a/lisp/i18n/ts/en/gst_backend.ts +++ b/lisp/i18n/ts/en/gst_backend.ts @@ -113,12 +113,12 @@ GstBackend - + Audio cue (from file) - + Select media files @@ -254,7 +254,7 @@ Pitch - + URI Input URI Input diff --git a/lisp/i18n/ts/en/lisp.ts b/lisp/i18n/ts/en/lisp.ts index 1613e4b23..6e3307793 100644 --- a/lisp/i18n/ts/en/lisp.ts +++ b/lisp/i18n/ts/en/lisp.ts @@ -1,5 +1,13 @@ + + APIServerInfo + + + Stop serving network API + + + About @@ -69,6 +77,22 @@ + + AlsaSinkSettings + + + ALSA device + + + + + ApiServerError + + + Network API server stopped working. + + + AppConfiguration @@ -80,734 +104,2750 @@ AppGeneralSettings - + Default layout - + Enable startup layout selector - + Application themes - + UI theme: - + Icons theme: + + + Languages: + + ApplicationError - + Startup error - ConfigurationDebug + AudioDynamicSettings - - Configuration written at {} + + Compressor - - - ConfigurationInfo - - New configuration installed at {} + + Expander - - - CueAction - - Default - Default + + Soft Knee + - - Pause - Pause + + Hard Knee + - - Start - Start + + Compressor/Expander + - - Stop - Stop + + Type + - - Faded Start + + Curve Shape - - Faded Resume + + Ratio - - Faded Pause + + Threshold (dB) + + + AudioPanSettings - - Faded Stop + + Audio Pan - - Faded Interrupt + + Center - - Resume + + Left - - Do Nothing + + Right - CueActionLog + CartLayout - - Cue settings changed: "{}" - Cue settings changed: "{}" + + Default behaviors + - - Cues settings changed. - Cues settings changed. + + Countdown mode + - - - CueAppearanceSettings - - The appearance depends on the layout - The appearance depends on the layout + + Show seek-bars + - - Cue name - Cue name + + Show dB-meters + - - NoName - NoName + + Show accurate time + - - Description/Note - Description/Note + + Show volume + - - Set Font Size - Set Font Size + + Grid size + - - Color - Color + + Number of columns: + - - Select background color - Select background color + + Number of rows: + - - Select font color - Select font color + + Play + - - - CueName - - Media Cue - Media Cue + + Pause + Pause - - - CueNextAction - - Do Nothing - + + Stop + Stop - - Trigger after the end + + Reset volume - - Trigger after post wait + + Add page - - Select after the end + + Add pages - - Select after post wait + + Remove current page - - - CueSettings - - Pre wait - Pre wait + + Number of Pages: + - - Wait before cue execution - Wait before cue execution + + Page {number} + - - Post wait - Post wait + + Warning + Warning - - Wait after cue execution - Wait after cue execution + + Every cue in the page will be lost. + - - Next action - Next action + + Are you sure to continue? + + + + CollectionCue - - Start action - Start action + + Add + - - Default action to start the cue - Default action to start the cue + + Remove + - - Stop action - Stop action + + Cue + Cue - - Default action to stop the cue - Default action to stop the cue + + Action + + + + CommandCue - - Interrupt fade + + Command - - Fade actions + + Command to execute, as in a shell - - - Fade - - Linear - Linear + + Discard command output + - - Quadratic - Quadratic + + Ignore command errors + - - Quadratic2 - Quadratic2 + + Kill instead of terminate + - FadeEdit + ConfigurationDebug - - Duration (sec): + + Configuration written at {} + + + ConfigurationInfo - - Curve: + + New configuration installed at {} - FadeSettings - - - Fade In - Fade In - + Controller - - Fade Out - Fade Out + + Cannot load controller protocol: "{}" + - LayoutSelect + ControllerKeySettings - - Layout selection - Layout selection + + Key + - - Select layout - Select layout + + Action + - - Open file - Open file + + Shortcuts + - ListLayout + ControllerMidiSettings - - Stop All - Stop All + + MIDI + - - Pause All - Pause All + + Type + - - Interrupt All - Interrupt All + + Channel + - - Use fade (global actions) + + Note - - Resume All + + Action - - - Logging - - Debug - Debug + + Filter "note on" + - - Warning - Warning + + Filter "note off" + - - Error - Error + + Capture + + + + + Listening MIDI messages ... + + + + + ControllerOscSettings + + + OSC Message + + + + + OSC + + + + + Path + + + + + Types + + + + + Arguments + + + + + Actions + + + + + OSC Capture + + + + + Add + + + + + Remove + + + + + Capture + + + + + ControllerSettings + + + Add + + + + + Remove + + + + + Cue Name + + + OSC Settings + + + + + CueAction + + + Default + Default + + + + Pause + Pause + + + + Start + Start + + + + Stop + Stop + + + + Faded Start + + + + + Faded Resume + + + + + Faded Pause + + + + + Faded Stop + + + + + Faded Interrupt + + + + + Resume + + + + + Do Nothing + + + + + CueActionLog + + + Cue settings changed: "{}" + Cue settings changed: "{}" + + + + Cues settings changed. + Cues settings changed. + + + + CueAppearanceSettings + + + The appearance depends on the layout + The appearance depends on the layout + + + + Cue name + Cue name + + + + NoName + NoName + + + + Description/Note + Description/Note + + + + Set Font Size + Set Font Size + + + + Color + Color + + + + Select background color + Select background color + + + + Select font color + Select font color + + + + CueCategory + + + Action cues + + + + + Integration cues + + + + + Misc cues + + + + + CueName + + + Media Cue + Media Cue + + + + Index Action + + + + + Seek Cue + + + + + Volume Control + + + + + Command Cue + + + + + Collection Cue + + + + + Stop-All + + + + + MIDI Cue + + + + + OSC Cue + + + + + CueNextAction + + + Do Nothing + + + + + Trigger after the end + + + + + Trigger after post wait + + + + + Select after the end + + + + + Select after post wait + + + + + CueSettings + + + Pre wait + Pre wait + + + + Wait before cue execution + Wait before cue execution + + + + Post wait + Post wait + + + + Wait after cue execution + Wait after cue execution + + + + Next action + Next action + + + + Start action + Start action + + + + Default action to start the cue + Default action to start the cue + + + + Stop action + Stop action + + + + Default action to stop the cue + Default action to stop the cue + + + + Interrupt fade + + + + + Fade actions + + + + + CueTriggers + + + Started + + + + + Paused + + + + + Stopped + + + + + Ended + + + + + DbMeterSettings + + + DbMeter settings + + + + + Time between levels (ms) + + + + + Peak ttl (ms) + + + + + Peak falloff (dB/sec) + + + + + Equalizer10Settings + + + 10 Bands Equalizer (IIR) + + + + + Fade + + + Linear + Linear + + + + Quadratic + Quadratic + + + + Quadratic2 + Quadratic2 + + + + FadeEdit + + + Duration (sec): + + + + + Curve: + + + + + FadeSettings + + + Fade In + Fade In + + + + Fade Out + Fade Out + + + + GlobalAction + + + Go + + + + + Reset + + + + + Stop all cues + + + + + Pause all cues + + + + + Resume all cues + + + + + Interrupt all cues + + + + + Fade-out all cues + + + + + Fade-in all cues + + + + + Move standby forward + + + + + Move standby back + + + + + GstBackend + + + Audio cue (from file) + + + + + Select media files + + + + + GstMediaError + + + Cannot create pipeline element: "{}" + + + + + GstMediaSettings + + + Change Pipeline + + + + + GstMediaWarning + + + Invalid pipeline element: "{}" + + + + + GstPipelineEdit + + + Edit Pipeline + + + + + GstSettings + + + Pipeline + + + + + IndexActionCue + + + No suggestion + + + + + Index + + + + + Use a relative index + + + + + Target index + + + + + Action + + + + + Suggested cue name + + + + + JackSinkSettings + + + Connections + + + + + Edit connections + + + + + Output ports + + + + + Input ports + + + + + Connect + + + + + Disconnect + + + + + LayoutDescription + + + Organize the cues in a list + + + + + Organize cues in grid like pages + + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + + + + + To copy cues drag them while pressing CTRL + + + + + To move cues drag them + + + + + Click a cue to run it + + + + + SHIFT + Click to edit a cue + + + + + CTRL + Click to select a cue + + + + + To move cues drag them while pressing SHIFT + + + + + LayoutName + + + List Layout + + + + + Cart Layout + + + + + LayoutSelect + + + Layout selection + Layout selection + + + + Select layout + Select layout + + + + Open file + Open file + + + + ListLayout + + + Stop All + Stop All + + + + Pause All + Pause All + + + + Interrupt All + Interrupt All + + + + Use fade (global actions) + + + + + Resume All + + + + + Default behaviors + + + + + Show playing cues + + + + + Show dB-meters + + + + + Show accurate time + + + + + Show seek-bars + + + + + Auto-select next cue + + + + + Enable selection mode + + + + + GO Key: + + + + + GO Action: + + + + + GO minimum interval (ms): + + + + + Use fade (buttons) + + + + + Stop Cue + + + + + Pause Cue + + + + + Resume Cue + + + + + Interrupt Cue + + + + + Edit cue + + + + + Edit selected + Edit selected + + + + Clone cue + + + + + Clone selected + + + + + Remove cue + + + + + Remove selected + + + + + Selection mode + + + + + Pause all + + + + + Stop all + + + + + Interrupt all + + + + + Resume all + + + + + Fade-Out all + + + + + Fade-In all + + + + + Edit selected cues + + + + + Remove selected cues + + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + + + + + Logging + + + Debug + Debug + + + + Warning + Warning + + + + Error + Error + + + + Dismiss all + + + + + Show details + + + + + Linux Show Player - Log Viewer + + + + + Info + Info + + + + Critical + + + + + Time + + + + + Milliseconds + + + + + Logger name + + + + + Level + + + + + Message + + + + + Function + + + + + Path name + + + + + File name + + + + + Line no. + + + + + Module + + + + + Process ID + + + + + Process name + + + + + Thread ID + + + + + Thread name + + + + + Exception info + + + + + MIDICue + + + MIDI Message + + + + + Message type + + + + + MIDIMessageAttr + + + Channel + + + + + Note + + + + + Velocity + + + + + Control + + + + + Program + + + + + Value + + + + + Song + + + + + Pitch + + + + + Position + + + + + MIDIMessageType + + + Note ON + + + + + Note OFF + + + + + Polyphonic After-touch + + + + + Control/Mode Change + + + + + Program Change + + + + + Channel After-touch + + + + + Pitch Bend Change + + + + + Song Select + + + + + Song Position + + + + + Start + Start + + + + Stop + Stop + + + + Continue + + + + + MIDISettings + + + MIDI default devices + + + + + Input + + + + + Output + + + + + MainWindow + + + &File + &File + + + + New session + New session + + + + Open + Open + + + + Save session + Save session + + + + Preferences + Preferences + + + + Save as + Save as + + + + Full Screen + Full Screen + + + + Exit + Exit + + + + &Edit + &Edit + + + + Undo + Undo + + + + Redo + Redo + + + + Select all + Select all + + + + Select all media cues + Select all media cues + + + + Deselect all + Deselect all + + + + CTRL+SHIFT+A + CTRL+SHIFT+A + + + + Invert selection + Invert selection + + + + CTRL+I + CTRL+I + + + + Edit selected + Edit selected + + + + CTRL+SHIFT+E + CTRL+SHIFT+E + + + + &Layout + &Layout + + + + &Tools + &Tools + + + + Edit selection + Edit selection + + + + &About + &About + + + + About + About + + + + About Qt + About Qt + + + + Close session + Close session + + + + The current session is not saved. + The current session is not saved. + + + + Discard the changes? + Discard the changes? + + + + Do you want to save them now? + + + + + MainWindowDebug + + + Registered cue menu: "{}" + + + + + MainWindowError + + + Cannot create cue {} + + + + + MediaCueSettings + + + Start time + Start time + + + + Stop position of the media + Stop position of the media + + + + Stop time + Stop time + + + + Start position of the media + Start position of the media + + + + Loop + Loop + + + + MediaElementName + + + Pitch + + + + + Volume + + + + + Audio Pan + + + + + Compressor/Expander + + + + + JACK Out + + + + + URI Input + + + + + 10 Bands Equalizer + + + + + ALSA Out + + + + + dB Meter + + + + + Preset Input + + + + + PulseAudio Out + + + + + Custom Element + + + + + Speed + + + + + System Out + + + + + System Input + + + + + MediaInfo + + + Media Info + + + + + Warning + Warning + + + + No info to display + + + + + Info + Info + + + + Value + + + + + NetworkApiDebug + + + New end-point: {} + + + + + NetworkDiscovery + + + Host discovery + + + + + Manage hosts + + + + + Discover hosts + + + + + Manually add a host + + + + + Remove selected host + + + + + Remove all host + + + + + Address + + + + + Host IP + + + + + Osc Cue + + + Type + + + + + Argument + + + + + OscCue + + + Add + + + + + Remove + + + + + Type + + + + + Value + + + + + FadeTo + + + + + Fade + + + + + OSC Message + + + + + OSC Path: + + + + + /path/to/something + + + + + Time (sec) + + + + + Curve + + + + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + + + + + OscServerError + + + Cannot start OSC sever + + + + + OscServerInfo + + + OSC server started at {} + + + + + OSC server stopped + + + + + OscSettings + + + OSC Settings + + + + + Input Port: + + + + + Output Port: + - - Dismiss all + + Hostname: + + + + + PitchSettings + + + Pitch + + + + + {0:+} semitones + + + + + PluginsError + + + Failed to load "{}" + + + + + Failed to terminate plugin: "{}" + + + + + the requested plugin is not loaded: {} + + + + + PluginsInfo + + + Plugin loaded: "{}" + + + + + Plugin terminated: "{}" + + + + + PluginsWarning + + + Cannot satisfy dependencies for: {} + + + + + Preset + + + Create Cue + + + + + Load on selected Cues + + + + + PresetSrcSettings + + + Presets + + + + + Presets + + + Cannot scan presets + + + + + Error while deleting preset "{}" + + + + + Cannot load preset "{}" + + + + + Cannot save preset "{}" + + + + + Cannot rename preset "{}" + + + + + Select Preset + + + + + Presets + + + + + Preset name + + + + + Add + + + + + Rename + + + + + Edit + + + + + Remove + + + + + Export selected + + + + + Import + + + + + Warning + Warning + + + + The same name is already used! + + + + + Cannot export correctly. + + + + + Cannot import correctly. + + + + + Cue type + + + + + Load on cue + + + + + Load on selected cues + + + + + Save as preset + + + + + QColorButton + + + Right click to reset + Right click to reset + + + + RenameCues + + + Rename Cues + + + + + Rename cues + + + + + Current + + + + + Preview + + + + + Capitalize + + + + + Lowercase + + + + + Uppercase + + + + + Remove Numbers + + + + + Add numbering + + + + + Reset + + + + + Type your regex here: + + + + + Regex help + + + + + RenameUiDebug + + + Regex error: Invalid pattern + + + + + ReplayGain + + + ReplayGain / Normalization + + + + + Threads number + + + + + Apply only to selected media + + + + + ReplayGain to (dB SPL) + + + + + Normalize to (dB) - - Show details - + + Processing files ... + + + + + Calculate + + + + + Reset all + + + + + Reset selected + + + + + ReplayGainDebug + + + Applied gain for: {} + + + + + Discarded gain for: {} + + + + + ReplayGainInfo + + + Gain processing stopped by user. + + + + + Started gain calculation for: {} + + + + + Gain calculated for: {} + + + + + SeekCue + + + Cue + Cue + + + + Click to select + + + + + Not selected + + + + + Seek + + + + + Time to reach + + + + + SettingsPageName + + + Appearance + Appearance + + + + General + General + + + + Cue + Cue + + + + Cue Settings + Cue Settings - - Linux Show Player - Log Viewer + + Plugins - - Info - Info + + Behaviours + Behaviours - - Critical - + + Pre/Post Wait + Pre/Post Wait - - Time - + + Fade In/Out + Fade In/Out - - Milliseconds + + Layouts - - Logger name + + Action Settings - - Level + + Seek Settings - - Message + + Volume Settings - - Function + + Command - - Path name + + Edit Collection - - File name + + Stop Settings - - Line no. + + List Layout - - Module + + Timecode - - Process ID + + Timecode Settings - - Process name + + Cue Control - - Thread ID + + Layout Controls - - Thread name + + MIDI Controls - - Exception info + + Keyboard Shortcuts - - - MainWindow - - - &File - &File - - - New session - New session + + OSC Controls + - - Open - Open + + MIDI Settings + - - Save session - Save session + + MIDI settings + - - Preferences - Preferences + + GStreamer + - - Save as - Save as + + Media Settings + - - Full Screen - Full Screen + + Triggers + - - Exit - Exit + + OSC settings + - - &Edit - &Edit + + Cart Layout + + + + SpeedSettings - - Undo - Undo + + Speed + + + + StopAll - - Redo - Redo + + Stop Action + + + + Synchronizer - - Select all - Select all + + Synchronization + - - Select all media cues - Select all media cues + + Manage connected peers + - - Deselect all - Deselect all + + Show your IP + - - CTRL+SHIFT+A - CTRL+SHIFT+A + + Your IP is: + + + + Timecode - - Invert selection - Invert selection + + Cannot load timecode protocol: "{}" + + + + TimecodeError - - CTRL+I - CTRL+I + + Cannot send timecode. + + + + TimecodeSettings - - Edit selected - Edit selected + + Replace HOURS by a static track number + - - CTRL+SHIFT+E - CTRL+SHIFT+E + + Enable Timecode + - - &Layout - &Layout + + Track number + - - &Tools - &Tools + + Timecode Settings + - - Edit selection - Edit selection + + Timecode Format: + - - &About - &About + + Timecode Protocol: + + + + TriggersSettings - - About - About + + Add + - - About Qt - About Qt + + Remove + - - Close session - Close session + + Trigger + - - The current session is not saved. - The current session is not saved. + + Cue + Cue - - Discard the changes? - Discard the changes? + + Action + - MediaCueSettings - - - Start time - Start time - - - - Stop position of the media - Stop position of the media - + UriInputSettings - - Stop time - Stop time + + Source + - - Start position of the media - Start position of the media + + Find File + - - Loop - Loop + + Buffering + - - - PluginsError - - Failed to load "{}" + + Use Buffering - - Failed to terminate plugin: "{}" + + Attempt download on network streams - - the requested plugin is not loaded: {} + + Buffer size (-1 default value) - - - PluginsInfo - - Plugin loaded: "{}" + + Choose file - - Plugin terminated: "{}" + + All files - PluginsWarning + UserElementSettings - - Cannot satisfy dependencies for: {} + + User defined elements - - - QColorButton - - Right click to reset - Right click to reset + + Only for advanced user! + - SettingsPageName + VolumeControl - - Appearance - Appearance + + Cue + Cue - - General - General + + Click to select + - - Cue - Cue + + Not selected + - - Cue Settings - Cue Settings + + Volume to reach + - - Plugins + + Fade + + + VolumeControlError - - Behaviours - Behaviours + + Error during cue execution. + + + + VolumeSettings - - Pre/Post Wait - Pre/Post Wait + + Volume + - - Fade In/Out - Fade In/Out + + Normalized volume + - - Layouts + + Reset diff --git a/lisp/i18n/ts/en/list_layout.ts b/lisp/i18n/ts/en/list_layout.ts index a5487a89e..2f0dcfc0c 100644 --- a/lisp/i18n/ts/en/list_layout.ts +++ b/lisp/i18n/ts/en/list_layout.ts @@ -42,27 +42,27 @@ - + Show playing cues - + Show dB-meters - + Show accurate time - + Show seek-bars - + Auto-select next cue @@ -72,42 +72,42 @@ - + Use fade (buttons) - + Stop Cue - + Pause Cue - + Resume Cue - + Interrupt Cue - + Edit cue - + Remove cue - + Selection mode @@ -142,38 +142,38 @@ - - GO key: + + Edit selected - - GO action: + + Clone cue - - GO delay (ms): + + Clone selected - - Edit selected + + Remove selected - - Clone cue + + GO Key: - - Clone selected + + GO Action: - - Remove selected + + GO minimum interval (ms): diff --git a/lisp/i18n/ts/en/media_info.ts b/lisp/i18n/ts/en/media_info.ts index 1d514868b..ee241206b 100644 --- a/lisp/i18n/ts/en/media_info.ts +++ b/lisp/i18n/ts/en/media_info.ts @@ -3,27 +3,27 @@ MediaInfo - + Media Info Media Info - + No info to display No info to display - + Info Info - + Value Value - + Warning diff --git a/lisp/i18n/ts/en/midi.ts b/lisp/i18n/ts/en/midi.ts index aeb6c14fb..f10d5d6c1 100644 --- a/lisp/i18n/ts/en/midi.ts +++ b/lisp/i18n/ts/en/midi.ts @@ -1,5 +1,21 @@ + + CueCategory + + + Integration cues + + + + + CueName + + + MIDI Cue + + + MIDICue @@ -149,5 +165,10 @@ MIDI settings MIDI settings + + + MIDI Settings + + diff --git a/lisp/i18n/ts/en/osc.ts b/lisp/i18n/ts/en/osc.ts index 23265b66d..91fe74e2e 100644 --- a/lisp/i18n/ts/en/osc.ts +++ b/lisp/i18n/ts/en/osc.ts @@ -1,5 +1,87 @@ + + Cue Name + + + OSC Settings + + + + + CueCategory + + + Integration cues + + + + + CueName + + + OSC Cue + + + + + OscCue + + + Type + + + + + Value + + + + + FadeTo + + + + + Fade + + + + + OSC Message + + + + + Add + + + + + Remove + + + + + OSC Path: + + + + + /path/to/something + + + + + Time (sec) + + + + + Curve + + + OscServerDebug diff --git a/lisp/i18n/ts/en/presets.ts b/lisp/i18n/ts/en/presets.ts index 348ead47e..ccb5d9e50 100644 --- a/lisp/i18n/ts/en/presets.ts +++ b/lisp/i18n/ts/en/presets.ts @@ -3,12 +3,12 @@ Preset - + Create Cue Create Cue - + Load on selected Cues Load on selected Cues @@ -16,12 +16,12 @@ Presets - + Presets Presets - + Save as preset Save as preset @@ -31,97 +31,97 @@ Cannot scan presets - + Error while deleting preset "{}" Error while deleting preset "{}" - + Cannot load preset "{}" Cannot load preset "{}" - + Cannot save preset "{}" Cannot save preset "{}" - + Cannot rename preset "{}" Cannot rename preset "{}" - + Select Preset Select Preset - + Preset name Preset name - + Add Add - + Rename Rename - + Edit Edit - + Remove Remove - + Export selected Export selected - + Import Import - + Warning Warning - + The same name is already used! The same name is already used! - + Cannot export correctly. Cannot export correctly. - + Cannot import correctly. Cannot import correctly. - + Cue type Cue type - + Load on cue - + Load on selected cues diff --git a/lisp/main.py b/lisp/main.py index b8bf68cfb..a77a7b501 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -18,22 +18,15 @@ import argparse import logging import os -import sys - import signal +import sys from functools import partial from logging.handlers import RotatingFileHandler from PyQt5.QtCore import QLocale, QLibraryInfo, QTimer from PyQt5.QtWidgets import QApplication -from lisp import ( - app_dirs, - DEFAULT_APP_CONFIG, - USER_APP_CONFIG, - plugins, - I18N_PATH, -) +from lisp import app_dirs, DEFAULT_APP_CONFIG, USER_APP_CONFIG, plugins from lisp.application import Application from lisp.core.configuration import JSONFileConfiguration from lisp.ui import themes @@ -116,6 +109,10 @@ def main(): locale = args.locale if locale: QLocale().setDefault(QLocale(locale)) + else: + locale = app_conf["locale"] + if locale != "": + QLocale().setDefault(QLocale(locale)) logging.info( 'Using "{}" locale -> {}'.format( @@ -128,7 +125,7 @@ def main(): # install_translation("qt", tr_path=qt_tr_path) install_translation("qtbase", tr_path=qt_tr_path) # Main app translations - install_translation("lisp", tr_path=I18N_PATH) + install_translation("base") # Set UI theme try: diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index afbd762fa..273e556b2 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -57,8 +57,7 @@ def load_plugins(application): ) plugin.Config = config - # Load plugin translations - install_translation(mod_name) + # Load plugin (self-contained) translations install_translation(mod_name, tr_path=path.join(mod_path, "i18n")) except Exception: logger.exception( diff --git a/lisp/ui/settings/app_pages/general.py b/lisp/ui/settings/app_pages/general.py index 67822ba23..2662e8099 100644 --- a/lisp/ui/settings/app_pages/general.py +++ b/lisp/ui/settings/app_pages/general.py @@ -23,6 +23,7 @@ QComboBox, QGridLayout, QLabel, + QHBoxLayout, ) from lisp import layout @@ -30,6 +31,7 @@ from lisp.ui.settings.pages import SettingsPage from lisp.ui.themes import themes_names from lisp.ui.ui_utils import translate +from lisp.ui.widgets import LocaleComboBox class AppGeneral(SettingsPage): @@ -60,8 +62,8 @@ def __init__(self, **kwargs): # Application theme self.themeGroup = QGroupBox(self) self.themeGroup.setLayout(QGridLayout()) - self.layout().addWidget(self.themeGroup) + self.themeLabel = QLabel(self.themeGroup) self.themeGroup.layout().addWidget(self.themeLabel, 0, 0) self.themeCombo = QComboBox(self.themeGroup) @@ -77,6 +79,16 @@ def __init__(self, **kwargs): self.themeGroup.layout().setColumnStretch(0, 1) self.themeGroup.layout().setColumnStretch(1, 1) + # Locale + self.localeGroup = QGroupBox(self) + self.localeGroup.setLayout(QHBoxLayout()) + self.layout().addWidget(self.localeGroup) + + self.localeLabel = QLabel(self.localeGroup) + self.localeGroup.layout().addWidget(self.localeLabel) + self.localeCombo = LocaleComboBox(self.localeGroup) + self.localeGroup.layout().addWidget(self.localeCombo) + self.retranslateUi() def retranslateUi(self): @@ -92,12 +104,20 @@ def retranslateUi(self): self.themeLabel.setText(translate("AppGeneralSettings", "UI theme:")) self.iconsLabel.setText(translate("AppGeneralSettings", "Icons theme:")) + self.localeGroup.setTitle( + translate( + "AppGeneralSettings", "Application language (require restart)" + ) + ) + self.localeLabel.setText(translate("AppGeneralSettings", "Language:")) + def getSettings(self): settings = { "theme": { "theme": self.themeCombo.currentText(), "icons": self.iconsCombo.currentText(), }, + "locale": self.localeCombo.currentLocale(), "layout": {}, } @@ -119,3 +139,4 @@ def loadSettings(self, settings): self.themeCombo.setCurrentText(settings["theme"]["theme"]) self.iconsCombo.setCurrentText(settings["theme"]["icons"]) + self.localeCombo.setCurrentLocale(settings["locale"]) diff --git a/lisp/ui/settings/app_pages/layouts.py b/lisp/ui/settings/app_pages/layouts.py index 853344023..22ef7085a 100644 --- a/lisp/ui/settings/app_pages/layouts.py +++ b/lisp/ui/settings/app_pages/layouts.py @@ -16,7 +16,7 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QGridLayout, QCheckBox +from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QCheckBox from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate @@ -31,31 +31,29 @@ def __init__(self, **kwargs): self.layout().setAlignment(Qt.AlignTop) self.useFadeGroup = QGroupBox(self) - self.useFadeGroup.setLayout(QGridLayout()) + self.useFadeGroup.setLayout(QVBoxLayout()) self.layout().addWidget(self.useFadeGroup) self.stopAllFade = QCheckBox(self.useFadeGroup) - self.useFadeGroup.layout().addWidget(self.stopAllFade, 0, 0) + self.useFadeGroup.layout().addWidget(self.stopAllFade) self.interruptAllFade = QCheckBox(self.useFadeGroup) - self.useFadeGroup.layout().addWidget(self.interruptAllFade, 0, 1) + self.useFadeGroup.layout().addWidget(self.interruptAllFade) self.pauseAllFade = QCheckBox(self.useFadeGroup) - self.useFadeGroup.layout().addWidget(self.pauseAllFade, 1, 0) + self.useFadeGroup.layout().addWidget(self.pauseAllFade) self.resumeAllFade = QCheckBox(self.useFadeGroup) - self.useFadeGroup.layout().addWidget(self.resumeAllFade, 1, 1) + self.useFadeGroup.layout().addWidget(self.resumeAllFade) self.retranslateUi() def retranslateUi(self): - self.useFadeGroup.setTitle( - translate("ListLayout", "Use fade (global actions)") - ) - self.stopAllFade.setText(translate("ListLayout", "Stop All")) - self.pauseAllFade.setText(translate("ListLayout", "Pause All")) - self.resumeAllFade.setText(translate("ListLayout", "Resume All")) - self.interruptAllFade.setText(translate("ListLayout", "Interrupt All")) + self.useFadeGroup.setTitle(translate("ListLayout", "Use fade")) + self.stopAllFade.setText(translate("ListLayout", "Stop all")) + self.pauseAllFade.setText(translate("ListLayout", "Pause all")) + self.resumeAllFade.setText(translate("ListLayout", "Resume all")) + self.interruptAllFade.setText(translate("ListLayout", "Interrupt all")) def loadSettings(self, settings): self.stopAllFade.setChecked(settings["layout"]["stopAllFade"]) diff --git a/lisp/ui/themes/dark/theme.qss b/lisp/ui/themes/dark/theme.qss index 81035aca4..bd8bfe6e7 100644 --- a/lisp/ui/themes/dark/theme.qss +++ b/lisp/ui/themes/dark/theme.qss @@ -54,7 +54,7 @@ QProgressBar { QProgressBar:horizontal { text-align: center; - border: 1px solid #3A3A3A; + border: 1px solid #3C3C3C; background: #202020; } @@ -85,7 +85,7 @@ QMenuBar:item:selected { } QMenuBar:item:pressed { - border: 1px solid #3A3A3A; + border: 1px solid #3C3C3C; background-color: #419BE6; color: black; margin-bottom:-1px; @@ -93,7 +93,7 @@ QMenuBar:item:pressed { } QMenu { - border: 1px solid #3A3A3A; + border: 1px solid #3C3C3C; color: white; } @@ -124,13 +124,13 @@ QTabWidget:focus, QCheckBox:focus, QRadioButton:focus { QLineEdit { background-color: #202020; padding: 2px; - border: 1px solid #3A3A3A; + border: 1px solid #3C3C3C; border-radius: 3px; color: white; } QGroupBox { - border: 1px solid #363636; + border: 1px solid #414141; border-radius: 2px; margin-top: 2ex; /* NOT px */ } @@ -145,7 +145,7 @@ QGroupBox:title { QAbstractScrollArea { color: white; background-color: #202020; - border: 1px solid #3A3A3A; + border: 1px solid #3C3C3C; border-radius: 3px; } @@ -164,7 +164,7 @@ QScrollBar:add-page, QScrollBar:sub-page { QScrollBar:horizontal { height: 12px; margin: 0px; - border: 1px solid #3A3A3A; + border: 1px solid #3C3C3C; border-radius: 6px; background-color: QLinearGradient( x1: 0, y1: 1, x2: 0, y2: 0, stop: 0 #333, stop: 1 #484846); } @@ -179,7 +179,7 @@ QScrollBar:vertical { background-color: QLinearGradient( x1: 1, y1: 0, x2: 0, y2: 0, stop: 0 #333, stop: 1 #484846); width: 12px; margin: 0px; - border: 1px solid #3A3A3A; + border: 1px solid #3C3C3C; border-radius: 6px; } @@ -249,7 +249,7 @@ QMainWindow:separator:hover { background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0 #58677b, stop:0.5 #419BE6 stop:1 #58677b); color: white; padding-left: 4px; - border: 1px solid #3A3A3A; + border: 1px solid #3C3C3C; spacing: 2px; } @@ -311,7 +311,7 @@ QPushButton:focus { QComboBox { background-color: #202020; border-style: solid; - border: 1px solid #3A3A3A; + border: 1px solid #3C3C3C; border-radius: 3px; padding: 2px; } @@ -343,7 +343,7 @@ QAbstractSpinBox { padding-top: 2px; padding-bottom: 2px; padding-right: 25px; - border: 1px solid #3A3A3A; + border: 1px solid #3C3C3C; background-color: #202020; border-radius: 3px; color: white; @@ -441,7 +441,7 @@ QTabBar::tab:selected:focus { } QTabBar QToolButton { - border: 1px solid #3A3A3A; + border: 1px solid #3C3C3C; } QDockWidget { @@ -451,7 +451,7 @@ QDockWidget { } QDockWidget:title { - border: 1px solid #3A3A3A; + border: 1px solid #3C3C3C; border-bottom: #333; text-align: left; spacing: 2px; @@ -482,7 +482,7 @@ QDockWidget:close-button:pressed, QDockWidget:float-button:pressed { } QTreeView, QListView, QTableView { - border: 1px solid #3A3A3A; + border: 1px solid #3C3C3C; background-color: #202020; } @@ -513,7 +513,7 @@ QSlider:disabled { } QSlider:groove { - border: 1px solid #3A3A3A; + border: 1px solid #3C3C3C; border-radius: 1px; background: #202020; } @@ -671,7 +671,7 @@ CueListView { } CueListView:focus { - border: 1px solid #3A3A3A; + border: 1px solid #3C3C3C; } #ListTimeWidget { @@ -725,7 +725,7 @@ CueListView:focus { } #InfoPanelDescription[readOnly="true"] { - border: 1px solid #3A3A3A; + border: 1px solid #3C3C3C; } #VolumeSlider:sub-page:horizontal { diff --git a/lisp/ui/ui_utils.py b/lisp/ui/ui_utils.py index 539a2e832..8a4fe145e 100644 --- a/lisp/ui/ui_utils.py +++ b/lisp/ui/ui_utils.py @@ -24,7 +24,7 @@ from PyQt5.QtCore import QTranslator, QLocale, QSocketNotifier from PyQt5.QtWidgets import QApplication, qApp -from lisp import I18N_PATH +from lisp import I18N_DIR logger = logging.getLogger(__name__) @@ -89,7 +89,14 @@ def qfile_filters(extensions, allexts=True, anyfile=True): _TRANSLATORS = [] -def install_translation(name, tr_path=I18N_PATH): +def search_translations(prefix="base", tr_path=I18N_DIR): + for entry in os.scandir(tr_path): + name = entry.name + if entry.is_file() and name.endswith(".qm") and name.startswith(prefix): + yield os.path.splitext(name)[0][len(prefix) + 1 :] + + +def install_translation(name, tr_path=I18N_DIR): translator = QTranslator() translator.load(QLocale(), name, "_", tr_path) diff --git a/lisp/ui/widgets/__init__.py b/lisp/ui/widgets/__init__.py index f96cb81a1..d777498fc 100644 --- a/lisp/ui/widgets/__init__.py +++ b/lisp/ui/widgets/__init__.py @@ -1,6 +1,7 @@ from .cue_actions import CueActionComboBox from .cue_next_actions import CueNextActionComboBox from .fades import FadeComboBox, FadeEdit +from .locales import LocaleComboBox from .qclicklabel import QClickLabel from .qclickslider import QClickSlider from .qcolorbutton import QColorButton diff --git a/lisp/ui/widgets/locales.py b/lisp/ui/widgets/locales.py new file mode 100644 index 000000000..8ae8cbdb2 --- /dev/null +++ b/lisp/ui/widgets/locales.py @@ -0,0 +1,50 @@ +# This file is part of Linux Show Player +# +# Copyright 2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import QLocale +from PyQt5.QtWidgets import QComboBox + +from lisp.ui.ui_utils import search_translations + + +class LocaleComboBox(QComboBox): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self._locales = [""] + self._locales.extend(search_translations()) + for locale in self._locales: + self.addItem(self._localeUiText(locale), locale) + + def setCurrentLocale(self, locale): + try: + self.setCurrentIndex(self._locales.index(locale)) + except IndexError: + pass + + def currentLocale(self): + return self.currentData() + + @staticmethod + def _localeUiText(locale): + if locale: + ql = QLocale(locale) + return "{} ({})".format( + ql.languageToString(ql.language()), ql.nativeLanguageName() + ) + else: + return "System ({})".format(QLocale().system().nativeLanguageName()) From c7520e6d7454e2dc2750ebf1390821bbdebc3c51 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 28 Apr 2019 22:28:34 +0200 Subject: [PATCH 177/333] Refactored "Actions", now renamed "Commands" Commands for adding/removing cues are called directly instead of relaying on the old proxy-model (MementoModel), this allow better undo/redo operations that may affect multiple cues at one. Also the global command "stack" has been removed. Some UI improvement --- Pipfile | 1 + Pipfile.lock | 166 +++++----- lisp/application.py | 113 +++---- lisp/command/__init__.py | 0 lisp/{core/action.py => command/command.py} | 25 +- lisp/{cues/cue_actions.py => command/cue.py} | 10 +- lisp/command/layout.py | 37 +++ lisp/command/model.py | 133 ++++++++ lisp/command/stack.py | 106 +++++++ lisp/core/actions_handler.py | 132 -------- lisp/core/memento_model.py | 86 ----- lisp/core/memento_model_actions.py | 96 ------ lisp/cues/cue_memento_model.py | 51 --- lisp/default.json | 7 +- lisp/i18n/ts/en/action_cues.ts | 8 +- lisp/i18n/ts/en/cart_layout.ts | 26 +- lisp/i18n/ts/en/gst_backend.ts | 20 +- lisp/i18n/ts/en/lisp.ts | 299 ++++++++++-------- lisp/i18n/ts/en/list_layout.ts | 37 ++- lisp/i18n/ts/en/presets.ts | 10 +- lisp/i18n/ts/en/rename_cues.ts | 10 +- lisp/i18n/ts/en/replay_gain.ts | 18 +- lisp/i18n/ts/en/triggers.ts | 10 +- lisp/layout/cue_layout.py | 26 +- lisp/main.py | 2 +- lisp/plugins/action_cues/collection_cue.py | 11 +- lisp/plugins/cart_layout/layout.py | 31 +- lisp/plugins/gst_backend/gst_backend.py | 8 +- lisp/plugins/list_layout/layout.py | 23 +- lisp/plugins/list_layout/list_view.py | 30 +- lisp/plugins/list_layout/view.py | 3 +- lisp/plugins/presets/lib.py | 31 +- lisp/plugins/presets/presets.py | 4 +- lisp/plugins/presets/presets_ui.py | 30 +- .../{rename_action.py => command.py} | 30 +- lisp/plugins/rename_cues/rename_cues.py | 7 +- lisp/plugins/replay_gain/replay_gain.py | 62 ++-- lisp/plugins/triggers/triggers_settings.py | 8 +- lisp/{core => }/session.py | 0 .../{numix => Numix}/custom/cue-interrupt.svg | 0 .../{numix => Numix}/custom/cue-pause.svg | 0 .../custom/cue-select-next.svg | 0 .../{numix => Numix}/custom/cue-start.svg | 0 .../{numix => Numix}/custom/cue-stop.svg | 0 .../custom/cue-trigger-next.svg | 0 .../custom/fadein-generic.svg | 0 .../custom/fadeout-generic.svg | 0 lisp/ui/icons/Numix/index.theme | 47 +++ .../standard/actions/address-book-new.svg | 0 .../standard/actions/application-exit.svg | 0 .../standard/actions/appointment-new.svg | 0 .../standard/actions/call-start.svg | 0 .../standard/actions/call-stop.svg | 0 .../standard/actions/contact-new.svg | 0 .../standard/actions/document-new.svg | 0 .../standard/actions/document-open-recent.svg | 0 .../standard/actions/document-open.svg | 0 .../standard/actions/document-page-setup.svg | 0 .../actions/document-print-preview.svg | 0 .../standard/actions/document-print.svg | 0 .../standard/actions/document-properties.svg | 0 .../standard/actions/document-revert.svg | 0 .../standard/actions/document-save-as.svg | 0 .../standard/actions/document-save.svg | 0 .../standard/actions/document-send.svg | 0 .../standard/actions/edit-clear.svg | 0 .../standard/actions/edit-copy.svg | 0 .../standard/actions/edit-cut.svg | 0 .../standard/actions/edit-delete.svg | 0 .../standard/actions/edit-find-replace.svg | 0 .../standard/actions/edit-find.svg | 0 .../standard/actions/edit-paste.svg | 0 .../standard/actions/edit-redo.svg | 0 .../standard/actions/edit-select-all.svg | 0 .../standard/actions/edit-undo.svg | 0 .../standard/actions/folder-new.svg | 0 .../standard/actions/format-indent-less.svg | 0 .../standard/actions/format-indent-more.svg | 0 .../actions/format-justify-center.svg | 0 .../standard/actions/format-justify-fill.svg | 0 .../standard/actions/format-justify-left.svg | 0 .../standard/actions/format-justify-right.svg | 0 .../standard/actions/format-text-bold.svg | 0 .../actions/format-text-direction-ltr.svg | 0 .../actions/format-text-direction-rtl.svg | 0 .../standard/actions/format-text-italic.svg | 0 .../actions/format-text-strikethrough.svg | 0 .../actions/format-text-underline.svg | 0 .../standard/actions/go-bottom.svg | 0 .../standard/actions/go-down.svg | 0 .../standard/actions/go-first.svg | 0 .../standard/actions/go-home.svg | 0 .../standard/actions/go-jump.svg | 0 .../standard/actions/go-last.svg | 0 .../standard/actions/go-next.svg | 0 .../standard/actions/go-previous.svg | 0 .../standard/actions/go-top.svg | 0 .../standard/actions/go-up.svg | 0 .../standard/actions/help-about.svg | 0 .../standard/actions/help-contents.svg | 0 .../standard/actions/help-faq.svg | 0 .../standard/actions/insert-image.svg | 0 .../standard/actions/insert-link.svg | 0 .../standard/actions/insert-object.svg | 0 .../standard/actions/insert-text.svg | 0 .../standard/actions/list-add.svg | 0 .../standard/actions/list-remove.svg | 0 .../standard/actions/mail-forward.svg | 0 .../standard/actions/mail-mark-important.svg | 0 .../standard/actions/mail-mark-junk.svg | 0 .../standard/actions/mail-mark-notjunk.svg | 0 .../standard/actions/mail-mark-read.svg | 0 .../standard/actions/mail-mark-unread.svg | 0 .../standard/actions/mail-message-new.svg | 0 .../standard/actions/mail-reply-all.svg | 0 .../standard/actions/mail-reply-sender.svg | 0 .../standard/actions/mail-send-receive.svg | 0 .../standard/actions/mail-send.svg | 0 .../standard/actions/media-eject.svg | 0 .../standard/actions/media-playback-pause.svg | 0 .../standard/actions/media-playback-start.svg | 0 .../standard/actions/media-playback-stop.svg | 0 .../standard/actions/media-record.svg | 0 .../standard/actions/media-seek-backward.svg | 0 .../standard/actions/media-seek-forward.svg | 0 .../standard/actions/media-skip-backward.svg | 0 .../standard/actions/media-skip-forward.svg | 0 .../actions/object-flip-horizontal.svg | 0 .../standard/actions/object-flip-vertical.svg | 0 .../standard/actions/object-rotate-left.svg | 0 .../standard/actions/object-rotate-right.svg | 0 .../standard/actions/process-stop.svg | 0 .../standard/actions/system-lock-screen.svg | 0 .../standard/actions/system-log-out.svg | 0 .../standard/actions/system-reboot.svg | 0 .../standard/actions/system-run.svg | 0 .../standard/actions/system-search.svg | 0 .../standard/actions/system-shutdown.svg | 0 .../standard/actions/tools-check-spelling.svg | 0 .../standard/actions/view-fullscreen.svg | 0 .../standard/actions/view-refresh.svg | 0 .../standard/actions/view-restore.svg | 0 .../standard/actions/view-sort-ascending.svg | 0 .../standard/actions/view-sort-descending.svg | 0 .../standard/actions/window-close.svg | 0 .../standard/actions/window-new.svg | 0 .../standard/actions/zoom-fit-best.svg | 0 .../standard/actions/zoom-in.svg | 0 .../standard/actions/zoom-original.svg | 0 .../standard/actions/zoom-out.svg | 0 .../categories/applications-accessories.svg | 0 .../categories/applications-development.svg | 0 .../categories/applications-engineering.svg | 0 .../categories/applications-games.svg | 0 .../categories/applications-graphics.svg | 0 .../categories/applications-internet.svg | 0 .../categories/applications-multimedia.svg | 0 .../categories/applications-office.svg | 0 .../categories/applications-other.svg | 0 .../categories/applications-science.svg | 0 .../categories/applications-system.svg | 0 .../categories/applications-utilities.svg | 0 .../preferences-desktop-peripherals.svg | 0 .../preferences-desktop-personal.svg | 0 .../categories/preferences-desktop.svg | 0 .../standard/categories/preferences-other.svg | 0 .../categories/preferences-system-network.svg | 0 .../categories/preferences-system.svg | 0 .../standard/categories/system-help.svg | 0 .../standard/devices/audio-card.svg | 0 .../devices/audio-input-microphone.svg | 0 .../standard/devices/battery.svg | 0 .../standard/devices/camera-photo.svg | 0 .../standard/devices/camera-web.svg | 0 .../standard/devices/computer.svg | 0 .../standard/devices/drive-harddisk.svg | 0 .../standard/devices/drive-optical.svg | 0 .../devices/drive-removable-media.svg | 0 .../standard/devices/input-gaming.svg | 0 .../standard/devices/input-keyboard.svg | 0 .../standard/devices/input-mouse.svg | 0 .../standard/devices/input-tablet.svg | 0 .../standard/devices/media-flash.svg | 0 .../standard/devices/media-floppy.svg | 0 .../standard/devices/media-optical.svg | 0 .../standard/devices/media-tape.svg | 0 .../standard/devices/multimedia-player.svg | 0 .../standard/devices/network-wired.svg | 0 .../standard/devices/network-wireless.svg | 0 .../{numix => Numix}/standard/devices/pda.svg | 0 .../standard/devices/phone.svg | 0 .../standard/devices/printer.svg | 0 .../standard/devices/scanner.svg | 0 .../standard/devices/video-display.svg | 0 .../standard/emblems/emblem-default.svg | 0 .../standard/emblems/emblem-documents.svg | 0 .../standard/emblems/emblem-downloads.svg | 0 .../standard/emblems/emblem-favorite.svg | 0 .../standard/emblems/emblem-important.svg | 0 .../standard/emblems/emblem-mail.svg | 0 .../standard/emblems/emblem-photos.svg | 0 .../standard/emblems/emblem-readonly.svg | 0 .../standard/emblems/emblem-shared.svg | 0 .../standard/emblems/emblem-symbolic-link.svg | 0 .../standard/emblems/emblem-system.svg | 0 .../standard/emblems/emblem-unreadable.svg | 0 .../standard/places/folder-remote.svg | 0 .../standard/places/folder.svg | 0 .../standard/places/network-server.svg | 0 .../standard/places/network-workgroup.svg | 0 .../standard/places/start-here.svg | 0 .../standard/places/user-bookmarks.svg | 0 .../standard/places/user-desktop.svg | 0 .../standard/places/user-home.svg | 0 .../standard/places/user-trash.svg | 0 .../standard/status/appointment-missed.svg | 0 .../standard/status/appointment-soon.svg | 0 .../standard/status/audio-volume-high.svg | 0 .../standard/status/audio-volume-low.svg | 0 .../standard/status/audio-volume-medium.svg | 0 .../standard/status/audio-volume-muted.svg | 0 .../standard/status/battery-caution.svg | 0 .../standard/status/battery-low.svg | 0 .../standard/status/dialog-error.svg | 0 .../standard/status/dialog-information.svg | 0 .../standard/status/dialog-password.svg | 0 .../standard/status/dialog-question.svg | 0 .../standard/status/dialog-warning.svg | 0 .../standard/status/image-missing.svg | 0 .../standard/status/network-error.svg | 0 .../standard/status/network-idle.svg | 0 .../standard/status/network-offline.svg | 0 .../standard/status/network-receive.svg | 0 .../status/network-transmit-receive.svg | 0 .../standard/status/network-transmit.svg | 0 .../standard/status/printer-printing.svg | 0 .../standard/status/security-high.svg | 0 .../standard/status/security-low.svg | 0 .../standard/status/security-medium.svg | 0 .../standard/status/task-due.svg | 0 .../standard/status/task-past-due.svg | 0 .../standard/status/user-available.svg | 0 .../standard/status/user-away.svg | 0 .../standard/status/user-idle.svg | 0 .../standard/status/user-offline.svg | 0 .../custom/cue-interrupt.svg | 0 .../custom/cue-pause.svg | 0 .../custom/cue-select-next.svg | 0 .../custom/cue-start.svg | 0 .../custom/cue-stop.svg | 0 .../custom/cue-trigger-next.svg | 0 .../custom/fadein-generic.svg | 0 .../custom/fadeout-generic.svg | 0 lisp/ui/icons/Papirus Symbolic/index.theme | 47 +++ .../standard/actions/address-book-new.svg | 0 .../standard/actions/application-exit.svg | 0 .../standard/actions/appointment-new.svg | 0 .../standard/actions/call-start.svg | 0 .../standard/actions/call-stop.svg | 0 .../standard/actions/contact-new.svg | 0 .../standard/actions/document-new.svg | 0 .../standard/actions/document-open-recent.svg | 0 .../standard/actions/document-open.svg | 0 .../standard/actions/document-page-setup.svg | 0 .../actions/document-print-preview.svg | 0 .../standard/actions/document-print.svg | 0 .../standard/actions/document-properties.svg | 0 .../standard/actions/document-revert.svg | 0 .../standard/actions/document-save-as.svg | 0 .../standard/actions/document-save.svg | 0 .../standard/actions/document-send.svg | 0 .../standard/actions/edit-clear.svg | 0 .../standard/actions/edit-copy.svg | 0 .../standard/actions/edit-cut.svg | 0 .../standard/actions/edit-delete.svg | 0 .../standard/actions/edit-find-replace.svg | 0 .../standard/actions/edit-find.svg | 0 .../standard/actions/edit-paste.svg | 0 .../standard/actions/edit-redo.svg | 0 .../standard/actions/edit-select-all.svg | 0 .../standard/actions/edit-undo.svg | 0 .../standard/actions/folder-new.svg | 0 .../standard/actions/format-indent-less.svg | 0 .../standard/actions/format-indent-more.svg | 0 .../actions/format-justify-center.svg | 0 .../standard/actions/format-justify-fill.svg | 0 .../standard/actions/format-justify-left.svg | 0 .../standard/actions/format-justify-right.svg | 0 .../standard/actions/format-text-bold.svg | 0 .../actions/format-text-direction-ltr.svg | 0 .../actions/format-text-direction-rtl.svg | 0 .../standard/actions/format-text-italic.svg | 0 .../actions/format-text-strikethrough.svg | 0 .../actions/format-text-underline.svg | 0 .../standard/actions/go-bottom.svg | 0 .../standard/actions/go-down.svg | 0 .../standard/actions/go-first.svg | 0 .../standard/actions/go-home.svg | 0 .../standard/actions/go-jump.svg | 0 .../standard/actions/go-last.svg | 0 .../standard/actions/go-next.svg | 0 .../standard/actions/go-previous.svg | 0 .../standard/actions/go-top.svg | 0 .../standard/actions/go-up.svg | 0 .../standard/actions/help-about.svg | 0 .../standard/actions/insert-image.svg | 0 .../standard/actions/insert-link.svg | 0 .../standard/actions/insert-object.svg | 0 .../standard/actions/insert-text.svg | 0 .../standard/actions/list-add.svg | 0 .../standard/actions/list-remove.svg | 0 .../standard/actions/mail-forward.svg | 0 .../standard/actions/mail-mark-important.svg | 0 .../standard/actions/mail-mark-junk.svg | 0 .../standard/actions/mail-mark-notjunk.svg | 0 .../standard/actions/mail-mark-read.svg | 0 .../standard/actions/mail-mark-unread.svg | 0 .../standard/actions/mail-message-new.svg | 0 .../standard/actions/mail-reply-all.svg | 0 .../standard/actions/mail-reply-sender.svg | 0 .../standard/actions/mail-send-receive.svg | 0 .../standard/actions/mail-send.svg | 0 .../standard/actions/media-eject.svg | 0 .../standard/actions/media-playback-pause.svg | 0 .../standard/actions/media-playback-start.svg | 0 .../standard/actions/media-playback-stop.svg | 0 .../standard/actions/media-record.svg | 0 .../standard/actions/media-seek-backward.svg | 0 .../standard/actions/media-seek-forward.svg | 0 .../standard/actions/media-skip-backward.svg | 0 .../standard/actions/media-skip-forward.svg | 0 .../actions/object-flip-horizontal.svg | 0 .../standard/actions/object-flip-vertical.svg | 0 .../standard/actions/object-rotate-left.svg | 0 .../standard/actions/object-rotate-right.svg | 0 .../standard/actions/process-stop.svg | 0 .../standard/actions/system-lock-screen.svg | 0 .../standard/actions/system-log-out.svg | 0 .../standard/actions/system-run.svg | 0 .../standard/actions/system-search.svg | 0 .../standard/actions/system-shutdown.svg | 0 .../standard/actions/tools-check-spelling.svg | 0 .../standard/actions/view-fullscreen.svg | 0 .../standard/actions/view-refresh.svg | 0 .../standard/actions/view-restore.svg | 0 .../standard/actions/view-sort-ascending.svg | 0 .../standard/actions/view-sort-descending.svg | 0 .../standard/actions/window-close.svg | 0 .../standard/actions/zoom-fit-best.svg | 0 .../standard/actions/zoom-in.svg | 0 .../standard/actions/zoom-original.svg | 0 .../standard/actions/zoom-out.svg | 0 .../categories/applications-engineering.svg | 0 .../categories/applications-games.svg | 0 .../categories/applications-graphics.svg | 0 .../categories/applications-multimedia.svg | 0 .../categories/applications-science.svg | 0 .../categories/applications-system.svg | 0 .../categories/applications-utilities.svg | 0 .../standard/categories/preferences-other.svg | 0 .../categories/preferences-system.svg | 0 .../standard/categories/system-help.svg | 0 .../standard/devices/audio-card.svg | 0 .../devices/audio-input-microphone.svg | 0 .../standard/devices/battery.svg | 0 .../standard/devices/camera-photo.svg | 0 .../standard/devices/camera-video.svg | 0 .../standard/devices/camera-web.svg | 0 .../standard/devices/computer.svg | 0 .../standard/devices/drive-harddisk.svg | 0 .../standard/devices/drive-optical.svg | 0 .../devices/drive-removable-media.svg | 0 .../standard/devices/input-gaming.svg | 0 .../standard/devices/input-keyboard.svg | 0 .../standard/devices/input-mouse.svg | 0 .../standard/devices/input-tablet.svg | 0 .../standard/devices/media-flash.svg | 0 .../standard/devices/media-floppy.svg | 0 .../standard/devices/media-tape.svg | 0 .../standard/devices/multimedia-player.svg | 0 .../standard/devices/network-wired.svg | 0 .../standard/devices/network-wireless.svg | 0 .../standard/devices/phone.svg | 0 .../standard/devices/printer.svg | 0 .../standard/devices/scanner.svg | 0 .../standard/devices/video-display.svg | 0 .../standard/emblems/emblem-default.svg | 0 .../standard/emblems/emblem-documents.svg | 0 .../standard/emblems/emblem-favorite.svg | 0 .../standard/emblems/emblem-important.svg | 0 .../standard/emblems/emblem-photos.svg | 0 .../standard/emblems/emblem-shared.svg | 0 .../standard/emblems/emblem-system.svg | 0 .../standard/places/folder-remote.svg | 0 .../standard/places/folder.svg | 0 .../standard/places/network-workgroup.svg | 0 .../standard/places/start-here.svg | 0 .../standard/places/user-bookmarks.svg | 0 .../standard/places/user-desktop.svg | 0 .../standard/places/user-home.svg | 0 .../standard/places/user-trash.svg | 0 .../standard/status/appointment-missed.svg | 0 .../standard/status/appointment-soon.svg | 0 .../standard/status/audio-volume-high.svg | 0 .../standard/status/audio-volume-low.svg | 0 .../standard/status/audio-volume-medium.svg | 0 .../standard/status/audio-volume-muted.svg | 0 .../standard/status/battery-caution.svg | 0 .../standard/status/battery-low.svg | 0 .../standard/status/dialog-error.svg | 0 .../standard/status/dialog-information.svg | 0 .../standard/status/dialog-password.svg | 0 .../standard/status/dialog-question.svg | 0 .../standard/status/dialog-warning.svg | 0 .../standard/status/folder-drag-accept.svg | 0 .../standard/status/folder-open.svg | 0 .../standard/status/folder-visiting.svg | 0 .../standard/status/image-loading.svg | 0 .../standard/status/mail-attachment.svg | 0 .../standard/status/mail-read.svg | 0 .../standard/status/mail-replied.svg | 0 .../standard/status/mail-unread.svg | 0 .../standard/status/media-playlist-repeat.svg | 0 .../status/media-playlist-shuffle.svg | 0 .../standard/status/network-error.svg | 0 .../standard/status/network-idle.svg | 0 .../standard/status/network-offline.svg | 0 .../standard/status/network-receive.svg | 0 .../status/network-transmit-receive.svg | 0 .../standard/status/network-transmit.svg | 0 .../standard/status/printer-error.svg | 0 .../standard/status/printer-printing.svg | 0 .../standard/status/security-high.svg | 0 .../standard/status/security-low.svg | 0 .../standard/status/security-medium.svg | 0 .../status/software-update-available.svg | 0 .../status/software-update-urgent.svg | 0 .../standard/status/task-due.svg | 0 .../standard/status/task-past-due.svg | 0 .../standard/status/user-available.svg | 0 .../standard/status/user-away.svg | 0 .../standard/status/user-idle.svg | 0 .../standard/status/user-offline.svg | 0 .../standard/status/user-trash-full.svg | 0 lisp/ui/icons/__init__.py | 3 + lisp/ui/mainwindow.py | 125 ++++---- lisp/ui/qdelegates.py | 22 +- lisp/ui/widgets/fades.py | 2 +- lisp/ui/widgets/pagestreewidget.py | 7 +- 449 files changed, 1041 insertions(+), 919 deletions(-) create mode 100644 lisp/command/__init__.py rename lisp/{core/action.py => command/command.py} (66%) rename lisp/{cues/cue_actions.py => command/cue.py} (88%) create mode 100644 lisp/command/layout.py create mode 100644 lisp/command/model.py create mode 100644 lisp/command/stack.py delete mode 100644 lisp/core/actions_handler.py delete mode 100644 lisp/core/memento_model.py delete mode 100644 lisp/core/memento_model_actions.py delete mode 100644 lisp/cues/cue_memento_model.py rename lisp/plugins/rename_cues/{rename_action.py => command.py} (61%) rename lisp/{core => }/session.py (100%) rename lisp/ui/icons/{numix => Numix}/custom/cue-interrupt.svg (100%) rename lisp/ui/icons/{numix => Numix}/custom/cue-pause.svg (100%) rename lisp/ui/icons/{numix => Numix}/custom/cue-select-next.svg (100%) rename lisp/ui/icons/{numix => Numix}/custom/cue-start.svg (100%) rename lisp/ui/icons/{numix => Numix}/custom/cue-stop.svg (100%) rename lisp/ui/icons/{numix => Numix}/custom/cue-trigger-next.svg (100%) rename lisp/ui/icons/{numix => Numix}/custom/fadein-generic.svg (100%) rename lisp/ui/icons/{numix => Numix}/custom/fadeout-generic.svg (100%) create mode 100644 lisp/ui/icons/Numix/index.theme rename lisp/ui/icons/{numix => Numix}/standard/actions/address-book-new.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/application-exit.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/appointment-new.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/call-start.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/call-stop.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/contact-new.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/document-new.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/document-open-recent.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/document-open.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/document-page-setup.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/document-print-preview.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/document-print.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/document-properties.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/document-revert.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/document-save-as.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/document-save.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/document-send.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/edit-clear.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/edit-copy.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/edit-cut.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/edit-delete.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/edit-find-replace.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/edit-find.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/edit-paste.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/edit-redo.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/edit-select-all.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/edit-undo.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/folder-new.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/format-indent-less.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/format-indent-more.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/format-justify-center.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/format-justify-fill.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/format-justify-left.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/format-justify-right.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/format-text-bold.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/format-text-direction-ltr.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/format-text-direction-rtl.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/format-text-italic.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/format-text-strikethrough.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/format-text-underline.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/go-bottom.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/go-down.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/go-first.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/go-home.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/go-jump.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/go-last.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/go-next.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/go-previous.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/go-top.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/go-up.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/help-about.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/help-contents.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/help-faq.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/insert-image.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/insert-link.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/insert-object.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/insert-text.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/list-add.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/list-remove.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/mail-forward.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/mail-mark-important.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/mail-mark-junk.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/mail-mark-notjunk.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/mail-mark-read.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/mail-mark-unread.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/mail-message-new.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/mail-reply-all.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/mail-reply-sender.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/mail-send-receive.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/mail-send.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/media-eject.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/media-playback-pause.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/media-playback-start.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/media-playback-stop.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/media-record.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/media-seek-backward.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/media-seek-forward.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/media-skip-backward.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/media-skip-forward.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/object-flip-horizontal.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/object-flip-vertical.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/object-rotate-left.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/object-rotate-right.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/process-stop.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/system-lock-screen.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/system-log-out.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/system-reboot.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/system-run.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/system-search.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/system-shutdown.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/tools-check-spelling.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/view-fullscreen.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/view-refresh.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/view-restore.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/view-sort-ascending.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/view-sort-descending.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/window-close.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/window-new.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/zoom-fit-best.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/zoom-in.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/zoom-original.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/actions/zoom-out.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/applications-accessories.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/applications-development.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/applications-engineering.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/applications-games.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/applications-graphics.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/applications-internet.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/applications-multimedia.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/applications-office.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/applications-other.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/applications-science.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/applications-system.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/applications-utilities.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/preferences-desktop-peripherals.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/preferences-desktop-personal.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/preferences-desktop.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/preferences-other.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/preferences-system-network.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/preferences-system.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/categories/system-help.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/audio-card.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/audio-input-microphone.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/battery.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/camera-photo.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/camera-web.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/computer.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/drive-harddisk.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/drive-optical.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/drive-removable-media.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/input-gaming.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/input-keyboard.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/input-mouse.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/input-tablet.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/media-flash.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/media-floppy.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/media-optical.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/media-tape.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/multimedia-player.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/network-wired.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/network-wireless.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/pda.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/phone.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/printer.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/scanner.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/devices/video-display.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/emblems/emblem-default.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/emblems/emblem-documents.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/emblems/emblem-downloads.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/emblems/emblem-favorite.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/emblems/emblem-important.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/emblems/emblem-mail.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/emblems/emblem-photos.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/emblems/emblem-readonly.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/emblems/emblem-shared.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/emblems/emblem-symbolic-link.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/emblems/emblem-system.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/emblems/emblem-unreadable.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/places/folder-remote.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/places/folder.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/places/network-server.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/places/network-workgroup.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/places/start-here.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/places/user-bookmarks.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/places/user-desktop.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/places/user-home.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/places/user-trash.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/appointment-missed.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/appointment-soon.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/audio-volume-high.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/audio-volume-low.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/audio-volume-medium.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/audio-volume-muted.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/battery-caution.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/battery-low.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/dialog-error.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/dialog-information.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/dialog-password.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/dialog-question.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/dialog-warning.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/image-missing.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/network-error.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/network-idle.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/network-offline.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/network-receive.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/network-transmit-receive.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/network-transmit.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/printer-printing.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/security-high.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/security-low.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/security-medium.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/task-due.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/task-past-due.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/user-available.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/user-away.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/user-idle.svg (100%) rename lisp/ui/icons/{numix => Numix}/standard/status/user-offline.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/custom/cue-interrupt.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/custom/cue-pause.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/custom/cue-select-next.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/custom/cue-start.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/custom/cue-stop.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/custom/cue-trigger-next.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/custom/fadein-generic.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/custom/fadeout-generic.svg (100%) create mode 100644 lisp/ui/icons/Papirus Symbolic/index.theme rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/address-book-new.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/application-exit.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/appointment-new.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/call-start.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/call-stop.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/contact-new.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/document-new.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/document-open-recent.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/document-open.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/document-page-setup.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/document-print-preview.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/document-print.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/document-properties.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/document-revert.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/document-save-as.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/document-save.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/document-send.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/edit-clear.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/edit-copy.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/edit-cut.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/edit-delete.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/edit-find-replace.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/edit-find.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/edit-paste.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/edit-redo.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/edit-select-all.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/edit-undo.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/folder-new.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/format-indent-less.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/format-indent-more.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/format-justify-center.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/format-justify-fill.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/format-justify-left.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/format-justify-right.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/format-text-bold.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/format-text-direction-ltr.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/format-text-direction-rtl.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/format-text-italic.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/format-text-strikethrough.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/format-text-underline.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/go-bottom.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/go-down.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/go-first.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/go-home.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/go-jump.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/go-last.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/go-next.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/go-previous.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/go-top.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/go-up.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/help-about.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/insert-image.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/insert-link.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/insert-object.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/insert-text.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/list-add.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/list-remove.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/mail-forward.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/mail-mark-important.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/mail-mark-junk.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/mail-mark-notjunk.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/mail-mark-read.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/mail-mark-unread.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/mail-message-new.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/mail-reply-all.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/mail-reply-sender.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/mail-send-receive.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/mail-send.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/media-eject.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/media-playback-pause.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/media-playback-start.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/media-playback-stop.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/media-record.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/media-seek-backward.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/media-seek-forward.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/media-skip-backward.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/media-skip-forward.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/object-flip-horizontal.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/object-flip-vertical.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/object-rotate-left.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/object-rotate-right.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/process-stop.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/system-lock-screen.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/system-log-out.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/system-run.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/system-search.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/system-shutdown.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/tools-check-spelling.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/view-fullscreen.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/view-refresh.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/view-restore.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/view-sort-ascending.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/view-sort-descending.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/window-close.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/zoom-fit-best.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/zoom-in.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/zoom-original.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/actions/zoom-out.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/categories/applications-engineering.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/categories/applications-games.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/categories/applications-graphics.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/categories/applications-multimedia.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/categories/applications-science.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/categories/applications-system.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/categories/applications-utilities.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/categories/preferences-other.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/categories/preferences-system.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/categories/system-help.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/audio-card.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/audio-input-microphone.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/battery.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/camera-photo.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/camera-video.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/camera-web.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/computer.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/drive-harddisk.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/drive-optical.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/drive-removable-media.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/input-gaming.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/input-keyboard.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/input-mouse.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/input-tablet.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/media-flash.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/media-floppy.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/media-tape.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/multimedia-player.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/network-wired.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/network-wireless.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/phone.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/printer.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/scanner.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/devices/video-display.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/emblems/emblem-default.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/emblems/emblem-documents.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/emblems/emblem-favorite.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/emblems/emblem-important.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/emblems/emblem-photos.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/emblems/emblem-shared.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/emblems/emblem-system.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/places/folder-remote.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/places/folder.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/places/network-workgroup.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/places/start-here.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/places/user-bookmarks.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/places/user-desktop.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/places/user-home.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/places/user-trash.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/appointment-missed.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/appointment-soon.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/audio-volume-high.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/audio-volume-low.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/audio-volume-medium.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/audio-volume-muted.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/battery-caution.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/battery-low.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/dialog-error.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/dialog-information.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/dialog-password.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/dialog-question.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/dialog-warning.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/folder-drag-accept.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/folder-open.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/folder-visiting.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/image-loading.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/mail-attachment.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/mail-read.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/mail-replied.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/mail-unread.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/media-playlist-repeat.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/media-playlist-shuffle.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/network-error.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/network-idle.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/network-offline.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/network-receive.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/network-transmit-receive.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/network-transmit.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/printer-error.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/printer-printing.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/security-high.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/security-low.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/security-medium.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/software-update-available.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/software-update-urgent.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/task-due.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/task-past-due.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/user-available.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/user-away.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/user-idle.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/user-offline.svg (100%) rename lisp/ui/icons/{papirus-symbolic => Papirus Symbolic}/standard/status/user-trash-full.svg (100%) diff --git a/Pipfile b/Pipfile index d93a9cff1..c4197dd84 100644 --- a/Pipfile +++ b/Pipfile @@ -17,4 +17,5 @@ requests = "~=2.20" sortedcontainers = "~=2.0" [dev-packages] +pysnooper = "*" html5lib = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 28f5b1657..21fb00a72 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "9ef1a5b405946616a8849996c6a5515f579c7666ccf22475c6ae6a36385a4950" + "sha256": "d791d363b62c9b211f8117991823e0bb53419ee97a9601429b6dd003dc875a58" }, "pipfile-spec": 6, "requires": {}, @@ -31,36 +31,36 @@ }, "cffi": { "hashes": [ - "sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f", - "sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11", - "sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d", - "sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891", - "sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf", - "sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c", - "sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed", - "sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b", - "sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a", - "sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585", - "sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea", - "sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f", - "sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33", - "sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145", - "sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a", - "sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3", - "sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f", - "sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd", - "sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804", - "sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d", - "sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92", - "sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f", - "sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84", - "sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb", - "sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7", - "sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7", - "sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35", - "sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889" + "sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774", + "sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d", + "sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90", + "sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b", + "sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63", + "sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45", + "sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25", + "sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3", + "sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b", + "sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647", + "sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016", + "sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4", + "sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb", + "sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753", + "sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7", + "sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9", + "sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f", + "sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8", + "sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f", + "sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc", + "sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42", + "sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3", + "sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909", + "sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45", + "sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d", + "sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512", + "sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff", + "sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201" ], - "version": "==1.12.2" + "version": "==1.12.3" }, "chardet": { "hashes": [ @@ -71,37 +71,37 @@ }, "cython": { "hashes": [ - "sha256:050b082accb95c0a3a6a9dd1ec6fb0bea5321f5b894c8fd0dc6c3abebb68822b", - "sha256:22e2b55baaa2c7d5aee0376fd1cca39a6036598b9423d8ecd44c514fde1952c3", - "sha256:2be438190b1acce073f3aa4220fdbefb7fc910204dc0a82ef725802195e58dfb", - "sha256:2da461af4845cf858f6ef366e74093917cce731f70e95e03b24e9c8e66d62e6d", - "sha256:46e9d20bd1a7431ac58f95d0b8e5ec14d33363b49bfa1a6245621037fb5bbd49", - "sha256:50a735fccd946217d1dd06a06ac16a582a1a1169c16588e234284b12d7d08d3f", - "sha256:63ddf6ea98dff4dc1d3567412015dbc21dbb728323b2f93ba7bc5994db28bbe2", - "sha256:6976bc08d6a6d1ea53564df6d9ab0bf1fa3e073556e2520931dffa5c3d68b5c3", - "sha256:6c5d33f1b5c864382fbce810a8fd9e015447869ae42e98e6301e977b8165e7ae", - "sha256:6db2fa279e235a9feef96833eb34be2a807add214a253e5e92e99b4b0e6659c2", - "sha256:71e44d9a85dbba538e5c2d0eb8b09b2233b0938010345d5e5bae428b429e6c47", - "sha256:750d9bb4aad23ddd61061d7a4e271bb39df1886c90861faf0b21c9219fd1c2c3", - "sha256:7a262e2e2807e05cacef76c1333bb5b5f46d882d1a6eca1a38c65a0c29df2a64", - "sha256:7f65d40728c6170f7f5a8a9329ba65568069b08581d899c44efb80c7d751524a", - "sha256:8002f39d504120ff0125783dd00464806c6d77519c63f7899c1781d68d7a7887", - "sha256:8cdf96a9ae2201a6423252dfe7a0d7121fdd451f9c81abd276d7112ad563f9fb", - "sha256:a3ea96c1fb1f80b3216f6300067dba85a6c03e4070ef8c4a1a150618d358c265", - "sha256:a8689c538ac7c3162df90f2b141aa9a210798485b8f6f72a31dedab60d3d06f7", - "sha256:aee85f89c22d3d88e958050834c9d16ec6ce9c9b1ce6042ae6e7a2125c2dc3a9", - "sha256:b05ad60e8b8750d3ce254ca3bd45bc222b8c9b7671affbda9bec36a36a24a484", - "sha256:b1ef8bd48a3ff6743c2facb09b666ffa78414cc12f971a21b6f977ffd1968d26", - "sha256:b890e55e7557d1a8a4e4defe082fdbabf241ac4a0fb38608e6ae50644dcc7d76", - "sha256:bddf0db28eca8e650ec120e9d8724646b811c517eae9aa688253abc757547881", - "sha256:be43609c12c85d2e3fefa4c0f7b6197431fdf7ea804e11a419709dc7df46d9ea", - "sha256:be69dc0c85cfc78e808fb22753ab2a99bdbd124702b21141a77da50f2fdae00e", - "sha256:d404cfa5e8595e8c0f81ff19d06a0dcfefcd9a3c8e2bf973985a6cc48731879b", - "sha256:df0696840e4da454475b83bbe0f8f4f8d90b75e68bfff7bfbe5b97cecc50a1df", - "sha256:f7cb71cafe05980404704045110f45c09143ed41dabadb91061c063dd543b924" + "sha256:0ce8f6c789c907472c9084a44b625eba76a85d0189513de1497ab102a9d39ef8", + "sha256:0d67964b747ac09758ba31fe25da2f66f575437df5f121ff481889a7a4485f56", + "sha256:1630823619a87a814e5c1fa9f96544272ce4f94a037a34093fbec74989342328", + "sha256:1a4c634bb049c8482b7a4f3121330de1f1c1f66eac3570e1e885b0c392b6a451", + "sha256:1ec91cc09e9f9a2c3173606232adccc68f3d14be1a15a8c5dc6ab97b47b31528", + "sha256:237a8fdd8333f7248718875d930d1e963ffa519fefeb0756d01d91cbfadab0bc", + "sha256:28a308cbfdf9b7bb44def918ad4a26b2d25a0095fa2f123addda33a32f308d00", + "sha256:2fe3dde34fa125abf29996580d0182c18b8a240d7fa46d10984cc28d27808731", + "sha256:30bda294346afa78c49a343e26f3ab2ad701e09f6a6373f579593f0cfcb1235a", + "sha256:33d27ea23e12bf0d420e40c20308c03ef192d312e187c1f72f385edd9bd6d570", + "sha256:34d24d9370a6089cdd5afe56aa3c4af456e6400f8b4abb030491710ee765bafc", + "sha256:4e4877c2b96fae90f26ee528a87b9347872472b71c6913715ca15c8fe86a68c9", + "sha256:50d6f1f26702e5f2a19890c7bc3de00f9b8a0ec131b52edccd56a60d02519649", + "sha256:55d081162191b7c11c7bfcb7c68e913827dfd5de6ecdbab1b99dab190586c1e8", + "sha256:59d339c7f99920ff7e1d9d162ea309b35775172e4bab9553f1b968cd43b21d6d", + "sha256:6cf4d10df9edc040c955fca708bbd65234920e44c30fccd057ecf3128efb31ad", + "sha256:6ec362539e2a6cf2329cd9820dec64868d8f0babe0d8dc5deff6c87a84d13f68", + "sha256:7edc61a17c14b6e54d5317b0300d2da23d94a719c466f93cafa3b666b058c43b", + "sha256:8e37fc4db3f2c4e7e1ed98fe4fe313f1b7202df985de4ee1451d2e331332afae", + "sha256:b8c996bde5852545507bff45af44328fa48a7b22b5bec2f43083f0b8d1024fd9", + "sha256:bf9c16f3d46af82f89fdefc0d64b2fb02f899c20da64548a8ea336beefcf8d23", + "sha256:c1038aba898bed34ab1b5ddb0d3f9c9ae33b0649387ab9ffe6d0af677f66bfc1", + "sha256:d405649c1bfc42e20d86178257658a859a3217b6e6d950ee8cb76353fcea9c39", + "sha256:db6eeb20a3bd60e1cdcf6ce9a784bc82aec6ab891c800dc5d7824d5cfbfe77f2", + "sha256:e382f8cb40dca45c3b439359028a4b60e74e22d391dc2deb360c0b8239d6ddc0", + "sha256:f3f6c09e2c76f2537d61f907702dd921b04d1c3972f01d5530ef1f748f22bd89", + "sha256:f749287087f67957c020e1de26906e88b8b0c4ea588facb7349c115a63346f67", + "sha256:f86b96e014732c0d1ded2c1f51444c80176a98c21856d0da533db4e4aef54070" ], "index": "pypi", - "version": "==0.29.6" + "version": "==0.29.7" }, "falcon": { "hashes": [ @@ -136,9 +136,9 @@ }, "pycairo": { "hashes": [ - "sha256:abd42a4c9c2069febb4c38fe74bfc4b4a9d3a89fea3bc2e4ba7baff7a20f783f" + "sha256:70172e58b6bad7572a3518c26729b074acdde15e6fee6cbab6d3528ad552b786" ], - "version": "==1.18.0" + "version": "==1.18.1" }, "pycparser": { "hashes": [ @@ -148,10 +148,10 @@ }, "pygobject": { "hashes": [ - "sha256:a4d6f6d8e688d28e07119fc83aabc6e7aad02057f48b2194089959d03654f695" + "sha256:4165d9fb4157e69c76f29e52a6d73a39a804e18ad9ef0eede15efd9c57c52bf2" ], "index": "pypi", - "version": "==3.32.0" + "version": "==3.32.1" }, "pyliblo": { "hashes": [ @@ -196,21 +196,21 @@ }, "python-rtmidi": { "hashes": [ - "sha256:024a7a466c5b97f8fbbbcb2a6c968f10f4344f348a318188c3c0a7ae5f3e1b19", - "sha256:136a6ab2ca38ce97324266411c47cb1004fb0a84c35946d52579c1c66acf4aa6", - "sha256:308fc922fd1e0f110bab6091e899e1d5fccbc8889a423de82bbd0999ea2258d0", - "sha256:31a4031f75e38d7257476c8abed1c8466ee04f65e9bd4380c581f5e394c85a0f", - "sha256:542970637ece4183c5160285661ae71246c80fc37e8d69edaee507c768a65af8", - "sha256:78ace337b36e94a2a83bcb4e3cc80d1a45f740ace0978b60d689bdd9c870eaf0", - "sha256:7e82585912e0f68a6561ea95c981cc6669d00744bc9ac48fb094cfa595411d3a", - "sha256:a9b444eb0706eed6de4fb81f2a08df2b7b5b62ee304bcf95d3e65c8cbaf7c789", - "sha256:b0dc5b6a8f0bee92200425056dd2d753042ba5386feb316b0c8d7fb9d4ea19f8", - "sha256:b240d10966debe6fcbe6f5bb5d20ccaad128fc1c5732a88ad8ad82094759ee8c", - "sha256:c4137111ce8cca60c959f00e592087d343c1c8a036b687c72f7e4c33ca982a92", - "sha256:dee4fbe88a5d7b124de3f91831804fb31a92ec13ace6ab192d1ea76143d2a261" + "sha256:171eaa08fe9b7efefbf1ff9edd9f4ace4c588e6771bf33f75f301d071ba72b20", + "sha256:178fbd5b648c7df2290567090c74eb721d5c547b9261a3509c228bb4be2ada24", + "sha256:4390763bdfc27e3b6e5cd3ba8a44d3a7b5ff5af37f4e06124939548ad93fe5f1", + "sha256:61e9d1c1f1202a1577f06644948af985427030984ff956970a22b50f080d4c2d", + "sha256:7de81e8c807fbc8c2af3890a970ebe38994c031a15b0b8ef64e622bdec1116a9", + "sha256:a5aaba71160f43223bd2e9ffcc99951b682d6fd50370916ccb55354625a81cd7", + "sha256:cbd7f4ca4a950776ee1b9aee66a9d03cbc1f8576135699373d2c5d690970a80f", + "sha256:cf4fce7eb13bf5e276c7357bd5db31decdee7f5e5746848b3aa56f3f325dfdcc", + "sha256:db2031d4ec4429fae3dffaadb0d2bf46e764b009581fc27ef1c94224e95a3ed5", + "sha256:de3b919715d3e86ff4fdf3077934a6932611cc28f40bd89ea97e44c387e61dbb", + "sha256:f108d55feebcdf537ab435557e29bc0c77f685daddc7b665223222e4914ef9d1", + "sha256:f32074ae6fefe43ab37c5ab8a52a1b2133ab488a43c6dfe256de1dd693d5bf7c" ], "index": "pypi", - "version": "==1.2.1" + "version": "==1.3.0" }, "requests": { "hashes": [ @@ -237,10 +237,10 @@ }, "urllib3": { "hashes": [ - "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", - "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + "sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0", + "sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3" ], - "version": "==1.24.1" + "version": "==1.24.2" } }, "develop": { @@ -252,6 +252,14 @@ "index": "pypi", "version": "==1.0.1" }, + "pysnooper": { + "hashes": [ + "sha256:340e1b5fd7f6c4268c5c9a2bc3cbff53ceab2c0625b21097f3f63082ec12310c", + "sha256:cc0fed0e162e34c6c83cb7b25fe2059427ca73d08150088e2374a6b1726ff2b2" + ], + "index": "pypi", + "version": "==0.0.23" + }, "six": { "hashes": [ "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", diff --git a/lisp/application.py b/lisp/application.py index b3a41f342..0b2723e25 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -21,8 +21,8 @@ from os.path import exists, dirname from lisp import layout -from lisp.core.actions_handler import MainActionsHandler -from lisp.core.session import Session +from lisp.command.stack import CommandsStack +from lisp.session import Session from lisp.core.signal import Signal from lisp.core.singleton import Singleton from lisp.core.util import filter_live_properties @@ -48,27 +48,27 @@ class Application(metaclass=Singleton): def __init__(self, app_conf): - self.conf = app_conf - self.session_created = Signal() self.session_before_finalize = Signal() - self.__main_window = MainWindow(self.conf) + self.__conf = app_conf self.__cue_model = CueModel() self.__session = None + self.__commands_stack = CommandsStack() + self.__main_window = MainWindow(self) # Register general settings widget AppConfigurationDialog.registerSettingsPage( - "general", AppGeneral, self.conf + "general", AppGeneral, self.__conf ) AppConfigurationDialog.registerSettingsPage( - "general.cue", CueAppSettings, self.conf + "general.cue", CueAppSettings, self.__conf ) AppConfigurationDialog.registerSettingsPage( - "layouts", LayoutsSettings, self.conf + "layouts", LayoutsSettings, self.__conf ) AppConfigurationDialog.registerSettingsPage( - "plugins", PluginsSettings, self.conf + "plugins", PluginsSettings, self.__conf ) # Register common cue-settings widgets @@ -77,9 +77,14 @@ def __init__(self, app_conf): CueSettingsRegistry().add(Appearance) # Connect mainWindow actions - self.__main_window.new_session.connect(self._new_session_dialog) - self.__main_window.save_session.connect(self._save_to_file) - self.__main_window.open_session.connect(self._load_from_file) + self.__main_window.new_session.connect(self.__new_session_dialog) + self.__main_window.save_session.connect(self.__save_to_file) + self.__main_window.open_session.connect(self.__load_from_file) + + @property + def conf(self): + """:rtype: lisp.core.configuration.Configuration""" + return self.__conf @property def session(self): @@ -94,34 +99,39 @@ def window(self): @property def layout(self): """:rtype: lisp.layout.cue_layout.CueLayout""" - return self.session.layout + return self.__session.layout @property def cue_model(self): """:rtype: lisp.cues.cue_model.CueModel""" return self.__cue_model + @property + def commands_stack(self): + """:rtype: lisp.command.stack.CommandsStack """ + return self.__commands_stack + def start(self, session_file=""): # Show the mainWindow maximized self.__main_window.showMaximized() if exists(session_file): - self._load_from_file(session_file) + self.__load_from_file(session_file) else: layout_name = self.conf.get("layout.default", "nodefault") if layout_name.lower() != "nodefault": - self._new_session(layout.get_layout(layout_name)) + self.__new_session(layout.get_layout(layout_name)) else: - self._new_session_dialog() + self.__new_session_dialog() def finalize(self): - self._delete_session() + self.__delete_session() self.__cue_model = None self.__main_window = None - def _new_session_dialog(self): + def __new_session_dialog(self): """Show the layout-selection dialog""" try: # Prompt the user for a new layout @@ -129,9 +139,9 @@ def _new_session_dialog(self): if dialog.exec() == QDialog.Accepted: # If a file is selected load it, otherwise load the layout if dialog.sessionPath: - self._load_from_file(dialog.sessionPath) + self.__load_from_file(dialog.sessionPath) else: - self._new_session(dialog.selected()) + self.__new_session(dialog.selected()) else: if self.__session is None: # If the user close the dialog, and no layout exists @@ -143,68 +153,64 @@ def _new_session_dialog(self): ) qApp.quit() - def _new_session(self, layout): - self._delete_session() - + def __new_session(self, layout): + self.__delete_session() self.__session = Session(layout(application=self)) - self.__main_window.setSession(self.__session) self.session_created.emit(self.__session) - def _delete_session(self): + def __delete_session(self): if self.__session is not None: - MainActionsHandler.clear() - self.session_before_finalize.emit(self.session) + self.__commands_stack.clear() self.__session.finalize() self.__session = None - def _save_to_file(self, session_file): + def __save_to_file(self, session_file): """Save the current session into a file.""" self.session.session_file = session_file - # Save session path - self.conf.set("session.last_dir", dirname(session_file)) - self.conf.write() - - # Add the cues - session_dict = {"cues": []} + # Get session settings + session_props = self.session.properties() + session_props.pop("session_file", None) - for cue in self.__cue_model: - session_dict["cues"].append( + # Get session cues + session_cues = [] + for cue in self.layout.cues(): + session_cues.append( cue.properties(defaults=False, filter=filter_live_properties) ) - # Sort cues by index, allow sorted-models to load properly - session_dict["cues"].sort(key=lambda cue: cue["index"]) - # Get session settings - session_dict["session"] = self.__session.properties() - # Remove the 'session_file' property (not needed in the session file) - session_dict["session"].pop("session_file", None) + session_dict = {"session": session_props, "cues": session_cues} - # Write to a file the json-encoded dictionary + # Write to a file the json-encoded session with open(session_file, mode="w", encoding="utf-8") as file: - if self.conf["session.minSave"]: + if self.conf.get("session.minSave", False): file.write(json.dumps(session_dict, separators=(",", ":"))) else: file.write(json.dumps(session_dict, sort_keys=True, indent=4)) - MainActionsHandler.set_saved() - self.__main_window.updateWindowTitle() + # Save last session path + self.conf.set("session.lastPath", dirname(session_file)) + self.conf.write() + + # Set the session as saved + self.commands_stack.set_saved() + self.window.updateWindowTitle() - def _load_from_file(self, session_file): + def __load_from_file(self, session_file): """ Load a saved session from file """ try: with open(session_file, mode="r", encoding="utf-8") as file: session_dict = json.load(file) # New session - self._new_session( + self.__new_session( layout.get_layout(session_dict["session"]["layout_type"]) ) - self.__session.update_properties(session_dict["session"]) - self.__session.session_file = session_file + self.session.update_properties(session_dict["session"]) + self.session.session_file = session_file # Load cues for cues_dict in session_dict.get("cues", {}): @@ -213,7 +219,7 @@ def _load_from_file(self, session_file): try: cue = CueFactory.create_cue(cue_type, cue_id=cue_id) cue.update_properties(cues_dict) - self.__cue_model.add(cue) + self.cue_model.add(cue) except Exception: name = cues_dict.get("name", "No name") logger.exception( @@ -223,8 +229,7 @@ def _load_from_file(self, session_file): ) ) - MainActionsHandler.set_saved() - self.__main_window.updateWindowTitle() + self.commands_stack.set_saved() except Exception: logger.exception( translate( @@ -232,4 +237,4 @@ def _load_from_file(self, session_file): 'Error while reading the session file "{}"', ).format(session_file) ) - self._new_session_dialog() + self.__new_session_dialog() diff --git a/lisp/command/__init__.py b/lisp/command/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lisp/core/action.py b/lisp/command/command.py similarity index 66% rename from lisp/core/action.py rename to lisp/command/command.py index 87bf4d57f..cccf5a11d 100644 --- a/lisp/core/action.py +++ b/lisp/command/command.py @@ -15,18 +15,19 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from abc import abstractmethod +from abc import ABC, abstractmethod -class Action: - """Base class for actions. +class Command(ABC): + """Base class for commands. - Action provides the ability to revert the changes done in the "do" method, - via "undo" method, and redo them via the "redo" method. - An action could provide, via the "log" function, a simple log message. + Commands provides the ability to revert the changes done in the "do" method, + via the "undo" method, and redo them via the "redo" method. + + A command could provide, via the "log" function, a simple log message. .. warning:: - Actions may keep reference to external objects. + Commands may keep reference to external objects. """ __slots__ = () @@ -46,14 +47,12 @@ def redo(self): """ self.do() - def log(self): - """Return a short message to describe what the action does. + def log(self) -> str: + """Return a short message to describe what the command does. - The method should return a message that can be used for do/undo/redo - generically, an handler will care about adding context to the message. + The method should return a message generic for do/undo/redo, + an handler will care about adding context to the message. The log message should be user-friendly and localized. - - :rtype: str """ return "" diff --git a/lisp/cues/cue_actions.py b/lisp/command/cue.py similarity index 88% rename from lisp/cues/cue_actions.py rename to lisp/command/cue.py index a51bca1f3..a2cb264e5 100644 --- a/lisp/cues/cue_actions.py +++ b/lisp/command/cue.py @@ -17,11 +17,11 @@ from ast import literal_eval -from lisp.core.action import Action +from lisp.command.command import Command from lisp.ui.ui_utils import translate -class UpdateCueAction(Action): +class UpdateCueCommand(Command): __slots__ = ("__cue", "__new", "__old") @@ -43,12 +43,12 @@ def redo(self): self.do() def log(self): - return translate("CueActionLog", 'Cue settings changed: "{}"').format( + return translate("CueCommandLog", 'Cue settings changed: "{}"').format( self.__cue.name ) -class UpdateCuesAction(Action): +class UpdateCuesCommand(Command): __slots__ = ("__cues", "__new", "__old") @@ -72,4 +72,4 @@ def redo(self): self.do() def log(self): - return translate("CueActionLog", "Cues settings changed.") + return translate("CueCommandLog", "Cues settings changed.") diff --git a/lisp/command/layout.py b/lisp/command/layout.py new file mode 100644 index 000000000..4bcb4def1 --- /dev/null +++ b/lisp/command/layout.py @@ -0,0 +1,37 @@ +# This file is part of Linux Show Player +# +# Copyright 2019 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from lisp.command.command import Command +from lisp.command.model import ModelInsertItemsCommand + + +class LayoutCommand(Command): + __slots__ = ("_layout",) + + def __init__(self, layout): + self._layout = layout + + +class LayoutAutoInsertCuesCommand(ModelInsertItemsCommand): + def __init__(self, layout, *cues): + # Insert after the current selection + selection = tuple(layout.selected_cues()) + super().__init__( + layout.model, + selection[-1].index + 1 if selection else len(layout.model), + *cues, + ) diff --git a/lisp/command/model.py b/lisp/command/model.py new file mode 100644 index 000000000..57cacf603 --- /dev/null +++ b/lisp/command/model.py @@ -0,0 +1,133 @@ +# This file is part of Linux Show Player +# +# Copyright 2019 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from .command import Command + + +class ModelCommand(Command): + __slots__ = "_model" + + def __init__(self, model): + self._model = model + + +class ModelItemsCommand(ModelCommand): + __slots__ = "_items" + + def __init__(self, model, *items): + super().__init__(model) + self._items = items + + +class ModelAddItemsCommand(ModelItemsCommand): + def __init__(self, model, *items): + super().__init__(model, *items) + + def do(self): + for item in self._items: + self._model.add(item) + + def undo(self): + for item in self._items: + self._model.remove(item) + + +class ModelRemoveItemsCommand(ModelItemsCommand): + def __init__(self, model, *items): + super().__init__(model, *items) + + def do(self): + for item in self._items: + self._model.remove(item) + + def undo(self): + for item in self._items: + self._model.add(item) + + +class ModelInsertItemsCommand(ModelItemsCommand): + __slots__ = ("_index",) + + def __init__(self, model_adapter, index, *items): + super().__init__(model_adapter) + self._index = index + self._items = items + + def do(self): + for item in reversed(self._items): + self._model.insert(item, self._index) + + def undo(self): + for item in self._items: + self._model.remove(item) + + +class ModelMoveItemCommand(ModelCommand): + __slots__ = ("_old_index", "_new_index") + + def __init__(self, model_adapter, old_index, new_index): + super().__init__(model_adapter) + self._old_index = old_index + self._new_index = new_index + + def do(self): + self._model.move(self._old_index, self._new_index) + + def undo(self): + self._model.move(self._new_index, self._old_index) + + +class ModelMoveItemsCommand(ModelCommand): + __slots__ = ("_old_indexes", "_new_index", "_before", "_after") + + def __init__(self, model_adapter, old_indexes, new_index): + super().__init__(model_adapter) + self._old_indexes = old_indexes + self._new_index = new_index + self._before = 0 + self._after = 0 + + for old_index in old_indexes: + if old_index < new_index: + self._before += 1 + elif old_index > new_index: + self._after += 1 + + def do(self): + before = after = 0 + shift = bool(self._before) + + for index in self._old_indexes: + if index < self._new_index: + self._model.move(index - before, self._new_index) + before += 1 + elif index > self._new_index: + self._model.move(index, self._new_index + after + shift) + after += 1 + + def undo(self): + before = self._before + after = self._after + shift = bool(self._before) + + for old_index in self._old_indexes: + if old_index < self._new_index: + before -= 1 + self._model.move(self._new_index - before, old_index) + elif old_index > self._new_index: + after -= 1 + self._model.move(self._new_index + shift, old_index + after) diff --git a/lisp/command/stack.py b/lisp/command/stack.py new file mode 100644 index 000000000..88d648bb1 --- /dev/null +++ b/lisp/command/stack.py @@ -0,0 +1,106 @@ +# This file is part of Linux Show Player +# +# Copyright 2016 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import logging +from collections import deque + +from lisp.command.command import Command +from lisp.core.signal import Signal +from lisp.ui.ui_utils import translate + +logger = logging.getLogger(__name__) + + +class CommandsStack: + """Provide a classic undo/redo mechanism based on stacks.""" + + DO_STR = "{}" + UNDO_STR = translate("CommandsStack", "Undo: {}") + REDO_STR = translate("CommandsStack", "Redo: {}") + + def __init__(self, stack_size=None): + super().__init__() + + self._undo = deque(maxlen=stack_size) + self._redo = deque(maxlen=stack_size) + self._saved = None + + self.done = Signal() + self.saved = Signal() + self.undone = Signal() + self.redone = Signal() + + def clear(self): + """Clear the `undo`, `redo` stacks and the save status.""" + self._undo.clear() + self._redo.clear() + self._saved = None + + def do(self, command: Command): + """Execute the command, and add it the `undo` stack.""" + command.do() + + self._logging(command, CommandsStack.DO_STR) + self._undo.append(command) + # Clean the redo stack for maintain consistency + self._redo.clear() + + self.done.emit(command) + + def undo_last(self): + """Undo the last executed command, and add it to the `redo` stack.""" + if self._undo: + command = self._undo.pop() + command.undo() + + self._logging(command, CommandsStack.UNDO_STR) + self._redo.append(command) + + self.undone.emit(command) + + def redo_last(self): + """Redo the last undone command, and add it back to the `undo` stack.""" + if self._redo: + command = self._redo.pop() + command.redo() + + self._logging(command, CommandsStack.REDO_STR) + self._undo.append(command) + + self.redone.emit(command) + + def set_saved(self): + """Set the command at the _top_ of the `undo` stack as `save-point`.""" + if self._undo: + self._saved = self._undo[-1] + self.saved.emit() + + def is_saved(self) -> bool: + """Return True if the command at the _top_ of the `undo` stack is the + one that was on the _top_ of stack the last time `set_saved()` + as been called. + """ + if self._undo: + return self._undo[-1] is self._saved + + return True + + @staticmethod + def _logging(command: Command, template: str): + message = command.log() + if message: + logger.info(template.format(message)) diff --git a/lisp/core/actions_handler.py b/lisp/core/actions_handler.py deleted file mode 100644 index 97f0eacd8..000000000 --- a/lisp/core/actions_handler.py +++ /dev/null @@ -1,132 +0,0 @@ -# This file is part of Linux Show Player -# -# Copyright 2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -import logging -from collections import deque - -from lisp.core.action import Action -from lisp.core.signal import Signal -from lisp.ui.ui_utils import translate - -logger = logging.getLogger(__name__) - - -class ActionsHandler: - """Provide a classic undo/redo mechanism based on stacks.""" - - DO_ACTION_STR = "{}" - UNDO_ACTION_STR = translate("Actions", "Undo: {}") - REDO_ACTION_STR = translate("Actions", "Redo: {}") - - def __init__(self, stack_size=None): - super().__init__() - - self.action_done = Signal() - self.action_undone = Signal() - self.action_redone = Signal() - - self._undo = deque(maxlen=stack_size) - self._redo = deque(maxlen=stack_size) - - self._saved_action = None - - def clear(self): - """Clear the `undo`, `redo` stacks and the save status.""" - self._undo.clear() - self._redo.clear() - self._saved_action = None - - def do_action(self, action: Action): - """Execute the action, and add it the `undo` stack. - - When an action is executed: - * the `do()` method is called - * is logged - * is appended to the `undo` stack - * the `redo` stack is cleared to maintain consistency - * the `action_done` signal is emitted - """ - action.do() - - self._logging(action, ActionsHandler.DO_ACTION_STR) - self._undo.append(action) - # Clean the redo stack for maintain consistency - self._redo.clear() - - self.action_done.emit(action) - - def undo_action(self): - """Undo the last executed action, and add it to the `redo` stack. - - When an action is undone: - * is removed from the `undo` stack - * the `undo` method is called - * is logged - * is appended to the `redo` stack - * the signal `action_undone` is emitted - """ - if self._undo: - action = self._undo.pop() - action.undo() - - self._logging(action, ActionsHandler.UNDO_ACTION_STR) - self._redo.append(action) - - self.action_undone.emit(action) - - def redo_action(self): - """Redo the last undone action, and add it back to the `undo` stack. - - When an action is redone: - * is remove from the `redo` stack - * the `redo` method is called - * is logged - * is added to the `undo` stack - * the `action_redone` signal is emitted - """ - if self._redo: - action = self._redo.pop() - action.redo() - - self._logging(action, ActionsHandler.REDO_ACTION_STR) - self._undo.append(action) - - self.action_redone.emit(action) - - def set_saved(self): - """Set the action at the _top_ of the `undo` stack as `save-point`.""" - if self._undo: - self._saved_action = self._undo[-1] - - def is_saved(self) -> bool: - """Return True if the action at the _top_ of the `undo` stack is the - `save-point`. - """ - if self._undo: - return self._undo[-1] is self._saved_action - - return True - - @staticmethod - def _logging(action: Action, action_str: str): - message = action.log() - if message: - logger.info(action_str.format(message)) - - -# TODO: remove this -MainActionsHandler = ActionsHandler() # "global" action-handler diff --git a/lisp/core/memento_model.py b/lisp/core/memento_model.py deleted file mode 100644 index 338d2fee6..000000000 --- a/lisp/core/memento_model.py +++ /dev/null @@ -1,86 +0,0 @@ -# This file is part of Linux Show Player -# -# Copyright 2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from contextlib import contextmanager - -from lisp.core.actions_handler import MainActionsHandler -from lisp.core.memento_model_actions import ( - AddItemAction, - RemoveItemAction, - MoveItemAction, -) -from lisp.core.proxy_model import ReadOnlyProxyModel - - -class MementoModel(ReadOnlyProxyModel): - """ProxyModel that allow to register models changes in an ActionHandler - - The changes (add/remove) of the base model are registered as actions that - can be undone/redone. - If no handler is specified the MainActionHandler is used. - - ..note:: - The methods, `locks` and `unlock`, avoid reentering when an action is - undone/redone. - """ - - def __init__(self, model, handler=None): - super().__init__(model) - self._add_action = AddItemAction - self._remove_action = RemoveItemAction - - if handler is None: - handler = MainActionsHandler - - self._handler = handler - self._locked = False - - def _item_added(self, item): - if not self._locked: - self._handler.do_action(self._add_action(self, self.model, item)) - - def _item_removed(self, item): - if not self._locked: - self._handler.do_action(self._remove_action(self, self.model, item)) - - def _model_reset(self): - """Reset cannot be reverted""" - - @contextmanager - def lock(self): - self._locked = True - - try: - yield self - finally: - self._locked = False - - -class MementoModelAdapter(MementoModel): - """Extension of the MementoModel that can handle ModelAdapter(s).""" - - def __init__(self, model_adapter, handler=None): - super().__init__(model_adapter, handler) - self._move_action = MoveItemAction - - self.model.item_moved.connect(self._item_moved) - - def _item_moved(self, old_index, new_index): - if not self._locked: - self._handler.do_action( - self._move_action(self, self.model, old_index, new_index) - ) diff --git a/lisp/core/memento_model_actions.py b/lisp/core/memento_model_actions.py deleted file mode 100644 index afb738864..000000000 --- a/lisp/core/memento_model_actions.py +++ /dev/null @@ -1,96 +0,0 @@ -# This file is part of Linux Show Player -# -# Copyright 2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from abc import abstractmethod - -from lisp.core.action import Action - - -class MementoAction(Action): - """Actions created by the MementoModel to register model changes.""" - - __slots__ = ("_m_model", "_model") - - def __init__(self, m_model, model): - super().__init__() - self._m_model = m_model - self._model = model - - def do(self): - pass - - def undo(self): - with self._m_model.lock(): - self.__undo__() - - def redo(self): - with self._m_model.lock(): - self.__redo__() - - @abstractmethod - def __undo__(self): - pass - - @abstractmethod - def __redo__(self): - pass - - -class AddItemAction(MementoAction): - - __slots__ = "_item" - - def __init__(self, m_model, model, item): - super().__init__(m_model, model) - self._item = item - - def __undo__(self): - self._model.remove(self._item) - - def __redo__(self): - self._model.add(self._item) - - -class RemoveItemAction(MementoAction): - - __slots__ = "_item" - - def __init__(self, m_model, model, item): - super().__init__(m_model, model) - self._item = item - - def __undo__(self): - self._model.add(self._item) - - def __redo__(self): - self._model.remove(self._item) - - -class MoveItemAction(MementoAction): - - __slots__ = ("_old_index", "_new_index") - - def __init__(self, m_model, model_adapter, old_index, new_index): - super().__init__(m_model, model_adapter) - self._old_index = old_index - self._new_index = new_index - - def __undo__(self): - self._model.move(self._new_index, self._old_index) - - def __redo__(self): - self._model.move(self._old_index, self._new_index) diff --git a/lisp/cues/cue_memento_model.py b/lisp/cues/cue_memento_model.py deleted file mode 100644 index 43cc8914a..000000000 --- a/lisp/cues/cue_memento_model.py +++ /dev/null @@ -1,51 +0,0 @@ -# This file is part of Linux Show Player -# -# Copyright 2018 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from lisp.core.memento_model import MementoModelAdapter -from lisp.core.memento_model_actions import ( - AddItemAction, - MoveItemAction, - RemoveItemAction, -) - - -class CueMementoAdapter(MementoModelAdapter): - def __init__(self, model_adapter, handler=None): - super().__init__(model_adapter, handler) - self._add_action = CueAddAction - self._remove_action = CueRemoveAction - self._move_action = CueMoveAction - - -# TODO: keep only the cue-properties for the cue-removed/added action ?? - - -class CueAddAction(AddItemAction): - def log(self): - return 'Add cue "{}"'.format(self._item.name) - - -class CueRemoveAction(RemoveItemAction): - def log(self): - return 'Remove cue "{}"'.format(self._item.name) - - -class CueMoveAction(MoveItemAction): - def log(self): - return 'Move cue from "{}" to "{}"'.format( - self._old_index + 1, self._new_index + 1 - ) diff --git a/lisp/default.json b/lisp/default.json index 9b140edae..342d76fec 100644 --- a/lisp/default.json +++ b/lisp/default.json @@ -1,5 +1,5 @@ { - "_version_": "0.6dev.13", + "_version_": "0.6dev.15", "cue": { "fadeAction": 3, "fadeActionType": "Linear", @@ -8,11 +8,12 @@ }, "theme": { "theme": "Dark", - "icons": "numix" + "icons": "Numix" }, "locale": "", "session": { - "minSave": true + "minSave": true, + "lastPath": "" }, "logging": { "limit": 10000, diff --git a/lisp/i18n/ts/en/action_cues.ts b/lisp/i18n/ts/en/action_cues.ts index fd0e909a3..058fbbc16 100644 --- a/lisp/i18n/ts/en/action_cues.ts +++ b/lisp/i18n/ts/en/action_cues.ts @@ -3,22 +3,22 @@ CollectionCue - + Add Add - + Remove Remove - + Cue Cue - + Action Action diff --git a/lisp/i18n/ts/en/cart_layout.ts b/lisp/i18n/ts/en/cart_layout.ts index 7f33f80d8..a59dff846 100644 --- a/lisp/i18n/ts/en/cart_layout.ts +++ b/lisp/i18n/ts/en/cart_layout.ts @@ -63,7 +63,7 @@ - + Add pages @@ -73,27 +73,27 @@ - + Number of Pages: - + Page {number} - + Warning - + Every cue in the page will be lost. - + Are you sure to continue? @@ -111,7 +111,7 @@ LayoutDescription - + Organize cues in grid like pages @@ -119,27 +119,27 @@ LayoutDetails - + Click a cue to run it - + SHIFT + Click to edit a cue - + CTRL + Click to select a cue - + To copy cues drag them while pressing CTRL - + To move cues drag them while pressing SHIFT @@ -147,7 +147,7 @@ LayoutName - + Cart Layout diff --git a/lisp/i18n/ts/en/gst_backend.ts b/lisp/i18n/ts/en/gst_backend.ts index c3d143bc9..3b7be8d54 100644 --- a/lisp/i18n/ts/en/gst_backend.ts +++ b/lisp/i18n/ts/en/gst_backend.ts @@ -113,12 +113,12 @@ GstBackend - + Audio cue (from file) - + Select media files @@ -319,42 +319,42 @@ UriInputSettings - + Source Source - + Find File Find File - + Buffering Buffering - + Use Buffering Use Buffering - + Attempt download on network streams Attempt download on network streams - + Buffer size (-1 default value) Buffer size (-1 default value) - + Choose file Choose file - + All files All files diff --git a/lisp/i18n/ts/en/lisp.ts b/lisp/i18n/ts/en/lisp.ts index 6e3307793..1d5656ab3 100644 --- a/lisp/i18n/ts/en/lisp.ts +++ b/lisp/i18n/ts/en/lisp.ts @@ -66,16 +66,6 @@ Actions - - - Undo: {} - - - - - Redo: {} - - AlsaSinkSettings @@ -104,40 +94,45 @@ AppGeneralSettings - + Default layout - + Enable startup layout selector - + Application themes - + UI theme: - + Icons theme: - - Languages: + + Application language (require restart) + + + + + Language: ApplicationError - + Startup error @@ -286,7 +281,7 @@ - + Add pages @@ -296,27 +291,27 @@ - + Number of Pages: - + Page {number} - + Warning Warning - + Every cue in the page will be lost. - + Are you sure to continue? @@ -324,22 +319,22 @@ CollectionCue - + Add - + Remove - + Cue Cue - + Action @@ -372,6 +367,19 @@ + + CommandsStack + + + Undo: {} + + + + + Redo: {} + + + ConfigurationDebug @@ -599,12 +607,12 @@ Cue settings changed: "{}" - Cue settings changed: "{}" + Cue settings changed: "{}" Cues settings changed. - Cues settings changed. + Cues settings changed. @@ -663,11 +671,24 @@ - + Misc cues + + CueCommandLog + + + Cue settings changed: "{}" + Cue settings changed: "{}" + + + + Cues settings changed. + Cues settings changed. + + CueName @@ -956,12 +977,12 @@ GstBackend - + Audio cue (from file) - + Select media files @@ -1080,7 +1101,7 @@ - + Organize cues in grid like pages @@ -1093,7 +1114,7 @@ - + To copy cues drag them while pressing CTRL @@ -1103,22 +1124,22 @@ - + Click a cue to run it - + SHIFT + Click to edit a cue - + CTRL + Click to select a cue - + To move cues drag them while pressing SHIFT @@ -1131,7 +1152,7 @@ - + Cart Layout @@ -1159,27 +1180,17 @@ Stop All - Stop All + Stop All Pause All - Pause All + Pause All Interrupt All - Interrupt All - - - - Use fade (global actions) - - - - - Resume All - + Interrupt All @@ -1187,27 +1198,27 @@ - + Show playing cues - + Show dB-meters - + Show accurate time - + Show seek-bars - + Auto-select next cue @@ -1262,17 +1273,17 @@ - + Edit selected Edit selected - + Clone cue - + Clone selected @@ -1282,32 +1293,32 @@ - + Remove selected - + Selection mode - + Pause all - + Stop all - + Interrupt all - + Resume all @@ -1331,26 +1342,36 @@ Remove selected cues + + + Use fade + + + + + Copy of {} + + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action - + Post wait Post wait @@ -1631,132 +1652,132 @@ MainWindow - + &File &File - + New session New session - + Open Open - + Save session Save session - + Preferences Preferences - + Save as Save as - + Full Screen Full Screen - + Exit Exit - + &Edit &Edit - + Undo Undo - + Redo Redo - + Select all Select all - + Select all media cues Select all media cues - + Deselect all Deselect all - + CTRL+SHIFT+A CTRL+SHIFT+A - + Invert selection Invert selection - + CTRL+I CTRL+I - + Edit selected Edit selected - + CTRL+SHIFT+E CTRL+SHIFT+E - + &Layout &Layout - + &Tools &Tools - + Edit selection Edit selection - + &About &About - + About About - + About Qt About Qt - + Close session Close session @@ -1771,7 +1792,7 @@ Discard the changes? - + Do you want to save them now? @@ -1779,7 +1800,7 @@ MainWindowDebug - + Registered cue menu: "{}" @@ -1787,7 +1808,7 @@ MainWindowError - + Cannot create cue {} @@ -2116,17 +2137,17 @@ PluginsError - + Failed to load "{}" - + Failed to terminate plugin: "{}" - + the requested plugin is not loaded: {} @@ -2134,12 +2155,12 @@ PluginsInfo - + Plugin loaded: "{}" - + Plugin terminated: "{}" @@ -2147,7 +2168,7 @@ PluginsWarning - + Cannot satisfy dependencies for: {} @@ -2211,7 +2232,7 @@ - + Preset name @@ -2246,7 +2267,7 @@ - + Warning Warning @@ -2256,17 +2277,17 @@ - + Cannot export correctly. - + Cannot import correctly. - + Cue type @@ -2297,7 +2318,7 @@ RenameCues - + Rename Cues @@ -2357,6 +2378,14 @@ + + RenameCuesCommand + + + Renamed {number} cues + + + RenameUiDebug @@ -2368,7 +2397,7 @@ ReplayGain - + ReplayGain / Normalization @@ -2398,17 +2427,17 @@ - + Calculate - + Reset all - + Reset selected @@ -2416,12 +2445,12 @@ ReplayGainDebug - + Applied gain for: {} - + Discarded gain for: {} @@ -2429,17 +2458,17 @@ ReplayGainInfo - + Gain processing stopped by user. - + Started gain calculation for: {} - + Gain calculated for: {} @@ -2480,7 +2509,7 @@ Appearance - + General General @@ -2716,27 +2745,27 @@ TriggersSettings - + Add - + Remove - + Trigger - + Cue Cue - + Action @@ -2744,42 +2773,42 @@ UriInputSettings - + Source - + Find File - + Buffering - + Use Buffering - + Attempt download on network streams - + Buffer size (-1 default value) - + Choose file - + All files diff --git a/lisp/i18n/ts/en/list_layout.ts b/lisp/i18n/ts/en/list_layout.ts index 2f0dcfc0c..4f8a34b25 100644 --- a/lisp/i18n/ts/en/list_layout.ts +++ b/lisp/i18n/ts/en/list_layout.ts @@ -42,27 +42,27 @@ - + Show playing cues - + Show dB-meters - + Show accurate time - + Show seek-bars - + Auto-select next cue @@ -97,17 +97,17 @@ - + Edit cue - + Remove cue - + Selection mode @@ -142,22 +142,22 @@ - + Edit selected - + Clone cue - + Clone selected - + Remove selected @@ -176,26 +176,31 @@ GO minimum interval (ms): + + + Copy of {} + + ListLayoutHeader - + Cue - + Pre wait - + Action - + Post wait diff --git a/lisp/i18n/ts/en/presets.ts b/lisp/i18n/ts/en/presets.ts index ccb5d9e50..36c2cb78f 100644 --- a/lisp/i18n/ts/en/presets.ts +++ b/lisp/i18n/ts/en/presets.ts @@ -56,7 +56,7 @@ Select Preset - + Preset name Preset name @@ -91,7 +91,7 @@ Import - + Warning Warning @@ -101,17 +101,17 @@ The same name is already used! - + Cannot export correctly. Cannot export correctly. - + Cannot import correctly. Cannot import correctly. - + Cue type Cue type diff --git a/lisp/i18n/ts/en/rename_cues.ts b/lisp/i18n/ts/en/rename_cues.ts index 3c386eebb..3f05cf36b 100644 --- a/lisp/i18n/ts/en/rename_cues.ts +++ b/lisp/i18n/ts/en/rename_cues.ts @@ -3,7 +3,7 @@ RenameCues - + Rename Cues @@ -63,6 +63,14 @@ + + RenameCuesCommand + + + Renamed {number} cues + + + RenameUiDebug diff --git a/lisp/i18n/ts/en/replay_gain.ts b/lisp/i18n/ts/en/replay_gain.ts index 70614ea78..4296e97c9 100644 --- a/lisp/i18n/ts/en/replay_gain.ts +++ b/lisp/i18n/ts/en/replay_gain.ts @@ -3,22 +3,22 @@ ReplayGain - + ReplayGain / Normalization ReplayGain / Normalization - + Calculate Calculate - + Reset all Reset all - + Reset selected Reset selected @@ -51,12 +51,12 @@ ReplayGainDebug - + Applied gain for: {} - + Discarded gain for: {} @@ -64,17 +64,17 @@ ReplayGainInfo - + Gain processing stopped by user. - + Started gain calculation for: {} - + Gain calculated for: {} diff --git a/lisp/i18n/ts/en/triggers.ts b/lisp/i18n/ts/en/triggers.ts index 65b66c922..245e5749a 100644 --- a/lisp/i18n/ts/en/triggers.ts +++ b/lisp/i18n/ts/en/triggers.ts @@ -34,27 +34,27 @@ TriggersSettings - + Add Add - + Remove Remove - + Trigger Trigger - + Cue Cue - + Action Action diff --git a/lisp/layout/cue_layout.py b/lisp/layout/cue_layout.py index 4a71fb752..9df790c1e 100644 --- a/lisp/layout/cue_layout.py +++ b/lisp/layout/cue_layout.py @@ -17,12 +17,12 @@ from abc import abstractmethod -from lisp.core.actions_handler import MainActionsHandler +from lisp.command.model import ModelRemoveItemsCommand from lisp.core.has_properties import HasProperties from lisp.core.signal import Signal from lisp.core.util import greatest_common_superclass from lisp.cues.cue import Cue, CueAction -from lisp.cues.cue_actions import UpdateCueAction, UpdateCuesAction +from lisp.command.cue import UpdateCueCommand, UpdateCuesCommand from lisp.layout.cue_menu import CueContextMenu from lisp.ui.settings.cue_settings import CueSettingsDialog from lisp.ui.ui_utils import adjust_widget_position @@ -56,6 +56,13 @@ def cue_model(self): """:rtype: lisp.cues.cue_model.CueModel""" return self.app.cue_model + @property + @abstractmethod + def model(self): + """:rtype: lisp.core.model_adapter.ModelAdapter""" + return None + + @property @abstractmethod def view(self): """:rtype: PyQt5.QtWidgets.QWidget""" @@ -180,8 +187,7 @@ def edit_cue(self, cue): dialog = CueSettingsDialog(cue, parent=self.app.window) def on_apply(settings): - action = UpdateCueAction(settings, cue) - MainActionsHandler.do_action(action) + self.app.commands_stack.do(UpdateCueCommand(settings, cue)) dialog.onApply.connect(on_apply) dialog.exec() @@ -194,8 +200,7 @@ def edit_cues(self, cues): ) def on_apply(settings): - action = UpdateCuesAction(settings, cues) - MainActionsHandler.do_action(action) + self.app.commands_stack.do(UpdateCuesCommand(settings, cues)) dialog.onApply.connect(on_apply) dialog.exec() @@ -209,7 +214,7 @@ def show_context_menu(self, position): def show_cue_context_menu(self, cues, position): if cues: - menu = self.CuesMenu.create_qmenu(cues, self.view()) + menu = self.CuesMenu.create_qmenu(cues, self.view) menu.move(position) menu.show() @@ -219,8 +224,9 @@ def finalize(self): """Destroy all the layout elements""" def _remove_cue(self, cue): - self.cue_model.remove(cue) + self._remove_cues((cue,)) def _remove_cues(self, cues): - for cue in cues: - self.cue_model.remove(cue) + self.app.commands_stack.do( + ModelRemoveItemsCommand(self.cue_model, *cues) + ) diff --git a/lisp/main.py b/lisp/main.py index a77a7b501..2392d6f28 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -135,7 +135,7 @@ def main(): except Exception: logging.exception("Unable to load theme.") - # Set LiSP icon theme (not the Qt one) + # Set LiSP icon theme try: icon_theme = app_conf["theme.icons"] IconTheme.set_theme_name(icon_theme) diff --git a/lisp/plugins/action_cues/collection_cue.py b/lisp/plugins/action_cues/collection_cue.py index 783e3eff5..bb3480683 100644 --- a/lisp/plugins/action_cues/collection_cue.py +++ b/lisp/plugins/action_cues/collection_cue.py @@ -69,7 +69,9 @@ def __init__(self, **kwargs): ) self.collectionModel = CollectionModel() - self.collectionView = CollectionView(self.cue_dialog, parent=self) + self.collectionView = CollectionView( + Application().cue_model, self.cue_dialog, parent=self + ) self.collectionView.setModel(self.collectionModel) self.collectionView.setAlternatingRowColors(True) self.layout().addWidget(self.collectionView) @@ -122,7 +124,7 @@ def _remove_selected(self): class CollectionView(QTableView): - def __init__(self, cue_select, **kwargs): + def __init__(self, cue_model, cue_select, **kwargs): super().__init__(**kwargs) self.setSelectionBehavior(QTableView.SelectRows) @@ -138,7 +140,10 @@ def __init__(self, cue_select, **kwargs): self.verticalHeader().setDefaultSectionSize(26) self.verticalHeader().setHighlightSections(False) - self.delegates = [CueSelectionDelegate(cue_select), CueActionDelegate()] + self.delegates = [ + CueSelectionDelegate(cue_model, cue_select), + CueActionDelegate(), + ] for column, delegate in enumerate(self.delegates): self.setItemDelegateForColumn(column, delegate) diff --git a/lisp/plugins/cart_layout/layout.py b/lisp/plugins/cart_layout/layout.py index 841395804..ae0aae8dd 100644 --- a/lisp/plugins/cart_layout/layout.py +++ b/lisp/plugins/cart_layout/layout.py @@ -15,15 +15,16 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import re + from PyQt5.QtCore import QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QAction, QInputDialog, QMessageBox +from lisp.command.model import ModelInsertItemsCommand, ModelMoveItemCommand from lisp.core.configuration import DummyConfiguration from lisp.core.properties import ProxyProperty -from lisp.core.signal import Connection from lisp.cues.cue import Cue from lisp.cues.cue_factory import CueFactory -from lisp.cues.cue_memento_model import CueMementoAdapter from lisp.cues.media_cue import MediaCue from lisp.layout.cue_layout import CueLayout from lisp.layout.cue_menu import ( @@ -79,7 +80,6 @@ def __init__(self, application): self._cart_model.item_removed.connect(self.__cue_removed) self._cart_model.item_moved.connect(self.__cue_moved) self._cart_model.model_reset.connect(self.__model_reset) - self._memento_model = CueMementoAdapter(self._cart_model) self._cart_view = CartTabWidget() self._cart_view.keyPressed.connect(self._key_pressed) @@ -148,7 +148,7 @@ def __init__(self, application): ), SimpleMenuAction( translate("ListLayout", "Remove cue"), - self.cue_model.remove, + self._remove_cue, translate("ListLayout", "Remove selected cues"), self._remove_cues, ), @@ -199,6 +199,11 @@ def retranslate(self): translate("CartLayout", "Show accurate time") ) + @property + def model(self): + return self._cart_model + + @property def view(self): return self._cart_view @@ -272,10 +277,12 @@ def remove_page(self, index): page.deleteLater() # Rename every successive tab accordingly - # TODO: check for "standard" naming using a regex text = translate("CartLayout", "Page {number}") + pattern = re.compile(text.format(number="[0-9]")) for n in range(index, self._cart_view.count()): - self._cart_view.setTabText(n, text.format(number=n + 1)) + # Only rename the tabs which text match the default pattern + if pattern.fullmatch(self._cart_view.tabText(n)): + self._cart_view.setTabText(n, text.format(number=n + 1)) @tabs.get def _get_tabs(self): @@ -386,7 +393,10 @@ def _move_widget(self, widget, to_row, to_column): new_index = self.to_1d_index( (self._cart_view.currentIndex(), to_row, to_column) ) - self._cart_model.move(widget.cue.index, new_index) + + self.app.commands_stack.do( + ModelMoveItemCommand(self._cart_model, widget.cue.index, new_index) + ) def _copy_widget(self, widget, to_row, to_column): new_index = self.to_1d_index( @@ -394,7 +404,9 @@ def _copy_widget(self, widget, to_row, to_column): ) new_cue = CueFactory.clone_cue(widget.cue) - self._cart_model.insert(new_cue, new_index) + self.app.commands_stack.do( + ModelInsertItemsCommand(self._cart_model, new_index, new_cue) + ) def _cue_context_menu(self, position): current_page = self._cart_view.currentWidget() @@ -408,9 +420,6 @@ def _cue_context_menu(self, position): self.show_cue_context_menu(cues, position) - def _remove_cue_action(self, cue): - self._cart_model.remove(cue) - def _reset_cue_volume(self, cue): page, row, column = self.to_3d_index(cue.index) widget = self._page(page).widget(row, column) diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index 4d7c7c1b5..78bdc0bee 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -22,6 +22,7 @@ from lisp import backend from lisp.backend.backend import Backend as BaseBackend +from lisp.command.layout import LayoutAutoInsertCuesCommand from lisp.core.decorators import memoize from lisp.core.plugin import Plugin from lisp.cues.cue_factory import CueFactory @@ -130,6 +131,7 @@ def _add_uri_audio_cue(self): if layout_selection: start_index = layout_selection[-1].index + 1 + cues = [] for index, file in enumerate(files, start_index): file = self.app.session.rel_path(file) cue = factory(uri="file://" + file) @@ -139,6 +141,10 @@ def _add_uri_audio_cue(self): if start_index != -1: cue.index = index - self.app.cue_model.add(cue) + cues.append(cue) + + self.app.commands_stack.do( + LayoutAutoInsertCuesCommand(self.app.session.layout, *cues) + ) QApplication.restoreOverrideCursor() diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index 5606f61c8..c0a10d2a8 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -19,12 +19,12 @@ from PyQt5.QtGui import QKeySequence from PyQt5.QtWidgets import QAction +from lisp.command.model import ModelInsertItemsCommand from lisp.core.configuration import DummyConfiguration from lisp.core.properties import ProxyProperty from lisp.core.signal import Connection from lisp.cues.cue import Cue, CueAction, CueNextAction from lisp.cues.cue_factory import CueFactory -from lisp.cues.cue_memento_model import CueMementoAdapter from lisp.layout.cue_layout import CueLayout from lisp.layout.cue_menu import ( SimpleMenuAction, @@ -64,7 +64,6 @@ def __init__(self, application): super().__init__(application) self._list_model = CueListModel(self.cue_model) self._list_model.item_added.connect(self.__cue_added) - self._memento_model = CueMementoAdapter(self._list_model) self._running_model = RunningCueModel(self.cue_model) self._go_timer = QTimer() self._go_timer.setSingleShot(True) @@ -152,7 +151,7 @@ def __init__(self, application): ), SimpleMenuAction( translate("ListLayout", "Remove cue"), - self.cue_model.remove, + self._remove_cue, translate("ListLayout", "Remove selected"), self._remove_cues, ), @@ -180,12 +179,17 @@ def retranslate(self): translate("ListLayout", "Selection mode") ) - def cues(self, cue_type=Cue): - yield from self._list_model + @property + def model(self): + return self._list_model + @property def view(self): return self._view + def cues(self, cue_type=Cue): + yield from self._list_model + def standby_index(self): return self._view.listView.standbyIndex() @@ -356,8 +360,13 @@ def _clone_cue(self, cue): def _clone_cues(self, cues): for pos, cue in enumerate(cues, cues[-1].index + 1): clone = CueFactory.clone_cue(cue) - clone.name = "Copy of {}".format(clone.name) - self._list_model.insert(clone, pos) + clone.name = translate("ListLayout", "Copy of {}").format( + clone.name + ) + + self.app.commands_stack.do( + ModelInsertItemsCommand(self.model, pos, clone) + ) def __go_slot(self): if not self._go_timer.isActive(): diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py index 4d66970b6..d48e3e45a 100644 --- a/lisp/plugins/list_layout/list_view.py +++ b/lisp/plugins/list_layout/list_view.py @@ -25,7 +25,8 @@ from PyQt5.QtGui import QKeyEvent, QContextMenuEvent, QBrush, QColor from PyQt5.QtWidgets import QTreeWidget, QHeaderView, QTreeWidgetItem -from lisp.core.signal import Connection +from lisp.application import Application +from lisp.command.model import ModelMoveItemsCommand, ModelInsertItemsCommand from lisp.cues.cue_factory import CueFactory from lisp.plugins.list_layout.list_widgets import ( CueStatusIcons, @@ -101,9 +102,9 @@ def __init__(self, listModel, parent=None): # Watch for model changes self._model = listModel - self._model.item_added.connect(self.__cueAdded, Connection.QtQueued) - self._model.item_moved.connect(self.__cueMoved, Connection.QtQueued) - self._model.item_removed.connect(self.__cueRemoved, Connection.QtQueued) + self._model.item_added.connect(self.__cueAdded) + self._model.item_moved.connect(self.__cueMoved) + self._model.item_removed.connect(self.__cueRemoved) self._model.model_reset.connect(self.__modelReset) # Setup the columns headers @@ -154,26 +155,17 @@ def dropEvent(self, event): rows.append(row) if event.proposedAction() == Qt.MoveAction: - before = 0 - after = 0 - for row in sorted(rows): - if row < to_index: - self._model.move(row - before, to_index) - before += 1 - elif row > to_index: - # if we have already moved something we need to shift, - # bool(before) is evaluated as 1 (True) or 0 (False) - self._model.move(row, to_index + after + bool(before)) - after += 1 + Application().commands_stack.do( + ModelMoveItemsCommand(self._model, rows, to_index) + ) elif event.proposedAction() == Qt.CopyAction: - # TODO: add a copy/clone method to the model? new_cues = [] for row in sorted(rows): new_cues.append(CueFactory.clone_cue(self._model.item(row))) - for cue in new_cues: - self._model.insert(cue, to_index) - to_index += 1 + Application().commands_stack.do( + ModelInsertItemsCommand(self._model, to_index, *new_cues) + ) def contextMenuEvent(self, event): self.contextMenuInvoked.emit(event) diff --git a/lisp/plugins/list_layout/view.py b/lisp/plugins/list_layout/view.py index 65f326ed7..410cca764 100644 --- a/lisp/plugins/list_layout/view.py +++ b/lisp/plugins/list_layout/view.py @@ -66,6 +66,7 @@ def __listViewCurrentChanged(self, current, _): cue = None if current is not None: index = self.listView.indexOfTopLevelItem(current) - cue = self.listModel.item(index) + if index < len(self.listModel): + cue = self.listModel.item(index) self.infoPanel.cue = cue diff --git a/lisp/plugins/presets/lib.py b/lisp/plugins/presets/lib.py index 098a13794..51d800a86 100644 --- a/lisp/plugins/presets/lib.py +++ b/lisp/plugins/presets/lib.py @@ -20,8 +20,9 @@ from zipfile import ZipFile from lisp import app_dirs -from lisp.core.actions_handler import MainActionsHandler -from lisp.cues.cue_actions import UpdateCueAction, UpdateCuesAction +from lisp.command.layout import LayoutAutoInsertCuesCommand +from lisp.command.cue import UpdateCueCommand, UpdateCuesCommand +from lisp.cues.cue_factory import CueFactory PRESETS_DIR = os.path.join(app_dirs.user_data_dir, "presets") @@ -55,31 +56,45 @@ def preset_exists(name): return os.path.exists(preset_path(name)) -def load_on_cue(preset_name, cue): +def insert_cue_from_preset(app, preset_name): + """Insert a new cue using the given preset name. + + :type app: lisp.application.Application + :param preset_name: The preset to be loaded in the new cue + :type preset_name: str + """ + preset = load_preset(preset_name) + cue = CueFactory.create_cue(preset["_type_"]) + cue.update_properties(preset) + + app.commands_stack.do(LayoutAutoInsertCuesCommand(app.layout, cue)) + + +def load_on_cue(app, preset_name, cue): """Load the preset with the given name on cue. Use `UpdateCueAction` + :type app: lisp.application.Application :param preset_name: The preset to be loaded :type preset_name: str :param cue: The cue on which load the preset :type cue: lisp.cue.Cue """ - MainActionsHandler.do_action(UpdateCueAction(load_preset(preset_name), cue)) + app.commands_stack.do(UpdateCueCommand(load_preset(preset_name), cue)) -def load_on_cues(preset_name, cues): +def load_on_cues(app, preset_name, cues): """ Use `UpdateCuesAction` + :type app: lisp.application.Application :param preset_name: The preset to be loaded :type preset_name: str :param cues: The cues on which load the preset :type cues: typing.Iterable[lisp.cue.Cue] """ - MainActionsHandler.do_action( - UpdateCuesAction(load_preset(preset_name), cues) - ) + app.commands_stack.do(UpdateCuesCommand(load_preset(preset_name), cues)) def load_preset(name): diff --git a/lisp/plugins/presets/presets.py b/lisp/plugins/presets/presets.py index 5de09ea4a..5fdc0620a 100644 --- a/lisp/plugins/presets/presets.py +++ b/lisp/plugins/presets/presets.py @@ -91,7 +91,7 @@ def __load_on_cue(self, cue): preset_name = select_preset_dialog() if preset_name is not None: try: - load_on_cue(preset_name, cue) + load_on_cue(self.app, preset_name, cue) except OSError as e: load_preset_error(e, preset_name) @@ -99,7 +99,7 @@ def __load_on_cues(self, cues): preset_name = select_preset_dialog() if preset_name is not None: try: - load_on_cues(preset_name, cues) + load_on_cues(self.app, preset_name, cues) except OSError as e: load_preset_error(e, preset_name) diff --git a/lisp/plugins/presets/presets_ui.py b/lisp/plugins/presets/presets_ui.py index fdf605d9f..24b92916b 100644 --- a/lisp/plugins/presets/presets_ui.py +++ b/lisp/plugins/presets/presets_ui.py @@ -16,6 +16,7 @@ # along with Linux Show Player. If not, see . import logging +from zipfile import BadZipFile from PyQt5.QtCore import Qt from PyQt5.QtWidgets import ( @@ -34,7 +35,6 @@ QFileDialog, QHBoxLayout, ) -from zipfile import BadZipFile from lisp.core.util import natural_keys from lisp.cues.cue import Cue @@ -50,6 +50,7 @@ rename_preset, load_preset, load_on_cues, + insert_cue_from_preset, ) from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.cue_settings import CueSettingsDialog, CueSettingsRegistry @@ -301,24 +302,17 @@ def __edit_preset(self): def __cue_from_preset(self, preset_name): try: - preset = load_preset(preset_name) - if preset is not None: - if CueFactory.has_factory(preset.get("_type_")): - cue = CueFactory.create_cue(preset["_type_"]) - - cue.update_properties(preset) - self.app.cue_model.add(cue) - else: - QMessageBox.warning( - self, - translate("Presets", "Warning"), - translate( - "Presets", - "Cannot create a cue from this " "preset: {}", - ).format(preset_name), - ) + insert_cue_from_preset(self.app, preset_name) except OSError as e: load_preset_error(e, preset_name) + except Exception as e: + QMessageBox.warning( + self, + translate("Presets", "Warning"), + translate( + "Presets", "Cannot create a cue from this preset: {}" + ).format(preset_name), + ) def __cue_from_selected(self): for item in self.presetsList.selectedItems(): @@ -331,7 +325,7 @@ def __load_on_selected(self): try: cues = self.app.layout.get_selected_cues() if cues: - load_on_cues(preset_name, cues) + load_on_cues(self.app, preset_name, cues) except OSError as e: load_preset_error(e, preset_name) diff --git a/lisp/plugins/rename_cues/rename_action.py b/lisp/plugins/rename_cues/command.py similarity index 61% rename from lisp/plugins/rename_cues/rename_action.py rename to lisp/plugins/rename_cues/command.py index 88589aaa2..0a84e970d 100644 --- a/lisp/plugins/rename_cues/rename_action.py +++ b/lisp/plugins/rename_cues/command.py @@ -16,27 +16,31 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from lisp.core.action import Action +from lisp.command.command import Command +from lisp.ui.ui_utils import translate -class RenameCueAction(Action): +class RenameCuesCommand(Command): + __slots__ = ("_model", "_names") - # Store names for undo/redo in a dict like that : {'id': name} - names = {} + def __init__(self, model, rename_list): + self._model = model + self._names = {} - def __init__(self, app, new_cue_list): - """Store new names with id""" - self.app = app - - for renamed_cue in new_cue_list: - self.names[renamed_cue["id"]] = renamed_cue["cue_preview"] + for renamed_cue in rename_list: + self._names[renamed_cue["id"]] = renamed_cue["cue_preview"] def do(self): """Use stored name and exchange with current names""" - for id in self.names: - cue = self.app.cue_model.get(id) - cue.name, self.names[id] = self.names[id], cue.name + for id in self._names: + cue = self._model.get(id) + cue.name, self._names[id] = self._names[id], cue.name def undo(self): """Restore previous names and save current for redo""" self.do() + + def log(self) -> str: + return translate("RenameCuesCommand", "Renamed {number} cues").format( + number=len(self._names) + ) diff --git a/lisp/plugins/rename_cues/rename_cues.py b/lisp/plugins/rename_cues/rename_cues.py index 3981717f0..f40c02da1 100644 --- a/lisp/plugins/rename_cues/rename_cues.py +++ b/lisp/plugins/rename_cues/rename_cues.py @@ -17,10 +17,9 @@ # along with Linux Show Player. If not, see . from PyQt5.QtWidgets import QAction, QDialog -from lisp.core.actions_handler import MainActionsHandler from lisp.core.plugin import Plugin -from lisp.plugins.rename_cues.rename_action import RenameCueAction +from lisp.plugins.rename_cues.command import RenameCuesCommand from lisp.ui.ui_utils import translate from .rename_ui import RenameUi @@ -57,8 +56,8 @@ def rename(self): renameUi.exec() if renameUi.result() == QDialog.Accepted: - MainActionsHandler.do_action( - RenameCueAction(self.app, renameUi.cues_list) + self.app.commands_stack.do( + RenameCuesCommand(self.app.cue_model, renameUi.cues_list) ) def terminate(self): diff --git a/lisp/plugins/replay_gain/replay_gain.py b/lisp/plugins/replay_gain/replay_gain.py index 1bd177c6a..f686192c2 100644 --- a/lisp/plugins/replay_gain/replay_gain.py +++ b/lisp/plugins/replay_gain/replay_gain.py @@ -26,8 +26,7 @@ from gi.repository import Gst from PyQt5.QtWidgets import QMenu, QAction, QDialog -from lisp.core.action import Action -from lisp.core.actions_handler import MainActionsHandler +from lisp.command.command import Command from lisp.core.plugin import Plugin from lisp.core.signal import Signal, Connection from lisp.cues.media_cue import MediaCue @@ -75,7 +74,7 @@ def gain(self): if gainUi.result() == QDialog.Accepted: if gainUi.only_selected(): - cues = self.app.layout.get_selected_cues(MediaCue) + cues = self.app.layout.selected_cues(MediaCue) else: cues = self.app.cue_model.filter(MediaCue) @@ -97,6 +96,7 @@ def gain(self): # Gain (main) thread self._gain_thread = GainMainThread( + self.app.commands_stack, files, gainUi.threads(), gainUi.mode(), @@ -125,58 +125,58 @@ def _reset_all(self): self._reset(self.app.cue_model.filter(MediaCue)) def _reset_selected(self): - self._reset(self.app.layout.get_selected_cues(MediaCue)) + self._reset(self.app.layout.selected_cues(MediaCue)) def _reset(self, cues): - action = GainAction() + action = UpdateGainCommand() for cue in cues: action.add_media(cue.media, ReplayGain.RESET_VALUE) - MainActionsHandler.do_action(action) + self.app.commands_stack.do(action) -class GainAction(Action): - __slots__ = ("__media_list", "__new_volumes", "__old_volumes") +class UpdateGainCommand(Command): + __slots__ = ("_media_list", "_new_volumes", "_old_volumes") def __init__(self): - self.__media_list = [] - self.__new_volumes = [] - self.__old_volumes = [] + self._media_list = [] + self._new_volumes = [] + self._old_volumes = [] def add_media(self, media, new_volume): - volume = media.element("Volume") - if volume is not None: - self.__media_list.append(media) - self.__new_volumes.append(new_volume) - self.__old_volumes.append(volume.normal_volume) + volume_element = media.element("Volume") + if volume_element is not None: + self._media_list.append(media) + self._new_volumes.append(new_volume) + self._old_volumes.append(volume_element.normal_volume) def do(self): - for n, media in enumerate(self.__media_list): - volume = media.element("Volume") - if volume is not None: - volume.normal_volume = self.__new_volumes[n] + self.__update(self._new_volumes) def undo(self): - for n, media in enumerate(self.__media_list): - volume = media.element("Volume") - if volume is not None: - volume.normal_volume = self.__old_volumes[n] + self.__update(self._old_volumes) - def redo(self): - self.do() + def __update(self, volumes): + for media, volume in zip(self._media_list, volumes): + volume_element = media.element("Volume") + if volume_element is not None: + volume_element.normal_volume = volume def log(self): return "Replay gain volume adjusted." class GainMainThread(Thread): - def __init__(self, files, threads, mode, ref_level, norm_level): + def __init__( + self, commands_stack, files, threads, mode, ref_level, norm_level + ): super().__init__() self.setDaemon(True) - self._futures = {} + self._commands_stack = commands_stack + self._update_command = UpdateGainCommand() self._running = False - self._action = GainAction() + self._futures = {} # file -> media {'filename1': [media1, media2], 'filename2': [media3]} self.files = files @@ -218,7 +218,7 @@ def run(self): break if self._running: - MainActionsHandler.do_action(self._action) + self._commands_stack.do(self._update_command) else: logger.info( translate("ReplayGainInfo", "Gain processing stopped by user.") @@ -238,7 +238,7 @@ def _post_process(self, gain): volume = 1 / gain.peak_value * pow(10, self.norm_level / 20) for media in self.files[gain.uri]: - self._action.add_media(media, volume) + self._update_command.add_media(media, volume) logger.debug( translate("ReplayGainDebug", "Applied gain for: {}").format( diff --git a/lisp/plugins/triggers/triggers_settings.py b/lisp/plugins/triggers/triggers_settings.py index 554e9e6b3..792dc9616 100644 --- a/lisp/plugins/triggers/triggers_settings.py +++ b/lisp/plugins/triggers/triggers_settings.py @@ -50,7 +50,9 @@ def __init__(self, **kwargs): self.triggersModel = TriggersModel() - self.triggersView = TriggersView(self.cue_select, parent=self) + self.triggersView = TriggersView( + Application().cue_model, self.cue_select, parent=self + ) self.triggersView.setModel(self.triggersModel) self.layout().addWidget(self.triggersView) @@ -114,7 +116,7 @@ def getSettings(self): class TriggersView(QTableView): - def __init__(self, cue_select, **kwargs): + def __init__(self, cue_model, cue_select, **kwargs): super().__init__(**kwargs) self.setSelectionBehavior(QTableView.SelectRows) @@ -134,7 +136,7 @@ def __init__(self, cue_select, **kwargs): ComboBoxDelegate( options=[e.value for e in CueTriggers], tr_context="CueTriggers" ), - CueSelectionDelegate(cue_select), + CueSelectionDelegate(cue_model, cue_select), CueActionDelegate(), ] diff --git a/lisp/core/session.py b/lisp/session.py similarity index 100% rename from lisp/core/session.py rename to lisp/session.py diff --git a/lisp/ui/icons/numix/custom/cue-interrupt.svg b/lisp/ui/icons/Numix/custom/cue-interrupt.svg similarity index 100% rename from lisp/ui/icons/numix/custom/cue-interrupt.svg rename to lisp/ui/icons/Numix/custom/cue-interrupt.svg diff --git a/lisp/ui/icons/numix/custom/cue-pause.svg b/lisp/ui/icons/Numix/custom/cue-pause.svg similarity index 100% rename from lisp/ui/icons/numix/custom/cue-pause.svg rename to lisp/ui/icons/Numix/custom/cue-pause.svg diff --git a/lisp/ui/icons/numix/custom/cue-select-next.svg b/lisp/ui/icons/Numix/custom/cue-select-next.svg similarity index 100% rename from lisp/ui/icons/numix/custom/cue-select-next.svg rename to lisp/ui/icons/Numix/custom/cue-select-next.svg diff --git a/lisp/ui/icons/numix/custom/cue-start.svg b/lisp/ui/icons/Numix/custom/cue-start.svg similarity index 100% rename from lisp/ui/icons/numix/custom/cue-start.svg rename to lisp/ui/icons/Numix/custom/cue-start.svg diff --git a/lisp/ui/icons/numix/custom/cue-stop.svg b/lisp/ui/icons/Numix/custom/cue-stop.svg similarity index 100% rename from lisp/ui/icons/numix/custom/cue-stop.svg rename to lisp/ui/icons/Numix/custom/cue-stop.svg diff --git a/lisp/ui/icons/numix/custom/cue-trigger-next.svg b/lisp/ui/icons/Numix/custom/cue-trigger-next.svg similarity index 100% rename from lisp/ui/icons/numix/custom/cue-trigger-next.svg rename to lisp/ui/icons/Numix/custom/cue-trigger-next.svg diff --git a/lisp/ui/icons/numix/custom/fadein-generic.svg b/lisp/ui/icons/Numix/custom/fadein-generic.svg similarity index 100% rename from lisp/ui/icons/numix/custom/fadein-generic.svg rename to lisp/ui/icons/Numix/custom/fadein-generic.svg diff --git a/lisp/ui/icons/numix/custom/fadeout-generic.svg b/lisp/ui/icons/Numix/custom/fadeout-generic.svg similarity index 100% rename from lisp/ui/icons/numix/custom/fadeout-generic.svg rename to lisp/ui/icons/Numix/custom/fadeout-generic.svg diff --git a/lisp/ui/icons/Numix/index.theme b/lisp/ui/icons/Numix/index.theme new file mode 100644 index 000000000..72b9b880e --- /dev/null +++ b/lisp/ui/icons/Numix/index.theme @@ -0,0 +1,47 @@ +[Icon Theme] +Name=Numix +Comment=Numix icon theme +Inherits=hicolor +Directories=standard/actions,standard/categories,standard/devices,standard/emblems,standard/places,standard/status + +[standard/actions] +Size=16 +MinSize=16 +MaxSize=256 +Context=Actions +Type=Scalable + +[standard/categories] +Size=16 +MinSize=16 +MaxSize=256 +Context=Categories +Type=Scalable + +[standard/devices] +Size=16 +MinSize=16 +MaxSize=256 +Context=Devices +Type=Scalable + +[standard/emblems] +Size=16 +MinSize=16 +MaxSize=256 +Context=Emblems +Type=Scalable + +[standard/places] +Size=16 +MinSize=16 +MaxSize=256 +Context=Places +Type=Scalable + +[standard/status] +Size=16 +MinSize=16 +MaxSize=256 +Context=Status +Type=Scalable diff --git a/lisp/ui/icons/numix/standard/actions/address-book-new.svg b/lisp/ui/icons/Numix/standard/actions/address-book-new.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/address-book-new.svg rename to lisp/ui/icons/Numix/standard/actions/address-book-new.svg diff --git a/lisp/ui/icons/numix/standard/actions/application-exit.svg b/lisp/ui/icons/Numix/standard/actions/application-exit.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/application-exit.svg rename to lisp/ui/icons/Numix/standard/actions/application-exit.svg diff --git a/lisp/ui/icons/numix/standard/actions/appointment-new.svg b/lisp/ui/icons/Numix/standard/actions/appointment-new.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/appointment-new.svg rename to lisp/ui/icons/Numix/standard/actions/appointment-new.svg diff --git a/lisp/ui/icons/numix/standard/actions/call-start.svg b/lisp/ui/icons/Numix/standard/actions/call-start.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/call-start.svg rename to lisp/ui/icons/Numix/standard/actions/call-start.svg diff --git a/lisp/ui/icons/numix/standard/actions/call-stop.svg b/lisp/ui/icons/Numix/standard/actions/call-stop.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/call-stop.svg rename to lisp/ui/icons/Numix/standard/actions/call-stop.svg diff --git a/lisp/ui/icons/numix/standard/actions/contact-new.svg b/lisp/ui/icons/Numix/standard/actions/contact-new.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/contact-new.svg rename to lisp/ui/icons/Numix/standard/actions/contact-new.svg diff --git a/lisp/ui/icons/numix/standard/actions/document-new.svg b/lisp/ui/icons/Numix/standard/actions/document-new.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/document-new.svg rename to lisp/ui/icons/Numix/standard/actions/document-new.svg diff --git a/lisp/ui/icons/numix/standard/actions/document-open-recent.svg b/lisp/ui/icons/Numix/standard/actions/document-open-recent.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/document-open-recent.svg rename to lisp/ui/icons/Numix/standard/actions/document-open-recent.svg diff --git a/lisp/ui/icons/numix/standard/actions/document-open.svg b/lisp/ui/icons/Numix/standard/actions/document-open.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/document-open.svg rename to lisp/ui/icons/Numix/standard/actions/document-open.svg diff --git a/lisp/ui/icons/numix/standard/actions/document-page-setup.svg b/lisp/ui/icons/Numix/standard/actions/document-page-setup.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/document-page-setup.svg rename to lisp/ui/icons/Numix/standard/actions/document-page-setup.svg diff --git a/lisp/ui/icons/numix/standard/actions/document-print-preview.svg b/lisp/ui/icons/Numix/standard/actions/document-print-preview.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/document-print-preview.svg rename to lisp/ui/icons/Numix/standard/actions/document-print-preview.svg diff --git a/lisp/ui/icons/numix/standard/actions/document-print.svg b/lisp/ui/icons/Numix/standard/actions/document-print.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/document-print.svg rename to lisp/ui/icons/Numix/standard/actions/document-print.svg diff --git a/lisp/ui/icons/numix/standard/actions/document-properties.svg b/lisp/ui/icons/Numix/standard/actions/document-properties.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/document-properties.svg rename to lisp/ui/icons/Numix/standard/actions/document-properties.svg diff --git a/lisp/ui/icons/numix/standard/actions/document-revert.svg b/lisp/ui/icons/Numix/standard/actions/document-revert.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/document-revert.svg rename to lisp/ui/icons/Numix/standard/actions/document-revert.svg diff --git a/lisp/ui/icons/numix/standard/actions/document-save-as.svg b/lisp/ui/icons/Numix/standard/actions/document-save-as.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/document-save-as.svg rename to lisp/ui/icons/Numix/standard/actions/document-save-as.svg diff --git a/lisp/ui/icons/numix/standard/actions/document-save.svg b/lisp/ui/icons/Numix/standard/actions/document-save.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/document-save.svg rename to lisp/ui/icons/Numix/standard/actions/document-save.svg diff --git a/lisp/ui/icons/numix/standard/actions/document-send.svg b/lisp/ui/icons/Numix/standard/actions/document-send.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/document-send.svg rename to lisp/ui/icons/Numix/standard/actions/document-send.svg diff --git a/lisp/ui/icons/numix/standard/actions/edit-clear.svg b/lisp/ui/icons/Numix/standard/actions/edit-clear.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/edit-clear.svg rename to lisp/ui/icons/Numix/standard/actions/edit-clear.svg diff --git a/lisp/ui/icons/numix/standard/actions/edit-copy.svg b/lisp/ui/icons/Numix/standard/actions/edit-copy.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/edit-copy.svg rename to lisp/ui/icons/Numix/standard/actions/edit-copy.svg diff --git a/lisp/ui/icons/numix/standard/actions/edit-cut.svg b/lisp/ui/icons/Numix/standard/actions/edit-cut.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/edit-cut.svg rename to lisp/ui/icons/Numix/standard/actions/edit-cut.svg diff --git a/lisp/ui/icons/numix/standard/actions/edit-delete.svg b/lisp/ui/icons/Numix/standard/actions/edit-delete.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/edit-delete.svg rename to lisp/ui/icons/Numix/standard/actions/edit-delete.svg diff --git a/lisp/ui/icons/numix/standard/actions/edit-find-replace.svg b/lisp/ui/icons/Numix/standard/actions/edit-find-replace.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/edit-find-replace.svg rename to lisp/ui/icons/Numix/standard/actions/edit-find-replace.svg diff --git a/lisp/ui/icons/numix/standard/actions/edit-find.svg b/lisp/ui/icons/Numix/standard/actions/edit-find.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/edit-find.svg rename to lisp/ui/icons/Numix/standard/actions/edit-find.svg diff --git a/lisp/ui/icons/numix/standard/actions/edit-paste.svg b/lisp/ui/icons/Numix/standard/actions/edit-paste.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/edit-paste.svg rename to lisp/ui/icons/Numix/standard/actions/edit-paste.svg diff --git a/lisp/ui/icons/numix/standard/actions/edit-redo.svg b/lisp/ui/icons/Numix/standard/actions/edit-redo.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/edit-redo.svg rename to lisp/ui/icons/Numix/standard/actions/edit-redo.svg diff --git a/lisp/ui/icons/numix/standard/actions/edit-select-all.svg b/lisp/ui/icons/Numix/standard/actions/edit-select-all.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/edit-select-all.svg rename to lisp/ui/icons/Numix/standard/actions/edit-select-all.svg diff --git a/lisp/ui/icons/numix/standard/actions/edit-undo.svg b/lisp/ui/icons/Numix/standard/actions/edit-undo.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/edit-undo.svg rename to lisp/ui/icons/Numix/standard/actions/edit-undo.svg diff --git a/lisp/ui/icons/numix/standard/actions/folder-new.svg b/lisp/ui/icons/Numix/standard/actions/folder-new.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/folder-new.svg rename to lisp/ui/icons/Numix/standard/actions/folder-new.svg diff --git a/lisp/ui/icons/numix/standard/actions/format-indent-less.svg b/lisp/ui/icons/Numix/standard/actions/format-indent-less.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/format-indent-less.svg rename to lisp/ui/icons/Numix/standard/actions/format-indent-less.svg diff --git a/lisp/ui/icons/numix/standard/actions/format-indent-more.svg b/lisp/ui/icons/Numix/standard/actions/format-indent-more.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/format-indent-more.svg rename to lisp/ui/icons/Numix/standard/actions/format-indent-more.svg diff --git a/lisp/ui/icons/numix/standard/actions/format-justify-center.svg b/lisp/ui/icons/Numix/standard/actions/format-justify-center.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/format-justify-center.svg rename to lisp/ui/icons/Numix/standard/actions/format-justify-center.svg diff --git a/lisp/ui/icons/numix/standard/actions/format-justify-fill.svg b/lisp/ui/icons/Numix/standard/actions/format-justify-fill.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/format-justify-fill.svg rename to lisp/ui/icons/Numix/standard/actions/format-justify-fill.svg diff --git a/lisp/ui/icons/numix/standard/actions/format-justify-left.svg b/lisp/ui/icons/Numix/standard/actions/format-justify-left.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/format-justify-left.svg rename to lisp/ui/icons/Numix/standard/actions/format-justify-left.svg diff --git a/lisp/ui/icons/numix/standard/actions/format-justify-right.svg b/lisp/ui/icons/Numix/standard/actions/format-justify-right.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/format-justify-right.svg rename to lisp/ui/icons/Numix/standard/actions/format-justify-right.svg diff --git a/lisp/ui/icons/numix/standard/actions/format-text-bold.svg b/lisp/ui/icons/Numix/standard/actions/format-text-bold.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/format-text-bold.svg rename to lisp/ui/icons/Numix/standard/actions/format-text-bold.svg diff --git a/lisp/ui/icons/numix/standard/actions/format-text-direction-ltr.svg b/lisp/ui/icons/Numix/standard/actions/format-text-direction-ltr.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/format-text-direction-ltr.svg rename to lisp/ui/icons/Numix/standard/actions/format-text-direction-ltr.svg diff --git a/lisp/ui/icons/numix/standard/actions/format-text-direction-rtl.svg b/lisp/ui/icons/Numix/standard/actions/format-text-direction-rtl.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/format-text-direction-rtl.svg rename to lisp/ui/icons/Numix/standard/actions/format-text-direction-rtl.svg diff --git a/lisp/ui/icons/numix/standard/actions/format-text-italic.svg b/lisp/ui/icons/Numix/standard/actions/format-text-italic.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/format-text-italic.svg rename to lisp/ui/icons/Numix/standard/actions/format-text-italic.svg diff --git a/lisp/ui/icons/numix/standard/actions/format-text-strikethrough.svg b/lisp/ui/icons/Numix/standard/actions/format-text-strikethrough.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/format-text-strikethrough.svg rename to lisp/ui/icons/Numix/standard/actions/format-text-strikethrough.svg diff --git a/lisp/ui/icons/numix/standard/actions/format-text-underline.svg b/lisp/ui/icons/Numix/standard/actions/format-text-underline.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/format-text-underline.svg rename to lisp/ui/icons/Numix/standard/actions/format-text-underline.svg diff --git a/lisp/ui/icons/numix/standard/actions/go-bottom.svg b/lisp/ui/icons/Numix/standard/actions/go-bottom.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/go-bottom.svg rename to lisp/ui/icons/Numix/standard/actions/go-bottom.svg diff --git a/lisp/ui/icons/numix/standard/actions/go-down.svg b/lisp/ui/icons/Numix/standard/actions/go-down.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/go-down.svg rename to lisp/ui/icons/Numix/standard/actions/go-down.svg diff --git a/lisp/ui/icons/numix/standard/actions/go-first.svg b/lisp/ui/icons/Numix/standard/actions/go-first.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/go-first.svg rename to lisp/ui/icons/Numix/standard/actions/go-first.svg diff --git a/lisp/ui/icons/numix/standard/actions/go-home.svg b/lisp/ui/icons/Numix/standard/actions/go-home.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/go-home.svg rename to lisp/ui/icons/Numix/standard/actions/go-home.svg diff --git a/lisp/ui/icons/numix/standard/actions/go-jump.svg b/lisp/ui/icons/Numix/standard/actions/go-jump.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/go-jump.svg rename to lisp/ui/icons/Numix/standard/actions/go-jump.svg diff --git a/lisp/ui/icons/numix/standard/actions/go-last.svg b/lisp/ui/icons/Numix/standard/actions/go-last.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/go-last.svg rename to lisp/ui/icons/Numix/standard/actions/go-last.svg diff --git a/lisp/ui/icons/numix/standard/actions/go-next.svg b/lisp/ui/icons/Numix/standard/actions/go-next.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/go-next.svg rename to lisp/ui/icons/Numix/standard/actions/go-next.svg diff --git a/lisp/ui/icons/numix/standard/actions/go-previous.svg b/lisp/ui/icons/Numix/standard/actions/go-previous.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/go-previous.svg rename to lisp/ui/icons/Numix/standard/actions/go-previous.svg diff --git a/lisp/ui/icons/numix/standard/actions/go-top.svg b/lisp/ui/icons/Numix/standard/actions/go-top.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/go-top.svg rename to lisp/ui/icons/Numix/standard/actions/go-top.svg diff --git a/lisp/ui/icons/numix/standard/actions/go-up.svg b/lisp/ui/icons/Numix/standard/actions/go-up.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/go-up.svg rename to lisp/ui/icons/Numix/standard/actions/go-up.svg diff --git a/lisp/ui/icons/numix/standard/actions/help-about.svg b/lisp/ui/icons/Numix/standard/actions/help-about.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/help-about.svg rename to lisp/ui/icons/Numix/standard/actions/help-about.svg diff --git a/lisp/ui/icons/numix/standard/actions/help-contents.svg b/lisp/ui/icons/Numix/standard/actions/help-contents.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/help-contents.svg rename to lisp/ui/icons/Numix/standard/actions/help-contents.svg diff --git a/lisp/ui/icons/numix/standard/actions/help-faq.svg b/lisp/ui/icons/Numix/standard/actions/help-faq.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/help-faq.svg rename to lisp/ui/icons/Numix/standard/actions/help-faq.svg diff --git a/lisp/ui/icons/numix/standard/actions/insert-image.svg b/lisp/ui/icons/Numix/standard/actions/insert-image.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/insert-image.svg rename to lisp/ui/icons/Numix/standard/actions/insert-image.svg diff --git a/lisp/ui/icons/numix/standard/actions/insert-link.svg b/lisp/ui/icons/Numix/standard/actions/insert-link.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/insert-link.svg rename to lisp/ui/icons/Numix/standard/actions/insert-link.svg diff --git a/lisp/ui/icons/numix/standard/actions/insert-object.svg b/lisp/ui/icons/Numix/standard/actions/insert-object.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/insert-object.svg rename to lisp/ui/icons/Numix/standard/actions/insert-object.svg diff --git a/lisp/ui/icons/numix/standard/actions/insert-text.svg b/lisp/ui/icons/Numix/standard/actions/insert-text.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/insert-text.svg rename to lisp/ui/icons/Numix/standard/actions/insert-text.svg diff --git a/lisp/ui/icons/numix/standard/actions/list-add.svg b/lisp/ui/icons/Numix/standard/actions/list-add.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/list-add.svg rename to lisp/ui/icons/Numix/standard/actions/list-add.svg diff --git a/lisp/ui/icons/numix/standard/actions/list-remove.svg b/lisp/ui/icons/Numix/standard/actions/list-remove.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/list-remove.svg rename to lisp/ui/icons/Numix/standard/actions/list-remove.svg diff --git a/lisp/ui/icons/numix/standard/actions/mail-forward.svg b/lisp/ui/icons/Numix/standard/actions/mail-forward.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/mail-forward.svg rename to lisp/ui/icons/Numix/standard/actions/mail-forward.svg diff --git a/lisp/ui/icons/numix/standard/actions/mail-mark-important.svg b/lisp/ui/icons/Numix/standard/actions/mail-mark-important.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/mail-mark-important.svg rename to lisp/ui/icons/Numix/standard/actions/mail-mark-important.svg diff --git a/lisp/ui/icons/numix/standard/actions/mail-mark-junk.svg b/lisp/ui/icons/Numix/standard/actions/mail-mark-junk.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/mail-mark-junk.svg rename to lisp/ui/icons/Numix/standard/actions/mail-mark-junk.svg diff --git a/lisp/ui/icons/numix/standard/actions/mail-mark-notjunk.svg b/lisp/ui/icons/Numix/standard/actions/mail-mark-notjunk.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/mail-mark-notjunk.svg rename to lisp/ui/icons/Numix/standard/actions/mail-mark-notjunk.svg diff --git a/lisp/ui/icons/numix/standard/actions/mail-mark-read.svg b/lisp/ui/icons/Numix/standard/actions/mail-mark-read.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/mail-mark-read.svg rename to lisp/ui/icons/Numix/standard/actions/mail-mark-read.svg diff --git a/lisp/ui/icons/numix/standard/actions/mail-mark-unread.svg b/lisp/ui/icons/Numix/standard/actions/mail-mark-unread.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/mail-mark-unread.svg rename to lisp/ui/icons/Numix/standard/actions/mail-mark-unread.svg diff --git a/lisp/ui/icons/numix/standard/actions/mail-message-new.svg b/lisp/ui/icons/Numix/standard/actions/mail-message-new.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/mail-message-new.svg rename to lisp/ui/icons/Numix/standard/actions/mail-message-new.svg diff --git a/lisp/ui/icons/numix/standard/actions/mail-reply-all.svg b/lisp/ui/icons/Numix/standard/actions/mail-reply-all.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/mail-reply-all.svg rename to lisp/ui/icons/Numix/standard/actions/mail-reply-all.svg diff --git a/lisp/ui/icons/numix/standard/actions/mail-reply-sender.svg b/lisp/ui/icons/Numix/standard/actions/mail-reply-sender.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/mail-reply-sender.svg rename to lisp/ui/icons/Numix/standard/actions/mail-reply-sender.svg diff --git a/lisp/ui/icons/numix/standard/actions/mail-send-receive.svg b/lisp/ui/icons/Numix/standard/actions/mail-send-receive.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/mail-send-receive.svg rename to lisp/ui/icons/Numix/standard/actions/mail-send-receive.svg diff --git a/lisp/ui/icons/numix/standard/actions/mail-send.svg b/lisp/ui/icons/Numix/standard/actions/mail-send.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/mail-send.svg rename to lisp/ui/icons/Numix/standard/actions/mail-send.svg diff --git a/lisp/ui/icons/numix/standard/actions/media-eject.svg b/lisp/ui/icons/Numix/standard/actions/media-eject.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/media-eject.svg rename to lisp/ui/icons/Numix/standard/actions/media-eject.svg diff --git a/lisp/ui/icons/numix/standard/actions/media-playback-pause.svg b/lisp/ui/icons/Numix/standard/actions/media-playback-pause.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/media-playback-pause.svg rename to lisp/ui/icons/Numix/standard/actions/media-playback-pause.svg diff --git a/lisp/ui/icons/numix/standard/actions/media-playback-start.svg b/lisp/ui/icons/Numix/standard/actions/media-playback-start.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/media-playback-start.svg rename to lisp/ui/icons/Numix/standard/actions/media-playback-start.svg diff --git a/lisp/ui/icons/numix/standard/actions/media-playback-stop.svg b/lisp/ui/icons/Numix/standard/actions/media-playback-stop.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/media-playback-stop.svg rename to lisp/ui/icons/Numix/standard/actions/media-playback-stop.svg diff --git a/lisp/ui/icons/numix/standard/actions/media-record.svg b/lisp/ui/icons/Numix/standard/actions/media-record.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/media-record.svg rename to lisp/ui/icons/Numix/standard/actions/media-record.svg diff --git a/lisp/ui/icons/numix/standard/actions/media-seek-backward.svg b/lisp/ui/icons/Numix/standard/actions/media-seek-backward.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/media-seek-backward.svg rename to lisp/ui/icons/Numix/standard/actions/media-seek-backward.svg diff --git a/lisp/ui/icons/numix/standard/actions/media-seek-forward.svg b/lisp/ui/icons/Numix/standard/actions/media-seek-forward.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/media-seek-forward.svg rename to lisp/ui/icons/Numix/standard/actions/media-seek-forward.svg diff --git a/lisp/ui/icons/numix/standard/actions/media-skip-backward.svg b/lisp/ui/icons/Numix/standard/actions/media-skip-backward.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/media-skip-backward.svg rename to lisp/ui/icons/Numix/standard/actions/media-skip-backward.svg diff --git a/lisp/ui/icons/numix/standard/actions/media-skip-forward.svg b/lisp/ui/icons/Numix/standard/actions/media-skip-forward.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/media-skip-forward.svg rename to lisp/ui/icons/Numix/standard/actions/media-skip-forward.svg diff --git a/lisp/ui/icons/numix/standard/actions/object-flip-horizontal.svg b/lisp/ui/icons/Numix/standard/actions/object-flip-horizontal.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/object-flip-horizontal.svg rename to lisp/ui/icons/Numix/standard/actions/object-flip-horizontal.svg diff --git a/lisp/ui/icons/numix/standard/actions/object-flip-vertical.svg b/lisp/ui/icons/Numix/standard/actions/object-flip-vertical.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/object-flip-vertical.svg rename to lisp/ui/icons/Numix/standard/actions/object-flip-vertical.svg diff --git a/lisp/ui/icons/numix/standard/actions/object-rotate-left.svg b/lisp/ui/icons/Numix/standard/actions/object-rotate-left.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/object-rotate-left.svg rename to lisp/ui/icons/Numix/standard/actions/object-rotate-left.svg diff --git a/lisp/ui/icons/numix/standard/actions/object-rotate-right.svg b/lisp/ui/icons/Numix/standard/actions/object-rotate-right.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/object-rotate-right.svg rename to lisp/ui/icons/Numix/standard/actions/object-rotate-right.svg diff --git a/lisp/ui/icons/numix/standard/actions/process-stop.svg b/lisp/ui/icons/Numix/standard/actions/process-stop.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/process-stop.svg rename to lisp/ui/icons/Numix/standard/actions/process-stop.svg diff --git a/lisp/ui/icons/numix/standard/actions/system-lock-screen.svg b/lisp/ui/icons/Numix/standard/actions/system-lock-screen.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/system-lock-screen.svg rename to lisp/ui/icons/Numix/standard/actions/system-lock-screen.svg diff --git a/lisp/ui/icons/numix/standard/actions/system-log-out.svg b/lisp/ui/icons/Numix/standard/actions/system-log-out.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/system-log-out.svg rename to lisp/ui/icons/Numix/standard/actions/system-log-out.svg diff --git a/lisp/ui/icons/numix/standard/actions/system-reboot.svg b/lisp/ui/icons/Numix/standard/actions/system-reboot.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/system-reboot.svg rename to lisp/ui/icons/Numix/standard/actions/system-reboot.svg diff --git a/lisp/ui/icons/numix/standard/actions/system-run.svg b/lisp/ui/icons/Numix/standard/actions/system-run.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/system-run.svg rename to lisp/ui/icons/Numix/standard/actions/system-run.svg diff --git a/lisp/ui/icons/numix/standard/actions/system-search.svg b/lisp/ui/icons/Numix/standard/actions/system-search.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/system-search.svg rename to lisp/ui/icons/Numix/standard/actions/system-search.svg diff --git a/lisp/ui/icons/numix/standard/actions/system-shutdown.svg b/lisp/ui/icons/Numix/standard/actions/system-shutdown.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/system-shutdown.svg rename to lisp/ui/icons/Numix/standard/actions/system-shutdown.svg diff --git a/lisp/ui/icons/numix/standard/actions/tools-check-spelling.svg b/lisp/ui/icons/Numix/standard/actions/tools-check-spelling.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/tools-check-spelling.svg rename to lisp/ui/icons/Numix/standard/actions/tools-check-spelling.svg diff --git a/lisp/ui/icons/numix/standard/actions/view-fullscreen.svg b/lisp/ui/icons/Numix/standard/actions/view-fullscreen.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/view-fullscreen.svg rename to lisp/ui/icons/Numix/standard/actions/view-fullscreen.svg diff --git a/lisp/ui/icons/numix/standard/actions/view-refresh.svg b/lisp/ui/icons/Numix/standard/actions/view-refresh.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/view-refresh.svg rename to lisp/ui/icons/Numix/standard/actions/view-refresh.svg diff --git a/lisp/ui/icons/numix/standard/actions/view-restore.svg b/lisp/ui/icons/Numix/standard/actions/view-restore.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/view-restore.svg rename to lisp/ui/icons/Numix/standard/actions/view-restore.svg diff --git a/lisp/ui/icons/numix/standard/actions/view-sort-ascending.svg b/lisp/ui/icons/Numix/standard/actions/view-sort-ascending.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/view-sort-ascending.svg rename to lisp/ui/icons/Numix/standard/actions/view-sort-ascending.svg diff --git a/lisp/ui/icons/numix/standard/actions/view-sort-descending.svg b/lisp/ui/icons/Numix/standard/actions/view-sort-descending.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/view-sort-descending.svg rename to lisp/ui/icons/Numix/standard/actions/view-sort-descending.svg diff --git a/lisp/ui/icons/numix/standard/actions/window-close.svg b/lisp/ui/icons/Numix/standard/actions/window-close.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/window-close.svg rename to lisp/ui/icons/Numix/standard/actions/window-close.svg diff --git a/lisp/ui/icons/numix/standard/actions/window-new.svg b/lisp/ui/icons/Numix/standard/actions/window-new.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/window-new.svg rename to lisp/ui/icons/Numix/standard/actions/window-new.svg diff --git a/lisp/ui/icons/numix/standard/actions/zoom-fit-best.svg b/lisp/ui/icons/Numix/standard/actions/zoom-fit-best.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/zoom-fit-best.svg rename to lisp/ui/icons/Numix/standard/actions/zoom-fit-best.svg diff --git a/lisp/ui/icons/numix/standard/actions/zoom-in.svg b/lisp/ui/icons/Numix/standard/actions/zoom-in.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/zoom-in.svg rename to lisp/ui/icons/Numix/standard/actions/zoom-in.svg diff --git a/lisp/ui/icons/numix/standard/actions/zoom-original.svg b/lisp/ui/icons/Numix/standard/actions/zoom-original.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/zoom-original.svg rename to lisp/ui/icons/Numix/standard/actions/zoom-original.svg diff --git a/lisp/ui/icons/numix/standard/actions/zoom-out.svg b/lisp/ui/icons/Numix/standard/actions/zoom-out.svg similarity index 100% rename from lisp/ui/icons/numix/standard/actions/zoom-out.svg rename to lisp/ui/icons/Numix/standard/actions/zoom-out.svg diff --git a/lisp/ui/icons/numix/standard/categories/applications-accessories.svg b/lisp/ui/icons/Numix/standard/categories/applications-accessories.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/applications-accessories.svg rename to lisp/ui/icons/Numix/standard/categories/applications-accessories.svg diff --git a/lisp/ui/icons/numix/standard/categories/applications-development.svg b/lisp/ui/icons/Numix/standard/categories/applications-development.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/applications-development.svg rename to lisp/ui/icons/Numix/standard/categories/applications-development.svg diff --git a/lisp/ui/icons/numix/standard/categories/applications-engineering.svg b/lisp/ui/icons/Numix/standard/categories/applications-engineering.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/applications-engineering.svg rename to lisp/ui/icons/Numix/standard/categories/applications-engineering.svg diff --git a/lisp/ui/icons/numix/standard/categories/applications-games.svg b/lisp/ui/icons/Numix/standard/categories/applications-games.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/applications-games.svg rename to lisp/ui/icons/Numix/standard/categories/applications-games.svg diff --git a/lisp/ui/icons/numix/standard/categories/applications-graphics.svg b/lisp/ui/icons/Numix/standard/categories/applications-graphics.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/applications-graphics.svg rename to lisp/ui/icons/Numix/standard/categories/applications-graphics.svg diff --git a/lisp/ui/icons/numix/standard/categories/applications-internet.svg b/lisp/ui/icons/Numix/standard/categories/applications-internet.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/applications-internet.svg rename to lisp/ui/icons/Numix/standard/categories/applications-internet.svg diff --git a/lisp/ui/icons/numix/standard/categories/applications-multimedia.svg b/lisp/ui/icons/Numix/standard/categories/applications-multimedia.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/applications-multimedia.svg rename to lisp/ui/icons/Numix/standard/categories/applications-multimedia.svg diff --git a/lisp/ui/icons/numix/standard/categories/applications-office.svg b/lisp/ui/icons/Numix/standard/categories/applications-office.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/applications-office.svg rename to lisp/ui/icons/Numix/standard/categories/applications-office.svg diff --git a/lisp/ui/icons/numix/standard/categories/applications-other.svg b/lisp/ui/icons/Numix/standard/categories/applications-other.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/applications-other.svg rename to lisp/ui/icons/Numix/standard/categories/applications-other.svg diff --git a/lisp/ui/icons/numix/standard/categories/applications-science.svg b/lisp/ui/icons/Numix/standard/categories/applications-science.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/applications-science.svg rename to lisp/ui/icons/Numix/standard/categories/applications-science.svg diff --git a/lisp/ui/icons/numix/standard/categories/applications-system.svg b/lisp/ui/icons/Numix/standard/categories/applications-system.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/applications-system.svg rename to lisp/ui/icons/Numix/standard/categories/applications-system.svg diff --git a/lisp/ui/icons/numix/standard/categories/applications-utilities.svg b/lisp/ui/icons/Numix/standard/categories/applications-utilities.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/applications-utilities.svg rename to lisp/ui/icons/Numix/standard/categories/applications-utilities.svg diff --git a/lisp/ui/icons/numix/standard/categories/preferences-desktop-peripherals.svg b/lisp/ui/icons/Numix/standard/categories/preferences-desktop-peripherals.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/preferences-desktop-peripherals.svg rename to lisp/ui/icons/Numix/standard/categories/preferences-desktop-peripherals.svg diff --git a/lisp/ui/icons/numix/standard/categories/preferences-desktop-personal.svg b/lisp/ui/icons/Numix/standard/categories/preferences-desktop-personal.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/preferences-desktop-personal.svg rename to lisp/ui/icons/Numix/standard/categories/preferences-desktop-personal.svg diff --git a/lisp/ui/icons/numix/standard/categories/preferences-desktop.svg b/lisp/ui/icons/Numix/standard/categories/preferences-desktop.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/preferences-desktop.svg rename to lisp/ui/icons/Numix/standard/categories/preferences-desktop.svg diff --git a/lisp/ui/icons/numix/standard/categories/preferences-other.svg b/lisp/ui/icons/Numix/standard/categories/preferences-other.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/preferences-other.svg rename to lisp/ui/icons/Numix/standard/categories/preferences-other.svg diff --git a/lisp/ui/icons/numix/standard/categories/preferences-system-network.svg b/lisp/ui/icons/Numix/standard/categories/preferences-system-network.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/preferences-system-network.svg rename to lisp/ui/icons/Numix/standard/categories/preferences-system-network.svg diff --git a/lisp/ui/icons/numix/standard/categories/preferences-system.svg b/lisp/ui/icons/Numix/standard/categories/preferences-system.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/preferences-system.svg rename to lisp/ui/icons/Numix/standard/categories/preferences-system.svg diff --git a/lisp/ui/icons/numix/standard/categories/system-help.svg b/lisp/ui/icons/Numix/standard/categories/system-help.svg similarity index 100% rename from lisp/ui/icons/numix/standard/categories/system-help.svg rename to lisp/ui/icons/Numix/standard/categories/system-help.svg diff --git a/lisp/ui/icons/numix/standard/devices/audio-card.svg b/lisp/ui/icons/Numix/standard/devices/audio-card.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/audio-card.svg rename to lisp/ui/icons/Numix/standard/devices/audio-card.svg diff --git a/lisp/ui/icons/numix/standard/devices/audio-input-microphone.svg b/lisp/ui/icons/Numix/standard/devices/audio-input-microphone.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/audio-input-microphone.svg rename to lisp/ui/icons/Numix/standard/devices/audio-input-microphone.svg diff --git a/lisp/ui/icons/numix/standard/devices/battery.svg b/lisp/ui/icons/Numix/standard/devices/battery.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/battery.svg rename to lisp/ui/icons/Numix/standard/devices/battery.svg diff --git a/lisp/ui/icons/numix/standard/devices/camera-photo.svg b/lisp/ui/icons/Numix/standard/devices/camera-photo.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/camera-photo.svg rename to lisp/ui/icons/Numix/standard/devices/camera-photo.svg diff --git a/lisp/ui/icons/numix/standard/devices/camera-web.svg b/lisp/ui/icons/Numix/standard/devices/camera-web.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/camera-web.svg rename to lisp/ui/icons/Numix/standard/devices/camera-web.svg diff --git a/lisp/ui/icons/numix/standard/devices/computer.svg b/lisp/ui/icons/Numix/standard/devices/computer.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/computer.svg rename to lisp/ui/icons/Numix/standard/devices/computer.svg diff --git a/lisp/ui/icons/numix/standard/devices/drive-harddisk.svg b/lisp/ui/icons/Numix/standard/devices/drive-harddisk.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/drive-harddisk.svg rename to lisp/ui/icons/Numix/standard/devices/drive-harddisk.svg diff --git a/lisp/ui/icons/numix/standard/devices/drive-optical.svg b/lisp/ui/icons/Numix/standard/devices/drive-optical.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/drive-optical.svg rename to lisp/ui/icons/Numix/standard/devices/drive-optical.svg diff --git a/lisp/ui/icons/numix/standard/devices/drive-removable-media.svg b/lisp/ui/icons/Numix/standard/devices/drive-removable-media.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/drive-removable-media.svg rename to lisp/ui/icons/Numix/standard/devices/drive-removable-media.svg diff --git a/lisp/ui/icons/numix/standard/devices/input-gaming.svg b/lisp/ui/icons/Numix/standard/devices/input-gaming.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/input-gaming.svg rename to lisp/ui/icons/Numix/standard/devices/input-gaming.svg diff --git a/lisp/ui/icons/numix/standard/devices/input-keyboard.svg b/lisp/ui/icons/Numix/standard/devices/input-keyboard.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/input-keyboard.svg rename to lisp/ui/icons/Numix/standard/devices/input-keyboard.svg diff --git a/lisp/ui/icons/numix/standard/devices/input-mouse.svg b/lisp/ui/icons/Numix/standard/devices/input-mouse.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/input-mouse.svg rename to lisp/ui/icons/Numix/standard/devices/input-mouse.svg diff --git a/lisp/ui/icons/numix/standard/devices/input-tablet.svg b/lisp/ui/icons/Numix/standard/devices/input-tablet.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/input-tablet.svg rename to lisp/ui/icons/Numix/standard/devices/input-tablet.svg diff --git a/lisp/ui/icons/numix/standard/devices/media-flash.svg b/lisp/ui/icons/Numix/standard/devices/media-flash.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/media-flash.svg rename to lisp/ui/icons/Numix/standard/devices/media-flash.svg diff --git a/lisp/ui/icons/numix/standard/devices/media-floppy.svg b/lisp/ui/icons/Numix/standard/devices/media-floppy.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/media-floppy.svg rename to lisp/ui/icons/Numix/standard/devices/media-floppy.svg diff --git a/lisp/ui/icons/numix/standard/devices/media-optical.svg b/lisp/ui/icons/Numix/standard/devices/media-optical.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/media-optical.svg rename to lisp/ui/icons/Numix/standard/devices/media-optical.svg diff --git a/lisp/ui/icons/numix/standard/devices/media-tape.svg b/lisp/ui/icons/Numix/standard/devices/media-tape.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/media-tape.svg rename to lisp/ui/icons/Numix/standard/devices/media-tape.svg diff --git a/lisp/ui/icons/numix/standard/devices/multimedia-player.svg b/lisp/ui/icons/Numix/standard/devices/multimedia-player.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/multimedia-player.svg rename to lisp/ui/icons/Numix/standard/devices/multimedia-player.svg diff --git a/lisp/ui/icons/numix/standard/devices/network-wired.svg b/lisp/ui/icons/Numix/standard/devices/network-wired.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/network-wired.svg rename to lisp/ui/icons/Numix/standard/devices/network-wired.svg diff --git a/lisp/ui/icons/numix/standard/devices/network-wireless.svg b/lisp/ui/icons/Numix/standard/devices/network-wireless.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/network-wireless.svg rename to lisp/ui/icons/Numix/standard/devices/network-wireless.svg diff --git a/lisp/ui/icons/numix/standard/devices/pda.svg b/lisp/ui/icons/Numix/standard/devices/pda.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/pda.svg rename to lisp/ui/icons/Numix/standard/devices/pda.svg diff --git a/lisp/ui/icons/numix/standard/devices/phone.svg b/lisp/ui/icons/Numix/standard/devices/phone.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/phone.svg rename to lisp/ui/icons/Numix/standard/devices/phone.svg diff --git a/lisp/ui/icons/numix/standard/devices/printer.svg b/lisp/ui/icons/Numix/standard/devices/printer.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/printer.svg rename to lisp/ui/icons/Numix/standard/devices/printer.svg diff --git a/lisp/ui/icons/numix/standard/devices/scanner.svg b/lisp/ui/icons/Numix/standard/devices/scanner.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/scanner.svg rename to lisp/ui/icons/Numix/standard/devices/scanner.svg diff --git a/lisp/ui/icons/numix/standard/devices/video-display.svg b/lisp/ui/icons/Numix/standard/devices/video-display.svg similarity index 100% rename from lisp/ui/icons/numix/standard/devices/video-display.svg rename to lisp/ui/icons/Numix/standard/devices/video-display.svg diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-default.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-default.svg similarity index 100% rename from lisp/ui/icons/numix/standard/emblems/emblem-default.svg rename to lisp/ui/icons/Numix/standard/emblems/emblem-default.svg diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-documents.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-documents.svg similarity index 100% rename from lisp/ui/icons/numix/standard/emblems/emblem-documents.svg rename to lisp/ui/icons/Numix/standard/emblems/emblem-documents.svg diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-downloads.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-downloads.svg similarity index 100% rename from lisp/ui/icons/numix/standard/emblems/emblem-downloads.svg rename to lisp/ui/icons/Numix/standard/emblems/emblem-downloads.svg diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-favorite.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-favorite.svg similarity index 100% rename from lisp/ui/icons/numix/standard/emblems/emblem-favorite.svg rename to lisp/ui/icons/Numix/standard/emblems/emblem-favorite.svg diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-important.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-important.svg similarity index 100% rename from lisp/ui/icons/numix/standard/emblems/emblem-important.svg rename to lisp/ui/icons/Numix/standard/emblems/emblem-important.svg diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-mail.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-mail.svg similarity index 100% rename from lisp/ui/icons/numix/standard/emblems/emblem-mail.svg rename to lisp/ui/icons/Numix/standard/emblems/emblem-mail.svg diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-photos.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-photos.svg similarity index 100% rename from lisp/ui/icons/numix/standard/emblems/emblem-photos.svg rename to lisp/ui/icons/Numix/standard/emblems/emblem-photos.svg diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-readonly.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-readonly.svg similarity index 100% rename from lisp/ui/icons/numix/standard/emblems/emblem-readonly.svg rename to lisp/ui/icons/Numix/standard/emblems/emblem-readonly.svg diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-shared.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-shared.svg similarity index 100% rename from lisp/ui/icons/numix/standard/emblems/emblem-shared.svg rename to lisp/ui/icons/Numix/standard/emblems/emblem-shared.svg diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-symbolic-link.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-symbolic-link.svg similarity index 100% rename from lisp/ui/icons/numix/standard/emblems/emblem-symbolic-link.svg rename to lisp/ui/icons/Numix/standard/emblems/emblem-symbolic-link.svg diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-system.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-system.svg similarity index 100% rename from lisp/ui/icons/numix/standard/emblems/emblem-system.svg rename to lisp/ui/icons/Numix/standard/emblems/emblem-system.svg diff --git a/lisp/ui/icons/numix/standard/emblems/emblem-unreadable.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-unreadable.svg similarity index 100% rename from lisp/ui/icons/numix/standard/emblems/emblem-unreadable.svg rename to lisp/ui/icons/Numix/standard/emblems/emblem-unreadable.svg diff --git a/lisp/ui/icons/numix/standard/places/folder-remote.svg b/lisp/ui/icons/Numix/standard/places/folder-remote.svg similarity index 100% rename from lisp/ui/icons/numix/standard/places/folder-remote.svg rename to lisp/ui/icons/Numix/standard/places/folder-remote.svg diff --git a/lisp/ui/icons/numix/standard/places/folder.svg b/lisp/ui/icons/Numix/standard/places/folder.svg similarity index 100% rename from lisp/ui/icons/numix/standard/places/folder.svg rename to lisp/ui/icons/Numix/standard/places/folder.svg diff --git a/lisp/ui/icons/numix/standard/places/network-server.svg b/lisp/ui/icons/Numix/standard/places/network-server.svg similarity index 100% rename from lisp/ui/icons/numix/standard/places/network-server.svg rename to lisp/ui/icons/Numix/standard/places/network-server.svg diff --git a/lisp/ui/icons/numix/standard/places/network-workgroup.svg b/lisp/ui/icons/Numix/standard/places/network-workgroup.svg similarity index 100% rename from lisp/ui/icons/numix/standard/places/network-workgroup.svg rename to lisp/ui/icons/Numix/standard/places/network-workgroup.svg diff --git a/lisp/ui/icons/numix/standard/places/start-here.svg b/lisp/ui/icons/Numix/standard/places/start-here.svg similarity index 100% rename from lisp/ui/icons/numix/standard/places/start-here.svg rename to lisp/ui/icons/Numix/standard/places/start-here.svg diff --git a/lisp/ui/icons/numix/standard/places/user-bookmarks.svg b/lisp/ui/icons/Numix/standard/places/user-bookmarks.svg similarity index 100% rename from lisp/ui/icons/numix/standard/places/user-bookmarks.svg rename to lisp/ui/icons/Numix/standard/places/user-bookmarks.svg diff --git a/lisp/ui/icons/numix/standard/places/user-desktop.svg b/lisp/ui/icons/Numix/standard/places/user-desktop.svg similarity index 100% rename from lisp/ui/icons/numix/standard/places/user-desktop.svg rename to lisp/ui/icons/Numix/standard/places/user-desktop.svg diff --git a/lisp/ui/icons/numix/standard/places/user-home.svg b/lisp/ui/icons/Numix/standard/places/user-home.svg similarity index 100% rename from lisp/ui/icons/numix/standard/places/user-home.svg rename to lisp/ui/icons/Numix/standard/places/user-home.svg diff --git a/lisp/ui/icons/numix/standard/places/user-trash.svg b/lisp/ui/icons/Numix/standard/places/user-trash.svg similarity index 100% rename from lisp/ui/icons/numix/standard/places/user-trash.svg rename to lisp/ui/icons/Numix/standard/places/user-trash.svg diff --git a/lisp/ui/icons/numix/standard/status/appointment-missed.svg b/lisp/ui/icons/Numix/standard/status/appointment-missed.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/appointment-missed.svg rename to lisp/ui/icons/Numix/standard/status/appointment-missed.svg diff --git a/lisp/ui/icons/numix/standard/status/appointment-soon.svg b/lisp/ui/icons/Numix/standard/status/appointment-soon.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/appointment-soon.svg rename to lisp/ui/icons/Numix/standard/status/appointment-soon.svg diff --git a/lisp/ui/icons/numix/standard/status/audio-volume-high.svg b/lisp/ui/icons/Numix/standard/status/audio-volume-high.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/audio-volume-high.svg rename to lisp/ui/icons/Numix/standard/status/audio-volume-high.svg diff --git a/lisp/ui/icons/numix/standard/status/audio-volume-low.svg b/lisp/ui/icons/Numix/standard/status/audio-volume-low.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/audio-volume-low.svg rename to lisp/ui/icons/Numix/standard/status/audio-volume-low.svg diff --git a/lisp/ui/icons/numix/standard/status/audio-volume-medium.svg b/lisp/ui/icons/Numix/standard/status/audio-volume-medium.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/audio-volume-medium.svg rename to lisp/ui/icons/Numix/standard/status/audio-volume-medium.svg diff --git a/lisp/ui/icons/numix/standard/status/audio-volume-muted.svg b/lisp/ui/icons/Numix/standard/status/audio-volume-muted.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/audio-volume-muted.svg rename to lisp/ui/icons/Numix/standard/status/audio-volume-muted.svg diff --git a/lisp/ui/icons/numix/standard/status/battery-caution.svg b/lisp/ui/icons/Numix/standard/status/battery-caution.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/battery-caution.svg rename to lisp/ui/icons/Numix/standard/status/battery-caution.svg diff --git a/lisp/ui/icons/numix/standard/status/battery-low.svg b/lisp/ui/icons/Numix/standard/status/battery-low.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/battery-low.svg rename to lisp/ui/icons/Numix/standard/status/battery-low.svg diff --git a/lisp/ui/icons/numix/standard/status/dialog-error.svg b/lisp/ui/icons/Numix/standard/status/dialog-error.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/dialog-error.svg rename to lisp/ui/icons/Numix/standard/status/dialog-error.svg diff --git a/lisp/ui/icons/numix/standard/status/dialog-information.svg b/lisp/ui/icons/Numix/standard/status/dialog-information.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/dialog-information.svg rename to lisp/ui/icons/Numix/standard/status/dialog-information.svg diff --git a/lisp/ui/icons/numix/standard/status/dialog-password.svg b/lisp/ui/icons/Numix/standard/status/dialog-password.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/dialog-password.svg rename to lisp/ui/icons/Numix/standard/status/dialog-password.svg diff --git a/lisp/ui/icons/numix/standard/status/dialog-question.svg b/lisp/ui/icons/Numix/standard/status/dialog-question.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/dialog-question.svg rename to lisp/ui/icons/Numix/standard/status/dialog-question.svg diff --git a/lisp/ui/icons/numix/standard/status/dialog-warning.svg b/lisp/ui/icons/Numix/standard/status/dialog-warning.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/dialog-warning.svg rename to lisp/ui/icons/Numix/standard/status/dialog-warning.svg diff --git a/lisp/ui/icons/numix/standard/status/image-missing.svg b/lisp/ui/icons/Numix/standard/status/image-missing.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/image-missing.svg rename to lisp/ui/icons/Numix/standard/status/image-missing.svg diff --git a/lisp/ui/icons/numix/standard/status/network-error.svg b/lisp/ui/icons/Numix/standard/status/network-error.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/network-error.svg rename to lisp/ui/icons/Numix/standard/status/network-error.svg diff --git a/lisp/ui/icons/numix/standard/status/network-idle.svg b/lisp/ui/icons/Numix/standard/status/network-idle.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/network-idle.svg rename to lisp/ui/icons/Numix/standard/status/network-idle.svg diff --git a/lisp/ui/icons/numix/standard/status/network-offline.svg b/lisp/ui/icons/Numix/standard/status/network-offline.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/network-offline.svg rename to lisp/ui/icons/Numix/standard/status/network-offline.svg diff --git a/lisp/ui/icons/numix/standard/status/network-receive.svg b/lisp/ui/icons/Numix/standard/status/network-receive.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/network-receive.svg rename to lisp/ui/icons/Numix/standard/status/network-receive.svg diff --git a/lisp/ui/icons/numix/standard/status/network-transmit-receive.svg b/lisp/ui/icons/Numix/standard/status/network-transmit-receive.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/network-transmit-receive.svg rename to lisp/ui/icons/Numix/standard/status/network-transmit-receive.svg diff --git a/lisp/ui/icons/numix/standard/status/network-transmit.svg b/lisp/ui/icons/Numix/standard/status/network-transmit.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/network-transmit.svg rename to lisp/ui/icons/Numix/standard/status/network-transmit.svg diff --git a/lisp/ui/icons/numix/standard/status/printer-printing.svg b/lisp/ui/icons/Numix/standard/status/printer-printing.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/printer-printing.svg rename to lisp/ui/icons/Numix/standard/status/printer-printing.svg diff --git a/lisp/ui/icons/numix/standard/status/security-high.svg b/lisp/ui/icons/Numix/standard/status/security-high.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/security-high.svg rename to lisp/ui/icons/Numix/standard/status/security-high.svg diff --git a/lisp/ui/icons/numix/standard/status/security-low.svg b/lisp/ui/icons/Numix/standard/status/security-low.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/security-low.svg rename to lisp/ui/icons/Numix/standard/status/security-low.svg diff --git a/lisp/ui/icons/numix/standard/status/security-medium.svg b/lisp/ui/icons/Numix/standard/status/security-medium.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/security-medium.svg rename to lisp/ui/icons/Numix/standard/status/security-medium.svg diff --git a/lisp/ui/icons/numix/standard/status/task-due.svg b/lisp/ui/icons/Numix/standard/status/task-due.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/task-due.svg rename to lisp/ui/icons/Numix/standard/status/task-due.svg diff --git a/lisp/ui/icons/numix/standard/status/task-past-due.svg b/lisp/ui/icons/Numix/standard/status/task-past-due.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/task-past-due.svg rename to lisp/ui/icons/Numix/standard/status/task-past-due.svg diff --git a/lisp/ui/icons/numix/standard/status/user-available.svg b/lisp/ui/icons/Numix/standard/status/user-available.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/user-available.svg rename to lisp/ui/icons/Numix/standard/status/user-available.svg diff --git a/lisp/ui/icons/numix/standard/status/user-away.svg b/lisp/ui/icons/Numix/standard/status/user-away.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/user-away.svg rename to lisp/ui/icons/Numix/standard/status/user-away.svg diff --git a/lisp/ui/icons/numix/standard/status/user-idle.svg b/lisp/ui/icons/Numix/standard/status/user-idle.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/user-idle.svg rename to lisp/ui/icons/Numix/standard/status/user-idle.svg diff --git a/lisp/ui/icons/numix/standard/status/user-offline.svg b/lisp/ui/icons/Numix/standard/status/user-offline.svg similarity index 100% rename from lisp/ui/icons/numix/standard/status/user-offline.svg rename to lisp/ui/icons/Numix/standard/status/user-offline.svg diff --git a/lisp/ui/icons/papirus-symbolic/custom/cue-interrupt.svg b/lisp/ui/icons/Papirus Symbolic/custom/cue-interrupt.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/custom/cue-interrupt.svg rename to lisp/ui/icons/Papirus Symbolic/custom/cue-interrupt.svg diff --git a/lisp/ui/icons/papirus-symbolic/custom/cue-pause.svg b/lisp/ui/icons/Papirus Symbolic/custom/cue-pause.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/custom/cue-pause.svg rename to lisp/ui/icons/Papirus Symbolic/custom/cue-pause.svg diff --git a/lisp/ui/icons/papirus-symbolic/custom/cue-select-next.svg b/lisp/ui/icons/Papirus Symbolic/custom/cue-select-next.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/custom/cue-select-next.svg rename to lisp/ui/icons/Papirus Symbolic/custom/cue-select-next.svg diff --git a/lisp/ui/icons/papirus-symbolic/custom/cue-start.svg b/lisp/ui/icons/Papirus Symbolic/custom/cue-start.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/custom/cue-start.svg rename to lisp/ui/icons/Papirus Symbolic/custom/cue-start.svg diff --git a/lisp/ui/icons/papirus-symbolic/custom/cue-stop.svg b/lisp/ui/icons/Papirus Symbolic/custom/cue-stop.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/custom/cue-stop.svg rename to lisp/ui/icons/Papirus Symbolic/custom/cue-stop.svg diff --git a/lisp/ui/icons/papirus-symbolic/custom/cue-trigger-next.svg b/lisp/ui/icons/Papirus Symbolic/custom/cue-trigger-next.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/custom/cue-trigger-next.svg rename to lisp/ui/icons/Papirus Symbolic/custom/cue-trigger-next.svg diff --git a/lisp/ui/icons/papirus-symbolic/custom/fadein-generic.svg b/lisp/ui/icons/Papirus Symbolic/custom/fadein-generic.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/custom/fadein-generic.svg rename to lisp/ui/icons/Papirus Symbolic/custom/fadein-generic.svg diff --git a/lisp/ui/icons/papirus-symbolic/custom/fadeout-generic.svg b/lisp/ui/icons/Papirus Symbolic/custom/fadeout-generic.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/custom/fadeout-generic.svg rename to lisp/ui/icons/Papirus Symbolic/custom/fadeout-generic.svg diff --git a/lisp/ui/icons/Papirus Symbolic/index.theme b/lisp/ui/icons/Papirus Symbolic/index.theme new file mode 100644 index 000000000..a6fb398b7 --- /dev/null +++ b/lisp/ui/icons/Papirus Symbolic/index.theme @@ -0,0 +1,47 @@ +[Icon Theme] +Name=Papirus-Symbolic +Comment=Papirus icon theme +Inherits=hicolor +Directories=standard/actions,standard/categories,standard/devices,standard/emblems,standard/places,standard/status + +[standard/actions] +Size=16 +MinSize=16 +MaxSize=256 +Context=Actions +Type=Scalable + +[standard/categories] +Size=16 +MinSize=16 +MaxSize=256 +Context=Categories +Type=Scalable + +[standard/devices] +Size=16 +MinSize=16 +MaxSize=256 +Context=Devices +Type=Scalable + +[standard/emblems] +Size=16 +MinSize=16 +MaxSize=256 +Context=Emblems +Type=Scalable + +[standard/places] +Size=16 +MinSize=16 +MaxSize=256 +Context=Places +Type=Scalable + +[standard/status] +Size=16 +MinSize=16 +MaxSize=256 +Context=Status +Type=Scalable diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/address-book-new.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/address-book-new.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/address-book-new.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/address-book-new.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/application-exit.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/application-exit.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/application-exit.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/application-exit.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/appointment-new.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/appointment-new.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/appointment-new.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/appointment-new.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/call-start.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/call-start.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/call-start.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/call-start.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/call-stop.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/call-stop.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/call-stop.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/call-stop.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/contact-new.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/contact-new.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/contact-new.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/contact-new.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-new.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-new.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/document-new.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/document-new.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-open-recent.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-open-recent.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/document-open-recent.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/document-open-recent.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-open.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-open.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/document-open.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/document-open.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-page-setup.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-page-setup.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/document-page-setup.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/document-page-setup.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-print-preview.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-print-preview.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/document-print-preview.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/document-print-preview.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-print.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-print.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/document-print.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/document-print.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-properties.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-properties.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/document-properties.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/document-properties.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-revert.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-revert.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/document-revert.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/document-revert.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-save-as.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-save-as.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/document-save-as.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/document-save-as.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-save.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-save.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/document-save.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/document-save.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/document-send.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-send.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/document-send.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/document-send.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-clear.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-clear.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/edit-clear.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/edit-clear.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-copy.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-copy.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/edit-copy.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/edit-copy.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-cut.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-cut.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/edit-cut.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/edit-cut.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-delete.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-delete.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/edit-delete.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/edit-delete.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-find-replace.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-find-replace.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/edit-find-replace.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/edit-find-replace.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-find.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-find.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/edit-find.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/edit-find.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-paste.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-paste.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/edit-paste.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/edit-paste.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-redo.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-redo.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/edit-redo.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/edit-redo.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-select-all.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-select-all.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/edit-select-all.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/edit-select-all.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/edit-undo.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-undo.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/edit-undo.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/edit-undo.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/folder-new.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/folder-new.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/folder-new.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/folder-new.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-less.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-indent-less.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-less.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/format-indent-less.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-more.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-indent-more.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/format-indent-more.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/format-indent-more.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-center.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-center.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-center.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-center.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-fill.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-fill.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-fill.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-fill.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-left.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-left.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-left.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-left.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-right.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-right.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/format-justify-right.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-right.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-bold.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-bold.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/format-text-bold.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-bold.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-ltr.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-direction-ltr.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-ltr.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-direction-ltr.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-rtl.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-direction-rtl.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/format-text-direction-rtl.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-direction-rtl.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-italic.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-italic.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/format-text-italic.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-italic.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-strikethrough.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-strikethrough.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/format-text-strikethrough.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-strikethrough.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/format-text-underline.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-underline.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/format-text-underline.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-underline.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-bottom.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-bottom.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/go-bottom.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/go-bottom.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-down.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-down.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/go-down.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/go-down.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-first.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-first.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/go-first.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/go-first.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-home.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-home.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/go-home.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/go-home.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-jump.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-jump.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/go-jump.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/go-jump.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-last.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-last.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/go-last.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/go-last.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-next.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-next.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/go-next.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/go-next.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-previous.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-previous.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/go-previous.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/go-previous.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-top.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-top.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/go-top.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/go-top.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/go-up.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-up.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/go-up.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/go-up.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/help-about.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/help-about.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/help-about.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/help-about.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/insert-image.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/insert-image.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/insert-image.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/insert-image.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/insert-link.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/insert-link.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/insert-link.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/insert-link.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/insert-object.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/insert-object.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/insert-object.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/insert-object.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/insert-text.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/insert-text.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/insert-text.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/insert-text.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/list-add.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/list-add.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/list-add.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/list-add.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/list-remove.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/list-remove.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/list-remove.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/list-remove.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-forward.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-forward.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/mail-forward.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/mail-forward.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-important.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-important.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-important.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-important.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-junk.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-junk.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-junk.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-junk.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-notjunk.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-notjunk.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-notjunk.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-notjunk.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-read.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-read.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-read.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-read.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-unread.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-unread.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/mail-mark-unread.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-unread.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-message-new.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-message-new.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/mail-message-new.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/mail-message-new.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-all.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-reply-all.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-all.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/mail-reply-all.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-sender.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-reply-sender.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/mail-reply-sender.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/mail-reply-sender.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-send-receive.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-send-receive.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/mail-send-receive.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/mail-send-receive.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/mail-send.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-send.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/mail-send.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/mail-send.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-eject.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-eject.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/media-eject.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/media-eject.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-pause.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-pause.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-pause.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-pause.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-start.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-start.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-start.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-start.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-stop.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-stop.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/media-playback-stop.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-stop.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-record.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-record.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/media-record.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/media-record.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-backward.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-seek-backward.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-backward.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/media-seek-backward.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-forward.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-seek-forward.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/media-seek-forward.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/media-seek-forward.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-backward.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-skip-backward.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-backward.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/media-skip-backward.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-forward.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-skip-forward.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/media-skip-forward.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/media-skip-forward.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-horizontal.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/object-flip-horizontal.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-horizontal.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/object-flip-horizontal.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-vertical.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/object-flip-vertical.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/object-flip-vertical.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/object-flip-vertical.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-left.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/object-rotate-left.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-left.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/object-rotate-left.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-right.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/object-rotate-right.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/object-rotate-right.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/object-rotate-right.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/process-stop.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/process-stop.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/process-stop.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/process-stop.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/system-lock-screen.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/system-lock-screen.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/system-lock-screen.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/system-lock-screen.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/system-log-out.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/system-log-out.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/system-log-out.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/system-log-out.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/system-run.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/system-run.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/system-run.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/system-run.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/system-search.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/system-search.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/system-search.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/system-search.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/system-shutdown.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/system-shutdown.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/system-shutdown.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/system-shutdown.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/tools-check-spelling.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/tools-check-spelling.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/tools-check-spelling.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/tools-check-spelling.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/view-fullscreen.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/view-fullscreen.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/view-fullscreen.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/view-fullscreen.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/view-refresh.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/view-refresh.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/view-refresh.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/view-refresh.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/view-restore.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/view-restore.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/view-restore.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/view-restore.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-ascending.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/view-sort-ascending.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-ascending.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/view-sort-ascending.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-descending.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/view-sort-descending.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/view-sort-descending.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/view-sort-descending.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/window-close.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/window-close.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/window-close.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/window-close.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-fit-best.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-fit-best.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/zoom-fit-best.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-fit-best.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-in.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-in.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/zoom-in.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-in.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-original.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-original.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/zoom-original.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-original.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/actions/zoom-out.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-out.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/actions/zoom-out.svg rename to lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-out.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-engineering.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-engineering.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/categories/applications-engineering.svg rename to lisp/ui/icons/Papirus Symbolic/standard/categories/applications-engineering.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-games.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-games.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/categories/applications-games.svg rename to lisp/ui/icons/Papirus Symbolic/standard/categories/applications-games.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-graphics.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-graphics.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/categories/applications-graphics.svg rename to lisp/ui/icons/Papirus Symbolic/standard/categories/applications-graphics.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-multimedia.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-multimedia.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/categories/applications-multimedia.svg rename to lisp/ui/icons/Papirus Symbolic/standard/categories/applications-multimedia.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-science.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-science.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/categories/applications-science.svg rename to lisp/ui/icons/Papirus Symbolic/standard/categories/applications-science.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-system.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-system.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/categories/applications-system.svg rename to lisp/ui/icons/Papirus Symbolic/standard/categories/applications-system.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/applications-utilities.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-utilities.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/categories/applications-utilities.svg rename to lisp/ui/icons/Papirus Symbolic/standard/categories/applications-utilities.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/preferences-other.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/preferences-other.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/categories/preferences-other.svg rename to lisp/ui/icons/Papirus Symbolic/standard/categories/preferences-other.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/preferences-system.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/preferences-system.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/categories/preferences-system.svg rename to lisp/ui/icons/Papirus Symbolic/standard/categories/preferences-system.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/categories/system-help.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/system-help.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/categories/system-help.svg rename to lisp/ui/icons/Papirus Symbolic/standard/categories/system-help.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/audio-card.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/audio-card.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/audio-card.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/audio-card.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/audio-input-microphone.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/audio-input-microphone.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/audio-input-microphone.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/audio-input-microphone.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/battery.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/battery.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/battery.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/battery.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/camera-photo.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/camera-photo.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/camera-photo.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/camera-photo.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/camera-video.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/camera-video.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/camera-video.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/camera-video.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/camera-web.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/camera-web.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/camera-web.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/camera-web.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/computer.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/computer.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/computer.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/computer.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/drive-harddisk.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/drive-harddisk.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/drive-harddisk.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/drive-harddisk.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/drive-optical.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/drive-optical.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/drive-optical.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/drive-optical.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/drive-removable-media.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/drive-removable-media.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/drive-removable-media.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/drive-removable-media.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/input-gaming.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/input-gaming.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/input-gaming.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/input-gaming.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/input-keyboard.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/input-keyboard.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/input-keyboard.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/input-keyboard.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/input-mouse.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/input-mouse.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/input-mouse.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/input-mouse.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/input-tablet.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/input-tablet.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/input-tablet.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/input-tablet.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/media-flash.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/media-flash.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/media-flash.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/media-flash.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/media-floppy.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/media-floppy.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/media-floppy.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/media-floppy.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/media-tape.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/media-tape.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/media-tape.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/media-tape.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/multimedia-player.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/multimedia-player.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/multimedia-player.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/multimedia-player.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/network-wired.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/network-wired.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/network-wired.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/network-wired.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/network-wireless.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/network-wireless.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/network-wireless.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/network-wireless.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/phone.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/phone.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/phone.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/phone.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/printer.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/printer.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/printer.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/printer.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/scanner.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/scanner.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/scanner.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/scanner.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/devices/video-display.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/video-display.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/devices/video-display.svg rename to lisp/ui/icons/Papirus Symbolic/standard/devices/video-display.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-default.svg b/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-default.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-default.svg rename to lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-default.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-documents.svg b/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-documents.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-documents.svg rename to lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-documents.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-favorite.svg b/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-favorite.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-favorite.svg rename to lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-favorite.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-important.svg b/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-important.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-important.svg rename to lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-important.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-photos.svg b/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-photos.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-photos.svg rename to lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-photos.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-shared.svg b/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-shared.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-shared.svg rename to lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-shared.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-system.svg b/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-system.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/emblems/emblem-system.svg rename to lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-system.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/folder-remote.svg b/lisp/ui/icons/Papirus Symbolic/standard/places/folder-remote.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/places/folder-remote.svg rename to lisp/ui/icons/Papirus Symbolic/standard/places/folder-remote.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/folder.svg b/lisp/ui/icons/Papirus Symbolic/standard/places/folder.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/places/folder.svg rename to lisp/ui/icons/Papirus Symbolic/standard/places/folder.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/network-workgroup.svg b/lisp/ui/icons/Papirus Symbolic/standard/places/network-workgroup.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/places/network-workgroup.svg rename to lisp/ui/icons/Papirus Symbolic/standard/places/network-workgroup.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/start-here.svg b/lisp/ui/icons/Papirus Symbolic/standard/places/start-here.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/places/start-here.svg rename to lisp/ui/icons/Papirus Symbolic/standard/places/start-here.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/user-bookmarks.svg b/lisp/ui/icons/Papirus Symbolic/standard/places/user-bookmarks.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/places/user-bookmarks.svg rename to lisp/ui/icons/Papirus Symbolic/standard/places/user-bookmarks.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/user-desktop.svg b/lisp/ui/icons/Papirus Symbolic/standard/places/user-desktop.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/places/user-desktop.svg rename to lisp/ui/icons/Papirus Symbolic/standard/places/user-desktop.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/user-home.svg b/lisp/ui/icons/Papirus Symbolic/standard/places/user-home.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/places/user-home.svg rename to lisp/ui/icons/Papirus Symbolic/standard/places/user-home.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/places/user-trash.svg b/lisp/ui/icons/Papirus Symbolic/standard/places/user-trash.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/places/user-trash.svg rename to lisp/ui/icons/Papirus Symbolic/standard/places/user-trash.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/appointment-missed.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/appointment-missed.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/appointment-missed.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/appointment-missed.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/appointment-soon.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/appointment-soon.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/appointment-soon.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/appointment-soon.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-high.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-high.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-high.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-high.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-low.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-low.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-low.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-low.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-medium.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-medium.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-medium.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-medium.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-muted.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-muted.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/audio-volume-muted.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-muted.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/battery-caution.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/battery-caution.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/battery-caution.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/battery-caution.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/battery-low.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/battery-low.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/battery-low.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/battery-low.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/dialog-error.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-error.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/dialog-error.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/dialog-error.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/dialog-information.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-information.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/dialog-information.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/dialog-information.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/dialog-password.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-password.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/dialog-password.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/dialog-password.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/dialog-question.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-question.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/dialog-question.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/dialog-question.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/dialog-warning.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-warning.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/dialog-warning.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/dialog-warning.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/folder-drag-accept.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/folder-drag-accept.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/folder-drag-accept.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/folder-drag-accept.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/folder-open.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/folder-open.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/folder-open.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/folder-open.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/folder-visiting.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/folder-visiting.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/folder-visiting.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/folder-visiting.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/image-loading.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/image-loading.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/image-loading.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/image-loading.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/mail-attachment.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/mail-attachment.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/mail-attachment.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/mail-attachment.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/mail-read.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/mail-read.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/mail-read.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/mail-read.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/mail-replied.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/mail-replied.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/mail-replied.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/mail-replied.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/mail-unread.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/mail-unread.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/mail-unread.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/mail-unread.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-repeat.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/media-playlist-repeat.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-repeat.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/media-playlist-repeat.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-shuffle.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/media-playlist-shuffle.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/media-playlist-shuffle.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/media-playlist-shuffle.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-error.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/network-error.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/network-error.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/network-error.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-idle.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/network-idle.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/network-idle.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/network-idle.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-offline.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/network-offline.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/network-offline.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/network-offline.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-receive.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/network-receive.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/network-receive.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/network-receive.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-transmit-receive.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/network-transmit-receive.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/network-transmit-receive.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/network-transmit-receive.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/network-transmit.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/network-transmit.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/network-transmit.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/network-transmit.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/printer-error.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/printer-error.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/printer-error.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/printer-error.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/printer-printing.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/printer-printing.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/printer-printing.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/printer-printing.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/security-high.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/security-high.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/security-high.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/security-high.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/security-low.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/security-low.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/security-low.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/security-low.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/security-medium.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/security-medium.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/security-medium.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/security-medium.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/software-update-available.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/software-update-available.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/software-update-available.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/software-update-available.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/software-update-urgent.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/software-update-urgent.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/software-update-urgent.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/software-update-urgent.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/task-due.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/task-due.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/task-due.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/task-due.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/task-past-due.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/task-past-due.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/task-past-due.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/task-past-due.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/user-available.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/user-available.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/user-available.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/user-available.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/user-away.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/user-away.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/user-away.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/user-away.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/user-idle.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/user-idle.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/user-idle.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/user-idle.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/user-offline.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/user-offline.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/user-offline.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/user-offline.svg diff --git a/lisp/ui/icons/papirus-symbolic/standard/status/user-trash-full.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/user-trash-full.svg similarity index 100% rename from lisp/ui/icons/papirus-symbolic/standard/status/user-trash-full.svg rename to lisp/ui/icons/Papirus Symbolic/standard/status/user-trash-full.svg diff --git a/lisp/ui/icons/__init__.py b/lisp/ui/icons/__init__.py index a00684e0d..1c0f83701 100644 --- a/lisp/ui/icons/__init__.py +++ b/lisp/ui/icons/__init__.py @@ -65,3 +65,6 @@ def get(icon_name): def set_theme_name(theme_name): IconTheme._GlobalCache.clear() IconTheme._GlobalTheme = IconTheme(theme_name, ICON_THEME_COMMON) + + QIcon.setThemeSearchPaths([ICON_THEMES_DIR]) + QIcon.setThemeName(theme_name) diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index e6d6ec6cc..e6de9410b 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -34,7 +34,7 @@ ) from functools import partial -from lisp.core.actions_handler import MainActionsHandler +from lisp.command.layout import LayoutAutoInsertCuesCommand from lisp.core.singleton import QSingleton from lisp.cues.cue_factory import CueFactory from lisp.cues.media_cue import MediaCue @@ -55,27 +55,29 @@ class MainWindow(QMainWindow, metaclass=QSingleton): save_session = pyqtSignal(str) open_session = pyqtSignal(str) - def __init__(self, conf, title="Linux Show Player", **kwargs): + def __init__(self, app, title="Linux Show Player", **kwargs): + """:type app: lisp.application.Application""" super().__init__(**kwargs) self.setMinimumSize(500, 400) self.setGeometry(qApp.desktop().availableGeometry(self)) self.setCentralWidget(QWidget()) self.centralWidget().setLayout(QVBoxLayout()) self.centralWidget().layout().setContentsMargins(5, 5, 5, 5) + self.setStatusBar(QStatusBar(self)) - self.__cueSubMenus = {} + self._app = app self._title = title + self._cueSubMenus = {} - self.conf = conf - self.session = None - - # Status Bar - self.setStatusBar(QStatusBar(self)) + # Session change + self._app.session_created.connect(self.__sessionCreated) + self._app.session_before_finalize.connect(self.__beforeSessionFinalize) # Changes - MainActionsHandler.action_done.connect(self.updateWindowTitle) - MainActionsHandler.action_undone.connect(self.updateWindowTitle) - MainActionsHandler.action_redone.connect(self.updateWindowTitle) + self._app.commands_stack.done.connect(self.updateWindowTitle) + self._app.commands_stack.saved.connect(self.updateWindowTitle) + self._app.commands_stack.undone.connect(self.updateWindowTitle) + self._app.commands_stack.redone.connect(self.updateWindowTitle) # Menubar self.menubar = QMenuBar(self) @@ -127,9 +129,9 @@ def __init__(self, conf, title="Linux Show Player", **kwargs): # menuEdit self.actionUndo = QAction(self) - self.actionUndo.triggered.connect(MainActionsHandler.undo_action) + self.actionUndo.triggered.connect(self._app.commands_stack.undo_last) self.actionRedo = QAction(self) - self.actionRedo.triggered.connect(MainActionsHandler.redo_action) + self.actionRedo.triggered.connect(self._app.commands_stack.redo_last) self.multiEdit = QAction(self) self.multiEdit.triggered.connect(self.__editSelectedCues) self.selectAll = QAction(self) @@ -164,14 +166,14 @@ def __init__(self, conf, title="Linux Show Player", **kwargs): self.menuAbout.addAction(self.actionAbout_Qt) # Logging model - self.logModel = log_model_factory(self.conf) + self.logModel = log_model_factory(self._app.conf) # Handler to populate the model self.logHandler = LogModelHandler(self.logModel) logging.getLogger().addHandler(self.logHandler) # Logging - self.logViewer = LogViewer(self.logModel, self.conf) + self.logViewer = LogViewer(self.logModel, self._app.conf) # Logging status widget self.logStatus = LogStatusView(self.logModel) @@ -232,25 +234,6 @@ def retranslateUi(self): self.actionAbout.setText(translate("MainWindow", "About")) self.actionAbout_Qt.setText(translate("MainWindow", "About Qt")) - def setSession(self, session): - if self.session is not None: - self.centralWidget().layout().removeWidget( - self.session.layout.view() - ) - # Remove ownership, this allow the widget to be deleted - self.session.layout.view().setParent(None) - - self.session = session - self.session.layout.view().show() - self.centralWidget().layout().addWidget(self.session.layout.view()) - - def closeEvent(self, event): - if self.__checkSessionSaved(): - qApp.quit() - event.accept() - else: - event.ignore() - def registerCueMenu(self, name, function, category="", shortcut=""): """Register a new-cue choice for the edit-menu @@ -267,12 +250,12 @@ def registerCueMenu(self, name, function, category="", shortcut=""): action.setShortcut(translate("CueCategory", shortcut)) if category: - if category not in self.__cueSubMenus: + if category not in self._cueSubMenus: subMenu = QMenu(category, self) - self.__cueSubMenus[category] = subMenu + self._cueSubMenus[category] = subMenu self.menuEdit.insertMenu(self.cueSeparator, subMenu) - self.__cueSubMenus[category].addAction(action) + self._cueSubMenus[category].addAction(action) else: self.menuEdit.insertAction(self.cueSeparator, action) @@ -290,8 +273,8 @@ def registerSimpleCueMenu(self, cueClass, category=""): ) def updateWindowTitle(self): - tile = self._title + " - " + self.session.name() - if not MainActionsHandler.is_saved(): + tile = self._title + " - " + self._app.session.name() + if not self._app.commands_stack.is_saved(): tile = "*" + tile self.setWindowTitle(tile) @@ -300,20 +283,22 @@ def getOpenSessionFile(self): path, _ = QFileDialog.getOpenFileName( self, filter="*.lsp", - directory=self.conf.get("session.last_dir", os.getenv("HOME")), + directory=self._app.conf.get("session.lastPath", os.getenv("HOME")), ) if os.path.exists(path): - self.conf.set("session.last_dir", os.path.dirname(path)) - self.conf.write() + self._app.conf.set("session.lastPath", os.path.dirname(path)) + self._app.conf.write() return path def getSaveSessionFile(self): - if self.session.session_file: - directory = self.session.dir() + if self._app.session.session_file: + directory = self._app.session.dir() else: - directory = self.conf.get("session.last_dir", os.getenv("HOME")) + directory = self._app.conf.get( + "session.lastPath", os.getenv("HOME") + ) path, _ = QFileDialog.getSaveFileName( parent=self, filter="*.lsp", directory=directory @@ -331,16 +316,32 @@ def setFullScreen(self, enable): else: self.showMaximized() - def __simpleCueInsert(self, cueClass): - try: - cue = CueFactory.create_cue(cueClass.__name__) + def closeEvent(self, event): + if self.__checkSessionSaved(): + qApp.quit() + event.accept() + else: + event.ignore() - # Get the (last) index of the current selection - layoutSelection = list(self.session.layout.selected_cues()) - if layoutSelection: - cue.index = layoutSelection[-1].index + 1 + def __beforeSessionFinalize(self): + self.centralWidget().layout().removeWidget( + self._app.session.layout.view + ) + # Remove ownership, this allow the widget to be deleted + self._app.session.layout.view.setParent(None) + + def __sessionCreated(self): + self._app.session.layout.view.show() + self.centralWidget().layout().addWidget(self._app.session.layout.view) - self.session.cue_model.add(cue) + def __simpleCueInsert(self, cueClass): + try: + self._app.commands_stack.do( + LayoutAutoInsertCuesCommand( + self._app.session.layout, + CueFactory.create_cue(cueClass.__name__), + ) + ) except Exception: logger.exception( translate("MainWindowError", "Cannot create cue {}").format( @@ -353,23 +354,25 @@ def __onEditPreferences(self): prefUi.exec() def __editSelectedCues(self): - self.session.layout.edit_cues(list(self.session.layout.selected_cues())) + self._app.session.layout.edit_cues( + list(self._app.session.layout.selected_cues()) + ) def __layoutSelectAll(self): - self.session.layout.select_all() + self._app.session.layout.select_all() def __layoutInvertSelection(self): - self.session.layout.invert_selection() + self._app.session.layout.invert_selection() def _layoutDeselectAll(self): - self.session.layout.deselect_all() + self._app.session.layout.deselect_all() def __layoutSelectAllMediaCues(self): - self.session.layout.select_all(cue_type=MediaCue) + self._app.session.layout.select_all(cue_type=MediaCue) def __saveSession(self): - if self.session.session_file: - self.save_session.emit(self.session.session_file) + if self._app.session.session_file: + self.save_session.emit(self._app.session.session_file) return True else: return self.__saveWithName() @@ -394,7 +397,7 @@ def __newSession(self): self.new_session.emit() def __checkSessionSaved(self): - if not MainActionsHandler.is_saved(): + if not self._app.commands_stack.is_saved(): saveMessageBox = QMessageBox( QMessageBox.Warning, translate("MainWindow", "Close session"), diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index f6354c741..f781a5a3d 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2019 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import Qt, QEvent, QPoint +from PyQt5.QtCore import Qt, QEvent, QPoint, QSize from PyQt5.QtWidgets import ( QStyledItemDelegate, QComboBox, @@ -28,7 +28,6 @@ qApp, ) -from lisp.application import Application from lisp.cues.cue import CueAction from lisp.ui.qmodels import CueClassRole from lisp.ui.ui_utils import translate @@ -37,9 +36,19 @@ from lisp.ui.widgets.qenumcombobox import QEnumComboBox +class PaddedDelegate(QStyledItemDelegate): + def __init__(self, hPad=0, vPad=0, **kwargs): + super().__init__(**kwargs) + self.hPad = hPad + self.vPad = vPad + + def sizeHint(self, option, index): + return super().sizeHint(option, index) + QSize(self.hPad, self.vPad) + + class LabelDelegate(QStyledItemDelegate): def _text(self, painter, option, index): - return "" + return index.data() def paint(self, painter, option, index): # Add 4px of left an right padding @@ -258,12 +267,13 @@ def createEditor(self, parent, option, index): class CueSelectionDelegate(LabelDelegate): - def __init__(self, cue_select_dialog, **kwargs): + def __init__(self, cue_model, cue_select_dialog, **kwargs): super().__init__(**kwargs) + self.cue_model = cue_model self.cue_select = cue_select_dialog def _text(self, painter, option, index): - cue = Application().cue_model.get(index.data()) + cue = self.cue_model.get(index.data()) if cue is not None: return "{} | {}".format(cue.index, cue.name) diff --git a/lisp/ui/widgets/fades.py b/lisp/ui/widgets/fades.py index 31443a69a..ff624ab8d 100644 --- a/lisp/ui/widgets/fades.py +++ b/lisp/ui/widgets/fades.py @@ -17,7 +17,7 @@ from enum import Enum -from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt +from PyQt5.QtCore import QT_TRANSLATE_NOOP from PyQt5.QtWidgets import ( QComboBox, QStyledItemDelegate, diff --git a/lisp/ui/widgets/pagestreewidget.py b/lisp/ui/widgets/pagestreewidget.py index e4339a1b7..5a9604d7d 100644 --- a/lisp/ui/widgets/pagestreewidget.py +++ b/lisp/ui/widgets/pagestreewidget.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2019 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,6 +18,8 @@ from PyQt5.QtCore import QModelIndex, QAbstractItemModel, Qt from PyQt5.QtWidgets import QWidget, QGridLayout, QTreeView, QSizePolicy +from lisp.ui.qdelegates import PaddedDelegate + class PagesTreeWidget(QWidget): def __init__(self, navModel, **kwargs): @@ -34,6 +36,7 @@ def __init__(self, navModel, **kwargs): self.navWidget = QTreeView() self.navWidget.setHeaderHidden(True) self.navWidget.setModel(self.navModel) + self.navWidget.setItemDelegate(PaddedDelegate(vPad=10)) self.layout().addWidget(self.navWidget, 0, 0) self._currentWidget = QWidget() @@ -73,7 +76,7 @@ def _changePage(self, selected): class PageNode: """ - :type parent: PageNode + :type parent: PageNode | None :type _children: list[PageNode] """ From 7ed8349326d6fcefe13e967178c5128140cf3f3d Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 1 May 2019 13:03:25 +0200 Subject: [PATCH 178/333] New clock widget in the status bar Add: clock widget Update: StatusBar widgets moved to a single new widget to allow custom positioning Update: Now using "QFontDatabase.systemFont" to get a Fixed font --- .circleci/config.yml | 2 + lisp/plugins/list_layout/list_widgets.py | 6 +-- lisp/ui/logging/details.py | 6 +-- lisp/ui/logging/models.py | 2 +- lisp/ui/logging/status.py | 67 ++++++++++++------------ lisp/ui/mainwindow.py | 66 ++++++++++++++++------- lisp/ui/widgets/qclock.py | 40 ++++++++++++++ 7 files changed, 128 insertions(+), 61 deletions(-) create mode 100644 lisp/ui/widgets/qclock.py diff --git a/.circleci/config.yml b/.circleci/config.yml index b4d9695e2..9614bb2d5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -124,6 +124,8 @@ workflows: context: Bintray filters: branches: + ignore: + - /l10n_.*/ only: - master - develop diff --git a/lisp/plugins/list_layout/list_widgets.py b/lisp/plugins/list_layout/list_widgets.py index 57803e0ea..c0e715f30 100644 --- a/lisp/plugins/list_layout/list_widgets.py +++ b/lisp/plugins/list_layout/list_widgets.py @@ -16,7 +16,7 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import QRect, Qt -from PyQt5.QtGui import QFont, QPainter, QBrush, QColor, QPen, QPainterPath +from PyQt5.QtGui import QFont, QPainter, QBrush, QColor, QPen, QPainterPath, QFontDatabase from PyQt5.QtWidgets import QLabel, QProgressBar, QWidget from lisp.core.signal import Connection @@ -169,9 +169,7 @@ def __init__(self, item, *args): self.setObjectName("ListTimeWidget") self.setValue(0) self.setTextVisible(True) - font = QFont("Monospace") - font.setStyleHint(QFont.Monospace) - self.setFont(font) + self.setFont(QFontDatabase.systemFont(QFontDatabase.FixedFont)) self.show_zero_duration = False self.accurate_time = True diff --git a/lisp/ui/logging/details.py b/lisp/ui/logging/details.py index 715e28663..493c2cc96 100644 --- a/lisp/ui/logging/details.py +++ b/lisp/ui/logging/details.py @@ -16,7 +16,7 @@ # along with Linux Show Player. If not, see . import traceback -from PyQt5.QtGui import QFont +from PyQt5.QtGui import QFont, QFontDatabase from PyQt5.QtWidgets import QTextEdit from lisp.ui.logging.common import LOG_ATTRIBUTES @@ -30,9 +30,7 @@ def __init__(self, *args): self.setLineWrapMode(self.NoWrap) self.setReadOnly(True) - font = QFont("Monospace") - font.setStyleHint(QFont.Monospace) - self.setFont(font) + self.setFont(QFontDatabase.systemFont(QFontDatabase.FixedFont)) def setLogRecord(self, record): self._record = record diff --git a/lisp/ui/logging/models.py b/lisp/ui/logging/models.py index f38b73516..05da4ef54 100644 --- a/lisp/ui/logging/models.py +++ b/lisp/ui/logging/models.py @@ -148,7 +148,7 @@ def filterAcceptsRow(self, source_row, source_parent): return False -def log_model_factory(config): +def create_log_model(config): # Get logging configuration limit = config.get("logging.limit", 0) levels_background = { diff --git a/lisp/ui/logging/status.py b/lisp/ui/logging/status.py index fabbc1586..e0a86ed74 100644 --- a/lisp/ui/logging/status.py +++ b/lisp/ui/logging/status.py @@ -18,12 +18,13 @@ import logging from PyQt5.QtCore import pyqtSignal -from PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout, QFrame +from PyQt5.QtWidgets import QLabel, QWidget, QHBoxLayout, QSizePolicy from lisp.ui.icons import IconTheme +from lisp.ui.ui_utils import translate -class LogStatusView(QWidget): +class LogStatusIcon(QWidget): double_clicked = pyqtSignal() def __init__(self, log_model, icons_size=16, **kwargs): @@ -32,55 +33,53 @@ def __init__(self, log_model, icons_size=16, **kwargs): """ super().__init__(**kwargs) self.setLayout(QHBoxLayout()) - self.layout().setSpacing(10) - self.layout().setContentsMargins(5, 5, 5, 5) + self.setToolTip(translate("LogStatusIcon", "Errors/Warnings")) + self.layout().setSpacing(5) + self.layout().setContentsMargins(0, 0, 0, 0) self._log_model = log_model - self._log_model.rowsInserted.connect(self._new_rows) + self._log_model.rowsInserted.connect(self._newRows) self._icons_size = icons_size self._errors = 0 - self._warnings = 0 - self.messageLabel = QLabel(self) - self.messageLabel.setMaximumWidth(300) - self.layout().addWidget(self.messageLabel) + self.errorsCount = QLabel("0", self) + self.errorsCount.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.layout().addWidget(self.errorsCount) - self.line = QFrame() - self.line.setFrameShape(QFrame.VLine) - self.line.setFrameShadow(QFrame.Sunken) - self.layout().addWidget(self.line) - - self.iconWidget = QWidget(self) - self.iconWidget.setToolTip("Errors/Warnings") - self.iconWidget.setLayout(QHBoxLayout()) - self.iconWidget.layout().setContentsMargins(0, 0, 0, 0) - self.iconWidget.layout().setSpacing(5) - self.layout().addWidget(self.iconWidget) - - self.errorsCount = QLabel("0", self.iconWidget) - self.iconWidget.layout().addWidget(self.errorsCount) - - self.errorIcon = QLabel(self.iconWidget) + self.errorIcon = QLabel(self) + self.errorIcon.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.errorIcon.setPixmap( IconTheme.get("dialog-error").pixmap(icons_size) ) - self.iconWidget.layout().addWidget(self.errorIcon) + self.layout().addWidget(self.errorIcon) def mouseDoubleClickEvent(self, e): self.double_clicked.emit() - def _new_rows(self, parent, start, end): - # Take the last record in the model - record = self._log_model.record(self._log_model.rowCount() - 1) - - if record.levelno >= logging.INFO: - # Display only the fist line of text - self.messageLabel.setText(record.message.split("\n")[0]) - + def _newRows(self, parent, start, end): for n in range(start, end + 1): level = self._log_model.record(n).levelno if level >= logging.WARNING: self._errors += 1 self.errorsCount.setText(str(self._errors)) + + +class LogMessageWidget(QLabel): + + def __init__(self, log_model, parent=None): + """ + :type log_model: lisp.ui.logging.models.LogRecordModel + """ + super().__init__(parent) + self._log_model = log_model + self._log_model.rowsInserted.connect(self._newRows) + + def _newRows(self, parent, start, end): + # Take the last record in the model + record = self._log_model.record(end) + + if record.levelno >= logging.INFO: + # Display only the fist line of text + self.setText(record.message.split("\n")[0]) diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index e6de9410b..f8ea33f35 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -18,7 +18,7 @@ import logging import os from PyQt5 import QtCore -from PyQt5.QtCore import pyqtSignal +from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtGui import QKeySequence from PyQt5.QtWidgets import ( QMainWindow, @@ -31,7 +31,7 @@ QMessageBox, QVBoxLayout, QWidget, -) + QFrame, QHBoxLayout, QSizePolicy) from functools import partial from lisp.command.layout import LayoutAutoInsertCuesCommand @@ -41,11 +41,12 @@ from lisp.ui.about import About from lisp.ui.logging.dialog import LogDialogs from lisp.ui.logging.handler import LogModelHandler -from lisp.ui.logging.models import log_model_factory -from lisp.ui.logging.status import LogStatusView +from lisp.ui.logging.models import create_log_model +from lisp.ui.logging.status import LogStatusIcon, LogMessageWidget from lisp.ui.logging.viewer import LogViewer from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.ui_utils import translate +from lisp.ui.widgets.qclock import QDigitalLabelClock logger = logging.getLogger(__name__) @@ -165,25 +166,17 @@ def __init__(self, app, title="Linux Show Player", **kwargs): self.menuAbout.addSeparator() self.menuAbout.addAction(self.actionAbout_Qt) - # Logging model - self.logModel = log_model_factory(self._app.conf) - - # Handler to populate the model - self.logHandler = LogModelHandler(self.logModel) - logging.getLogger().addHandler(self.logHandler) - # Logging + self.logModel = create_log_model(self._app.conf) + self.logHandler = LogModelHandler(self.logModel) self.logViewer = LogViewer(self.logModel, self._app.conf) - - # Logging status widget - self.logStatus = LogStatusView(self.logModel) - self.logStatus.double_clicked.connect(self.logViewer.showMaximized) - self.statusBar().addPermanentWidget(self.logStatus) - - # Logging dialogs for errors self.logDialogs = LogDialogs( self.logModel, level=logging.ERROR, parent=self ) + logging.getLogger().addHandler(self.logHandler) + + # Status bar + self.statusBar().addPermanentWidget(MainStatusBar(self), 1) # Set component text self.retranslateUi() @@ -423,3 +416,40 @@ def __checkSessionSaved(self): def __about(self): About(self).show() + + +class MainStatusBar(QWidget): + def __init__(self, mainWindow): + super().__init__(parent=mainWindow.statusBar()) + self.setLayout(QHBoxLayout()) + self.layout().setSpacing(10) + self.layout().setContentsMargins(5, 5, 5, 5) + + # Clock + self.clock = QDigitalLabelClock(parent=self) + self.clock.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.addWidget(self.clock) + # --------- + self.addDivider() + # Logging Messages + self.logMessage = LogMessageWidget(mainWindow.logModel, parent=self) + self.addWidget(self.logMessage) + # --------- + self.addDivider() + # Logging StatusIcon + self.logStatus = LogStatusIcon(mainWindow.logModel, parent=self) + self.logStatus.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.logStatus.double_clicked.connect( + mainWindow.logViewer.showMaximized + ) + self.addWidget(self.logStatus) + + def addWidget(self, widget): + self.layout().addWidget(widget) + + def addDivider(self): + divider = QFrame(self) + divider.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Minimum) + divider.setFrameShape(QFrame.VLine) + divider.setFrameShadow(QFrame.Sunken) + self.addWidget(divider) diff --git a/lisp/ui/widgets/qclock.py b/lisp/ui/widgets/qclock.py new file mode 100644 index 000000000..133bda89c --- /dev/null +++ b/lisp/ui/widgets/qclock.py @@ -0,0 +1,40 @@ +# This file is part of Linux Show Player +# +# Copyright 2019 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + + +from PyQt5.QtCore import QTime, QTimer +from PyQt5.QtGui import QFontDatabase +from PyQt5.QtWidgets import QLabel + + +class QDigitalLabelClock(QLabel): + def __init__(self, resolution=1000, time_format="hh:mm", parent=None): + super().__init__(parent) + self.setFont(QFontDatabase.systemFont(QFontDatabase.FixedFont)) + + self.time_format = time_format + self.timer = QTimer(self) + self.timer.timeout.connect(self.updateTime) + self.timer.start(resolution) + + self.updateTime() + + def updateTime(self): + time = QTime.currentTime() + text = time.toString(self.time_format) + + self.setText(text) From e5aa738f67c4f71157dd22c9227f825f7728ca32 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 1 May 2019 17:06:08 +0200 Subject: [PATCH 179/333] Import cleanup, updated translations files --- lisp/__init__.py | 1 + lisp/application.py | 5 +- lisp/i18n/ts/en/action_cues.ts | 8 +- lisp/i18n/ts/en/lisp.ts | 136 ++++++++++-------- lisp/i18n/ts/en/presets.ts | 37 ++--- lisp/i18n/ts/en/replay_gain.ts | 10 +- lisp/i18n/ts/en/triggers.ts | 10 +- lisp/layout/cue_layout.py | 2 +- lisp/plugins/cart_layout/__init__.py | 2 +- lisp/plugins/controller/common.py | 3 +- lisp/plugins/controller/protocols/osc.py | 1 + lisp/plugins/gst_backend/gst_backend.py | 1 + lisp/plugins/gst_backend/gst_media.py | 1 - .../plugins/gst_backend/settings/jack_sink.py | 4 +- .../plugins/gst_backend/settings/uri_input.py | 2 +- lisp/plugins/list_layout/__init__.py | 2 +- lisp/plugins/list_layout/list_widgets.py | 9 +- lisp/plugins/list_layout/playing_widgets.py | 2 +- lisp/plugins/midi/midi_cue.py | 1 + lisp/plugins/midi/midi_io.py | 3 +- lisp/plugins/network/network.py | 2 +- lisp/plugins/osc/osc_cue.py | 1 + lisp/plugins/osc/osc_server.py | 4 +- lisp/plugins/presets/lib.py | 2 +- lisp/plugins/synchronizer/synchronizer.py | 1 + lisp/plugins/timecode/timecode.py | 2 +- lisp/ui/layoutselect.py | 1 - lisp/ui/logging/details.py | 3 +- lisp/ui/logging/dialog.py | 2 +- lisp/ui/logging/status.py | 1 - lisp/ui/logging/viewer.py | 2 - lisp/ui/mainwindow.py | 10 +- lisp/ui/qdelegates.py | 2 +- lisp/ui/settings/app_configuration.py | 2 +- lisp/ui/settings/cue_pages/cue_appearance.py | 2 +- lisp/ui/themes/dark/dark.py | 1 + lisp/ui/ui_utils.py | 4 +- lisp/ui/widgets/cue_next_actions.py | 1 - lisp/ui/widgets/qclickslider.py | 3 +- lisp/ui/widgets/qenumcombobox.py | 3 +- 40 files changed, 160 insertions(+), 129 deletions(-) diff --git a/lisp/__init__.py b/lisp/__init__.py index 2b6d3f6de..dfbf3bd55 100644 --- a/lisp/__init__.py +++ b/lisp/__init__.py @@ -16,6 +16,7 @@ # along with Linux Show Player. If not, see . from os import path + from appdirs import AppDirs __author__ = "Francesco Ceruti" diff --git a/lisp/application.py b/lisp/application.py index 0b2723e25..182c145b3 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -17,12 +17,12 @@ import json import logging -from PyQt5.QtWidgets import QDialog, qApp from os.path import exists, dirname +from PyQt5.QtWidgets import QDialog, qApp + from lisp import layout from lisp.command.stack import CommandsStack -from lisp.session import Session from lisp.core.signal import Signal from lisp.core.singleton import Singleton from lisp.core.util import filter_live_properties @@ -30,6 +30,7 @@ from lisp.cues.cue_factory import CueFactory from lisp.cues.cue_model import CueModel from lisp.cues.media_cue import MediaCue +from lisp.session import Session from lisp.ui.layoutselect import LayoutSelect from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.app_configuration import AppConfigurationDialog diff --git a/lisp/i18n/ts/en/action_cues.ts b/lisp/i18n/ts/en/action_cues.ts index 058fbbc16..fd8a9c3e6 100644 --- a/lisp/i18n/ts/en/action_cues.ts +++ b/lisp/i18n/ts/en/action_cues.ts @@ -3,22 +3,22 @@ CollectionCue - + Add Add - + Remove Remove - + Cue Cue - + Action Action diff --git a/lisp/i18n/ts/en/lisp.ts b/lisp/i18n/ts/en/lisp.ts index 1d5656ab3..43461f578 100644 --- a/lisp/i18n/ts/en/lisp.ts +++ b/lisp/i18n/ts/en/lisp.ts @@ -64,9 +64,6 @@ - - Actions - AlsaSinkSettings @@ -319,22 +316,22 @@ CollectionCue - + Add - + Remove - + Cue Cue - + Action @@ -671,7 +668,7 @@ - + Misc cues @@ -1389,6 +1386,14 @@ + + LogStatusIcon + + + Errors/Warnings + + + Logging @@ -1652,132 +1657,132 @@ MainWindow - + &File &File - + New session New session - + Open Open - + Save session Save session - + Preferences Preferences - + Save as Save as - + Full Screen Full Screen - + Exit Exit - + &Edit &Edit - + Undo Undo - + Redo Redo - + Select all Select all - + Select all media cues Select all media cues - + Deselect all Deselect all - + CTRL+SHIFT+A CTRL+SHIFT+A - + Invert selection Invert selection - + CTRL+I CTRL+I - + Edit selected Edit selected - + CTRL+SHIFT+E CTRL+SHIFT+E - + &Layout &Layout - + &Tools &Tools - + Edit selection Edit selection - + &About &About - + About About - + About Qt About Qt - + Close session Close session @@ -1792,7 +1797,7 @@ Discard the changes? - + Do you want to save them now? @@ -1800,7 +1805,7 @@ MainWindowDebug - + Registered cue menu: "{}" @@ -1808,7 +1813,7 @@ MainWindowError - + Cannot create cue {} @@ -2176,12 +2181,12 @@ Preset - + Create Cue - + Load on selected Cues @@ -2197,32 +2202,32 @@ Presets - + Cannot scan presets - + Error while deleting preset "{}" - + Cannot load preset "{}" - + Cannot save preset "{}" - + Cannot rename preset "{}" - + Select Preset @@ -2237,42 +2242,42 @@ - + Add - + Rename - + Edit - + Remove - + Export selected - + Import - + Warning Warning - + The same name is already used! @@ -2306,6 +2311,11 @@ Save as preset + + + Cannot create a cue from this preset: {} + + QColorButton @@ -2445,12 +2455,12 @@ ReplayGainDebug - + Applied gain for: {} - + Discarded gain for: {} @@ -2458,17 +2468,17 @@ ReplayGainInfo - + Gain processing stopped by user. - + Started gain calculation for: {} - + Gain calculated for: {} @@ -2745,27 +2755,27 @@ TriggersSettings - + Add - + Remove - + Trigger - + Cue Cue - + Action diff --git a/lisp/i18n/ts/en/presets.ts b/lisp/i18n/ts/en/presets.ts index 36c2cb78f..2d373cfe3 100644 --- a/lisp/i18n/ts/en/presets.ts +++ b/lisp/i18n/ts/en/presets.ts @@ -3,12 +3,12 @@ Preset - + Create Cue Create Cue - + Load on selected Cues Load on selected Cues @@ -26,32 +26,32 @@ Save as preset - + Cannot scan presets Cannot scan presets - + Error while deleting preset "{}" Error while deleting preset "{}" - + Cannot load preset "{}" Cannot load preset "{}" - + Cannot save preset "{}" Cannot save preset "{}" - + Cannot rename preset "{}" Cannot rename preset "{}" - + Select Preset Select Preset @@ -61,42 +61,42 @@ Preset name - + Add Add - + Rename Rename - + Edit Edit - + Remove Remove - + Export selected Export selected - + Import Import - + Warning Warning - + The same name is already used! The same name is already used! @@ -125,5 +125,10 @@ Load on selected cues + + + Cannot create a cue from this preset: {} + + diff --git a/lisp/i18n/ts/en/replay_gain.ts b/lisp/i18n/ts/en/replay_gain.ts index 4296e97c9..504e0301f 100644 --- a/lisp/i18n/ts/en/replay_gain.ts +++ b/lisp/i18n/ts/en/replay_gain.ts @@ -51,12 +51,12 @@ ReplayGainDebug - + Applied gain for: {} - + Discarded gain for: {} @@ -64,17 +64,17 @@ ReplayGainInfo - + Gain processing stopped by user. - + Started gain calculation for: {} - + Gain calculated for: {} diff --git a/lisp/i18n/ts/en/triggers.ts b/lisp/i18n/ts/en/triggers.ts index 245e5749a..5599d998b 100644 --- a/lisp/i18n/ts/en/triggers.ts +++ b/lisp/i18n/ts/en/triggers.ts @@ -34,27 +34,27 @@ TriggersSettings - + Add Add - + Remove Remove - + Trigger Trigger - + Cue Cue - + Action Action diff --git a/lisp/layout/cue_layout.py b/lisp/layout/cue_layout.py index 9df790c1e..4e3eea760 100644 --- a/lisp/layout/cue_layout.py +++ b/lisp/layout/cue_layout.py @@ -17,12 +17,12 @@ from abc import abstractmethod +from lisp.command.cue import UpdateCueCommand, UpdateCuesCommand from lisp.command.model import ModelRemoveItemsCommand from lisp.core.has_properties import HasProperties from lisp.core.signal import Signal from lisp.core.util import greatest_common_superclass from lisp.cues.cue import Cue, CueAction -from lisp.command.cue import UpdateCueCommand, UpdateCuesCommand from lisp.layout.cue_menu import CueContextMenu from lisp.ui.settings.cue_settings import CueSettingsDialog from lisp.ui.ui_utils import adjust_widget_position diff --git a/lisp/plugins/cart_layout/__init__.py b/lisp/plugins/cart_layout/__init__.py index e29154335..ca9fd7654 100644 --- a/lisp/plugins/cart_layout/__init__.py +++ b/lisp/plugins/cart_layout/__init__.py @@ -1,7 +1,7 @@ from lisp.core.plugin import Plugin from lisp.layout import register_layout -from lisp.plugins.cart_layout.settings import CartLayoutSettings from lisp.plugins.cart_layout.layout import CartLayout as _CartLayout +from lisp.plugins.cart_layout.settings import CartLayoutSettings from lisp.ui.settings.app_configuration import AppConfigurationDialog diff --git a/lisp/plugins/controller/common.py b/lisp/plugins/controller/common.py index 4fb8e7100..be587f8c0 100644 --- a/lisp/plugins/controller/common.py +++ b/lisp/plugins/controller/common.py @@ -15,9 +15,10 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import QT_TRANSLATE_NOOP from enum import Enum +from PyQt5.QtCore import QT_TRANSLATE_NOOP + from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 5854df7ad..64fe2a7db 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -18,6 +18,7 @@ import ast + from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import ( QGroupBox, diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index 78bdc0bee..23a38ca71 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -16,6 +16,7 @@ # along with Linux Show Player. If not, see . import os.path + from PyQt5.QtCore import Qt from PyQt5.QtGui import QCursor from PyQt5.QtWidgets import QFileDialog, QApplication diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index 832a2ef9e..36b685b6c 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -14,7 +14,6 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from time import perf_counter import logging import weakref diff --git a/lisp/plugins/gst_backend/settings/jack_sink.py b/lisp/plugins/gst_backend/settings/jack_sink.py index 7e6577e0b..3f5ce5645 100644 --- a/lisp/plugins/gst_backend/settings/jack_sink.py +++ b/lisp/plugins/gst_backend/settings/jack_sink.py @@ -15,8 +15,9 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import jack import logging + +import jack from PyQt5.QtCore import Qt from PyQt5.QtGui import QPainter, QPolygon, QPainterPath from PyQt5.QtWidgets import ( @@ -36,7 +37,6 @@ from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate - logger = logging.getLogger(__name__) diff --git a/lisp/plugins/gst_backend/settings/uri_input.py b/lisp/plugins/gst_backend/settings/uri_input.py index f71b2f943..533091161 100644 --- a/lisp/plugins/gst_backend/settings/uri_input.py +++ b/lisp/plugins/gst_backend/settings/uri_input.py @@ -16,7 +16,7 @@ # along with Linux Show Player. If not, see . import os -from PyQt5.QtCore import QStandardPaths, Qt +from PyQt5.QtCore import Qt from PyQt5.QtWidgets import ( QGroupBox, QHBoxLayout, diff --git a/lisp/plugins/list_layout/__init__.py b/lisp/plugins/list_layout/__init__.py index 93c6b2d07..20e91e8fc 100644 --- a/lisp/plugins/list_layout/__init__.py +++ b/lisp/plugins/list_layout/__init__.py @@ -1,7 +1,7 @@ from lisp.core.plugin import Plugin from lisp.layout import register_layout -from lisp.plugins.list_layout.settings import ListLayoutSettings from lisp.plugins.list_layout.layout import ListLayout as _ListLayout +from lisp.plugins.list_layout.settings import ListLayoutSettings from lisp.ui.settings.app_configuration import AppConfigurationDialog diff --git a/lisp/plugins/list_layout/list_widgets.py b/lisp/plugins/list_layout/list_widgets.py index c0e715f30..6acd24324 100644 --- a/lisp/plugins/list_layout/list_widgets.py +++ b/lisp/plugins/list_layout/list_widgets.py @@ -16,7 +16,14 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import QRect, Qt -from PyQt5.QtGui import QFont, QPainter, QBrush, QColor, QPen, QPainterPath, QFontDatabase +from PyQt5.QtGui import ( + QPainter, + QBrush, + QColor, + QPen, + QPainterPath, + QFontDatabase, +) from PyQt5.QtWidgets import QLabel, QProgressBar, QWidget from lisp.core.signal import Connection diff --git a/lisp/plugins/list_layout/playing_widgets.py b/lisp/plugins/list_layout/playing_widgets.py index eb5de2ce2..13df55b82 100644 --- a/lisp/plugins/list_layout/playing_widgets.py +++ b/lisp/plugins/list_layout/playing_widgets.py @@ -25,10 +25,10 @@ QLCDNumber, QHBoxLayout, ) -from lisp.cues.cue import CueAction from lisp.core.signal import Connection from lisp.core.util import strtime +from lisp.cues.cue import CueAction from lisp.cues.cue_time import CueTime from lisp.cues.media_cue import MediaCue from lisp.plugins.list_layout.control_buttons import CueControlButtons diff --git a/lisp/plugins/midi/midi_cue.py b/lisp/plugins/midi/midi_cue.py index 7ca18ce74..9c3fd8c56 100644 --- a/lisp/plugins/midi/midi_cue.py +++ b/lisp/plugins/midi/midi_cue.py @@ -16,6 +16,7 @@ # along with Linux Show Player. If not, see . import logging + from PyQt5.QtCore import QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QVBoxLayout diff --git a/lisp/plugins/midi/midi_io.py b/lisp/plugins/midi/midi_io.py index cecea8fd2..84edc5d04 100644 --- a/lisp/plugins/midi/midi_io.py +++ b/lisp/plugins/midi/midi_io.py @@ -15,9 +15,10 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import mido from abc import ABC, abstractmethod +import mido + from lisp.core.signal import Signal from lisp.plugins.midi.midi_utils import mido_backend diff --git a/lisp/plugins/network/network.py b/lisp/plugins/network/network.py index a8670b735..dcd8d2674 100644 --- a/lisp/plugins/network/network.py +++ b/lisp/plugins/network/network.py @@ -19,8 +19,8 @@ from lisp.core.plugin import Plugin from lisp.plugins.network.api import route_all -from lisp.plugins.network.server import APIServerThread from lisp.plugins.network.discovery import Announcer +from lisp.plugins.network.server import APIServerThread class Network(Plugin): diff --git a/lisp/plugins/osc/osc_cue.py b/lisp/plugins/osc/osc_cue.py index cdb86f39c..effa748b4 100644 --- a/lisp/plugins/osc/osc_cue.py +++ b/lisp/plugins/osc/osc_cue.py @@ -17,6 +17,7 @@ # along with Linux Show Player. If not, see . import logging + from PyQt5 import QtCore from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import ( diff --git a/lisp/plugins/osc/osc_server.py b/lisp/plugins/osc/osc_server.py index 6c59470cc..cdf42cb98 100644 --- a/lisp/plugins/osc/osc_server.py +++ b/lisp/plugins/osc/osc_server.py @@ -16,11 +16,11 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from liblo import ServerThread, ServerError - import logging from threading import Lock +from liblo import ServerThread, ServerError + from lisp.core.signal import Signal from lisp.core.util import EqEnum from lisp.ui.ui_utils import translate diff --git a/lisp/plugins/presets/lib.py b/lisp/plugins/presets/lib.py index 51d800a86..0c443bd4f 100644 --- a/lisp/plugins/presets/lib.py +++ b/lisp/plugins/presets/lib.py @@ -20,8 +20,8 @@ from zipfile import ZipFile from lisp import app_dirs -from lisp.command.layout import LayoutAutoInsertCuesCommand from lisp.command.cue import UpdateCueCommand, UpdateCuesCommand +from lisp.command.layout import LayoutAutoInsertCuesCommand from lisp.cues.cue_factory import CueFactory PRESETS_DIR = os.path.join(app_dirs.user_data_dir, "presets") diff --git a/lisp/plugins/synchronizer/synchronizer.py b/lisp/plugins/synchronizer/synchronizer.py index 0b3ee0b39..ff270583d 100644 --- a/lisp/plugins/synchronizer/synchronizer.py +++ b/lisp/plugins/synchronizer/synchronizer.py @@ -16,6 +16,7 @@ # along with Linux Show Player. If not, see . import logging + import requests from PyQt5.QtWidgets import QMenu, QAction, QMessageBox diff --git a/lisp/plugins/timecode/timecode.py b/lisp/plugins/timecode/timecode.py index 77728ca0d..845de45ca 100644 --- a/lisp/plugins/timecode/timecode.py +++ b/lisp/plugins/timecode/timecode.py @@ -18,8 +18,8 @@ import logging -from lisp.core.properties import Property from lisp.core.plugin import Plugin +from lisp.core.properties import Property from lisp.core.signal import Connection from lisp.cues.cue import Cue from lisp.cues.media_cue import MediaCue diff --git a/lisp/ui/layoutselect.py b/lisp/ui/layoutselect.py index 25ae03e42..a5d2edd4e 100644 --- a/lisp/ui/layoutselect.py +++ b/lisp/ui/layoutselect.py @@ -15,7 +15,6 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import Qt from PyQt5.QtWidgets import ( QDialog, QComboBox, diff --git a/lisp/ui/logging/details.py b/lisp/ui/logging/details.py index 493c2cc96..fd6162c3f 100644 --- a/lisp/ui/logging/details.py +++ b/lisp/ui/logging/details.py @@ -16,7 +16,8 @@ # along with Linux Show Player. If not, see . import traceback -from PyQt5.QtGui import QFont, QFontDatabase + +from PyQt5.QtGui import QFontDatabase from PyQt5.QtWidgets import QTextEdit from lisp.ui.logging.common import LOG_ATTRIBUTES diff --git a/lisp/ui/logging/dialog.py b/lisp/ui/logging/dialog.py index 29342b79d..adf57a285 100644 --- a/lisp/ui/logging/dialog.py +++ b/lisp/ui/logging/dialog.py @@ -28,8 +28,8 @@ QTextEdit, ) -from lisp.ui.logging.common import LOG_LEVELS, LOG_ICONS_NAMES from lisp.ui.icons import IconTheme +from lisp.ui.logging.common import LOG_LEVELS, LOG_ICONS_NAMES from lisp.ui.ui_utils import translate diff --git a/lisp/ui/logging/status.py b/lisp/ui/logging/status.py index e0a86ed74..8852cfb92 100644 --- a/lisp/ui/logging/status.py +++ b/lisp/ui/logging/status.py @@ -67,7 +67,6 @@ def _newRows(self, parent, start, end): class LogMessageWidget(QLabel): - def __init__(self, log_model, parent=None): """ :type log_model: lisp.ui.logging.models.LogRecordModel diff --git a/lisp/ui/logging/viewer.py b/lisp/ui/logging/viewer.py index df2dde86a..c91509dca 100644 --- a/lisp/ui/logging/viewer.py +++ b/lisp/ui/logging/viewer.py @@ -23,8 +23,6 @@ QStatusBar, QLabel, QTableView, - QVBoxLayout, - QWidget, QSplitter, ) diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index f8ea33f35..06e588dc2 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -17,8 +17,10 @@ import logging import os +from functools import partial + from PyQt5 import QtCore -from PyQt5.QtCore import pyqtSignal, Qt +from PyQt5.QtCore import pyqtSignal from PyQt5.QtGui import QKeySequence from PyQt5.QtWidgets import ( QMainWindow, @@ -31,8 +33,10 @@ QMessageBox, QVBoxLayout, QWidget, - QFrame, QHBoxLayout, QSizePolicy) -from functools import partial + QFrame, + QHBoxLayout, + QSizePolicy, +) from lisp.command.layout import LayoutAutoInsertCuesCommand from lisp.core.singleton import QSingleton diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index f781a5a3d..f165a2eec 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import Qt, QEvent, QPoint, QSize +from PyQt5.QtCore import Qt, QEvent, QSize from PyQt5.QtWidgets import ( QStyledItemDelegate, QComboBox, diff --git a/lisp/ui/settings/app_configuration.py b/lisp/ui/settings/app_configuration.py index 18c66ed10..9d4886199 100644 --- a/lisp/ui/settings/app_configuration.py +++ b/lisp/ui/settings/app_configuration.py @@ -15,9 +15,9 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging from collections import namedtuple -import logging from PyQt5 import QtCore from PyQt5.QtCore import QModelIndex from PyQt5.QtWidgets import QVBoxLayout, QDialogButtonBox, QDialog diff --git a/lisp/ui/settings/cue_pages/cue_appearance.py b/lisp/ui/settings/cue_pages/cue_appearance.py index 07fb9f339..a620a2a58 100644 --- a/lisp/ui/settings/cue_pages/cue_appearance.py +++ b/lisp/ui/settings/cue_pages/cue_appearance.py @@ -27,8 +27,8 @@ ) from lisp.ui.settings.pages import SettingsPage -from lisp.ui.widgets import QColorButton from lisp.ui.ui_utils import translate +from lisp.ui.widgets import QColorButton class Appearance(SettingsPage): diff --git a/lisp/ui/themes/dark/dark.py b/lisp/ui/themes/dark/dark.py index 6427b3067..7e50c63b5 100644 --- a/lisp/ui/themes/dark/dark.py +++ b/lisp/ui/themes/dark/dark.py @@ -16,6 +16,7 @@ # along with Linux Show Player. If not, see . import os + from PyQt5.QtGui import QColor, QPalette # Import resources diff --git a/lisp/ui/ui_utils.py b/lisp/ui/ui_utils.py index 8a4fe145e..b6d601765 100644 --- a/lisp/ui/ui_utils.py +++ b/lisp/ui/ui_utils.py @@ -16,11 +16,11 @@ # along with Linux Show Player. If not, see . import fcntl -from itertools import chain - import logging import os import signal +from itertools import chain + from PyQt5.QtCore import QTranslator, QLocale, QSocketNotifier from PyQt5.QtWidgets import QApplication, qApp diff --git a/lisp/ui/widgets/cue_next_actions.py b/lisp/ui/widgets/cue_next_actions.py index 5eecc0832..2f56faaa3 100644 --- a/lisp/ui/widgets/cue_next_actions.py +++ b/lisp/ui/widgets/cue_next_actions.py @@ -21,7 +21,6 @@ from lisp.cues.cue import CueNextAction from lisp.ui.ui_utils import translate - CueNextActionsStrings = { CueNextAction.DoNothing: QT_TRANSLATE_NOOP("CueNextAction", "Do Nothing"), CueNextAction.TriggerAfterEnd: QT_TRANSLATE_NOOP( diff --git a/lisp/ui/widgets/qclickslider.py b/lisp/ui/widgets/qclickslider.py index adbbf51fd..625d60bda 100644 --- a/lisp/ui/widgets/qclickslider.py +++ b/lisp/ui/widgets/qclickslider.py @@ -16,8 +16,7 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, pyqtSignal -from PyQt5.QtWidgets import QSlider -from PyQt5.QtWidgets import QStyle, QStyleOptionSlider +from PyQt5.QtWidgets import QSlider, QStyle, QStyleOptionSlider class QClickSlider(QSlider): diff --git a/lisp/ui/widgets/qenumcombobox.py b/lisp/ui/widgets/qenumcombobox.py index b7e7b7784..9e00c2de8 100644 --- a/lisp/ui/widgets/qenumcombobox.py +++ b/lisp/ui/widgets/qenumcombobox.py @@ -1,6 +1,7 @@ -from PyQt5.QtWidgets import QComboBox from enum import Enum +from PyQt5.QtWidgets import QComboBox + class QEnumComboBox(QComboBox): class Mode(Enum): From 0ff87e5a294d991c67459f1a6fb2eb84fe2f8f22 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 4 May 2019 16:27:24 +0200 Subject: [PATCH 180/333] UI Improvements Add: new KeySequenceEdit widget to edit a single key combination Update: renamed some widget class Update: minor theme improvement --- lisp/plugins/cart_layout/cue_widget.py | 4 +- lisp/plugins/controller/protocols/keyboard.py | 15 ++- lisp/plugins/list_layout/playing_widgets.py | 4 +- lisp/ui/mainwindow.py | 4 +- lisp/ui/settings/cue_pages/cue_appearance.py | 6 +- lisp/ui/themes/dark/theme.qss | 5 +- lisp/ui/widgets/__init__.py | 7 +- .../{qcolorbutton.py => colorbutton.py} | 4 +- lisp/ui/widgets/{qdbmeter.py => dbmeter.py} | 2 +- .../ui/widgets/{qclock.py => digitalclock.py} | 2 +- lisp/ui/widgets/keysequenceedit.py | 107 ++++++++++++++++++ lisp/ui/widgets/qmessagebox.py | 59 ---------- 12 files changed, 139 insertions(+), 80 deletions(-) rename lisp/ui/widgets/{qcolorbutton.py => colorbutton.py} (95%) rename lisp/ui/widgets/{qdbmeter.py => dbmeter.py} (99%) rename lisp/ui/widgets/{qclock.py => digitalclock.py} (97%) create mode 100644 lisp/ui/widgets/keysequenceedit.py delete mode 100644 lisp/ui/widgets/qmessagebox.py diff --git a/lisp/plugins/cart_layout/cue_widget.py b/lisp/plugins/cart_layout/cue_widget.py index f06ed778b..8d4387397 100644 --- a/lisp/plugins/cart_layout/cue_widget.py +++ b/lisp/plugins/cart_layout/cue_widget.py @@ -35,7 +35,7 @@ from lisp.cues.media_cue import MediaCue from lisp.plugins.cart_layout.page_widget import CartPageWidget from lisp.ui.icons import IconTheme -from lisp.ui.widgets import QClickLabel, QClickSlider, QDbMeter +from lisp.ui.widgets import QClickLabel, QClickSlider, DBMeter class CueWidget(QWidget): @@ -103,7 +103,7 @@ def __init__(self, cue, **kwargs): ) self.volumeSlider.setVisible(False) - self.dbMeter = QDbMeter(self) + self.dbMeter = DBMeter(self) self.dbMeter.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.dbMeter.setVisible(False) diff --git a/lisp/plugins/controller/protocols/keyboard.py b/lisp/plugins/controller/protocols/keyboard.py index c996ef1e9..9a6be79ee 100644 --- a/lisp/plugins/controller/protocols/keyboard.py +++ b/lisp/plugins/controller/protocols/keyboard.py @@ -14,8 +14,8 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . - from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP +from PyQt5.QtGui import QKeySequence from PyQt5.QtWidgets import ( QGroupBox, QGridLayout, @@ -36,6 +36,7 @@ from lisp.ui.qmodels import SimpleTableModel from lisp.ui.settings.pages import SettingsPage, CuePageMixin from lisp.ui.ui_utils import translate +from lisp.ui.widgets.keysequenceedit import keyEventKeySequence class KeyboardSettings(SettingsPage): @@ -137,7 +138,8 @@ def __init__(self, actionDelegate, **kwargs): self.verticalHeader().setDefaultSectionSize(24) self.verticalHeader().setHighlightSections(False) - self.delegates = [LineEditDelegate(max_length=1), actionDelegate] + # TODO: QKeySequenceDelegate + self.delegates = [LineEditDelegate(max_length=100), actionDelegate] for column, delegate in enumerate(self.delegates): self.setItemDelegateForColumn(column, delegate) @@ -154,5 +156,10 @@ def reset(self): Application().layout.key_pressed.disconnect(self.__key_pressed) def __key_pressed(self, key_event): - if not key_event.isAutoRepeat() and key_event.text() != "": - self.protocol_event.emit(key_event.text()) + if not key_event.isAutoRepeat(): + sequence = keyEventKeySequence(key_event) + print(bool(sequence)) + if sequence: + self.protocol_event.emit( + sequence.toString(QKeySequence.NativeText) + ) diff --git a/lisp/plugins/list_layout/playing_widgets.py b/lisp/plugins/list_layout/playing_widgets.py index 13df55b82..81bc9912e 100644 --- a/lisp/plugins/list_layout/playing_widgets.py +++ b/lisp/plugins/list_layout/playing_widgets.py @@ -32,7 +32,7 @@ from lisp.cues.cue_time import CueTime from lisp.cues.media_cue import MediaCue from lisp.plugins.list_layout.control_buttons import CueControlButtons -from lisp.ui.widgets import QClickSlider, QDbMeter +from lisp.ui.widgets import QClickSlider, DBMeter def get_running_widget(cue, config, **kwargs): @@ -185,7 +185,7 @@ def __init__(self, cue, config, **kwargs): self.seekSlider.sliderJumped.connect(self._seek) self.seekSlider.setVisible(False) - self.dbmeter = QDbMeter(self.gridLayoutWidget) + self.dbmeter = DBMeter(self.gridLayoutWidget) self.dbmeter.setVisible(False) cue.changed("duration").connect( diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 06e588dc2..d38827515 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -50,7 +50,7 @@ from lisp.ui.logging.viewer import LogViewer from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.ui_utils import translate -from lisp.ui.widgets.qclock import QDigitalLabelClock +from lisp.ui.widgets import DigitalLabelClock logger = logging.getLogger(__name__) @@ -430,7 +430,7 @@ def __init__(self, mainWindow): self.layout().setContentsMargins(5, 5, 5, 5) # Clock - self.clock = QDigitalLabelClock(parent=self) + self.clock = DigitalLabelClock(parent=self) self.clock.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.addWidget(self.clock) # --------- diff --git a/lisp/ui/settings/cue_pages/cue_appearance.py b/lisp/ui/settings/cue_pages/cue_appearance.py index a620a2a58..d6fa98e02 100644 --- a/lisp/ui/settings/cue_pages/cue_appearance.py +++ b/lisp/ui/settings/cue_pages/cue_appearance.py @@ -28,7 +28,7 @@ from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate -from lisp.ui.widgets import QColorButton +from lisp.ui.widgets import ColorButton class Appearance(SettingsPage): @@ -68,8 +68,8 @@ def __init__(self, **kwargs): self.colorGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.colorGroup) - self.colorBButton = QColorButton(self.colorGroup) - self.colorFButton = QColorButton(self.colorGroup) + self.colorBButton = ColorButton(self.colorGroup) + self.colorFButton = ColorButton(self.colorGroup) self.colorGroup.layout().addWidget(self.colorBButton) self.colorGroup.layout().addWidget(self.colorFButton) diff --git a/lisp/ui/themes/dark/theme.qss b/lisp/ui/themes/dark/theme.qss index bd8bfe6e7..72772bae1 100644 --- a/lisp/ui/themes/dark/theme.qss +++ b/lisp/ui/themes/dark/theme.qss @@ -303,8 +303,11 @@ QPushButton:pressed { background: #35393d; } + +QLineEdit:focus, QComboBox:focus, -QPushButton:focus { +QPushButton:focus +QAbstractSpinBox:focus { border: 1px solid #419BE6; } diff --git a/lisp/ui/widgets/__init__.py b/lisp/ui/widgets/__init__.py index d777498fc..34d65c5f4 100644 --- a/lisp/ui/widgets/__init__.py +++ b/lisp/ui/widgets/__init__.py @@ -1,12 +1,13 @@ +from .colorbutton import ColorButton from .cue_actions import CueActionComboBox from .cue_next_actions import CueNextActionComboBox +from .dbmeter import DBMeter +from .digitalclock import DigitalLabelClock from .fades import FadeComboBox, FadeEdit +from .keysequenceedit import KeySequenceEdit from .locales import LocaleComboBox from .qclicklabel import QClickLabel from .qclickslider import QClickSlider -from .qcolorbutton import QColorButton -from .qdbmeter import QDbMeter -from .qmessagebox import QDetailedMessageBox from .qmutebutton import QMuteButton from .qsteptimeedit import QStepTimeEdit from .qstyledslider import QStyledSlider diff --git a/lisp/ui/widgets/qcolorbutton.py b/lisp/ui/widgets/colorbutton.py similarity index 95% rename from lisp/ui/widgets/qcolorbutton.py rename to lisp/ui/widgets/colorbutton.py index 6c323e272..bf064ccdc 100644 --- a/lisp/ui/widgets/qcolorbutton.py +++ b/lisp/ui/widgets/colorbutton.py @@ -22,7 +22,7 @@ from lisp.ui.ui_utils import translate -class QColorButton(QPushButton): +class ColorButton(QPushButton): """Custom Qt Widget to show a chosen color. Left-clicking the button shows the color-chooser. @@ -65,4 +65,4 @@ def mousePressEvent(self, e): if e.button() == Qt.RightButton: self.setColor(None) - return super(QColorButton, self).mousePressEvent(e) + return super(ColorButton, self).mousePressEvent(e) diff --git a/lisp/ui/widgets/qdbmeter.py b/lisp/ui/widgets/dbmeter.py similarity index 99% rename from lisp/ui/widgets/qdbmeter.py rename to lisp/ui/widgets/dbmeter.py index f96a22220..97faaa0d1 100644 --- a/lisp/ui/widgets/qdbmeter.py +++ b/lisp/ui/widgets/dbmeter.py @@ -20,7 +20,7 @@ from PyQt5.QtWidgets import QWidget -class QDbMeter(QWidget): +class DBMeter(QWidget): def __init__(self, parent, min=-60, max=0, clip=0): super().__init__(parent) self.db_min = min diff --git a/lisp/ui/widgets/qclock.py b/lisp/ui/widgets/digitalclock.py similarity index 97% rename from lisp/ui/widgets/qclock.py rename to lisp/ui/widgets/digitalclock.py index 133bda89c..a40c77056 100644 --- a/lisp/ui/widgets/qclock.py +++ b/lisp/ui/widgets/digitalclock.py @@ -21,7 +21,7 @@ from PyQt5.QtWidgets import QLabel -class QDigitalLabelClock(QLabel): +class DigitalLabelClock(QLabel): def __init__(self, resolution=1000, time_format="hh:mm", parent=None): super().__init__(parent) self.setFont(QFontDatabase.systemFont(QFontDatabase.FixedFont)) diff --git a/lisp/ui/widgets/keysequenceedit.py b/lisp/ui/widgets/keysequenceedit.py new file mode 100644 index 000000000..83c7cbcee --- /dev/null +++ b/lisp/ui/widgets/keysequenceedit.py @@ -0,0 +1,107 @@ +# This file is part of Linux Show Player +# +# Copyright 2019 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import Qt, QEvent +from PyQt5.QtGui import QKeySequence +from PyQt5.QtWidgets import QWidget, QLineEdit, QSizePolicy, QHBoxLayout, QPushButton + +from lisp.ui.icons import IconTheme +from lisp.ui.ui_utils import translate + +KEYS_FILTER = { + Qt.Key_Control, + Qt.Key_Shift, + Qt.Key_Meta, + Qt.Key_Alt, + Qt.Key_AltGr, + Qt.Key_unknown, +} + + +def keyEventKeySequence(keyEvent): + key = keyEvent.key() + if key not in KEYS_FILTER: + modifiers = keyEvent.modifiers() + if modifiers & Qt.ShiftModifier: + key += Qt.SHIFT + if modifiers & Qt.ControlModifier: + key += Qt.CTRL + if modifiers & Qt.AltModifier: + key += Qt.ALT + if modifiers & Qt.MetaModifier: + key += Qt.META + + return QKeySequence(key) + + return QKeySequence() + + +class KeySequenceEdit(QWidget): + def __init__(self, parent, **kwargs): + super().__init__(parent, **kwargs) + self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + self.setFocusPolicy(Qt.StrongFocus) + self.setAttribute(Qt.WA_MacShowFocusRect, True) + self.setAttribute(Qt.WA_InputMethodEnabled, False) + self.setLayout(QHBoxLayout()) + self.layout().setContentsMargins(0, 0, 0, 0) + + self.previewEdit = QLineEdit(self) + self.previewEdit.setReadOnly(True) + self.previewEdit.setFocusProxy(self) + self.previewEdit.installEventFilter(self) + self.layout().addWidget(self.previewEdit) + + self.clearButton = QPushButton(self) + self.clearButton.setIcon(IconTheme.get("edit-clear")) + self.clearButton.clicked.connect(self.clear) + self.layout().addWidget(self.clearButton) + + self._keySequence = QKeySequence() + + self.retranslateUi() + + def retranslateUi(self): + self.previewEdit.setPlaceholderText( + translate("QKeyEdit", "Press shortcut") + ) + + def keySequence(self) -> QKeySequence: + return self._keySequence + + def setKeySequence(self, keySequence: QKeySequence): + self._keySequence = keySequence + self.previewEdit.setText(keySequence.toString(QKeySequence.NativeText)) + + def clear(self): + self.setKeySequence(QKeySequence()) + + def event(self, event: QEvent): + event_type = event.type() + + if event_type == QEvent.Shortcut: + return True + elif event_type == QEvent.ShortcutOverride: + event.accept() + return True + + return super().event(event) + + def keyPressEvent(self, event): + sequence = keyEventKeySequence(event) + if sequence: + self.setKeySequence(sequence) diff --git a/lisp/ui/widgets/qmessagebox.py b/lisp/ui/widgets/qmessagebox.py deleted file mode 100644 index 478e0560e..000000000 --- a/lisp/ui/widgets/qmessagebox.py +++ /dev/null @@ -1,59 +0,0 @@ -# This file is part of Linux Show Player -# -# Copyright 2016 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - -from PyQt5.QtWidgets import QMessageBox - - -class QDetailedMessageBox(QMessageBox): - """QMessageBox providing statics methods to show detailed-text""" - - @staticmethod - def dcritical(title, text, detailed_text, parent=None): - """MessageBox with "Critical" icon""" - QDetailedMessageBox.dgeneric( - title, text, detailed_text, QMessageBox.Critical, parent - ) - - @staticmethod - def dwarning(title, text, detailed_text, parent=None): - """MessageBox with "Warning" icon""" - QDetailedMessageBox.dgeneric( - title, text, detailed_text, QMessageBox.Warning, parent - ) - - @staticmethod - def dinformation(title, text, detailed_text, parent=None): - """MessageBox with "Information" icon""" - QDetailedMessageBox.dgeneric( - title, text, detailed_text, QMessageBox.Information, parent - ) - - @staticmethod - def dgeneric(title, text, detail_text, icon, parent=None): - """Build and show a MessageBox with detailed text""" - messageBox = QMessageBox(parent) - - messageBox.setIcon(icon) - messageBox.setWindowTitle(title) - messageBox.setText(text) - # Show the detail text only if not an empty string - if detail_text.strip() != "": - messageBox.setDetailedText(detail_text) - messageBox.addButton(QMessageBox.Ok) - messageBox.setDefaultButton(QMessageBox.Ok) - - messageBox.exec() From 564413bdf5ad351c4028de1991c8b16d55fbb43a Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 6 May 2019 20:11:34 +0200 Subject: [PATCH 181/333] UI Improvements Add: HotKeyEditDelegate Update: renamed KeySequenceEdit into HotKeyEdit Update: controller.keyboard settings now use the HotKeyEditDelegate (issue #160) Fix: status indicator in ListLayout works correctly when moving the cues in the list (issue #171) Fix: fixed some edge case in the ModelMoveItemsCommand (issue #171) --- lisp/command/model.py | 9 +- lisp/plugins/controller/common.py | 2 +- lisp/plugins/controller/protocols/keyboard.py | 13 +- lisp/plugins/list_layout/list_widgets.py | 171 +++++++++--------- lisp/ui/qdelegates.py | 52 +++++- lisp/ui/widgets/__init__.py | 2 +- .../{keysequenceedit.py => hotkeyedit.py} | 29 ++- 7 files changed, 168 insertions(+), 110 deletions(-) rename lisp/ui/widgets/{keysequenceedit.py => hotkeyedit.py} (83%) diff --git a/lisp/command/model.py b/lisp/command/model.py index 57cacf603..46158cefc 100644 --- a/lisp/command/model.py +++ b/lisp/command/model.py @@ -101,6 +101,11 @@ def __init__(self, model_adapter, old_indexes, new_index): self._before = 0 self._after = 0 + if self._new_index < 0: + self._new_index = 0 + elif self._new_index >= len(model_adapter): + self._new_index = len(model_adapter) - 1 + for old_index in old_indexes: if old_index < new_index: self._before += 1 @@ -112,7 +117,7 @@ def do(self): shift = bool(self._before) for index in self._old_indexes: - if index < self._new_index: + if index <= self._new_index: self._model.move(index - before, self._new_index) before += 1 elif index > self._new_index: @@ -125,7 +130,7 @@ def undo(self): shift = bool(self._before) for old_index in self._old_indexes: - if old_index < self._new_index: + if old_index <= self._new_index: before -= 1 self._model.move(self._new_index - before, old_index) elif old_index > self._new_index: diff --git a/lisp/plugins/controller/common.py b/lisp/plugins/controller/common.py index be587f8c0..eb7f66a41 100644 --- a/lisp/plugins/controller/common.py +++ b/lisp/plugins/controller/common.py @@ -29,7 +29,7 @@ class LayoutAction(Enum): StopAll = "StopAll" PauseAll = "PauseAll" ResumeAll = "ResumeAll" - InterruptAll = "InterrupAll" + InterruptAll = "InterruptAll" FadeOutAll = "FadeOutAll" FadeInAll = "FadeInAll" diff --git a/lisp/plugins/controller/protocols/keyboard.py b/lisp/plugins/controller/protocols/keyboard.py index 9a6be79ee..0b4fd8797 100644 --- a/lisp/plugins/controller/protocols/keyboard.py +++ b/lisp/plugins/controller/protocols/keyboard.py @@ -14,6 +14,7 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . + from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtGui import QKeySequence from PyQt5.QtWidgets import ( @@ -29,14 +30,14 @@ from lisp.plugins.controller.common import LayoutAction, tr_layout_action from lisp.plugins.controller.protocol import Protocol from lisp.ui.qdelegates import ( - LineEditDelegate, CueActionDelegate, EnumComboBoxDelegate, + HotKeyEditDelegate, ) from lisp.ui.qmodels import SimpleTableModel from lisp.ui.settings.pages import SettingsPage, CuePageMixin from lisp.ui.ui_utils import translate -from lisp.ui.widgets.keysequenceedit import keyEventKeySequence +from lisp.ui.widgets.hotkeyedit import keyEventKeySequence class KeyboardSettings(SettingsPage): @@ -53,7 +54,7 @@ def __init__(self, actionDelegate, **kwargs): self.keyboardModel = SimpleTableModel( [ - translate("ControllerKeySettings", "Key"), + translate("ControllerKeySettings", "Shortcut"), translate("ControllerKeySettings", "Action"), ] ) @@ -138,8 +139,7 @@ def __init__(self, actionDelegate, **kwargs): self.verticalHeader().setDefaultSectionSize(24) self.verticalHeader().setHighlightSections(False) - # TODO: QKeySequenceDelegate - self.delegates = [LineEditDelegate(max_length=100), actionDelegate] + self.delegates = [HotKeyEditDelegate(), actionDelegate] for column, delegate in enumerate(self.delegates): self.setItemDelegateForColumn(column, delegate) @@ -158,8 +158,7 @@ def reset(self): def __key_pressed(self, key_event): if not key_event.isAutoRepeat(): sequence = keyEventKeySequence(key_event) - print(bool(sequence)) if sequence: self.protocol_event.emit( - sequence.toString(QKeySequence.NativeText) + sequence.toString(QKeySequence.PortableText) ) diff --git a/lisp/plugins/list_layout/list_widgets.py b/lisp/plugins/list_layout/list_widgets.py index 6acd24324..cfe6108f1 100644 --- a/lisp/plugins/list_layout/list_widgets.py +++ b/lisp/plugins/list_layout/list_widgets.py @@ -25,6 +25,7 @@ QFontDatabase, ) from PyQt5.QtWidgets import QLabel, QProgressBar, QWidget +from pysnooper import pysnooper from lisp.core.signal import Connection from lisp.core.util import strtime @@ -64,35 +65,30 @@ class CueStatusIcons(QWidget): def __init__(self, item, *args): super().__init__(*args) - self._statusPixmap = None - - self.item = item - self.item.cue.interrupted.connect(self._stop, Connection.QtQueued) - self.item.cue.started.connect(self._start, Connection.QtQueued) - self.item.cue.stopped.connect(self._stop, Connection.QtQueued) - self.item.cue.paused.connect(self._pause, Connection.QtQueued) - self.item.cue.error.connect(self._error, Connection.QtQueued) - self.item.cue.end.connect(self._stop, Connection.QtQueued) - - def setPixmap(self, pixmap): - self._statusPixmap = pixmap - self.update() + self._icon = None + self._item = item + + self._item.cue.interrupted.connect(self.updateIcon, Connection.QtQueued) + self._item.cue.started.connect(self.updateIcon, Connection.QtQueued) + self._item.cue.stopped.connect(self.updateIcon, Connection.QtQueued) + self._item.cue.paused.connect(self.updateIcon, Connection.QtQueued) + self._item.cue.error.connect(self.updateIcon, Connection.QtQueued) + self._item.cue.end.connect(self.updateIcon, Connection.QtQueued) + + self.updateIcon() + + def updateIcon(self): + if self._item.cue.state & CueState.Running: + self._icon = IconTheme.get("led-running") + elif self._item.cue.state & CueState.Pause: + self._icon = IconTheme.get("led-pause") + elif self._item.cue.state & CueState.Error: + self._icon = IconTheme.get("led-error") + else: + self._icon = None - def _standbyChange(self): self.update() - def _start(self): - self.setPixmap(IconTheme.get("led-running").pixmap(self._size())) - - def _pause(self): - self.setPixmap(IconTheme.get("led-pause").pixmap(self._size())) - - def _error(self): - self.setPixmap(IconTheme.get("led-error").pixmap(self._size())) - - def _stop(self): - self.setPixmap(None) - def _size(self): return self.height() - CueStatusIcons.MARGIN * 2 @@ -105,7 +101,7 @@ def paintEvent(self, event): indicator_height = self.height() indicator_width = indicator_height // 2 - if self.item.current: + if self._item.current: # Draw something like this # |‾\ # | \ @@ -122,7 +118,7 @@ def paintEvent(self, event): qp.setPen(QPen(QBrush(QColor(0, 0, 0)), 2)) qp.setBrush(QBrush(QColor(250, 220, 0))) qp.drawPath(path) - if self._statusPixmap is not None: + if self._icon is not None: qp.drawPixmap( QRect( indicator_width + CueStatusIcons.MARGIN, @@ -130,7 +126,7 @@ def paintEvent(self, event): status_size, status_size, ), - self._statusPixmap, + self._icon.pixmap(self._size()), ) qp.end() @@ -146,27 +142,26 @@ def __init__(self, item, *args): self.setAlignment(Qt.AlignCenter) item.cue.changed("next_action").connect( - self.__update, Connection.QtQueued + self._updateIcon, Connection.QtQueued ) - self.__update(item.cue.next_action) + self._updateIcon(item.cue.next_action) - def __update(self, next_action): - next_action = CueNextAction(next_action) + def _updateIcon(self, nextAction): + nextAction = CueNextAction(nextAction) pixmap = IconTheme.get("").pixmap(self.SIZE) if ( - next_action == CueNextAction.TriggerAfterWait - or next_action == CueNextAction.TriggerAfterEnd + nextAction == CueNextAction.TriggerAfterWait + or nextAction == CueNextAction.TriggerAfterEnd ): pixmap = IconTheme.get("cue-trigger-next").pixmap(self.SIZE) elif ( - next_action == CueNextAction.SelectAfterWait - or next_action == CueNextAction.SelectAfterEnd + nextAction == CueNextAction.SelectAfterWait + or nextAction == CueNextAction.SelectAfterEnd ): pixmap = IconTheme.get("cue-select-next").pixmap(self.SIZE) - self.setToolTip(tr_next_action(next_action)) - + self.setToolTip(tr_next_action(nextAction)) self.setPixmap(pixmap) @@ -178,49 +173,50 @@ def __init__(self, item, *args): self.setTextVisible(True) self.setFont(QFontDatabase.systemFont(QFontDatabase.FixedFont)) - self.show_zero_duration = False - self.accurate_time = True self.cue = item.cue + self.accurateTime = True + self.showZeroDuration = False - def _update_time(self, time): + def _updateTime(self, time): self.setValue(time) - self.setFormat(strtime(time, accurate=self.accurate_time)) + self.setFormat(strtime(time, accurate=self.accurateTime)) - def _update_duration(self, duration): - if duration > 0 or self.show_zero_duration: + def _updateDuration(self, duration): + if duration > 0 or self.showZeroDuration: # Display as disabled if duration < 0 self.setEnabled(duration > 0) self.setTextVisible(True) - self.setFormat(strtime(duration, accurate=self.accurate_time)) + self.setFormat(strtime(duration, accurate=self.accurateTime)) # Avoid settings min and max to 0, or the the bar go in busy state self.setRange(0 if duration > 0 else -1, duration) else: self.setTextVisible(False) - def _update_style(self, state): + def _updateStyle(self, state): self.setProperty("state", state) self.style().unpolish(self) self.style().polish(self) def _running(self): - self._update_style("running") + self._updateStyle("running") def _pause(self): - self._update_style("pause") - self._update_time(self.value()) + self._updateStyle("pause") + self._updateTime(self.value()) def _stop(self): - self._update_style("stop") + self._updateStyle("stop") self.setValue(self.minimum()) def _error(self): - self._update_style("error") + self._updateStyle("error") self.setValue(self.minimum()) class CueTimeWidget(TimeWidget): def __init__(self, *args): super().__init__(*args) + self._updateDuration(self.cue.duration) self.cue.interrupted.connect(self._stop, Connection.QtQueued) self.cue.started.connect(self._running, Connection.QtQueued) @@ -229,11 +225,11 @@ def __init__(self, *args): self.cue.error.connect(self._error, Connection.QtQueued) self.cue.end.connect(self._stop, Connection.QtQueued) self.cue.changed("duration").connect( - self._update_duration, Connection.QtQueued + self._updateDuration, Connection.QtQueued ) - self.cue_time = CueTime(self.cue) - self.cue_time.notify.connect(self._update_time, Connection.QtQueued) + self.cueTime = CueTime(self.cue) + self.cueTime.notify.connect(self._updateTime, Connection.QtQueued) if self.cue.state & CueState.Running: self._running() @@ -246,51 +242,50 @@ def __init__(self, *args): def _stop(self): super()._stop() - self._update_duration(self.cue.duration) + self._updateDuration(self.cue.duration) class PreWaitWidget(TimeWidget): def __init__(self, *args): super().__init__(*args) - self.show_zero_duration = True + self.showZeroDuration = True + self._updateDuration(self.cue.pre_wait) self.cue.prewait_start.connect(self._running, Connection.QtQueued) self.cue.prewait_stopped.connect(self._stop, Connection.QtQueued) self.cue.prewait_paused.connect(self._pause, Connection.QtQueued) self.cue.prewait_ended.connect(self._stop, Connection.QtQueued) self.cue.changed("pre_wait").connect( - self._update_duration, Connection.QtQueued + self._updateDuration, Connection.QtQueued ) - self._update_duration(self.cue.pre_wait) - - self.wait_time = CueWaitTime(self.cue, mode=CueWaitTime.Mode.Pre) - self.wait_time.notify.connect(self._update_time, Connection.QtQueued) + self.waitTime = CueWaitTime(self.cue, mode=CueWaitTime.Mode.Pre) + self.waitTime.notify.connect(self._updateTime, Connection.QtQueued) - def _update_duration(self, duration): + def _updateDuration(self, duration): # The wait time is in seconds, we need milliseconds - super()._update_duration(duration * 1000) + super()._updateDuration(duration * 1000) def _stop(self): super()._stop() - self._update_duration(self.cue.pre_wait) + self._updateDuration(self.cue.pre_wait) class PostWaitWidget(TimeWidget): def __init__(self, *args): super().__init__(*args) - self.show_zero_duration = True + self.showZeroDuration = True self.cue.changed("next_action").connect( - self._next_action_changed, Connection.QtQueued + self._nextActionChanged, Connection.QtQueued ) - self.wait_time = CueWaitTime(self.cue, mode=CueWaitTime.Mode.Post) - self.cue_time = CueTime(self.cue) + self.waitTime = CueWaitTime(self.cue, mode=CueWaitTime.Mode.Post) + self.cueTime = CueTime(self.cue) - self._next_action_changed(self.cue.next_action) + self._nextActionChanged(self.cue.next_action) - def _update_duration(self, duration): + def _updateDuration(self, duration): if ( self.cue.next_action == CueNextAction.TriggerAfterWait or self.cue.next_action == CueNextAction.SelectAfterWait @@ -298,9 +293,9 @@ def _update_duration(self, duration): # The wait time is in seconds, we need milliseconds duration *= 1000 - super()._update_duration(duration) + super()._updateDuration(duration) - def _next_action_changed(self, next_action): + def _nextActionChanged(self, nextAction): self.cue.postwait_start.disconnect(self._running) self.cue.postwait_stopped.disconnect(self._stop) self.cue.postwait_paused.disconnect(self._pause) @@ -313,15 +308,15 @@ def _next_action_changed(self, next_action): self.cue.error.disconnect(self._stop) self.cue.end.disconnect(self._stop) - self.cue_time.notify.disconnect(self._update_time) - self.wait_time.notify.disconnect(self._update_time) + self.cueTime.notify.disconnect(self._updateTime) + self.waitTime.notify.disconnect(self._updateTime) - self.cue.changed("post_wait").disconnect(self._update_duration) - self.cue.changed("duration").disconnect(self._update_duration) + self.cue.changed("post_wait").disconnect(self._updateDuration) + self.cue.changed("duration").disconnect(self._updateDuration) if ( - next_action == CueNextAction.TriggerAfterEnd - or next_action == CueNextAction.SelectAfterEnd + nextAction == CueNextAction.TriggerAfterEnd + or nextAction == CueNextAction.SelectAfterEnd ): self.cue.interrupted.connect(self._stop, Connection.QtQueued) self.cue.started.connect(self._running, Connection.QtQueued) @@ -330,24 +325,24 @@ def _next_action_changed(self, next_action): self.cue.error.connect(self._stop, Connection.QtQueued) self.cue.end.connect(self._stop, Connection.QtQueued) self.cue.changed("duration").connect( - self._update_duration, Connection.QtQueued + self._updateDuration, Connection.QtQueued ) - self.cue_time.notify.connect(self._update_time, Connection.QtQueued) - self._update_duration(self.cue.duration) + self.cueTime.notify.connect(self._updateTime, Connection.QtQueued) + self._updateDuration(self.cue.duration) else: self.cue.postwait_start.connect(self._running, Connection.QtQueued) self.cue.postwait_stopped.connect(self._stop, Connection.QtQueued) self.cue.postwait_paused.connect(self._pause, Connection.QtQueued) self.cue.postwait_ended.connect(self._stop, Connection.QtQueued) self.cue.changed("post_wait").connect( - self._update_duration, Connection.QtQueued + self._updateDuration, Connection.QtQueued ) - self.wait_time.notify.connect( - self._update_time, Connection.QtQueued + self.waitTime.notify.connect( + self._updateTime, Connection.QtQueued ) - self._update_duration(self.cue.post_wait) + self._updateDuration(self.cue.post_wait) def _stop(self): super()._stop() @@ -356,6 +351,6 @@ def _stop(self): self.cue.next_action == CueNextAction.TriggerAfterEnd or self.cue.next_action == CueNextAction.SelectAfterEnd ): - self._update_duration(self.cue.duration) + self._updateDuration(self.cue.duration) else: - self._update_duration(self.cue.post_wait) + self._updateDuration(self.cue.post_wait) diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index f165a2eec..e4f62201a 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -15,7 +15,10 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +from enum import Enum + from PyQt5.QtCore import Qt, QEvent, QSize +from PyQt5.QtGui import QKeySequence from PyQt5.QtWidgets import ( QStyledItemDelegate, QComboBox, @@ -31,7 +34,7 @@ from lisp.cues.cue import CueAction from lisp.ui.qmodels import CueClassRole from lisp.ui.ui_utils import translate -from lisp.ui.widgets import CueActionComboBox +from lisp.ui.widgets import CueActionComboBox, HotKeyEdit from lisp.ui.widgets.cue_actions import tr_action from lisp.ui.widgets.qenumcombobox import QEnumComboBox @@ -205,6 +208,53 @@ def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) +class HotKeyEditDelegate(LabelDelegate): + class Mode(Enum): + KeySequence = 0 + NativeText = 1 + PortableText = 2 + + def __init__(self, mode=Mode.PortableText, **kwargs): + super().__init__(**kwargs) + self.mode = mode + + def paint(self, painter, option, index): + option.displayAlignment = Qt.AlignHCenter | Qt.AlignVCenter + super().paint(painter, option, index) + + def createEditor(self, parent, option, index): + return HotKeyEdit(sequence=self._sequence(index), parent=parent) + + def setEditorData(self, editor: HotKeyEdit, index): + editor.setKeySequence(self._sequence(index)) + + def setModelData(self, editor: HotKeyEdit, model, index): + sequence = editor.keySequence() + if self.mode == HotKeyEditDelegate.Mode.NativeText: + data = sequence.toString(QKeySequence.NativeText) + elif self.mode == HotKeyEditDelegate.Mode.PortableText: + data = sequence.toString(QKeySequence.PortableText) + else: + data = sequence + + model.setData(index, data, Qt.EditRole) + + def updateEditorGeometry(self, editor, option, index): + editor.setGeometry(option.rect) + + def _text(self, painter, option, index): + return self._sequence(index).toString(QKeySequence.NativeText) + + def _sequence(self, index): + data = index.data(Qt.EditRole) + if self.mode == HotKeyEditDelegate.Mode.NativeText: + return QKeySequence(data, QKeySequence.NativeText) + elif self.mode == HotKeyEditDelegate.Mode.PortableText: + return QKeySequence(data, QKeySequence.PortableText) + + return data + + class EnumComboBoxDelegate(LabelDelegate): Mode = QEnumComboBox.Mode diff --git a/lisp/ui/widgets/__init__.py b/lisp/ui/widgets/__init__.py index 34d65c5f4..07b5a7a73 100644 --- a/lisp/ui/widgets/__init__.py +++ b/lisp/ui/widgets/__init__.py @@ -4,7 +4,7 @@ from .dbmeter import DBMeter from .digitalclock import DigitalLabelClock from .fades import FadeComboBox, FadeEdit -from .keysequenceedit import KeySequenceEdit +from .hotkeyedit import HotKeyEdit from .locales import LocaleComboBox from .qclicklabel import QClickLabel from .qclickslider import QClickSlider diff --git a/lisp/ui/widgets/keysequenceedit.py b/lisp/ui/widgets/hotkeyedit.py similarity index 83% rename from lisp/ui/widgets/keysequenceedit.py rename to lisp/ui/widgets/hotkeyedit.py index 83c7cbcee..5eba0f85a 100644 --- a/lisp/ui/widgets/keysequenceedit.py +++ b/lisp/ui/widgets/hotkeyedit.py @@ -17,7 +17,13 @@ from PyQt5.QtCore import Qt, QEvent from PyQt5.QtGui import QKeySequence -from PyQt5.QtWidgets import QWidget, QLineEdit, QSizePolicy, QHBoxLayout, QPushButton +from PyQt5.QtWidgets import ( + QWidget, + QLineEdit, + QSizePolicy, + QHBoxLayout, + QPushButton, +) from lisp.ui.icons import IconTheme from lisp.ui.ui_utils import translate @@ -32,7 +38,7 @@ } -def keyEventKeySequence(keyEvent): +def keyEventKeySequence(keyEvent) -> QKeySequence: key = keyEvent.key() if key not in KEYS_FILTER: modifiers = keyEvent.modifiers() @@ -50,8 +56,10 @@ def keyEventKeySequence(keyEvent): return QKeySequence() -class KeySequenceEdit(QWidget): - def __init__(self, parent, **kwargs): +class HotKeyEdit(QWidget): + def __init__( + self, sequence=QKeySequence(), clearButton=True, parent=None, **kwargs + ): super().__init__(parent, **kwargs) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.setFocusPolicy(Qt.StrongFocus) @@ -60,18 +68,19 @@ def __init__(self, parent, **kwargs): self.setLayout(QHBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) + self._keySequence = sequence + self.previewEdit = QLineEdit(self) self.previewEdit.setReadOnly(True) self.previewEdit.setFocusProxy(self) self.previewEdit.installEventFilter(self) self.layout().addWidget(self.previewEdit) - self.clearButton = QPushButton(self) - self.clearButton.setIcon(IconTheme.get("edit-clear")) - self.clearButton.clicked.connect(self.clear) - self.layout().addWidget(self.clearButton) - - self._keySequence = QKeySequence() + if clearButton: + self.clearButton = QPushButton(self) + self.clearButton.setIcon(IconTheme.get("edit-clear")) + self.clearButton.clicked.connect(self.clear) + self.layout().addWidget(self.clearButton) self.retranslateUi() From b04f22f9b0510c83977ace6b700d9c928097e3da Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 7 May 2019 01:12:55 +0200 Subject: [PATCH 182/333] UI Improvements Update: improved the HotKeyEdit widget, better clean button and new special-key selector Update: icon themes are now supposed to ship symbolic icons Update: updated Numix icons Update: removed Paipirus icons --- lisp/plugins/gst_backend/gst_pipe_edit.py | 4 +- .../actions/address-book-new-symbolic.svg | 6 + .../icons/Numix/actions/address-book-new.svg | 5 + .../actions/application-exit-symbolic.svg | 5 + .../icons/Numix/actions/application-exit.svg | 9 + .../actions/appointment-new-symbolic.svg | 8 + .../icons/Numix/actions/appointment-new.svg | 6 + .../Numix/actions/call-start-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/call-start.svg | 5 + .../Numix/actions/call-stop-symbolic.svg | 3 + lisp/ui/icons/Numix/actions/call-stop.svg | 4 + .../Numix/actions/contact-new-symbolic.svg | 3 + lisp/ui/icons/Numix/actions/contact-new.svg | 4 + lisp/ui/icons/Numix/actions/dialog-apply.svg | 5 + lisp/ui/icons/Numix/actions/dialog-cancel.svg | 6 + lisp/ui/icons/Numix/actions/dialog-close.svg | 3 + lisp/ui/icons/Numix/actions/dialog-no.svg | 6 + lisp/ui/icons/Numix/actions/dialog-ok.svg | 5 + lisp/ui/icons/Numix/actions/dialog-yes.svg | 5 + .../Numix/actions/document-new-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/document-new.svg | 4 + .../actions/document-open-recent-symbolic.svg | 3 + .../Numix/actions/document-open-recent.svg | 20 ++ .../Numix/actions/document-open-symbolic.svg | 3 + lisp/ui/icons/Numix/actions/document-open.svg | 18 ++ .../actions/document-page-setup-symbolic.svg | 4 + .../Numix/actions/document-page-setup.svg | 9 + .../document-print-preview-symbolic.svg | 9 + .../Numix/actions/document-print-preview.svg | 14 ++ .../Numix/actions/document-print-symbolic.svg | 3 + .../ui/icons/Numix/actions/document-print.svg | 9 + .../actions/document-properties-symbolic.svg | 4 + .../Numix/actions/document-properties.svg | 7 + .../actions/document-revert-symbolic.svg | 6 + .../icons/Numix/actions/document-revert.svg | 4 + .../actions/document-save-as-symbolic.svg | 8 + .../icons/Numix/actions/document-save-as.svg | 10 + .../Numix/actions/document-save-symbolic.svg | 8 + lisp/ui/icons/Numix/actions/document-save.svg | 9 + .../Numix/actions/document-send-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/document-send.svg | 11 ++ .../Numix/actions/edit-clear-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/edit-clear.svg | 4 + .../Numix/actions/edit-copy-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/edit-copy.svg | 11 ++ .../icons/Numix/actions/edit-cut-symbolic.svg | 9 + lisp/ui/icons/Numix/actions/edit-cut.svg | 6 + .../Numix/actions/edit-delete-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/edit-delete.svg | 9 + .../actions/edit-find-replace-symbolic.svg | 10 + .../icons/Numix/actions/edit-find-replace.svg | 14 ++ .../Numix/actions/edit-find-symbolic.svg | 3 + lisp/ui/icons/Numix/actions/edit-find.svg | 7 + .../Numix/actions/edit-paste-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/edit-paste.svg | 9 + .../Numix/actions/edit-redo-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/edit-redo.svg | 3 + .../actions/edit-select-all-symbolic.svg | 5 + .../icons/Numix/actions/edit-select-all.svg | 14 ++ .../Numix/actions/edit-undo-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/edit-undo.svg | 3 + lisp/ui/icons/Numix/actions/folder-new.svg | 11 ++ .../actions/format-indent-less-symbolic.svg | 8 + .../Numix/actions/format-indent-less.svg | 9 + .../actions/format-indent-more-symbolic.svg | 8 + .../Numix/actions/format-indent-more.svg | 9 + .../format-justify-center-symbolic.svg | 7 + .../actions/format-justify-center.svg | 4 +- .../actions/format-justify-fill-symbolic.svg | 7 + .../actions/format-justify-fill.svg | 4 +- .../actions/format-justify-left-symbolic.svg | 7 + .../actions/format-justify-left.svg | 4 +- .../actions/format-justify-right-symbolic.svg | 7 + .../Numix/actions/format-justify-right.svg | 8 + .../actions/format-text-bold-symbolic.svg | 6 + .../icons/Numix/actions/format-text-bold.svg | 3 + .../format-text-direction-ltr-symbolic.svg | 7 + .../actions/format-text-direction-ltr.svg | 10 + .../format-text-direction-rtl-symbolic.svg | 7 + .../actions/format-text-direction-rtl.svg | 10 + .../actions/format-text-italic-symbolic.svg | 5 + .../Numix/actions/format-text-italic.svg | 5 + .../format-text-strikethrough-symbolic.svg | 6 + .../actions/format-text-strikethrough.svg | 7 + .../format-text-underline-symbolic.svg | 6 + .../Numix/actions/format-text-underline.svg | 6 + .../Numix/actions/go-bottom-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/go-bottom.svg | 9 + .../icons/Numix/actions/go-down-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/go-down.svg | 6 + .../icons/Numix/actions/go-first-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/go-first.svg | 11 ++ .../icons/Numix/actions/go-home-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/go-home.svg | 4 + .../icons/Numix/actions/go-jump-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/go-jump.svg | 8 + .../icons/Numix/actions/go-last-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/go-last.svg | 11 ++ .../icons/Numix/actions/go-next-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/go-next.svg | 6 + .../Numix/actions/go-previous-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/go-previous.svg | 6 + .../icons/Numix/actions/go-top-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/go-top.svg | 11 ++ .../ui/icons/Numix/actions/go-up-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/go-up.svg | 6 + .../Numix/actions/help-about-symbolic.svg | 3 + lisp/ui/icons/Numix/actions/help-about.svg | 7 + lisp/ui/icons/Numix/actions/help-contents.svg | 8 + lisp/ui/icons/Numix/actions/help-faq.svg | 11 ++ .../Numix/actions/insert-image-symbolic.svg | 9 + lisp/ui/icons/Numix/actions/insert-image.svg | 7 + .../Numix/actions/insert-link-symbolic.svg | 10 + lisp/ui/icons/Numix/actions/insert-link.svg | 14 ++ .../Numix/actions/insert-object-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/insert-object.svg | 13 ++ .../Numix/actions/insert-text-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/insert-text.svg | 10 + .../icons/Numix/actions/list-add-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/list-add.svg | 3 + .../Numix/actions/list-remove-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/list-remove.svg | 3 + .../Numix/actions/mail-forward-symbolic.svg | 3 + lisp/ui/icons/Numix/actions/mail-forward.svg | 3 + .../actions/mail-mark-important-symbolic.svg | 7 + .../Numix/actions/mail-mark-important.svg | 16 ++ .../ui/icons/Numix/actions/mail-mark-junk.svg | 12 ++ .../icons/Numix/actions/mail-mark-notjunk.svg | 13 ++ .../ui/icons/Numix/actions/mail-mark-read.svg | 14 ++ .../icons/Numix/actions/mail-mark-unread.svg | 12 ++ .../icons/Numix/actions/mail-message-new.svg | 15 ++ .../Numix/actions/mail-reply-all-symbolic.svg | 4 + .../ui/icons/Numix/actions/mail-reply-all.svg | 7 + .../actions/mail-reply-sender-symbolic.svg | 3 + .../icons/Numix/actions/mail-reply-sender.svg | 3 + .../actions/mail-send-receive-symbolic.svg | 8 + .../icons/Numix/actions/mail-send-receive.svg | 6 + .../Numix/actions/mail-send-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/mail-send.svg | 7 + .../Numix/actions/media-eject-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/media-eject.svg | 6 + .../actions/media-playback-pause-symbolic.svg | 4 + .../Numix/actions/media-playback-pause.svg | 6 + .../actions/media-playback-start-symbolic.svg | 5 + .../Numix/actions/media-playback-start.svg | 3 + .../actions/media-playback-stop-symbolic.svg | 3 + .../Numix/actions/media-playback-stop.svg | 3 + .../Numix/actions/media-record-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/media-record.svg | 3 + .../actions/media-seek-backward-symbolic.svg | 4 + .../Numix/actions/media-seek-backward.svg | 7 + .../actions/media-seek-forward-symbolic.svg | 4 + .../Numix/actions/media-seek-forward.svg | 7 + .../actions/media-skip-backward-symbolic.svg | 7 + .../Numix/actions/media-skip-backward.svg | 10 + .../actions/media-skip-forward-symbolic.svg | 5 + .../Numix/actions/media-skip-forward.svg | 10 + .../object-flip-horizontal-symbolic.svg | 9 + .../Numix/actions/object-flip-horizontal.svg | 12 ++ .../actions/object-flip-vertical-symbolic.svg | 7 + .../Numix/actions/object-flip-vertical.svg | 12 ++ .../actions/object-rotate-left-symbolic.svg | 6 + .../Numix/actions/object-rotate-left.svg | 6 + .../actions/object-rotate-right-symbolic.svg | 6 + .../Numix/actions/object-rotate-right.svg | 6 + .../Numix/actions/process-stop-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/process-stop.svg | 7 + .../Numix/actions/system-lock-screen.svg | 6 + .../ui/icons/Numix/actions/system-log-out.svg | 9 + lisp/ui/icons/Numix/actions/system-reboot.svg | 6 + .../Numix/actions/system-run-symbolic.svg | 7 + lisp/ui/icons/Numix/actions/system-run.svg | 13 ++ .../Numix/actions/system-search-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/system-search.svg | 7 + .../actions/system-shutdown-symbolic.svg | 6 + .../icons/Numix/actions/system-shutdown.svg | 9 + .../actions/tools-check-spelling-symbolic.svg | 7 + .../Numix/actions/tools-check-spelling.svg | 10 + .../actions/view-fullscreen-symbolic.svg | 6 + .../icons/Numix/actions/view-fullscreen.svg | 12 ++ .../Numix/actions/view-refresh-symbolic.svg | 6 + lisp/ui/icons/Numix/actions/view-refresh.svg | 9 + .../Numix/actions/view-restore-symbolic.svg | 17 ++ lisp/ui/icons/Numix/actions/view-restore.svg | 8 + .../actions/view-sort-ascending-symbolic.svg | 10 + .../Numix/actions/view-sort-ascending.svg | 10 + .../actions/view-sort-descending-symbolic.svg | 10 + .../Numix/actions/view-sort-descending.svg | 10 + .../Numix/actions/window-close-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/window-close.svg | 3 + lisp/ui/icons/Numix/actions/window-new.svg | 5 + .../Numix/actions/zoom-fit-best-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/zoom-fit-best.svg | 9 + .../icons/Numix/actions/zoom-in-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/zoom-in.svg | 6 + .../Numix/actions/zoom-original-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/zoom-original.svg | 6 + .../icons/Numix/actions/zoom-out-symbolic.svg | 5 + lisp/ui/icons/Numix/actions/zoom-out.svg | 6 + .../categories/applications-accessories.svg | 10 + .../categories/applications-development.svg | 6 + .../applications-engineering-symbolic.svg | 6 + .../categories/applications-engineering.svg | 4 + .../applications-games-symbolic.svg | 3 + .../Numix/categories/applications-games.svg | 3 + .../applications-graphics-symbolic.svg | 7 + .../categories/applications-graphics.svg | 7 + .../applications-internet-symbolic.svg | 11 ++ .../categories/applications-internet.svg | 13 ++ .../applications-multimedia-symbolic.svg | 5 + .../categories/applications-multimedia.svg | 5 + .../Numix/categories/applications-office.svg | 3 + .../Numix/categories/applications-other.svg | 7 + .../applications-science-symbolic.svg | 4 + .../Numix/categories/applications-science.svg | 8 + .../applications-system-symbolic.svg | 12 ++ .../Numix/categories/applications-system.svg | 3 + .../applications-utilities-symbolic.svg | 8 + .../categories/applications-utilities.svg | 8 + .../preferences-desktop-peripherals.svg | 15 ++ .../preferences-desktop-personal.svg | 4 + .../Numix/categories/preferences-desktop.svg | 4 + .../categories/preferences-other-symbolic.svg | 9 + .../Numix/categories/preferences-other.svg | 7 + .../categories/preferences-system-network.svg | 9 + .../preferences-system-symbolic.svg | 8 + .../Numix/categories/preferences-system.svg | 4 + .../Numix/categories/system-help-symbolic.svg | 5 + .../ui/icons/Numix/categories/system-help.svg | 3 + .../Numix/devices/audio-card-symbolic.svg | 6 + lisp/ui/icons/Numix/devices/audio-card.svg | 12 ++ .../audio-input-microphone-symbolic.svg | 5 + .../Numix/devices/audio-input-microphone.svg | 16 ++ .../icons/Numix/devices/battery-symbolic.svg | 3 + lisp/ui/icons/Numix/devices/battery.svg | 13 ++ .../Numix/devices/camera-photo-symbolic.svg | 3 + lisp/ui/icons/Numix/devices/camera-photo.svg | 19 ++ .../Numix/devices/camera-video-symbolic.svg | 7 + .../Numix/devices/camera-web-symbolic.svg | 5 + lisp/ui/icons/Numix/devices/camera-web.svg | 11 ++ .../icons/Numix/devices/computer-symbolic.svg | 3 + lisp/ui/icons/Numix/devices/computer.svg | 5 + .../Numix/devices/drive-harddisk-symbolic.svg | 6 + .../ui/icons/Numix/devices/drive-harddisk.svg | 10 + .../Numix/devices/drive-optical-symbolic.svg | 6 + lisp/ui/icons/Numix/devices/drive-optical.svg | 11 ++ .../Numix/devices/drive-removable-media.svg | 10 + .../Numix/devices/input-gaming-symbolic.svg | 5 + lisp/ui/icons/Numix/devices/input-gaming.svg | 14 ++ .../Numix/devices/input-keyboard-symbolic.svg | 3 + .../ui/icons/Numix/devices/input-keyboard.svg | 10 + .../Numix/devices/input-mouse-symbolic.svg | 5 + lisp/ui/icons/Numix/devices/input-mouse.svg | 9 + .../Numix/devices/input-tablet-symbolic.svg | 4 + lisp/ui/icons/Numix/devices/input-tablet.svg | 11 ++ .../Numix/devices/media-flash-symbolic.svg | 3 + lisp/ui/icons/Numix/devices/media-flash.svg | 11 ++ .../Numix/devices/media-floppy-symbolic.svg | 8 + lisp/ui/icons/Numix/devices/media-floppy.svg | 9 + .../Numix/devices/media-optical-symbolic.svg | 5 + lisp/ui/icons/Numix/devices/media-optical.svg | 10 + .../Numix/devices/media-tape-symbolic.svg | 10 + lisp/ui/icons/Numix/devices/media-tape.svg | 11 ++ .../devices/multimedia-player-symbolic.svg | 6 + .../icons/Numix/devices/multimedia-player.svg | 8 + lisp/ui/icons/Numix/devices/network-wired.svg | 10 + .../icons/Numix/devices/network-wireless.svg | 9 + lisp/ui/icons/Numix/devices/pda-symbolic.svg | 4 + lisp/ui/icons/Numix/devices/pda.svg | 9 + .../ui/icons/Numix/devices/phone-symbolic.svg | 5 + lisp/ui/icons/Numix/devices/phone.svg | 10 + .../icons/Numix/devices/printer-symbolic.svg | 3 + lisp/ui/icons/Numix/devices/printer.svg | 9 + .../icons/Numix/devices/scanner-symbolic.svg | 7 + lisp/ui/icons/Numix/devices/scanner.svg | 11 ++ .../Numix/devices/video-display-symbolic.svg | 4 + lisp/ui/icons/Numix/devices/video-display.svg | 9 + .../Numix/emblems/emblem-default-symbolic.svg | 3 + .../ui/icons/Numix/emblems/emblem-default.svg | 4 + .../emblems/emblem-documents-symbolic.svg | 5 + .../icons/Numix/emblems/emblem-documents.svg | 6 + .../icons/Numix/emblems/emblem-downloads.svg | 7 + .../emblems/emblem-favorite-symbolic.svg | 7 + .../icons/Numix/emblems/emblem-favorite.svg | 4 + .../emblems/emblem-important-symbolic.svg | 3 + .../icons/Numix/emblems/emblem-important.svg | 4 + lisp/ui/icons/Numix/emblems/emblem-mail.svg | 9 + .../Numix/emblems/emblem-photos-symbolic.svg | 3 + lisp/ui/icons/Numix/emblems/emblem-photos.svg | 4 + .../icons/Numix/emblems/emblem-readonly.svg | 6 + .../Numix/emblems/emblem-shared-symbolic.svg | 5 + lisp/ui/icons/Numix/emblems/emblem-shared.svg | 4 + .../Numix/emblems/emblem-symbolic-link.svg | 6 + .../Numix/emblems/emblem-system-symbolic.svg | 7 + lisp/ui/icons/Numix/emblems/emblem-system.svg | 6 + .../icons/Numix/emblems/emblem-unreadable.svg | 4 + lisp/ui/icons/Numix/index.theme | 16 +- lisp/ui/icons/Numix/places/folder-remote.svg | 18 ++ .../ui/icons/Numix/places/folder-symbolic.svg | 4 + lisp/ui/icons/Numix/places/folder.svg | 17 ++ .../Numix/places/network-server-symbolic.svg | 8 + lisp/ui/icons/Numix/places/network-server.svg | 182 ++++++++++++++++++ .../places/network-workgroup-symbolic.svg | 4 + .../icons/Numix/places/network-workgroup.svg | 18 ++ .../Numix/places/start-here-symbolic.svg | 7 + lisp/ui/icons/Numix/places/start-here.svg | 6 + .../Numix/places/user-bookmarks-symbolic.svg | 5 + lisp/ui/icons/Numix/places/user-bookmarks.svg | 6 + .../Numix/places/user-desktop-symbolic.svg | 5 + lisp/ui/icons/Numix/places/user-desktop.svg | 22 +++ .../icons/Numix/places/user-home-symbolic.svg | 5 + lisp/ui/icons/Numix/places/user-home.svg | 22 +++ .../Numix/places/user-trash-symbolic.svg | 5 + lisp/ui/icons/Numix/places/user-trash.svg | 158 +++++++++++++++ .../standard/actions/address-book-new.svg | 1 - .../standard/actions/application-exit.svg | 1 - .../standard/actions/appointment-new.svg | 1 - .../Numix/standard/actions/call-start.svg | 1 - .../Numix/standard/actions/call-stop.svg | 1 - .../Numix/standard/actions/contact-new.svg | 1 - .../Numix/standard/actions/document-new.svg | 4 - .../standard/actions/document-open-recent.svg | 1 - .../Numix/standard/actions/document-open.svg | 1 - .../standard/actions/document-page-setup.svg | 1 - .../actions/document-print-preview.svg | 1 - .../Numix/standard/actions/document-print.svg | 1 - .../standard/actions/document-properties.svg | 1 - .../standard/actions/document-revert.svg | 1 - .../standard/actions/document-save-as.svg | 1 - .../Numix/standard/actions/document-save.svg | 1 - .../Numix/standard/actions/document-send.svg | 1 - .../Numix/standard/actions/edit-clear.svg | 4 - .../Numix/standard/actions/edit-copy.svg | 11 -- .../icons/Numix/standard/actions/edit-cut.svg | 1 - .../Numix/standard/actions/edit-delete.svg | 9 - .../standard/actions/edit-find-replace.svg | 1 - .../Numix/standard/actions/edit-find.svg | 1 - .../Numix/standard/actions/edit-paste.svg | 1 - .../Numix/standard/actions/edit-redo.svg | 1 - .../standard/actions/edit-select-all.svg | 12 -- .../Numix/standard/actions/edit-undo.svg | 1 - .../Numix/standard/actions/folder-new.svg | 1 - .../standard/actions/format-indent-less.svg | 9 - .../standard/actions/format-indent-more.svg | 9 - .../standard/actions/format-justify-right.svg | 8 - .../standard/actions/format-text-bold.svg | 1 - .../actions/format-text-direction-ltr.svg | 1 - .../actions/format-text-direction-rtl.svg | 1 - .../standard/actions/format-text-italic.svg | 1 - .../actions/format-text-strikethrough.svg | 1 - .../actions/format-text-underline.svg | 1 - .../Numix/standard/actions/go-bottom.svg | 1 - .../icons/Numix/standard/actions/go-down.svg | 1 - .../icons/Numix/standard/actions/go-first.svg | 1 - .../icons/Numix/standard/actions/go-home.svg | 1 - .../icons/Numix/standard/actions/go-jump.svg | 1 - .../icons/Numix/standard/actions/go-last.svg | 1 - .../icons/Numix/standard/actions/go-next.svg | 1 - .../Numix/standard/actions/go-previous.svg | 1 - .../icons/Numix/standard/actions/go-top.svg | 1 - .../ui/icons/Numix/standard/actions/go-up.svg | 1 - .../Numix/standard/actions/help-about.svg | 1 - .../Numix/standard/actions/help-contents.svg | 1 - .../icons/Numix/standard/actions/help-faq.svg | 1 - .../Numix/standard/actions/insert-image.svg | 1 - .../Numix/standard/actions/insert-link.svg | 1 - .../Numix/standard/actions/insert-object.svg | 1 - .../Numix/standard/actions/insert-text.svg | 1 - .../icons/Numix/standard/actions/list-add.svg | 1 - .../Numix/standard/actions/list-remove.svg | 1 - .../Numix/standard/actions/mail-forward.svg | 1 - .../standard/actions/mail-mark-important.svg | 1 - .../Numix/standard/actions/mail-mark-junk.svg | 1 - .../standard/actions/mail-mark-notjunk.svg | 1 - .../Numix/standard/actions/mail-mark-read.svg | 1 - .../standard/actions/mail-mark-unread.svg | 1 - .../standard/actions/mail-message-new.svg | 1 - .../Numix/standard/actions/mail-reply-all.svg | 1 - .../standard/actions/mail-reply-sender.svg | 1 - .../standard/actions/mail-send-receive.svg | 1 - .../Numix/standard/actions/mail-send.svg | 6 - .../Numix/standard/actions/media-eject.svg | 1 - .../standard/actions/media-playback-pause.svg | 1 - .../standard/actions/media-playback-start.svg | 1 - .../standard/actions/media-playback-stop.svg | 1 - .../Numix/standard/actions/media-record.svg | 1 - .../standard/actions/media-seek-backward.svg | 1 - .../standard/actions/media-seek-forward.svg | 1 - .../standard/actions/media-skip-backward.svg | 1 - .../standard/actions/media-skip-forward.svg | 1 - .../actions/object-flip-horizontal.svg | 1 - .../standard/actions/object-flip-vertical.svg | 1 - .../standard/actions/object-rotate-left.svg | 3 - .../standard/actions/object-rotate-right.svg | 1 - .../Numix/standard/actions/process-stop.svg | 1 - .../standard/actions/system-lock-screen.svg | 1 - .../Numix/standard/actions/system-log-out.svg | 1 - .../Numix/standard/actions/system-reboot.svg | 3 - .../Numix/standard/actions/system-run.svg | 1 - .../Numix/standard/actions/system-search.svg | 1 - .../standard/actions/system-shutdown.svg | 1 - .../standard/actions/tools-check-spelling.svg | 1 - .../standard/actions/view-fullscreen.svg | 1 - .../Numix/standard/actions/view-refresh.svg | 7 - .../Numix/standard/actions/view-restore.svg | 1 - .../standard/actions/view-sort-ascending.svg | 1 - .../standard/actions/view-sort-descending.svg | 1 - .../Numix/standard/actions/window-close.svg | 1 - .../Numix/standard/actions/window-new.svg | 1 - .../Numix/standard/actions/zoom-fit-best.svg | 1 - .../icons/Numix/standard/actions/zoom-in.svg | 1 - .../Numix/standard/actions/zoom-original.svg | 1 - .../icons/Numix/standard/actions/zoom-out.svg | 1 - .../categories/applications-accessories.svg | 1 - .../categories/applications-development.svg | 1 - .../categories/applications-engineering.svg | 1 - .../categories/applications-games.svg | 1 - .../categories/applications-graphics.svg | 1 - .../categories/applications-internet.svg | 1 - .../categories/applications-multimedia.svg | 1 - .../categories/applications-office.svg | 1 - .../categories/applications-other.svg | 1 - .../categories/applications-science.svg | 1 - .../categories/applications-system.svg | 1 - .../categories/applications-utilities.svg | 1 - .../preferences-desktop-peripherals.svg | 1 - .../preferences-desktop-personal.svg | 1 - .../categories/preferences-desktop.svg | 1 - .../standard/categories/preferences-other.svg | 1 - .../categories/preferences-system-network.svg | 1 - .../categories/preferences-system.svg | 1 - .../Numix/standard/categories/system-help.svg | 1 - .../Numix/standard/devices/audio-card.svg | 1 - .../devices/audio-input-microphone.svg | 1 - .../icons/Numix/standard/devices/battery.svg | 1 - .../Numix/standard/devices/camera-photo.svg | 1 - .../Numix/standard/devices/camera-web.svg | 1 - .../icons/Numix/standard/devices/computer.svg | 1 - .../Numix/standard/devices/drive-harddisk.svg | 1 - .../Numix/standard/devices/drive-optical.svg | 1 - .../devices/drive-removable-media.svg | 1 - .../Numix/standard/devices/input-gaming.svg | 1 - .../Numix/standard/devices/input-keyboard.svg | 1 - .../Numix/standard/devices/input-mouse.svg | 1 - .../Numix/standard/devices/input-tablet.svg | 1 - .../Numix/standard/devices/media-flash.svg | 1 - .../Numix/standard/devices/media-floppy.svg | 1 - .../Numix/standard/devices/media-optical.svg | 1 - .../Numix/standard/devices/media-tape.svg | 1 - .../standard/devices/multimedia-player.svg | 1 - .../Numix/standard/devices/network-wired.svg | 1 - .../standard/devices/network-wireless.svg | 1 - lisp/ui/icons/Numix/standard/devices/pda.svg | 1 - .../ui/icons/Numix/standard/devices/phone.svg | 1 - .../icons/Numix/standard/devices/printer.svg | 1 - .../icons/Numix/standard/devices/scanner.svg | 1 - .../Numix/standard/devices/video-display.svg | 1 - .../Numix/standard/emblems/emblem-default.svg | 1 - .../standard/emblems/emblem-documents.svg | 1 - .../standard/emblems/emblem-downloads.svg | 1 - .../standard/emblems/emblem-favorite.svg | 1 - .../standard/emblems/emblem-important.svg | 1 - .../Numix/standard/emblems/emblem-mail.svg | 1 - .../Numix/standard/emblems/emblem-photos.svg | 1 - .../standard/emblems/emblem-readonly.svg | 1 - .../Numix/standard/emblems/emblem-shared.svg | 1 - .../standard/emblems/emblem-symbolic-link.svg | 1 - .../Numix/standard/emblems/emblem-system.svg | 1 - .../standard/emblems/emblem-unreadable.svg | 1 - .../Numix/standard/places/folder-remote.svg | 1 - .../ui/icons/Numix/standard/places/folder.svg | 1 - .../Numix/standard/places/network-server.svg | 1 - .../standard/places/network-workgroup.svg | 1 - .../Numix/standard/places/start-here.svg | 1 - .../Numix/standard/places/user-bookmarks.svg | 1 - .../Numix/standard/places/user-desktop.svg | 1 - .../icons/Numix/standard/places/user-home.svg | 1 - .../Numix/standard/places/user-trash.svg | 1 - .../standard/status/appointment-missed.svg | 1 - .../standard/status/appointment-soon.svg | 1 - .../standard/status/audio-volume-high.svg | 1 - .../standard/status/audio-volume-low.svg | 1 - .../standard/status/audio-volume-medium.svg | 1 - .../standard/status/audio-volume-muted.svg | 1 - .../Numix/standard/status/battery-caution.svg | 1 - .../Numix/standard/status/battery-low.svg | 1 - .../Numix/standard/status/dialog-error.svg | 1 - .../standard/status/dialog-information.svg | 3 - .../Numix/standard/status/dialog-password.svg | 1 - .../Numix/standard/status/dialog-question.svg | 3 - .../Numix/standard/status/dialog-warning.svg | 1 - .../Numix/standard/status/image-missing.svg | 4 - .../Numix/standard/status/network-error.svg | 1 - .../Numix/standard/status/network-idle.svg | 1 - .../Numix/standard/status/network-offline.svg | 1 - .../Numix/standard/status/network-receive.svg | 1 - .../status/network-transmit-receive.svg | 1 - .../standard/status/network-transmit.svg | 1 - .../standard/status/printer-printing.svg | 1 - .../Numix/standard/status/security-high.svg | 1 - .../Numix/standard/status/security-low.svg | 1 - .../Numix/standard/status/security-medium.svg | 1 - .../icons/Numix/standard/status/task-due.svg | 1 - .../Numix/standard/status/task-past-due.svg | 1 - .../Numix/standard/status/user-available.svg | 1 - .../icons/Numix/standard/status/user-away.svg | 1 - .../icons/Numix/standard/status/user-idle.svg | 1 - .../Numix/standard/status/user-offline.svg | 1 - .../status/appointment-missed-symbolic.svg | 7 + .../icons/Numix/status/appointment-missed.svg | 10 + .../status/appointment-soon-symbolic.svg | 6 + .../icons/Numix/status/appointment-soon.svg | 8 + .../status/audio-volume-high-symbolic.svg | 7 + .../icons/Numix/status/audio-volume-high.svg | 9 + .../status/audio-volume-low-symbolic.svg | 7 + .../icons/Numix/status/audio-volume-low.svg | 7 + .../status/audio-volume-medium-symbolic.svg | 7 + .../Numix/status/audio-volume-medium.svg | 7 + .../status/audio-volume-muted-symbolic.svg | 5 + .../icons/Numix/status/audio-volume-muted.svg | 7 + .../Numix/status/battery-caution-symbolic.svg | 4 + .../ui/icons/Numix/status/battery-caution.svg | 4 + .../Numix/status/battery-low-symbolic.svg | 4 + lisp/ui/icons/Numix/status/battery-low.svg | 4 + .../Numix/status/dialog-error-symbolic.svg | 3 + lisp/ui/icons/Numix/status/dialog-error.svg | 3 + .../status/dialog-information-symbolic.svg | 3 + .../icons/Numix/status/dialog-information.svg | 3 + .../Numix/status/dialog-password-symbolic.svg | 3 + .../ui/icons/Numix/status/dialog-password.svg | 7 + .../Numix/status/dialog-question-symbolic.svg | 3 + .../ui/icons/Numix/status/dialog-question.svg | 3 + .../Numix/status/dialog-warning-symbolic.svg | 3 + lisp/ui/icons/Numix/status/dialog-warning.svg | 3 + .../icons/Numix/status/folder-drag-accept.svg | 15 ++ lisp/ui/icons/Numix/status/folder-open.svg | 15 ++ .../ui/icons/Numix/status/folder-visiting.svg | 15 ++ lisp/ui/icons/Numix/status/image-loading.svg | 59 ++++++ lisp/ui/icons/Numix/status/image-missing.svg | 4 + .../Numix/status/mail-attachment-symbolic.svg | 5 + .../ui/icons/Numix/status/mail-attachment.svg | 3 + .../icons/Numix/status/mail-read-symbolic.svg | 7 + lisp/ui/icons/Numix/status/mail-read.svg | 14 ++ .../Numix/status/mail-replied-symbolic.svg | 3 + lisp/ui/icons/Numix/status/mail-replied.svg | 15 ++ .../Numix/status/mail-unread-symbolic.svg | 5 + lisp/ui/icons/Numix/status/mail-unread.svg | 12 ++ .../status/media-playlist-repeat-symbolic.svg | 3 + .../Numix/status/media-playlist-repeat.svg | 3 + .../media-playlist-shuffle-symbolic.svg | 4 + .../Numix/status/media-playlist-shuffle.svg | 7 + .../Numix/status/network-error-symbolic.svg | 3 + lisp/ui/icons/Numix/status/network-error.svg | 4 + .../Numix/status/network-idle-symbolic.svg | 3 + lisp/ui/icons/Numix/status/network-idle.svg | 4 + .../Numix/status/network-offline-symbolic.svg | 4 + .../ui/icons/Numix/status/network-offline.svg | 4 + .../Numix/status/network-receive-symbolic.svg | 4 + .../ui/icons/Numix/status/network-receive.svg | 4 + .../network-transmit-receive-symbolic.svg | 4 + .../Numix/status/network-transmit-receive.svg | 4 + .../status/network-transmit-symbolic.svg | 4 + .../icons/Numix/status/network-transmit.svg | 4 + .../Numix/status/printer-error-symbolic.svg | 4 + lisp/ui/icons/Numix/status/printer-error.svg | 14 ++ .../status/printer-printing-symbolic.svg | 3 + .../icons/Numix/status/printer-printing.svg | 11 ++ .../Numix/status/security-high-symbolic.svg | 7 + lisp/ui/icons/Numix/status/security-high.svg | 5 + .../Numix/status/security-low-symbolic.svg | 7 + lisp/ui/icons/Numix/status/security-low.svg | 5 + .../Numix/status/security-medium-symbolic.svg | 5 + .../ui/icons/Numix/status/security-medium.svg | 5 + .../software-update-available-symbolic.svg | 5 + .../software-update-urgent-symbolic.svg | 5 + .../icons/Numix/status/task-due-symbolic.svg | 6 + lisp/ui/icons/Numix/status/task-due.svg | 7 + .../Numix/status/task-past-due-symbolic.svg | 4 + lisp/ui/icons/Numix/status/task-past-due.svg | 5 + .../Numix/status/user-available-symbolic.svg | 5 + lisp/ui/icons/Numix/status/user-available.svg | 6 + .../icons/Numix/status/user-away-symbolic.svg | 5 + lisp/ui/icons/Numix/status/user-away.svg | 6 + .../icons/Numix/status/user-idle-symbolic.svg | 5 + lisp/ui/icons/Numix/status/user-idle.svg | 6 + .../Numix/status/user-offline-symbolic.svg | 5 + lisp/ui/icons/Numix/status/user-offline.svg | 6 + .../ui/icons/Numix/status/user-trash-full.svg | 158 +++++++++++++++ .../Papirus Symbolic/custom/cue-interrupt.svg | 1 - .../Papirus Symbolic/custom/cue-pause.svg | 1 - .../custom/cue-select-next.svg | 69 ------- .../Papirus Symbolic/custom/cue-start.svg | 1 - .../Papirus Symbolic/custom/cue-stop.svg | 1 - .../custom/cue-trigger-next.svg | 59 ------ .../custom/fadein-generic.svg | 1 - .../custom/fadeout-generic.svg | 1 - lisp/ui/icons/Papirus Symbolic/index.theme | 47 ----- .../standard/actions/address-book-new.svg | 3 - .../standard/actions/application-exit.svg | 1 - .../standard/actions/appointment-new.svg | 1 - .../standard/actions/call-start.svg | 1 - .../standard/actions/call-stop.svg | 1 - .../standard/actions/contact-new.svg | 1 - .../standard/actions/document-new.svg | 1 - .../standard/actions/document-open-recent.svg | 1 - .../standard/actions/document-open.svg | 1 - .../standard/actions/document-page-setup.svg | 1 - .../actions/document-print-preview.svg | 1 - .../standard/actions/document-print.svg | 1 - .../standard/actions/document-properties.svg | 1 - .../standard/actions/document-revert.svg | 1 - .../standard/actions/document-save-as.svg | 1 - .../standard/actions/document-save.svg | 1 - .../standard/actions/document-send.svg | 1 - .../standard/actions/edit-clear.svg | 1 - .../standard/actions/edit-copy.svg | 1 - .../standard/actions/edit-cut.svg | 3 - .../standard/actions/edit-delete.svg | 1 - .../standard/actions/edit-find-replace.svg | 1 - .../standard/actions/edit-find.svg | 1 - .../standard/actions/edit-paste.svg | 1 - .../standard/actions/edit-redo.svg | 1 - .../standard/actions/edit-select-all.svg | 1 - .../standard/actions/edit-undo.svg | 1 - .../standard/actions/folder-new.svg | 1 - .../standard/actions/format-indent-less.svg | 1 - .../standard/actions/format-indent-more.svg | 1 - .../actions/format-justify-center.svg | 1 - .../standard/actions/format-justify-fill.svg | 1 - .../standard/actions/format-justify-left.svg | 1 - .../standard/actions/format-justify-right.svg | 1 - .../standard/actions/format-text-bold.svg | 1 - .../actions/format-text-direction-ltr.svg | 1 - .../actions/format-text-direction-rtl.svg | 1 - .../standard/actions/format-text-italic.svg | 1 - .../actions/format-text-strikethrough.svg | 1 - .../actions/format-text-underline.svg | 1 - .../standard/actions/go-bottom.svg | 1 - .../standard/actions/go-down.svg | 1 - .../standard/actions/go-first.svg | 1 - .../standard/actions/go-home.svg | 1 - .../standard/actions/go-jump.svg | 1 - .../standard/actions/go-last.svg | 1 - .../standard/actions/go-next.svg | 1 - .../standard/actions/go-previous.svg | 1 - .../standard/actions/go-top.svg | 1 - .../standard/actions/go-up.svg | 1 - .../standard/actions/help-about.svg | 1 - .../standard/actions/insert-image.svg | 1 - .../standard/actions/insert-link.svg | 1 - .../standard/actions/insert-object.svg | 1 - .../standard/actions/insert-text.svg | 1 - .../standard/actions/list-add.svg | 1 - .../standard/actions/list-remove.svg | 1 - .../standard/actions/mail-forward.svg | 1 - .../standard/actions/mail-mark-important.svg | 1 - .../standard/actions/mail-mark-junk.svg | 1 - .../standard/actions/mail-mark-notjunk.svg | 1 - .../standard/actions/mail-mark-read.svg | 1 - .../standard/actions/mail-mark-unread.svg | 1 - .../standard/actions/mail-message-new.svg | 1 - .../standard/actions/mail-reply-all.svg | 1 - .../standard/actions/mail-reply-sender.svg | 1 - .../standard/actions/mail-send-receive.svg | 1 - .../standard/actions/mail-send.svg | 1 - .../standard/actions/media-eject.svg | 1 - .../standard/actions/media-playback-pause.svg | 1 - .../standard/actions/media-playback-start.svg | 1 - .../standard/actions/media-playback-stop.svg | 1 - .../standard/actions/media-record.svg | 1 - .../standard/actions/media-seek-backward.svg | 1 - .../standard/actions/media-seek-forward.svg | 1 - .../standard/actions/media-skip-backward.svg | 1 - .../standard/actions/media-skip-forward.svg | 1 - .../actions/object-flip-horizontal.svg | 1 - .../standard/actions/object-flip-vertical.svg | 1 - .../standard/actions/object-rotate-left.svg | 1 - .../standard/actions/object-rotate-right.svg | 1 - .../standard/actions/process-stop.svg | 1 - .../standard/actions/system-lock-screen.svg | 1 - .../standard/actions/system-log-out.svg | 1 - .../standard/actions/system-run.svg | 4 - .../standard/actions/system-search.svg | 1 - .../standard/actions/system-shutdown.svg | 1 - .../standard/actions/tools-check-spelling.svg | 1 - .../standard/actions/view-fullscreen.svg | 1 - .../standard/actions/view-refresh.svg | 1 - .../standard/actions/view-restore.svg | 1 - .../standard/actions/view-sort-ascending.svg | 1 - .../standard/actions/view-sort-descending.svg | 1 - .../standard/actions/window-close.svg | 1 - .../standard/actions/zoom-fit-best.svg | 1 - .../standard/actions/zoom-in.svg | 1 - .../standard/actions/zoom-original.svg | 1 - .../standard/actions/zoom-out.svg | 1 - .../categories/applications-engineering.svg | 1 - .../categories/applications-games.svg | 3 - .../categories/applications-graphics.svg | 1 - .../categories/applications-multimedia.svg | 1 - .../categories/applications-science.svg | 1 - .../categories/applications-system.svg | 3 - .../categories/applications-utilities.svg | 1 - .../standard/categories/preferences-other.svg | 1 - .../categories/preferences-system.svg | 3 - .../standard/categories/system-help.svg | 1 - .../standard/devices/audio-card.svg | 1 - .../devices/audio-input-microphone.svg | 1 - .../standard/devices/battery.svg | 1 - .../standard/devices/camera-photo.svg | 1 - .../standard/devices/camera-video.svg | 1 - .../standard/devices/camera-web.svg | 1 - .../standard/devices/computer.svg | 1 - .../standard/devices/drive-harddisk.svg | 1 - .../standard/devices/drive-optical.svg | 1 - .../devices/drive-removable-media.svg | 1 - .../standard/devices/input-gaming.svg | 1 - .../standard/devices/input-keyboard.svg | 1 - .../standard/devices/input-mouse.svg | 1 - .../standard/devices/input-tablet.svg | 1 - .../standard/devices/media-flash.svg | 1 - .../standard/devices/media-floppy.svg | 1 - .../standard/devices/media-tape.svg | 1 - .../standard/devices/multimedia-player.svg | 1 - .../standard/devices/network-wired.svg | 1 - .../standard/devices/network-wireless.svg | 1 - .../standard/devices/phone.svg | 1 - .../standard/devices/printer.svg | 1 - .../standard/devices/scanner.svg | 1 - .../standard/devices/video-display.svg | 1 - .../standard/emblems/emblem-default.svg | 1 - .../standard/emblems/emblem-documents.svg | 1 - .../standard/emblems/emblem-favorite.svg | 1 - .../standard/emblems/emblem-important.svg | 1 - .../standard/emblems/emblem-photos.svg | 1 - .../standard/emblems/emblem-shared.svg | 1 - .../standard/emblems/emblem-system.svg | 3 - .../standard/places/folder-remote.svg | 1 - .../standard/places/folder.svg | 1 - .../standard/places/network-workgroup.svg | 3 - .../standard/places/start-here.svg | 1 - .../standard/places/user-bookmarks.svg | 1 - .../standard/places/user-desktop.svg | 1 - .../standard/places/user-home.svg | 1 - .../standard/places/user-trash.svg | 1 - .../standard/status/appointment-missed.svg | 1 - .../standard/status/appointment-soon.svg | 1 - .../standard/status/audio-volume-high.svg | 1 - .../standard/status/audio-volume-low.svg | 1 - .../standard/status/audio-volume-medium.svg | 1 - .../standard/status/audio-volume-muted.svg | 1 - .../standard/status/battery-caution.svg | 1 - .../standard/status/battery-low.svg | 1 - .../standard/status/dialog-error.svg | 1 - .../standard/status/dialog-information.svg | 1 - .../standard/status/dialog-password.svg | 11 -- .../standard/status/dialog-question.svg | 8 - .../standard/status/dialog-warning.svg | 1 - .../standard/status/folder-drag-accept.svg | 1 - .../standard/status/folder-open.svg | 1 - .../standard/status/folder-visiting.svg | 1 - .../standard/status/image-loading.svg | 1 - .../standard/status/mail-attachment.svg | 1 - .../standard/status/mail-read.svg | 1 - .../standard/status/mail-replied.svg | 1 - .../standard/status/mail-unread.svg | 1 - .../standard/status/media-playlist-repeat.svg | 1 - .../status/media-playlist-shuffle.svg | 1 - .../standard/status/network-error.svg | 1 - .../standard/status/network-idle.svg | 1 - .../standard/status/network-offline.svg | 1 - .../standard/status/network-receive.svg | 1 - .../status/network-transmit-receive.svg | 1 - .../standard/status/network-transmit.svg | 1 - .../standard/status/printer-error.svg | 1 - .../standard/status/printer-printing.svg | 1 - .../standard/status/security-high.svg | 1 - .../standard/status/security-low.svg | 1 - .../standard/status/security-medium.svg | 1 - .../status/software-update-available.svg | 1 - .../status/software-update-urgent.svg | 1 - .../standard/status/task-due.svg | 1 - .../standard/status/task-past-due.svg | 1 - .../standard/status/user-available.svg | 1 - .../standard/status/user-away.svg | 1 - .../standard/status/user-idle.svg | 1 - .../standard/status/user-offline.svg | 1 - .../standard/status/user-trash-full.svg | 1 - lisp/ui/settings/app_pages/general.py | 2 +- lisp/ui/themes/dark/theme.qss | 8 +- lisp/ui/widgets/hotkeyedit.py | 55 +++++- lisp/ui/widgets/qmutebutton.py | 4 +- 791 files changed, 3295 insertions(+), 708 deletions(-) create mode 100644 lisp/ui/icons/Numix/actions/address-book-new-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/address-book-new.svg create mode 100644 lisp/ui/icons/Numix/actions/application-exit-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/application-exit.svg create mode 100644 lisp/ui/icons/Numix/actions/appointment-new-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/appointment-new.svg create mode 100644 lisp/ui/icons/Numix/actions/call-start-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/call-start.svg create mode 100644 lisp/ui/icons/Numix/actions/call-stop-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/call-stop.svg create mode 100644 lisp/ui/icons/Numix/actions/contact-new-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/contact-new.svg create mode 100644 lisp/ui/icons/Numix/actions/dialog-apply.svg create mode 100644 lisp/ui/icons/Numix/actions/dialog-cancel.svg create mode 100644 lisp/ui/icons/Numix/actions/dialog-close.svg create mode 100644 lisp/ui/icons/Numix/actions/dialog-no.svg create mode 100644 lisp/ui/icons/Numix/actions/dialog-ok.svg create mode 100644 lisp/ui/icons/Numix/actions/dialog-yes.svg create mode 100644 lisp/ui/icons/Numix/actions/document-new-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/document-new.svg create mode 100644 lisp/ui/icons/Numix/actions/document-open-recent-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/document-open-recent.svg create mode 100644 lisp/ui/icons/Numix/actions/document-open-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/document-open.svg create mode 100644 lisp/ui/icons/Numix/actions/document-page-setup-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/document-page-setup.svg create mode 100644 lisp/ui/icons/Numix/actions/document-print-preview-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/document-print-preview.svg create mode 100644 lisp/ui/icons/Numix/actions/document-print-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/document-print.svg create mode 100644 lisp/ui/icons/Numix/actions/document-properties-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/document-properties.svg create mode 100644 lisp/ui/icons/Numix/actions/document-revert-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/document-revert.svg create mode 100644 lisp/ui/icons/Numix/actions/document-save-as-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/document-save-as.svg create mode 100644 lisp/ui/icons/Numix/actions/document-save-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/document-save.svg create mode 100644 lisp/ui/icons/Numix/actions/document-send-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/document-send.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-clear-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-clear.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-copy-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-copy.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-cut-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-cut.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-delete-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-delete.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-find-replace-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-find-replace.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-find-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-find.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-paste-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-paste.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-redo-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-redo.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-select-all-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-select-all.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-undo-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/edit-undo.svg create mode 100644 lisp/ui/icons/Numix/actions/folder-new.svg create mode 100644 lisp/ui/icons/Numix/actions/format-indent-less-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/format-indent-less.svg create mode 100644 lisp/ui/icons/Numix/actions/format-indent-more-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/format-indent-more.svg create mode 100644 lisp/ui/icons/Numix/actions/format-justify-center-symbolic.svg rename lisp/ui/icons/Numix/{standard => }/actions/format-justify-center.svg (53%) create mode 100644 lisp/ui/icons/Numix/actions/format-justify-fill-symbolic.svg rename lisp/ui/icons/Numix/{standard => }/actions/format-justify-fill.svg (53%) create mode 100644 lisp/ui/icons/Numix/actions/format-justify-left-symbolic.svg rename lisp/ui/icons/Numix/{standard => }/actions/format-justify-left.svg (52%) create mode 100644 lisp/ui/icons/Numix/actions/format-justify-right-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/format-justify-right.svg create mode 100644 lisp/ui/icons/Numix/actions/format-text-bold-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/format-text-bold.svg create mode 100644 lisp/ui/icons/Numix/actions/format-text-direction-ltr-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/format-text-direction-ltr.svg create mode 100644 lisp/ui/icons/Numix/actions/format-text-direction-rtl-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/format-text-direction-rtl.svg create mode 100644 lisp/ui/icons/Numix/actions/format-text-italic-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/format-text-italic.svg create mode 100644 lisp/ui/icons/Numix/actions/format-text-strikethrough-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/format-text-strikethrough.svg create mode 100644 lisp/ui/icons/Numix/actions/format-text-underline-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/format-text-underline.svg create mode 100644 lisp/ui/icons/Numix/actions/go-bottom-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/go-bottom.svg create mode 100644 lisp/ui/icons/Numix/actions/go-down-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/go-down.svg create mode 100644 lisp/ui/icons/Numix/actions/go-first-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/go-first.svg create mode 100644 lisp/ui/icons/Numix/actions/go-home-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/go-home.svg create mode 100644 lisp/ui/icons/Numix/actions/go-jump-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/go-jump.svg create mode 100644 lisp/ui/icons/Numix/actions/go-last-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/go-last.svg create mode 100644 lisp/ui/icons/Numix/actions/go-next-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/go-next.svg create mode 100644 lisp/ui/icons/Numix/actions/go-previous-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/go-previous.svg create mode 100644 lisp/ui/icons/Numix/actions/go-top-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/go-top.svg create mode 100644 lisp/ui/icons/Numix/actions/go-up-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/go-up.svg create mode 100644 lisp/ui/icons/Numix/actions/help-about-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/help-about.svg create mode 100644 lisp/ui/icons/Numix/actions/help-contents.svg create mode 100644 lisp/ui/icons/Numix/actions/help-faq.svg create mode 100644 lisp/ui/icons/Numix/actions/insert-image-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/insert-image.svg create mode 100644 lisp/ui/icons/Numix/actions/insert-link-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/insert-link.svg create mode 100644 lisp/ui/icons/Numix/actions/insert-object-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/insert-object.svg create mode 100644 lisp/ui/icons/Numix/actions/insert-text-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/insert-text.svg create mode 100644 lisp/ui/icons/Numix/actions/list-add-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/list-add.svg create mode 100644 lisp/ui/icons/Numix/actions/list-remove-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/list-remove.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-forward-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-forward.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-mark-important-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-mark-important.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-mark-junk.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-mark-notjunk.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-mark-read.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-mark-unread.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-message-new.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-reply-all-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-reply-all.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-reply-sender-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-reply-sender.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-send-receive-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-send-receive.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-send-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/mail-send.svg create mode 100644 lisp/ui/icons/Numix/actions/media-eject-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/media-eject.svg create mode 100644 lisp/ui/icons/Numix/actions/media-playback-pause-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/media-playback-pause.svg create mode 100644 lisp/ui/icons/Numix/actions/media-playback-start-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/media-playback-start.svg create mode 100644 lisp/ui/icons/Numix/actions/media-playback-stop-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/media-playback-stop.svg create mode 100644 lisp/ui/icons/Numix/actions/media-record-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/media-record.svg create mode 100644 lisp/ui/icons/Numix/actions/media-seek-backward-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/media-seek-backward.svg create mode 100644 lisp/ui/icons/Numix/actions/media-seek-forward-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/media-seek-forward.svg create mode 100644 lisp/ui/icons/Numix/actions/media-skip-backward-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/media-skip-backward.svg create mode 100644 lisp/ui/icons/Numix/actions/media-skip-forward-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/media-skip-forward.svg create mode 100644 lisp/ui/icons/Numix/actions/object-flip-horizontal-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/object-flip-horizontal.svg create mode 100644 lisp/ui/icons/Numix/actions/object-flip-vertical-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/object-flip-vertical.svg create mode 100644 lisp/ui/icons/Numix/actions/object-rotate-left-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/object-rotate-left.svg create mode 100644 lisp/ui/icons/Numix/actions/object-rotate-right-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/object-rotate-right.svg create mode 100644 lisp/ui/icons/Numix/actions/process-stop-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/process-stop.svg create mode 100644 lisp/ui/icons/Numix/actions/system-lock-screen.svg create mode 100644 lisp/ui/icons/Numix/actions/system-log-out.svg create mode 100644 lisp/ui/icons/Numix/actions/system-reboot.svg create mode 100644 lisp/ui/icons/Numix/actions/system-run-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/system-run.svg create mode 100644 lisp/ui/icons/Numix/actions/system-search-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/system-search.svg create mode 100644 lisp/ui/icons/Numix/actions/system-shutdown-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/system-shutdown.svg create mode 100644 lisp/ui/icons/Numix/actions/tools-check-spelling-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/tools-check-spelling.svg create mode 100644 lisp/ui/icons/Numix/actions/view-fullscreen-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/view-fullscreen.svg create mode 100644 lisp/ui/icons/Numix/actions/view-refresh-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/view-refresh.svg create mode 100644 lisp/ui/icons/Numix/actions/view-restore-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/view-restore.svg create mode 100644 lisp/ui/icons/Numix/actions/view-sort-ascending-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/view-sort-ascending.svg create mode 100644 lisp/ui/icons/Numix/actions/view-sort-descending-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/view-sort-descending.svg create mode 100644 lisp/ui/icons/Numix/actions/window-close-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/window-close.svg create mode 100644 lisp/ui/icons/Numix/actions/window-new.svg create mode 100644 lisp/ui/icons/Numix/actions/zoom-fit-best-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/zoom-fit-best.svg create mode 100644 lisp/ui/icons/Numix/actions/zoom-in-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/zoom-in.svg create mode 100644 lisp/ui/icons/Numix/actions/zoom-original-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/zoom-original.svg create mode 100644 lisp/ui/icons/Numix/actions/zoom-out-symbolic.svg create mode 100644 lisp/ui/icons/Numix/actions/zoom-out.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-accessories.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-development.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-engineering-symbolic.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-engineering.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-games-symbolic.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-games.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-graphics-symbolic.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-graphics.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-internet-symbolic.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-internet.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-multimedia-symbolic.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-multimedia.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-office.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-other.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-science-symbolic.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-science.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-system-symbolic.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-system.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-utilities-symbolic.svg create mode 100644 lisp/ui/icons/Numix/categories/applications-utilities.svg create mode 100644 lisp/ui/icons/Numix/categories/preferences-desktop-peripherals.svg create mode 100644 lisp/ui/icons/Numix/categories/preferences-desktop-personal.svg create mode 100644 lisp/ui/icons/Numix/categories/preferences-desktop.svg create mode 100644 lisp/ui/icons/Numix/categories/preferences-other-symbolic.svg create mode 100644 lisp/ui/icons/Numix/categories/preferences-other.svg create mode 100644 lisp/ui/icons/Numix/categories/preferences-system-network.svg create mode 100644 lisp/ui/icons/Numix/categories/preferences-system-symbolic.svg create mode 100644 lisp/ui/icons/Numix/categories/preferences-system.svg create mode 100644 lisp/ui/icons/Numix/categories/system-help-symbolic.svg create mode 100644 lisp/ui/icons/Numix/categories/system-help.svg create mode 100644 lisp/ui/icons/Numix/devices/audio-card-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/audio-card.svg create mode 100644 lisp/ui/icons/Numix/devices/audio-input-microphone-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/audio-input-microphone.svg create mode 100644 lisp/ui/icons/Numix/devices/battery-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/battery.svg create mode 100644 lisp/ui/icons/Numix/devices/camera-photo-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/camera-photo.svg create mode 100644 lisp/ui/icons/Numix/devices/camera-video-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/camera-web-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/camera-web.svg create mode 100644 lisp/ui/icons/Numix/devices/computer-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/computer.svg create mode 100644 lisp/ui/icons/Numix/devices/drive-harddisk-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/drive-harddisk.svg create mode 100644 lisp/ui/icons/Numix/devices/drive-optical-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/drive-optical.svg create mode 100644 lisp/ui/icons/Numix/devices/drive-removable-media.svg create mode 100644 lisp/ui/icons/Numix/devices/input-gaming-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/input-gaming.svg create mode 100644 lisp/ui/icons/Numix/devices/input-keyboard-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/input-keyboard.svg create mode 100644 lisp/ui/icons/Numix/devices/input-mouse-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/input-mouse.svg create mode 100644 lisp/ui/icons/Numix/devices/input-tablet-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/input-tablet.svg create mode 100644 lisp/ui/icons/Numix/devices/media-flash-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/media-flash.svg create mode 100644 lisp/ui/icons/Numix/devices/media-floppy-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/media-floppy.svg create mode 100644 lisp/ui/icons/Numix/devices/media-optical-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/media-optical.svg create mode 100644 lisp/ui/icons/Numix/devices/media-tape-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/media-tape.svg create mode 100644 lisp/ui/icons/Numix/devices/multimedia-player-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/multimedia-player.svg create mode 100644 lisp/ui/icons/Numix/devices/network-wired.svg create mode 100644 lisp/ui/icons/Numix/devices/network-wireless.svg create mode 100644 lisp/ui/icons/Numix/devices/pda-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/pda.svg create mode 100644 lisp/ui/icons/Numix/devices/phone-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/phone.svg create mode 100644 lisp/ui/icons/Numix/devices/printer-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/printer.svg create mode 100644 lisp/ui/icons/Numix/devices/scanner-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/scanner.svg create mode 100644 lisp/ui/icons/Numix/devices/video-display-symbolic.svg create mode 100644 lisp/ui/icons/Numix/devices/video-display.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-default-symbolic.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-default.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-documents-symbolic.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-documents.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-downloads.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-favorite-symbolic.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-favorite.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-important-symbolic.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-important.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-mail.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-photos-symbolic.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-photos.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-readonly.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-shared-symbolic.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-shared.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-symbolic-link.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-system-symbolic.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-system.svg create mode 100644 lisp/ui/icons/Numix/emblems/emblem-unreadable.svg create mode 100644 lisp/ui/icons/Numix/places/folder-remote.svg create mode 100644 lisp/ui/icons/Numix/places/folder-symbolic.svg create mode 100644 lisp/ui/icons/Numix/places/folder.svg create mode 100644 lisp/ui/icons/Numix/places/network-server-symbolic.svg create mode 100644 lisp/ui/icons/Numix/places/network-server.svg create mode 100644 lisp/ui/icons/Numix/places/network-workgroup-symbolic.svg create mode 100644 lisp/ui/icons/Numix/places/network-workgroup.svg create mode 100644 lisp/ui/icons/Numix/places/start-here-symbolic.svg create mode 100644 lisp/ui/icons/Numix/places/start-here.svg create mode 100644 lisp/ui/icons/Numix/places/user-bookmarks-symbolic.svg create mode 100644 lisp/ui/icons/Numix/places/user-bookmarks.svg create mode 100644 lisp/ui/icons/Numix/places/user-desktop-symbolic.svg create mode 100644 lisp/ui/icons/Numix/places/user-desktop.svg create mode 100644 lisp/ui/icons/Numix/places/user-home-symbolic.svg create mode 100644 lisp/ui/icons/Numix/places/user-home.svg create mode 100644 lisp/ui/icons/Numix/places/user-trash-symbolic.svg create mode 100644 lisp/ui/icons/Numix/places/user-trash.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/address-book-new.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/application-exit.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/appointment-new.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/call-start.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/call-stop.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/contact-new.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/document-new.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/document-open-recent.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/document-open.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/document-page-setup.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/document-print-preview.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/document-print.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/document-properties.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/document-revert.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/document-save-as.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/document-save.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/document-send.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/edit-clear.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/edit-copy.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/edit-cut.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/edit-delete.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/edit-find-replace.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/edit-find.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/edit-paste.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/edit-redo.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/edit-select-all.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/edit-undo.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/folder-new.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/format-indent-less.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/format-indent-more.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/format-justify-right.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/format-text-bold.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/format-text-direction-ltr.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/format-text-direction-rtl.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/format-text-italic.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/format-text-strikethrough.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/format-text-underline.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/go-bottom.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/go-down.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/go-first.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/go-home.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/go-jump.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/go-last.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/go-next.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/go-previous.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/go-top.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/go-up.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/help-about.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/help-contents.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/help-faq.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/insert-image.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/insert-link.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/insert-object.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/insert-text.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/list-add.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/list-remove.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/mail-forward.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/mail-mark-important.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/mail-mark-junk.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/mail-mark-notjunk.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/mail-mark-read.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/mail-mark-unread.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/mail-message-new.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/mail-reply-all.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/mail-reply-sender.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/mail-send-receive.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/mail-send.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/media-eject.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/media-playback-pause.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/media-playback-start.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/media-playback-stop.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/media-record.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/media-seek-backward.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/media-seek-forward.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/media-skip-backward.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/media-skip-forward.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/object-flip-horizontal.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/object-flip-vertical.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/object-rotate-left.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/object-rotate-right.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/process-stop.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/system-lock-screen.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/system-log-out.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/system-reboot.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/system-run.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/system-search.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/system-shutdown.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/tools-check-spelling.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/view-fullscreen.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/view-refresh.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/view-restore.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/view-sort-ascending.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/view-sort-descending.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/window-close.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/window-new.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/zoom-fit-best.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/zoom-in.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/zoom-original.svg delete mode 100644 lisp/ui/icons/Numix/standard/actions/zoom-out.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/applications-accessories.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/applications-development.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/applications-engineering.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/applications-games.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/applications-graphics.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/applications-internet.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/applications-multimedia.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/applications-office.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/applications-other.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/applications-science.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/applications-system.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/applications-utilities.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/preferences-desktop-peripherals.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/preferences-desktop-personal.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/preferences-desktop.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/preferences-other.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/preferences-system-network.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/preferences-system.svg delete mode 100644 lisp/ui/icons/Numix/standard/categories/system-help.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/audio-card.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/audio-input-microphone.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/battery.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/camera-photo.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/camera-web.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/computer.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/drive-harddisk.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/drive-optical.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/drive-removable-media.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/input-gaming.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/input-keyboard.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/input-mouse.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/input-tablet.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/media-flash.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/media-floppy.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/media-optical.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/media-tape.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/multimedia-player.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/network-wired.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/network-wireless.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/pda.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/phone.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/printer.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/scanner.svg delete mode 100644 lisp/ui/icons/Numix/standard/devices/video-display.svg delete mode 100644 lisp/ui/icons/Numix/standard/emblems/emblem-default.svg delete mode 100644 lisp/ui/icons/Numix/standard/emblems/emblem-documents.svg delete mode 100644 lisp/ui/icons/Numix/standard/emblems/emblem-downloads.svg delete mode 100644 lisp/ui/icons/Numix/standard/emblems/emblem-favorite.svg delete mode 100644 lisp/ui/icons/Numix/standard/emblems/emblem-important.svg delete mode 100644 lisp/ui/icons/Numix/standard/emblems/emblem-mail.svg delete mode 100644 lisp/ui/icons/Numix/standard/emblems/emblem-photos.svg delete mode 100644 lisp/ui/icons/Numix/standard/emblems/emblem-readonly.svg delete mode 100644 lisp/ui/icons/Numix/standard/emblems/emblem-shared.svg delete mode 100644 lisp/ui/icons/Numix/standard/emblems/emblem-symbolic-link.svg delete mode 100644 lisp/ui/icons/Numix/standard/emblems/emblem-system.svg delete mode 100644 lisp/ui/icons/Numix/standard/emblems/emblem-unreadable.svg delete mode 100644 lisp/ui/icons/Numix/standard/places/folder-remote.svg delete mode 100644 lisp/ui/icons/Numix/standard/places/folder.svg delete mode 100644 lisp/ui/icons/Numix/standard/places/network-server.svg delete mode 100644 lisp/ui/icons/Numix/standard/places/network-workgroup.svg delete mode 100644 lisp/ui/icons/Numix/standard/places/start-here.svg delete mode 100644 lisp/ui/icons/Numix/standard/places/user-bookmarks.svg delete mode 100644 lisp/ui/icons/Numix/standard/places/user-desktop.svg delete mode 100644 lisp/ui/icons/Numix/standard/places/user-home.svg delete mode 100644 lisp/ui/icons/Numix/standard/places/user-trash.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/appointment-missed.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/appointment-soon.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/audio-volume-high.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/audio-volume-low.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/audio-volume-medium.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/audio-volume-muted.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/battery-caution.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/battery-low.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/dialog-error.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/dialog-information.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/dialog-password.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/dialog-question.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/dialog-warning.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/image-missing.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/network-error.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/network-idle.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/network-offline.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/network-receive.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/network-transmit-receive.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/network-transmit.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/printer-printing.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/security-high.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/security-low.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/security-medium.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/task-due.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/task-past-due.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/user-available.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/user-away.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/user-idle.svg delete mode 100644 lisp/ui/icons/Numix/standard/status/user-offline.svg create mode 100644 lisp/ui/icons/Numix/status/appointment-missed-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/appointment-missed.svg create mode 100644 lisp/ui/icons/Numix/status/appointment-soon-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/appointment-soon.svg create mode 100644 lisp/ui/icons/Numix/status/audio-volume-high-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/audio-volume-high.svg create mode 100644 lisp/ui/icons/Numix/status/audio-volume-low-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/audio-volume-low.svg create mode 100644 lisp/ui/icons/Numix/status/audio-volume-medium-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/audio-volume-medium.svg create mode 100644 lisp/ui/icons/Numix/status/audio-volume-muted-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/audio-volume-muted.svg create mode 100644 lisp/ui/icons/Numix/status/battery-caution-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/battery-caution.svg create mode 100644 lisp/ui/icons/Numix/status/battery-low-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/battery-low.svg create mode 100644 lisp/ui/icons/Numix/status/dialog-error-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/dialog-error.svg create mode 100644 lisp/ui/icons/Numix/status/dialog-information-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/dialog-information.svg create mode 100644 lisp/ui/icons/Numix/status/dialog-password-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/dialog-password.svg create mode 100644 lisp/ui/icons/Numix/status/dialog-question-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/dialog-question.svg create mode 100644 lisp/ui/icons/Numix/status/dialog-warning-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/dialog-warning.svg create mode 100644 lisp/ui/icons/Numix/status/folder-drag-accept.svg create mode 100644 lisp/ui/icons/Numix/status/folder-open.svg create mode 100644 lisp/ui/icons/Numix/status/folder-visiting.svg create mode 100644 lisp/ui/icons/Numix/status/image-loading.svg create mode 100644 lisp/ui/icons/Numix/status/image-missing.svg create mode 100644 lisp/ui/icons/Numix/status/mail-attachment-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/mail-attachment.svg create mode 100644 lisp/ui/icons/Numix/status/mail-read-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/mail-read.svg create mode 100644 lisp/ui/icons/Numix/status/mail-replied-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/mail-replied.svg create mode 100644 lisp/ui/icons/Numix/status/mail-unread-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/mail-unread.svg create mode 100644 lisp/ui/icons/Numix/status/media-playlist-repeat-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/media-playlist-repeat.svg create mode 100644 lisp/ui/icons/Numix/status/media-playlist-shuffle-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/media-playlist-shuffle.svg create mode 100644 lisp/ui/icons/Numix/status/network-error-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/network-error.svg create mode 100644 lisp/ui/icons/Numix/status/network-idle-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/network-idle.svg create mode 100644 lisp/ui/icons/Numix/status/network-offline-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/network-offline.svg create mode 100644 lisp/ui/icons/Numix/status/network-receive-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/network-receive.svg create mode 100644 lisp/ui/icons/Numix/status/network-transmit-receive-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/network-transmit-receive.svg create mode 100644 lisp/ui/icons/Numix/status/network-transmit-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/network-transmit.svg create mode 100644 lisp/ui/icons/Numix/status/printer-error-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/printer-error.svg create mode 100644 lisp/ui/icons/Numix/status/printer-printing-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/printer-printing.svg create mode 100644 lisp/ui/icons/Numix/status/security-high-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/security-high.svg create mode 100644 lisp/ui/icons/Numix/status/security-low-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/security-low.svg create mode 100644 lisp/ui/icons/Numix/status/security-medium-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/security-medium.svg create mode 100644 lisp/ui/icons/Numix/status/software-update-available-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/software-update-urgent-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/task-due-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/task-due.svg create mode 100644 lisp/ui/icons/Numix/status/task-past-due-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/task-past-due.svg create mode 100644 lisp/ui/icons/Numix/status/user-available-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/user-available.svg create mode 100644 lisp/ui/icons/Numix/status/user-away-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/user-away.svg create mode 100644 lisp/ui/icons/Numix/status/user-idle-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/user-idle.svg create mode 100644 lisp/ui/icons/Numix/status/user-offline-symbolic.svg create mode 100644 lisp/ui/icons/Numix/status/user-offline.svg create mode 100644 lisp/ui/icons/Numix/status/user-trash-full.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/custom/cue-interrupt.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/custom/cue-pause.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/custom/cue-select-next.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/custom/cue-start.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/custom/cue-stop.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/custom/cue-trigger-next.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/custom/fadein-generic.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/custom/fadeout-generic.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/index.theme delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/address-book-new.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/application-exit.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/appointment-new.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/call-start.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/call-stop.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/contact-new.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/document-new.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/document-open-recent.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/document-open.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/document-page-setup.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/document-print-preview.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/document-print.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/document-properties.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/document-revert.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/document-save-as.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/document-save.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/document-send.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/edit-clear.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/edit-copy.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/edit-cut.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/edit-delete.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/edit-find-replace.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/edit-find.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/edit-paste.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/edit-redo.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/edit-select-all.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/edit-undo.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/folder-new.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/format-indent-less.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/format-indent-more.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-center.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-fill.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-left.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-right.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-bold.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-direction-ltr.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-direction-rtl.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-italic.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-strikethrough.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-underline.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/go-bottom.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/go-down.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/go-first.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/go-home.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/go-jump.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/go-last.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/go-next.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/go-previous.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/go-top.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/go-up.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/help-about.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/insert-image.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/insert-link.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/insert-object.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/insert-text.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/list-add.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/list-remove.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/mail-forward.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-important.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-junk.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-notjunk.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-read.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-unread.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/mail-message-new.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/mail-reply-all.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/mail-reply-sender.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/mail-send-receive.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/mail-send.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/media-eject.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-pause.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-start.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-stop.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/media-record.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/media-seek-backward.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/media-seek-forward.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/media-skip-backward.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/media-skip-forward.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/object-flip-horizontal.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/object-flip-vertical.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/object-rotate-left.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/object-rotate-right.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/process-stop.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/system-lock-screen.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/system-log-out.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/system-run.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/system-search.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/system-shutdown.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/tools-check-spelling.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/view-fullscreen.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/view-refresh.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/view-restore.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/view-sort-ascending.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/view-sort-descending.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/window-close.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-fit-best.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-in.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-original.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-out.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/categories/applications-engineering.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/categories/applications-games.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/categories/applications-graphics.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/categories/applications-multimedia.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/categories/applications-science.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/categories/applications-system.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/categories/applications-utilities.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/categories/preferences-other.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/categories/preferences-system.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/categories/system-help.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/audio-card.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/audio-input-microphone.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/battery.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/camera-photo.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/camera-video.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/camera-web.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/computer.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/drive-harddisk.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/drive-optical.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/drive-removable-media.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/input-gaming.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/input-keyboard.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/input-mouse.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/input-tablet.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/media-flash.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/media-floppy.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/media-tape.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/multimedia-player.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/network-wired.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/network-wireless.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/phone.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/printer.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/scanner.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/devices/video-display.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-default.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-documents.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-favorite.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-important.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-photos.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-shared.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-system.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/places/folder-remote.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/places/folder.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/places/network-workgroup.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/places/start-here.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/places/user-bookmarks.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/places/user-desktop.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/places/user-home.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/places/user-trash.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/appointment-missed.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/appointment-soon.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-high.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-low.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-medium.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-muted.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/battery-caution.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/battery-low.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/dialog-error.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/dialog-information.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/dialog-password.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/dialog-question.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/dialog-warning.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/folder-drag-accept.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/folder-open.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/folder-visiting.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/image-loading.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/mail-attachment.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/mail-read.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/mail-replied.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/mail-unread.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/media-playlist-repeat.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/media-playlist-shuffle.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/network-error.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/network-idle.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/network-offline.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/network-receive.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/network-transmit-receive.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/network-transmit.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/printer-error.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/printer-printing.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/security-high.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/security-low.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/security-medium.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/software-update-available.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/software-update-urgent.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/task-due.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/task-past-due.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/user-available.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/user-away.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/user-idle.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/user-offline.svg delete mode 100644 lisp/ui/icons/Papirus Symbolic/standard/status/user-trash-full.svg diff --git a/lisp/plugins/gst_backend/gst_pipe_edit.py b/lisp/plugins/gst_backend/gst_pipe_edit.py index 81c08cea3..e56ab493d 100644 --- a/lisp/plugins/gst_backend/gst_pipe_edit.py +++ b/lisp/plugins/gst_backend/gst_pipe_edit.py @@ -68,13 +68,13 @@ def __init__(self, pipe, app_mode=False, **kwargs): self.layout().setAlignment(self.buttonsLayout, Qt.AlignHCenter) self.addButton = QPushButton(self) - self.addButton.setIcon(IconTheme.get("go-previous")) + self.addButton.setIcon(IconTheme.get("go-previous-symbolic")) self.addButton.clicked.connect(self.__add_plugin) self.buttonsLayout.addWidget(self.addButton) self.buttonsLayout.setAlignment(self.addButton, Qt.AlignHCenter) self.delButton = QPushButton(self) - self.delButton.setIcon(IconTheme.get("go-next")) + self.delButton.setIcon(IconTheme.get("go-next-symbolic")) self.delButton.clicked.connect(self.__remove_plugin) self.buttonsLayout.addWidget(self.delButton) self.buttonsLayout.setAlignment(self.delButton, Qt.AlignHCenter) diff --git a/lisp/ui/icons/Numix/actions/address-book-new-symbolic.svg b/lisp/ui/icons/Numix/actions/address-book-new-symbolic.svg new file mode 100644 index 000000000..55e3f3516 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/address-book-new-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/address-book-new.svg b/lisp/ui/icons/Numix/actions/address-book-new.svg new file mode 100644 index 000000000..b4ada5aa7 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/address-book-new.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/application-exit-symbolic.svg b/lisp/ui/icons/Numix/actions/application-exit-symbolic.svg new file mode 100644 index 000000000..19aaf984b --- /dev/null +++ b/lisp/ui/icons/Numix/actions/application-exit-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/application-exit.svg b/lisp/ui/icons/Numix/actions/application-exit.svg new file mode 100644 index 000000000..d1a51be02 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/application-exit.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/appointment-new-symbolic.svg b/lisp/ui/icons/Numix/actions/appointment-new-symbolic.svg new file mode 100644 index 000000000..3dfb8d9c4 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/appointment-new-symbolic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/appointment-new.svg b/lisp/ui/icons/Numix/actions/appointment-new.svg new file mode 100644 index 000000000..e144c7304 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/appointment-new.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/call-start-symbolic.svg b/lisp/ui/icons/Numix/actions/call-start-symbolic.svg new file mode 100644 index 000000000..a5d8bfd3b --- /dev/null +++ b/lisp/ui/icons/Numix/actions/call-start-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/call-start.svg b/lisp/ui/icons/Numix/actions/call-start.svg new file mode 100644 index 000000000..bfa15ae17 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/call-start.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/call-stop-symbolic.svg b/lisp/ui/icons/Numix/actions/call-stop-symbolic.svg new file mode 100644 index 000000000..3485c58c2 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/call-stop-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/call-stop.svg b/lisp/ui/icons/Numix/actions/call-stop.svg new file mode 100644 index 000000000..ca9b03285 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/call-stop.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/actions/contact-new-symbolic.svg b/lisp/ui/icons/Numix/actions/contact-new-symbolic.svg new file mode 100644 index 000000000..59bbd7b4f --- /dev/null +++ b/lisp/ui/icons/Numix/actions/contact-new-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/contact-new.svg b/lisp/ui/icons/Numix/actions/contact-new.svg new file mode 100644 index 000000000..771479a20 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/contact-new.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/actions/dialog-apply.svg b/lisp/ui/icons/Numix/actions/dialog-apply.svg new file mode 100644 index 000000000..01c624de1 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/dialog-apply.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/dialog-cancel.svg b/lisp/ui/icons/Numix/actions/dialog-cancel.svg new file mode 100644 index 000000000..8b1e2f0b9 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/dialog-cancel.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/dialog-close.svg b/lisp/ui/icons/Numix/actions/dialog-close.svg new file mode 100644 index 000000000..346f98dc1 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/dialog-close.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/dialog-no.svg b/lisp/ui/icons/Numix/actions/dialog-no.svg new file mode 100644 index 000000000..8b1e2f0b9 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/dialog-no.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/dialog-ok.svg b/lisp/ui/icons/Numix/actions/dialog-ok.svg new file mode 100644 index 000000000..01c624de1 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/dialog-ok.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/dialog-yes.svg b/lisp/ui/icons/Numix/actions/dialog-yes.svg new file mode 100644 index 000000000..01c624de1 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/dialog-yes.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/document-new-symbolic.svg b/lisp/ui/icons/Numix/actions/document-new-symbolic.svg new file mode 100644 index 000000000..689216229 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-new-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/document-new.svg b/lisp/ui/icons/Numix/actions/document-new.svg new file mode 100644 index 000000000..22b2ec83a --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-new.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/actions/document-open-recent-symbolic.svg b/lisp/ui/icons/Numix/actions/document-open-recent-symbolic.svg new file mode 100644 index 000000000..7b0b5e586 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-open-recent-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/document-open-recent.svg b/lisp/ui/icons/Numix/actions/document-open-recent.svg new file mode 100644 index 000000000..64363b237 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-open-recent.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/document-open-symbolic.svg b/lisp/ui/icons/Numix/actions/document-open-symbolic.svg new file mode 100644 index 000000000..302bba47f --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-open-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/document-open.svg b/lisp/ui/icons/Numix/actions/document-open.svg new file mode 100644 index 000000000..e0e6adf99 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-open.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/document-page-setup-symbolic.svg b/lisp/ui/icons/Numix/actions/document-page-setup-symbolic.svg new file mode 100644 index 000000000..f6c57fd9f --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-page-setup-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/actions/document-page-setup.svg b/lisp/ui/icons/Numix/actions/document-page-setup.svg new file mode 100644 index 000000000..73d796107 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-page-setup.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/document-print-preview-symbolic.svg b/lisp/ui/icons/Numix/actions/document-print-preview-symbolic.svg new file mode 100644 index 000000000..7d26e3996 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-print-preview-symbolic.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/document-print-preview.svg b/lisp/ui/icons/Numix/actions/document-print-preview.svg new file mode 100644 index 000000000..4ada48642 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-print-preview.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/document-print-symbolic.svg b/lisp/ui/icons/Numix/actions/document-print-symbolic.svg new file mode 100644 index 000000000..24bd8b785 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-print-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/document-print.svg b/lisp/ui/icons/Numix/actions/document-print.svg new file mode 100644 index 000000000..15c6e827e --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-print.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/document-properties-symbolic.svg b/lisp/ui/icons/Numix/actions/document-properties-symbolic.svg new file mode 100644 index 000000000..dc6c20ed0 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-properties-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/actions/document-properties.svg b/lisp/ui/icons/Numix/actions/document-properties.svg new file mode 100644 index 000000000..cdc865516 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-properties.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/document-revert-symbolic.svg b/lisp/ui/icons/Numix/actions/document-revert-symbolic.svg new file mode 100644 index 000000000..5133c248f --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-revert-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/document-revert.svg b/lisp/ui/icons/Numix/actions/document-revert.svg new file mode 100644 index 000000000..603f047fb --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-revert.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/actions/document-save-as-symbolic.svg b/lisp/ui/icons/Numix/actions/document-save-as-symbolic.svg new file mode 100644 index 000000000..26092c826 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-save-as-symbolic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/document-save-as.svg b/lisp/ui/icons/Numix/actions/document-save-as.svg new file mode 100644 index 000000000..eebea0898 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-save-as.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/document-save-symbolic.svg b/lisp/ui/icons/Numix/actions/document-save-symbolic.svg new file mode 100644 index 000000000..9d5ea9f60 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-save-symbolic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/document-save.svg b/lisp/ui/icons/Numix/actions/document-save.svg new file mode 100644 index 000000000..0590e3f4a --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-save.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/document-send-symbolic.svg b/lisp/ui/icons/Numix/actions/document-send-symbolic.svg new file mode 100644 index 000000000..bcb2c96e8 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-send-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/document-send.svg b/lisp/ui/icons/Numix/actions/document-send.svg new file mode 100644 index 000000000..ed4179e38 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/document-send.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-clear-symbolic.svg b/lisp/ui/icons/Numix/actions/edit-clear-symbolic.svg new file mode 100644 index 000000000..3e7eb49d1 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-clear-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-clear.svg b/lisp/ui/icons/Numix/actions/edit-clear.svg new file mode 100644 index 000000000..afb0697d5 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-clear.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-copy-symbolic.svg b/lisp/ui/icons/Numix/actions/edit-copy-symbolic.svg new file mode 100644 index 000000000..56fcfd973 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-copy-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-copy.svg b/lisp/ui/icons/Numix/actions/edit-copy.svg new file mode 100644 index 000000000..cdb94fb38 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-copy.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-cut-symbolic.svg b/lisp/ui/icons/Numix/actions/edit-cut-symbolic.svg new file mode 100644 index 000000000..a99131378 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-cut-symbolic.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-cut.svg b/lisp/ui/icons/Numix/actions/edit-cut.svg new file mode 100644 index 000000000..d0d1c9db5 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-cut.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-delete-symbolic.svg b/lisp/ui/icons/Numix/actions/edit-delete-symbolic.svg new file mode 100644 index 000000000..41df1f0c0 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-delete-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-delete.svg b/lisp/ui/icons/Numix/actions/edit-delete.svg new file mode 100644 index 000000000..534857f56 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-delete.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-find-replace-symbolic.svg b/lisp/ui/icons/Numix/actions/edit-find-replace-symbolic.svg new file mode 100644 index 000000000..7292d7af4 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-find-replace-symbolic.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-find-replace.svg b/lisp/ui/icons/Numix/actions/edit-find-replace.svg new file mode 100644 index 000000000..4dc932788 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-find-replace.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-find-symbolic.svg b/lisp/ui/icons/Numix/actions/edit-find-symbolic.svg new file mode 100644 index 000000000..212f8f271 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-find-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/edit-find.svg b/lisp/ui/icons/Numix/actions/edit-find.svg new file mode 100644 index 000000000..79ae701dc --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-find.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-paste-symbolic.svg b/lisp/ui/icons/Numix/actions/edit-paste-symbolic.svg new file mode 100644 index 000000000..bf8c22a56 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-paste-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-paste.svg b/lisp/ui/icons/Numix/actions/edit-paste.svg new file mode 100644 index 000000000..15d4257c1 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-paste.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-redo-symbolic.svg b/lisp/ui/icons/Numix/actions/edit-redo-symbolic.svg new file mode 100644 index 000000000..476eb2b93 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-redo-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-redo.svg b/lisp/ui/icons/Numix/actions/edit-redo.svg new file mode 100644 index 000000000..c6db9e80b --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-redo.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/edit-select-all-symbolic.svg b/lisp/ui/icons/Numix/actions/edit-select-all-symbolic.svg new file mode 100644 index 000000000..0d4d4ea4f --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-select-all-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-select-all.svg b/lisp/ui/icons/Numix/actions/edit-select-all.svg new file mode 100644 index 000000000..c289dfc9d --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-select-all.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-undo-symbolic.svg b/lisp/ui/icons/Numix/actions/edit-undo-symbolic.svg new file mode 100644 index 000000000..9e7faf3e7 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-undo-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/edit-undo.svg b/lisp/ui/icons/Numix/actions/edit-undo.svg new file mode 100644 index 000000000..ae177e802 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/edit-undo.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/folder-new.svg b/lisp/ui/icons/Numix/actions/folder-new.svg new file mode 100644 index 000000000..d1076fb75 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/folder-new.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-indent-less-symbolic.svg b/lisp/ui/icons/Numix/actions/format-indent-less-symbolic.svg new file mode 100644 index 000000000..584493eab --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-indent-less-symbolic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-indent-less.svg b/lisp/ui/icons/Numix/actions/format-indent-less.svg new file mode 100644 index 000000000..38f485497 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-indent-less.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-indent-more-symbolic.svg b/lisp/ui/icons/Numix/actions/format-indent-more-symbolic.svg new file mode 100644 index 000000000..54f1ff332 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-indent-more-symbolic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-indent-more.svg b/lisp/ui/icons/Numix/actions/format-indent-more.svg new file mode 100644 index 000000000..f00d922a0 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-indent-more.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-justify-center-symbolic.svg b/lisp/ui/icons/Numix/actions/format-justify-center-symbolic.svg new file mode 100644 index 000000000..e5a495504 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-justify-center-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/standard/actions/format-justify-center.svg b/lisp/ui/icons/Numix/actions/format-justify-center.svg similarity index 53% rename from lisp/ui/icons/Numix/standard/actions/format-justify-center.svg rename to lisp/ui/icons/Numix/actions/format-justify-center.svg index 7e034f99e..88c9ec748 100644 --- a/lisp/ui/icons/Numix/standard/actions/format-justify-center.svg +++ b/lisp/ui/icons/Numix/actions/format-justify-center.svg @@ -1,5 +1,5 @@ - - + + diff --git a/lisp/ui/icons/Numix/actions/format-justify-fill-symbolic.svg b/lisp/ui/icons/Numix/actions/format-justify-fill-symbolic.svg new file mode 100644 index 000000000..85ced7b7a --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-justify-fill-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/standard/actions/format-justify-fill.svg b/lisp/ui/icons/Numix/actions/format-justify-fill.svg similarity index 53% rename from lisp/ui/icons/Numix/standard/actions/format-justify-fill.svg rename to lisp/ui/icons/Numix/actions/format-justify-fill.svg index 9fa248fcc..af77ba273 100644 --- a/lisp/ui/icons/Numix/standard/actions/format-justify-fill.svg +++ b/lisp/ui/icons/Numix/actions/format-justify-fill.svg @@ -1,5 +1,5 @@ - - + + diff --git a/lisp/ui/icons/Numix/actions/format-justify-left-symbolic.svg b/lisp/ui/icons/Numix/actions/format-justify-left-symbolic.svg new file mode 100644 index 000000000..4f3aec116 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-justify-left-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/standard/actions/format-justify-left.svg b/lisp/ui/icons/Numix/actions/format-justify-left.svg similarity index 52% rename from lisp/ui/icons/Numix/standard/actions/format-justify-left.svg rename to lisp/ui/icons/Numix/actions/format-justify-left.svg index c920c6d21..a4944e1e7 100644 --- a/lisp/ui/icons/Numix/standard/actions/format-justify-left.svg +++ b/lisp/ui/icons/Numix/actions/format-justify-left.svg @@ -1,5 +1,5 @@ - - + + diff --git a/lisp/ui/icons/Numix/actions/format-justify-right-symbolic.svg b/lisp/ui/icons/Numix/actions/format-justify-right-symbolic.svg new file mode 100644 index 000000000..a083f74f9 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-justify-right-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-justify-right.svg b/lisp/ui/icons/Numix/actions/format-justify-right.svg new file mode 100644 index 000000000..5da5e00d9 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-justify-right.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-text-bold-symbolic.svg b/lisp/ui/icons/Numix/actions/format-text-bold-symbolic.svg new file mode 100644 index 000000000..1c1d2f902 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-text-bold-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-text-bold.svg b/lisp/ui/icons/Numix/actions/format-text-bold.svg new file mode 100644 index 000000000..c177abef9 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-text-bold.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/format-text-direction-ltr-symbolic.svg b/lisp/ui/icons/Numix/actions/format-text-direction-ltr-symbolic.svg new file mode 100644 index 000000000..8a7734b7f --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-text-direction-ltr-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-text-direction-ltr.svg b/lisp/ui/icons/Numix/actions/format-text-direction-ltr.svg new file mode 100644 index 000000000..6b8231ede --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-text-direction-ltr.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-text-direction-rtl-symbolic.svg b/lisp/ui/icons/Numix/actions/format-text-direction-rtl-symbolic.svg new file mode 100644 index 000000000..574bd9bd0 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-text-direction-rtl-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-text-direction-rtl.svg b/lisp/ui/icons/Numix/actions/format-text-direction-rtl.svg new file mode 100644 index 000000000..2e88f9df9 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-text-direction-rtl.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-text-italic-symbolic.svg b/lisp/ui/icons/Numix/actions/format-text-italic-symbolic.svg new file mode 100644 index 000000000..909a9dad4 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-text-italic-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-text-italic.svg b/lisp/ui/icons/Numix/actions/format-text-italic.svg new file mode 100644 index 000000000..bc134bf47 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-text-italic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-text-strikethrough-symbolic.svg b/lisp/ui/icons/Numix/actions/format-text-strikethrough-symbolic.svg new file mode 100644 index 000000000..1fc281da5 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-text-strikethrough-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-text-strikethrough.svg b/lisp/ui/icons/Numix/actions/format-text-strikethrough.svg new file mode 100644 index 000000000..e2e919fa1 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-text-strikethrough.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-text-underline-symbolic.svg b/lisp/ui/icons/Numix/actions/format-text-underline-symbolic.svg new file mode 100644 index 000000000..385ac0bf9 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-text-underline-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/format-text-underline.svg b/lisp/ui/icons/Numix/actions/format-text-underline.svg new file mode 100644 index 000000000..54476ea88 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/format-text-underline.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-bottom-symbolic.svg b/lisp/ui/icons/Numix/actions/go-bottom-symbolic.svg new file mode 100644 index 000000000..747bea45b --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-bottom-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-bottom.svg b/lisp/ui/icons/Numix/actions/go-bottom.svg new file mode 100644 index 000000000..c99f2f141 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-bottom.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-down-symbolic.svg b/lisp/ui/icons/Numix/actions/go-down-symbolic.svg new file mode 100644 index 000000000..b04bc5bd4 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-down-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-down.svg b/lisp/ui/icons/Numix/actions/go-down.svg new file mode 100644 index 000000000..cdff99e84 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-down.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-first-symbolic.svg b/lisp/ui/icons/Numix/actions/go-first-symbolic.svg new file mode 100644 index 000000000..d49a5d7cf --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-first-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-first.svg b/lisp/ui/icons/Numix/actions/go-first.svg new file mode 100644 index 000000000..d4d929d57 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-first.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-home-symbolic.svg b/lisp/ui/icons/Numix/actions/go-home-symbolic.svg new file mode 100644 index 000000000..922e93ccf --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-home-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-home.svg b/lisp/ui/icons/Numix/actions/go-home.svg new file mode 100644 index 000000000..34eed4dc9 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-home.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/actions/go-jump-symbolic.svg b/lisp/ui/icons/Numix/actions/go-jump-symbolic.svg new file mode 100644 index 000000000..81c7b84b0 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-jump-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-jump.svg b/lisp/ui/icons/Numix/actions/go-jump.svg new file mode 100644 index 000000000..0c74d5eb7 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-jump.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-last-symbolic.svg b/lisp/ui/icons/Numix/actions/go-last-symbolic.svg new file mode 100644 index 000000000..c775548d3 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-last-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-last.svg b/lisp/ui/icons/Numix/actions/go-last.svg new file mode 100644 index 000000000..30e5f8053 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-last.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-next-symbolic.svg b/lisp/ui/icons/Numix/actions/go-next-symbolic.svg new file mode 100644 index 000000000..5fb0530fe --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-next-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-next.svg b/lisp/ui/icons/Numix/actions/go-next.svg new file mode 100644 index 000000000..5c26e58d1 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-next.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-previous-symbolic.svg b/lisp/ui/icons/Numix/actions/go-previous-symbolic.svg new file mode 100644 index 000000000..002fc01e8 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-previous-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-previous.svg b/lisp/ui/icons/Numix/actions/go-previous.svg new file mode 100644 index 000000000..c267022d3 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-previous.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-top-symbolic.svg b/lisp/ui/icons/Numix/actions/go-top-symbolic.svg new file mode 100644 index 000000000..08a4640f2 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-top-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-top.svg b/lisp/ui/icons/Numix/actions/go-top.svg new file mode 100644 index 000000000..55b7a82a1 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-top.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-up-symbolic.svg b/lisp/ui/icons/Numix/actions/go-up-symbolic.svg new file mode 100644 index 000000000..0fce07da6 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-up-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/go-up.svg b/lisp/ui/icons/Numix/actions/go-up.svg new file mode 100644 index 000000000..6db993464 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/go-up.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/help-about-symbolic.svg b/lisp/ui/icons/Numix/actions/help-about-symbolic.svg new file mode 100644 index 000000000..fc2f221a7 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/help-about-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/help-about.svg b/lisp/ui/icons/Numix/actions/help-about.svg new file mode 100644 index 000000000..9679fc9bf --- /dev/null +++ b/lisp/ui/icons/Numix/actions/help-about.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/help-contents.svg b/lisp/ui/icons/Numix/actions/help-contents.svg new file mode 100644 index 000000000..cefdaca82 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/help-contents.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/help-faq.svg b/lisp/ui/icons/Numix/actions/help-faq.svg new file mode 100644 index 000000000..ead0d1980 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/help-faq.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/insert-image-symbolic.svg b/lisp/ui/icons/Numix/actions/insert-image-symbolic.svg new file mode 100644 index 000000000..8c159b4e9 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/insert-image-symbolic.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/insert-image.svg b/lisp/ui/icons/Numix/actions/insert-image.svg new file mode 100644 index 000000000..8821bef4c --- /dev/null +++ b/lisp/ui/icons/Numix/actions/insert-image.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/insert-link-symbolic.svg b/lisp/ui/icons/Numix/actions/insert-link-symbolic.svg new file mode 100644 index 000000000..7c17b5336 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/insert-link-symbolic.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/insert-link.svg b/lisp/ui/icons/Numix/actions/insert-link.svg new file mode 100644 index 000000000..55860acbc --- /dev/null +++ b/lisp/ui/icons/Numix/actions/insert-link.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/insert-object-symbolic.svg b/lisp/ui/icons/Numix/actions/insert-object-symbolic.svg new file mode 100644 index 000000000..465be980c --- /dev/null +++ b/lisp/ui/icons/Numix/actions/insert-object-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/insert-object.svg b/lisp/ui/icons/Numix/actions/insert-object.svg new file mode 100644 index 000000000..edf637fcf --- /dev/null +++ b/lisp/ui/icons/Numix/actions/insert-object.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/insert-text-symbolic.svg b/lisp/ui/icons/Numix/actions/insert-text-symbolic.svg new file mode 100644 index 000000000..ecd5da9aa --- /dev/null +++ b/lisp/ui/icons/Numix/actions/insert-text-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/insert-text.svg b/lisp/ui/icons/Numix/actions/insert-text.svg new file mode 100644 index 000000000..c768767b8 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/insert-text.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/list-add-symbolic.svg b/lisp/ui/icons/Numix/actions/list-add-symbolic.svg new file mode 100644 index 000000000..6e6109904 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/list-add-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/list-add.svg b/lisp/ui/icons/Numix/actions/list-add.svg new file mode 100644 index 000000000..a536ee560 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/list-add.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/list-remove-symbolic.svg b/lisp/ui/icons/Numix/actions/list-remove-symbolic.svg new file mode 100644 index 000000000..09c45d52f --- /dev/null +++ b/lisp/ui/icons/Numix/actions/list-remove-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/list-remove.svg b/lisp/ui/icons/Numix/actions/list-remove.svg new file mode 100644 index 000000000..9ca41a89d --- /dev/null +++ b/lisp/ui/icons/Numix/actions/list-remove.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/mail-forward-symbolic.svg b/lisp/ui/icons/Numix/actions/mail-forward-symbolic.svg new file mode 100644 index 000000000..b62af735d --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-forward-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/mail-forward.svg b/lisp/ui/icons/Numix/actions/mail-forward.svg new file mode 100644 index 000000000..c4db58414 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-forward.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/mail-mark-important-symbolic.svg b/lisp/ui/icons/Numix/actions/mail-mark-important-symbolic.svg new file mode 100644 index 000000000..b96c2be97 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-mark-important-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/mail-mark-important.svg b/lisp/ui/icons/Numix/actions/mail-mark-important.svg new file mode 100644 index 000000000..d31828bdc --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-mark-important.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/mail-mark-junk.svg b/lisp/ui/icons/Numix/actions/mail-mark-junk.svg new file mode 100644 index 000000000..03d10f16e --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-mark-junk.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/mail-mark-notjunk.svg b/lisp/ui/icons/Numix/actions/mail-mark-notjunk.svg new file mode 100644 index 000000000..ab9881b20 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-mark-notjunk.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/mail-mark-read.svg b/lisp/ui/icons/Numix/actions/mail-mark-read.svg new file mode 100644 index 000000000..e59a3d5e8 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-mark-read.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/mail-mark-unread.svg b/lisp/ui/icons/Numix/actions/mail-mark-unread.svg new file mode 100644 index 000000000..55034ce04 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-mark-unread.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/mail-message-new.svg b/lisp/ui/icons/Numix/actions/mail-message-new.svg new file mode 100644 index 000000000..a4f775eec --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-message-new.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/mail-reply-all-symbolic.svg b/lisp/ui/icons/Numix/actions/mail-reply-all-symbolic.svg new file mode 100644 index 000000000..d01fd49ac --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-reply-all-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/actions/mail-reply-all.svg b/lisp/ui/icons/Numix/actions/mail-reply-all.svg new file mode 100644 index 000000000..6067a1795 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-reply-all.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/mail-reply-sender-symbolic.svg b/lisp/ui/icons/Numix/actions/mail-reply-sender-symbolic.svg new file mode 100644 index 000000000..95fde1f2b --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-reply-sender-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/mail-reply-sender.svg b/lisp/ui/icons/Numix/actions/mail-reply-sender.svg new file mode 100644 index 000000000..7679e1f91 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-reply-sender.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/mail-send-receive-symbolic.svg b/lisp/ui/icons/Numix/actions/mail-send-receive-symbolic.svg new file mode 100644 index 000000000..b0bd9cddb --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-send-receive-symbolic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/mail-send-receive.svg b/lisp/ui/icons/Numix/actions/mail-send-receive.svg new file mode 100644 index 000000000..dd0e128c7 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-send-receive.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/mail-send-symbolic.svg b/lisp/ui/icons/Numix/actions/mail-send-symbolic.svg new file mode 100644 index 000000000..bc662acbd --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-send-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/mail-send.svg b/lisp/ui/icons/Numix/actions/mail-send.svg new file mode 100644 index 000000000..2743dd977 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/mail-send.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/media-eject-symbolic.svg b/lisp/ui/icons/Numix/actions/media-eject-symbolic.svg new file mode 100644 index 000000000..9ce04e8fc --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-eject-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/media-eject.svg b/lisp/ui/icons/Numix/actions/media-eject.svg new file mode 100644 index 000000000..22d746140 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-eject.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/media-playback-pause-symbolic.svg b/lisp/ui/icons/Numix/actions/media-playback-pause-symbolic.svg new file mode 100644 index 000000000..a6e9c125c --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-playback-pause-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/actions/media-playback-pause.svg b/lisp/ui/icons/Numix/actions/media-playback-pause.svg new file mode 100644 index 000000000..39a356a5c --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-playback-pause.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/media-playback-start-symbolic.svg b/lisp/ui/icons/Numix/actions/media-playback-start-symbolic.svg new file mode 100644 index 000000000..fbd6019fd --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-playback-start-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/media-playback-start.svg b/lisp/ui/icons/Numix/actions/media-playback-start.svg new file mode 100644 index 000000000..07df6ef16 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-playback-start.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/media-playback-stop-symbolic.svg b/lisp/ui/icons/Numix/actions/media-playback-stop-symbolic.svg new file mode 100644 index 000000000..b07a57ef1 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-playback-stop-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/media-playback-stop.svg b/lisp/ui/icons/Numix/actions/media-playback-stop.svg new file mode 100644 index 000000000..b397553dd --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-playback-stop.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/media-record-symbolic.svg b/lisp/ui/icons/Numix/actions/media-record-symbolic.svg new file mode 100644 index 000000000..f4e2d6e44 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-record-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/media-record.svg b/lisp/ui/icons/Numix/actions/media-record.svg new file mode 100644 index 000000000..25b172d93 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-record.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/media-seek-backward-symbolic.svg b/lisp/ui/icons/Numix/actions/media-seek-backward-symbolic.svg new file mode 100644 index 000000000..04aa589cd --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-seek-backward-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/actions/media-seek-backward.svg b/lisp/ui/icons/Numix/actions/media-seek-backward.svg new file mode 100644 index 000000000..6d810ad18 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-seek-backward.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/media-seek-forward-symbolic.svg b/lisp/ui/icons/Numix/actions/media-seek-forward-symbolic.svg new file mode 100644 index 000000000..bb9719ce0 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-seek-forward-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/actions/media-seek-forward.svg b/lisp/ui/icons/Numix/actions/media-seek-forward.svg new file mode 100644 index 000000000..862e3f64b --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-seek-forward.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/media-skip-backward-symbolic.svg b/lisp/ui/icons/Numix/actions/media-skip-backward-symbolic.svg new file mode 100644 index 000000000..4d8741d23 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-skip-backward-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/media-skip-backward.svg b/lisp/ui/icons/Numix/actions/media-skip-backward.svg new file mode 100644 index 000000000..078849d90 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-skip-backward.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/media-skip-forward-symbolic.svg b/lisp/ui/icons/Numix/actions/media-skip-forward-symbolic.svg new file mode 100644 index 000000000..6966ce420 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-skip-forward-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/media-skip-forward.svg b/lisp/ui/icons/Numix/actions/media-skip-forward.svg new file mode 100644 index 000000000..613fafe49 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/media-skip-forward.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/object-flip-horizontal-symbolic.svg b/lisp/ui/icons/Numix/actions/object-flip-horizontal-symbolic.svg new file mode 100644 index 000000000..d619aa897 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/object-flip-horizontal-symbolic.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/object-flip-horizontal.svg b/lisp/ui/icons/Numix/actions/object-flip-horizontal.svg new file mode 100644 index 000000000..2a59753f6 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/object-flip-horizontal.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/object-flip-vertical-symbolic.svg b/lisp/ui/icons/Numix/actions/object-flip-vertical-symbolic.svg new file mode 100644 index 000000000..ab48cfb5c --- /dev/null +++ b/lisp/ui/icons/Numix/actions/object-flip-vertical-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/object-flip-vertical.svg b/lisp/ui/icons/Numix/actions/object-flip-vertical.svg new file mode 100644 index 000000000..bc7913b11 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/object-flip-vertical.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/object-rotate-left-symbolic.svg b/lisp/ui/icons/Numix/actions/object-rotate-left-symbolic.svg new file mode 100644 index 000000000..94942be49 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/object-rotate-left-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/object-rotate-left.svg b/lisp/ui/icons/Numix/actions/object-rotate-left.svg new file mode 100644 index 000000000..a72b4dc09 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/object-rotate-left.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/object-rotate-right-symbolic.svg b/lisp/ui/icons/Numix/actions/object-rotate-right-symbolic.svg new file mode 100644 index 000000000..0551f3606 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/object-rotate-right-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/object-rotate-right.svg b/lisp/ui/icons/Numix/actions/object-rotate-right.svg new file mode 100644 index 000000000..d81d59063 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/object-rotate-right.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/process-stop-symbolic.svg b/lisp/ui/icons/Numix/actions/process-stop-symbolic.svg new file mode 100644 index 000000000..d3d06539b --- /dev/null +++ b/lisp/ui/icons/Numix/actions/process-stop-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/process-stop.svg b/lisp/ui/icons/Numix/actions/process-stop.svg new file mode 100644 index 000000000..2c6cfebdc --- /dev/null +++ b/lisp/ui/icons/Numix/actions/process-stop.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/system-lock-screen.svg b/lisp/ui/icons/Numix/actions/system-lock-screen.svg new file mode 100644 index 000000000..a68028eae --- /dev/null +++ b/lisp/ui/icons/Numix/actions/system-lock-screen.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/system-log-out.svg b/lisp/ui/icons/Numix/actions/system-log-out.svg new file mode 100644 index 000000000..06dde144e --- /dev/null +++ b/lisp/ui/icons/Numix/actions/system-log-out.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/system-reboot.svg b/lisp/ui/icons/Numix/actions/system-reboot.svg new file mode 100644 index 000000000..f36e7b24c --- /dev/null +++ b/lisp/ui/icons/Numix/actions/system-reboot.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/system-run-symbolic.svg b/lisp/ui/icons/Numix/actions/system-run-symbolic.svg new file mode 100644 index 000000000..53d468604 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/system-run-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/system-run.svg b/lisp/ui/icons/Numix/actions/system-run.svg new file mode 100644 index 000000000..80c345b08 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/system-run.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/system-search-symbolic.svg b/lisp/ui/icons/Numix/actions/system-search-symbolic.svg new file mode 100644 index 000000000..97219e648 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/system-search-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/system-search.svg b/lisp/ui/icons/Numix/actions/system-search.svg new file mode 100644 index 000000000..79ae701dc --- /dev/null +++ b/lisp/ui/icons/Numix/actions/system-search.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/system-shutdown-symbolic.svg b/lisp/ui/icons/Numix/actions/system-shutdown-symbolic.svg new file mode 100644 index 000000000..f4644cc4f --- /dev/null +++ b/lisp/ui/icons/Numix/actions/system-shutdown-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/system-shutdown.svg b/lisp/ui/icons/Numix/actions/system-shutdown.svg new file mode 100644 index 000000000..d1a51be02 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/system-shutdown.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/tools-check-spelling-symbolic.svg b/lisp/ui/icons/Numix/actions/tools-check-spelling-symbolic.svg new file mode 100644 index 000000000..67ca0b09e --- /dev/null +++ b/lisp/ui/icons/Numix/actions/tools-check-spelling-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/tools-check-spelling.svg b/lisp/ui/icons/Numix/actions/tools-check-spelling.svg new file mode 100644 index 000000000..87f11f5ec --- /dev/null +++ b/lisp/ui/icons/Numix/actions/tools-check-spelling.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/view-fullscreen-symbolic.svg b/lisp/ui/icons/Numix/actions/view-fullscreen-symbolic.svg new file mode 100644 index 000000000..f0b2b5d93 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/view-fullscreen-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/view-fullscreen.svg b/lisp/ui/icons/Numix/actions/view-fullscreen.svg new file mode 100644 index 000000000..47cc01c4d --- /dev/null +++ b/lisp/ui/icons/Numix/actions/view-fullscreen.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/view-refresh-symbolic.svg b/lisp/ui/icons/Numix/actions/view-refresh-symbolic.svg new file mode 100644 index 000000000..30ff6e81c --- /dev/null +++ b/lisp/ui/icons/Numix/actions/view-refresh-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/view-refresh.svg b/lisp/ui/icons/Numix/actions/view-refresh.svg new file mode 100644 index 000000000..1f25b3e66 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/view-refresh.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/view-restore-symbolic.svg b/lisp/ui/icons/Numix/actions/view-restore-symbolic.svg new file mode 100644 index 000000000..e6d2ab69b --- /dev/null +++ b/lisp/ui/icons/Numix/actions/view-restore-symbolic.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/view-restore.svg b/lisp/ui/icons/Numix/actions/view-restore.svg new file mode 100644 index 000000000..7147787c3 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/view-restore.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/view-sort-ascending-symbolic.svg b/lisp/ui/icons/Numix/actions/view-sort-ascending-symbolic.svg new file mode 100644 index 000000000..9e68557e2 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/view-sort-ascending-symbolic.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/view-sort-ascending.svg b/lisp/ui/icons/Numix/actions/view-sort-ascending.svg new file mode 100644 index 000000000..4310cdcda --- /dev/null +++ b/lisp/ui/icons/Numix/actions/view-sort-ascending.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/view-sort-descending-symbolic.svg b/lisp/ui/icons/Numix/actions/view-sort-descending-symbolic.svg new file mode 100644 index 000000000..4e31ac302 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/view-sort-descending-symbolic.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/view-sort-descending.svg b/lisp/ui/icons/Numix/actions/view-sort-descending.svg new file mode 100644 index 000000000..3c0739a54 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/view-sort-descending.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/window-close-symbolic.svg b/lisp/ui/icons/Numix/actions/window-close-symbolic.svg new file mode 100644 index 000000000..19aaf984b --- /dev/null +++ b/lisp/ui/icons/Numix/actions/window-close-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/window-close.svg b/lisp/ui/icons/Numix/actions/window-close.svg new file mode 100644 index 000000000..346f98dc1 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/window-close.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/actions/window-new.svg b/lisp/ui/icons/Numix/actions/window-new.svg new file mode 100644 index 000000000..bca57fa60 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/window-new.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/zoom-fit-best-symbolic.svg b/lisp/ui/icons/Numix/actions/zoom-fit-best-symbolic.svg new file mode 100644 index 000000000..0e57f8417 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/zoom-fit-best-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/zoom-fit-best.svg b/lisp/ui/icons/Numix/actions/zoom-fit-best.svg new file mode 100644 index 000000000..d628d3e15 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/zoom-fit-best.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/actions/zoom-in-symbolic.svg b/lisp/ui/icons/Numix/actions/zoom-in-symbolic.svg new file mode 100644 index 000000000..c217ab19e --- /dev/null +++ b/lisp/ui/icons/Numix/actions/zoom-in-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/zoom-in.svg b/lisp/ui/icons/Numix/actions/zoom-in.svg new file mode 100644 index 000000000..c45de7263 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/zoom-in.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/zoom-original-symbolic.svg b/lisp/ui/icons/Numix/actions/zoom-original-symbolic.svg new file mode 100644 index 000000000..db074b6b9 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/zoom-original-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/zoom-original.svg b/lisp/ui/icons/Numix/actions/zoom-original.svg new file mode 100644 index 000000000..d719d532c --- /dev/null +++ b/lisp/ui/icons/Numix/actions/zoom-original.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/actions/zoom-out-symbolic.svg b/lisp/ui/icons/Numix/actions/zoom-out-symbolic.svg new file mode 100644 index 000000000..49c202ace --- /dev/null +++ b/lisp/ui/icons/Numix/actions/zoom-out-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/actions/zoom-out.svg b/lisp/ui/icons/Numix/actions/zoom-out.svg new file mode 100644 index 000000000..394fa1b78 --- /dev/null +++ b/lisp/ui/icons/Numix/actions/zoom-out.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/categories/applications-accessories.svg b/lisp/ui/icons/Numix/categories/applications-accessories.svg new file mode 100644 index 000000000..2b3a46178 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-accessories.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/categories/applications-development.svg b/lisp/ui/icons/Numix/categories/applications-development.svg new file mode 100644 index 000000000..735c4dfd9 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-development.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/categories/applications-engineering-symbolic.svg b/lisp/ui/icons/Numix/categories/applications-engineering-symbolic.svg new file mode 100644 index 000000000..d293067f7 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-engineering-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/categories/applications-engineering.svg b/lisp/ui/icons/Numix/categories/applications-engineering.svg new file mode 100644 index 000000000..d40da6520 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-engineering.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/categories/applications-games-symbolic.svg b/lisp/ui/icons/Numix/categories/applications-games-symbolic.svg new file mode 100644 index 000000000..77d468f7f --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-games-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/categories/applications-games.svg b/lisp/ui/icons/Numix/categories/applications-games.svg new file mode 100644 index 000000000..d81bac234 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-games.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/categories/applications-graphics-symbolic.svg b/lisp/ui/icons/Numix/categories/applications-graphics-symbolic.svg new file mode 100644 index 000000000..f63d31668 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-graphics-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/categories/applications-graphics.svg b/lisp/ui/icons/Numix/categories/applications-graphics.svg new file mode 100644 index 000000000..804384700 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-graphics.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/categories/applications-internet-symbolic.svg b/lisp/ui/icons/Numix/categories/applications-internet-symbolic.svg new file mode 100644 index 000000000..a187f61b2 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-internet-symbolic.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/categories/applications-internet.svg b/lisp/ui/icons/Numix/categories/applications-internet.svg new file mode 100644 index 000000000..fe6bcb8f3 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-internet.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/categories/applications-multimedia-symbolic.svg b/lisp/ui/icons/Numix/categories/applications-multimedia-symbolic.svg new file mode 100644 index 000000000..4f5db60de --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-multimedia-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/categories/applications-multimedia.svg b/lisp/ui/icons/Numix/categories/applications-multimedia.svg new file mode 100644 index 000000000..6805a5099 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-multimedia.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/categories/applications-office.svg b/lisp/ui/icons/Numix/categories/applications-office.svg new file mode 100644 index 000000000..266f00d37 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-office.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/categories/applications-other.svg b/lisp/ui/icons/Numix/categories/applications-other.svg new file mode 100644 index 000000000..f030965ee --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-other.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/categories/applications-science-symbolic.svg b/lisp/ui/icons/Numix/categories/applications-science-symbolic.svg new file mode 100644 index 000000000..1d55ac6de --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-science-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/categories/applications-science.svg b/lisp/ui/icons/Numix/categories/applications-science.svg new file mode 100644 index 000000000..461bc9844 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-science.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/categories/applications-system-symbolic.svg b/lisp/ui/icons/Numix/categories/applications-system-symbolic.svg new file mode 100644 index 000000000..16533469b --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-system-symbolic.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/categories/applications-system.svg b/lisp/ui/icons/Numix/categories/applications-system.svg new file mode 100644 index 000000000..bcef355f8 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-system.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/categories/applications-utilities-symbolic.svg b/lisp/ui/icons/Numix/categories/applications-utilities-symbolic.svg new file mode 100644 index 000000000..60b868a5a --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-utilities-symbolic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/categories/applications-utilities.svg b/lisp/ui/icons/Numix/categories/applications-utilities.svg new file mode 100644 index 000000000..1d9711878 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/applications-utilities.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/categories/preferences-desktop-peripherals.svg b/lisp/ui/icons/Numix/categories/preferences-desktop-peripherals.svg new file mode 100644 index 000000000..00c43ec6e --- /dev/null +++ b/lisp/ui/icons/Numix/categories/preferences-desktop-peripherals.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/categories/preferences-desktop-personal.svg b/lisp/ui/icons/Numix/categories/preferences-desktop-personal.svg new file mode 100644 index 000000000..9f81f13e5 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/preferences-desktop-personal.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/categories/preferences-desktop.svg b/lisp/ui/icons/Numix/categories/preferences-desktop.svg new file mode 100644 index 000000000..9f81f13e5 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/preferences-desktop.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/categories/preferences-other-symbolic.svg b/lisp/ui/icons/Numix/categories/preferences-other-symbolic.svg new file mode 100644 index 000000000..59c78cda8 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/preferences-other-symbolic.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/categories/preferences-other.svg b/lisp/ui/icons/Numix/categories/preferences-other.svg new file mode 100644 index 000000000..f030965ee --- /dev/null +++ b/lisp/ui/icons/Numix/categories/preferences-other.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/categories/preferences-system-network.svg b/lisp/ui/icons/Numix/categories/preferences-system-network.svg new file mode 100644 index 000000000..1c75be9d8 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/preferences-system-network.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/categories/preferences-system-symbolic.svg b/lisp/ui/icons/Numix/categories/preferences-system-symbolic.svg new file mode 100644 index 000000000..92917a861 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/preferences-system-symbolic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/categories/preferences-system.svg b/lisp/ui/icons/Numix/categories/preferences-system.svg new file mode 100644 index 000000000..9f81f13e5 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/preferences-system.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/categories/system-help-symbolic.svg b/lisp/ui/icons/Numix/categories/system-help-symbolic.svg new file mode 100644 index 000000000..076518302 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/system-help-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/categories/system-help.svg b/lisp/ui/icons/Numix/categories/system-help.svg new file mode 100644 index 000000000..b7caec5f1 --- /dev/null +++ b/lisp/ui/icons/Numix/categories/system-help.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/devices/audio-card-symbolic.svg b/lisp/ui/icons/Numix/devices/audio-card-symbolic.svg new file mode 100644 index 000000000..9e367371f --- /dev/null +++ b/lisp/ui/icons/Numix/devices/audio-card-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/devices/audio-card.svg b/lisp/ui/icons/Numix/devices/audio-card.svg new file mode 100644 index 000000000..cc5bc2149 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/audio-card.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/audio-input-microphone-symbolic.svg b/lisp/ui/icons/Numix/devices/audio-input-microphone-symbolic.svg new file mode 100644 index 000000000..74d9b655a --- /dev/null +++ b/lisp/ui/icons/Numix/devices/audio-input-microphone-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/devices/audio-input-microphone.svg b/lisp/ui/icons/Numix/devices/audio-input-microphone.svg new file mode 100644 index 000000000..28c602c8a --- /dev/null +++ b/lisp/ui/icons/Numix/devices/audio-input-microphone.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/battery-symbolic.svg b/lisp/ui/icons/Numix/devices/battery-symbolic.svg new file mode 100644 index 000000000..0b0f1fbd7 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/battery-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/devices/battery.svg b/lisp/ui/icons/Numix/devices/battery.svg new file mode 100644 index 000000000..aad8c085f --- /dev/null +++ b/lisp/ui/icons/Numix/devices/battery.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/camera-photo-symbolic.svg b/lisp/ui/icons/Numix/devices/camera-photo-symbolic.svg new file mode 100644 index 000000000..5c17aff62 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/camera-photo-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/devices/camera-photo.svg b/lisp/ui/icons/Numix/devices/camera-photo.svg new file mode 100644 index 000000000..98ea59558 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/camera-photo.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/camera-video-symbolic.svg b/lisp/ui/icons/Numix/devices/camera-video-symbolic.svg new file mode 100644 index 000000000..69c7caaf2 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/camera-video-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/camera-web-symbolic.svg b/lisp/ui/icons/Numix/devices/camera-web-symbolic.svg new file mode 100644 index 000000000..03efc7238 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/camera-web-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/devices/camera-web.svg b/lisp/ui/icons/Numix/devices/camera-web.svg new file mode 100644 index 000000000..c3d9e5670 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/camera-web.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/computer-symbolic.svg b/lisp/ui/icons/Numix/devices/computer-symbolic.svg new file mode 100644 index 000000000..b3b8cc0d8 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/computer-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/devices/computer.svg b/lisp/ui/icons/Numix/devices/computer.svg new file mode 100644 index 000000000..309e325e7 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/computer.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/devices/drive-harddisk-symbolic.svg b/lisp/ui/icons/Numix/devices/drive-harddisk-symbolic.svg new file mode 100644 index 000000000..a5a5cf118 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/drive-harddisk-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/devices/drive-harddisk.svg b/lisp/ui/icons/Numix/devices/drive-harddisk.svg new file mode 100644 index 000000000..3897d77cd --- /dev/null +++ b/lisp/ui/icons/Numix/devices/drive-harddisk.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/drive-optical-symbolic.svg b/lisp/ui/icons/Numix/devices/drive-optical-symbolic.svg new file mode 100644 index 000000000..1b520245e --- /dev/null +++ b/lisp/ui/icons/Numix/devices/drive-optical-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/devices/drive-optical.svg b/lisp/ui/icons/Numix/devices/drive-optical.svg new file mode 100644 index 000000000..991d2278c --- /dev/null +++ b/lisp/ui/icons/Numix/devices/drive-optical.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/drive-removable-media.svg b/lisp/ui/icons/Numix/devices/drive-removable-media.svg new file mode 100644 index 000000000..551350c66 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/drive-removable-media.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/input-gaming-symbolic.svg b/lisp/ui/icons/Numix/devices/input-gaming-symbolic.svg new file mode 100644 index 000000000..85ea55075 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/input-gaming-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/devices/input-gaming.svg b/lisp/ui/icons/Numix/devices/input-gaming.svg new file mode 100644 index 000000000..71242965c --- /dev/null +++ b/lisp/ui/icons/Numix/devices/input-gaming.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/input-keyboard-symbolic.svg b/lisp/ui/icons/Numix/devices/input-keyboard-symbolic.svg new file mode 100644 index 000000000..9aaaad8f7 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/input-keyboard-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/devices/input-keyboard.svg b/lisp/ui/icons/Numix/devices/input-keyboard.svg new file mode 100644 index 000000000..5d2383471 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/input-keyboard.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/input-mouse-symbolic.svg b/lisp/ui/icons/Numix/devices/input-mouse-symbolic.svg new file mode 100644 index 000000000..d863ddd2a --- /dev/null +++ b/lisp/ui/icons/Numix/devices/input-mouse-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/devices/input-mouse.svg b/lisp/ui/icons/Numix/devices/input-mouse.svg new file mode 100644 index 000000000..15204e706 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/input-mouse.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/input-tablet-symbolic.svg b/lisp/ui/icons/Numix/devices/input-tablet-symbolic.svg new file mode 100644 index 000000000..f5dd40209 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/input-tablet-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/devices/input-tablet.svg b/lisp/ui/icons/Numix/devices/input-tablet.svg new file mode 100644 index 000000000..a9f1a9002 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/input-tablet.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/media-flash-symbolic.svg b/lisp/ui/icons/Numix/devices/media-flash-symbolic.svg new file mode 100644 index 000000000..adbd58511 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/media-flash-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/devices/media-flash.svg b/lisp/ui/icons/Numix/devices/media-flash.svg new file mode 100644 index 000000000..9a53b1a22 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/media-flash.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/media-floppy-symbolic.svg b/lisp/ui/icons/Numix/devices/media-floppy-symbolic.svg new file mode 100644 index 000000000..f04e31a83 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/media-floppy-symbolic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/media-floppy.svg b/lisp/ui/icons/Numix/devices/media-floppy.svg new file mode 100644 index 000000000..f5284ad0d --- /dev/null +++ b/lisp/ui/icons/Numix/devices/media-floppy.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/media-optical-symbolic.svg b/lisp/ui/icons/Numix/devices/media-optical-symbolic.svg new file mode 100644 index 000000000..f3c1d61cb --- /dev/null +++ b/lisp/ui/icons/Numix/devices/media-optical-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/devices/media-optical.svg b/lisp/ui/icons/Numix/devices/media-optical.svg new file mode 100644 index 000000000..b50ebc5dd --- /dev/null +++ b/lisp/ui/icons/Numix/devices/media-optical.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/media-tape-symbolic.svg b/lisp/ui/icons/Numix/devices/media-tape-symbolic.svg new file mode 100644 index 000000000..38b9a9ceb --- /dev/null +++ b/lisp/ui/icons/Numix/devices/media-tape-symbolic.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/media-tape.svg b/lisp/ui/icons/Numix/devices/media-tape.svg new file mode 100644 index 000000000..8c55c9f09 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/media-tape.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/multimedia-player-symbolic.svg b/lisp/ui/icons/Numix/devices/multimedia-player-symbolic.svg new file mode 100644 index 000000000..4a9606d1a --- /dev/null +++ b/lisp/ui/icons/Numix/devices/multimedia-player-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/devices/multimedia-player.svg b/lisp/ui/icons/Numix/devices/multimedia-player.svg new file mode 100644 index 000000000..589923784 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/multimedia-player.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/network-wired.svg b/lisp/ui/icons/Numix/devices/network-wired.svg new file mode 100644 index 000000000..0d385b849 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/network-wired.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/network-wireless.svg b/lisp/ui/icons/Numix/devices/network-wireless.svg new file mode 100644 index 000000000..6e47dea66 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/network-wireless.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/pda-symbolic.svg b/lisp/ui/icons/Numix/devices/pda-symbolic.svg new file mode 100644 index 000000000..51240de16 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/pda-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/devices/pda.svg b/lisp/ui/icons/Numix/devices/pda.svg new file mode 100644 index 000000000..885c8b30b --- /dev/null +++ b/lisp/ui/icons/Numix/devices/pda.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/phone-symbolic.svg b/lisp/ui/icons/Numix/devices/phone-symbolic.svg new file mode 100644 index 000000000..a748502d5 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/phone-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/devices/phone.svg b/lisp/ui/icons/Numix/devices/phone.svg new file mode 100644 index 000000000..99973a9e6 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/phone.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/printer-symbolic.svg b/lisp/ui/icons/Numix/devices/printer-symbolic.svg new file mode 100644 index 000000000..24bd8b785 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/printer-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/devices/printer.svg b/lisp/ui/icons/Numix/devices/printer.svg new file mode 100644 index 000000000..7a95b35f9 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/printer.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/scanner-symbolic.svg b/lisp/ui/icons/Numix/devices/scanner-symbolic.svg new file mode 100644 index 000000000..d81fdd12f --- /dev/null +++ b/lisp/ui/icons/Numix/devices/scanner-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/scanner.svg b/lisp/ui/icons/Numix/devices/scanner.svg new file mode 100644 index 000000000..eab92e10a --- /dev/null +++ b/lisp/ui/icons/Numix/devices/scanner.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/devices/video-display-symbolic.svg b/lisp/ui/icons/Numix/devices/video-display-symbolic.svg new file mode 100644 index 000000000..9277fd7a2 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/video-display-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/devices/video-display.svg b/lisp/ui/icons/Numix/devices/video-display.svg new file mode 100644 index 000000000..b658b1188 --- /dev/null +++ b/lisp/ui/icons/Numix/devices/video-display.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-default-symbolic.svg b/lisp/ui/icons/Numix/emblems/emblem-default-symbolic.svg new file mode 100644 index 000000000..2db329928 --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-default-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-default.svg b/lisp/ui/icons/Numix/emblems/emblem-default.svg new file mode 100644 index 000000000..0238a1a0f --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-default.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-documents-symbolic.svg b/lisp/ui/icons/Numix/emblems/emblem-documents-symbolic.svg new file mode 100644 index 000000000..1aa3355da --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-documents-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-documents.svg b/lisp/ui/icons/Numix/emblems/emblem-documents.svg new file mode 100644 index 000000000..9bcd799b8 --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-documents.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-downloads.svg b/lisp/ui/icons/Numix/emblems/emblem-downloads.svg new file mode 100644 index 000000000..fbbd5bb7d --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-downloads.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-favorite-symbolic.svg b/lisp/ui/icons/Numix/emblems/emblem-favorite-symbolic.svg new file mode 100644 index 000000000..1502b2f4f --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-favorite-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-favorite.svg b/lisp/ui/icons/Numix/emblems/emblem-favorite.svg new file mode 100644 index 000000000..b130037f9 --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-favorite.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-important-symbolic.svg b/lisp/ui/icons/Numix/emblems/emblem-important-symbolic.svg new file mode 100644 index 000000000..93b4b3134 --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-important-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-important.svg b/lisp/ui/icons/Numix/emblems/emblem-important.svg new file mode 100644 index 000000000..7e6c8e3ef --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-important.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-mail.svg b/lisp/ui/icons/Numix/emblems/emblem-mail.svg new file mode 100644 index 000000000..092750930 --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-mail.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-photos-symbolic.svg b/lisp/ui/icons/Numix/emblems/emblem-photos-symbolic.svg new file mode 100644 index 000000000..765c938b8 --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-photos-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-photos.svg b/lisp/ui/icons/Numix/emblems/emblem-photos.svg new file mode 100644 index 000000000..e92b99014 --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-photos.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-readonly.svg b/lisp/ui/icons/Numix/emblems/emblem-readonly.svg new file mode 100644 index 000000000..78e94a96d --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-readonly.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-shared-symbolic.svg b/lisp/ui/icons/Numix/emblems/emblem-shared-symbolic.svg new file mode 100644 index 000000000..894a54ed3 --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-shared-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-shared.svg b/lisp/ui/icons/Numix/emblems/emblem-shared.svg new file mode 100644 index 000000000..02f5c15c4 --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-shared.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-symbolic-link.svg b/lisp/ui/icons/Numix/emblems/emblem-symbolic-link.svg new file mode 100644 index 000000000..ca9292f13 --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-symbolic-link.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-system-symbolic.svg b/lisp/ui/icons/Numix/emblems/emblem-system-symbolic.svg new file mode 100644 index 000000000..e72a7bd5d --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-system-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-system.svg b/lisp/ui/icons/Numix/emblems/emblem-system.svg new file mode 100644 index 000000000..c76caab8a --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-system.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/emblems/emblem-unreadable.svg b/lisp/ui/icons/Numix/emblems/emblem-unreadable.svg new file mode 100644 index 000000000..a7b32f915 --- /dev/null +++ b/lisp/ui/icons/Numix/emblems/emblem-unreadable.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/index.theme b/lisp/ui/icons/Numix/index.theme index 72b9b880e..e42458c4f 100644 --- a/lisp/ui/icons/Numix/index.theme +++ b/lisp/ui/icons/Numix/index.theme @@ -1,45 +1,45 @@ [Icon Theme] Name=Numix -Comment=Numix icon theme +Comment=Icon theme from the Numix project Inherits=hicolor -Directories=standard/actions,standard/categories,standard/devices,standard/emblems,standard/places,standard/status +Directories=actions,categories,devices,emblems,places,status -[standard/actions] +[actions] Size=16 MinSize=16 MaxSize=256 Context=Actions Type=Scalable -[standard/categories] +[categories] Size=16 MinSize=16 MaxSize=256 Context=Categories Type=Scalable -[standard/devices] +[devices] Size=16 MinSize=16 MaxSize=256 Context=Devices Type=Scalable -[standard/emblems] +[emblems] Size=16 MinSize=16 MaxSize=256 Context=Emblems Type=Scalable -[standard/places] +[places] Size=16 MinSize=16 MaxSize=256 Context=Places Type=Scalable -[standard/status] +[status] Size=16 MinSize=16 MaxSize=256 diff --git a/lisp/ui/icons/Numix/places/folder-remote.svg b/lisp/ui/icons/Numix/places/folder-remote.svg new file mode 100644 index 000000000..0da3703ff --- /dev/null +++ b/lisp/ui/icons/Numix/places/folder-remote.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/places/folder-symbolic.svg b/lisp/ui/icons/Numix/places/folder-symbolic.svg new file mode 100644 index 000000000..7f4165d93 --- /dev/null +++ b/lisp/ui/icons/Numix/places/folder-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/places/folder.svg b/lisp/ui/icons/Numix/places/folder.svg new file mode 100644 index 000000000..4cd715e77 --- /dev/null +++ b/lisp/ui/icons/Numix/places/folder.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/places/network-server-symbolic.svg b/lisp/ui/icons/Numix/places/network-server-symbolic.svg new file mode 100644 index 000000000..1bad42040 --- /dev/null +++ b/lisp/ui/icons/Numix/places/network-server-symbolic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/places/network-server.svg b/lisp/ui/icons/Numix/places/network-server.svg new file mode 100644 index 000000000..6141f7434 --- /dev/null +++ b/lisp/ui/icons/Numix/places/network-server.svg @@ -0,0 +1,182 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/places/network-workgroup-symbolic.svg b/lisp/ui/icons/Numix/places/network-workgroup-symbolic.svg new file mode 100644 index 000000000..c5807694a --- /dev/null +++ b/lisp/ui/icons/Numix/places/network-workgroup-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/places/network-workgroup.svg b/lisp/ui/icons/Numix/places/network-workgroup.svg new file mode 100644 index 000000000..0da3703ff --- /dev/null +++ b/lisp/ui/icons/Numix/places/network-workgroup.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/places/start-here-symbolic.svg b/lisp/ui/icons/Numix/places/start-here-symbolic.svg new file mode 100644 index 000000000..20ab91976 --- /dev/null +++ b/lisp/ui/icons/Numix/places/start-here-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/places/start-here.svg b/lisp/ui/icons/Numix/places/start-here.svg new file mode 100644 index 000000000..51cf0c449 --- /dev/null +++ b/lisp/ui/icons/Numix/places/start-here.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/places/user-bookmarks-symbolic.svg b/lisp/ui/icons/Numix/places/user-bookmarks-symbolic.svg new file mode 100644 index 000000000..e4f85c32c --- /dev/null +++ b/lisp/ui/icons/Numix/places/user-bookmarks-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/places/user-bookmarks.svg b/lisp/ui/icons/Numix/places/user-bookmarks.svg new file mode 100644 index 000000000..b94f6804d --- /dev/null +++ b/lisp/ui/icons/Numix/places/user-bookmarks.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/places/user-desktop-symbolic.svg b/lisp/ui/icons/Numix/places/user-desktop-symbolic.svg new file mode 100644 index 000000000..7b418973c --- /dev/null +++ b/lisp/ui/icons/Numix/places/user-desktop-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/places/user-desktop.svg b/lisp/ui/icons/Numix/places/user-desktop.svg new file mode 100644 index 000000000..259981a32 --- /dev/null +++ b/lisp/ui/icons/Numix/places/user-desktop.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/places/user-home-symbolic.svg b/lisp/ui/icons/Numix/places/user-home-symbolic.svg new file mode 100644 index 000000000..b468059bb --- /dev/null +++ b/lisp/ui/icons/Numix/places/user-home-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/places/user-home.svg b/lisp/ui/icons/Numix/places/user-home.svg new file mode 100644 index 000000000..e4d85073c --- /dev/null +++ b/lisp/ui/icons/Numix/places/user-home.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/places/user-trash-symbolic.svg b/lisp/ui/icons/Numix/places/user-trash-symbolic.svg new file mode 100644 index 000000000..fbc4ccfbd --- /dev/null +++ b/lisp/ui/icons/Numix/places/user-trash-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/places/user-trash.svg b/lisp/ui/icons/Numix/places/user-trash.svg new file mode 100644 index 000000000..03762c7c6 --- /dev/null +++ b/lisp/ui/icons/Numix/places/user-trash.svg @@ -0,0 +1,158 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/standard/actions/address-book-new.svg b/lisp/ui/icons/Numix/standard/actions/address-book-new.svg deleted file mode 100644 index 8e06a17cc..000000000 --- a/lisp/ui/icons/Numix/standard/actions/address-book-new.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/application-exit.svg b/lisp/ui/icons/Numix/standard/actions/application-exit.svg deleted file mode 100644 index 1dafcd22c..000000000 --- a/lisp/ui/icons/Numix/standard/actions/application-exit.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/appointment-new.svg b/lisp/ui/icons/Numix/standard/actions/appointment-new.svg deleted file mode 100644 index 4b1859dfc..000000000 --- a/lisp/ui/icons/Numix/standard/actions/appointment-new.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/call-start.svg b/lisp/ui/icons/Numix/standard/actions/call-start.svg deleted file mode 100644 index 319dce4c6..000000000 --- a/lisp/ui/icons/Numix/standard/actions/call-start.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/call-stop.svg b/lisp/ui/icons/Numix/standard/actions/call-stop.svg deleted file mode 100644 index c4652d69d..000000000 --- a/lisp/ui/icons/Numix/standard/actions/call-stop.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/contact-new.svg b/lisp/ui/icons/Numix/standard/actions/contact-new.svg deleted file mode 100644 index 452759c87..000000000 --- a/lisp/ui/icons/Numix/standard/actions/contact-new.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/document-new.svg b/lisp/ui/icons/Numix/standard/actions/document-new.svg deleted file mode 100644 index 596ca80b9..000000000 --- a/lisp/ui/icons/Numix/standard/actions/document-new.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/icons/Numix/standard/actions/document-open-recent.svg b/lisp/ui/icons/Numix/standard/actions/document-open-recent.svg deleted file mode 100644 index 33db3be93..000000000 --- a/lisp/ui/icons/Numix/standard/actions/document-open-recent.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/document-open.svg b/lisp/ui/icons/Numix/standard/actions/document-open.svg deleted file mode 100644 index 308e6895a..000000000 --- a/lisp/ui/icons/Numix/standard/actions/document-open.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/document-page-setup.svg b/lisp/ui/icons/Numix/standard/actions/document-page-setup.svg deleted file mode 100644 index e1ee2e83f..000000000 --- a/lisp/ui/icons/Numix/standard/actions/document-page-setup.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/document-print-preview.svg b/lisp/ui/icons/Numix/standard/actions/document-print-preview.svg deleted file mode 100644 index 56ca8ec4f..000000000 --- a/lisp/ui/icons/Numix/standard/actions/document-print-preview.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/document-print.svg b/lisp/ui/icons/Numix/standard/actions/document-print.svg deleted file mode 100644 index 61fde1473..000000000 --- a/lisp/ui/icons/Numix/standard/actions/document-print.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/document-properties.svg b/lisp/ui/icons/Numix/standard/actions/document-properties.svg deleted file mode 100644 index b2e3bbfa8..000000000 --- a/lisp/ui/icons/Numix/standard/actions/document-properties.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/document-revert.svg b/lisp/ui/icons/Numix/standard/actions/document-revert.svg deleted file mode 100644 index 3570a6a32..000000000 --- a/lisp/ui/icons/Numix/standard/actions/document-revert.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/document-save-as.svg b/lisp/ui/icons/Numix/standard/actions/document-save-as.svg deleted file mode 100644 index f8f1820ca..000000000 --- a/lisp/ui/icons/Numix/standard/actions/document-save-as.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/document-save.svg b/lisp/ui/icons/Numix/standard/actions/document-save.svg deleted file mode 100644 index 592fdf7bd..000000000 --- a/lisp/ui/icons/Numix/standard/actions/document-save.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/document-send.svg b/lisp/ui/icons/Numix/standard/actions/document-send.svg deleted file mode 100644 index 5b9eedccc..000000000 --- a/lisp/ui/icons/Numix/standard/actions/document-send.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/edit-clear.svg b/lisp/ui/icons/Numix/standard/actions/edit-clear.svg deleted file mode 100644 index ec4b69d4e..000000000 --- a/lisp/ui/icons/Numix/standard/actions/edit-clear.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/icons/Numix/standard/actions/edit-copy.svg b/lisp/ui/icons/Numix/standard/actions/edit-copy.svg deleted file mode 100644 index a1e989989..000000000 --- a/lisp/ui/icons/Numix/standard/actions/edit-copy.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/lisp/ui/icons/Numix/standard/actions/edit-cut.svg b/lisp/ui/icons/Numix/standard/actions/edit-cut.svg deleted file mode 100644 index 4c2e6486d..000000000 --- a/lisp/ui/icons/Numix/standard/actions/edit-cut.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/edit-delete.svg b/lisp/ui/icons/Numix/standard/actions/edit-delete.svg deleted file mode 100644 index f2523a11f..000000000 --- a/lisp/ui/icons/Numix/standard/actions/edit-delete.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/lisp/ui/icons/Numix/standard/actions/edit-find-replace.svg b/lisp/ui/icons/Numix/standard/actions/edit-find-replace.svg deleted file mode 100644 index d93cab944..000000000 --- a/lisp/ui/icons/Numix/standard/actions/edit-find-replace.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/edit-find.svg b/lisp/ui/icons/Numix/standard/actions/edit-find.svg deleted file mode 100644 index e333dab53..000000000 --- a/lisp/ui/icons/Numix/standard/actions/edit-find.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/edit-paste.svg b/lisp/ui/icons/Numix/standard/actions/edit-paste.svg deleted file mode 100644 index c81728041..000000000 --- a/lisp/ui/icons/Numix/standard/actions/edit-paste.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/edit-redo.svg b/lisp/ui/icons/Numix/standard/actions/edit-redo.svg deleted file mode 100644 index b5d5bb51c..000000000 --- a/lisp/ui/icons/Numix/standard/actions/edit-redo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/edit-select-all.svg b/lisp/ui/icons/Numix/standard/actions/edit-select-all.svg deleted file mode 100644 index bacbcc02c..000000000 --- a/lisp/ui/icons/Numix/standard/actions/edit-select-all.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/lisp/ui/icons/Numix/standard/actions/edit-undo.svg b/lisp/ui/icons/Numix/standard/actions/edit-undo.svg deleted file mode 100644 index 7a48060be..000000000 --- a/lisp/ui/icons/Numix/standard/actions/edit-undo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/folder-new.svg b/lisp/ui/icons/Numix/standard/actions/folder-new.svg deleted file mode 100644 index ed879babd..000000000 --- a/lisp/ui/icons/Numix/standard/actions/folder-new.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/format-indent-less.svg b/lisp/ui/icons/Numix/standard/actions/format-indent-less.svg deleted file mode 100644 index 417f9a8e6..000000000 --- a/lisp/ui/icons/Numix/standard/actions/format-indent-less.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/lisp/ui/icons/Numix/standard/actions/format-indent-more.svg b/lisp/ui/icons/Numix/standard/actions/format-indent-more.svg deleted file mode 100644 index a8ad4f0e8..000000000 --- a/lisp/ui/icons/Numix/standard/actions/format-indent-more.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/lisp/ui/icons/Numix/standard/actions/format-justify-right.svg b/lisp/ui/icons/Numix/standard/actions/format-justify-right.svg deleted file mode 100644 index 368e3190a..000000000 --- a/lisp/ui/icons/Numix/standard/actions/format-justify-right.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/icons/Numix/standard/actions/format-text-bold.svg b/lisp/ui/icons/Numix/standard/actions/format-text-bold.svg deleted file mode 100644 index 9bb172ec1..000000000 --- a/lisp/ui/icons/Numix/standard/actions/format-text-bold.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/format-text-direction-ltr.svg b/lisp/ui/icons/Numix/standard/actions/format-text-direction-ltr.svg deleted file mode 100644 index 650bb6d24..000000000 --- a/lisp/ui/icons/Numix/standard/actions/format-text-direction-ltr.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/format-text-direction-rtl.svg b/lisp/ui/icons/Numix/standard/actions/format-text-direction-rtl.svg deleted file mode 100644 index 514f97f1e..000000000 --- a/lisp/ui/icons/Numix/standard/actions/format-text-direction-rtl.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/format-text-italic.svg b/lisp/ui/icons/Numix/standard/actions/format-text-italic.svg deleted file mode 100644 index cedb7f8b1..000000000 --- a/lisp/ui/icons/Numix/standard/actions/format-text-italic.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/format-text-strikethrough.svg b/lisp/ui/icons/Numix/standard/actions/format-text-strikethrough.svg deleted file mode 100644 index 5e137df02..000000000 --- a/lisp/ui/icons/Numix/standard/actions/format-text-strikethrough.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/format-text-underline.svg b/lisp/ui/icons/Numix/standard/actions/format-text-underline.svg deleted file mode 100644 index c521b2274..000000000 --- a/lisp/ui/icons/Numix/standard/actions/format-text-underline.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/go-bottom.svg b/lisp/ui/icons/Numix/standard/actions/go-bottom.svg deleted file mode 100644 index f698ce1ec..000000000 --- a/lisp/ui/icons/Numix/standard/actions/go-bottom.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/go-down.svg b/lisp/ui/icons/Numix/standard/actions/go-down.svg deleted file mode 100644 index 655ffc131..000000000 --- a/lisp/ui/icons/Numix/standard/actions/go-down.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/go-first.svg b/lisp/ui/icons/Numix/standard/actions/go-first.svg deleted file mode 100644 index 0fa7f3199..000000000 --- a/lisp/ui/icons/Numix/standard/actions/go-first.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/go-home.svg b/lisp/ui/icons/Numix/standard/actions/go-home.svg deleted file mode 100644 index f72be25f9..000000000 --- a/lisp/ui/icons/Numix/standard/actions/go-home.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/go-jump.svg b/lisp/ui/icons/Numix/standard/actions/go-jump.svg deleted file mode 100644 index d12b377f1..000000000 --- a/lisp/ui/icons/Numix/standard/actions/go-jump.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/go-last.svg b/lisp/ui/icons/Numix/standard/actions/go-last.svg deleted file mode 100644 index 6d84fbf06..000000000 --- a/lisp/ui/icons/Numix/standard/actions/go-last.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/go-next.svg b/lisp/ui/icons/Numix/standard/actions/go-next.svg deleted file mode 100644 index 139079a84..000000000 --- a/lisp/ui/icons/Numix/standard/actions/go-next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/go-previous.svg b/lisp/ui/icons/Numix/standard/actions/go-previous.svg deleted file mode 100644 index abc21f477..000000000 --- a/lisp/ui/icons/Numix/standard/actions/go-previous.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/go-top.svg b/lisp/ui/icons/Numix/standard/actions/go-top.svg deleted file mode 100644 index 2786269eb..000000000 --- a/lisp/ui/icons/Numix/standard/actions/go-top.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/go-up.svg b/lisp/ui/icons/Numix/standard/actions/go-up.svg deleted file mode 100644 index d42860214..000000000 --- a/lisp/ui/icons/Numix/standard/actions/go-up.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/help-about.svg b/lisp/ui/icons/Numix/standard/actions/help-about.svg deleted file mode 100644 index af8ba627c..000000000 --- a/lisp/ui/icons/Numix/standard/actions/help-about.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/help-contents.svg b/lisp/ui/icons/Numix/standard/actions/help-contents.svg deleted file mode 100644 index e136f442f..000000000 --- a/lisp/ui/icons/Numix/standard/actions/help-contents.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/help-faq.svg b/lisp/ui/icons/Numix/standard/actions/help-faq.svg deleted file mode 100644 index 91b58e7b4..000000000 --- a/lisp/ui/icons/Numix/standard/actions/help-faq.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/insert-image.svg b/lisp/ui/icons/Numix/standard/actions/insert-image.svg deleted file mode 100644 index 6fad8b8c7..000000000 --- a/lisp/ui/icons/Numix/standard/actions/insert-image.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/insert-link.svg b/lisp/ui/icons/Numix/standard/actions/insert-link.svg deleted file mode 100644 index 3ecce8ba2..000000000 --- a/lisp/ui/icons/Numix/standard/actions/insert-link.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/insert-object.svg b/lisp/ui/icons/Numix/standard/actions/insert-object.svg deleted file mode 100644 index cbe99e5ca..000000000 --- a/lisp/ui/icons/Numix/standard/actions/insert-object.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/insert-text.svg b/lisp/ui/icons/Numix/standard/actions/insert-text.svg deleted file mode 100644 index 7031e6c8c..000000000 --- a/lisp/ui/icons/Numix/standard/actions/insert-text.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/list-add.svg b/lisp/ui/icons/Numix/standard/actions/list-add.svg deleted file mode 100644 index 0c8011d87..000000000 --- a/lisp/ui/icons/Numix/standard/actions/list-add.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/list-remove.svg b/lisp/ui/icons/Numix/standard/actions/list-remove.svg deleted file mode 100644 index 02b865e30..000000000 --- a/lisp/ui/icons/Numix/standard/actions/list-remove.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/mail-forward.svg b/lisp/ui/icons/Numix/standard/actions/mail-forward.svg deleted file mode 100644 index a067ebae8..000000000 --- a/lisp/ui/icons/Numix/standard/actions/mail-forward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/mail-mark-important.svg b/lisp/ui/icons/Numix/standard/actions/mail-mark-important.svg deleted file mode 100644 index 3557071ae..000000000 --- a/lisp/ui/icons/Numix/standard/actions/mail-mark-important.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/mail-mark-junk.svg b/lisp/ui/icons/Numix/standard/actions/mail-mark-junk.svg deleted file mode 100644 index 04bb0e223..000000000 --- a/lisp/ui/icons/Numix/standard/actions/mail-mark-junk.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/mail-mark-notjunk.svg b/lisp/ui/icons/Numix/standard/actions/mail-mark-notjunk.svg deleted file mode 100644 index d06a7eb1a..000000000 --- a/lisp/ui/icons/Numix/standard/actions/mail-mark-notjunk.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/mail-mark-read.svg b/lisp/ui/icons/Numix/standard/actions/mail-mark-read.svg deleted file mode 100644 index 08daa6c68..000000000 --- a/lisp/ui/icons/Numix/standard/actions/mail-mark-read.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/mail-mark-unread.svg b/lisp/ui/icons/Numix/standard/actions/mail-mark-unread.svg deleted file mode 100644 index eb5f0ed18..000000000 --- a/lisp/ui/icons/Numix/standard/actions/mail-mark-unread.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/mail-message-new.svg b/lisp/ui/icons/Numix/standard/actions/mail-message-new.svg deleted file mode 100644 index 10a59f192..000000000 --- a/lisp/ui/icons/Numix/standard/actions/mail-message-new.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/mail-reply-all.svg b/lisp/ui/icons/Numix/standard/actions/mail-reply-all.svg deleted file mode 100644 index f4c2d1d7a..000000000 --- a/lisp/ui/icons/Numix/standard/actions/mail-reply-all.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/mail-reply-sender.svg b/lisp/ui/icons/Numix/standard/actions/mail-reply-sender.svg deleted file mode 100644 index c9363fa47..000000000 --- a/lisp/ui/icons/Numix/standard/actions/mail-reply-sender.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/mail-send-receive.svg b/lisp/ui/icons/Numix/standard/actions/mail-send-receive.svg deleted file mode 100644 index 14c6ac5ec..000000000 --- a/lisp/ui/icons/Numix/standard/actions/mail-send-receive.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/mail-send.svg b/lisp/ui/icons/Numix/standard/actions/mail-send.svg deleted file mode 100644 index ff6842349..000000000 --- a/lisp/ui/icons/Numix/standard/actions/mail-send.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/lisp/ui/icons/Numix/standard/actions/media-eject.svg b/lisp/ui/icons/Numix/standard/actions/media-eject.svg deleted file mode 100644 index 533c153d5..000000000 --- a/lisp/ui/icons/Numix/standard/actions/media-eject.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/media-playback-pause.svg b/lisp/ui/icons/Numix/standard/actions/media-playback-pause.svg deleted file mode 100644 index dd8ef7a01..000000000 --- a/lisp/ui/icons/Numix/standard/actions/media-playback-pause.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/media-playback-start.svg b/lisp/ui/icons/Numix/standard/actions/media-playback-start.svg deleted file mode 100644 index f254b810e..000000000 --- a/lisp/ui/icons/Numix/standard/actions/media-playback-start.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/media-playback-stop.svg b/lisp/ui/icons/Numix/standard/actions/media-playback-stop.svg deleted file mode 100644 index 1fbd09493..000000000 --- a/lisp/ui/icons/Numix/standard/actions/media-playback-stop.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/media-record.svg b/lisp/ui/icons/Numix/standard/actions/media-record.svg deleted file mode 100644 index 46bc5f865..000000000 --- a/lisp/ui/icons/Numix/standard/actions/media-record.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/media-seek-backward.svg b/lisp/ui/icons/Numix/standard/actions/media-seek-backward.svg deleted file mode 100644 index 606a13a99..000000000 --- a/lisp/ui/icons/Numix/standard/actions/media-seek-backward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/media-seek-forward.svg b/lisp/ui/icons/Numix/standard/actions/media-seek-forward.svg deleted file mode 100644 index 87c646a54..000000000 --- a/lisp/ui/icons/Numix/standard/actions/media-seek-forward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/media-skip-backward.svg b/lisp/ui/icons/Numix/standard/actions/media-skip-backward.svg deleted file mode 100644 index 1f1eca67d..000000000 --- a/lisp/ui/icons/Numix/standard/actions/media-skip-backward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/media-skip-forward.svg b/lisp/ui/icons/Numix/standard/actions/media-skip-forward.svg deleted file mode 100644 index a12a6a81f..000000000 --- a/lisp/ui/icons/Numix/standard/actions/media-skip-forward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/object-flip-horizontal.svg b/lisp/ui/icons/Numix/standard/actions/object-flip-horizontal.svg deleted file mode 100644 index 0a5d57f94..000000000 --- a/lisp/ui/icons/Numix/standard/actions/object-flip-horizontal.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/object-flip-vertical.svg b/lisp/ui/icons/Numix/standard/actions/object-flip-vertical.svg deleted file mode 100644 index 9e750ea00..000000000 --- a/lisp/ui/icons/Numix/standard/actions/object-flip-vertical.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/object-rotate-left.svg b/lisp/ui/icons/Numix/standard/actions/object-rotate-left.svg deleted file mode 100644 index 486d1d72e..000000000 --- a/lisp/ui/icons/Numix/standard/actions/object-rotate-left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/icons/Numix/standard/actions/object-rotate-right.svg b/lisp/ui/icons/Numix/standard/actions/object-rotate-right.svg deleted file mode 100644 index 6fc43f9be..000000000 --- a/lisp/ui/icons/Numix/standard/actions/object-rotate-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/process-stop.svg b/lisp/ui/icons/Numix/standard/actions/process-stop.svg deleted file mode 100644 index d9407c7c4..000000000 --- a/lisp/ui/icons/Numix/standard/actions/process-stop.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/system-lock-screen.svg b/lisp/ui/icons/Numix/standard/actions/system-lock-screen.svg deleted file mode 100644 index f856055e4..000000000 --- a/lisp/ui/icons/Numix/standard/actions/system-lock-screen.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/system-log-out.svg b/lisp/ui/icons/Numix/standard/actions/system-log-out.svg deleted file mode 100644 index 4a0c976c7..000000000 --- a/lisp/ui/icons/Numix/standard/actions/system-log-out.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/system-reboot.svg b/lisp/ui/icons/Numix/standard/actions/system-reboot.svg deleted file mode 100644 index fb4e9a7d6..000000000 --- a/lisp/ui/icons/Numix/standard/actions/system-reboot.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/icons/Numix/standard/actions/system-run.svg b/lisp/ui/icons/Numix/standard/actions/system-run.svg deleted file mode 100644 index d996c78ad..000000000 --- a/lisp/ui/icons/Numix/standard/actions/system-run.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/system-search.svg b/lisp/ui/icons/Numix/standard/actions/system-search.svg deleted file mode 100644 index e333dab53..000000000 --- a/lisp/ui/icons/Numix/standard/actions/system-search.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/system-shutdown.svg b/lisp/ui/icons/Numix/standard/actions/system-shutdown.svg deleted file mode 100644 index 1dafcd22c..000000000 --- a/lisp/ui/icons/Numix/standard/actions/system-shutdown.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/tools-check-spelling.svg b/lisp/ui/icons/Numix/standard/actions/tools-check-spelling.svg deleted file mode 100644 index a8685f9d1..000000000 --- a/lisp/ui/icons/Numix/standard/actions/tools-check-spelling.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/view-fullscreen.svg b/lisp/ui/icons/Numix/standard/actions/view-fullscreen.svg deleted file mode 100644 index 9f7771ea1..000000000 --- a/lisp/ui/icons/Numix/standard/actions/view-fullscreen.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/view-refresh.svg b/lisp/ui/icons/Numix/standard/actions/view-refresh.svg deleted file mode 100644 index 2018e5d7e..000000000 --- a/lisp/ui/icons/Numix/standard/actions/view-refresh.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/lisp/ui/icons/Numix/standard/actions/view-restore.svg b/lisp/ui/icons/Numix/standard/actions/view-restore.svg deleted file mode 100644 index 33320e2ec..000000000 --- a/lisp/ui/icons/Numix/standard/actions/view-restore.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/view-sort-ascending.svg b/lisp/ui/icons/Numix/standard/actions/view-sort-ascending.svg deleted file mode 100644 index 02fbbb9a4..000000000 --- a/lisp/ui/icons/Numix/standard/actions/view-sort-ascending.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/view-sort-descending.svg b/lisp/ui/icons/Numix/standard/actions/view-sort-descending.svg deleted file mode 100644 index 9ef72a85b..000000000 --- a/lisp/ui/icons/Numix/standard/actions/view-sort-descending.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/window-close.svg b/lisp/ui/icons/Numix/standard/actions/window-close.svg deleted file mode 100644 index be0dd754c..000000000 --- a/lisp/ui/icons/Numix/standard/actions/window-close.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/window-new.svg b/lisp/ui/icons/Numix/standard/actions/window-new.svg deleted file mode 100644 index c330dbd42..000000000 --- a/lisp/ui/icons/Numix/standard/actions/window-new.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/zoom-fit-best.svg b/lisp/ui/icons/Numix/standard/actions/zoom-fit-best.svg deleted file mode 100644 index d1a083edd..000000000 --- a/lisp/ui/icons/Numix/standard/actions/zoom-fit-best.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/zoom-in.svg b/lisp/ui/icons/Numix/standard/actions/zoom-in.svg deleted file mode 100644 index 87245a1f5..000000000 --- a/lisp/ui/icons/Numix/standard/actions/zoom-in.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/zoom-original.svg b/lisp/ui/icons/Numix/standard/actions/zoom-original.svg deleted file mode 100644 index 11ecd1789..000000000 --- a/lisp/ui/icons/Numix/standard/actions/zoom-original.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/actions/zoom-out.svg b/lisp/ui/icons/Numix/standard/actions/zoom-out.svg deleted file mode 100644 index 688614f7b..000000000 --- a/lisp/ui/icons/Numix/standard/actions/zoom-out.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/applications-accessories.svg b/lisp/ui/icons/Numix/standard/categories/applications-accessories.svg deleted file mode 100644 index 97b46e8d9..000000000 --- a/lisp/ui/icons/Numix/standard/categories/applications-accessories.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/applications-development.svg b/lisp/ui/icons/Numix/standard/categories/applications-development.svg deleted file mode 100644 index 0bb494091..000000000 --- a/lisp/ui/icons/Numix/standard/categories/applications-development.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/applications-engineering.svg b/lisp/ui/icons/Numix/standard/categories/applications-engineering.svg deleted file mode 100644 index 8822a887a..000000000 --- a/lisp/ui/icons/Numix/standard/categories/applications-engineering.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/applications-games.svg b/lisp/ui/icons/Numix/standard/categories/applications-games.svg deleted file mode 100644 index 5e9b65f2f..000000000 --- a/lisp/ui/icons/Numix/standard/categories/applications-games.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/applications-graphics.svg b/lisp/ui/icons/Numix/standard/categories/applications-graphics.svg deleted file mode 100644 index a7f96aa5e..000000000 --- a/lisp/ui/icons/Numix/standard/categories/applications-graphics.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/applications-internet.svg b/lisp/ui/icons/Numix/standard/categories/applications-internet.svg deleted file mode 100644 index 7a0de6c9e..000000000 --- a/lisp/ui/icons/Numix/standard/categories/applications-internet.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/applications-multimedia.svg b/lisp/ui/icons/Numix/standard/categories/applications-multimedia.svg deleted file mode 100644 index 142aaa150..000000000 --- a/lisp/ui/icons/Numix/standard/categories/applications-multimedia.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/applications-office.svg b/lisp/ui/icons/Numix/standard/categories/applications-office.svg deleted file mode 100644 index 17559f4f5..000000000 --- a/lisp/ui/icons/Numix/standard/categories/applications-office.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/applications-other.svg b/lisp/ui/icons/Numix/standard/categories/applications-other.svg deleted file mode 100644 index 4ad4d2d32..000000000 --- a/lisp/ui/icons/Numix/standard/categories/applications-other.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/applications-science.svg b/lisp/ui/icons/Numix/standard/categories/applications-science.svg deleted file mode 100644 index 13607ad0d..000000000 --- a/lisp/ui/icons/Numix/standard/categories/applications-science.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/applications-system.svg b/lisp/ui/icons/Numix/standard/categories/applications-system.svg deleted file mode 100644 index bcc275cfe..000000000 --- a/lisp/ui/icons/Numix/standard/categories/applications-system.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/applications-utilities.svg b/lisp/ui/icons/Numix/standard/categories/applications-utilities.svg deleted file mode 100644 index 64dbb7302..000000000 --- a/lisp/ui/icons/Numix/standard/categories/applications-utilities.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/preferences-desktop-peripherals.svg b/lisp/ui/icons/Numix/standard/categories/preferences-desktop-peripherals.svg deleted file mode 100644 index 81dd4d96c..000000000 --- a/lisp/ui/icons/Numix/standard/categories/preferences-desktop-peripherals.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/preferences-desktop-personal.svg b/lisp/ui/icons/Numix/standard/categories/preferences-desktop-personal.svg deleted file mode 100644 index 5d1228ff7..000000000 --- a/lisp/ui/icons/Numix/standard/categories/preferences-desktop-personal.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/preferences-desktop.svg b/lisp/ui/icons/Numix/standard/categories/preferences-desktop.svg deleted file mode 100644 index 5d1228ff7..000000000 --- a/lisp/ui/icons/Numix/standard/categories/preferences-desktop.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/preferences-other.svg b/lisp/ui/icons/Numix/standard/categories/preferences-other.svg deleted file mode 100644 index 4ad4d2d32..000000000 --- a/lisp/ui/icons/Numix/standard/categories/preferences-other.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/preferences-system-network.svg b/lisp/ui/icons/Numix/standard/categories/preferences-system-network.svg deleted file mode 100644 index d9e5ce647..000000000 --- a/lisp/ui/icons/Numix/standard/categories/preferences-system-network.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/preferences-system.svg b/lisp/ui/icons/Numix/standard/categories/preferences-system.svg deleted file mode 100644 index 5d1228ff7..000000000 --- a/lisp/ui/icons/Numix/standard/categories/preferences-system.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/categories/system-help.svg b/lisp/ui/icons/Numix/standard/categories/system-help.svg deleted file mode 100644 index 3c43308a8..000000000 --- a/lisp/ui/icons/Numix/standard/categories/system-help.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/audio-card.svg b/lisp/ui/icons/Numix/standard/devices/audio-card.svg deleted file mode 100644 index 876dbb319..000000000 --- a/lisp/ui/icons/Numix/standard/devices/audio-card.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/audio-input-microphone.svg b/lisp/ui/icons/Numix/standard/devices/audio-input-microphone.svg deleted file mode 100644 index ea9b4f657..000000000 --- a/lisp/ui/icons/Numix/standard/devices/audio-input-microphone.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/battery.svg b/lisp/ui/icons/Numix/standard/devices/battery.svg deleted file mode 100644 index aa8c2aa25..000000000 --- a/lisp/ui/icons/Numix/standard/devices/battery.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/camera-photo.svg b/lisp/ui/icons/Numix/standard/devices/camera-photo.svg deleted file mode 100644 index 4226388f2..000000000 --- a/lisp/ui/icons/Numix/standard/devices/camera-photo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/camera-web.svg b/lisp/ui/icons/Numix/standard/devices/camera-web.svg deleted file mode 100644 index 4d54f5a2b..000000000 --- a/lisp/ui/icons/Numix/standard/devices/camera-web.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/computer.svg b/lisp/ui/icons/Numix/standard/devices/computer.svg deleted file mode 100644 index 02b956c56..000000000 --- a/lisp/ui/icons/Numix/standard/devices/computer.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/drive-harddisk.svg b/lisp/ui/icons/Numix/standard/devices/drive-harddisk.svg deleted file mode 100644 index 939ff085f..000000000 --- a/lisp/ui/icons/Numix/standard/devices/drive-harddisk.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/drive-optical.svg b/lisp/ui/icons/Numix/standard/devices/drive-optical.svg deleted file mode 100644 index fd2c35d85..000000000 --- a/lisp/ui/icons/Numix/standard/devices/drive-optical.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/drive-removable-media.svg b/lisp/ui/icons/Numix/standard/devices/drive-removable-media.svg deleted file mode 100644 index 4b6d1c06d..000000000 --- a/lisp/ui/icons/Numix/standard/devices/drive-removable-media.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/input-gaming.svg b/lisp/ui/icons/Numix/standard/devices/input-gaming.svg deleted file mode 100644 index 1dd6581a9..000000000 --- a/lisp/ui/icons/Numix/standard/devices/input-gaming.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/input-keyboard.svg b/lisp/ui/icons/Numix/standard/devices/input-keyboard.svg deleted file mode 100644 index 115082c18..000000000 --- a/lisp/ui/icons/Numix/standard/devices/input-keyboard.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/input-mouse.svg b/lisp/ui/icons/Numix/standard/devices/input-mouse.svg deleted file mode 100644 index e92e29254..000000000 --- a/lisp/ui/icons/Numix/standard/devices/input-mouse.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/input-tablet.svg b/lisp/ui/icons/Numix/standard/devices/input-tablet.svg deleted file mode 100644 index c4ffef2a1..000000000 --- a/lisp/ui/icons/Numix/standard/devices/input-tablet.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/media-flash.svg b/lisp/ui/icons/Numix/standard/devices/media-flash.svg deleted file mode 100644 index ad7f4c7e1..000000000 --- a/lisp/ui/icons/Numix/standard/devices/media-flash.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/media-floppy.svg b/lisp/ui/icons/Numix/standard/devices/media-floppy.svg deleted file mode 100644 index eb6dcd281..000000000 --- a/lisp/ui/icons/Numix/standard/devices/media-floppy.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/media-optical.svg b/lisp/ui/icons/Numix/standard/devices/media-optical.svg deleted file mode 100644 index 17f2b3e0b..000000000 --- a/lisp/ui/icons/Numix/standard/devices/media-optical.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/media-tape.svg b/lisp/ui/icons/Numix/standard/devices/media-tape.svg deleted file mode 100644 index d07590843..000000000 --- a/lisp/ui/icons/Numix/standard/devices/media-tape.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/multimedia-player.svg b/lisp/ui/icons/Numix/standard/devices/multimedia-player.svg deleted file mode 100644 index 1492931f0..000000000 --- a/lisp/ui/icons/Numix/standard/devices/multimedia-player.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/network-wired.svg b/lisp/ui/icons/Numix/standard/devices/network-wired.svg deleted file mode 100644 index bb488f06b..000000000 --- a/lisp/ui/icons/Numix/standard/devices/network-wired.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/network-wireless.svg b/lisp/ui/icons/Numix/standard/devices/network-wireless.svg deleted file mode 100644 index 6c28991f9..000000000 --- a/lisp/ui/icons/Numix/standard/devices/network-wireless.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/pda.svg b/lisp/ui/icons/Numix/standard/devices/pda.svg deleted file mode 100644 index 88a7a26d7..000000000 --- a/lisp/ui/icons/Numix/standard/devices/pda.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/phone.svg b/lisp/ui/icons/Numix/standard/devices/phone.svg deleted file mode 100644 index e89eb9e02..000000000 --- a/lisp/ui/icons/Numix/standard/devices/phone.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/printer.svg b/lisp/ui/icons/Numix/standard/devices/printer.svg deleted file mode 100644 index 21e9c780c..000000000 --- a/lisp/ui/icons/Numix/standard/devices/printer.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/scanner.svg b/lisp/ui/icons/Numix/standard/devices/scanner.svg deleted file mode 100644 index 8a89975d0..000000000 --- a/lisp/ui/icons/Numix/standard/devices/scanner.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/devices/video-display.svg b/lisp/ui/icons/Numix/standard/devices/video-display.svg deleted file mode 100644 index 7160ca3b9..000000000 --- a/lisp/ui/icons/Numix/standard/devices/video-display.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/emblems/emblem-default.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-default.svg deleted file mode 100644 index 0c14c861e..000000000 --- a/lisp/ui/icons/Numix/standard/emblems/emblem-default.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/emblems/emblem-documents.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-documents.svg deleted file mode 100644 index d9aaa6495..000000000 --- a/lisp/ui/icons/Numix/standard/emblems/emblem-documents.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/emblems/emblem-downloads.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-downloads.svg deleted file mode 100644 index d4ccc38d1..000000000 --- a/lisp/ui/icons/Numix/standard/emblems/emblem-downloads.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/emblems/emblem-favorite.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-favorite.svg deleted file mode 100644 index e5477990e..000000000 --- a/lisp/ui/icons/Numix/standard/emblems/emblem-favorite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/emblems/emblem-important.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-important.svg deleted file mode 100644 index b5495e372..000000000 --- a/lisp/ui/icons/Numix/standard/emblems/emblem-important.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/emblems/emblem-mail.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-mail.svg deleted file mode 100644 index d49b62cfd..000000000 --- a/lisp/ui/icons/Numix/standard/emblems/emblem-mail.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/emblems/emblem-photos.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-photos.svg deleted file mode 100644 index 4a0382fc3..000000000 --- a/lisp/ui/icons/Numix/standard/emblems/emblem-photos.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/emblems/emblem-readonly.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-readonly.svg deleted file mode 100644 index b2db2682a..000000000 --- a/lisp/ui/icons/Numix/standard/emblems/emblem-readonly.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/emblems/emblem-shared.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-shared.svg deleted file mode 100644 index 54ad73410..000000000 --- a/lisp/ui/icons/Numix/standard/emblems/emblem-shared.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/emblems/emblem-symbolic-link.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-symbolic-link.svg deleted file mode 100644 index f79139de9..000000000 --- a/lisp/ui/icons/Numix/standard/emblems/emblem-symbolic-link.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/emblems/emblem-system.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-system.svg deleted file mode 100644 index 550585a4e..000000000 --- a/lisp/ui/icons/Numix/standard/emblems/emblem-system.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/emblems/emblem-unreadable.svg b/lisp/ui/icons/Numix/standard/emblems/emblem-unreadable.svg deleted file mode 100644 index bbfaf22ee..000000000 --- a/lisp/ui/icons/Numix/standard/emblems/emblem-unreadable.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/places/folder-remote.svg b/lisp/ui/icons/Numix/standard/places/folder-remote.svg deleted file mode 100644 index ca16aebec..000000000 --- a/lisp/ui/icons/Numix/standard/places/folder-remote.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/places/folder.svg b/lisp/ui/icons/Numix/standard/places/folder.svg deleted file mode 100644 index 3d0f943ed..000000000 --- a/lisp/ui/icons/Numix/standard/places/folder.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/places/network-server.svg b/lisp/ui/icons/Numix/standard/places/network-server.svg deleted file mode 100644 index 416ec6651..000000000 --- a/lisp/ui/icons/Numix/standard/places/network-server.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/places/network-workgroup.svg b/lisp/ui/icons/Numix/standard/places/network-workgroup.svg deleted file mode 100644 index ca16aebec..000000000 --- a/lisp/ui/icons/Numix/standard/places/network-workgroup.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/places/start-here.svg b/lisp/ui/icons/Numix/standard/places/start-here.svg deleted file mode 100644 index 8954fd91b..000000000 --- a/lisp/ui/icons/Numix/standard/places/start-here.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/places/user-bookmarks.svg b/lisp/ui/icons/Numix/standard/places/user-bookmarks.svg deleted file mode 100644 index 55da9ae8e..000000000 --- a/lisp/ui/icons/Numix/standard/places/user-bookmarks.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/places/user-desktop.svg b/lisp/ui/icons/Numix/standard/places/user-desktop.svg deleted file mode 100644 index 5368e4f82..000000000 --- a/lisp/ui/icons/Numix/standard/places/user-desktop.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/places/user-home.svg b/lisp/ui/icons/Numix/standard/places/user-home.svg deleted file mode 100644 index 78bae8522..000000000 --- a/lisp/ui/icons/Numix/standard/places/user-home.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/places/user-trash.svg b/lisp/ui/icons/Numix/standard/places/user-trash.svg deleted file mode 100644 index ac3d1cc54..000000000 --- a/lisp/ui/icons/Numix/standard/places/user-trash.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/appointment-missed.svg b/lisp/ui/icons/Numix/standard/status/appointment-missed.svg deleted file mode 100644 index a33c69a0b..000000000 --- a/lisp/ui/icons/Numix/standard/status/appointment-missed.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/appointment-soon.svg b/lisp/ui/icons/Numix/standard/status/appointment-soon.svg deleted file mode 100644 index 2ff619cbe..000000000 --- a/lisp/ui/icons/Numix/standard/status/appointment-soon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/audio-volume-high.svg b/lisp/ui/icons/Numix/standard/status/audio-volume-high.svg deleted file mode 100644 index de890ace0..000000000 --- a/lisp/ui/icons/Numix/standard/status/audio-volume-high.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/audio-volume-low.svg b/lisp/ui/icons/Numix/standard/status/audio-volume-low.svg deleted file mode 100644 index 852d7822a..000000000 --- a/lisp/ui/icons/Numix/standard/status/audio-volume-low.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/audio-volume-medium.svg b/lisp/ui/icons/Numix/standard/status/audio-volume-medium.svg deleted file mode 100644 index e67619c11..000000000 --- a/lisp/ui/icons/Numix/standard/status/audio-volume-medium.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/audio-volume-muted.svg b/lisp/ui/icons/Numix/standard/status/audio-volume-muted.svg deleted file mode 100644 index e62e7dae0..000000000 --- a/lisp/ui/icons/Numix/standard/status/audio-volume-muted.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/battery-caution.svg b/lisp/ui/icons/Numix/standard/status/battery-caution.svg deleted file mode 100644 index e4a958aa2..000000000 --- a/lisp/ui/icons/Numix/standard/status/battery-caution.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/battery-low.svg b/lisp/ui/icons/Numix/standard/status/battery-low.svg deleted file mode 100644 index 176f09f4d..000000000 --- a/lisp/ui/icons/Numix/standard/status/battery-low.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/dialog-error.svg b/lisp/ui/icons/Numix/standard/status/dialog-error.svg deleted file mode 100644 index 42d3465f9..000000000 --- a/lisp/ui/icons/Numix/standard/status/dialog-error.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/dialog-information.svg b/lisp/ui/icons/Numix/standard/status/dialog-information.svg deleted file mode 100644 index 716b8b43f..000000000 --- a/lisp/ui/icons/Numix/standard/status/dialog-information.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/icons/Numix/standard/status/dialog-password.svg b/lisp/ui/icons/Numix/standard/status/dialog-password.svg deleted file mode 100644 index 32cbd1fad..000000000 --- a/lisp/ui/icons/Numix/standard/status/dialog-password.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/dialog-question.svg b/lisp/ui/icons/Numix/standard/status/dialog-question.svg deleted file mode 100644 index 2e42fbdf7..000000000 --- a/lisp/ui/icons/Numix/standard/status/dialog-question.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/icons/Numix/standard/status/dialog-warning.svg b/lisp/ui/icons/Numix/standard/status/dialog-warning.svg deleted file mode 100644 index a55b8eff4..000000000 --- a/lisp/ui/icons/Numix/standard/status/dialog-warning.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/image-missing.svg b/lisp/ui/icons/Numix/standard/status/image-missing.svg deleted file mode 100644 index 633b72bd3..000000000 --- a/lisp/ui/icons/Numix/standard/status/image-missing.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/icons/Numix/standard/status/network-error.svg b/lisp/ui/icons/Numix/standard/status/network-error.svg deleted file mode 100644 index a1beace45..000000000 --- a/lisp/ui/icons/Numix/standard/status/network-error.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/network-idle.svg b/lisp/ui/icons/Numix/standard/status/network-idle.svg deleted file mode 100644 index 1ca616a33..000000000 --- a/lisp/ui/icons/Numix/standard/status/network-idle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/network-offline.svg b/lisp/ui/icons/Numix/standard/status/network-offline.svg deleted file mode 100644 index 9e3fb8d1b..000000000 --- a/lisp/ui/icons/Numix/standard/status/network-offline.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/network-receive.svg b/lisp/ui/icons/Numix/standard/status/network-receive.svg deleted file mode 100644 index f97752a03..000000000 --- a/lisp/ui/icons/Numix/standard/status/network-receive.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/network-transmit-receive.svg b/lisp/ui/icons/Numix/standard/status/network-transmit-receive.svg deleted file mode 100644 index 1ca616a33..000000000 --- a/lisp/ui/icons/Numix/standard/status/network-transmit-receive.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/network-transmit.svg b/lisp/ui/icons/Numix/standard/status/network-transmit.svg deleted file mode 100644 index 5cfd3ec75..000000000 --- a/lisp/ui/icons/Numix/standard/status/network-transmit.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/printer-printing.svg b/lisp/ui/icons/Numix/standard/status/printer-printing.svg deleted file mode 100644 index 6b1e0f46c..000000000 --- a/lisp/ui/icons/Numix/standard/status/printer-printing.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/security-high.svg b/lisp/ui/icons/Numix/standard/status/security-high.svg deleted file mode 100644 index a41eb9e54..000000000 --- a/lisp/ui/icons/Numix/standard/status/security-high.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/security-low.svg b/lisp/ui/icons/Numix/standard/status/security-low.svg deleted file mode 100644 index 85a523ac6..000000000 --- a/lisp/ui/icons/Numix/standard/status/security-low.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/security-medium.svg b/lisp/ui/icons/Numix/standard/status/security-medium.svg deleted file mode 100644 index e7f083229..000000000 --- a/lisp/ui/icons/Numix/standard/status/security-medium.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/task-due.svg b/lisp/ui/icons/Numix/standard/status/task-due.svg deleted file mode 100644 index 504b9c7b4..000000000 --- a/lisp/ui/icons/Numix/standard/status/task-due.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/task-past-due.svg b/lisp/ui/icons/Numix/standard/status/task-past-due.svg deleted file mode 100644 index c94993907..000000000 --- a/lisp/ui/icons/Numix/standard/status/task-past-due.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/user-available.svg b/lisp/ui/icons/Numix/standard/status/user-available.svg deleted file mode 100644 index 7cc310098..000000000 --- a/lisp/ui/icons/Numix/standard/status/user-available.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/user-away.svg b/lisp/ui/icons/Numix/standard/status/user-away.svg deleted file mode 100644 index f28b02e55..000000000 --- a/lisp/ui/icons/Numix/standard/status/user-away.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/user-idle.svg b/lisp/ui/icons/Numix/standard/status/user-idle.svg deleted file mode 100644 index f28b02e55..000000000 --- a/lisp/ui/icons/Numix/standard/status/user-idle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/standard/status/user-offline.svg b/lisp/ui/icons/Numix/standard/status/user-offline.svg deleted file mode 100644 index c3e1ab34b..000000000 --- a/lisp/ui/icons/Numix/standard/status/user-offline.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Numix/status/appointment-missed-symbolic.svg b/lisp/ui/icons/Numix/status/appointment-missed-symbolic.svg new file mode 100644 index 000000000..39e0c6d65 --- /dev/null +++ b/lisp/ui/icons/Numix/status/appointment-missed-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/status/appointment-missed.svg b/lisp/ui/icons/Numix/status/appointment-missed.svg new file mode 100644 index 000000000..9d576d70c --- /dev/null +++ b/lisp/ui/icons/Numix/status/appointment-missed.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/status/appointment-soon-symbolic.svg b/lisp/ui/icons/Numix/status/appointment-soon-symbolic.svg new file mode 100644 index 000000000..c42c2c566 --- /dev/null +++ b/lisp/ui/icons/Numix/status/appointment-soon-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/status/appointment-soon.svg b/lisp/ui/icons/Numix/status/appointment-soon.svg new file mode 100644 index 000000000..a49c027a1 --- /dev/null +++ b/lisp/ui/icons/Numix/status/appointment-soon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lisp/ui/icons/Numix/status/audio-volume-high-symbolic.svg b/lisp/ui/icons/Numix/status/audio-volume-high-symbolic.svg new file mode 100644 index 000000000..af38d46c5 --- /dev/null +++ b/lisp/ui/icons/Numix/status/audio-volume-high-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/status/audio-volume-high.svg b/lisp/ui/icons/Numix/status/audio-volume-high.svg new file mode 100644 index 000000000..65ee60f73 --- /dev/null +++ b/lisp/ui/icons/Numix/status/audio-volume-high.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lisp/ui/icons/Numix/status/audio-volume-low-symbolic.svg b/lisp/ui/icons/Numix/status/audio-volume-low-symbolic.svg new file mode 100644 index 000000000..38a8ee9e8 --- /dev/null +++ b/lisp/ui/icons/Numix/status/audio-volume-low-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/status/audio-volume-low.svg b/lisp/ui/icons/Numix/status/audio-volume-low.svg new file mode 100644 index 000000000..d9e694cce --- /dev/null +++ b/lisp/ui/icons/Numix/status/audio-volume-low.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/status/audio-volume-medium-symbolic.svg b/lisp/ui/icons/Numix/status/audio-volume-medium-symbolic.svg new file mode 100644 index 000000000..4da33d214 --- /dev/null +++ b/lisp/ui/icons/Numix/status/audio-volume-medium-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/status/audio-volume-medium.svg b/lisp/ui/icons/Numix/status/audio-volume-medium.svg new file mode 100644 index 000000000..4286fc8f8 --- /dev/null +++ b/lisp/ui/icons/Numix/status/audio-volume-medium.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/status/audio-volume-muted-symbolic.svg b/lisp/ui/icons/Numix/status/audio-volume-muted-symbolic.svg new file mode 100644 index 000000000..5ccda3712 --- /dev/null +++ b/lisp/ui/icons/Numix/status/audio-volume-muted-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/status/audio-volume-muted.svg b/lisp/ui/icons/Numix/status/audio-volume-muted.svg new file mode 100644 index 000000000..d19c73603 --- /dev/null +++ b/lisp/ui/icons/Numix/status/audio-volume-muted.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/status/battery-caution-symbolic.svg b/lisp/ui/icons/Numix/status/battery-caution-symbolic.svg new file mode 100644 index 000000000..852cb856f --- /dev/null +++ b/lisp/ui/icons/Numix/status/battery-caution-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/battery-caution.svg b/lisp/ui/icons/Numix/status/battery-caution.svg new file mode 100644 index 000000000..852cb856f --- /dev/null +++ b/lisp/ui/icons/Numix/status/battery-caution.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/battery-low-symbolic.svg b/lisp/ui/icons/Numix/status/battery-low-symbolic.svg new file mode 100644 index 000000000..328153b66 --- /dev/null +++ b/lisp/ui/icons/Numix/status/battery-low-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/battery-low.svg b/lisp/ui/icons/Numix/status/battery-low.svg new file mode 100644 index 000000000..328153b66 --- /dev/null +++ b/lisp/ui/icons/Numix/status/battery-low.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/dialog-error-symbolic.svg b/lisp/ui/icons/Numix/status/dialog-error-symbolic.svg new file mode 100644 index 000000000..4b175553b --- /dev/null +++ b/lisp/ui/icons/Numix/status/dialog-error-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/status/dialog-error.svg b/lisp/ui/icons/Numix/status/dialog-error.svg new file mode 100644 index 000000000..fb595800a --- /dev/null +++ b/lisp/ui/icons/Numix/status/dialog-error.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/status/dialog-information-symbolic.svg b/lisp/ui/icons/Numix/status/dialog-information-symbolic.svg new file mode 100644 index 000000000..3451031be --- /dev/null +++ b/lisp/ui/icons/Numix/status/dialog-information-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/status/dialog-information.svg b/lisp/ui/icons/Numix/status/dialog-information.svg new file mode 100644 index 000000000..dd8cc7360 --- /dev/null +++ b/lisp/ui/icons/Numix/status/dialog-information.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/status/dialog-password-symbolic.svg b/lisp/ui/icons/Numix/status/dialog-password-symbolic.svg new file mode 100644 index 000000000..c129a09d2 --- /dev/null +++ b/lisp/ui/icons/Numix/status/dialog-password-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/status/dialog-password.svg b/lisp/ui/icons/Numix/status/dialog-password.svg new file mode 100644 index 000000000..a0c20dbdc --- /dev/null +++ b/lisp/ui/icons/Numix/status/dialog-password.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/status/dialog-question-symbolic.svg b/lisp/ui/icons/Numix/status/dialog-question-symbolic.svg new file mode 100644 index 000000000..302ed363a --- /dev/null +++ b/lisp/ui/icons/Numix/status/dialog-question-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/status/dialog-question.svg b/lisp/ui/icons/Numix/status/dialog-question.svg new file mode 100644 index 000000000..11524af1b --- /dev/null +++ b/lisp/ui/icons/Numix/status/dialog-question.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/status/dialog-warning-symbolic.svg b/lisp/ui/icons/Numix/status/dialog-warning-symbolic.svg new file mode 100644 index 000000000..c77aa3a7f --- /dev/null +++ b/lisp/ui/icons/Numix/status/dialog-warning-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/status/dialog-warning.svg b/lisp/ui/icons/Numix/status/dialog-warning.svg new file mode 100644 index 000000000..68c706ab8 --- /dev/null +++ b/lisp/ui/icons/Numix/status/dialog-warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/status/folder-drag-accept.svg b/lisp/ui/icons/Numix/status/folder-drag-accept.svg new file mode 100644 index 000000000..c1654713c --- /dev/null +++ b/lisp/ui/icons/Numix/status/folder-drag-accept.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/status/folder-open.svg b/lisp/ui/icons/Numix/status/folder-open.svg new file mode 100644 index 000000000..c1654713c --- /dev/null +++ b/lisp/ui/icons/Numix/status/folder-open.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/status/folder-visiting.svg b/lisp/ui/icons/Numix/status/folder-visiting.svg new file mode 100644 index 000000000..c1654713c --- /dev/null +++ b/lisp/ui/icons/Numix/status/folder-visiting.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/status/image-loading.svg b/lisp/ui/icons/Numix/status/image-loading.svg new file mode 100644 index 000000000..707c2d7be --- /dev/null +++ b/lisp/ui/icons/Numix/status/image-loading.svg @@ -0,0 +1,59 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/lisp/ui/icons/Numix/status/image-missing.svg b/lisp/ui/icons/Numix/status/image-missing.svg new file mode 100644 index 000000000..59f832871 --- /dev/null +++ b/lisp/ui/icons/Numix/status/image-missing.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/mail-attachment-symbolic.svg b/lisp/ui/icons/Numix/status/mail-attachment-symbolic.svg new file mode 100644 index 000000000..228073e32 --- /dev/null +++ b/lisp/ui/icons/Numix/status/mail-attachment-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/status/mail-attachment.svg b/lisp/ui/icons/Numix/status/mail-attachment.svg new file mode 100644 index 000000000..3efbdceaa --- /dev/null +++ b/lisp/ui/icons/Numix/status/mail-attachment.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/status/mail-read-symbolic.svg b/lisp/ui/icons/Numix/status/mail-read-symbolic.svg new file mode 100644 index 000000000..021674a45 --- /dev/null +++ b/lisp/ui/icons/Numix/status/mail-read-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/status/mail-read.svg b/lisp/ui/icons/Numix/status/mail-read.svg new file mode 100644 index 000000000..e59a3d5e8 --- /dev/null +++ b/lisp/ui/icons/Numix/status/mail-read.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/status/mail-replied-symbolic.svg b/lisp/ui/icons/Numix/status/mail-replied-symbolic.svg new file mode 100644 index 000000000..1216b59bd --- /dev/null +++ b/lisp/ui/icons/Numix/status/mail-replied-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/status/mail-replied.svg b/lisp/ui/icons/Numix/status/mail-replied.svg new file mode 100644 index 000000000..e5420d8c1 --- /dev/null +++ b/lisp/ui/icons/Numix/status/mail-replied.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/status/mail-unread-symbolic.svg b/lisp/ui/icons/Numix/status/mail-unread-symbolic.svg new file mode 100644 index 000000000..34a8050e6 --- /dev/null +++ b/lisp/ui/icons/Numix/status/mail-unread-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/status/mail-unread.svg b/lisp/ui/icons/Numix/status/mail-unread.svg new file mode 100644 index 000000000..55034ce04 --- /dev/null +++ b/lisp/ui/icons/Numix/status/mail-unread.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/status/media-playlist-repeat-symbolic.svg b/lisp/ui/icons/Numix/status/media-playlist-repeat-symbolic.svg new file mode 100644 index 000000000..11484ca83 --- /dev/null +++ b/lisp/ui/icons/Numix/status/media-playlist-repeat-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/status/media-playlist-repeat.svg b/lisp/ui/icons/Numix/status/media-playlist-repeat.svg new file mode 100644 index 000000000..13e0d0059 --- /dev/null +++ b/lisp/ui/icons/Numix/status/media-playlist-repeat.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/status/media-playlist-shuffle-symbolic.svg b/lisp/ui/icons/Numix/status/media-playlist-shuffle-symbolic.svg new file mode 100644 index 000000000..d3a0e90c5 --- /dev/null +++ b/lisp/ui/icons/Numix/status/media-playlist-shuffle-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/media-playlist-shuffle.svg b/lisp/ui/icons/Numix/status/media-playlist-shuffle.svg new file mode 100644 index 000000000..1440aa73b --- /dev/null +++ b/lisp/ui/icons/Numix/status/media-playlist-shuffle.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/status/network-error-symbolic.svg b/lisp/ui/icons/Numix/status/network-error-symbolic.svg new file mode 100644 index 000000000..1827da459 --- /dev/null +++ b/lisp/ui/icons/Numix/status/network-error-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/status/network-error.svg b/lisp/ui/icons/Numix/status/network-error.svg new file mode 100644 index 000000000..17744ad23 --- /dev/null +++ b/lisp/ui/icons/Numix/status/network-error.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/network-idle-symbolic.svg b/lisp/ui/icons/Numix/status/network-idle-symbolic.svg new file mode 100644 index 000000000..c1117b1be --- /dev/null +++ b/lisp/ui/icons/Numix/status/network-idle-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/status/network-idle.svg b/lisp/ui/icons/Numix/status/network-idle.svg new file mode 100644 index 000000000..6849a9c79 --- /dev/null +++ b/lisp/ui/icons/Numix/status/network-idle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/network-offline-symbolic.svg b/lisp/ui/icons/Numix/status/network-offline-symbolic.svg new file mode 100644 index 000000000..d3026d741 --- /dev/null +++ b/lisp/ui/icons/Numix/status/network-offline-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/network-offline.svg b/lisp/ui/icons/Numix/status/network-offline.svg new file mode 100644 index 000000000..08d9d7dc8 --- /dev/null +++ b/lisp/ui/icons/Numix/status/network-offline.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/network-receive-symbolic.svg b/lisp/ui/icons/Numix/status/network-receive-symbolic.svg new file mode 100644 index 000000000..a4ec3a60b --- /dev/null +++ b/lisp/ui/icons/Numix/status/network-receive-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/network-receive.svg b/lisp/ui/icons/Numix/status/network-receive.svg new file mode 100644 index 000000000..e4778c16c --- /dev/null +++ b/lisp/ui/icons/Numix/status/network-receive.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/network-transmit-receive-symbolic.svg b/lisp/ui/icons/Numix/status/network-transmit-receive-symbolic.svg new file mode 100644 index 000000000..078761dc9 --- /dev/null +++ b/lisp/ui/icons/Numix/status/network-transmit-receive-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/network-transmit-receive.svg b/lisp/ui/icons/Numix/status/network-transmit-receive.svg new file mode 100644 index 000000000..6849a9c79 --- /dev/null +++ b/lisp/ui/icons/Numix/status/network-transmit-receive.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/network-transmit-symbolic.svg b/lisp/ui/icons/Numix/status/network-transmit-symbolic.svg new file mode 100644 index 000000000..b3dfb6949 --- /dev/null +++ b/lisp/ui/icons/Numix/status/network-transmit-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/network-transmit.svg b/lisp/ui/icons/Numix/status/network-transmit.svg new file mode 100644 index 000000000..f512a29f7 --- /dev/null +++ b/lisp/ui/icons/Numix/status/network-transmit.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/printer-error-symbolic.svg b/lisp/ui/icons/Numix/status/printer-error-symbolic.svg new file mode 100644 index 000000000..bb33f54e2 --- /dev/null +++ b/lisp/ui/icons/Numix/status/printer-error-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/printer-error.svg b/lisp/ui/icons/Numix/status/printer-error.svg new file mode 100644 index 000000000..164d1a18d --- /dev/null +++ b/lisp/ui/icons/Numix/status/printer-error.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/status/printer-printing-symbolic.svg b/lisp/ui/icons/Numix/status/printer-printing-symbolic.svg new file mode 100644 index 000000000..24bd8b785 --- /dev/null +++ b/lisp/ui/icons/Numix/status/printer-printing-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/lisp/ui/icons/Numix/status/printer-printing.svg b/lisp/ui/icons/Numix/status/printer-printing.svg new file mode 100644 index 000000000..30a3dfb7e --- /dev/null +++ b/lisp/ui/icons/Numix/status/printer-printing.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lisp/ui/icons/Numix/status/security-high-symbolic.svg b/lisp/ui/icons/Numix/status/security-high-symbolic.svg new file mode 100644 index 000000000..b4f2dddf7 --- /dev/null +++ b/lisp/ui/icons/Numix/status/security-high-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/status/security-high.svg b/lisp/ui/icons/Numix/status/security-high.svg new file mode 100644 index 000000000..550c96e07 --- /dev/null +++ b/lisp/ui/icons/Numix/status/security-high.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/status/security-low-symbolic.svg b/lisp/ui/icons/Numix/status/security-low-symbolic.svg new file mode 100644 index 000000000..116fd8092 --- /dev/null +++ b/lisp/ui/icons/Numix/status/security-low-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/status/security-low.svg b/lisp/ui/icons/Numix/status/security-low.svg new file mode 100644 index 000000000..1e5c78bae --- /dev/null +++ b/lisp/ui/icons/Numix/status/security-low.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/status/security-medium-symbolic.svg b/lisp/ui/icons/Numix/status/security-medium-symbolic.svg new file mode 100644 index 000000000..6f48a4546 --- /dev/null +++ b/lisp/ui/icons/Numix/status/security-medium-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/status/security-medium.svg b/lisp/ui/icons/Numix/status/security-medium.svg new file mode 100644 index 000000000..511fd0c1c --- /dev/null +++ b/lisp/ui/icons/Numix/status/security-medium.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/status/software-update-available-symbolic.svg b/lisp/ui/icons/Numix/status/software-update-available-symbolic.svg new file mode 100644 index 000000000..d9764fb1b --- /dev/null +++ b/lisp/ui/icons/Numix/status/software-update-available-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/status/software-update-urgent-symbolic.svg b/lisp/ui/icons/Numix/status/software-update-urgent-symbolic.svg new file mode 100644 index 000000000..54ba0b170 --- /dev/null +++ b/lisp/ui/icons/Numix/status/software-update-urgent-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/status/task-due-symbolic.svg b/lisp/ui/icons/Numix/status/task-due-symbolic.svg new file mode 100644 index 000000000..03c4bd73e --- /dev/null +++ b/lisp/ui/icons/Numix/status/task-due-symbolic.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/status/task-due.svg b/lisp/ui/icons/Numix/status/task-due.svg new file mode 100644 index 000000000..f4accad6f --- /dev/null +++ b/lisp/ui/icons/Numix/status/task-due.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lisp/ui/icons/Numix/status/task-past-due-symbolic.svg b/lisp/ui/icons/Numix/status/task-past-due-symbolic.svg new file mode 100644 index 000000000..87b350278 --- /dev/null +++ b/lisp/ui/icons/Numix/status/task-past-due-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lisp/ui/icons/Numix/status/task-past-due.svg b/lisp/ui/icons/Numix/status/task-past-due.svg new file mode 100644 index 000000000..178d5d11b --- /dev/null +++ b/lisp/ui/icons/Numix/status/task-past-due.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/status/user-available-symbolic.svg b/lisp/ui/icons/Numix/status/user-available-symbolic.svg new file mode 100644 index 000000000..dda65c3cc --- /dev/null +++ b/lisp/ui/icons/Numix/status/user-available-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/status/user-available.svg b/lisp/ui/icons/Numix/status/user-available.svg new file mode 100644 index 000000000..d5a62ed4c --- /dev/null +++ b/lisp/ui/icons/Numix/status/user-available.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/status/user-away-symbolic.svg b/lisp/ui/icons/Numix/status/user-away-symbolic.svg new file mode 100644 index 000000000..f90d40218 --- /dev/null +++ b/lisp/ui/icons/Numix/status/user-away-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/status/user-away.svg b/lisp/ui/icons/Numix/status/user-away.svg new file mode 100644 index 000000000..eded3e504 --- /dev/null +++ b/lisp/ui/icons/Numix/status/user-away.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/status/user-idle-symbolic.svg b/lisp/ui/icons/Numix/status/user-idle-symbolic.svg new file mode 100644 index 000000000..ee80aa591 --- /dev/null +++ b/lisp/ui/icons/Numix/status/user-idle-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/status/user-idle.svg b/lisp/ui/icons/Numix/status/user-idle.svg new file mode 100644 index 000000000..eded3e504 --- /dev/null +++ b/lisp/ui/icons/Numix/status/user-idle.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/status/user-offline-symbolic.svg b/lisp/ui/icons/Numix/status/user-offline-symbolic.svg new file mode 100644 index 000000000..242f7169d --- /dev/null +++ b/lisp/ui/icons/Numix/status/user-offline-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lisp/ui/icons/Numix/status/user-offline.svg b/lisp/ui/icons/Numix/status/user-offline.svg new file mode 100644 index 000000000..a3f2e1255 --- /dev/null +++ b/lisp/ui/icons/Numix/status/user-offline.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lisp/ui/icons/Numix/status/user-trash-full.svg b/lisp/ui/icons/Numix/status/user-trash-full.svg new file mode 100644 index 000000000..5fa4fb480 --- /dev/null +++ b/lisp/ui/icons/Numix/status/user-trash-full.svg @@ -0,0 +1,158 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/Papirus Symbolic/custom/cue-interrupt.svg b/lisp/ui/icons/Papirus Symbolic/custom/cue-interrupt.svg deleted file mode 100644 index c35b008b8..000000000 --- a/lisp/ui/icons/Papirus Symbolic/custom/cue-interrupt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/custom/cue-pause.svg b/lisp/ui/icons/Papirus Symbolic/custom/cue-pause.svg deleted file mode 100644 index 59f205c2b..000000000 --- a/lisp/ui/icons/Papirus Symbolic/custom/cue-pause.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/custom/cue-select-next.svg b/lisp/ui/icons/Papirus Symbolic/custom/cue-select-next.svg deleted file mode 100644 index f1c11003f..000000000 --- a/lisp/ui/icons/Papirus Symbolic/custom/cue-select-next.svg +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/lisp/ui/icons/Papirus Symbolic/custom/cue-start.svg b/lisp/ui/icons/Papirus Symbolic/custom/cue-start.svg deleted file mode 100644 index e72965424..000000000 --- a/lisp/ui/icons/Papirus Symbolic/custom/cue-start.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/custom/cue-stop.svg b/lisp/ui/icons/Papirus Symbolic/custom/cue-stop.svg deleted file mode 100644 index 35948a37b..000000000 --- a/lisp/ui/icons/Papirus Symbolic/custom/cue-stop.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/custom/cue-trigger-next.svg b/lisp/ui/icons/Papirus Symbolic/custom/cue-trigger-next.svg deleted file mode 100644 index 13217be6b..000000000 --- a/lisp/ui/icons/Papirus Symbolic/custom/cue-trigger-next.svg +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - diff --git a/lisp/ui/icons/Papirus Symbolic/custom/fadein-generic.svg b/lisp/ui/icons/Papirus Symbolic/custom/fadein-generic.svg deleted file mode 100644 index 731868850..000000000 --- a/lisp/ui/icons/Papirus Symbolic/custom/fadein-generic.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/custom/fadeout-generic.svg b/lisp/ui/icons/Papirus Symbolic/custom/fadeout-generic.svg deleted file mode 100644 index 5083f8359..000000000 --- a/lisp/ui/icons/Papirus Symbolic/custom/fadeout-generic.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/index.theme b/lisp/ui/icons/Papirus Symbolic/index.theme deleted file mode 100644 index a6fb398b7..000000000 --- a/lisp/ui/icons/Papirus Symbolic/index.theme +++ /dev/null @@ -1,47 +0,0 @@ -[Icon Theme] -Name=Papirus-Symbolic -Comment=Papirus icon theme -Inherits=hicolor -Directories=standard/actions,standard/categories,standard/devices,standard/emblems,standard/places,standard/status - -[standard/actions] -Size=16 -MinSize=16 -MaxSize=256 -Context=Actions -Type=Scalable - -[standard/categories] -Size=16 -MinSize=16 -MaxSize=256 -Context=Categories -Type=Scalable - -[standard/devices] -Size=16 -MinSize=16 -MaxSize=256 -Context=Devices -Type=Scalable - -[standard/emblems] -Size=16 -MinSize=16 -MaxSize=256 -Context=Emblems -Type=Scalable - -[standard/places] -Size=16 -MinSize=16 -MaxSize=256 -Context=Places -Type=Scalable - -[standard/status] -Size=16 -MinSize=16 -MaxSize=256 -Context=Status -Type=Scalable diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/address-book-new.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/address-book-new.svg deleted file mode 100644 index d19b16ca1..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/address-book-new.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/application-exit.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/application-exit.svg deleted file mode 100644 index 609904248..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/application-exit.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/appointment-new.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/appointment-new.svg deleted file mode 100644 index 254b2b683..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/appointment-new.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/call-start.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/call-start.svg deleted file mode 100644 index 6963d21ba..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/call-start.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/call-stop.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/call-stop.svg deleted file mode 100644 index b095d4745..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/call-stop.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/contact-new.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/contact-new.svg deleted file mode 100644 index 30708e57e..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/contact-new.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-new.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-new.svg deleted file mode 100644 index a49b1c9a0..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-new.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-open-recent.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-open-recent.svg deleted file mode 100644 index c5e792c55..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-open-recent.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-open.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-open.svg deleted file mode 100644 index 287220aa6..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-open.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-page-setup.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-page-setup.svg deleted file mode 100644 index 9cfc8f39a..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-page-setup.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-print-preview.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-print-preview.svg deleted file mode 100644 index c15d96de5..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-print-preview.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-print.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-print.svg deleted file mode 100644 index 7e8e588b2..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-print.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-properties.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-properties.svg deleted file mode 100644 index f185d91cc..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-properties.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-revert.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-revert.svg deleted file mode 100644 index b08a5af6c..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-revert.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-save-as.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-save-as.svg deleted file mode 100644 index 98146a837..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-save-as.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-save.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-save.svg deleted file mode 100644 index d74d3cfa1..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-save.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-send.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/document-send.svg deleted file mode 100644 index ab260cbe0..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/document-send.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-clear.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-clear.svg deleted file mode 100644 index b910266ad..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-clear.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-copy.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-copy.svg deleted file mode 100644 index 50af10b03..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-copy.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-cut.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-cut.svg deleted file mode 100644 index 3517a8f2b..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-cut.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-delete.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-delete.svg deleted file mode 100644 index 48cd5c331..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-delete.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-find-replace.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-find-replace.svg deleted file mode 100644 index 97fbb40fb..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-find-replace.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-find.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-find.svg deleted file mode 100644 index 38812ebe8..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-find.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-paste.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-paste.svg deleted file mode 100644 index 4c9e5617a..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-paste.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-redo.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-redo.svg deleted file mode 100644 index 1e4a5c0b7..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-redo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-select-all.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-select-all.svg deleted file mode 100644 index b8eb41951..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-select-all.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-undo.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-undo.svg deleted file mode 100644 index 35aa4206f..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/edit-undo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/folder-new.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/folder-new.svg deleted file mode 100644 index 9e94f39ba..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/folder-new.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-indent-less.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-indent-less.svg deleted file mode 100644 index a064aad15..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-indent-less.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-indent-more.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-indent-more.svg deleted file mode 100644 index ec00f9406..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-indent-more.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-center.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-center.svg deleted file mode 100644 index c7dcb7fc4..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-center.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-fill.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-fill.svg deleted file mode 100644 index 6ca7bfaa8..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-fill.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-left.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-left.svg deleted file mode 100644 index d9d3c9b7d..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-left.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-right.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-right.svg deleted file mode 100644 index a50cb6e67..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-justify-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-bold.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-bold.svg deleted file mode 100644 index 6c2abd4e3..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-bold.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-direction-ltr.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-direction-ltr.svg deleted file mode 100644 index a44321c14..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-direction-ltr.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-direction-rtl.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-direction-rtl.svg deleted file mode 100644 index c8ac59893..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-direction-rtl.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-italic.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-italic.svg deleted file mode 100644 index f5bf4cb66..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-italic.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-strikethrough.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-strikethrough.svg deleted file mode 100644 index 809782c17..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-strikethrough.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-underline.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-underline.svg deleted file mode 100644 index 86c35e7d9..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/format-text-underline.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-bottom.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-bottom.svg deleted file mode 100644 index 3b0913235..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-bottom.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-down.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-down.svg deleted file mode 100644 index 4af385f38..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-down.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-first.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-first.svg deleted file mode 100644 index 5d78b1805..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-first.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-home.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-home.svg deleted file mode 100644 index ed2b2defd..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-home.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-jump.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-jump.svg deleted file mode 100644 index e4ac948da..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-jump.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-last.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-last.svg deleted file mode 100644 index ebc596977..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-last.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-next.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-next.svg deleted file mode 100644 index 349a62d58..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-previous.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-previous.svg deleted file mode 100644 index e19203391..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-previous.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-top.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-top.svg deleted file mode 100644 index f453c58e5..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-top.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-up.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/go-up.svg deleted file mode 100644 index bc89e9806..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/go-up.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/help-about.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/help-about.svg deleted file mode 100644 index b5f4d7f40..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/help-about.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/insert-image.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/insert-image.svg deleted file mode 100644 index 5960c1143..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/insert-image.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/insert-link.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/insert-link.svg deleted file mode 100644 index 41a47a4db..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/insert-link.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/insert-object.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/insert-object.svg deleted file mode 100644 index 757b3bdd3..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/insert-object.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/insert-text.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/insert-text.svg deleted file mode 100644 index 9dde74e98..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/insert-text.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/list-add.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/list-add.svg deleted file mode 100644 index 1fa391c7c..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/list-add.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/list-remove.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/list-remove.svg deleted file mode 100644 index ff6657208..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/list-remove.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-forward.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-forward.svg deleted file mode 100644 index 5411527bf..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-forward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-important.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-important.svg deleted file mode 100644 index acb50ddfd..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-important.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-junk.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-junk.svg deleted file mode 100644 index 9a5456ec6..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-junk.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-notjunk.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-notjunk.svg deleted file mode 100644 index f98e36d9b..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-notjunk.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-read.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-read.svg deleted file mode 100644 index 6426ef99a..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-read.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-unread.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-unread.svg deleted file mode 100644 index 90fdbaf75..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-mark-unread.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-message-new.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-message-new.svg deleted file mode 100644 index 90fdbaf75..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-message-new.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-reply-all.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-reply-all.svg deleted file mode 100644 index 01234575c..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-reply-all.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-reply-sender.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-reply-sender.svg deleted file mode 100644 index 60007b570..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-reply-sender.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-send-receive.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-send-receive.svg deleted file mode 100644 index 6bf988e39..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-send-receive.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-send.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-send.svg deleted file mode 100644 index ab260cbe0..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/mail-send.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-eject.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-eject.svg deleted file mode 100644 index 5e1a1359e..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-eject.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-pause.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-pause.svg deleted file mode 100644 index 59f205c2b..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-pause.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-start.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-start.svg deleted file mode 100644 index e72965424..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-start.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-stop.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-stop.svg deleted file mode 100644 index 35948a37b..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-playback-stop.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-record.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-record.svg deleted file mode 100644 index 9da42dcfa..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-record.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-seek-backward.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-seek-backward.svg deleted file mode 100644 index dceba05b9..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-seek-backward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-seek-forward.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-seek-forward.svg deleted file mode 100644 index 630fa1653..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-seek-forward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-skip-backward.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-skip-backward.svg deleted file mode 100644 index 9326f7204..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-skip-backward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-skip-forward.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/media-skip-forward.svg deleted file mode 100644 index 7c6210d3b..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/media-skip-forward.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/object-flip-horizontal.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/object-flip-horizontal.svg deleted file mode 100644 index 6b2df07fe..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/object-flip-horizontal.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/object-flip-vertical.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/object-flip-vertical.svg deleted file mode 100644 index 2b15fe924..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/object-flip-vertical.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/object-rotate-left.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/object-rotate-left.svg deleted file mode 100644 index 50ce6e10e..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/object-rotate-left.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/object-rotate-right.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/object-rotate-right.svg deleted file mode 100644 index 9e077e2a7..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/object-rotate-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/process-stop.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/process-stop.svg deleted file mode 100644 index c35b008b8..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/process-stop.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/system-lock-screen.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/system-lock-screen.svg deleted file mode 100644 index f53cd1700..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/system-lock-screen.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/system-log-out.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/system-log-out.svg deleted file mode 100644 index eb0b25a1a..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/system-log-out.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/system-run.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/system-run.svg deleted file mode 100644 index 2dbe12a05..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/system-run.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/system-search.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/system-search.svg deleted file mode 100644 index 38812ebe8..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/system-search.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/system-shutdown.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/system-shutdown.svg deleted file mode 100644 index 14e92d22e..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/system-shutdown.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/tools-check-spelling.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/tools-check-spelling.svg deleted file mode 100644 index 17e8b6ea7..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/tools-check-spelling.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/view-fullscreen.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/view-fullscreen.svg deleted file mode 100644 index f8101065d..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/view-fullscreen.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/view-refresh.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/view-refresh.svg deleted file mode 100644 index bfa2a3e07..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/view-refresh.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/view-restore.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/view-restore.svg deleted file mode 100644 index 66f1bd974..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/view-restore.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/view-sort-ascending.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/view-sort-ascending.svg deleted file mode 100644 index b8eef499e..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/view-sort-ascending.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/view-sort-descending.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/view-sort-descending.svg deleted file mode 100644 index 92f4cedd1..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/view-sort-descending.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/window-close.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/window-close.svg deleted file mode 100644 index 873182ad2..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/window-close.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-fit-best.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-fit-best.svg deleted file mode 100644 index 7c3a4569b..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-fit-best.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-in.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-in.svg deleted file mode 100644 index f1c6695b3..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-in.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-original.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-original.svg deleted file mode 100644 index 61c65e3a2..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-original.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-out.svg b/lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-out.svg deleted file mode 100644 index e8cea04c3..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/actions/zoom-out.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-engineering.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-engineering.svg deleted file mode 100644 index b680eb12d..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-engineering.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-games.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-games.svg deleted file mode 100644 index 0dd64a087..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-games.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-graphics.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-graphics.svg deleted file mode 100644 index 1d62dd473..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-graphics.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-multimedia.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-multimedia.svg deleted file mode 100644 index 422c2d382..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-multimedia.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-science.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-science.svg deleted file mode 100644 index fdaaa5841..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-science.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-system.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-system.svg deleted file mode 100644 index c48f4104e..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-system.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-utilities.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-utilities.svg deleted file mode 100644 index a3c1ff4d3..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/categories/applications-utilities.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/categories/preferences-other.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/preferences-other.svg deleted file mode 100644 index 8ca71f517..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/categories/preferences-other.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/categories/preferences-system.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/preferences-system.svg deleted file mode 100644 index c48f4104e..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/categories/preferences-system.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/icons/Papirus Symbolic/standard/categories/system-help.svg b/lisp/ui/icons/Papirus Symbolic/standard/categories/system-help.svg deleted file mode 100644 index 5eea82cdb..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/categories/system-help.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/audio-card.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/audio-card.svg deleted file mode 100644 index 817a3cce6..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/audio-card.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/audio-input-microphone.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/audio-input-microphone.svg deleted file mode 100644 index 370a47bef..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/audio-input-microphone.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/battery.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/battery.svg deleted file mode 100644 index 39b10757b..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/battery.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/camera-photo.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/camera-photo.svg deleted file mode 100644 index 8e3df810a..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/camera-photo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/camera-video.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/camera-video.svg deleted file mode 100644 index 59ff701e6..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/camera-video.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/camera-web.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/camera-web.svg deleted file mode 100644 index eeef1ba81..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/camera-web.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/computer.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/computer.svg deleted file mode 100644 index a8c3a590c..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/computer.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/drive-harddisk.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/drive-harddisk.svg deleted file mode 100644 index 77e2199d5..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/drive-harddisk.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/drive-optical.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/drive-optical.svg deleted file mode 100644 index 731aed592..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/drive-optical.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/drive-removable-media.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/drive-removable-media.svg deleted file mode 100644 index 5a8bb45f8..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/drive-removable-media.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/input-gaming.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/input-gaming.svg deleted file mode 100644 index 13a28edfc..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/input-gaming.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/input-keyboard.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/input-keyboard.svg deleted file mode 100644 index d7e993d3a..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/input-keyboard.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/input-mouse.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/input-mouse.svg deleted file mode 100644 index ce5ddfe42..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/input-mouse.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/input-tablet.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/input-tablet.svg deleted file mode 100644 index f0de4ade5..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/input-tablet.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/media-flash.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/media-flash.svg deleted file mode 100644 index eb5e2bc4a..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/media-flash.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/media-floppy.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/media-floppy.svg deleted file mode 100644 index 9654611f9..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/media-floppy.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/media-tape.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/media-tape.svg deleted file mode 100644 index 5f40bb21c..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/media-tape.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/multimedia-player.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/multimedia-player.svg deleted file mode 100644 index 4cdc61843..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/multimedia-player.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/network-wired.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/network-wired.svg deleted file mode 100644 index f45e8f962..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/network-wired.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/network-wireless.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/network-wireless.svg deleted file mode 100644 index 313a60fce..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/network-wireless.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/phone.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/phone.svg deleted file mode 100644 index b2932d846..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/phone.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/printer.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/printer.svg deleted file mode 100644 index 09c122678..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/printer.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/scanner.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/scanner.svg deleted file mode 100644 index 3edef53ed..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/scanner.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/devices/video-display.svg b/lisp/ui/icons/Papirus Symbolic/standard/devices/video-display.svg deleted file mode 100644 index 3fde695b4..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/devices/video-display.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-default.svg b/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-default.svg deleted file mode 100644 index e90e74bba..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-default.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-documents.svg b/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-documents.svg deleted file mode 100644 index ea342230b..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-documents.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-favorite.svg b/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-favorite.svg deleted file mode 100644 index d157404f6..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-favorite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-important.svg b/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-important.svg deleted file mode 100644 index db097bd63..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-important.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-photos.svg b/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-photos.svg deleted file mode 100644 index b4b3248f7..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-photos.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-shared.svg b/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-shared.svg deleted file mode 100644 index 731cfda79..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-shared.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-system.svg b/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-system.svg deleted file mode 100644 index c48f4104e..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/emblems/emblem-system.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/icons/Papirus Symbolic/standard/places/folder-remote.svg b/lisp/ui/icons/Papirus Symbolic/standard/places/folder-remote.svg deleted file mode 100644 index 65fb87096..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/places/folder-remote.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/places/folder.svg b/lisp/ui/icons/Papirus Symbolic/standard/places/folder.svg deleted file mode 100644 index bdaebb92b..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/places/folder.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/places/network-workgroup.svg b/lisp/ui/icons/Papirus Symbolic/standard/places/network-workgroup.svg deleted file mode 100644 index 06b1c5ac2..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/places/network-workgroup.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/lisp/ui/icons/Papirus Symbolic/standard/places/start-here.svg b/lisp/ui/icons/Papirus Symbolic/standard/places/start-here.svg deleted file mode 100644 index 020224146..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/places/start-here.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/places/user-bookmarks.svg b/lisp/ui/icons/Papirus Symbolic/standard/places/user-bookmarks.svg deleted file mode 100644 index 87637cf24..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/places/user-bookmarks.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/places/user-desktop.svg b/lisp/ui/icons/Papirus Symbolic/standard/places/user-desktop.svg deleted file mode 100644 index a63971595..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/places/user-desktop.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/places/user-home.svg b/lisp/ui/icons/Papirus Symbolic/standard/places/user-home.svg deleted file mode 100644 index ed2b2defd..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/places/user-home.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/places/user-trash.svg b/lisp/ui/icons/Papirus Symbolic/standard/places/user-trash.svg deleted file mode 100644 index 04ca4b816..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/places/user-trash.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/appointment-missed.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/appointment-missed.svg deleted file mode 100644 index c27251d03..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/appointment-missed.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/appointment-soon.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/appointment-soon.svg deleted file mode 100644 index c5e792c55..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/appointment-soon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-high.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-high.svg deleted file mode 100644 index c63c39ea5..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-high.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-low.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-low.svg deleted file mode 100644 index 612bfbb1a..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-low.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-medium.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-medium.svg deleted file mode 100644 index 801b0692d..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-medium.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-muted.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-muted.svg deleted file mode 100644 index 124d42d6c..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/audio-volume-muted.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/battery-caution.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/battery-caution.svg deleted file mode 100644 index 2680524ef..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/battery-caution.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/battery-low.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/battery-low.svg deleted file mode 100644 index 9881c65a0..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/battery-low.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-error.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-error.svg deleted file mode 100644 index de97af4a3..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-error.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-information.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-information.svg deleted file mode 100644 index b5c14fda1..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-information.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-password.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-password.svg deleted file mode 100644 index e22f4804e..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-password.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-question.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-question.svg deleted file mode 100644 index 2321a393b..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-question.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-warning.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-warning.svg deleted file mode 100644 index 86524985b..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/dialog-warning.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/folder-drag-accept.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/folder-drag-accept.svg deleted file mode 100644 index ae2717b6b..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/folder-drag-accept.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/folder-open.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/folder-open.svg deleted file mode 100644 index 3e8767829..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/folder-open.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/folder-visiting.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/folder-visiting.svg deleted file mode 100644 index 476221a9b..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/folder-visiting.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/image-loading.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/image-loading.svg deleted file mode 100644 index 8abf7988a..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/image-loading.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/mail-attachment.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/mail-attachment.svg deleted file mode 100644 index bcf71298a..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/mail-attachment.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/mail-read.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/mail-read.svg deleted file mode 100644 index 6426ef99a..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/mail-read.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/mail-replied.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/mail-replied.svg deleted file mode 100644 index 60007b570..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/mail-replied.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/mail-unread.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/mail-unread.svg deleted file mode 100644 index 90fdbaf75..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/mail-unread.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/media-playlist-repeat.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/media-playlist-repeat.svg deleted file mode 100644 index 79b0cf7e8..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/media-playlist-repeat.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/media-playlist-shuffle.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/media-playlist-shuffle.svg deleted file mode 100644 index c3466668c..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/media-playlist-shuffle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/network-error.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/network-error.svg deleted file mode 100644 index e42e9fcdb..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/network-error.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/network-idle.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/network-idle.svg deleted file mode 100644 index 6841ba161..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/network-idle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/network-offline.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/network-offline.svg deleted file mode 100644 index b5b7e9204..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/network-offline.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/network-receive.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/network-receive.svg deleted file mode 100644 index 5f7b2d55e..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/network-receive.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/network-transmit-receive.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/network-transmit-receive.svg deleted file mode 100644 index 8fdad752e..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/network-transmit-receive.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/network-transmit.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/network-transmit.svg deleted file mode 100644 index 423b3e1d3..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/network-transmit.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/printer-error.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/printer-error.svg deleted file mode 100644 index ada96bd90..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/printer-error.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/printer-printing.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/printer-printing.svg deleted file mode 100644 index 63edcdb87..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/printer-printing.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/security-high.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/security-high.svg deleted file mode 100644 index a907d3054..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/security-high.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/security-low.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/security-low.svg deleted file mode 100644 index 23b74dd4a..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/security-low.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/security-medium.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/security-medium.svg deleted file mode 100644 index 9c9b99e36..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/security-medium.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/software-update-available.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/software-update-available.svg deleted file mode 100644 index c25383dcd..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/software-update-available.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/software-update-urgent.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/software-update-urgent.svg deleted file mode 100644 index c7ac5b7f3..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/software-update-urgent.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/task-due.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/task-due.svg deleted file mode 100644 index 17fb8cc89..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/task-due.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/task-past-due.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/task-past-due.svg deleted file mode 100644 index 43da2565f..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/task-past-due.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/user-available.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/user-available.svg deleted file mode 100644 index ece148e54..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/user-available.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/user-away.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/user-away.svg deleted file mode 100644 index c43b2b833..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/user-away.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/user-idle.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/user-idle.svg deleted file mode 100644 index 4edc9a0ea..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/user-idle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/user-offline.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/user-offline.svg deleted file mode 100644 index 4edc9a0ea..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/user-offline.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/icons/Papirus Symbolic/standard/status/user-trash-full.svg b/lisp/ui/icons/Papirus Symbolic/standard/status/user-trash-full.svg deleted file mode 100644 index 99cc4ef88..000000000 --- a/lisp/ui/icons/Papirus Symbolic/standard/status/user-trash-full.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lisp/ui/settings/app_pages/general.py b/lisp/ui/settings/app_pages/general.py index 2662e8099..46f56d2b7 100644 --- a/lisp/ui/settings/app_pages/general.py +++ b/lisp/ui/settings/app_pages/general.py @@ -73,7 +73,7 @@ def __init__(self, **kwargs): self.iconsLabel = QLabel(self.themeGroup) self.themeGroup.layout().addWidget(self.iconsLabel, 1, 0) self.iconsCombo = QComboBox(self.themeGroup) - self.iconsCombo.addItems(icon_themes_names()) + self.iconsCombo.addItems(sorted(icon_themes_names())) self.themeGroup.layout().addWidget(self.iconsCombo, 1, 1) self.themeGroup.layout().setColumnStretch(0, 1) diff --git a/lisp/ui/themes/dark/theme.qss b/lisp/ui/themes/dark/theme.qss index 72772bae1..40311c702 100644 --- a/lisp/ui/themes/dark/theme.qss +++ b/lisp/ui/themes/dark/theme.qss @@ -285,12 +285,16 @@ QToolBar:separator:vertical { image: url(:/assets/Vsepartoolbars.png); } +QToolButton { + padding: 1px; +} + QPushButton { color: white; background-color: QLinearGradient( x1: 0, y1: 1, x2: 0, y2: 0, stop: 0 #333, stop: 1 #444); - border: 1px solid #262626; + border: 1px solid #202020; border-radius: 4px; - padding: 3px; + padding: 4px; padding-left: 5px; padding-right: 5px; } diff --git a/lisp/ui/widgets/hotkeyedit.py b/lisp/ui/widgets/hotkeyedit.py index 5eba0f85a..f345beceb 100644 --- a/lisp/ui/widgets/hotkeyedit.py +++ b/lisp/ui/widgets/hotkeyedit.py @@ -14,6 +14,7 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +from functools import partial from PyQt5.QtCore import Qt, QEvent from PyQt5.QtGui import QKeySequence @@ -23,10 +24,10 @@ QSizePolicy, QHBoxLayout, QPushButton, -) + QMenu, QAction, QToolButton) from lisp.ui.icons import IconTheme -from lisp.ui.ui_utils import translate +from lisp.ui.ui_utils import translate, adjust_widget_position KEYS_FILTER = { Qt.Key_Control, @@ -57,8 +58,17 @@ def keyEventKeySequence(keyEvent) -> QKeySequence: class HotKeyEdit(QWidget): + SPECIAL_KEYS = [ + QKeySequence(Qt.Key_Escape), + QKeySequence(Qt.Key_Return), + QKeySequence(Qt.Key_Tab), + QKeySequence(Qt.Key_Tab + Qt.SHIFT), + QKeySequence(Qt.Key_Tab + Qt.CTRL), + QKeySequence(Qt.Key_Tab + Qt.SHIFT + Qt.CTRL), + ] + def __init__( - self, sequence=QKeySequence(), clearButton=True, parent=None, **kwargs + self, sequence=QKeySequence(), keysButton=True, clearButton=True, parent=None, **kwargs ): super().__init__(parent, **kwargs) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) @@ -74,19 +84,39 @@ def __init__( self.previewEdit.setReadOnly(True) self.previewEdit.setFocusProxy(self) self.previewEdit.installEventFilter(self) + self.previewEdit.setContextMenuPolicy(Qt.NoContextMenu) + self.previewEdit.selectionChanged.connect(self.previewEdit.deselect) self.layout().addWidget(self.previewEdit) + self.specialMenu = QMenu() + for special in HotKeyEdit.SPECIAL_KEYS: + action = QAction( + IconTheme.get("list-add-symbolic"), + special.toString(QKeySequence.NativeText), + self.specialMenu + ) + action.triggered.connect(partial(self.setKeySequence, special)) + self.specialMenu.addAction(action) + + if keysButton: + action = self.previewEdit.addAction( + IconTheme.get("input-keyboard-symbolic"), + QLineEdit.TrailingPosition + ) + action.triggered.connect(self.onToolsAction) + if clearButton: - self.clearButton = QPushButton(self) - self.clearButton.setIcon(IconTheme.get("edit-clear")) - self.clearButton.clicked.connect(self.clear) - self.layout().addWidget(self.clearButton) + action = self.previewEdit.addAction( + IconTheme.get("edit-clear-symbolic"), + QLineEdit.TrailingPosition + ) + action.triggered.connect(self.clear) self.retranslateUi() def retranslateUi(self): self.previewEdit.setPlaceholderText( - translate("QKeyEdit", "Press shortcut") + translate("HotKeyEdit", "Press shortcut") ) def keySequence(self) -> QKeySequence: @@ -110,7 +140,16 @@ def event(self, event: QEvent): return super().event(event) + def contextMenuEvent(self, event): + event.accept() + self.specialMenu.popup(event.globalPos()) + def keyPressEvent(self, event): sequence = keyEventKeySequence(event) if sequence: self.setKeySequence(sequence) + + def onToolsAction(self): + self.specialMenu.popup( + self.previewEdit.mapToGlobal(self.previewEdit.geometry().topRight()) + ) diff --git a/lisp/ui/widgets/qmutebutton.py b/lisp/ui/widgets/qmutebutton.py index 10741ba98..69670021a 100644 --- a/lisp/ui/widgets/qmutebutton.py +++ b/lisp/ui/widgets/qmutebutton.py @@ -38,6 +38,6 @@ def __init__(self, *args): def onToggle(self): if self.isChecked(): - self.setIcon(IconTheme.get("audio-volume-muted")) + self.setIcon(IconTheme.get("audio-volume-muted-symbolic")) else: - self.setIcon(IconTheme.get("audio-volume-high")) + self.setIcon(IconTheme.get("audio-volume-high-symbolic")) From 09964ae60e23d2b7b3262fa34a17f49013fe5e4f Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 8 May 2019 16:30:23 +0200 Subject: [PATCH 183/333] Fix #174 --- lisp/plugins/list_layout/list_widgets.py | 9 +++------ lisp/ui/widgets/hotkeyedit.py | 22 ++++++++++++++-------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/lisp/plugins/list_layout/list_widgets.py b/lisp/plugins/list_layout/list_widgets.py index cfe6108f1..6ab1f4b52 100644 --- a/lisp/plugins/list_layout/list_widgets.py +++ b/lisp/plugins/list_layout/list_widgets.py @@ -25,7 +25,6 @@ QFontDatabase, ) from PyQt5.QtWidgets import QLabel, QProgressBar, QWidget -from pysnooper import pysnooper from lisp.core.signal import Connection from lisp.core.util import strtime @@ -156,8 +155,8 @@ def _updateIcon(self, nextAction): ): pixmap = IconTheme.get("cue-trigger-next").pixmap(self.SIZE) elif ( - nextAction == CueNextAction.SelectAfterWait - or nextAction == CueNextAction.SelectAfterEnd + nextAction == CueNextAction.SelectAfterWait + or nextAction == CueNextAction.SelectAfterEnd ): pixmap = IconTheme.get("cue-select-next").pixmap(self.SIZE) @@ -339,9 +338,7 @@ def _nextActionChanged(self, nextAction): self._updateDuration, Connection.QtQueued ) - self.waitTime.notify.connect( - self._updateTime, Connection.QtQueued - ) + self.waitTime.notify.connect(self._updateTime, Connection.QtQueued) self._updateDuration(self.cue.post_wait) def _stop(self): diff --git a/lisp/ui/widgets/hotkeyedit.py b/lisp/ui/widgets/hotkeyedit.py index f345beceb..265ebd7ef 100644 --- a/lisp/ui/widgets/hotkeyedit.py +++ b/lisp/ui/widgets/hotkeyedit.py @@ -14,6 +14,7 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . + from functools import partial from PyQt5.QtCore import Qt, QEvent @@ -23,11 +24,12 @@ QLineEdit, QSizePolicy, QHBoxLayout, - QPushButton, - QMenu, QAction, QToolButton) + QMenu, + QAction, +) from lisp.ui.icons import IconTheme -from lisp.ui.ui_utils import translate, adjust_widget_position +from lisp.ui.ui_utils import translate KEYS_FILTER = { Qt.Key_Control, @@ -68,7 +70,12 @@ class HotKeyEdit(QWidget): ] def __init__( - self, sequence=QKeySequence(), keysButton=True, clearButton=True, parent=None, **kwargs + self, + sequence=QKeySequence(), + keysButton=True, + clearButton=True, + parent=None, + **kwargs, ): super().__init__(parent, **kwargs) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) @@ -93,7 +100,7 @@ def __init__( action = QAction( IconTheme.get("list-add-symbolic"), special.toString(QKeySequence.NativeText), - self.specialMenu + self.specialMenu, ) action.triggered.connect(partial(self.setKeySequence, special)) self.specialMenu.addAction(action) @@ -101,14 +108,13 @@ def __init__( if keysButton: action = self.previewEdit.addAction( IconTheme.get("input-keyboard-symbolic"), - QLineEdit.TrailingPosition + QLineEdit.TrailingPosition, ) action.triggered.connect(self.onToolsAction) if clearButton: action = self.previewEdit.addAction( - IconTheme.get("edit-clear-symbolic"), - QLineEdit.TrailingPosition + IconTheme.get("edit-clear-symbolic"), QLineEdit.TrailingPosition ) action.triggered.connect(self.clear) From 880eb94f19b30870c5db7b35623ae52b4f13ff50 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 13 May 2019 22:08:59 +0200 Subject: [PATCH 184/333] New Crowdin translations (#159) --- lisp/i18n/ts/ar_SA/action_cues.ts | 270 +++ lisp/i18n/ts/ar_SA/cart_layout.ts | 187 ++ lisp/i18n/ts/ar_SA/controller.ts | 251 +++ lisp/i18n/ts/ar_SA/gst_backend.ts | 394 ++++ lisp/i18n/ts/ar_SA/lisp.ts | 2894 ++++++++++++++++++++++++++++ lisp/i18n/ts/ar_SA/list_layout.ts | 230 +++ lisp/i18n/ts/ar_SA/media_info.ts | 32 + lisp/i18n/ts/ar_SA/midi.ts | 175 ++ lisp/i18n/ts/ar_SA/network.ts | 71 + lisp/i18n/ts/ar_SA/osc.ts | 146 ++ lisp/i18n/ts/ar_SA/presets.ts | 135 ++ lisp/i18n/ts/ar_SA/rename_cues.ts | 83 + lisp/i18n/ts/ar_SA/replay_gain.ts | 83 + lisp/i18n/ts/ar_SA/synchronizer.ts | 27 + lisp/i18n/ts/ar_SA/timecode.ts | 66 + lisp/i18n/ts/ar_SA/triggers.ts | 63 + lisp/i18n/ts/en/controller.ts | 67 +- lisp/i18n/ts/en/gst_backend.ts | 8 +- lisp/i18n/ts/en/lisp.ts | 200 +- lisp/i18n/ts/en/midi.ts | 4 +- lisp/i18n/ts/en/osc.ts | 26 +- lisp/i18n/ts/en/synchronizer.ts | 8 +- lisp/i18n/ts/fr_FR/lisp.ts | 2331 ++++++++++++++++++++-- 23 files changed, 7475 insertions(+), 276 deletions(-) create mode 100644 lisp/i18n/ts/ar_SA/action_cues.ts create mode 100644 lisp/i18n/ts/ar_SA/cart_layout.ts create mode 100644 lisp/i18n/ts/ar_SA/controller.ts create mode 100644 lisp/i18n/ts/ar_SA/gst_backend.ts create mode 100644 lisp/i18n/ts/ar_SA/lisp.ts create mode 100644 lisp/i18n/ts/ar_SA/list_layout.ts create mode 100644 lisp/i18n/ts/ar_SA/media_info.ts create mode 100644 lisp/i18n/ts/ar_SA/midi.ts create mode 100644 lisp/i18n/ts/ar_SA/network.ts create mode 100644 lisp/i18n/ts/ar_SA/osc.ts create mode 100644 lisp/i18n/ts/ar_SA/presets.ts create mode 100644 lisp/i18n/ts/ar_SA/rename_cues.ts create mode 100644 lisp/i18n/ts/ar_SA/replay_gain.ts create mode 100644 lisp/i18n/ts/ar_SA/synchronizer.ts create mode 100644 lisp/i18n/ts/ar_SA/timecode.ts create mode 100644 lisp/i18n/ts/ar_SA/triggers.ts diff --git a/lisp/i18n/ts/ar_SA/action_cues.ts b/lisp/i18n/ts/ar_SA/action_cues.ts new file mode 100644 index 000000000..295829eae --- /dev/null +++ b/lisp/i18n/ts/ar_SA/action_cues.ts @@ -0,0 +1,270 @@ + + + + + CollectionCue + + + Add + أضف + + + + Remove + احذف + + + + Cue + الأغنية + + + + Action + الإجراءات + + + + CommandCue + + + Command + الأوامر + + + + Command to execute, as in a shell + Command to execute, as in a shell + + + + Discard command output + تجاهل إخراج الأمر + + + + Ignore command errors + Ignore command errors + + + + Kill instead of terminate + Kill instead of terminate + + + + CueCategory + + + Action cues + Action cues + + + + CueName + + + Command Cue + أمر الأغنية + + + + MIDI Cue + MIDI Cue + + + + Volume Control + التحكم بمستوى الصوت + + + + Seek Cue + البحث عن الأغنية + + + + Collection Cue + مختارات الأغنية + + + + Stop-All + إيقاف الجميع + + + + Index Action + فهرس الأعمال + + + + IndexActionCue + + + Index + القائمة + + + + Use a relative index + استخدام مؤشر نسبي + + + + Target index + Target index + + + + Action + الإجراءات + + + + No suggestion + لا توجد اقتراحات + + + + Suggested cue name + Suggested cue name + + + + Osc Cue + + + Fade + التلاشي + + + + OscCue + + + Add + أضف + + + + Remove + احذف + + + + Fade + تلاشي + + + + SeekCue + + + Cue + الأغنية + + + + Click to select + إضغط من أجل الإختيار + + + + Not selected + لم يتم اختياره + + + + Seek + بحث + + + + Time to reach + الوقت للوصول + + + + SettingsPageName + + + Command + الأوامر + + + + MIDI Settings + MIDI Settings + + + + Volume Settings + إعدادات مستوى الصوت + + + + Seek Settings + البحث عن إعدادات + + + + Edit Collection + تعديل المجموعة + + + + Action Settings + إعدادات الإجراء + + + + Stop Settings + إيقاف إعدادات + + + + StopAll + + + Stop Action + التوقف عن العمل + + + + VolumeControl + + + Cue + الأغنية + + + + Click to select + إضغط من أجل الإختيار + + + + Not selected + لم يتم اختياره + + + + Volume to reach + الصوت للوصول + + + + Fade + تلاشي + + + + VolumeControlError + + + Error during cue execution. + حدث خطأ أثناء تنفيذ الأغنية. + + + diff --git a/lisp/i18n/ts/ar_SA/cart_layout.ts b/lisp/i18n/ts/ar_SA/cart_layout.ts new file mode 100644 index 000000000..9eb7709b7 --- /dev/null +++ b/lisp/i18n/ts/ar_SA/cart_layout.ts @@ -0,0 +1,187 @@ + + + + + CartLayout + + + Default behaviors + السلوك الافتراضي + + + + Countdown mode + وضع العد التنازلي + + + + Show seek-bars + اعرض أشرطة البحث + + + + Show dB-meters + إظهار dB-متر + + + + Show accurate time + Show accurate time + + + + Show volume + إظهار حجم الصوت + + + + Grid size + حجم الشبكة + + + + Play + قراءة + + + + Pause + إيقاف مؤقت + + + + Stop + إيقاف + + + + Reset volume + إعادة تعيين حجم الصوت + + + + Add page + إضافة صفحة + + + + Add pages + إضافة صفحات + + + + Remove current page + إزالة الصفحة الحالية + + + + Number of Pages: + عدد الصفحات: + + + + Page {number} + الصفحة{number} + + + + Warning + تحذير + + + + Every cue in the page will be lost. + ستفقد كل أغنية في الصفحة. + + + + Are you sure to continue? + هل أنت متأكد من أن تواصل؟ + + + + Number of columns: + عدد الأعمدة: + + + + Number of rows: + عدد الصفوف: + + + + LayoutDescription + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + LayoutDetails + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them while pressing SHIFT + To move cues drag them while pressing SHIFT + + + + LayoutName + + + Cart Layout + Cart Layout + + + + ListLayout + + + Edit cue + تصحيح الاغنية + + + + Edit selected cues + تصحيح الأغاني المحددة + + + + Remove cue + إزالة الأغاني + + + + Remove selected cues + إزالة الأغاني المحددة + + + + SettingsPageName + + + Cart Layout + Cart Layout + + + diff --git a/lisp/i18n/ts/ar_SA/controller.ts b/lisp/i18n/ts/ar_SA/controller.ts new file mode 100644 index 000000000..e4c5f2fbf --- /dev/null +++ b/lisp/i18n/ts/ar_SA/controller.ts @@ -0,0 +1,251 @@ + + + + + Controller + + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + + ControllerKeySettings + + + Key + مفتاح + + + + Action + الإجراءات + + + + Shortcuts + الاختصارات + + + + ControllerMidiSettings + + + MIDI + MIDI + + + + Type + النوع + + + + Channel + القناة + + + + Note + ملاحظة + + + + Action + الإجراءات + + + + Filter "note on" + Filter "note on" + + + + Filter "note off" + Filter "note off" + + + + Capture + Capture + + + + Listening MIDI messages ... + Listening MIDI messages ... + + + + ControllerOscSettings + + + OSC Message + OSC Message + + + + OSC + OSC + + + + Path + المسار + + + + Types + الأنواع + + + + Arguments + الحجج + + + + Actions + الاجراءت + + + + OSC Capture + التقاط OSC + + + + Add + أضف + + + + Remove + احذف + + + + Capture + Capture + + + + ControllerSettings + + + Add + أضف + + + + Remove + احذف + + + + GlobalAction + + + Go + إبدأ + + + + Reset + إعادة ضبط + + + + Stop all cues + إيقاف كل الأغاني + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + قطع كل الأغاني + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + + + Osc Cue + + + Type + النوع + + + + Argument + الحجج + + + + OscCue + + + Add + أضف + + + + Remove + احذف + + + + SettingsPageName + + + Cue Control + التحكم بالأغاني + + + + MIDI Controls + MIDI Controls + + + + Keyboard Shortcuts + اختصارات لوحة المفاتيح + + + + OSC Controls + ضوابط OSC + + + + Layout Controls + Layout Controls + + + diff --git a/lisp/i18n/ts/ar_SA/gst_backend.ts b/lisp/i18n/ts/ar_SA/gst_backend.ts new file mode 100644 index 000000000..4e478c806 --- /dev/null +++ b/lisp/i18n/ts/ar_SA/gst_backend.ts @@ -0,0 +1,394 @@ + + + + + AlsaSinkSettings + + + ALSA device + ALSA الجهاز + + + + AudioDynamicSettings + + + Compressor + ضاغط + + + + Expander + موسع + + + + Soft Knee + Soft Knee + + + + Hard Knee + Hard Knee + + + + Compressor/Expander + ضاغط/موسع + + + + Type + النوع + + + + Curve Shape + شكل المنحنى + + + + Ratio + النسبة + + + + Threshold (dB) + Threshold (dB) + + + + AudioPanSettings + + + Audio Pan + Audio Pan + + + + Center + الوسط + + + + Left + اليسار + + + + Right + اليمين + + + + DbMeterSettings + + + DbMeter settings + DbMeter settings + + + + Time between levels (ms) + Time between levels (ms) + + + + Peak ttl (ms) + Peak ttl (ms) + + + + Peak falloff (dB/sec) + Peak falloff (dB/sec) + + + + Equalizer10Settings + + + 10 Bands Equalizer (IIR) + 10 Bands Equalizer (IIR) + + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + + + GstMediaSettings + + + Change Pipeline + Change Pipeline + + + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + + + GstPipelineEdit + + + Edit Pipeline + Edit Pipeline + + + + GstSettings + + + Pipeline + Pipeline + + + + JackSinkSettings + + + Connections + الاتصالات + + + + Edit connections + تعديل الاتصالات + + + + Output ports + Output ports + + + + Input ports + Input ports + + + + Connect + الاتصال + + + + Disconnect + قطع الاتصال + + + + MediaElementName + + + Compressor/Expander + ضاغط/موسع + + + + Audio Pan + Audio Pan + + + + PulseAudio Out + PulseAudio Out + + + + Volume + مستوى الصوت + + + + dB Meter + dB Meter + + + + System Input + System Input + + + + ALSA Out + ALSA Out + + + + JACK Out + JACK Out + + + + Custom Element + Custom Element + + + + System Out + System Out + + + + Pitch + Pitch + + + + URI Input + URI Input + + + + 10 Bands Equalizer + 10 Bands Equalizer + + + + Speed + السرعة + + + + Preset Input + Preset Input + + + + PitchSettings + + + Pitch + Pitch + + + + {0:+} semitones + {0:+} semitones + + + + PresetSrcSettings + + + Presets + Presets + + + + SettingsPageName + + + Media Settings + Media Settings + + + + GStreamer + GStreamer + + + + SpeedSettings + + + Speed + السرعة + + + + UriInputSettings + + + Source + المصدر + + + + Find File + إيجاد الملف + + + + Buffering + تخزين مؤقت + + + + Use Buffering + استخدام التخزين المؤقت + + + + Attempt download on network streams + Attempt download on network streams + + + + Buffer size (-1 default value) + Buffer size (-1 default value) + + + + Choose file + إختيار ملف + + + + All files + كل الملفات + + + + UserElementSettings + + + User defined elements + User defined elements + + + + Only for advanced user! + فقط للمستخدمين المتقدمين! + + + + VolumeSettings + + + Volume + مستوى الصوت + + + + Normalized volume + Normalized volume + + + + Reset + إعادة ضبط + + + diff --git a/lisp/i18n/ts/ar_SA/lisp.ts b/lisp/i18n/ts/ar_SA/lisp.ts new file mode 100644 index 000000000..34d32a153 --- /dev/null +++ b/lisp/i18n/ts/ar_SA/lisp.ts @@ -0,0 +1,2894 @@ + + + + + APIServerInfo + + + Stop serving network API + Stop serving network API + + + + About + + + Authors + المؤلفون + + + + Contributors + Contributors + + + + Translators + المترجمون + + + + About Linux Show Player + عن Linux Show Player + + + + AboutDialog + + + Web site + موقع ويب + + + + Source code + Source code + + + + Info + Info + + + + License + License + + + + Contributors + Contributors + + + + Discussion + Discussion + + + + AlsaSinkSettings + + + ALSA device + ALSA device + + + + ApiServerError + + + Network API server stopped working. + Network API server stopped working. + + + + AppConfiguration + + + LiSP preferences + LiSP preferences + + + + AppGeneralSettings + + + Default layout + Default layout + + + + Enable startup layout selector + Enable startup layout selector + + + + Application themes + Application themes + + + + UI theme: + UI theme: + + + + Icons theme: + Icons theme: + + + + Application language (require restart) + Application language (require restart) + + + + Language: + Language: + + + + ApplicationError + + + Startup error + Startup error + + + + AudioDynamicSettings + + + Compressor + Compressor + + + + Expander + Expander + + + + Soft Knee + Soft Knee + + + + Hard Knee + Hard Knee + + + + Compressor/Expander + Compressor/Expander + + + + Type + Type + + + + Curve Shape + Curve Shape + + + + Ratio + Ratio + + + + Threshold (dB) + Threshold (dB) + + + + AudioPanSettings + + + Audio Pan + Audio Pan + + + + Center + Center + + + + Left + Left + + + + Right + Right + + + + CartLayout + + + Default behaviors + Default behaviors + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show volume + Show volume + + + + Grid size + Grid size + + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Number of Pages: + Number of Pages: + + + + Page {number} + Page {number} + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + CollectionCue + + + Add + Add + + + + Remove + Remove + + + + Cue + Cue + + + + Action + Action + + + + CommandCue + + + Command + Command + + + + Command to execute, as in a shell + Command to execute, as in a shell + + + + Discard command output + Discard command output + + + + Ignore command errors + Ignore command errors + + + + Kill instead of terminate + Kill instead of terminate + + + + CommandsStack + + + Undo: {} + Undo: {} + + + + Redo: {} + Redo: {} + + + + ConfigurationDebug + + + Configuration written at {} + Configuration written at {} + + + + ConfigurationInfo + + + New configuration installed at {} + New configuration installed at {} + + + + Controller + + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + + ControllerKeySettings + + + Key + Key + + + + Action + Action + + + + Shortcuts + Shortcuts + + + + ControllerMidiSettings + + + MIDI + MIDI + + + + Type + Type + + + + Channel + Channel + + + + Note + Note + + + + Action + Action + + + + Filter "note on" + Filter "note on" + + + + Filter "note off" + Filter "note off" + + + + Capture + Capture + + + + Listening MIDI messages ... + Listening MIDI messages ... + + + + ControllerOscSettings + + + OSC Message + OSC Message + + + + OSC + OSC + + + + Path + Path + + + + Types + Types + + + + Arguments + Arguments + + + + Actions + Actions + + + + OSC Capture + OSC Capture + + + + Add + Add + + + + Remove + Remove + + + + Capture + Capture + + + + ControllerSettings + + + Add + Add + + + + Remove + Remove + + + + Cue Name + + + OSC Settings + OSC Settings + + + + CueAction + + + Default + الطبيعي + + + + Pause + إيقاف مؤقت + + + + Start + بدء + + + + Stop + إيقاف + + + + Faded Start + Faded Start + + + + Faded Resume + Faded Resume + + + + Faded Pause + Faded Pause + + + + Faded Stop + Faded Stop + + + + Faded Interrupt + Faded Interrupt + + + + Resume + إستكمال + + + + Do Nothing + لا تفعل شيئا + + + + CueActionLog + + + Cue settings changed: "{}" + Cue settings changed: "{}" + + + + Cues settings changed. + Cues settings changed. + + + + CueAppearanceSettings + + + The appearance depends on the layout + The appearance depends on the layout + + + + Cue name + Cue name + + + + NoName + NoName + + + + Description/Note + وصف/ملاحظة + + + + Set Font Size + Set Font Size + + + + Color + اللون + + + + Select background color + اختر لون الخلفية + + + + Select font color + Select font color + + + + CueCategory + + + Action cues + Action cues + + + + Integration cues + Integration cues + + + + Misc cues + Misc cues + + + + CueCommandLog + + + Cue settings changed: "{}" + Cue settings changed: "{}" + + + + Cues settings changed. + Cues settings changed. + + + + CueName + + + Media Cue + Media Cue + + + + Index Action + Index Action + + + + Seek Cue + Seek Cue + + + + Volume Control + Volume Control + + + + Command Cue + Command Cue + + + + Collection Cue + Collection Cue + + + + Stop-All + Stop-All + + + + MIDI Cue + MIDI Cue + + + + OSC Cue + OSC Cue + + + + CueNextAction + + + Do Nothing + لا تفعل شيئا + + + + Trigger after the end + Trigger after the end + + + + Trigger after post wait + Trigger after post wait + + + + Select after the end + Select after the end + + + + Select after post wait + Select after post wait + + + + CueSettings + + + Pre wait + قبل الانتظار + + + + Wait before cue execution + Wait before cue execution + + + + Post wait + بعد الانتظار + + + + Wait after cue execution + Wait after cue execution + + + + Next action + الإجراءت التالية + + + + Start action + بدء الإجراء + + + + Default action to start the cue + Default action to start the cue + + + + Stop action + توقف الإجراءت + + + + Default action to stop the cue + Default action to stop the cue + + + + Interrupt fade + Interrupt fade + + + + Fade actions + Fade actions + + + + CueTriggers + + + Started + Started + + + + Paused + Paused + + + + Stopped + Stopped + + + + Ended + Ended + + + + DbMeterSettings + + + DbMeter settings + DbMeter settings + + + + Time between levels (ms) + Time between levels (ms) + + + + Peak ttl (ms) + Peak ttl (ms) + + + + Peak falloff (dB/sec) + Peak falloff (dB/sec) + + + + Equalizer10Settings + + + 10 Bands Equalizer (IIR) + 10 Bands Equalizer (IIR) + + + + Fade + + + Linear + Linear + + + + Quadratic + Quadratic + + + + Quadratic2 + Quadratic2 + + + + FadeEdit + + + Duration (sec): + Duration (sec): + + + + Curve: + منحنى: + + + + FadeSettings + + + Fade In + ظهور تدريجي + + + + Fade Out + تلاشي تدريجي + + + + GlobalAction + + + Go + Go + + + + Reset + Reset + + + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + + + GstMediaSettings + + + Change Pipeline + Change Pipeline + + + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + + + GstPipelineEdit + + + Edit Pipeline + Edit Pipeline + + + + GstSettings + + + Pipeline + Pipeline + + + + IndexActionCue + + + No suggestion + No suggestion + + + + Index + Index + + + + Use a relative index + Use a relative index + + + + Target index + Target index + + + + Action + Action + + + + Suggested cue name + Suggested cue name + + + + JackSinkSettings + + + Connections + Connections + + + + Edit connections + Edit connections + + + + Output ports + Output ports + + + + Input ports + Input ports + + + + Connect + Connect + + + + Disconnect + Disconnect + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them + To move cues drag them + + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + To move cues drag them while pressing SHIFT + To move cues drag them while pressing SHIFT + + + + LayoutName + + + List Layout + List Layout + + + + Cart Layout + Cart Layout + + + + LayoutSelect + + + Layout selection + Layout selection + + + + Select layout + Select layout + + + + Open file + فتح الملف + + + + ListLayout + + + Stop All + إيقاف الكل + + + + Pause All + إيقاف مؤقت للكل + + + + Interrupt All + قطع كل + + + + Default behaviors + Default behaviors + + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show seek-bars + Show seek-bars + + + + Auto-select next cue + Auto-select next cue + + + + Enable selection mode + Enable selection mode + + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Resume Cue + Resume Cue + + + + Interrupt Cue + Interrupt Cue + + + + Edit cue + Edit cue + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove cue + Remove cue + + + + Remove selected + Remove selected + + + + Selection mode + Selection mode + + + + Pause all + Pause all + + + + Stop all + Stop all + + + + Interrupt all + Interrupt all + + + + Resume all + Resume all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + Edit selected cues + Edit selected cues + + + + Remove selected cues + Remove selected cues + + + + Use fade + Use fade + + + + Copy of {} + Copy of {} + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + + LogStatusIcon + + + Errors/Warnings + Errors/Warnings + + + + Logging + + + Debug + التنقيح + + + + Warning + تحذير + + + + Error + خطأ + + + + Dismiss all + تجاهل الكل + + + + Show details + إظهار التفاصيل + + + + Linux Show Player - Log Viewer + Linux Show Player--عارض السجل + + + + Info + معلومات + + + + Critical + Critical + + + + Time + الوقت + + + + Milliseconds + Milliseconds + + + + Logger name + Logger name + + + + Level + المستوى + + + + Message + رسالة + + + + Function + الوظيفة + + + + Path name + اسم المسار + + + + File name + إسم الملف + + + + Line no. + Line no. + + + + Module + Module + + + + Process ID + Process ID + + + + Process name + Process name + + + + Thread ID + Thread ID + + + + Thread name + Thread name + + + + Exception info + معلومات الاستثناء + + + + MIDICue + + + MIDI Message + MIDI Message + + + + Message type + Message type + + + + MIDIMessageAttr + + + Channel + Channel + + + + Note + Note + + + + Velocity + Velocity + + + + Control + Control + + + + Program + Program + + + + Value + Value + + + + Song + Song + + + + Pitch + Pitch + + + + Position + Position + + + + MIDIMessageType + + + Note ON + Note ON + + + + Note OFF + Note OFF + + + + Polyphonic After-touch + Polyphonic After-touch + + + + Control/Mode Change + Control/Mode Change + + + + Program Change + Program Change + + + + Channel After-touch + Channel After-touch + + + + Pitch Bend Change + Pitch Bend Change + + + + Song Select + Song Select + + + + Song Position + Song Position + + + + Start + Start + + + + Stop + Stop + + + + Continue + Continue + + + + MIDISettings + + + MIDI default devices + MIDI default devices + + + + Input + Input + + + + Output + Output + + + + MainWindow + + + &File + &ملف + + + + New session + دورة جديدة + + + + Open + افتح + + + + Save session + حفظ الدورة + + + + Preferences + Preferences + + + + Save as + حفظ كـ + + + + Full Screen + كامل الشاشة + + + + Exit + الخروج + + + + &Edit + &تعديل + + + + Undo + التراجع + + + + Redo + الإعادة + + + + Select all + اختيار الكل + + + + Select all media cues + Select all media cues + + + + Deselect all + إلغاء تحديد الكل + + + + CTRL+SHIFT+A + CTRL + SHIFT + A + + + + Invert selection + Invert selection + + + + CTRL+I + CTRL + I + + + + Edit selected + تعديل المحددة + + + + CTRL+SHIFT+E + CTRL + SHIFT + E + + + + &Layout + &Layout + + + + &Tools + &أدوات + + + + Edit selection + تعديل المجموعة + + + + &About + &حول + + + + About + حول + + + + About Qt + حول Qt + + + + Close session + Close session + + + + The current session is not saved. + لم يتم حفظ في الدورة الحالية. + + + + Discard the changes? + Discard the changes? + + + + Do you want to save them now? + Do you want to save them now? + + + + MainWindowDebug + + + Registered cue menu: "{}" + Registered cue menu: "{}" + + + + MainWindowError + + + Cannot create cue {} + Cannot create cue {} + + + + MediaCueSettings + + + Start time + وقت البدء + + + + Stop position of the media + Stop position of the media + + + + Stop time + وقت الإيقاف + + + + Start position of the media + Start position of the media + + + + Loop + Loop + + + + MediaElementName + + + Pitch + Pitch + + + + Volume + Volume + + + + Audio Pan + Audio Pan + + + + Compressor/Expander + Compressor/Expander + + + + JACK Out + JACK Out + + + + URI Input + URI Input + + + + 10 Bands Equalizer + 10 Bands Equalizer + + + + ALSA Out + ALSA Out + + + + dB Meter + dB Meter + + + + Preset Input + Preset Input + + + + PulseAudio Out + PulseAudio Out + + + + Custom Element + Custom Element + + + + Speed + Speed + + + + System Out + System Out + + + + System Input + System Input + + + + MediaInfo + + + Media Info + Media Info + + + + Warning + Warning + + + + No info to display + No info to display + + + + Info + Info + + + + Value + Value + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + OscCue + + + Add + Add + + + + Remove + Remove + + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + PitchSettings + + + Pitch + Pitch + + + + {0:+} semitones + {0:+} semitones + + + + PluginsError + + + Failed to load "{}" + Failed to load "{}" + + + + Failed to terminate plugin: "{}" + Failed to terminate plugin: "{}" + + + + the requested plugin is not loaded: {} + the requested plugin is not loaded: {} + + + + PluginsInfo + + + Plugin loaded: "{}" + Plugin loaded: "{}" + + + + Plugin terminated: "{}" + Plugin terminated: "{}" + + + + PluginsWarning + + + Cannot satisfy dependencies for: {} + Cannot satisfy dependencies for: {} + + + + Preset + + + Create Cue + Create Cue + + + + Load on selected Cues + Load on selected Cues + + + + PresetSrcSettings + + + Presets + Presets + + + + Presets + + + Cannot scan presets + Cannot scan presets + + + + Error while deleting preset "{}" + Error while deleting preset "{}" + + + + Cannot load preset "{}" + Cannot load preset "{}" + + + + Cannot save preset "{}" + Cannot save preset "{}" + + + + Cannot rename preset "{}" + Cannot rename preset "{}" + + + + Select Preset + Select Preset + + + + Presets + Presets + + + + Preset name + Preset name + + + + Add + Add + + + + Rename + Rename + + + + Edit + Edit + + + + Remove + Remove + + + + Export selected + Export selected + + + + Import + Import + + + + Warning + Warning + + + + The same name is already used! + The same name is already used! + + + + Cannot export correctly. + Cannot export correctly. + + + + Cannot import correctly. + Cannot import correctly. + + + + Cue type + Cue type + + + + Load on cue + Load on cue + + + + Load on selected cues + Load on selected cues + + + + Save as preset + Save as preset + + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + + + + QColorButton + + + Right click to reset + Right click to reset + + + + RenameCues + + + Rename Cues + Rename Cues + + + + Rename cues + Rename cues + + + + Current + Current + + + + Preview + Preview + + + + Capitalize + Capitalize + + + + Lowercase + Lowercase + + + + Uppercase + Uppercase + + + + Remove Numbers + Remove Numbers + + + + Add numbering + Add numbering + + + + Reset + Reset + + + + Type your regex here: + Type your regex here: + + + + Regex help + Regex help + + + + RenameCuesCommand + + + Renamed {number} cues + Renamed {number} cues + + + + RenameUiDebug + + + Regex error: Invalid pattern + Regex error: Invalid pattern + + + + ReplayGain + + + ReplayGain / Normalization + ReplayGain / Normalization + + + + Threads number + Threads number + + + + Apply only to selected media + Apply only to selected media + + + + ReplayGain to (dB SPL) + ReplayGain to (dB SPL) + + + + Normalize to (dB) + Normalize to (dB) + + + + Processing files ... + Processing files ... + + + + Calculate + Calculate + + + + Reset all + Reset all + + + + Reset selected + Reset selected + + + + ReplayGainDebug + + + Applied gain for: {} + Applied gain for: {} + + + + Discarded gain for: {} + Discarded gain for: {} + + + + ReplayGainInfo + + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} + + + + SeekCue + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Seek + Seek + + + + Time to reach + Time to reach + + + + SettingsPageName + + + Appearance + المظهر + + + + General + إعدادات عامة + + + + Cue + الأغنية + + + + Cue Settings + إعدادات الأغنية + + + + Plugins + الإضافات + + + + Behaviours + السلوك + + + + Pre/Post Wait + Pre/Post Wait + + + + Fade In/Out + Fade In/Out + + + + Layouts + Layouts + + + + Action Settings + Action Settings + + + + Seek Settings + Seek Settings + + + + Volume Settings + Volume Settings + + + + Command + Command + + + + Edit Collection + Edit Collection + + + + Stop Settings + Stop Settings + + + + List Layout + List Layout + + + + Timecode + Timecode + + + + Timecode Settings + Timecode Settings + + + + Cue Control + Cue Control + + + + Layout Controls + Layout Controls + + + + MIDI Controls + MIDI Controls + + + + Keyboard Shortcuts + Keyboard Shortcuts + + + + OSC Controls + OSC Controls + + + + MIDI Settings + MIDI Settings + + + + MIDI settings + MIDI settings + + + + GStreamer + GStreamer + + + + Media Settings + Media Settings + + + + Triggers + Triggers + + + + OSC settings + OSC settings + + + + Cart Layout + Cart Layout + + + + SpeedSettings + + + Speed + Speed + + + + StopAll + + + Stop Action + Stop Action + + + + Synchronizer + + + Synchronization + Synchronization + + + + Manage connected peers + Manage connected peers + + + + Show your IP + Show your IP + + + + Your IP is: + Your IP is: + + + + Timecode + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + TimecodeError + + + Cannot send timecode. + Cannot send timecode. + + + + TimecodeSettings + + + Replace HOURS by a static track number + Replace HOURS by a static track number + + + + Enable Timecode + Enable Timecode + + + + Track number + Track number + + + + Timecode Settings + Timecode Settings + + + + Timecode Format: + Timecode Format: + + + + Timecode Protocol: + Timecode Protocol: + + + + TriggersSettings + + + Add + Add + + + + Remove + Remove + + + + Trigger + Trigger + + + + Cue + Cue + + + + Action + Action + + + + UriInputSettings + + + Source + Source + + + + Find File + Find File + + + + Buffering + Buffering + + + + Use Buffering + Use Buffering + + + + Attempt download on network streams + Attempt download on network streams + + + + Buffer size (-1 default value) + Buffer size (-1 default value) + + + + Choose file + Choose file + + + + All files + All files + + + + UserElementSettings + + + User defined elements + User defined elements + + + + Only for advanced user! + Only for advanced user! + + + + VolumeControl + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Volume to reach + Volume to reach + + + + Fade + Fade + + + + VolumeControlError + + + Error during cue execution. + Error during cue execution. + + + + VolumeSettings + + + Volume + Volume + + + + Normalized volume + Normalized volume + + + + Reset + Reset + + + diff --git a/lisp/i18n/ts/ar_SA/list_layout.ts b/lisp/i18n/ts/ar_SA/list_layout.ts new file mode 100644 index 000000000..365da9c9f --- /dev/null +++ b/lisp/i18n/ts/ar_SA/list_layout.ts @@ -0,0 +1,230 @@ + + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them + To move cues drag them + + + + LayoutName + + + List Layout + List Layout + + + + ListLayout + + + Default behaviors + السلوك الافتراضي + + + + Show playing cues + Show playing cues + + + + Show dB-meters + إظهار dB-متر + + + + Show accurate time + Show accurate time + + + + Show seek-bars + اعرض أشرطة البحث + + + + Auto-select next cue + تحديد تلقائي الاغنية التالية + + + + Enable selection mode + Enable selection mode + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + توقف الاغنية + + + + Pause Cue + Pause Cue + + + + Resume Cue + إستئناف الاغنية + + + + Interrupt Cue + قطع الاغنية + + + + Edit cue + تصحيح الاغنية + + + + Remove cue + إزالة الأغاني + + + + Selection mode + وضع الإختيار + + + + Pause all + إيقاف مؤقت للكل + + + + Stop all + إيقاف الكل + + + + Interrupt all + قطع كل + + + + Resume all + إستئناف الكل + + + + Fade-Out all + تلاشى كل + + + + Fade-In all + Fade-In all + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove selected + Remove selected + + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Copy of {} + Copy of {} + + + + ListLayoutHeader + + + Cue + أغنية + + + + Pre wait + قبل الانتظار + + + + Action + الإجراءات + + + + Post wait + بعد الانتظار + + + + ListLayoutInfoPanel + + + Cue name + اسم الأغنية + + + + Cue description + تفاصيل الأغنية + + + + SettingsPageName + + + List Layout + List Layout + + + diff --git a/lisp/i18n/ts/ar_SA/media_info.ts b/lisp/i18n/ts/ar_SA/media_info.ts new file mode 100644 index 000000000..fe2c4ce19 --- /dev/null +++ b/lisp/i18n/ts/ar_SA/media_info.ts @@ -0,0 +1,32 @@ + + + + + MediaInfo + + + Media Info + Media Info + + + + No info to display + لا توجد معلومات لعرضها + + + + Info + معلومات + + + + Value + القيمة + + + + Warning + تحذير + + + diff --git a/lisp/i18n/ts/ar_SA/midi.ts b/lisp/i18n/ts/ar_SA/midi.ts new file mode 100644 index 000000000..4e3344055 --- /dev/null +++ b/lisp/i18n/ts/ar_SA/midi.ts @@ -0,0 +1,175 @@ + + + + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + MIDI Cue + MIDI Cue + + + + MIDICue + + + MIDI Message + MIDI Message + + + + Message type + نوع الرسالة + + + + MIDIMessageAttr + + + Channel + Channel + + + + Note + ملاحظة + + + + Velocity + Velocity + + + + Control + التحكم + + + + Program + البرنامج + + + + Value + القيمة + + + + Song + الأغنية + + + + Pitch + Pitch + + + + Position + الموقع + + + + MIDIMessageType + + + Note ON + Note ON + + + + Note OFF + Note OFF + + + + Polyphonic After-touch + Polyphonic After-touch + + + + Control/Mode Change + Control/Mode Change + + + + Program Change + تغيير البرنامج + + + + Channel After-touch + Channel After-touch + + + + Pitch Bend Change + Pitch Bend Change + + + + Song Select + حدد الأغنية + + + + Song Position + Song Position + + + + Start + إبدء + + + + Stop + توقف + + + + Continue + إستمرار + + + + MIDISettings + + + MIDI default devices + MIDI default devices + + + + Input + الإدخال + + + + Output + المخرج + + + + SettingsPageName + + + MIDI settings + MIDI settings + + + + MIDI Settings + MIDI Settings + + + diff --git a/lisp/i18n/ts/ar_SA/network.ts b/lisp/i18n/ts/ar_SA/network.ts new file mode 100644 index 000000000..5eb819730 --- /dev/null +++ b/lisp/i18n/ts/ar_SA/network.ts @@ -0,0 +1,71 @@ + + + + + APIServerInfo + + + Stop serving network API + Stop serving network API + + + + ApiServerError + + + Network API server stopped working. + Network API server stopped working. + + + + NetworkApiDebug + + + New end-point: {} + نقطة النهاية الجديدة: {} + + + + NetworkDiscovery + + + Host discovery + اكتشاف المضيف + + + + Manage hosts + إدارة المضيفين + + + + Discover hosts + اكتشف المضيفين + + + + Manually add a host + إضافة مضيف يدوياً + + + + Remove selected host + إزالة المضيف المحدد + + + + Remove all host + إزالة جميع المضيفين + + + + Address + العنوان + + + + Host IP + IP المضيف + + + diff --git a/lisp/i18n/ts/ar_SA/osc.ts b/lisp/i18n/ts/ar_SA/osc.ts new file mode 100644 index 000000000..52d1d07a1 --- /dev/null +++ b/lisp/i18n/ts/ar_SA/osc.ts @@ -0,0 +1,146 @@ + + + + + Cue Name + + + OSC Settings + OSC Settings + + + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + OSC Cue + OSC Cue + + + + OscCue + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + Add + Add + + + + Remove + Remove + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + رسالة من {}--المسار>: " args" {}: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + + + OscSettings + + + OSC Settings + إعدادات OSC + + + + Input Port: + منفذ الإدخال: + + + + Output Port: + منفذ الإخراج: + + + + Hostname: + Hostname: + + + + SettingsPageName + + + OSC settings + إعدادات OSC + + + diff --git a/lisp/i18n/ts/ar_SA/presets.ts b/lisp/i18n/ts/ar_SA/presets.ts new file mode 100644 index 000000000..4ae1de383 --- /dev/null +++ b/lisp/i18n/ts/ar_SA/presets.ts @@ -0,0 +1,135 @@ + + + + + Preset + + + Create Cue + Create Cue + + + + Load on selected Cues + Load on selected Cues + + + + Presets + + + Presets + إعدادات مسبقة + + + + Save as preset + الحفظ كإعداد مسبق + + + + Cannot scan presets + لا يمكن تفحص إعدادات مسبقة + + + + Error while deleting preset "{}" + Error while deleting preset "{}" + + + + Cannot load preset "{}" + Cannot load preset "{}" + + + + Cannot save preset "{}" + Cannot save preset "{}" + + + + Cannot rename preset "{}" + Cannot rename preset "{}" + + + + Select Preset + حدّد ضبط مُسبق + + + + Preset name + Preset name + + + + Add + أضف + + + + Rename + إعادة التسمية + + + + Edit + تعديل + + + + Remove + احذف + + + + Export selected + Export selected + + + + Import + تحميل + + + + Warning + تحذير + + + + The same name is already used! + هذا الإسم مستعمل مسبقا! + + + + Cannot export correctly. + Cannot export correctly. + + + + Cannot import correctly. + Cannot import correctly. + + + + Cue type + Cue type + + + + Load on cue + Load on cue + + + + Load on selected cues + Load on selected cues + + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + + + diff --git a/lisp/i18n/ts/ar_SA/rename_cues.ts b/lisp/i18n/ts/ar_SA/rename_cues.ts new file mode 100644 index 000000000..41a76d390 --- /dev/null +++ b/lisp/i18n/ts/ar_SA/rename_cues.ts @@ -0,0 +1,83 @@ + + + + + RenameCues + + + Rename Cues + إعادة تسمية الرموز + + + + Rename cues + Rename cues + + + + Current + الحالي + + + + Preview + استعراض + + + + Capitalize + كبّر الحروف + + + + Lowercase + احرف صغيرة + + + + Uppercase + أحرف كبيرة + + + + Remove Numbers + إزالة الأرقام + + + + Add numbering + إضافة أرقام + + + + Reset + إعادة ضبط + + + + Type your regex here: + أدخل regex هنا: + + + + Regex help + مساعدة regex + + + + RenameCuesCommand + + + Renamed {number} cues + Renamed {number} cues + + + + RenameUiDebug + + + Regex error: Invalid pattern + Regex error: Invalid pattern + + + diff --git a/lisp/i18n/ts/ar_SA/replay_gain.ts b/lisp/i18n/ts/ar_SA/replay_gain.ts new file mode 100644 index 000000000..0b1153562 --- /dev/null +++ b/lisp/i18n/ts/ar_SA/replay_gain.ts @@ -0,0 +1,83 @@ + + + + + ReplayGain + + + ReplayGain / Normalization + ReplayGain / Normalization + + + + Calculate + إحسب + + + + Reset all + إعادة كل شيء إلى الوضع العادى + + + + Reset selected + إعادة المحدد إلى الوضع العادى + + + + Threads number + Threads number + + + + Apply only to selected media + Apply only to selected media + + + + ReplayGain to (dB SPL) + ReplayGain to (dB SPL) + + + + Normalize to (dB) + Normalize to (dB) + + + + Processing files ... + معالجة الملفات... + + + + ReplayGainDebug + + + Applied gain for: {} + Applied gain for: {} + + + + Discarded gain for: {} + تجاهل الكسب: {} + + + + ReplayGainInfo + + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + بدأ حساب الربح عن: {} + + + + Gain calculated for: {} + حساب الربح بالنسبة: {} + + + diff --git a/lisp/i18n/ts/ar_SA/synchronizer.ts b/lisp/i18n/ts/ar_SA/synchronizer.ts new file mode 100644 index 000000000..9e3f6d86b --- /dev/null +++ b/lisp/i18n/ts/ar_SA/synchronizer.ts @@ -0,0 +1,27 @@ + + + + + Synchronizer + + + Synchronization + المزامنة + + + + Manage connected peers + إدارة الأقران متصل + + + + Show your IP + إظهار IP الخاص بك + + + + Your IP is: + IP الخاص بك: + + + diff --git a/lisp/i18n/ts/ar_SA/timecode.ts b/lisp/i18n/ts/ar_SA/timecode.ts new file mode 100644 index 000000000..7561177bf --- /dev/null +++ b/lisp/i18n/ts/ar_SA/timecode.ts @@ -0,0 +1,66 @@ + + + + + SettingsPageName + + + Timecode Settings + إعدادات رمز الوقت + + + + Timecode + رمز الوقت + + + + Timecode + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + TimecodeError + + + Cannot send timecode. + لا يمكن إرسال رمز الوقت. + + + + TimecodeSettings + + + Timecode Format: + Timecode Format: + + + + Replace HOURS by a static track number + Replace HOURS by a static track number + + + + Track number + رقم الأغنية + + + + Enable Timecode + تفعيل رمز الوقت + + + + Timecode Settings + إعدادات رمز الوقت + + + + Timecode Protocol: + رمز الوقت protocol: + + + diff --git a/lisp/i18n/ts/ar_SA/triggers.ts b/lisp/i18n/ts/ar_SA/triggers.ts new file mode 100644 index 000000000..efdb5d75e --- /dev/null +++ b/lisp/i18n/ts/ar_SA/triggers.ts @@ -0,0 +1,63 @@ + + + + + CueTriggers + + + Started + بدأت + + + + Paused + متوقف مؤقتا + + + + Stopped + توقفت + + + + Ended + انتهت + + + + SettingsPageName + + + Triggers + المشغلات + + + + TriggersSettings + + + Add + أضف + + + + Remove + احذف + + + + Trigger + مشغل + + + + Cue + Cue + + + + Action + الإجراءات + + + diff --git a/lisp/i18n/ts/en/controller.ts b/lisp/i18n/ts/en/controller.ts index da98848cc..dbdbec8e9 100644 --- a/lisp/i18n/ts/en/controller.ts +++ b/lisp/i18n/ts/en/controller.ts @@ -13,18 +13,23 @@ Key - Key + Key - + Action Action - + Shortcuts Shortcuts + + + Shortcut + + ControllerMidiSettings @@ -77,52 +82,52 @@ ControllerOscSettings - + OSC Message - + OSC - + Path - + Types - + Arguments - + Actions - + OSC Capture - + Add Add - + Remove Remove - + Capture Capture @@ -130,12 +135,12 @@ ControllerSettings - + Add Add - + Remove Remove @@ -143,52 +148,52 @@ GlobalAction - + Go - + Reset - + Stop all cues - + Pause all cues - + Resume all cues - + Interrupt all cues - + Fade-out all cues - + Fade-in all cues - + Move standby forward - + Move standby back @@ -196,12 +201,12 @@ Osc Cue - + Type Type - + Argument @@ -209,12 +214,12 @@ OscCue - + Add Add - + Remove Remove @@ -232,12 +237,12 @@ MIDI Controls - + Keyboard Shortcuts Keyboard Shortcuts - + OSC Controls diff --git a/lisp/i18n/ts/en/gst_backend.ts b/lisp/i18n/ts/en/gst_backend.ts index 3b7be8d54..df0a8e36d 100644 --- a/lisp/i18n/ts/en/gst_backend.ts +++ b/lisp/i18n/ts/en/gst_backend.ts @@ -113,12 +113,12 @@ GstBackend - + Audio cue (from file) - + Select media files @@ -126,7 +126,7 @@ GstMediaError - + Cannot create pipeline element: "{}" @@ -142,7 +142,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" diff --git a/lisp/i18n/ts/en/lisp.ts b/lisp/i18n/ts/en/lisp.ts index 43461f578..df5d423b7 100644 --- a/lisp/i18n/ts/en/lisp.ts +++ b/lisp/i18n/ts/en/lisp.ts @@ -129,7 +129,7 @@ ApplicationError - + Startup error @@ -404,18 +404,18 @@ ControllerKeySettings - - Key + + Action - - Action + + Shortcuts - - Shortcuts + + Shortcut @@ -470,52 +470,52 @@ ControllerOscSettings - + OSC Message - + OSC - + Path - + Types - + Arguments - + Actions - + OSC Capture - + Add - + Remove - + Capture @@ -523,12 +523,12 @@ ControllerSettings - + Add - + Remove @@ -536,7 +536,7 @@ Cue Name - + OSC Settings @@ -668,7 +668,7 @@ - + Misc cues @@ -724,12 +724,12 @@ - + MIDI Cue - + OSC Cue @@ -737,27 +737,27 @@ CueNextAction - + Do Nothing - + Trigger after the end - + Trigger after post wait - + Select after the end - + Select after post wait @@ -921,52 +921,52 @@ GlobalAction - + Go - + Reset - + Stop all cues - + Pause all cues - + Resume all cues - + Interrupt all cues - + Fade-out all cues - + Fade-in all cues - + Move standby forward - + Move standby back @@ -974,12 +974,12 @@ GstBackend - + Audio cue (from file) - + Select media files @@ -987,7 +987,7 @@ GstMediaError - + Cannot create pipeline element: "{}" @@ -1003,7 +1003,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" @@ -1024,6 +1024,14 @@ + + HotKeyEdit + + + Press shortcut + + + IndexActionCue @@ -1157,17 +1165,17 @@ LayoutSelect - + Layout selection Layout selection - + Select layout Select layout - + Open file Open file @@ -1422,7 +1430,7 @@ - + Linux Show Player - Log Viewer @@ -1657,132 +1665,132 @@ MainWindow - + &File &File - + New session New session - + Open Open - + Save session Save session - + Preferences Preferences - + Save as Save as - + Full Screen Full Screen - + Exit Exit - + &Edit &Edit - + Undo Undo - + Redo Redo - + Select all Select all - + Select all media cues Select all media cues - + Deselect all Deselect all - + CTRL+SHIFT+A CTRL+SHIFT+A - + Invert selection Invert selection - + CTRL+I CTRL+I - + Edit selected Edit selected - + CTRL+SHIFT+E CTRL+SHIFT+E - + &Layout &Layout - + &Tools &Tools - + Edit selection Edit selection - + &About &About - + About About - + About Qt About Qt - + Close session Close session @@ -1797,7 +1805,7 @@ Discard the changes? - + Do you want to save them now? @@ -1805,7 +1813,7 @@ MainWindowDebug - + Registered cue menu: "{}" @@ -1813,7 +1821,7 @@ MainWindowError - + Cannot create cue {} @@ -2006,12 +2014,12 @@ Osc Cue - + Type - + Argument @@ -2019,57 +2027,57 @@ OscCue - + Add - + Remove - + Type - + Value - + FadeTo - + Fade - + OSC Message - + OSC Path: - + /path/to/something - + Time (sec) - + Curve @@ -2320,7 +2328,7 @@ QColorButton - + Right click to reset Right click to reset @@ -2619,17 +2627,17 @@ - + Keyboard Shortcuts - + OSC Controls - + MIDI Settings @@ -2683,22 +2691,22 @@ Synchronizer - + Synchronization - + Manage connected peers - + Show your IP - + Your IP is: diff --git a/lisp/i18n/ts/en/midi.ts b/lisp/i18n/ts/en/midi.ts index f10d5d6c1..435abfff5 100644 --- a/lisp/i18n/ts/en/midi.ts +++ b/lisp/i18n/ts/en/midi.ts @@ -11,7 +11,7 @@ CueName - + MIDI Cue @@ -166,7 +166,7 @@ MIDI settings - + MIDI Settings diff --git a/lisp/i18n/ts/en/osc.ts b/lisp/i18n/ts/en/osc.ts index 91fe74e2e..30d8e8c57 100644 --- a/lisp/i18n/ts/en/osc.ts +++ b/lisp/i18n/ts/en/osc.ts @@ -3,7 +3,7 @@ Cue Name - + OSC Settings @@ -19,7 +19,7 @@ CueName - + OSC Cue @@ -27,57 +27,57 @@ OscCue - + Type - + Value - + FadeTo - + Fade - + OSC Message - + Add - + Remove - + OSC Path: - + /path/to/something - + Time (sec) - + Curve diff --git a/lisp/i18n/ts/en/synchronizer.ts b/lisp/i18n/ts/en/synchronizer.ts index 3bea676b1..482dda3fb 100644 --- a/lisp/i18n/ts/en/synchronizer.ts +++ b/lisp/i18n/ts/en/synchronizer.ts @@ -3,22 +3,22 @@ Synchronizer - + Synchronization Synchronization - + Manage connected peers Manage connected peers - + Show your IP Show your IP - + Your IP is: Your IP is: diff --git a/lisp/i18n/ts/fr_FR/lisp.ts b/lisp/i18n/ts/fr_FR/lisp.ts index ddc724297..dac9aa26b 100644 --- a/lisp/i18n/ts/fr_FR/lisp.ts +++ b/lisp/i18n/ts/fr_FR/lisp.ts @@ -1,6 +1,14 @@ + + APIServerInfo + + + Stop serving network API + Stop serving network API + + About @@ -39,7 +47,7 @@ Info - Info + Informations @@ -54,20 +62,23 @@ Discussion - Discussion + Discussion - Actions + AlsaSinkSettings - - Undo: {} - Undo: {} + + ALSA device + ALSA device + + + ApiServerError - - Redo: {} - Redo: {} + + Network API server stopped working. + Network API server stopped working. @@ -81,45 +92,298 @@ AppGeneralSettings - + Default layout Default layout - + Enable startup layout selector Enable startup layout selector - + Application themes Application themes - + UI theme: - UI theme: + Thème UI : - + Icons theme: - Icons theme: + Thème des icônes : + + + + Application language (require restart) + Application language (require restart) + + + + Language: + Langue : ApplicationError - + Startup error Startup error + + AudioDynamicSettings + + + Compressor + Compressor + + + + Expander + Expander + + + + Soft Knee + Soft Knee + + + + Hard Knee + Hard Knee + + + + Compressor/Expander + Compressor/Expander + + + + Type + Type + + + + Curve Shape + Curve Shape + + + + Ratio + Ratio + + + + Threshold (dB) + Seuil (dB) + + + + AudioPanSettings + + + Audio Pan + Audio Pan + + + + Center + Center + + + + Left + Gauche + + + + Right + Droite + + + + CartLayout + + + Default behaviors + Comportements par défaut + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Afficher le temps exact + + + + Show volume + Afficher volume + + + + Grid size + Grid size + + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Réinitialiser le volume + + + + Add page + Ajouter une page + + + + Add pages + Ajouter des pages + + + + Remove current page + Retirer la page actuelle + + + + Number of Pages: + Number of Pages: + + + + Page {number} + Page {number} + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Voulez-vous vraiment continuer ? + + + + CollectionCue + + + Add + Ajouter + + + + Remove + Retirer + + + + Cue + Cue + + + + Action + Action + + + + CommandCue + + + Command + Commande + + + + Command to execute, as in a shell + Command to execute, as in a shell + + + + Discard command output + Discard command output + + + + Ignore command errors + Ignore command errors + + + + Kill instead of terminate + Kill instead of terminate + + + + CommandsStack + + + Undo: {} + Annuler : {} + + + + Redo: {} + Refaire : {} + + ConfigurationDebug Configuration written at {} - Configuration written at {} + Configuration écrite à {} @@ -127,7 +391,155 @@ New configuration installed at {} - New configuration installed at {} + Nouvelle configuration installée à {} + + + + Controller + + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + + ControllerKeySettings + + + Key + Touche + + + + Action + Action + + + + Shortcuts + Shortcuts + + + + ControllerMidiSettings + + + MIDI + MIDI + + + + Type + Type + + + + Channel + Channel + + + + Note + Note + + + + Action + Action + + + + Filter "note on" + Filter "note on" + + + + Filter "note off" + Filter "note off" + + + + Capture + Capture + + + + Listening MIDI messages ... + Listening MIDI messages ... + + + + ControllerOscSettings + + + OSC Message + OSC Message + + + + OSC + OSC + + + + Path + Path + + + + Types + Types + + + + Arguments + Arguments + + + + Actions + Actions + + + + OSC Capture + Capture OSC + + + + Add + Ajouter + + + + Remove + Retirer + + + + Capture + Capture + + + + ControllerSettings + + + Add + Ajouter + + + + Remove + Retirer + + + + Cue Name + + + OSC Settings + Paramètres OSC @@ -140,7 +552,7 @@ Pause - Pause + Suspendre @@ -150,7 +562,7 @@ Stop - Stop + Arrêter @@ -185,7 +597,7 @@ Do Nothing - Do Nothing + Ne rien faire @@ -244,6 +656,37 @@ Sélectionner la couleur de la police + + CueCategory + + + Action cues + Action cues + + + + Integration cues + Integration cues + + + + Misc cues + Misc cues + + + + CueCommandLog + + + Cue settings changed: "{}" + Cue settings changed: "{}" + + + + Cues settings changed. + Cues settings changed. + + CueName @@ -251,13 +694,53 @@ Media Cue Cue de média + + + Index Action + Index Action + + + + Seek Cue + Seek Cue + + + + Volume Control + Volume Control + + + + Command Cue + Command Cue + + + + Collection Cue + Collection Cue + + + + Stop-All + Stop-All + + + + MIDI Cue + MIDI Cue + + + + OSC Cue + OSC Cue + CueNextAction Do Nothing - Do Nothing + Ne rien faire @@ -338,6 +821,60 @@ Fade actions + + CueTriggers + + + Started + Démarré + + + + Paused + En pause + + + + Stopped + Arrêté + + + + Ended + Terminé + + + + DbMeterSettings + + + DbMeter settings + DbMeter settings + + + + Time between levels (ms) + Time between levels (ms) + + + + Peak ttl (ms) + Peak ttl (ms) + + + + Peak falloff (dB/sec) + Peak falloff (dB/sec) + + + + Equalizer10Settings + + + 10 Bands Equalizer (IIR) + 10 Bands Equalizer (IIR) + + Fade @@ -361,7 +898,7 @@ Duration (sec): - Duration (sec): + Durée (sec): @@ -383,28 +920,264 @@ - LayoutSelect + GlobalAction - - Layout selection - Sélection de l'agencement + + Go + Go - - Select layout - Sélectionner l'agencement + + Reset + Réinitialiser - - Open file - Ouvrir un fichier + + Stop all cues + Arrêter toutes les cues - - - ListLayout - - Stop All + + Pause all cues + Mettre en pause toutes les cues + + + + Resume all cues + Reprendre toutes les cues + + + + Interrupt all cues + Interrompre toutes les cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + + + GstMediaSettings + + + Change Pipeline + Change Pipeline + + + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + + + GstPipelineEdit + + + Edit Pipeline + Edit Pipeline + + + + GstSettings + + + Pipeline + Pipeline + + + + IndexActionCue + + + No suggestion + No suggestion + + + + Index + Index + + + + Use a relative index + Use a relative index + + + + Target index + Target index + + + + Action + Action + + + + Suggested cue name + Suggested cue name + + + + JackSinkSettings + + + Connections + Connections + + + + Edit connections + Éditer les connexions + + + + Output ports + Output ports + + + + Input ports + Input ports + + + + Connect + Connecter + + + + Disconnect + Déconnecter + + + + LayoutDescription + + + Organize the cues in a list + Organiser les cues dans une liste + + + + Organize cues in grid like pages + Organiser les cues en grille comme les pages + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Espace ou Double-Clic pour éditer une cue + + + + To copy cues drag them while pressing CTRL + Pour copier les cues, glissez-les en pressant CTRL + + + + To move cues drag them + Pour déplacer les cues, glissez-les + + + + Click a cue to run it + Cliquer sur une cue pour l'exécuter + + + + SHIFT + Click to edit a cue + MAJ + clic pour éditer une cue + + + + CTRL + Click to select a cue + CTRL + clic pour sélectionner une cue + + + + To move cues drag them while pressing SHIFT + Pour déplacer les cues, glissez-les en pressant SHIFT + + + + LayoutName + + + List Layout + List Layout + + + + Cart Layout + Cart Layout + + + + LayoutSelect + + + Layout selection + Sélection de l'agencement + + + + Select layout + Sélectionner l'agencement + + + + Open file + Ouvrir un fichier + + + + ListLayout + + + Stop All Tout arrêter @@ -417,15 +1190,209 @@ Interrupt All Tout interrompre + + + Default behaviors + Comportements par défaut + + + + Show playing cues + Afficher les cues en cours + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Afficher le temps exact + + + + Show seek-bars + Afficher la barre de progression + + + + Auto-select next cue + Auto-sélectionner la prochaine cue + + + + Enable selection mode + Activer le mode sélection + + + + GO Key: + Touche GO: + + + + GO Action: + Action GO: + + + + GO minimum interval (ms): + Intervalle minimum GO (ms): + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Arrêter la cue + + + + Pause Cue + Mettre la cue en pause + + + + Resume Cue + Reprendre la cue + + + + Interrupt Cue + Interrompre la cue + + + + Edit cue + Éditer la cue + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove cue + Remove cue + + + + Remove selected + Remove selected + + + + Selection mode + Mode sélection + + + + Pause all + Tout mettre en pause + + + + Stop all + Tout stopper + + + + Interrupt all + Tout interrompre + + + + Resume all + Tout reprendre + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + Edit selected cues + Éditer les cues sélectionnées + + + + Remove selected cues + Retirer les cues sélectionnées + - Use fade (global actions) - Use fade (global actions) + Use fade + Utiliser le fondu + + + + Copy of {} + Copie de {} + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pré-attente + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + LogStatusIcon - - Resume All - Resume All + + Errors/Warnings + Errors/Warnings @@ -453,7 +1420,7 @@ Show details - Show details + Afficher les détails @@ -463,22 +1430,22 @@ Info - Info + Informations Critical - Critical + Critique Time - Time + Temps Milliseconds - Milliseconds + Millisecondes @@ -488,17 +1455,17 @@ Level - Level + Niveau Message - Message + Message Function - Function + Fonction @@ -508,7 +1475,7 @@ File name - File name + Nom du fichier @@ -518,17 +1485,17 @@ Module - Module + Module Process ID - Process ID + ID du processus Process name - Process name + Nom du processus @@ -547,134 +1514,276 @@ - MainWindow + MIDICue - - &File - &Fichier + + MIDI Message + MIDI Message - - New session - Nouvelle session + + Message type + Message type + + + MIDIMessageAttr - - Open - Ouvrir + + Channel + Channel - - Save session - Sauvegarder la session + + Note + Note - - Preferences - Préférences + + Velocity + Velocity - - Save as - Sauvegarder sous + + Control + Control - - Full Screen - Plein écran + + Program + Program - - Exit - Quitter + + Value + Value - - &Edit - Édit&er + + Song + Song - - Undo - Annuler + + Pitch + Pitch - - Redo - Refaire + + Position + Position + + + MIDIMessageType - - Select all - Tout sélectionner + + Note ON + Note ON - - Select all media cues - Sélectionner toutes les cues média + + Note OFF + Note OFF - - Deselect all - Tout désélectionner + + Polyphonic After-touch + Polyphonic After-touch - - CTRL+SHIFT+A - CTRL+MAJ+A + + Control/Mode Change + Control/Mode Change - - Invert selection - Inverser la sélection + + Program Change + Program Change - - CTRL+I - CTRL+I + + Channel After-touch + Channel After-touch - - Edit selected - Éditer la sélection + + Pitch Bend Change + Pitch Bend Change - + + Song Select + Song Select + + + + Song Position + Song Position + + + + Start + Start + + + + Stop + Stop + + + + Continue + Continue + + + + MIDISettings + + + MIDI default devices + MIDI default devices + + + + Input + Input + + + + Output + Output + + + + MainWindow + + + &File + &Fichier + + + + New session + Nouvelle session + + + + Open + Ouvrir + + + + Save session + Sauvegarder la session + + + + Preferences + Préférences + + + + Save as + Sauvegarder sous + + + + Full Screen + Plein écran + + + + Exit + Quitter + + + + &Edit + Édit&er + + + + Undo + Annuler + + + + Redo + Refaire + + + + Select all + Tout sélectionner + + + + Select all media cues + Sélectionner toutes les cues média + + + + Deselect all + Tout désélectionner + + + + CTRL+SHIFT+A + CTRL+MAJ+A + + + + Invert selection + Inverser la sélection + + + + CTRL+I + CTRL+I + + + + Edit selected + Éditer la sélection + + + CTRL+SHIFT+E CTRL+MAJ+E - + &Layout É&tat - + &Tools Ou&tils - + Edit selection Éditer la sélection - + &About À &propos - + About À propos - + About Qt À propos de Qt - + Close session Fermer la session @@ -688,6 +1797,27 @@ Discard the changes? Annuler les changements ? + + + Do you want to save them now? + Do you want to save them now? + + + + MainWindowDebug + + + Registered cue menu: "{}" + Registered cue menu: "{}" + + + + MainWindowError + + + Cannot create cue {} + Cannot create cue {} + MediaCueSettings @@ -717,6 +1847,299 @@ Boucle + + MediaElementName + + + Pitch + Pitch + + + + Volume + Volume + + + + Audio Pan + Audio Pan + + + + Compressor/Expander + Compressor/Expander + + + + JACK Out + JACK Out + + + + URI Input + URI Input + + + + 10 Bands Equalizer + 10 Bands Equalizer + + + + ALSA Out + ALSA Out + + + + dB Meter + dB Meter + + + + Preset Input + Preset Input + + + + PulseAudio Out + PulseAudio Out + + + + Custom Element + Custom Element + + + + Speed + Speed + + + + System Out + System Out + + + + System Input + System Input + + + + MediaInfo + + + Media Info + Media Info + + + + Warning + Warning + + + + No info to display + No info to display + + + + Info + Info + + + + Value + Value + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + OscCue + + + Add + Add + + + + Remove + Remove + + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + PitchSettings + + + Pitch + Pitch + + + + {0:+} semitones + {0:+} semitones + + PluginsError @@ -757,22 +2180,347 @@ - QColorButton + Preset - - Right click to reset - Clic-droit pour réinitialiser + + Create Cue + Create Cue + + + + Load on selected Cues + Load on selected Cues - SettingsPageName + PresetSrcSettings - + + Presets + Presets + + + + Presets + + + Cannot scan presets + Cannot scan presets + + + + Error while deleting preset "{}" + Error while deleting preset "{}" + + + + Cannot load preset "{}" + Cannot load preset "{}" + + + + Cannot save preset "{}" + Cannot save preset "{}" + + + + Cannot rename preset "{}" + Cannot rename preset "{}" + + + + Select Preset + Select Preset + + + + Presets + Presets + + + + Preset name + Preset name + + + + Add + Add + + + + Rename + Rename + + + + Edit + Edit + + + + Remove + Remove + + + + Export selected + Export selected + + + + Import + Import + + + + Warning + Warning + + + + The same name is already used! + The same name is already used! + + + + Cannot export correctly. + Cannot export correctly. + + + + Cannot import correctly. + Cannot import correctly. + + + + Cue type + Cue type + + + + Load on cue + Load on cue + + + + Load on selected cues + Load on selected cues + + + + Save as preset + Save as preset + + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + + + + QColorButton + + + Right click to reset + Clic-droit pour réinitialiser + + + + RenameCues + + + Rename Cues + Rename Cues + + + + Rename cues + Rename cues + + + + Current + Current + + + + Preview + Preview + + + + Capitalize + Capitalize + + + + Lowercase + Lowercase + + + + Uppercase + Uppercase + + + + Remove Numbers + Remove Numbers + + + + Add numbering + Add numbering + + + + Reset + Reset + + + + Type your regex here: + Type your regex here: + + + + Regex help + Regex help + + + + RenameCuesCommand + + + Renamed {number} cues + Renamed {number} cues + + + + RenameUiDebug + + + Regex error: Invalid pattern + Regex error: Invalid pattern + + + + ReplayGain + + + ReplayGain / Normalization + ReplayGain / Normalization + + + + Threads number + Threads number + + + + Apply only to selected media + Apply only to selected media + + + + ReplayGain to (dB SPL) + ReplayGain to (dB SPL) + + + + Normalize to (dB) + Normalize to (dB) + + + + Processing files ... + Processing files ... + + + + Calculate + Calculate + + + + Reset all + Reset all + + + + Reset selected + Reset selected + + + + ReplayGainDebug + + + Applied gain for: {} + Applied gain for: {} + + + + Discarded gain for: {} + Discarded gain for: {} + + + + ReplayGainInfo + + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} + + + + SeekCue + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Seek + Seek + + + + Time to reach + Time to reach + + + + SettingsPageName + + Appearance Apparence - + General Générale @@ -811,5 +2559,336 @@ Layouts Layouts + + + Action Settings + Action Settings + + + + Seek Settings + Seek Settings + + + + Volume Settings + Volume Settings + + + + Command + Command + + + + Edit Collection + Edit Collection + + + + Stop Settings + Stop Settings + + + + List Layout + List Layout + + + + Timecode + Timecode + + + + Timecode Settings + Timecode Settings + + + + Cue Control + Cue Control + + + + Layout Controls + Layout Controls + + + + MIDI Controls + MIDI Controls + + + + Keyboard Shortcuts + Keyboard Shortcuts + + + + OSC Controls + OSC Controls + + + + MIDI Settings + MIDI Settings + + + + MIDI settings + MIDI settings + + + + GStreamer + GStreamer + + + + Media Settings + Media Settings + + + + Triggers + Triggers + + + + OSC settings + OSC settings + + + + Cart Layout + Cart Layout + + + + SpeedSettings + + + Speed + Speed + + + + StopAll + + + Stop Action + Stop Action + + + + Synchronizer + + + Synchronization + Synchronization + + + + Manage connected peers + Manage connected peers + + + + Show your IP + Show your IP + + + + Your IP is: + Your IP is: + + + + Timecode + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + TimecodeError + + + Cannot send timecode. + Cannot send timecode. + + + + TimecodeSettings + + + Replace HOURS by a static track number + Replace HOURS by a static track number + + + + Enable Timecode + Enable Timecode + + + + Track number + Track number + + + + Timecode Settings + Timecode Settings + + + + Timecode Format: + Timecode Format: + + + + Timecode Protocol: + Timecode Protocol: + + + + TriggersSettings + + + Add + Add + + + + Remove + Remove + + + + Trigger + Trigger + + + + Cue + Cue + + + + Action + Action + + + + UriInputSettings + + + Source + Source + + + + Find File + Find File + + + + Buffering + Buffering + + + + Use Buffering + Use Buffering + + + + Attempt download on network streams + Attempt download on network streams + + + + Buffer size (-1 default value) + Buffer size (-1 default value) + + + + Choose file + Choose file + + + + All files + All files + + + + UserElementSettings + + + User defined elements + User defined elements + + + + Only for advanced user! + Only for advanced user! + + + + VolumeControl + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Volume to reach + Volume to reach + + + + Fade + Fade + + + + VolumeControlError + + + Error during cue execution. + Error during cue execution. + + + + VolumeSettings + + + Volume + Volume + + + + Normalized volume + Normalized volume + + + + Reset + Reset + From fd883a43750064849516dee5cb7b6f8e786e237d Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 21 May 2019 21:49:26 +0200 Subject: [PATCH 185/333] Allow color and font change in ListLayout #36 #73 #175 --- lisp/core/util.py | 11 +++ lisp/i18n/qm/base_en.qm | Bin 21618 -> 21163 bytes lisp/i18n/qm/base_fr_FR.qm | Bin 43571 -> 45318 bytes lisp/i18n/ts/en/action_cues.ts | 36 --------- lisp/i18n/ts/en/controller.ts | 5 -- lisp/i18n/ts/en/lisp.ts | 76 +++++-------------- lisp/i18n/ts/en/list_layout.ts | 44 +++++------ lisp/plugins/list_layout/layout.py | 35 +++------ lisp/plugins/list_layout/list_view.py | 40 +++++++--- lisp/plugins/list_layout/list_widgets.py | 5 +- lisp/ui/settings/cue_pages/cue_appearance.py | 24 +----- lisp/ui/ui_utils.py | 22 ++++++ pyproject.toml | 2 +- 13 files changed, 119 insertions(+), 181 deletions(-) diff --git a/lisp/core/util.py b/lisp/core/util.py index 8f6baaf11..eb7c74ae6 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -68,6 +68,17 @@ def dict_merge_diff(dct, cmp_dct): return diff +def subdict(d, keys): + return dict(isubdict(d, keys)) + + +def isubdict(d, keys): + for k in keys: + v = d.get(k) + if v is not None: + yield k, v + + def find_packages(path="."): """List the python packages in the given directory.""" diff --git a/lisp/i18n/qm/base_en.qm b/lisp/i18n/qm/base_en.qm index 605b2b478a592c787e14aa0415f0e6a59a2ea03a..a356b3356b6a025a88fe66aacfc7e825641853b3 100644 GIT binary patch delta 2297 zcmYk7d015E6~@oZ+SRW*U1dRx&DX0;mJYsdKw5bak z5jBPwMS()pKIRdWMAW)~pcz~uNyQkO2GXXA_(ae)iP*lA@1OqlyUTaa_n!Bh^WCTI zs^hJy-FD^!L>Vna!VsfU$fy??gXS^@KV~#tBFa8Zq~#c0&NIfuF~-egG(BO=%pk5C zYs2b@>uV*-tR(Ix?me4`=WY_2RXOa!^(AAVLj25aMDl6k{RfHo4&tMh5c#YmegOi= zqr@+}K;+d({Em}E-aPRibQ8rO;Bl-I(i!!LXT5jZPW;6(#L+S;9*jZW#P^&eGx?;D zji-soA&UI0O5Kl>-z_h2h~k>;i4s1ijQu@CvnnXNWGRthHf4X>MdaE}bFw>$rj^s2 z_wn5I4$aH%Cvx_ndHGoHc9s^@?Sr7{jQSSJm*BRag)!(b6>R^nnTU&~f@}C7YX=of zPC)4h6_z8g{{hCt=Tz9fmq>q`(Jh}|?`tDUze`26C`I5^s_>5_aymoncf267oki_U zNWjoc4~u?JcL^dYX2Lq*8Sk34;RW-Dg zBbu^6HSWHN=vM{mJ;U`xkuTM4RwvwR)F+CuLDU*`moEbP98z~L3x`8->h2FSQR9E8 zA0#7g+&%S^R1Hy5srtv>ULsYs#!-#Z*&on2Kd2&_xlrS2P7WdpS*Mx(gOxU#=tJ1p zpERjMKM;AS!DnEaX74*XYZ2KhDj6f}8B=HSb=Hj{O#FMv1|*>Hf9l@{2afPx?1j@& zll+(QcpqRsz%H`R^MB2PC7z!N%HORSydyX)*n|ud zDo!A>dqb${fyL7AgvMtOtlK1X@3}&hXk<+JmvAEj&okHv> zv*t~${U8>+a#;JS2b4}D?ba!?u;8M$?%*&SIK>z~qCNTu0>m0^TP^}Qexv>5bA0ER zs6BV}4pFQuYMo$t^i#%+|AQBjJAX^c33LQ8Nn3%EG^$$iUgY&T^>o56A&`>qEwY&z5;<3X~RvNOfmgZ zLphY&u8`Ur@!>R!bY>msppwoEypMCBSLzlJD8ODiR|m)RYoy!hVG#6@H15d5>ROo; zI1ztBrb)c_x5z4&8<>`OM&F|{w`doR@EF8s>-zeXuj^W^3i3XE%$&;J_wZO+OA zO&XlQ59M1WNL0Kl-_Kc#(wEEM9I=H_k@9%pZX6SRiqM#bo*q}cE~KCpLhdPHmR5{y zo07W_&U=?CubDoD0gsjZ*D=1vc4hI#JRF2al;wZMz4Sn-dHVu{98>n&UB-zjDb0K* zHgr^)$Lw){^2#v-5>7s-oUNL`Gi30+sr! zf;H0%`p5MVh%=@iTbqSAod(~*2iRCPEXu`!npR|3ra~ebEojDt@3)4E>%&mA%dlZE z2F)IA`0NvuMEjoM{tibtvEA^~Xf39*+$dQs_qbv7E}2ghbjuj}x7GN^L>co6%kVFY zG_IJdM&J%(`N?KDIbvKt3PFi2#*G7>XtgDbnN7y7Q)reyos?vs!9Tpn??|#ne9rrW#EB^xwC0Mio delta 2603 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 Qt#~$`sk1`apPnuHZz{G=!~g&Q delta 9200 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 diff --git a/lisp/i18n/ts/en/action_cues.ts b/lisp/i18n/ts/en/action_cues.ts index fd8a9c3e6..1aaf14dca 100644 --- a/lisp/i18n/ts/en/action_cues.ts +++ b/lisp/i18n/ts/en/action_cues.ts @@ -66,11 +66,6 @@ Command Cue Command Cue - - - MIDI Cue - MIDI Cue - Volume Control @@ -130,32 +125,6 @@ - - Osc Cue - - - Fade - Fade - - - - OscCue - - - Add - Add - - - - Remove - Remove - - - - Fade - Fade - - SeekCue @@ -191,11 +160,6 @@ Command Command - - - MIDI Settings - MIDI Settings - Volume Settings diff --git a/lisp/i18n/ts/en/controller.ts b/lisp/i18n/ts/en/controller.ts index dbdbec8e9..e176368ef 100644 --- a/lisp/i18n/ts/en/controller.ts +++ b/lisp/i18n/ts/en/controller.ts @@ -10,11 +10,6 @@ ControllerKeySettings - - - Key - Key - Action diff --git a/lisp/i18n/ts/en/lisp.ts b/lisp/i18n/ts/en/lisp.ts index df5d423b7..d7f82ee8d 100644 --- a/lisp/i18n/ts/en/lisp.ts +++ b/lisp/i18n/ts/en/lisp.ts @@ -599,19 +599,6 @@ - - CueActionLog - - - Cue settings changed: "{}" - Cue settings changed: "{}" - - - - Cues settings changed. - Cues settings changed. - - CueAppearanceSettings @@ -1101,7 +1088,7 @@ LayoutDescription - + Organize the cues in a list @@ -1114,7 +1101,7 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue @@ -1124,7 +1111,7 @@ - + To move cues drag them @@ -1152,7 +1139,7 @@ LayoutName - + List Layout @@ -1182,48 +1169,33 @@ ListLayout - - - Stop All - Stop All - - - - Pause All - Pause All - - - - Interrupt All - Interrupt All - Default behaviors - + Show playing cues - + Show dB-meters - + Show accurate time - + Show seek-bars - + Auto-select next cue @@ -1278,17 +1250,17 @@ - + Edit selected Edit selected - + Clone cue - + Clone selected @@ -1298,12 +1270,12 @@ - + Remove selected - + Selection mode @@ -1353,7 +1325,7 @@ - + Copy of {} @@ -1361,22 +1333,22 @@ ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action - + Post wait Post wait @@ -1794,16 +1766,6 @@ Close session Close session - - - The current session is not saved. - The current session is not saved. - - - - Discard the changes? - Discard the changes? - Do you want to save them now? diff --git a/lisp/i18n/ts/en/list_layout.ts b/lisp/i18n/ts/en/list_layout.ts index 4f8a34b25..9eca834b8 100644 --- a/lisp/i18n/ts/en/list_layout.ts +++ b/lisp/i18n/ts/en/list_layout.ts @@ -3,7 +3,7 @@ LayoutDescription - + Organize the cues in a list @@ -11,17 +11,17 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue - + To copy cues drag them while pressing CTRL - + To move cues drag them @@ -29,7 +29,7 @@ LayoutName - + List Layout @@ -42,27 +42,27 @@ - + Show playing cues - + Show dB-meters - + Show accurate time - + Show seek-bars - + Auto-select next cue @@ -97,17 +97,17 @@ - + Edit cue - + Remove cue - + Selection mode @@ -142,22 +142,22 @@ - + Edit selected - + Clone cue - + Clone selected - + Remove selected @@ -177,7 +177,7 @@ - + Copy of {} @@ -185,22 +185,22 @@ ListLayoutHeader - + Cue - + Pre wait - + Action - + Post wait diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index c0a10d2a8..aa09e79e5 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -17,7 +17,7 @@ from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP, QTimer from PyQt5.QtGui import QKeySequence -from PyQt5.QtWidgets import QAction +from PyQt5.QtWidgets import QAction, QTreeWidget from lisp.command.model import ModelInsertItemsCommand from lisp.core.configuration import DummyConfiguration @@ -35,6 +35,7 @@ from lisp.plugins.list_layout.models import CueListModel, RunningCueModel from lisp.plugins.list_layout.view import ListLayoutView from lisp.ui.ui_utils import translate +from lisp.ui.widgets.hotkeyedit import keyEventKeySequence class ListLayout(CueLayout): @@ -241,36 +242,24 @@ def invert_selection(self): def _key_pressed(self, event): event.ignore() - if not event.isAutoRepeat(): - keys = event.key() - modifiers = event.modifiers() - - if modifiers & Qt.ShiftModifier: - keys += Qt.SHIFT - if modifiers & Qt.ControlModifier: - keys += Qt.CTRL - if modifiers & Qt.AltModifier: - keys += Qt.ALT - if modifiers & Qt.MetaModifier: - keys += Qt.META - - sequence = QKeySequence(keys) + sequence = keyEventKeySequence(event) if sequence in self._go_key_sequence: event.accept() self.__go_slot() elif sequence == QKeySequence.Delete: + event.accept() self._remove_cues(self.selected_cues()) - elif event.key() == Qt.Key_Space: - if event.modifiers() == Qt.ShiftModifier: - event.accept() - - cue = self.standby_cue() - if cue is not None: - self.edit_cue(cue) + elif ( + event.key() == Qt.Key_Space + and event.modifiers() == Qt.ShiftModifier + ): + event.accept() + cue = self.standby_cue() + if cue is not None: + self.edit_cue(cue) else: self.key_pressed.emit(event) - event.ignore() @accurate_time.set def _set_accurate_time(self, accurate): diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py index d48e3e45a..34822f9a0 100644 --- a/lisp/plugins/list_layout/list_view.py +++ b/lisp/plugins/list_layout/list_view.py @@ -27,6 +27,7 @@ from lisp.application import Application from lisp.command.model import ModelMoveItemsCommand, ModelInsertItemsCommand +from lisp.core.util import subdict from lisp.cues.cue_factory import CueFactory from lisp.plugins.list_layout.list_widgets import ( CueStatusIcons, @@ -37,7 +38,7 @@ PostWaitWidget, IndexWidget, ) -from lisp.ui.ui_utils import translate +from lisp.ui.ui_utils import translate, css_to_dict, dict_to_css class ListColumn: @@ -171,7 +172,6 @@ def contextMenuEvent(self, event): self.contextMenuInvoked.emit(event) def keyPressEvent(self, event): - event.ignore() self.keyPressed.emit(event) # If the event object has been accepted during the `keyPressed` # emission don't call the base implementation @@ -194,27 +194,43 @@ def setStandbyIndex(self, newIndex): def __currentItemChanged(self, current, previous): if previous is not None: - self.__updateItem(previous, False) + previous.current = False + self.__updateItemStyle(previous) if current is not None: - self.__updateItem(current, True) + current.current = True + self.__updateItemStyle(current) if self.selectionMode() == QTreeWidget.NoSelection: # ensure the current item is in the middle self.scrollToItem(current, QTreeWidget.PositionAtCenter) - def __updateItem(self, item, current): - item.current = current - if current: - background = CueListView.ITEM_CURRENT_BG + def __updateItemStyle(self, item): + css = css_to_dict(item.cue.stylesheet) + brush = QBrush() + + if item.current: + widget_css = subdict(css, ("font-size",)) + brush = CueListView.ITEM_CURRENT_BG else: - background = CueListView.ITEM_DEFAULT_BG + widget_css = subdict(css, ("color", "font-size")) + css_bg = css.get("background") + if css_bg is not None: + color = QColor(css_bg) + color.setAlpha(150) + brush = QBrush(color) for column in range(self.columnCount()): - item.setBackground(column, background) + self.itemWidget(item, column).setStyleSheet(dict_to_css(widget_css)) + item.setBackground(column, brush) + + def __cuePropChanged(self, cue, property_name, _): + if property_name == "stylesheet": + self.__updateItemStyle(self.topLevelItem(cue.index)) def __cueAdded(self, cue): item = CueTreeWidgetItem(cue) item.setFlags(item.flags() & ~Qt.ItemIsDropEnabled) + cue.property_changed.connect(self.__cuePropChanged) self.insertTopLevelItem(cue.index, item) self.__setupItemWidgets(item) @@ -235,8 +251,8 @@ def __cueMoved(self, before, after): self.__setupItemWidgets(item) def __cueRemoved(self, cue): - index = cue.index - self.takeTopLevelItem(index) + cue.property_changed.disconnect(self.__cuePropChanged) + self.takeTopLevelItem(cue.index) def __modelReset(self): self.reset() diff --git a/lisp/plugins/list_layout/list_widgets.py b/lisp/plugins/list_layout/list_widgets.py index 6ab1f4b52..43c947f5c 100644 --- a/lisp/plugins/list_layout/list_widgets.py +++ b/lisp/plugins/list_layout/list_widgets.py @@ -64,6 +64,8 @@ class CueStatusIcons(QWidget): def __init__(self, item, *args): super().__init__(*args) + self.setAttribute(Qt.WA_TranslucentBackground) + self._icon = None self._item = item @@ -132,12 +134,11 @@ def paintEvent(self, event): class NextActionIcon(QLabel): - STYLESHEET = "background: transparent;" SIZE = 16 def __init__(self, item, *args): super().__init__(*args) - self.setStyleSheet(self.STYLESHEET) + self.setAttribute(Qt.WA_TranslucentBackground) self.setAlignment(Qt.AlignCenter) item.cue.changed("next_action").connect( diff --git a/lisp/ui/settings/cue_pages/cue_appearance.py b/lisp/ui/settings/cue_pages/cue_appearance.py index d6fa98e02..232246933 100644 --- a/lisp/ui/settings/cue_pages/cue_appearance.py +++ b/lisp/ui/settings/cue_pages/cue_appearance.py @@ -27,7 +27,7 @@ ) from lisp.ui.settings.pages import SettingsPage -from lisp.ui.ui_utils import translate +from lisp.ui.ui_utils import translate, css_to_dict, dict_to_css from lisp.ui.widgets import ColorButton @@ -155,25 +155,3 @@ def loadSettings(self, settings): if "font-size" in settings: # [:-2] for removing "pt" self.fontSizeSpin.setValue(int(settings["font-size"][:-2])) - - -def css_to_dict(css): - dict = {} - css = css.strip() - - for attribute in css.split(";"): - try: - name, value = attribute.split(":") - dict[name.strip()] = value.strip() - except Exception: - pass - - return dict - - -def dict_to_css(css_dict): - css = "" - for name in css_dict: - css += name + ":" + str(css_dict[name]) + ";" - - return css diff --git a/lisp/ui/ui_utils.py b/lisp/ui/ui_utils.py index b6d601765..125cceb53 100644 --- a/lisp/ui/ui_utils.py +++ b/lisp/ui/ui_utils.py @@ -53,6 +53,28 @@ def adjust_position(rect): return rect +def css_to_dict(css): + dict = {} + css = css.strip() + + for attribute in css.split(";"): + try: + name, value = attribute.split(":") + dict[name.strip()] = value.strip() + except Exception: + pass + + return dict + + +def dict_to_css(css_dict): + css = "" + for name in css_dict: + css += name + ":" + str(css_dict[name]) + ";" + + return css + + def qfile_filters(extensions, allexts=True, anyfile=True): """Create a filter-string for a FileChooser. diff --git a/pyproject.toml b/pyproject.toml index 60c0f5b34..57303d9a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.black] line-length = 80 -py36 = true +target-version = ['py36'] exclude = ''' /( \.git From f2ca1087ec660b49e8e0212995dd0edaaee07edd Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 22 May 2019 15:10:43 +0200 Subject: [PATCH 186/333] Minor changes, improving README --- README.md | 62 +++++++++++++++++++++--------- lisp/main.py | 12 ++++-- lisp/plugins/list_layout/layout.py | 2 +- lisp/ui/ui_utils.py | 15 ++++---- 4 files changed, 60 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 081b483b1..33c1d6e73 100644 --- a/README.md +++ b/README.md @@ -13,35 +13,61 @@ --- -Every component on which LiSP relies (python3, GStreamer and Qt5) is multi-platform, but the program is currently tested and developed only for **GNU/Linux**. +Linux Show Player, LiSP for short, is a free cue player, mainly intended for sound-playback in stage productions, the goal is to provide a complete playback software for musical plays, theater shows and similar. -No special hardware is required to run LiSP, but some processing operations would surely take benefit from a recent CPU. - -For bugs/requests an issue can be open on the GitHub issues-tracker, for everything else [gitter](https://gitter.im/linux-show-player/linux-show-player) can be used. +For bugs/requests you can open an issue on the GitHub issues tracker, for support, discussions, an anything else you should use the [gitter](https://gitter.im/linux-show-player/linux-show-player) chat. --- ### Installation -You can download an [AppImage](http://appimage.org/) bundle of LiSP from the release page, once downloaded make the file executable. -Now you should be able to run LiSP by double-clicking the file. +Linux Show Player is currently developed/test only on **GNU/Linux**.
+_The core components (Python3, GStreamer and Qt5) are multi-platform, in future, despite the name, LiSP might get ported on others platforms_ + +#### ⚙️ AppImage + +For version _0.5_ you can download an [AppImage](http://appimage.org/) bundle of LiSP from the release page, once downloaded make the file executable. + +#### 📦 Flatpak + +From version _0.6_ it will be possible to install a [Flatpak](https://flatpak.org/) package, follow the _simple_ instructions on their website to get everything ready. + +You can get the latest **development** version [here](https://bintray.com/francescoceruti/LinuxShowPlayer/ci/_latestVersion) - might be unstable (crash, breaking changes, etc...) + +#### 🐧 From yours distribution repository -Tested on the following systems *(it should work on newer versions)*: - * Debian 8 - * Ubuntu 16.04 - * Fedora 24 +For some GNU/Linux distribution you can install a native package.
+Keep in mind that it might not be the latest version, here a list: -
Otherwise follow the instructions on the [wiki](https://github.com/FrancescoCeruti/linux-show-player/wiki/Install) +[![Packaging status](https://repology.org/badge/vertical-allrepos/linux-show-player.svg)](https://repology.org/metapackage/linux-show-player) + + +#### 🔧 Manual installation + +_Following instruction are valid for the version of the software this README file is included with._ + +**TODO** --- -### Usage +### 📖 Usage + +User manual can be [viewed online](http://linux-show-player-users.readthedocs.io/en/latest/index.html) +or downloaded in the GitHub release page, for a specific version. + +##### ⌨️ Command line: -Use the installed launcher from the menu, or: +``` +usage: linux-show-player [-h] [-f [FILE]] [-l {debug,info,warning}] + [--locale LOCALE] - $ linux-show-player # Launch the program - $ linux-show-player -l [debug/warning/info] # Launch with different log options - $ linux-show-player -f # Open a saved session - $ linux-show-player --locale # Launch using the given locale +Cue player for stage productions. -User documentation can be downloaded under the GitHub release page or [viewed online](http://linux-show-player-users.readthedocs.io/en/latest/index.html) \ No newline at end of file +optional arguments: + -h, --help show this help message and exit + -f [FILE], --file [FILE] + Session file to open + -l {debug,info,warning}, --log {debug,info,warning} + Change output verbosity. default: warning + --locale LOCALE Force specified locale/language +``` diff --git a/lisp/main.py b/lisp/main.py index 2392d6f28..6cc4c356b 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -36,23 +36,27 @@ def main(): # Parse the command-line arguments - parser = argparse.ArgumentParser(description="Linux Show Player") + parser = argparse.ArgumentParser( + description="Cue player for stage productions." + ) parser.add_argument( "-f", "--file", default="", nargs="?", const="", - help="Session file path", + help="Session file to open", ) parser.add_argument( "-l", "--log", choices=["debug", "info", "warning"], default="warning", - help="Log level", + help="Change output verbosity. default: warning", + ) + parser.add_argument( + "--locale", default="", help="Force specified locale/language" ) - parser.add_argument("--locale", default="", help="Force specified locale") args = parser.parse_args() diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index aa09e79e5..0c82cac90 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -17,7 +17,7 @@ from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP, QTimer from PyQt5.QtGui import QKeySequence -from PyQt5.QtWidgets import QAction, QTreeWidget +from PyQt5.QtWidgets import QAction from lisp.command.model import ModelInsertItemsCommand from lisp.core.configuration import DummyConfiguration diff --git a/lisp/ui/ui_utils.py b/lisp/ui/ui_utils.py index 125cceb53..c284f8320 100644 --- a/lisp/ui/ui_utils.py +++ b/lisp/ui/ui_utils.py @@ -54,23 +54,22 @@ def adjust_position(rect): def css_to_dict(css): - dict = {} - css = css.strip() + css_dict = {} - for attribute in css.split(";"): + for attribute in css.strip().split(";"): try: name, value = attribute.split(":") - dict[name.strip()] = value.strip() - except Exception: + css_dict[name.strip()] = value.strip() + except ValueError: pass - return dict + return css_dict def dict_to_css(css_dict): css = "" - for name in css_dict: - css += name + ":" + str(css_dict[name]) + ";" + for name, value in css_dict.items(): + css += name + ":" + str(value) + ";" return css From 9537c49a12a6efdc9c50bb8214c838d3058592fc Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 22 May 2019 15:15:02 +0200 Subject: [PATCH 187/333] improving README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 33c1d6e73..7bf4fb335 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Linux Show Player, LiSP for short, is a free cue player, mainly intended for sound-playback in stage productions, the goal is to provide a complete playback software for musical plays, theater shows and similar. -For bugs/requests you can open an issue on the GitHub issues tracker, for support, discussions, an anything else you should use the [gitter](https://gitter.im/linux-show-player/linux-show-player) chat. +For bugs/requests you can open an issue on the GitHub issues tracker, for support, discussions, and anything else you should use the [gitter](https://gitter.im/linux-show-player/linux-show-player) chat. --- @@ -53,9 +53,9 @@ _Following instruction are valid for the version of the software this README fil ### 📖 Usage User manual can be [viewed online](http://linux-show-player-users.readthedocs.io/en/latest/index.html) -or downloaded in the GitHub release page, for a specific version. +or downloaded from the GitHub release page, for a specific version. -##### ⌨️ Command line: +#### ⌨️ Command line: ``` usage: linux-show-player [-h] [-f [FILE]] [-l {debug,info,warning}] From b5c75d090d3ac1997ae777bae7e932ebad105b8f Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 1 Jun 2019 13:25:57 +0200 Subject: [PATCH 188/333] fix setup.py --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 4f8926eec..ca5e02d52 100644 --- a/setup.py +++ b/setup.py @@ -36,13 +36,13 @@ def package_data_dirs(package_path): author_email=lisp.__email__, version=lisp.__version__, license=lisp.__license__, - url=lisp.__email__, - description="Cue player for live shows", + url=lisp.__url__, + description="Cue player designed for stage productions", long_description=long_description(), long_description_content_type="text/markdown", packages=find_packages(), package_data={ - "": ["i18n/qm/*", "*.qss", "*.json"], + "": ["*.qm", "*.qss", "*.json"], "lisp.ui.icons": package_data_dirs("lisp/ui/icons"), }, entry_points={"console_scripts": ["linux-show-player=lisp.main:main"]}, From ed764dd6d9d19ca489abb6286f3f372fdddfa229 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 1 Jun 2019 13:36:49 +0200 Subject: [PATCH 189/333] New Crowdin translations (#179) --- lisp/i18n/ts/cs_CZ/action_cues.ts | 189 +- lisp/i18n/ts/cs_CZ/cart_layout.ts | 56 +- lisp/i18n/ts/cs_CZ/controller.ts | 44 +- lisp/i18n/ts/cs_CZ/gst_backend.ts | 26 +- lisp/i18n/ts/cs_CZ/lisp.ts | 2401 ++++++++++++++++++++-- lisp/i18n/ts/cs_CZ/list_layout.ts | 87 +- lisp/i18n/ts/cs_CZ/media_info.ts | 10 +- lisp/i18n/ts/cs_CZ/midi.ts | 21 + lisp/i18n/ts/cs_CZ/osc.ts | 82 + lisp/i18n/ts/cs_CZ/presets.ts | 53 +- lisp/i18n/ts/cs_CZ/rename_cues.ts | 10 +- lisp/i18n/ts/cs_CZ/replay_gain.ts | 8 +- lisp/i18n/ts/cs_CZ/synchronizer.ts | 8 +- lisp/i18n/ts/cs_CZ/triggers.ts | 10 +- lisp/i18n/ts/de_DE/action_cues.ts | 245 +-- lisp/i18n/ts/de_DE/cart_layout.ts | 56 +- lisp/i18n/ts/de_DE/controller.ts | 64 +- lisp/i18n/ts/de_DE/gst_backend.ts | 104 +- lisp/i18n/ts/de_DE/lisp.ts | 2453 ++++++++++++++++++++-- lisp/i18n/ts/de_DE/list_layout.ts | 87 +- lisp/i18n/ts/de_DE/media_info.ts | 18 +- lisp/i18n/ts/de_DE/midi.ts | 25 +- lisp/i18n/ts/de_DE/osc.ts | 82 + lisp/i18n/ts/de_DE/presets.ts | 95 +- lisp/i18n/ts/de_DE/rename_cues.ts | 10 +- lisp/i18n/ts/de_DE/replay_gain.ts | 26 +- lisp/i18n/ts/de_DE/synchronizer.ts | 16 +- lisp/i18n/ts/de_DE/timecode.ts | 6 +- lisp/i18n/ts/de_DE/triggers.ts | 26 +- lisp/i18n/ts/es_ES/action_cues.ts | 189 +- lisp/i18n/ts/es_ES/cart_layout.ts | 56 +- lisp/i18n/ts/es_ES/controller.ts | 44 +- lisp/i18n/ts/es_ES/gst_backend.ts | 26 +- lisp/i18n/ts/es_ES/lisp.ts | 2331 +++++++++++++++++++-- lisp/i18n/ts/es_ES/list_layout.ts | 87 +- lisp/i18n/ts/es_ES/media_info.ts | 10 +- lisp/i18n/ts/es_ES/midi.ts | 21 + lisp/i18n/ts/es_ES/osc.ts | 82 + lisp/i18n/ts/es_ES/presets.ts | 53 +- lisp/i18n/ts/es_ES/rename_cues.ts | 10 +- lisp/i18n/ts/es_ES/replay_gain.ts | 8 +- lisp/i18n/ts/es_ES/synchronizer.ts | 8 +- lisp/i18n/ts/es_ES/triggers.ts | 10 +- lisp/i18n/ts/fr_FR/action_cues.ts | 207 +- lisp/i18n/ts/fr_FR/cart_layout.ts | 118 +- lisp/i18n/ts/fr_FR/controller.ts | 86 +- lisp/i18n/ts/fr_FR/gst_backend.ts | 26 +- lisp/i18n/ts/fr_FR/list_layout.ts | 135 +- lisp/i18n/ts/fr_FR/media_info.ts | 10 +- lisp/i18n/ts/fr_FR/midi.ts | 45 +- lisp/i18n/ts/fr_FR/network.ts | 12 +- lisp/i18n/ts/fr_FR/osc.ts | 100 +- lisp/i18n/ts/fr_FR/presets.ts | 53 +- lisp/i18n/ts/fr_FR/rename_cues.ts | 32 +- lisp/i18n/ts/fr_FR/replay_gain.ts | 8 +- lisp/i18n/ts/fr_FR/synchronizer.ts | 8 +- lisp/i18n/ts/fr_FR/timecode.ts | 6 +- lisp/i18n/ts/fr_FR/triggers.ts | 14 +- lisp/i18n/ts/it_IT/action_cues.ts | 189 +- lisp/i18n/ts/it_IT/cart_layout.ts | 56 +- lisp/i18n/ts/it_IT/controller.ts | 44 +- lisp/i18n/ts/it_IT/gst_backend.ts | 26 +- lisp/i18n/ts/it_IT/lisp.ts | 2329 +++++++++++++++++++-- lisp/i18n/ts/it_IT/list_layout.ts | 87 +- lisp/i18n/ts/it_IT/media_info.ts | 10 +- lisp/i18n/ts/it_IT/midi.ts | 21 + lisp/i18n/ts/it_IT/osc.ts | 82 + lisp/i18n/ts/it_IT/presets.ts | 53 +- lisp/i18n/ts/it_IT/rename_cues.ts | 10 +- lisp/i18n/ts/it_IT/replay_gain.ts | 8 +- lisp/i18n/ts/it_IT/synchronizer.ts | 8 +- lisp/i18n/ts/it_IT/triggers.ts | 10 +- lisp/i18n/ts/nl_BE/action_cues.ts | 251 +-- lisp/i18n/ts/nl_BE/cart_layout.ts | 56 +- lisp/i18n/ts/nl_BE/controller.ts | 68 +- lisp/i18n/ts/nl_BE/gst_backend.ts | 122 +- lisp/i18n/ts/nl_BE/lisp.ts | 2451 ++++++++++++++++++++-- lisp/i18n/ts/nl_BE/list_layout.ts | 87 +- lisp/i18n/ts/nl_BE/media_info.ts | 16 +- lisp/i18n/ts/nl_BE/midi.ts | 29 +- lisp/i18n/ts/nl_BE/osc.ts | 82 + lisp/i18n/ts/nl_BE/presets.ts | 93 +- lisp/i18n/ts/nl_BE/rename_cues.ts | 10 +- lisp/i18n/ts/nl_BE/replay_gain.ts | 26 +- lisp/i18n/ts/nl_BE/synchronizer.ts | 16 +- lisp/i18n/ts/nl_BE/timecode.ts | 10 +- lisp/i18n/ts/nl_BE/triggers.ts | 26 +- lisp/i18n/ts/nl_NL/action_cues.ts | 275 +-- lisp/i18n/ts/nl_NL/cart_layout.ts | 99 +- lisp/i18n/ts/nl_NL/controller.ts | 165 +- lisp/i18n/ts/nl_NL/gst_backend.ts | 158 +- lisp/i18n/ts/nl_NL/lisp.ts | 3054 +++++++++++++++++++++------- lisp/i18n/ts/nl_NL/list_layout.ts | 133 +- lisp/i18n/ts/nl_NL/media_info.ts | 23 +- lisp/i18n/ts/nl_NL/midi.ts | 161 +- lisp/i18n/ts/nl_NL/network.ts | 40 +- lisp/i18n/ts/nl_NL/osc.ts | 121 +- lisp/i18n/ts/nl_NL/presets.ts | 117 +- lisp/i18n/ts/nl_NL/rename_cues.ts | 64 +- lisp/i18n/ts/nl_NL/replay_gain.ts | 67 +- lisp/i18n/ts/nl_NL/synchronizer.ts | 74 +- lisp/i18n/ts/nl_NL/timecode.ts | 74 +- lisp/i18n/ts/nl_NL/triggers.ts | 34 +- lisp/i18n/ts/sl_SI/action_cues.ts | 189 +- lisp/i18n/ts/sl_SI/cart_layout.ts | 56 +- lisp/i18n/ts/sl_SI/controller.ts | 44 +- lisp/i18n/ts/sl_SI/gst_backend.ts | 26 +- lisp/i18n/ts/sl_SI/lisp.ts | 2401 ++++++++++++++++++++-- lisp/i18n/ts/sl_SI/list_layout.ts | 87 +- lisp/i18n/ts/sl_SI/media_info.ts | 10 +- lisp/i18n/ts/sl_SI/midi.ts | 21 + lisp/i18n/ts/sl_SI/osc.ts | 82 + lisp/i18n/ts/sl_SI/presets.ts | 53 +- lisp/i18n/ts/sl_SI/rename_cues.ts | 10 +- lisp/i18n/ts/sl_SI/replay_gain.ts | 8 +- lisp/i18n/ts/sl_SI/synchronizer.ts | 8 +- lisp/i18n/ts/sl_SI/triggers.ts | 10 +- 117 files changed, 19125 insertions(+), 4928 deletions(-) diff --git a/lisp/i18n/ts/cs_CZ/action_cues.ts b/lisp/i18n/ts/cs_CZ/action_cues.ts index ae6dec644..6e323de63 100644 --- a/lisp/i18n/ts/cs_CZ/action_cues.ts +++ b/lisp/i18n/ts/cs_CZ/action_cues.ts @@ -1,41 +1,25 @@ - - ActionCuesDebug - - - Registered cue: "{}" - Registered cue: "{}" - - - - ActionsCuesError - - - Cannot create cue {} - Cannot create cue {} - - CollectionCue - + Add Přidat - + Remove Odstranit - + Cue Narážka - + Action Činnost @@ -43,37 +27,37 @@ CommandCue - + Command Příkaz - + Command to execute, as in a shell Příkaz k provedení, jako v terminálu - + Discard command output Zahodit výstup příkazu - + Ignore command errors Přehlížet chyby příkazu - + Kill instead of terminate Zabít namísto ukončit - Cue Name + CueCategory - - OSC Settings - OSC Settings + + Action cues + Action cues @@ -83,11 +67,6 @@ Command Cue Narážka příkazu - - - MIDI Cue - Narážka MIDI - Volume Control @@ -113,153 +92,64 @@ Index Action Činnost indexu - - - OSC Cue - OSC Cue - IndexActionCue - + Index Index - + Use a relative index Použít poměrný index - + Target index Cílový index - + Action Činnost - + No suggestion No suggestion - + Suggested cue name Suggested cue name - - Osc Cue - - - Type - Type - - - - Argument - Argument - - - - FadeTo - FadeTo - - - - Fade - Fade - - - - OscCue - - - OSC Message - OSC Message - - - - Add - Add - - - - Remove - Remove - - - - Test - Test - - - - OSC Path: (example: "/path/to/something") - OSC Path: (example: "/path/to/something") - - - - Fade - Fade - - - - Time (sec) - Time (sec) - - - - Curve - Curve - - - - OscCueError - - - Could not parse argument list, nothing sent - Could not parse argument list, nothing sent - - - - Error while parsing arguments, nothing sent - Error while parsing arguments, nothing sent - - - - Error during cue execution. - Error during cue execution. - - SeekCue - + Cue Narážka - + Click to select Klepnout pro vybrání - + Not selected Nevybráno - + Seek Hledat - + Time to reach Čas k dosažení @@ -267,37 +157,32 @@ SettingsPageName - + Command Příkaz - - MIDI Settings - Nastavení MIDI - - - + Volume Settings Nastavení hlasitosti - + Seek Settings Nastavení prohledávání - + Edit Collection Upravit sbírku - + Action Settings Nastavení činnosti - + Stop Settings Nastavení zastavení @@ -305,7 +190,7 @@ StopAll - + Stop Action Činnost zastavení @@ -313,27 +198,27 @@ VolumeControl - + Cue Narážka - + Click to select Klepnout pro vybrání - + Not selected Nevybráno - + Volume to reach Hlasitost k dosažení - + Fade Prolínat @@ -341,7 +226,7 @@ VolumeControlError - + Error during cue execution. Error during cue execution. diff --git a/lisp/i18n/ts/cs_CZ/cart_layout.ts b/lisp/i18n/ts/cs_CZ/cart_layout.ts index e6b342c7b..4d17fd09e 100644 --- a/lisp/i18n/ts/cs_CZ/cart_layout.ts +++ b/lisp/i18n/ts/cs_CZ/cart_layout.ts @@ -9,27 +9,27 @@ Default behaviors - + Countdown mode Countdown mode - + Show seek-bars Show seek-bars - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show volume Show volume @@ -39,62 +39,62 @@ Grid size - + Play Play - + Pause Pause - + Stop Stop - + Reset volume Reset volume - + Add page Add page - + Add pages Add pages - + Remove current page Remove current page - + Number of Pages: Number of Pages: - + Page {number} Page {number} - + Warning Warning - + Every cue in the page will be lost. Every cue in the page will be lost. - + Are you sure to continue? Are you sure to continue? @@ -112,7 +112,7 @@ LayoutDescription - + Organize cues in grid like pages Organize cues in grid like pages @@ -120,27 +120,27 @@ LayoutDetails - + Click a cue to run it Click a cue to run it - + SHIFT + Click to edit a cue SHIFT + Click to edit a cue - + CTRL + Click to select a cue CTRL + Click to select a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them while pressing SHIFT To move cues drag them while pressing SHIFT @@ -148,7 +148,7 @@ LayoutName - + Cart Layout Cart Layout @@ -156,22 +156,22 @@ ListLayout - + Edit cue Edit cue - + Edit selected cues Edit selected cues - + Remove cue Remove cue - + Remove selected cues Remove selected cues diff --git a/lisp/i18n/ts/cs_CZ/controller.ts b/lisp/i18n/ts/cs_CZ/controller.ts index f76fe0694..b3a8ceb3e 100644 --- a/lisp/i18n/ts/cs_CZ/controller.ts +++ b/lisp/i18n/ts/cs_CZ/controller.ts @@ -12,20 +12,20 @@ ControllerKeySettings - - Key - Klávesa - - - + Action Činnost - + Shortcuts Klávesové zkratky + + + Shortcut + Shortcut + ControllerMidiSettings @@ -131,12 +131,12 @@ ControllerSettings - + Add Přidat - + Remove Odstranit @@ -144,52 +144,52 @@ GlobalAction - + Go Go - + Reset Reset - + Stop all cues Stop all cues - + Pause all cues Pause all cues - + Resume all cues Resume all cues - + Interrupt all cues Interrupt all cues - + Fade-out all cues Fade-out all cues - + Fade-in all cues Fade-in all cues - + Move standby forward Move standby forward - + Move standby back Move standby back @@ -197,12 +197,12 @@ Osc Cue - + Type Type - + Argument Argument @@ -233,7 +233,7 @@ Ovládání MIDI - + Keyboard Shortcuts Klávesové zkratky diff --git a/lisp/i18n/ts/cs_CZ/gst_backend.ts b/lisp/i18n/ts/cs_CZ/gst_backend.ts index b6f6caf97..e9d012a5b 100644 --- a/lisp/i18n/ts/cs_CZ/gst_backend.ts +++ b/lisp/i18n/ts/cs_CZ/gst_backend.ts @@ -114,12 +114,12 @@ GstBackend - + Audio cue (from file) Audio cue (from file) - + Select media files Select media files @@ -127,7 +127,7 @@ GstMediaError - + Cannot create pipeline element: "{}" Cannot create pipeline element: "{}" @@ -143,7 +143,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" Invalid pipeline element: "{}" @@ -255,7 +255,7 @@ Výška tónu - + URI Input Vstup URI @@ -320,42 +320,42 @@ UriInputSettings - + Source Zdroj - + Find File Najít soubor - + Buffering Ukládání do vyrovnávací paměti - + Use Buffering Použít ukládání do vyrovnávací paměti - + Attempt download on network streams Pokusit se o stažení na síťových proudech - + Buffer size (-1 default value) Velikost vyrovnávací paměti (-1 výchozí hodnota) - + Choose file Vybrat soubor - + All files Všechny soubory diff --git a/lisp/i18n/ts/cs_CZ/lisp.ts b/lisp/i18n/ts/cs_CZ/lisp.ts index ce8b3b382..db4cbc503 100644 --- a/lisp/i18n/ts/cs_CZ/lisp.ts +++ b/lisp/i18n/ts/cs_CZ/lisp.ts @@ -1,6 +1,14 @@ + + APIServerInfo + + + Stop serving network API + Stop serving network API + + About @@ -58,16 +66,19 @@ - Actions + AlsaSinkSettings - - Undo: {} - Undo: {} + + ALSA device + ALSA device + + + ApiServerError - - Redo: {} - Redo: {} + + Network API server stopped working. + Network API server stopped working. @@ -81,39 +92,292 @@ AppGeneralSettings - + Default layout Default layout - + Enable startup layout selector Enable startup layout selector - + Application themes Application themes - + UI theme: UI theme: - + Icons theme: Icons theme: + + + Application language (require restart) + Application language (require restart) + + + + Language: + Language: + ApplicationError - + Startup error Startup error + + AudioDynamicSettings + + + Compressor + Compressor + + + + Expander + Expander + + + + Soft Knee + Soft Knee + + + + Hard Knee + Hard Knee + + + + Compressor/Expander + Compressor/Expander + + + + Type + Type + + + + Curve Shape + Curve Shape + + + + Ratio + Ratio + + + + Threshold (dB) + Threshold (dB) + + + + AudioPanSettings + + + Audio Pan + Audio Pan + + + + Center + Center + + + + Left + Left + + + + Right + Right + + + + CartLayout + + + Default behaviors + Default behaviors + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show volume + Show volume + + + + Grid size + Grid size + + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Number of Pages: + Number of Pages: + + + + Page {number} + Page {number} + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + CollectionCue + + + Add + Add + + + + Remove + Remove + + + + Cue + Cue + + + + Action + Action + + + + CommandCue + + + Command + Command + + + + Command to execute, as in a shell + Command to execute, as in a shell + + + + Discard command output + Discard command output + + + + Ignore command errors + Ignore command errors + + + + Kill instead of terminate + Kill instead of terminate + + + + CommandsStack + + + Undo: {} + Undo: {} + + + + Redo: {} + Redo: {} + + ConfigurationDebug @@ -130,6 +394,154 @@ New configuration installed at {} + + Controller + + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + + ControllerKeySettings + + + Action + Action + + + + Shortcuts + Shortcuts + + + + Shortcut + Shortcut + + + + ControllerMidiSettings + + + MIDI + MIDI + + + + Type + Type + + + + Channel + Channel + + + + Note + Note + + + + Action + Action + + + + Filter "note on" + Filter "note on" + + + + Filter "note off" + Filter "note off" + + + + Capture + Capture + + + + Listening MIDI messages ... + Listening MIDI messages ... + + + + ControllerOscSettings + + + OSC Message + OSC Message + + + + OSC + OSC + + + + Path + Path + + + + Types + Types + + + + Arguments + Arguments + + + + Actions + Actions + + + + OSC Capture + OSC Capture + + + + Add + Add + + + + Remove + Remove + + + + Capture + Capture + + + + ControllerSettings + + + Add + Add + + + + Remove + Remove + + + + Cue Name + + + OSC Settings + OSC Settings + + CueAction @@ -188,19 +600,6 @@ Do Nothing - - CueActionLog - - - Cue settings changed: "{}" - Nastavení narážky změněna: "{}" - - - - Cues settings changed. - Nastavení narážky změněna. - - CueAppearanceSettings @@ -244,6 +643,37 @@ Vybrat barvu písma + + CueCategory + + + Action cues + Action cues + + + + Integration cues + Integration cues + + + + Misc cues + Misc cues + + + + CueCommandLog + + + Cue settings changed: "{}" + Cue settings changed: "{}" + + + + Cues settings changed. + Cues settings changed. + + CueName @@ -251,31 +681,71 @@ Media Cue Narážka v záznamu + + + Index Action + Index Action + + + + Seek Cue + Seek Cue + + + + Volume Control + Volume Control + + + + Command Cue + Command Cue + + + + Collection Cue + Collection Cue + + + + Stop-All + Stop-All + + + + MIDI Cue + MIDI Cue + + + + OSC Cue + OSC Cue + CueNextAction - + Do Nothing Do Nothing - + Trigger after the end Trigger after the end - + Trigger after post wait Trigger after post wait - + Select after the end Select after the end - + Select after post wait Select after post wait @@ -338,6 +808,60 @@ Fade actions + + CueTriggers + + + Started + Started + + + + Paused + Paused + + + + Stopped + Stopped + + + + Ended + Ended + + + + DbMeterSettings + + + DbMeter settings + DbMeter settings + + + + Time between levels (ms) + Time between levels (ms) + + + + Peak ttl (ms) + Peak ttl (ms) + + + + Peak falloff (dB/sec) + Peak falloff (dB/sec) + + + + Equalizer10Settings + + + 10 Bands Equalizer (IIR) + 10 Bands Equalizer (IIR) + + Fade @@ -383,19 +907,263 @@ - LayoutSelect + GlobalAction - - Layout selection + + Go + Go + + + + Reset + Reset + + + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + + + GstMediaSettings + + + Change Pipeline + Change Pipeline + + + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + + + GstPipelineEdit + + + Edit Pipeline + Edit Pipeline + + + + GstSettings + + + Pipeline + Pipeline + + + + HotKeyEdit + + + Press shortcut + Press shortcut + + + + IndexActionCue + + + No suggestion + No suggestion + + + + Index + Index + + + + Use a relative index + Use a relative index + + + + Target index + Target index + + + + Action + Action + + + + Suggested cue name + Suggested cue name + + + + JackSinkSettings + + + Connections + Connections + + + + Edit connections + Edit connections + + + + Output ports + Output ports + + + + Input ports + Input ports + + + + Connect + Connect + + + + Disconnect + Disconnect + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them + To move cues drag them + + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + To move cues drag them while pressing SHIFT + To move cues drag them while pressing SHIFT + + + + LayoutName + + + List Layout + List Layout + + + + Cart Layout + Cart Layout + + + + LayoutSelect + + + Layout selection Výběr rozvržení - + Select layout Vybrat rozvržení - + Open file Otevřít soubor @@ -403,29 +1171,208 @@ ListLayout - - Stop All - Zastavit vše + + Default behaviors + Default behaviors + + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show seek-bars + Show seek-bars + + + + Auto-select next cue + Auto-select next cue + + + + Enable selection mode + Enable selection mode + + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Resume Cue + Resume Cue + + + + Interrupt Cue + Interrupt Cue + + + + Edit cue + Edit cue + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove cue + Remove cue + + + + Remove selected + Remove selected + + + + Selection mode + Selection mode + + + + Pause all + Pause all + + + + Stop all + Stop all - Pause All - Pozastavit vše + Interrupt all + Interrupt all + + + + Resume all + Resume all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + Edit selected cues + Edit selected cues - - Interrupt All - Přerušit vše + + Remove selected cues + Remove selected cues - Use fade (global actions) - Use fade (global actions) + Use fade + Use fade + + + + Copy of {} + Copy of {} + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + LogStatusIcon - - Resume All - Resume All + + Errors/Warnings + Errors/Warnings @@ -456,7 +1403,7 @@ Show details - + Linux Show Player - Log Viewer Linux Show Player - Log Viewer @@ -547,221 +1494,992 @@ - MainWindow + MIDICue - - &File - &Soubor + + MIDI Message + MIDI Message - - New session - Nové sezení + + Message type + Message type + + + MIDIMessageAttr - - Open - Otevřít + + Channel + Channel - - Save session - Uložit sezení + + Note + Note - - Preferences - Nastavení + + Velocity + Velocity - - Save as - Uložit jako + + Control + Control - - Full Screen - Na celou obrazovku + + Program + Program - - Exit - Ukončit + + Value + Value - - &Edit - Úp&ravy + + Song + Song - - Undo - Zpět + + Pitch + Pitch - - Redo - Znovu + + Position + Position + + + MIDIMessageType - - Select all - Vybrat vše + + Note ON + Note ON - - Select all media cues - Vybrat všechny narážky v záznamu + + Note OFF + Note OFF - - Deselect all - Odznačit všechny narážky + + Polyphonic After-touch + Polyphonic After-touch - - CTRL+SHIFT+A - Ctrl+Shift+A + + Control/Mode Change + Control/Mode Change - - Invert selection - Obrátit výběr + + Program Change + Program Change - - CTRL+I - CTRL+I + + Channel After-touch + Channel After-touch - - Edit selected - Upravit vybrané + + Pitch Bend Change + Pitch Bend Change - - CTRL+SHIFT+E - Ctrl+Shift+E + + Song Select + Song Select - - &Layout - &Rozvržení + + Song Position + Song Position - - &Tools - &Nástroje + + Start + Start - - Edit selection - Upravit výběr + + Stop + Stop - - &About - &O programu + + Continue + Continue + + + MIDISettings - - About + + MIDI default devices + MIDI default devices + + + + Input + Input + + + + Output + Output + + + + MainWindow + + + &File + &Soubor + + + + New session + Nové sezení + + + + Open + Otevřít + + + + Save session + Uložit sezení + + + + Preferences + Nastavení + + + + Save as + Uložit jako + + + + Full Screen + Na celou obrazovku + + + + Exit + Ukončit + + + + &Edit + Úp&ravy + + + + Undo + Zpět + + + + Redo + Znovu + + + + Select all + Vybrat vše + + + + Select all media cues + Vybrat všechny narážky v záznamu + + + + Deselect all + Odznačit všechny narážky + + + + CTRL+SHIFT+A + Ctrl+Shift+A + + + + Invert selection + Obrátit výběr + + + + CTRL+I + CTRL+I + + + + Edit selected + Upravit vybrané + + + + CTRL+SHIFT+E + Ctrl+Shift+E + + + + &Layout + &Rozvržení + + + + &Tools + &Nástroje + + + + Edit selection + Upravit výběr + + + + &About + &O programu + + + + About O programu - - About Qt - O Qt + + About Qt + O Qt + + + + Close session + Zavřít sezení + + + + Do you want to save them now? + Do you want to save them now? + + + + MainWindowDebug + + + Registered cue menu: "{}" + Registered cue menu: "{}" + + + + MainWindowError + + + Cannot create cue {} + Cannot create cue {} + + + + MediaCueSettings + + + Start time + Čas spuštění + + + + Stop position of the media + Poloha zastavení záznamu + + + + Stop time + Čas zastavení + + + + Start position of the media + Poloha spuštění záznamu + + + + Loop + Smyčka + + + + MediaElementName + + + Pitch + Pitch + + + + Volume + Volume + + + + Audio Pan + Audio Pan + + + + Compressor/Expander + Compressor/Expander + + + + JACK Out + JACK Out + + + + URI Input + URI Input + + + + 10 Bands Equalizer + 10 Bands Equalizer + + + + ALSA Out + ALSA Out + + + + dB Meter + dB Meter + + + + Preset Input + Preset Input + + + + PulseAudio Out + PulseAudio Out + + + + Custom Element + Custom Element + + + + Speed + Speed + + + + System Out + System Out + + + + System Input + System Input + + + + MediaInfo + + + Media Info + Media Info + + + + Warning + Warning + + + + No info to display + No info to display + + + + Info + Info + + + + Value + Value + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + OscCue + + + Add + Add + + + + Remove + Remove + + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + PitchSettings + + + Pitch + Pitch + + + + {0:+} semitones + {0:+} semitones + + + + PluginsError + + + Failed to load "{}" + Failed to load "{}" + + + + Failed to terminate plugin: "{}" + Failed to terminate plugin: "{}" + + + + the requested plugin is not loaded: {} + the requested plugin is not loaded: {} + + + + PluginsInfo + + + Plugin loaded: "{}" + Plugin loaded: "{}" + + + + Plugin terminated: "{}" + Plugin terminated: "{}" + + + + PluginsWarning + + + Cannot satisfy dependencies for: {} + Cannot satisfy dependencies for: {} + + + + Preset + + + Create Cue + Create Cue + + + + Load on selected Cues + Load on selected Cues + + + + PresetSrcSettings + + + Presets + Presets + + + + Presets + + + Cannot scan presets + Cannot scan presets + + + + Error while deleting preset "{}" + Error while deleting preset "{}" + + + + Cannot load preset "{}" + Cannot load preset "{}" + + + + Cannot save preset "{}" + Cannot save preset "{}" + + + + Cannot rename preset "{}" + Cannot rename preset "{}" + + + + Select Preset + Select Preset + + + + Presets + Presets + + + + Preset name + Preset name + + + + Add + Add + + + + Rename + Rename + + + + Edit + Edit + + + + Remove + Remove + + + + Export selected + Export selected + + + + Import + Import - - Close session - Zavřít sezení + + Warning + Warning + + + + The same name is already used! + The same name is already used! + + + + Cannot export correctly. + Cannot export correctly. + + + + Cannot import correctly. + Cannot import correctly. + + + + Cue type + Cue type + + + + Load on cue + Load on cue - - The current session is not saved. - Nynější sezení není uloženo. + + Load on selected cues + Load on selected cues - - Discard the changes? - Zahodit změny? + + Save as preset + Save as preset + + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} - MediaCueSettings + QColorButton - - Start time - Čas spuštění + + Right click to reset + Klepnutí pravým tlačítkem myši pro nastavení na výchozí hodnotu + + + RenameCues - - Stop position of the media - Poloha zastavení záznamu + + Rename Cues + Rename Cues - - Stop time - Čas zastavení + + Rename cues + Rename cues - - Start position of the media - Poloha spuštění záznamu + + Current + Current - - Loop - Smyčka + + Preview + Preview + + + + Capitalize + Capitalize + + + + Lowercase + Lowercase + + + + Uppercase + Uppercase + + + + Remove Numbers + Remove Numbers + + + + Add numbering + Add numbering + + + + Reset + Reset + + + + Type your regex here: + Type your regex here: + + + + Regex help + Regex help - PluginsError + RenameCuesCommand - - Failed to load "{}" - Failed to load "{}" + + Renamed {number} cues + Renamed {number} cues + + + RenameUiDebug - - Failed to terminate plugin: "{}" - Failed to terminate plugin: "{}" + + Regex error: Invalid pattern + Regex error: Invalid pattern + + + ReplayGain - - the requested plugin is not loaded: {} - the requested plugin is not loaded: {} + + ReplayGain / Normalization + ReplayGain / Normalization + + + + Threads number + Threads number + + + + Apply only to selected media + Apply only to selected media + + + + ReplayGain to (dB SPL) + ReplayGain to (dB SPL) + + + + Normalize to (dB) + Normalize to (dB) + + + + Processing files ... + Processing files ... + + + + Calculate + Calculate + + + + Reset all + Reset all + + + + Reset selected + Reset selected - PluginsInfo + ReplayGainDebug - - Plugin loaded: "{}" - Plugin loaded: "{}" + + Applied gain for: {} + Applied gain for: {} - - Plugin terminated: "{}" - Plugin terminated: "{}" + + Discarded gain for: {} + Discarded gain for: {} - PluginsWarning + ReplayGainInfo - - Cannot satisfy dependencies for: {} - Cannot satisfy dependencies for: {} + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} - QColorButton + SeekCue - - Right click to reset - Klepnutí pravým tlačítkem myši pro nastavení na výchozí hodnotu + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Seek + Seek + + + + Time to reach + Time to reach @@ -772,7 +2490,7 @@ Vzhled - + General Obecné @@ -811,5 +2529,336 @@ Layouts Layouts + + + Action Settings + Action Settings + + + + Seek Settings + Seek Settings + + + + Volume Settings + Volume Settings + + + + Command + Command + + + + Edit Collection + Edit Collection + + + + Stop Settings + Stop Settings + + + + List Layout + List Layout + + + + Timecode + Timecode + + + + Timecode Settings + Timecode Settings + + + + Cue Control + Cue Control + + + + Layout Controls + Layout Controls + + + + MIDI Controls + MIDI Controls + + + + Keyboard Shortcuts + Keyboard Shortcuts + + + + OSC Controls + OSC Controls + + + + MIDI Settings + MIDI Settings + + + + MIDI settings + MIDI settings + + + + GStreamer + GStreamer + + + + Media Settings + Media Settings + + + + Triggers + Triggers + + + + OSC settings + OSC settings + + + + Cart Layout + Cart Layout + + + + SpeedSettings + + + Speed + Speed + + + + StopAll + + + Stop Action + Stop Action + + + + Synchronizer + + + Synchronization + Synchronization + + + + Manage connected peers + Manage connected peers + + + + Show your IP + Show your IP + + + + Your IP is: + Your IP is: + + + + Timecode + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + TimecodeError + + + Cannot send timecode. + Cannot send timecode. + + + + TimecodeSettings + + + Replace HOURS by a static track number + Replace HOURS by a static track number + + + + Enable Timecode + Enable Timecode + + + + Track number + Track number + + + + Timecode Settings + Timecode Settings + + + + Timecode Format: + Timecode Format: + + + + Timecode Protocol: + Timecode Protocol: + + + + TriggersSettings + + + Add + Add + + + + Remove + Remove + + + + Trigger + Trigger + + + + Cue + Cue + + + + Action + Action + + + + UriInputSettings + + + Source + Source + + + + Find File + Find File + + + + Buffering + Buffering + + + + Use Buffering + Use Buffering + + + + Attempt download on network streams + Attempt download on network streams + + + + Buffer size (-1 default value) + Buffer size (-1 default value) + + + + Choose file + Choose file + + + + All files + All files + + + + UserElementSettings + + + User defined elements + User defined elements + + + + Only for advanced user! + Only for advanced user! + + + + VolumeControl + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Volume to reach + Volume to reach + + + + Fade + Fade + + + + VolumeControlError + + + Error during cue execution. + Error during cue execution. + + + + VolumeSettings + + + Volume + Volume + + + + Normalized volume + Normalized volume + + + + Reset + Reset + diff --git a/lisp/i18n/ts/cs_CZ/list_layout.ts b/lisp/i18n/ts/cs_CZ/list_layout.ts index d3740e883..d7d3696a1 100644 --- a/lisp/i18n/ts/cs_CZ/list_layout.ts +++ b/lisp/i18n/ts/cs_CZ/list_layout.ts @@ -4,7 +4,7 @@ LayoutDescription - + Organize the cues in a list Organize the cues in a list @@ -12,17 +12,17 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue SHIFT + Space or Double-Click to edit a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them To move cues drag them @@ -30,7 +30,7 @@ LayoutName - + List Layout List Layout @@ -43,27 +43,27 @@ Default behaviors - + Show playing cues Show playing cues - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue @@ -73,42 +73,42 @@ Enable selection mode - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - + Remove cue Remove cue - + Selection mode Selection mode @@ -143,60 +143,65 @@ Fade-In all - - GO key: - GO key: - - - - GO action: - GO action: - - - - GO delay (ms): - GO delay (ms): - - - + Edit selected Edit selected - + Clone cue Clone cue - + Clone selected Clone selected - + Remove selected Remove selected + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Copy of {} + Copy of {} + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait diff --git a/lisp/i18n/ts/cs_CZ/media_info.ts b/lisp/i18n/ts/cs_CZ/media_info.ts index daae04bec..37b1647d2 100644 --- a/lisp/i18n/ts/cs_CZ/media_info.ts +++ b/lisp/i18n/ts/cs_CZ/media_info.ts @@ -4,27 +4,27 @@ MediaInfo - + Media Info Údaje o záznamu - + No info to display Žádné údaje k zobrazení - + Info Informace - + Value Hodnota - + Warning Warning diff --git a/lisp/i18n/ts/cs_CZ/midi.ts b/lisp/i18n/ts/cs_CZ/midi.ts index 23d74e7e7..0ee4d5107 100644 --- a/lisp/i18n/ts/cs_CZ/midi.ts +++ b/lisp/i18n/ts/cs_CZ/midi.ts @@ -1,6 +1,22 @@ + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + MIDI Cue + MIDI Cue + + MIDICue @@ -150,5 +166,10 @@ MIDI settings Nastavení MIDI + + + MIDI Settings + MIDI Settings + diff --git a/lisp/i18n/ts/cs_CZ/osc.ts b/lisp/i18n/ts/cs_CZ/osc.ts index 8c4703f23..92feb5519 100644 --- a/lisp/i18n/ts/cs_CZ/osc.ts +++ b/lisp/i18n/ts/cs_CZ/osc.ts @@ -1,6 +1,88 @@ + + Cue Name + + + OSC Settings + OSC Settings + + + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + OSC Cue + OSC Cue + + + + OscCue + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + Add + Add + + + + Remove + Remove + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + OscServerDebug diff --git a/lisp/i18n/ts/cs_CZ/presets.ts b/lisp/i18n/ts/cs_CZ/presets.ts index 9e8ca26f6..bd2069ea1 100644 --- a/lisp/i18n/ts/cs_CZ/presets.ts +++ b/lisp/i18n/ts/cs_CZ/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Vytvořit narážku - + Load on selected Cues Nahrát na vybraných narážkách @@ -17,114 +17,119 @@ Presets - + Presets Přednastavení - + Save as preset Uložit jako přednastavení - + Cannot scan presets Nelze prohledat přednastavení - + Error while deleting preset "{}" Chyba při mazání přednastavení "{}" - + Cannot load preset "{}" Nelze nahrát přednastavení "{}" - + Cannot save preset "{}" Nelze uložit přednastavení "{}" - + Cannot rename preset "{}" Nelze přejmenovat přednastavení "{}" - + Select Preset Vybrat přednastavení - + Preset name Název přednastavení - + Add Přidat - + Rename Přejmenovat - + Edit Upravit - + Remove Odstranit - + Export selected Vybráno vyvedení - + Import Vyvedení - + Warning Varování - + The same name is already used! Stejný název se již používá! - + Cannot export correctly. Nelze vyvést správně. - + Cannot import correctly. Nelze zavést správně. - + Cue type Typ narážky - + Load on cue Load on cue - + Load on selected cues Load on selected cues + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + diff --git a/lisp/i18n/ts/cs_CZ/rename_cues.ts b/lisp/i18n/ts/cs_CZ/rename_cues.ts index 3deed7d41..047580ad9 100644 --- a/lisp/i18n/ts/cs_CZ/rename_cues.ts +++ b/lisp/i18n/ts/cs_CZ/rename_cues.ts @@ -4,7 +4,7 @@ RenameCues - + Rename Cues Rename Cues @@ -64,6 +64,14 @@ Regex help + + RenameCuesCommand + + + Renamed {number} cues + Renamed {number} cues + + RenameUiDebug diff --git a/lisp/i18n/ts/cs_CZ/replay_gain.ts b/lisp/i18n/ts/cs_CZ/replay_gain.ts index fbfbf9a14..3faff1dda 100644 --- a/lisp/i18n/ts/cs_CZ/replay_gain.ts +++ b/lisp/i18n/ts/cs_CZ/replay_gain.ts @@ -4,22 +4,22 @@ ReplayGain - + ReplayGain / Normalization Vyrovnání hlasitosti/Normalizace - + Calculate Spočítat - + Reset all Nastavit vše znovu - + Reset selected Nastavit vybrané znovu diff --git a/lisp/i18n/ts/cs_CZ/synchronizer.ts b/lisp/i18n/ts/cs_CZ/synchronizer.ts index 98c6c523a..054008bab 100644 --- a/lisp/i18n/ts/cs_CZ/synchronizer.ts +++ b/lisp/i18n/ts/cs_CZ/synchronizer.ts @@ -4,22 +4,22 @@ Synchronizer - + Synchronization Seřízení - + Manage connected peers Spravovat spojené protějšky - + Show your IP Ukázat vaši adresu (IP) - + Your IP is: Vaše adresu (IP): diff --git a/lisp/i18n/ts/cs_CZ/triggers.ts b/lisp/i18n/ts/cs_CZ/triggers.ts index fedc2b20d..39380bb88 100644 --- a/lisp/i18n/ts/cs_CZ/triggers.ts +++ b/lisp/i18n/ts/cs_CZ/triggers.ts @@ -35,27 +35,27 @@ TriggersSettings - + Add Přidat - + Remove Odstranit - + Trigger Spouštěč - + Cue Narážka - + Action Činnost diff --git a/lisp/i18n/ts/de_DE/action_cues.ts b/lisp/i18n/ts/de_DE/action_cues.ts index e93fe57f5..1704ea560 100644 --- a/lisp/i18n/ts/de_DE/action_cues.ts +++ b/lisp/i18n/ts/de_DE/action_cues.ts @@ -1,79 +1,63 @@ - - ActionCuesDebug - - - Registered cue: "{}" - Registered cue: "{}" - - - - ActionsCuesError - - - Cannot create cue {} - Cannot create cue {} - - CollectionCue - + Add - Add + Hinzufügen - + Remove - Remove + Entfernen - + Cue Cue - + Action - Action + Aktion CommandCue - + Command - Command + Befehl - + Command to execute, as in a shell - Command to execute, as in a shell + Befehl zum Ausführen, wie in einer Shell - + Discard command output - Discard command output + Befehl Output verwerfen - + Ignore command errors - Ignore command errors + Befehl Fehler ignorieren - + Kill instead of terminate - Kill instead of terminate + Kill statt Terminate - Cue Name + CueCategory - - OSC Settings - OSC Settings + + Action cues + Action cues @@ -81,17 +65,12 @@ Command Cue - Command Cue - - - - MIDI Cue - MIDI Cue + Befehl Cue Volume Control - Volume Control + Lautstärkeregelung @@ -106,234 +85,140 @@ Stop-All - Stop-All + Alle stoppen Index Action - Index Action - - - - OSC Cue - OSC Cue + Index Aktion IndexActionCue - + Index Index - + Use a relative index - Use a relative index + Einen relativen Index verwenden - + Target index - Target index + Ziel Index - + Action - Action + Aktion - + No suggestion No suggestion - + Suggested cue name Suggested cue name - - Osc Cue - - - Type - Type - - - - Argument - Argument - - - - FadeTo - FadeTo - - - - Fade - Fade - - - - OscCue - - - OSC Message - OSC Message - - - - Add - Add - - - - Remove - Remove - - - - Test - Test - - - - OSC Path: (example: "/path/to/something") - OSC Path: (example: "/path/to/something") - - - - Fade - Fade - - - - Time (sec) - Time (sec) - - - - Curve - Curve - - - - OscCueError - - - Could not parse argument list, nothing sent - Could not parse argument list, nothing sent - - - - Error while parsing arguments, nothing sent - Error while parsing arguments, nothing sent - - - - Error during cue execution. - Error during cue execution. - - SeekCue - + Cue Cue - + Click to select - Click to select + Klicken zum Auswählen - + Not selected - Not selected + Nicht ausgewählt - + Seek Seek - + Time to reach - Time to reach + Zeit zum Erreichen SettingsPageName - + Command - Command + Befehl - - MIDI Settings - MIDI Settings - - - + Volume Settings - Volume Settings + Lautstärke Einstellungen - + Seek Settings - Seek Settings + Seek Einstellungen - + Edit Collection - Edit Collection + Collection bearbeiten - + Action Settings - Action Settings + Aktionseinstellungen - + Stop Settings - Stop Settings + Stop Einstellungen StopAll - + Stop Action - Stop Action + Stop Aktion VolumeControl - + Cue Cue - + Click to select - Click to select + Klicken zum Auswählen - + Not selected - Not selected + Nicht ausgewählt - + Volume to reach - Volume to reach + Lautstärke zu erreichen - + Fade Fade @@ -341,7 +226,7 @@ VolumeControlError - + Error during cue execution. Error during cue execution. diff --git a/lisp/i18n/ts/de_DE/cart_layout.ts b/lisp/i18n/ts/de_DE/cart_layout.ts index 624d3d1bf..752000db8 100644 --- a/lisp/i18n/ts/de_DE/cart_layout.ts +++ b/lisp/i18n/ts/de_DE/cart_layout.ts @@ -9,27 +9,27 @@ Default behaviors - + Countdown mode Countdown mode - + Show seek-bars Show seek-bars - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show volume Show volume @@ -39,62 +39,62 @@ Grid size - + Play Play - + Pause Pause - + Stop Stop - + Reset volume Reset volume - + Add page Add page - + Add pages Add pages - + Remove current page Remove current page - + Number of Pages: Number of Pages: - + Page {number} Page {number} - + Warning Warning - + Every cue in the page will be lost. Every cue in the page will be lost. - + Are you sure to continue? Are you sure to continue? @@ -112,7 +112,7 @@ LayoutDescription - + Organize cues in grid like pages Organize cues in grid like pages @@ -120,27 +120,27 @@ LayoutDetails - + Click a cue to run it Click a cue to run it - + SHIFT + Click to edit a cue SHIFT + Click to edit a cue - + CTRL + Click to select a cue CTRL + Click to select a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them while pressing SHIFT To move cues drag them while pressing SHIFT @@ -148,7 +148,7 @@ LayoutName - + Cart Layout Cart Layout @@ -156,22 +156,22 @@ ListLayout - + Edit cue Edit cue - + Edit selected cues Edit selected cues - + Remove cue Remove cue - + Remove selected cues Remove selected cues diff --git a/lisp/i18n/ts/de_DE/controller.ts b/lisp/i18n/ts/de_DE/controller.ts index bdb1b872d..a92a64916 100644 --- a/lisp/i18n/ts/de_DE/controller.ts +++ b/lisp/i18n/ts/de_DE/controller.ts @@ -12,20 +12,20 @@ ControllerKeySettings - - Key - Key - - - + Action - Action + Aktion - + Shortcuts Shortcuts + + + Shortcut + Shortcut + ControllerMidiSettings @@ -37,12 +37,12 @@ Type - Type + Typ Channel - Channel + Kanal @@ -52,17 +52,17 @@ Action - Action + Aktion Filter "note on" - Filter "note on" + Filter "Note an" Filter "note off" - Filter "note off" + Filter "Note aus" @@ -72,7 +72,7 @@ Listening MIDI messages ... - Listening MIDI messages ... + Höre MIDI Nachrichten @@ -131,65 +131,65 @@ ControllerSettings - + Add - Add + Hinzufügen - + Remove - Remove + Entfernen GlobalAction - + Go Go - + Reset Reset - + Stop all cues Stop all cues - + Pause all cues Pause all cues - + Resume all cues Resume all cues - + Interrupt all cues Interrupt all cues - + Fade-out all cues Fade-out all cues - + Fade-in all cues Fade-in all cues - + Move standby forward Move standby forward - + Move standby back Move standby back @@ -197,12 +197,12 @@ Osc Cue - + Type Type - + Argument Argument @@ -233,9 +233,9 @@ MIDI Controls - + Keyboard Shortcuts - Keyboard Shortcuts + Tastatur Shortcuts diff --git a/lisp/i18n/ts/de_DE/gst_backend.ts b/lisp/i18n/ts/de_DE/gst_backend.ts index 113560e74..08ac78ad2 100644 --- a/lisp/i18n/ts/de_DE/gst_backend.ts +++ b/lisp/i18n/ts/de_DE/gst_backend.ts @@ -6,7 +6,7 @@ ALSA device - ALSA device + ALSA Gerät @@ -14,7 +14,7 @@ Compressor - Compressor + Kompressor @@ -34,17 +34,17 @@ Compressor/Expander - Compressor/Expander + Kompressor/Expander Type - Type + Typ Curve Shape - Curve Shape + Kurvenform @@ -54,7 +54,7 @@ Threshold (dB) - Threshold (dB) + Threshhold (dB) @@ -85,12 +85,12 @@ DbMeter settings - DbMeter settings + DbMeter Einstellungen Time between levels (ms) - Time between levels (ms) + Zeit zwischen Pegeln(ms) @@ -100,7 +100,7 @@ Peak falloff (dB/sec) - Peak falloff (dB/sec) + Peak fallof (dB/sek) @@ -108,18 +108,18 @@ 10 Bands Equalizer (IIR) - 10 Bands Equalizer (IIR) + 10Band Equalizer (IIR) GstBackend - + Audio cue (from file) Audio cue (from file) - + Select media files Select media files @@ -127,7 +127,7 @@ GstMediaError - + Cannot create pipeline element: "{}" Cannot create pipeline element: "{}" @@ -137,13 +137,13 @@ Change Pipeline - Change Pipeline + Pipeline verändern GstMediaWarning - + Invalid pipeline element: "{}" Invalid pipeline element: "{}" @@ -153,7 +153,7 @@ Edit Pipeline - Edit Pipeline + Pipeline bearbeiten @@ -169,32 +169,32 @@ Connections - Connections + Verbindungen Edit connections - Edit connections + Verbindungen bearbeiten Output ports - Output ports + Ausgangsports Input ports - Input ports + Eingangsports Connect - Connect + Verbinden Disconnect - Disconnect + Trennen @@ -202,7 +202,7 @@ Compressor/Expander - Compressor/Expander + Kompressor/Expander @@ -217,7 +217,7 @@ Volume - Volume + Lautstärke @@ -237,12 +237,12 @@ JACK Out - JACK Out + Jack Out Custom Element - Custom Element + angepasstes Element @@ -255,19 +255,19 @@ Pitch - + URI Input URI Input 10 Bands Equalizer - 10 Bands Equalizer + 10Band Equalizer Speed - Speed + Geschwindigkeit @@ -285,7 +285,7 @@ {0:+} semitones - {0:+} semitones + {0:+} Halbtöne @@ -301,7 +301,7 @@ Media Settings - Media Settings + Medien Einstellungen @@ -314,50 +314,50 @@ Speed - Speed + Geschwindigkeit UriInputSettings - + Source - Source + Quelle - + Find File - Find File + Finde Datei - + Buffering Buffering - + Use Buffering - Use Buffering + Buffering benutzen - + Attempt download on network streams - Attempt download on network streams + Versuche Download in Netzwerkstreams - + Buffer size (-1 default value) - Buffer size (-1 default value) + Buffer Größe(-1 Standard Wert) - + Choose file - Choose file + Datei auswählen - + All files - All files + Alle Dateien @@ -365,12 +365,12 @@ User defined elements - User defined elements + Benutzerdefinierte Elemente Only for advanced user! - Only for advanced user! + Nur für fortgeschrittene Benutzer! @@ -378,17 +378,17 @@ Volume - Volume + Lautstärke Normalized volume - Normalized volume + Normalisierte Lautstärke Reset - Reset + Zurücksetzen diff --git a/lisp/i18n/ts/de_DE/lisp.ts b/lisp/i18n/ts/de_DE/lisp.ts index 3c379105f..f67c3c570 100644 --- a/lisp/i18n/ts/de_DE/lisp.ts +++ b/lisp/i18n/ts/de_DE/lisp.ts @@ -1,27 +1,35 @@ + + APIServerInfo + + + Stop serving network API + Stop serving network API + + About Authors - Authors + Autoren Contributors - Contributors + Mitwirkende Translators - Translators + Übersetzer About Linux Show Player - About Linux Show Player + Über Linux Show Player @@ -29,27 +37,27 @@ Web site - Web site + Website Source code - Source code + Quellcode Info - Info + Informationen License - License + Lizenz Contributors - Contributors + Mitwirkende @@ -58,16 +66,19 @@ - Actions + AlsaSinkSettings - - Undo: {} - Undo: {} + + ALSA device + ALSA device + + + ApiServerError - - Redo: {} - Redo: {} + + Network API server stopped working. + Network API server stopped working. @@ -81,39 +92,292 @@ AppGeneralSettings - + Default layout Default layout - + Enable startup layout selector Enable startup layout selector - + Application themes Application themes - + UI theme: UI theme: - + Icons theme: Icons theme: + + + Application language (require restart) + Application language (require restart) + + + + Language: + Language: + ApplicationError - + Startup error Startup error + + AudioDynamicSettings + + + Compressor + Compressor + + + + Expander + Expander + + + + Soft Knee + Soft Knee + + + + Hard Knee + Hard Knee + + + + Compressor/Expander + Compressor/Expander + + + + Type + Type + + + + Curve Shape + Curve Shape + + + + Ratio + Ratio + + + + Threshold (dB) + Threshold (dB) + + + + AudioPanSettings + + + Audio Pan + Audio Pan + + + + Center + Center + + + + Left + Left + + + + Right + Right + + + + CartLayout + + + Default behaviors + Default behaviors + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show volume + Show volume + + + + Grid size + Grid size + + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Number of Pages: + Number of Pages: + + + + Page {number} + Page {number} + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + CollectionCue + + + Add + Add + + + + Remove + Remove + + + + Cue + Cue + + + + Action + Action + + + + CommandCue + + + Command + Command + + + + Command to execute, as in a shell + Command to execute, as in a shell + + + + Discard command output + Discard command output + + + + Ignore command errors + Ignore command errors + + + + Kill instead of terminate + Kill instead of terminate + + + + CommandsStack + + + Undo: {} + Undo: {} + + + + Redo: {} + Redo: {} + + ConfigurationDebug @@ -130,12 +394,160 @@ New configuration installed at {} + + Controller + + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + + ControllerKeySettings + + + Action + Action + + + + Shortcuts + Shortcuts + + + + Shortcut + Shortcut + + + + ControllerMidiSettings + + + MIDI + MIDI + + + + Type + Type + + + + Channel + Channel + + + + Note + Note + + + + Action + Action + + + + Filter "note on" + Filter "note on" + + + + Filter "note off" + Filter "note off" + + + + Capture + Capture + + + + Listening MIDI messages ... + Listening MIDI messages ... + + + + ControllerOscSettings + + + OSC Message + OSC Message + + + + OSC + OSC + + + + Path + Path + + + + Types + Types + + + + Arguments + Arguments + + + + Actions + Actions + + + + OSC Capture + OSC Capture + + + + Add + Add + + + + Remove + Remove + + + + Capture + Capture + + + + ControllerSettings + + + Add + Add + + + + Remove + Remove + + + + Cue Name + + + OSC Settings + OSC Settings + + CueAction Default - Default + Standard @@ -188,60 +600,78 @@ Do Nothing - - CueActionLog - - - Cue settings changed: "{}" - Cue settings changed: "{}" - - - - Cues settings changed. - Cues settings changed. - - CueAppearanceSettings The appearance depends on the layout - The appearance depends on the layout + Das Aussehen ist abhängig vom Layout Cue name - Cue name + Cue Name NoName - NoName + Kein Name Description/Note - Description/Note + Beschreibung/Notizen Set Font Size - Set Font Size + Font Größe einstellen Color - Color + Farbe Select background color - Select background color + Hintergrundfarbe auswählen Select font color - Select font color + Font-Farbe auswählen + + + + CueCategory + + + Action cues + Action cues + + + + Integration cues + Integration cues + + + + Misc cues + Misc cues + + + + CueCommandLog + + + Cue settings changed: "{}" + Cue settings changed: "{}" + + + + Cues settings changed. + Cues settings changed. @@ -249,33 +679,73 @@ Media Cue - Media Cue + Medien-Cue + + + + Index Action + Index Action + + + + Seek Cue + Seek Cue + + + + Volume Control + Volume Control + + + + Command Cue + Command Cue + + + + Collection Cue + Collection Cue + + + + Stop-All + Stop-All + + + + MIDI Cue + MIDI Cue + + + + OSC Cue + OSC Cue CueNextAction - + Do Nothing Do Nothing - + Trigger after the end Trigger after the end - + Trigger after post wait Trigger after post wait - + Select after the end Select after the end - + Select after post wait Select after post wait @@ -290,7 +760,7 @@ Wait before cue execution - Wait before cue execution + Vor Cue Ausführung warten @@ -300,32 +770,32 @@ Wait after cue execution - Wait after cue execution + Nach Cue Ausführung warten Next action - Next action + Nächste Aktion Start action - Start action + Start Aktion Default action to start the cue - Default action to start the cue + Standard Aktion zum Starten der Cue Stop action - Stop action + Stop Aktion Default action to stop the cue - Default action to stop the cue + Standard Aktion zum Beenden der Cue @@ -339,33 +809,87 @@ - Fade + CueTriggers - - Linear - Linear + + Started + Started - - Quadratic - Quadratic + + Paused + Paused - - Quadratic2 - Quadratic2 + + Stopped + Stopped + + + + Ended + Ended - FadeEdit + DbMeterSettings - - Duration (sec): - Duration (sec): + + DbMeter settings + DbMeter settings - - Curve: + + Time between levels (ms) + Time between levels (ms) + + + + Peak ttl (ms) + Peak ttl (ms) + + + + Peak falloff (dB/sec) + Peak falloff (dB/sec) + + + + Equalizer10Settings + + + 10 Bands Equalizer (IIR) + 10 Bands Equalizer (IIR) + + + + Fade + + + Linear + Linear + + + + Quadratic + Quadratisch + + + + Quadratic2 + Logistisch + + + + FadeEdit + + + Duration (sec): + Duration (sec): + + + + Curve: Curve: @@ -382,50 +906,473 @@ Fade Out + + GlobalAction + + + Go + Go + + + + Reset + Reset + + + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + + + GstMediaSettings + + + Change Pipeline + Change Pipeline + + + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + + + GstPipelineEdit + + + Edit Pipeline + Edit Pipeline + + + + GstSettings + + + Pipeline + Pipeline + + + + HotKeyEdit + + + Press shortcut + Press shortcut + + + + IndexActionCue + + + No suggestion + No suggestion + + + + Index + Index + + + + Use a relative index + Use a relative index + + + + Target index + Target index + + + + Action + Action + + + + Suggested cue name + Suggested cue name + + + + JackSinkSettings + + + Connections + Connections + + + + Edit connections + Edit connections + + + + Output ports + Output ports + + + + Input ports + Input ports + + + + Connect + Connect + + + + Disconnect + Disconnect + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them + To move cues drag them + + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + To move cues drag them while pressing SHIFT + To move cues drag them while pressing SHIFT + + + + LayoutName + + + List Layout + List Layout + + + + Cart Layout + Cart Layout + + LayoutSelect - + Layout selection - Layout selection + Layout Auswahl - + Select layout - Select layout + Layout auswählen - + Open file - Open file + Datei öffnen ListLayout - - Stop All - Stop All + + Default behaviors + Default behaviors + + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show seek-bars + Show seek-bars + + + + Auto-select next cue + Auto-select next cue + + + + Enable selection mode + Enable selection mode + + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Resume Cue + Resume Cue + + + + Interrupt Cue + Interrupt Cue + + + + Edit cue + Edit cue + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove cue + Remove cue + + + + Remove selected + Remove selected + + + + Selection mode + Selection mode + + + + Pause all + Pause all + + + + Stop all + Stop all - Pause All - Pause All + Interrupt all + Interrupt all + + + + Resume all + Resume all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all - - Interrupt All - Interrupt All + + Edit selected cues + Edit selected cues + + + + Remove selected cues + Remove selected cues - Use fade (global actions) - Use fade (global actions) + Use fade + Use fade + + + + Copy of {} + Copy of {} + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + LogStatusIcon - - Resume All - Resume All + + Errors/Warnings + Errors/Warnings @@ -438,12 +1385,12 @@ Warning - Warning + Warnung Error - Error + Fehler @@ -456,7 +1403,7 @@ Show details - + Linux Show Player - Log Viewer Linux Show Player - Log Viewer @@ -547,146 +1494,299 @@ - MainWindow + MIDICue - - &File - &File + + MIDI Message + MIDI Message - - New session - New session + + Message type + Message type + + + MIDIMessageAttr - - Open - Open + + Channel + Channel - - Save session - Save session + + Note + Note - - Preferences - Preferences + + Velocity + Velocity - - Save as - Save as + + Control + Control - - Full Screen - Full Screen + + Program + Program - - Exit - Exit + + Value + Value - - &Edit - &Edit + + Song + Song - - Undo - Undo + + Pitch + Pitch - - Redo - Redo + + Position + Position + + + MIDIMessageType - - Select all - Select all + + Note ON + Note ON - - Select all media cues - Select all media cues + + Note OFF + Note OFF - - Deselect all - Deselect all + + Polyphonic After-touch + Polyphonic After-touch - - CTRL+SHIFT+A - CTRL+SHIFT+A + + Control/Mode Change + Control/Mode Change - - Invert selection - Invert selection + + Program Change + Program Change + + + + Channel After-touch + Channel After-touch + + + + Pitch Bend Change + Pitch Bend Change + + + + Song Select + Song Select + + + + Song Position + Song Position + + + + Start + Start + + + + Stop + Stop + + + + Continue + Continue + + + + MIDISettings + + + MIDI default devices + MIDI default devices + + + + Input + Input + + + + Output + Output + + + + MainWindow + + + &File + &Datei + + + + New session + Neue Sitzung + + + + Open + Öffnen + + + + Save session + Sitzung Speichern + + + + Preferences + Einstellungen + + + + Save as + Speichern als + + + + Full Screen + Vollbild + + + + Exit + Verlassen + + + + &Edit + &Bearbeiten + + + + Undo + Rückgängig + + + + Redo + Wiederherstellen + + + + Select all + Alle auswählen + + + + Select all media cues + Alle Media Cues auswählen + + + + Deselect all + Auswahl aufheben + + + + CTRL+SHIFT+A + CTRL+Shift+A + Invert selection + Auswahl invertieren + + + CTRL+I CTRL+I - + Edit selected - Edit selected + Auswahl bearbeiten - + CTRL+SHIFT+E - CTRL+SHIFT+E + CTRL + Shift + E - + &Layout &Layout - + &Tools &Tools - + Edit selection - Edit selection + Auswahl bearbeiten - + &About - &About + &Über - + About - About + Über - + About Qt - About Qt + Über Qt - + Close session - Close session + Sitzung schließen + + + + Do you want to save them now? + Do you want to save them now? + + + MainWindowDebug - - The current session is not saved. - The current session is not saved. + + Registered cue menu: "{}" + Registered cue menu: "{}" + + + MainWindowError - - Discard the changes? - Discard the changes? + + Cannot create cue {} + Cannot create cue {} @@ -694,22 +1794,22 @@ Start time - Start time + Start Zeit Stop position of the media - Stop position of the media + Stop Position der Datei Stop time - Stop time + Stop Zeit Start position of the media - Start position of the media + Start Position der Datei @@ -717,6 +1817,299 @@ Loop + + MediaElementName + + + Pitch + Pitch + + + + Volume + Volume + + + + Audio Pan + Audio Pan + + + + Compressor/Expander + Compressor/Expander + + + + JACK Out + JACK Out + + + + URI Input + URI Input + + + + 10 Bands Equalizer + 10 Bands Equalizer + + + + ALSA Out + ALSA Out + + + + dB Meter + dB Meter + + + + Preset Input + Preset Input + + + + PulseAudio Out + PulseAudio Out + + + + Custom Element + Custom Element + + + + Speed + Speed + + + + System Out + System Out + + + + System Input + System Input + + + + MediaInfo + + + Media Info + Media Info + + + + Warning + Warning + + + + No info to display + No info to display + + + + Info + Info + + + + Value + Value + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + OscCue + + + Add + Add + + + + Remove + Remove + + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + PitchSettings + + + Pitch + Pitch + + + + {0:+} semitones + {0:+} semitones + + PluginsError @@ -757,59 +2150,715 @@ - QColorButton + Preset - - Right click to reset - Right click to reset + + Create Cue + Create Cue + + + + Load on selected Cues + Load on selected Cues - SettingsPageName + PresetSrcSettings - - Appearance - Appearance + + Presets + Presets + + + Presets - - General - General + + Cannot scan presets + Cannot scan presets - - Cue - Cue + + Error while deleting preset "{}" + Error while deleting preset "{}" - - Cue Settings - Cue Settings + + Cannot load preset "{}" + Cannot load preset "{}" - - Plugins - Plugins + + Cannot save preset "{}" + Cannot save preset "{}" - - Behaviours - Behaviours + + Cannot rename preset "{}" + Cannot rename preset "{}" - - Pre/Post Wait - Pre/Post Wait + + Select Preset + Select Preset - - Fade In/Out - Fade In/Out + + Presets + Presets - - Layouts - Layouts + + Preset name + Preset name + + + + Add + Add + + + + Rename + Rename + + + + Edit + Edit + + + + Remove + Remove + + + + Export selected + Export selected + + + + Import + Import + + + + Warning + Warning + + + + The same name is already used! + The same name is already used! + + + + Cannot export correctly. + Cannot export correctly. + + + + Cannot import correctly. + Cannot import correctly. + + + + Cue type + Cue type + + + + Load on cue + Load on cue + + + + Load on selected cues + Load on selected cues + + + + Save as preset + Save as preset + + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + + + + QColorButton + + + Right click to reset + Rechtsklick zum Zurücksetzen + + + + RenameCues + + + Rename Cues + Rename Cues + + + + Rename cues + Rename cues + + + + Current + Current + + + + Preview + Preview + + + + Capitalize + Capitalize + + + + Lowercase + Lowercase + + + + Uppercase + Uppercase + + + + Remove Numbers + Remove Numbers + + + + Add numbering + Add numbering + + + + Reset + Reset + + + + Type your regex here: + Type your regex here: + + + + Regex help + Regex help + + + + RenameCuesCommand + + + Renamed {number} cues + Renamed {number} cues + + + + RenameUiDebug + + + Regex error: Invalid pattern + Regex error: Invalid pattern + + + + ReplayGain + + + ReplayGain / Normalization + ReplayGain / Normalization + + + + Threads number + Threads number + + + + Apply only to selected media + Apply only to selected media + + + + ReplayGain to (dB SPL) + ReplayGain to (dB SPL) + + + + Normalize to (dB) + Normalize to (dB) + + + + Processing files ... + Processing files ... + + + + Calculate + Calculate + + + + Reset all + Reset all + + + + Reset selected + Reset selected + + + + ReplayGainDebug + + + Applied gain for: {} + Applied gain for: {} + + + + Discarded gain for: {} + Discarded gain for: {} + + + + ReplayGainInfo + + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} + + + + SeekCue + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Seek + Seek + + + + Time to reach + Time to reach + + + + SettingsPageName + + + Appearance + Aussehen + + + + General + Generell + + + + Cue + Cue + + + + Cue Settings + Cue Einstellungen + + + + Plugins + Plugins + + + + Behaviours + Behaviours + + + + Pre/Post Wait + Pre/Post Wait + + + + Fade In/Out + Fade In/Out + + + + Layouts + Layouts + + + + Action Settings + Action Settings + + + + Seek Settings + Seek Settings + + + + Volume Settings + Volume Settings + + + + Command + Command + + + + Edit Collection + Edit Collection + + + + Stop Settings + Stop Settings + + + + List Layout + List Layout + + + + Timecode + Timecode + + + + Timecode Settings + Timecode Settings + + + + Cue Control + Cue Control + + + + Layout Controls + Layout Controls + + + + MIDI Controls + MIDI Controls + + + + Keyboard Shortcuts + Keyboard Shortcuts + + + + OSC Controls + OSC Controls + + + + MIDI Settings + MIDI Settings + + + + MIDI settings + MIDI settings + + + + GStreamer + GStreamer + + + + Media Settings + Media Settings + + + + Triggers + Triggers + + + + OSC settings + OSC settings + + + + Cart Layout + Cart Layout + + + + SpeedSettings + + + Speed + Speed + + + + StopAll + + + Stop Action + Stop Action + + + + Synchronizer + + + Synchronization + Synchronization + + + + Manage connected peers + Manage connected peers + + + + Show your IP + Show your IP + + + + Your IP is: + Your IP is: + + + + Timecode + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + TimecodeError + + + Cannot send timecode. + Cannot send timecode. + + + + TimecodeSettings + + + Replace HOURS by a static track number + Replace HOURS by a static track number + + + + Enable Timecode + Enable Timecode + + + + Track number + Track number + + + + Timecode Settings + Timecode Settings + + + + Timecode Format: + Timecode Format: + + + + Timecode Protocol: + Timecode Protocol: + + + + TriggersSettings + + + Add + Add + + + + Remove + Remove + + + + Trigger + Trigger + + + + Cue + Cue + + + + Action + Action + + + + UriInputSettings + + + Source + Source + + + + Find File + Find File + + + + Buffering + Buffering + + + + Use Buffering + Use Buffering + + + + Attempt download on network streams + Attempt download on network streams + + + + Buffer size (-1 default value) + Buffer size (-1 default value) + + + + Choose file + Choose file + + + + All files + All files + + + + UserElementSettings + + + User defined elements + User defined elements + + + + Only for advanced user! + Only for advanced user! + + + + VolumeControl + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Volume to reach + Volume to reach + + + + Fade + Fade + + + + VolumeControlError + + + Error during cue execution. + Error during cue execution. + + + + VolumeSettings + + + Volume + Volume + + + + Normalized volume + Normalized volume + + + + Reset + Reset diff --git a/lisp/i18n/ts/de_DE/list_layout.ts b/lisp/i18n/ts/de_DE/list_layout.ts index 9012c2cc6..1c58ed22f 100644 --- a/lisp/i18n/ts/de_DE/list_layout.ts +++ b/lisp/i18n/ts/de_DE/list_layout.ts @@ -4,7 +4,7 @@ LayoutDescription - + Organize the cues in a list Organize the cues in a list @@ -12,17 +12,17 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue SHIFT + Space or Double-Click to edit a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them To move cues drag them @@ -30,7 +30,7 @@ LayoutName - + List Layout List Layout @@ -43,27 +43,27 @@ Default behaviors - + Show playing cues Show playing cues - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue @@ -73,42 +73,42 @@ Enable selection mode - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - + Remove cue Remove cue - + Selection mode Selection mode @@ -143,60 +143,65 @@ Fade-In all - - GO key: - GO key: - - - - GO action: - GO action: - - - - GO delay (ms): - GO delay (ms): - - - + Edit selected Edit selected - + Clone cue Clone cue - + Clone selected Clone selected - + Remove selected Remove selected + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Copy of {} + Copy of {} + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait diff --git a/lisp/i18n/ts/de_DE/media_info.ts b/lisp/i18n/ts/de_DE/media_info.ts index 65ad21a55..b810eb7d6 100644 --- a/lisp/i18n/ts/de_DE/media_info.ts +++ b/lisp/i18n/ts/de_DE/media_info.ts @@ -4,27 +4,27 @@ MediaInfo - + Media Info - Media Info + Medien Information - + No info to display - No info to display + Keine Informationen zum darstellen - + Info - Info + Informationen - + Value - Value + Wert - + Warning Warning diff --git a/lisp/i18n/ts/de_DE/midi.ts b/lisp/i18n/ts/de_DE/midi.ts index 58ac9f26c..f0538e668 100644 --- a/lisp/i18n/ts/de_DE/midi.ts +++ b/lisp/i18n/ts/de_DE/midi.ts @@ -1,6 +1,22 @@ + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + MIDI Cue + MIDI Cue + + MIDICue @@ -130,7 +146,7 @@ MIDI default devices - MIDI default devices + MIDI Standardgeräte @@ -148,7 +164,12 @@ MIDI settings - MIDI settings + MIDI Einstellungen + + + + MIDI Settings + MIDI Settings diff --git a/lisp/i18n/ts/de_DE/osc.ts b/lisp/i18n/ts/de_DE/osc.ts index f30769d5a..99d38ad39 100644 --- a/lisp/i18n/ts/de_DE/osc.ts +++ b/lisp/i18n/ts/de_DE/osc.ts @@ -1,6 +1,88 @@ + + Cue Name + + + OSC Settings + OSC Settings + + + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + OSC Cue + OSC Cue + + + + OscCue + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + Add + Add + + + + Remove + Remove + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + OscServerDebug diff --git a/lisp/i18n/ts/de_DE/presets.ts b/lisp/i18n/ts/de_DE/presets.ts index 18a7634b5..335fccff3 100644 --- a/lisp/i18n/ts/de_DE/presets.ts +++ b/lisp/i18n/ts/de_DE/presets.ts @@ -4,127 +4,132 @@ Preset - + Create Cue - Create Cue + Cue erstellen - + Load on selected Cues - Load on selected Cues + Last auf ausgewählten cues Presets - + Presets Presets - + Save as preset - Save as preset + Als Preset speichern - + Cannot scan presets - Cannot scan presets + Kann keine Presets scannen - + Error while deleting preset "{}" - Error while deleting preset "{}" + Fehler beim Löschen des Preset "{}" - + Cannot load preset "{}" - Cannot load preset "{}" + Kann Preset "{}" nicht laden - + Cannot save preset "{}" - Cannot save preset "{}" + Kann Preset "{}" nicht speichern - + Cannot rename preset "{}" - Cannot rename preset "{}" + Kann Preset "{}" nicht umbenennen - + Select Preset - Select Preset + Preset Auswählen - + Preset name - Preset name + Preset Name - + Add - Add + Hinzufügen - + Rename - Rename + Umbenennen - + Edit - Edit + Bearbeiten - + Remove - Remove + Entfernen - + Export selected - Export selected + Ausgewähltes exportieren - + Import - Import + Importieren - + Warning - Warning + Warnung - + The same name is already used! - The same name is already used! + Der gleiche Name wird bereits verwendet - + Cannot export correctly. - Cannot export correctly. + Kann nicht richtig exportieren - + Cannot import correctly. - Cannot import correctly. + Kann nicht richtig eimportieren - + Cue type - Cue type + Cue Typ - + Load on cue Load on cue - + Load on selected cues Load on selected cues + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + diff --git a/lisp/i18n/ts/de_DE/rename_cues.ts b/lisp/i18n/ts/de_DE/rename_cues.ts index cb57b2b58..248662d15 100644 --- a/lisp/i18n/ts/de_DE/rename_cues.ts +++ b/lisp/i18n/ts/de_DE/rename_cues.ts @@ -4,7 +4,7 @@ RenameCues - + Rename Cues Rename Cues @@ -64,6 +64,14 @@ Regex help + + RenameCuesCommand + + + Renamed {number} cues + Renamed {number} cues + + RenameUiDebug diff --git a/lisp/i18n/ts/de_DE/replay_gain.ts b/lisp/i18n/ts/de_DE/replay_gain.ts index 2de3dc767..b412ae1ac 100644 --- a/lisp/i18n/ts/de_DE/replay_gain.ts +++ b/lisp/i18n/ts/de_DE/replay_gain.ts @@ -4,49 +4,49 @@ ReplayGain - + ReplayGain / Normalization - ReplayGain / Normalization + ReplayGain / Normalisierung - + Calculate - Calculate + Berechnen - + Reset all - Reset all + Alle zurücksetzen - + Reset selected - Reset selected + Ausgewählte zurücksetzen Threads number - Threads number + Thread Nummer Apply only to selected media - Apply only to selected media + Nur für ausgewählte Medien anwenden ReplayGain to (dB SPL) - ReplayGain to (dB SPL) + ReplayGain auf (dB SPL) Normalize to (dB) - Normalize to (dB) + Normalisieren auf (dB) Processing files ... - Processing files ... + Bearbeite Dateien diff --git a/lisp/i18n/ts/de_DE/synchronizer.ts b/lisp/i18n/ts/de_DE/synchronizer.ts index 03e21668b..422beddc4 100644 --- a/lisp/i18n/ts/de_DE/synchronizer.ts +++ b/lisp/i18n/ts/de_DE/synchronizer.ts @@ -4,24 +4,24 @@ Synchronizer - + Synchronization - Synchronization + Synchronisation - + Manage connected peers - Manage connected peers + Bearbeite verbundene Peers - + Show your IP - Show your IP + Zeige ihre IP - + Your IP is: - Your IP is: + Ihre IP ist:
diff --git a/lisp/i18n/ts/de_DE/timecode.ts b/lisp/i18n/ts/de_DE/timecode.ts index ab03e1bf5..38356223b 100644 --- a/lisp/i18n/ts/de_DE/timecode.ts +++ b/lisp/i18n/ts/de_DE/timecode.ts @@ -6,7 +6,7 @@ Timecode Settings - Timecode Settings + Timecode Einstellungen @@ -40,12 +40,12 @@ Replace HOURS by a static track number - Replace HOURS by a static track number + Ersetze HOURS mit statischer Tracknummer Track number - Track number + Track Nummer diff --git a/lisp/i18n/ts/de_DE/triggers.ts b/lisp/i18n/ts/de_DE/triggers.ts index 43694dfcd..4d99f33f6 100644 --- a/lisp/i18n/ts/de_DE/triggers.ts +++ b/lisp/i18n/ts/de_DE/triggers.ts @@ -6,22 +6,22 @@ Started - Started + Gestartet Paused - Paused + Pausiert Stopped - Stopped + Gestoppt Ended - Ended + Beendet
@@ -29,35 +29,35 @@ Triggers - Triggers + Trigger TriggersSettings - + Add - Add + Hinzufügen - + Remove - Remove + Entfernen - + Trigger Trigger - + Cue Cue - + Action - Action + Aktion
diff --git a/lisp/i18n/ts/es_ES/action_cues.ts b/lisp/i18n/ts/es_ES/action_cues.ts index be5a40c53..bf056e5a1 100644 --- a/lisp/i18n/ts/es_ES/action_cues.ts +++ b/lisp/i18n/ts/es_ES/action_cues.ts @@ -1,41 +1,25 @@ - - ActionCuesDebug - - - Registered cue: "{}" - Registered cue: "{}" - - - - ActionsCuesError - - - Cannot create cue {} - Cannot create cue {} - - CollectionCue - + Add Añadir - + Remove Eliminar - + Cue Cue - + Action Acción @@ -43,37 +27,37 @@ CommandCue - + Command Comando - + Command to execute, as in a shell Comando a ejecutar, como en una línea de comando - + Discard command output Descartar salida de comando - + Ignore command errors Ignorar errores de comando - + Kill instead of terminate Matar en vez de terminar - Cue Name + CueCategory - - OSC Settings - Configuración de OSC + + Action cues + Action cues @@ -83,11 +67,6 @@ Command Cue Cue de comando - - - MIDI Cue - Cue de MIDI - Volume Control @@ -113,153 +92,64 @@ Index Action Índice de Acciones - - - OSC Cue - Cue OSC - IndexActionCue - + Index Índice - + Use a relative index Usar índice relativo - + Target index Índice de Target - + Action Acción - + No suggestion No hay sugerencias - + Suggested cue name Nombre sugerido del Cue - - Osc Cue - - - Type - Tipo - - - - Argument - Argumento - - - - FadeTo - Fundido a - - - - Fade - Fundido - - - - OscCue - - - OSC Message - Mensaje OSC - - - - Add - Añadir - - - - Remove - Eliminar - - - - Test - Probar - - - - OSC Path: (example: "/path/to/something") - Dirección OSC: (ejemplo: "/path/to/something") - - - - Fade - Fundido - - - - Time (sec) - Tiempo (seg) - - - - Curve - Curva - - - - OscCueError - - - Could not parse argument list, nothing sent - Could not parse argument list, nothing sent - - - - Error while parsing arguments, nothing sent - Error while parsing arguments, nothing sent - - - - Error during cue execution. - Error during cue execution. - - SeekCue - + Cue Cue - + Click to select Click para seleccionar - + Not selected No seleccionado - + Seek Búsqueda - + Time to reach Tiempo a alcanzar @@ -267,37 +157,32 @@ SettingsPageName - + Command Comando - - MIDI Settings - Ajustes MIDI - - - + Volume Settings Ajustes de volumen - + Seek Settings Ajustes de búsqueda - + Edit Collection Editar Colección - + Action Settings Ajustes de Acción - + Stop Settings Ajustes de Detención @@ -305,7 +190,7 @@ StopAll - + Stop Action Detener Acción @@ -313,27 +198,27 @@ VolumeControl - + Cue Cue - + Click to select Click para seleccionar - + Not selected No seleccionado - + Volume to reach Volumen a alcanzar - + Fade Fundido @@ -341,7 +226,7 @@ VolumeControlError - + Error during cue execution. Error during cue execution. diff --git a/lisp/i18n/ts/es_ES/cart_layout.ts b/lisp/i18n/ts/es_ES/cart_layout.ts index 3b0ed3d92..6eac2ec9d 100644 --- a/lisp/i18n/ts/es_ES/cart_layout.ts +++ b/lisp/i18n/ts/es_ES/cart_layout.ts @@ -9,27 +9,27 @@ Comportamiento por defecto - + Countdown mode Modo de cuenta regresiva - + Show seek-bars Mostrar barra de búsqueda - + Show dB-meters Mostrar medidores de dB - + Show accurate time Mostrar tiempo preciso - + Show volume Mostrar volumen @@ -39,62 +39,62 @@ Tamaño de cuadrícula - + Play Reproducir - + Pause Pausa - + Stop Detener - + Reset volume Reestablecer volumen - + Add page Añadir página - + Add pages Añadir páginas - + Remove current page Eliminar página actual - + Number of Pages: Número de páginas: - + Page {number} Página {number} - + Warning Advertencia - + Every cue in the page will be lost. Todos los cues en la página se perderán. - + Are you sure to continue? ¿Está seguro de continuar? @@ -112,7 +112,7 @@ LayoutDescription - + Organize cues in grid like pages Organizar cues en cuadrícula como páginas @@ -120,27 +120,27 @@ LayoutDetails - + Click a cue to run it Hacer click en un cue para ejecutarlo - + SHIFT + Click to edit a cue SHIFT + Click para editar un cue - + CTRL + Click to select a cue CTRL + Click para seleccionar un cue - + To copy cues drag them while pressing CTRL Para copiar cues, arrástrelos mientras presiona CTRL - + To move cues drag them while pressing SHIFT Para mover Cues arrástrelos mientras presiona SHIFT @@ -148,7 +148,7 @@ LayoutName - + Cart Layout Cart Layout @@ -156,22 +156,22 @@ ListLayout - + Edit cue Editar cue - + Edit selected cues Editar los cues seleccionados - + Remove cue Eliminar cue - + Remove selected cues Eliminar los cues seleccionados diff --git a/lisp/i18n/ts/es_ES/controller.ts b/lisp/i18n/ts/es_ES/controller.ts index fa43810f2..22c329409 100644 --- a/lisp/i18n/ts/es_ES/controller.ts +++ b/lisp/i18n/ts/es_ES/controller.ts @@ -12,20 +12,20 @@ ControllerKeySettings - - Key - Tecla - - - + Action Acción - + Shortcuts Atajos de teclado + + + Shortcut + Shortcut + ControllerMidiSettings @@ -131,12 +131,12 @@ ControllerSettings - + Add Añadir - + Remove Eliminar @@ -144,52 +144,52 @@ GlobalAction - + Go Go - + Reset Reset - + Stop all cues Stop all cues - + Pause all cues Pause all cues - + Resume all cues Resume all cues - + Interrupt all cues Interrupt all cues - + Fade-out all cues Fade-out all cues - + Fade-in all cues Fade-in all cues - + Move standby forward Move standby forward - + Move standby back Move standby back @@ -197,12 +197,12 @@ Osc Cue - + Type Tipo - + Argument Argumento @@ -233,7 +233,7 @@ Controles MIDI - + Keyboard Shortcuts Atajos de teclado diff --git a/lisp/i18n/ts/es_ES/gst_backend.ts b/lisp/i18n/ts/es_ES/gst_backend.ts index 93b776f0d..38a20348d 100644 --- a/lisp/i18n/ts/es_ES/gst_backend.ts +++ b/lisp/i18n/ts/es_ES/gst_backend.ts @@ -114,12 +114,12 @@ GstBackend - + Audio cue (from file) Cue de audio (desde archivo) - + Select media files Seleccionar archivos de medios @@ -127,7 +127,7 @@ GstMediaError - + Cannot create pipeline element: "{}" Cannot create pipeline element: "{}" @@ -143,7 +143,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" Invalid pipeline element: "{}" @@ -255,7 +255,7 @@ Tono - + URI Input Entrada URI @@ -320,42 +320,42 @@ UriInputSettings - + Source Origen - + Find File Encontrar archivo - + Buffering Pre-Carga - + Use Buffering Usar Buffering - + Attempt download on network streams Intentar descargar en streams de red - + Buffer size (-1 default value) Tamaño del buffer (-1 valor por defecto) - + Choose file Elegir archivo - + All files Todos los archivos diff --git a/lisp/i18n/ts/es_ES/lisp.ts b/lisp/i18n/ts/es_ES/lisp.ts index b5ea42d98..a85327549 100644 --- a/lisp/i18n/ts/es_ES/lisp.ts +++ b/lisp/i18n/ts/es_ES/lisp.ts @@ -1,6 +1,14 @@ + + APIServerInfo + + + Stop serving network API + Stop serving network API + + About @@ -58,16 +66,19 @@ - Actions + AlsaSinkSettings - - Undo: {} - Deshacer: {} + + ALSA device + ALSA device + + + ApiServerError - - Redo: {} - Rehacer: {} + + Network API server stopped working. + Network API server stopped working. @@ -81,39 +92,292 @@ AppGeneralSettings - + Default layout Diseño por defecto - + Enable startup layout selector Activar a selector de diseño de inicio - + Application themes Temas de la Aplicación - + UI theme: UI theme: - + Icons theme: Icons theme: + + + Application language (require restart) + Application language (require restart) + + + + Language: + Language: + ApplicationError - + Startup error Startup error + + AudioDynamicSettings + + + Compressor + Compressor + + + + Expander + Expander + + + + Soft Knee + Soft Knee + + + + Hard Knee + Hard Knee + + + + Compressor/Expander + Compressor/Expander + + + + Type + Type + + + + Curve Shape + Curve Shape + + + + Ratio + Ratio + + + + Threshold (dB) + Threshold (dB) + + + + AudioPanSettings + + + Audio Pan + Audio Pan + + + + Center + Center + + + + Left + Left + + + + Right + Right + + + + CartLayout + + + Default behaviors + Default behaviors + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show volume + Show volume + + + + Grid size + Grid size + + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Number of Pages: + Number of Pages: + + + + Page {number} + Page {number} + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + CollectionCue + + + Add + Add + + + + Remove + Remove + + + + Cue + Cue + + + + Action + Action + + + + CommandCue + + + Command + Command + + + + Command to execute, as in a shell + Command to execute, as in a shell + + + + Discard command output + Discard command output + + + + Ignore command errors + Ignore command errors + + + + Kill instead of terminate + Kill instead of terminate + + + + CommandsStack + + + Undo: {} + Undo: {} + + + + Redo: {} + Redo: {} + + ConfigurationDebug @@ -130,6 +394,154 @@ New configuration installed at {} + + Controller + + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + + ControllerKeySettings + + + Action + Action + + + + Shortcuts + Shortcuts + + + + Shortcut + Shortcut + + + + ControllerMidiSettings + + + MIDI + MIDI + + + + Type + Type + + + + Channel + Channel + + + + Note + Note + + + + Action + Action + + + + Filter "note on" + Filter "note on" + + + + Filter "note off" + Filter "note off" + + + + Capture + Capture + + + + Listening MIDI messages ... + Listening MIDI messages ... + + + + ControllerOscSettings + + + OSC Message + OSC Message + + + + OSC + OSC + + + + Path + Path + + + + Types + Types + + + + Arguments + Arguments + + + + Actions + Actions + + + + OSC Capture + OSC Capture + + + + Add + Add + + + + Remove + Remove + + + + Capture + Capture + + + + ControllerSettings + + + Add + Add + + + + Remove + Remove + + + + Cue Name + + + OSC Settings + OSC Settings + + CueAction @@ -188,19 +600,6 @@ No hacer nada - - CueActionLog - - - Cue settings changed: "{}" - Ajustes de Cue cambiados: "{}" - - - - Cues settings changed. - Ajustes de Cue han cambiado. - - CueAppearanceSettings @@ -244,6 +643,37 @@ Seleccionar el color de la fuente + + CueCategory + + + Action cues + Action cues + + + + Integration cues + Integration cues + + + + Misc cues + Misc cues + + + + CueCommandLog + + + Cue settings changed: "{}" + Cue settings changed: "{}" + + + + Cues settings changed. + Cues settings changed. + + CueName @@ -251,31 +681,71 @@ Media Cue Cue de Media + + + Index Action + Index Action + + + + Seek Cue + Seek Cue + + + + Volume Control + Volume Control + + + + Command Cue + Command Cue + + + + Collection Cue + Collection Cue + + + + Stop-All + Stop-All + + + + MIDI Cue + MIDI Cue + + + + OSC Cue + OSC Cue + CueNextAction - + Do Nothing No hacer nada - + Trigger after the end Trigger after the end - + Trigger after post wait Trigger after post wait - + Select after the end Select after the end - + Select after post wait Select after post wait @@ -338,6 +808,60 @@ Acciones de Fundido + + CueTriggers + + + Started + Started + + + + Paused + Paused + + + + Stopped + Stopped + + + + Ended + Ended + + + + DbMeterSettings + + + DbMeter settings + DbMeter settings + + + + Time between levels (ms) + Time between levels (ms) + + + + Peak ttl (ms) + Peak ttl (ms) + + + + Peak falloff (dB/sec) + Peak falloff (dB/sec) + + + + Equalizer10Settings + + + 10 Bands Equalizer (IIR) + 10 Bands Equalizer (IIR) + + Fade @@ -383,49 +907,472 @@ - LayoutSelect + GlobalAction - - Layout selection - Selección de Layout + + Go + Go - - Select layout - Seleccionar Layout + + Reset + Reset - - Open file + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + + + GstMediaSettings + + + Change Pipeline + Change Pipeline + + + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + + + GstPipelineEdit + + + Edit Pipeline + Edit Pipeline + + + + GstSettings + + + Pipeline + Pipeline + + + + HotKeyEdit + + + Press shortcut + Press shortcut + + + + IndexActionCue + + + No suggestion + No suggestion + + + + Index + Index + + + + Use a relative index + Use a relative index + + + + Target index + Target index + + + + Action + Action + + + + Suggested cue name + Suggested cue name + + + + JackSinkSettings + + + Connections + Connections + + + + Edit connections + Edit connections + + + + Output ports + Output ports + + + + Input ports + Input ports + + + + Connect + Connect + + + + Disconnect + Disconnect + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them + To move cues drag them + + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + To move cues drag them while pressing SHIFT + To move cues drag them while pressing SHIFT + + + + LayoutName + + + List Layout + List Layout + + + + Cart Layout + Cart Layout + + + + LayoutSelect + + + Layout selection + Selección de Layout + + + + Select layout + Seleccionar Layout + + + + Open file Abrir archivo ListLayout - - Stop All - Detener Todo + + Default behaviors + Default behaviors + + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show seek-bars + Show seek-bars + + + + Auto-select next cue + Auto-select next cue + + + + Enable selection mode + Enable selection mode + + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Resume Cue + Resume Cue + + + + Interrupt Cue + Interrupt Cue + + + + Edit cue + Edit cue + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove cue + Remove cue + + + + Remove selected + Remove selected + + + + Selection mode + Selection mode + + + + Pause all + Pause all + + + + Stop all + Stop all - Pause All - Pausar Todo + Interrupt all + Interrupt all + + + + Resume all + Resume all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + Edit selected cues + Edit selected cues - - Interrupt All - Interrumpir todo + + Remove selected cues + Remove selected cues - Use fade (global actions) - Usar Fundido (acciones globales) + Use fade + Use fade + + + + Copy of {} + Copy of {} + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + LogStatusIcon - - Resume All - Reanudar todo + + Errors/Warnings + Errors/Warnings @@ -456,7 +1403,7 @@ Mostrar detalles - + Linux Show Player - Log Viewer Linux Show Player - Visor de registro @@ -547,146 +1494,299 @@ - MainWindow + MIDICue - - &File - &Archivo + + MIDI Message + MIDI Message - - New session - Nueva sesión + + Message type + Message type + + + MIDIMessageAttr - - Open - Abrir + + Channel + Channel - - Save session - Guardar sesión + + Note + Note - - Preferences - Preferencias + + Velocity + Velocity - - Save as - Guardar como + + Control + Control - - Full Screen - Pantalla completa + + Program + Program - - Exit - Salir + + Value + Value - - &Edit - &Editar + + Song + Song - - Undo - Deshacer + + Pitch + Pitch - - Redo - Rehacer + + Position + Position + + + MIDIMessageType - - Select all - Seleccionar todo + + Note ON + Note ON - - Select all media cues - Seleccionar todos los Media cues + + Note OFF + Note OFF - - Deselect all - Deseleccionar todo + + Polyphonic After-touch + Polyphonic After-touch - - CTRL+SHIFT+A - CTRL + SHIFT + A + + Control/Mode Change + Control/Mode Change - - Invert selection - Invertir selección + + Program Change + Program Change - - CTRL+I - CTRL + I + + Channel After-touch + Channel After-touch - - Edit selected - Editar seleccionado + + Pitch Bend Change + Pitch Bend Change - - CTRL+SHIFT+E - CTRL + SHIFT + E + + Song Select + Song Select - - &Layout - &Layout + + Song Position + Song Position - - &Tools - &Herramientas + + Start + Start - - Edit selection - Editar selección + + Stop + Stop - - &About - &Acerca + + Continue + Continue + + + + MIDISettings + + + MIDI default devices + MIDI default devices + + + + Input + Input + + + + Output + Output + + + + MainWindow + + + &File + &Archivo + + + + New session + Nueva sesión + + + + Open + Abrir + + + + Save session + Guardar sesión + + + + Preferences + Preferencias + + + + Save as + Guardar como + + + + Full Screen + Pantalla completa + + + + Exit + Salir + + + + &Edit + &Editar + + + + Undo + Deshacer + + + + Redo + Rehacer + + + + Select all + Seleccionar todo + + + + Select all media cues + Seleccionar todos los Media cues + + + + Deselect all + Deseleccionar todo + + + + CTRL+SHIFT+A + CTRL + SHIFT + A + + + + Invert selection + Invertir selección + + + + CTRL+I + CTRL + I + + + + Edit selected + Editar seleccionado + + + + CTRL+SHIFT+E + CTRL + SHIFT + E + + + + &Layout + &Layout + + + + &Tools + &Herramientas + Edit selection + Editar selección + + + + &About + &Acerca + + + About Acerca - + About Qt Acerca de Qt - + Close session Cerrar sesión - - The current session is not saved. - La sección actual no se ha guardado. + + Do you want to save them now? + Do you want to save them now? + + + + MainWindowDebug + + + Registered cue menu: "{}" + Registered cue menu: "{}" + + + MainWindowError - - Discard the changes? - ¿Descartar cambios? + + Cannot create cue {} + Cannot create cue {} @@ -717,6 +1817,299 @@ Bucle + + MediaElementName + + + Pitch + Pitch + + + + Volume + Volume + + + + Audio Pan + Audio Pan + + + + Compressor/Expander + Compressor/Expander + + + + JACK Out + JACK Out + + + + URI Input + URI Input + + + + 10 Bands Equalizer + 10 Bands Equalizer + + + + ALSA Out + ALSA Out + + + + dB Meter + dB Meter + + + + Preset Input + Preset Input + + + + PulseAudio Out + PulseAudio Out + + + + Custom Element + Custom Element + + + + Speed + Speed + + + + System Out + System Out + + + + System Input + System Input + + + + MediaInfo + + + Media Info + Media Info + + + + Warning + Warning + + + + No info to display + No info to display + + + + Info + Info + + + + Value + Value + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + OscCue + + + Add + Add + + + + Remove + Remove + + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + PitchSettings + + + Pitch + Pitch + + + + {0:+} semitones + {0:+} semitones + + PluginsError @@ -757,22 +2150,347 @@ - QColorButton + Preset - - Right click to reset - Click derecho para reiniciar + + Create Cue + Create Cue + + + + Load on selected Cues + Load on selected Cues - SettingsPageName + PresetSrcSettings - - Appearance - Apariencia + + Presets + Presets + + + Presets - + + Cannot scan presets + Cannot scan presets + + + + Error while deleting preset "{}" + Error while deleting preset "{}" + + + + Cannot load preset "{}" + Cannot load preset "{}" + + + + Cannot save preset "{}" + Cannot save preset "{}" + + + + Cannot rename preset "{}" + Cannot rename preset "{}" + + + + Select Preset + Select Preset + + + + Presets + Presets + + + + Preset name + Preset name + + + + Add + Add + + + + Rename + Rename + + + + Edit + Edit + + + + Remove + Remove + + + + Export selected + Export selected + + + + Import + Import + + + + Warning + Warning + + + + The same name is already used! + The same name is already used! + + + + Cannot export correctly. + Cannot export correctly. + + + + Cannot import correctly. + Cannot import correctly. + + + + Cue type + Cue type + + + + Load on cue + Load on cue + + + + Load on selected cues + Load on selected cues + + + + Save as preset + Save as preset + + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + + + + QColorButton + + + Right click to reset + Click derecho para reiniciar + + + + RenameCues + + + Rename Cues + Rename Cues + + + + Rename cues + Rename cues + + + + Current + Current + + + + Preview + Preview + + + + Capitalize + Capitalize + + + + Lowercase + Lowercase + + + + Uppercase + Uppercase + + + + Remove Numbers + Remove Numbers + + + + Add numbering + Add numbering + + + + Reset + Reset + + + + Type your regex here: + Type your regex here: + + + + Regex help + Regex help + + + + RenameCuesCommand + + + Renamed {number} cues + Renamed {number} cues + + + + RenameUiDebug + + + Regex error: Invalid pattern + Regex error: Invalid pattern + + + + ReplayGain + + + ReplayGain / Normalization + ReplayGain / Normalization + + + + Threads number + Threads number + + + + Apply only to selected media + Apply only to selected media + + + + ReplayGain to (dB SPL) + ReplayGain to (dB SPL) + + + + Normalize to (dB) + Normalize to (dB) + + + + Processing files ... + Processing files ... + + + + Calculate + Calculate + + + + Reset all + Reset all + + + + Reset selected + Reset selected + + + + ReplayGainDebug + + + Applied gain for: {} + Applied gain for: {} + + + + Discarded gain for: {} + Discarded gain for: {} + + + + ReplayGainInfo + + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} + + + + SeekCue + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Seek + Seek + + + + Time to reach + Time to reach + + + + SettingsPageName + + + Appearance + Apariencia + + + General General @@ -811,5 +2529,336 @@ Layouts Layouts + + + Action Settings + Action Settings + + + + Seek Settings + Seek Settings + + + + Volume Settings + Volume Settings + + + + Command + Command + + + + Edit Collection + Edit Collection + + + + Stop Settings + Stop Settings + + + + List Layout + List Layout + + + + Timecode + Timecode + + + + Timecode Settings + Timecode Settings + + + + Cue Control + Cue Control + + + + Layout Controls + Layout Controls + + + + MIDI Controls + MIDI Controls + + + + Keyboard Shortcuts + Keyboard Shortcuts + + + + OSC Controls + OSC Controls + + + + MIDI Settings + MIDI Settings + + + + MIDI settings + MIDI settings + + + + GStreamer + GStreamer + + + + Media Settings + Media Settings + + + + Triggers + Triggers + + + + OSC settings + OSC settings + + + + Cart Layout + Cart Layout + + + + SpeedSettings + + + Speed + Speed + + + + StopAll + + + Stop Action + Stop Action + + + + Synchronizer + + + Synchronization + Synchronization + + + + Manage connected peers + Manage connected peers + + + + Show your IP + Show your IP + + + + Your IP is: + Your IP is: + + + + Timecode + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + TimecodeError + + + Cannot send timecode. + Cannot send timecode. + + + + TimecodeSettings + + + Replace HOURS by a static track number + Replace HOURS by a static track number + + + + Enable Timecode + Enable Timecode + + + + Track number + Track number + + + + Timecode Settings + Timecode Settings + + + + Timecode Format: + Timecode Format: + + + + Timecode Protocol: + Timecode Protocol: + + + + TriggersSettings + + + Add + Add + + + + Remove + Remove + + + + Trigger + Trigger + + + + Cue + Cue + + + + Action + Action + + + + UriInputSettings + + + Source + Source + + + + Find File + Find File + + + + Buffering + Buffering + + + + Use Buffering + Use Buffering + + + + Attempt download on network streams + Attempt download on network streams + + + + Buffer size (-1 default value) + Buffer size (-1 default value) + + + + Choose file + Choose file + + + + All files + All files + + + + UserElementSettings + + + User defined elements + User defined elements + + + + Only for advanced user! + Only for advanced user! + + + + VolumeControl + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Volume to reach + Volume to reach + + + + Fade + Fade + + + + VolumeControlError + + + Error during cue execution. + Error during cue execution. + + + + VolumeSettings + + + Volume + Volume + + + + Normalized volume + Normalized volume + + + + Reset + Reset + diff --git a/lisp/i18n/ts/es_ES/list_layout.ts b/lisp/i18n/ts/es_ES/list_layout.ts index 5329c5ce2..61678c6b4 100644 --- a/lisp/i18n/ts/es_ES/list_layout.ts +++ b/lisp/i18n/ts/es_ES/list_layout.ts @@ -4,7 +4,7 @@ LayoutDescription - + Organize the cues in a list Organizar cues en una lista @@ -12,17 +12,17 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue SHIFT + Barra espaciadora o Doble click para editar un cue - + To copy cues drag them while pressing CTRL Para copiar cues, arrástrelos mientras presiona CTRL - + To move cues drag them Para mover Cues arrástrelos @@ -30,7 +30,7 @@ LayoutName - + List Layout List Layout @@ -43,27 +43,27 @@ Comportamientos por defecto - + Show playing cues Mostrar los cues en ejecución - + Show dB-meters Mostrar medidores de dB - + Show accurate time Mostrar tiempo preciso - + Show seek-bars Mostrar barras de búsqueda - + Auto-select next cue Seleccionar automáticamente el siguiente cue @@ -73,42 +73,42 @@ Habilitar modo de selección - + Use fade (buttons) Usar fundido (botones) - + Stop Cue Detener Cue - + Pause Cue Pausar Cue - + Resume Cue Reanudar Cue - + Interrupt Cue Interrumpir Cue - + Edit cue Editar cue - + Remove cue Eliminar cue - + Selection mode Modo de selección @@ -143,60 +143,65 @@ Fundido de Entrada para Todo - - GO key: - GO key: - - - - GO action: - GO action: - - - - GO delay (ms): - GO delay (ms): - - - + Edit selected Edit selected - + Clone cue Clone cue - + Clone selected Clone selected - + Remove selected Remove selected + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Copy of {} + Copy of {} + ListLayoutHeader - + Cue Cue - + Pre wait Pre Espera - + Action Acción - + Post wait Post Espera diff --git a/lisp/i18n/ts/es_ES/media_info.ts b/lisp/i18n/ts/es_ES/media_info.ts index 4388a1dc5..7d8cf6f25 100644 --- a/lisp/i18n/ts/es_ES/media_info.ts +++ b/lisp/i18n/ts/es_ES/media_info.ts @@ -4,27 +4,27 @@ MediaInfo - + Media Info Información del Media - + No info to display Ninguna información para mostrar - + Info Información - + Value Valor - + Warning Advertencia diff --git a/lisp/i18n/ts/es_ES/midi.ts b/lisp/i18n/ts/es_ES/midi.ts index f7c7eb28d..904aaaa00 100644 --- a/lisp/i18n/ts/es_ES/midi.ts +++ b/lisp/i18n/ts/es_ES/midi.ts @@ -1,6 +1,22 @@ + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + MIDI Cue + MIDI Cue + + MIDICue @@ -150,5 +166,10 @@ MIDI settings Ajustes MIDI + + + MIDI Settings + MIDI Settings + diff --git a/lisp/i18n/ts/es_ES/osc.ts b/lisp/i18n/ts/es_ES/osc.ts index 760cff170..308aef712 100644 --- a/lisp/i18n/ts/es_ES/osc.ts +++ b/lisp/i18n/ts/es_ES/osc.ts @@ -1,6 +1,88 @@ + + Cue Name + + + OSC Settings + OSC Settings + + + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + OSC Cue + OSC Cue + + + + OscCue + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + Add + Add + + + + Remove + Remove + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + OscServerDebug diff --git a/lisp/i18n/ts/es_ES/presets.ts b/lisp/i18n/ts/es_ES/presets.ts index 57b1caee4..505545e09 100644 --- a/lisp/i18n/ts/es_ES/presets.ts +++ b/lisp/i18n/ts/es_ES/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Crear Cue - + Load on selected Cues Abrir en Cues seleccionadas @@ -17,114 +17,119 @@ Presets - + Presets Preajustes - + Save as preset Guardar como Preset - + Cannot scan presets No se pueden escanear Presets - + Error while deleting preset "{}" Error al borrar Preset "{}" - + Cannot load preset "{}" No se puede cargar Preset "{}" - + Cannot save preset "{}" No se puede guardar Preset "{}" - + Cannot rename preset "{}" No se puede cambiar el nombre del Preset "{}" - + Select Preset Seleccionar Preset - + Preset name Nombre del Preset - + Add Añadir - + Rename Cambiar nombre - + Edit Editar - + Remove Eliminar - + Export selected Exportar seleccionados - + Import Importar - + Warning Advertencia - + The same name is already used! ¡El mismo nombre ya está siendo usado! - + Cannot export correctly. No se puede exportar correctamente. - + Cannot import correctly. No se puede importar correctamente. - + Cue type Tipo de Cue - + Load on cue Cargar en cue - + Load on selected cues Cargar en cues seleccionados + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + diff --git a/lisp/i18n/ts/es_ES/rename_cues.ts b/lisp/i18n/ts/es_ES/rename_cues.ts index 990d55965..8e6a031ba 100644 --- a/lisp/i18n/ts/es_ES/rename_cues.ts +++ b/lisp/i18n/ts/es_ES/rename_cues.ts @@ -4,7 +4,7 @@ RenameCues - + Rename Cues Renombrar cues @@ -64,6 +64,14 @@ Ayuda de regex + + RenameCuesCommand + + + Renamed {number} cues + Renamed {number} cues + + RenameUiDebug diff --git a/lisp/i18n/ts/es_ES/replay_gain.ts b/lisp/i18n/ts/es_ES/replay_gain.ts index b69a782ac..e67739714 100644 --- a/lisp/i18n/ts/es_ES/replay_gain.ts +++ b/lisp/i18n/ts/es_ES/replay_gain.ts @@ -4,22 +4,22 @@ ReplayGain - + ReplayGain / Normalization ReplayGain / Normalización - + Calculate Calcular - + Reset all Reestablecer todo - + Reset selected Reestablecer seleccionados diff --git a/lisp/i18n/ts/es_ES/synchronizer.ts b/lisp/i18n/ts/es_ES/synchronizer.ts index b8069d261..cb50fdd8a 100644 --- a/lisp/i18n/ts/es_ES/synchronizer.ts +++ b/lisp/i18n/ts/es_ES/synchronizer.ts @@ -4,22 +4,22 @@ Synchronizer - + Synchronization Sincronización - + Manage connected peers Gestionar peers conectados - + Show your IP Mostrar su IP - + Your IP is: Su IP es: diff --git a/lisp/i18n/ts/es_ES/triggers.ts b/lisp/i18n/ts/es_ES/triggers.ts index 3622d7f3c..87916fccf 100644 --- a/lisp/i18n/ts/es_ES/triggers.ts +++ b/lisp/i18n/ts/es_ES/triggers.ts @@ -35,27 +35,27 @@ TriggersSettings - + Add Añadir - + Remove Eliminar - + Trigger Disparador - + Cue Cue - + Action Acción diff --git a/lisp/i18n/ts/fr_FR/action_cues.ts b/lisp/i18n/ts/fr_FR/action_cues.ts index 39ff424bc..23596979f 100644 --- a/lisp/i18n/ts/fr_FR/action_cues.ts +++ b/lisp/i18n/ts/fr_FR/action_cues.ts @@ -1,79 +1,63 @@ - - ActionCuesDebug - - - Registered cue: "{}" - Registered cue: "{}" - - - - ActionsCuesError - - - Cannot create cue {} - Cannot create cue {} - - CollectionCue - + Add Ajouter - + Remove Retirer - + Cue - Cue + Cue - + Action - Action + Action CommandCue - + Command Commande - + Command to execute, as in a shell Commande à exécuter, comme dans un terminal - + Discard command output Abandonner la sortie de commande - + Ignore command errors Ignorer les erreurs de commande - + Kill instead of terminate Killer au lieu de terminer - Cue Name + CueCategory - - OSC Settings - OSC Settings + + Action cues + Action cues @@ -83,11 +67,6 @@ Command Cue Cue de commande - - - MIDI Cue - Cue MIDI - Volume Control @@ -113,153 +92,64 @@ Index Action Index d'action - - - OSC Cue - OSC Cue - IndexActionCue - + Index - Index + Index - + Use a relative index Utiliser un index relatif - + Target index Index cible - + Action - Action + Action - + No suggestion - No suggestion + Aucune suggestion - + Suggested cue name - Suggested cue name - - - - Osc Cue - - - Type - Type - - - - Argument - Argument - - - - FadeTo - FadeTo - - - - Fade - Fade - - - - OscCue - - - OSC Message - OSC Message - - - - Add - Add - - - - Remove - Remove - - - - Test - Test - - - - OSC Path: (example: "/path/to/something") - OSC Path: (example: "/path/to/something") - - - - Fade - Fade - - - - Time (sec) - Time (sec) - - - - Curve - Curve - - - - OscCueError - - - Could not parse argument list, nothing sent - Could not parse argument list, nothing sent - - - - Error while parsing arguments, nothing sent - Error while parsing arguments, nothing sent - - - - Error during cue execution. - Error during cue execution. + Nom de cue suggéré SeekCue - + Cue - Cue + Cue - + Click to select Cliquer pour sélectionner - + Not selected Non sélectionné - + Seek Recherche - + Time to reach Temps à atteindre @@ -267,37 +157,32 @@ SettingsPageName - + Command Commande - - MIDI Settings - Préférences MIDI - - - + Volume Settings Préférences de volume - + Seek Settings Préférences de recherche - + Edit Collection Editer la collection - + Action Settings Paramètres d'action - + Stop Settings Paramètres d'arrêt @@ -305,7 +190,7 @@ StopAll - + Stop Action Action d'arrêt @@ -313,27 +198,27 @@ VolumeControl - + Cue - Cue + Cue - + Click to select Cliquer pour sélectionner - + Not selected Non sélectionné - + Volume to reach Volume à atteindre - + Fade Fondu @@ -341,9 +226,9 @@ VolumeControlError - + Error during cue execution. - Error during cue execution. + Erreur lors de l'exécution de la cue. diff --git a/lisp/i18n/ts/fr_FR/cart_layout.ts b/lisp/i18n/ts/fr_FR/cart_layout.ts index e57083893..860f3822b 100644 --- a/lisp/i18n/ts/fr_FR/cart_layout.ts +++ b/lisp/i18n/ts/fr_FR/cart_layout.ts @@ -6,149 +6,149 @@ Default behaviors - Default behaviors + Comportements par défaut - + Countdown mode - Countdown mode + Compte à rebours - + Show seek-bars - Show seek-bars + Afficher barre de progression - + Show dB-meters - Show dB-meters + Afficher dB mètres - + Show accurate time - Show accurate time + Afficher temps exact - + Show volume - Show volume + Afficher volume Grid size - Grid size + Taille de la grille - + Play - Play + Lecture - + Pause - Pause + Pause - + Stop - Stop + Stop - + Reset volume - Reset volume + Réinitialisation du volume - + Add page - Add page + Ajouter une page - + Add pages - Add pages + Ajouter des pages - + Remove current page - Remove current page + Retirer la page actuelle - + Number of Pages: - Number of Pages: + Nombre de pages: - + Page {number} - Page {number} + Page {number} - + Warning - Warning + Attention - + Every cue in the page will be lost. - Every cue in the page will be lost. + Toutes les cues dans la page seront perdues. - + Are you sure to continue? - Are you sure to continue? + Êtes-vous sûr de vouloir continuer? Number of columns: - Number of columns: + Nombre de colonnes: Number of rows: - Number of rows: + Nombre de lignes:
LayoutDescription - + Organize cues in grid like pages - Organize cues in grid like pages + Organiser les cues en grille comme les pages LayoutDetails - + Click a cue to run it - Click a cue to run it + Cliquer sur une cue pour l'exécuter - + SHIFT + Click to edit a cue - SHIFT + Click to edit a cue + SHIFT + Clic pour éditer une cue - + CTRL + Click to select a cue - CTRL + Click to select a cue + CTRL + Clic pour sélectionner une cue - + To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL + Pour copier les cues, glissez-les en pressant CTLR - + To move cues drag them while pressing SHIFT - To move cues drag them while pressing SHIFT + Pour déplacer les cues, glissez-les en pressant SHIFT LayoutName - + Cart Layout Cart Layout @@ -156,24 +156,24 @@ ListLayout - + Edit cue - Edit cue + Éditer la cue - + Edit selected cues - Edit selected cues + Éditer les cues sélectionnées - + Remove cue - Remove cue + Retirer la cue - + Remove selected cues - Remove selected cues + Retirer les cues sélectionnées diff --git a/lisp/i18n/ts/fr_FR/controller.ts b/lisp/i18n/ts/fr_FR/controller.ts index df046de83..ae7e3c93a 100644 --- a/lisp/i18n/ts/fr_FR/controller.ts +++ b/lisp/i18n/ts/fr_FR/controller.ts @@ -12,32 +12,32 @@ ControllerKeySettings - - Key - Touche - - - + Action - Action + Action - + Shortcuts Raccourcis + + + Shortcut + Shortcut + ControllerMidiSettings MIDI - MIDI + MIDI Type - Type + Type @@ -52,7 +52,7 @@ Action - Action + Action @@ -80,12 +80,12 @@ OSC Message - OSC Message + Message OSC OSC - OSC + OSC @@ -95,17 +95,17 @@ Types - Types + Types Arguments - Arguments + Arguments Actions - Actions + Actions @@ -115,12 +115,12 @@ Add - Add + Ajouter Remove - Remove + Retirer @@ -131,12 +131,12 @@ ControllerSettings - + Add Ajouter - + Remove Retirer @@ -144,52 +144,52 @@ GlobalAction - + Go - Go + Go - + Reset - Reset + Réinitialiser - + Stop all cues - Stop all cues + Arrêter toutes les cues - + Pause all cues - Pause all cues + Mettre en pause toutes les cues - + Resume all cues - Resume all cues + Reprendre toutes les cues - + Interrupt all cues - Interrupt all cues + Interrompre toutes les cues - + Fade-out all cues Fade-out all cues - + Fade-in all cues Fade-in all cues - + Move standby forward Move standby forward - + Move standby back Move standby back @@ -197,14 +197,14 @@ Osc Cue - + Type - Type + Type - + Argument - Argument + Argument @@ -212,12 +212,12 @@ Add - Add + Ajouter Remove - Remove + Retirer @@ -233,7 +233,7 @@ Contrôles MIDI - + Keyboard Shortcuts Raccourcis-clavier diff --git a/lisp/i18n/ts/fr_FR/gst_backend.ts b/lisp/i18n/ts/fr_FR/gst_backend.ts index 20286761e..5c2027422 100644 --- a/lisp/i18n/ts/fr_FR/gst_backend.ts +++ b/lisp/i18n/ts/fr_FR/gst_backend.ts @@ -114,12 +114,12 @@ GstBackend - + Audio cue (from file) Audio cue (from file) - + Select media files Select media files @@ -127,7 +127,7 @@ GstMediaError - + Cannot create pipeline element: "{}" Cannot create pipeline element: "{}" @@ -143,7 +143,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" Invalid pipeline element: "{}" @@ -255,7 +255,7 @@ Hauteur de note - + URI Input Entrée URI @@ -320,42 +320,42 @@ UriInputSettings - + Source Source - + Find File Trouver le fichier - + Buffering Tampon - + Use Buffering Utiliser le tampon - + Attempt download on network streams Essayer de télécharger en flux de réseau - + Buffer size (-1 default value) Taille du tampon (valeur par défaut -1) - + Choose file Choisir un fichier - + All files Tous les fichiers diff --git a/lisp/i18n/ts/fr_FR/list_layout.ts b/lisp/i18n/ts/fr_FR/list_layout.ts index 3e32454e4..ce8df49fd 100644 --- a/lisp/i18n/ts/fr_FR/list_layout.ts +++ b/lisp/i18n/ts/fr_FR/list_layout.ts @@ -4,33 +4,33 @@ LayoutDescription - + Organize the cues in a list - Organize the cues in a list + Organiser les cues dans une liste LayoutDetails - + SHIFT + Space or Double-Click to edit a cue - SHIFT + Space or Double-Click to edit a cue + SHIFT + Espace ou Double-Clic pour éditer une cue - + To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL + Pour copier les cues, glissez-les en pressant CTRL - + To move cues drag them - To move cues drag them + Pour déplacer les cues, glissez-les LayoutName - + List Layout List Layout @@ -40,32 +40,32 @@ Default behaviors - Default behaviors + Comportements par défaut - + Show playing cues - Show playing cues + Afficher les cues en cours - + Show dB-meters Show dB-meters - + Show accurate time - Show accurate time + Afficher le temps exact - + Show seek-bars Show seek-bars - + Auto-select next cue - Auto-select next cue + Auto-sélectionner la prochaine cue @@ -73,64 +73,64 @@ Enable selection mode - + Use fade (buttons) Use fade (buttons) - + Stop Cue - Stop Cue + Arrêter la cue - + Pause Cue - Pause Cue + Mettre la cue en pause - + Resume Cue - Resume Cue + Reprendre la cue - + Interrupt Cue - Interrupt Cue + Interrompre la cue - + Edit cue - Edit cue + Éditer la cue - + Remove cue - Remove cue + Retirer la cue - + Selection mode Selection mode Pause all - Pause all + Tout mettre en pause Stop all - Stop all + Tout arrêter Interrupt all - Interrupt all + Tout interrompre Resume all - Resume all + Tout reprendre @@ -143,60 +143,65 @@ Fade-In all - - GO key: - GO key: + + Edit selected + Éditer la sélection - - GO action: - GO action: + + Clone cue + Cloner la cue - - GO delay (ms): - GO delay (ms): + + Clone selected + Cloner la selection - - Edit selected - Edit selected + + Remove selected + Retirer la selection - - Clone cue - Clone cue + + GO Key: + GO Key: - - Clone selected - Clone selected + + GO Action: + GO Action: - - Remove selected - Remove selected + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Copy of {} + Copy of {} ListLayoutHeader - + Cue - Cue + Cue - + Pre wait Pre wait - + Action - Action + Action - + Post wait Post wait @@ -206,12 +211,12 @@ Cue name - Cue name + Nom de la cue Cue description - Cue description + Description de la cue diff --git a/lisp/i18n/ts/fr_FR/media_info.ts b/lisp/i18n/ts/fr_FR/media_info.ts index 0f030b739..3c554d7fa 100644 --- a/lisp/i18n/ts/fr_FR/media_info.ts +++ b/lisp/i18n/ts/fr_FR/media_info.ts @@ -4,27 +4,27 @@ MediaInfo - + Media Info Info sur le média - + No info to display Pas d'info à afficher - + Info Info - + Value Valeur - + Warning Warning diff --git a/lisp/i18n/ts/fr_FR/midi.ts b/lisp/i18n/ts/fr_FR/midi.ts index 16e45cc72..9a72d6464 100644 --- a/lisp/i18n/ts/fr_FR/midi.ts +++ b/lisp/i18n/ts/fr_FR/midi.ts @@ -1,17 +1,33 @@ + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + MIDI Cue + MIDI Cue + + MIDICue MIDI Message - MIDI Message + Message MIDI Message type - Message type + Type de message @@ -19,7 +35,7 @@ Channel - Channel + Canal @@ -39,17 +55,17 @@ Program - Program + Programme Value - Value + Valeur Song - Song + Musique @@ -59,7 +75,7 @@ Position - Position + Position @@ -102,27 +118,27 @@ Song Select - Song Select + Selection de musique Song Position - Song Position + Position de musique Start - Start + Démarrer Stop - Stop + Arrêter Continue - Continue + Continuer @@ -150,5 +166,10 @@ MIDI settings Préférences MIDI + + + MIDI Settings + MIDI Settings +
diff --git a/lisp/i18n/ts/fr_FR/network.ts b/lisp/i18n/ts/fr_FR/network.ts index 3f8c65471..0f29e1de5 100644 --- a/lisp/i18n/ts/fr_FR/network.ts +++ b/lisp/i18n/ts/fr_FR/network.ts @@ -35,7 +35,7 @@ Manage hosts - Manage hosts + Gérer les hôtes @@ -45,27 +45,27 @@ Manually add a host - Manually add a host + Ajouter manuellement un hôte Remove selected host - Remove selected host + Retirer l'hôte sélectionné Remove all host - Remove all host + Retirer tous les hôtes Address - Address + Adresse Host IP - Host IP + IP de l'hôte
diff --git a/lisp/i18n/ts/fr_FR/osc.ts b/lisp/i18n/ts/fr_FR/osc.ts index 3007228bc..2c7bb06e0 100644 --- a/lisp/i18n/ts/fr_FR/osc.ts +++ b/lisp/i18n/ts/fr_FR/osc.ts @@ -1,12 +1,94 @@ + + Cue Name + + + OSC Settings + OSC Settings + + + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + OSC Cue + OSC Cue + + + + OscCue + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + Add + Add + + + + Remove + Remove + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + OscServerDebug Message from {} -> path: "{}" args: {} - Message from {} -> path: "{}" args: {} + Message from {} -> chemin: "{}" args: {} @@ -14,7 +96,7 @@ Cannot start OSC sever - Cannot start OSC sever + Impossible de démarrer le serveur OSC @@ -22,12 +104,12 @@ OSC server started at {} - OSC server started at {} + Serveur OSC démarré à {} OSC server stopped - OSC server stopped + Serveur OSC arrêté @@ -35,22 +117,22 @@ OSC Settings - OSC Settings + Paramètres OSC Input Port: - Input Port: + Port d'entrée: Output Port: - Output Port: + Port de sortie: Hostname: - Hostname: + Nom d'hôte: @@ -58,7 +140,7 @@ OSC settings - OSC settings + Paramètres OSC diff --git a/lisp/i18n/ts/fr_FR/presets.ts b/lisp/i18n/ts/fr_FR/presets.ts index 05c824068..eb4aad613 100644 --- a/lisp/i18n/ts/fr_FR/presets.ts +++ b/lisp/i18n/ts/fr_FR/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Créer un cue - + Load on selected Cues Charger les cues sélectionnés @@ -17,114 +17,119 @@ Presets - + Presets Pré-réglages - + Save as preset Sauvegarder un pré-réglage sous - + Cannot scan presets Impossible d'examiner les pré-réglages - + Error while deleting preset "{}" Erreur lors de la suppression du pré-réglage "{}" - + Cannot load preset "{}" Impossible de charger le pré-réglage "{}" - + Cannot save preset "{}" Impossible de sauvegarder le pré-réglage "{}" - + Cannot rename preset "{}" Impossible de renommer le pré-réglage "{}" - + Select Preset Sélectionner le pré-réglage - + Preset name Nom du pré-réglage - + Add Ajouter - + Rename Renommer - + Edit Éditer - + Remove Supprimer - + Export selected Exporter le sélectionné - + Import Importer - + Warning Attention - + The same name is already used! Le même nom est déjà utilisé ! - + Cannot export correctly. Impossible d'exporter correctement - + Cannot import correctly. Impossible d'importer correctement. - + Cue type Type de cue - + Load on cue Load on cue - + Load on selected cues Load on selected cues + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + diff --git a/lisp/i18n/ts/fr_FR/rename_cues.ts b/lisp/i18n/ts/fr_FR/rename_cues.ts index 261646b55..131ebec58 100644 --- a/lisp/i18n/ts/fr_FR/rename_cues.ts +++ b/lisp/i18n/ts/fr_FR/rename_cues.ts @@ -4,24 +4,24 @@ RenameCues - + Rename Cues - Rename Cues + Renommer les cues Rename cues - Rename cues + Renommer les cues Current - Current + Courant Preview - Preview + Aperçu @@ -31,17 +31,17 @@ Lowercase - Lowercase + Minuscule Uppercase - Uppercase + Majuscule Remove Numbers - Remove Numbers + Retirer les numéros @@ -51,17 +51,25 @@ Reset - Reset + Réinitialiser Type your regex here: - Type your regex here: + Tapez votre regex ici: Regex help - Regex help + Aide regex + + + + RenameCuesCommand + + + Renamed {number} cues + Renamed {number} cues @@ -69,7 +77,7 @@ Regex error: Invalid pattern - Regex error: Invalid pattern + Erreur Regex : Modèle invalide diff --git a/lisp/i18n/ts/fr_FR/replay_gain.ts b/lisp/i18n/ts/fr_FR/replay_gain.ts index f114701bb..fca545c17 100644 --- a/lisp/i18n/ts/fr_FR/replay_gain.ts +++ b/lisp/i18n/ts/fr_FR/replay_gain.ts @@ -4,22 +4,22 @@ ReplayGain - + ReplayGain / Normalization Gain de lecture / normalisation - + Calculate Calculer - + Reset all Tout réinitialiser - + Reset selected Réinitialiser la séléction diff --git a/lisp/i18n/ts/fr_FR/synchronizer.ts b/lisp/i18n/ts/fr_FR/synchronizer.ts index 8bf3985a9..88e713043 100644 --- a/lisp/i18n/ts/fr_FR/synchronizer.ts +++ b/lisp/i18n/ts/fr_FR/synchronizer.ts @@ -4,22 +4,22 @@ Synchronizer - + Synchronization Synchronisation - + Manage connected peers Gérer les paiers connectées - + Show your IP Afficher votre IP - + Your IP is: Votre IP est : diff --git a/lisp/i18n/ts/fr_FR/timecode.ts b/lisp/i18n/ts/fr_FR/timecode.ts index ac60b2f1b..a182022ce 100644 --- a/lisp/i18n/ts/fr_FR/timecode.ts +++ b/lisp/i18n/ts/fr_FR/timecode.ts @@ -27,7 +27,7 @@ Cannot send timecode. - Cannot send timecode. + Impossible d'envoyer un code temporel. @@ -40,7 +40,7 @@ Replace HOURS by a static track number - Remplace HOURS par un nombre de piste statique + Remplacer HOURS par un nombre de piste statique @@ -50,7 +50,7 @@ Enable Timecode - Enable Timecode + Activer le code temporel diff --git a/lisp/i18n/ts/fr_FR/triggers.ts b/lisp/i18n/ts/fr_FR/triggers.ts index 06eab38c6..480501557 100644 --- a/lisp/i18n/ts/fr_FR/triggers.ts +++ b/lisp/i18n/ts/fr_FR/triggers.ts @@ -35,29 +35,29 @@ TriggersSettings - + Add Ajouter - + Remove Retirer - + Trigger Déclencheur - + Cue - Cue + Cue - + Action - Action + Action diff --git a/lisp/i18n/ts/it_IT/action_cues.ts b/lisp/i18n/ts/it_IT/action_cues.ts index b5a64ea94..8b8f24670 100644 --- a/lisp/i18n/ts/it_IT/action_cues.ts +++ b/lisp/i18n/ts/it_IT/action_cues.ts @@ -1,41 +1,25 @@ - - ActionCuesDebug - - - Registered cue: "{}" - Registered cue: "{}" - - - - ActionsCuesError - - - Cannot create cue {} - Cannot create cue {} - - CollectionCue - + Add Aggiungi - + Remove Rimuovi - + Cue Cue - + Action Azione @@ -43,37 +27,37 @@ CommandCue - + Command Comando - + Command to execute, as in a shell Comando da eseguire, come in una shell - + Discard command output Scarta l'output del comando - + Ignore command errors Ignora errori - + Kill instead of terminate Uccidi invece di terminare - Cue Name + CueCategory - - OSC Settings - Impostazioni OSC + + Action cues + Action cues @@ -83,11 +67,6 @@ Command Cue Cue Comando - - - MIDI Cue - Cue MIDI - Volume Control @@ -113,153 +92,64 @@ Index Action Azione Posizionale - - - OSC Cue - Cue OSC - IndexActionCue - + Index Posizione - + Use a relative index Usa una posizione relativa - + Target index Posizione bersaglio - + Action Azione - + No suggestion Nessun suggerimento - + Suggested cue name Nome suggerito per la cue - - Osc Cue - - - Type - Tipo - - - - Argument - Argomento - - - - FadeTo - Dissolvi a - - - - Fade - Dissolvenza - - - - OscCue - - - OSC Message - Messaggio OSC - - - - Add - Aggiungi - - - - Remove - Rimuovi - - - - Test - Testa - - - - OSC Path: (example: "/path/to/something") - Percorso OSC: (esempio: "/percorso/per/qualcosa") - - - - Fade - Dissolvenza - - - - Time (sec) - Tempo (sec) - - - - Curve - Curva - - - - OscCueError - - - Could not parse argument list, nothing sent - Could not parse argument list, nothing sent - - - - Error while parsing arguments, nothing sent - Error while parsing arguments, nothing sent - - - - Error during cue execution. - Error during cue execution. - - SeekCue - + Cue Cue - + Click to select Clicca per selezionare - + Not selected Non selezionata - + Seek Posizionamento - + Time to reach Posizione da raggiungere @@ -267,37 +157,32 @@ SettingsPageName - + Command Comando - - MIDI Settings - Impostazioni MIDI - - - + Volume Settings Impostazioni Volume - + Seek Settings Impostazioni Posizione - + Edit Collection Modifica Collezione - + Action Settings Impostazioni Azione - + Stop Settings Impostazioni di Arresto @@ -305,7 +190,7 @@ StopAll - + Stop Action Azione di arresto @@ -313,27 +198,27 @@ VolumeControl - + Cue Cue - + Click to select Clicca per selezionare - + Not selected Non selezionata - + Volume to reach Volume da raggiungere - + Fade Dissolvenza @@ -341,7 +226,7 @@ VolumeControlError - + Error during cue execution. Error during cue execution. diff --git a/lisp/i18n/ts/it_IT/cart_layout.ts b/lisp/i18n/ts/it_IT/cart_layout.ts index e55f4c7db..ee2f9c9de 100644 --- a/lisp/i18n/ts/it_IT/cart_layout.ts +++ b/lisp/i18n/ts/it_IT/cart_layout.ts @@ -9,27 +9,27 @@ Comportamenti predefiniti - + Countdown mode Modalità countdown - + Show seek-bars Mostra barre di avanzamento - + Show dB-meters Mostra indicatori dB - + Show accurate time Mostra tempo accurato - + Show volume Mostra volume @@ -39,62 +39,62 @@ Dimensione griglia - + Play Riproduci - + Pause Pausa - + Stop Ferma - + Reset volume Resetta volume - + Add page Aggiungi pagina - + Add pages Aggiungi pagine - + Remove current page Rimuovi pagina corrente - + Number of Pages: Numero di Pagine: - + Page {number} Pagina {number} - + Warning Attenzione - + Every cue in the page will be lost. Ogni cue nella pagina sarà persa. - + Are you sure to continue? Sei sicuro di voler continuare? @@ -112,7 +112,7 @@ LayoutDescription - + Organize cues in grid like pages Organizza le cue in pagine simili a tabelle @@ -120,27 +120,27 @@ LayoutDetails - + Click a cue to run it Clicca una cue per eseguirla - + SHIFT + Click to edit a cue SHIFT + Click per modificare una cue - + CTRL + Click to select a cue CTRL + Click per selezionare una cue - + To copy cues drag them while pressing CTRL Per copiare le cue trascinale mentre premi CTRL - + To move cues drag them while pressing SHIFT Per muovere le cue trascinale mentre premi SHIFT @@ -148,7 +148,7 @@ LayoutName - + Cart Layout Cart Layout @@ -156,22 +156,22 @@ ListLayout - + Edit cue Modifica Cue - + Edit selected cues Modifica cue selezionate - + Remove cue Rimuovi cue - + Remove selected cues Rimuovi cue selezionate diff --git a/lisp/i18n/ts/it_IT/controller.ts b/lisp/i18n/ts/it_IT/controller.ts index d9f98d912..f28e47ccb 100644 --- a/lisp/i18n/ts/it_IT/controller.ts +++ b/lisp/i18n/ts/it_IT/controller.ts @@ -12,20 +12,20 @@ ControllerKeySettings - - Key - Tasto - - - + Action Azione - + Shortcuts Scorciatoie + + + Shortcut + Shortcut + ControllerMidiSettings @@ -131,12 +131,12 @@ ControllerSettings - + Add Aggiungi - + Remove Rimuovi @@ -144,52 +144,52 @@ GlobalAction - + Go Go - + Reset Reset - + Stop all cues Stop all cues - + Pause all cues Pause all cues - + Resume all cues Resume all cues - + Interrupt all cues Interrupt all cues - + Fade-out all cues Fade-out all cues - + Fade-in all cues Fade-in all cues - + Move standby forward Move standby forward - + Move standby back Move standby back @@ -197,12 +197,12 @@ Osc Cue - + Type Tipo - + Argument Argomento @@ -233,7 +233,7 @@ Controlli MIDI - + Keyboard Shortcuts Scorciatoie Tastiera diff --git a/lisp/i18n/ts/it_IT/gst_backend.ts b/lisp/i18n/ts/it_IT/gst_backend.ts index cb0de3f4c..840f17cf9 100644 --- a/lisp/i18n/ts/it_IT/gst_backend.ts +++ b/lisp/i18n/ts/it_IT/gst_backend.ts @@ -114,12 +114,12 @@ GstBackend - + Audio cue (from file) Cue audio (da file) - + Select media files Seleziona file multimediali @@ -127,7 +127,7 @@ GstMediaError - + Cannot create pipeline element: "{}" Cannot create pipeline element: "{}" @@ -143,7 +143,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" Invalid pipeline element: "{}" @@ -255,7 +255,7 @@ Tonalità - + URI Input Ingresso URI @@ -320,42 +320,42 @@ UriInputSettings - + Source Sorgente - + Find File Seleziona file - + Buffering Pre-Caricamento - + Use Buffering Usare buffering - + Attempt download on network streams Prova a scaricare con flussi di rete - + Buffer size (-1 default value) Dimensione buffer (-1 valore predefinito) - + Choose file Seleziona file - + All files Tutti i file diff --git a/lisp/i18n/ts/it_IT/lisp.ts b/lisp/i18n/ts/it_IT/lisp.ts index 6952961c1..5999b4af0 100644 --- a/lisp/i18n/ts/it_IT/lisp.ts +++ b/lisp/i18n/ts/it_IT/lisp.ts @@ -1,6 +1,14 @@ + + APIServerInfo + + + Stop serving network API + Stop serving network API + + About @@ -58,16 +66,19 @@ - Actions + AlsaSinkSettings - - Undo: {} - Annulla: {} + + ALSA device + ALSA device + + + ApiServerError - - Redo: {} - Ripeti: {} + + Network API server stopped working. + Network API server stopped working. @@ -81,39 +92,292 @@ AppGeneralSettings - + Default layout Layout Predefinito - + Enable startup layout selector Attivare il selettore di layout all'avvio - + Application themes Temi dell'applicazione - + UI theme: UI theme: - + Icons theme: Icons theme: + + + Application language (require restart) + Application language (require restart) + + + + Language: + Language: + ApplicationError - + Startup error Startup error + + AudioDynamicSettings + + + Compressor + Compressor + + + + Expander + Expander + + + + Soft Knee + Soft Knee + + + + Hard Knee + Hard Knee + + + + Compressor/Expander + Compressor/Expander + + + + Type + Type + + + + Curve Shape + Curve Shape + + + + Ratio + Ratio + + + + Threshold (dB) + Threshold (dB) + + + + AudioPanSettings + + + Audio Pan + Audio Pan + + + + Center + Center + + + + Left + Left + + + + Right + Right + + + + CartLayout + + + Default behaviors + Default behaviors + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show volume + Show volume + + + + Grid size + Grid size + + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Number of Pages: + Number of Pages: + + + + Page {number} + Page {number} + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + CollectionCue + + + Add + Add + + + + Remove + Remove + + + + Cue + Cue + + + + Action + Action + + + + CommandCue + + + Command + Command + + + + Command to execute, as in a shell + Command to execute, as in a shell + + + + Discard command output + Discard command output + + + + Ignore command errors + Ignore command errors + + + + Kill instead of terminate + Kill instead of terminate + + + + CommandsStack + + + Undo: {} + Undo: {} + + + + Redo: {} + Redo: {} + + ConfigurationDebug @@ -130,6 +394,154 @@ New configuration installed at {} + + Controller + + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + + ControllerKeySettings + + + Action + Action + + + + Shortcuts + Shortcuts + + + + Shortcut + Shortcut + + + + ControllerMidiSettings + + + MIDI + MIDI + + + + Type + Type + + + + Channel + Channel + + + + Note + Note + + + + Action + Action + + + + Filter "note on" + Filter "note on" + + + + Filter "note off" + Filter "note off" + + + + Capture + Capture + + + + Listening MIDI messages ... + Listening MIDI messages ... + + + + ControllerOscSettings + + + OSC Message + OSC Message + + + + OSC + OSC + + + + Path + Path + + + + Types + Types + + + + Arguments + Arguments + + + + Actions + Actions + + + + OSC Capture + OSC Capture + + + + Add + Add + + + + Remove + Remove + + + + Capture + Capture + + + + ControllerSettings + + + Add + Add + + + + Remove + Remove + + + + Cue Name + + + OSC Settings + OSC Settings + + CueAction @@ -188,19 +600,6 @@ Non fare nulla - - CueActionLog - - - Cue settings changed: "{}" - Impostazioni cue cambiate "{}" - - - - Cues settings changed. - Impostazioni di più cue cambiate. - - CueAppearanceSettings @@ -244,6 +643,37 @@ Seleziona colore del carattere + + CueCategory + + + Action cues + Action cues + + + + Integration cues + Integration cues + + + + Misc cues + Misc cues + + + + CueCommandLog + + + Cue settings changed: "{}" + Cue settings changed: "{}" + + + + Cues settings changed. + Cues settings changed. + + CueName @@ -251,31 +681,71 @@ Media Cue Cue Multimediale + + + Index Action + Index Action + + + + Seek Cue + Seek Cue + + + + Volume Control + Volume Control + + + + Command Cue + Command Cue + + + + Collection Cue + Collection Cue + + + + Stop-All + Stop-All + + + + MIDI Cue + MIDI Cue + + + + OSC Cue + OSC Cue + CueNextAction - + Do Nothing Non fare nulla - + Trigger after the end Trigger after the end - + Trigger after post wait Trigger after post wait - + Select after the end Select after the end - + Select after post wait Select after post wait @@ -338,6 +808,60 @@ Fade actions + + CueTriggers + + + Started + Started + + + + Paused + Paused + + + + Stopped + Stopped + + + + Ended + Ended + + + + DbMeterSettings + + + DbMeter settings + DbMeter settings + + + + Time between levels (ms) + Time between levels (ms) + + + + Peak ttl (ms) + Peak ttl (ms) + + + + Peak falloff (dB/sec) + Peak falloff (dB/sec) + + + + Equalizer10Settings + + + 10 Bands Equalizer (IIR) + 10 Bands Equalizer (IIR) + + Fade @@ -383,19 +907,263 @@ - LayoutSelect + GlobalAction - - Layout selection - Selezione del layout + + Go + Go - - Select layout - Seleziona layout + + Reset + Reset - + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + + + GstMediaSettings + + + Change Pipeline + Change Pipeline + + + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + + + GstPipelineEdit + + + Edit Pipeline + Edit Pipeline + + + + GstSettings + + + Pipeline + Pipeline + + + + HotKeyEdit + + + Press shortcut + Press shortcut + + + + IndexActionCue + + + No suggestion + No suggestion + + + + Index + Index + + + + Use a relative index + Use a relative index + + + + Target index + Target index + + + + Action + Action + + + + Suggested cue name + Suggested cue name + + + + JackSinkSettings + + + Connections + Connections + + + + Edit connections + Edit connections + + + + Output ports + Output ports + + + + Input ports + Input ports + + + + Connect + Connect + + + + Disconnect + Disconnect + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them + To move cues drag them + + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + To move cues drag them while pressing SHIFT + To move cues drag them while pressing SHIFT + + + + LayoutName + + + List Layout + List Layout + + + + Cart Layout + Cart Layout + + + + LayoutSelect + + + Layout selection + Selezione del layout + + + + Select layout + Seleziona layout + + + Open file Apri file @@ -403,29 +1171,208 @@ ListLayout - - Stop All - Ferma Tutte + + Default behaviors + Default behaviors + + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show seek-bars + Show seek-bars + + + + Auto-select next cue + Auto-select next cue + + + + Enable selection mode + Enable selection mode + + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Resume Cue + Resume Cue + + + + Interrupt Cue + Interrupt Cue + + + + Edit cue + Edit cue + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove cue + Remove cue + + + + Remove selected + Remove selected + + + + Selection mode + Selection mode + + + + Pause all + Pause all + + + + Stop all + Stop all - Pause All - Pausa Tutte + Interrupt all + Interrupt all + + + + Resume all + Resume all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all - - Interrupt All - Interrompi Tutte + + Edit selected cues + Edit selected cues + + + + Remove selected cues + Remove selected cues - Use fade (global actions) - Use fade (global actions) + Use fade + Use fade + + + + Copy of {} + Copy of {} + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + LogStatusIcon - - Resume All - Resume All + + Errors/Warnings + Errors/Warnings @@ -456,7 +1403,7 @@ Show details - + Linux Show Player - Log Viewer Linux Show Player - Log Viewer @@ -547,146 +1494,299 @@ - MainWindow + MIDICue - - &File - &File + + MIDI Message + MIDI Message - - New session - Nuova sessione + + Message type + Message type + + + MIDIMessageAttr - - Open - Apri + + Channel + Channel - - Save session - Salva sessione + + Note + Note - - Preferences - Preferenze + + Velocity + Velocity - - Save as - Salva come + + Control + Control - - Full Screen - Schermo Intero + + Program + Program - - Exit - Esci + + Value + Value - - &Edit - &Modifica + + Song + Song - - Undo - Annulla + + Pitch + Pitch - - Redo - Ripeti + + Position + Position + + + MIDIMessageType - - Select all - Seleziona tutti + + Note ON + Note ON - - Select all media cues - Seleziona tutte le media-cue + + Note OFF + Note OFF - - Deselect all - Deseleziona tutti + + Polyphonic After-touch + Polyphonic After-touch - - CTRL+SHIFT+A - CTRL+SHIFT+A + + Control/Mode Change + Control/Mode Change - - Invert selection - Inverti selezione + + Program Change + Program Change - - CTRL+I - CTRL+I + + Channel After-touch + Channel After-touch - - Edit selected - Modifica selezionati + + Pitch Bend Change + Pitch Bend Change - - CTRL+SHIFT+E - CTRL+SHIFT+E + + Song Select + Song Select - - &Layout - &Layout + + Song Position + Song Position - - &Tools - &Strumenti + + Start + Start - - Edit selection - Modifica selezionati + + Stop + Stop - + + Continue + Continue + + + + MIDISettings + + + MIDI default devices + MIDI default devices + + + + Input + Input + + + + Output + Output + + + + MainWindow + + + &File + &File + + + + New session + Nuova sessione + + + + Open + Apri + + + + Save session + Salva sessione + + + + Preferences + Preferenze + + + + Save as + Salva come + + + + Full Screen + Schermo Intero + + + + Exit + Esci + + + + &Edit + &Modifica + + + + Undo + Annulla + + + + Redo + Ripeti + + + + Select all + Seleziona tutti + + + + Select all media cues + Seleziona tutte le media-cue + + + + Deselect all + Deseleziona tutti + + + + CTRL+SHIFT+A + CTRL+SHIFT+A + + + + Invert selection + Inverti selezione + + + + CTRL+I + CTRL+I + + + + Edit selected + Modifica selezionati + + + + CTRL+SHIFT+E + CTRL+SHIFT+E + + + + &Layout + &Layout + + + + &Tools + &Strumenti + + + + Edit selection + Modifica selezionati + + + &About &About - + About Informazioni - + About Qt Informazioni su Qt - + Close session Chiudi sessione - - The current session is not saved. - La sessione corrente non è salvata. + + Do you want to save them now? + Do you want to save them now? + + + + MainWindowDebug + + + Registered cue menu: "{}" + Registered cue menu: "{}" + + + MainWindowError - - Discard the changes? - Scartare la modifiche? + + Cannot create cue {} + Cannot create cue {} @@ -717,6 +1817,299 @@ Loop + + MediaElementName + + + Pitch + Pitch + + + + Volume + Volume + + + + Audio Pan + Audio Pan + + + + Compressor/Expander + Compressor/Expander + + + + JACK Out + JACK Out + + + + URI Input + URI Input + + + + 10 Bands Equalizer + 10 Bands Equalizer + + + + ALSA Out + ALSA Out + + + + dB Meter + dB Meter + + + + Preset Input + Preset Input + + + + PulseAudio Out + PulseAudio Out + + + + Custom Element + Custom Element + + + + Speed + Speed + + + + System Out + System Out + + + + System Input + System Input + + + + MediaInfo + + + Media Info + Media Info + + + + Warning + Warning + + + + No info to display + No info to display + + + + Info + Info + + + + Value + Value + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + OscCue + + + Add + Add + + + + Remove + Remove + + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + PitchSettings + + + Pitch + Pitch + + + + {0:+} semitones + {0:+} semitones + + PluginsError @@ -757,22 +2150,347 @@ - QColorButton + Preset - - Right click to reset - Clicca con il pulsante destro per resettare + + Create Cue + Create Cue + + + + Load on selected Cues + Load on selected Cues - SettingsPageName + PresetSrcSettings - - Appearance - Aspetto + + Presets + Presets - - + + + Presets + + + Cannot scan presets + Cannot scan presets + + + + Error while deleting preset "{}" + Error while deleting preset "{}" + + + + Cannot load preset "{}" + Cannot load preset "{}" + + + + Cannot save preset "{}" + Cannot save preset "{}" + + + + Cannot rename preset "{}" + Cannot rename preset "{}" + + + + Select Preset + Select Preset + + + + Presets + Presets + + + + Preset name + Preset name + + + + Add + Add + + + + Rename + Rename + + + + Edit + Edit + + + + Remove + Remove + + + + Export selected + Export selected + + + + Import + Import + + + + Warning + Warning + + + + The same name is already used! + The same name is already used! + + + + Cannot export correctly. + Cannot export correctly. + + + + Cannot import correctly. + Cannot import correctly. + + + + Cue type + Cue type + + + + Load on cue + Load on cue + + + + Load on selected cues + Load on selected cues + + + + Save as preset + Save as preset + + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + + + + QColorButton + + + Right click to reset + Clicca con il pulsante destro per resettare + + + + RenameCues + + + Rename Cues + Rename Cues + + + + Rename cues + Rename cues + + + + Current + Current + + + + Preview + Preview + + + + Capitalize + Capitalize + + + + Lowercase + Lowercase + + + + Uppercase + Uppercase + + + + Remove Numbers + Remove Numbers + + + + Add numbering + Add numbering + + + + Reset + Reset + + + + Type your regex here: + Type your regex here: + + + + Regex help + Regex help + + + + RenameCuesCommand + + + Renamed {number} cues + Renamed {number} cues + + + + RenameUiDebug + + + Regex error: Invalid pattern + Regex error: Invalid pattern + + + + ReplayGain + + + ReplayGain / Normalization + ReplayGain / Normalization + + + + Threads number + Threads number + + + + Apply only to selected media + Apply only to selected media + + + + ReplayGain to (dB SPL) + ReplayGain to (dB SPL) + + + + Normalize to (dB) + Normalize to (dB) + + + + Processing files ... + Processing files ... + + + + Calculate + Calculate + + + + Reset all + Reset all + + + + Reset selected + Reset selected + + + + ReplayGainDebug + + + Applied gain for: {} + Applied gain for: {} + + + + Discarded gain for: {} + Discarded gain for: {} + + + + ReplayGainInfo + + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} + + + + SeekCue + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Seek + Seek + + + + Time to reach + Time to reach + + + + SettingsPageName + + + Appearance + Aspetto + + + General Generale @@ -811,5 +2529,336 @@ Layouts Layouts + + + Action Settings + Action Settings + + + + Seek Settings + Seek Settings + + + + Volume Settings + Volume Settings + + + + Command + Command + + + + Edit Collection + Edit Collection + + + + Stop Settings + Stop Settings + + + + List Layout + List Layout + + + + Timecode + Timecode + + + + Timecode Settings + Timecode Settings + + + + Cue Control + Cue Control + + + + Layout Controls + Layout Controls + + + + MIDI Controls + MIDI Controls + + + + Keyboard Shortcuts + Keyboard Shortcuts + + + + OSC Controls + OSC Controls + + + + MIDI Settings + MIDI Settings + + + + MIDI settings + MIDI settings + + + + GStreamer + GStreamer + + + + Media Settings + Media Settings + + + + Triggers + Triggers + + + + OSC settings + OSC settings + + + + Cart Layout + Cart Layout + + + + SpeedSettings + + + Speed + Speed + + + + StopAll + + + Stop Action + Stop Action + + + + Synchronizer + + + Synchronization + Synchronization + + + + Manage connected peers + Manage connected peers + + + + Show your IP + Show your IP + + + + Your IP is: + Your IP is: + + + + Timecode + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + TimecodeError + + + Cannot send timecode. + Cannot send timecode. + + + + TimecodeSettings + + + Replace HOURS by a static track number + Replace HOURS by a static track number + + + + Enable Timecode + Enable Timecode + + + + Track number + Track number + + + + Timecode Settings + Timecode Settings + + + + Timecode Format: + Timecode Format: + + + + Timecode Protocol: + Timecode Protocol: + + + + TriggersSettings + + + Add + Add + + + + Remove + Remove + + + + Trigger + Trigger + + + + Cue + Cue + + + + Action + Action + + + + UriInputSettings + + + Source + Source + + + + Find File + Find File + + + + Buffering + Buffering + + + + Use Buffering + Use Buffering + + + + Attempt download on network streams + Attempt download on network streams + + + + Buffer size (-1 default value) + Buffer size (-1 default value) + + + + Choose file + Choose file + + + + All files + All files + + + + UserElementSettings + + + User defined elements + User defined elements + + + + Only for advanced user! + Only for advanced user! + + + + VolumeControl + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Volume to reach + Volume to reach + + + + Fade + Fade + + + + VolumeControlError + + + Error during cue execution. + Error during cue execution. + + + + VolumeSettings + + + Volume + Volume + + + + Normalized volume + Normalized volume + + + + Reset + Reset + diff --git a/lisp/i18n/ts/it_IT/list_layout.ts b/lisp/i18n/ts/it_IT/list_layout.ts index a3d5f607a..f18245946 100644 --- a/lisp/i18n/ts/it_IT/list_layout.ts +++ b/lisp/i18n/ts/it_IT/list_layout.ts @@ -4,7 +4,7 @@ LayoutDescription - + Organize the cues in a list Organize the cues in a list @@ -12,17 +12,17 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue SHIFT + Space or Double-Click to edit a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them To move cues drag them @@ -30,7 +30,7 @@ LayoutName - + List Layout List Layout @@ -43,27 +43,27 @@ Default behaviors - + Show playing cues Show playing cues - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue @@ -73,42 +73,42 @@ Enable selection mode - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - + Remove cue Remove cue - + Selection mode Selection mode @@ -143,60 +143,65 @@ Fade-In all - - GO key: - GO key: - - - - GO action: - GO action: - - - - GO delay (ms): - GO delay (ms): - - - + Edit selected Edit selected - + Clone cue Clone cue - + Clone selected Clone selected - + Remove selected Remove selected + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Copy of {} + Copy of {} + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait diff --git a/lisp/i18n/ts/it_IT/media_info.ts b/lisp/i18n/ts/it_IT/media_info.ts index 404a699ac..2be0447c3 100644 --- a/lisp/i18n/ts/it_IT/media_info.ts +++ b/lisp/i18n/ts/it_IT/media_info.ts @@ -4,27 +4,27 @@ MediaInfo - + Media Info Informazioni Media - + No info to display Nessuna informazione da mostrare - + Info Informazione - + Value Valore - + Warning Warning diff --git a/lisp/i18n/ts/it_IT/midi.ts b/lisp/i18n/ts/it_IT/midi.ts index 230c4f9dc..8cc733e3e 100644 --- a/lisp/i18n/ts/it_IT/midi.ts +++ b/lisp/i18n/ts/it_IT/midi.ts @@ -1,6 +1,22 @@ + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + MIDI Cue + MIDI Cue + + MIDICue @@ -150,5 +166,10 @@ MIDI settings Impostazioni MIDI + + + MIDI Settings + MIDI Settings + diff --git a/lisp/i18n/ts/it_IT/osc.ts b/lisp/i18n/ts/it_IT/osc.ts index bbe2f58d9..33aa40494 100644 --- a/lisp/i18n/ts/it_IT/osc.ts +++ b/lisp/i18n/ts/it_IT/osc.ts @@ -1,6 +1,88 @@ + + Cue Name + + + OSC Settings + OSC Settings + + + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + OSC Cue + OSC Cue + + + + OscCue + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + Add + Add + + + + Remove + Remove + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + OscServerDebug diff --git a/lisp/i18n/ts/it_IT/presets.ts b/lisp/i18n/ts/it_IT/presets.ts index 22dbb2103..cc555e0ad 100644 --- a/lisp/i18n/ts/it_IT/presets.ts +++ b/lisp/i18n/ts/it_IT/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Crea Cue - + Load on selected Cues Carica sulle cue selezionate @@ -17,114 +17,119 @@ Presets - + Presets Preset - + Save as preset Salva come preset - + Cannot scan presets Impossibile cercare i preset - + Error while deleting preset "{}" Errore durate l'eliminazione del preset "{}"; - + Cannot load preset "{}" Impossible caricare il preset "{}" - + Cannot save preset "{}" Impossibile salvare il preset "{}" - + Cannot rename preset "{}" Impossible rinominare il preset "{}" - + Select Preset Seleziona Preset - + Preset name Nome preset - + Add Aggiungi - + Rename Rinomina - + Edit Modifica - + Remove Rimuovi - + Export selected Esporta selezionati - + Import Importa - + Warning Avviso - + The same name is already used! Lo stesso nome è già in uso! - + Cannot export correctly. Impossibile esportare correttamente. - + Cannot import correctly. Impossible importare correttamente. - + Cue type Tipo di cue - + Load on cue Load on cue - + Load on selected cues Load on selected cues + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + diff --git a/lisp/i18n/ts/it_IT/rename_cues.ts b/lisp/i18n/ts/it_IT/rename_cues.ts index c29e7143c..fe18aadbb 100644 --- a/lisp/i18n/ts/it_IT/rename_cues.ts +++ b/lisp/i18n/ts/it_IT/rename_cues.ts @@ -4,7 +4,7 @@ RenameCues - + Rename Cues Rename Cues @@ -64,6 +64,14 @@ Regex help + + RenameCuesCommand + + + Renamed {number} cues + Renamed {number} cues + + RenameUiDebug diff --git a/lisp/i18n/ts/it_IT/replay_gain.ts b/lisp/i18n/ts/it_IT/replay_gain.ts index 4aed1e3e5..e05921cba 100644 --- a/lisp/i18n/ts/it_IT/replay_gain.ts +++ b/lisp/i18n/ts/it_IT/replay_gain.ts @@ -4,22 +4,22 @@ ReplayGain - + ReplayGain / Normalization ReplayGain / Normalizzazione - + Calculate Calcola - + Reset all Resetta tutto - + Reset selected Resetta selezionati diff --git a/lisp/i18n/ts/it_IT/synchronizer.ts b/lisp/i18n/ts/it_IT/synchronizer.ts index e894d98f0..dbf3a9529 100644 --- a/lisp/i18n/ts/it_IT/synchronizer.ts +++ b/lisp/i18n/ts/it_IT/synchronizer.ts @@ -4,22 +4,22 @@ Synchronizer - + Synchronization Sincronizzazione - + Manage connected peers Gestisci peer connessi - + Show your IP Visualizza IP - + Your IP is: Il tuo IP è: diff --git a/lisp/i18n/ts/it_IT/triggers.ts b/lisp/i18n/ts/it_IT/triggers.ts index 4a3bd9a8f..4a6308847 100644 --- a/lisp/i18n/ts/it_IT/triggers.ts +++ b/lisp/i18n/ts/it_IT/triggers.ts @@ -35,27 +35,27 @@ TriggersSettings - + Add Aggiungi - + Remove Rimuovi - + Trigger Evento - + Cue Cue - + Action Azione diff --git a/lisp/i18n/ts/nl_BE/action_cues.ts b/lisp/i18n/ts/nl_BE/action_cues.ts index 14e3e1c06..05acd55ee 100644 --- a/lisp/i18n/ts/nl_BE/action_cues.ts +++ b/lisp/i18n/ts/nl_BE/action_cues.ts @@ -1,79 +1,63 @@ - - ActionCuesDebug - - - Registered cue: "{}" - Registered cue: "{}" - - - - ActionsCuesError - - - Cannot create cue {} - Cannot create cue {} - - CollectionCue - + Add - Add + Toevoegen - + Remove - Remove + Verwijderen - + Cue Cue - + Action - Action + Actie CommandCue - + Command - Command + Opdracht - + Command to execute, as in a shell - Command to execute, as in a shell + Opdracht om uit te voeren, zoals in terminalvenster - + Discard command output - Discard command output + Verwerp opdracht resultaat - + Ignore command errors - Ignore command errors + Negeer opdracht fouten - + Kill instead of terminate - Kill instead of terminate + Beëindig in plaats van af te sluiten - Cue Name + CueCategory - - OSC Settings - OSC Settings + + Action cues + Action cues @@ -81,259 +65,160 @@ Command Cue - Command Cue - - - - MIDI Cue - MIDI Cue + Opdracht cue Volume Control - Volume Control + volumeregeling Seek Cue - Seek Cue + Zoek cue Collection Cue - Collection Cue + Groep cue Stop-All - Stop-All + Stop Alles Index Action - Index Action - - - - OSC Cue - OSC Cue + Index actie IndexActionCue - + Index Index - + Use a relative index - Use a relative index + Gebruik relatieve index - + Target index - Target index + Doelindex - + Action - Action + Actie - + No suggestion No suggestion - + Suggested cue name Suggested cue name - - Osc Cue - - - Type - Type - - - - Argument - Argument - - - - FadeTo - FadeTo - - - - Fade - Fade - - - - OscCue - - - OSC Message - OSC Message - - - - Add - Add - - - - Remove - Remove - - - - Test - Test - - - - OSC Path: (example: "/path/to/something") - OSC Path: (example: "/path/to/something") - - - - Fade - Fade - - - - Time (sec) - Time (sec) - - - - Curve - Curve - - - - OscCueError - - - Could not parse argument list, nothing sent - Could not parse argument list, nothing sent - - - - Error while parsing arguments, nothing sent - Error while parsing arguments, nothing sent - - - - Error during cue execution. - Error during cue execution. - - SeekCue - + Cue Cue - + Click to select - Click to select + Klik om te selecteren - + Not selected - Not selected + Niet geselecteerd - + Seek - Seek + Zoek - + Time to reach - Time to reach + Gewenste tijd SettingsPageName - + Command - Command + Opdracht - - MIDI Settings - MIDI Settings - - - + Volume Settings - Volume Settings + Volume instellingen - + Seek Settings - Seek Settings + Zoek instellingen - + Edit Collection - Edit Collection + Bewerk groep - + Action Settings - Action Settings + Actie instellingen - + Stop Settings - Stop Settings + Stop instellingen StopAll - + Stop Action - Stop Action + Stop actie VolumeControl - + Cue Cue - + Click to select - Click to select + Klik om te selecteren - + Not selected - Not selected + Niet geselecteerd - + Volume to reach - Volume to reach + Gewenste volume - + Fade Fade @@ -341,7 +226,7 @@ VolumeControlError - + Error during cue execution. Error during cue execution. diff --git a/lisp/i18n/ts/nl_BE/cart_layout.ts b/lisp/i18n/ts/nl_BE/cart_layout.ts index cc0ecb7ec..560f1f4d7 100644 --- a/lisp/i18n/ts/nl_BE/cart_layout.ts +++ b/lisp/i18n/ts/nl_BE/cart_layout.ts @@ -9,27 +9,27 @@ Default behaviors - + Countdown mode Countdown mode - + Show seek-bars Show seek-bars - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show volume Show volume @@ -39,62 +39,62 @@ Grid size - + Play Play - + Pause Pause - + Stop Stop - + Reset volume Reset volume - + Add page Add page - + Add pages Add pages - + Remove current page Remove current page - + Number of Pages: Number of Pages: - + Page {number} Page {number} - + Warning Warning - + Every cue in the page will be lost. Every cue in the page will be lost. - + Are you sure to continue? Are you sure to continue? @@ -112,7 +112,7 @@ LayoutDescription - + Organize cues in grid like pages Organize cues in grid like pages @@ -120,27 +120,27 @@ LayoutDetails - + Click a cue to run it Click a cue to run it - + SHIFT + Click to edit a cue SHIFT + Click to edit a cue - + CTRL + Click to select a cue CTRL + Click to select a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them while pressing SHIFT To move cues drag them while pressing SHIFT @@ -148,7 +148,7 @@ LayoutName - + Cart Layout Cart Layout @@ -156,22 +156,22 @@ ListLayout - + Edit cue Edit cue - + Edit selected cues Edit selected cues - + Remove cue Remove cue - + Remove selected cues Remove selected cues diff --git a/lisp/i18n/ts/nl_BE/controller.ts b/lisp/i18n/ts/nl_BE/controller.ts index 9fe9989e0..7843c1ac5 100644 --- a/lisp/i18n/ts/nl_BE/controller.ts +++ b/lisp/i18n/ts/nl_BE/controller.ts @@ -12,19 +12,19 @@ ControllerKeySettings - - Key - Key - - - + Action - Action + Actie - + Shortcuts - Shortcuts + Snelkoppeling + + + + Shortcut + Shortcut @@ -42,17 +42,17 @@ Channel - Channel + Kanaal Note - Note + Notitie Action - Action + Actie @@ -67,12 +67,12 @@ Capture - Capture + Neem over Listening MIDI messages ... - Listening MIDI messages ... + Wacht op MIDI boodschap ... @@ -131,65 +131,65 @@ ControllerSettings - + Add - Add + Toevoegen - + Remove - Remove + Verwijder GlobalAction - + Go Go - + Reset Reset - + Stop all cues Stop all cues - + Pause all cues Pause all cues - + Resume all cues Resume all cues - + Interrupt all cues Interrupt all cues - + Fade-out all cues Fade-out all cues - + Fade-in all cues Fade-in all cues - + Move standby forward Move standby forward - + Move standby back Move standby back @@ -197,12 +197,12 @@ Osc Cue - + Type Type - + Argument Argument @@ -225,17 +225,17 @@ Cue Control - Cue Control + Cue bediening MIDI Controls - MIDI Controls + MIDI bediening - + Keyboard Shortcuts - Keyboard Shortcuts + Toetsenbord snelkoppeling diff --git a/lisp/i18n/ts/nl_BE/gst_backend.ts b/lisp/i18n/ts/nl_BE/gst_backend.ts index fc313b84e..8c08a8c5f 100644 --- a/lisp/i18n/ts/nl_BE/gst_backend.ts +++ b/lisp/i18n/ts/nl_BE/gst_backend.ts @@ -6,7 +6,7 @@ ALSA device - ALSA device + ALSA apparaat @@ -14,7 +14,7 @@ Compressor - Compressor + Compressor @@ -34,7 +34,7 @@ Compressor/Expander - Compressor/Expander + Compressor/Expander @@ -44,7 +44,7 @@ Curve Shape - Curve Shape + Curve Vorm @@ -54,7 +54,7 @@ Threshold (dB) - Threshold (dB) + Threshold (dB) @@ -67,17 +67,17 @@ Center - Center + Centraal Left - Left + Links Right - Right + Rechts @@ -85,22 +85,22 @@ DbMeter settings - DbMeter settings + dB-meter instelling Time between levels (ms) - Time between levels (ms) + tijd tussen de niveaus (ms) Peak ttl (ms) - Peak ttl (ms) + Piek ttl (ms) Peak falloff (dB/sec) - Peak falloff (dB/sec) + Piekdaling (dB/sec) @@ -114,12 +114,12 @@ GstBackend - + Audio cue (from file) Audio cue (from file) - + Select media files Select media files @@ -127,7 +127,7 @@ GstMediaError - + Cannot create pipeline element: "{}" Cannot create pipeline element: "{}" @@ -137,13 +137,13 @@ Change Pipeline - Change Pipeline + Wijzig regel lijn GstMediaWarning - + Invalid pipeline element: "{}" Invalid pipeline element: "{}" @@ -153,7 +153,7 @@ Edit Pipeline - Edit Pipeline + Bewerk regel lijn @@ -161,7 +161,7 @@ Pipeline - Pipeline + Regel lijn @@ -169,32 +169,32 @@ Connections - Connections + Aansluitingen Edit connections - Edit connections + Bewerk aansluitingen Output ports - Output ports + Uitgangen Input ports - Input ports + Ingangen Connect - Connect + Aansluiten Disconnect - Disconnect + Loskoppelen @@ -202,7 +202,7 @@ Compressor/Expander - Compressor/Expander + Compressor/Expander @@ -212,7 +212,7 @@ PulseAudio Out - PulseAudio Out + PulseAudio uit @@ -227,52 +227,52 @@ System Input - System Input + Systeem Ingang ALSA Out - ALSA Out + ALSA uitgang JACK Out - JACK Out + JACK uitgang Custom Element - Custom Element + Vrij Element System Out - System Out + Systeem uitgang Pitch - Pitch + Toonhoogte - + URI Input - URI Input + URI Ingang 10 Bands Equalizer - 10 Bands Equalizer + 10 Bands Equalizer Speed - Speed + Snelheid Preset Input - Preset Input + Preset Ingang @@ -280,12 +280,12 @@ Pitch - Pitch + Toonhoogte {0:+} semitones - {0:+} semitones + {0:+} halve tonen @@ -293,7 +293,7 @@ Presets - Presets + Presets @@ -301,7 +301,7 @@ Media Settings - Media Settings + Media Instellingen @@ -314,50 +314,50 @@ Speed - Speed + Snelheid UriInputSettings - + Source - Source + Bron - + Find File - Find File + Zoek bestand - + Buffering Buffering - + Use Buffering - Use Buffering + Gebruik Buffering - + Attempt download on network streams - Attempt download on network streams + Poging tot download op netwerk streams - + Buffer size (-1 default value) - Buffer size (-1 default value) + Buffer grootte (-1 standaard waarde) - + Choose file - Choose file + Kies bestand - + All files - All files + Alle Bestanden @@ -365,12 +365,12 @@ User defined elements - User defined elements + Door de gebruiker gedefinieerde elementen Only for advanced user! - Only for advanced user! + Alleen voor de gevorderde gebruiker! @@ -383,7 +383,7 @@ Normalized volume - Normalized volume + Normaliseer volume diff --git a/lisp/i18n/ts/nl_BE/lisp.ts b/lisp/i18n/ts/nl_BE/lisp.ts index f9a223380..ba4ffd719 100644 --- a/lisp/i18n/ts/nl_BE/lisp.ts +++ b/lisp/i18n/ts/nl_BE/lisp.ts @@ -1,27 +1,35 @@ + + APIServerInfo + + + Stop serving network API + Stop serving network API + + About Authors - Authors + Auteurs Contributors - Contributors + Medewerkers Translators - Translators + Vertalers About Linux Show Player - About Linux Show Player + Over Linux Show Player @@ -34,7 +42,7 @@ Source code - Source code + Bron code @@ -44,12 +52,12 @@ License - License + Licentie Contributors - Contributors + medewerker @@ -58,16 +66,19 @@ - Actions + AlsaSinkSettings - - Undo: {} - Undo: {} + + ALSA device + ALSA device + + + ApiServerError - - Redo: {} - Redo: {} + + Network API server stopped working. + Network API server stopped working. @@ -81,39 +92,292 @@ AppGeneralSettings - + Default layout Default layout - + Enable startup layout selector Enable startup layout selector - + Application themes Application themes - + UI theme: UI theme: - + Icons theme: Icons theme: + + + Application language (require restart) + Application language (require restart) + + + + Language: + Language: + ApplicationError - + Startup error Startup error + + AudioDynamicSettings + + + Compressor + Compressor + + + + Expander + Expander + + + + Soft Knee + Soft Knee + + + + Hard Knee + Hard Knee + + + + Compressor/Expander + Compressor/Expander + + + + Type + Type + + + + Curve Shape + Curve Shape + + + + Ratio + Ratio + + + + Threshold (dB) + Threshold (dB) + + + + AudioPanSettings + + + Audio Pan + Audio Pan + + + + Center + Center + + + + Left + Left + + + + Right + Right + + + + CartLayout + + + Default behaviors + Default behaviors + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show volume + Show volume + + + + Grid size + Grid size + + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Number of Pages: + Number of Pages: + + + + Page {number} + Page {number} + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + CollectionCue + + + Add + Add + + + + Remove + Remove + + + + Cue + Cue + + + + Action + Action + + + + CommandCue + + + Command + Command + + + + Command to execute, as in a shell + Command to execute, as in a shell + + + + Discard command output + Discard command output + + + + Ignore command errors + Ignore command errors + + + + Kill instead of terminate + Kill instead of terminate + + + + CommandsStack + + + Undo: {} + Undo: {} + + + + Redo: {} + Redo: {} + + ConfigurationDebug @@ -130,17 +394,165 @@ New configuration installed at {} + + Controller + + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + + ControllerKeySettings + + + Action + Action + + + + Shortcuts + Shortcuts + + + + Shortcut + Shortcut + + + + ControllerMidiSettings + + + MIDI + MIDI + + + + Type + Type + + + + Channel + Channel + + + + Note + Note + + + + Action + Action + + + + Filter "note on" + Filter "note on" + + + + Filter "note off" + Filter "note off" + + + + Capture + Capture + + + + Listening MIDI messages ... + Listening MIDI messages ... + + + + ControllerOscSettings + + + OSC Message + OSC Message + + + + OSC + OSC + + + + Path + Path + + + + Types + Types + + + + Arguments + Arguments + + + + Actions + Actions + + + + OSC Capture + OSC Capture + + + + Add + Add + + + + Remove + Remove + + + + Capture + Capture + + + + ControllerSettings + + + Add + Add + + + + Remove + Remove + + + + Cue Name + + + OSC Settings + OSC Settings + + CueAction Default - Default + Standaard Pause - Pause + Pauze @@ -188,60 +600,78 @@ Do Nothing - - CueActionLog - - - Cue settings changed: "{}" - Cue settings changed: "{}" - - - - Cues settings changed. - Cues settings changed. - - CueAppearanceSettings The appearance depends on the layout - The appearance depends on the layout + De weergave hangt af van de gekozen lay-out Cue name - Cue name + Cue naam NoName - NoName + Geen naam Description/Note - Description/Note + Omschrijving/Notities Set Font Size - Set Font Size + Stel tekengrootte in Color - Color + Kleur Select background color - Select background color + Stel achtergrondkleur in Select font color - Select font color + Stel tekstkleur in + + + + CueCategory + + + Action cues + Action cues + + + + Integration cues + Integration cues + + + + Misc cues + Misc cues + + + + CueCommandLog + + + Cue settings changed: "{}" + Cue settings changed: "{}" + + + + Cues settings changed. + Cues settings changed. @@ -251,31 +681,71 @@ Media Cue Media Cue + + + Index Action + Index Action + + + + Seek Cue + Seek Cue + + + + Volume Control + Volume Control + + + + Command Cue + Command Cue + + + + Collection Cue + Collection Cue + + + + Stop-All + Stop-All + + + + MIDI Cue + MIDI Cue + + + + OSC Cue + OSC Cue + CueNextAction - + Do Nothing Do Nothing - + Trigger after the end Trigger after the end - + Trigger after post wait Trigger after post wait - + Select after the end Select after the end - + Select after post wait Select after post wait @@ -285,47 +755,47 @@ Pre wait - Pre wait + Wachttijd vooraf Wait before cue execution - Wait before cue execution + Wachttijd voor starten cue Post wait - Post wait + Wachttijd achteraf Wait after cue execution - Wait after cue execution + Wachttijd na starten cue Next action - Next action + Volgende actie Start action - Start action + Start actie Default action to start the cue - Default action to start the cue + Standaard Actie om de cue te starten Stop action - Stop action + Stop actie Default action to stop the cue - Default action to stop the cue + Standaard Actie om de cue te stoppen @@ -339,32 +809,86 @@ - Fade + CueTriggers - - Linear - Linear + + Started + Started - - Quadratic - Quadratic + + Paused + Paused - - Quadratic2 - Quadratic2 + + Stopped + Stopped + + + + Ended + Ended - FadeEdit + DbMeterSettings - - Duration (sec): - Duration (sec): + + DbMeter settings + DbMeter settings - + + Time between levels (ms) + Time between levels (ms) + + + + Peak ttl (ms) + Peak ttl (ms) + + + + Peak falloff (dB/sec) + Peak falloff (dB/sec) + + + + Equalizer10Settings + + + 10 Bands Equalizer (IIR) + 10 Bands Equalizer (IIR) + + + + Fade + + + Linear + Lineair + + + + Quadratic + Exponentieel + + + + Quadratic2 + Exponentieel2 + + + + FadeEdit + + + Duration (sec): + Duration (sec): + + + Curve: Curve: @@ -382,50 +906,473 @@ Fade Out + + GlobalAction + + + Go + Go + + + + Reset + Reset + + + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + + + GstMediaSettings + + + Change Pipeline + Change Pipeline + + + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + + + GstPipelineEdit + + + Edit Pipeline + Edit Pipeline + + + + GstSettings + + + Pipeline + Pipeline + + + + HotKeyEdit + + + Press shortcut + Press shortcut + + + + IndexActionCue + + + No suggestion + No suggestion + + + + Index + Index + + + + Use a relative index + Use a relative index + + + + Target index + Target index + + + + Action + Action + + + + Suggested cue name + Suggested cue name + + + + JackSinkSettings + + + Connections + Connections + + + + Edit connections + Edit connections + + + + Output ports + Output ports + + + + Input ports + Input ports + + + + Connect + Connect + + + + Disconnect + Disconnect + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them + To move cues drag them + + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + To move cues drag them while pressing SHIFT + To move cues drag them while pressing SHIFT + + + + LayoutName + + + List Layout + List Layout + + + + Cart Layout + Cart Layout + + LayoutSelect - + Layout selection - Layout selection + Lay-out keuze - + Select layout - Select layout + kies lay-out - + Open file - Open file + Bestand openen ListLayout - - Stop All - Stop All + + Default behaviors + Default behaviors + + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show seek-bars + Show seek-bars + + + + Auto-select next cue + Auto-select next cue + + + + Enable selection mode + Enable selection mode + + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Resume Cue + Resume Cue + + + + Interrupt Cue + Interrupt Cue + + + + Edit cue + Edit cue + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove cue + Remove cue + + + + Remove selected + Remove selected + + + + Selection mode + Selection mode + + + + Pause all + Pause all + + + + Stop all + Stop all - Pause All - Pause All + Interrupt all + Interrupt all + + + + Resume all + Resume all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + Edit selected cues + Edit selected cues - - Interrupt All - Interrupt All + + Remove selected cues + Remove selected cues - Use fade (global actions) - Use fade (global actions) + Use fade + Use fade + + + + Copy of {} + Copy of {} + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name - - Resume All - Resume All + + Cue description + Cue description + + + + LogStatusIcon + + + Errors/Warnings + Errors/Warnings @@ -433,17 +1380,17 @@ Debug - Debug + Debuggen Warning - Warning + waarschuwing Error - Error + Fout @@ -456,7 +1403,7 @@ Show details - + Linux Show Player - Log Viewer Linux Show Player - Log Viewer @@ -547,146 +1494,299 @@ - MainWindow + MIDICue - - &File - &File + + MIDI Message + MIDI Message - - New session - New session + + Message type + Message type + + + MIDIMessageAttr - - Open - Open + + Channel + Channel - - Save session - Save session + + Note + Note - - Preferences - Preferences + + Velocity + Velocity - - Save as - Save as + + Control + Control - - Full Screen - Full Screen + + Program + Program - - Exit - Exit + + Value + Value - - &Edit - &Edit + + Song + Song - - Undo - Undo + + Pitch + Pitch - - Redo - Redo + + Position + Position + + + MIDIMessageType - - Select all - Select all + + Note ON + Note ON - - Select all media cues - Select all media cues + + Note OFF + Note OFF - - Deselect all - Deselect all + + Polyphonic After-touch + Polyphonic After-touch - - CTRL+SHIFT+A - CTRL+SHIFT+A + + Control/Mode Change + Control/Mode Change - - Invert selection - Invert selection + + Program Change + Program Change + + + + Channel After-touch + Channel After-touch + + + + Pitch Bend Change + Pitch Bend Change + + + + Song Select + Song Select + + + + Song Position + Song Position + + + + Start + Start + + + + Stop + Stop + + + + Continue + Continue + + + + MIDISettings + + + MIDI default devices + MIDI default devices + + + + Input + Input + + + + Output + Output + + + + MainWindow + + + &File + &Bestand + + + + New session + Nieuwe Sessie + + + + Open + Open + + + + Save session + Opslaan + + + + Preferences + Voorkeuren + + + + Save as + Opslaan als + + + + Full Screen + Volledig scherm + + + + Exit + Afsluiten + + + + &Edit + &Bewerken + + + + Undo + Ongedaan maken + + + + Redo + Opnieuw uitvoeren + + + + Select all + Alles selecteren + + + + Select all media cues + Selecteer alle media cues + + + + Deselect all + Alles deselecteren + + + + CTRL+SHIFT+A + CTRL+SHIFT+A + Invert selection + Selectie inverteren + + + CTRL+I CTRL+I - + Edit selected - Edit selected + Bewerk geselecteerde cues - + CTRL+SHIFT+E CTRL+SHIFT+E - + &Layout - &Layout + &Lay-out - + &Tools &Tools - + Edit selection - Edit selection + Bewerk selectie - + &About - &About + &Over - + About - About + Over - + About Qt - About Qt + Over Qt - + Close session - Close session + Sessie afsluiten - - The current session is not saved. - The current session is not saved. + + Do you want to save them now? + Do you want to save them now? + + + + MainWindowDebug + + + Registered cue menu: "{}" + Registered cue menu: "{}" + + + MainWindowError - - Discard the changes? - Discard the changes? + + Cannot create cue {} + Cannot create cue {} @@ -694,27 +1794,320 @@ Start time - Start time + Start tijd Stop position of the media - Stop position of the media + Stop positie Stop time - Stop time + Stop tijd Start position of the media - Start position of the media + start positie Loop - Loop + Herhaling + + + + MediaElementName + + + Pitch + Pitch + + + + Volume + Volume + + + + Audio Pan + Audio Pan + + + + Compressor/Expander + Compressor/Expander + + + + JACK Out + JACK Out + + + + URI Input + URI Input + + + + 10 Bands Equalizer + 10 Bands Equalizer + + + + ALSA Out + ALSA Out + + + + dB Meter + dB Meter + + + + Preset Input + Preset Input + + + + PulseAudio Out + PulseAudio Out + + + + Custom Element + Custom Element + + + + Speed + Speed + + + + System Out + System Out + + + + System Input + System Input + + + + MediaInfo + + + Media Info + Media Info + + + + Warning + Warning + + + + No info to display + No info to display + + + + Info + Info + + + + Value + Value + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + OscCue + + + Add + Add + + + + Remove + Remove + + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + PitchSettings + + + Pitch + Pitch + + + + {0:+} semitones + {0:+} semitones @@ -757,59 +2150,715 @@ - QColorButton + Preset - - Right click to reset - Right click to reset + + Create Cue + Create Cue + + + + Load on selected Cues + Load on selected Cues - SettingsPageName + PresetSrcSettings - - Appearance - Appearance + + Presets + Presets + + + Presets - - General - General + + Cannot scan presets + Cannot scan presets - - Cue - Cue + + Error while deleting preset "{}" + Error while deleting preset "{}" - - Cue Settings - Cue Settings + + Cannot load preset "{}" + Cannot load preset "{}" - - Plugins - Plugins + + Cannot save preset "{}" + Cannot save preset "{}" - - Behaviours - Behaviours + + Cannot rename preset "{}" + Cannot rename preset "{}" - - Pre/Post Wait - Pre/Post Wait + + Select Preset + Select Preset - - Fade In/Out - Fade In/Out + + Presets + Presets - + + Preset name + Preset name + + + + Add + Add + + + + Rename + Rename + + + + Edit + Edit + + + + Remove + Remove + + + + Export selected + Export selected + + + + Import + Import + + + + Warning + Warning + + + + The same name is already used! + The same name is already used! + + + + Cannot export correctly. + Cannot export correctly. + + + + Cannot import correctly. + Cannot import correctly. + + + + Cue type + Cue type + + + + Load on cue + Load on cue + + + + Load on selected cues + Load on selected cues + + + + Save as preset + Save as preset + + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + + + + QColorButton + + + Right click to reset + Rechts klikken om te resetten + + + + RenameCues + + + Rename Cues + Rename Cues + + + + Rename cues + Rename cues + + + + Current + Current + + + + Preview + Preview + + + + Capitalize + Capitalize + + + + Lowercase + Lowercase + + + + Uppercase + Uppercase + + + + Remove Numbers + Remove Numbers + + + + Add numbering + Add numbering + + + + Reset + Reset + + + + Type your regex here: + Type your regex here: + + + + Regex help + Regex help + + + + RenameCuesCommand + + + Renamed {number} cues + Renamed {number} cues + + + + RenameUiDebug + + + Regex error: Invalid pattern + Regex error: Invalid pattern + + + + ReplayGain + + + ReplayGain / Normalization + ReplayGain / Normalization + + + + Threads number + Threads number + + + + Apply only to selected media + Apply only to selected media + + + + ReplayGain to (dB SPL) + ReplayGain to (dB SPL) + + + + Normalize to (dB) + Normalize to (dB) + + + + Processing files ... + Processing files ... + + + + Calculate + Calculate + + + + Reset all + Reset all + + + + Reset selected + Reset selected + + + + ReplayGainDebug + + + Applied gain for: {} + Applied gain for: {} + + + + Discarded gain for: {} + Discarded gain for: {} + + + + ReplayGainInfo + + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} + + + + SeekCue + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Seek + Seek + + + + Time to reach + Time to reach + + + + SettingsPageName + + + Appearance + Weergave + + + + General + Algemeen + + + + Cue + Cue + + + + Cue Settings + Cue instellingen + + + + Plugins + Plugins + + + + Behaviours + Behaviours + + + + Pre/Post Wait + Pre/Post Wait + + + + Fade In/Out + Fade In/Out + + + Layouts Layouts + + + Action Settings + Action Settings + + + + Seek Settings + Seek Settings + + + + Volume Settings + Volume Settings + + + + Command + Command + + + + Edit Collection + Edit Collection + + + + Stop Settings + Stop Settings + + + + List Layout + List Layout + + + + Timecode + Timecode + + + + Timecode Settings + Timecode Settings + + + + Cue Control + Cue Control + + + + Layout Controls + Layout Controls + + + + MIDI Controls + MIDI Controls + + + + Keyboard Shortcuts + Keyboard Shortcuts + + + + OSC Controls + OSC Controls + + + + MIDI Settings + MIDI Settings + + + + MIDI settings + MIDI settings + + + + GStreamer + GStreamer + + + + Media Settings + Media Settings + + + + Triggers + Triggers + + + + OSC settings + OSC settings + + + + Cart Layout + Cart Layout + + + + SpeedSettings + + + Speed + Speed + + + + StopAll + + + Stop Action + Stop Action + + + + Synchronizer + + + Synchronization + Synchronization + + + + Manage connected peers + Manage connected peers + + + + Show your IP + Show your IP + + + + Your IP is: + Your IP is: + + + + Timecode + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + TimecodeError + + + Cannot send timecode. + Cannot send timecode. + + + + TimecodeSettings + + + Replace HOURS by a static track number + Replace HOURS by a static track number + + + + Enable Timecode + Enable Timecode + + + + Track number + Track number + + + + Timecode Settings + Timecode Settings + + + + Timecode Format: + Timecode Format: + + + + Timecode Protocol: + Timecode Protocol: + + + + TriggersSettings + + + Add + Add + + + + Remove + Remove + + + + Trigger + Trigger + + + + Cue + Cue + + + + Action + Action + + + + UriInputSettings + + + Source + Source + + + + Find File + Find File + + + + Buffering + Buffering + + + + Use Buffering + Use Buffering + + + + Attempt download on network streams + Attempt download on network streams + + + + Buffer size (-1 default value) + Buffer size (-1 default value) + + + + Choose file + Choose file + + + + All files + All files + + + + UserElementSettings + + + User defined elements + User defined elements + + + + Only for advanced user! + Only for advanced user! + + + + VolumeControl + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Volume to reach + Volume to reach + + + + Fade + Fade + + + + VolumeControlError + + + Error during cue execution. + Error during cue execution. + + + + VolumeSettings + + + Volume + Volume + + + + Normalized volume + Normalized volume + + + + Reset + Reset + diff --git a/lisp/i18n/ts/nl_BE/list_layout.ts b/lisp/i18n/ts/nl_BE/list_layout.ts index 41b5c6eac..1b499069b 100644 --- a/lisp/i18n/ts/nl_BE/list_layout.ts +++ b/lisp/i18n/ts/nl_BE/list_layout.ts @@ -4,7 +4,7 @@ LayoutDescription - + Organize the cues in a list Organize the cues in a list @@ -12,17 +12,17 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue SHIFT + Space or Double-Click to edit a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them To move cues drag them @@ -30,7 +30,7 @@ LayoutName - + List Layout List Layout @@ -43,27 +43,27 @@ Default behaviors - + Show playing cues Show playing cues - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue @@ -73,42 +73,42 @@ Enable selection mode - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - + Remove cue Remove cue - + Selection mode Selection mode @@ -143,60 +143,65 @@ Fade-In all - - GO key: - GO key: - - - - GO action: - GO action: - - - - GO delay (ms): - GO delay (ms): - - - + Edit selected Edit selected - + Clone cue Clone cue - + Clone selected Clone selected - + Remove selected Remove selected + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Copy of {} + Copy of {} + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait diff --git a/lisp/i18n/ts/nl_BE/media_info.ts b/lisp/i18n/ts/nl_BE/media_info.ts index 773bb30de..ee5f6774e 100644 --- a/lisp/i18n/ts/nl_BE/media_info.ts +++ b/lisp/i18n/ts/nl_BE/media_info.ts @@ -4,27 +4,27 @@ MediaInfo - + Media Info - Media Info + Media info - + No info to display - No info to display + Geen informatie om weer te geven - + Info Info - + Value - Value + Waarde - + Warning Warning diff --git a/lisp/i18n/ts/nl_BE/midi.ts b/lisp/i18n/ts/nl_BE/midi.ts index 65c17e530..ac771b298 100644 --- a/lisp/i18n/ts/nl_BE/midi.ts +++ b/lisp/i18n/ts/nl_BE/midi.ts @@ -1,6 +1,22 @@ + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + MIDI Cue + MIDI Cue + + MIDICue @@ -130,17 +146,17 @@ MIDI default devices - MIDI default devices + Standaard MIDI apparaat Input - Input + Ingang Output - Output + Uitgang @@ -148,7 +164,12 @@ MIDI settings - MIDI settings + MIDI instellingen + + + + MIDI Settings + MIDI Settings diff --git a/lisp/i18n/ts/nl_BE/osc.ts b/lisp/i18n/ts/nl_BE/osc.ts index 47a3b801e..1ae0fee4a 100644 --- a/lisp/i18n/ts/nl_BE/osc.ts +++ b/lisp/i18n/ts/nl_BE/osc.ts @@ -1,6 +1,88 @@ + + Cue Name + + + OSC Settings + OSC Settings + + + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + OSC Cue + OSC Cue + + + + OscCue + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + Add + Add + + + + Remove + Remove + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + OscServerDebug diff --git a/lisp/i18n/ts/nl_BE/presets.ts b/lisp/i18n/ts/nl_BE/presets.ts index c6ce90b18..9f67c867d 100644 --- a/lisp/i18n/ts/nl_BE/presets.ts +++ b/lisp/i18n/ts/nl_BE/presets.ts @@ -4,127 +4,132 @@ Preset - + Create Cue - Create Cue + Maak cue aan - + Load on selected Cues - Load on selected Cues + Toepassen op geselecteerde cue Presets - + Presets Presets - + Save as preset - Save as preset + Opslaan als preset - + Cannot scan presets - Cannot scan presets + De presets kunnen niet doorzocht worden - + Error while deleting preset "{}" - Error while deleting preset "{}" + Fout bij het verwijderen van preset "{}" - + Cannot load preset "{}" - Cannot load preset "{}" + Kan preset "{}" niet laden - + Cannot save preset "{}" - Cannot save preset "{}" + Kan preset "{}" niet opslaan - + Cannot rename preset "{}" - Cannot rename preset "{}" + Kan preset "{}" niet hernoemen - + Select Preset - Select Preset + Selecteer Preset - + Preset name - Preset name + Preset naam - + Add - Add + Toevoegen - + Rename - Rename + Hernoemen - + Edit - Edit + Bewerken - + Remove - Remove + Verwijderen - + Export selected - Export selected + Exporteer selectie - + Import - Import + Importeren - + Warning - Warning + Waarschuwing - + The same name is already used! - The same name is already used! + Deze naam is alreeds in gebruik! - + Cannot export correctly. - Cannot export correctly. + Kan niet correct exporteren - + Cannot import correctly. - Cannot import correctly. + Kan niet correct importeren - + Cue type Cue type - + Load on cue Load on cue - + Load on selected cues Load on selected cues + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + diff --git a/lisp/i18n/ts/nl_BE/rename_cues.ts b/lisp/i18n/ts/nl_BE/rename_cues.ts index 3555a4276..1711da214 100644 --- a/lisp/i18n/ts/nl_BE/rename_cues.ts +++ b/lisp/i18n/ts/nl_BE/rename_cues.ts @@ -4,7 +4,7 @@ RenameCues - + Rename Cues Rename Cues @@ -64,6 +64,14 @@ Regex help + + RenameCuesCommand + + + Renamed {number} cues + Renamed {number} cues + + RenameUiDebug diff --git a/lisp/i18n/ts/nl_BE/replay_gain.ts b/lisp/i18n/ts/nl_BE/replay_gain.ts index 3f48cad48..8b3f23808 100644 --- a/lisp/i18n/ts/nl_BE/replay_gain.ts +++ b/lisp/i18n/ts/nl_BE/replay_gain.ts @@ -4,49 +4,49 @@ ReplayGain - + ReplayGain / Normalization - ReplayGain / Normalization + Afspeelvolume / Normalisatie - + Calculate - Calculate + Bereken - + Reset all - Reset all + Reset alles - + Reset selected - Reset selected + Reset selectie Threads number - Threads number + Aantal processen Apply only to selected media - Apply only to selected media + Pas enkel toe op selectie ReplayGain to (dB SPL) - ReplayGain to (dB SPL) + Afspeelvolume (dB SPL) Normalize to (dB) - Normalize to (dB) + Normaliseer tot (dB) Processing files ... - Processing files ... + Bezig met verwerken van bestanden ... diff --git a/lisp/i18n/ts/nl_BE/synchronizer.ts b/lisp/i18n/ts/nl_BE/synchronizer.ts index 4798ba3ec..b218b7e07 100644 --- a/lisp/i18n/ts/nl_BE/synchronizer.ts +++ b/lisp/i18n/ts/nl_BE/synchronizer.ts @@ -4,24 +4,24 @@ Synchronizer - + Synchronization - Synchronization + Synchronisatie - + Manage connected peers - Manage connected peers + Beheer verbonden gebruikers - + Show your IP - Show your IP + geef mijn IP adres weer - + Your IP is: - Your IP is: + Uw IP adres is: diff --git a/lisp/i18n/ts/nl_BE/timecode.ts b/lisp/i18n/ts/nl_BE/timecode.ts index bfd0ad96e..0089e3a41 100644 --- a/lisp/i18n/ts/nl_BE/timecode.ts +++ b/lisp/i18n/ts/nl_BE/timecode.ts @@ -6,12 +6,12 @@ Timecode Settings - Timecode Settings + tijdscode Instellingen Timecode - Timecode + tijdscode @@ -35,17 +35,17 @@ Timecode Format: - Timecode Format: + tijdscode Formaat Replace HOURS by a static track number - Replace HOURS by a static track number + Vervang UREN door een statisch tracknummer Track number - Track number + Tracknummer diff --git a/lisp/i18n/ts/nl_BE/triggers.ts b/lisp/i18n/ts/nl_BE/triggers.ts index 1d2add464..00188c2c2 100644 --- a/lisp/i18n/ts/nl_BE/triggers.ts +++ b/lisp/i18n/ts/nl_BE/triggers.ts @@ -6,22 +6,22 @@ Started - Started + Gestart Paused - Paused + gepauzeerd Stopped - Stopped + Gestopt Ended - Ended + Beëindigd @@ -35,29 +35,29 @@ TriggersSettings - + Add - Add + Toevoegen - + Remove - Remove + Verwijderen - + Trigger - Trigger + Triggers - + Cue Cue - + Action - Action + Actie diff --git a/lisp/i18n/ts/nl_NL/action_cues.ts b/lisp/i18n/ts/nl_NL/action_cues.ts index 668de4e1d..b75973275 100644 --- a/lisp/i18n/ts/nl_NL/action_cues.ts +++ b/lisp/i18n/ts/nl_NL/action_cues.ts @@ -4,342 +4,231 @@ CollectionCue - + Add - Add + Toevoegen - + Remove - Remove + Verwijderen - + Cue Cue - + Action - Action + Actie CommandCue - - Process ended with an error status. - Process ended with an error status. - - - - Exit code: - Exit code: - - - + Command - Command + Opdracht - + Command to execute, as in a shell - Command to execute, as in a shell + Uit te voeren opdracht, zoals in een shell - + Discard command output - Discard command output + Opdracht-uitvoer verwerpen - + Ignore command errors - Ignore command errors + Opdrachtfouten negeren - + Kill instead of terminate - Kill instead of terminate - - - - Command cue ended with an error status. Exit code: {} - Command cue ended with an error status. Exit code: {} + Vernietigen i.p.v. beëindigen - Cue Name + CueCategory - - OSC Settings - OSC Settings + + Action cues + Action cues CueName - + Command Cue - Command Cue + Opdracht-cue - - MIDI Cue - MIDI Cue - - - + Volume Control - Volume Control + Volumebeheer - + Seek Cue - Seek Cue + Cue zoeken - + Collection Cue - Collection Cue + Collectie-cue - + Stop-All - Stop-All + Alles stoppen - + Index Action - Index Action - - - - OSC Cue - OSC Cue + Indexactie IndexActionCue - + Index Index - + Use a relative index - Use a relative index + Relatieve index gebruiken - + Target index - Target index + Doelindex - + Action - Action + Actie - + No suggestion No suggestion - + Suggested cue name Suggested cue name - - MIDICue - - - MIDI Message - MIDI Message - - - - Message type - Message type - - - - Osc Cue - - - Type - Type - - - - Argument - Argument - - - - FadeTo - FadeTo - - - - Fade - Fade - - - - OscCue - - - Error during cue execution. - Error during cue execution. - - - - OSC Message - OSC Message - - - - Add - Add - - - - Remove - Remove - - - - Test - Test - - - - OSC Path: (example: "/path/to/something") - OSC Path: (example: "/path/to/something") - - - - Fade - Fade - - - - Time (sec) - Time (sec) - - - - Curve - Curve - - SeekCue - + Cue Cue - + Click to select - Click to select + Klik om te selecteren - + Not selected - Not selected + Niet geselecteerd - + Seek - Seek + Zoeken - + Time to reach - Time to reach + Tijdbereik SettingsPageName - + Command - Command - - - - MIDI Settings - MIDI Settings + Opdracht - + Volume Settings - Volume Settings + Volume-instellingen - + Seek Settings - Seek Settings + Zoekinstellingen - + Edit Collection - Edit Collection + Collectie bewerken - + Action Settings - Action Settings + Actie-instellingen - + Stop Settings - Stop Settings + Stop-instellingen StopAll - + Stop Action - Stop Action + Stopactie VolumeControl - - Error during cue execution - Error during cue execution - - - + Cue Cue - + Click to select - Click to select + Klik om te selecteren - + Not selected - Not selected + Niet geselecteerd - + Volume to reach - Volume to reach + Volumebereik - + Fade - Fade + Regelen + + + + VolumeControlError + + + Error during cue execution. + Error during cue execution. diff --git a/lisp/i18n/ts/nl_NL/cart_layout.ts b/lisp/i18n/ts/nl_NL/cart_layout.ts index f52c8d203..590929271 100644 --- a/lisp/i18n/ts/nl_NL/cart_layout.ts +++ b/lisp/i18n/ts/nl_NL/cart_layout.ts @@ -4,120 +4,115 @@ CartLayout - + Default behaviors Default behaviors - + Countdown mode Countdown mode - + Show seek-bars Show seek-bars - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show volume Show volume - - Automatically add new page - Automatically add new page - - - + Grid size Grid size - - Number of columns - Number of columns - - - - Number of rows - Number of rows - - - + Play Play - + Pause Pause - + Stop Stop - + Reset volume Reset volume - + Add page Add page - + Add pages Add pages - + Remove current page Remove current page - + Number of Pages: Number of Pages: - + Page {number} Page {number} - + Warning Warning - + Every cue in the page will be lost. Every cue in the page will be lost. - + Are you sure to continue? Are you sure to continue? + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + LayoutDescription - + Organize cues in grid like pages Organize cues in grid like pages @@ -125,52 +120,68 @@ LayoutDetails - + Click a cue to run it Click a cue to run it - + SHIFT + Click to edit a cue SHIFT + Click to edit a cue - + CTRL + Click to select a cue CTRL + Click to select a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them while pressing SHIFT To move cues drag them while pressing SHIFT + + LayoutName + + + Cart Layout + Cart Layout + + ListLayout - + Edit cue Edit cue - + Edit selected cues Edit selected cues - + Remove cue Remove cue - + Remove selected cues Remove selected cues + + SettingsPageName + + + Cart Layout + Cart Layout + + diff --git a/lisp/i18n/ts/nl_NL/controller.ts b/lisp/i18n/ts/nl_NL/controller.ts index 56da86ec0..d5c2b6ef0 100644 --- a/lisp/i18n/ts/nl_NL/controller.ts +++ b/lisp/i18n/ts/nl_NL/controller.ts @@ -4,7 +4,7 @@ Controller - + Cannot load controller protocol: "{}" Cannot load controller protocol: "{}" @@ -12,123 +12,118 @@ ControllerKeySettings - - Key - Key - - - + Action - Action + Actie - + Shortcuts - Shortcuts + Sneltoetsen + + + + Shortcut + Shortcut ControllerMidiSettings - + MIDI MIDI - + Type Type - + Channel - Channel + Kanaal - + Note - Note + Aantekening - + Action - Action + Actie - + Filter "note on" - Filter "note on" + Filteren op "aantekening aan" - + Filter "note off" - Filter "note off" + Filteren op "aantekening uit" - + Capture - Capture + Vastleggen - + Listening MIDI messages ... - Listening MIDI messages ... + Bezig met luisteren naar MIDI-berichten... ControllerOscSettings - + OSC Message OSC Message - - OSC Path: (example: "/path/to/something") - OSC Path: (example: "/path/to/something") - - - + OSC OSC - + Path Path - + Types Types - + Arguments Arguments - + Actions Actions - + OSC Capture OSC Capture - + Add Add - + Remove Remove - + Capture Capture @@ -136,25 +131,78 @@ ControllerSettings - + Add - Add + Toevoegen - + Remove - Remove + Verwijderen + + + + GlobalAction + + + Go + Go + + + + Reset + Reset + + + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back Osc Cue - + Type Type - + Argument Argument @@ -162,12 +210,12 @@ OscCue - + Add Add - + Remove Remove @@ -175,24 +223,29 @@ SettingsPageName - + Cue Control - Cue Control + Cue-beheer - + MIDI Controls - MIDI Controls + MIDI-beheer - + Keyboard Shortcuts - Keyboard Shortcuts + Sneltoetsen - + OSC Controls OSC Controls + + + Layout Controls + Layout Controls + diff --git a/lisp/i18n/ts/nl_NL/gst_backend.ts b/lisp/i18n/ts/nl_NL/gst_backend.ts index 9f4949abc..671bbd0d0 100644 --- a/lisp/i18n/ts/nl_NL/gst_backend.ts +++ b/lisp/i18n/ts/nl_NL/gst_backend.ts @@ -4,60 +4,55 @@ AlsaSinkSettings - + ALSA device - ALSA device - - - - ALSA devices, as defined in an asound configuration file - ALSA devices, as defined in an asound configuration file + ALSA-apparaat AudioDynamicSettings - + Compressor Compressor - + Expander - Expander + Uitvouwer - + Soft Knee Soft Knee - + Hard Knee Hard Knee - + Compressor/Expander Compressor/Expander - + Type Type - + Curve Shape Curve Shape - + Ratio Ratio - + Threshold (dB) Threshold (dB) @@ -65,22 +60,22 @@ AudioPanSettings - + Audio Pan Audio Pan - + Center Center - + Left Left - + Right Right @@ -88,22 +83,22 @@ DbMeterSettings - + DbMeter settings DbMeter settings - + Time between levels (ms) Time between levels (ms) - + Peak ttl (ms) Peak ttl (ms) - + Peak falloff (dB/sec) Peak falloff (dB/sec) @@ -111,7 +106,7 @@ Equalizer10Settings - + 10 Bands Equalizer (IIR) 10 Bands Equalizer (IIR) @@ -119,28 +114,44 @@ GstBackend - + Audio cue (from file) Audio cue (from file) - + Select media files Select media files + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + GstMediaSettings - + Change Pipeline Change Pipeline + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + GstPipelineEdit - + Edit Pipeline Edit Pipeline @@ -148,7 +159,7 @@ GstSettings - + Pipeline Pipeline @@ -156,32 +167,32 @@ JackSinkSettings - + Connections Connections - + Edit connections Edit connections - + Output ports Output ports - + Input ports Input ports - + Connect Connect - + Disconnect Disconnect @@ -189,77 +200,77 @@ MediaElementName - + Compressor/Expander Compressor/Expander - + Audio Pan Audio Pan - + PulseAudio Out PulseAudio Out - + Volume Volume - + dB Meter dB Meter - + System Input System Input - + ALSA Out ALSA Out - + JACK Out JACK Out - + Custom Element Custom Element - + System Out System Out - + Pitch Pitch - + URI Input URI Input - + 10 Bands Equalizer 10 Bands Equalizer - + Speed Speed - + Preset Input Preset Input @@ -267,12 +278,12 @@ PitchSettings - + Pitch Pitch - + {0:+} semitones {0:+} semitones @@ -280,7 +291,7 @@ PresetSrcSettings - + Presets Presets @@ -288,17 +299,12 @@ SettingsPageName - - GStreamer settings - GStreamer settings - - - + Media Settings Media Settings - + GStreamer GStreamer @@ -306,7 +312,7 @@ SpeedSettings - + Speed Speed @@ -314,42 +320,42 @@ UriInputSettings - + Source Source - + Find File Find File - + Buffering Buffering - + Use Buffering Use Buffering - + Attempt download on network streams Attempt download on network streams - + Buffer size (-1 default value) Buffer size (-1 default value) - + Choose file Choose file - + All files All files @@ -357,12 +363,12 @@ UserElementSettings - + User defined elements User defined elements - + Only for advanced user! Only for advanced user! @@ -370,17 +376,17 @@ VolumeSettings - + Volume Volume - + Normalized volume Normalized volume - + Reset Reset diff --git a/lisp/i18n/ts/nl_NL/lisp.ts b/lisp/i18n/ts/nl_NL/lisp.ts index 6961717a1..48d3d69ec 100644 --- a/lisp/i18n/ts/nl_NL/lisp.ts +++ b/lisp/i18n/ts/nl_NL/lisp.ts @@ -1,89 +1,90 @@ + + APIServerInfo + + + Stop serving network API + Stop serving network API + + About - + Authors - Authors + Makers - + Contributors - Contributors + Bijdragers - + Translators - Translators + Vertalers - + About Linux Show Player - About Linux Show Player + Over Linux Show Player AboutDialog - - Linux Show Player is a cue-player designed for stage productions. - Linux Show Player is a cue-player designed for stage productions. - - - + Web site - Web site - - - - Users group - Users group + Website - + Source code - Source code + Broncode - + Info - Info + Informatie - + License - License + Licentie - + Contributors - Contributors + Bijdragers - + Discussion Discussion - Actions + AlsaSinkSettings - - Undo: {} - Undo: {} + + ALSA device + ALSA device + + + ApiServerError - - Redo: {} - Redo: {} + + Network API server stopped working. + Network API server stopped working. AppConfiguration - + LiSP preferences LiSP preferences @@ -91,1090 +92,2773 @@ AppGeneralSettings - - Startup layout - Startup layout - - - - Use startup dialog - Use startup dialog - - - - Application theme - Application theme - - - + Default layout Default layout - + Enable startup layout selector Enable startup layout selector - + Application themes Application themes - - UI theme - UI theme + + UI theme: + UI theme: - - Icons theme - Icons theme + + Icons theme: + Icons theme: - - - CartLayout - - Add page - Add page + + Application language (require restart) + Application language (require restart) - - Add pages - Add pages + + Language: + Language: + + + ApplicationError - - Remove current page - Remove current page + + Startup error + Startup error + + + AudioDynamicSettings - - Countdown mode - Countdown mode + + Compressor + Compressor - - Show seek-bars - Show seek-bars + + Expander + Expander - - Show dB-meters - Show dB-meters + + Soft Knee + Soft Knee - - Show volume - Show volume + + Hard Knee + Hard Knee - - Show accurate time - Show accurate time + + Compressor/Expander + Compressor/Expander - - Edit cue - Edit cue + + Type + Type - - Remove - Remove + + Curve Shape + Curve Shape - - Select - Select + + Ratio + Ratio - - Play - Play + + Threshold (dB) + Threshold (dB) + + + AudioPanSettings - - Pause - Pause + + Audio Pan + Audio Pan - - Stop - Stop + + Center + Center - - Reset volume - Reset volume + + Left + Left - - Number of Pages: - Number of Pages: + + Right + Right + + + CartLayout - - Warning - Warning + + Default behaviors + Default behaviors - - Every cue in the page will be lost. - Every cue in the page will be lost. + + Countdown mode + Countdown mode - - Are you sure to continue? - Are you sure to continue? + + Show seek-bars + Show seek-bars - - Page - Page + + Show dB-meters + Show dB-meters - - Default behaviors - Default behaviors + + Show accurate time + Show accurate time - - Automatically add new page - Automatically add new page + + Show volume + Show volume - + Grid size Grid size - - Number of columns - Number of columns + + Number of columns: + Number of columns: - - Number of rows - Number of rows + + Number of rows: + Number of rows: - - - CueAction - - Default - Default + + Play + Play - + Pause Pause - - Start - Start - - - + Stop Stop - - FadeInStart - FadeInStart - - - - FadeOutStop - FadeOutStop + + Reset volume + Reset volume - - FadeOutPause - FadeOutPause + + Add page + Add page - - Faded Start - Faded Start + + Add pages + Add pages - - Faded Resume - Faded Resume + + Remove current page + Remove current page - - Faded Pause - Faded Pause + + Number of Pages: + Number of Pages: - - Faded Stop - Faded Stop + + Page {number} + Page {number} - - Faded Interrupt - Faded Interrupt + + Warning + Warning - - Resume - Resume + + Every cue in the page will be lost. + Every cue in the page will be lost. - - Do Nothing - Do Nothing + + Are you sure to continue? + Are you sure to continue? - CueActionLog + CollectionCue - - Cue settings changed: "{}" - Cue settings changed: "{}" + + Add + Add - - Cues settings changed. - Cues settings changed. + + Remove + Remove + + + + Cue + Cue + + + + Action + Action - CueAppearanceSettings + CommandCue - - The appearance depends on the layout - The appearance depends on the layout + + Command + Command - - Cue name - Cue name + + Command to execute, as in a shell + Command to execute, as in a shell - - NoName - NoName + + Discard command output + Discard command output - - Description/Note - Description/Note + + Ignore command errors + Ignore command errors - - Set Font Size - Set Font Size + + Kill instead of terminate + Kill instead of terminate + + + CommandsStack - - Color - Color + + Undo: {} + Undo: {} - - Select background color - Select background color + + Redo: {} + Redo: {} + + + ConfigurationDebug - - Select font color - Select font color + + Configuration written at {} + Configuration written at {} - CueName + ConfigurationInfo - - Media Cue - Media Cue + + New configuration installed at {} + New configuration installed at {} - CueNextAction + Controller - - Do Nothing - Do Nothing + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + + ControllerKeySettings + + + Action + Action - - Auto Follow - Auto Follow + + Shortcuts + Shortcuts - - Auto Next - Auto Next + + Shortcut + Shortcut - CueSettings + ControllerMidiSettings - - Pre wait - Pre wait + + MIDI + MIDI - - Wait before cue execution - Wait before cue execution + + Type + Type - - Post wait - Post wait + + Channel + Channel - - Wait after cue execution - Wait after cue execution + + Note + Note - - Next action - Next action + + Action + Action - - Interrupt Fade - Interrupt Fade + + Filter "note on" + Filter "note on" - - Fade Action - Fade Action + + Filter "note off" + Filter "note off" - - Behaviours - Behaviours + + Capture + Capture - - Pre/Post Wait - Pre/Post Wait + + Listening MIDI messages ... + Listening MIDI messages ... + + + ControllerOscSettings - - Fade In/Out - Fade In/Out + + OSC Message + OSC Message - - Start action - Start action + + OSC + OSC - - Default action to start the cue - Default action to start the cue + + Path + Path - - Stop action - Stop action + + Types + Types - - Default action to stop the cue - Default action to stop the cue + + Arguments + Arguments - - Interrupt fade - Interrupt fade + + Actions + Actions - - Fade actions - Fade actions + + OSC Capture + OSC Capture - - - Fade - - Linear - Linear + + Add + Add - - Quadratic - Quadratic + + Remove + Remove - - Quadratic2 - Quadratic2 + + Capture + Capture - FadeEdit + ControllerSettings - - Duration (sec) - Duration (sec) + + Add + Add - - Curve - Curve + + Remove + Remove - FadeSettings + Cue Name - - Fade In - Fade In - - - - Fade Out - Fade Out + + OSC Settings + OSC Settings - LayoutDescription + CueAction - - Organize cues in grid like pages - Organize cues in grid like pages + + Default + Standaard - - Organize the cues in a list - Organize the cues in a list + + Pause + Pauzeren - - - LayoutDetails - - Click a cue to run it - Click a cue to run it + + Start + Starten - - SHIFT + Click to edit a cue - SHIFT + Click to edit a cue + + Stop + Stoppen - - CTRL + Click to select a cue - CTRL + Click to select a cue + + Faded Start + Faded Start - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Space or Double-Click to edit a cue + + Faded Resume + Faded Resume - - To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL + + Faded Pause + Faded Pause - - To copy cues drag them while pressing SHIFT - To copy cues drag them while pressing SHIFT + + Faded Stop + Faded Stop - - CTRL + Left Click to select cues - CTRL + Left Click to select cues + + Faded Interrupt + Faded Interrupt - + + Resume + Resume + + + + Do Nothing + Do Nothing + + + + CueAppearanceSettings + + + The appearance depends on the layout + Het uiterlijk hangt af van de indeling + + + + Cue name + Cuenaam + + + + NoName + Geen naam + + + + Description/Note + Omschrijving/Aantekening + + + + Set Font Size + Lettertypegrootte instellen + + + + Color + Kleur + + + + Select background color + Achtergrondkleur instellen + + + + Select font color + Lettertypekleur instellen + + + + CueCategory + + + Action cues + Action cues + + + + Integration cues + Integration cues + + + + Misc cues + Misc cues + + + + CueCommandLog + + + Cue settings changed: "{}" + Cue settings changed: "{}" + + + + Cues settings changed. + Cues settings changed. + + + + CueName + + + Media Cue + Mediacue + + + + Index Action + Index Action + + + + Seek Cue + Seek Cue + + + + Volume Control + Volume Control + + + + Command Cue + Command Cue + + + + Collection Cue + Collection Cue + + + + Stop-All + Stop-All + + + + MIDI Cue + MIDI Cue + + + + OSC Cue + OSC Cue + + + + CueNextAction + + + Do Nothing + Do Nothing + + + + Trigger after the end + Trigger after the end + + + + Trigger after post wait + Trigger after post wait + + + + Select after the end + Select after the end + + + + Select after post wait + Select after post wait + + + + CueSettings + + + Pre wait + Vooraf wachten + + + + Wait before cue execution + Wachten voor dat de cue wordt uitgevoerd + + + + Post wait + Achteraf wachten + + + + Wait after cue execution + Wachten nadat de cue is uitgevoerd + + + + Next action + Volgende actie + + + + Start action + Startactie + + + + Default action to start the cue + Standaard actie om de cue te starten + + + + Stop action + Stopactie + + + + Default action to stop the cue + Standaard actie om de cue te stoppen + + + + Interrupt fade + Interrupt fade + + + + Fade actions + Fade actions + + + + CueTriggers + + + Started + Started + + + + Paused + Paused + + + + Stopped + Stopped + + + + Ended + Ended + + + + DbMeterSettings + + + DbMeter settings + DbMeter settings + + + + Time between levels (ms) + Time between levels (ms) + + + + Peak ttl (ms) + Peak ttl (ms) + + + + Peak falloff (dB/sec) + Peak falloff (dB/sec) + + + + Equalizer10Settings + + + 10 Bands Equalizer (IIR) + 10 Bands Equalizer (IIR) + + + + Fade + + + Linear + Lineair + + + + Quadratic + Vierkant + + + + Quadratic2 + Vierkant2 + + + + FadeEdit + + + Duration (sec): + Duration (sec): + + + + Curve: + Curve: + + + + FadeSettings + + + Fade In + Inregelen + + + + Fade Out + Uitregelen + + + + GlobalAction + + + Go + Go + + + + Reset + Reset + + + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + + + GstMediaSettings + + + Change Pipeline + Change Pipeline + + + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + + + GstPipelineEdit + + + Edit Pipeline + Edit Pipeline + + + + GstSettings + + + Pipeline + Pipeline + + + + HotKeyEdit + + + Press shortcut + Press shortcut + + + + IndexActionCue + + + No suggestion + No suggestion + + + + Index + Index + + + + Use a relative index + Use a relative index + + + + Target index + Target index + + + + Action + Action + + + + Suggested cue name + Suggested cue name + + + + JackSinkSettings + + + Connections + Connections + + + + Edit connections + Edit connections + + + + Output ports + Output ports + + + + Input ports + Input ports + + + + Connect + Connect + + + + Disconnect + Disconnect + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + To move cues drag them To move cues drag them + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + To move cues drag them while pressing SHIFT + To move cues drag them while pressing SHIFT + + + + LayoutName + + + List Layout + List Layout + + + + Cart Layout + Cart Layout + + + + LayoutSelect + + + Layout selection + Indelingselectie + + + + Select layout + Indeling selecteren + + + + Open file + Bestand openen + + + + ListLayout + + + Default behaviors + Default behaviors + + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show seek-bars + Show seek-bars + + + + Auto-select next cue + Auto-select next cue + + + + Enable selection mode + Enable selection mode + + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Resume Cue + Resume Cue + + + + Interrupt Cue + Interrupt Cue + + + + Edit cue + Edit cue + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove cue + Remove cue + + + + Remove selected + Remove selected + + + + Selection mode + Selection mode + + + + Pause all + Pause all + + + + Stop all + Stop all + + + + Interrupt all + Interrupt all + + + + Resume all + Resume all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + Edit selected cues + Edit selected cues + + + + Remove selected cues + Remove selected cues + + + + Use fade + Use fade + + + + Copy of {} + Copy of {} + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + + LogStatusIcon + + + Errors/Warnings + Errors/Warnings + + + + Logging + + + Debug + Foutopsporing + + + + Warning + Waarschuwing + + + + Error + Fout + + + + Dismiss all + Dismiss all + + + + Show details + Show details + + + + Linux Show Player - Log Viewer + Linux Show Player - Log Viewer + + + + Info + Info + + + + Critical + Critical + + + + Time + Time + + + + Milliseconds + Milliseconds + + + + Logger name + Logger name + + + + Level + Level + + + + Message + Message + + + + Function + Function + + + + Path name + Path name + + + + File name + File name + + + + Line no. + Line no. + + + + Module + Module + + + + Process ID + Process ID + + + + Process name + Process name + + + + Thread ID + Thread ID + + + + Thread name + Thread name + + + + Exception info + Exception info + + + + MIDICue + + + MIDI Message + MIDI Message + + + + Message type + Message type + + + + MIDIMessageAttr + + + Channel + Channel + + + + Note + Note + + + + Velocity + Velocity + + + + Control + Control + + + + Program + Program + + + + Value + Value + + + + Song + Song + + + + Pitch + Pitch + + + + Position + Position + + + + MIDIMessageType + + + Note ON + Note ON + + + + Note OFF + Note OFF + + + + Polyphonic After-touch + Polyphonic After-touch + + + + Control/Mode Change + Control/Mode Change + + + + Program Change + Program Change + + + + Channel After-touch + Channel After-touch + + + + Pitch Bend Change + Pitch Bend Change + + + + Song Select + Song Select + + + + Song Position + Song Position + + + + Start + Start + + + + Stop + Stop + + + + Continue + Continue + + + + MIDISettings + + + MIDI default devices + MIDI default devices + + + + Input + Input + + + + Output + Output + + + + MainWindow + + + &File + &Bestand: + + + + New session + Nieuwe sessie + + + + Open + Openen + + + + Save session + Sessie opslaan + + + + Preferences + Voorkeuren + + + + Save as + Opslaan als + + + + Full Screen + Volledig scherm + + + + Exit + Afsluiten + + + + &Edit + B&ewerken + + + + Undo + Ongedaan maken + + + + Redo + Opnieuw + + + + Select all + Alles selecteren + + + + Select all media cues + Alle mediacues selecteren + + + + Deselect all + Alles deselecteren + + + + CTRL+SHIFT+A + CTRL+SHIFT+A + + + + Invert selection + Selectie omkeren + + + + CTRL+I + CTRL+I + + + + Edit selected + Geselecteerde bewerken + + + + CTRL+SHIFT+E + CTRL+SHIFT+E + + + + &Layout + Inde&ling + + + + &Tools + &Hulpmiddelen + + + + Edit selection + Selectie bewerken + + + + &About + &Over + + + + About + Over + + + + About Qt + Over Qt + + + + Close session + Sessie sluiten + + + + Do you want to save them now? + Do you want to save them now? + - LayoutSelect + MainWindowDebug + + + Registered cue menu: "{}" + Registered cue menu: "{}" + + + + MainWindowError + + + Cannot create cue {} + Cannot create cue {} + + + + MediaCueSettings + + + Start time + Starttijd + + + + Stop position of the media + Stoppositie van de media + + + + Stop time + Stoptijd + + + + Start position of the media + Startpositie van de media + + + + Loop + Herhalen + + + + MediaElementName + + + Pitch + Pitch + + + + Volume + Volume + + + + Audio Pan + Audio Pan + + + + Compressor/Expander + Compressor/Expander + + + + JACK Out + JACK Out + + + + URI Input + URI Input + + + + 10 Bands Equalizer + 10 Bands Equalizer + + + + ALSA Out + ALSA Out + + + + dB Meter + dB Meter + + + + Preset Input + Preset Input + + + + PulseAudio Out + PulseAudio Out + + + + Custom Element + Custom Element + + + + Speed + Speed + + + + System Out + System Out + + + + System Input + System Input + + + + MediaInfo + + + Media Info + Media Info + + + + Warning + Warning + + + + No info to display + No info to display + + + + Info + Info + + + + Value + Value + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + OscCue + + + Add + Add + + + + Remove + Remove + + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + PitchSettings + + + Pitch + Pitch + + + + {0:+} semitones + {0:+} semitones + + + + PluginsError + + + Failed to load "{}" + Failed to load "{}" + + + + Failed to terminate plugin: "{}" + Failed to terminate plugin: "{}" + + + + the requested plugin is not loaded: {} + the requested plugin is not loaded: {} + + + + PluginsInfo + + + Plugin loaded: "{}" + Plugin loaded: "{}" + + + + Plugin terminated: "{}" + Plugin terminated: "{}" + + + + PluginsWarning + + + Cannot satisfy dependencies for: {} + Cannot satisfy dependencies for: {} + + + + Preset + + + Create Cue + Create Cue + + + + Load on selected Cues + Load on selected Cues + + + + PresetSrcSettings + + + Presets + Presets + + + + Presets + + + Cannot scan presets + Cannot scan presets + + + + Error while deleting preset "{}" + Error while deleting preset "{}" + + + + Cannot load preset "{}" + Cannot load preset "{}" + + + + Cannot save preset "{}" + Cannot save preset "{}" + + + + Cannot rename preset "{}" + Cannot rename preset "{}" + + + + Select Preset + Select Preset + + + + Presets + Presets + + + + Preset name + Preset name + + + + Add + Add + + + + Rename + Rename + + + + Edit + Edit + + + + Remove + Remove + - - Layout selection - Layout selection + + Export selected + Export selected - - Select layout - Select layout + + Import + Import - - Open file - Open file + + Warning + Warning - - - ListLayout - - Show playing cues - Show playing cues + + The same name is already used! + The same name is already used! - - Show dB-meters - Show dB-meters + + Cannot export correctly. + Cannot export correctly. - - Show seek-bars - Show seek-bars + + Cannot import correctly. + Cannot import correctly. - - Show accurate time - Show accurate time + + Cue type + Cue type - - Auto-select next cue - Auto-select next cue + + Load on cue + Load on cue - - Edit cue - Edit cue + + Load on selected cues + Load on selected cues - - Remove - Remove + + Save as preset + Save as preset - - Select - Select + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + + + QColorButton - - Stop all - Stop all + + Right click to reset + Rechtsklikken om te herstellen + + + RenameCues - - Pause all - Pause all + + Rename Cues + Rename Cues - - Restart all - Restart all + + Rename cues + Rename cues - - Stop - Stop + + Current + Current - - Restart - Restart + + Preview + Preview - - Default behaviors - Default behaviors + + Capitalize + Capitalize - - At list end: - At list end: + + Lowercase + Lowercase - - Go key: - Go key: + + Uppercase + Uppercase - - Interrupt all - Interrupt all + + Remove Numbers + Remove Numbers - - Fade-Out all - Fade-Out all + + Add numbering + Add numbering - - Fade-In all - Fade-In all + + Reset + Reset - - Use fade - Use fade + + Type your regex here: + Type your regex here: - - Stop Cue - Stop Cue + + Regex help + Regex help + + + RenameCuesCommand - - Pause Cue - Pause Cue + + Renamed {number} cues + Renamed {number} cues + + + RenameUiDebug - - Restart Cue - Restart Cue + + Regex error: Invalid pattern + Regex error: Invalid pattern + + + ReplayGain - - Interrupt Cue - Interrupt Cue + + ReplayGain / Normalization + ReplayGain / Normalization - - Stop All - Stop All + + Threads number + Threads number - - Pause All - Pause All + + Apply only to selected media + Apply only to selected media - - Restart All - Restart All + + ReplayGain to (dB SPL) + ReplayGain to (dB SPL) - - Interrupt All - Interrupt All + + Normalize to (dB) + Normalize to (dB) - - Use fade (global actions) - Use fade (global actions) + + Processing files ... + Processing files ... - - Resume All - Resume All + + Calculate + Calculate - - - ListLayoutHeader - - Cue - Cue + + Reset all + Reset all - - Pre wait - Pre wait + + Reset selected + Reset selected + + + ReplayGainDebug - - Action - Action + + Applied gain for: {} + Applied gain for: {} - - Post wait - Post wait + + Discarded gain for: {} + Discarded gain for: {} - ListLayoutInfoPanel + ReplayGainInfo - - Cue name - Cue name + + Gain processing stopped by user. + Gain processing stopped by user. - - Cue description - Cue description + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} - Logging + SeekCue - - Information - Information + + Cue + Cue - - Debug - Debug + + Click to select + Click to select - - Warning - Warning + + Not selected + Not selected - - Error - Error + + Seek + Seek - - Details: - Details: + + Time to reach + Time to reach + + + SettingsPageName - - Dismiss all - Dismiss all + + Appearance + Uiterlijk - - Show details - Show details + + General + Algemeen - - Linux Show Player - Log Viewer - Linux Show Player - Log Viewer + + Cue + Cue - - Info - Info + + Cue Settings + Cue-instellingen - - Critical - Critical + + Plugins + Plugins - - Time - Time + + Behaviours + Behaviours - - Milliseconds - Milliseconds + + Pre/Post Wait + Pre/Post Wait - - Logger name - Logger name + + Fade In/Out + Fade In/Out - - Level - Level + + Layouts + Layouts - - Message - Message + + Action Settings + Action Settings - - Function - Function + + Seek Settings + Seek Settings - - Path name - Path name + + Volume Settings + Volume Settings - - File name - File name + + Command + Command - - Line no. - Line no. + + Edit Collection + Edit Collection - - Module - Module + + Stop Settings + Stop Settings - - Process ID - Process ID + + List Layout + List Layout - - Process name - Process name + + Timecode + Timecode - - Thread ID - Thread ID + + Timecode Settings + Timecode Settings - - Thread name - Thread name + + Cue Control + Cue Control - - - MainWindow - - &File - &File + + Layout Controls + Layout Controls - - New session - New session + + MIDI Controls + MIDI Controls - - Open - Open + + Keyboard Shortcuts + Keyboard Shortcuts - - Save session - Save session + + OSC Controls + OSC Controls - - Preferences - Preferences + + MIDI Settings + MIDI Settings - - Save as - Save as + + MIDI settings + MIDI settings - - Full Screen - Full Screen + + GStreamer + GStreamer - - Exit - Exit + + Media Settings + Media Settings - - &Edit - &Edit + + Triggers + Triggers - - Undo - Undo + + OSC settings + OSC settings - - Redo - Redo + + Cart Layout + Cart Layout + + + SpeedSettings - - Select all - Select all + + Speed + Speed + + + StopAll - - Select all media cues - Select all media cues + + Stop Action + Stop Action + + + Synchronizer - - Deselect all - Deselect all + + Synchronization + Synchronization - - CTRL+SHIFT+A - CTRL+SHIFT+A + + Manage connected peers + Manage connected peers - - Invert selection - Invert selection + + Show your IP + Show your IP - - CTRL+I - CTRL+I + + Your IP is: + Your IP is: + + + Timecode - - Edit selected - Edit selected + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + TimecodeError - - CTRL+SHIFT+E - CTRL+SHIFT+E + + Cannot send timecode. + Cannot send timecode. + + + TimecodeSettings - - &Layout - &Layout + + Replace HOURS by a static track number + Replace HOURS by a static track number - - &Tools - &Tools + + Enable Timecode + Enable Timecode - - Edit selection - Edit selection + + Track number + Track number - - &About - &About + + Timecode Settings + Timecode Settings - - About - About + + Timecode Format: + Timecode Format: - - About Qt - About Qt + + Timecode Protocol: + Timecode Protocol: + + + TriggersSettings - - Undone: - Undone: + + Add + Add - - Redone: - Redone: + + Remove + Remove - - Close session - Close session + + Trigger + Trigger - - The current session is not saved. - The current session is not saved. + + Cue + Cue - - Discard the changes? - Discard the changes? + + Action + Action - MediaCueMenus + UriInputSettings - - Audio cue (from file) - Audio cue (from file) + + Source + Source - - Select media files - Select media files + + Find File + Find File - - - MediaCueSettings - - Start time - Start time + + Buffering + Buffering - - Stop position of the media - Stop position of the media + + Use Buffering + Use Buffering - - Stop time - Stop time + + Attempt download on network streams + Attempt download on network streams - - Start position of the media - Start position of the media + + Buffer size (-1 default value) + Buffer size (-1 default value) - - Loop - Loop + + Choose file + Choose file - - Repetition after first play (-1 = infinite) - Repetition after first play (-1 = infinite) + + All files + All files - QColorButton + UserElementSettings - - Right click to reset - Right click to reset + + User defined elements + User defined elements + + + + Only for advanced user! + Only for advanced user! - SettingsPageName + VolumeControl - - Appearance - Appearance + + Cue + Cue - - General - General + + Click to select + Click to select - - Cue - Cue + + Not selected + Not selected - - Cue Settings - Cue Settings + + Volume to reach + Volume to reach - - Plugins - Plugins + + Fade + Fade + + + VolumeControlError - - Behaviours - Behaviours + + Error during cue execution. + Error during cue execution. + + + VolumeSettings - - Pre/Post Wait - Pre/Post Wait + + Volume + Volume - - Fade In/Out - Fade In/Out + + Normalized volume + Normalized volume + + + + Reset + Reset diff --git a/lisp/i18n/ts/nl_NL/list_layout.ts b/lisp/i18n/ts/nl_NL/list_layout.ts index 7eadaab5d..534150ecf 100644 --- a/lisp/i18n/ts/nl_NL/list_layout.ts +++ b/lisp/i18n/ts/nl_NL/list_layout.ts @@ -4,7 +4,7 @@ LayoutDescription - + Organize the cues in a list Organize the cues in a list @@ -12,163 +12,196 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue SHIFT + Space or Double-Click to edit a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them To move cues drag them + + LayoutName + + + List Layout + List Layout + + ListLayout - + Default behaviors Default behaviors - + Show playing cues Show playing cues - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue - + Enable selection mode Enable selection mode - - Go key: - Go key: - - - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - - Edit selected cues - Edit selected cues - - - + Remove cue Remove cue - - Remove selected cues - Remove selected cues - - - + Selection mode Selection mode - + Pause all Pause all - + Stop all Stop all - + Interrupt all Interrupt all - + Resume all Resume all - + Fade-Out all Fade-Out all - + Fade-In all Fade-In all + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove selected + Remove selected + + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Copy of {} + Copy of {} + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait @@ -176,14 +209,22 @@ ListLayoutInfoPanel - + Cue name Cue name - + Cue description Cue description + + SettingsPageName + + + List Layout + List Layout + + diff --git a/lisp/i18n/ts/nl_NL/media_info.ts b/lisp/i18n/ts/nl_NL/media_info.ts index 40b3c02b3..ce7600bf2 100644 --- a/lisp/i18n/ts/nl_NL/media_info.ts +++ b/lisp/i18n/ts/nl_NL/media_info.ts @@ -4,32 +4,27 @@ MediaInfo - + Media Info - Media Info + Media-informatie - - Error - Error - - - + No info to display - No info to display + Er is geen weer te geven informatie - + Info - Info + Informatie - + Value - Value + Waarde - + Warning Warning diff --git a/lisp/i18n/ts/nl_NL/midi.ts b/lisp/i18n/ts/nl_NL/midi.ts index 229c5f84f..5b0698a65 100644 --- a/lisp/i18n/ts/nl_NL/midi.ts +++ b/lisp/i18n/ts/nl_NL/midi.ts @@ -1,30 +1,175 @@ + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + MIDI Cue + MIDI Cue + + + + MIDICue + + + MIDI Message + MIDI Message + + + + Message type + Message type + + + + MIDIMessageAttr + + + Channel + Channel + + + + Note + Note + + + + Velocity + Velocity + + + + Control + Control + + + + Program + Program + + + + Value + Value + + + + Song + Song + + + + Pitch + Pitch + + + + Position + Position + + + + MIDIMessageType + + + Note ON + Note ON + + + + Note OFF + Note OFF + + + + Polyphonic After-touch + Polyphonic After-touch + + + + Control/Mode Change + Control/Mode Change + + + + Program Change + Program Change + + + + Channel After-touch + Channel After-touch + + + + Pitch Bend Change + Pitch Bend Change + + + + Song Select + Song Select + + + + Song Position + Song Position + + + + Start + Start + + + + Stop + Stop + + + + Continue + Continue + + MIDISettings - + MIDI default devices - MIDI default devices + MIDI - standaard apparaten - + Input - Input + Invoer - + Output - Output + Uitvoer SettingsPageName - + MIDI settings - MIDI settings + MIDI-instellingen + + + + MIDI Settings + MIDI Settings diff --git a/lisp/i18n/ts/nl_NL/network.ts b/lisp/i18n/ts/nl_NL/network.ts index bdf4f09f4..b34390dd4 100644 --- a/lisp/i18n/ts/nl_NL/network.ts +++ b/lisp/i18n/ts/nl_NL/network.ts @@ -1,45 +1,69 @@ + + APIServerInfo + + + Stop serving network API + Stop serving network API + + + + ApiServerError + + + Network API server stopped working. + Network API server stopped working. + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + NetworkDiscovery - + Host discovery Host discovery - + Manage hosts Manage hosts - + Discover hosts Discover hosts - + Manually add a host Manually add a host - + Remove selected host Remove selected host - + Remove all host Remove all host - + Address Address - + Host IP Host IP diff --git a/lisp/i18n/ts/nl_NL/osc.ts b/lisp/i18n/ts/nl_NL/osc.ts index a3d32f704..e9cd98f42 100644 --- a/lisp/i18n/ts/nl_NL/osc.ts +++ b/lisp/i18n/ts/nl_NL/osc.ts @@ -1,25 +1,136 @@ + + Cue Name + + + OSC Settings + OSC Settings + + + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + OSC Cue + OSC Cue + + + + OscCue + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + Add + Add + + + + Remove + Remove + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + OscSettings - + OSC Settings OSC Settings - + Input Port: Input Port: - + Output Port: Output Port: - + Hostname: Hostname: @@ -27,7 +138,7 @@ SettingsPageName - + OSC settings OSC settings diff --git a/lisp/i18n/ts/nl_NL/presets.ts b/lisp/i18n/ts/nl_NL/presets.ts index ebcc7e510..80586a0db 100644 --- a/lisp/i18n/ts/nl_NL/presets.ts +++ b/lisp/i18n/ts/nl_NL/presets.ts @@ -4,147 +4,132 @@ Preset - + Create Cue - Create Cue + Cue creëren - + Load on selected Cues - Load on selected Cues + Laden op geselecteerde cues Presets - + Presets - Presets + Voorinstellingen - - Load preset - Load preset - - - + Save as preset - Save as preset + Opslaan als voorinstelling - + Cannot scan presets - Cannot scan presets + De voorinstellingen kunnen niet worden doorzocht - + Error while deleting preset "{}" - Error while deleting preset "{}" + Er is een fout opgetreden tijdens het verwijderen van voorinstelling "{}" - + Cannot load preset "{}" - Cannot load preset "{}" + De voorinstelling "{}" kan niet worden geladen - + Cannot save preset "{}" - Cannot save preset "{}" + De voorinstelling "{}" kan niet worden opgeslagen - + Cannot rename preset "{}" - Cannot rename preset "{}" + De naam van voorinstelling "{}" kan niet worden gewijzigd - + Select Preset - Select Preset - - - - Preset already exists, overwrite? - Preset already exists, overwrite? + Voorinstelling selecteren - + Preset name - Preset name + Naam van voorinstelling - + Add - Add + Toevoegen - + Rename - Rename + Naam wijzigen - + Edit - Edit + Bewerken - + Remove - Remove + Verwijderen - + Export selected - Export selected + Geselecteerde exporteren - + Import - Import + Importeren - + Warning - Warning + Waarschuwing - + The same name is already used! - The same name is already used! - - - - Cannot create a cue from this preset: {} - Cannot create a cue from this preset: {} + De naam is al in gebruik! - + Cannot export correctly. - Cannot export correctly. - - - - Some presets already exists, overwrite? - Some presets already exists, overwrite? + Er kan niet correct worden geëxporteerd. - + Cannot import correctly. - Cannot import correctly. + Er kan niet correct worden geïmporteerd. - + Cue type - Cue type + Cue-type - + Load on cue Load on cue - + Load on selected cues Load on selected cues + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + diff --git a/lisp/i18n/ts/nl_NL/rename_cues.ts b/lisp/i18n/ts/nl_NL/rename_cues.ts index d447a5f19..84fb15d68 100644 --- a/lisp/i18n/ts/nl_NL/rename_cues.ts +++ b/lisp/i18n/ts/nl_NL/rename_cues.ts @@ -4,92 +4,80 @@ RenameCues - + Rename Cues Rename Cues - + Rename cues Rename cues - + Current Current - + Preview Preview - + Capitalize Capitalize - + Lowercase Lowercase - + Uppercase Uppercase - + Remove Numbers Remove Numbers - + Add numbering Add numbering - + Reset Reset - - Rename all cue. () in regex below usable with $0, $1 ... - Rename all cue. () in regex below usable with $0, $1 ... - - - + Type your regex here: Type your regex here: - + Regex help Regex help + + + RenameCuesCommand + + + Renamed {number} cues + Renamed {number} cues + + + + RenameUiDebug - - You can use Regexes to rename your cues. - -Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... -In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. - -Exemple : -^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. -Only the numbers are between parenthesis and will be usable with $0 in the first line. - -For more information about Regexes, consult python documentation - You can use Regexes to rename your cues. - -Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... -In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. - -Exemple : -^[a-z]([0-9]+) will find a lower case character ([a-z]), followed by one or more number. -Only the numbers are between parenthesis and will be usable with $0 in the first line. - -For more information about Regexes, consult python documentation + + Regex error: Invalid pattern + Regex error: Invalid pattern diff --git a/lisp/i18n/ts/nl_NL/replay_gain.ts b/lisp/i18n/ts/nl_NL/replay_gain.ts index 0c0153352..41c89f1f8 100644 --- a/lisp/i18n/ts/nl_NL/replay_gain.ts +++ b/lisp/i18n/ts/nl_NL/replay_gain.ts @@ -4,49 +4,80 @@ ReplayGain - + ReplayGain / Normalization - ReplayGain / Normalization + ReplayGain / Normalisatie - + Calculate - Calculate + Berekenen - + Reset all - Reset all + Alles herstellen - + Reset selected - Reset selected + Geselecteerde herstellen - + Threads number - Threads number + Aantal processen - + Apply only to selected media - Apply only to selected media + Alleen toepassen op geselecteerde media - + ReplayGain to (dB SPL) - ReplayGain to (dB SPL) + ReplayGain naar (dB SPL) - + Normalize to (dB) - Normalize to (dB) + Normaliseren naar (dB) - + Processing files ... - Processing files ... + Bezig met verwerken van bestanden ... + + + + ReplayGainDebug + + + Applied gain for: {} + Applied gain for: {} + + + + Discarded gain for: {} + Discarded gain for: {} + + + + ReplayGainInfo + + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} diff --git a/lisp/i18n/ts/nl_NL/synchronizer.ts b/lisp/i18n/ts/nl_NL/synchronizer.ts index bb31ac524..e48d834db 100644 --- a/lisp/i18n/ts/nl_NL/synchronizer.ts +++ b/lisp/i18n/ts/nl_NL/synchronizer.ts @@ -1,85 +1,27 @@ - - SyncPeerDialog - - - Manage connected peers - Manage connected peers - - - - Discover peers - Discover peers - - - - Manually add a peer - Manually add a peer - - - - Remove selected peer - Remove selected peer - - - - Remove all peers - Remove all peers - - - - Address - Address - - - - Peer IP - Peer IP - - - - Error - Error - - - - Already connected - Already connected - - - - Cannot add peer - Cannot add peer - - Synchronizer - + Synchronization - Synchronization + Synchronisatie - + Manage connected peers - Manage connected peers + Verbonden peers beheren - + Show your IP - Show your IP + Uw IP weergeven - + Your IP is: - Your IP is: - - - - Discovering peers ... - Discovering peers ... + Uw IP is: diff --git a/lisp/i18n/ts/nl_NL/timecode.ts b/lisp/i18n/ts/nl_NL/timecode.ts index fbceb7550..2c38a088b 100644 --- a/lisp/i18n/ts/nl_NL/timecode.ts +++ b/lisp/i18n/ts/nl_NL/timecode.ts @@ -4,12 +4,12 @@ SettingsPageName - + Timecode Settings Timecode Settings - + Timecode Timecode @@ -17,92 +17,48 @@ Timecode - - Cannot send timecode. - Cannot send timecode. - - - - OLA has stopped. - OLA has stopped. - - - + Cannot load timecode protocol: "{}" Cannot load timecode protocol: "{}" + + + TimecodeError - - Cannot send timecode. -OLA has stopped. - Cannot send timecode. -OLA has stopped. + + Cannot send timecode. + Cannot send timecode. TimecodeSettings - - OLA Timecode Settings - OLA Timecode Settings - - - - Enable Plugin - Enable Plugin - - - - High-Resolution Timecode - High-Resolution Timecode - - - + Timecode Format: Timecode Format: - - OLA status - OLA status - - - - OLA is not running - start the OLA daemon. - OLA is not running - start the OLA daemon. - - - + Replace HOURS by a static track number Replace HOURS by a static track number - - Enable ArtNet Timecode - Enable ArtNet Timecode - - - + Track number Track number - - To send ArtNet Timecode you need to setup a running OLA session! - To send ArtNet Timecode you need to setup a running OLA session! - - - + Enable Timecode Enable Timecode - + Timecode Settings Timecode Settings - + Timecode Protocol: Timecode Protocol: diff --git a/lisp/i18n/ts/nl_NL/triggers.ts b/lisp/i18n/ts/nl_NL/triggers.ts index 55fc89bb3..7eb870385 100644 --- a/lisp/i18n/ts/nl_NL/triggers.ts +++ b/lisp/i18n/ts/nl_NL/triggers.ts @@ -4,30 +4,30 @@ CueTriggers - + Started - Started + Gestart - + Paused - Paused + Gepauzeerd - + Stopped - Stopped + Gestopt - + Ended - Ended + Beëindigd SettingsPageName - + Triggers Triggers @@ -35,29 +35,29 @@ TriggersSettings - + Add - Add + Toevoegen - + Remove - Remove + Verwijderen - + Trigger Trigger - + Cue Cue - + Action - Action + Actie diff --git a/lisp/i18n/ts/sl_SI/action_cues.ts b/lisp/i18n/ts/sl_SI/action_cues.ts index 34186ae88..f4e464a3e 100644 --- a/lisp/i18n/ts/sl_SI/action_cues.ts +++ b/lisp/i18n/ts/sl_SI/action_cues.ts @@ -1,41 +1,25 @@ - - ActionCuesDebug - - - Registered cue: "{}" - Registered cue: "{}" - - - - ActionsCuesError - - - Cannot create cue {} - Cannot create cue {} - - CollectionCue - + Add Dodaj - + Remove Odstrani - + Cue Vrsta - + Action Akcija @@ -43,37 +27,37 @@ CommandCue - + Command Ukaz - + Command to execute, as in a shell Ukaz za izvršitev, kot v ukazni lupini - + Discard command output Zavrži izhod ukaza - + Ignore command errors Ignoriraj napake ukaza - + Kill instead of terminate Kill namesto terminate - Cue Name + CueCategory - - OSC Settings - OSC Settings + + Action cues + Action cues @@ -83,11 +67,6 @@ Command Cue Ukazna vrsta - - - MIDI Cue - MIDI vrsta - Volume Control @@ -113,153 +92,64 @@ Index Action Akcija indeksa - - - OSC Cue - OSC Cue - IndexActionCue - + Index Indeks - + Use a relative index Uporabi relativni indeks - + Target index Ciljni indeks - + Action Akcija - + No suggestion No suggestion - + Suggested cue name Suggested cue name - - Osc Cue - - - Type - Type - - - - Argument - Argument - - - - FadeTo - FadeTo - - - - Fade - Fade - - - - OscCue - - - OSC Message - OSC Message - - - - Add - Add - - - - Remove - Remove - - - - Test - Test - - - - OSC Path: (example: "/path/to/something") - OSC Path: (example: "/path/to/something") - - - - Fade - Fade - - - - Time (sec) - Time (sec) - - - - Curve - Curve - - - - OscCueError - - - Could not parse argument list, nothing sent - Could not parse argument list, nothing sent - - - - Error while parsing arguments, nothing sent - Error while parsing arguments, nothing sent - - - - Error during cue execution. - Error during cue execution. - - SeekCue - + Cue Vrsta - + Click to select Klikni za izbor - + Not selected Ni izbrano - + Seek Iskanje - + Time to reach Čas za dosego @@ -267,37 +157,32 @@ SettingsPageName - + Command Ukaz - - MIDI Settings - MIDI nastavitve - - - + Volume Settings Nastavitve glasnosti - + Seek Settings Nastavitve iskanja - + Edit Collection Uredi zbirko - + Action Settings Nastavitve akcije - + Stop Settings Nastavitve ustavitve @@ -305,7 +190,7 @@ StopAll - + Stop Action Akcija ustavitve @@ -313,27 +198,27 @@ VolumeControl - + Cue Vrsta - + Click to select Klikni za izbor - + Not selected Ni izbrano - + Volume to reach Glasnost za dosego - + Fade Pojenjanje @@ -341,7 +226,7 @@ VolumeControlError - + Error during cue execution. Error during cue execution. diff --git a/lisp/i18n/ts/sl_SI/cart_layout.ts b/lisp/i18n/ts/sl_SI/cart_layout.ts index 23d5dc2a0..b9a2c8016 100644 --- a/lisp/i18n/ts/sl_SI/cart_layout.ts +++ b/lisp/i18n/ts/sl_SI/cart_layout.ts @@ -9,27 +9,27 @@ Default behaviors - + Countdown mode Countdown mode - + Show seek-bars Show seek-bars - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show volume Show volume @@ -39,62 +39,62 @@ Grid size - + Play Play - + Pause Pause - + Stop Stop - + Reset volume Reset volume - + Add page Add page - + Add pages Add pages - + Remove current page Remove current page - + Number of Pages: Number of Pages: - + Page {number} Page {number} - + Warning Warning - + Every cue in the page will be lost. Every cue in the page will be lost. - + Are you sure to continue? Are you sure to continue? @@ -112,7 +112,7 @@ LayoutDescription - + Organize cues in grid like pages Organize cues in grid like pages @@ -120,27 +120,27 @@ LayoutDetails - + Click a cue to run it Click a cue to run it - + SHIFT + Click to edit a cue SHIFT + Click to edit a cue - + CTRL + Click to select a cue CTRL + Click to select a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them while pressing SHIFT To move cues drag them while pressing SHIFT @@ -148,7 +148,7 @@ LayoutName - + Cart Layout Cart Layout @@ -156,22 +156,22 @@ ListLayout - + Edit cue Edit cue - + Edit selected cues Edit selected cues - + Remove cue Remove cue - + Remove selected cues Remove selected cues diff --git a/lisp/i18n/ts/sl_SI/controller.ts b/lisp/i18n/ts/sl_SI/controller.ts index 01efb1c10..a9308c75e 100644 --- a/lisp/i18n/ts/sl_SI/controller.ts +++ b/lisp/i18n/ts/sl_SI/controller.ts @@ -12,20 +12,20 @@ ControllerKeySettings - - Key - Ključ - - - + Action Akcija - + Shortcuts Bližnjica + + + Shortcut + Shortcut + ControllerMidiSettings @@ -131,12 +131,12 @@ ControllerSettings - + Add Dodaj - + Remove Odstrani @@ -144,52 +144,52 @@ GlobalAction - + Go Go - + Reset Reset - + Stop all cues Stop all cues - + Pause all cues Pause all cues - + Resume all cues Resume all cues - + Interrupt all cues Interrupt all cues - + Fade-out all cues Fade-out all cues - + Fade-in all cues Fade-in all cues - + Move standby forward Move standby forward - + Move standby back Move standby back @@ -197,12 +197,12 @@ Osc Cue - + Type Type - + Argument Argument @@ -233,7 +233,7 @@ Krmiljenje MIDI - + Keyboard Shortcuts Bližnjice na tipkovnici diff --git a/lisp/i18n/ts/sl_SI/gst_backend.ts b/lisp/i18n/ts/sl_SI/gst_backend.ts index 31614da54..eb1ee0e3a 100644 --- a/lisp/i18n/ts/sl_SI/gst_backend.ts +++ b/lisp/i18n/ts/sl_SI/gst_backend.ts @@ -114,12 +114,12 @@ GstBackend - + Audio cue (from file) Audio cue (from file) - + Select media files Select media files @@ -127,7 +127,7 @@ GstMediaError - + Cannot create pipeline element: "{}" Cannot create pipeline element: "{}" @@ -143,7 +143,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" Invalid pipeline element: "{}" @@ -255,7 +255,7 @@ Višina - + URI Input URI vhod @@ -320,42 +320,42 @@ UriInputSettings - + Source Izvor - + Find File Najdi datoteko - + Buffering Pred-pomnjenje - + Use Buffering Uporabi pred-pomnjenje - + Attempt download on network streams Poizkusi prenesti omrežni tok - + Buffer size (-1 default value) Velikost pred-pomnilnika (-1 privzeta vrednost) - + Choose file Izberi datoteko - + All files Vse datoteke diff --git a/lisp/i18n/ts/sl_SI/lisp.ts b/lisp/i18n/ts/sl_SI/lisp.ts index a93f63dee..f5d494d90 100644 --- a/lisp/i18n/ts/sl_SI/lisp.ts +++ b/lisp/i18n/ts/sl_SI/lisp.ts @@ -1,6 +1,14 @@ + + APIServerInfo + + + Stop serving network API + Stop serving network API + + About @@ -58,16 +66,19 @@ - Actions + AlsaSinkSettings - - Undo: {} - Undo: {} + + ALSA device + ALSA device + + + ApiServerError - - Redo: {} - Redo: {} + + Network API server stopped working. + Network API server stopped working. @@ -81,39 +92,292 @@ AppGeneralSettings - + Default layout Default layout - + Enable startup layout selector Enable startup layout selector - + Application themes Application themes - + UI theme: UI theme: - + Icons theme: Icons theme: + + + Application language (require restart) + Application language (require restart) + + + + Language: + Language: + ApplicationError - + Startup error Startup error + + AudioDynamicSettings + + + Compressor + Compressor + + + + Expander + Expander + + + + Soft Knee + Soft Knee + + + + Hard Knee + Hard Knee + + + + Compressor/Expander + Compressor/Expander + + + + Type + Type + + + + Curve Shape + Curve Shape + + + + Ratio + Ratio + + + + Threshold (dB) + Threshold (dB) + + + + AudioPanSettings + + + Audio Pan + Audio Pan + + + + Center + Center + + + + Left + Left + + + + Right + Right + + + + CartLayout + + + Default behaviors + Default behaviors + + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show volume + Show volume + + + + Grid size + Grid size + + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Number of Pages: + Number of Pages: + + + + Page {number} + Page {number} + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + CollectionCue + + + Add + Add + + + + Remove + Remove + + + + Cue + Cue + + + + Action + Action + + + + CommandCue + + + Command + Command + + + + Command to execute, as in a shell + Command to execute, as in a shell + + + + Discard command output + Discard command output + + + + Ignore command errors + Ignore command errors + + + + Kill instead of terminate + Kill instead of terminate + + + + CommandsStack + + + Undo: {} + Undo: {} + + + + Redo: {} + Redo: {} + + ConfigurationDebug @@ -130,6 +394,154 @@ New configuration installed at {} + + Controller + + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + + ControllerKeySettings + + + Action + Action + + + + Shortcuts + Shortcuts + + + + Shortcut + Shortcut + + + + ControllerMidiSettings + + + MIDI + MIDI + + + + Type + Type + + + + Channel + Channel + + + + Note + Note + + + + Action + Action + + + + Filter "note on" + Filter "note on" + + + + Filter "note off" + Filter "note off" + + + + Capture + Capture + + + + Listening MIDI messages ... + Listening MIDI messages ... + + + + ControllerOscSettings + + + OSC Message + OSC Message + + + + OSC + OSC + + + + Path + Path + + + + Types + Types + + + + Arguments + Arguments + + + + Actions + Actions + + + + OSC Capture + OSC Capture + + + + Add + Add + + + + Remove + Remove + + + + Capture + Capture + + + + ControllerSettings + + + Add + Add + + + + Remove + Remove + + + + Cue Name + + + OSC Settings + OSC Settings + + CueAction @@ -188,19 +600,6 @@ Do Nothing - - CueActionLog - - - Cue settings changed: "{}" - Nastavitev vrste spremenjene: "{}" - - - - Cues settings changed. - Nastavitev vrst spremenjene. - - CueAppearanceSettings @@ -244,6 +643,37 @@ Izberi barvo pisave + + CueCategory + + + Action cues + Action cues + + + + Integration cues + Integration cues + + + + Misc cues + Misc cues + + + + CueCommandLog + + + Cue settings changed: "{}" + Cue settings changed: "{}" + + + + Cues settings changed. + Cues settings changed. + + CueName @@ -251,31 +681,71 @@ Media Cue Medijska vrsta + + + Index Action + Index Action + + + + Seek Cue + Seek Cue + + + + Volume Control + Volume Control + + + + Command Cue + Command Cue + + + + Collection Cue + Collection Cue + + + + Stop-All + Stop-All + + + + MIDI Cue + MIDI Cue + + + + OSC Cue + OSC Cue + CueNextAction - + Do Nothing Do Nothing - + Trigger after the end Trigger after the end - + Trigger after post wait Trigger after post wait - + Select after the end Select after the end - + Select after post wait Select after post wait @@ -338,6 +808,60 @@ Fade actions + + CueTriggers + + + Started + Started + + + + Paused + Paused + + + + Stopped + Stopped + + + + Ended + Ended + + + + DbMeterSettings + + + DbMeter settings + DbMeter settings + + + + Time between levels (ms) + Time between levels (ms) + + + + Peak ttl (ms) + Peak ttl (ms) + + + + Peak falloff (dB/sec) + Peak falloff (dB/sec) + + + + Equalizer10Settings + + + 10 Bands Equalizer (IIR) + 10 Bands Equalizer (IIR) + + Fade @@ -383,19 +907,263 @@ - LayoutSelect + GlobalAction - - Layout selection + + Go + Go + + + + Reset + Reset + + + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + + + GstMediaSettings + + + Change Pipeline + Change Pipeline + + + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + + + GstPipelineEdit + + + Edit Pipeline + Edit Pipeline + + + + GstSettings + + + Pipeline + Pipeline + + + + HotKeyEdit + + + Press shortcut + Press shortcut + + + + IndexActionCue + + + No suggestion + No suggestion + + + + Index + Index + + + + Use a relative index + Use a relative index + + + + Target index + Target index + + + + Action + Action + + + + Suggested cue name + Suggested cue name + + + + JackSinkSettings + + + Connections + Connections + + + + Edit connections + Edit connections + + + + Output ports + Output ports + + + + Input ports + Input ports + + + + Connect + Connect + + + + Disconnect + Disconnect + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them + To move cues drag them + + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + To move cues drag them while pressing SHIFT + To move cues drag them while pressing SHIFT + + + + LayoutName + + + List Layout + List Layout + + + + Cart Layout + Cart Layout + + + + LayoutSelect + + + Layout selection Izbor razporeditve - + Select layout Izberi razporeditev - + Open file Odpri datoteko @@ -403,29 +1171,208 @@ ListLayout - - Stop All - Ustavi vse + + Default behaviors + Default behaviors + + + + Show playing cues + Show playing cues + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show seek-bars + Show seek-bars + + + + Auto-select next cue + Auto-select next cue + + + + Enable selection mode + Enable selection mode + + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Resume Cue + Resume Cue + + + + Interrupt Cue + Interrupt Cue + + + + Edit cue + Edit cue + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove cue + Remove cue + + + + Remove selected + Remove selected + + + + Selection mode + Selection mode + + + + Pause all + Pause all + + + + Stop all + Stop all - Pause All - V premor vse + Interrupt all + Interrupt all + + + + Resume all + Resume all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + Edit selected cues + Edit selected cues - - Interrupt All - Prekini vse + + Remove selected cues + Remove selected cues - Use fade (global actions) - Use fade (global actions) + Use fade + Use fade + + + + Copy of {} + Copy of {} + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + LogStatusIcon - - Resume All - Resume All + + Errors/Warnings + Errors/Warnings @@ -456,7 +1403,7 @@ Show details - + Linux Show Player - Log Viewer Linux Show Player - Log Viewer @@ -547,221 +1494,992 @@ - MainWindow + MIDICue - - &File - &Datoteka + + MIDI Message + MIDI Message - - New session - Nova seja + + Message type + Message type + + + MIDIMessageAttr - - Open - Odpri + + Channel + Channel - - Save session - Shrani sejo + + Note + Note - - Preferences - Nastavitve + + Velocity + Velocity - - Save as - Shrani kot + + Control + Control - - Full Screen - Polni zaslon + + Program + Program - - Exit - Izhod + + Value + Value - - &Edit - &Uredi + + Song + Song - - Undo - Razveljavi + + Pitch + Pitch - - Redo - Obnovi + + Position + Position + + + MIDIMessageType - - Select all - Izberi vse + + Note ON + Note ON - - Select all media cues - Izberi vse medijske vrste + + Note OFF + Note OFF - - Deselect all - Od izberi vse + + Polyphonic After-touch + Polyphonic After-touch - - CTRL+SHIFT+A - CTRL+SHIFT+A + + Control/Mode Change + Control/Mode Change - - Invert selection - Invertiraj izbor + + Program Change + Program Change - - CTRL+I - CTRL+I + + Channel After-touch + Channel After-touch - - Edit selected - Uredi izbrano + + Pitch Bend Change + Pitch Bend Change - - CTRL+SHIFT+E - CTRL+SHIFT+E + + Song Select + Song Select - - &Layout - &Razporeditev + + Song Position + Song Position - - &Tools - &Orodja + + Start + Start - - Edit selection - Uredi izbor + + Stop + Stop - - &About - &Opis + + Continue + Continue + + + MIDISettings - - About + + MIDI default devices + MIDI default devices + + + + Input + Input + + + + Output + Output + + + + MainWindow + + + &File + &Datoteka + + + + New session + Nova seja + + + + Open + Odpri + + + + Save session + Shrani sejo + + + + Preferences + Nastavitve + + + + Save as + Shrani kot + + + + Full Screen + Polni zaslon + + + + Exit + Izhod + + + + &Edit + &Uredi + + + + Undo + Razveljavi + + + + Redo + Obnovi + + + + Select all + Izberi vse + + + + Select all media cues + Izberi vse medijske vrste + + + + Deselect all + Od izberi vse + + + + CTRL+SHIFT+A + CTRL+SHIFT+A + + + + Invert selection + Invertiraj izbor + + + + CTRL+I + CTRL+I + + + + Edit selected + Uredi izbrano + + + + CTRL+SHIFT+E + CTRL+SHIFT+E + + + + &Layout + &Razporeditev + + + + &Tools + &Orodja + + + + Edit selection + Uredi izbor + + + + &About + &Opis + + + + About Opis - - About Qt - Opis Qt + + About Qt + Opis Qt + + + + Close session + Zapri sejo + + + + Do you want to save them now? + Do you want to save them now? + + + + MainWindowDebug + + + Registered cue menu: "{}" + Registered cue menu: "{}" + + + + MainWindowError + + + Cannot create cue {} + Cannot create cue {} + + + + MediaCueSettings + + + Start time + Čas začetka + + + + Stop position of the media + Zaustavitvena pozicija medija + + + + Stop time + Čas zaustavitve + + + + Start position of the media + Začetna pozicija medija + + + + Loop + Ponavljaj + + + + MediaElementName + + + Pitch + Pitch + + + + Volume + Volume + + + + Audio Pan + Audio Pan + + + + Compressor/Expander + Compressor/Expander + + + + JACK Out + JACK Out + + + + URI Input + URI Input + + + + 10 Bands Equalizer + 10 Bands Equalizer + + + + ALSA Out + ALSA Out + + + + dB Meter + dB Meter + + + + Preset Input + Preset Input + + + + PulseAudio Out + PulseAudio Out + + + + Custom Element + Custom Element + + + + Speed + Speed + + + + System Out + System Out + + + + System Input + System Input + + + + MediaInfo + + + Media Info + Media Info + + + + Warning + Warning + + + + No info to display + No info to display + + + + Info + Info + + + + Value + Value + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + OscCue + + + Add + Add + + + + Remove + Remove + + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + PitchSettings + + + Pitch + Pitch + + + + {0:+} semitones + {0:+} semitones + + + + PluginsError + + + Failed to load "{}" + Failed to load "{}" + + + + Failed to terminate plugin: "{}" + Failed to terminate plugin: "{}" + + + + the requested plugin is not loaded: {} + the requested plugin is not loaded: {} + + + + PluginsInfo + + + Plugin loaded: "{}" + Plugin loaded: "{}" + + + + Plugin terminated: "{}" + Plugin terminated: "{}" + + + + PluginsWarning + + + Cannot satisfy dependencies for: {} + Cannot satisfy dependencies for: {} + + + + Preset + + + Create Cue + Create Cue + + + + Load on selected Cues + Load on selected Cues + + + + PresetSrcSettings + + + Presets + Presets + + + + Presets + + + Cannot scan presets + Cannot scan presets + + + + Error while deleting preset "{}" + Error while deleting preset "{}" + + + + Cannot load preset "{}" + Cannot load preset "{}" + + + + Cannot save preset "{}" + Cannot save preset "{}" + + + + Cannot rename preset "{}" + Cannot rename preset "{}" + + + + Select Preset + Select Preset + + + + Presets + Presets + + + + Preset name + Preset name + + + + Add + Add + + + + Rename + Rename + + + + Edit + Edit + + + + Remove + Remove + + + + Export selected + Export selected + + + + Import + Import - - Close session - Zapri sejo + + Warning + Warning + + + + The same name is already used! + The same name is already used! + + + + Cannot export correctly. + Cannot export correctly. + + + + Cannot import correctly. + Cannot import correctly. + + + + Cue type + Cue type + + + + Load on cue + Load on cue - - The current session is not saved. - Trenutna seja ni shranjena. + + Load on selected cues + Load on selected cues - - Discard the changes? - Zavržem spremembe? + + Save as preset + Save as preset + + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} - MediaCueSettings + QColorButton - - Start time - Čas začetka + + Right click to reset + Desno klik za ponastavitev + + + RenameCues - - Stop position of the media - Zaustavitvena pozicija medija + + Rename Cues + Rename Cues - - Stop time - Čas zaustavitve + + Rename cues + Rename cues - - Start position of the media - Začetna pozicija medija + + Current + Current - - Loop - Ponavljaj + + Preview + Preview + + + + Capitalize + Capitalize + + + + Lowercase + Lowercase + + + + Uppercase + Uppercase + + + + Remove Numbers + Remove Numbers + + + + Add numbering + Add numbering + + + + Reset + Reset + + + + Type your regex here: + Type your regex here: + + + + Regex help + Regex help - PluginsError + RenameCuesCommand - - Failed to load "{}" - Failed to load "{}" + + Renamed {number} cues + Renamed {number} cues + + + RenameUiDebug - - Failed to terminate plugin: "{}" - Failed to terminate plugin: "{}" + + Regex error: Invalid pattern + Regex error: Invalid pattern + + + ReplayGain - - the requested plugin is not loaded: {} - the requested plugin is not loaded: {} + + ReplayGain / Normalization + ReplayGain / Normalization + + + + Threads number + Threads number + + + + Apply only to selected media + Apply only to selected media + + + + ReplayGain to (dB SPL) + ReplayGain to (dB SPL) + + + + Normalize to (dB) + Normalize to (dB) + + + + Processing files ... + Processing files ... + + + + Calculate + Calculate + + + + Reset all + Reset all + + + + Reset selected + Reset selected - PluginsInfo + ReplayGainDebug - - Plugin loaded: "{}" - Plugin loaded: "{}" + + Applied gain for: {} + Applied gain for: {} - - Plugin terminated: "{}" - Plugin terminated: "{}" + + Discarded gain for: {} + Discarded gain for: {} - PluginsWarning + ReplayGainInfo - - Cannot satisfy dependencies for: {} - Cannot satisfy dependencies for: {} + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} - QColorButton + SeekCue - - Right click to reset - Desno klik za ponastavitev + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Seek + Seek + + + + Time to reach + Time to reach @@ -772,7 +2490,7 @@ Prikaz - + General Splošno @@ -811,5 +2529,336 @@ Layouts Layouts + + + Action Settings + Action Settings + + + + Seek Settings + Seek Settings + + + + Volume Settings + Volume Settings + + + + Command + Command + + + + Edit Collection + Edit Collection + + + + Stop Settings + Stop Settings + + + + List Layout + List Layout + + + + Timecode + Timecode + + + + Timecode Settings + Timecode Settings + + + + Cue Control + Cue Control + + + + Layout Controls + Layout Controls + + + + MIDI Controls + MIDI Controls + + + + Keyboard Shortcuts + Keyboard Shortcuts + + + + OSC Controls + OSC Controls + + + + MIDI Settings + MIDI Settings + + + + MIDI settings + MIDI settings + + + + GStreamer + GStreamer + + + + Media Settings + Media Settings + + + + Triggers + Triggers + + + + OSC settings + OSC settings + + + + Cart Layout + Cart Layout + + + + SpeedSettings + + + Speed + Speed + + + + StopAll + + + Stop Action + Stop Action + + + + Synchronizer + + + Synchronization + Synchronization + + + + Manage connected peers + Manage connected peers + + + + Show your IP + Show your IP + + + + Your IP is: + Your IP is: + + + + Timecode + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + TimecodeError + + + Cannot send timecode. + Cannot send timecode. + + + + TimecodeSettings + + + Replace HOURS by a static track number + Replace HOURS by a static track number + + + + Enable Timecode + Enable Timecode + + + + Track number + Track number + + + + Timecode Settings + Timecode Settings + + + + Timecode Format: + Timecode Format: + + + + Timecode Protocol: + Timecode Protocol: + + + + TriggersSettings + + + Add + Add + + + + Remove + Remove + + + + Trigger + Trigger + + + + Cue + Cue + + + + Action + Action + + + + UriInputSettings + + + Source + Source + + + + Find File + Find File + + + + Buffering + Buffering + + + + Use Buffering + Use Buffering + + + + Attempt download on network streams + Attempt download on network streams + + + + Buffer size (-1 default value) + Buffer size (-1 default value) + + + + Choose file + Choose file + + + + All files + All files + + + + UserElementSettings + + + User defined elements + User defined elements + + + + Only for advanced user! + Only for advanced user! + + + + VolumeControl + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Volume to reach + Volume to reach + + + + Fade + Fade + + + + VolumeControlError + + + Error during cue execution. + Error during cue execution. + + + + VolumeSettings + + + Volume + Volume + + + + Normalized volume + Normalized volume + + + + Reset + Reset + diff --git a/lisp/i18n/ts/sl_SI/list_layout.ts b/lisp/i18n/ts/sl_SI/list_layout.ts index 056660c50..981d4c09e 100644 --- a/lisp/i18n/ts/sl_SI/list_layout.ts +++ b/lisp/i18n/ts/sl_SI/list_layout.ts @@ -4,7 +4,7 @@ LayoutDescription - + Organize the cues in a list Organize the cues in a list @@ -12,17 +12,17 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue SHIFT + Space or Double-Click to edit a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them To move cues drag them @@ -30,7 +30,7 @@ LayoutName - + List Layout List Layout @@ -43,27 +43,27 @@ Default behaviors - + Show playing cues Show playing cues - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue @@ -73,42 +73,42 @@ Enable selection mode - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - + Remove cue Remove cue - + Selection mode Selection mode @@ -143,60 +143,65 @@ Fade-In all - - GO key: - GO key: - - - - GO action: - GO action: - - - - GO delay (ms): - GO delay (ms): - - - + Edit selected Edit selected - + Clone cue Clone cue - + Clone selected Clone selected - + Remove selected Remove selected + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Copy of {} + Copy of {} + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait diff --git a/lisp/i18n/ts/sl_SI/media_info.ts b/lisp/i18n/ts/sl_SI/media_info.ts index adade229f..ba9e6220e 100644 --- a/lisp/i18n/ts/sl_SI/media_info.ts +++ b/lisp/i18n/ts/sl_SI/media_info.ts @@ -4,27 +4,27 @@ MediaInfo - + Media Info Podrobnosti o mediju - + No info to display Brez podrobnosti za prikaz - + Info Podrobnosti - + Value Vrednost - + Warning Warning diff --git a/lisp/i18n/ts/sl_SI/midi.ts b/lisp/i18n/ts/sl_SI/midi.ts index bf0ab8e91..9bf6515d8 100644 --- a/lisp/i18n/ts/sl_SI/midi.ts +++ b/lisp/i18n/ts/sl_SI/midi.ts @@ -1,6 +1,22 @@ + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + MIDI Cue + MIDI Cue + + MIDICue @@ -150,5 +166,10 @@ MIDI settings MIDI nastavitve + + + MIDI Settings + MIDI Settings + diff --git a/lisp/i18n/ts/sl_SI/osc.ts b/lisp/i18n/ts/sl_SI/osc.ts index ec9d19730..5fe972e67 100644 --- a/lisp/i18n/ts/sl_SI/osc.ts +++ b/lisp/i18n/ts/sl_SI/osc.ts @@ -1,6 +1,88 @@ + + Cue Name + + + OSC Settings + OSC Settings + + + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + OSC Cue + OSC Cue + + + + OscCue + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + Add + Add + + + + Remove + Remove + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + OscServerDebug diff --git a/lisp/i18n/ts/sl_SI/presets.ts b/lisp/i18n/ts/sl_SI/presets.ts index 008edbcd9..d2bc1ea19 100644 --- a/lisp/i18n/ts/sl_SI/presets.ts +++ b/lisp/i18n/ts/sl_SI/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Ustvari vrsto - + Load on selected Cues Naloži izbrane vrste @@ -17,114 +17,119 @@ Presets - + Presets Pred-nastavitve - + Save as preset Shrani kot pred-nastavitev - + Cannot scan presets Ne morem prebrat pred-nastavitve - + Error while deleting preset "{}" Napaka med brisanjem pred-nastavitve "{}" - + Cannot load preset "{}" Ne morem naložit pred-nastavitve "{}" - + Cannot save preset "{}" Ne morem shranit pred-nastavitev "{}" - + Cannot rename preset "{}" Ne morem preimenovat pred-nastavitve "{}" - + Select Preset Izberi pred-nastavitve - + Preset name Ime pred-nastavitve - + Add Dodaj - + Rename Preimenuj - + Edit Uredi - + Remove Odstrani - + Export selected Izvozi izbrano - + Import Uvozi - + Warning Opozorilo - + The same name is already used! Ime je že v uporabi! - + Cannot export correctly. Ne morem pravilno izvozit. - + Cannot import correctly. Ne morem pravilno uvozit. - + Cue type Tip vrste - + Load on cue Load on cue - + Load on selected cues Load on selected cues + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + diff --git a/lisp/i18n/ts/sl_SI/rename_cues.ts b/lisp/i18n/ts/sl_SI/rename_cues.ts index ff72d956c..0c01928d2 100644 --- a/lisp/i18n/ts/sl_SI/rename_cues.ts +++ b/lisp/i18n/ts/sl_SI/rename_cues.ts @@ -4,7 +4,7 @@ RenameCues - + Rename Cues Rename Cues @@ -64,6 +64,14 @@ Regex help + + RenameCuesCommand + + + Renamed {number} cues + Renamed {number} cues + + RenameUiDebug diff --git a/lisp/i18n/ts/sl_SI/replay_gain.ts b/lisp/i18n/ts/sl_SI/replay_gain.ts index aac9bae36..aaaf8a842 100644 --- a/lisp/i18n/ts/sl_SI/replay_gain.ts +++ b/lisp/i18n/ts/sl_SI/replay_gain.ts @@ -4,22 +4,22 @@ ReplayGain - + ReplayGain / Normalization Ojačanje predvajanja / Normalizacija - + Calculate Izračunaj - + Reset all Ponastavi vse - + Reset selected Ponastavi izbrano diff --git a/lisp/i18n/ts/sl_SI/synchronizer.ts b/lisp/i18n/ts/sl_SI/synchronizer.ts index 615f011f5..8180cf6f0 100644 --- a/lisp/i18n/ts/sl_SI/synchronizer.ts +++ b/lisp/i18n/ts/sl_SI/synchronizer.ts @@ -4,22 +4,22 @@ Synchronizer - + Synchronization Sinhronizacija - + Manage connected peers Upravljaj povezane soležnike - + Show your IP Prikaži tvoj IP - + Your IP is: Tvoj IP je: diff --git a/lisp/i18n/ts/sl_SI/triggers.ts b/lisp/i18n/ts/sl_SI/triggers.ts index 02f0e5d9b..948557722 100644 --- a/lisp/i18n/ts/sl_SI/triggers.ts +++ b/lisp/i18n/ts/sl_SI/triggers.ts @@ -35,27 +35,27 @@ TriggersSettings - + Add Dodaj - + Remove Odstrani - + Trigger Prožilec - + Cue Vrsta - + Action Akcija From 56435f559dd0a81924c0710d14d5a6cb382ef2db Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 1 Jun 2019 14:17:23 +0200 Subject: [PATCH 190/333] updates --- .circleci/config.yml | 5 ++--- README.md | 11 +++-------- lisp/i18n/qm/base_ar_SA.qm | Bin 0 -> 42280 bytes lisp/i18n/qm/base_cs_CZ.qm | Bin 42965 -> 42285 bytes lisp/i18n/qm/base_de_DE.qm | Bin 41365 -> 42061 bytes lisp/i18n/qm/base_es_ES.qm | Bin 44747 -> 43031 bytes lisp/i18n/qm/base_fr_FR.qm | Bin 45318 -> 44447 bytes lisp/i18n/qm/base_it_IT.qm | Bin 43603 -> 42637 bytes lisp/i18n/qm/base_nl_BE.qm | Bin 41365 -> 42125 bytes lisp/i18n/qm/base_nl_NL.qm | Bin 42813 -> 42639 bytes lisp/i18n/qm/base_sl_SI.qm | Bin 42518 -> 42170 bytes 11 files changed, 5 insertions(+), 11 deletions(-) create mode 100644 lisp/i18n/qm/base_ar_SA.qm diff --git a/.circleci/config.yml b/.circleci/config.yml index 9614bb2d5..e10bbf79a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -109,7 +109,7 @@ jobs: --publish \ --override \ out/linux-show-payer.flatpak \ - francescoceruti/LinuxShowPlayer/ci/$PKG_VERSION \ + francescoceruti/LinuxShowPlayer/$CIRCLE_BRANCH/$PKG_VERSION \ linux-show-player_$PKG_VERSION.flatpak - store_artifacts: @@ -128,5 +128,4 @@ workflows: - /l10n_.*/ only: - master - - develop - - building \ No newline at end of file + - develop \ No newline at end of file diff --git a/README.md b/README.md index 7bf4fb335..15d5bab80 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,9 @@ For version _0.5_ you can download an [AppImage](http://appimage.org/) bundle of From version _0.6_ it will be possible to install a [Flatpak](https://flatpak.org/) package, follow the _simple_ instructions on their website to get everything ready. -You can get the latest **development** version [here](https://bintray.com/francescoceruti/LinuxShowPlayer/ci/_latestVersion) - might be unstable (crash, breaking changes, etc...) +You can get the latest **development** builds here: + * [Master](https://bintray.com/francescoceruti/LinuxShowPlayer/master/_latestVersion) + * [Development](https://bintray.com/francescoceruti/LinuxShowPlayer/develop/_latestVersion) - might be unstable (crash, breaking changes, etc...) #### 🐧 From yours distribution repository @@ -41,13 +43,6 @@ Keep in mind that it might not be the latest version, here a list: [![Packaging status](https://repology.org/badge/vertical-allrepos/linux-show-player.svg)](https://repology.org/metapackage/linux-show-player) - -#### 🔧 Manual installation - -_Following instruction are valid for the version of the software this README file is included with._ - -**TODO** - --- ### 📖 Usage diff --git a/lisp/i18n/qm/base_ar_SA.qm b/lisp/i18n/qm/base_ar_SA.qm new file mode 100644 index 0000000000000000000000000000000000000000..824c0bfb2fe9fff9aca4372c4928dc757971af5a GIT binary patch literal 42280 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 delta 9218 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 Ps0xK7*ZJqJ4ORUgoGL9| delta 8143 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 diff --git a/lisp/i18n/qm/base_es_ES.qm b/lisp/i18n/qm/base_es_ES.qm index 7499459b7598697193ef69a0311e3ece553c30da..7d49c2c664a6953192a7fd70b54abf3ec6572084 100644 GIT binary patch delta 11531 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 delta 12620 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 Y=)RgP+SmUN@cyZNeSg!kU)q=azYlI17XSbN diff --git a/lisp/i18n/qm/base_fr_FR.qm b/lisp/i18n/qm/base_fr_FR.qm index e1129a16a8d0d1347317432d526085352a946e79..0ff621c54bcdc4ac166c1e3aeaaaf0555111f8c5 100644 GIT binary patch delta 4726 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 delta 10405 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

Linux Show Player {0}

Copyright © Francesco Ceruti".format( - str(lisp.__version__) - ) + f"

Linux Show Player {lisp.__version__}

" + "Copyright © Francesco Ceruti" ) self.layout().addWidget(self.shortInfo, 0, 1) @@ -177,7 +176,7 @@ def __contributors(self): text += ' -
{0}'.format(person[1]) if len(person) >= 3: - text += " ({})".format(person[2]) + text += f" ({person[2]})" text += "
" diff --git a/lisp/ui/logging/details.py b/lisp/ui/logging/details.py index fd6162c3f..08dce24ad 100644 --- a/lisp/ui/logging/details.py +++ b/lisp/ui/logging/details.py @@ -37,7 +37,7 @@ def setLogRecord(self, record): self._record = record text = "" for attr, attr_text in LOG_ATTRIBUTES.items(): - text += "⇨{}:\n {}".format(attr_text, self.formatAttribute(attr)) + text += f"⇨{attr_text}:\n {self.formatAttribute(attr)}" self.setText(text) diff --git a/lisp/ui/logging/models.py b/lisp/ui/logging/models.py index 05da4ef54..9e448c3f9 100644 --- a/lisp/ui/logging/models.py +++ b/lisp/ui/logging/models.py @@ -135,9 +135,8 @@ def setSourceModel(self, source_model): super().setSourceModel(source_model) else: raise TypeError( - "LogRecordFilterModel source must be LogRecordModel, not {}".format( - typename(source_model) - ) + "LogRecordFilterModel source must be LogRecordModel, " + f"not {typename(source_model)}" ) def filterAcceptsRow(self, source_row, source_parent): diff --git a/lisp/ui/logging/viewer.py b/lisp/ui/logging/viewer.py index c91509dca..447f6c895 100644 --- a/lisp/ui/logging/viewer.py +++ b/lisp/ui/logging/viewer.py @@ -123,7 +123,7 @@ def _selectionChanged(self, selection): def _rowsChanged(self): self.statusLabel.setText( - "Showing {} of {} records".format( + translate("Logging", "Showing {} of {} records").format( self.filterModel.rowCount(), self.filterModel.sourceModel().rowCount(), ) diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index a8563f0de..88a7cd7e5 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -330,7 +330,7 @@ def __init__(self, cue_model, cue_select_dialog, **kwargs): def _text(self, option, index): cue = self.cue_model.get(index.data()) if cue is not None: - return "{} | {}".format(cue.index, cue.name) + return f"{cue.index} | {cue.name}" return "UNDEF" diff --git a/lisp/ui/settings/app_pages/plugins.py b/lisp/ui/settings/app_pages/plugins.py index fb8a54c19..46063d9c9 100644 --- a/lisp/ui/settings/app_pages/plugins.py +++ b/lisp/ui/settings/app_pages/plugins.py @@ -73,9 +73,9 @@ def __selection_changed(self): if item is not None: plugin = item.data(Qt.UserRole) - html = "Description: {}".format(plugin.Description) + html = f"Description: {plugin.Description}" html += "

" - html += "Authors: {}".format(", ".join(plugin.Authors)) + html += f"Authors: {', '.join(plugin.Authors)}" self.pluginDescription.setHtml(html) else: diff --git a/lisp/ui/settings/cue_settings.py b/lisp/ui/settings/cue_settings.py index 27793119b..80686449b 100644 --- a/lisp/ui/settings/cue_settings.py +++ b/lisp/ui/settings/cue_settings.py @@ -57,7 +57,7 @@ def __init__(self, cue, **kwargs): else: raise TypeError( "invalid cue type, must be a Cue subclass or a Cue object, " - "not {}".format(cue.__name__) + f"not {cue.__name__}" ) elif isinstance(cue, Cue): self.setWindowTitle(cue.name) @@ -66,7 +66,7 @@ def __init__(self, cue, **kwargs): else: raise TypeError( "invalid cue type, must be a Cue subclass or a Cue object, " - "not {}".format(typename(cue)) + f"not {typename(cue)}" ) self.mainPage = SettingsPagesTabWidget(parent=self) diff --git a/lisp/ui/themes/dark/theme.qss b/lisp/ui/themes/dark/theme.qss index a10ab8213..fb660275e 100644 --- a/lisp/ui/themes/dark/theme.qss +++ b/lisp/ui/themes/dark/theme.qss @@ -29,8 +29,8 @@ */ QWidget { - color: #ddd; - background-color: #333; + color: #dddddd; + background-color: #333333; selection-background-color: #419BE6; selection-color: black; outline: none; @@ -38,13 +38,13 @@ QWidget { QWidget:disabled { color: #707070; - background-color: #333; + background-color: #333333; } -QTreeView:branch:selected, -QTreeView:branch:selected:hover, -QWidget:item:selected, -QWidget:item:selected:hover { +QTreeView::branch:selected, +QTreeView::branch:selected:hover, +QWidget::item:selected, +QWidget::item:selected:hover { background-color: #419BE6; } @@ -58,7 +58,7 @@ QProgressBar:horizontal { background: #202020; } -QProgressBar:chunk:horizontal { +QProgressBar::chunk:horizontal { border-radius: 2px; background-color: rgba(40, 90, 150, 255); } @@ -71,20 +71,20 @@ QToolTip { border: 1px solid transparent; border-radius: 5px; background-color: #1A1A1A; - color: #ddd; + color: #dddddd; padding: 2px; opacity: 200; } -QMenuBar:item { +QMenuBar::item { background: transparent; } -QMenuBar:item:selected { +QMenuBar::item:selected { background: transparent; } -QMenuBar:item:pressed { +QMenuBar::item:pressed { border: 1px solid #4A4A4A; background-color: #419BE6; color: black; @@ -94,18 +94,23 @@ QMenuBar:item:pressed { QMenu { border: 1px solid #4A4A4A; - color: #ddd; + color: #dddddd; } -QMenu:item { - padding: 2px 20px 2px 20px; +QMenu::item { + padding: 2px 20px 2px 25px; } -QMenu:item:selected{ +QMenu::item:selected{ color: black; } -QMenu:separator { +QMenu::icon, +QMenu::indicator { + margin-right: -25px; +} + +QMenu::separator { height: 1px; margin: 4px 0px 4px 0px; background-color: #3C3C3C; @@ -117,15 +122,17 @@ QAbstractItemView { /*paint-alternating-row-colors-for-empty-area: true;*/ background-color: #202020; border-radius: 3px; - color: #ddd; + color: #dddddd; } QTreeView::branch:hover, -QAbstractItemView:item:hover { +QAbstractItemView::item:hover { background-color: none; } -QTabWidget:focus, QCheckBox:focus, QRadioButton:focus { +QTabWidget:focus, +QCheckBox:focus, +QRadioButton:focus { border: none; } @@ -134,7 +141,7 @@ QLineEdit { padding: 2px; border: 1px solid #4A4A4A; border-radius: 3px; - color: #ddd; + color: #dddddd; } QGroupBox { @@ -143,7 +150,7 @@ QGroupBox { padding-top: 1ex; /* NOT px */ } -QGroupBox:title { +QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top center; padding-top: 0.5ex; @@ -152,7 +159,7 @@ QGroupBox:title { } QAbstractScrollArea { - color: #ddd; + color: #dddddd; background-color: #202020; border: 1px solid #4A4A4A; border-radius: 3px; @@ -170,33 +177,33 @@ QScrollBar:vertical { width: 12px; } -QScrollBar:handle { +QScrollBar::handle { background-color: rgba(112, 112, 112, 0.7); /* #707070 */ border-radius: 4px; } -QScrollBar:handle:hover { +QScrollBar::handle:hover { background-color: rgba(112, 112, 112, 1); /* #707070 */ } -QScrollBar:handle:horizontal { +QScrollBar::handle:horizontal { min-width: 8px; } -QScrollBar:handle:vertical { +QScrollBar::handle:vertical { min-height: 8px; } -QScrollBar:add-line, -QScrollBar:sub-line, -QScrollBar:right-arrow, -QScrollBar:left-arrow { +QScrollBar::add-line, +QScrollBar::sub-line, +QScrollBar::right-arrow, +QScrollBar::left-arrow { width: 0px; height: 0px; } -QScrollBar:add-page, -QScrollBar:sub-page { +QScrollBar::add-page, +QScrollBar::sub-page { background: none; } @@ -205,7 +212,7 @@ QAbstractItemView QScrollBar { } QTextEdit[readOnly="true"] { - background-color: #333; + background-color: #333333; } QTextBrowser[readOnly="true"] { @@ -215,34 +222,44 @@ QTextBrowser[readOnly="true"] { QHeaderView { border: none; text-align: center; - qproperty-defaultAlignment: AlignCenter; + qproperty-defaultAlignment: "AlignCenter"; } -QHeaderView:up-arrow { +QHeaderView::up-arrow { image: url(:/assets/up-arrow.png); width: 24px; height: 24px; } -QHeaderView:down-arrow { +QHeaderView::down-arrow { image: url(:/assets/down-arrow.png); width: 24px; height: 24px; } -QHeaderView:section { +QHeaderView::section { border: none; border-right: 1px solid #3A3A3A; border-bottom: 1px solid #3A3A3A; } -QHeaderView:section, -QTableView QTableCornerButton:section { - background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #565656, stop: 0.1 #525252, stop: 0.5 #4e4e4e, stop: 0.9 #4a4a4a, stop: 1 #464646); - color: #ddd; +QHeaderView::section, +QTableView QTableCornerButton::section { + background-color: qlineargradient( + x1: 0, + y1: 0, + x2: 0, + y2: 1, + stop: 0 #565656, + stop: 0.1 #525252, + stop: 0.5 #4e4e4e, + stop: 0.9 #4a4a4a, + stop: 1 #464646 + ); + color: #dddddd; } -QHeaderView:section:checked { +QHeaderView::section:checked { font-weight: bold; } @@ -252,17 +269,25 @@ QSizeGrip { height: 12px; } -QMainWindow:separator { - background-color: #333; - color: #ddd; +QMainWindow::separator { + background-color: #333333; + color: #dddddd; padding-left: 4px; spacing: 2px; border: 1px dashed #3A3A3A; } -QMainWindow:separator:hover { - background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0 #58677b, stop:0.5 #419BE6 stop:1 #58677b); - color: #ddd; +QMainWindow::separator:hover { + background-color: qlineargradient( + x1: 0, + y1: 0, + x2: 0, + y2: 1, + stop: 0 #58677b, + stop: 0.5 #419BE6, + stop: 1 #58677b + ); + color: #dddddd; padding-left: 4px; border: 1px solid #4A4A4A; spacing: 2px; @@ -274,20 +299,20 @@ QStackedWidget { QToolBar { border: 1px solid #393838; - background: 1px solid #333; + background: 1px solid #333333; font-weight: bold; } -QToolBar:handle:horizontal { +QToolBar::handle:horizontal { image: url(:/assets/Hmovetoolbar.png); } -QToolBar:handle:vertical { +QToolBar::handle:vertical { image: url(:/assets/Vmovetoolbar.png); } -QToolBar:separator:horizontal { +QToolBar::separator:horizontal { image: url(:/assets/Hsepartoolbar.png); } -QToolBar:separator:vertical { +QToolBar::separator:vertical { image: url(:/assets/Vsepartoolbars.png); } @@ -301,8 +326,15 @@ QSplitter::handle:horizontal:disabled { } QPushButton { - color: #ddd; - background-color: QLinearGradient( x1: 0, y1: 1, x2: 0, y2: 0, stop: 0 #333, stop: 1 #444); + color: #dddddd; + background-color: qlineargradient( + x1: 0, + y1: 1, + x2: 0, + y2: 0, + stop: 0 #333333, + stop: 1 #444444 + ); border: 1px solid #202020; border-radius: 4px; padding: 4px; @@ -341,7 +373,7 @@ QComboBox:on { selection-background-color: #4a4a4a; } -QComboBox:drop-down { +QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 15px; @@ -353,7 +385,7 @@ QComboBox:drop-down { border-bottom-right-radius: 3px; } -QComboBox:down-arrow { +QComboBox::down-arrow { image: url(:/assets/down-arrow.png); height: 16px; width: 12px; @@ -366,10 +398,10 @@ QAbstractSpinBox { border: 1px solid #4A4A4A; background-color: #202020; border-radius: 3px; - color: #ddd; + color: #dddddd; } -QAbstractSpinBox:up-button { +QAbstractSpinBox::up-button { background-color: transparent; border-left: 1px solid #3A3A3A; padding: 6px; @@ -380,7 +412,7 @@ QAbstractSpinBox:up-button { subcontrol-position: center right; } -QAbstractSpinBox:down-button { +QAbstractSpinBox::down-button { background-color: transparent; border-left: 1px solid #3A3A3A; padding: 6px; @@ -390,19 +422,23 @@ QAbstractSpinBox:down-button { subcontrol-position: center right; } -QAbstractSpinBox:up-arrow,QAbstractSpinBox:up-arrow:disabled,QAbstractSpinBox:up-arrow:off { +QAbstractSpinBox::up-arrow, +QAbstractSpinBox::up-arrow:disabled, +QAbstractSpinBox::up-arrow:off { image: url(:/assets/spin-up-disabled.png); } -QAbstractSpinBox:down-arrow,QAbstractSpinBox:down-arrow:disabled,QAbstractSpinBox:down-arrow:off { +QAbstractSpinBox::down-arrow, +QAbstractSpinBox::down-arrow:disabled, +QAbstractSpinBox::down-arrow:off { image: url(:/assets/spin-down-disabled.png); } -QAbstractSpinBox:up-arrow:hover { +QAbstractSpinBox::up-arrow:hover { image: url(:/assets/spin-up.png); } -QAbstractSpinBox:down-arrow:hover { +QAbstractSpinBox::down-arrow:hover { image: url(:/assets/spin-down.png); } @@ -411,9 +447,9 @@ QLabel { } QTabBar::tab { - color: #ddd; - border: 1px solid #444; - background-color: #333; + color: #dddddd; + border: 1px solid #444444; + background-color: #333333; padding-left: 10px; padding-right: 10px; padding-top: 3px; @@ -422,7 +458,7 @@ QTabBar::tab { } QTabWidget::pane { - border: 1px solid #444; + border: 1px solid #444444; top: -1px; } @@ -449,11 +485,18 @@ QTabBar::tab:selected { QTabBar::tab:!selected { margin-top: 3px; - background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:1 #212121, stop:.4 #343434); + background-color: qlineargradient( + x1: 0, + y1: 0, + x2: 0, + y2: 1, + stop: 1 #212121, + stop: 0.4 #343434 + ); } QTabBar::tab:selected { - border-top-color: #CCC; + border-top-color: #CCCCCC; } QTabBar::tab:selected:focus { @@ -465,17 +508,24 @@ QTabBar QToolButton { } QDockWidget { - color: #ddd; + color: #dddddd; titlebar-close-icon: url(:/assets/close.png); titlebar-normal-icon: url(:/assets/undock.png); } -QDockWidget:title { +QDockWidget::title { border: 1px solid #4A4A4A; - border-bottom: #333; + border-bottom: #333333; text-align: left; spacing: 2px; - background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:1 #333, stop:0 #3A3A3A);; + background-color: qlineargradient( + x1: 0, + y1: 0, + x2: 0, + y2: 1, + stop: 1 #333333, + stop: 0 #3A3A3A + ); background-image: none; padding-left: 10px; } @@ -486,18 +536,21 @@ QDockWidget { titlebar-normal-icon: url(:/assets/undock.png); } -QDockWidget:close-button, QDockWidget:float-button { +QDockWidget::close-button, +QDockWidget::float-button { border: 1px solid transparent; border-radius: 5px; background: transparent; icon-size: 10px; } -QDockWidget:close-button:hover, QDockWidget:float-button:hover { +QDockWidget::close-button:hover, +QDockWidget::float-button:hover { background: #3A3A3A; } -QDockWidget:close-button:pressed, QDockWidget:float-button:pressed { +QDockWidget::close-button:pressed, +QDockWidget::float-button:pressed { padding: 1px -1px -1px 1px; } @@ -527,65 +580,65 @@ QSlider:disabled { background: none; } -QSlider:groove { +QSlider::groove { border: 1px solid #4A4A4A; border-radius: 1px; background: #202020; } -QSlider:groove:disabled { +QSlider::groove:disabled { background: #2D2D2D; } -QSlider:groove:horizontal { +QSlider::groove:horizontal { height: 3px; margin: 0px 3px; } -QSlider:groove:vertical { +QSlider::groove:vertical { width: 3px; margin: 3px 0px; } -QSlider:add-page:vertical:disabled, -QSlider:sub-page:horizontal:disabled { +QSlider::add-page:vertical:disabled, +QSlider::sub-page:horizontal:disabled { background-color: white; } -QSlider:sub-page:horizontal { +QSlider::sub-page:horizontal { height: 2px; border-radius: 1px; border: none; background-color: #80AAD5; } -QSlider:add-page:vertical { +QSlider::add-page:vertical { width: 2px; border-radius: 1px; border: none; background-color: #80AAD5; } -QSlider:handle { - background-color: #666; +QSlider::handle { + background-color: #666666; border: 1px solid black; border-radius: 2px; } -QSlider:handle:horizontal { +QSlider::handle:horizontal { margin: -6px -4px; width: 8px; height: 8px; } -QSlider:handle:vertical { +QSlider::handle:vertical { margin: -4px -6px; width: 8px; height: 8px; } -QSlider:handle:disabled { - background-color: #CCC; +QSlider::handle:disabled { + background-color: #CCCCCC; } QToolButton { @@ -604,48 +657,63 @@ QToolButton::menu-indicator { image: none; } -QCheckBox, QRadioButton { +QCheckBox, +QRadioButton { background-color: transparent; } -QCheckBox:indicator, QGroupBox:indicator, QMenu:indicator, QTreeWidget:indicator { - image: url(':/assets/checkbox-unchecked.png'); +QCheckBox::indicator, +QGroupBox::indicator, +QMenu::indicator, +QTreeWidget::indicator { + image: url(:/assets/checkbox-unchecked.png); } -QCheckBox:indicator:disabled, QGroupBox:indicator:disabled, QMenu:indicator:disabled, QTreeWidget:indicator:disabled { - image: url(':/assets/checkbox-unchecked-disabled.png'); +QCheckBox::indicator:disabled, +QGroupBox::indicator:disabled, +QMenu::indicator:disabled, +QTreeWidget::indicator:disabled { + image: url(:/assets/checkbox-unchecked-disabled.png); } -QCheckBox:indicator:checked, QGroupBox:indicator:checked, QMenu:indicator:checked, QTreeWidget:indicator:checked { - image: url(':/assets/checkbox-checked.png'); +QCheckBox::indicator:checked, +QGroupBox::indicator:checked, +QMenu::indicator:checked, +QTreeWidget::indicator:checked { + image: url(:/assets/checkbox-checked.png); } -QCheckBox:indicator:checked:disabled, QGroupBox:indicator:checked:disabled, QMenu:indicator:checked:disabled, QTreeWidget:indicator:checked:disabled { - image: url(':/assets/checkbox-checked-disabled.png'); +QCheckBox::indicator:checked:disabled, +QGroupBox::indicator:checked:disabled, +QMenu::indicator:checked:disabled, +QTreeWidget::indicator:checked:disabled { + image: url(:/assets/checkbox-checked-disabled.png); } -QCheckBox:indicator:indeterminate, QTreeWidget:indicator:indeterminate { - image: url(':/assets/checkbox-mixed.png'); +QCheckBox::indicator:indeterminate, +QTreeWidget::indicator:indeterminate { + image: url(:/assets/checkbox-mixed.png); } -QCheckBox:indicator:indeterminate:disabled, QTreeWidget:indicator:indeterminate:disabled { - image: url(':/assets/checkbox-mixed-disabled.png'); +QCheckBox::indicator:indeterminate:disabled, +QTreeWidget::indicator:indeterminate:disabled { + image: url(:/assets/checkbox-mixed-disabled.png); } -QRadioButton:indicator { - image: url(':/assets/radio-unchecked.png'); +QRadioButton::indicator { + image: url(:/assets/radio-unchecked.png); } -QRadioButton:indicator:disabled { - image: url(':/assets/radio-unchecked-disabled.png'); +QRadioButton::indicator:disabled { + image: url(:/assets/radio-unchecked-disabled.png); } -QRadioButton:indicator:checked { - image: url(':/assets/radio-checked.png'); +QRadioButton::indicator:checked { + image: url(:/assets/radio-checked.png); } -QRadioButton:indicator:checked:disabled { - image: url(':/assets/radio-checked-disabled.png'); +QRadioButton::indicator:checked:disabled { + image: url(:/assets/radio-checked-disabled.png); } @@ -663,7 +731,7 @@ QRadioButton:indicator:checked:disabled { #CartTabBar::tab { height: 35px; min-width: 100px; - color: #ddd; + color: #dddddd; } #CartTabBar::tab:selected { @@ -706,7 +774,7 @@ CueListView:focus { background: transparent; } -#ListTimeWidget:chunk:horizontal { +#ListTimeWidget::chunk:horizontal { border-radius: 0px; background-color: transparent; } @@ -716,29 +784,29 @@ CueListView:focus { } /* running */ -#ListTimeWidget:horizontal[state="running"] { +#ListTimeWidget[state="running"]:horizontal { border: 1px solid #00FF00; } -#ListTimeWidget:chunk:horizontal[state="running"] { +#ListTimeWidget[state="running"]::chunk:horizontal { background-color: #00A222; } /* pause */ -#ListTimeWidget:horizontal[state="pause"] { +#ListTimeWidget[state="pause"]:horizontal { border: 1px solid #FFAA00; } -#ListTimeWidget:chunk:horizontal[state="pause"] { +#ListTimeWidget[state="pause"]::chunk:horizontal { background-color: #FF8800; } /* error */ -#ListTimeWidget:horizontal[state="error"] { +#ListTimeWidget[state="error"]:horizontal { border: 1px solid #FF0000; } -#ListTimeWidget:chunk:horizontal[state="error"] { +#ListTimeWidget[state="error"]::chunk:horizontal { background-color: #CC0000; } @@ -746,12 +814,12 @@ CueListView:focus { border: 1px solid #4A4A4A; } -#VolumeSlider:sub-page:horizontal { +#VolumeSlider::sub-page:horizontal { border: none; background-color: none; } -#VolumeSlider:add-page:vertical { +#VolumeSlider::add-page:vertical { border: none; background-color: none; } \ No newline at end of file diff --git a/lisp/ui/ui_utils.py b/lisp/ui/ui_utils.py index 1143cb6a0..5f92d0903 100644 --- a/lisp/ui/ui_utils.py +++ b/lisp/ui/ui_utils.py @@ -129,11 +129,9 @@ def install_translation(name, tr_path=I18N_DIR): if QApplication.installTranslator(translator): # Keep a reference, QApplication does not _TRANSLATORS.append(translator) - logger.debug( - 'Installed translation for "{}" from {}'.format(name, tr_path) - ) + logger.debug(f'Installed translation for "{name}" from {tr_path}') else: - logger.debug('No translation for "{}" in {}'.format(name, tr_path)) + logger.debug(f'No translation for "{name}" in {tr_path}') def translate(context, text, disambiguation=None, n=-1): diff --git a/lisp/ui/widgets/colorbutton.py b/lisp/ui/widgets/colorbutton.py index d4b78e269..9cbf37ef0 100644 --- a/lisp/ui/widgets/colorbutton.py +++ b/lisp/ui/widgets/colorbutton.py @@ -45,7 +45,7 @@ def setColor(self, color): if self._color is not None: self.setStyleSheet( - "ColorButton {{ background-color: {0}; }}".format(self._color) + f"ColorButton {{ background-color: {self._color}; }}" ) else: self.setStyleSheet("") diff --git a/lisp/ui/widgets/locales.py b/lisp/ui/widgets/locales.py index 148b2c89f..0b34abb26 100644 --- a/lisp/ui/widgets/locales.py +++ b/lisp/ui/widgets/locales.py @@ -50,4 +50,4 @@ def _localeUiText(locale): ql.nativeCountryName(), ) else: - return "System ({})".format(QLocale().system().nativeLanguageName()) + return f"System ({QLocale().system().nativeLanguageName()})" From ef71275095eb75f368062cf644947d5183882379 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 28 Mar 2020 00:50:15 +0100 Subject: [PATCH 224/333] Update flatpak app-id --- .circleci/config.yml | 4 ++-- dist/linuxshowplayer.appdata.xml | 2 +- scripts/Flatpak/config.sh | 2 +- scripts/Flatpak/template.json | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 20d025828..7f6c78fa5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -64,7 +64,7 @@ jobs: - restore_cache: name: FLATPAK BUILD ⟹ Restore cache keys: - - v2-flatpak-{{ checksum "scripts/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json" }} + - v2-flatpak-{{ checksum "scripts/Flatpak/org.linux_show_player.LinuxShowPlayer.json" }} - v2-flatpak- - run: @@ -74,7 +74,7 @@ jobs: - save_cache: name: FLATPAK BUILD ⟹ Save cache - key: v2-flatpak-{{ checksum "scripts/Flatpak/com.github.FrancescoCeruti.LinuxShowPlayer.json" }} + key: v2-flatpak-{{ checksum "scripts/Flatpak/org.linux_show_player.LinuxShowPlayer.json" }} paths: - ~/repo/scripts/Flatpak/.flatpak-builder diff --git a/dist/linuxshowplayer.appdata.xml b/dist/linuxshowplayer.appdata.xml index 1c86c0da0..2e8072091 100644 --- a/dist/linuxshowplayer.appdata.xml +++ b/dist/linuxshowplayer.appdata.xml @@ -1,6 +1,6 @@ - com.github.FrancescoCeruti.LinuxShowPlayer.desktop + org.linux_show_player.LinuxShowPlayer.desktop CC-BY-3.0 GPL-3.0+ Linux Show Player diff --git a/scripts/Flatpak/config.sh b/scripts/Flatpak/config.sh index 758fbdb54..17ecc0122 100755 --- a/scripts/Flatpak/config.sh +++ b/scripts/Flatpak/config.sh @@ -9,5 +9,5 @@ export FLATPAK_INSTALL="$FLATPAK_RUNTIME//$FLATPAK_RUNTIME_VERSION $FLATPAK_SDK/ export FLATPAK_PY_VERSION="3.7" export FLATPAK_PY_IGNORE_PACKAGES="setuptools six pygobject pycairo pyqt5 sip" -export FLATPAK_APP_ID="com.github.FrancescoCeruti.LinuxShowPlayer" +export FLATPAK_APP_ID="org.linux_show_player.LinuxShowPlayer" export FLATPAK_APP_MODULE="linux-show-player" \ No newline at end of file diff --git a/scripts/Flatpak/template.json b/scripts/Flatpak/template.json index 1f40b55f5..8fd68676b 100644 --- a/scripts/Flatpak/template.json +++ b/scripts/Flatpak/template.json @@ -1,5 +1,5 @@ { - "app-id": "com.github.FrancescoCeruti.LinuxShowPlayer", + "app-id": "org.linux_show_player.LinuxShowPlayer", "runtime": "org.gnome.Platform", "runtime-version": "3.34", "sdk": "org.gnome.Sdk", @@ -270,7 +270,7 @@ "mkdir -p /app/share/applications/", "cp dist/linuxshowplayer.desktop /app/share/applications/", "mkdir -p /app/share/mime/packages/", - "cp dist/linuxshowplayer.xml /app/share/mime/packages/com.github.FrancescoCeruti.LinuxShowPlayer.xml", + "cp dist/linuxshowplayer.xml /app/share/mime/packages/org.linux_show_player.LinuxShowPlayer.xml", "mkdir -p /app/share/metainfo/", "cp dist/linuxshowplayer.appdata.xml /app/share/metainfo/", "mkdir -p /app/share/icons/hicolor/512x512/apps/", From 383010cf9ef71201af7b7048a1b521b151aee3e1 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 1 Apr 2020 12:48:06 +0200 Subject: [PATCH 225/333] Add a new waveform seek-bar widget (only to list-view) Add: waveform extraction api to the backend (aimed to ui usage) Add: cache-manager plugin (currently waveforms are the only thing cached) Fix: various fixes --- Pipfile | 1 + Pipfile.lock | 10 +- lisp/__init__.py | 2 + lisp/backend/__init__.py | 2 +- lisp/backend/backend.py | 21 +- lisp/backend/waveform.py | 135 ++++++++++++ lisp/core/class_based_registry.py | 6 +- lisp/core/has_properties.py | 46 ++-- lisp/core/util.py | 22 +- lisp/default.json | 5 +- lisp/plugins/cache_manager/__init__.py | 2 + lisp/plugins/cache_manager/cache_manager.py | 160 ++++++++++++++ lisp/plugins/cache_manager/default.json | 5 + .../plugins/gst_backend/elements/uri_input.py | 8 +- lisp/plugins/gst_backend/gst_backend.py | 9 + lisp/plugins/gst_backend/gst_media.py | 7 +- lisp/plugins/gst_backend/gst_utils.py | 4 + lisp/plugins/gst_backend/gst_waveform.py | 144 +++++++++++++ lisp/plugins/list_layout/default.json | 3 +- lisp/plugins/list_layout/playing_widgets.py | 15 +- lisp/plugins/osc/osc.py | 1 + lisp/ui/widgets/waveform.py | 199 ++++++++++++++++++ 22 files changed, 753 insertions(+), 54 deletions(-) create mode 100644 lisp/backend/waveform.py create mode 100644 lisp/plugins/cache_manager/__init__.py create mode 100644 lisp/plugins/cache_manager/cache_manager.py create mode 100644 lisp/plugins/cache_manager/default.json create mode 100644 lisp/plugins/gst_backend/gst_waveform.py create mode 100644 lisp/ui/widgets/waveform.py diff --git a/Pipfile b/Pipfile index 8939c0636..5ab8eae5b 100644 --- a/Pipfile +++ b/Pipfile @@ -16,6 +16,7 @@ python-rtmidi = "~=1.1" requests = "~=2.20" sortedcontainers = "~=2.0" pyalsa = {editable = true,git = "https://github.com/alsa-project/alsa-python.git",ref = "v1.1.6"} +humanize = "*" [dev-packages] html5lib = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 672a98a1d..fc51b98e7 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b76c9327c4b71f648a8d88df6b06b02d5dc3f68aa7ea55857728d05356e5218f" + "sha256": "79fbda0026ebefdff842b0a9d1124dc5583dbbf4be35102314cafbcd089bd5c8" }, "pipfile-spec": 6, "requires": {}, @@ -127,6 +127,14 @@ "index": "pypi", "version": "==2.0.0" }, + "humanize": { + "hashes": [ + "sha256:0b3157df0f3fcdcb7611180d305a23fd4b6290eb23586e058762f8576348fbab", + "sha256:de8ef6ffee618a9d369b3d1fb1359780ccaa2cc76a0e777c6ff21f04d19a0eb8" + ], + "index": "pypi", + "version": "==2.2.0" + }, "idna": { "hashes": [ "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", diff --git a/lisp/__init__.py b/lisp/__init__.py index dfbf3bd55..20068517c 100644 --- a/lisp/__init__.py +++ b/lisp/__init__.py @@ -38,5 +38,7 @@ DEFAULT_APP_CONFIG = path.join(APP_DIR, "default.json") USER_APP_CONFIG = path.join(app_dirs.user_config_dir, "lisp.json") +DEFAULT_CACHE_DIR = path.join(app_dirs.user_data_dir, "cache") + ICON_THEMES_DIR = path.join(APP_DIR, "ui", "icons") ICON_THEME_COMMON = "lisp" diff --git a/lisp/backend/__init__.py b/lisp/backend/__init__.py index 7867a91e5..dee4b6603 100644 --- a/lisp/backend/__init__.py +++ b/lisp/backend/__init__.py @@ -27,6 +27,6 @@ def set_backend(backend): def get_backend(): """ - :rtype: lisp.backends.base.backend.Backend + :rtype: lisp.backend.backend.Backend """ return __backend diff --git a/lisp/backend/backend.py b/lisp/backend/backend.py index 6758dbfce..511c9cc1e 100644 --- a/lisp/backend/backend.py +++ b/lisp/backend/backend.py @@ -17,6 +17,9 @@ from abc import abstractmethod, ABCMeta +from lisp.backend.media import Media +from lisp.backend.waveform import Waveform + class Backend(metaclass=ABCMeta): """Common interface that any backend must provide. @@ -25,32 +28,28 @@ class Backend(metaclass=ABCMeta): """ @abstractmethod - def uri_duration(self, uri): + def uri_duration(self, uri: str) -> int: """Return the file duration in milliseconds. :param uri: The URI of the file - :type uri: str - - :rtype: int """ @abstractmethod - def uri_tags(self, uri): + def uri_tags(self, uri: str) -> dict: """Return a dictionary containing the file metadata/tags. :param uri: The URI of the file - :type uri: str - - :rtype: dict """ @abstractmethod - def supported_extensions(self): + def supported_extensions(self) -> dict: """Return file extensions supported by the backend. Extensions will be categorized in 'audio' and 'video', optionally the backend can create others categories. e.g. {'audio': ['wav', 'mp3', ...], 'video': ['mp4', 'mov', ...]} - - :rtype: dict """ + + @abstractmethod + def media_waveform(self, media: Media) -> Waveform: + """Return a Waveform object capable of loading the waveform of the given media.""" diff --git a/lisp/backend/waveform.py b/lisp/backend/waveform.py new file mode 100644 index 000000000..d44819124 --- /dev/null +++ b/lisp/backend/waveform.py @@ -0,0 +1,135 @@ +# This file is part of Linux Show Player +# +# Copyright 2020 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . +import logging +import pickle +from abc import ABCMeta, abstractmethod +from os import path, makedirs + +from lisp import DEFAULT_CACHE_DIR +from lisp.application import Application +from lisp.core.signal import Signal +from lisp.core.util import file_hash + +logger = logging.getLogger(__name__) + + +class Waveform(metaclass=ABCMeta): + CACHE_VERSION = "1" + CACHE_DIR_NAME = "waveforms" + MAX_DECIMALS = 5 + + def __init__(self, uri, duration, max_samples=1280, cache=True): + self.ready = Signal() + self.rms_samples = [] + self.peak_samples = [] + self.max_samples = max_samples + self.duration = duration + self.uri = uri + self.failed = Signal() + self._hash = None + self.cache = cache + + def cache_dir(self): + cache_dir = Application().conf.get("cache.position", "") + if not cache_dir: + cache_dir = DEFAULT_CACHE_DIR + + return path.join(cache_dir, self.CACHE_DIR_NAME) + + def cache_path(self, refresh=True): + """Return the path of the file used to cache the waveform. + + The path name is based on the hash of the source file, which will be + calculated and saved the first time. + """ + scheme, _, file_path = self.uri.partition("://") + if scheme != "file": + return "" + + if not self._hash or refresh: + self._hash = file_hash( + file_path, digest_size=16, person=self.CACHE_VERSION.encode(), + ) + + return path.join( + path.dirname(file_path), self.cache_dir(), self._hash + ".waveform", + ) + + def load_waveform(self): + """ Load the waveform. + + If the waveform is ready returns True, False otherwise, in that case + the "ready" signal will be emitted when the processing is complete. + """ + if self.is_ready() or self._from_cache(): + # The waveform has already been loaded, or is in cache + return True + else: + # Delegate the actual work to concrete subclasses + return self._load_waveform() + + def is_ready(self): + return bool(self.peak_samples and self.rms_samples) + + def clear(self): + self.rms_samples = [] + self.peak_samples = [] + + @abstractmethod + def _load_waveform(self): + """ Implemented by subclasses. Load the waveform from the file. + + Should return True if the waveform is already available, False + if otherwise. + Once available the "ready" signal should be emitted. + """ + + def _from_cache(self): + """ Retrieve data from a cache file, if caching is enabled. """ + try: + cache_path = self.cache_path() + if self.cache and path.exists(cache_path): + with open(cache_path, "rb") as cache_file: + cache_data = pickle.load(cache_file) + if len(cache_data) >= 2: + self.peak_samples = cache_data[0] + self.rms_samples = cache_data[1] + + logger.debug( + f"Loaded waveform from the cache: {cache_path}" + ) + return True + except Exception: + pass + + return False + + def _to_cache(self): + """ Dump the waveform data to a file, if caching is enabled. """ + if self.cache: + cache_path = self.cache_path() + cache_dir = path.dirname(cache_path) + if cache_dir: + if not path.exists(cache_dir): + makedirs(cache_dir, exist_ok=True) + + with open(cache_path, "wb") as cache_file: + pickle.dump( + (self.peak_samples, self.rms_samples,), cache_file, + ) + + logger.debug(f"Dumped waveform to the cache: {cache_path}") diff --git a/lisp/core/class_based_registry.py b/lisp/core/class_based_registry.py index b0a90ddf7..d1108acd8 100644 --- a/lisp/core/class_based_registry.py +++ b/lisp/core/class_based_registry.py @@ -17,10 +17,10 @@ class ClassBasedRegistry: - """Allow to register/un-register and filter items using a reference class. + """Register and filter items using a reference class. The filter "check" whenever the filter-class is a subclass of - the one used to register the item or exactly the the same. + the one used to register the item or exactly the same. .. highlight:: @@ -36,7 +36,7 @@ def __init__(self): self._registry = {} def add(self, item, ref_class=object): - """Register a item for ref_class.""" + """Register an item for ref_class.""" if ref_class not in self._registry: self._registry[ref_class] = [item] elif item not in self._registry[ref_class]: diff --git a/lisp/core/has_properties.py b/lisp/core/has_properties.py index 08fd1afdf..41f251488 100644 --- a/lisp/core/has_properties.py +++ b/lisp/core/has_properties.py @@ -41,7 +41,7 @@ def __new__(mcls, name, bases, namespace, **kwargs): cls = super().__new__(mcls, name, bases, namespace, **kwargs) # Compute the set of property names - cls.__pro__ = { + cls._properties_ = { name for name, value in namespace.items() if isinstance(value, Property) @@ -50,7 +50,7 @@ def __new__(mcls, name, bases, namespace, **kwargs): # Update the set from "proper" base classes for base in bases: if isinstance(base, HasPropertiesMeta): - cls.__pro__.update(base.__pro__) + cls._properties_.update(base._properties_) return cls @@ -63,16 +63,16 @@ def __setattr__(cls, name, value): def __delattr__(cls, name): super().__delattr__(name) - if name in cls.__pro__: + if name in cls._properties_: cls._del_property(name) def _add_property(cls, name): - cls.__pro__.add(name) + cls._properties_.add(name) for subclass in cls.__subclasses__(): subclass._add_property(name) def _del_property(cls, name): - cls.__pro__.discard(name) + cls._properties_.discard(name) for subclass in cls.__subclasses__(): subclass._del_property(name) @@ -95,7 +95,7 @@ class DeepThought(HasProperties): def __init__(self): self.__changed_signals = {} - # Contains signals that are emitted after the associated property is + # Contain signals that are emitted after the associated property is # changed, the signal are create only when requested the first time. self.property_changed = Signal() @@ -124,7 +124,7 @@ def _properties_names(self): :rtype: set """ - return self.__class__.__pro__.copy() + return self.__class__._properties_.copy() def properties_defaults(self, filter=None): """Instance properties defaults. @@ -139,7 +139,7 @@ def properties_defaults(self, filter=None): defaults = {} for name in self.properties_names(filter=filter): - value = self._pro(name).default + value = self._property(name).default if isinstance(value, HasProperties): value = value.properties_defaults() @@ -161,10 +161,12 @@ def class_defaults(cls, filter=None): if callable(filter): return { name: getattr(cls, name).default - for name in filter(cls.__pro__.copy()) + for name in filter(cls._properties_.copy()) } else: - return {name: getattr(cls, name).default for name in cls.__pro__} + return { + name: getattr(cls, name).default for name in cls._properties_ + } def properties(self, defaults=True, filter=None): """ @@ -184,7 +186,7 @@ def properties(self, defaults=True, filter=None): value = value.properties(defaults=defaults, filter=filter) if defaults or value: properties[name] = value - elif defaults or value != self._pro(name).default: + elif defaults or value != self._property(name).default: properties[name] = value return properties @@ -233,8 +235,8 @@ def _emit_changed(self, name, value): except KeyError: pass - def _pro(self, name): - if name in self.__class__.__pro__: + def _property(self, name): + if name in self.__class__._properties_: return getattr(self.__class__, name) # TODO: PropertyError ?? @@ -245,15 +247,15 @@ def _pro(self, name): class HasInstanceProperties(HasProperties): # Fallback __init__ - __ipro__ = set() + _i_properties_ = set() def __init__(self): super().__init__() - self.__ipro__ = set() + self._i_properties_ = set() # Registry to keep track of instance-properties def _properties_names(self): - return super()._properties_names().union(self.__ipro__) + return super()._properties_names().union(self._i_properties_) def __getattribute__(self, name): attribute = super().__getattribute__(name) @@ -265,8 +267,8 @@ def __getattribute__(self, name): def __setattr__(self, name, value): if isinstance(value, InstanceProperty): super().__setattr__(name, value) - self.__ipro__.add(name) - elif name in self.__ipro__: + self._i_properties_.add(name) + elif name in self._i_properties_: property = super().__getattribute__(name) property.__pset__(value) self._emit_changed(name, value) @@ -275,10 +277,10 @@ def __setattr__(self, name, value): def __delattr__(self, name): super().__delattr__(name) - self.__ipro__.discard(name) + self._i_properties_.discard(name) - def _pro(self, name): - if name in self.__ipro__: + def _property(self, name): + if name in self._i_properties_: return self._getattribute(name) - return super()._pro(name) + return super()._property(name) diff --git a/lisp/core/util.py b/lisp/core/util.py index 34feecf0b..9849d67b2 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -16,6 +16,7 @@ # along with Linux Show Player. If not, see . import functools +import hashlib import re import socket from collections.abc import Mapping, MutableMapping @@ -121,12 +122,12 @@ def strtime(time, accurate=False): return f"{minutes:02}:{seconds:02}.00" -def compose_url(schema, host, port, path="/"): +def compose_url(scheme, host, port, path="/"): """Compose a URL.""" if not path.startswith("/"): path = "/" + path - return f"{schema}://{host}:{port}{path}" + return f"{scheme}://{host}:{port}{path}" def greatest_common_superclass(instances): @@ -256,6 +257,23 @@ def filter_live_properties(properties): return set(p for p in properties if not p.startswith("live_")) +def file_hash(path, block_size=65536, **hasher_kwargs): + """Hash a file using `hashlib.blake2b`, the file is read in chunks. + + :param path: the path of the file to hash + :param block_size: the size in bytes of the chunk to read + :param **hasher_kwargs: will be passed to the hash function + """ + h = hashlib.blake2b(**hasher_kwargs) + with open(path, "rb") as file_to_hash: + buffer = file_to_hash.read(block_size) + while buffer: + h.update(buffer) + buffer = file_to_hash.read(block_size) + + return h.hexdigest() + + class EqEnum(Enum): """Value-comparable Enum. diff --git a/lisp/default.json b/lisp/default.json index 342d76fec..2b380f9fb 100644 --- a/lisp/default.json +++ b/lisp/default.json @@ -1,11 +1,14 @@ { - "_version_": "0.6dev.15", + "_version_": "0.6dev.16", "cue": { "fadeAction": 3, "fadeActionType": "Linear", "interruptFade": 3, "interruptFadeType": "Linear" }, + "cache": { + "position": "" + }, "theme": { "theme": "Dark", "icons": "Numix" diff --git a/lisp/plugins/cache_manager/__init__.py b/lisp/plugins/cache_manager/__init__.py new file mode 100644 index 000000000..6f75038d1 --- /dev/null +++ b/lisp/plugins/cache_manager/__init__.py @@ -0,0 +1,2 @@ +# Auto-generated __init__.py for plugin +from .cache_manager import CacheManager diff --git a/lisp/plugins/cache_manager/cache_manager.py b/lisp/plugins/cache_manager/cache_manager.py new file mode 100644 index 000000000..bda1c306f --- /dev/null +++ b/lisp/plugins/cache_manager/cache_manager.py @@ -0,0 +1,160 @@ +import os +from pathlib import Path +from threading import Thread + +import humanize +from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt +from PyQt5.QtWidgets import ( + QVBoxLayout, + QGroupBox, + QPushButton, + QLabel, + QSpinBox, + QHBoxLayout, + QMessageBox, +) + +from lisp import DEFAULT_CACHE_DIR +from lisp.core.plugin import Plugin +from lisp.core.signal import Signal, Connection +from lisp.plugins import get_plugin +from lisp.ui.settings.app_configuration import AppConfigurationDialog +from lisp.ui.settings.pages import SettingsPage +from lisp.ui.ui_utils import translate + + +class CacheManager(Plugin): + Name = "CacheManager" + Authors = ("Francesco Ceruti",) + Description = "Utility to manage application cache" + + def __init__(self, app): + super().__init__(app) + # Register GStreamer settings widgets + AppConfigurationDialog.registerSettingsPage( + "plugins.cache_manager", CacheManagerSettings, CacheManager.Config + ) + + self.threshold_warning = Signal() + self.threshold_warning.connect( + self._show_threshold_warning, Connection.QtQueued + ) + Thread(target=self._check_cache_size).start() + + def _check_cache_size(self): + threshold = self.Config.get("sizeWarningThreshold", 0) * 1_000_000 + if threshold > 0: + cache_size = self.cache_size() + if cache_size > threshold: + self.threshold_warning.emit(threshold, cache_size) + + def _show_threshold_warning(self, threshold, _): + QMessageBox.warning( + self.app.window, + "Cache size", + translate( + "CacheManager", + "The cache has exceeded {}. Consider clean it.\n" + "You can do it in the application settings.", + ).format(humanize.naturalsize(threshold)), + ) + + def cache_root(self): + cache_dir = self.app.conf.get("cache.position", "") + if not cache_dir: + cache_dir = DEFAULT_CACHE_DIR + + return Path(cache_dir) + + def cache_size(self): + """This could take some time if we have a lot of files.""" + return sum( + entry.stat().st_size + for entry in self.cache_root().glob("**/*") + if entry.is_file() and not entry.is_symlink() + ) + + def purge(self): + cache_dir = self.cache_root() + if not cache_dir.exists(): + return + + for entry in cache_dir.iterdir(): + if not entry.is_symlink(): + if entry.is_dir(): + self._remove_dir_content(entry) + elif entry.is_file(): + os.remove(entry) + + def _remove_dir_content(self, path: Path): + for entry in path.iterdir(): + if entry.is_file() and not entry.is_symlink(): + os.remove(entry) + + +class CacheManagerSettings(SettingsPage): + Name = QT_TRANSLATE_NOOP("SettingsPageName", "Cache Manager") + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.setLayout(QVBoxLayout()) + self.layout().setAlignment(Qt.AlignTop) + + self.warningGroup = QGroupBox(self) + self.warningGroup.setLayout(QHBoxLayout()) + self.layout().addWidget(self.warningGroup) + + self.warningThresholdLabel = QLabel(self.warningGroup) + self.warningGroup.layout().addWidget(self.warningThresholdLabel) + + self.warningThresholdSpin = QSpinBox(self.warningGroup) + self.warningThresholdSpin.setRange(0, 10000) + self.warningGroup.layout().addWidget(self.warningThresholdSpin) + + self.warningGroup.layout().setStretch(0, 3) + self.warningGroup.layout().setStretch(1, 1) + + self.cleanGroup = QGroupBox(self) + self.cleanGroup.setLayout(QVBoxLayout()) + self.layout().addWidget(self.cleanGroup) + + self.currentSizeLabel = QLabel(self.cleanGroup) + self.currentSizeLabel.setAlignment(Qt.AlignCenter) + self.cleanGroup.layout().addWidget(self.currentSizeLabel) + + self.cleanButton = QPushButton(self) + self.cleanButton.clicked.connect(self.cleanCache) + self.cleanGroup.layout().addWidget(self.cleanButton) + + self.retranslateUi() + + self.cacheManager = get_plugin("CacheManager") + self.updateCacheSize() + + def retranslateUi(self): + self.warningGroup.setTitle( + translate("CacheManager", "Cache size warning") + ) + self.warningThresholdLabel.setText( + translate("CacheManager", "Warning threshold in MB (0 = disabled)") + ) + + self.cleanGroup.setTitle(translate("CacheManager", "Cache cleanup")) + self.cleanButton.setText( + translate("CacheManager", "Delete the cache content") + ) + + def loadSettings(self, settings): + self.warningThresholdSpin.setValue(settings.get("sizeWarningThreshold")) + + def getSettings(self): + return {"sizeWarningThreshold": self.warningThresholdSpin.value()} + + def updateCacheSize(self): + self.currentSizeLabel.setText( + humanize.naturalsize(self.cacheManager.cache_size()) + ) + + def cleanCache(self): + self.cacheManager.purge() + self.updateCacheSize() diff --git a/lisp/plugins/cache_manager/default.json b/lisp/plugins/cache_manager/default.json new file mode 100644 index 000000000..467ae6ea9 --- /dev/null +++ b/lisp/plugins/cache_manager/default.json @@ -0,0 +1,5 @@ +{ + "_version_": "0.4", + "_enabled_": true, + "sizeWarningThreshold": 500 +} \ No newline at end of file diff --git a/lisp/plugins/gst_backend/elements/uri_input.py b/lisp/plugins/gst_backend/elements/uri_input.py index b7d293fba..81244b7c1 100644 --- a/lisp/plugins/gst_backend/elements/uri_input.py +++ b/lisp/plugins/gst_backend/elements/uri_input.py @@ -45,7 +45,10 @@ def uri_split(uri): def uri_adapter(uri): scheme, path = uri_split(uri) - return scheme + "://" + urlquote(abs_path(path)) + if scheme == "file": + path = abs_path(path) + + return scheme + "://" + urlquote(path) class UriInput(GstSrcElement): @@ -104,8 +107,7 @@ def __uri_changed(self, uri): @async_in_pool(pool=ThreadPoolExecutor(1)) def __duration(self): - scheme, path = uri_split(self.uri) - self.duration = gst_uri_duration(scheme + "://" + abs_path(path)) + self.duration = gst_uri_duration(uri_adapter(self.uri)) def __session_moved(self, _): scheme, path_ = uri_split(self.decoder.get_property("uri")) diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index f60bda348..d92547bc3 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -42,6 +42,7 @@ gst_mime_types, gst_uri_duration, ) +from lisp.plugins.gst_backend.gst_waveform import GstWaveform from lisp.ui.settings.app_configuration import AppConfigurationDialog from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.ui_utils import translate, qfile_filters @@ -104,6 +105,14 @@ def supported_extensions(self): return extensions + def media_waveform(self, media): + uri = media.input_uri() + scheme, _, path = uri.partition("://") + if scheme == "file": + uri = scheme + "://" + self.app.session.abs_path(path) + + return GstWaveform(uri, media.duration) + def _add_uri_audio_cue(self): """Add audio MediaCue(s) form user-selected files""" # Get the last visited directory, or use the session-file location diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index 513fab690..adda2e8a3 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -24,6 +24,7 @@ from lisp.plugins.gst_backend import elements as gst_elements from lisp.plugins.gst_backend.gi_repository import Gst from lisp.plugins.gst_backend.gst_element import GstMediaElements +from lisp.plugins.gst_backend.gst_utils import GstError from lisp.ui.ui_utils import translate logger = logging.getLogger(__name__) @@ -50,12 +51,6 @@ def media_finalizer(pipeline, message_handler, media_elements): media_elements.clear() -class GstError(Exception): - """Used to wrap GStreamer debug messages for the logging system.""" - - pass - - class GstMedia(Media): """Media implementation based on the GStreamer framework.""" diff --git a/lisp/plugins/gst_backend/gst_utils.py b/lisp/plugins/gst_backend/gst_utils.py index c165fb897..7690a96b8 100644 --- a/lisp/plugins/gst_backend/gst_utils.py +++ b/lisp/plugins/gst_backend/gst_utils.py @@ -64,3 +64,7 @@ def parse_tag(gst_tag_list, tag_name, parsed_tags): gst_tag_list.foreach(parse_tag, parsed_tags) return parsed_tags + + +class GstError(Exception): + """Used to wrap GStreamer debug messages for the logging system.""" diff --git a/lisp/plugins/gst_backend/gst_waveform.py b/lisp/plugins/gst_backend/gst_waveform.py new file mode 100644 index 000000000..adf31dd98 --- /dev/null +++ b/lisp/plugins/gst_backend/gst_waveform.py @@ -0,0 +1,144 @@ +# This file is part of Linux Show Player +# +# Copyright 2020 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import audioop +import logging +from array import array + +from lisp.backend.waveform import Waveform +from .gi_repository import Gst +from .gst_utils import GstError + +logger = logging.getLogger(__name__) + + +class GstWaveform(Waveform): + """Waveform container.""" + + PIPELINE_TEMPLATE = ( + 'uridecodebin uri="{uri}" ' + "! audioconvert ! audio/x-raw, format=S16LE " + "! audiobuffersplit output-buffer-duration={sample_length} " + "! appsink name=app_sink emit-signals=true sync=false" + ) + MAX_PCM_VALUE = 32768 + + def __init__(self, uri, duration, max_samples=1280, cache=True): + super().__init__(uri, duration, max_samples=max_samples, cache=cache) + self._pipeline = None + self._bus_id = None + + self._temp_peak = array("i") + self._temp_rms = array("i") + + def _load_waveform(self): + # Make sure we start from zero + self._clear() + + # Create the pipeline with an appropriate buffer-size to control + # how many seconds of data we receive at each 'new-sample' event. + self._pipeline = Gst.parse_launch( + GstWaveform.PIPELINE_TEMPLATE.format( + uri=self.uri, + sample_length=f"{self.duration // 1000}/{self.max_samples}", + ) + ) + + # Connect to the app-sink + app_sink = self._pipeline.get_by_name("app_sink") + app_sink.connect("new-sample", self._on_new_sample, app_sink) + + # Watch the event bus + bus = self._pipeline.get_bus() + bus.add_signal_watch() + self._bus_id = bus.connect("message", self._on_bus_message) + + # Start the pipeline + self._pipeline.set_state(Gst.State.PLAYING) + + return False + + def clear(self): + super().clear() + self._clear() + + def _clear(self): + if self._pipeline is not None: + # Stop the pipeline + self._pipeline.set_state(Gst.State.NULL) + + if self._bus_id is not None: + # Disconnect from the bus + bus = self._pipeline.get_bus() + bus.remove_signal_watch() + bus.disconnect(self._bus_id) + + self._bus_id = None + self._pipeline = None + + self._temp_peak = array("i") + self._temp_rms = array("i") + + def _on_new_sample(self, sink, _): + """ Called by GStreamer every time we have a new sample ready. """ + buffer = sink.emit("pull-sample").get_buffer() + if buffer is not None: + # Get the all data from the buffer, as bytes + # We expect each audio sample to be 16bits signed integer + data_bytes = buffer.extract_dup(0, buffer.get_size()) + # Get max (peak-to-peak) of the samples + self._temp_peak.append(audioop.maxpp(data_bytes, 2)) + # Get rms of the samples + self._temp_rms.append(audioop.rms(data_bytes, 2)) + + return Gst.FlowReturn.OK + + def _on_bus_message(self, bus, message): + if message.type == Gst.MessageType.EOS: + self._eos() + elif message.type == Gst.MessageType.ERROR: + self._clear() + + error, debug = message.parse_error() + logger.warning( + f'Cannot generate waveform for "{self.uri}": {error.message}', + exc_info=GstError(debug), + ) + + def _eos(self): + """Called when the file has been processed.""" + self.peak_samples = [] + self.rms_samples = [] + + # Normalize data + for peak, rms in zip(self._temp_peak, self._temp_rms): + self.peak_samples.append( + round( + peak / GstWaveform.MAX_PCM_VALUE, GstWaveform.MAX_DECIMALS + ) + ) + self.rms_samples.append( + round(rms / GstWaveform.MAX_PCM_VALUE, GstWaveform.MAX_DECIMALS) + ) + + # Dump the data into a file (does nothing if caching is disabled) + self._to_cache() + # Clear leftovers + self._clear() + + # Notify that the waveform data are ready + self.ready.emit() diff --git a/lisp/plugins/list_layout/default.json b/lisp/plugins/list_layout/default.json index 2e2611f48..916d316cd 100644 --- a/lisp/plugins/list_layout/default.json +++ b/lisp/plugins/list_layout/default.json @@ -1,9 +1,10 @@ { - "_version_": "1.6.2", + "_version_": "1.7", "_enabled_": true, "show": { "dBMeters": true, "seekSliders": true, + "waveformSlider": true, "accurateTime": false, "indexColumn": true }, diff --git a/lisp/plugins/list_layout/playing_widgets.py b/lisp/plugins/list_layout/playing_widgets.py index 0fee09b43..033154038 100644 --- a/lisp/plugins/list_layout/playing_widgets.py +++ b/lisp/plugins/list_layout/playing_widgets.py @@ -25,6 +25,7 @@ QHBoxLayout, ) +from lisp.backend import get_backend from lisp.core.signal import Connection from lisp.core.util import strtime from lisp.cues.cue import CueAction @@ -33,6 +34,7 @@ from lisp.plugins.list_layout.control_buttons import CueControlButtons from lisp.ui.widgets import QClickSlider, DBMeter from lisp.ui.widgets.elidedlabel import ElidedLabel +from lisp.ui.widgets.waveform import WaveformSlider def get_running_widget(cue, config, **kwargs): @@ -177,9 +179,16 @@ def __init__(self, cue, config, **kwargs): super().__init__(cue, config, **kwargs) self._dbmeter_element = None - self.seekSlider = QClickSlider(self.gridLayoutWidget) - self.seekSlider.setOrientation(Qt.Horizontal) - self.seekSlider.setRange(0, cue.duration) + if config.get("show.waveformSlider", False): + self.waveform = get_backend().media_waveform(cue.media) + self.seekSlider = WaveformSlider( + self.waveform, parent=self.gridLayoutWidget + ) + else: + self.seekSlider = QClickSlider(self.gridLayoutWidget) + self.seekSlider.setOrientation(Qt.Horizontal) + self.seekSlider.setRange(0, cue.duration) + self.seekSlider.setFocusPolicy(Qt.NoFocus) self.seekSlider.sliderMoved.connect(self._seek) self.seekSlider.sliderJumped.connect(self._seek) diff --git a/lisp/plugins/osc/osc.py b/lisp/plugins/osc/osc.py index 538aa8e76..20149874c 100644 --- a/lisp/plugins/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -15,6 +15,7 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . + from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.core.plugin import Plugin diff --git a/lisp/ui/widgets/waveform.py b/lisp/ui/widgets/waveform.py new file mode 100644 index 000000000..cd96776aa --- /dev/null +++ b/lisp/ui/widgets/waveform.py @@ -0,0 +1,199 @@ +from math import floor, ceil + +from PyQt5.QtCore import QLineF, pyqtSignal +from PyQt5.QtGui import QPainter, QPen, QColor, QBrush +from PyQt5.QtWidgets import QWidget + +from lisp.core.signal import Connection + + +class WaveformWidget(QWidget): + def __init__(self, waveform, **kwargs): + super().__init__(**kwargs) + self._waveform = waveform + self._maximum = self._waveform.duration + self._valueToPx = 0 + self._value = 0 + self._lastDrawnValue = 0 + + self.elapsedPeakColor = QColor(75, 154, 250) + self.elapsedRmsColor = QColor(153, 199, 255) + self.remainsPeakColor = QColor(90, 90, 90) + self.remainsRmsColor = QColor(130, 130, 130) + + # Watch for the waveform to be ready + self._waveform.ready.connect(self._ready, Connection.QtQueued) + # Load the waveform + self._waveform.load_waveform() + + def _ready(self): + self.setMaximum(self._waveform.duration) + self.update() + + def maximum(self): + return self._maximum + + def setMaximum(self, maximum): + self._maximum = maximum + self._valueToPx = self._maximum / self.width() + self.update() + + def value(self): + return self._value + + def setValue(self, value): + self._value = min(value, self._maximum) + + if self.isVisible(): + # Repaint only if we have new pixels to draw + if self._value >= floor(self._lastDrawnValue + self._valueToPx): + x = self._lastDrawnValue / self._valueToPx + width = (self._value - self._lastDrawnValue) / self._valueToPx + # Repaint only the changed area + self.update(floor(x), 0, ceil(width), self.height()) + elif self._value <= ceil(self._lastDrawnValue - self._valueToPx): + x = self._value / self._valueToPx + width = (self._lastDrawnValue - self._value) / self._valueToPx + # Repaint only the changed area + self.update(floor(x), 0, ceil(width) + 1, self.height()) + + def resizeEvent(self, event): + self._valueToPx = self._maximum / self.width() + + def paintEvent(self, event): + halfHeight = self.height() / 2 + + painter = QPainter() + painter.begin(self) + + pen = QPen() + pen.setWidth(1) + painter.setPen(pen) + + if self._valueToPx and self._waveform.is_ready(): + peakSamples = self._waveform.peak_samples + rmsSamples = self._waveform.rms_samples + samplesToPx = len(peakSamples) / self.width() + elapsedWidth = floor(self._value / self._valueToPx) + + peakElapsedLines = [] + peakRemainsLines = [] + rmsElapsedLines = [] + rmsRemainsLines = [] + for x in range(event.rect().x(), self.rect().right()): + # Calculate re-sample interval + s0 = floor(x * samplesToPx) + s1 = ceil(x * samplesToPx + samplesToPx) + # Re-sample the values + peak = max(peakSamples[s0:s1]) * halfHeight + rms = (sum(rmsSamples[s0:s1]) / samplesToPx) * halfHeight + + # Create lines to draw + peakLine = QLineF(x, halfHeight + peak, x, halfHeight - peak) + rmsLine = QLineF(x, halfHeight + rms, x, halfHeight - rms) + + # Decide if elapsed or remaining + if x <= elapsedWidth: + peakElapsedLines.append(peakLine) + rmsElapsedLines.append(rmsLine) + else: + peakRemainsLines.append(peakLine) + rmsRemainsLines.append(rmsLine) + + # Draw peak for elapsed + if peakElapsedLines: + pen.setColor(self.elapsedPeakColor) + painter.setPen(pen) + painter.drawLines(peakElapsedLines) + + # Draw rms for elapsed + if rmsElapsedLines: + pen.setColor(self.elapsedRmsColor) + painter.setPen(pen) + painter.drawLines(rmsElapsedLines) + + # Draw peak for remaining + if peakRemainsLines: + pen.setColor(self.remainsPeakColor) + painter.setPen(pen) + painter.drawLines(peakRemainsLines) + + # Draw rms for remaining + if rmsRemainsLines: + pen.setColor(self.remainsRmsColor) + painter.setPen(pen) + painter.drawLines(rmsRemainsLines) + + # Remember the last drawn item + self._lastDrawnValue = self._value + else: + # Draw a single line in the middle + pen.setColor(self.remainsRmsColor) + painter.setPen(pen) + painter.drawLine(0, halfHeight, self.width(), halfHeight) + + painter.end() + + +class WaveformSlider(WaveformWidget): + """ Implement an API similar to a QAbstractSlider. """ + + sliderMoved = pyqtSignal(int) + sliderJumped = pyqtSignal(int) + + def __init__(self, waveform, **kwargs): + super().__init__(waveform, **kwargs) + self.setMouseTracking(True) + + self._mouseDown = False + self._lastPosition = -1 + + self.backgroundColor = QColor(32, 32, 32) + self.seekIndicatorColor = QColor(255, 0, 0) + + def leaveEvent(self, event): + self._lastPosition = -1 + + def mouseMoveEvent(self, event): + self._lastPosition = event.x() + self.update() + + if self._mouseDown: + self.sliderMoved.emit(round(self._lastPosition * self._valueToPx)) + + def mousePressEvent(self, event): + self._mouseDown = True + + def mouseReleaseEvent(self, event): + self._mouseDown = False + self.sliderJumped.emit(round(event.x() * self._valueToPx)) + + def paintEvent(self, event): + # Draw background + painter = QPainter() + painter.begin(self) + painter.setRenderHint(QPainter.Antialiasing) + + pen = QPen(QColor(0, 0, 0, 0)) + painter.setPen(pen) + painter.setBrush(QBrush(self.backgroundColor)) + painter.drawRoundedRect(self.rect(), 6, 6) + + painter.end() + + # Draw the waveform + super().paintEvent(event) + + # If necessary (mouse-over) draw the seek indicator + if self._lastPosition >= 0: + painter.begin(self) + + pen.setWidth(1) + pen.setColor(self.seekIndicatorColor) + painter.setPen(pen) + + painter.drawLine( + self._lastPosition, 0, self._lastPosition, self.height() + ) + + painter.end() From 332cc4fef4b272c8f32a3ae6ee68d4e3c3cf0657 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 4 Apr 2020 14:08:40 +0200 Subject: [PATCH 226/333] Add a new SessionURI class to simplify URI manipulation. Add: new property type "GstURIProperty" Update: various functions/methods to use the new SessionURI objects instead of strings Update: other minor improvements --- lisp/application.py | 8 +- lisp/backend/__init__.py | 16 ++-- lisp/backend/audio_utils.py | 18 ++-- lisp/backend/backend.py | 4 +- lisp/backend/media.py | 42 ++++------ lisp/backend/waveform.py | 50 ++++++----- lisp/core/properties.py | 2 +- lisp/{ => core}/session.py | 0 lisp/core/session_uri.py | 82 +++++++++++++++++++ lisp/core/signal.py | 5 ++ lisp/default.json | 4 +- .../plugins/gst_backend/elements/uri_input.py | 73 ++++++----------- lisp/plugins/gst_backend/gst_backend.py | 14 ++-- lisp/plugins/gst_backend/gst_element.py | 32 +++++++- lisp/plugins/gst_backend/gst_media.py | 2 +- lisp/plugins/gst_backend/gst_utils.py | 19 +++-- lisp/plugins/gst_backend/gst_waveform.py | 16 ++-- lisp/plugins/media_info/media_info.py | 9 +- lisp/plugins/replay_gain/replay_gain.py | 12 +-- 19 files changed, 238 insertions(+), 170 deletions(-) rename lisp/{ => core}/session.py (100%) create mode 100644 lisp/core/session_uri.py diff --git a/lisp/application.py b/lisp/application.py index 1f3a96dd5..c162f83fb 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -24,6 +24,7 @@ from lisp import layout from lisp.command.stack import CommandsStack from lisp.core.configuration import DummyConfiguration +from lisp.core.session import Session from lisp.core.signal import Signal from lisp.core.singleton import Singleton from lisp.core.util import filter_live_properties @@ -31,7 +32,6 @@ from lisp.cues.cue_factory import CueFactory from lisp.cues.cue_model import CueModel from lisp.cues.media_cue import MediaCue -from lisp.session import Session from lisp.ui.layoutselect import LayoutSelect from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.app_configuration import AppConfigurationDialog @@ -188,9 +188,11 @@ def __save_to_file(self, session_file): # Write to a file the json-encoded session with open(session_file, mode="w", encoding="utf-8") as file: if self.conf.get("session.minSave", False): - file.write(json.dumps(session_dict, separators=(",", ":"))) + dump_options = {"separators": (",", ":")} else: - file.write(json.dumps(session_dict, sort_keys=True, indent=4)) + dump_options = {"sort_keys": True, "indent": 4} + + file.write(json.dumps(session_dict, **dump_options)) # Save last session path self.conf.set("session.lastPath", dirname(session_file)) diff --git a/lisp/backend/__init__.py b/lisp/backend/__init__.py index dee4b6603..90db380af 100644 --- a/lisp/backend/__init__.py +++ b/lisp/backend/__init__.py @@ -16,17 +16,13 @@ # along with Linux Show Player. If not, see . -# In the current state set/get_backend are quite useless -__backend = None +from .backend import Backend -def set_backend(backend): - global __backend - __backend = backend +def set_backend(backend: Backend): + global CurrentBackend + CurrentBackend = backend -def get_backend(): - """ - :rtype: lisp.backend.backend.Backend - """ - return __backend +def get_backend() -> Backend: + return CurrentBackend diff --git a/lisp/backend/audio_utils.py b/lisp/backend/audio_utils.py index 666ea7b35..873e5adb7 100644 --- a/lisp/backend/audio_utils.py +++ b/lisp/backend/audio_utils.py @@ -17,10 +17,10 @@ import aifc import math -import urllib.parse import wave # Decibel value to be considered -inf + MIN_VOLUME_DB = -144 # Linear value of MIN_VOLUME_DB MIN_VOLUME = 6.309_573_444_801_93e-08 @@ -84,15 +84,11 @@ def python_duration(path, sound_module): return duration -def uri_duration(uri): - """Return the audio-file duration, using the given uri""" - scheme, path = uri.split("://") - path = urllib.parse.unquote(path) - - if scheme == "file": - for mod in [wave, aifc]: - duration = python_duration(path, mod) - if duration > 0: - return duration +def audio_file_duration(path: str): + """Return the audio-file duration, using the given file path""" + for mod in [wave, aifc]: + duration = python_duration(path, mod) + if duration > 0: + return duration return 0 diff --git a/lisp/backend/backend.py b/lisp/backend/backend.py index 511c9cc1e..f4c418c4d 100644 --- a/lisp/backend/backend.py +++ b/lisp/backend/backend.py @@ -17,8 +17,8 @@ from abc import abstractmethod, ABCMeta -from lisp.backend.media import Media -from lisp.backend.waveform import Waveform +from .media import Media +from .waveform import Waveform class Backend(metaclass=ABCMeta): diff --git a/lisp/backend/media.py b/lisp/backend/media.py index d7800823e..6e3e2810a 100644 --- a/lisp/backend/media.py +++ b/lisp/backend/media.py @@ -17,9 +17,12 @@ from abc import abstractmethod from enum import Enum +from typing import Union +from lisp.backend.media_element import MediaElement from lisp.core.has_properties import HasProperties from lisp.core.properties import Property +from lisp.core.session_uri import SessionURI from lisp.core.signal import Signal @@ -36,10 +39,10 @@ class Media(HasProperties): """Interface for Media objects. Media(s) provides control over multimedia contents. - To control various parameter of the media, MediaElement(s) should be used. + MediaElement(s) should be used to control the media parameter. - .. note:: - The play/stop/pause functions must be non-blocking functions. + functions such as play/stop/pause must be non-blocking, communications + should be done via signals. """ loop = Property(default=0) @@ -77,35 +80,23 @@ def __init__(self): @property @abstractmethod - def state(self): - """ - :return: the media current state - :rtype: MediaState - """ + def state(self) -> MediaState: + """Return the media current state.""" @abstractmethod - def current_time(self): - """ - :return: the current playback time in milliseconds or 0 - :rtype: int - """ + def current_time(self) -> int: + """Return he current playback time in milliseconds or 0.""" @abstractmethod - def element(self, class_name): - """ - :param class_name: The element class-name - :type class_name: str + def element(self, class_name: str) -> Union[MediaElement, type(None)]: + """Return the element with the specified class-name or None - :return: The element with the specified class-name or None - :rtype: lisp.core.base.media_element.MediaElement + :param class_name: The element class-name """ @abstractmethod - def input_uri(self): - """ - :return: The media input uri (e.g. "file:///home/..."), or None - :rtype: str - """ + def input_uri(self) -> Union[SessionURI, type(None)]: + """Return the media SessionURI, or None.""" @abstractmethod def pause(self): @@ -116,11 +107,10 @@ def play(self): """The media go in PLAYING state (starts the playback).""" @abstractmethod - def seek(self, position): + def seek(self, position: int): """Seek to the specified point. :param position: The position to be reached in milliseconds - :type position: int """ @abstractmethod diff --git a/lisp/backend/waveform.py b/lisp/backend/waveform.py index d44819124..8bbc57d3c 100644 --- a/lisp/backend/waveform.py +++ b/lisp/backend/waveform.py @@ -14,13 +14,14 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . + import logging import pickle from abc import ABCMeta, abstractmethod from os import path, makedirs from lisp import DEFAULT_CACHE_DIR -from lisp.application import Application +from lisp.core.session_uri import SessionURI from lisp.core.signal import Signal from lisp.core.util import file_hash @@ -32,23 +33,30 @@ class Waveform(metaclass=ABCMeta): CACHE_DIR_NAME = "waveforms" MAX_DECIMALS = 5 - def __init__(self, uri, duration, max_samples=1280, cache=True): - self.ready = Signal() - self.rms_samples = [] - self.peak_samples = [] + def __init__( + self, + uri: SessionURI, + duration, + max_samples=2560, + enable_cache=True, + cache_dir=None, + ): + if not cache_dir: + cache_dir = DEFAULT_CACHE_DIR + self.max_samples = max_samples self.duration = duration - self.uri = uri - self.failed = Signal() + + self._uri = uri self._hash = None - self.cache = cache + self._cache_dir = path.join(cache_dir, self.CACHE_DIR_NAME) + self._enable_cache = enable_cache - def cache_dir(self): - cache_dir = Application().conf.get("cache.position", "") - if not cache_dir: - cache_dir = DEFAULT_CACHE_DIR + self.rms_samples = [] + self.peak_samples = [] - return path.join(cache_dir, self.CACHE_DIR_NAME) + self.ready = Signal() + self.failed = Signal() def cache_path(self, refresh=True): """Return the path of the file used to cache the waveform. @@ -56,17 +64,17 @@ def cache_path(self, refresh=True): The path name is based on the hash of the source file, which will be calculated and saved the first time. """ - scheme, _, file_path = self.uri.partition("://") - if scheme != "file": + if not self._uri.is_local: return "" + file_path = self._uri.absolute_path if not self._hash or refresh: self._hash = file_hash( file_path, digest_size=16, person=self.CACHE_VERSION.encode(), ) return path.join( - path.dirname(file_path), self.cache_dir(), self._hash + ".waveform", + path.dirname(file_path), self._cache_dir, self._hash + ".waveform", ) def load_waveform(self): @@ -102,7 +110,7 @@ def _from_cache(self): """ Retrieve data from a cache file, if caching is enabled. """ try: cache_path = self.cache_path() - if self.cache and path.exists(cache_path): + if self._enable_cache and path.exists(cache_path): with open(cache_path, "rb") as cache_file: cache_data = pickle.load(cache_file) if len(cache_data) >= 2: @@ -119,11 +127,11 @@ def _from_cache(self): return False def _to_cache(self): - """ Dump the waveform data to a file, if caching is enabled. """ - if self.cache: + """Dump the waveform data to a file, if caching is enabled.""" + if self._enable_cache: cache_path = self.cache_path() - cache_dir = path.dirname(cache_path) - if cache_dir: + if cache_path: + cache_dir = path.dirname(cache_path) if not path.exists(cache_dir): makedirs(cache_dir, exist_ok=True) diff --git a/lisp/core/properties.py b/lisp/core/properties.py index cf1b23fdd..718c67011 100644 --- a/lisp/core/properties.py +++ b/lisp/core/properties.py @@ -62,7 +62,7 @@ class WriteOnceProperty(Property): """Property that can be modified only once. Obviously this is not really "write-once", but when used as normal attribute - will ignore any change when the stored value is different than the default. + will ignore any change when the stored value is different from the default. """ def __set__(self, instance, value): diff --git a/lisp/session.py b/lisp/core/session.py similarity index 100% rename from lisp/session.py rename to lisp/core/session.py diff --git a/lisp/core/session_uri.py b/lisp/core/session_uri.py new file mode 100644 index 000000000..ef3912ed6 --- /dev/null +++ b/lisp/core/session_uri.py @@ -0,0 +1,82 @@ +# This file is part of Linux Show Player +# +# Copyright 2020 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from functools import lru_cache +from urllib.parse import urlsplit, urlunsplit, quote, unquote + + +class SessionURI: + LOCAL_SCHEMES = ("", "file") + + def __init__(self, uri: str = ""): + split = urlsplit(uri) + + self._is_local = self.is_local_scheme(split.scheme) + if self._is_local: + # If the path doesn't start with "/" the first part is interpreted + # as "netloc", older session have this problem. + rel_path = split.netloc + split.path + # We need the absolute path + path = self.path_to_absolute(rel_path) + # For local schemes, we assume the URI path is unquoted. + self._uri = urlunsplit(("file", "", quote(path), "", "")) + else: + self._uri = uri + + @property + def relative_path(self): + """The path relative to the session file. Make sense for local files.""" + path = unquote(urlsplit(self._uri).path) + return self.path_to_relative(path) + + @property + @lru_cache + def absolute_path(self): + """Unquoted "path" component of the URI.""" + # We can cache this, the absolute path doesn't change + return unquote(urlsplit(self._uri).path) + + @property + def uri(self): + """The raw URI string.""" + return self._uri + + @property + def unquoted_uri(self): + """The URI string, unquoted.""" + return unquote(self._uri) + + @property + def is_local(self): + """True if the URI point to a local file.""" + return self._is_local + + @classmethod + def path_to_relative(cls, path): + from lisp.application import Application + + return Application().session.rel_path(path) + + @classmethod + def path_to_absolute(cls, path): + from lisp.application import Application + + return Application().session.abs_path(path) + + @classmethod + def is_local_scheme(cls, scheme): + return scheme in cls.LOCAL_SCHEMES diff --git a/lisp/core/signal.py b/lisp/core/signal.py index cdfca3a4b..1b38188e9 100644 --- a/lisp/core/signal.py +++ b/lisp/core/signal.py @@ -81,6 +81,11 @@ def _expired(self, _): if self._callback is not None: self._callback(self._slot_id) + def __str__(self): + return ( + f"{self.__class__.__qualname__}: {self._reference().__qualname__}" + ) + class AsyncSlot(Slot): """Asynchronous slot, NOT queued, any call is performed in a new thread.""" diff --git a/lisp/default.json b/lisp/default.json index 2b380f9fb..2612a763f 100644 --- a/lisp/default.json +++ b/lisp/default.json @@ -1,5 +1,5 @@ { - "_version_": "0.6dev.16", + "_version_": "0.6dev.17", "cue": { "fadeAction": 3, "fadeActionType": "Linear", @@ -15,7 +15,7 @@ }, "locale": "", "session": { - "minSave": true, + "minSave": false, "lastPath": "" }, "logging": { diff --git a/lisp/plugins/gst_backend/elements/uri_input.py b/lisp/plugins/gst_backend/elements/uri_input.py index 81244b7c1..6041495df 100644 --- a/lisp/plugins/gst_backend/elements/uri_input.py +++ b/lisp/plugins/gst_backend/elements/uri_input.py @@ -16,47 +16,29 @@ # along with Linux Show Player. If not, see . from concurrent.futures import ThreadPoolExecutor -from os import path -from urllib.parse import quote as urlquote, unquote as urlunquote +from pathlib import Path from PyQt5.QtCore import QT_TRANSLATE_NOOP -from lisp.application import Application from lisp.backend.media_element import MediaType from lisp.core.decorators import async_in_pool from lisp.core.properties import Property +from lisp.core.session_uri import SessionURI from lisp.plugins.gst_backend.gi_repository import Gst -from lisp.plugins.gst_backend.gst_element import GstProperty, GstSrcElement +from lisp.plugins.gst_backend.gst_element import ( + GstProperty, + GstSrcElement, + GstURIProperty, +) from lisp.plugins.gst_backend.gst_utils import gst_uri_duration -def abs_path(path_): - return Application().session.abs_path(path_) - - -def uri_split(uri): - try: - scheme, path = uri.split("://") - except ValueError: - scheme = path = "" - - return scheme, path - - -def uri_adapter(uri): - scheme, path = uri_split(uri) - if scheme == "file": - path = abs_path(path) - - return scheme + "://" + urlquote(path) - - class UriInput(GstSrcElement): MediaType = MediaType.Audio Name = QT_TRANSLATE_NOOP("MediaElementName", "URI Input") _mtime = Property(default=-1) - uri = GstProperty("decoder", "uri", default="", adapter=uri_adapter) + uri = GstURIProperty("decoder", "uri") download = GstProperty("decoder", "download", default=False) buffer_size = GstProperty("decoder", "buffer-size", default=-1) use_buffering = GstProperty("decoder", "use-buffering", default=False) @@ -72,12 +54,9 @@ def __init__(self, pipeline): self.pipeline.add(self.audio_convert) self.changed("uri").connect(self.__uri_changed) - Application().session.changed("session_file").connect( - self.__session_moved - ) - def input_uri(self): - return self.uri + def input_uri(self) -> SessionURI: + return SessionURI(self.uri) def dispose(self): self.decoder.disconnect(self._handler) @@ -89,28 +68,22 @@ def __on_pad_added(self, *args): self.decoder.link(self.audio_convert) def __uri_changed(self, uri): - # Save the current mtime (file flag for last-change time) - mtime = self._mtime - - # If the uri is a file update the current mtime - scheme, path_ = uri_split(uri) - if scheme == "file": - path_ = abs_path(path_) - if path.exists(path_): - self._mtime = path.getmtime(path_) + uri = SessionURI(uri) + + old_mtime = self._mtime + if uri.is_local: + # If the uri is a file update the current mtime + path = Path(uri.absolute_path) + if path.exists(): + self._mtime = path.stat().st_mtime else: - mtime = None + old_mtime = None + self._mtime = -1 - # If something is changed or the duration is invalid - if mtime != self._mtime or self.duration < 0: + # If the file changed, or the duration is invalid + if old_mtime != self._mtime or self.duration < 0: self.__duration() @async_in_pool(pool=ThreadPoolExecutor(1)) def __duration(self): - self.duration = gst_uri_duration(uri_adapter(self.uri)) - - def __session_moved(self, _): - scheme, path_ = uri_split(self.decoder.get_property("uri")) - if scheme == "file": - path_ = urlunquote(path_) - self.uri = "file://" + Application().session.rel_path(path_) + self.duration = gst_uri_duration(self.input_uri()) diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index d92547bc3..ecee0f7c1 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -106,12 +106,11 @@ def supported_extensions(self): return extensions def media_waveform(self, media): - uri = media.input_uri() - scheme, _, path = uri.partition("://") - if scheme == "file": - uri = scheme + "://" + self.app.session.abs_path(path) - - return GstWaveform(uri, media.duration) + return GstWaveform( + media.input_uri(), + media.duration, + cache_dir=self.app.conf.get("cache.position", ""), + ) def _add_uri_audio_cue(self): """Add audio MediaCue(s) form user-selected files""" @@ -163,8 +162,7 @@ def add_cue_from_files(self, files): cues = [] for index, file in enumerate(files, start_index): - file = self.app.session.rel_path(file) - cue = factory(uri="file://" + file) + cue = factory(uri=file) # Use the filename without extension as cue name cue.name = os.path.splitext(os.path.basename(file))[0] # Set the index (if something is selected) diff --git a/lisp/plugins/gst_backend/gst_element.py b/lisp/plugins/gst_backend/gst_element.py index 375597830..9f3c16fc1 100644 --- a/lisp/plugins/gst_backend/gst_element.py +++ b/lisp/plugins/gst_backend/gst_element.py @@ -15,9 +15,12 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +from typing import Union + from lisp.backend.media_element import MediaElement, ElementType from lisp.core.has_properties import HasInstanceProperties from lisp.core.properties import Property, InstanceProperty +from lisp.core.session_uri import SessionURI from lisp.core.util import typename @@ -41,6 +44,33 @@ def __set__(self, instance, value): element.set_property(self.property_name, value) +class GstURIProperty(GstProperty): + def __init__(self, element_name, property_name, **meta): + super().__init__( + element_name, + property_name, + default="", + adapter=self._adepter, + **meta, + ) + + def __set__(self, instance, value): + super().__set__(instance, SessionURI(value)) + + def __get__(self, instance, owner=None): + value = super().__get__(instance, owner) + if isinstance(value, SessionURI): + if value.is_local: + return value.relative_path + else: + return value.uri + + return value + + def _adepter(self, value: SessionURI): + return value.uri + + class GstLiveProperty(Property): def __init__(self, element_name, property_name, adapter=None, **meta): super().__init__(**meta) @@ -118,7 +148,7 @@ class GstSrcElement(GstMediaElement): duration = Property(default=0) - def input_uri(self): + def input_uri(self) -> Union[SessionURI, type(None)]: """Return the input uri or None""" return None diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index adda2e8a3..2f19c80e2 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -145,7 +145,7 @@ def element(self, class_name): def input_uri(self): try: return self.elements[0].input_uri() - except Exception: + except (IndexError, AttributeError): pass def update_properties(self, properties): diff --git a/lisp/plugins/gst_backend/gst_utils.py b/lisp/plugins/gst_backend/gst_utils.py index 7690a96b8..0ada8bf95 100644 --- a/lisp/plugins/gst_backend/gst_utils.py +++ b/lisp/plugins/gst_backend/gst_utils.py @@ -15,16 +15,20 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see -from urllib.parse import unquote, quote - -from lisp.backend.audio_utils import uri_duration +from lisp.backend.audio_utils import audio_file_duration +from lisp.core.session_uri import SessionURI from lisp.plugins.gst_backend.gi_repository import Gst, GstPbutils -def gst_uri_duration(uri): +def gst_uri_duration(uri: SessionURI): # First try to use the base implementation, because it's faster - duration = uri_duration(uri) + duration = 0 + if uri.is_local: + duration = audio_file_duration(uri.absolute_path) + try: + # Fallback to GStreamer discoverer + # TODO: we can probabbly make this faster see https://github.com/mopidy/mopidy/blob/develop/mopidy/audio/scan.py if duration <= 0: duration = gst_uri_metadata(uri).get_duration() // Gst.MSECOND finally: @@ -42,12 +46,11 @@ def gst_mime_types(): yield mime, extensions -def gst_uri_metadata(uri): +def gst_uri_metadata(uri: SessionURI): """Discover media-file metadata using GStreamer.""" try: discoverer = GstPbutils.Discoverer() - scheme, _, path = uri.partition("://") - return discoverer.discover_uri(scheme + "://" + quote(unquote(path))) + return discoverer.discover_uri(uri.uri) except Exception: pass diff --git a/lisp/plugins/gst_backend/gst_waveform.py b/lisp/plugins/gst_backend/gst_waveform.py index adf31dd98..fe8b04e79 100644 --- a/lisp/plugins/gst_backend/gst_waveform.py +++ b/lisp/plugins/gst_backend/gst_waveform.py @@ -37,8 +37,8 @@ class GstWaveform(Waveform): ) MAX_PCM_VALUE = 32768 - def __init__(self, uri, duration, max_samples=1280, cache=True): - super().__init__(uri, duration, max_samples=max_samples, cache=cache) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self._pipeline = None self._bus_id = None @@ -52,8 +52,8 @@ def _load_waveform(self): # Create the pipeline with an appropriate buffer-size to control # how many seconds of data we receive at each 'new-sample' event. self._pipeline = Gst.parse_launch( - GstWaveform.PIPELINE_TEMPLATE.format( - uri=self.uri, + self.PIPELINE_TEMPLATE.format( + uri=self._uri.uri, sample_length=f"{self.duration // 1000}/{self.max_samples}", ) ) @@ -115,7 +115,7 @@ def _on_bus_message(self, bus, message): error, debug = message.parse_error() logger.warning( - f'Cannot generate waveform for "{self.uri}": {error.message}', + f'Cannot generate waveform for "{self._uri.unquoted_uri}": {error.message}', exc_info=GstError(debug), ) @@ -127,12 +127,10 @@ def _eos(self): # Normalize data for peak, rms in zip(self._temp_peak, self._temp_rms): self.peak_samples.append( - round( - peak / GstWaveform.MAX_PCM_VALUE, GstWaveform.MAX_DECIMALS - ) + round(peak / self.MAX_PCM_VALUE, self.MAX_DECIMALS) ) self.rms_samples.append( - round(rms / GstWaveform.MAX_PCM_VALUE, GstWaveform.MAX_DECIMALS) + round(rms / self.MAX_PCM_VALUE, self.MAX_DECIMALS) ) # Dump the data into a file (does nothing if caching is disabled) diff --git a/lisp/plugins/media_info/media_info.py b/lisp/plugins/media_info/media_info.py index 5cc81daa0..716c56ced 100644 --- a/lisp/plugins/media_info/media_info.py +++ b/lisp/plugins/media_info/media_info.py @@ -63,15 +63,8 @@ def __init__(self, app): CueLayout.CuesMenu.add(self.cue_action_group, MediaCue) - def _gst_uri_metadata(self, uri): - if uri is not None: - if uri.startswith("file://"): - uri = "file://" + self.app.session.abs_path(uri[7:]) - - return gst_uri_metadata(uri) - def _show_info(self, cue): - gst_meta = self._gst_uri_metadata(cue.media.input_uri()) + gst_meta = gst_uri_metadata(cue.media.input_uri()) if gst_meta is None: return QMessageBox.warning( diff --git a/lisp/plugins/replay_gain/replay_gain.py b/lisp/plugins/replay_gain/replay_gain.py index 756b24d76..33c4bf166 100644 --- a/lisp/plugins/replay_gain/replay_gain.py +++ b/lisp/plugins/replay_gain/replay_gain.py @@ -77,21 +77,15 @@ def gain(self): else: cues = self.app.cue_model.filter(MediaCue) - # Extract single uri(s), this way if a uri is used in more than - # one place, the gain is only calculated once. + # Extract single uri(s), this way if one uri is used in more than + # one place, the gain will be calculated only once. files = {} for cue in cues: media = cue.media uri = media.input_uri() if uri is not None: - if uri[:7] == "file://": - uri = "file://" + self.app.session.abs_path(uri[7:]) - - if uri not in files: - files[uri] = [media] - else: - files[uri].append(media) + files.setdefault(uri.uri, []).append(media) # Gain (main) thread self._gain_thread = GainMainThread( From d41060d4149a16593de66bf0be98450bea04f1d6 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 8 Apr 2020 12:14:33 +0200 Subject: [PATCH 227/333] UI improvements, minor fixes, updated translations files --- Pipfile | 2 +- Pipfile.lock | 50 +- lisp/i18n/ts/en/action_cues.ts | 2 +- lisp/i18n/ts/en/cache_manager.ts | 34 ++ lisp/i18n/ts/en/cart_layout.ts | 18 +- lisp/i18n/ts/en/controller.ts | 113 ++-- lisp/i18n/ts/en/gst_backend.ts | 10 +- lisp/i18n/ts/en/lisp.ts | 563 ++++++++++++-------- lisp/i18n/ts/en/list_layout.ts | 72 +-- lisp/i18n/ts/en/media_info.ts | 16 +- lisp/i18n/ts/en/midi.ts | 105 ++-- lisp/i18n/ts/en/network.ts | 21 +- lisp/i18n/ts/en/osc.ts | 2 +- lisp/i18n/ts/en/presets.ts | 30 +- lisp/i18n/ts/en/replay_gain.ts | 18 +- lisp/i18n/ts/en/synchronizer.ts | 4 +- lisp/plugins/cache_manager/cache_manager.py | 2 +- lisp/plugins/controller/protocols/midi.py | 2 +- lisp/plugins/controller/protocols/osc.py | 274 ++++++---- lisp/plugins/osc/osc_delegate.py | 75 +-- lisp/ui/qdelegates.py | 4 +- 21 files changed, 832 insertions(+), 585 deletions(-) create mode 100644 lisp/i18n/ts/en/cache_manager.ts diff --git a/Pipfile b/Pipfile index 5ab8eae5b..8a5fd9432 100644 --- a/Pipfile +++ b/Pipfile @@ -16,7 +16,7 @@ python-rtmidi = "~=1.1" requests = "~=2.20" sortedcontainers = "~=2.0" pyalsa = {editable = true,git = "https://github.com/alsa-project/alsa-python.git",ref = "v1.1.6"} -humanize = "*" +humanize = "~=2.2" [dev-packages] html5lib = "*" diff --git a/Pipfile.lock b/Pipfile.lock index fc51b98e7..fd0d68304 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "79fbda0026ebefdff842b0a9d1124dc5583dbbf4be35102314cafbcd089bd5c8" + "sha256": "17c34b4e1c5c2fab86e0f252dd7aa182ac73deec7799c681f8330a23cc9ec831" }, "pipfile-spec": 6, "requires": {}, @@ -192,36 +192,36 @@ }, "pyqt5": { "hashes": [ - "sha256:2d94ec761fb656707050c68b41958e3a9f755bb1df96c064470f4096d2899e32", - "sha256:2f230f2dbd767099de7a0cb915abdf0cbc3256a0b5bb910eb09b99117db7a65b", - "sha256:31b142a868152d60c6323e0527edb692fdf05fd7cb4fe2fe9ce07d1ce560221a", - "sha256:713b9a201f5e7b2fca8691373e5d5c8c2552a51d87ca9ffbb1461e34e3241211", - "sha256:a0bfe9fd718bca4de3e33000347e048f73126b6dc46530eb020b0251a638ee9d" + "sha256:3b91dd1d0cbfaea85ad057247ba621187e511434b0c9d6d40de69fd5e833b109", + "sha256:a9bdc46ab1f6397770e6b8dca84ac07a0250d26b1a31587f25619cf31a075532", + "sha256:bd230c6fd699eabf1ceb51e13a8b79b74c00a80272c622427b80141a22269eb0", + "sha256:ee168a486c9a758511568147815e2959652cd0aabea832fa5e87cf6b241d2180", + "sha256:f61ddc78547d6ca763323ccd4a9e374c71b29feda1f5ce2d3e91e4f8d2cf1942" ], "index": "pypi", - "version": "==5.14.1" + "version": "==5.14.2" }, "pyqt5-sip": { "hashes": [ - "sha256:1115728644bbadcde5fc8a16e7918bd31915a42dd6fb36b10d4afb78c582753e", - "sha256:1f4289276d355b6521dc2cc956189315da6f13adfb6bbab8f25ebd15e3bce1d4", - "sha256:288c6dc18a8d6a20981c07b715b5695d9b66880778565f3792bc6e38f14f20fb", - "sha256:3f665376d9e52faa9855c3736a66ce6d825f85c86d7774d3c393f09da23f4f86", - "sha256:6b4860c4305980db509415d0af802f111d15f92016c9422eb753bc8883463456", - "sha256:7ffa39763097f64de129cf5cc770a651c3f65d2466b4fe05bef2bd2efbaa38e6", - "sha256:8a18e6f45d482ddfe381789979d09ee13aa6450caa3a0476503891bccb3ac709", - "sha256:8da842d3d7bf8931d1093105fb92702276b6dbb7e801abbaaa869405d616171a", - "sha256:b42021229424aa44e99b3b49520b799fd64ff6ae8b53f79f903bbd85719a28e4", - "sha256:b5b4906445fe980aee76f20400116b6904bf5f30d0767489c13370e42a764020", - "sha256:c1e730a9eb2ec3869ed5d81b0f99f6e2460fb4d77750444c0ec183b771d798f7", - "sha256:cbeeae6b45234a1654657f79943f8bccd3d14b4e7496746c62cf6fbce69442c7", - "sha256:d46b0f8effc554de52a1466b1bd80e5cb4bce635a75ac4e7ad6247c965dec5b9", - "sha256:e28c3abc9b62a1b7e796891648b9f14f8167b31c8e7990fae79654777252bb4d", - "sha256:e6078f5ee7d31c102910d0c277a110e1c2a20a3fc88cd017a39e170120586d3f", - "sha256:ee1a12f09d5af2304273bfd2f6b43835c1467d5ed501a6c95f5405637fa7750a", - "sha256:f314f31f5fd39b06897f013f425137e511d45967150eb4e424a363d8138521c6" + "sha256:01919371d32b26208b2f0318f1e15680d3aa60d1ced1812a5dac8bdb483fea69", + "sha256:11f8cc2de287c3457fee53e781f06fb71f04251e7ae408ed22696ed65fd2bcf4", + "sha256:168a6d700daf366b7cf255a8cabf8d07bfe2294859e6b3b2636c36c2f89265c9", + "sha256:16a19b9f36985b8bff30b89fb8859d831713dd528fba5600563e36ff077960a2", + "sha256:16a9a4daf85bfaa3aec35237ff28d8773a3ec937d9f8dc7fc3db7716de42d4a9", + "sha256:31c74602ccd6b70e4352550eb41aa980dc1d6009444f3c8eb1b844e84bd144cf", + "sha256:360de29634e2ce1df84d2b588bd8c1a29b768f3a5225869d63adb03bc21bd32a", + "sha256:3cb9076ba0e574b2f026759103eb0e12051128714f5aa136cca53229d3ad72d1", + "sha256:4f87d59d29ca1c5a4005bbec27af002be787210dc5f8f87fe5d747883a836083", + "sha256:65fceeea2ac738a92f7e3e459ece1b4e2fbf49fd1d6b732a73d0d4bcfc434452", + "sha256:85e68b8936f1756060ddcb3ef0a84af78ce89993fa6594b3049a0eca53d6d2fa", + "sha256:9dd5769e83e64d017d02981563c8159d825425b6c4998c937a880888f4dcb7a3", + "sha256:a8a6c0512641fc042726b6253b2d5f3f3f800098334d198d8ebdf337d85ab3d7", + "sha256:b068f4791e97427d82a27e7df28cc3ee33f7e4353f48ed6a123f8cdba44266b2", + "sha256:b34c1f227a8f8e97059f20e5424f117f66a302b42e34d4039158494c6371b1ce", + "sha256:b68cfe632a512c0551e8860f35c1fcab5cd1ad5e168b4814fddd88121f447b0a", + "sha256:df4f5cdb86f47df5f6fc35be29cc45df7b5a2c171d07dbf377d558b226554ea3" ], - "version": "==12.7.1" + "version": "==12.7.2" }, "python-rtmidi": { "hashes": [ diff --git a/lisp/i18n/ts/en/action_cues.ts b/lisp/i18n/ts/en/action_cues.ts index 1aaf14dca..687fd32f9 100644 --- a/lisp/i18n/ts/en/action_cues.ts +++ b/lisp/i18n/ts/en/action_cues.ts @@ -54,7 +54,7 @@ CueCategory - + Action cues diff --git a/lisp/i18n/ts/en/cache_manager.ts b/lisp/i18n/ts/en/cache_manager.ts new file mode 100644 index 000000000..17be057ad --- /dev/null +++ b/lisp/i18n/ts/en/cache_manager.ts @@ -0,0 +1,34 @@ + + + + CacheManager + + + Cache size warning + + + + + Warning threshold in MB (0 = disabled) + + + + + Cache cleanup + + + + + Delete the cache content + + + + + SettingsPageName + + + Cache Manager + + + + diff --git a/lisp/i18n/ts/en/cart_layout.ts b/lisp/i18n/ts/en/cart_layout.ts index a59dff846..b172437e6 100644 --- a/lisp/i18n/ts/en/cart_layout.ts +++ b/lisp/i18n/ts/en/cart_layout.ts @@ -8,27 +8,27 @@ - + Countdown mode - + Show seek-bars - + Show dB-meters - + Show accurate time - + Show volume @@ -78,22 +78,22 @@ - + Page {number} - + Warning - + Every cue in the page will be lost. - + Are you sure to continue? diff --git a/lisp/i18n/ts/en/controller.ts b/lisp/i18n/ts/en/controller.ts index e176368ef..b699533de 100644 --- a/lisp/i18n/ts/en/controller.ts +++ b/lisp/i18n/ts/en/controller.ts @@ -29,113 +29,136 @@ ControllerMidiSettings - + MIDI MIDI - + Type Type - - Channel - Channel + + Action + Action - - Note - Note + + Capture + Capture - - Action - Action + + Listening MIDI messages ... + Listening MIDI messages ... + + + + -- All Messages -- + - - Filter "note on" - Filter "note on" + + Capture filter + - - Filter "note off" - Filter "note off" + + Data 1 + - - Capture - Capture + + Data 2 + - - Listening MIDI messages ... - Listening MIDI messages ... + + Data 3 + ControllerOscSettings - + OSC Message - + OSC - + Path - + Types - + Arguments - - Actions - - - - + OSC Capture - + Add Add - + Remove Remove - + Capture Capture + + + Waiting for messages: + + + + + /path/to/method + + + + + Action + Action + + + + ControllerOscSettingsWarning + + + Warning + + ControllerSettings - + Add Add - + Remove Remove @@ -196,12 +219,12 @@ Osc Cue - + Type Type - + Argument @@ -209,12 +232,12 @@ OscCue - + Add Add - + Remove Remove @@ -227,7 +250,7 @@ Cue Control - + MIDI Controls MIDI Controls @@ -237,7 +260,7 @@ Keyboard Shortcuts - + OSC Controls diff --git a/lisp/i18n/ts/en/gst_backend.ts b/lisp/i18n/ts/en/gst_backend.ts index df0a8e36d..592064d86 100644 --- a/lisp/i18n/ts/en/gst_backend.ts +++ b/lisp/i18n/ts/en/gst_backend.ts @@ -118,7 +118,7 @@ - + Select media files @@ -126,7 +126,7 @@ GstMediaError - + Cannot create pipeline element: "{}" @@ -142,7 +142,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" @@ -150,7 +150,7 @@ GstPipelineEdit - + Edit Pipeline Edit Pipeline @@ -254,7 +254,7 @@ Pitch - + URI Input URI Input diff --git a/lisp/i18n/ts/en/lisp.ts b/lisp/i18n/ts/en/lisp.ts index d7f82ee8d..4b5ff4eff 100644 --- a/lisp/i18n/ts/en/lisp.ts +++ b/lisp/i18n/ts/en/lisp.ts @@ -34,32 +34,32 @@ AboutDialog - + Web site Web site - + Source code Source code - + Info Info - + License License - + Contributors Contributors - + Discussion @@ -91,45 +91,50 @@ AppGeneralSettings - + Default layout - - Enable startup layout selector - - - - + Application themes - + UI theme: - + Icons theme: - - Application language (require restart) + + Language: - - Language: + + Show layout selection at startup + + + + + Use layout at startup: + + + + + Application language (require a restart) ApplicationError - + Startup error @@ -205,6 +210,29 @@ + + CacheManager + + + Cache size warning + + + + + Warning threshold in MB (0 = disabled) + + + + + Cache cleanup + + + + + Delete the cache content + + + CartLayout @@ -213,27 +241,27 @@ - + Countdown mode - + Show seek-bars - + Show dB-meters - + Show accurate time - + Show volume @@ -293,22 +321,22 @@ - + Page {number} - + Warning Warning - + Every cue in the page will be lost. - + Are you sure to continue? @@ -422,113 +450,136 @@ ControllerMidiSettings - + MIDI - + Type - - Channel + + Action - - Note + + Capture - - Action + + Listening MIDI messages ... - - Filter "note on" + + -- All Messages -- - - Filter "note off" + + Capture filter - - Capture + + Data 1 - - Listening MIDI messages ... + + Data 2 + + + + + Data 3 ControllerOscSettings - + OSC Message - + OSC - + Path - + Types - + Arguments - - Actions - - - - + OSC Capture - + Add - + Remove - + Capture + + + Waiting for messages: + + + + + /path/to/method + + + + + Action + + + + + ControllerOscSettingsWarning + + + Warning + Warning + ControllerSettings - + Add - + Remove @@ -645,17 +696,17 @@ CueCategory - + Action cues - + Integration cues - + Misc cues @@ -676,7 +727,7 @@ CueName - + Media Cue Media Cue @@ -711,7 +762,7 @@ - + MIDI Cue @@ -797,13 +848,13 @@ Default action to stop the cue - - Interrupt fade + + Interrupt action fade - - Fade actions + + Fade actions default value @@ -882,12 +933,12 @@ FadeEdit - + Duration (sec): - + Curve: @@ -966,7 +1017,7 @@ - + Select media files @@ -974,7 +1025,7 @@ GstMediaError - + Cannot create pipeline element: "{}" @@ -990,7 +1041,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" @@ -998,7 +1049,7 @@ GstPipelineEdit - + Edit Pipeline @@ -1106,7 +1157,7 @@ - + To copy cues drag them while pressing CTRL @@ -1170,132 +1221,127 @@ ListLayout - + Default behaviors - - Show playing cues - - - - + Show dB-meters - + Show accurate time - + Show seek-bars - + Auto-select next cue - + Enable selection mode - + GO Key: - + GO Action: - + GO minimum interval (ms): - + Use fade (buttons) - + Stop Cue - + Pause Cue - + Resume Cue - + Interrupt Cue - + Edit cue - + Edit selected Edit selected - + Clone cue - + Clone selected - + Remove cue - + Remove selected - + Selection mode - + Pause all - + Stop all - + Interrupt all - + Resume all @@ -1319,36 +1365,71 @@ Remove selected cues + + + Copy of {} + + - Use fade + Layout actions - - Copy of {} + + Fade out when stopping all cues + + + + + Fade out when interrupting all cues + + + + + Fade out when pausing all cues + + + + + Fade in when resuming all cues + + + + + Show index column + + + + + Show resize handles + + + + + Restore default size ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action - + Post wait Post wait @@ -1491,6 +1572,11 @@ Exception info + + + Showing {} of {} records + + MIDICue @@ -1505,50 +1591,81 @@ + + MIDIError + + + Cannot connect to MIDI output port '{}'. + + + + + Cannot connect to MIDI input port '{}'. + + + + + MIDIInfo + + + MIDI port disconnected: '{}' + + + + + Connecting to MIDI port: '{}' + + + + + Connecting to matching MIDI port: '{}' + + + MIDIMessageAttr - + Channel - + Note - + Velocity - + Control - + Program - + Value - + Song - + Pitch - + Position @@ -1556,62 +1673,62 @@ MIDIMessageType - + Note ON - + Note OFF - + Polyphonic After-touch - + Control/Mode Change - + Program Change - + Channel After-touch - + Pitch Bend Change - + Song Select - + Song Position - + Start Start - + Stop Stop - + Continue @@ -1619,18 +1736,28 @@ MIDISettings - - MIDI default devices + + Input - - Input + + Output - - Output + + MIDI devices + + + + + Misc options + + + + + Try to connect using only device/port name @@ -1677,97 +1804,97 @@ Exit - + &Edit &Edit - + Undo Undo - + Redo Redo - + Select all Select all - + Select all media cues Select all media cues - + Deselect all Deselect all - + CTRL+SHIFT+A CTRL+SHIFT+A - + Invert selection Invert selection - + CTRL+I CTRL+I - + Edit selected Edit selected - + CTRL+SHIFT+E CTRL+SHIFT+E - + &Layout &Layout - + &Tools &Tools - + Edit selection Edit selection - + &About &About - + About About - + About Qt About Qt - + Close session Close session - + Do you want to save them now? @@ -1775,7 +1902,7 @@ MainWindowDebug - + Registered cue menu: "{}" @@ -1783,7 +1910,7 @@ MainWindowError - + Cannot create cue {} @@ -1844,7 +1971,7 @@ - + URI Input @@ -1897,7 +2024,7 @@ MediaInfo - + Media Info @@ -1907,20 +2034,20 @@ Warning - - No info to display - - - - + Info Info - + Value + + + Cannot get any information. + + NetworkApiDebug @@ -1933,55 +2060,60 @@ NetworkDiscovery - + Host discovery - + Manage hosts - + Discover hosts - + Manually add a host - + Remove selected host - + Remove all host - + Address - + Host IP + + + Select the hosts you want to add + + Osc Cue - + Type - + Argument @@ -2117,12 +2249,12 @@ - + Failed to terminate plugin: "{}" - + the requested plugin is not loaded: {} @@ -2135,7 +2267,7 @@ - + Plugin terminated: "{}" @@ -2151,12 +2283,12 @@ Preset - + Create Cue - + Load on selected Cues @@ -2207,62 +2339,62 @@ - + Preset name - + Add - + Rename - + Edit - + Remove - + Export selected - + Import - + Warning Warning - + The same name is already used! - + Cannot export correctly. - + Cannot import correctly. - + Cue type @@ -2282,7 +2414,7 @@ - + Cannot create a cue from this preset: {} @@ -2377,7 +2509,7 @@ ReplayGain - + ReplayGain / Normalization @@ -2407,17 +2539,17 @@ - + Calculate - + Reset all - + Reset selected @@ -2425,12 +2557,12 @@ ReplayGainDebug - + Applied gain for: {} - + Discarded gain for: {} @@ -2438,17 +2570,17 @@ ReplayGainInfo - + Gain processing stopped by user. - + Started gain calculation for: {} - + Gain calculated for: {} @@ -2584,7 +2716,7 @@ - + MIDI Controls @@ -2594,17 +2726,17 @@ - + OSC Controls - + MIDI Settings - + MIDI settings @@ -2633,6 +2765,11 @@ Cart Layout + + + Cache Manager + + SpeedSettings @@ -2669,7 +2806,7 @@ - Your IP is: + Your IP is: {} diff --git a/lisp/i18n/ts/en/list_layout.ts b/lisp/i18n/ts/en/list_layout.ts index 9eca834b8..68e5f8f3c 100644 --- a/lisp/i18n/ts/en/list_layout.ts +++ b/lisp/i18n/ts/en/list_layout.ts @@ -37,77 +37,72 @@ ListLayout - + Default behaviors - - Show playing cues - - - - + Show dB-meters - + Show accurate time - + Show seek-bars - + Auto-select next cue - + Enable selection mode - + Use fade (buttons) - + Stop Cue - + Pause Cue - + Resume Cue - + Interrupt Cue - + Edit cue - + Remove cue - + Selection mode @@ -142,65 +137,80 @@ - + Edit selected - + Clone cue - + Clone selected - + Remove selected - + GO Key: - + GO Action: - + GO minimum interval (ms): - + Copy of {} + + + Show index column + + + + + Show resize handles + + + + + Restore default size + + ListLayoutHeader - + Cue - + Pre wait - + Action - + Post wait diff --git a/lisp/i18n/ts/en/media_info.ts b/lisp/i18n/ts/en/media_info.ts index ee241206b..1657970c9 100644 --- a/lisp/i18n/ts/en/media_info.ts +++ b/lisp/i18n/ts/en/media_info.ts @@ -3,22 +3,17 @@ MediaInfo - + Media Info Media Info - - No info to display - No info to display - - - + Info Info - + Value Value @@ -27,5 +22,10 @@ Warning + + + Cannot get any information. + + diff --git a/lisp/i18n/ts/en/midi.ts b/lisp/i18n/ts/en/midi.ts index 435abfff5..eb1f8d835 100644 --- a/lisp/i18n/ts/en/midi.ts +++ b/lisp/i18n/ts/en/midi.ts @@ -3,7 +3,7 @@ CueCategory - + Integration cues @@ -11,7 +11,7 @@ CueName - + MIDI Cue @@ -29,50 +29,81 @@ + + MIDIError + + + Cannot connect to MIDI output port '{}'. + + + + + Cannot connect to MIDI input port '{}'. + + + + + MIDIInfo + + + MIDI port disconnected: '{}' + + + + + Connecting to MIDI port: '{}' + + + + + Connecting to matching MIDI port: '{}' + + + MIDIMessageAttr - + Channel - + Note - + Velocity - + Control - + Program - + Value - + Song - + Pitch - + Position @@ -80,62 +111,62 @@ MIDIMessageType - + Note ON - + Note OFF - + Polyphonic After-touch - + Control/Mode Change - + Program Change - + Channel After-touch - + Pitch Bend Change - + Song Select - + Song Position - + Start - + Stop - + Continue @@ -143,30 +174,40 @@ MIDISettings - - MIDI default devices - MIDI default devices - - - + Input Input - + Output Output + + + MIDI devices + + + + + Misc options + + + + + Try to connect using only device/port name + + SettingsPageName - + MIDI settings MIDI settings - + MIDI Settings diff --git a/lisp/i18n/ts/en/network.ts b/lisp/i18n/ts/en/network.ts index f2829baea..6b7d6ce5d 100644 --- a/lisp/i18n/ts/en/network.ts +++ b/lisp/i18n/ts/en/network.ts @@ -27,44 +27,49 @@ NetworkDiscovery - + Host discovery - + Manage hosts - + Discover hosts - + Manually add a host - + Remove selected host - + Remove all host - + Address - + Host IP + + + Select the hosts you want to add + + diff --git a/lisp/i18n/ts/en/osc.ts b/lisp/i18n/ts/en/osc.ts index 30d8e8c57..cf0d68c87 100644 --- a/lisp/i18n/ts/en/osc.ts +++ b/lisp/i18n/ts/en/osc.ts @@ -11,7 +11,7 @@ CueCategory - + Integration cues diff --git a/lisp/i18n/ts/en/presets.ts b/lisp/i18n/ts/en/presets.ts index 2d373cfe3..63ecb84e1 100644 --- a/lisp/i18n/ts/en/presets.ts +++ b/lisp/i18n/ts/en/presets.ts @@ -3,12 +3,12 @@ Preset - + Create Cue Create Cue - + Load on selected Cues Load on selected Cues @@ -56,62 +56,62 @@ Select Preset - + Preset name Preset name - + Add Add - + Rename Rename - + Edit Edit - + Remove Remove - + Export selected Export selected - + Import Import - + Warning Warning - + The same name is already used! The same name is already used! - + Cannot export correctly. Cannot export correctly. - + Cannot import correctly. Cannot import correctly. - + Cue type Cue type @@ -126,7 +126,7 @@ - + Cannot create a cue from this preset: {} diff --git a/lisp/i18n/ts/en/replay_gain.ts b/lisp/i18n/ts/en/replay_gain.ts index 504e0301f..4a04e4469 100644 --- a/lisp/i18n/ts/en/replay_gain.ts +++ b/lisp/i18n/ts/en/replay_gain.ts @@ -3,22 +3,22 @@ ReplayGain - + ReplayGain / Normalization ReplayGain / Normalization - + Calculate Calculate - + Reset all Reset all - + Reset selected Reset selected @@ -51,12 +51,12 @@ ReplayGainDebug - + Applied gain for: {} - + Discarded gain for: {} @@ -64,17 +64,17 @@ ReplayGainInfo - + Gain processing stopped by user. - + Started gain calculation for: {} - + Gain calculated for: {} diff --git a/lisp/i18n/ts/en/synchronizer.ts b/lisp/i18n/ts/en/synchronizer.ts index 482dda3fb..be461c457 100644 --- a/lisp/i18n/ts/en/synchronizer.ts +++ b/lisp/i18n/ts/en/synchronizer.ts @@ -19,8 +19,8 @@ - Your IP is: - Your IP is: + Your IP is: {} + diff --git a/lisp/plugins/cache_manager/cache_manager.py b/lisp/plugins/cache_manager/cache_manager.py index bda1c306f..fd52029b0 100644 --- a/lisp/plugins/cache_manager/cache_manager.py +++ b/lisp/plugins/cache_manager/cache_manager.py @@ -51,7 +51,7 @@ def _check_cache_size(self): def _show_threshold_warning(self, threshold, _): QMessageBox.warning( self.app.window, - "Cache size", + translate("CacheManager", "Cache size",), translate( "CacheManager", "The cache has exceeded {}. Consider clean it.\n" diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index 27ccf64d1..fd398c93b 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -233,7 +233,7 @@ def _text(self, option, index): attr = message_spec[index.column() - 1] attr_spec = MIDI_ATTRS_SPEC.get(attr) - if MIDI_MSGS_SPEC is not None: + if attr_spec is not None: return str(value - attr_spec[-1]) return "" diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index d249cbab5..60ef0b712 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -34,6 +34,7 @@ QDialogButtonBox, QLineEdit, QMessageBox, + QSpacerItem, ) from lisp.plugins import get_plugin, PluginNotLoadedError @@ -43,9 +44,9 @@ from lisp.plugins.osc.osc_server import OscMessageType from lisp.ui.qdelegates import ( ComboBoxDelegate, - LineEditDelegate, CueActionDelegate, EnumComboBoxDelegate, + LabelDelegate, ) from lisp.ui.qmodels import SimpleTableModel from lisp.ui.settings.pages import SettingsPage, CuePageMixin @@ -57,98 +58,137 @@ class OscMessageDialog(QDialog): def __init__(self, **kwargs): super().__init__(**kwargs) - self.setMaximumSize(500, 300) + self.setLayout(QGridLayout()) self.setMinimumSize(500, 300) - self.resize(500, 300) + self.resize(500, 350) - self.setLayout(QVBoxLayout()) - self.layout().setAlignment(Qt.AlignTop) - - self.groupBox = QGroupBox(self) - self.groupBox.setLayout(QGridLayout()) - self.layout().addWidget(self.groupBox) - - self.pathLabel = QLabel() - self.groupBox.layout().addWidget(self.pathLabel, 0, 0, 1, 2) + self.pathLabel = QLabel(self) + self.layout().addWidget(self.pathLabel, 0, 0, 1, 2) - self.pathEdit = QLineEdit() - self.groupBox.layout().addWidget(self.pathEdit, 1, 0, 1, 2) + self.pathEdit = QLineEdit(self) + self.layout().addWidget(self.pathEdit, 1, 0, 1, 2) self.model = SimpleTableModel( [translate("Osc Cue", "Type"), translate("Osc Cue", "Argument")] ) + self.model.dataChanged.connect(self.__argumentChanged) - self.model.dataChanged.connect(self.__argument_changed) - - self.view = OscArgumentView(parent=self.groupBox) - self.view.doubleClicked.connect(self.__update_editor) + self.view = OscArgumentView(parent=self) self.view.setModel(self.model) - self.groupBox.layout().addWidget(self.view, 2, 0, 1, 2) + self.layout().addWidget(self.view, 2, 0, 1, 2) + + self.addButton = QPushButton(self) + self.addButton.clicked.connect(self.__addArgument) + self.layout().addWidget(self.addButton, 3, 0) + + self.removeButton = QPushButton(self) + self.removeButton.clicked.connect(self.__removeArgument) + self.layout().addWidget(self.removeButton, 3, 1) + + self.layout().addItem(QSpacerItem(0, 20), 4, 0, 1, 2) self.buttons = QDialogButtonBox(self) self.buttons.addButton(QDialogButtonBox.Cancel) self.buttons.addButton(QDialogButtonBox.Ok) - self.layout().addWidget(self.buttons) - - self.addButton = QPushButton(self.groupBox) - self.addButton.clicked.connect(self.__add_argument) - self.groupBox.layout().addWidget(self.addButton, 3, 0) - - self.removeButton = QPushButton(self.groupBox) - self.removeButton.clicked.connect(self.__remove_argument) - self.groupBox.layout().addWidget(self.removeButton, 3, 1) + self.layout().addWidget(self.buttons, 5, 0, 1, 2) self.buttons.accepted.connect(self.accept) self.buttons.rejected.connect(self.reject) self.retranslateUi() - def __add_argument(self): + def retranslateUi(self): + self.setWindowTitle(translate("ControllerOscSettings", "OSC Message")) + self.pathLabel.setText(translate("ControllerOscSettings", "OSC Path:",)) + self.pathEdit.setPlaceholderText( + translate("ControllerOscSettings", "/path/to/method") + ) + self.addButton.setText(translate("OscCue", "Add")) + self.removeButton.setText(translate("OscCue", "Remove")) + + def getMessage(self): + path = self.pathEdit.text() + types = "" + arguments = [] + + for rowType, rowValue in self.model.rows: + if rowType == OscMessageType.Bool: + types += "T" if rowValue else "F" + else: + if rowType == OscMessageType.Int: + types += "i" + elif rowType == OscMessageType.Float: + types += "f" + elif rowType == OscMessageType.String: + types += "s" + else: + raise TypeError("Unsupported Osc Type") + + arguments.append(rowValue) + + return path, types, str(arguments)[1:-1] + + def setMessage(self, path, types, arguments): + self.pathEdit.setText(path) + # Split strings + types = tuple(types) + arguments = ast.literal_eval(f"[{arguments}]") + + # We keep a separate index, because booleans don't have a value + valIndex = 0 + for t in types: + if t == "T" or t == "F": + rowType = OscMessageType.Bool.value + rowValue = t == "T" + elif valIndex < len(arguments): + if t == "i": + rowType = OscMessageType.Int.value + rowValue = self.__castValue(arguments[valIndex], int, 0) + elif t == "f": + rowType = OscMessageType.Float.value + rowValue = self.__castValue(arguments[valIndex], float, 0.0) + elif t == "s": + rowType = OscMessageType.String.value + rowValue = arguments[valIndex] + else: + valIndex += 1 + continue + + valIndex += 1 + else: + continue + + self.model.appendRow(rowType, rowValue) + + def __addArgument(self): self.model.appendRow(OscMessageType.Int.value, 0) - def __remove_argument(self): + def __removeArgument(self): if self.model.rowCount() and self.view.currentIndex().row() > -1: self.model.removeRow(self.view.currentIndex().row()) - def __update_editor(self, model_index): - column = model_index.column() - row = model_index.row() - if column == 1: - model = model_index.model() - osc_type = model.rows[row][0] - delegate = self.view.itemDelegate(model_index) - delegate.updateEditor(OscMessageType(osc_type)) - - def __argument_changed(self, index_topleft, index_bottomright, roles): - if not (Qt.EditRole in roles): - return - - model = self.model - curr_row = index_topleft.row() - model_row = model.rows[curr_row] - curr_col = index_bottomright.column() - osc_type = model_row[0] - - if curr_col == 0: - if osc_type == "Integer" or osc_type == "Float": - model_row[1] = 0 - elif osc_type == "Bool": - model_row[1] = True + def __argumentChanged(self, topLeft, bottomRight, roles): + # If the "Type" column has changed + if Qt.EditRole in roles and bottomRight.column() == 0: + # Get the edited row + row = self.model.rows[topLeft.row()] + oscType = row[0] + + # Update the value column with a proper type + if oscType == OscMessageType.Int: + row[1] = self.__castValue(row[1], int, 0) + elif oscType == OscMessageType.Float: + row[1] = self.__castValue(row[1], float, 0.0) + elif oscType == OscMessageType.Bool: + row[1] = True else: - model_row[1] = "" + row[1] = "" - def retranslateUi(self): - self.groupBox.setTitle( - translate("ControllerOscSettings", "OSC Message") - ) - self.pathLabel.setText( - translate( - "ControllerOscSettings", - 'OSC Path: (example: "/path/to/something")', - ) - ) - self.addButton.setText(translate("OscCue", "Add")) - self.removeButton.setText(translate("OscCue", "Remove")) + def __castValue(self, value, toType, default): + try: + return toType(value) + except (TypeError, ValueError): + return default class OscArgumentView(QTableView): @@ -193,14 +233,7 @@ def __init__(self, actionDelegate, **kwargs): self.oscGroup.setLayout(QGridLayout()) self.layout().addWidget(self.oscGroup) - self.oscModel = SimpleTableModel( - [ - translate("ControllerOscSettings", "Path"), - translate("ControllerOscSettings", "Types"), - translate("ControllerOscSettings", "Arguments"), - translate("ControllerOscSettings", "Actions"), - ] - ) + self.oscModel = OscModel() self.OscView = OscView(actionDelegate, parent=self.oscGroup) self.OscView.setModel(self.oscModel) @@ -263,16 +296,16 @@ def getSettings(self): entries = [] for row in self.oscModel.rows: message = Osc.key_from_values(row[0], row[1], row[2]) - entries.append((message, row[-1])) + entries.append((message, row[3])) return {"osc": entries} def loadSettings(self, settings): - for options in settings.get("osc", ()): + for entry in settings.get("osc", ()): try: - key = Osc.message_from_key(options[0]) + message = Osc.message_from_key(entry[0]) self.oscModel.appendRow( - key[0], key[1], str(key[2:])[1:-1], options[1] + message[0], message[1], str(message[2:])[1:-1], entry[1] ) except Exception: logger.warning( @@ -298,7 +331,9 @@ def capture_message(self): self.__osc.server.new_message.disconnect(self.__show_message) - self.captureLabel.setText("Waiting for messages:") + self.captureLabel.setText( + translate("ControllerOscSettings", "Waiting for messages:") + ) def __show_message(self, path, args, types): self.capturedMessage["path"] = path @@ -309,37 +344,20 @@ def __show_message(self, path, args, types): def __new_message(self): dialog = OscMessageDialog(parent=self) if dialog.exec() == dialog.Accepted: - path = dialog.pathEdit.text() + path, types, arguments = dialog.getMessage() + if len(path) < 2 or path[0] != "/": QMessageBox.warning( self, - "Warning", - "Osc path seems not valid,\n" - "do not forget to edit the path later.", + translate("ControllerOscSettingsWarning", "Warning"), + translate( + "ControllerOscSettingsWarning", + "Osc path seems invalid,\n" + "do not forget to edit the path later.", + ), ) - types = "" - arguments = [] - for row in dialog.model.rows: - if row[0] == "Bool": - if row[1] is True: - types += "T" - else: - types += "F" - else: - if row[0] == "Integer": - types += "i" - elif row[0] == "Float": - types += "f" - elif row[0] == "String": - types += "s" - else: - raise TypeError("Unsupported Osc Type") - arguments.append(row[1]) - - self.oscModel.appendRow( - path, types, str([0, 1])[1:-1], self._defaultAction - ) + self.oscModel.appendRow(path, types, arguments, self._defaultAction) def __remove_message(self): if self.oscModel.rowCount() and self.OscView.currentIndex().row() > -1: @@ -376,9 +394,9 @@ def __init__(self, actionDelegate, **kwargs): super().__init__(**kwargs) self.delegates = [ - LineEditDelegate(), - LineEditDelegate(), - LineEditDelegate(), + LabelDelegate(), + LabelDelegate(), + LabelDelegate(), actionDelegate, ] @@ -398,6 +416,38 @@ def __init__(self, actionDelegate, **kwargs): for column, delegate in enumerate(self.delegates): self.setItemDelegateForColumn(column, delegate) + self.doubleClicked.connect(self.__doubleClicked) + + def __doubleClicked(self, index): + if index.column() <= 2: + row = self.model().rows[index.row()] + dialog = OscMessageDialog() + dialog.setMessage(row[0], row[1], row[2]) + + if dialog.exec() == dialog.Accepted: + path, types, arguments = dialog.getMessage() + self.model().updateRow( + index.row(), path, types, arguments, row[3] + ) + + +class OscModel(SimpleTableModel): + def __init__(self): + super().__init__( + [ + translate("ControllerOscSettings", "Path"), + translate("ControllerOscSettings", "Types"), + translate("ControllerOscSettings", "Arguments"), + translate("ControllerOscSettings", "Action"), + ] + ) + + def flags(self, index): + if index.column() <= 2: + return Qt.ItemIsEnabled | Qt.ItemIsSelectable + + return super().flags(index) + class Osc(Protocol): CueSettings = OscCueSettings @@ -415,8 +465,7 @@ def __new_message(self, path, args, types, *_, **__): @staticmethod def key_from_message(path, types, args): - key = [path, types, *args] - return f"OSC{key}" + return f"OSC{[path, types, *args]}" @staticmethod def key_from_values(path, types, args): @@ -427,5 +476,4 @@ def key_from_values(path, types, args): @staticmethod def message_from_key(key): - key = ast.literal_eval(key[3:]) - return key + return ast.literal_eval(key[3:]) diff --git a/lisp/plugins/osc/osc_delegate.py b/lisp/plugins/osc/osc_delegate.py index 0a359a2e0..106cedcf1 100644 --- a/lisp/plugins/osc/osc_delegate.py +++ b/lisp/plugins/osc/osc_delegate.py @@ -15,76 +15,23 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import ( - QStyledItemDelegate, - QSpinBox, - QDoubleSpinBox, - QCheckBox, - QLineEdit, -) - -from lisp.plugins.osc.osc_server import OscMessageType +from PyQt5.QtWidgets import QStyledItemDelegate, QSpinBox, QDoubleSpinBox class OscArgumentDelegate(QStyledItemDelegate): - MIN = -1_000_000 - MAX = 1_000_000 - DECIMALS = 4 - - def __init__(self, tag=OscMessageType.Int, **kwargs): - super().__init__(**kwargs) - self.tag = tag + NUMERIC_MIN = -1_000_000 + NUMERIC_MAX = 1_000_000 + FLOAT_DECIMALS = 3 def createEditor(self, parent, option, index): - if self.tag is OscMessageType.Int: - editor = QSpinBox(parent) - editor.setRange(self.MIN, self.MAX) + editor = super().createEditor(parent, option, index) + + if isinstance(editor, QSpinBox): + editor.setRange(self.NUMERIC_MIN, self.NUMERIC_MAX) editor.setSingleStep(1) - elif self.tag is OscMessageType.Float: - editor = QDoubleSpinBox(parent) - editor.setRange(self.MIN, self.MAX) - editor.setDecimals(3) + elif isinstance(editor, QDoubleSpinBox): + editor.setRange(self.NUMERIC_MIN, self.NUMERIC_MAX) + editor.setDecimals(self.FLOAT_DECIMALS) editor.setSingleStep(0.01) - elif self.tag is OscMessageType.Bool: - editor = QCheckBox(parent) - elif self.tag is OscMessageType.String: - editor = QLineEdit(parent) - editor.setFrame(False) - else: - editor = None return editor - - def setEditorData(self, widget, index): - value = index.model().data(index, Qt.EditRole) - - if self.tag is OscMessageType.Int: - if isinstance(value, int): - widget.setValue(value) - elif self.tag is OscMessageType.Float: - if isinstance(value, float): - widget.setValue(value) - elif self.tag is OscMessageType.Bool: - if isinstance(value, bool): - widget.setChecked(value) - else: - widget.setText(str(value)) - - def setModelData(self, widget, model, index): - if self.tag is OscMessageType.Int or self.tag is OscMessageType.Float: - widget.interpretText() - model.setData(index, widget.value(), Qt.EditRole) - elif self.tag is OscMessageType.Bool: - model.setData(index, widget.isChecked(), Qt.EditRole) - else: - model.setData(index, widget.text(), Qt.EditRole) - - def updateEditorGeometry(self, editor, option, index): - editor.setGeometry(option.rect) - - def updateEditor(self, tag=None): - if isinstance(tag, OscMessageType): - self.tag = tag - else: - self.tag = None diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index 88a7cd7e5..792c7155d 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -54,7 +54,9 @@ def _text(self, option, index): return index.data() def paint(self, painter, option, index): - # Add 4px of left an right padding + self.initStyleOption(option, index) + + # Add 4px of left and right padding option.rect.adjust(4, 0, -4, 0) text = self._text(option, index) From f7f730eb3551c01acae407550fb2b1e32bc739f8 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 10 Apr 2020 12:08:29 +0200 Subject: [PATCH 228/333] Waveform and DBMeter widgets improvements Update: WaveformSlider is now displaying the timestamp on hover Update: DBMeter is now using the "IEC 268-18" standard to scaling for decibel values Update: DBMeter is now smoothing the values (when falling) - the default rate is "optimized" for a 33ms refresh rate. Update: slight changed to color gradient used by DBMeter Update: DBMeter is now rendered using a cached pixmap Update: gst_backend DbMeter defaults to better fit the updated widget Fix: WaveformWidget is now correctly limiting the repainted area --- lisp/backend/audio_utils.py | 25 ++ lisp/plugins/gst_backend/elements/db_meter.py | 8 +- lisp/plugins/gst_backend/gst_waveform.py | 15 +- lisp/plugins/gst_backend/settings/db_meter.py | 8 +- lisp/ui/widgets/dbmeter.py | 220 +++++++++--------- lisp/ui/widgets/dynamicfontsize.py | 6 +- lisp/ui/widgets/waveform.py | 106 +++++++-- 7 files changed, 242 insertions(+), 146 deletions(-) diff --git a/lisp/backend/audio_utils.py b/lisp/backend/audio_utils.py index 873e5adb7..36c3bd8f8 100644 --- a/lisp/backend/audio_utils.py +++ b/lisp/backend/audio_utils.py @@ -92,3 +92,28 @@ def audio_file_duration(path: str): return duration return 0 + + +def iec_scale(dB): + """IEC 268-18:1995 standard dB scaling. + + adapted from: http://plugin.org.uk/meterbridge/ + """ + scale = 100 + + if dB < -70.0: + scale = 0.0 + elif dB < -60.0: + scale = (dB + 70.0) * 0.25 + elif dB < -50.0: + scale = (dB + 60.0) * 0.50 + 5 + elif dB < -40.0: + scale = (dB + 50.0) * 0.75 + 7.5 + elif dB < -30.0: + scale = (dB + 40.0) * 1.5 + 15 + elif dB < -20.0: + scale = (dB + 30.0) * 2.0 + 30 + elif dB < 0: + scale = (dB + 20.0) * 2.5 + 50 + + return scale / 100 diff --git a/lisp/plugins/gst_backend/elements/db_meter.py b/lisp/plugins/gst_backend/elements/db_meter.py index bbab82262..c14a00ac3 100644 --- a/lisp/plugins/gst_backend/elements/db_meter.py +++ b/lisp/plugins/gst_backend/elements/db_meter.py @@ -28,7 +28,7 @@ class DbMeter(GstMediaElement): MediaType = MediaType.Audio Name = QT_TRANSLATE_NOOP("MediaElementName", "dB Meter") - interval = GstProperty("level", "interval", default=50 * Gst.MSECOND) + interval = GstProperty("level", "interval", default=33 * Gst.MSECOND) peak_ttl = GstProperty("level", "peak-ttl", default=Gst.SECOND) peak_falloff = GstProperty("level", "peak-falloff", default=20) @@ -39,9 +39,9 @@ def __init__(self, pipeline): self.pipeline = pipeline self.level = Gst.ElementFactory.make("level", None) self.level.set_property("post-messages", True) - self.level.set_property("interval", 50 * Gst.MSECOND) - self.level.set_property("peak-ttl", Gst.SECOND) - self.level.set_property("peak-falloff", 20) + self.level.set_property("interval", self.interval) + self.level.set_property("peak-ttl", self.peak_ttl) + self.level.set_property("peak-falloff", self.peak_falloff) self.audio_convert = Gst.ElementFactory.make("audioconvert", None) self.pipeline.add(self.level) diff --git a/lisp/plugins/gst_backend/gst_waveform.py b/lisp/plugins/gst_backend/gst_waveform.py index fe8b04e79..099b64618 100644 --- a/lisp/plugins/gst_backend/gst_waveform.py +++ b/lisp/plugins/gst_backend/gst_waveform.py @@ -35,7 +35,7 @@ class GstWaveform(Waveform): "! audiobuffersplit output-buffer-duration={sample_length} " "! appsink name=app_sink emit-signals=true sync=false" ) - MAX_PCM_VALUE = 32768 + MAX_S16_PCM_VALUE = 32768 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -100,8 +100,8 @@ def _on_new_sample(self, sink, _): # Get the all data from the buffer, as bytes # We expect each audio sample to be 16bits signed integer data_bytes = buffer.extract_dup(0, buffer.get_size()) - # Get max (peak-to-peak) of the samples - self._temp_peak.append(audioop.maxpp(data_bytes, 2)) + # Get the max of the absolute values in the samples + self._temp_peak.append(audioop.max(data_bytes, 2)) # Get rms of the samples self._temp_rms.append(audioop.rms(data_bytes, 2)) @@ -111,14 +111,15 @@ def _on_bus_message(self, bus, message): if message.type == Gst.MessageType.EOS: self._eos() elif message.type == Gst.MessageType.ERROR: - self._clear() - error, debug = message.parse_error() logger.warning( f'Cannot generate waveform for "{self._uri.unquoted_uri}": {error.message}', exc_info=GstError(debug), ) + self._clear() + self.failed.emit() + def _eos(self): """Called when the file has been processed.""" self.peak_samples = [] @@ -127,10 +128,10 @@ def _eos(self): # Normalize data for peak, rms in zip(self._temp_peak, self._temp_rms): self.peak_samples.append( - round(peak / self.MAX_PCM_VALUE, self.MAX_DECIMALS) + round(peak / self.MAX_S16_PCM_VALUE, self.MAX_DECIMALS) ) self.rms_samples.append( - round(rms / self.MAX_PCM_VALUE, self.MAX_DECIMALS) + round(rms / self.MAX_S16_PCM_VALUE, self.MAX_DECIMALS) ) # Dump the data into a file (does nothing if caching is disabled) diff --git a/lisp/plugins/gst_backend/settings/db_meter.py b/lisp/plugins/gst_backend/settings/db_meter.py index 5822c8b02..d8cdde190 100644 --- a/lisp/plugins/gst_backend/settings/db_meter.py +++ b/lisp/plugins/gst_backend/settings/db_meter.py @@ -50,7 +50,7 @@ def __init__(self, **kwargs): self.intervalSpin = QSpinBox(self.groupBox) self.intervalSpin.setRange(1, 1000) self.intervalSpin.setMaximum(1000) - self.intervalSpin.setValue(50) + self.intervalSpin.setValue(33) self.groupBox.layout().addWidget(self.intervalSpin, 0, 0) self.intervalLabel = QLabel(self.groupBox) @@ -61,7 +61,7 @@ def __init__(self, **kwargs): self.ttlSpin = QSpinBox(self.groupBox) self.ttlSpin.setSingleStep(10) self.ttlSpin.setRange(10, 10000) - self.ttlSpin.setValue(500) + self.ttlSpin.setValue(1000) self.groupBox.layout().addWidget(self.ttlSpin, 1, 0) self.ttlLabel = QLabel(self.groupBox) @@ -91,8 +91,8 @@ def retranslateUi(self): ) def loadSettings(self, settings): - self.intervalSpin.setValue(settings.get("interval", 50) / Gst.MSECOND) - self.ttlSpin.setValue(settings.get("peak_ttl", 500) / Gst.MSECOND) + self.intervalSpin.setValue(settings.get("interval", 33) / Gst.MSECOND) + self.ttlSpin.setValue(settings.get("peak_ttl", 1000) / Gst.MSECOND) self.falloffSpin.setValue(settings.get("peak_falloff", 20)) def getSettings(self): diff --git a/lisp/ui/widgets/dbmeter.py b/lisp/ui/widgets/dbmeter.py index 97faaa0d1..0cc265fc9 100644 --- a/lisp/ui/widgets/dbmeter.py +++ b/lisp/ui/widgets/dbmeter.py @@ -15,123 +15,135 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5 import QtCore -from PyQt5.QtGui import QLinearGradient, QColor, QPainter -from PyQt5.QtWidgets import QWidget +from typing import Callable +from PyQt5.QtCore import QRect, QRectF +from PyQt5.QtGui import QLinearGradient, QColor, QPainter, QPixmap +from PyQt5.QtWidgets import QWidget -class DBMeter(QWidget): - def __init__(self, parent, min=-60, max=0, clip=0): - super().__init__(parent) - self.db_min = min - self.db_max = max - self.db_clip = clip +from lisp.backend.audio_utils import iec_scale - db_range = abs(self.db_min - self.db_max) - yellow = abs(self.db_min + 20) / db_range # -20 db - red = abs(self.db_min) / db_range # 0 db - self.grad = QLinearGradient() - self.grad.setColorAt(0, QColor(0, 255, 0)) # Green - self.grad.setColorAt(yellow, QColor(255, 255, 0)) # Yellow - self.grad.setColorAt(red, QColor(255, 0, 0)) # Red +class DBMeter(QWidget): + """DPM - Digital Peak Meter widget""" + + def __init__( + self, + parent=None, + dBMin: int = -70, + dBMax: int = 0, + clipping: int = 0, + smoothing: float = 0.66, + scale: Callable = iec_scale, + **kwargs, + ): + super().__init__(parent, **kwargs) + self.dBMin = dBMin + self.dBMax = dBMax + self.dbClipping = clipping + self.valueSmoothing = smoothing + self.scale = scale + + self.backgroundColor = QColor(32, 32, 32) + self.borderColor = QColor(80, 80, 80) + self.clippingColor = QColor(220, 50, 50) + + self._currentSmoothing = self.valueSmoothing + self._pixmap = QPixmap() self.reset() def reset(self): - self.peaks = [self.db_min, self.db_min] - self.rmss = [self.db_min, self.db_min] - self.decPeak = [self.db_min, self.db_min] + self.peaks = [self.dBMin, self.dBMin] + self.decayPeaks = [self.dBMin, self.dBMin] self.clipping = {} - self.repaint() - def plot(self, peaks, rms, decPeak): - self.peaks = peaks - self.rmss = rms - self.decPeak = decPeak + self.update() - self.repaint() + def plot(self, peaks, _, decayPeak): + for n in range(min(len(peaks), len(self.peaks))): + if peaks[n] > self.dbClipping: + self.clipping[n] = True - def paintEvent(self, e): - if not self.visibleRegion().isEmpty(): - # Stretch factor - mul = self.height() - 4 - mul /= self.db_max - self.db_min - - peaks = [] - for n, peak in enumerate(self.peaks): - if peak > self.db_clip: - self.clipping[n] = True - - # Checks also for NaN values - if peak < self.db_min or peak != peak: - peak = self.db_min - elif peak > self.db_max: - peak = self.db_max - - peaks.append(round((peak - self.db_min) * mul)) - - rmss = [] - for rms in self.rmss: - # Checks also for NaN values - if rms < self.db_min or rms != rms: - rms = self.db_min - elif rms > self.db_max: - rms = self.db_max - - rmss.append(round((rms - self.db_min) * mul)) - - dPeaks = [] - for dPeak in self.decPeak: - # Checks also for NaN values - if dPeak < self.db_min or dPeak != dPeak: - dPeak = self.db_min - elif dPeak > self.db_max: - dPeak = self.db_max - - dPeaks.append(round((dPeak - self.db_min) * mul)) - - qp = QPainter() - qp.begin(self) - qp.setBrush(QColor(0, 0, 0, 0)) - - xpos = 0 - xdim = self.width() / len(peaks) - - for n, (peak, rms, dPeak) in enumerate(zip(peaks, rmss, dPeaks)): - # Maximum 'peak-rect' size - maxRect = QtCore.QRect( - xpos, self.height() - 2, xdim - 2, 2 - self.height() - ) - - # Set QLinearGradient start and final-stop position - self.grad.setStart(maxRect.topLeft()) - self.grad.setFinalStop(maxRect.bottomRight()) - - # Draw peak (audio peak in dB) - rect = QtCore.QRect(xpos, self.height() - 2, xdim - 2, -peak) - qp.setOpacity(0.6) - qp.fillRect(rect, self.grad) - qp.setOpacity(1.0) - - # Draw rms (in db) - rect = QtCore.QRect(xpos, self.height() - 2, xdim - 2, -rms) - qp.fillRect(rect, self.grad) - - # Draw decay peak - decRect = QtCore.QRect( - xpos, (self.height() - 3) - dPeak, xdim - 2, 2 - ) - qp.fillRect(decRect, self.grad) - - # Draw Borders - if self.clipping.get(n, False): - qp.setPen(QColor(200, 0, 0)) + if self.valueSmoothing: + if peaks[n] < self.peaks[n]: + peaks[n] = self.peaks[n] - self._currentSmoothing + self._currentSmoothing *= 1.1 else: - qp.setPen(QColor(100, 100, 100)) + self._currentSmoothing = self.valueSmoothing - qp.drawRect(maxRect) + self.peaks = peaks + self.decayPeaks = decayPeak - xpos += xdim + self.update() - qp.end() + def updatePixmap(self): + """Prepare the colored rect to be used during paintEvent(s)""" + w = self.width() + h = self.height() + + dbRange = abs(self.dBMin - self.dBMax) + + gradient = QLinearGradient(0, 0, 0, h) + gradient.setColorAt(0, QColor(230, 0, 0)) + gradient.setColorAt(10 / dbRange, QColor(255, 220, 0)) + gradient.setColorAt(30 / dbRange, QColor(0, 220, 0)) + gradient.setColorAt(1, QColor(0, 160, 0)) + + self._pixmap = QPixmap(w, h) + QPainter(self._pixmap).fillRect(0, 0, w, h, gradient) + + def resizeEvent(self, event): + self.updatePixmap() + + def paintEvent(self, e): + height = self.height() + width = self.width() + + painter = QPainter() + painter.begin(self) + painter.setBrush(self.backgroundColor) + + # Calculate the meter size (per single channel) + meterWidth = width / len(self.peaks) + meterRect = QRect(0, 0, meterWidth - 2, height - 1) + + # Draw each channel + for n, values in enumerate(zip(self.peaks, self.decayPeaks)): + # Draw Background + if self.clipping.get(n, False): + painter.setPen(self.clippingColor) + else: + painter.setPen(self.borderColor) + + painter.drawRect(meterRect) + + # Limit the values between dbMin and dbMax + peak = max(self.dBMin, min(self.dBMax, values[0])) + decayPeak = max(self.dBMin, min(self.dBMax, values[1])) + # Scale the values to the widget height + peak = self.scale(peak) * (height - 2) + decayPeak = self.scale(decayPeak) * (height - 2) + + # Draw peak (audio peak in dB) + peakRect = QRectF( + meterRect.x() + 1, + height - peak - 1, + meterRect.width() - 1, + peak, + ) + painter.drawPixmap(peakRect, self._pixmap, peakRect) + + # Draw decay indicator + decayRect = QRectF( + meterRect.x() + 1, + height - decayPeak - 1, + meterRect.width() - 1, + 1, + ) + painter.drawPixmap(decayRect, self._pixmap, decayRect) + + # Move to the next meter + meterRect.translate(meterWidth, 0) + + painter.end() diff --git a/lisp/ui/widgets/dynamicfontsize.py b/lisp/ui/widgets/dynamicfontsize.py index d4402b2dc..c58390226 100644 --- a/lisp/ui/widgets/dynamicfontsize.py +++ b/lisp/ui/widgets/dynamicfontsize.py @@ -24,7 +24,7 @@ class DynamicFontSizeMixin: FONT_PRECISION = 0.5 - PADDING = 8 + FONT_PADDING = 8 def getWidgetMaximumFontSize(self, text: str): font = self.font() @@ -34,8 +34,8 @@ def getWidgetMaximumFontSize(self, text: str): return currentSize widgetRect = QRectF(self.contentsRect()) - widgetWidth = widgetRect.width() - self.PADDING - widgetHeight = widgetRect.height() - self.PADDING + widgetWidth = widgetRect.width() - self.FONT_PADDING + widgetHeight = widgetRect.height() - self.FONT_PADDING step = currentSize / 2.0 # If too small, increase step diff --git a/lisp/ui/widgets/waveform.py b/lisp/ui/widgets/waveform.py index cd96776aa..7c5c3a930 100644 --- a/lisp/ui/widgets/waveform.py +++ b/lisp/ui/widgets/waveform.py @@ -1,14 +1,17 @@ from math import floor, ceil -from PyQt5.QtCore import QLineF, pyqtSignal +from PyQt5.QtCore import QLineF, pyqtSignal, Qt, QRectF from PyQt5.QtGui import QPainter, QPen, QColor, QBrush from PyQt5.QtWidgets import QWidget +from lisp.backend.waveform import Waveform from lisp.core.signal import Connection +from lisp.core.util import strtime +from lisp.ui.widgets.dynamicfontsize import DynamicFontSizeMixin class WaveformWidget(QWidget): - def __init__(self, waveform, **kwargs): + def __init__(self, waveform: Waveform, **kwargs): super().__init__(**kwargs) self._waveform = waveform self._maximum = self._waveform.duration @@ -16,6 +19,8 @@ def __init__(self, waveform, **kwargs): self._value = 0 self._lastDrawnValue = 0 + self.backgroundColor = QColor(32, 32, 32) + self.backgroundRadius = 6 self.elapsedPeakColor = QColor(75, 154, 250) self.elapsedRmsColor = QColor(153, 199, 255) self.remainsPeakColor = QColor(90, 90, 90) @@ -44,28 +49,28 @@ def value(self): def setValue(self, value): self._value = min(value, self._maximum) - if self.isVisible(): + # if we are not visible we can skip this + if not self.visibleRegion().isEmpty(): # Repaint only if we have new pixels to draw if self._value >= floor(self._lastDrawnValue + self._valueToPx): - x = self._lastDrawnValue / self._valueToPx - width = (self._value - self._lastDrawnValue) / self._valueToPx + x = self._lastDrawnValue // self._valueToPx + width = (self._value - self._lastDrawnValue) // self._valueToPx # Repaint only the changed area - self.update(floor(x), 0, ceil(width), self.height()) + self.update(x - 1, 0, width + 1, self.height()) elif self._value <= ceil(self._lastDrawnValue - self._valueToPx): - x = self._value / self._valueToPx - width = (self._lastDrawnValue - self._value) / self._valueToPx + x = self._value // self._valueToPx + width = (self._lastDrawnValue - self._value) // self._valueToPx # Repaint only the changed area - self.update(floor(x), 0, ceil(width) + 1, self.height()) + self.update(x - 1, 0, width + 1, self.height()) def resizeEvent(self, event): self._valueToPx = self._maximum / self.width() def paintEvent(self, event): halfHeight = self.height() / 2 - painter = QPainter() - painter.begin(self) + painter.begin(self) pen = QPen() pen.setWidth(1) painter.setPen(pen) @@ -80,7 +85,7 @@ def paintEvent(self, event): peakRemainsLines = [] rmsElapsedLines = [] rmsRemainsLines = [] - for x in range(event.rect().x(), self.rect().right()): + for x in range(event.rect().x(), event.rect().right() + 1): # Calculate re-sample interval s0 = floor(x * samplesToPx) s1 = ceil(x * samplesToPx + samplesToPx) @@ -135,8 +140,10 @@ def paintEvent(self, event): painter.end() -class WaveformSlider(WaveformWidget): - """ Implement an API similar to a QAbstractSlider. """ +class WaveformSlider(DynamicFontSizeMixin, WaveformWidget): + """Implement an API similar to a QAbstractSlider.""" + + FONT_PADDING = 1 sliderMoved = pyqtSignal(int) sliderJumped = pyqtSignal(int) @@ -145,13 +152,20 @@ def __init__(self, waveform, **kwargs): super().__init__(waveform, **kwargs) self.setMouseTracking(True) - self._mouseDown = False self._lastPosition = -1 + self._mouseDown = False + self._labelRight = True + self._maxFontSize = self.font().pointSizeF() - self.backgroundColor = QColor(32, 32, 32) - self.seekIndicatorColor = QColor(255, 0, 0) + self.seekIndicatorColor = QColor(Qt.red) + self.seekTimestampBG = QColor(32, 32, 32) + self.seekTimestampFG = QColor(Qt.white) + + def _xToValue(self, x): + return round(x * self._valueToPx) def leaveEvent(self, event): + self._labelRight = True self._lastPosition = -1 def mouseMoveEvent(self, event): @@ -159,24 +173,36 @@ def mouseMoveEvent(self, event): self.update() if self._mouseDown: - self.sliderMoved.emit(round(self._lastPosition * self._valueToPx)) + self.sliderMoved.emit(self._xToValue(self._lastPosition)) def mousePressEvent(self, event): self._mouseDown = True def mouseReleaseEvent(self, event): self._mouseDown = False - self.sliderJumped.emit(round(event.x() * self._valueToPx)) + self.sliderJumped.emit(self._xToValue(event.x())) + + def resizeEvent(self, event): + fontSize = self.getWidgetMaximumFontSize("0123456789") + if fontSize > self._maxFontSize: + fontSize = self._maxFontSize + + font = self.font() + font.setPointSizeF(fontSize) + self.setFont(font) + + super().resizeEvent(event) def paintEvent(self, event): - # Draw background painter = QPainter() + pen = QPen(QColor(0, 0, 0, 0)) + + # Draw the background (it will be clipped to event.rect()) painter.begin(self) - painter.setRenderHint(QPainter.Antialiasing) - pen = QPen(QColor(0, 0, 0, 0)) painter.setPen(pen) painter.setBrush(QBrush(self.backgroundColor)) + painter.setRenderHint(QPainter.Antialiasing) painter.drawRoundedRect(self.rect(), 6, 6) painter.end() @@ -184,16 +210,48 @@ def paintEvent(self, event): # Draw the waveform super().paintEvent(event) - # If necessary (mouse-over) draw the seek indicator + # If needed (mouse-over) draw the seek indicator, and it's timestamp if self._lastPosition >= 0: painter.begin(self) + # Draw the indicator as a 1px vertical line pen.setWidth(1) pen.setColor(self.seekIndicatorColor) painter.setPen(pen) - painter.drawLine( self._lastPosition, 0, self._lastPosition, self.height() ) + # Get the timestamp of the indicator position + text = strtime(self._xToValue(self._lastPosition))[:-3] + textSize = self.fontMetrics().size(Qt.TextSingleLine, text) + # Vertical offset to center the label + vOffset = (self.height() - textSize.height()) / 2 + + # Decide on which side of the indicator the label should be drawn + left = self._lastPosition - textSize.width() - 14 + right = self._lastPosition + textSize.width() + 14 + if (self._labelRight and right < self.width()) or left < 0: + xOffset = self._lastPosition + 6 + self._labelRight = True + else: + xOffset = self._lastPosition - textSize.width() - 14 + self._labelRight = False + + # Define the label rect, add 8px of width for left/right padding + rect = QRectF( + xOffset, vOffset, textSize.width() + 8, textSize.height() + ) + + # Draw the label rect + pen.setColor(self.seekIndicatorColor.darker(150)) + painter.setPen(pen) + painter.setBrush(QBrush(self.seekTimestampBG)) + painter.drawRoundedRect(rect, 2, 2) + + # Draw the timestamp + pen.setColor(self.seekTimestampFG) + painter.setPen(pen) + painter.drawText(rect, Qt.AlignCenter, text) + painter.end() From e9636fbfad4429eb431af991f570f7dabbf9445a Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 10 Apr 2020 17:38:47 +0200 Subject: [PATCH 229/333] Add option to enable/disable waveform in ListLayout Update: the "GO" key shortcut dose not require a restart anymore Fix: ListLayout sidebar widget are now shown properly when resized --- lisp/backend/waveform.py | 6 +- lisp/plugins/cart_layout/settings.py | 4 +- lisp/plugins/gst_backend/gst_settings.py | 17 ++++- lisp/plugins/list_layout/layout.py | 8 +-- lisp/plugins/list_layout/playing_view.py | 6 +- lisp/plugins/list_layout/settings.py | 79 ++++++++++++++---------- lisp/ui/settings/app_pages/cue.py | 8 ++- 7 files changed, 85 insertions(+), 43 deletions(-) diff --git a/lisp/backend/waveform.py b/lisp/backend/waveform.py index 8bbc57d3c..03e09c775 100644 --- a/lisp/backend/waveform.py +++ b/lisp/backend/waveform.py @@ -78,7 +78,7 @@ def cache_path(self, refresh=True): ) def load_waveform(self): - """ Load the waveform. + """Load the waveform. If the waveform is ready returns True, False otherwise, in that case the "ready" signal will be emitted when the processing is complete. @@ -99,7 +99,7 @@ def clear(self): @abstractmethod def _load_waveform(self): - """ Implemented by subclasses. Load the waveform from the file. + """Implemented by subclasses. Load the waveform from the file. Should return True if the waveform is already available, False if otherwise. @@ -107,7 +107,7 @@ def _load_waveform(self): """ def _from_cache(self): - """ Retrieve data from a cache file, if caching is enabled. """ + """Retrieve data from a cache file, if caching is enabled.""" try: cache_path = self.cache_path() if self._enable_cache and path.exists(cache_path): diff --git a/lisp/plugins/cart_layout/settings.py b/lisp/plugins/cart_layout/settings.py index c029c6b21..e6fc3feae 100644 --- a/lisp/plugins/cart_layout/settings.py +++ b/lisp/plugins/cart_layout/settings.py @@ -79,7 +79,9 @@ def __init__(self, **kwargs): def retranslateUi(self): self.behaviorsGroup.setTitle( - translate("CartLayout", "Default behaviors") + translate( + "CartLayout", "Default behaviors (applied to new sessions)" + ) ) self.countdownMode.setText(translate("CartLayout", "Countdown mode")) self.showSeek.setText(translate("CartLayout", "Show seek-bars")) diff --git a/lisp/plugins/gst_backend/gst_settings.py b/lisp/plugins/gst_backend/gst_settings.py index 412f43cb8..f0cc92a1d 100644 --- a/lisp/plugins/gst_backend/gst_settings.py +++ b/lisp/plugins/gst_backend/gst_settings.py @@ -16,7 +16,7 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP -from PyQt5.QtWidgets import QVBoxLayout, QGroupBox +from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel from lisp.plugins.gst_backend.gst_pipe_edit import GstPipeEdit from lisp.ui.settings.pages import SettingsPage @@ -33,15 +33,28 @@ def __init__(self, **kwargs): self.pipeGroup = QGroupBox(self) self.pipeGroup.setLayout(QVBoxLayout()) + self.pipeGroup.layout().setSpacing( + self.pipeGroup.layout().contentsMargins().top() + ) self.layout().addWidget(self.pipeGroup) + self.noticeLabel = QLabel(self.pipeGroup) + self.noticeLabel.setAlignment(Qt.AlignCenter) + font = self.noticeLabel.font() + font.setPointSizeF(font.pointSizeF() * 0.9) + self.noticeLabel.setFont(font) + self.pipeGroup.layout().addWidget(self.noticeLabel) + self.pipeEdit = GstPipeEdit("", app_mode=True) self.pipeGroup.layout().addWidget(self.pipeEdit) self.retranslateUi() def retranslateUi(self): - self.pipeGroup.setTitle(translate("GstSettings", "Pipeline")) + self.pipeGroup.setTitle(translate("GstSettings", "Default pipeline")) + self.noticeLabel.setText( + translate("GstSettings", "Applied only to new cues.") + ) def loadSettings(self, settings): self.pipeEdit.set_pipe(settings["pipeline"]) diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index b8f52278e..6ec0a9d94 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -140,9 +140,6 @@ def __init__(self, application): layout_menu.addAction(self.reset_size_action) # Load settings - self._go_key_sequence = QKeySequence( - ListLayout.Config["goKey"], QKeySequence.NativeText - ) self._set_seeksliders_visible(ListLayout.Config["show.seekSliders"]) self._set_accurate_time(ListLayout.Config["show.accurateTime"]) self._set_dbmeters_visible(ListLayout.Config["show.dBMeters"]) @@ -265,7 +262,10 @@ def _key_pressed(self, event): event.ignore() if not event.isAutoRepeat(): sequence = keyEventKeySequence(event) - if sequence in self._go_key_sequence: + goSequence = QKeySequence( + ListLayout.Config["goKey"], QKeySequence.NativeText + ) + if sequence in goSequence: event.accept() self.__go_slot() elif sequence == QKeySequence.Delete: diff --git a/lisp/plugins/list_layout/playing_view.py b/lisp/plugins/list_layout/playing_view.py index 22cbfcadf..505df84e1 100644 --- a/lisp/plugins/list_layout/playing_view.py +++ b/lisp/plugins/list_layout/playing_view.py @@ -120,4 +120,8 @@ def resizeEvent(self, event): width = self.viewport().width() for n in range(self.count()): - self.itemWidget(self.item(n)).updateSize(width) + item = self.item(n) + widget = self.itemWidget(item) + + widget.updateSize(width) + item.setSizeHint(widget.size()) diff --git a/lisp/plugins/list_layout/settings.py b/lisp/plugins/list_layout/settings.py index 169511010..f8ee3b958 100644 --- a/lisp/plugins/list_layout/settings.py +++ b/lisp/plugins/list_layout/settings.py @@ -25,6 +25,8 @@ QKeySequenceEdit, QGridLayout, QSpinBox, + QWidget, + QScrollArea, ) from lisp.cues.cue import CueAction @@ -33,60 +35,66 @@ from lisp.ui.widgets import CueActionComboBox -class ListLayoutSettings(SettingsPage): +class ListLayoutSettings(QScrollArea, SettingsPage): Name = QT_TRANSLATE_NOOP("SettingsPageName", "List Layout") def __init__(self, **kwargs): super().__init__(**kwargs) - self.setLayout(QVBoxLayout()) - self.layout().setAlignment(Qt.AlignTop) + self.setWidgetResizable(True) + self.contentWidget = QWidget(self) + self.contentWidget.setLayout(QVBoxLayout()) + self.contentWidget.layout().setAlignment(Qt.AlignTop) - self.behaviorsGroup = QGroupBox(self) - self.behaviorsGroup.setLayout(QVBoxLayout()) - self.layout().addWidget(self.behaviorsGroup) + self.defaultBehaviorsGroup = QGroupBox(self.contentWidget) + self.defaultBehaviorsGroup.setLayout(QVBoxLayout()) + self.contentWidget.layout().addWidget(self.defaultBehaviorsGroup) - self.showDbMeters = QCheckBox(self.behaviorsGroup) - self.behaviorsGroup.layout().addWidget(self.showDbMeters) + self.showDbMeters = QCheckBox(self.defaultBehaviorsGroup) + self.defaultBehaviorsGroup.layout().addWidget(self.showDbMeters) - self.showAccurate = QCheckBox(self.behaviorsGroup) - self.behaviorsGroup.layout().addWidget(self.showAccurate) + self.showAccurate = QCheckBox(self.defaultBehaviorsGroup) + self.defaultBehaviorsGroup.layout().addWidget(self.showAccurate) - self.showSeek = QCheckBox(self.behaviorsGroup) - self.behaviorsGroup.layout().addWidget(self.showSeek) + self.showSeek = QCheckBox(self.defaultBehaviorsGroup) + self.defaultBehaviorsGroup.layout().addWidget(self.showSeek) - self.autoNext = QCheckBox(self.behaviorsGroup) - self.behaviorsGroup.layout().addWidget(self.autoNext) + self.autoNext = QCheckBox(self.defaultBehaviorsGroup) + self.defaultBehaviorsGroup.layout().addWidget(self.autoNext) - self.selectionMode = QCheckBox(self.behaviorsGroup) - self.behaviorsGroup.layout().addWidget(self.selectionMode) + self.selectionMode = QCheckBox(self.defaultBehaviorsGroup) + self.defaultBehaviorsGroup.layout().addWidget(self.selectionMode) - self.goLayout = QGridLayout() - self.goLayout.setColumnStretch(0, 1) - self.goLayout.setColumnStretch(1, 1) - self.behaviorsGroup.layout().addLayout(self.goLayout) + self.behaviorsGroup = QGroupBox(self.contentWidget) + self.behaviorsGroup.setLayout(QGridLayout()) + self.behaviorsGroup.layout().setColumnStretch(0, 1) + self.behaviorsGroup.layout().setColumnStretch(1, 1) + self.contentWidget.layout().addWidget(self.behaviorsGroup) + + self.useWaveformSeek = QCheckBox(self.behaviorsGroup) + self.behaviorsGroup.layout().addWidget(self.useWaveformSeek, 0, 0, 1, 2) self.goKeyLabel = QLabel(self.behaviorsGroup) - self.goLayout.addWidget(self.goKeyLabel, 0, 0) + self.behaviorsGroup.layout().addWidget(self.goKeyLabel, 1, 0) self.goKeyEdit = QKeySequenceEdit(self.behaviorsGroup) - self.goLayout.addWidget(self.goKeyEdit, 0, 1) + self.behaviorsGroup.layout().addWidget(self.goKeyEdit, 1, 1) self.goActionLabel = QLabel(self.behaviorsGroup) - self.goLayout.addWidget(self.goActionLabel, 1, 0) + self.behaviorsGroup.layout().addWidget(self.goActionLabel, 2, 0) self.goActionCombo = CueActionComboBox( actions=(CueAction.Default, CueAction.Start, CueAction.FadeInStart), mode=CueActionComboBox.Mode.Value, ) - self.goLayout.addWidget(self.goActionCombo, 1, 1) + self.behaviorsGroup.layout().addWidget(self.goActionCombo, 2, 1) self.goDelayLabel = QLabel(self.behaviorsGroup) - self.goLayout.addWidget(self.goDelayLabel, 2, 0) + self.behaviorsGroup.layout().addWidget(self.goDelayLabel, 3, 0) self.goDelaySpin = QSpinBox(self.behaviorsGroup) self.goDelaySpin.setMaximum(10000) - self.goLayout.addWidget(self.goDelaySpin, 2, 1) + self.behaviorsGroup.layout().addWidget(self.goDelaySpin, 3, 1) - self.useFadeGroup = QGroupBox(self) + self.useFadeGroup = QGroupBox(self.contentWidget) self.useFadeGroup.setLayout(QGridLayout()) - self.layout().addWidget(self.useFadeGroup) + self.contentWidget.layout().addWidget(self.useFadeGroup) # Fade settings self.stopCueFade = QCheckBox(self.useFadeGroup) @@ -98,11 +106,14 @@ def __init__(self, **kwargs): self.interruptCueFade = QCheckBox(self.useFadeGroup) self.useFadeGroup.layout().addWidget(self.interruptCueFade, 3, 0) + self.setWidget(self.contentWidget) self.retranslateUi() def retranslateUi(self): - self.behaviorsGroup.setTitle( - translate("ListLayout", "Default behaviors") + self.defaultBehaviorsGroup.setTitle( + translate( + "ListLayout", "Default behaviors (applied to new sessions)" + ) ) self.showDbMeters.setText(translate("ListLayout", "Show dB-meters")) self.showAccurate.setText(translate("ListLayout", "Show accurate time")) @@ -112,6 +123,10 @@ def retranslateUi(self): translate("ListLayout", "Enable selection mode") ) + self.behaviorsGroup.setTitle(translate("ListLayout", "Behaviors")) + self.useWaveformSeek.setText( + translate("ListLayout", "Use waveform seek-bar") + ) self.goKeyLabel.setText(translate("ListLayout", "GO Key:")) self.goActionLabel.setText(translate("ListLayout", "GO Action:")) self.goDelayLabel.setText( @@ -130,6 +145,7 @@ def loadSettings(self, settings): self.showDbMeters.setChecked(settings["show"]["dBMeters"]) self.showAccurate.setChecked(settings["show"]["accurateTime"]) self.showSeek.setChecked(settings["show"]["seekSliders"]) + self.useWaveformSeek.setChecked(settings["show"]["waveformSlider"]) self.autoNext.setChecked(settings["autoContinue"]) self.selectionMode.setChecked(settings["selectionMode"]) @@ -149,7 +165,8 @@ def getSettings(self): "show": { "accurateTime": self.showAccurate.isChecked(), "dBMeters": self.showDbMeters.isChecked(), - "seekBars": self.showSeek.isChecked(), + "seekSliders": self.showSeek.isChecked(), + "waveformSlider": self.useWaveformSeek.isChecked(), }, "autoContinue": self.autoNext.isChecked(), "selectionMode": self.selectionMode.isChecked(), diff --git a/lisp/ui/settings/app_pages/cue.py b/lisp/ui/settings/app_pages/cue.py index 370a470be..612ef4d5c 100644 --- a/lisp/ui/settings/app_pages/cue.py +++ b/lisp/ui/settings/app_pages/cue.py @@ -46,10 +46,16 @@ def __init__(self, **kwargs): # Fade action defaults self.fadeActionsDefaultsGroup = QGroupBox(self) self.fadeActionsDefaultsGroup.setLayout(QVBoxLayout()) + self.fadeActionsDefaultsGroup.layout().setSpacing( + self.fadeActionsDefaultsGroup.layout().contentsMargins().top() + ) self.layout().addWidget(self.fadeActionsDefaultsGroup) self.fadeActionDefaultsHelpText = QLabel(self.fadeActionsDefaultsGroup) - self.fadeActionDefaultsHelpText.setWordWrap(True) + self.fadeActionDefaultsHelpText.setAlignment(Qt.AlignCenter) + font = self.fadeActionDefaultsHelpText.font() + font.setPointSizeF(font.pointSizeF() * 0.9) + self.fadeActionDefaultsHelpText.setFont(font) self.fadeActionsDefaultsGroup.layout().addWidget( self.fadeActionDefaultsHelpText ) From 4bcf033177193afd5525f79a4006472a6a0c20e9 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 10 Apr 2020 17:40:30 +0200 Subject: [PATCH 230/333] Update translation files --- lisp/i18n/ts/en/cart_layout.ts | 26 ++++---- lisp/i18n/ts/en/gst_backend.ts | 11 +++- lisp/i18n/ts/en/lisp.ts | 107 +++++++++++++++++++-------------- lisp/i18n/ts/en/list_layout.ts | 68 ++++++++++++--------- 4 files changed, 121 insertions(+), 91 deletions(-) diff --git a/lisp/i18n/ts/en/cart_layout.ts b/lisp/i18n/ts/en/cart_layout.ts index b172437e6..706f608db 100644 --- a/lisp/i18n/ts/en/cart_layout.ts +++ b/lisp/i18n/ts/en/cart_layout.ts @@ -3,37 +3,32 @@ CartLayout - - Default behaviors - - - - + Countdown mode - + Show seek-bars - + Show dB-meters - + Show accurate time - + Show volume - + Grid size @@ -98,15 +93,20 @@ - + Number of columns: - + Number of rows: + + + Default behaviors (applied to new sessions) + + LayoutDescription diff --git a/lisp/i18n/ts/en/gst_backend.ts b/lisp/i18n/ts/en/gst_backend.ts index 592064d86..f550912b9 100644 --- a/lisp/i18n/ts/en/gst_backend.ts +++ b/lisp/i18n/ts/en/gst_backend.ts @@ -158,9 +158,14 @@ GstSettings - - Pipeline - Pipeline + + Default pipeline + + + + + Applied only to new cues. + diff --git a/lisp/i18n/ts/en/lisp.ts b/lisp/i18n/ts/en/lisp.ts index 4b5ff4eff..61e53f60a 100644 --- a/lisp/i18n/ts/en/lisp.ts +++ b/lisp/i18n/ts/en/lisp.ts @@ -236,47 +236,42 @@ CartLayout - - Default behaviors - - - - + Countdown mode - + Show seek-bars - + Show dB-meters - + Show accurate time - + Show volume - + Grid size - + Number of columns: - + Number of rows: @@ -340,6 +335,11 @@ Are you sure to continue? + + + Default behaviors (applied to new sessions) + + CollectionCue @@ -848,12 +848,12 @@ Default action to stop the cue - + Interrupt action fade - + Fade actions default value @@ -1057,8 +1057,13 @@ GstSettings - - Pipeline + + Default pipeline + + + + + Applied only to new cues. @@ -1221,107 +1226,102 @@ ListLayout - - Default behaviors - - - - + Show dB-meters - + Show accurate time - + Show seek-bars - + Auto-select next cue - + Enable selection mode - + GO Key: - + GO Action: - + GO minimum interval (ms): - + Use fade (buttons) - + Stop Cue - + Pause Cue - + Resume Cue - + Interrupt Cue - + Edit cue - + Edit selected Edit selected - + Clone cue - + Clone selected - + Remove cue - + Remove selected - + Selection mode @@ -1396,20 +1396,35 @@ - + Show index column - + Show resize handles - + Restore default size + + + Default behaviors (applied to new sessions) + + + + + Behaviors + + + + + Use waveform seek-bar + + ListLayoutHeader @@ -2691,7 +2706,7 @@ - + List Layout diff --git a/lisp/i18n/ts/en/list_layout.ts b/lisp/i18n/ts/en/list_layout.ts index 68e5f8f3c..2a8121d02 100644 --- a/lisp/i18n/ts/en/list_layout.ts +++ b/lisp/i18n/ts/en/list_layout.ts @@ -37,72 +37,67 @@ ListLayout - - Default behaviors - - - - + Show dB-meters - + Show accurate time - + Show seek-bars - + Auto-select next cue - + Enable selection mode - + Use fade (buttons) - + Stop Cue - + Pause Cue - + Resume Cue - + Interrupt Cue - + Edit cue - + Remove cue - + Selection mode @@ -137,37 +132,37 @@ - + Edit selected - + Clone cue - + Clone selected - + Remove selected - + GO Key: - + GO Action: - + GO minimum interval (ms): @@ -177,20 +172,35 @@ - + Show index column - + Show resize handles - + Restore default size + + + Default behaviors (applied to new sessions) + + + + + Behaviors + + + + + Use waveform seek-bar + + ListLayoutHeader @@ -231,7 +241,7 @@ SettingsPageName - + List Layout From d7816ad652613413e2e09bdcd2e0a91383d4a0a3 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 11 Apr 2020 14:10:49 +0200 Subject: [PATCH 231/333] Merge "config to disable go key while playing" (close #192) --- lisp/plugins/list_layout/default.json | 3 ++- lisp/plugins/list_layout/layout.py | 28 ++++++++++++++++++++++++++- lisp/plugins/list_layout/settings.py | 24 ++++++++++++++++------- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/lisp/plugins/list_layout/default.json b/lisp/plugins/list_layout/default.json index 916d316cd..449d09c6c 100644 --- a/lisp/plugins/list_layout/default.json +++ b/lisp/plugins/list_layout/default.json @@ -11,6 +11,7 @@ "selectionMode": false, "autoContinue": true, "goKey": "Space", + "goKeyDisabledWhilePlaying": false, "goAction": "Default", "goDelay": 100, "stopCueFade": true, @@ -18,4 +19,4 @@ "resumeCueFade": true, "interruptCueFade": true, "infoPanelFontSize": 12 -} \ No newline at end of file +} diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index 6ec0a9d94..ccbc4eac2 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -61,6 +61,7 @@ class ListLayout(CueLayout): accurate_time = ProxyProperty() selection_mode = ProxyProperty() view_sizes = ProxyProperty() + go_key_disabled_while_playing = ProxyProperty() def __init__(self, application): super().__init__(application) @@ -126,6 +127,13 @@ def __init__(self, application): self.selection_mode_action.setShortcut(QKeySequence("Ctrl+Alt+S")) layout_menu.addAction(self.selection_mode_action) + self.go_key_disabled_while_playing_action = QAction(layout_menu) + self.go_key_disabled_while_playing_action.setCheckable(True) + self.go_key_disabled_while_playing_action.triggered.connect( + self._set_go_key_disabled_while_playing + ) + layout_menu.addAction(self.go_key_disabled_while_playing_action) + layout_menu.addSeparator() self.enable_view_resize_action = QAction(layout_menu) @@ -146,6 +154,9 @@ def __init__(self, application): self._set_index_visible(ListLayout.Config["show.indexColumn"]) self._set_selection_mode(ListLayout.Config["selectionMode"]) self._set_auto_continue(ListLayout.Config["autoContinue"]) + self._set_go_key_disabled_while_playing( + ListLayout.Config["goKeyDisabledWhilePlaying"] + ) # Context menu actions self._edit_actions_group = MenuActionsGroup(priority=MENU_PRIORITY_CUE) @@ -197,6 +208,9 @@ def retranslate(self): self.reset_size_action.setText( translate("ListLayout", "Restore default size") ) + self.go_key_disabled_while_playing_action.setText( + translate("ListLayout", "Disable GO Key While Playing") + ) @property def model(self): @@ -267,7 +281,11 @@ def _key_pressed(self, event): ) if sequence in goSequence: event.accept() - self.__go_slot() + if not ( + self.go_key_disabled_while_playing + and len(self._running_model) + ): + self.__go_slot() elif sequence == QKeySequence.Delete: event.accept() self._remove_cues(self.selected_cues()) @@ -282,6 +300,14 @@ def _key_pressed(self, event): else: self.key_pressed.emit(event) + @go_key_disabled_while_playing.set + def _set_go_key_disabled_while_playing(self, enable): + self.go_key_disabled_while_playing_action.setChecked(enable) + + @go_key_disabled_while_playing.get + def _get_go_key_disabled_while_playing(self): + return self.go_key_disabled_while_playing_action.isChecked() + @accurate_time.set def _set_accurate_time(self, accurate): self.show_accurate_action.setChecked(accurate) diff --git a/lisp/plugins/list_layout/settings.py b/lisp/plugins/list_layout/settings.py index f8ee3b958..87ed37476 100644 --- a/lisp/plugins/list_layout/settings.py +++ b/lisp/plugins/list_layout/settings.py @@ -73,24 +73,27 @@ def __init__(self, **kwargs): self.useWaveformSeek = QCheckBox(self.behaviorsGroup) self.behaviorsGroup.layout().addWidget(self.useWaveformSeek, 0, 0, 1, 2) + self.goKeyDisabledWhilePlaying = QCheckBox(self.behaviorsGroup) + self.behaviorsGroup.layout().addWidget(self.goKeyDisabledWhilePlaying, 1, 0, 1, 2) + self.goKeyLabel = QLabel(self.behaviorsGroup) - self.behaviorsGroup.layout().addWidget(self.goKeyLabel, 1, 0) + self.behaviorsGroup.layout().addWidget(self.goKeyLabel, 2, 0) self.goKeyEdit = QKeySequenceEdit(self.behaviorsGroup) - self.behaviorsGroup.layout().addWidget(self.goKeyEdit, 1, 1) + self.behaviorsGroup.layout().addWidget(self.goKeyEdit, 2, 1) self.goActionLabel = QLabel(self.behaviorsGroup) - self.behaviorsGroup.layout().addWidget(self.goActionLabel, 2, 0) + self.behaviorsGroup.layout().addWidget(self.goActionLabel, 3, 0) self.goActionCombo = CueActionComboBox( actions=(CueAction.Default, CueAction.Start, CueAction.FadeInStart), mode=CueActionComboBox.Mode.Value, ) - self.behaviorsGroup.layout().addWidget(self.goActionCombo, 2, 1) + self.behaviorsGroup.layout().addWidget(self.goActionCombo, 3, 1) self.goDelayLabel = QLabel(self.behaviorsGroup) - self.behaviorsGroup.layout().addWidget(self.goDelayLabel, 3, 0) + self.behaviorsGroup.layout().addWidget(self.goDelayLabel, 4, 0) self.goDelaySpin = QSpinBox(self.behaviorsGroup) self.goDelaySpin.setMaximum(10000) - self.behaviorsGroup.layout().addWidget(self.goDelaySpin, 3, 1) + self.behaviorsGroup.layout().addWidget(self.goDelaySpin, 4, 1) self.useFadeGroup = QGroupBox(self.contentWidget) self.useFadeGroup.setLayout(QGridLayout()) @@ -125,7 +128,10 @@ def retranslateUi(self): self.behaviorsGroup.setTitle(translate("ListLayout", "Behaviors")) self.useWaveformSeek.setText( - translate("ListLayout", "Use waveform seek-bar") + translate("ListLayout", "Use waveform seek-bars") + ) + self.goKeyDisabledWhilePlaying.setText( + translate("ListLayout", "GO Key Disabled While Playing") ) self.goKeyLabel.setText(translate("ListLayout", "GO Key:")) self.goActionLabel.setText(translate("ListLayout", "GO Action:")) @@ -149,6 +155,9 @@ def loadSettings(self, settings): self.autoNext.setChecked(settings["autoContinue"]) self.selectionMode.setChecked(settings["selectionMode"]) + self.goKeyDisabledWhilePlaying.setChecked( + settings["goKeyDisabledWhilePlaying"] + ) self.goKeyEdit.setKeySequence( QKeySequence(settings["goKey"], QKeySequence.NativeText) ) @@ -175,6 +184,7 @@ def getSettings(self): ), "goAction": self.goActionCombo.currentItem(), "goDelay": self.goDelaySpin.value(), + "goKeyDisabledWhilePlaying": self.goKeyDisabledWhilePlaying.isChecked(), "stopCueFade": self.stopCueFade.isChecked(), "pauseCueFade": self.pauseCueFade.isChecked(), "resumeCueFade": self.resumeCueFade.isChecked(), From 50db8a010d0f60c94f2064096bffc21634604eec Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 21 Apr 2020 09:36:05 +0200 Subject: [PATCH 232/333] Fix a problem with python 3.7, update readme logo --- README.md | 20 +++++++++++--------- lisp/core/session_uri.py | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 15d5bab80..4d21c297b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ -![Linux Show Player Logo](https://raw.githubusercontent.com/wiki/FrancescoCeruti/linux-show-player/media/site_logo.png) -

Cue player designed for stage productions

+

+ Logo +

+

Linux Show Player

+

Cue player for stage productions

-License: GPL -GitHub release -Github All Releases -Code Health -Gitter -Code style: black -Say Thanks! + License: GPL + GitHub release + Github All Releases + Code Health + Gitter + Code style: black

--- diff --git a/lisp/core/session_uri.py b/lisp/core/session_uri.py index ef3912ed6..980501bc6 100644 --- a/lisp/core/session_uri.py +++ b/lisp/core/session_uri.py @@ -44,7 +44,7 @@ def relative_path(self): return self.path_to_relative(path) @property - @lru_cache + @lru_cache(maxsize=256) def absolute_path(self): """Unquoted "path" component of the URI.""" # We can cache this, the absolute path doesn't change From 2443e763452a3a61a8f5a67019beec70de1dda1e Mon Sep 17 00:00:00 2001 From: s0600204 <2053873+s0600204@users.noreply.github.com> Date: Mon, 18 May 2020 20:17:21 +0100 Subject: [PATCH 233/333] Draw scale markings on level meters (#202) --- lisp/plugins/list_layout/playing_widgets.py | 2 +- lisp/ui/widgets/dbmeter.py | 83 ++++++++++++++++++++- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/lisp/plugins/list_layout/playing_widgets.py b/lisp/plugins/list_layout/playing_widgets.py index 033154038..ffdceb65c 100644 --- a/lisp/plugins/list_layout/playing_widgets.py +++ b/lisp/plugins/list_layout/playing_widgets.py @@ -227,7 +227,7 @@ def set_dbmeter_visible(self, visible): # Add/Remove the QDbMeter in the layout if visible and not self.dbmeter.isVisible(): self.gridLayout.addWidget(self.dbmeter, 0, 2, 3, 1) - self.gridLayout.setColumnStretch(2, 1) + self.gridLayout.setColumnStretch(2, 2) elif not visible and self.dbmeter.isVisible(): self.gridLayout.removeWidget(self.dbmeter) self.gridLayout.setColumnStretch(2, 0) diff --git a/lisp/ui/widgets/dbmeter.py b/lisp/ui/widgets/dbmeter.py index 0cc265fc9..a9edf0afd 100644 --- a/lisp/ui/widgets/dbmeter.py +++ b/lisp/ui/widgets/dbmeter.py @@ -15,10 +15,11 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +from math import ceil from typing import Callable -from PyQt5.QtCore import QRect, QRectF -from PyQt5.QtGui import QLinearGradient, QColor, QPainter, QPixmap +from PyQt5.QtCore import QPoint, QPointF, QRect, QRectF, Qt +from PyQt5.QtGui import QLinearGradient, QColor, QPainter, QPixmap, QFontDatabase, QFontMetrics from PyQt5.QtWidgets import QWidget from lisp.backend.audio_utils import iec_scale @@ -27,6 +28,8 @@ class DBMeter(QWidget): """DPM - Digital Peak Meter widget""" + scale_steps = [1, 5, 10, 20, 50] + def __init__( self, parent=None, @@ -35,6 +38,7 @@ def __init__( clipping: int = 0, smoothing: float = 0.66, scale: Callable = iec_scale, + unit: str = 'dBFS', **kwargs, ): super().__init__(parent, **kwargs) @@ -43,14 +47,24 @@ def __init__( self.dbClipping = clipping self.valueSmoothing = smoothing self.scale = scale + self.unit = unit self.backgroundColor = QColor(32, 32, 32) self.borderColor = QColor(80, 80, 80) self.clippingColor = QColor(220, 50, 50) self._currentSmoothing = self.valueSmoothing + self._markings = [] self._pixmap = QPixmap() + font = QFontDatabase.systemFont(QFontDatabase.FixedFont) + font.setPointSize(font.pointSize() - 3) + self.setFont(font) + font.setPointSize(font.pointSize() - 1) + self.unit_font = font + self.scale_width = 0 + self.paint_scale_text = True + self.reset() def reset(self): @@ -75,7 +89,40 @@ def plot(self, peaks, _, decayPeak): self.peaks = peaks self.decayPeaks = decayPeak - self.update() + if self.paint_scale_text: + self.repaint(0, 0, self.width() - self.scale_width, self.height()) + else: + self.repaint(0, 0, self.width(), self.height()) + + def updateMarkings(self): + self._markings = [] + + height = self.height() + # We assume that we're using numerals that lack descenders + font_height = QFontMetrics(self.font()).ascent() + curr_level = self.dBMax + curr_y = ceil(font_height / 2) + + while curr_y < height - font_height: + prev_level = curr_level + prev_y = curr_y + font_height + + for step in self.scale_steps: + curr_level = prev_level - step + curr_y = height - self.scale(curr_level) * (height - 2) + if curr_y > prev_y: + break + + self._markings.append([curr_y, curr_level]) + + self._markings.pop() + + self.scale_width = max( + QFontMetrics(self.font()).boundingRect(str(-abs(self.dBMax - self.dBMin))).width(), + QFontMetrics(self.unit_font).boundingRect(self.unit).width() + ) + + self.paint_scale_text = self.width() > self.scale_width * 2 def updatePixmap(self): """Prepare the colored rect to be used during paintEvent(s)""" @@ -95,6 +142,7 @@ def updatePixmap(self): def resizeEvent(self, event): self.updatePixmap() + self.updateMarkings() def paintEvent(self, e): height = self.height() @@ -105,7 +153,11 @@ def paintEvent(self, e): painter.setBrush(self.backgroundColor) # Calculate the meter size (per single channel) - meterWidth = width / len(self.peaks) + if self.paint_scale_text: + usableWidth = (width - self.scale_width) + else: + usableWidth = width + meterWidth = usableWidth / len(self.peaks) meterRect = QRect(0, 0, meterWidth - 2, height - 1) # Draw each channel @@ -143,7 +195,30 @@ def paintEvent(self, e): ) painter.drawPixmap(decayRect, self._pixmap, decayRect) + # Draw markings + x_start = meterRect.x() + meterRect.width() / 2 + x_end = meterRect.x() + meterRect.width() + for mark in self._markings: + painter.drawLine(QPointF(x_start, mark[0]), QPointF(x_end, mark[0])) + # Move to the next meter meterRect.translate(meterWidth, 0) + if not self.paint_scale_text or not e.region().contains(QPoint(usableWidth, 0)): + painter.end() + return + + # Write the scale marking text + text_height = QFontMetrics(self.font()).height() + text_offset = text_height / 2 + painter.setPen(self.palette().windowText().color()) + painter.drawText(0, 0, width, text_height, Qt.AlignRight, str(self.dBMax)) + for mark in self._markings: + painter.drawText(0, mark[0] - text_offset, width, width, Qt.AlignRight, str(mark[1])) + + # And the units that the scale uses + text_height = QFontMetrics(self.unit_font).height() + painter.setFont(self.unit_font) + painter.drawText(0, height - text_height, width, text_height, Qt.AlignRight, self.unit) + painter.end() From fa09dab462060349c8acf16fe4890dde27a1805b Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 18 May 2020 23:34:41 +0200 Subject: [PATCH 234/333] Various UI and translations-related fixes Fix: plugins translations strings being included into the main ts files Fix: "SettingsPageName" are now translated correctly in AppConfigurationDialog Fix: "CueCategory" (Edit Menu) are now translated correctly Fix: Layouts names are now translated correctly in the startup dialog Fix: GStreamer DbMeter emitting "level_ready" after the media "stopped" signal Update: update translations strings Update: other minor updates --- i18n_update.py | 42 +- lisp/i18n/ts/en/lisp.ts | 2965 +++-------------- lisp/i18n/ts/en/list_layout.ts | 66 +- lisp/plugins/cart_layout/cue_widget.py | 4 +- lisp/plugins/gst_backend/elements/db_meter.py | 16 +- lisp/plugins/list_layout/settings.py | 4 +- lisp/ui/layoutselect.py | 6 +- lisp/ui/mainwindow.py | 8 +- lisp/ui/settings/app_configuration.py | 33 +- lisp/ui/widgets/dbmeter.py | 100 +- lisp/ui/widgets/pagestreewidget.py | 7 +- lisp/ui/widgets/waveform.py | 2 +- 12 files changed, 562 insertions(+), 2691 deletions(-) diff --git a/i18n_update.py b/i18n_update.py index 9cb338e8b..fc0d1decf 100755 --- a/i18n_update.py +++ b/i18n_update.py @@ -17,6 +17,7 @@ # along with Linux Show Player. If not, see . import os +import re import subprocess import sys from argparse import ArgumentParser @@ -36,39 +37,39 @@ def dirs_path(path: str): def existing_locales(): - for entry in os.scandir(TS_DIR): - if entry.is_dir(): - yield entry.name - + for language_dir in dirs_path(TS_DIR): + yield language_dir.name -def source_files(root: Path, extensions: Iterable[str] = ("py",)): - for ext in extensions: - yield from root.glob("**/*.{}".format(ext)) - -def module_sources(module_path: Path, extensions: Iterable[str] = ("py",)): - return module_path.stem, tuple(source_files(module_path, extensions)) +def source_files(root: Path, extensions: Iterable[str], exclude: str = ""): + for entry in root.glob("**/*"): + if ( + entry.is_file() + and entry.suffix in extensions + and (not exclude or re.search(exclude, str(entry)) is None) + ): + yield entry def modules_sources( - modules_path: Iterable[Path], extensions: Iterable[str] = ("py",) + modules_path: Iterable[Path], extensions: Iterable[str], exclude: str = "", ): for module_path in modules_path: - if not module_path.match("__*__"): - yield module_sources(module_path, extensions) + yield module_path.stem, source_files(module_path, extensions, exclude), def lupdate( modules_path: Iterable[Path], locales: Iterable[str], options: Iterable[str] = (), - extensions: Iterable[str] = ("py",), + extensions: Iterable[str] = (".py",), + exclude: str = "", ): - ts_files = dict(modules_sources(modules_path, extensions)) + ts_files = modules_sources(modules_path, extensions, exclude) for locale in locales: locale_path = TS_DIR.joinpath(locale) locale_path.mkdir(exist_ok=True) - for module_name, sources in ts_files.items(): + for module_name, sources in ts_files: subprocess.run( ( PYLUPDATE, @@ -86,7 +87,7 @@ def lrelease(locales: Iterable[str], options: Iterable[str] = ()): for locale in locales: qm_file = QM_DIR.joinpath("base_{}.qm".format(locale)) locale_path = TS_DIR.joinpath(locale) - ts_files = source_files(locale_path, extensions=("ts",)) + ts_files = source_files(locale_path, extensions=(".ts",)) subprocess.run( (LRELEASE, *options, *ts_files, "-qm", qm_file), @@ -108,7 +109,7 @@ def lrelease(locales: Iterable[str], options: Iterable[str] = ()): parser.add_argument( "-n", "--noobsolete", - help="If --qm, discard obsolete strings", + help="If updating (--ts), discard obsolete strings", action="store_true", ) parser.add_argument( @@ -138,9 +139,12 @@ def lrelease(locales: Iterable[str], options: Iterable[str] = ()): options.append("-noobsolete") lupdate( - list(dirs_path("lisp/plugins")) + [Path("lisp")], locales, options + [Path("lisp")], locales, options=options, exclude="^lisp/plugins/" ) + lupdate(dirs_path("lisp/plugins"), locales, options=options) if do_release: print(">>> RELEASING ...") lrelease(locales) + + print(">>> DONE!") diff --git a/lisp/i18n/ts/en/lisp.ts b/lisp/i18n/ts/en/lisp.ts index 61e53f60a..bbf899f9f 100644 --- a/lisp/i18n/ts/en/lisp.ts +++ b/lisp/i18n/ts/en/lisp.ts @@ -1,13 +1,5 @@ - - APIServerInfo - - - Stop serving network API - - - About @@ -64,22 +56,6 @@ - - AlsaSinkSettings - - - ALSA device - - - - - ApiServerError - - - Network API server stopped working. - - - AppConfiguration @@ -140,2875 +116,714 @@ - AudioDynamicSettings - - - Compressor - - - - - Expander - - + CommandsStack - - Soft Knee + + Undo: {} - - Hard Knee + + Redo: {} + + + ConfigurationDebug - - Compressor/Expander + + Configuration written at {} + + + ConfigurationInfo - - Type + + New configuration installed at {} + + + CueAction - - Curve Shape - + + Default + Default - - Ratio - + + Pause + Pause - - Threshold (dB) - + + Start + Start - - - AudioPanSettings - - Audio Pan - + + Stop + Stop - - Center + + Faded Start - - Left + + Faded Resume - - Right + + Faded Pause - - - CacheManager - - Cache size warning + + Faded Stop - - Warning threshold in MB (0 = disabled) + + Faded Interrupt - - Cache cleanup + + Resume - - Delete the cache content + + Do Nothing - CartLayout + CueAppearanceSettings - - Countdown mode - + + The appearance depends on the layout + The appearance depends on the layout - - Show seek-bars - + + Cue name + Cue name - - Show dB-meters - + + NoName + NoName - - Show accurate time - + + Description/Note + Description/Note - - Show volume - + + Set Font Size + Set Font Size - - Grid size - + + Color + Color - - Number of columns: - + + Select background color + Select background color - - Number of rows: - + + Select font color + Select font color + + + CueCategory - - Play + + Misc cues + + + CueCommandLog - - Pause - Pause + + Cue settings changed: "{}" + Cue settings changed: "{}" - - Stop - Stop + + Cues settings changed. + Cues settings changed. + + + CueName - - Reset volume - + + Media Cue + Media Cue + + + CueNextAction - - Add page + + Do Nothing - - Add pages + + Trigger after the end - - Remove current page + + Trigger after post wait - - Number of Pages: + + Select after the end - - Page {number} + + Select after post wait + + + CueSettings - - Warning - Warning + + Pre wait + Pre wait - - Every cue in the page will be lost. - + + Wait before cue execution + Wait before cue execution - - Are you sure to continue? - + + Post wait + Post wait - - Default behaviors (applied to new sessions) - + + Wait after cue execution + Wait after cue execution - - - CollectionCue - - Add - + + Next action + Next action - - Remove - + + Start action + Start action - - Cue - Cue + + Default action to start the cue + Default action to start the cue - - Action - + + Stop action + Stop action - - - CommandCue - - Command - + + Default action to stop the cue + Default action to stop the cue - - Command to execute, as in a shell + + Interrupt action fade - - Discard command output + + Fade actions default value + + + Fade + + + Linear + Linear + - - Ignore command errors - + + Quadratic + Quadratic - - Kill instead of terminate - + + Quadratic2 + Quadratic2 - CommandsStack + FadeEdit - - Undo: {} + + Duration (sec): - - Redo: {} + + Curve: - ConfigurationDebug + FadeSettings - - Configuration written at {} - + + Fade In + Fade In - - - ConfigurationInfo - - New configuration installed at {} - + + Fade Out + Fade Out - Controller + HotKeyEdit - - Cannot load controller protocol: "{}" + + Press shortcut - ControllerKeySettings + LayoutSelect - - Action - + + Layout selection + Layout selection - - Shortcuts - + + Select layout + Select layout - - Shortcut - + + Open file + Open file - ControllerMidiSettings + ListLayout - - MIDI + + Layout actions - - Type + + Fade out when stopping all cues - - Action + + Fade out when interrupting all cues - - Capture - - - - - Listening MIDI messages ... - - - - - -- All Messages -- - - - - - Capture filter - - - - - Data 1 - - - - - Data 2 - - - - - Data 3 - - - - - ControllerOscSettings - - - OSC Message - - - - - OSC - - - - - Path - - - - - Types - - - - - Arguments - - - - - OSC Capture - - - - - Add - - - - - Remove - - - - - Capture - - - - - Waiting for messages: - - - - - /path/to/method - - - - - Action - - - - - ControllerOscSettingsWarning - - - Warning - Warning - - - - ControllerSettings - - - Add - - - - - Remove - - - - - Cue Name - - - OSC Settings - - - - - CueAction - - - Default - Default - - - - Pause - Pause - - - - Start - Start - - - - Stop - Stop - - - - Faded Start - - - - - Faded Resume - - - - - Faded Pause - - - - - Faded Stop - - - - - Faded Interrupt - - - - - Resume - - - - - Do Nothing - - - - - CueAppearanceSettings - - - The appearance depends on the layout - The appearance depends on the layout - - - - Cue name - Cue name - - - - NoName - NoName - - - - Description/Note - Description/Note - - - - Set Font Size - Set Font Size - - - - Color - Color - - - - Select background color - Select background color - - - - Select font color - Select font color - - - - CueCategory - - - Action cues - - - - - Integration cues - - - - - Misc cues - - - - - CueCommandLog - - - Cue settings changed: "{}" - Cue settings changed: "{}" - - - - Cues settings changed. - Cues settings changed. - - - - CueName - - - Media Cue - Media Cue - - - - Index Action - - - - - Seek Cue - - - - - Volume Control - - - - - Command Cue - - - - - Collection Cue - - - - - Stop-All - - - - - MIDI Cue - - - - - OSC Cue - - - - - CueNextAction - - - Do Nothing - - - - - Trigger after the end - - - - - Trigger after post wait - - - - - Select after the end - - - - - Select after post wait - - - - - CueSettings - - - Pre wait - Pre wait - - - - Wait before cue execution - Wait before cue execution - - - - Post wait - Post wait - - - - Wait after cue execution - Wait after cue execution - - - - Next action - Next action - - - - Start action - Start action - - - - Default action to start the cue - Default action to start the cue - - - - Stop action - Stop action - - - - Default action to stop the cue - Default action to stop the cue - - - - Interrupt action fade - - - - - Fade actions default value - - - - - CueTriggers - - - Started - - - - - Paused - - - - - Stopped - - - - - Ended - - - - - DbMeterSettings - - - DbMeter settings - - - - - Time between levels (ms) - - - - - Peak ttl (ms) - - - - - Peak falloff (dB/sec) - - - - - Equalizer10Settings - - - 10 Bands Equalizer (IIR) - - - - - Fade - - - Linear - Linear - - - - Quadratic - Quadratic - - - - Quadratic2 - Quadratic2 - - - - FadeEdit - - - Duration (sec): - - - - - Curve: - - - - - FadeSettings - - - Fade In - Fade In - - - - Fade Out - Fade Out - - - - GlobalAction - - - Go - - - - - Reset - - - - - Stop all cues - - - - - Pause all cues - - - - - Resume all cues - - - - - Interrupt all cues - - - - - Fade-out all cues - - - - - Fade-in all cues - - - - - Move standby forward - - - - - Move standby back - - - - - GstBackend - - - Audio cue (from file) - - - - - Select media files - - - - - GstMediaError - - - Cannot create pipeline element: "{}" - - - - - GstMediaSettings - - - Change Pipeline - - - - - GstMediaWarning - - - Invalid pipeline element: "{}" - - - - - GstPipelineEdit - - - Edit Pipeline - - - - - GstSettings - - - Default pipeline - - - - - Applied only to new cues. - - - - - HotKeyEdit - - - Press shortcut - - - - - IndexActionCue - - - No suggestion - - - - - Index - - - - - Use a relative index - - - - - Target index - - - - - Action - - - - - Suggested cue name - - - - - JackSinkSettings - - - Connections - - - - - Edit connections - - - - - Output ports - - - - - Input ports - - - - - Connect - - - - - Disconnect - - - - - LayoutDescription - - - Organize the cues in a list - - - - - Organize cues in grid like pages - - - - - LayoutDetails - - - SHIFT + Space or Double-Click to edit a cue - - - - - To copy cues drag them while pressing CTRL - - - - - To move cues drag them - - - - - Click a cue to run it - - - - - SHIFT + Click to edit a cue - - - - - CTRL + Click to select a cue - - - - - To move cues drag them while pressing SHIFT - - - - - LayoutName - - - List Layout - - - - - Cart Layout - - - - - LayoutSelect - - - Layout selection - Layout selection - - - - Select layout - Select layout - - - - Open file - Open file - - - - ListLayout - - - Show dB-meters - - - - - Show accurate time - - - - - Show seek-bars - - - - - Auto-select next cue - - - - - Enable selection mode - - - - - GO Key: - - - - - GO Action: - - - - - GO minimum interval (ms): - - - - - Use fade (buttons) - - - - - Stop Cue - - - - - Pause Cue - - - - - Resume Cue - - - - - Interrupt Cue - - - - - Edit cue - - - - - Edit selected - Edit selected - - - - Clone cue - - - - - Clone selected - - - - - Remove cue - - - - - Remove selected - - - - - Selection mode - - - - - Pause all - - - - - Stop all - - - - - Interrupt all - - - - - Resume all - - - - - Fade-Out all - - - - - Fade-In all - - - - - Edit selected cues - - - - - Remove selected cues - - - - - Copy of {} - - - - - Layout actions - - - - - Fade out when stopping all cues - - - - - Fade out when interrupting all cues - - - - - Fade out when pausing all cues - - - - - Fade in when resuming all cues - - - - - Show index column - - - - - Show resize handles - - - - - Restore default size - - - - - Default behaviors (applied to new sessions) - - - - - Behaviors - - - - - Use waveform seek-bar - - - - - ListLayoutHeader - - - Cue - Cue - - - - Pre wait - Pre wait - - - - Action - - - - - Post wait - Post wait - - - - ListLayoutInfoPanel - - - Cue name - Cue name - - - - Cue description - - - - - LogStatusIcon - - - Errors/Warnings - - - - - Logging - - - Debug - Debug - - - - Warning - Warning - - - - Error - Error - - - - Dismiss all - - - - - Show details - - - - - Linux Show Player - Log Viewer - - - - - Info - Info - - - - Critical - - - - - Time - - - - - Milliseconds - - - - - Logger name - - - - - Level - - - - - Message - - - - - Function - - - - - Path name - - - - - File name - - - - - Line no. - - - - - Module - - - - - Process ID - - - - - Process name - - - - - Thread ID - - - - - Thread name - - - - - Exception info - - - - - Showing {} of {} records - - - - - MIDICue - - - MIDI Message - - - - - Message type - - - - - MIDIError - - - Cannot connect to MIDI output port '{}'. - - - - - Cannot connect to MIDI input port '{}'. - - - - - MIDIInfo - - - MIDI port disconnected: '{}' - - - - - Connecting to MIDI port: '{}' - - - - - Connecting to matching MIDI port: '{}' - - - - - MIDIMessageAttr - - - Channel - - - - - Note - - - - - Velocity - - - - - Control - - - - - Program - - - - - Value - - - - - Song - - - - - Pitch - - - - - Position - - - - - MIDIMessageType - - - Note ON - - - - - Note OFF - - - - - Polyphonic After-touch - - - - - Control/Mode Change - - - - - Program Change - - - - - Channel After-touch - - - - - Pitch Bend Change - - - - - Song Select - - - - - Song Position - - - - - Start - Start - - - - Stop - Stop - - - - Continue - - - - - MIDISettings - - - Input - - - - - Output - - - - - MIDI devices - - - - - Misc options - - - - - Try to connect using only device/port name - - - - - MainWindow - - - &File - &File - - - - New session - New session - - - - Open - Open - - - - Save session - Save session - - - - Preferences - Preferences - - - - Save as - Save as - - - - Full Screen - Full Screen - - - - Exit - Exit - - - - &Edit - &Edit - - - - Undo - Undo - - - - Redo - Redo - - - - Select all - Select all - - - - Select all media cues - Select all media cues - - - - Deselect all - Deselect all - - - - CTRL+SHIFT+A - CTRL+SHIFT+A - - - - Invert selection - Invert selection - - - - CTRL+I - CTRL+I - - - - Edit selected - Edit selected - - - - CTRL+SHIFT+E - CTRL+SHIFT+E - - - - &Layout - &Layout - - - - &Tools - &Tools - - - - Edit selection - Edit selection - - - - &About - &About - - - - About - About - - - - About Qt - About Qt - - - - Close session - Close session - - - - Do you want to save them now? - - - - - MainWindowDebug - - - Registered cue menu: "{}" - - - - - MainWindowError - - - Cannot create cue {} - - - - - MediaCueSettings - - - Start time - Start time - - - - Stop position of the media - Stop position of the media - - - - Stop time - Stop time - - - - Start position of the media - Start position of the media - - - - Loop - Loop - - - - MediaElementName - - - Pitch - - - - - Volume - - - - - Audio Pan - - - - - Compressor/Expander - - - - - JACK Out - - - - - URI Input - - - - - 10 Bands Equalizer - - - - - ALSA Out - - - - - dB Meter - - - - - Preset Input - - - - - PulseAudio Out - - - - - Custom Element - - - - - Speed - - - - - System Out - - - - - System Input - - - - - MediaInfo - - - Media Info - - - - - Warning - Warning - - - - Info - Info - - - - Value - - - - - Cannot get any information. - - - - - NetworkApiDebug - - - New end-point: {} - - - - - NetworkDiscovery - - - Host discovery - - - - - Manage hosts - - - - - Discover hosts - - - - - Manually add a host - - - - - Remove selected host - - - - - Remove all host - - - - - Address - - - - - Host IP - - - - - Select the hosts you want to add - - - - - Osc Cue - - - Type - - - - - Argument - - - - - OscCue - - - Add - - - - - Remove - - - - - Type - - - - - Value - - - - - FadeTo - - - - - Fade - - - - - OSC Message - - - - - OSC Path: - - - - - /path/to/something - - - - - Time (sec) - - - - - Curve - - - - - OscServerDebug - - - Message from {} -> path: "{}" args: {} - - - - - OscServerError - - - Cannot start OSC sever - - - - - OscServerInfo - - - OSC server started at {} - - - - - OSC server stopped - - - - - OscSettings - - - OSC Settings - - - - - Input Port: - - - - - Output Port: - - - - - Hostname: - - - - - PitchSettings - - - Pitch - - - - - {0:+} semitones - - - - - PluginsError - - - Failed to load "{}" - - - - - Failed to terminate plugin: "{}" - - - - - the requested plugin is not loaded: {} - - - - - PluginsInfo - - - Plugin loaded: "{}" - - - - - Plugin terminated: "{}" - - - - - PluginsWarning - - - Cannot satisfy dependencies for: {} - - - - - Preset - - - Create Cue - - - - - Load on selected Cues - - - - - PresetSrcSettings - - - Presets - - - - - Presets - - - Cannot scan presets - - - - - Error while deleting preset "{}" - - - - - Cannot load preset "{}" - - - - - Cannot save preset "{}" - - - - - Cannot rename preset "{}" - - - - - Select Preset - - - - - Presets - - - - - Preset name - - - - - Add - - - - - Rename - - - - - Edit - - - - - Remove - - - - - Export selected - - - - - Import - - - - - Warning - Warning - - - - The same name is already used! - - - - - Cannot export correctly. - - - - - Cannot import correctly. - - - - - Cue type - - - - - Load on cue - - - - - Load on selected cues - - - - - Save as preset - - - - - Cannot create a cue from this preset: {} - - - - - QColorButton - - - Right click to reset - Right click to reset - - - - RenameCues - - - Rename Cues - - - - - Rename cues - - - - - Current - - - - - Preview - - - - - Capitalize - - - - - Lowercase - - - - - Uppercase - - - - - Remove Numbers - - - - - Add numbering - - - - - Reset - - - - - Type your regex here: - - - - - Regex help - - - - - RenameCuesCommand - - - Renamed {number} cues - - - - - RenameUiDebug - - - Regex error: Invalid pattern - - - - - ReplayGain - - - ReplayGain / Normalization - - - - - Threads number - - - - - Apply only to selected media - - - - - ReplayGain to (dB SPL) - - - - - Normalize to (dB) - - - - - Processing files ... - - - - - Calculate - - - - - Reset all - - - - - Reset selected - - - - - ReplayGainDebug - - - Applied gain for: {} - - - - - Discarded gain for: {} - - - - - ReplayGainInfo - - - Gain processing stopped by user. - - - - - Started gain calculation for: {} + + Fade out when pausing all cues - - Gain calculated for: {} + + Fade in when resuming all cues - SeekCue - - - Cue - Cue - - - - Click to select - - - - - Not selected - - - - - Seek - - + LogStatusIcon - - Time to reach + + Errors/Warnings - - SettingsPageName - - - Appearance - Appearance - - - - General - General - - - - Cue - Cue - - - - Cue Settings - Cue Settings - - - - Plugins - - - - - Behaviours - Behaviours - - - - Pre/Post Wait - Pre/Post Wait - + + Logging - - Fade In/Out - Fade In/Out + + Debug + Debug - - Layouts - + + Warning + Warning - - Action Settings - + + Error + Error - - Seek Settings + + Dismiss all - - Volume Settings + + Show details - - Command + + Linux Show Player - Log Viewer - - Edit Collection - + + Info + Info - - Stop Settings + + Critical - - List Layout + + Time - - Timecode + + Milliseconds - - Timecode Settings + + Logger name - - Cue Control + + Level - - Layout Controls + + Message - - MIDI Controls + + Function - - Keyboard Shortcuts + + Path name - - OSC Controls + + File name - - MIDI Settings + + Line no. - - MIDI settings + + Module - - GStreamer + + Process ID - - Media Settings + + Process name - - Triggers + + Thread ID - - OSC settings + + Thread name - - Cart Layout + + Exception info - - Cache Manager + + Showing {} of {} records - SpeedSettings + MainWindow - - Speed - + + &File + &File - - - StopAll - - Stop Action - + + New session + New session - - - Synchronizer - - Synchronization - + + Open + Open - - Manage connected peers - + + Save session + Save session - - Show your IP - + + Preferences + Preferences - - Your IP is: {} - + + Save as + Save as - - - Timecode - - Cannot load timecode protocol: "{}" - + + Full Screen + Full Screen - - - TimecodeError - - Cannot send timecode. - + + Exit + Exit - - - TimecodeSettings - - Replace HOURS by a static track number - + + &Edit + &Edit - - Enable Timecode - + + Undo + Undo - - Track number - + + Redo + Redo - - Timecode Settings - + + Select all + Select all - - Timecode Format: - + + Select all media cues + Select all media cues - - Timecode Protocol: - + + Deselect all + Deselect all - - - TriggersSettings - - Add - + + CTRL+SHIFT+A + CTRL+SHIFT+A - - Remove - + + Invert selection + Invert selection - - Trigger - + + CTRL+I + CTRL+I - - Cue - Cue + + Edit selected + Edit selected - - Action - + + CTRL+SHIFT+E + CTRL+SHIFT+E - - - UriInputSettings - - Source - + + &Layout + &Layout - - Find File - + + &Tools + &Tools - - Buffering - + + Edit selection + Edit selection - - Use Buffering - + + &About + &About - - Attempt download on network streams - + + About + About - - Buffer size (-1 default value) - + + About Qt + About Qt - - Choose file - + + Close session + Close session - - All files + + Do you want to save them now? - UserElementSettings + MainWindowDebug - - User defined elements + + Registered cue menu: "{}" + + + MainWindowError - - Only for advanced user! + + Cannot create cue {} - VolumeControl + MediaCueSettings - - Cue - Cue + + Start time + Start time - - Click to select - + + Stop position of the media + Stop position of the media - - Not selected - + + Stop time + Stop time - - Volume to reach - + + Start position of the media + Start position of the media - - Fade - + + Loop + Loop - VolumeControlError + QColorButton - - Error during cue execution. - + + Right click to reset + Right click to reset - VolumeSettings + SettingsPageName - - Volume - + + Appearance + Appearance + + + + General + General + + + + Cue + Cue - - Normalized volume + + Cue Settings + Cue Settings + + + + Plugins - - Reset + + Behaviours + Behaviours + + + + Pre/Post Wait + Pre/Post Wait + + + + Fade In/Out + Fade In/Out + + + + Layouts diff --git a/lisp/i18n/ts/en/list_layout.ts b/lisp/i18n/ts/en/list_layout.ts index 2a8121d02..fc5325aa3 100644 --- a/lisp/i18n/ts/en/list_layout.ts +++ b/lisp/i18n/ts/en/list_layout.ts @@ -37,67 +37,67 @@ ListLayout - + Show dB-meters - + Show accurate time - + Show seek-bars - + Auto-select next cue - + Enable selection mode - + Use fade (buttons) - + Stop Cue - + Pause Cue - + Resume Cue - + Interrupt Cue - + Edit cue - + Remove cue - + Selection mode @@ -132,73 +132,83 @@ - + Edit selected - + Clone cue - + Clone selected - + Remove selected - + GO Key: - + GO Action: - + GO minimum interval (ms): - + Copy of {} - + Show index column - + Show resize handles - + Restore default size - + Default behaviors (applied to new sessions) - + Behaviors - - Use waveform seek-bar + + Disable GO Key While Playing + + + + + Use waveform seek-bars + + + + + GO Key Disabled While Playing diff --git a/lisp/plugins/cart_layout/cue_widget.py b/lisp/plugins/cart_layout/cue_widget.py index 8d4387397..eea530567 100644 --- a/lisp/plugins/cart_layout/cue_widget.py +++ b/lisp/plugins/cart_layout/cue_widget.py @@ -185,7 +185,9 @@ def showDBMeters(self, visible): if visible: self._dBMeterElement = self._cue.media.element("DbMeter") if self._dBMeterElement is not None: - self._dBMeterElement.level_ready.connect(self.dbMeter.plot) + self._dBMeterElement.level_ready.connect( + self.dbMeter.plot, Connection.QtQueued + ) self.hLayout.insertWidget(2, self.dbMeter, 1) self.dbMeter.show() diff --git a/lisp/plugins/gst_backend/elements/db_meter.py b/lisp/plugins/gst_backend/elements/db_meter.py index c14a00ac3..8d3236d46 100644 --- a/lisp/plugins/gst_backend/elements/db_meter.py +++ b/lisp/plugins/gst_backend/elements/db_meter.py @@ -66,10 +66,12 @@ def src(self): def __on_message(self, bus, message): if message.src == self.level: - structure = message.get_structure() - if structure is not None and structure.has_name("level"): - self.level_ready.emit( - structure.get_value("peak"), - structure.get_value("rms"), - structure.get_value("decay"), - ) + state = self.level.get_state(Gst.MSECOND)[1] + if state == Gst.State.PLAYING or state == Gst.State.PAUSED: + structure = message.get_structure() + if structure is not None and structure.has_name("level"): + self.level_ready.emit( + structure.get_value("peak"), + structure.get_value("rms"), + structure.get_value("decay"), + ) diff --git a/lisp/plugins/list_layout/settings.py b/lisp/plugins/list_layout/settings.py index 87ed37476..522df9f4a 100644 --- a/lisp/plugins/list_layout/settings.py +++ b/lisp/plugins/list_layout/settings.py @@ -74,7 +74,9 @@ def __init__(self, **kwargs): self.behaviorsGroup.layout().addWidget(self.useWaveformSeek, 0, 0, 1, 2) self.goKeyDisabledWhilePlaying = QCheckBox(self.behaviorsGroup) - self.behaviorsGroup.layout().addWidget(self.goKeyDisabledWhilePlaying, 1, 0, 1, 2) + self.behaviorsGroup.layout().addWidget( + self.goKeyDisabledWhilePlaying, 1, 0, 1, 2 + ) self.goKeyLabel = QLabel(self.behaviorsGroup) self.behaviorsGroup.layout().addWidget(self.goKeyLabel, 2, 0) diff --git a/lisp/ui/layoutselect.py b/lisp/ui/layoutselect.py index a5d2edd4e..e1e1ed5e3 100644 --- a/lisp/ui/layoutselect.py +++ b/lisp/ui/layoutselect.py @@ -67,7 +67,9 @@ def __init__(self, application, **kwargs): self.layout().addWidget(self.description, 2, 0, 1, 3) for layoutClass in layout.get_layouts(): - self.layoutCombo.addItem(layoutClass.NAME, layoutClass) + self.layoutCombo.addItem( + translate("LayoutName", layoutClass.NAME), layoutClass + ) if self.layoutCombo.count() == 0: raise Exception("No layout installed!") @@ -88,7 +90,7 @@ def updateDescription(self): self.description.setHtml( "

{}

{}

{}".format( - layoutClass.NAME, + translate("LayoutName", layoutClass.NAME), translate("LayoutDescription", layoutClass.DESCRIPTION), details, ) diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 1507acee3..e37cafcd0 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -20,7 +20,7 @@ from functools import partial from PyQt5 import QtCore -from PyQt5.QtCore import pyqtSignal +from PyQt5.QtCore import pyqtSignal, QT_TRANSLATE_NOOP from PyQt5.QtGui import QKeySequence from PyQt5.QtWidgets import ( QMainWindow, @@ -249,7 +249,7 @@ def registerCueMenu(self, name, function, category="", shortcut=""): if category: if category not in self._cueSubMenus: - subMenu = QMenu(category, self) + subMenu = QMenu(translate("CueCategory", category), self) self._cueSubMenus[category] = subMenu self.menuEdit.insertMenu(self.cueSeparator, subMenu) @@ -265,9 +265,9 @@ def registerCueMenu(self, name, function, category="", shortcut=""): def registerSimpleCueMenu(self, cueClass, category=""): self.registerCueMenu( - translate("CueName", cueClass.Name), + cueClass.Name, partial(self.__simpleCueInsert, cueClass), - category or translate("CueCategory", "Misc cues"), + category or QT_TRANSLATE_NOOP("CueCategory", "Misc cues"), ) def updateWindowTitle(self): diff --git a/lisp/ui/settings/app_configuration.py b/lisp/ui/settings/app_configuration.py index c15669ded..551cfa944 100644 --- a/lisp/ui/settings/app_configuration.py +++ b/lisp/ui/settings/app_configuration.py @@ -42,10 +42,11 @@ def __init__(self, **kwargs): self.resize(800, 510) self.setLayout(QVBoxLayout()) - self._confsMap = {} - self.model = PagesTreeModel() - for r_node in AppConfigurationDialog.PagesRegistry.children: - self._populateModel(QModelIndex(), r_node) + self.configurations = {} + + self.model = PagesTreeModel(tr_context="SettingsPageName") + for page_node in AppConfigurationDialog.PagesRegistry.children: + self._populateModel(QModelIndex(), page_node) self.mainPage = PagesTreeWidget(self.model) self.mainPage.selectFirst() @@ -70,16 +71,16 @@ def __init__(self, **kwargs): ) def applySettings(self): - for conf, pages in self._confsMap.items(): + for conf, pages in self.configurations.items(): for page in pages: conf.update(page.getSettings()) conf.write() - def _populateModel(self, m_parent, r_parent): - if r_parent.value is not None: - page_class = r_parent.value.page - config = r_parent.value.config + def _populateModel(self, model_parent, page_parent): + if page_parent.value is not None: + page_class = page_parent.value.page + config = page_parent.value.config else: page_class = None config = None @@ -88,14 +89,16 @@ def _populateModel(self, m_parent, r_parent): if page_class is None: # The current node have no page, use the parent model-index # as parent for it's children - mod_index = m_parent + model_index = model_parent else: page_instance = page_class() page_instance.loadSettings(config) - mod_index = self.model.addPage(page_instance, parent=m_parent) + model_index = self.model.addPage( + page_instance, parent=model_parent + ) # Keep track of configurations and corresponding pages - self._confsMap.setdefault(config, []).append(page_instance) + self.configurations.setdefault(config, []).append(page_instance) except Exception: if not isinstance(page_class, type): page_name = "InvalidPage" @@ -106,12 +109,12 @@ def _populateModel(self, m_parent, r_parent): translate( "AppConfigurationWarning", 'Cannot load configuration page: "{}" ({})', - ).format(page_name, r_parent.path()), + ).format(page_name, page_parent.path()), exc_info=True, ) else: - for r_node in r_parent.children: - self._populateModel(mod_index, r_node) + for page_node in page_parent.children: + self._populateModel(model_index, page_node) def __onOk(self): self.applySettings() diff --git a/lisp/ui/widgets/dbmeter.py b/lisp/ui/widgets/dbmeter.py index a9edf0afd..c87956a39 100644 --- a/lisp/ui/widgets/dbmeter.py +++ b/lisp/ui/widgets/dbmeter.py @@ -18,8 +18,15 @@ from math import ceil from typing import Callable -from PyQt5.QtCore import QPoint, QPointF, QRect, QRectF, Qt -from PyQt5.QtGui import QLinearGradient, QColor, QPainter, QPixmap, QFontDatabase, QFontMetrics +from PyQt5.QtCore import QPointF, QRect, QRectF, Qt, QPoint +from PyQt5.QtGui import ( + QLinearGradient, + QColor, + QPainter, + QPixmap, + QFontDatabase, + QFontMetrics, +) from PyQt5.QtWidgets import QWidget from lisp.backend.audio_utils import iec_scale @@ -38,7 +45,7 @@ def __init__( clipping: int = 0, smoothing: float = 0.66, scale: Callable = iec_scale, - unit: str = 'dBFS', + unit: str = "dBFS", **kwargs, ): super().__init__(parent, **kwargs) @@ -56,14 +63,14 @@ def __init__( self._currentSmoothing = self.valueSmoothing self._markings = [] self._pixmap = QPixmap() + self._scale_width = 0 + self._paint_scale_text = True font = QFontDatabase.systemFont(QFontDatabase.FixedFont) font.setPointSize(font.pointSize() - 3) self.setFont(font) font.setPointSize(font.pointSize() - 1) self.unit_font = font - self.scale_width = 0 - self.paint_scale_text = True self.reset() @@ -89,17 +96,16 @@ def plot(self, peaks, _, decayPeak): self.peaks = peaks self.decayPeaks = decayPeak - if self.paint_scale_text: - self.repaint(0, 0, self.width() - self.scale_width, self.height()) - else: - self.repaint(0, 0, self.width(), self.height()) + # Update, excluding the scale labels + self.update(0, 0, self.width() - self._scale_width, self.height()) def updateMarkings(self): self._markings = [] + fm = QFontMetrics(self.font()) height = self.height() # We assume that we're using numerals that lack descenders - font_height = QFontMetrics(self.font()).ascent() + font_height = fm.ascent() curr_level = self.dBMax curr_y = ceil(font_height / 2) @@ -117,12 +123,18 @@ def updateMarkings(self): self._markings.pop() - self.scale_width = max( - QFontMetrics(self.font()).boundingRect(str(-abs(self.dBMax - self.dBMin))).width(), - QFontMetrics(self.unit_font).boundingRect(self.unit).width() + self._scale_width = ( + max( + fm.boundingRect(str(self.dBMin)).width(), + fm.boundingRect(str(self.dBMax)).width(), + QFontMetrics(self.unit_font).boundingRect(self.unit).width(), + ) + + 2 ) - self.paint_scale_text = self.width() > self.scale_width * 2 + # Decide if the widget is too small to draw the scale labels + if self.width() <= self._scale_width * 2: + self._scale_width = 0 def updatePixmap(self): """Prepare the colored rect to be used during paintEvent(s)""" @@ -144,7 +156,7 @@ def resizeEvent(self, event): self.updatePixmap() self.updateMarkings() - def paintEvent(self, e): + def paintEvent(self, event): height = self.height() width = self.width() @@ -153,10 +165,7 @@ def paintEvent(self, e): painter.setBrush(self.backgroundColor) # Calculate the meter size (per single channel) - if self.paint_scale_text: - usableWidth = (width - self.scale_width) - else: - usableWidth = width + usableWidth = width - self._scale_width meterWidth = usableWidth / len(self.peaks) meterRect = QRect(0, 0, meterWidth - 2, height - 1) @@ -199,26 +208,43 @@ def paintEvent(self, e): x_start = meterRect.x() + meterRect.width() / 2 x_end = meterRect.x() + meterRect.width() for mark in self._markings: - painter.drawLine(QPointF(x_start, mark[0]), QPointF(x_end, mark[0])) + painter.drawLine( + QPointF(x_start, mark[0]), QPointF(x_end, mark[0]) + ) # Move to the next meter meterRect.translate(meterWidth, 0) - if not self.paint_scale_text or not e.region().contains(QPoint(usableWidth, 0)): + if self._scale_width > 0 and event.region().contains( + QPoint(usableWidth, 0) + ): + # Draw the scale marking text + text_height = QFontMetrics(self.font()).height() + text_offset = text_height / 2 + painter.setPen(self.palette().windowText().color()) + painter.drawText( + 0, 0, width, text_height, Qt.AlignRight, str(self.dBMax) + ) + for mark in self._markings: + painter.drawText( + 0, + mark[0] - text_offset, + width, + width, + Qt.AlignRight, + str(mark[1]), + ) + + # Draw the units that the scale uses + text_height = QFontMetrics(self.unit_font).height() + painter.setFont(self.unit_font) + painter.drawText( + 0, + height - text_height, + width, + text_height, + Qt.AlignRight, + self.unit, + ) + painter.end() - return - - # Write the scale marking text - text_height = QFontMetrics(self.font()).height() - text_offset = text_height / 2 - painter.setPen(self.palette().windowText().color()) - painter.drawText(0, 0, width, text_height, Qt.AlignRight, str(self.dBMax)) - for mark in self._markings: - painter.drawText(0, mark[0] - text_offset, width, width, Qt.AlignRight, str(mark[1])) - - # And the units that the scale uses - text_height = QFontMetrics(self.unit_font).height() - painter.setFont(self.unit_font) - painter.drawText(0, height - text_height, width, text_height, Qt.AlignRight, self.unit) - - painter.end() diff --git a/lisp/ui/widgets/pagestreewidget.py b/lisp/ui/widgets/pagestreewidget.py index 5a9604d7d..cecd46174 100644 --- a/lisp/ui/widgets/pagestreewidget.py +++ b/lisp/ui/widgets/pagestreewidget.py @@ -19,6 +19,7 @@ from PyQt5.QtWidgets import QWidget, QGridLayout, QTreeView, QSizePolicy from lisp.ui.qdelegates import PaddedDelegate +from lisp.ui.ui_utils import translate class PagesTreeWidget(QWidget): @@ -120,8 +121,9 @@ def walk(self): class PagesTreeModel(QAbstractItemModel): PageRole = Qt.UserRole + 1 - def __init__(self, **kwargs): + def __init__(self, tr_context="", **kwargs): super().__init__(**kwargs) + self._tr_context = tr_context self._root = PageNode(None) def rowCount(self, parent=QModelIndex()): @@ -137,6 +139,9 @@ def data(self, index, role=Qt.DisplayRole): if index.isValid(): node = index.internalPointer() if role == Qt.DisplayRole: + if self._tr_context: + return translate(self._tr_context, node.page.Name) + return node.page.Name elif role == PagesTreeModel.PageRole: return node.page diff --git a/lisp/ui/widgets/waveform.py b/lisp/ui/widgets/waveform.py index 7c5c3a930..1d2b00c9c 100644 --- a/lisp/ui/widgets/waveform.py +++ b/lisp/ui/widgets/waveform.py @@ -61,7 +61,7 @@ def setValue(self, value): x = self._value // self._valueToPx width = (self._lastDrawnValue - self._value) // self._valueToPx # Repaint only the changed area - self.update(x - 1, 0, width + 1, self.height()) + self.update(x - 1, 0, width + 2, self.height()) def resizeEvent(self, event): self._valueToPx = self._maximum / self.width() From 218ff3deabfe7ed5d839a6c8265d816b6907028b Mon Sep 17 00:00:00 2001 From: s0600204 <2053873+s0600204@users.noreply.github.com> Date: Wed, 20 May 2020 17:51:06 +0100 Subject: [PATCH 235/333] Add support for user-provided plugins (#201) * Users may now place plugins under $XDG_DATA_HOME/LinuxShowPlayer//plugins and lisp will find and load them (By default: ~/.local/share/LinuxShowPlayer/0.6/plugins). * Plugins provided with lisp are protected from being overridden by the user. --- lisp/plugins/__init__.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index ffb0f0b7d..09cb74463 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -17,7 +17,8 @@ import inspect import logging -from os import path +from os import makedirs, path +import sys from lisp import app_dirs from lisp.core.configuration import JSONFileConfiguration @@ -28,6 +29,11 @@ LOADED = {} FALLBACK_CONFIG_PATH = path.join(path.dirname(__file__), "default.json") +USER_PLUGIN_PATH = path.join(app_dirs.user_data_dir, "plugins") + +# Make sure the path exists, and insert it into the list of paths python uses to find modules +makedirs(USER_PLUGIN_PATH, exist_ok=True) +sys.path.insert(1, USER_PLUGIN_PATH) logger = logging.getLogger(__name__) @@ -38,7 +44,17 @@ class PluginNotLoadedError(Exception): def load_plugins(application): """Load and instantiate available plugins.""" - for name, plugin in load_classes(__package__, path.dirname(__file__)): + + def callback(name, plugin): + + if name in PLUGINS: + # We don't want users to be able to override plugins that are provided with lisp + logger.error( + translate("PluginsError", + 'A plugin by the name of "{}" already exists.').format(name) + ) + return + try: PLUGINS[name] = plugin @@ -64,6 +80,14 @@ def load_plugins(application): translate("PluginsError", 'Failed to load "{}"').format(name) ) + # Load plugins that install with lisp + for name, plugin in load_classes(__package__, path.dirname(__file__)): + callback(name, plugin) + + # Load plugins that a user has installed to their profile + for name, plugin in load_classes("", USER_PLUGIN_PATH): + callback(name, plugin) + __init_plugins(application) From 6ef4fb1aa141e2dc3b604e19f7cb060d7aee544b Mon Sep 17 00:00:00 2001 From: s0600204 <2053873+s0600204@users.noreply.github.com> Date: Fri, 29 May 2020 19:08:47 +0100 Subject: [PATCH 236/333] Correct OSC callback setup (#203) * Having two `add_method()` with the same `path` and `args` arguments doesn't seem to work - the second callback is never called. * Fix OSC message capture in controller plugin --- lisp/plugins/controller/protocols/osc.py | 2 +- lisp/plugins/osc/osc_server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 60ef0b712..4a517231c 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -335,7 +335,7 @@ def capture_message(self): translate("ControllerOscSettings", "Waiting for messages:") ) - def __show_message(self, path, args, types): + def __show_message(self, path, args, types, *_, **__): self.capturedMessage["path"] = path self.capturedMessage["types"] = types self.capturedMessage["args"] = args diff --git a/lisp/plugins/osc/osc_server.py b/lisp/plugins/osc/osc_server.py index cdf42cb98..f9cb92973 100644 --- a/lisp/plugins/osc/osc_server.py +++ b/lisp/plugins/osc/osc_server.py @@ -46,6 +46,7 @@ def __init__(self, hostname, in_port, out_port): self.__lock = Lock() self.new_message = Signal() + self.new_message.connect(self.__log_message) @property def out_port(self): @@ -84,7 +85,6 @@ def start(self): try: self.__srv = ServerThread(self.__in_port) - self.__srv.add_method(None, None, self.__log_message) self.__srv.add_method(None, None, self.new_message.emit) self.__srv.start() From a1059d1e2decc21c169ae4d590754e2c2723adec Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 29 May 2020 20:24:27 +0200 Subject: [PATCH 237/333] Merge translations (close #186) --- lisp/i18n/ts/ar_SA/action_cues.ts | 38 +- lisp/i18n/ts/ar_SA/cache_manager.ts | 35 + lisp/i18n/ts/ar_SA/cart_layout.ts | 34 +- lisp/i18n/ts/ar_SA/controller.ts | 149 +- lisp/i18n/ts/ar_SA/gst_backend.ts | 23 +- lisp/i18n/ts/ar_SA/lisp.ts | 2407 ++------------------------ lisp/i18n/ts/ar_SA/list_layout.ts | 112 +- lisp/i18n/ts/ar_SA/media_info.ts | 16 +- lisp/i18n/ts/ar_SA/midi.ts | 105 +- lisp/i18n/ts/ar_SA/network.ts | 21 +- lisp/i18n/ts/ar_SA/osc.ts | 28 +- lisp/i18n/ts/ar_SA/presets.ts | 30 +- lisp/i18n/ts/ar_SA/replay_gain.ts | 18 +- lisp/i18n/ts/ar_SA/synchronizer.ts | 12 +- lisp/i18n/ts/cs_CZ/action_cues.ts | 6 +- lisp/i18n/ts/cs_CZ/cache_manager.ts | 35 + lisp/i18n/ts/cs_CZ/cart_layout.ts | 34 +- lisp/i18n/ts/cs_CZ/controller.ts | 113 +- lisp/i18n/ts/cs_CZ/gst_backend.ts | 21 +- lisp/i18n/ts/cs_CZ/lisp.ts | 1880 +++----------------- lisp/i18n/ts/cs_CZ/list_layout.ts | 102 +- lisp/i18n/ts/cs_CZ/media_info.ts | 18 +- lisp/i18n/ts/cs_CZ/midi.ts | 105 +- lisp/i18n/ts/cs_CZ/network.ts | 21 +- lisp/i18n/ts/cs_CZ/osc.ts | 2 +- lisp/i18n/ts/cs_CZ/presets.ts | 30 +- lisp/i18n/ts/cs_CZ/replay_gain.ts | 18 +- lisp/i18n/ts/cs_CZ/synchronizer.ts | 4 +- lisp/i18n/ts/de_DE/action_cues.ts | 50 +- lisp/i18n/ts/de_DE/cache_manager.ts | 35 + lisp/i18n/ts/de_DE/cart_layout.ts | 98 +- lisp/i18n/ts/de_DE/controller.ts | 180 +- lisp/i18n/ts/de_DE/gst_backend.ts | 73 +- lisp/i18n/ts/de_DE/lisp.ts | 1904 +++------------------ lisp/i18n/ts/de_DE/list_layout.ts | 184 +- lisp/i18n/ts/de_DE/media_info.ts | 20 +- lisp/i18n/ts/de_DE/midi.ts | 163 +- lisp/i18n/ts/de_DE/network.ts | 43 +- lisp/i18n/ts/de_DE/osc.ts | 48 +- lisp/i18n/ts/de_DE/presets.ts | 41 +- lisp/i18n/ts/de_DE/rename_cues.ts | 34 +- lisp/i18n/ts/de_DE/replay_gain.ts | 28 +- lisp/i18n/ts/de_DE/synchronizer.ts | 4 +- lisp/i18n/ts/de_DE/timecode.ts | 16 +- lisp/i18n/ts/de_DE/triggers.ts | 6 +- lisp/i18n/ts/es_ES/action_cues.ts | 2 +- lisp/i18n/ts/es_ES/cache_manager.ts | 35 + lisp/i18n/ts/es_ES/cart_layout.ts | 34 +- lisp/i18n/ts/es_ES/controller.ts | 113 +- lisp/i18n/ts/es_ES/gst_backend.ts | 21 +- lisp/i18n/ts/es_ES/lisp.ts | 1963 +++------------------ lisp/i18n/ts/es_ES/list_layout.ts | 102 +- lisp/i18n/ts/es_ES/media_info.ts | 16 +- lisp/i18n/ts/es_ES/midi.ts | 105 +- lisp/i18n/ts/es_ES/network.ts | 21 +- lisp/i18n/ts/es_ES/osc.ts | 2 +- lisp/i18n/ts/es_ES/presets.ts | 30 +- lisp/i18n/ts/es_ES/replay_gain.ts | 18 +- lisp/i18n/ts/es_ES/synchronizer.ts | 4 +- lisp/i18n/ts/fr_FR/action_cues.ts | 6 +- lisp/i18n/ts/fr_FR/cache_manager.ts | 35 + lisp/i18n/ts/fr_FR/cart_layout.ts | 38 +- lisp/i18n/ts/fr_FR/controller.ts | 139 +- lisp/i18n/ts/fr_FR/gst_backend.ts | 41 +- lisp/i18n/ts/fr_FR/lisp.ts | 2471 +++------------------------ lisp/i18n/ts/fr_FR/list_layout.ts | 132 +- lisp/i18n/ts/fr_FR/media_info.ts | 20 +- lisp/i18n/ts/fr_FR/midi.ts | 133 +- lisp/i18n/ts/fr_FR/network.ts | 31 +- lisp/i18n/ts/fr_FR/osc.ts | 30 +- lisp/i18n/ts/fr_FR/presets.ts | 39 +- lisp/i18n/ts/fr_FR/rename_cues.ts | 8 +- lisp/i18n/ts/fr_FR/replay_gain.ts | 28 +- lisp/i18n/ts/fr_FR/synchronizer.ts | 4 +- lisp/i18n/ts/fr_FR/timecode.ts | 6 +- lisp/i18n/ts/it_IT/action_cues.ts | 2 +- lisp/i18n/ts/it_IT/cache_manager.ts | 35 + lisp/i18n/ts/it_IT/cart_layout.ts | 34 +- lisp/i18n/ts/it_IT/controller.ts | 113 +- lisp/i18n/ts/it_IT/gst_backend.ts | 21 +- lisp/i18n/ts/it_IT/lisp.ts | 1964 +++------------------ lisp/i18n/ts/it_IT/list_layout.ts | 102 +- lisp/i18n/ts/it_IT/media_info.ts | 16 +- lisp/i18n/ts/it_IT/midi.ts | 105 +- lisp/i18n/ts/it_IT/network.ts | 21 +- lisp/i18n/ts/it_IT/osc.ts | 2 +- lisp/i18n/ts/it_IT/presets.ts | 30 +- lisp/i18n/ts/it_IT/replay_gain.ts | 18 +- lisp/i18n/ts/it_IT/synchronizer.ts | 4 +- lisp/i18n/ts/nl_BE/action_cues.ts | 2 +- lisp/i18n/ts/nl_BE/cache_manager.ts | 35 + lisp/i18n/ts/nl_BE/cart_layout.ts | 34 +- lisp/i18n/ts/nl_BE/controller.ts | 113 +- lisp/i18n/ts/nl_BE/gst_backend.ts | 21 +- lisp/i18n/ts/nl_BE/lisp.ts | 1488 ++-------------- lisp/i18n/ts/nl_BE/list_layout.ts | 102 +- lisp/i18n/ts/nl_BE/media_info.ts | 16 +- lisp/i18n/ts/nl_BE/midi.ts | 105 +- lisp/i18n/ts/nl_BE/network.ts | 21 +- lisp/i18n/ts/nl_BE/osc.ts | 2 +- lisp/i18n/ts/nl_BE/presets.ts | 30 +- lisp/i18n/ts/nl_BE/replay_gain.ts | 18 +- lisp/i18n/ts/nl_BE/synchronizer.ts | 4 +- lisp/i18n/ts/nl_NL/action_cues.ts | 2 +- lisp/i18n/ts/nl_NL/cache_manager.ts | 35 + lisp/i18n/ts/nl_NL/cart_layout.ts | 34 +- lisp/i18n/ts/nl_NL/controller.ts | 113 +- lisp/i18n/ts/nl_NL/gst_backend.ts | 21 +- lisp/i18n/ts/nl_NL/lisp.ts | 1488 ++++------------ lisp/i18n/ts/nl_NL/list_layout.ts | 102 +- lisp/i18n/ts/nl_NL/media_info.ts | 16 +- lisp/i18n/ts/nl_NL/midi.ts | 105 +- lisp/i18n/ts/nl_NL/network.ts | 21 +- lisp/i18n/ts/nl_NL/osc.ts | 2 +- lisp/i18n/ts/nl_NL/presets.ts | 30 +- lisp/i18n/ts/nl_NL/replay_gain.ts | 18 +- lisp/i18n/ts/nl_NL/synchronizer.ts | 4 +- lisp/i18n/ts/sl_SI/action_cues.ts | 2 +- lisp/i18n/ts/sl_SI/cache_manager.ts | 35 + lisp/i18n/ts/sl_SI/cart_layout.ts | 34 +- lisp/i18n/ts/sl_SI/controller.ts | 113 +- lisp/i18n/ts/sl_SI/gst_backend.ts | 21 +- lisp/i18n/ts/sl_SI/lisp.ts | 1880 +++----------------- lisp/i18n/ts/sl_SI/list_layout.ts | 102 +- lisp/i18n/ts/sl_SI/media_info.ts | 16 +- lisp/i18n/ts/sl_SI/midi.ts | 105 +- lisp/i18n/ts/sl_SI/network.ts | 21 +- lisp/i18n/ts/sl_SI/osc.ts | 2 +- lisp/i18n/ts/sl_SI/presets.ts | 30 +- lisp/i18n/ts/sl_SI/replay_gain.ts | 18 +- lisp/i18n/ts/sl_SI/synchronizer.ts | 4 +- lisp/i18n/ts/zh_CN/action_cues.ts | 234 +++ lisp/i18n/ts/zh_CN/cache_manager.ts | 35 + lisp/i18n/ts/zh_CN/cart_layout.ts | 187 ++ lisp/i18n/ts/zh_CN/controller.ts | 274 +++ lisp/i18n/ts/zh_CN/gst_backend.ts | 399 +++++ lisp/i18n/ts/zh_CN/lisp.ts | 831 +++++++++ lisp/i18n/ts/zh_CN/list_layout.ts | 260 +++ lisp/i18n/ts/zh_CN/media_info.ts | 32 + lisp/i18n/ts/zh_CN/midi.ts | 216 +++ lisp/i18n/ts/zh_CN/network.ts | 76 + lisp/i18n/ts/zh_CN/osc.ts | 146 ++ lisp/i18n/ts/zh_CN/presets.ts | 135 ++ lisp/i18n/ts/zh_CN/rename_cues.ts | 83 + lisp/i18n/ts/zh_CN/replay_gain.ts | 83 + lisp/i18n/ts/zh_CN/synchronizer.ts | 27 + lisp/i18n/ts/zh_CN/timecode.ts | 66 + lisp/i18n/ts/zh_CN/triggers.ts | 63 + 148 files changed, 8549 insertions(+), 17401 deletions(-) create mode 100644 lisp/i18n/ts/ar_SA/cache_manager.ts create mode 100644 lisp/i18n/ts/cs_CZ/cache_manager.ts create mode 100644 lisp/i18n/ts/de_DE/cache_manager.ts create mode 100644 lisp/i18n/ts/es_ES/cache_manager.ts create mode 100644 lisp/i18n/ts/fr_FR/cache_manager.ts create mode 100644 lisp/i18n/ts/it_IT/cache_manager.ts create mode 100644 lisp/i18n/ts/nl_BE/cache_manager.ts create mode 100644 lisp/i18n/ts/nl_NL/cache_manager.ts create mode 100644 lisp/i18n/ts/sl_SI/cache_manager.ts create mode 100644 lisp/i18n/ts/zh_CN/action_cues.ts create mode 100644 lisp/i18n/ts/zh_CN/cache_manager.ts create mode 100644 lisp/i18n/ts/zh_CN/cart_layout.ts create mode 100644 lisp/i18n/ts/zh_CN/controller.ts create mode 100644 lisp/i18n/ts/zh_CN/gst_backend.ts create mode 100644 lisp/i18n/ts/zh_CN/lisp.ts create mode 100644 lisp/i18n/ts/zh_CN/list_layout.ts create mode 100644 lisp/i18n/ts/zh_CN/media_info.ts create mode 100644 lisp/i18n/ts/zh_CN/midi.ts create mode 100644 lisp/i18n/ts/zh_CN/network.ts create mode 100644 lisp/i18n/ts/zh_CN/osc.ts create mode 100644 lisp/i18n/ts/zh_CN/presets.ts create mode 100644 lisp/i18n/ts/zh_CN/rename_cues.ts create mode 100644 lisp/i18n/ts/zh_CN/replay_gain.ts create mode 100644 lisp/i18n/ts/zh_CN/synchronizer.ts create mode 100644 lisp/i18n/ts/zh_CN/timecode.ts create mode 100644 lisp/i18n/ts/zh_CN/triggers.ts diff --git a/lisp/i18n/ts/ar_SA/action_cues.ts b/lisp/i18n/ts/ar_SA/action_cues.ts index 295829eae..ae0fa801e 100644 --- a/lisp/i18n/ts/ar_SA/action_cues.ts +++ b/lisp/i18n/ts/ar_SA/action_cues.ts @@ -55,7 +55,7 @@ CueCategory - + Action cues Action cues @@ -67,11 +67,6 @@ Command Cue أمر الأغنية - - - MIDI Cue - MIDI Cue - Volume Control @@ -131,32 +126,6 @@ Suggested cue name - - Osc Cue - - - Fade - التلاشي - - - - OscCue - - - Add - أضف - - - - Remove - احذف - - - - Fade - تلاشي - - SeekCue @@ -192,11 +161,6 @@ Command الأوامر - - - MIDI Settings - MIDI Settings - Volume Settings diff --git a/lisp/i18n/ts/ar_SA/cache_manager.ts b/lisp/i18n/ts/ar_SA/cache_manager.ts new file mode 100644 index 000000000..965b28d15 --- /dev/null +++ b/lisp/i18n/ts/ar_SA/cache_manager.ts @@ -0,0 +1,35 @@ + + + + + CacheManager + + + Cache size warning + Cache size warning + + + + Warning threshold in MB (0 = disabled) + Warning threshold in MB (0 = disabled) + + + + Cache cleanup + Cache cleanup + + + + Delete the cache content + Delete the cache content + + + + SettingsPageName + + + Cache Manager + Cache Manager + + + diff --git a/lisp/i18n/ts/ar_SA/cart_layout.ts b/lisp/i18n/ts/ar_SA/cart_layout.ts index 9eb7709b7..b601c1f2c 100644 --- a/lisp/i18n/ts/ar_SA/cart_layout.ts +++ b/lisp/i18n/ts/ar_SA/cart_layout.ts @@ -4,37 +4,32 @@ CartLayout - - Default behaviors - السلوك الافتراضي - - - + Countdown mode وضع العد التنازلي - + Show seek-bars اعرض أشرطة البحث - + Show dB-meters إظهار dB-متر - + Show accurate time Show accurate time - + Show volume إظهار حجم الصوت - + Grid size حجم الشبكة @@ -79,35 +74,40 @@ عدد الصفحات: - + Page {number} الصفحة{number} - + Warning تحذير - + Every cue in the page will be lost. ستفقد كل أغنية في الصفحة. - + Are you sure to continue? هل أنت متأكد من أن تواصل؟ - + Number of columns: عدد الأعمدة: - + Number of rows: عدد الصفوف: + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + LayoutDescription diff --git a/lisp/i18n/ts/ar_SA/controller.ts b/lisp/i18n/ts/ar_SA/controller.ts index e4c5f2fbf..e3c95700e 100644 --- a/lisp/i18n/ts/ar_SA/controller.ts +++ b/lisp/i18n/ts/ar_SA/controller.ts @@ -12,131 +12,154 @@ ControllerKeySettings - - Key - مفتاح - - - + Action الإجراءات - + Shortcuts الاختصارات + + + Shortcut + Shortcut + ControllerMidiSettings - + MIDI MIDI - + Type النوع - - Channel - القناة + + Action + الإجراءات - - Note - ملاحظة + + Capture + Capture - - Action - الإجراءات + + Listening MIDI messages ... + Listening MIDI messages ... - - Filter "note on" - Filter "note on" + + -- All Messages -- + -- All Messages -- - - Filter "note off" - Filter "note off" + + Capture filter + Capture filter - - Capture - Capture + + Data 1 + Data 1 - - Listening MIDI messages ... - Listening MIDI messages ... + + Data 2 + Data 2 + + + + Data 3 + Data 3 ControllerOscSettings - + OSC Message OSC Message - + OSC OSC - + Path المسار - + Types الأنواع - + Arguments الحجج - - Actions - الاجراءت - - - + OSC Capture التقاط OSC - + Add أضف - + Remove احذف - + Capture Capture + + + Waiting for messages: + Waiting for messages: + + + + /path/to/method + /path/to/method + + + + Action + Action + + + + ControllerOscSettingsWarning + + + Warning + Warning + ControllerSettings - + Add أضف - + Remove احذف @@ -144,52 +167,52 @@ GlobalAction - + Go إبدأ - + Reset إعادة ضبط - + Stop all cues إيقاف كل الأغاني - + Pause all cues Pause all cues - + Resume all cues Resume all cues - + Interrupt all cues قطع كل الأغاني - + Fade-out all cues Fade-out all cues - + Fade-in all cues Fade-in all cues - + Move standby forward Move standby forward - + Move standby back Move standby back @@ -197,12 +220,12 @@ Osc Cue - + Type النوع - + Argument الحجج @@ -210,12 +233,12 @@ OscCue - + Add أضف - + Remove احذف @@ -228,17 +251,17 @@ التحكم بالأغاني - + MIDI Controls MIDI Controls - + Keyboard Shortcuts اختصارات لوحة المفاتيح - + OSC Controls ضوابط OSC diff --git a/lisp/i18n/ts/ar_SA/gst_backend.ts b/lisp/i18n/ts/ar_SA/gst_backend.ts index 4e478c806..73cc2a29c 100644 --- a/lisp/i18n/ts/ar_SA/gst_backend.ts +++ b/lisp/i18n/ts/ar_SA/gst_backend.ts @@ -114,12 +114,12 @@ GstBackend - + Audio cue (from file) Audio cue (from file) - + Select media files Select media files @@ -127,7 +127,7 @@ GstMediaError - + Cannot create pipeline element: "{}" Cannot create pipeline element: "{}" @@ -143,7 +143,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" Invalid pipeline element: "{}" @@ -151,7 +151,7 @@ GstPipelineEdit - + Edit Pipeline Edit Pipeline @@ -159,9 +159,14 @@ GstSettings - - Pipeline - Pipeline + + Default pipeline + Default pipeline + + + + Applied only to new cues. + Applied only to new cues. @@ -255,7 +260,7 @@ Pitch - + URI Input URI Input diff --git a/lisp/i18n/ts/ar_SA/lisp.ts b/lisp/i18n/ts/ar_SA/lisp.ts index 34d32a153..d2f077d2b 100644 --- a/lisp/i18n/ts/ar_SA/lisp.ts +++ b/lisp/i18n/ts/ar_SA/lisp.ts @@ -1,14 +1,6 @@ - - APIServerInfo - - - Stop serving network API - Stop serving network API - - About @@ -35,52 +27,36 @@ AboutDialog - + Web site موقع ويب - + Source code Source code - + Info Info - + License License - + Contributors Contributors - + Discussion Discussion - - AlsaSinkSettings - - - ALSA device - ALSA device - - - - ApiServerError - - - Network API server stopped working. - Network API server stopped working. - - AppConfiguration @@ -92,277 +68,52 @@ AppGeneralSettings - + Default layout Default layout - - Enable startup layout selector - Enable startup layout selector - - - + Application themes Application themes - + UI theme: UI theme: - + Icons theme: Icons theme: - - Application language (require restart) - Application language (require restart) - - - + Language: Language: - - - ApplicationError - - - Startup error - Startup error - - - - AudioDynamicSettings - - - Compressor - Compressor - - - - Expander - Expander - - - - Soft Knee - Soft Knee - - - - Hard Knee - Hard Knee - - - - Compressor/Expander - Compressor/Expander - - - - Type - Type - - - - Curve Shape - Curve Shape - - - - Ratio - Ratio - - - - Threshold (dB) - Threshold (dB) - - - - AudioPanSettings - - - Audio Pan - Audio Pan - - - - Center - Center - - - - Left - Left - - - - Right - Right - - - - CartLayout - - - Default behaviors - Default behaviors - - - - Countdown mode - Countdown mode - - - - Show seek-bars - Show seek-bars - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Show accurate time - - - - Show volume - Show volume - - - - Grid size - Grid size - - - - Number of columns: - Number of columns: - - - - Number of rows: - Number of rows: - - - - Play - Play - - - - Pause - Pause - - - - Stop - Stop - - - - Reset volume - Reset volume - - - - Add page - Add page - - - - Add pages - Add pages - - - - Remove current page - Remove current page - - - - Number of Pages: - Number of Pages: - - - - Page {number} - Page {number} - - - - Warning - Warning - - - - Every cue in the page will be lost. - Every cue in the page will be lost. - - - - Are you sure to continue? - Are you sure to continue? - - - - CollectionCue - - - Add - Add - - - Remove - Remove + + Show layout selection at startup + Show layout selection at startup - - Cue - Cue + + Use layout at startup: + Use layout at startup: - - Action - Action + + Application language (require a restart) + Application language (require a restart) - CommandCue - - - Command - Command - - - - Command to execute, as in a shell - Command to execute, as in a shell - - - - Discard command output - Discard command output - - - - Ignore command errors - Ignore command errors - + ApplicationError - - Kill instead of terminate - Kill instead of terminate + + Startup error + Startup error @@ -394,154 +145,6 @@ New configuration installed at {} - - Controller - - - Cannot load controller protocol: "{}" - Cannot load controller protocol: "{}" - - - - ControllerKeySettings - - - Key - Key - - - - Action - Action - - - - Shortcuts - Shortcuts - - - - ControllerMidiSettings - - - MIDI - MIDI - - - - Type - Type - - - - Channel - Channel - - - - Note - Note - - - - Action - Action - - - - Filter "note on" - Filter "note on" - - - - Filter "note off" - Filter "note off" - - - - Capture - Capture - - - - Listening MIDI messages ... - Listening MIDI messages ... - - - - ControllerOscSettings - - - OSC Message - OSC Message - - - - OSC - OSC - - - - Path - Path - - - - Types - Types - - - - Arguments - Arguments - - - - Actions - Actions - - - - OSC Capture - OSC Capture - - - - Add - Add - - - - Remove - Remove - - - - Capture - Capture - - - - ControllerSettings - - - Add - Add - - - - Remove - Remove - - - - Cue Name - - - OSC Settings - OSC Settings - - CueAction @@ -600,19 +203,6 @@ لا تفعل شيئا - - CueActionLog - - - Cue settings changed: "{}" - Cue settings changed: "{}" - - - - Cues settings changed. - Cues settings changed. - - CueAppearanceSettings @@ -659,17 +249,7 @@ CueCategory - - Action cues - Action cues - - - - Integration cues - Integration cues - - - + Misc cues Misc cues @@ -690,75 +270,35 @@ CueName - + Media Cue Media Cue - - - Index Action - Index Action - - - - Seek Cue - Seek Cue - - - - Volume Control - Volume Control - - - - Command Cue - Command Cue - - - - Collection Cue - Collection Cue - - - - Stop-All - Stop-All - - - - MIDI Cue - MIDI Cue - - - - OSC Cue - OSC Cue - CueNextAction - + Do Nothing لا تفعل شيئا - + Trigger after the end Trigger after the end - + Trigger after post wait Trigger after post wait - + Select after the end Select after the end - + Select after post wait Select after post wait @@ -811,68 +351,14 @@ Default action to stop the cue - - Interrupt fade - Interrupt fade - - - - Fade actions - Fade actions - - - - CueTriggers - - - Started - Started - - - - Paused - Paused - - - - Stopped - Stopped + + Interrupt action fade + Interrupt action fade - - Ended - Ended - - - - DbMeterSettings - - - DbMeter settings - DbMeter settings - - - - Time between levels (ms) - Time between levels (ms) - - - - Peak ttl (ms) - Peak ttl (ms) - - - - Peak falloff (dB/sec) - Peak falloff (dB/sec) - - - - Equalizer10Settings - - - 10 Bands Equalizer (IIR) - 10 Bands Equalizer (IIR) + + Fade actions default value + Fade actions default value @@ -896,12 +382,12 @@ FadeEdit - + Duration (sec): Duration (sec): - + Curve: منحنى: @@ -920,471 +406,57 @@ - GlobalAction + HotKeyEdit - - Go - Go + + Press shortcut + Press shortcut + + + LayoutSelect - - Reset - Reset + + Layout selection + Layout selection - - Stop all cues - Stop all cues + + Select layout + Select layout - - Pause all cues - Pause all cues + + Open file + فتح الملف + + + ListLayout - - Resume all cues - Resume all cues + + Layout actions + Layout actions - - Interrupt all cues - Interrupt all cues + + Fade out when stopping all cues + Fade out when stopping all cues - - Fade-out all cues - Fade-out all cues + + Fade out when interrupting all cues + Fade out when interrupting all cues - - Fade-in all cues - Fade-in all cues + + Fade out when pausing all cues + Fade out when pausing all cues - - Move standby forward - Move standby forward - - - - Move standby back - Move standby back - - - - GstBackend - - - Audio cue (from file) - Audio cue (from file) - - - - Select media files - Select media files - - - - GstMediaError - - - Cannot create pipeline element: "{}" - Cannot create pipeline element: "{}" - - - - GstMediaSettings - - - Change Pipeline - Change Pipeline - - - - GstMediaWarning - - - Invalid pipeline element: "{}" - Invalid pipeline element: "{}" - - - - GstPipelineEdit - - - Edit Pipeline - Edit Pipeline - - - - GstSettings - - - Pipeline - Pipeline - - - - IndexActionCue - - - No suggestion - No suggestion - - - - Index - Index - - - - Use a relative index - Use a relative index - - - - Target index - Target index - - - - Action - Action - - - - Suggested cue name - Suggested cue name - - - - JackSinkSettings - - - Connections - Connections - - - - Edit connections - Edit connections - - - - Output ports - Output ports - - - - Input ports - Input ports - - - - Connect - Connect - - - - Disconnect - Disconnect - - - - LayoutDescription - - - Organize the cues in a list - Organize the cues in a list - - - - Organize cues in grid like pages - Organize cues in grid like pages - - - - LayoutDetails - - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Space or Double-Click to edit a cue - - - - To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL - - - - To move cues drag them - To move cues drag them - - - - Click a cue to run it - Click a cue to run it - - - - SHIFT + Click to edit a cue - SHIFT + Click to edit a cue - - - - CTRL + Click to select a cue - CTRL + Click to select a cue - - - - To move cues drag them while pressing SHIFT - To move cues drag them while pressing SHIFT - - - - LayoutName - - - List Layout - List Layout - - - - Cart Layout - Cart Layout - - - - LayoutSelect - - - Layout selection - Layout selection - - - - Select layout - Select layout - - - - Open file - فتح الملف - - - - ListLayout - - - Stop All - إيقاف الكل - - - - Pause All - إيقاف مؤقت للكل - - - - Interrupt All - قطع كل - - - - Default behaviors - Default behaviors - - - - Show playing cues - Show playing cues - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Show accurate time - - - - Show seek-bars - Show seek-bars - - - - Auto-select next cue - Auto-select next cue - - - - Enable selection mode - Enable selection mode - - - - GO Key: - GO Key: - - - - GO Action: - GO Action: - - - - GO minimum interval (ms): - GO minimum interval (ms): - - - - Use fade (buttons) - Use fade (buttons) - - - - Stop Cue - Stop Cue - - - - Pause Cue - Pause Cue - - - - Resume Cue - Resume Cue - - - - Interrupt Cue - Interrupt Cue - - - - Edit cue - Edit cue - - - - Edit selected - Edit selected - - - - Clone cue - Clone cue - - - - Clone selected - Clone selected - - - - Remove cue - Remove cue - - - - Remove selected - Remove selected - - - - Selection mode - Selection mode - - - - Pause all - Pause all - - - - Stop all - Stop all - - - - Interrupt all - Interrupt all - - - - Resume all - Resume all - - - - Fade-Out all - Fade-Out all - - - - Fade-In all - Fade-In all - - - - Edit selected cues - Edit selected cues - - - - Remove selected cues - Remove selected cues - - - - Use fade - Use fade - - - - Copy of {} - Copy of {} - - - - ListLayoutHeader - - - Cue - Cue - - - - Pre wait - Pre wait - - - - Action - Action - - - - Post wait - Post wait - - - - ListLayoutInfoPanel - - - Cue name - Cue name - - - - Cue description - Cue description + + Fade in when resuming all cues + Fade in when resuming all cues @@ -1423,7 +495,7 @@ إظهار التفاصيل - + Linux Show Player - Log Viewer Linux Show Player--عارض السجل @@ -1512,293 +584,146 @@ Exception info معلومات الاستثناء - - - MIDICue - - - MIDI Message - MIDI Message - - - Message type - Message type + + Showing {} of {} records + Showing {} of {} records - MIDIMessageAttr + MainWindow - - Channel - Channel + + &File + &ملف - - Note - Note + + New session + دورة جديدة - - Velocity - Velocity + + Open + افتح - - Control - Control + + Save session + حفظ الدورة - - Program - Program + + Preferences + Preferences - - Value - Value + + Save as + حفظ كـ - - Song - Song + + Full Screen + كامل الشاشة - - Pitch - Pitch + + Exit + الخروج - - Position - Position + + &Edit + &تعديل - - - MIDIMessageType - - Note ON - Note ON + + Undo + التراجع - - Note OFF - Note OFF + + Redo + الإعادة - - Polyphonic After-touch - Polyphonic After-touch + + Select all + اختيار الكل - - Control/Mode Change - Control/Mode Change + + Select all media cues + Select all media cues - - Program Change - Program Change + + Deselect all + إلغاء تحديد الكل - - Channel After-touch - Channel After-touch + + CTRL+SHIFT+A + CTRL + SHIFT + A - - Pitch Bend Change - Pitch Bend Change + + Invert selection + Invert selection - - Song Select - Song Select + + CTRL+I + CTRL + I - - Song Position - Song Position + + Edit selected + تعديل المحددة - - Start - Start - - - - Stop - Stop - - - - Continue - Continue - - - - MIDISettings - - - MIDI default devices - MIDI default devices - - - - Input - Input - - - - Output - Output - - - - MainWindow - - - &File - &ملف - - - - New session - دورة جديدة - - - - Open - افتح - - - - Save session - حفظ الدورة - - - - Preferences - Preferences - - - - Save as - حفظ كـ - - - - Full Screen - كامل الشاشة - - - - Exit - الخروج - - - - &Edit - &تعديل - - - - Undo - التراجع - - - - Redo - الإعادة - - - - Select all - اختيار الكل - - - - Select all media cues - Select all media cues - - - - Deselect all - إلغاء تحديد الكل - - - - CTRL+SHIFT+A - CTRL + SHIFT + A - - - - Invert selection - Invert selection - - - - CTRL+I - CTRL + I - - - - Edit selected - تعديل المحددة - - - + CTRL+SHIFT+E CTRL + SHIFT + E - + &Layout &Layout - + &Tools &أدوات - + Edit selection تعديل المجموعة - + &About &حول - + About حول - + About Qt حول Qt - + Close session Close session - - The current session is not saved. - لم يتم حفظ في الدورة الحالية. - - - - Discard the changes? - Discard the changes? - - - + Do you want to save them now? Do you want to save them now? @@ -1806,7 +731,7 @@ MainWindowDebug - + Registered cue menu: "{}" Registered cue menu: "{}" @@ -1814,7 +739,7 @@ MainWindowError - + Cannot create cue {} Cannot create cue {} @@ -1848,1047 +773,59 @@ - MediaElementName - - - Pitch - Pitch - - - - Volume - Volume - - - - Audio Pan - Audio Pan - - - - Compressor/Expander - Compressor/Expander - - - - JACK Out - JACK Out - - - - URI Input - URI Input - - - - 10 Bands Equalizer - 10 Bands Equalizer - - - - ALSA Out - ALSA Out - - - - dB Meter - dB Meter - - - - Preset Input - Preset Input - - - - PulseAudio Out - PulseAudio Out - - - - Custom Element - Custom Element - - - - Speed - Speed - - - - System Out - System Out - - - - System Input - System Input - - - - MediaInfo - - - Media Info - Media Info - - - - Warning - Warning - - - - No info to display - No info to display - - - - Info - Info - - - - Value - Value - - - - NetworkApiDebug - - - New end-point: {} - New end-point: {} - - - - NetworkDiscovery - - - Host discovery - Host discovery - - - - Manage hosts - Manage hosts - - - - Discover hosts - Discover hosts - - - - Manually add a host - Manually add a host - - - - Remove selected host - Remove selected host - - - - Remove all host - Remove all host - - - - Address - Address - - - - Host IP - Host IP - - - - Osc Cue - - - Type - Type - - - - Argument - Argument - - - - OscCue - - - Add - Add - - - - Remove - Remove - - - - Type - Type - - - - Value - Value - - - - FadeTo - FadeTo - - - - Fade - Fade - - - - OSC Message - OSC Message - - - - OSC Path: - OSC Path: - - - - /path/to/something - /path/to/something - - - - Time (sec) - Time (sec) - - - - Curve - Curve - - - - OscServerDebug - - - Message from {} -> path: "{}" args: {} - Message from {} -> path: "{}" args: {} - - - - OscServerError - - - Cannot start OSC sever - Cannot start OSC sever - - - - OscServerInfo - - - OSC server started at {} - OSC server started at {} - + QColorButton - - OSC server stopped - OSC server stopped + + Right click to reset + Right click to reset - OscSettings + SettingsPageName - - OSC Settings - OSC Settings + + Appearance + المظهر - - Input Port: - Input Port: + + General + إعدادات عامة - - Output Port: - Output Port: + + Cue + الأغنية - - Hostname: - Hostname: + + Cue Settings + إعدادات الأغنية - - - PitchSettings - - Pitch - Pitch + + Plugins + الإضافات - - {0:+} semitones - {0:+} semitones + + Behaviours + السلوك - - - PluginsError - - Failed to load "{}" - Failed to load "{}" + + Pre/Post Wait + Pre/Post Wait - - Failed to terminate plugin: "{}" - Failed to terminate plugin: "{}" - - - - the requested plugin is not loaded: {} - the requested plugin is not loaded: {} - - - - PluginsInfo - - - Plugin loaded: "{}" - Plugin loaded: "{}" - - - - Plugin terminated: "{}" - Plugin terminated: "{}" - - - - PluginsWarning - - - Cannot satisfy dependencies for: {} - Cannot satisfy dependencies for: {} - - - - Preset - - - Create Cue - Create Cue - - - - Load on selected Cues - Load on selected Cues - - - - PresetSrcSettings - - - Presets - Presets - - - - Presets - - - Cannot scan presets - Cannot scan presets - - - - Error while deleting preset "{}" - Error while deleting preset "{}" - - - - Cannot load preset "{}" - Cannot load preset "{}" - - - - Cannot save preset "{}" - Cannot save preset "{}" - - - - Cannot rename preset "{}" - Cannot rename preset "{}" - - - - Select Preset - Select Preset - - - - Presets - Presets - - - - Preset name - Preset name - - - - Add - Add - - - - Rename - Rename - - - - Edit - Edit - - - - Remove - Remove - - - - Export selected - Export selected - - - - Import - Import - - - - Warning - Warning - - - - The same name is already used! - The same name is already used! - - - - Cannot export correctly. - Cannot export correctly. - - - - Cannot import correctly. - Cannot import correctly. - - - - Cue type - Cue type - - - - Load on cue - Load on cue - - - - Load on selected cues - Load on selected cues - - - - Save as preset - Save as preset - - - - Cannot create a cue from this preset: {} - Cannot create a cue from this preset: {} - - - - QColorButton - - - Right click to reset - Right click to reset - - - - RenameCues - - - Rename Cues - Rename Cues - - - - Rename cues - Rename cues - - - - Current - Current - - - - Preview - Preview - - - - Capitalize - Capitalize - - - - Lowercase - Lowercase - - - - Uppercase - Uppercase - - - - Remove Numbers - Remove Numbers - - - - Add numbering - Add numbering - - - - Reset - Reset - - - - Type your regex here: - Type your regex here: - - - - Regex help - Regex help - - - - RenameCuesCommand - - - Renamed {number} cues - Renamed {number} cues - - - - RenameUiDebug - - - Regex error: Invalid pattern - Regex error: Invalid pattern - - - - ReplayGain - - - ReplayGain / Normalization - ReplayGain / Normalization - - - - Threads number - Threads number - - - - Apply only to selected media - Apply only to selected media - - - - ReplayGain to (dB SPL) - ReplayGain to (dB SPL) - - - - Normalize to (dB) - Normalize to (dB) - - - - Processing files ... - Processing files ... - - - - Calculate - Calculate - - - - Reset all - Reset all - - - - Reset selected - Reset selected - - - - ReplayGainDebug - - - Applied gain for: {} - Applied gain for: {} - - - - Discarded gain for: {} - Discarded gain for: {} - - - - ReplayGainInfo - - - Gain processing stopped by user. - Gain processing stopped by user. - - - - Started gain calculation for: {} - Started gain calculation for: {} - - - - Gain calculated for: {} - Gain calculated for: {} - - - - SeekCue - - - Cue - Cue - - - - Click to select - Click to select - - - - Not selected - Not selected - - - - Seek - Seek - - - - Time to reach - Time to reach - - - - SettingsPageName - - - Appearance - المظهر - - - - General - إعدادات عامة - - - - Cue - الأغنية - - - - Cue Settings - إعدادات الأغنية - - - - Plugins - الإضافات - - - - Behaviours - السلوك - - - - Pre/Post Wait - Pre/Post Wait - - - - Fade In/Out - Fade In/Out + + Fade In/Out + Fade In/Out Layouts Layouts - - - Action Settings - Action Settings - - - - Seek Settings - Seek Settings - - - - Volume Settings - Volume Settings - - - - Command - Command - - - - Edit Collection - Edit Collection - - - - Stop Settings - Stop Settings - - - - List Layout - List Layout - - - - Timecode - Timecode - - - - Timecode Settings - Timecode Settings - - - - Cue Control - Cue Control - - - - Layout Controls - Layout Controls - - - - MIDI Controls - MIDI Controls - - - - Keyboard Shortcuts - Keyboard Shortcuts - - - - OSC Controls - OSC Controls - - - - MIDI Settings - MIDI Settings - - - - MIDI settings - MIDI settings - - - - GStreamer - GStreamer - - - - Media Settings - Media Settings - - - - Triggers - Triggers - - - - OSC settings - OSC settings - - - - Cart Layout - Cart Layout - - - - SpeedSettings - - - Speed - Speed - - - - StopAll - - - Stop Action - Stop Action - - - - Synchronizer - - - Synchronization - Synchronization - - - - Manage connected peers - Manage connected peers - - - - Show your IP - Show your IP - - - - Your IP is: - Your IP is: - - - - Timecode - - - Cannot load timecode protocol: "{}" - Cannot load timecode protocol: "{}" - - - - TimecodeError - - - Cannot send timecode. - Cannot send timecode. - - - - TimecodeSettings - - - Replace HOURS by a static track number - Replace HOURS by a static track number - - - - Enable Timecode - Enable Timecode - - - - Track number - Track number - - - - Timecode Settings - Timecode Settings - - - - Timecode Format: - Timecode Format: - - - - Timecode Protocol: - Timecode Protocol: - - - - TriggersSettings - - - Add - Add - - - - Remove - Remove - - - - Trigger - Trigger - - - - Cue - Cue - - - - Action - Action - - - - UriInputSettings - - - Source - Source - - - - Find File - Find File - - - - Buffering - Buffering - - - - Use Buffering - Use Buffering - - - - Attempt download on network streams - Attempt download on network streams - - - - Buffer size (-1 default value) - Buffer size (-1 default value) - - - - Choose file - Choose file - - - - All files - All files - - - - UserElementSettings - - - User defined elements - User defined elements - - - - Only for advanced user! - Only for advanced user! - - - - VolumeControl - - - Cue - Cue - - - - Click to select - Click to select - - - - Not selected - Not selected - - - - Volume to reach - Volume to reach - - - - Fade - Fade - - - - VolumeControlError - - - Error during cue execution. - Error during cue execution. - - - - VolumeSettings - - - Volume - Volume - - - - Normalized volume - Normalized volume - - - - Reset - Reset - diff --git a/lisp/i18n/ts/ar_SA/list_layout.ts b/lisp/i18n/ts/ar_SA/list_layout.ts index 365da9c9f..14270fdce 100644 --- a/lisp/i18n/ts/ar_SA/list_layout.ts +++ b/lisp/i18n/ts/ar_SA/list_layout.ts @@ -4,7 +4,7 @@ LayoutDescription - + Organize the cues in a list Organize the cues in a list @@ -12,17 +12,17 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue SHIFT + Space or Double-Click to edit a cue - + To copy cues drag them while pressing CTRL To copy cues drag them while pressing CTRL - + To move cues drag them To move cues drag them @@ -30,7 +30,7 @@ LayoutName - + List Layout List Layout @@ -38,77 +38,67 @@ ListLayout - - Default behaviors - السلوك الافتراضي - - - - Show playing cues - Show playing cues - - - + Show dB-meters إظهار dB-متر - + Show accurate time Show accurate time - + Show seek-bars اعرض أشرطة البحث - + Auto-select next cue تحديد تلقائي الاغنية التالية - + Enable selection mode Enable selection mode - + Use fade (buttons) Use fade (buttons) - + Stop Cue توقف الاغنية - + Pause Cue Pause Cue - + Resume Cue إستئناف الاغنية - + Interrupt Cue قطع الاغنية - + Edit cue تصحيح الاغنية - + Remove cue إزالة الأغاني - + Selection mode وضع الإختيار @@ -143,65 +133,105 @@ Fade-In all - + Edit selected Edit selected - + Clone cue Clone cue - + Clone selected Clone selected - + Remove selected Remove selected - + GO Key: GO Key: - + GO Action: GO Action: - + GO minimum interval (ms): GO minimum interval (ms): - + Copy of {} Copy of {} + + + Show index column + Show index column + + + + Show resize handles + Show resize handles + + + + Restore default size + Restore default size + + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + + + + Behaviors + Behaviors + + + + Disable GO Key While Playing + Disable GO Key While Playing + + + + Use waveform seek-bars + Use waveform seek-bars + + + + GO Key Disabled While Playing + GO Key Disabled While Playing + ListLayoutHeader - + Cue أغنية - + Pre wait قبل الانتظار - + Action الإجراءات - + Post wait بعد الانتظار @@ -222,7 +252,7 @@ SettingsPageName - + List Layout List Layout diff --git a/lisp/i18n/ts/ar_SA/media_info.ts b/lisp/i18n/ts/ar_SA/media_info.ts index fe2c4ce19..009ecc27b 100644 --- a/lisp/i18n/ts/ar_SA/media_info.ts +++ b/lisp/i18n/ts/ar_SA/media_info.ts @@ -4,22 +4,17 @@ MediaInfo - + Media Info Media Info - - No info to display - لا توجد معلومات لعرضها - - - + Info معلومات - + Value القيمة @@ -28,5 +23,10 @@ Warning تحذير + + + Cannot get any information. + Cannot get any information. +
diff --git a/lisp/i18n/ts/ar_SA/midi.ts b/lisp/i18n/ts/ar_SA/midi.ts index 4e3344055..a4881e9c6 100644 --- a/lisp/i18n/ts/ar_SA/midi.ts +++ b/lisp/i18n/ts/ar_SA/midi.ts @@ -4,7 +4,7 @@ CueCategory - + Integration cues Integration cues @@ -12,7 +12,7 @@ CueName - + MIDI Cue MIDI Cue @@ -30,50 +30,81 @@ نوع الرسالة + + MIDIError + + + Cannot connect to MIDI output port '{}'. + Cannot connect to MIDI output port '{}'. + + + + Cannot connect to MIDI input port '{}'. + Cannot connect to MIDI input port '{}'. + + + + MIDIInfo + + + MIDI port disconnected: '{}' + MIDI port disconnected: '{}' + + + + Connecting to MIDI port: '{}' + Connecting to MIDI port: '{}' + + + + Connecting to matching MIDI port: '{}' + Connecting to matching MIDI port: '{}' + + MIDIMessageAttr - + Channel Channel - + Note ملاحظة - + Velocity Velocity - + Control التحكم - + Program البرنامج - + Value القيمة - + Song الأغنية - + Pitch Pitch - + Position الموقع @@ -81,62 +112,62 @@ MIDIMessageType - + Note ON Note ON - + Note OFF Note OFF - + Polyphonic After-touch Polyphonic After-touch - + Control/Mode Change Control/Mode Change - + Program Change تغيير البرنامج - + Channel After-touch Channel After-touch - + Pitch Bend Change Pitch Bend Change - + Song Select حدد الأغنية - + Song Position Song Position - + Start إبدء - + Stop توقف - + Continue إستمرار @@ -144,30 +175,40 @@ MIDISettings - - MIDI default devices - MIDI default devices - - - + Input الإدخال - + Output المخرج + + + MIDI devices + MIDI devices + + + + Misc options + Misc options + + + + Try to connect using only device/port name + Try to connect using only device/port name + SettingsPageName - + MIDI settings MIDI settings - + MIDI Settings MIDI Settings diff --git a/lisp/i18n/ts/ar_SA/network.ts b/lisp/i18n/ts/ar_SA/network.ts index 5eb819730..73d19cff7 100644 --- a/lisp/i18n/ts/ar_SA/network.ts +++ b/lisp/i18n/ts/ar_SA/network.ts @@ -28,44 +28,49 @@ NetworkDiscovery - + Host discovery اكتشاف المضيف - + Manage hosts إدارة المضيفين - + Discover hosts اكتشف المضيفين - + Manually add a host إضافة مضيف يدوياً - + Remove selected host إزالة المضيف المحدد - + Remove all host إزالة جميع المضيفين - + Address العنوان - + Host IP IP المضيف + + + Select the hosts you want to add + Select the hosts you want to add + diff --git a/lisp/i18n/ts/ar_SA/osc.ts b/lisp/i18n/ts/ar_SA/osc.ts index 52d1d07a1..7d8258b31 100644 --- a/lisp/i18n/ts/ar_SA/osc.ts +++ b/lisp/i18n/ts/ar_SA/osc.ts @@ -4,7 +4,7 @@ Cue Name - + OSC Settings OSC Settings @@ -12,7 +12,7 @@ CueCategory - + Integration cues Integration cues @@ -20,7 +20,7 @@ CueName - + OSC Cue OSC Cue @@ -28,57 +28,57 @@ OscCue - + Type Type - + Value Value - + FadeTo FadeTo - + Fade Fade - + OSC Message OSC Message - + Add Add - + Remove Remove - + OSC Path: OSC Path: - + /path/to/something /path/to/something - + Time (sec) Time (sec) - + Curve Curve diff --git a/lisp/i18n/ts/ar_SA/presets.ts b/lisp/i18n/ts/ar_SA/presets.ts index 4ae1de383..21fbb5547 100644 --- a/lisp/i18n/ts/ar_SA/presets.ts +++ b/lisp/i18n/ts/ar_SA/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Create Cue - + Load on selected Cues Load on selected Cues @@ -57,62 +57,62 @@ حدّد ضبط مُسبق - + Preset name Preset name - + Add أضف - + Rename إعادة التسمية - + Edit تعديل - + Remove احذف - + Export selected Export selected - + Import تحميل - + Warning تحذير - + The same name is already used! هذا الإسم مستعمل مسبقا! - + Cannot export correctly. Cannot export correctly. - + Cannot import correctly. Cannot import correctly. - + Cue type Cue type @@ -127,7 +127,7 @@ Load on selected cues - + Cannot create a cue from this preset: {} Cannot create a cue from this preset: {} diff --git a/lisp/i18n/ts/ar_SA/replay_gain.ts b/lisp/i18n/ts/ar_SA/replay_gain.ts index 0b1153562..d7b7ca25c 100644 --- a/lisp/i18n/ts/ar_SA/replay_gain.ts +++ b/lisp/i18n/ts/ar_SA/replay_gain.ts @@ -4,22 +4,22 @@ ReplayGain - + ReplayGain / Normalization ReplayGain / Normalization - + Calculate إحسب - + Reset all إعادة كل شيء إلى الوضع العادى - + Reset selected إعادة المحدد إلى الوضع العادى @@ -52,12 +52,12 @@ ReplayGainDebug - + Applied gain for: {} Applied gain for: {} - + Discarded gain for: {} تجاهل الكسب: {} @@ -65,17 +65,17 @@ ReplayGainInfo - + Gain processing stopped by user. Gain processing stopped by user. - + Started gain calculation for: {} بدأ حساب الربح عن: {} - + Gain calculated for: {} حساب الربح بالنسبة: {} diff --git a/lisp/i18n/ts/ar_SA/synchronizer.ts b/lisp/i18n/ts/ar_SA/synchronizer.ts index 9e3f6d86b..5b86a175e 100644 --- a/lisp/i18n/ts/ar_SA/synchronizer.ts +++ b/lisp/i18n/ts/ar_SA/synchronizer.ts @@ -4,24 +4,24 @@ Synchronizer - + Synchronization المزامنة - + Manage connected peers إدارة الأقران متصل - + Show your IP إظهار IP الخاص بك - - Your IP is: - IP الخاص بك: + + Your IP is: {} + Your IP is: {} diff --git a/lisp/i18n/ts/cs_CZ/action_cues.ts b/lisp/i18n/ts/cs_CZ/action_cues.ts index 6e323de63..3d0c97418 100644 --- a/lisp/i18n/ts/cs_CZ/action_cues.ts +++ b/lisp/i18n/ts/cs_CZ/action_cues.ts @@ -55,7 +55,7 @@ CueCategory - + Action cues Action cues @@ -98,7 +98,7 @@ Index - Index + Index @@ -228,7 +228,7 @@ Error during cue execution. - Error during cue execution. + Chyba! Při spuštění narážky došlo k chybě. diff --git a/lisp/i18n/ts/cs_CZ/cache_manager.ts b/lisp/i18n/ts/cs_CZ/cache_manager.ts new file mode 100644 index 000000000..ce6aa7112 --- /dev/null +++ b/lisp/i18n/ts/cs_CZ/cache_manager.ts @@ -0,0 +1,35 @@ + + + + + CacheManager + + + Cache size warning + Cache size warning + + + + Warning threshold in MB (0 = disabled) + Warning threshold in MB (0 = disabled) + + + + Cache cleanup + Cache cleanup + + + + Delete the cache content + Delete the cache content + + + + SettingsPageName + + + Cache Manager + Cache Manager + + + diff --git a/lisp/i18n/ts/cs_CZ/cart_layout.ts b/lisp/i18n/ts/cs_CZ/cart_layout.ts index 4d17fd09e..d81524fd1 100644 --- a/lisp/i18n/ts/cs_CZ/cart_layout.ts +++ b/lisp/i18n/ts/cs_CZ/cart_layout.ts @@ -4,37 +4,32 @@ CartLayout - - Default behaviors - Default behaviors - - - + Countdown mode Countdown mode - + Show seek-bars Show seek-bars - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show volume Show volume - + Grid size Grid size @@ -79,35 +74,40 @@ Number of Pages: - + Page {number} Page {number} - + Warning Warning - + Every cue in the page will be lost. Every cue in the page will be lost. - + Are you sure to continue? Are you sure to continue? - + Number of columns: Number of columns: - + Number of rows: Number of rows: + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + LayoutDescription diff --git a/lisp/i18n/ts/cs_CZ/controller.ts b/lisp/i18n/ts/cs_CZ/controller.ts index b3a8ceb3e..c44367fbb 100644 --- a/lisp/i18n/ts/cs_CZ/controller.ts +++ b/lisp/i18n/ts/cs_CZ/controller.ts @@ -30,113 +30,136 @@ ControllerMidiSettings - + MIDI MIDI - + Type Typ - - Channel - Kanál + + Action + Činnost - - Note - Nota + + Capture + Zachytávání - - Action - Činnost + + Listening MIDI messages ... + Naslouchá se zprávám MIDI... - - Filter "note on" - Filtr "nota zapnuta" + + -- All Messages -- + -- All Messages -- - - Filter "note off" - Filtr (nota vypnuta" + + Capture filter + Capture filter - - Capture - Zachytávání + + Data 1 + Data 1 - - Listening MIDI messages ... - Naslouchá se zprávám MIDI... + + Data 2 + Data 2 + + + + Data 3 + Data 3 ControllerOscSettings - + OSC Message OSC Message - + OSC OSC - + Path Path - + Types Types - + Arguments Arguments - - Actions - Actions - - - + OSC Capture OSC Capture - + Add Add - + Remove Remove - + Capture Capture + + + Waiting for messages: + Waiting for messages: + + + + /path/to/method + /path/to/method + + + + Action + Action + + + + ControllerOscSettingsWarning + + + Warning + Warning + ControllerSettings - + Add Přidat - + Remove Odstranit @@ -197,12 +220,12 @@ Osc Cue - + Type Type - + Argument Argument @@ -210,12 +233,12 @@ OscCue - + Add Add - + Remove Remove @@ -228,7 +251,7 @@ Ovládání narážky - + MIDI Controls Ovládání MIDI @@ -238,7 +261,7 @@ Klávesové zkratky - + OSC Controls OSC Controls diff --git a/lisp/i18n/ts/cs_CZ/gst_backend.ts b/lisp/i18n/ts/cs_CZ/gst_backend.ts index e9d012a5b..653c7817b 100644 --- a/lisp/i18n/ts/cs_CZ/gst_backend.ts +++ b/lisp/i18n/ts/cs_CZ/gst_backend.ts @@ -119,7 +119,7 @@ Audio cue (from file) - + Select media files Select media files @@ -127,7 +127,7 @@ GstMediaError - + Cannot create pipeline element: "{}" Cannot create pipeline element: "{}" @@ -143,7 +143,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" Invalid pipeline element: "{}" @@ -151,7 +151,7 @@ GstPipelineEdit - + Edit Pipeline Upravit komunikační spojení @@ -159,9 +159,14 @@ GstSettings - - Pipeline - Komunikační spojení + + Default pipeline + Default pipeline + + + + Applied only to new cues. + Applied only to new cues. @@ -255,7 +260,7 @@ Výška tónu - + URI Input Vstup URI diff --git a/lisp/i18n/ts/cs_CZ/lisp.ts b/lisp/i18n/ts/cs_CZ/lisp.ts index db4cbc503..df1771bd8 100644 --- a/lisp/i18n/ts/cs_CZ/lisp.ts +++ b/lisp/i18n/ts/cs_CZ/lisp.ts @@ -35,52 +35,36 @@ AboutDialog - + Web site Stránky - + Source code Zdrojový kód - + Info Informace - + License Licence - + Contributors Přispěvatelé - + Discussion Discussion - - AlsaSinkSettings - - - ALSA device - ALSA device - - - - ApiServerError - - - Network API server stopped working. - Network API server stopped working. - - AppConfiguration @@ -92,277 +76,52 @@ AppGeneralSettings - + Default layout Default layout - - Enable startup layout selector - Enable startup layout selector - - - + Application themes Application themes - + UI theme: UI theme: - + Icons theme: Icons theme: - - Application language (require restart) - Application language (require restart) - - - + Language: Language: - - - ApplicationError - - - Startup error - Startup error - - - - AudioDynamicSettings - - - Compressor - Compressor - - - - Expander - Expander - - - - Soft Knee - Soft Knee - - - - Hard Knee - Hard Knee - - - - Compressor/Expander - Compressor/Expander - - - - Type - Type - - - - Curve Shape - Curve Shape - - - - Ratio - Ratio - - - - Threshold (dB) - Threshold (dB) - - - - AudioPanSettings - - - Audio Pan - Audio Pan - - - - Center - Center - - - - Left - Left - - - - Right - Right - - - - CartLayout - - - Default behaviors - Default behaviors - - - - Countdown mode - Countdown mode - - - - Show seek-bars - Show seek-bars - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Show accurate time - - - - Show volume - Show volume - - - - Grid size - Grid size - - - - Number of columns: - Number of columns: - - - - Number of rows: - Number of rows: - - - - Play - Play - - - - Pause - Pause - - - - Stop - Stop - - - - Reset volume - Reset volume - - - - Add page - Add page - - - - Add pages - Add pages - - - - Remove current page - Remove current page - - - - Number of Pages: - Number of Pages: - - - - Page {number} - Page {number} - - - - Warning - Warning - - - Every cue in the page will be lost. - Every cue in the page will be lost. + + Show layout selection at startup + Show layout selection at startup - - Are you sure to continue? - Are you sure to continue? - - - - CollectionCue - - - Add - Add - - - - Remove - Remove - - - - Cue - Cue + + Use layout at startup: + Use layout at startup: - - Action - Action + + Application language (require a restart) + Application language (require a restart) - CommandCue - - - Command - Command - - - - Command to execute, as in a shell - Command to execute, as in a shell - - - - Discard command output - Discard command output - - - - Ignore command errors - Ignore command errors - + ApplicationError - - Kill instead of terminate - Kill instead of terminate + + Startup error + Startup error @@ -646,17 +405,7 @@ CueCategory - - Action cues - Action cues - - - - Integration cues - Integration cues - - - + Misc cues Misc cues @@ -677,7 +426,7 @@ CueName - + Media Cue Narážka v záznamu @@ -798,14 +547,14 @@ Výchozí činnost pro zastavení narážky - - Interrupt fade - Interrupt fade + + Interrupt action fade + Interrupt action fade - - Fade actions - Fade actions + + Fade actions default value + Fade actions default value @@ -883,12 +632,12 @@ FadeEdit - + Duration (sec): Duration (sec): - + Curve: Curve: @@ -907,1385 +656,351 @@ - GlobalAction - - - Go - Go - - - - Reset - Reset - - - - Stop all cues - Stop all cues - - - - Pause all cues - Pause all cues - - - - Resume all cues - Resume all cues - - - - Interrupt all cues - Interrupt all cues - + HotKeyEdit - - Fade-out all cues - Fade-out all cues + + Press shortcut + Press shortcut + + + LayoutSelect - - Fade-in all cues - Fade-in all cues + + Layout selection + Výběr rozvržení - - Move standby forward - Move standby forward + + Select layout + Vybrat rozvržení - - Move standby back - Move standby back + + Open file + Otevřít soubor - GstBackend + ListLayout - - Audio cue (from file) - Audio cue (from file) + + Layout actions + Layout actions - - Select media files - Select media files + + Fade out when stopping all cues + Fade out when stopping all cues - - - GstMediaError - - Cannot create pipeline element: "{}" - Cannot create pipeline element: "{}" + + Fade out when interrupting all cues + Fade out when interrupting all cues - - - GstMediaSettings - - Change Pipeline - Change Pipeline + + Fade out when pausing all cues + Fade out when pausing all cues - - - GstMediaWarning - - Invalid pipeline element: "{}" - Invalid pipeline element: "{}" + + Fade in when resuming all cues + Fade in when resuming all cues - GstPipelineEdit + LogStatusIcon - - Edit Pipeline - Edit Pipeline + + Errors/Warnings + Errors/Warnings - GstSettings + Logging - - Pipeline - Pipeline + + Debug + Ladění - - - HotKeyEdit - - Press shortcut - Press shortcut + + Warning + Varování - - - IndexActionCue - - No suggestion - No suggestion + + Error + Chyba - - Index - Index + + Dismiss all + Dismiss all - - Use a relative index - Use a relative index + + Show details + Show details - - Target index - Target index + + Linux Show Player - Log Viewer + Linux Show Player - Log Viewer - - Action - Action + + Info + Info - - Suggested cue name - Suggested cue name + + Critical + Critical - - - JackSinkSettings - - Connections - Connections + + Time + Time - - Edit connections - Edit connections + + Milliseconds + Milliseconds - - Output ports - Output ports + + Logger name + Logger name - - Input ports - Input ports + + Level + Level - - Connect - Connect + + Message + Message - - Disconnect - Disconnect + + Function + Function - - - LayoutDescription - - Organize the cues in a list - Organize the cues in a list + + Path name + Path name - - Organize cues in grid like pages - Organize cues in grid like pages + + File name + File name - - - LayoutDetails - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Space or Double-Click to edit a cue + + Line no. + Line no. - - To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL + + Module + Module - - To move cues drag them - To move cues drag them + + Process ID + Process ID - - Click a cue to run it - Click a cue to run it + + Process name + Process name - - SHIFT + Click to edit a cue - SHIFT + Click to edit a cue + + Thread ID + Thread ID - - CTRL + Click to select a cue - CTRL + Click to select a cue - - - - To move cues drag them while pressing SHIFT - To move cues drag them while pressing SHIFT - - - - LayoutName - - - List Layout - List Layout - - - - Cart Layout - Cart Layout - - - - LayoutSelect - - - Layout selection - Výběr rozvržení - - - - Select layout - Vybrat rozvržení - - - - Open file - Otevřít soubor - - - - ListLayout - - - Default behaviors - Default behaviors - - - - Show playing cues - Show playing cues - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Show accurate time - - - - Show seek-bars - Show seek-bars - - - - Auto-select next cue - Auto-select next cue - - - - Enable selection mode - Enable selection mode - - - - GO Key: - GO Key: - - - - GO Action: - GO Action: - - - - GO minimum interval (ms): - GO minimum interval (ms): - - - - Use fade (buttons) - Use fade (buttons) - - - - Stop Cue - Stop Cue - - - - Pause Cue - Pause Cue - - - - Resume Cue - Resume Cue - - - - Interrupt Cue - Interrupt Cue - - - - Edit cue - Edit cue - - - - Edit selected - Edit selected - - - - Clone cue - Clone cue - - - - Clone selected - Clone selected - - - - Remove cue - Remove cue - - - - Remove selected - Remove selected - - - - Selection mode - Selection mode - - - - Pause all - Pause all - - - - Stop all - Stop all - - - - Interrupt all - Interrupt all - - - - Resume all - Resume all - - - - Fade-Out all - Fade-Out all - - - - Fade-In all - Fade-In all - - - - Edit selected cues - Edit selected cues - - - - Remove selected cues - Remove selected cues - - - - Use fade - Use fade - - - - Copy of {} - Copy of {} - - - - ListLayoutHeader - - - Cue - Cue - - - - Pre wait - Pre wait - - - - Action - Action - - - - Post wait - Post wait - - - - ListLayoutInfoPanel - - - Cue name - Cue name - - - - Cue description - Cue description - - - - LogStatusIcon - - - Errors/Warnings - Errors/Warnings - - - - Logging - - - Debug - Ladění - - - - Warning - Varování - - - - Error - Chyba - - - - Dismiss all - Dismiss all - - - - Show details - Show details - - - - Linux Show Player - Log Viewer - Linux Show Player - Log Viewer - - - - Info - Info - - - - Critical - Critical - - - - Time - Time - - - - Milliseconds - Milliseconds - - - - Logger name - Logger name - - - - Level - Level - - - - Message - Message - - - - Function - Function - - - - Path name - Path name - - - - File name - File name - - - - Line no. - Line no. - - - - Module - Module - - - - Process ID - Process ID - - - - Process name - Process name - - - - Thread ID - Thread ID - - - - Thread name - Thread name - - - - Exception info - Exception info - - - - MIDICue - - - MIDI Message - MIDI Message - - - - Message type - Message type - - - - MIDIMessageAttr - - - Channel - Channel - - - - Note - Note - - - - Velocity - Velocity - - - - Control - Control - - - - Program - Program - - - - Value - Value - - - - Song - Song - - - - Pitch - Pitch - - - - Position - Position - - - - MIDIMessageType - - - Note ON - Note ON - - - - Note OFF - Note OFF - - - - Polyphonic After-touch - Polyphonic After-touch - - - - Control/Mode Change - Control/Mode Change - - - - Program Change - Program Change - - - - Channel After-touch - Channel After-touch - - - - Pitch Bend Change - Pitch Bend Change - - - - Song Select - Song Select - - - - Song Position - Song Position - - - - Start - Start - - - - Stop - Stop - - - - Continue - Continue - - - - MIDISettings - - - MIDI default devices - MIDI default devices - - - - Input - Input - - - - Output - Output - - - - MainWindow - - - &File - &Soubor - - - - New session - Nové sezení - - - - Open - Otevřít - - - - Save session - Uložit sezení - - - - Preferences - Nastavení - - - - Save as - Uložit jako - - - - Full Screen - Na celou obrazovku - - - - Exit - Ukončit - - - - &Edit - Úp&ravy - - - - Undo - Zpět - - - - Redo - Znovu - - - - Select all - Vybrat vše - - - - Select all media cues - Vybrat všechny narážky v záznamu - - - - Deselect all - Odznačit všechny narážky - - - - CTRL+SHIFT+A - Ctrl+Shift+A - - - - Invert selection - Obrátit výběr - - - - CTRL+I - CTRL+I - - - - Edit selected - Upravit vybrané - - - - CTRL+SHIFT+E - Ctrl+Shift+E - - - - &Layout - &Rozvržení - - - - &Tools - &Nástroje - - - - Edit selection - Upravit výběr - - - - &About - &O programu - - - - About - O programu - - - - About Qt - O Qt - - - - Close session - Zavřít sezení - - - - Do you want to save them now? - Do you want to save them now? - - - - MainWindowDebug - - - Registered cue menu: "{}" - Registered cue menu: "{}" - - - - MainWindowError - - - Cannot create cue {} - Cannot create cue {} - - - - MediaCueSettings - - - Start time - Čas spuštění - - - - Stop position of the media - Poloha zastavení záznamu - - - - Stop time - Čas zastavení - - - - Start position of the media - Poloha spuštění záznamu - - - - Loop - Smyčka - - - - MediaElementName - - - Pitch - Pitch - - - - Volume - Volume - - - - Audio Pan - Audio Pan - - - - Compressor/Expander - Compressor/Expander - - - - JACK Out - JACK Out - - - - URI Input - URI Input - - - - 10 Bands Equalizer - 10 Bands Equalizer - - - - ALSA Out - ALSA Out - - - - dB Meter - dB Meter - - - - Preset Input - Preset Input - - - - PulseAudio Out - PulseAudio Out - - - - Custom Element - Custom Element - - - - Speed - Speed - - - - System Out - System Out - - - - System Input - System Input - - - - MediaInfo - - - Media Info - Media Info - - - - Warning - Warning - - - - No info to display - No info to display - - - - Info - Info - - - - Value - Value - - - - NetworkApiDebug - - - New end-point: {} - New end-point: {} - - - - NetworkDiscovery - - - Host discovery - Host discovery - - - - Manage hosts - Manage hosts - - - - Discover hosts - Discover hosts - - - - Manually add a host - Manually add a host - - - - Remove selected host - Remove selected host - - - - Remove all host - Remove all host + + Thread name + Thread name - - Address - Address + + Exception info + Exception info - - Host IP - Host IP + + Showing {} of {} records + Showing {} of {} records - Osc Cue + MIDICue - - Type - Type + + &File + &Soubor - - Argument - Argument + + New session + Nové sezení - OscCue - - - Add - Add - - - - Remove - Remove - - - - Type - Type - + MIDIMessageAttr - - Value - Value + + Open + Otevřít - - FadeTo - FadeTo + + Save session + Uložit sezení - - Fade - Fade + + Preferences + Nastavení - - OSC Message - OSC Message + + Save as + Uložit jako - - OSC Path: - OSC Path: + + Full Screen + Na celou obrazovku - - /path/to/something - /path/to/something + + Exit + Ukončit - - Time (sec) - Time (sec) + + &Edit + Úp&ravy - - Curve - Curve + + Undo + Zpět - - - OscServerDebug - - Message from {} -> path: "{}" args: {} - Message from {} -> path: "{}" args: {} + + Redo + Znovu - OscServerError + MIDIMessageType - - Cannot start OSC sever - Cannot start OSC sever + + Select all + Vybrat vše - - - OscServerInfo - - OSC server started at {} - OSC server started at {} + + Select all media cues + Vybrat všechny narážky v záznamu - - OSC server stopped - OSC server stopped + + Deselect all + Odznačit všechny narážky - - - OscSettings - - OSC Settings - OSC Settings + + CTRL+SHIFT+A + Ctrl+Shift+A - - Input Port: - Input Port: + + Invert selection + Obrátit výběr - - Output Port: - Output Port: + + CTRL+I + CTRL+I - - Hostname: - Hostname: + + Edit selected + Upravit vybrané - - - PitchSettings - - Pitch - Pitch + + CTRL+SHIFT+E + Ctrl+Shift+E - - {0:+} semitones - {0:+} semitones + + &Layout + &Rozvržení - - - PluginsError - - Failed to load "{}" - Failed to load "{}" + + &Tools + &Nástroje - - Failed to terminate plugin: "{}" - Failed to terminate plugin: "{}" + + Edit selection + Upravit výběr - - the requested plugin is not loaded: {} - the requested plugin is not loaded: {} + + &About + &O programu - PluginsInfo - - - Plugin loaded: "{}" - Plugin loaded: "{}" - + MIDISettings - - Plugin terminated: "{}" - Plugin terminated: "{}" + + About + O programu - - - PluginsWarning - - Cannot satisfy dependencies for: {} - Cannot satisfy dependencies for: {} + + About Qt + O Qt - - - Preset - - Create Cue - Create Cue + + Close session + Zavřít sezení - - Load on selected Cues - Load on selected Cues + + Do you want to save them now? + Do you want to save them now? - PresetSrcSettings + MainWindowDebug - - Presets - Presets + + Registered cue menu: "{}" + Registered cue menu: "{}" - Presets - - - Cannot scan presets - Cannot scan presets - - - - Error while deleting preset "{}" - Error while deleting preset "{}" - - - - Cannot load preset "{}" - Cannot load preset "{}" - - - - Cannot save preset "{}" - Cannot save preset "{}" - - - - Cannot rename preset "{}" - Cannot rename preset "{}" - - - - Select Preset - Select Preset - - - - Presets - Presets - - - - Preset name - Preset name - - - - Add - Add - - - - Rename - Rename - - - - Edit - Edit - - - - Remove - Remove - - - - Export selected - Export selected - - - - Import - Import - - - - Warning - Warning - - - - The same name is already used! - The same name is already used! - - - - Cannot export correctly. - Cannot export correctly. - - - - Cannot import correctly. - Cannot import correctly. - - - - Cue type - Cue type - - - - Load on cue - Load on cue - - - - Load on selected cues - Load on selected cues - - - - Save as preset - Save as preset - + MainWindowError - - Cannot create a cue from this preset: {} - Cannot create a cue from this preset: {} + + Cannot create cue {} + Cannot create cue {} @@ -2360,126 +1075,11 @@ - RenameCuesCommand - - - Renamed {number} cues - Renamed {number} cues - - - - RenameUiDebug - - - Regex error: Invalid pattern - Regex error: Invalid pattern - - - - ReplayGain - - - ReplayGain / Normalization - ReplayGain / Normalization - - - - Threads number - Threads number - - - - Apply only to selected media - Apply only to selected media - - - - ReplayGain to (dB SPL) - ReplayGain to (dB SPL) - - - - Normalize to (dB) - Normalize to (dB) - - - - Processing files ... - Processing files ... - - - - Calculate - Calculate - - - - Reset all - Reset all - - - - Reset selected - Reset selected - - - - ReplayGainDebug - - - Applied gain for: {} - Applied gain for: {} - - - - Discarded gain for: {} - Discarded gain for: {} - - - - ReplayGainInfo - - - Gain processing stopped by user. - Gain processing stopped by user. - - - - Started gain calculation for: {} - Started gain calculation for: {} - - - - Gain calculated for: {} - Gain calculated for: {} - - - - SeekCue - - - Cue - Cue - - - - Click to select - Click to select - - - - Not selected - Not selected - - - - Seek - Seek - + QColorButton - - Time to reach - Time to reach + + Right click to reset + Klepnutí pravým tlačítkem myši pro nastavení na výchozí hodnotu diff --git a/lisp/i18n/ts/cs_CZ/list_layout.ts b/lisp/i18n/ts/cs_CZ/list_layout.ts index d7d3696a1..1774c896b 100644 --- a/lisp/i18n/ts/cs_CZ/list_layout.ts +++ b/lisp/i18n/ts/cs_CZ/list_layout.ts @@ -38,77 +38,67 @@ ListLayout - - Default behaviors - Default behaviors - - - - Show playing cues - Show playing cues - - - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue - + Enable selection mode Enable selection mode - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - + Remove cue Remove cue - + Selection mode Selection mode @@ -143,65 +133,105 @@ Fade-In all - + Edit selected Edit selected - + Clone cue Clone cue - + Clone selected Clone selected - + Remove selected Remove selected - + GO Key: GO Key: - + GO Action: GO Action: - + GO minimum interval (ms): GO minimum interval (ms): - + Copy of {} Copy of {} + + + Show index column + Show index column + + + + Show resize handles + Show resize handles + + + + Restore default size + Restore default size + + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + + + + Behaviors + Behaviors + + + + Disable GO Key While Playing + Disable GO Key While Playing + + + + Use waveform seek-bars + Use waveform seek-bars + + + + GO Key Disabled While Playing + GO Key Disabled While Playing + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait @@ -222,7 +252,7 @@ SettingsPageName - + List Layout List Layout diff --git a/lisp/i18n/ts/cs_CZ/media_info.ts b/lisp/i18n/ts/cs_CZ/media_info.ts index 37b1647d2..8a4743ddf 100644 --- a/lisp/i18n/ts/cs_CZ/media_info.ts +++ b/lisp/i18n/ts/cs_CZ/media_info.ts @@ -4,29 +4,29 @@ MediaInfo - + Media Info Údaje o záznamu - - No info to display - Žádné údaje k zobrazení - - - + Info Informace - + Value Hodnota Warning - Warning + Varování + + + + Cannot get any information. + Nelze získat žádné informace. diff --git a/lisp/i18n/ts/cs_CZ/midi.ts b/lisp/i18n/ts/cs_CZ/midi.ts index 0ee4d5107..f58564e53 100644 --- a/lisp/i18n/ts/cs_CZ/midi.ts +++ b/lisp/i18n/ts/cs_CZ/midi.ts @@ -4,7 +4,7 @@ CueCategory - + Integration cues Integration cues @@ -12,7 +12,7 @@ CueName - + MIDI Cue MIDI Cue @@ -30,50 +30,81 @@ Message type + + MIDIError + + + Cannot connect to MIDI output port '{}'. + Cannot connect to MIDI output port '{}'. + + + + Cannot connect to MIDI input port '{}'. + Cannot connect to MIDI input port '{}'. + + + + MIDIInfo + + + MIDI port disconnected: '{}' + MIDI port disconnected: '{}' + + + + Connecting to MIDI port: '{}' + Connecting to MIDI port: '{}' + + + + Connecting to matching MIDI port: '{}' + Connecting to matching MIDI port: '{}' + + MIDIMessageAttr - + Channel Channel - + Note Note - + Velocity Velocity - + Control Control - + Program Program - + Value Value - + Song Song - + Pitch Pitch - + Position Position @@ -81,62 +112,62 @@ MIDIMessageType - + Note ON Note ON - + Note OFF Note OFF - + Polyphonic After-touch Polyphonic After-touch - + Control/Mode Change Control/Mode Change - + Program Change Program Change - + Channel After-touch Channel After-touch - + Pitch Bend Change Pitch Bend Change - + Song Select Song Select - + Song Position Song Position - + Start Start - + Stop Stop - + Continue Continue @@ -144,30 +175,40 @@ MIDISettings - - MIDI default devices - Výchozí zařízení MIDI - - - + Input Vstup - + Output Výstup + + + MIDI devices + MIDI devices + + + + Misc options + Misc options + + + + Try to connect using only device/port name + Try to connect using only device/port name + SettingsPageName - + MIDI settings Nastavení MIDI - + MIDI Settings MIDI Settings diff --git a/lisp/i18n/ts/cs_CZ/network.ts b/lisp/i18n/ts/cs_CZ/network.ts index 6a9dc1cb2..098a75f46 100644 --- a/lisp/i18n/ts/cs_CZ/network.ts +++ b/lisp/i18n/ts/cs_CZ/network.ts @@ -28,44 +28,49 @@ NetworkDiscovery - + Host discovery Host discovery - + Manage hosts Manage hosts - + Discover hosts Discover hosts - + Manually add a host Manually add a host - + Remove selected host Remove selected host - + Remove all host Remove all host - + Address Address - + Host IP Host IP + + + Select the hosts you want to add + Select the hosts you want to add + diff --git a/lisp/i18n/ts/cs_CZ/osc.ts b/lisp/i18n/ts/cs_CZ/osc.ts index 92feb5519..4e62f473f 100644 --- a/lisp/i18n/ts/cs_CZ/osc.ts +++ b/lisp/i18n/ts/cs_CZ/osc.ts @@ -12,7 +12,7 @@ CueCategory - + Integration cues Integration cues diff --git a/lisp/i18n/ts/cs_CZ/presets.ts b/lisp/i18n/ts/cs_CZ/presets.ts index bd2069ea1..6528d111d 100644 --- a/lisp/i18n/ts/cs_CZ/presets.ts +++ b/lisp/i18n/ts/cs_CZ/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Vytvořit narážku - + Load on selected Cues Nahrát na vybraných narážkách @@ -57,62 +57,62 @@ Vybrat přednastavení - + Preset name Název přednastavení - + Add Přidat - + Rename Přejmenovat - + Edit Upravit - + Remove Odstranit - + Export selected Vybráno vyvedení - + Import Vyvedení - + Warning Varování - + The same name is already used! Stejný název se již používá! - + Cannot export correctly. Nelze vyvést správně. - + Cannot import correctly. Nelze zavést správně. - + Cue type Typ narážky @@ -127,7 +127,7 @@ Load on selected cues - + Cannot create a cue from this preset: {} Cannot create a cue from this preset: {} diff --git a/lisp/i18n/ts/cs_CZ/replay_gain.ts b/lisp/i18n/ts/cs_CZ/replay_gain.ts index 3faff1dda..7cf61d418 100644 --- a/lisp/i18n/ts/cs_CZ/replay_gain.ts +++ b/lisp/i18n/ts/cs_CZ/replay_gain.ts @@ -4,22 +4,22 @@ ReplayGain - + ReplayGain / Normalization Vyrovnání hlasitosti/Normalizace - + Calculate Spočítat - + Reset all Nastavit vše znovu - + Reset selected Nastavit vybrané znovu @@ -52,12 +52,12 @@ ReplayGainDebug - + Applied gain for: {} Applied gain for: {} - + Discarded gain for: {} Discarded gain for: {} @@ -65,17 +65,17 @@ ReplayGainInfo - + Gain processing stopped by user. Gain processing stopped by user. - + Started gain calculation for: {} Started gain calculation for: {} - + Gain calculated for: {} Gain calculated for: {} diff --git a/lisp/i18n/ts/cs_CZ/synchronizer.ts b/lisp/i18n/ts/cs_CZ/synchronizer.ts index 054008bab..a7b693c9a 100644 --- a/lisp/i18n/ts/cs_CZ/synchronizer.ts +++ b/lisp/i18n/ts/cs_CZ/synchronizer.ts @@ -20,8 +20,8 @@ - Your IP is: - Vaše adresu (IP): + Your IP is: {} + Your IP is: {} diff --git a/lisp/i18n/ts/de_DE/action_cues.ts b/lisp/i18n/ts/de_DE/action_cues.ts index 1704ea560..08ec03aa1 100644 --- a/lisp/i18n/ts/de_DE/action_cues.ts +++ b/lisp/i18n/ts/de_DE/action_cues.ts @@ -16,7 +16,7 @@ Cue - Cue + Cue @@ -34,17 +34,17 @@ Command to execute, as in a shell - Befehl zum Ausführen, wie in einer Shell + Auszuführender Befehl, wie in einer Shell Discard command output - Befehl Output verwerfen + Befehls-Output verwerfen Ignore command errors - Befehl Fehler ignorieren + Befehls-Fehler ignorieren @@ -55,9 +55,9 @@ CueCategory - + Action cues - Action cues + Aktions-Cues @@ -65,7 +65,7 @@ Command Cue - Befehl Cue + Befehls-Cue @@ -75,12 +75,12 @@ Seek Cue - Seek Cue + Positions-Cue Collection Cue - Collection Cue + Sammlungs-Cue @@ -90,7 +90,7 @@ Index Action - Index Aktion + Index-Aktion @@ -98,7 +98,7 @@ Index - Index + Index @@ -108,7 +108,7 @@ Target index - Ziel Index + Ziel-Index @@ -118,12 +118,12 @@ No suggestion - No suggestion + Kein Vorschlag Suggested cue name - Suggested cue name + Vorgeschlagener Cue-Name @@ -131,7 +131,7 @@ Cue - Cue + Cue @@ -146,7 +146,7 @@ Seek - Seek + Aufsuchen @@ -164,17 +164,17 @@ Volume Settings - Lautstärke Einstellungen + Lautstärkeeinstellungen Seek Settings - Seek Einstellungen + Sucheinstellungen Edit Collection - Collection bearbeiten + Sammlung bearbeiten @@ -184,7 +184,7 @@ Stop Settings - Stop Einstellungen + Stoppeinstellungen @@ -192,7 +192,7 @@ Stop Action - Stop Aktion + Stopp-Aktion @@ -200,7 +200,7 @@ Cue - Cue + Cue @@ -215,12 +215,12 @@ Volume to reach - Lautstärke zu erreichen + Zu erreichende Lautstärke Fade - Fade + Überblenden @@ -228,7 +228,7 @@ Error during cue execution. - Error during cue execution. + Fehler bei der Cue-Ausführung. diff --git a/lisp/i18n/ts/de_DE/cache_manager.ts b/lisp/i18n/ts/de_DE/cache_manager.ts new file mode 100644 index 000000000..27594deed --- /dev/null +++ b/lisp/i18n/ts/de_DE/cache_manager.ts @@ -0,0 +1,35 @@ + + + + + CacheManager + + + Cache size warning + Cache-Größenwarnung + + + + Warning threshold in MB (0 = disabled) + Warnschwelle in MB (0 = deaktiviert) + + + + Cache cleanup + Cache-Bereinigung + + + + Delete the cache content + Cache-Inhalt löschen + + + + SettingsPageName + + + Cache Manager + Cache-Verwaltung + + + diff --git a/lisp/i18n/ts/de_DE/cart_layout.ts b/lisp/i18n/ts/de_DE/cart_layout.ts index 752000db8..29425e6c3 100644 --- a/lisp/i18n/ts/de_DE/cart_layout.ts +++ b/lisp/i18n/ts/de_DE/cart_layout.ts @@ -4,109 +4,109 @@ CartLayout - - Default behaviors - Default behaviors - - - + Countdown mode - Countdown mode + Countdown-Modus - + Show seek-bars - Show seek-bars + Suchleisten anzeigen - + Show dB-meters - Show dB-meters + dB-Meter anzeigen - + Show accurate time - Show accurate time + Genaue Zeit anzeigen - + Show volume - Show volume + Lautstärke anzeigen - + Grid size - Grid size + Rastergröße Play - Play + Wiedergabe Pause - Pause + Pause Stop - Stop + Stopp Reset volume - Reset volume + Lautstärke zurücksetzen Add page - Add page + Seite hinzufügen Add pages - Add pages + Seiten hinzufügen Remove current page - Remove current page + Aktuelle Seite entfernen Number of Pages: - Number of Pages: + Seitenanzahl: - + Page {number} - Page {number} + Seite {number} - + Warning - Warning + Warnung - + Every cue in the page will be lost. - Every cue in the page will be lost. + Alle Cues auf der Seite gehen verloren. - + Are you sure to continue? - Are you sure to continue? + Möchten Sie wirklich fortsetzen? - + Number of columns: - Number of columns: + Spalten-Anzahl: - + Number of rows: - Number of rows: + Zeilen-Anzahl: + + + + Default behaviors (applied to new sessions) + Standardverhalten (auf neue Sitzungen angewendet) @@ -114,7 +114,7 @@ Organize cues in grid like pages - Organize cues in grid like pages + Cues in rasterartigen Seiten organisieren @@ -122,27 +122,27 @@ Click a cue to run it - Click a cue to run it + Klicke auf einen Cue, um ihn auszuführen SHIFT + Click to edit a cue - SHIFT + Click to edit a cue + SHIFT + Klicken, um einen Cue zu bearbeiten CTRL + Click to select a cue - CTRL + Click to select a cue + STRG + Klicken, um einen Cue auszuwählen To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL + STRG und Ziehen, um Cues zu kopieren To move cues drag them while pressing SHIFT - To move cues drag them while pressing SHIFT + SHIFT und Ziehen, um Cues zu verschieben @@ -150,7 +150,7 @@ Cart Layout - Cart Layout + Warenkorb Layout @@ -158,22 +158,22 @@ Edit cue - Edit cue + Cue bearbeiten Edit selected cues - Edit selected cues + Ausgewählte Cues bearbeiten Remove cue - Remove cue + Cue entfernen Remove selected cues - Remove selected cues + Ausgewählte Cues entfernen @@ -181,7 +181,7 @@ Cart Layout - Cart Layout + Warenkorb Layout diff --git a/lisp/i18n/ts/de_DE/controller.ts b/lisp/i18n/ts/de_DE/controller.ts index a92a64916..1d0bf9089 100644 --- a/lisp/i18n/ts/de_DE/controller.ts +++ b/lisp/i18n/ts/de_DE/controller.ts @@ -6,7 +6,7 @@ Cannot load controller protocol: "{}" - Cannot load controller protocol: "{}" + Kann das Controller-Protokoll nicht laden: "{}" @@ -19,7 +19,12 @@ Shortcuts - Shortcuts + Tastenkürzel + + + + Shortcut + Tastenkürzel @@ -30,113 +35,136 @@ ControllerMidiSettings - + MIDI - MIDI + MIDI - + Type Typ - - Channel - Kanal + + Action + Aktion - - Note - Note + + Capture + Aufnehmen - - Action - Aktion + + Listening MIDI messages ... + Warte auf MIDI Nachrichten... - - Filter "note on" - Filter "Note an" + + -- All Messages -- + -- Alle Nachrichten -- - - Filter "note off" - Filter "Note aus" + + Capture filter + Aufnahmefilter - - Capture - Capture + + Data 1 + Daten 1 - - Listening MIDI messages ... - Höre MIDI Nachrichten + + Data 2 + Daten 2 + + + + Data 3 + Daten 3 ControllerOscSettings - + OSC Message - OSC Message + OSC-Nachricht - + OSC - OSC + OSC - + Path - Path + Pfad - + Types - Types + Typen - + Arguments - Arguments - - - - Actions - Actions + Argumente - + OSC Capture - OSC Capture + OSC-Aufnahme - + Add - Add + Hinzufügen - + Remove - Remove + Entfernen - + Capture - Capture + Aufnehmen + + + + Waiting for messages: + Warte auf Nachrichten: + + + + /path/to/method + /pfad/zu/methode + + + + Action + Aktion + + + + ControllerOscSettingsWarning + + + Warning + Warnung ControllerSettings - + Add Hinzufügen - + Remove Entfernen @@ -146,78 +174,78 @@ Go - Go + Go Reset - Reset + Zurücksetzen Stop all cues - Stop all cues + Alle Cues stoppen Pause all cues - Pause all cues + Alle Cues pausieren Resume all cues - Resume all cues + Alle Cues fortsetzen Interrupt all cues - Interrupt all cues + Alle Cues unterbrechen Fade-out all cues - Fade-out all cues + Alle Cues ausblenden Fade-in all cues - Fade-in all cues + Alle Cues einblenden Move standby forward - Move standby forward + Standby vorwärts bewegen Move standby back - Move standby back + Standby zurück bewegen Osc Cue - + Type - Type + Typ - + Argument - Argument + Argument OscCue - + Add - Add + Hinzufügen - + Remove - Remove + Entfernen @@ -225,27 +253,27 @@ Cue Control - Cue Control + Cue-Steuerung - + MIDI Controls - MIDI Controls + MIDI-Kontrollelemente Keyboard Shortcuts - Tastatur Shortcuts + Tastaturkürzel - + OSC Controls - OSC Controls + OSC-Kontrollelemente Layout Controls - Layout Controls + Layout-Kontrollelemente diff --git a/lisp/i18n/ts/de_DE/gst_backend.ts b/lisp/i18n/ts/de_DE/gst_backend.ts index 08ac78ad2..ca22e90ee 100644 --- a/lisp/i18n/ts/de_DE/gst_backend.ts +++ b/lisp/i18n/ts/de_DE/gst_backend.ts @@ -19,17 +19,17 @@ Expander - Expander + Expander Soft Knee - Soft Knee + Weiches Knie Hard Knee - Hard Knee + Hartes Knie @@ -49,7 +49,7 @@ Ratio - Ratio + Ratio @@ -62,22 +62,22 @@ Audio Pan - Audio Pan + Audio-Pan Center - Center + Mitte Left - Left + Links Right - Right + Rechts @@ -95,7 +95,7 @@ Peak ttl (ms) - Peak ttl (ms) + Spitze ttl (ms) @@ -116,20 +116,20 @@ Audio cue (from file) - Audio cue (from file) + Audio-Cue (von Datei) - + Select media files - Select media files + Mediendateien auswählen GstMediaError - + Cannot create pipeline element: "{}" - Cannot create pipeline element: "{}" + Pipeline-Element kann nicht erstellt werden: "{}" @@ -143,15 +143,15 @@ GstMediaWarning - + Invalid pipeline element: "{}" - Invalid pipeline element: "{}" + Ungültiges Pipeline-Element: "{}" GstPipelineEdit - + Edit Pipeline Pipeline bearbeiten @@ -159,9 +159,14 @@ GstSettings - - Pipeline - Pipeline + + Default pipeline + Standard-Pipeline + + + + Applied only to new cues. + Wird nur auf neue Cues angewendet. @@ -207,12 +212,12 @@ Audio Pan - Audio Pan + Audio-Pan PulseAudio Out - PulseAudio Out + PulseAudio Ausgang @@ -222,17 +227,17 @@ dB Meter - dB Meter + dB Meter System Input - System Input + System-Eingang ALSA Out - ALSA Out + ALSA Ausgang @@ -247,17 +252,17 @@ System Out - System Out + System-Ausgang Pitch - Pitch + Tonhöhe - + URI Input - URI Input + URI Eingabe @@ -272,7 +277,7 @@ Preset Input - Preset Input + Preset Eingabe @@ -280,7 +285,7 @@ Pitch - Pitch + Tonhöhe @@ -293,7 +298,7 @@ Presets - Presets + Voreinstellungen @@ -306,7 +311,7 @@ GStreamer - GStreamer + GStreamer @@ -332,7 +337,7 @@ Buffering - Buffering + Pufferung diff --git a/lisp/i18n/ts/de_DE/lisp.ts b/lisp/i18n/ts/de_DE/lisp.ts index f67c3c570..e8f894872 100644 --- a/lisp/i18n/ts/de_DE/lisp.ts +++ b/lisp/i18n/ts/de_DE/lisp.ts @@ -35,50 +35,34 @@ AboutDialog - + Web site Website - + Source code Quellcode - + Info Informationen - + License Lizenz - + Contributors Mitwirkende - + Discussion - Discussion - - - - AlsaSinkSettings - - - ALSA device - ALSA device - - - - ApiServerError - - - Network API server stopped working. - Network API server stopped working. + Diskussion @@ -86,35 +70,50 @@ LiSP preferences - LiSP preferences + LiSP Einstellungen AppGeneralSettings - + Default layout - Default layout + Standard Layout - - Enable startup layout selector - Enable startup layout selector - - - + Application themes - Application themes + App-Thema - + UI theme: - UI theme: + UI-Thema: - + Icons theme: - Icons theme: + Symbolthema: + + + + Language: + Sprache: + + + + Show layout selection at startup + Layoutauswahl beim Start anzeigen + + + + Use layout at startup: + Layout beim Start verwenden: + + + + Application language (require a restart) + Anwendungssprache (Neustart erforderlich) @@ -130,9 +129,22 @@ ApplicationError - + Startup error - Startup error + Fehler beim Starten + + + + CommandsStack + + + Undo: {} + Rückgängig: {} + + + + Redo: {} + Wiederherstellen: {} @@ -383,7 +395,7 @@ Configuration written at {} - Configuration written at {} + Konfiguration geschrieben unter {} @@ -391,7 +403,7 @@ New configuration installed at {} - New configuration installed at {} + Neue Konfiguration installiert unter {} @@ -552,52 +564,52 @@ Pause - Pause + Pause Start - Start + Start Stop - Stop + Stopp Faded Start - Faded Start + Eingeblendeter Start Faded Resume - Faded Resume + Eingeblendetes Fortsetzen Faded Pause - Faded Pause + Ausgeblendete Pause Faded Stop - Faded Stop + Ausgeblendetes Stoppen Faded Interrupt - Faded Interrupt + Ausgeblendete Unterbrechung Resume - Resume + Fortsetzen Do Nothing - Do Nothing + Keine Aktion @@ -610,12 +622,12 @@ Cue name - Cue Name + Cue-Name NoName - Kein Name + KeinName @@ -625,7 +637,7 @@ Set Font Size - Font Größe einstellen + Schriftgröße einstellen @@ -640,25 +652,15 @@ Select font color - Font-Farbe auswählen + Schriftfarbe auswählen CueCategory - - Action cues - Action cues - - - - Integration cues - Integration cues - - - + Misc cues - Misc cues + Sonstige Cues @@ -666,88 +668,48 @@ Cue settings changed: "{}" - Cue settings changed: "{}" + Cue Einstellungen wurden verändert: "{}" Cues settings changed. - Cues settings changed. + Cue Einstellungen wurden verändert. CueName - + Media Cue Medien-Cue - - - Index Action - Index Action - - - - Seek Cue - Seek Cue - - - - Volume Control - Volume Control - - - - Command Cue - Command Cue - - - - Collection Cue - Collection Cue - - - - Stop-All - Stop-All - - - - MIDI Cue - MIDI Cue - - - - OSC Cue - OSC Cue - CueNextAction Do Nothing - Do Nothing + Keine Aktion Trigger after the end - Trigger after the end + Nach Ende auslösen Trigger after post wait - Trigger after post wait + Auslösen nach dem Post wait Select after the end - Select after the end + Nach Ende auswählen Select after post wait - Select after post wait + Nach dem Post wait auswählen @@ -755,7 +717,7 @@ Pre wait - Pre wait + Pre wait @@ -765,12 +727,12 @@ Post wait - Post wait + Post wait Wait after cue execution - Nach Cue Ausführung warten + Nach Cue-Ausführung warten @@ -785,89 +747,35 @@ Default action to start the cue - Standard Aktion zum Starten der Cue + Standard Aktion zum Starten des Cues Stop action - Stop Aktion + Stopp-Aktion Default action to stop the cue - Standard Aktion zum Beenden der Cue + Standard Aktion zum Beenden des Cues - - Interrupt fade - Interrupt fade + + Interrupt action fade + Unterbreche Aktions-Überblendung - - Fade actions - Fade actions + + Fade actions default value + Standardwert für Überblende-Aktionen CueTriggers - - - Started - Started - - - - Paused - Paused - - - - Stopped - Stopped - - - - Ended - Ended - - - - DbMeterSettings - - - DbMeter settings - DbMeter settings - - - - Time between levels (ms) - Time between levels (ms) - - - - Peak ttl (ms) - Peak ttl (ms) - - - - Peak falloff (dB/sec) - Peak falloff (dB/sec) - - - - Equalizer10Settings - - - 10 Bands Equalizer (IIR) - 10 Bands Equalizer (IIR) - - - - Fade Linear - Linear + Linear @@ -877,20 +785,20 @@ Quadratic2 - Logistisch + Quadratisch2 - FadeEdit + DbMeterSettings - + Duration (sec): - Duration (sec): + Dauer (sek): - + Curve: - Curve: + Kurve: @@ -898,12 +806,20 @@ Fade In - Fade In + Einblenden Fade Out - Fade Out + Ausblenden + + + + HotKeyEdit + + + Press shortcut + Tastenkürzel drücken @@ -1155,7 +1071,7 @@ Layout selection - Layout Auswahl + Layout-Auswahl @@ -1171,200 +1087,29 @@ ListLayout - - Default behaviors - Default behaviors - - - - Show playing cues - Show playing cues - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Show accurate time - - - - Show seek-bars - Show seek-bars - - - - Auto-select next cue - Auto-select next cue - - - - Enable selection mode - Enable selection mode - - - - GO Key: - GO Key: - - - - GO Action: - GO Action: - - - - GO minimum interval (ms): - GO minimum interval (ms): - - - - Use fade (buttons) - Use fade (buttons) - - - - Stop Cue - Stop Cue - - - - Pause Cue - Pause Cue - - - - Resume Cue - Resume Cue - - - - Interrupt Cue - Interrupt Cue - - - - Edit cue - Edit cue - - - - Edit selected - Edit selected - - - - Clone cue - Clone cue - - - - Clone selected - Clone selected - - - - Remove cue - Remove cue - - - - Remove selected - Remove selected - - - - Selection mode - Selection mode - - - - Pause all - Pause all + + Layout actions + Layout-Aktionen - Stop all - Stop all + Fade out when stopping all cues + Beim Beenden aller Cues ausblenden - Interrupt all - Interrupt all - - - - Resume all - Resume all - - - - Fade-Out all - Fade-Out all - - - - Fade-In all - Fade-In all - - - - Edit selected cues - Edit selected cues - - - - Remove selected cues - Remove selected cues - - - - Use fade - Use fade - - - - Copy of {} - Copy of {} - - - - ListLayoutHeader - - - Cue - Cue - - - - Pre wait - Pre wait - - - - Action - Action - - - - Post wait - Post wait + Fade out when interrupting all cues + Beim Unterbrechen aller Cues ausblenden - - - ListLayoutInfoPanel - - Cue name - Cue name + + Fade out when pausing all cues + Beim Pausieren aller Cues ausblenden - - Cue description - Cue description + + Fade in when resuming all cues + Beim Fortsetzen aller Cues einblenden @@ -1372,7 +1117,7 @@ Errors/Warnings - Errors/Warnings + Fehler/Warnungen @@ -1380,7 +1125,7 @@ Debug - Debug + Fehlersuche @@ -1395,1109 +1140,327 @@ Dismiss all - Dismiss all + Alle verwerfen Show details - Show details + Details anzeigen Linux Show Player - Log Viewer - Linux Show Player - Log Viewer + Linux Show Player - Log-Viewer Info - Info + Informationen Critical - Critical + Kritisch Time - Time + Zeit Milliseconds - Milliseconds + Millisekunden Logger name - Logger name + Name des Loggers Level - Level + Level Message - Message + Nachricht Function - Function + Funktion Path name - Path name + Pfadname File name - File name + Dateiname Line no. - Line no. + Zeilennr. Module - Module + Modul Process ID - Process ID + Prozess-ID Process name - Process name + Prozessname Thread ID - Thread ID + Thread-ID Thread name - Thread name + Thread-Name Exception info - Exception info + Ausnahme-Info + + + + Showing {} of {} records + Zeige {} von {} Datensätzen MIDICue - - MIDI Message - MIDI Message + + &File + &Datei - - Message type - Message type + + New session + Neue Sitzung MIDIMessageAttr - - Channel - Channel + + Open + Öffnen - - Note - Note + + Save session + Sitzung Speichern - - Velocity - Velocity + + Preferences + Einstellungen - - Control - Control + + Save as + Speichern als - - Program - Program + + Full Screen + Vollbild - - Value - Value + + Exit + Verlassen - - Song - Song + + &Edit + &Bearbeiten - - Pitch - Pitch + + Undo + Rückgängig - - Position - Position + + Redo + Wiederherstellen MIDIMessageType - - Note ON - Note ON + + Select all + Alle auswählen - - Note OFF - Note OFF + + Select all media cues + Alle Media-Cues auswählen - - Polyphonic After-touch - Polyphonic After-touch + + Deselect all + Auswahl aufheben - - Control/Mode Change - Control/Mode Change + + CTRL+SHIFT+A + CTRL+SHIFT+A - - Program Change - Program Change + + Invert selection + Auswahl invertieren - - Channel After-touch - Channel After-touch + + CTRL+I + STRG+I - - Pitch Bend Change - Pitch Bend Change + + Edit selected + Auswahl bearbeiten - - Song Select - Song Select + + CTRL+SHIFT+E + CTRL+SHIFT+E - - Song Position - Song Position + + &Layout + &Layout - - Start - Start + + &Tools + &Werkzeuge - - Stop - Stop + + Edit selection + Auswahl bearbeiten - - Continue - Continue + + &About + &Über - - - MIDISettings - - MIDI default devices - MIDI default devices + + About + Über - - Input - Input + + About Qt + Über Qt - - Output - Output + + Close session + Sitzung schließen - MainWindow + MainWindowDebug - - &File - &Datei - - - - New session - Neue Sitzung - - - - Open - Öffnen - - - - Save session - Sitzung Speichern - - - - Preferences - Einstellungen - - - - Save as - Speichern als - - - - Full Screen - Vollbild - - - - Exit - Verlassen - - - - &Edit - &Bearbeiten - - - - Undo - Rückgängig - - - - Redo - Wiederherstellen - - - - Select all - Alle auswählen - - - - Select all media cues - Alle Media Cues auswählen - - - - Deselect all - Auswahl aufheben - - - - CTRL+SHIFT+A - CTRL+Shift+A - - - - Invert selection - Auswahl invertieren - - - - CTRL+I - CTRL+I - - - - Edit selected - Auswahl bearbeiten - - - - CTRL+SHIFT+E - CTRL + Shift + E - - - - &Layout - &Layout - - - - &Tools - &Tools - - - - Edit selection - Auswahl bearbeiten - - - - &About - &Über - - - - About - Über - - - - About Qt - Über Qt - - - - Close session - Sitzung schließen - - - - Do you want to save them now? - Do you want to save them now? - - - - MainWindowDebug - - - Registered cue menu: "{}" - Registered cue menu: "{}" - - - - MainWindowError - - - Cannot create cue {} - Cannot create cue {} - - - - MediaCueSettings - - - Start time - Start Zeit - - - - Stop position of the media - Stop Position der Datei - - - - Stop time - Stop Zeit - - - - Start position of the media - Start Position der Datei - - - - Loop - Loop - - - - MediaElementName - - - Pitch - Pitch - - - - Volume - Volume - - - - Audio Pan - Audio Pan - - - - Compressor/Expander - Compressor/Expander - - - - JACK Out - JACK Out - - - - URI Input - URI Input - - - - 10 Bands Equalizer - 10 Bands Equalizer - - - - ALSA Out - ALSA Out - - - - dB Meter - dB Meter - - - - Preset Input - Preset Input - - - - PulseAudio Out - PulseAudio Out - - - - Custom Element - Custom Element - - - - Speed - Speed - - - - System Out - System Out - - - - System Input - System Input - - - - MediaInfo - - - Media Info - Media Info - - - - Warning - Warning - - - - No info to display - No info to display - - - - Info - Info - - - - Value - Value - - - - NetworkApiDebug - - - New end-point: {} - New end-point: {} - - - - NetworkDiscovery - - - Host discovery - Host discovery - - - - Manage hosts - Manage hosts - - - - Discover hosts - Discover hosts - - - - Manually add a host - Manually add a host - - - - Remove selected host - Remove selected host - - - - Remove all host - Remove all host - - - - Address - Address - - - - Host IP - Host IP - - - - Osc Cue - - - Type - Type - - - - Argument - Argument - - - - OscCue - - - Add - Add - - - - Remove - Remove - - - - Type - Type - - - - Value - Value - - - - FadeTo - FadeTo - - - - Fade - Fade - - - - OSC Message - OSC Message - - - - OSC Path: - OSC Path: - - - - /path/to/something - /path/to/something - - - - Time (sec) - Time (sec) - - - - Curve - Curve - - - - OscServerDebug - - - Message from {} -> path: "{}" args: {} - Message from {} -> path: "{}" args: {} - - - - OscServerError - - - Cannot start OSC sever - Cannot start OSC sever - - - - OscServerInfo - - - OSC server started at {} - OSC server started at {} - - - - OSC server stopped - OSC server stopped - - - - OscSettings - - - OSC Settings - OSC Settings - - - - Input Port: - Input Port: - - - - Output Port: - Output Port: - - - - Hostname: - Hostname: - - - - PitchSettings - - - Pitch - Pitch - - - - {0:+} semitones - {0:+} semitones - - - - PluginsError - - - Failed to load "{}" - Failed to load "{}" - - - - Failed to terminate plugin: "{}" - Failed to terminate plugin: "{}" - - - - the requested plugin is not loaded: {} - the requested plugin is not loaded: {} - - - - PluginsInfo - - - Plugin loaded: "{}" - Plugin loaded: "{}" - - - - Plugin terminated: "{}" - Plugin terminated: "{}" - - - - PluginsWarning - - - Cannot satisfy dependencies for: {} - Cannot satisfy dependencies for: {} - - - - Preset - - - Create Cue - Create Cue - - - - Load on selected Cues - Load on selected Cues - - - - PresetSrcSettings - - - Presets - Presets - - - - Presets - - - Cannot scan presets - Cannot scan presets - - - - Error while deleting preset "{}" - Error while deleting preset "{}" - - - - Cannot load preset "{}" - Cannot load preset "{}" - - - - Cannot save preset "{}" - Cannot save preset "{}" - - - - Cannot rename preset "{}" - Cannot rename preset "{}" - - - - Select Preset - Select Preset - - - - Presets - Presets - - - - Preset name - Preset name - - - - Add - Add - - - - Rename - Rename - - - - Edit - Edit - - - - Remove - Remove - - - - Export selected - Export selected - - - - Import - Import - - - - Warning - Warning - - - - The same name is already used! - The same name is already used! - - - - Cannot export correctly. - Cannot export correctly. - - - - Cannot import correctly. - Cannot import correctly. - - - - Cue type - Cue type - - - - Load on cue - Load on cue - - - - Load on selected cues - Load on selected cues - - - - Save as preset - Save as preset - - - - Cannot create a cue from this preset: {} - Cannot create a cue from this preset: {} - - - - QColorButton - - - Right click to reset - Rechtsklick zum Zurücksetzen - - - - RenameCues - - - Rename Cues - Rename Cues - - - - Rename cues - Rename cues - - - - Current - Current - - - - Preview - Preview - - - - Capitalize - Capitalize - - - - Lowercase - Lowercase - - - - Uppercase - Uppercase - - - - Remove Numbers - Remove Numbers - - - - Add numbering - Add numbering - - - - Reset - Reset - - - - Type your regex here: - Type your regex here: - - - - Regex help - Regex help - - - - RenameCuesCommand - - - Renamed {number} cues - Renamed {number} cues + + Do you want to save them now? + Möchten Sie sie jetzt speichern? - RenameUiDebug + MainWindowDebug - - Regex error: Invalid pattern - Regex error: Invalid pattern + + Registered cue menu: "{}" + Cue-Menü registriert: "{}" - ReplayGain - - - ReplayGain / Normalization - ReplayGain / Normalization - - - - Threads number - Threads number - - - - Apply only to selected media - Apply only to selected media - - - - ReplayGain to (dB SPL) - ReplayGain to (dB SPL) - - - - Normalize to (dB) - Normalize to (dB) - - - - Processing files ... - Processing files ... - - - - Calculate - Calculate - - - - Reset all - Reset all - + MainWindowError - - Reset selected - Reset selected + + Cannot create cue {} + Kann Cue-{} nicht erstellen - ReplayGainDebug + MediaCueSettings - - Applied gain for: {} - Applied gain for: {} + + Start time + Start-Zeit - - Discarded gain for: {} - Discarded gain for: {} + + Stop position of the media + Stop Position der Datei - - - ReplayGainInfo - - Gain processing stopped by user. - Gain processing stopped by user. + + Stop time + Stopp-Zeit - - Started gain calculation for: {} - Started gain calculation for: {} + + Start position of the media + Startposition der Datei - - Gain calculated for: {} - Gain calculated for: {} + + Loop + Schleife - SeekCue - - - Cue - Cue - - - - Click to select - Click to select - - - - Not selected - Not selected - - - - Seek - Seek - + Preset - - Time to reach - Time to reach + + Right click to reset + Rechtsklick zum Zurücksetzen - SettingsPageName + PresetSrcSettings Appearance Aussehen + + + Presets General - Generell + Allgemein Cue - Cue + Cue @@ -2507,358 +1470,27 @@ Plugins - Plugins + Plug-ins Behaviours - Behaviours + Verhalten Pre/Post Wait - Pre/Post Wait + Pre/Post Warten Fade In/Out - Fade In/Out + Ein-/Ausblenden Layouts - Layouts - - - - Action Settings - Action Settings - - - - Seek Settings - Seek Settings - - - - Volume Settings - Volume Settings - - - - Command - Command - - - - Edit Collection - Edit Collection - - - - Stop Settings - Stop Settings - - - - List Layout - List Layout - - - - Timecode - Timecode - - - - Timecode Settings - Timecode Settings - - - - Cue Control - Cue Control - - - - Layout Controls - Layout Controls - - - - MIDI Controls - MIDI Controls - - - - Keyboard Shortcuts - Keyboard Shortcuts - - - - OSC Controls - OSC Controls - - - - MIDI Settings - MIDI Settings - - - - MIDI settings - MIDI settings - - - - GStreamer - GStreamer - - - - Media Settings - Media Settings - - - - Triggers - Triggers - - - - OSC settings - OSC settings - - - - Cart Layout - Cart Layout - - - - SpeedSettings - - - Speed - Speed - - - - StopAll - - - Stop Action - Stop Action - - - - Synchronizer - - - Synchronization - Synchronization - - - - Manage connected peers - Manage connected peers - - - - Show your IP - Show your IP - - - - Your IP is: - Your IP is: - - - - Timecode - - - Cannot load timecode protocol: "{}" - Cannot load timecode protocol: "{}" - - - - TimecodeError - - - Cannot send timecode. - Cannot send timecode. - - - - TimecodeSettings - - - Replace HOURS by a static track number - Replace HOURS by a static track number - - - - Enable Timecode - Enable Timecode - - - - Track number - Track number - - - - Timecode Settings - Timecode Settings - - - - Timecode Format: - Timecode Format: - - - - Timecode Protocol: - Timecode Protocol: - - - - TriggersSettings - - - Add - Add - - - - Remove - Remove - - - - Trigger - Trigger - - - - Cue - Cue - - - - Action - Action - - - - UriInputSettings - - - Source - Source - - - - Find File - Find File - - - - Buffering - Buffering - - - - Use Buffering - Use Buffering - - - - Attempt download on network streams - Attempt download on network streams - - - - Buffer size (-1 default value) - Buffer size (-1 default value) - - - - Choose file - Choose file - - - - All files - All files - - - - UserElementSettings - - - User defined elements - User defined elements - - - - Only for advanced user! - Only for advanced user! - - - - VolumeControl - - - Cue - Cue - - - - Click to select - Click to select - - - - Not selected - Not selected - - - - Volume to reach - Volume to reach - - - - Fade - Fade - - - - VolumeControlError - - - Error during cue execution. - Error during cue execution. - - - - VolumeSettings - - - Volume - Volume - - - - Normalized volume - Normalized volume - - - - Reset - Reset + Layouts diff --git a/lisp/i18n/ts/de_DE/list_layout.ts b/lisp/i18n/ts/de_DE/list_layout.ts index 1c58ed22f..c318ef839 100644 --- a/lisp/i18n/ts/de_DE/list_layout.ts +++ b/lisp/i18n/ts/de_DE/list_layout.ts @@ -6,7 +6,7 @@ Organize the cues in a list - Organize the cues in a list + Organisieren Sie die Cues in einer Liste @@ -14,17 +14,17 @@ SHIFT + Space or Double-Click to edit a cue - SHIFT + Space or Double-Click to edit a cue + SHIFT + Leerzeichen oder Doppelklick, um einen Cue zu bearbeiten To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL + Um Cues zu kopieren, ziehen Sie sie, während Sie STRG drücken To move cues drag them - To move cues drag them + Um Cues zu verschieben, ziehen Sie sie @@ -32,135 +32,185 @@ List Layout - List Layout + Listen-Layout ListLayout - - Default behaviors - Default behaviors - - - - Show playing cues - Show playing cues - - - + Show dB-meters - Show dB-meters + dB-Meter anzeigen - + Show accurate time - Show accurate time + Genaue Zeit anzeigen - + Show seek-bars - Show seek-bars + Suchleiste anzeigen - + Auto-select next cue - Auto-select next cue + Nächsten Cue automatisch auswählen - + Enable selection mode - Enable selection mode + Selektionsmodus aktivieren - + Use fade (buttons) - Use fade (buttons) + Fade verwenden (Buttons) - + Stop Cue - Stop Cue + Cue stoppen - + Pause Cue - Pause Cue + Cue pausieren - + Resume Cue - Resume Cue + Cue fortsetzen - + Interrupt Cue - Interrupt Cue + Cue unterbrechen - + Edit cue - Edit cue + Cue bearbeiten - + Remove cue - Remove cue + Cue entfernen - + Selection mode - Selection mode + Auswahlmodus Pause all - Pause all + Alle pausieren Stop all - Stop all + Alle stoppen Interrupt all - Interrupt all + Alle unterbrechen Resume all - Resume all + Alle fortsetzen Fade-Out all - Fade-Out all + Alle ausblenden Fade-In all - Fade-In all + Alle einblenden - + Edit selected - Edit selected + Ausgewählte bearbeiten - + Clone cue - Clone cue + Cue klonen - + Clone selected - Clone selected + Ausgewählte klonen - + Remove selected - Remove selected + Ausgewählte entfernen + + + + GO Key: + GO Taste: + + + + GO Action: + GO-Aktion: + + + + GO minimum interval (ms): + GO minimales Intervall (ms): + + + + Copy of {} + Kopie von {} + + + + Show index column + Indexspalte anzeigen + + + + Show resize handles + Größen-Griffe anzeigen + + + + Restore default size + Standard-Größe wiederherstellen + + + + Default behaviors (applied to new sessions) + Standardverhalten (auf neue Sitzungen angewendet) + + + + Behaviors + Verhalten + + + + Disable GO Key While Playing + GO-Taste während der Wiedergabe deaktivieren + + + + Use waveform seek-bars + Wellenform Suchleisten verwenden + + + + GO Key Disabled While Playing + GO-Taste während der Wiedergabe deaktiviert @@ -186,24 +236,24 @@ ListLayoutHeader - + Cue - Cue + Cue - + Pre wait - Pre wait + Pre wait - + Action - Action + Aktion - + Post wait - Post wait + Post wait @@ -211,20 +261,20 @@ Cue name - Cue name + Cue-Name Cue description - Cue description + Cue-Beschreibung SettingsPageName - + List Layout - List Layout + Listen-Layout diff --git a/lisp/i18n/ts/de_DE/media_info.ts b/lisp/i18n/ts/de_DE/media_info.ts index b810eb7d6..a1e69a8bd 100644 --- a/lisp/i18n/ts/de_DE/media_info.ts +++ b/lisp/i18n/ts/de_DE/media_info.ts @@ -4,29 +4,29 @@ MediaInfo - + Media Info - Medien Information + Medien-Information - - No info to display - Keine Informationen zum darstellen - - - + Info Informationen - + Value Wert Warning - Warning + Warnung + + + + Cannot get any information. + Kann keine Informationen erhalten. diff --git a/lisp/i18n/ts/de_DE/midi.ts b/lisp/i18n/ts/de_DE/midi.ts index f0538e668..41d7cc9a9 100644 --- a/lisp/i18n/ts/de_DE/midi.ts +++ b/lisp/i18n/ts/de_DE/midi.ts @@ -4,17 +4,17 @@ CueCategory - + Integration cues - Integration cues + Integrations-Cues CueName - + MIDI Cue - MIDI Cue + MIDI-Cue @@ -22,154 +22,195 @@ MIDI Message - MIDI Message + MIDI-Nachricht Message type - Message type + Nachrichtentyp + + + + MIDIError + + + Cannot connect to MIDI output port '{}'. + Verbindung zum MIDI-Ausgangsport '{}' nicht möglich. + + + + Cannot connect to MIDI input port '{}'. + Verbindung zum MIDI-Eingangsport '{}' nicht möglich. + + + + MIDIInfo + + + MIDI port disconnected: '{}' + MIDI-Port getrennt: '{}' + + + + Connecting to MIDI port: '{}' + Verbinde mit MIDI-Port: '{}' + + + + Connecting to matching MIDI port: '{}' + Verbinde mit dem passenden MIDI-Port: '{}' MIDIMessageAttr - + Channel - Channel + Kanal - + Note - Note + Note - + Velocity - Velocity + Velocity - + Control - Control + Kontroller - + Program - Program + Programm - + Value - Value + Wert - + Song - Song + Lied - + Pitch - Pitch + Tonhöhe - + Position - Position + Position MIDIMessageType - + Note ON - Note ON + Note AN - + Note OFF - Note OFF + Note AUS - + Polyphonic After-touch - Polyphonic After-touch + Polyphones Aftertouch - + Control/Mode Change - Control/Mode Change + Kontroller/Modus Änderung - + Program Change - Program Change + Programmwechsel - + Channel After-touch - Channel After-touch + Kanal-Aftertouch - + Pitch Bend Change - Pitch Bend Change + Pitch Bend-Änderung - + Song Select - Song Select + Song Select - + Song Position - Song Position + Songposition - + Start - Start + Start - + Stop - Stop + Stopp - + Continue - Continue + Fortsetzen MIDISettings - - MIDI default devices - MIDI Standardgeräte - - - + Input - Input + Eingang - + Output - Output + Ausgang + + + + MIDI devices + MIDI Geräte + + + + Misc options + Sonstige Optionen + + + + Try to connect using only device/port name + Versuche nur per Geräte-/Portname zu verbinden SettingsPageName - + MIDI settings - MIDI Einstellungen + MIDI-Einstellungen - + MIDI Settings - MIDI Settings + MIDI-Einstellungen diff --git a/lisp/i18n/ts/de_DE/network.ts b/lisp/i18n/ts/de_DE/network.ts index 1b8daf435..71a5b6414 100644 --- a/lisp/i18n/ts/de_DE/network.ts +++ b/lisp/i18n/ts/de_DE/network.ts @@ -6,7 +6,7 @@ Stop serving network API - Stop serving network API + Netzwerk-API nicht mehr bereitstellen @@ -14,7 +14,7 @@ Network API server stopped working. - Network API server stopped working. + Netzwerk-API-Server funktionierte nicht. @@ -22,50 +22,55 @@ New end-point: {} - New end-point: {} + Neuer Endpunkt: {} NetworkDiscovery - + Host discovery - Host discovery + Host-Entdeckung - + Manage hosts - Manage hosts + Hosts verwalten - + Discover hosts - Discover hosts + Hosts entdecken - + Manually add a host - Manually add a host + Einen Host manuell hinzufügen - + Remove selected host - Remove selected host + Ausgewählten Host entfernen - + Remove all host - Remove all host + Alle Hosts entfernen - + Address - Address + Adresse - + Host IP - Host IP + Host-IP + + + + Select the hosts you want to add + Wählen Sie die Hosts aus, die Sie hinzufügen möchten diff --git a/lisp/i18n/ts/de_DE/osc.ts b/lisp/i18n/ts/de_DE/osc.ts index 99d38ad39..e3aa8765f 100644 --- a/lisp/i18n/ts/de_DE/osc.ts +++ b/lisp/i18n/ts/de_DE/osc.ts @@ -6,15 +6,15 @@ OSC Settings - OSC Settings + OSC-Einstellungen CueCategory - + Integration cues - Integration cues + Integrations-Cues @@ -22,7 +22,7 @@ OSC Cue - OSC Cue + OSC Cue @@ -30,57 +30,57 @@ Type - Type + Typ Value - Value + Wert FadeTo - FadeTo + ÜberblendenZu Fade - Fade + Überblenden OSC Message - OSC Message + OSC-Nachricht Add - Add + Hinzufügen Remove - Remove + Entfernen OSC Path: - OSC Path: + OSC-Pfad: /path/to/something - /path/to/something + /pfad/zu/etwas Time (sec) - Time (sec) + Zeit (Sek) Curve - Curve + Kurve @@ -88,7 +88,7 @@ Message from {} -> path: "{}" args: {} - Message from {} -> path: "{}" args: {} + Nachricht von {} -> Pfad: "{}" Args: {} @@ -96,7 +96,7 @@ Cannot start OSC sever - Cannot start OSC sever + OSC-Sever kann nicht gestartet werden @@ -104,12 +104,12 @@ OSC server started at {} - OSC server started at {} + OSC-Server gestartet bei {} OSC server stopped - OSC server stopped + OSC-Server gestoppt @@ -117,22 +117,22 @@ OSC Settings - OSC Settings + OSC-Einstellungen Input Port: - Input Port: + Eingangsport: Output Port: - Output Port: + Ausgangsport: Hostname: - Hostname: + Hostname: @@ -140,7 +140,7 @@ OSC settings - OSC settings + OSC-Einstellungen diff --git a/lisp/i18n/ts/de_DE/presets.ts b/lisp/i18n/ts/de_DE/presets.ts index 335fccff3..645e4546e 100644 --- a/lisp/i18n/ts/de_DE/presets.ts +++ b/lisp/i18n/ts/de_DE/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Cue erstellen - + Load on selected Cues Last auf ausgewählten cues @@ -19,7 +19,7 @@ Presets - Presets + Presets @@ -57,74 +57,79 @@ Preset Auswählen - + Preset name Preset Name - + Add Hinzufügen - + Rename Umbenennen - + Edit Bearbeiten - + Remove Entfernen - + Export selected - Ausgewähltes exportieren + Ausgewählte exportieren - + Import Importieren - + Warning Warnung - + The same name is already used! Der gleiche Name wird bereits verwendet - + Cannot export correctly. Kann nicht richtig exportieren - + Cannot import correctly. Kann nicht richtig eimportieren - + Cue type Cue Typ Load on cue - Load on cue + Cue überladen Load on selected cues - Load on selected cues + Ausgewählte Cues überladen + + + + Cannot create a cue from this preset: {} + Kann aus diesem Preset keinen Cue erstellen: {} diff --git a/lisp/i18n/ts/de_DE/rename_cues.ts b/lisp/i18n/ts/de_DE/rename_cues.ts index 248662d15..f93c963c8 100644 --- a/lisp/i18n/ts/de_DE/rename_cues.ts +++ b/lisp/i18n/ts/de_DE/rename_cues.ts @@ -6,62 +6,70 @@ Rename Cues - Rename Cues + Cues umbenennen Rename cues - Rename cues + Cues umbenennen Current - Current + Aktuell Preview - Preview + Vorschau Capitalize - Capitalize + Wortanfänge großschreiben Lowercase - Lowercase + Kleinbuchstaben Uppercase - Uppercase + Großbuchstaben Remove Numbers - Remove Numbers + Nummern entfernen Add numbering - Add numbering + Nummerierung hinzufügen Reset - Reset + Zurücksetzen Type your regex here: - Type your regex here: + Geben Sie hier Ihr Regex ein: Regex help - Regex help + Regex-Hilfe + + + + RenameCuesCommand + + + Renamed {number} cues + {number} Cues umbenannt @@ -77,7 +85,7 @@ Regex error: Invalid pattern - Regex error: Invalid pattern + Regex-Fehler: Ungültiges Muster diff --git a/lisp/i18n/ts/de_DE/replay_gain.ts b/lisp/i18n/ts/de_DE/replay_gain.ts index b412ae1ac..4a1e11742 100644 --- a/lisp/i18n/ts/de_DE/replay_gain.ts +++ b/lisp/i18n/ts/de_DE/replay_gain.ts @@ -4,22 +4,22 @@ ReplayGain - + ReplayGain / Normalization ReplayGain / Normalisierung - + Calculate Berechnen - + Reset all Alle zurücksetzen - + Reset selected Ausgewählte zurücksetzen @@ -52,32 +52,32 @@ ReplayGainDebug - + Applied gain for: {} - Applied gain for: {} + Angewendetes Gain für: {} - + Discarded gain for: {} - Discarded gain for: {} + Verworfenes Gain für: {} ReplayGainInfo - + Gain processing stopped by user. - Gain processing stopped by user. + Gain-Bearbeitung durch Benutzer gestoppt. - + Started gain calculation for: {} - Started gain calculation for: {} + Gain-Berechnung gestartet für: {} - + Gain calculated for: {} - Gain calculated for: {} + Gain berechnet für: {} diff --git a/lisp/i18n/ts/de_DE/synchronizer.ts b/lisp/i18n/ts/de_DE/synchronizer.ts index 422beddc4..05e29137d 100644 --- a/lisp/i18n/ts/de_DE/synchronizer.ts +++ b/lisp/i18n/ts/de_DE/synchronizer.ts @@ -20,8 +20,8 @@ - Your IP is: - Ihre IP ist: + Your IP is: {} + Ihre IP ist: {} diff --git a/lisp/i18n/ts/de_DE/timecode.ts b/lisp/i18n/ts/de_DE/timecode.ts index 38356223b..6cd7d89cb 100644 --- a/lisp/i18n/ts/de_DE/timecode.ts +++ b/lisp/i18n/ts/de_DE/timecode.ts @@ -11,7 +11,7 @@ Timecode - Timecode + Timecode @@ -19,7 +19,7 @@ Cannot load timecode protocol: "{}" - Cannot load timecode protocol: "{}" + Timecode Protokoll kann nicht geladen werden: "{}" @@ -27,7 +27,7 @@ Cannot send timecode. - Cannot send timecode. + Zeitcode kann nicht gesendet werden. @@ -35,7 +35,7 @@ Timecode Format: - Timecode Format: + Timecode-Format: @@ -45,22 +45,22 @@ Track number - Track Nummer + Spurnummer Enable Timecode - Enable Timecode + Timecode aktivieren Timecode Settings - Timecode Settings + Timecode-Einstellungen Timecode Protocol: - Timecode Protocol: + Timecode Protokoll: diff --git a/lisp/i18n/ts/de_DE/triggers.ts b/lisp/i18n/ts/de_DE/triggers.ts index 4d99f33f6..939c56c6e 100644 --- a/lisp/i18n/ts/de_DE/triggers.ts +++ b/lisp/i18n/ts/de_DE/triggers.ts @@ -29,7 +29,7 @@ Triggers - Trigger + Auslöser @@ -47,12 +47,12 @@ Trigger - Trigger + Auslöser Cue - Cue + Cue diff --git a/lisp/i18n/ts/es_ES/action_cues.ts b/lisp/i18n/ts/es_ES/action_cues.ts index bf056e5a1..86ddeb2dc 100644 --- a/lisp/i18n/ts/es_ES/action_cues.ts +++ b/lisp/i18n/ts/es_ES/action_cues.ts @@ -55,7 +55,7 @@ CueCategory - + Action cues Action cues diff --git a/lisp/i18n/ts/es_ES/cache_manager.ts b/lisp/i18n/ts/es_ES/cache_manager.ts new file mode 100644 index 000000000..68cc7f48f --- /dev/null +++ b/lisp/i18n/ts/es_ES/cache_manager.ts @@ -0,0 +1,35 @@ + + + + + CacheManager + + + Cache size warning + Cache size warning + + + + Warning threshold in MB (0 = disabled) + Warning threshold in MB (0 = disabled) + + + + Cache cleanup + Cache cleanup + + + + Delete the cache content + Delete the cache content + + + + SettingsPageName + + + Cache Manager + Cache Manager + + + diff --git a/lisp/i18n/ts/es_ES/cart_layout.ts b/lisp/i18n/ts/es_ES/cart_layout.ts index 6eac2ec9d..65d11388c 100644 --- a/lisp/i18n/ts/es_ES/cart_layout.ts +++ b/lisp/i18n/ts/es_ES/cart_layout.ts @@ -4,37 +4,32 @@ CartLayout - - Default behaviors - Comportamiento por defecto - - - + Countdown mode Modo de cuenta regresiva - + Show seek-bars Mostrar barra de búsqueda - + Show dB-meters Mostrar medidores de dB - + Show accurate time Mostrar tiempo preciso - + Show volume Mostrar volumen - + Grid size Tamaño de cuadrícula @@ -79,35 +74,40 @@ Número de páginas: - + Page {number} Página {number} - + Warning Advertencia - + Every cue in the page will be lost. Todos los cues en la página se perderán. - + Are you sure to continue? ¿Está seguro de continuar? - + Number of columns: Number of columns: - + Number of rows: Number of rows: + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + LayoutDescription diff --git a/lisp/i18n/ts/es_ES/controller.ts b/lisp/i18n/ts/es_ES/controller.ts index 22c329409..c6c07f4a1 100644 --- a/lisp/i18n/ts/es_ES/controller.ts +++ b/lisp/i18n/ts/es_ES/controller.ts @@ -30,113 +30,136 @@ ControllerMidiSettings - + MIDI MIDI - + Type Tipo - - Channel - Canal + + Action + Acción - - Note - Nota + + Capture + Captura - - Action - Acción + + Listening MIDI messages ... + Escuchando mensajes MIDI ... - - Filter "note on" - Filtrar "Nota ON" + + -- All Messages -- + -- All Messages -- - - Filter "note off" - Filtrar "Nota OFF" + + Capture filter + Capture filter - - Capture - Captura + + Data 1 + Data 1 - - Listening MIDI messages ... - Escuchando mensajes MIDI ... + + Data 2 + Data 2 + + + + Data 3 + Data 3 ControllerOscSettings - + OSC Message Mensaje OSC - + OSC OSC - + Path Ruta - + Types Tipos - + Arguments Argumentos - - Actions - Acciones - - - + OSC Capture Captura OSC - + Add Añadir - + Remove Eliminar - + Capture Capturar + + + Waiting for messages: + Waiting for messages: + + + + /path/to/method + /path/to/method + + + + Action + Action + + + + ControllerOscSettingsWarning + + + Warning + Warning + ControllerSettings - + Add Añadir - + Remove Eliminar @@ -197,12 +220,12 @@ Osc Cue - + Type Tipo - + Argument Argumento @@ -210,12 +233,12 @@ OscCue - + Add Añadir - + Remove Eliminar @@ -228,7 +251,7 @@ Control de Cue - + MIDI Controls Controles MIDI @@ -238,7 +261,7 @@ Atajos de teclado - + OSC Controls Controles OSC diff --git a/lisp/i18n/ts/es_ES/gst_backend.ts b/lisp/i18n/ts/es_ES/gst_backend.ts index 38a20348d..56e04231d 100644 --- a/lisp/i18n/ts/es_ES/gst_backend.ts +++ b/lisp/i18n/ts/es_ES/gst_backend.ts @@ -119,7 +119,7 @@ Cue de audio (desde archivo) - + Select media files Seleccionar archivos de medios @@ -127,7 +127,7 @@ GstMediaError - + Cannot create pipeline element: "{}" Cannot create pipeline element: "{}" @@ -143,7 +143,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" Invalid pipeline element: "{}" @@ -151,7 +151,7 @@ GstPipelineEdit - + Edit Pipeline Editar Pipeline @@ -159,9 +159,14 @@ GstSettings - - Pipeline - Pipeline + + Default pipeline + Default pipeline + + + + Applied only to new cues. + Applied only to new cues. @@ -255,7 +260,7 @@ Tono - + URI Input Entrada URI diff --git a/lisp/i18n/ts/es_ES/lisp.ts b/lisp/i18n/ts/es_ES/lisp.ts index a85327549..944c4fdba 100644 --- a/lisp/i18n/ts/es_ES/lisp.ts +++ b/lisp/i18n/ts/es_ES/lisp.ts @@ -35,52 +35,36 @@ AboutDialog - + Web site Sitio Web - + Source code Código fuente - + Info Información - + License Licencia - + Contributors Contibuidores - + Discussion Discusión - - AlsaSinkSettings - - - ALSA device - ALSA device - - - - ApiServerError - - - Network API server stopped working. - Network API server stopped working. - - AppConfiguration @@ -92,277 +76,52 @@ AppGeneralSettings - + Default layout Diseño por defecto - - Enable startup layout selector - Activar a selector de diseño de inicio - - - + Application themes Temas de la Aplicación - + UI theme: UI theme: - + Icons theme: Icons theme: - - Application language (require restart) - Application language (require restart) - - - + Language: Language: - - - ApplicationError - - - Startup error - Startup error - - - - AudioDynamicSettings - - - Compressor - Compressor - - - - Expander - Expander - - - - Soft Knee - Soft Knee - - - - Hard Knee - Hard Knee - - - - Compressor/Expander - Compressor/Expander - - - - Type - Type - - - - Curve Shape - Curve Shape - - - - Ratio - Ratio - - - - Threshold (dB) - Threshold (dB) - - - - AudioPanSettings - - - Audio Pan - Audio Pan - - - - Center - Center - - - - Left - Left - - - - Right - Right - - - - CartLayout - - - Default behaviors - Default behaviors - - - - Countdown mode - Countdown mode - - - - Show seek-bars - Show seek-bars - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Show accurate time - - - - Show volume - Show volume - - - - Grid size - Grid size - - - - Number of columns: - Number of columns: - - - - Number of rows: - Number of rows: - - - - Play - Play - - - - Pause - Pause - - - - Stop - Stop - - - - Reset volume - Reset volume - - - - Add page - Add page - - - - Add pages - Add pages - - - - Remove current page - Remove current page - - - - Number of Pages: - Number of Pages: - - - - Page {number} - Page {number} - - - - Warning - Warning - - - Every cue in the page will be lost. - Every cue in the page will be lost. + + Show layout selection at startup + Show layout selection at startup - - Are you sure to continue? - Are you sure to continue? - - - - CollectionCue - - - Add - Add - - - - Remove - Remove - - - - Cue - Cue + + Use layout at startup: + Use layout at startup: - - Action - Action + + Application language (require a restart) + Application language (require a restart) - CommandCue - - - Command - Command - - - - Command to execute, as in a shell - Command to execute, as in a shell - - - - Discard command output - Discard command output - - - - Ignore command errors - Ignore command errors - + ApplicationError - - Kill instead of terminate - Kill instead of terminate + + Startup error + Startup error @@ -646,17 +405,7 @@ CueCategory - - Action cues - Action cues - - - - Integration cues - Integration cues - - - + Misc cues Misc cues @@ -677,7 +426,7 @@ CueName - + Media Cue Cue de Media @@ -798,14 +547,14 @@ Acción predeterminada para detener el Cue - - Interrupt fade - Interrumpir Fundido + + Interrupt action fade + Interrupt action fade - - Fade actions - Acciones de Fundido + + Fade actions default value + Fade actions default value @@ -883,12 +632,12 @@ FadeEdit - + Duration (sec): Duration (sec): - + Curve: Curve: @@ -907,1588 +656,396 @@ - GlobalAction - - - Go - Go - - - - Reset - Reset - - - - Stop all cues - Stop all cues - - - - Pause all cues - Pause all cues - - - - Resume all cues - Resume all cues - - - - Interrupt all cues - Interrupt all cues - + HotKeyEdit - - Fade-out all cues - Fade-out all cues + + Press shortcut + Press shortcut + + + LayoutSelect - - Fade-in all cues - Fade-in all cues + + Layout selection + Selección de Layout - - Move standby forward - Move standby forward + + Select layout + Seleccionar Layout - - Move standby back - Move standby back + + Open file + Abrir archivo - GstBackend + ListLayout - - Audio cue (from file) - Audio cue (from file) + + Layout actions + Layout actions - - Select media files - Select media files + + Fade out when stopping all cues + Fade out when stopping all cues - - - GstMediaError - - Cannot create pipeline element: "{}" - Cannot create pipeline element: "{}" + + Fade out when interrupting all cues + Fade out when interrupting all cues - - - GstMediaSettings - - Change Pipeline - Change Pipeline + + Fade out when pausing all cues + Fade out when pausing all cues - - - GstMediaWarning - - Invalid pipeline element: "{}" - Invalid pipeline element: "{}" + + Fade in when resuming all cues + Fade in when resuming all cues - GstPipelineEdit + LogStatusIcon - - Edit Pipeline - Edit Pipeline + + Errors/Warnings + Errors/Warnings - GstSettings + Logging - - Pipeline - Pipeline + + Debug + Depurar - - - HotKeyEdit - - Press shortcut - Press shortcut + + Warning + Advertencia - - - IndexActionCue - - No suggestion - No suggestion + + Error + Error - - Index - Index + + Dismiss all + Descartar todo - - Use a relative index - Use a relative index + + Show details + Mostrar detalles - - Target index - Target index + + Linux Show Player - Log Viewer + Linux Show Player - Visor de registro - - Action - Action + + Info + Información - - Suggested cue name - Suggested cue name + + Critical + Crítico - - - JackSinkSettings - - Connections - Connections + + Time + Tiempo - - Edit connections - Edit connections + + Milliseconds + Milisegundos - - Output ports - Output ports + + Logger name + Número de Logger - - Input ports - Input ports + + Level + Nivel - - Connect - Connect + + Message + Mensaje - - Disconnect - Disconnect + + Function + Función - - - LayoutDescription - - Organize the cues in a list - Organize the cues in a list + + Path name + Nombre de la ruta - - Organize cues in grid like pages - Organize cues in grid like pages + + File name + Nombre del archivo - - - LayoutDetails - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Space or Double-Click to edit a cue + + Line no. + Línea no. - - To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL + + Module + Módulo - - To move cues drag them - To move cues drag them + + Process ID + ID del proceso - - Click a cue to run it - Click a cue to run it + + Process name + Nombre del proceso - - SHIFT + Click to edit a cue - SHIFT + Click to edit a cue + + Thread ID + ID del hilo - - CTRL + Click to select a cue - CTRL + Click to select a cue - - - - To move cues drag them while pressing SHIFT - To move cues drag them while pressing SHIFT - - - - LayoutName - - - List Layout - List Layout - - - - Cart Layout - Cart Layout - - - - LayoutSelect - - - Layout selection - Selección de Layout - - - - Select layout - Seleccionar Layout - - - - Open file - Abrir archivo - - - - ListLayout - - - Default behaviors - Default behaviors - - - - Show playing cues - Show playing cues - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Show accurate time - - - - Show seek-bars - Show seek-bars - - - - Auto-select next cue - Auto-select next cue - - - - Enable selection mode - Enable selection mode - - - - GO Key: - GO Key: - - - - GO Action: - GO Action: - - - - GO minimum interval (ms): - GO minimum interval (ms): - - - - Use fade (buttons) - Use fade (buttons) - - - - Stop Cue - Stop Cue - - - - Pause Cue - Pause Cue - - - - Resume Cue - Resume Cue - - - - Interrupt Cue - Interrupt Cue - - - - Edit cue - Edit cue - - - - Edit selected - Edit selected - - - - Clone cue - Clone cue - - - - Clone selected - Clone selected - - - - Remove cue - Remove cue - - - - Remove selected - Remove selected - - - - Selection mode - Selection mode - - - - Pause all - Pause all - - - - Stop all - Stop all - - - - Interrupt all - Interrupt all - - - - Resume all - Resume all - - - - Fade-Out all - Fade-Out all - - - - Fade-In all - Fade-In all - - - - Edit selected cues - Edit selected cues - - - - Remove selected cues - Remove selected cues - - - - Use fade - Use fade - - - - Copy of {} - Copy of {} - - - - ListLayoutHeader - - - Cue - Cue - - - - Pre wait - Pre wait - - - - Action - Action - - - - Post wait - Post wait - - - - ListLayoutInfoPanel - - - Cue name - Cue name - - - - Cue description - Cue description - - - - LogStatusIcon - - - Errors/Warnings - Errors/Warnings - - - - Logging - - - Debug - Depurar - - - - Warning - Advertencia - - - - Error - Error - - - - Dismiss all - Descartar todo - - - - Show details - Mostrar detalles - - - - Linux Show Player - Log Viewer - Linux Show Player - Visor de registro - - - - Info - Información - - - - Critical - Crítico - - - - Time - Tiempo - - - - Milliseconds - Milisegundos - - - - Logger name - Número de Logger - - - - Level - Nivel - - - - Message - Mensaje - - - - Function - Función - - - - Path name - Nombre de la ruta - - - - File name - Nombre del archivo - - - - Line no. - Línea no. - - - - Module - Módulo - - - - Process ID - ID del proceso - - - - Process name - Nombre del proceso - - - - Thread ID - ID del hilo - - - - Thread name - Nombre del hilo - - - - Exception info - Exception info - - - - MIDICue - - - MIDI Message - MIDI Message - - - - Message type - Message type - - - - MIDIMessageAttr - - - Channel - Channel - - - - Note - Note - - - - Velocity - Velocity - - - - Control - Control - - - - Program - Program - - - - Value - Value - - - - Song - Song - - - - Pitch - Pitch - - - - Position - Position - - - - MIDIMessageType - - - Note ON - Note ON - - - - Note OFF - Note OFF - - - - Polyphonic After-touch - Polyphonic After-touch - - - - Control/Mode Change - Control/Mode Change - - - - Program Change - Program Change - - - - Channel After-touch - Channel After-touch - - - - Pitch Bend Change - Pitch Bend Change - - - - Song Select - Song Select - - - - Song Position - Song Position - - - - Start - Start - - - - Stop - Stop - - - - Continue - Continue - - - - MIDISettings - - - MIDI default devices - MIDI default devices - - - - Input - Input - - - - Output - Output - - - - MainWindow - - - &File - &Archivo - - - - New session - Nueva sesión - - - - Open - Abrir - - - - Save session - Guardar sesión - - - - Preferences - Preferencias - - - - Save as - Guardar como - - - - Full Screen - Pantalla completa - - - - Exit - Salir - - - - &Edit - &Editar - - - - Undo - Deshacer - - - - Redo - Rehacer - - - - Select all - Seleccionar todo - - - - Select all media cues - Seleccionar todos los Media cues - - - - Deselect all - Deseleccionar todo - - - - CTRL+SHIFT+A - CTRL + SHIFT + A - - - - Invert selection - Invertir selección - - - - CTRL+I - CTRL + I - - - - Edit selected - Editar seleccionado - - - - CTRL+SHIFT+E - CTRL + SHIFT + E - - - - &Layout - &Layout - - - - &Tools - &Herramientas - - - - Edit selection - Editar selección - - - - &About - &Acerca - - - - About - Acerca - - - - About Qt - Acerca de Qt - - - - Close session - Cerrar sesión - - - - Do you want to save them now? - Do you want to save them now? - - - - MainWindowDebug - - - Registered cue menu: "{}" - Registered cue menu: "{}" - - - - MainWindowError - - - Cannot create cue {} - Cannot create cue {} - - - - MediaCueSettings - - - Start time - Tiempo de inicio - - - - Stop position of the media - Posición de detención del media - - - - Stop time - Tiempo de detención - - - - Start position of the media - Posición de inicio del media - - - - Loop - Bucle - - - - MediaElementName - - - Pitch - Pitch - - - - Volume - Volume - - - - Audio Pan - Audio Pan - - - - Compressor/Expander - Compressor/Expander - - - - JACK Out - JACK Out - - - - URI Input - URI Input - - - - 10 Bands Equalizer - 10 Bands Equalizer - - - - ALSA Out - ALSA Out - - - - dB Meter - dB Meter - - - - Preset Input - Preset Input - - - - PulseAudio Out - PulseAudio Out - - - - Custom Element - Custom Element - - - - Speed - Speed - - - - System Out - System Out - - - - System Input - System Input - - - - MediaInfo - - - Media Info - Media Info - - - - Warning - Warning - - - - No info to display - No info to display - - - - Info - Info - - - - Value - Value - - - - NetworkApiDebug - - - New end-point: {} - New end-point: {} - - - - NetworkDiscovery - - - Host discovery - Host discovery - - - - Manage hosts - Manage hosts - - - - Discover hosts - Discover hosts - - - - Manually add a host - Manually add a host - - - - Remove selected host - Remove selected host - - - - Remove all host - Remove all host - - - - Address - Address - - - - Host IP - Host IP - - - - Osc Cue - - - Type - Type - - - - Argument - Argument - - - - OscCue - - - Add - Add - - - - Remove - Remove - - - - Type - Type - - - - Value - Value - - - - FadeTo - FadeTo - - - - Fade - Fade - - - - OSC Message - OSC Message - - - - OSC Path: - OSC Path: - - - - /path/to/something - /path/to/something - - - - Time (sec) - Time (sec) - - - - Curve - Curve - - - - OscServerDebug - - - Message from {} -> path: "{}" args: {} - Message from {} -> path: "{}" args: {} - - - - OscServerError - - - Cannot start OSC sever - Cannot start OSC sever - - - - OscServerInfo - - - OSC server started at {} - OSC server started at {} - - - - OSC server stopped - OSC server stopped - - - - OscSettings - - - OSC Settings - OSC Settings - - - - Input Port: - Input Port: + + Thread name + Nombre del hilo - - Output Port: - Output Port: + + Exception info + Exception info - - Hostname: - Hostname: + + Showing {} of {} records + Showing {} of {} records - PitchSettings + MIDICue - - Pitch - Pitch + + &File + &Archivo - - {0:+} semitones - {0:+} semitones + + New session + Nueva sesión - PluginsError + MIDIMessageAttr - - Failed to load "{}" - Failed to load "{}" + + Open + Abrir - - Failed to terminate plugin: "{}" - Failed to terminate plugin: "{}" + + Save session + Guardar sesión - - the requested plugin is not loaded: {} - the requested plugin is not loaded: {} + + Preferences + Preferencias - - - PluginsInfo - - Plugin loaded: "{}" - Plugin loaded: "{}" + + Save as + Guardar como - - Plugin terminated: "{}" - Plugin terminated: "{}" + + Full Screen + Pantalla completa - - - PluginsWarning - - Cannot satisfy dependencies for: {} - Cannot satisfy dependencies for: {} + + Exit + Salir - - - Preset - - Create Cue - Create Cue + + &Edit + &Editar - - Load on selected Cues - Load on selected Cues + + Undo + Deshacer - - - PresetSrcSettings - - Presets - Presets + + Redo + Rehacer - Presets - - - Cannot scan presets - Cannot scan presets - - - - Error while deleting preset "{}" - Error while deleting preset "{}" - - - - Cannot load preset "{}" - Cannot load preset "{}" - - - - Cannot save preset "{}" - Cannot save preset "{}" - - - - Cannot rename preset "{}" - Cannot rename preset "{}" - - - - Select Preset - Select Preset - - - - Presets - Presets - - - - Preset name - Preset name - - - - Add - Add - - - - Rename - Rename - - - - Edit - Edit - - - - Remove - Remove - - - - Export selected - Export selected - - - - Import - Import - - - - Warning - Warning - - - - The same name is already used! - The same name is already used! - - - - Cannot export correctly. - Cannot export correctly. - - - - Cannot import correctly. - Cannot import correctly. - - - - Cue type - Cue type - - - - Load on cue - Load on cue - + MIDIMessageType - - Load on selected cues - Load on selected cues + + Select all + Seleccionar todo - - Save as preset - Save as preset + + Select all media cues + Seleccionar todos los Media cues - - Cannot create a cue from this preset: {} - Cannot create a cue from this preset: {} + + Deselect all + Deseleccionar todo - - - QColorButton - - Right click to reset - Click derecho para reiniciar + + CTRL+SHIFT+A + CTRL + SHIFT + A - - - RenameCues - - Rename Cues - Rename Cues + + Invert selection + Invertir selección - - Rename cues - Rename cues + + CTRL+I + CTRL + I - - Current - Current + + Edit selected + Editar seleccionado - - Preview - Preview + + CTRL+SHIFT+E + CTRL + SHIFT + E - - Capitalize - Capitalize + + &Layout + &Layout - - Lowercase - Lowercase + + &Tools + &Herramientas - - Uppercase - Uppercase + + Edit selection + Editar selección - - Remove Numbers - Remove Numbers + + &About + &Acerca - - Add numbering - Add numbering + + About + Acerca - - Reset - Reset + + About Qt + Acerca de Qt - - Type your regex here: - Type your regex here: + + Close session + Cerrar sesión - - Regex help - Regex help + + Do you want to save them now? + Do you want to save them now? - RenameCuesCommand + MainWindowDebug - - Renamed {number} cues - Renamed {number} cues + + Registered cue menu: "{}" + Registered cue menu: "{}" - RenameUiDebug + MainWindowError - - Regex error: Invalid pattern - Regex error: Invalid pattern + + Cannot create cue {} + Cannot create cue {} - ReplayGain - - - ReplayGain / Normalization - ReplayGain / Normalization - - - - Threads number - Threads number - - - - Apply only to selected media - Apply only to selected media - - - - ReplayGain to (dB SPL) - ReplayGain to (dB SPL) - - - - Normalize to (dB) - Normalize to (dB) - - - - Processing files ... - Processing files ... - + MediaCueSettings - - Calculate - Calculate + + Start time + Tiempo de inicio - - Reset all - Reset all + + Stop position of the media + Posición de detención del media - - Reset selected - Reset selected + + Stop time + Tiempo de detención - - - ReplayGainDebug - - Applied gain for: {} - Applied gain for: {} + + Start position of the media + Posición de inicio del media - - Discarded gain for: {} - Discarded gain for: {} + + Loop + Bucle - ReplayGainInfo - - - Gain processing stopped by user. - Gain processing stopped by user. - - - - Started gain calculation for: {} - Started gain calculation for: {} - + QColorButton - - Gain calculated for: {} - Gain calculated for: {} + + Right click to reset + Click derecho para reiniciar - SeekCue - - - Cue - Cue - - - - Click to select - Click to select - - - - Not selected - Not selected - - - - Seek - Seek - + PresetSrcSettings - - Time to reach - Time to reach + + Presets + Presets - SettingsPageName - - - Appearance - Apariencia - + Presets General diff --git a/lisp/i18n/ts/es_ES/list_layout.ts b/lisp/i18n/ts/es_ES/list_layout.ts index 61678c6b4..86361bf52 100644 --- a/lisp/i18n/ts/es_ES/list_layout.ts +++ b/lisp/i18n/ts/es_ES/list_layout.ts @@ -38,77 +38,67 @@ ListLayout - - Default behaviors - Comportamientos por defecto - - - - Show playing cues - Mostrar los cues en ejecución - - - + Show dB-meters Mostrar medidores de dB - + Show accurate time Mostrar tiempo preciso - + Show seek-bars Mostrar barras de búsqueda - + Auto-select next cue Seleccionar automáticamente el siguiente cue - + Enable selection mode Habilitar modo de selección - + Use fade (buttons) Usar fundido (botones) - + Stop Cue Detener Cue - + Pause Cue Pausar Cue - + Resume Cue Reanudar Cue - + Interrupt Cue Interrumpir Cue - + Edit cue Editar cue - + Remove cue Eliminar cue - + Selection mode Modo de selección @@ -143,65 +133,105 @@ Fundido de Entrada para Todo - + Edit selected Edit selected - + Clone cue Clone cue - + Clone selected Clone selected - + Remove selected Remove selected - + GO Key: GO Key: - + GO Action: GO Action: - + GO minimum interval (ms): GO minimum interval (ms): - + Copy of {} Copy of {} + + + Show index column + Show index column + + + + Show resize handles + Show resize handles + + + + Restore default size + Restore default size + + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + + + + Behaviors + Behaviors + + + + Disable GO Key While Playing + Disable GO Key While Playing + + + + Use waveform seek-bars + Use waveform seek-bars + + + + GO Key Disabled While Playing + GO Key Disabled While Playing + ListLayoutHeader - + Cue Cue - + Pre wait Pre Espera - + Action Acción - + Post wait Post Espera @@ -222,7 +252,7 @@ SettingsPageName - + List Layout List Layout diff --git a/lisp/i18n/ts/es_ES/media_info.ts b/lisp/i18n/ts/es_ES/media_info.ts index 7d8cf6f25..34c8c4c10 100644 --- a/lisp/i18n/ts/es_ES/media_info.ts +++ b/lisp/i18n/ts/es_ES/media_info.ts @@ -4,22 +4,17 @@ MediaInfo - + Media Info Información del Media - - No info to display - Ninguna información para mostrar - - - + Info Información - + Value Valor @@ -28,5 +23,10 @@ Warning Advertencia + + + Cannot get any information. + Cannot get any information. + diff --git a/lisp/i18n/ts/es_ES/midi.ts b/lisp/i18n/ts/es_ES/midi.ts index 904aaaa00..68978a089 100644 --- a/lisp/i18n/ts/es_ES/midi.ts +++ b/lisp/i18n/ts/es_ES/midi.ts @@ -4,7 +4,7 @@ CueCategory - + Integration cues Integration cues @@ -12,7 +12,7 @@ CueName - + MIDI Cue MIDI Cue @@ -30,50 +30,81 @@ Message type + + MIDIError + + + Cannot connect to MIDI output port '{}'. + Cannot connect to MIDI output port '{}'. + + + + Cannot connect to MIDI input port '{}'. + Cannot connect to MIDI input port '{}'. + + + + MIDIInfo + + + MIDI port disconnected: '{}' + MIDI port disconnected: '{}' + + + + Connecting to MIDI port: '{}' + Connecting to MIDI port: '{}' + + + + Connecting to matching MIDI port: '{}' + Connecting to matching MIDI port: '{}' + + MIDIMessageAttr - + Channel Channel - + Note Note - + Velocity Velocity - + Control Control - + Program Program - + Value Value - + Song Song - + Pitch Pitch - + Position Position @@ -81,62 +112,62 @@ MIDIMessageType - + Note ON Note ON - + Note OFF Note OFF - + Polyphonic After-touch Polyphonic After-touch - + Control/Mode Change Control/Mode Change - + Program Change Program Change - + Channel After-touch Channel After-touch - + Pitch Bend Change Pitch Bend Change - + Song Select Song Select - + Song Position Song Position - + Start Start - + Stop Stop - + Continue Continue @@ -144,30 +175,40 @@ MIDISettings - - MIDI default devices - Dispositivos MIDI por defecto - - - + Input Entrada - + Output Salida + + + MIDI devices + MIDI devices + + + + Misc options + Misc options + + + + Try to connect using only device/port name + Try to connect using only device/port name + SettingsPageName - + MIDI settings Ajustes MIDI - + MIDI Settings MIDI Settings diff --git a/lisp/i18n/ts/es_ES/network.ts b/lisp/i18n/ts/es_ES/network.ts index 6430d1cfa..52db93d7b 100644 --- a/lisp/i18n/ts/es_ES/network.ts +++ b/lisp/i18n/ts/es_ES/network.ts @@ -28,44 +28,49 @@ NetworkDiscovery - + Host discovery Descubrimiento de host - + Manage hosts Administrar hosts - + Discover hosts Descubrir hosts - + Manually add a host Añadir un host manualmente - + Remove selected host Remover el host seleccionado - + Remove all host Eliminar todos los hosts - + Address Dirección - + Host IP IP del host + + + Select the hosts you want to add + Select the hosts you want to add + diff --git a/lisp/i18n/ts/es_ES/osc.ts b/lisp/i18n/ts/es_ES/osc.ts index 308aef712..9a0b50a5a 100644 --- a/lisp/i18n/ts/es_ES/osc.ts +++ b/lisp/i18n/ts/es_ES/osc.ts @@ -12,7 +12,7 @@ CueCategory - + Integration cues Integration cues diff --git a/lisp/i18n/ts/es_ES/presets.ts b/lisp/i18n/ts/es_ES/presets.ts index 505545e09..ddaa29a63 100644 --- a/lisp/i18n/ts/es_ES/presets.ts +++ b/lisp/i18n/ts/es_ES/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Crear Cue - + Load on selected Cues Abrir en Cues seleccionadas @@ -57,62 +57,62 @@ Seleccionar Preset - + Preset name Nombre del Preset - + Add Añadir - + Rename Cambiar nombre - + Edit Editar - + Remove Eliminar - + Export selected Exportar seleccionados - + Import Importar - + Warning Advertencia - + The same name is already used! ¡El mismo nombre ya está siendo usado! - + Cannot export correctly. No se puede exportar correctamente. - + Cannot import correctly. No se puede importar correctamente. - + Cue type Tipo de Cue @@ -127,7 +127,7 @@ Cargar en cues seleccionados - + Cannot create a cue from this preset: {} Cannot create a cue from this preset: {} diff --git a/lisp/i18n/ts/es_ES/replay_gain.ts b/lisp/i18n/ts/es_ES/replay_gain.ts index e67739714..08ac947b3 100644 --- a/lisp/i18n/ts/es_ES/replay_gain.ts +++ b/lisp/i18n/ts/es_ES/replay_gain.ts @@ -4,22 +4,22 @@ ReplayGain - + ReplayGain / Normalization ReplayGain / Normalización - + Calculate Calcular - + Reset all Reestablecer todo - + Reset selected Reestablecer seleccionados @@ -52,12 +52,12 @@ ReplayGainDebug - + Applied gain for: {} Applied gain for: {} - + Discarded gain for: {} Discarded gain for: {} @@ -65,17 +65,17 @@ ReplayGainInfo - + Gain processing stopped by user. Gain processing stopped by user. - + Started gain calculation for: {} Started gain calculation for: {} - + Gain calculated for: {} Gain calculated for: {} diff --git a/lisp/i18n/ts/es_ES/synchronizer.ts b/lisp/i18n/ts/es_ES/synchronizer.ts index cb50fdd8a..871ee7f67 100644 --- a/lisp/i18n/ts/es_ES/synchronizer.ts +++ b/lisp/i18n/ts/es_ES/synchronizer.ts @@ -20,8 +20,8 @@ - Your IP is: - Su IP es: + Your IP is: {} + Your IP is: {} diff --git a/lisp/i18n/ts/fr_FR/action_cues.ts b/lisp/i18n/ts/fr_FR/action_cues.ts index 23596979f..548977db6 100644 --- a/lisp/i18n/ts/fr_FR/action_cues.ts +++ b/lisp/i18n/ts/fr_FR/action_cues.ts @@ -16,7 +16,7 @@ Cue - Cue + Go @@ -55,9 +55,9 @@ CueCategory - + Action cues - Action cues + Go d'action diff --git a/lisp/i18n/ts/fr_FR/cache_manager.ts b/lisp/i18n/ts/fr_FR/cache_manager.ts new file mode 100644 index 000000000..081c7f20d --- /dev/null +++ b/lisp/i18n/ts/fr_FR/cache_manager.ts @@ -0,0 +1,35 @@ + + + + + CacheManager + + + Cache size warning + Cache size warning + + + + Warning threshold in MB (0 = disabled) + Warning threshold in MB (0 = disabled) + + + + Cache cleanup + Cache cleanup + + + + Delete the cache content + Delete the cache content + + + + SettingsPageName + + + Cache Manager + Cache Manager + + + diff --git a/lisp/i18n/ts/fr_FR/cart_layout.ts b/lisp/i18n/ts/fr_FR/cart_layout.ts index 860f3822b..0dc93562d 100644 --- a/lisp/i18n/ts/fr_FR/cart_layout.ts +++ b/lisp/i18n/ts/fr_FR/cart_layout.ts @@ -4,37 +4,32 @@ CartLayout - - Default behaviors - Comportements par défaut - - - + Countdown mode Compte à rebours - + Show seek-bars Afficher barre de progression - + Show dB-meters Afficher dB mètres - + Show accurate time Afficher temps exact - + Show volume Afficher volume - + Grid size Taille de la grille @@ -79,35 +74,40 @@ Nombre de pages: - + Page {number} Page {number} - + Warning Attention - + Every cue in the page will be lost. Toutes les cues dans la page seront perdues. - + Are you sure to continue? Êtes-vous sûr de vouloir continuer? - + Number of columns: Nombre de colonnes: - + Number of rows: Nombre de lignes: + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + LayoutDescription @@ -150,7 +150,7 @@ Cart Layout - Cart Layout + Disposition en carte @@ -181,7 +181,7 @@ Cart Layout - Cart Layout + Disposition en carte diff --git a/lisp/i18n/ts/fr_FR/controller.ts b/lisp/i18n/ts/fr_FR/controller.ts index ae7e3c93a..1ba16758f 100644 --- a/lisp/i18n/ts/fr_FR/controller.ts +++ b/lisp/i18n/ts/fr_FR/controller.ts @@ -6,7 +6,7 @@ Cannot load controller protocol: "{}" - Cannot load controller protocol: "{}" + Impossible de charger le protocole du contrôleur : "{}" @@ -24,119 +24,142 @@ Shortcut - Shortcut + Raccourci ControllerMidiSettings - + MIDI MIDI - + Type Type - - Channel - Canal + + Action + Action - - Note - Note + + Capture + Capture - - Action - Action + + Listening MIDI messages ... + Écoute des messages MIDI ... - - Filter "note on" - Filtrer les "note on" + + -- All Messages -- + -- All Messages -- - - Filter "note off" - Filtrer les "note off" + + Capture filter + Capture filter - - Capture - Capture + + Data 1 + Data 1 - - Listening MIDI messages ... - Écoute des messages MIDI ... + + Data 2 + Data 2 + + + + Data 3 + Data 3 ControllerOscSettings - + OSC Message Message OSC - + OSC OSC - + Path - Path + Chemin d'accès - + Types Types - + Arguments Arguments - - Actions - Actions - - - + OSC Capture - OSC Capture + Capture OSC - + Add Ajouter - + Remove Retirer - + Capture - Capture + Capture + + + + Waiting for messages: + Waiting for messages: + + + + /path/to/method + /path/to/method + + + + Action + Action + + + + ControllerOscSettingsWarning + + + Warning + Warning ControllerSettings - + Add Ajouter - + Remove Retirer @@ -156,7 +179,7 @@ Stop all cues - Arrêter toutes les cues + Arrêter toutes les go @@ -176,33 +199,33 @@ Fade-out all cues - Fade-out all cues + Arrêter en fondu tous les go Fade-in all cues - Fade-in all cues + Démarrer en fondu tous les go Move standby forward - Move standby forward + Avancer en mode veille Move standby back - Move standby back + Reculer en mode veille Osc Cue - + Type Type - + Argument Argument @@ -210,12 +233,12 @@ OscCue - + Add Ajouter - + Remove Retirer @@ -225,10 +248,10 @@ Cue Control - Contrôle de la cue + Contrôle du go - + MIDI Controls Contrôles MIDI @@ -238,14 +261,14 @@ Raccourcis-clavier - + OSC Controls - OSC Controls + Contrôles OSC Layout Controls - Layout Controls + Contrôles d'agencement diff --git a/lisp/i18n/ts/fr_FR/gst_backend.ts b/lisp/i18n/ts/fr_FR/gst_backend.ts index 5c2027422..7f28ffe9b 100644 --- a/lisp/i18n/ts/fr_FR/gst_backend.ts +++ b/lisp/i18n/ts/fr_FR/gst_backend.ts @@ -39,7 +39,7 @@ Type - Type + Type @@ -49,7 +49,7 @@ Ratio - Ratio + Taux @@ -116,20 +116,20 @@ Audio cue (from file) - Audio cue (from file) + Go audio (depuis un fichier) - + Select media files - Select media files + Sélectionner des fichiers médias GstMediaError - + Cannot create pipeline element: "{}" - Cannot create pipeline element: "{}" + Impossible de créer l'élément tunnel : "{}" @@ -143,15 +143,15 @@ GstMediaWarning - + Invalid pipeline element: "{}" - Invalid pipeline element: "{}" + Élément tunnel incorrect : "{}" GstPipelineEdit - + Edit Pipeline Éditer le bitoduc @@ -159,9 +159,14 @@ GstSettings - - Pipeline - Bitoduc + + Default pipeline + Default pipeline + + + + Applied only to new cues. + Applied only to new cues. @@ -217,7 +222,7 @@ Volume - Volume + Volume @@ -255,7 +260,7 @@ Hauteur de note - + URI Input Entrée URI @@ -306,7 +311,7 @@ GStreamer - GStreamer + GStreamer @@ -322,7 +327,7 @@ Source - Source + Source @@ -378,7 +383,7 @@ Volume - Volume + Volume diff --git a/lisp/i18n/ts/fr_FR/lisp.ts b/lisp/i18n/ts/fr_FR/lisp.ts index dac9aa26b..881022493 100644 --- a/lisp/i18n/ts/fr_FR/lisp.ts +++ b/lisp/i18n/ts/fr_FR/lisp.ts @@ -1,14 +1,6 @@ - - APIServerInfo - - - Stop serving network API - Stop serving network API - - About @@ -35,52 +27,36 @@ AboutDialog - + Web site Site web - + Source code Code source - + Info Informations - + License Licence - + Contributors Contributeurs - + Discussion Discussion - - AlsaSinkSettings - - - ALSA device - ALSA device - - - - ApiServerError - - - Network API server stopped working. - Network API server stopped working. - - AppConfiguration @@ -92,277 +68,52 @@ AppGeneralSettings - + Default layout - Default layout + Disposition par défaut - - Enable startup layout selector - Enable startup layout selector - - - + Application themes - Application themes + Thèmes de l'application - + UI theme: Thème UI : - + Icons theme: Thème des icônes : - - Application language (require restart) - Application language (require restart) - - - + Language: Langue : - - - ApplicationError - - - Startup error - Startup error - - - - AudioDynamicSettings - - - Compressor - Compressor - - - - Expander - Expander - - - - Soft Knee - Soft Knee - - - - Hard Knee - Hard Knee - - - - Compressor/Expander - Compressor/Expander - - - - Type - Type - - - - Curve Shape - Curve Shape - - - - Ratio - Ratio - - - - Threshold (dB) - Seuil (dB) - - - - AudioPanSettings - - - Audio Pan - Audio Pan - - - - Center - Center - - - - Left - Gauche - - - - Right - Droite - - - - CartLayout - - - Default behaviors - Comportements par défaut - - - - Countdown mode - Countdown mode - - - - Show seek-bars - Show seek-bars - - - - Show dB-meters - Show dB-meters - - - Show accurate time - Afficher le temps exact + + Show layout selection at startup + Show layout selection at startup - - Show volume - Afficher volume + + Use layout at startup: + Use layout at startup: - - Grid size - Grid size - - - - Number of columns: - Number of columns: - - - - Number of rows: - Number of rows: - - - - Play - Play - - - - Pause - Pause - - - - Stop - Stop - - - - Reset volume - Réinitialiser le volume - - - - Add page - Ajouter une page - - - - Add pages - Ajouter des pages - - - - Remove current page - Retirer la page actuelle - - - - Number of Pages: - Number of Pages: - - - - Page {number} - Page {number} - - - - Warning - Warning - - - - Every cue in the page will be lost. - Every cue in the page will be lost. - - - - Are you sure to continue? - Voulez-vous vraiment continuer ? - - - - CollectionCue - - - Add - Ajouter - - - - Remove - Retirer - - - - Cue - Cue - - - - Action - Action + + Application language (require a restart) + Application language (require a restart) - CommandCue - - - Command - Commande - - - - Command to execute, as in a shell - Command to execute, as in a shell - - - - Discard command output - Discard command output - - - - Ignore command errors - Ignore command errors - + ApplicationError - - Kill instead of terminate - Kill instead of terminate + + Startup error + Erreur au démarrage @@ -394,154 +145,6 @@ Nouvelle configuration installée à {} - - Controller - - - Cannot load controller protocol: "{}" - Cannot load controller protocol: "{}" - - - - ControllerKeySettings - - - Key - Touche - - - - Action - Action - - - - Shortcuts - Shortcuts - - - - ControllerMidiSettings - - - MIDI - MIDI - - - - Type - Type - - - - Channel - Channel - - - - Note - Note - - - - Action - Action - - - - Filter "note on" - Filter "note on" - - - - Filter "note off" - Filter "note off" - - - - Capture - Capture - - - - Listening MIDI messages ... - Listening MIDI messages ... - - - - ControllerOscSettings - - - OSC Message - OSC Message - - - - OSC - OSC - - - - Path - Path - - - - Types - Types - - - - Arguments - Arguments - - - - Actions - Actions - - - - OSC Capture - Capture OSC - - - - Add - Ajouter - - - - Remove - Retirer - - - - Capture - Capture - - - - ControllerSettings - - - Add - Ajouter - - - - Remove - Retirer - - - - Cue Name - - - OSC Settings - Paramètres OSC - - CueAction @@ -567,32 +170,32 @@ Faded Start - Faded Start + Fondu de début Faded Resume - Faded Resume + Reprise en fondu Faded Pause - Faded Pause + Pause en fondu Faded Stop - Faded Stop + Stoppé en fondu Faded Interrupt - Faded Interrupt + Interruption du fondu Resume - Resume + Reprendre @@ -600,19 +203,6 @@ Ne rien faire - - CueActionLog - - - Cue settings changed: "{}" - Paramètres de cue modifiés : "{}" - - - - Cues settings changed. - Paramètres de cues modifiés. - - CueAppearanceSettings @@ -659,19 +249,9 @@ CueCategory - - Action cues - Action cues - - - - Integration cues - Integration cues - - - + Misc cues - Misc cues + Go divers @@ -679,88 +259,48 @@ Cue settings changed: "{}" - Cue settings changed: "{}" + Paramètres du go modifiés : "{}" Cues settings changed. - Cues settings changed. + Paramètres de go modifiés. CueName - + Media Cue Cue de média - - - Index Action - Index Action - - - - Seek Cue - Seek Cue - - - - Volume Control - Volume Control - - - - Command Cue - Command Cue - - - - Collection Cue - Collection Cue - - - - Stop-All - Stop-All - - - - MIDI Cue - MIDI Cue - - - - OSC Cue - OSC Cue - CueNextAction - + Do Nothing Ne rien faire - + Trigger after the end - Trigger after the end + Déclencher après la fin - + Trigger after post wait - Trigger after post wait + Déclencher après l'attente - + Select after the end - Select after the end + Sélectionner après la fin - + Select after post wait - Select after post wait + Sélectionner après l'attente @@ -811,68 +351,14 @@ Action par défaut pour arrêter le cue - - Interrupt fade - Interrupt fade - - - - Fade actions - Fade actions - - - - CueTriggers - - - Started - Démarré - - - - Paused - En pause - - - - Stopped - Arrêté - - - - Ended - Terminé - - - - DbMeterSettings - - - DbMeter settings - DbMeter settings - - - - Time between levels (ms) - Time between levels (ms) - - - - Peak ttl (ms) - Peak ttl (ms) - - - - Peak falloff (dB/sec) - Peak falloff (dB/sec) + + Interrupt action fade + Interrupt action fade - - - Equalizer10Settings - - 10 Bands Equalizer (IIR) - 10 Bands Equalizer (IIR) + + Fade actions default value + Fade actions default value @@ -896,14 +382,14 @@ FadeEdit - + Duration (sec): Durée (sec): - + Curve: - Curve: + Courbe : @@ -920,479 +406,65 @@ - GlobalAction + HotKeyEdit - - Go - Go - - - - Reset - Réinitialiser + + Press shortcut + Appuyez sur le raccourci + + + LayoutSelect - - Stop all cues - Arrêter toutes les cues + + Layout selection + Sélection de l'agencement - - Pause all cues - Mettre en pause toutes les cues + + Select layout + Sélectionner l'agencement - - Resume all cues - Reprendre toutes les cues + + Open file + Ouvrir un fichier + + + ListLayout - - Interrupt all cues - Interrompre toutes les cues + + Layout actions + Layout actions - - Fade-out all cues - Fade-out all cues + + Fade out when stopping all cues + Fade out when stopping all cues - - Fade-in all cues - Fade-in all cues + + Fade out when interrupting all cues + Fade out when interrupting all cues - - Move standby forward - Move standby forward + + Fade out when pausing all cues + Fade out when pausing all cues - - Move standby back - Move standby back + + Fade in when resuming all cues + Fade in when resuming all cues - GstBackend - - - Audio cue (from file) - Audio cue (from file) - - - - Select media files - Select media files - - - - GstMediaError - - - Cannot create pipeline element: "{}" - Cannot create pipeline element: "{}" - - - - GstMediaSettings - - - Change Pipeline - Change Pipeline - - - - GstMediaWarning - - - Invalid pipeline element: "{}" - Invalid pipeline element: "{}" - - - - GstPipelineEdit - - - Edit Pipeline - Edit Pipeline - - - - GstSettings - - - Pipeline - Pipeline - - - - IndexActionCue - - - No suggestion - No suggestion - - - - Index - Index - - - - Use a relative index - Use a relative index - - - - Target index - Target index - - - - Action - Action - - - - Suggested cue name - Suggested cue name - - - - JackSinkSettings - - - Connections - Connections - - - - Edit connections - Éditer les connexions - - - - Output ports - Output ports - - - - Input ports - Input ports - - - - Connect - Connecter - - - - Disconnect - Déconnecter - - - - LayoutDescription - - - Organize the cues in a list - Organiser les cues dans une liste - - - - Organize cues in grid like pages - Organiser les cues en grille comme les pages - - - - LayoutDetails - - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Espace ou Double-Clic pour éditer une cue - - - - To copy cues drag them while pressing CTRL - Pour copier les cues, glissez-les en pressant CTRL - - - - To move cues drag them - Pour déplacer les cues, glissez-les - - - - Click a cue to run it - Cliquer sur une cue pour l'exécuter - - - - SHIFT + Click to edit a cue - MAJ + clic pour éditer une cue - - - - CTRL + Click to select a cue - CTRL + clic pour sélectionner une cue - - - - To move cues drag them while pressing SHIFT - Pour déplacer les cues, glissez-les en pressant SHIFT - - - - LayoutName - - - List Layout - List Layout - - - - Cart Layout - Cart Layout - - - - LayoutSelect - - - Layout selection - Sélection de l'agencement - - - - Select layout - Sélectionner l'agencement - - - - Open file - Ouvrir un fichier - - - - ListLayout - - - Stop All - Tout arrêter - - - - Pause All - Tout mettre en pause - - - - Interrupt All - Tout interrompre - - - - Default behaviors - Comportements par défaut - - - - Show playing cues - Afficher les cues en cours - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Afficher le temps exact - - - - Show seek-bars - Afficher la barre de progression - - - - Auto-select next cue - Auto-sélectionner la prochaine cue - - - - Enable selection mode - Activer le mode sélection - - - - GO Key: - Touche GO: - - - - GO Action: - Action GO: - - - - GO minimum interval (ms): - Intervalle minimum GO (ms): - - - - Use fade (buttons) - Use fade (buttons) - - - - Stop Cue - Arrêter la cue - - - - Pause Cue - Mettre la cue en pause - - - - Resume Cue - Reprendre la cue - - - - Interrupt Cue - Interrompre la cue - - - - Edit cue - Éditer la cue - - - - Edit selected - Edit selected - - - - Clone cue - Clone cue - - - - Clone selected - Clone selected - - - - Remove cue - Remove cue - - - - Remove selected - Remove selected - - - - Selection mode - Mode sélection - - - - Pause all - Tout mettre en pause - - - - Stop all - Tout stopper - - - - Interrupt all - Tout interrompre - - - - Resume all - Tout reprendre - - - - Fade-Out all - Fade-Out all - - - - Fade-In all - Fade-In all - - - - Edit selected cues - Éditer les cues sélectionnées - - - - Remove selected cues - Retirer les cues sélectionnées - - - - Use fade - Utiliser le fondu - - - - Copy of {} - Copie de {} - - - - ListLayoutHeader - - - Cue - Cue - - - - Pre wait - Pré-attente - - - - Action - Action - - - - Post wait - Post wait - - - - ListLayoutInfoPanel - - - Cue name - Cue name - - - - Cue description - Cue description - - - - LogStatusIcon + LogStatusIcon Errors/Warnings - Errors/Warnings + Erreurs/avertissements @@ -1415,7 +487,7 @@ Dismiss all - Dismiss all + Tout ignorer @@ -1423,9 +495,9 @@ Afficher les détails - + Linux Show Player - Log Viewer - Linux Show Player - Log Viewer + Lecteur Linux Show - Visionneur de log @@ -1450,7 +522,7 @@ Logger name - Logger name + Nom de log @@ -1470,7 +542,7 @@ Path name - Path name + Nom du chemin @@ -1480,7 +552,7 @@ Line no. - Line no. + Ligne n° @@ -1500,323 +572,176 @@ Thread ID - Thread ID + ID du fil Thread name - Thread name + Nom du sujet Exception info - Exception info - - - - MIDICue - - - MIDI Message - MIDI Message + Information d'exception - - Message type - Message type + + Showing {} of {} records + Showing {} of {} records - MIDIMessageAttr + MainWindow - - Channel - Channel + + &File + &Fichier - - Note - Note + + New session + Nouvelle session - - Velocity - Velocity + + Open + Ouvrir - - Control - Control + + Save session + Sauvegarder la session - - Program - Program + + Preferences + Préférences - - Value - Value + + Save as + Sauvegarder sous - - Song - Song + + Full Screen + Plein écran - - Pitch - Pitch + + Exit + Quitter - - Position - Position + + &Edit + Édit&er - - - MIDIMessageType - - Note ON - Note ON + + Undo + Annuler - - Note OFF - Note OFF + + Redo + Refaire - - Polyphonic After-touch - Polyphonic After-touch + + Select all + Tout sélectionner - - Control/Mode Change - Control/Mode Change + + Select all media cues + Sélectionner toutes les cues média - - Program Change - Program Change + + Deselect all + Tout désélectionner - - Channel After-touch - Channel After-touch + + CTRL+SHIFT+A + CTRL+MAJ+A - - Pitch Bend Change - Pitch Bend Change + + Invert selection + Inverser la sélection - - Song Select - Song Select + + CTRL+I + CTRL+I - - Song Position - Song Position + + Edit selected + Éditer la sélection - - Start - Start + + CTRL+SHIFT+E + CTRL+MAJ+E - - Stop - Stop - - - - Continue - Continue - - - - MIDISettings - - - MIDI default devices - MIDI default devices - - - - Input - Input - - - - Output - Output - - - - MainWindow - - - &File - &Fichier - - - - New session - Nouvelle session - - - - Open - Ouvrir - - - - Save session - Sauvegarder la session - - - - Preferences - Préférences - - - - Save as - Sauvegarder sous - - - - Full Screen - Plein écran - - - - Exit - Quitter - - - - &Edit - Édit&er - - - - Undo - Annuler - - - - Redo - Refaire - - - - Select all - Tout sélectionner - - - - Select all media cues - Sélectionner toutes les cues média - - - - Deselect all - Tout désélectionner - - - - CTRL+SHIFT+A - CTRL+MAJ+A - - - - Invert selection - Inverser la sélection - - - - CTRL+I - CTRL+I - - - - Edit selected - Éditer la sélection - - - - CTRL+SHIFT+E - CTRL+MAJ+E - - - + &Layout É&tat - + &Tools Ou&tils - + Edit selection Éditer la sélection - + &About À &propos - + About À propos - + About Qt À propos de Qt - + Close session Fermer la session - - The current session is not saved. - La session actuelle n'est pas sauvegardée - - - - Discard the changes? - Annuler les changements ? - - - + Do you want to save them now? - Do you want to save them now? + Voulez-vous les enregistrer maintenant ? MainWindowDebug - + Registered cue menu: "{}" - Registered cue menu: "{}" + Menu de go inscrit : "{}" MainWindowError - + Cannot create cue {} - Cannot create cue {} + Impossible de créer le go {} @@ -1848,1047 +773,59 @@ - MediaElementName - - - Pitch - Pitch - - - - Volume - Volume - - - - Audio Pan - Audio Pan - - - - Compressor/Expander - Compressor/Expander - - - - JACK Out - JACK Out - - - - URI Input - URI Input - - - - 10 Bands Equalizer - 10 Bands Equalizer - - - - ALSA Out - ALSA Out - - - - dB Meter - dB Meter - - - - Preset Input - Preset Input - - - - PulseAudio Out - PulseAudio Out - - - - Custom Element - Custom Element - - - - Speed - Speed - - - - System Out - System Out - - - - System Input - System Input - - - - MediaInfo - - - Media Info - Media Info - - - - Warning - Warning - - - - No info to display - No info to display - - - - Info - Info - - - - Value - Value - - - - NetworkApiDebug - - - New end-point: {} - New end-point: {} - - - - NetworkDiscovery - - - Host discovery - Host discovery - - - - Manage hosts - Manage hosts - - - - Discover hosts - Discover hosts - - - - Manually add a host - Manually add a host - - - - Remove selected host - Remove selected host - - - - Remove all host - Remove all host - - - - Address - Address - - - - Host IP - Host IP - - - - Osc Cue - - - Type - Type - - - - Argument - Argument - - - - OscCue - - - Add - Add - - - - Remove - Remove - - - - Type - Type - - - - Value - Value - - - - FadeTo - FadeTo - - - - Fade - Fade - - - - OSC Message - OSC Message - - - - OSC Path: - OSC Path: - - - - /path/to/something - /path/to/something - - - - Time (sec) - Time (sec) - - - - Curve - Curve - - - - OscServerDebug - - - Message from {} -> path: "{}" args: {} - Message from {} -> path: "{}" args: {} - - - - OscServerError - - - Cannot start OSC sever - Cannot start OSC sever - - - - OscServerInfo - - - OSC server started at {} - OSC server started at {} - + QColorButton - - OSC server stopped - OSC server stopped + + Right click to reset + Clic-droit pour réinitialiser - OscSettings - - - OSC Settings - OSC Settings - - - - Input Port: - Input Port: - + SettingsPageName - - Output Port: - Output Port: + + Appearance + Apparence - - Hostname: - Hostname: + + General + Générale - - - PitchSettings - - Pitch - Pitch + + Cue + Go - - {0:+} semitones - {0:+} semitones + + Cue Settings + Paramètres de cue - - - PluginsError - - Failed to load "{}" - Failed to load "{}" + + Plugins + Greffons - - Failed to terminate plugin: "{}" - Failed to terminate plugin: "{}" + + Behaviours + Comportements - - the requested plugin is not loaded: {} - the requested plugin is not loaded: {} + + Pre/Post Wait + Attente pré/post - - - PluginsInfo - - Plugin loaded: "{}" - Plugin loaded: "{}" - - - - Plugin terminated: "{}" - Plugin terminated: "{}" - - - - PluginsWarning - - - Cannot satisfy dependencies for: {} - Cannot satisfy dependencies for: {} - - - - Preset - - - Create Cue - Create Cue - - - - Load on selected Cues - Load on selected Cues - - - - PresetSrcSettings - - - Presets - Presets - - - - Presets - - - Cannot scan presets - Cannot scan presets - - - - Error while deleting preset "{}" - Error while deleting preset "{}" - - - - Cannot load preset "{}" - Cannot load preset "{}" - - - - Cannot save preset "{}" - Cannot save preset "{}" - - - - Cannot rename preset "{}" - Cannot rename preset "{}" - - - - Select Preset - Select Preset - - - - Presets - Presets - - - - Preset name - Preset name - - - - Add - Add - - - - Rename - Rename - - - - Edit - Edit - - - - Remove - Remove - - - - Export selected - Export selected - - - - Import - Import - - - - Warning - Warning - - - - The same name is already used! - The same name is already used! - - - - Cannot export correctly. - Cannot export correctly. - - - - Cannot import correctly. - Cannot import correctly. - - - - Cue type - Cue type - - - - Load on cue - Load on cue - - - - Load on selected cues - Load on selected cues - - - - Save as preset - Save as preset - - - - Cannot create a cue from this preset: {} - Cannot create a cue from this preset: {} - - - - QColorButton - - - Right click to reset - Clic-droit pour réinitialiser - - - - RenameCues - - - Rename Cues - Rename Cues - - - - Rename cues - Rename cues - - - - Current - Current - - - - Preview - Preview - - - - Capitalize - Capitalize - - - - Lowercase - Lowercase - - - - Uppercase - Uppercase - - - - Remove Numbers - Remove Numbers - - - - Add numbering - Add numbering - - - - Reset - Reset - - - - Type your regex here: - Type your regex here: - - - - Regex help - Regex help - - - - RenameCuesCommand - - - Renamed {number} cues - Renamed {number} cues - - - - RenameUiDebug - - - Regex error: Invalid pattern - Regex error: Invalid pattern - - - - ReplayGain - - - ReplayGain / Normalization - ReplayGain / Normalization - - - - Threads number - Threads number - - - - Apply only to selected media - Apply only to selected media - - - - ReplayGain to (dB SPL) - ReplayGain to (dB SPL) - - - - Normalize to (dB) - Normalize to (dB) - - - - Processing files ... - Processing files ... - - - - Calculate - Calculate - - - - Reset all - Reset all - - - - Reset selected - Reset selected - - - - ReplayGainDebug - - - Applied gain for: {} - Applied gain for: {} - - - - Discarded gain for: {} - Discarded gain for: {} - - - - ReplayGainInfo - - - Gain processing stopped by user. - Gain processing stopped by user. - - - - Started gain calculation for: {} - Started gain calculation for: {} - - - - Gain calculated for: {} - Gain calculated for: {} - - - - SeekCue - - - Cue - Cue - - - - Click to select - Click to select - - - - Not selected - Not selected - - - - Seek - Seek - - - - Time to reach - Time to reach - - - - SettingsPageName - - - Appearance - Apparence - - - - General - Générale - - - - Cue - Cue - - - - Cue Settings - Paramètres de cue - - - - Plugins - Plugins - - - - Behaviours - Behaviours - - - - Pre/Post Wait - Pre/Post Wait - - - - Fade In/Out - Fade In/Out + + Fade In/Out + Fade de Début/Fin Layouts - Layouts - - - - Action Settings - Action Settings - - - - Seek Settings - Seek Settings - - - - Volume Settings - Volume Settings - - - - Command - Command - - - - Edit Collection - Edit Collection - - - - Stop Settings - Stop Settings - - - - List Layout - List Layout - - - - Timecode - Timecode - - - - Timecode Settings - Timecode Settings - - - - Cue Control - Cue Control - - - - Layout Controls - Layout Controls - - - - MIDI Controls - MIDI Controls - - - - Keyboard Shortcuts - Keyboard Shortcuts - - - - OSC Controls - OSC Controls - - - - MIDI Settings - MIDI Settings - - - - MIDI settings - MIDI settings - - - - GStreamer - GStreamer - - - - Media Settings - Media Settings - - - - Triggers - Triggers - - - - OSC settings - OSC settings - - - - Cart Layout - Cart Layout - - - - SpeedSettings - - - Speed - Speed - - - - StopAll - - - Stop Action - Stop Action - - - - Synchronizer - - - Synchronization - Synchronization - - - - Manage connected peers - Manage connected peers - - - - Show your IP - Show your IP - - - - Your IP is: - Your IP is: - - - - Timecode - - - Cannot load timecode protocol: "{}" - Cannot load timecode protocol: "{}" - - - - TimecodeError - - - Cannot send timecode. - Cannot send timecode. - - - - TimecodeSettings - - - Replace HOURS by a static track number - Replace HOURS by a static track number - - - - Enable Timecode - Enable Timecode - - - - Track number - Track number - - - - Timecode Settings - Timecode Settings - - - - Timecode Format: - Timecode Format: - - - - Timecode Protocol: - Timecode Protocol: - - - - TriggersSettings - - - Add - Add - - - - Remove - Remove - - - - Trigger - Trigger - - - - Cue - Cue - - - - Action - Action - - - - UriInputSettings - - - Source - Source - - - - Find File - Find File - - - - Buffering - Buffering - - - - Use Buffering - Use Buffering - - - - Attempt download on network streams - Attempt download on network streams - - - - Buffer size (-1 default value) - Buffer size (-1 default value) - - - - Choose file - Choose file - - - - All files - All files - - - - UserElementSettings - - - User defined elements - User defined elements - - - - Only for advanced user! - Only for advanced user! - - - - VolumeControl - - - Cue - Cue - - - - Click to select - Click to select - - - - Not selected - Not selected - - - - Volume to reach - Volume to reach - - - - Fade - Fade - - - - VolumeControlError - - - Error during cue execution. - Error during cue execution. - - - - VolumeSettings - - - Volume - Volume - - - - Normalized volume - Normalized volume - - - - Reset - Reset + Dispositions diff --git a/lisp/i18n/ts/fr_FR/list_layout.ts b/lisp/i18n/ts/fr_FR/list_layout.ts index ce8df49fd..219d96a7c 100644 --- a/lisp/i18n/ts/fr_FR/list_layout.ts +++ b/lisp/i18n/ts/fr_FR/list_layout.ts @@ -32,85 +32,75 @@ List Layout - List Layout + Disposition en liste ListLayout - - Default behaviors - Comportements par défaut - - - - Show playing cues - Afficher les cues en cours - - - + Show dB-meters - Show dB-meters + Afficher le niveau de décibels - + Show accurate time Afficher le temps exact - + Show seek-bars - Show seek-bars + Montrer la barre de recherche - + Auto-select next cue Auto-sélectionner la prochaine cue - + Enable selection mode - Enable selection mode + Activer le mode de sélection - + Use fade (buttons) - Use fade (buttons) + Utiliser le fondu (boutons) - + Stop Cue Arrêter la cue - + Pause Cue Mettre la cue en pause - + Resume Cue Reprendre la cue - + Interrupt Cue Interrompre la cue - + Edit cue Éditer la cue - + Remove cue Retirer la cue - + Selection mode - Selection mode + Mode de sélection @@ -135,75 +125,115 @@ Fade-Out all - Fade-Out all + Tout arrêter en fondu Fade-In all - Fade-In all + Tout démarrer en fondu - + Edit selected Éditer la sélection - + Clone cue Cloner la cue - + Clone selected Cloner la selection - + Remove selected Retirer la selection - + GO Key: - GO Key: + Touche GO: - + GO Action: - GO Action: + Action de GO: - + GO minimum interval (ms): - GO minimum interval (ms): + Intervalle minimum de GO (ms): - + Copy of {} - Copy of {} + Copie de {} + + + + Show index column + Show index column + + + + Show resize handles + Show resize handles + + + + Restore default size + Restore default size + + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + + + + Behaviors + Behaviors + + + + Disable GO Key While Playing + Disable GO Key While Playing + + + + Use waveform seek-bars + Use waveform seek-bars + + + + GO Key Disabled While Playing + GO Key Disabled While Playing ListLayoutHeader - + Cue Cue - + Pre wait - Pre wait + Attente avant - + Action Action - + Post wait - Post wait + Attente après @@ -222,9 +252,9 @@ SettingsPageName - + List Layout - List Layout + Disposition en liste diff --git a/lisp/i18n/ts/fr_FR/media_info.ts b/lisp/i18n/ts/fr_FR/media_info.ts index 3c554d7fa..2d22b5948 100644 --- a/lisp/i18n/ts/fr_FR/media_info.ts +++ b/lisp/i18n/ts/fr_FR/media_info.ts @@ -4,29 +4,29 @@ MediaInfo - + Media Info Info sur le média - - No info to display - Pas d'info à afficher - - - + Info - Info + Information - + Value Valeur Warning - Warning + Avertissement + + + + Cannot get any information. + Cannot get any information. diff --git a/lisp/i18n/ts/fr_FR/midi.ts b/lisp/i18n/ts/fr_FR/midi.ts index 9a72d6464..de0c34e1c 100644 --- a/lisp/i18n/ts/fr_FR/midi.ts +++ b/lisp/i18n/ts/fr_FR/midi.ts @@ -4,17 +4,17 @@ CueCategory - + Integration cues - Integration cues + Go d'intégration CueName - + MIDI Cue - MIDI Cue + Go MIDI @@ -30,50 +30,81 @@ Type de message + + MIDIError + + + Cannot connect to MIDI output port '{}'. + Cannot connect to MIDI output port '{}'. + + + + Cannot connect to MIDI input port '{}'. + Cannot connect to MIDI input port '{}'. + + + + MIDIInfo + + + MIDI port disconnected: '{}' + MIDI port disconnected: '{}' + + + + Connecting to MIDI port: '{}' + Connecting to MIDI port: '{}' + + + + Connecting to matching MIDI port: '{}' + Connecting to matching MIDI port: '{}' + + MIDIMessageAttr - + Channel Canal - + Note - Note + Commentaire - + Velocity - Velocity + Vitesse - + Control - Control + Commandes - + Program Programme - + Value Valeur - + Song Musique - + Pitch - Pitch + Hauteur de note - + Position Position @@ -81,62 +112,62 @@ MIDIMessageType - + Note ON - Note ON + Note ON - + Note OFF - Note OFF + Note OFF - + Polyphonic After-touch - Polyphonic After-touch + Post-traitement polyphonique - + Control/Mode Change - Control/Mode Change + Contrôle/Mode de changement - + Program Change - Program Change + Programme de changement - + Channel After-touch - Channel After-touch + Canal d'après-toucher - + Pitch Bend Change - Pitch Bend Change + Changement de courbe du pitch - + Song Select Selection de musique - + Song Position Position de musique - + Start Démarrer - + Stop Arrêter - + Continue Continuer @@ -144,32 +175,42 @@ MIDISettings - - MIDI default devices - Périphériques MIDI par défaut - - - + Input Entrée - + Output Sortie + + + MIDI devices + MIDI devices + + + + Misc options + Misc options + + + + Try to connect using only device/port name + Try to connect using only device/port name + SettingsPageName - + MIDI settings Préférences MIDI - + MIDI Settings - MIDI Settings + Préférences MIDI diff --git a/lisp/i18n/ts/fr_FR/network.ts b/lisp/i18n/ts/fr_FR/network.ts index 0f29e1de5..d4aed4ba5 100644 --- a/lisp/i18n/ts/fr_FR/network.ts +++ b/lisp/i18n/ts/fr_FR/network.ts @@ -6,7 +6,7 @@ Stop serving network API - Stop serving network API + Arrêter l'API de service réseau @@ -14,7 +14,7 @@ Network API server stopped working. - Network API server stopped working. + Le serveur d'API réseau a cessé de fonctionner. @@ -22,50 +22,55 @@ New end-point: {} - New end-point: {} + Nouveau point d'arrêt : {} NetworkDiscovery - + Host discovery - Host discovery + Découverte d'hôte - + Manage hosts Gérer les hôtes - + Discover hosts - Discover hosts + Découvrir les hôtes - + Manually add a host Ajouter manuellement un hôte - + Remove selected host Retirer l'hôte sélectionné - + Remove all host Retirer tous les hôtes - + Address Adresse - + Host IP IP de l'hôte + + + Select the hosts you want to add + Select the hosts you want to add + diff --git a/lisp/i18n/ts/fr_FR/osc.ts b/lisp/i18n/ts/fr_FR/osc.ts index 2c7bb06e0..68cd62fe6 100644 --- a/lisp/i18n/ts/fr_FR/osc.ts +++ b/lisp/i18n/ts/fr_FR/osc.ts @@ -6,15 +6,15 @@ OSC Settings - OSC Settings + Paramètres OSC CueCategory - + Integration cues - Integration cues + Go d'intégration @@ -22,7 +22,7 @@ OSC Cue - OSC Cue + Go OSC @@ -30,57 +30,57 @@ Type - Type + Type Value - Value + Valeur FadeTo - FadeTo + Fondu vers Fade - Fade + Fondu OSC Message - OSC Message + Message OSC Add - Add + Ajouter Remove - Remove + Supprimer OSC Path: - OSC Path: + Chemin OSC : /path/to/something - /path/to/something + /chemin/vers/quelque chose Time (sec) - Time (sec) + Durée (en sec) Curve - Curve + Courbe diff --git a/lisp/i18n/ts/fr_FR/presets.ts b/lisp/i18n/ts/fr_FR/presets.ts index eb4aad613..a8049e07b 100644 --- a/lisp/i18n/ts/fr_FR/presets.ts +++ b/lisp/i18n/ts/fr_FR/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue - Créer un cue + Créer un go - + Load on selected Cues Charger les cues sélectionnés @@ -57,74 +57,79 @@ Sélectionner le pré-réglage - + Preset name Nom du pré-réglage - + Add Ajouter - + Rename Renommer - + Edit Éditer - + Remove Supprimer - + Export selected Exporter le sélectionné - + Import Importer - + Warning Attention - + The same name is already used! Le même nom est déjà utilisé ! - + Cannot export correctly. Impossible d'exporter correctement - + Cannot import correctly. Impossible d'importer correctement. - + Cue type Type de cue Load on cue - Load on cue + Charger au go Load on selected cues - Load on selected cues + Charger les go sélectionnés + + + + Cannot create a cue from this preset: {} + Impossible de créer un go depuis ce pré-réglage : {} diff --git a/lisp/i18n/ts/fr_FR/rename_cues.ts b/lisp/i18n/ts/fr_FR/rename_cues.ts index 131ebec58..f033f6863 100644 --- a/lisp/i18n/ts/fr_FR/rename_cues.ts +++ b/lisp/i18n/ts/fr_FR/rename_cues.ts @@ -16,7 +16,7 @@ Current - Courant + Actuel @@ -26,7 +26,7 @@ Capitalize - Capitalize + Convertir en majuscules @@ -46,7 +46,7 @@ Add numbering - Add numbering + Ajouter un numéro @@ -69,7 +69,7 @@ Renamed {number} cues - Renamed {number} cues + Renommer {number} go diff --git a/lisp/i18n/ts/fr_FR/replay_gain.ts b/lisp/i18n/ts/fr_FR/replay_gain.ts index fca545c17..c936f41b9 100644 --- a/lisp/i18n/ts/fr_FR/replay_gain.ts +++ b/lisp/i18n/ts/fr_FR/replay_gain.ts @@ -4,22 +4,22 @@ ReplayGain - + ReplayGain / Normalization Gain de lecture / normalisation - + Calculate Calculer - + Reset all Tout réinitialiser - + Reset selected Réinitialiser la séléction @@ -52,32 +52,32 @@ ReplayGainDebug - + Applied gain for: {} - Applied gain for: {} + Gain appliqué pour : {} - + Discarded gain for: {} - Discarded gain for: {} + Gain annulé pour : {} ReplayGainInfo - + Gain processing stopped by user. - Gain processing stopped by user. + Traitement des gains arrêté par l'utilisateur. - + Started gain calculation for: {} - Started gain calculation for: {} + Calcul de gain commencé pour : {} - + Gain calculated for: {} - Gain calculated for: {} + Gain calculé pour : {} diff --git a/lisp/i18n/ts/fr_FR/synchronizer.ts b/lisp/i18n/ts/fr_FR/synchronizer.ts index 88e713043..d447c421a 100644 --- a/lisp/i18n/ts/fr_FR/synchronizer.ts +++ b/lisp/i18n/ts/fr_FR/synchronizer.ts @@ -20,8 +20,8 @@ - Your IP is: - Votre IP est : + Your IP is: {} + Your IP is: {} diff --git a/lisp/i18n/ts/fr_FR/timecode.ts b/lisp/i18n/ts/fr_FR/timecode.ts index a182022ce..992211af6 100644 --- a/lisp/i18n/ts/fr_FR/timecode.ts +++ b/lisp/i18n/ts/fr_FR/timecode.ts @@ -19,7 +19,7 @@ Cannot load timecode protocol: "{}" - Cannot load timecode protocol: "{}" + Impossible de charger le protocole de code temporel : "{}" @@ -55,12 +55,12 @@ Timecode Settings - Timecode Settings + Paramètres de code temporel Timecode Protocol: - Timecode Protocol: + Protocole de code temporel : diff --git a/lisp/i18n/ts/it_IT/action_cues.ts b/lisp/i18n/ts/it_IT/action_cues.ts index 8b8f24670..2ebc42ac5 100644 --- a/lisp/i18n/ts/it_IT/action_cues.ts +++ b/lisp/i18n/ts/it_IT/action_cues.ts @@ -55,7 +55,7 @@ CueCategory - + Action cues Action cues diff --git a/lisp/i18n/ts/it_IT/cache_manager.ts b/lisp/i18n/ts/it_IT/cache_manager.ts new file mode 100644 index 000000000..c0c629c21 --- /dev/null +++ b/lisp/i18n/ts/it_IT/cache_manager.ts @@ -0,0 +1,35 @@ + + + + + CacheManager + + + Cache size warning + Cache size warning + + + + Warning threshold in MB (0 = disabled) + Warning threshold in MB (0 = disabled) + + + + Cache cleanup + Cache cleanup + + + + Delete the cache content + Delete the cache content + + + + SettingsPageName + + + Cache Manager + Cache Manager + + + diff --git a/lisp/i18n/ts/it_IT/cart_layout.ts b/lisp/i18n/ts/it_IT/cart_layout.ts index ee2f9c9de..4f803fdf7 100644 --- a/lisp/i18n/ts/it_IT/cart_layout.ts +++ b/lisp/i18n/ts/it_IT/cart_layout.ts @@ -4,37 +4,32 @@ CartLayout - - Default behaviors - Comportamenti predefiniti - - - + Countdown mode Modalità countdown - + Show seek-bars Mostra barre di avanzamento - + Show dB-meters Mostra indicatori dB - + Show accurate time Mostra tempo accurato - + Show volume Mostra volume - + Grid size Dimensione griglia @@ -79,35 +74,40 @@ Numero di Pagine: - + Page {number} Pagina {number} - + Warning Attenzione - + Every cue in the page will be lost. Ogni cue nella pagina sarà persa. - + Are you sure to continue? Sei sicuro di voler continuare? - + Number of columns: Number of columns: - + Number of rows: Number of rows: + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + LayoutDescription diff --git a/lisp/i18n/ts/it_IT/controller.ts b/lisp/i18n/ts/it_IT/controller.ts index f28e47ccb..07ffdc80a 100644 --- a/lisp/i18n/ts/it_IT/controller.ts +++ b/lisp/i18n/ts/it_IT/controller.ts @@ -30,113 +30,136 @@ ControllerMidiSettings - + MIDI MIDI - + Type Tipo - - Channel - Canale + + Action + Azione - - Note - Nota + + Capture + Cattura - - Action - Azione + + Listening MIDI messages ... + Ascoltando messaggi MIDI ... - - Filter "note on" - Filtro "note on" + + -- All Messages -- + -- All Messages -- - - Filter "note off" - Filtro "note off" + + Capture filter + Capture filter - - Capture - Cattura + + Data 1 + Data 1 - - Listening MIDI messages ... - Ascoltando messaggi MIDI ... + + Data 2 + Data 2 + + + + Data 3 + Data 3 ControllerOscSettings - + OSC Message Messaggio OSC - + OSC OSC - + Path Percorso - + Types Tipi - + Arguments Argomenti - - Actions - Azioni - - - + OSC Capture Acquisisci OSC - + Add Aggiungi - + Remove Rimuovi - + Capture Acquisisci + + + Waiting for messages: + Waiting for messages: + + + + /path/to/method + /path/to/method + + + + Action + Action + + + + ControllerOscSettingsWarning + + + Warning + Warning + ControllerSettings - + Add Aggiungi - + Remove Rimuovi @@ -197,12 +220,12 @@ Osc Cue - + Type Tipo - + Argument Argomento @@ -210,12 +233,12 @@ OscCue - + Add Aggiungi - + Remove Rimuovi @@ -228,7 +251,7 @@ Controllo Cue - + MIDI Controls Controlli MIDI @@ -238,7 +261,7 @@ Scorciatoie Tastiera - + OSC Controls Controlli OSC diff --git a/lisp/i18n/ts/it_IT/gst_backend.ts b/lisp/i18n/ts/it_IT/gst_backend.ts index 840f17cf9..a4ce714b9 100644 --- a/lisp/i18n/ts/it_IT/gst_backend.ts +++ b/lisp/i18n/ts/it_IT/gst_backend.ts @@ -119,7 +119,7 @@ Cue audio (da file) - + Select media files Seleziona file multimediali @@ -127,7 +127,7 @@ GstMediaError - + Cannot create pipeline element: "{}" Cannot create pipeline element: "{}" @@ -143,7 +143,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" Invalid pipeline element: "{}" @@ -151,7 +151,7 @@ GstPipelineEdit - + Edit Pipeline Modifica Pipeline @@ -159,9 +159,14 @@ GstSettings - - Pipeline - Pipeline + + Default pipeline + Default pipeline + + + + Applied only to new cues. + Applied only to new cues. @@ -255,7 +260,7 @@ Tonalità - + URI Input Ingresso URI diff --git a/lisp/i18n/ts/it_IT/lisp.ts b/lisp/i18n/ts/it_IT/lisp.ts index 5999b4af0..ce5c5d71f 100644 --- a/lisp/i18n/ts/it_IT/lisp.ts +++ b/lisp/i18n/ts/it_IT/lisp.ts @@ -35,52 +35,36 @@ AboutDialog - + Web site Sito web - + Source code Codice sorgente - + Info Informazioni - + License Licenza - + Contributors Contributori - + Discussion Discussione - - AlsaSinkSettings - - - ALSA device - ALSA device - - - - ApiServerError - - - Network API server stopped working. - Network API server stopped working. - - AppConfiguration @@ -92,277 +76,52 @@ AppGeneralSettings - + Default layout Layout Predefinito - - Enable startup layout selector - Attivare il selettore di layout all'avvio - - - + Application themes Temi dell'applicazione - + UI theme: UI theme: - + Icons theme: Icons theme: - - Application language (require restart) - Application language (require restart) - - - + Language: Language: - - - ApplicationError - - - Startup error - Startup error - - - - AudioDynamicSettings - - - Compressor - Compressor - - - - Expander - Expander - - - - Soft Knee - Soft Knee - - - - Hard Knee - Hard Knee - - - - Compressor/Expander - Compressor/Expander - - - - Type - Type - - - - Curve Shape - Curve Shape - - - - Ratio - Ratio - - - - Threshold (dB) - Threshold (dB) - - - - AudioPanSettings - - - Audio Pan - Audio Pan - - - - Center - Center - - - - Left - Left - - - - Right - Right - - - - CartLayout - - - Default behaviors - Default behaviors - - - - Countdown mode - Countdown mode - - - - Show seek-bars - Show seek-bars - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Show accurate time - - - - Show volume - Show volume - - - - Grid size - Grid size - - - - Number of columns: - Number of columns: - - - - Number of rows: - Number of rows: - - - - Play - Play - - - - Pause - Pause - - - - Stop - Stop - - - - Reset volume - Reset volume - - - - Add page - Add page - - - - Add pages - Add pages - - - - Remove current page - Remove current page - - - - Number of Pages: - Number of Pages: - - - - Page {number} - Page {number} - - - - Warning - Warning - - - Every cue in the page will be lost. - Every cue in the page will be lost. + + Show layout selection at startup + Show layout selection at startup - - Are you sure to continue? - Are you sure to continue? - - - - CollectionCue - - - Add - Add - - - - Remove - Remove - - - - Cue - Cue + + Use layout at startup: + Use layout at startup: - - Action - Action + + Application language (require a restart) + Application language (require a restart) - CommandCue - - - Command - Command - - - - Command to execute, as in a shell - Command to execute, as in a shell - - - - Discard command output - Discard command output - - - - Ignore command errors - Ignore command errors - + ApplicationError - - Kill instead of terminate - Kill instead of terminate + + Startup error + Startup error @@ -646,17 +405,7 @@ CueCategory - - Action cues - Action cues - - - - Integration cues - Integration cues - - - + Misc cues Misc cues @@ -677,7 +426,7 @@ CueName - + Media Cue Cue Multimediale @@ -798,14 +547,14 @@ Azione standard per arrestare la cue - - Interrupt fade - Interrupt fade + + Interrupt action fade + Interrupt action fade - - Fade actions - Fade actions + + Fade actions default value + Fade actions default value @@ -883,12 +632,12 @@ FadeEdit - + Duration (sec): Duration (sec): - + Curve: Curve: @@ -907,1587 +656,392 @@ - GlobalAction - - - Go - Go - - - - Reset - Reset - - - - Stop all cues - Stop all cues - - - - Pause all cues - Pause all cues - - - - Resume all cues - Resume all cues - - - - Interrupt all cues - Interrupt all cues - + HotKeyEdit - - Fade-out all cues - Fade-out all cues + + Press shortcut + Press shortcut + + + LayoutSelect - - Fade-in all cues - Fade-in all cues + + Layout selection + Selezione del layout - - Move standby forward - Move standby forward + + Select layout + Seleziona layout - - Move standby back - Move standby back + + Open file + Apri file - GstBackend + ListLayout - - Audio cue (from file) - Audio cue (from file) + + Layout actions + Layout actions - - Select media files - Select media files + + Fade out when stopping all cues + Fade out when stopping all cues - - - GstMediaError - - Cannot create pipeline element: "{}" - Cannot create pipeline element: "{}" + + Fade out when interrupting all cues + Fade out when interrupting all cues - - - GstMediaSettings - - Change Pipeline - Change Pipeline + + Fade out when pausing all cues + Fade out when pausing all cues - - - GstMediaWarning - - Invalid pipeline element: "{}" - Invalid pipeline element: "{}" + + Fade in when resuming all cues + Fade in when resuming all cues - GstPipelineEdit + LogStatusIcon - - Edit Pipeline - Edit Pipeline + + Errors/Warnings + Errors/Warnings - GstSettings + Logging - - Pipeline - Pipeline + + Debug + Debug - - - HotKeyEdit - - Press shortcut - Press shortcut + + Warning + Avviso - - - IndexActionCue - - No suggestion - No suggestion + + Error + Errore - - Index - Index + + Dismiss all + Dismiss all - - Use a relative index - Use a relative index + + Show details + Show details - - Target index - Target index + + Linux Show Player - Log Viewer + Linux Show Player - Log Viewer - - Action - Action + + Info + Info - - Suggested cue name - Suggested cue name + + Critical + Critical - - - JackSinkSettings - - Connections - Connections + + Time + Time - - Edit connections - Edit connections + + Milliseconds + Milliseconds - - Output ports - Output ports + + Logger name + Logger name - - Input ports - Input ports + + Level + Level - - Connect - Connect + + Message + Message - - Disconnect - Disconnect + + Function + Function - - - LayoutDescription - - Organize the cues in a list - Organize the cues in a list + + Path name + Path name - - Organize cues in grid like pages - Organize cues in grid like pages + + File name + File name - - - LayoutDetails - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Space or Double-Click to edit a cue + + Line no. + Line no. - - To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL + + Module + Module - - To move cues drag them - To move cues drag them + + Process ID + Process ID - - Click a cue to run it - Click a cue to run it + + Process name + Process name - - SHIFT + Click to edit a cue - SHIFT + Click to edit a cue + + Thread ID + Thread ID - - CTRL + Click to select a cue - CTRL + Click to select a cue - - - - To move cues drag them while pressing SHIFT - To move cues drag them while pressing SHIFT - - - - LayoutName - - - List Layout - List Layout - - - - Cart Layout - Cart Layout - - - - LayoutSelect - - - Layout selection - Selezione del layout - - - - Select layout - Seleziona layout - - - - Open file - Apri file - - - - ListLayout - - - Default behaviors - Default behaviors - - - - Show playing cues - Show playing cues - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Show accurate time - - - - Show seek-bars - Show seek-bars - - - - Auto-select next cue - Auto-select next cue - - - - Enable selection mode - Enable selection mode - - - - GO Key: - GO Key: - - - - GO Action: - GO Action: - - - - GO minimum interval (ms): - GO minimum interval (ms): - - - - Use fade (buttons) - Use fade (buttons) - - - - Stop Cue - Stop Cue - - - - Pause Cue - Pause Cue - - - - Resume Cue - Resume Cue - - - - Interrupt Cue - Interrupt Cue - - - - Edit cue - Edit cue - - - - Edit selected - Edit selected - - - - Clone cue - Clone cue - - - - Clone selected - Clone selected - - - - Remove cue - Remove cue - - - - Remove selected - Remove selected - - - - Selection mode - Selection mode - - - - Pause all - Pause all - - - - Stop all - Stop all - - - - Interrupt all - Interrupt all - - - - Resume all - Resume all - - - - Fade-Out all - Fade-Out all - - - - Fade-In all - Fade-In all - - - - Edit selected cues - Edit selected cues - - - - Remove selected cues - Remove selected cues - - - - Use fade - Use fade - - - - Copy of {} - Copy of {} - - - - ListLayoutHeader - - - Cue - Cue - - - - Pre wait - Pre wait - - - - Action - Action - - - - Post wait - Post wait - - - - ListLayoutInfoPanel - - - Cue name - Cue name - - - - Cue description - Cue description - - - - LogStatusIcon - - - Errors/Warnings - Errors/Warnings - - - - Logging - - - Debug - Debug - - - - Warning - Avviso - - - - Error - Errore - - - - Dismiss all - Dismiss all - - - - Show details - Show details - - - - Linux Show Player - Log Viewer - Linux Show Player - Log Viewer - - - - Info - Info - - - - Critical - Critical - - - - Time - Time - - - - Milliseconds - Milliseconds - - - - Logger name - Logger name - - - - Level - Level - - - - Message - Message - - - - Function - Function - - - - Path name - Path name - - - - File name - File name - - - - Line no. - Line no. - - - - Module - Module - - - - Process ID - Process ID - - - - Process name - Process name - - - - Thread ID - Thread ID - - - - Thread name - Thread name - - - - Exception info - Exception info - - - - MIDICue - - - MIDI Message - MIDI Message - - - - Message type - Message type - - - - MIDIMessageAttr - - - Channel - Channel - - - - Note - Note - - - - Velocity - Velocity - - - - Control - Control - - - - Program - Program - - - - Value - Value - - - - Song - Song - - - - Pitch - Pitch - - - - Position - Position - - - - MIDIMessageType - - - Note ON - Note ON - - - - Note OFF - Note OFF - - - - Polyphonic After-touch - Polyphonic After-touch - - - - Control/Mode Change - Control/Mode Change - - - - Program Change - Program Change - - - - Channel After-touch - Channel After-touch - - - - Pitch Bend Change - Pitch Bend Change - - - - Song Select - Song Select - - - - Song Position - Song Position - - - - Start - Start - - - - Stop - Stop - - - - Continue - Continue - - - - MIDISettings - - - MIDI default devices - MIDI default devices - - - - Input - Input - - - - Output - Output - - - - MainWindow - - - &File - &File - - - - New session - Nuova sessione - - - - Open - Apri - - - - Save session - Salva sessione - - - - Preferences - Preferenze - - - - Save as - Salva come - - - - Full Screen - Schermo Intero - - - - Exit - Esci - - - - &Edit - &Modifica - - - - Undo - Annulla - - - - Redo - Ripeti - - - - Select all - Seleziona tutti - - - - Select all media cues - Seleziona tutte le media-cue - - - - Deselect all - Deseleziona tutti - - - - CTRL+SHIFT+A - CTRL+SHIFT+A - - - - Invert selection - Inverti selezione - - - - CTRL+I - CTRL+I - - - - Edit selected - Modifica selezionati - - - - CTRL+SHIFT+E - CTRL+SHIFT+E - - - - &Layout - &Layout - - - - &Tools - &Strumenti - - - - Edit selection - Modifica selezionati - - - - &About - &About - - - - About - Informazioni - - - - About Qt - Informazioni su Qt - - - - Close session - Chiudi sessione - - - - Do you want to save them now? - Do you want to save them now? - - - - MainWindowDebug - - - Registered cue menu: "{}" - Registered cue menu: "{}" - - - - MainWindowError - - - Cannot create cue {} - Cannot create cue {} - - - - MediaCueSettings - - - Start time - Posizione iniziale - - - - Stop position of the media - Posizione in cui la traccia si deve fermare - - - - Stop time - Posizione finale - - - - Start position of the media - Posizione da cui la traccia deve partire - - - - Loop - Loop - - - - MediaElementName - - - Pitch - Pitch - - - - Volume - Volume - - - - Audio Pan - Audio Pan - - - - Compressor/Expander - Compressor/Expander - - - - JACK Out - JACK Out - - - - URI Input - URI Input - - - - 10 Bands Equalizer - 10 Bands Equalizer - - - - ALSA Out - ALSA Out - - - - dB Meter - dB Meter - - - - Preset Input - Preset Input - - - - PulseAudio Out - PulseAudio Out - - - - Custom Element - Custom Element - - - - Speed - Speed - - - - System Out - System Out - - - - System Input - System Input - - - - MediaInfo - - - Media Info - Media Info - - - - Warning - Warning - - - - No info to display - No info to display - - - - Info - Info - - - - Value - Value - - - - NetworkApiDebug - - - New end-point: {} - New end-point: {} - - - - NetworkDiscovery - - - Host discovery - Host discovery - - - - Manage hosts - Manage hosts - - - - Discover hosts - Discover hosts - - - - Manually add a host - Manually add a host - - - - Remove selected host - Remove selected host - - - - Remove all host - Remove all host - - - - Address - Address - - - - Host IP - Host IP - - - - Osc Cue - - - Type - Type - - - - Argument - Argument - - - - OscCue - - - Add - Add - - - - Remove - Remove - - - - Type - Type - - - - Value - Value - - - - FadeTo - FadeTo - - - - Fade - Fade - - - - OSC Message - OSC Message - - - - OSC Path: - OSC Path: - - - - /path/to/something - /path/to/something - - - - Time (sec) - Time (sec) - - - - Curve - Curve - - - - OscServerDebug - - - Message from {} -> path: "{}" args: {} - Message from {} -> path: "{}" args: {} - - - - OscServerError - - - Cannot start OSC sever - Cannot start OSC sever - - - - OscServerInfo - - - OSC server started at {} - OSC server started at {} - - - - OSC server stopped - OSC server stopped - - - - OscSettings - - - OSC Settings - OSC Settings - - - - Input Port: - Input Port: + + Thread name + Thread name - - Output Port: - Output Port: + + Exception info + Exception info - - Hostname: - Hostname: + + Showing {} of {} records + Showing {} of {} records - PitchSettings + MIDICue - - Pitch - Pitch + + &File + &File - - {0:+} semitones - {0:+} semitones + + New session + Nuova sessione - PluginsError + MIDIMessageAttr - - Failed to load "{}" - Failed to load "{}" + + Open + Apri - - Failed to terminate plugin: "{}" - Failed to terminate plugin: "{}" + + Save session + Salva sessione - - the requested plugin is not loaded: {} - the requested plugin is not loaded: {} + + Preferences + Preferenze - - - PluginsInfo - - Plugin loaded: "{}" - Plugin loaded: "{}" + + Save as + Salva come - - Plugin terminated: "{}" - Plugin terminated: "{}" + + Full Screen + Schermo Intero - - - PluginsWarning - - Cannot satisfy dependencies for: {} - Cannot satisfy dependencies for: {} + + Exit + Esci - - - Preset - - Create Cue - Create Cue + + &Edit + &Modifica - - Load on selected Cues - Load on selected Cues + + Undo + Annulla - - - PresetSrcSettings - - Presets - Presets + + Redo + Ripeti - Presets - - - Cannot scan presets - Cannot scan presets - - - - Error while deleting preset "{}" - Error while deleting preset "{}" - - - - Cannot load preset "{}" - Cannot load preset "{}" - - - - Cannot save preset "{}" - Cannot save preset "{}" - - - - Cannot rename preset "{}" - Cannot rename preset "{}" - - - - Select Preset - Select Preset - - - - Presets - Presets - - - - Preset name - Preset name - - - - Add - Add - - - - Rename - Rename - - - - Edit - Edit - - - - Remove - Remove - - - - Export selected - Export selected - - - - Import - Import - - - - Warning - Warning - - - - The same name is already used! - The same name is already used! - - - - Cannot export correctly. - Cannot export correctly. - - - - Cannot import correctly. - Cannot import correctly. - - - - Cue type - Cue type - - - - Load on cue - Load on cue - - - - Load on selected cues - Load on selected cues - + MIDIMessageType - - Save as preset - Save as preset + + Select all + Seleziona tutti - - Cannot create a cue from this preset: {} - Cannot create a cue from this preset: {} + + Select all media cues + Seleziona tutte le media-cue - - - QColorButton - - Right click to reset - Clicca con il pulsante destro per resettare + + Deselect all + Deseleziona tutti - - - RenameCues - - Rename Cues - Rename Cues + + CTRL+SHIFT+A + CTRL+SHIFT+A - - Rename cues - Rename cues + + Invert selection + Inverti selezione - - Current - Current + + CTRL+I + CTRL+I - - Preview - Preview + + Edit selected + Modifica selezionati - - Capitalize - Capitalize + + CTRL+SHIFT+E + CTRL+SHIFT+E - - Lowercase - Lowercase + + &Layout + &Layout - - Uppercase - Uppercase + + &Tools + &Strumenti - - Remove Numbers - Remove Numbers + + Edit selection + Modifica selezionati - - Add numbering - Add numbering + + &About + &About - - Reset - Reset + + About + Informazioni - - Type your regex here: - Type your regex here: + + About Qt + Informazioni su Qt - - Regex help - Regex help + + Close session + Chiudi sessione - - - RenameCuesCommand - - Renamed {number} cues - Renamed {number} cues + + Do you want to save them now? + Do you want to save them now? - RenameUiDebug + MainWindowDebug - - Regex error: Invalid pattern - Regex error: Invalid pattern + + Registered cue menu: "{}" + Registered cue menu: "{}" - ReplayGain - - - ReplayGain / Normalization - ReplayGain / Normalization - - - - Threads number - Threads number - - - - Apply only to selected media - Apply only to selected media - - - - ReplayGain to (dB SPL) - ReplayGain to (dB SPL) - - - - Normalize to (dB) - Normalize to (dB) - - - - Processing files ... - Processing files ... - - - - Calculate - Calculate - - - - Reset all - Reset all - + MainWindowError - - Reset selected - Reset selected + + Cannot create cue {} + Cannot create cue {} - ReplayGainDebug + MediaCueSettings - - Applied gain for: {} - Applied gain for: {} + + Start time + Posizione iniziale - - Discarded gain for: {} - Discarded gain for: {} + + Stop position of the media + Posizione in cui la traccia si deve fermare - - - ReplayGainInfo - - Gain processing stopped by user. - Gain processing stopped by user. + + Stop time + Posizione finale - - Started gain calculation for: {} - Started gain calculation for: {} + + Start position of the media + Posizione da cui la traccia deve partire - - Gain calculated for: {} - Gain calculated for: {} + + Loop + Loop - SeekCue - - - Cue - Cue - - - - Click to select - Click to select - - - - Not selected - Not selected - - - - Seek - Seek - + QColorButton - - Time to reach - Time to reach + + Right click to reset + Clicca con il pulsante destro per resettare - SettingsPageName + PresetSrcSettings - - Appearance - Aspetto + + Presets + Presets diff --git a/lisp/i18n/ts/it_IT/list_layout.ts b/lisp/i18n/ts/it_IT/list_layout.ts index f18245946..bd1eda052 100644 --- a/lisp/i18n/ts/it_IT/list_layout.ts +++ b/lisp/i18n/ts/it_IT/list_layout.ts @@ -38,77 +38,67 @@ ListLayout - - Default behaviors - Default behaviors - - - - Show playing cues - Show playing cues - - - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue - + Enable selection mode Enable selection mode - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - + Remove cue Remove cue - + Selection mode Selection mode @@ -143,65 +133,105 @@ Fade-In all - + Edit selected Edit selected - + Clone cue Clone cue - + Clone selected Clone selected - + Remove selected Remove selected - + GO Key: GO Key: - + GO Action: GO Action: - + GO minimum interval (ms): GO minimum interval (ms): - + Copy of {} Copy of {} + + + Show index column + Show index column + + + + Show resize handles + Show resize handles + + + + Restore default size + Restore default size + + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + + + + Behaviors + Behaviors + + + + Disable GO Key While Playing + Disable GO Key While Playing + + + + Use waveform seek-bars + Use waveform seek-bars + + + + GO Key Disabled While Playing + GO Key Disabled While Playing + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait @@ -222,7 +252,7 @@ SettingsPageName - + List Layout List Layout diff --git a/lisp/i18n/ts/it_IT/media_info.ts b/lisp/i18n/ts/it_IT/media_info.ts index 2be0447c3..f3750b713 100644 --- a/lisp/i18n/ts/it_IT/media_info.ts +++ b/lisp/i18n/ts/it_IT/media_info.ts @@ -4,22 +4,17 @@ MediaInfo - + Media Info Informazioni Media - - No info to display - Nessuna informazione da mostrare - - - + Info Informazione - + Value Valore @@ -28,5 +23,10 @@ Warning Warning + + + Cannot get any information. + Cannot get any information. + diff --git a/lisp/i18n/ts/it_IT/midi.ts b/lisp/i18n/ts/it_IT/midi.ts index 8cc733e3e..a8ffc868f 100644 --- a/lisp/i18n/ts/it_IT/midi.ts +++ b/lisp/i18n/ts/it_IT/midi.ts @@ -4,7 +4,7 @@ CueCategory - + Integration cues Integration cues @@ -12,7 +12,7 @@ CueName - + MIDI Cue MIDI Cue @@ -30,50 +30,81 @@ Message type + + MIDIError + + + Cannot connect to MIDI output port '{}'. + Cannot connect to MIDI output port '{}'. + + + + Cannot connect to MIDI input port '{}'. + Cannot connect to MIDI input port '{}'. + + + + MIDIInfo + + + MIDI port disconnected: '{}' + MIDI port disconnected: '{}' + + + + Connecting to MIDI port: '{}' + Connecting to MIDI port: '{}' + + + + Connecting to matching MIDI port: '{}' + Connecting to matching MIDI port: '{}' + + MIDIMessageAttr - + Channel Channel - + Note Note - + Velocity Velocity - + Control Control - + Program Program - + Value Value - + Song Song - + Pitch Pitch - + Position Position @@ -81,62 +112,62 @@ MIDIMessageType - + Note ON Note ON - + Note OFF Note OFF - + Polyphonic After-touch Polyphonic After-touch - + Control/Mode Change Control/Mode Change - + Program Change Program Change - + Channel After-touch Channel After-touch - + Pitch Bend Change Pitch Bend Change - + Song Select Song Select - + Song Position Song Position - + Start Start - + Stop Stop - + Continue Continue @@ -144,30 +175,40 @@ MIDISettings - - MIDI default devices - Dispositivi MIDI predefiniti - - - + Input Ingresso - + Output Uscita + + + MIDI devices + MIDI devices + + + + Misc options + Misc options + + + + Try to connect using only device/port name + Try to connect using only device/port name + SettingsPageName - + MIDI settings Impostazioni MIDI - + MIDI Settings MIDI Settings diff --git a/lisp/i18n/ts/it_IT/network.ts b/lisp/i18n/ts/it_IT/network.ts index 9f372d313..82bff1d99 100644 --- a/lisp/i18n/ts/it_IT/network.ts +++ b/lisp/i18n/ts/it_IT/network.ts @@ -28,44 +28,49 @@ NetworkDiscovery - + Host discovery Host discovery - + Manage hosts Manage hosts - + Discover hosts Discover hosts - + Manually add a host Manually add a host - + Remove selected host Remove selected host - + Remove all host Remove all host - + Address Address - + Host IP Host IP + + + Select the hosts you want to add + Select the hosts you want to add + diff --git a/lisp/i18n/ts/it_IT/osc.ts b/lisp/i18n/ts/it_IT/osc.ts index 33aa40494..d9eae8133 100644 --- a/lisp/i18n/ts/it_IT/osc.ts +++ b/lisp/i18n/ts/it_IT/osc.ts @@ -12,7 +12,7 @@ CueCategory - + Integration cues Integration cues diff --git a/lisp/i18n/ts/it_IT/presets.ts b/lisp/i18n/ts/it_IT/presets.ts index cc555e0ad..d487e6cea 100644 --- a/lisp/i18n/ts/it_IT/presets.ts +++ b/lisp/i18n/ts/it_IT/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Crea Cue - + Load on selected Cues Carica sulle cue selezionate @@ -57,62 +57,62 @@ Seleziona Preset - + Preset name Nome preset - + Add Aggiungi - + Rename Rinomina - + Edit Modifica - + Remove Rimuovi - + Export selected Esporta selezionati - + Import Importa - + Warning Avviso - + The same name is already used! Lo stesso nome è già in uso! - + Cannot export correctly. Impossibile esportare correttamente. - + Cannot import correctly. Impossible importare correttamente. - + Cue type Tipo di cue @@ -127,7 +127,7 @@ Load on selected cues - + Cannot create a cue from this preset: {} Cannot create a cue from this preset: {} diff --git a/lisp/i18n/ts/it_IT/replay_gain.ts b/lisp/i18n/ts/it_IT/replay_gain.ts index e05921cba..be7803d1c 100644 --- a/lisp/i18n/ts/it_IT/replay_gain.ts +++ b/lisp/i18n/ts/it_IT/replay_gain.ts @@ -4,22 +4,22 @@ ReplayGain - + ReplayGain / Normalization ReplayGain / Normalizzazione - + Calculate Calcola - + Reset all Resetta tutto - + Reset selected Resetta selezionati @@ -52,12 +52,12 @@ ReplayGainDebug - + Applied gain for: {} Applied gain for: {} - + Discarded gain for: {} Discarded gain for: {} @@ -65,17 +65,17 @@ ReplayGainInfo - + Gain processing stopped by user. Gain processing stopped by user. - + Started gain calculation for: {} Started gain calculation for: {} - + Gain calculated for: {} Gain calculated for: {} diff --git a/lisp/i18n/ts/it_IT/synchronizer.ts b/lisp/i18n/ts/it_IT/synchronizer.ts index dbf3a9529..8f757d966 100644 --- a/lisp/i18n/ts/it_IT/synchronizer.ts +++ b/lisp/i18n/ts/it_IT/synchronizer.ts @@ -20,8 +20,8 @@ - Your IP is: - Il tuo IP è: + Your IP is: {} + Your IP is: {} diff --git a/lisp/i18n/ts/nl_BE/action_cues.ts b/lisp/i18n/ts/nl_BE/action_cues.ts index 05acd55ee..e52a200ec 100644 --- a/lisp/i18n/ts/nl_BE/action_cues.ts +++ b/lisp/i18n/ts/nl_BE/action_cues.ts @@ -55,7 +55,7 @@ CueCategory - + Action cues Action cues diff --git a/lisp/i18n/ts/nl_BE/cache_manager.ts b/lisp/i18n/ts/nl_BE/cache_manager.ts new file mode 100644 index 000000000..45f58f18a --- /dev/null +++ b/lisp/i18n/ts/nl_BE/cache_manager.ts @@ -0,0 +1,35 @@ + + + + + CacheManager + + + Cache size warning + Cache size warning + + + + Warning threshold in MB (0 = disabled) + Warning threshold in MB (0 = disabled) + + + + Cache cleanup + Cache cleanup + + + + Delete the cache content + Delete the cache content + + + + SettingsPageName + + + Cache Manager + Cache Manager + + + diff --git a/lisp/i18n/ts/nl_BE/cart_layout.ts b/lisp/i18n/ts/nl_BE/cart_layout.ts index 560f1f4d7..0b48ef824 100644 --- a/lisp/i18n/ts/nl_BE/cart_layout.ts +++ b/lisp/i18n/ts/nl_BE/cart_layout.ts @@ -4,37 +4,32 @@ CartLayout - - Default behaviors - Default behaviors - - - + Countdown mode Countdown mode - + Show seek-bars Show seek-bars - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show volume Show volume - + Grid size Grid size @@ -79,35 +74,40 @@ Number of Pages: - + Page {number} Page {number} - + Warning Warning - + Every cue in the page will be lost. Every cue in the page will be lost. - + Are you sure to continue? Are you sure to continue? - + Number of columns: Number of columns: - + Number of rows: Number of rows: + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + LayoutDescription diff --git a/lisp/i18n/ts/nl_BE/controller.ts b/lisp/i18n/ts/nl_BE/controller.ts index 7843c1ac5..42ddbb923 100644 --- a/lisp/i18n/ts/nl_BE/controller.ts +++ b/lisp/i18n/ts/nl_BE/controller.ts @@ -30,113 +30,136 @@ ControllerMidiSettings - + MIDI MIDI - + Type Type - - Channel - Kanaal + + Action + Actie - - Note - Notitie + + Capture + Neem over - - Action - Actie + + Listening MIDI messages ... + Wacht op MIDI boodschap ... - - Filter "note on" - Filter "note on" + + -- All Messages -- + -- All Messages -- - - Filter "note off" - Filter "note off" + + Capture filter + Capture filter - - Capture - Neem over + + Data 1 + Data 1 - - Listening MIDI messages ... - Wacht op MIDI boodschap ... + + Data 2 + Data 2 + + + + Data 3 + Data 3 ControllerOscSettings - + OSC Message OSC Message - + OSC OSC - + Path Path - + Types Types - + Arguments Arguments - - Actions - Actions - - - + OSC Capture OSC Capture - + Add Add - + Remove Remove - + Capture Capture + + + Waiting for messages: + Waiting for messages: + + + + /path/to/method + /path/to/method + + + + Action + Action + + + + ControllerOscSettingsWarning + + + Warning + Warning + ControllerSettings - + Add Toevoegen - + Remove Verwijder @@ -197,12 +220,12 @@ Osc Cue - + Type Type - + Argument Argument @@ -210,12 +233,12 @@ OscCue - + Add Add - + Remove Remove @@ -228,7 +251,7 @@ Cue bediening - + MIDI Controls MIDI bediening @@ -238,7 +261,7 @@ Toetsenbord snelkoppeling - + OSC Controls OSC Controls diff --git a/lisp/i18n/ts/nl_BE/gst_backend.ts b/lisp/i18n/ts/nl_BE/gst_backend.ts index 8c08a8c5f..90c50a1d6 100644 --- a/lisp/i18n/ts/nl_BE/gst_backend.ts +++ b/lisp/i18n/ts/nl_BE/gst_backend.ts @@ -119,7 +119,7 @@ Audio cue (from file) - + Select media files Select media files @@ -127,7 +127,7 @@ GstMediaError - + Cannot create pipeline element: "{}" Cannot create pipeline element: "{}" @@ -143,7 +143,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" Invalid pipeline element: "{}" @@ -151,7 +151,7 @@ GstPipelineEdit - + Edit Pipeline Bewerk regel lijn @@ -159,9 +159,14 @@ GstSettings - - Pipeline - Regel lijn + + Default pipeline + Default pipeline + + + + Applied only to new cues. + Applied only to new cues. @@ -255,7 +260,7 @@ Toonhoogte - + URI Input URI Ingang diff --git a/lisp/i18n/ts/nl_BE/lisp.ts b/lisp/i18n/ts/nl_BE/lisp.ts index ba4ffd719..c451c7d49 100644 --- a/lisp/i18n/ts/nl_BE/lisp.ts +++ b/lisp/i18n/ts/nl_BE/lisp.ts @@ -35,52 +35,36 @@ AboutDialog - + Web site Web site - + Source code Bron code - + Info Info - + License Licentie - + Contributors medewerker - + Discussion Discussion - - AlsaSinkSettings - - - ALSA device - ALSA device - - - - ApiServerError - - - Network API server stopped working. - Network API server stopped working. - - AppConfiguration @@ -92,277 +76,52 @@ AppGeneralSettings - + Default layout Default layout - - Enable startup layout selector - Enable startup layout selector - - - + Application themes Application themes - + UI theme: UI theme: - + Icons theme: Icons theme: - - Application language (require restart) - Application language (require restart) - - - + Language: Language: - - - ApplicationError - - - Startup error - Startup error - - - - AudioDynamicSettings - - - Compressor - Compressor - - - - Expander - Expander - - - - Soft Knee - Soft Knee - - - - Hard Knee - Hard Knee - - - - Compressor/Expander - Compressor/Expander - - - - Type - Type - - - - Curve Shape - Curve Shape - - - - Ratio - Ratio - - - - Threshold (dB) - Threshold (dB) - - - - AudioPanSettings - - - Audio Pan - Audio Pan - - - - Center - Center - - - - Left - Left - - - - Right - Right - - - - CartLayout - - - Default behaviors - Default behaviors - - - - Countdown mode - Countdown mode - - - - Show seek-bars - Show seek-bars - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Show accurate time - - - - Show volume - Show volume - - - - Grid size - Grid size - - - - Number of columns: - Number of columns: - - - - Number of rows: - Number of rows: - - - - Play - Play - - - - Pause - Pause - - - - Stop - Stop - - - - Reset volume - Reset volume - - - - Add page - Add page - - - - Add pages - Add pages - - - - Remove current page - Remove current page - - - - Number of Pages: - Number of Pages: - - - - Page {number} - Page {number} - - - - Warning - Warning - - - - Every cue in the page will be lost. - Every cue in the page will be lost. - - - - Are you sure to continue? - Are you sure to continue? - - - - CollectionCue - - - Add - Add - - - Remove - Remove + + Show layout selection at startup + Show layout selection at startup - - Cue - Cue + + Use layout at startup: + Use layout at startup: - - Action - Action + + Application language (require a restart) + Application language (require a restart) - CommandCue - - - Command - Command - - - - Command to execute, as in a shell - Command to execute, as in a shell - - - - Discard command output - Discard command output - - - - Ignore command errors - Ignore command errors - + ApplicationError - - Kill instead of terminate - Kill instead of terminate + + Startup error + Startup error @@ -646,17 +405,7 @@ CueCategory - - Action cues - Action cues - - - - Integration cues - Integration cues - - - + Misc cues Misc cues @@ -677,7 +426,7 @@ CueName - + Media Cue Media Cue @@ -798,72 +547,18 @@ Standaard Actie om de cue te stoppen - - Interrupt fade - Interrupt fade + + Interrupt action fade + Interrupt action fade - - Fade actions - Fade actions + + Fade actions default value + Fade actions default value CueTriggers - - - Started - Started - - - - Paused - Paused - - - - Stopped - Stopped - - - - Ended - Ended - - - - DbMeterSettings - - - DbMeter settings - DbMeter settings - - - - Time between levels (ms) - Time between levels (ms) - - - - Peak ttl (ms) - Peak ttl (ms) - - - - Peak falloff (dB/sec) - Peak falloff (dB/sec) - - - - Equalizer10Settings - - - 10 Bands Equalizer (IIR) - 10 Bands Equalizer (IIR) - - - - Fade Linear @@ -881,14 +576,14 @@ - FadeEdit + DbMeterSettings - + Duration (sec): Duration (sec): - + Curve: Curve: @@ -907,540 +602,133 @@ - GlobalAction - - - Go - Go - - - - Reset - Reset - - - - Stop all cues - Stop all cues - - - - Pause all cues - Pause all cues - - - - Resume all cues - Resume all cues - - - - Interrupt all cues - Interrupt all cues - + HotKeyEdit - - Fade-out all cues - Fade-out all cues + + Press shortcut + Press shortcut + + + LayoutSelect - - Fade-in all cues - Fade-in all cues + + Layout selection + Lay-out keuze - - Move standby forward - Move standby forward + + Select layout + kies lay-out - - Move standby back - Move standby back + + Open file + Bestand openen - GstBackend + ListLayout - - Audio cue (from file) - Audio cue (from file) + + Layout actions + Layout actions - - Select media files - Select media files + + Fade out when stopping all cues + Fade out when stopping all cues - - - GstMediaError - - Cannot create pipeline element: "{}" - Cannot create pipeline element: "{}" + + Fade out when interrupting all cues + Fade out when interrupting all cues - - - GstMediaSettings - - Change Pipeline - Change Pipeline + + Fade out when pausing all cues + Fade out when pausing all cues - - - GstMediaWarning - - Invalid pipeline element: "{}" - Invalid pipeline element: "{}" + + Fade in when resuming all cues + Fade in when resuming all cues - GstPipelineEdit + LogStatusIcon - - Edit Pipeline - Edit Pipeline + + Errors/Warnings + Errors/Warnings - GstSettings + Logging - - Pipeline - Pipeline + + Debug + Debuggen - - - HotKeyEdit - - Press shortcut - Press shortcut + + Warning + waarschuwing - - - IndexActionCue - - No suggestion - No suggestion + + Error + Fout - - Index - Index + + Dismiss all + Dismiss all - - Use a relative index - Use a relative index + + Show details + Show details - - Target index - Target index + + Linux Show Player - Log Viewer + Linux Show Player - Log Viewer - - Action - Action + + Info + Info - - Suggested cue name - Suggested cue name + + Critical + Critical - - - JackSinkSettings - - Connections - Connections + + Time + Time - - Edit connections - Edit connections + + Milliseconds + Milliseconds - - Output ports - Output ports + + Logger name + Logger name - - Input ports - Input ports + + Level + Level - - Connect - Connect - - - - Disconnect - Disconnect - - - - LayoutDescription - - - Organize the cues in a list - Organize the cues in a list - - - - Organize cues in grid like pages - Organize cues in grid like pages - - - - LayoutDetails - - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Space or Double-Click to edit a cue - - - - To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL - - - - To move cues drag them - To move cues drag them - - - - Click a cue to run it - Click a cue to run it - - - - SHIFT + Click to edit a cue - SHIFT + Click to edit a cue - - - - CTRL + Click to select a cue - CTRL + Click to select a cue - - - - To move cues drag them while pressing SHIFT - To move cues drag them while pressing SHIFT - - - - LayoutName - - - List Layout - List Layout - - - - Cart Layout - Cart Layout - - - - LayoutSelect - - - Layout selection - Lay-out keuze - - - - Select layout - kies lay-out - - - - Open file - Bestand openen - - - - ListLayout - - - Default behaviors - Default behaviors - - - - Show playing cues - Show playing cues - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Show accurate time - - - - Show seek-bars - Show seek-bars - - - - Auto-select next cue - Auto-select next cue - - - - Enable selection mode - Enable selection mode - - - - GO Key: - GO Key: - - - - GO Action: - GO Action: - - - - GO minimum interval (ms): - GO minimum interval (ms): - - - - Use fade (buttons) - Use fade (buttons) - - - - Stop Cue - Stop Cue - - - - Pause Cue - Pause Cue - - - - Resume Cue - Resume Cue - - - - Interrupt Cue - Interrupt Cue - - - - Edit cue - Edit cue - - - - Edit selected - Edit selected - - - - Clone cue - Clone cue - - - - Clone selected - Clone selected - - - - Remove cue - Remove cue - - - - Remove selected - Remove selected - - - - Selection mode - Selection mode - - - - Pause all - Pause all - - - - Stop all - Stop all - - - - Interrupt all - Interrupt all - - - - Resume all - Resume all - - - - Fade-Out all - Fade-Out all - - - - Fade-In all - Fade-In all - - - - Edit selected cues - Edit selected cues - - - - Remove selected cues - Remove selected cues - - - - Use fade - Use fade - - - - Copy of {} - Copy of {} - - - - ListLayoutHeader - - - Cue - Cue - - - - Pre wait - Pre wait - - - - Action - Action - - - - Post wait - Post wait - - - - ListLayoutInfoPanel - - - Cue name - Cue name - - - - Cue description - Cue description - - - - LogStatusIcon - - - Errors/Warnings - Errors/Warnings - - - - Logging - - - Debug - Debuggen - - - - Warning - waarschuwing - - - - Error - Fout - - - - Dismiss all - Dismiss all - - - - Show details - Show details - - - - Linux Show Player - Log Viewer - Linux Show Player - Log Viewer - - - - Info - Info - - - - Critical - Critical - - - - Time - Time - - - - Milliseconds - Milliseconds - - - - Logger name - Logger name - - - - Level - Level - - - - Message - Message + + Message + Message @@ -1492,151 +780,14 @@ Exception info Exception info - - - MIDICue - - - MIDI Message - MIDI Message - - - Message type - Message type + + Showing {} of {} records + Showing {} of {} records - MIDIMessageAttr - - - Channel - Channel - - - - Note - Note - - - - Velocity - Velocity - - - - Control - Control - - - - Program - Program - - - - Value - Value - - - - Song - Song - - - - Pitch - Pitch - - - - Position - Position - - - - MIDIMessageType - - - Note ON - Note ON - - - - Note OFF - Note OFF - - - - Polyphonic After-touch - Polyphonic After-touch - - - - Control/Mode Change - Control/Mode Change - - - - Program Change - Program Change - - - - Channel After-touch - Channel After-touch - - - - Pitch Bend Change - Pitch Bend Change - - - - Song Select - Song Select - - - - Song Position - Song Position - - - - Start - Start - - - - Stop - Stop - - - - Continue - Continue - - - - MIDISettings - - - MIDI default devices - MIDI default devices - - - - Input - Input - - - - Output - Output - - - - MainWindow + MIDICue &File @@ -1647,6 +798,9 @@ New session Nieuwe Sessie + + + MIDIMessageAttr Open @@ -1678,97 +832,100 @@ Afsluiten - + &Edit &Bewerken - + Undo Ongedaan maken - + Redo Opnieuw uitvoeren + + + MIDIMessageType - + Select all Alles selecteren - + Select all media cues Selecteer alle media cues - + Deselect all Alles deselecteren - + CTRL+SHIFT+A CTRL+SHIFT+A - + Invert selection Selectie inverteren - + CTRL+I CTRL+I - + Edit selected Bewerk geselecteerde cues - + CTRL+SHIFT+E CTRL+SHIFT+E - + &Layout &Lay-out - + &Tools &Tools - + Edit selection Bewerk selectie - + &About &Over - + About Over - + About Qt Over Qt - + Close session Sessie afsluiten - + Do you want to save them now? Do you want to save them now? @@ -1776,7 +933,7 @@ MainWindowDebug - + Registered cue menu: "{}" Registered cue menu: "{}" @@ -1784,7 +941,7 @@ MainWindowError - + Cannot create cue {} Cannot create cue {} @@ -1817,365 +974,28 @@ Herhaling - - MediaElementName - - - Pitch - Pitch - - - - Volume - Volume - - - - Audio Pan - Audio Pan - - - - Compressor/Expander - Compressor/Expander - - - - JACK Out - JACK Out - - - - URI Input - URI Input - - - - 10 Bands Equalizer - 10 Bands Equalizer - - - - ALSA Out - ALSA Out - - - - dB Meter - dB Meter - - - - Preset Input - Preset Input - - - - PulseAudio Out - PulseAudio Out - - - - Custom Element - Custom Element - - - - Speed - Speed - - - - System Out - System Out - - - - System Input - System Input - - - - MediaInfo - - - Media Info - Media Info - - - - Warning - Warning - - - - No info to display - No info to display - - - - Info - Info - - - - Value - Value - - - - NetworkApiDebug - - - New end-point: {} - New end-point: {} - - - - NetworkDiscovery - - - Host discovery - Host discovery - - - - Manage hosts - Manage hosts - - - - Discover hosts - Discover hosts - - - - Manually add a host - Manually add a host - - - - Remove selected host - Remove selected host - - - - Remove all host - Remove all host - - - - Address - Address - - - - Host IP - Host IP - - - - Osc Cue - - - Type - Type - - - - Argument - Argument - - - - OscCue - - - Add - Add - - - - Remove - Remove - - - - Type - Type - - - - Value - Value - - - - FadeTo - FadeTo - - - - Fade - Fade - - - - OSC Message - OSC Message - - - - OSC Path: - OSC Path: - - - - /path/to/something - /path/to/something - - - - Time (sec) - Time (sec) - - - - Curve - Curve - - - - OscServerDebug - - - Message from {} -> path: "{}" args: {} - Message from {} -> path: "{}" args: {} - - - - OscServerError - - - Cannot start OSC sever - Cannot start OSC sever - - - - OscServerInfo - - - OSC server started at {} - OSC server started at {} - - - - OSC server stopped - OSC server stopped - - - - OscSettings - - - OSC Settings - OSC Settings - - - - Input Port: - Input Port: - - - - Output Port: - Output Port: - - - - Hostname: - Hostname: - - - - PitchSettings - - - Pitch - Pitch - - - - {0:+} semitones - {0:+} semitones - - - - PluginsError - - - Failed to load "{}" - Failed to load "{}" - - - - Failed to terminate plugin: "{}" - Failed to terminate plugin: "{}" - - - - the requested plugin is not loaded: {} - the requested plugin is not loaded: {} - - - - PluginsInfo - - - Plugin loaded: "{}" - Plugin loaded: "{}" - - - - Plugin terminated: "{}" - Plugin terminated: "{}" - - - - PluginsWarning - - - Cannot satisfy dependencies for: {} - Cannot satisfy dependencies for: {} - - Preset - - Create Cue - Create Cue - - - - Load on selected Cues - Load on selected Cues + + Right click to reset + Rechts klikken om te resetten PresetSrcSettings - - Presets - Presets + + Appearance + Weergave Presets - - Cannot scan presets - Cannot scan presets + + General + Algemeen @@ -2183,9 +1003,9 @@ Error while deleting preset "{}" - - Cannot load preset "{}" - Cannot load preset "{}" + + Cue Settings + Cue instellingen diff --git a/lisp/i18n/ts/nl_BE/list_layout.ts b/lisp/i18n/ts/nl_BE/list_layout.ts index 1b499069b..afab10986 100644 --- a/lisp/i18n/ts/nl_BE/list_layout.ts +++ b/lisp/i18n/ts/nl_BE/list_layout.ts @@ -38,77 +38,67 @@ ListLayout - - Default behaviors - Default behaviors - - - - Show playing cues - Show playing cues - - - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue - + Enable selection mode Enable selection mode - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - + Remove cue Remove cue - + Selection mode Selection mode @@ -143,65 +133,105 @@ Fade-In all - + Edit selected Edit selected - + Clone cue Clone cue - + Clone selected Clone selected - + Remove selected Remove selected - + GO Key: GO Key: - + GO Action: GO Action: - + GO minimum interval (ms): GO minimum interval (ms): - + Copy of {} Copy of {} + + + Show index column + Show index column + + + + Show resize handles + Show resize handles + + + + Restore default size + Restore default size + + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + + + + Behaviors + Behaviors + + + + Disable GO Key While Playing + Disable GO Key While Playing + + + + Use waveform seek-bars + Use waveform seek-bars + + + + GO Key Disabled While Playing + GO Key Disabled While Playing + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait @@ -222,7 +252,7 @@ SettingsPageName - + List Layout List Layout diff --git a/lisp/i18n/ts/nl_BE/media_info.ts b/lisp/i18n/ts/nl_BE/media_info.ts index ee5f6774e..c2acd6993 100644 --- a/lisp/i18n/ts/nl_BE/media_info.ts +++ b/lisp/i18n/ts/nl_BE/media_info.ts @@ -4,22 +4,17 @@ MediaInfo - + Media Info Media info - - No info to display - Geen informatie om weer te geven - - - + Info Info - + Value Waarde @@ -28,5 +23,10 @@ Warning Warning + + + Cannot get any information. + Cannot get any information. + diff --git a/lisp/i18n/ts/nl_BE/midi.ts b/lisp/i18n/ts/nl_BE/midi.ts index ac771b298..22c189f31 100644 --- a/lisp/i18n/ts/nl_BE/midi.ts +++ b/lisp/i18n/ts/nl_BE/midi.ts @@ -4,7 +4,7 @@ CueCategory - + Integration cues Integration cues @@ -12,7 +12,7 @@ CueName - + MIDI Cue MIDI Cue @@ -30,50 +30,81 @@ Message type + + MIDIError + + + Cannot connect to MIDI output port '{}'. + Cannot connect to MIDI output port '{}'. + + + + Cannot connect to MIDI input port '{}'. + Cannot connect to MIDI input port '{}'. + + + + MIDIInfo + + + MIDI port disconnected: '{}' + MIDI port disconnected: '{}' + + + + Connecting to MIDI port: '{}' + Connecting to MIDI port: '{}' + + + + Connecting to matching MIDI port: '{}' + Connecting to matching MIDI port: '{}' + + MIDIMessageAttr - + Channel Channel - + Note Note - + Velocity Velocity - + Control Control - + Program Program - + Value Value - + Song Song - + Pitch Pitch - + Position Position @@ -81,62 +112,62 @@ MIDIMessageType - + Note ON Note ON - + Note OFF Note OFF - + Polyphonic After-touch Polyphonic After-touch - + Control/Mode Change Control/Mode Change - + Program Change Program Change - + Channel After-touch Channel After-touch - + Pitch Bend Change Pitch Bend Change - + Song Select Song Select - + Song Position Song Position - + Start Start - + Stop Stop - + Continue Continue @@ -144,30 +175,40 @@ MIDISettings - - MIDI default devices - Standaard MIDI apparaat - - - + Input Ingang - + Output Uitgang + + + MIDI devices + MIDI devices + + + + Misc options + Misc options + + + + Try to connect using only device/port name + Try to connect using only device/port name + SettingsPageName - + MIDI settings MIDI instellingen - + MIDI Settings MIDI Settings diff --git a/lisp/i18n/ts/nl_BE/network.ts b/lisp/i18n/ts/nl_BE/network.ts index aab98b429..85ed024ce 100644 --- a/lisp/i18n/ts/nl_BE/network.ts +++ b/lisp/i18n/ts/nl_BE/network.ts @@ -28,44 +28,49 @@ NetworkDiscovery - + Host discovery Host discovery - + Manage hosts Manage hosts - + Discover hosts Discover hosts - + Manually add a host Manually add a host - + Remove selected host Remove selected host - + Remove all host Remove all host - + Address Address - + Host IP Host IP + + + Select the hosts you want to add + Select the hosts you want to add + diff --git a/lisp/i18n/ts/nl_BE/osc.ts b/lisp/i18n/ts/nl_BE/osc.ts index 1ae0fee4a..af330acb7 100644 --- a/lisp/i18n/ts/nl_BE/osc.ts +++ b/lisp/i18n/ts/nl_BE/osc.ts @@ -12,7 +12,7 @@ CueCategory - + Integration cues Integration cues diff --git a/lisp/i18n/ts/nl_BE/presets.ts b/lisp/i18n/ts/nl_BE/presets.ts index 9f67c867d..0d7abdf57 100644 --- a/lisp/i18n/ts/nl_BE/presets.ts +++ b/lisp/i18n/ts/nl_BE/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Maak cue aan - + Load on selected Cues Toepassen op geselecteerde cue @@ -57,62 +57,62 @@ Selecteer Preset - + Preset name Preset naam - + Add Toevoegen - + Rename Hernoemen - + Edit Bewerken - + Remove Verwijderen - + Export selected Exporteer selectie - + Import Importeren - + Warning Waarschuwing - + The same name is already used! Deze naam is alreeds in gebruik! - + Cannot export correctly. Kan niet correct exporteren - + Cannot import correctly. Kan niet correct importeren - + Cue type Cue type @@ -127,7 +127,7 @@ Load on selected cues - + Cannot create a cue from this preset: {} Cannot create a cue from this preset: {} diff --git a/lisp/i18n/ts/nl_BE/replay_gain.ts b/lisp/i18n/ts/nl_BE/replay_gain.ts index 8b3f23808..549474b85 100644 --- a/lisp/i18n/ts/nl_BE/replay_gain.ts +++ b/lisp/i18n/ts/nl_BE/replay_gain.ts @@ -4,22 +4,22 @@ ReplayGain - + ReplayGain / Normalization Afspeelvolume / Normalisatie - + Calculate Bereken - + Reset all Reset alles - + Reset selected Reset selectie @@ -52,12 +52,12 @@ ReplayGainDebug - + Applied gain for: {} Applied gain for: {} - + Discarded gain for: {} Discarded gain for: {} @@ -65,17 +65,17 @@ ReplayGainInfo - + Gain processing stopped by user. Gain processing stopped by user. - + Started gain calculation for: {} Started gain calculation for: {} - + Gain calculated for: {} Gain calculated for: {} diff --git a/lisp/i18n/ts/nl_BE/synchronizer.ts b/lisp/i18n/ts/nl_BE/synchronizer.ts index b218b7e07..bea96cac1 100644 --- a/lisp/i18n/ts/nl_BE/synchronizer.ts +++ b/lisp/i18n/ts/nl_BE/synchronizer.ts @@ -20,8 +20,8 @@ - Your IP is: - Uw IP adres is: + Your IP is: {} + Your IP is: {} diff --git a/lisp/i18n/ts/nl_NL/action_cues.ts b/lisp/i18n/ts/nl_NL/action_cues.ts index b75973275..30ffd0194 100644 --- a/lisp/i18n/ts/nl_NL/action_cues.ts +++ b/lisp/i18n/ts/nl_NL/action_cues.ts @@ -55,7 +55,7 @@ CueCategory - + Action cues Action cues diff --git a/lisp/i18n/ts/nl_NL/cache_manager.ts b/lisp/i18n/ts/nl_NL/cache_manager.ts new file mode 100644 index 000000000..536017f1d --- /dev/null +++ b/lisp/i18n/ts/nl_NL/cache_manager.ts @@ -0,0 +1,35 @@ + + + + + CacheManager + + + Cache size warning + Cache size warning + + + + Warning threshold in MB (0 = disabled) + Warning threshold in MB (0 = disabled) + + + + Cache cleanup + Cache cleanup + + + + Delete the cache content + Delete the cache content + + + + SettingsPageName + + + Cache Manager + Cache Manager + + + diff --git a/lisp/i18n/ts/nl_NL/cart_layout.ts b/lisp/i18n/ts/nl_NL/cart_layout.ts index 590929271..03c5b30ae 100644 --- a/lisp/i18n/ts/nl_NL/cart_layout.ts +++ b/lisp/i18n/ts/nl_NL/cart_layout.ts @@ -4,37 +4,32 @@ CartLayout - - Default behaviors - Default behaviors - - - + Countdown mode Countdown mode - + Show seek-bars Show seek-bars - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show volume Show volume - + Grid size Grid size @@ -79,35 +74,40 @@ Number of Pages: - + Page {number} Page {number} - + Warning Warning - + Every cue in the page will be lost. Every cue in the page will be lost. - + Are you sure to continue? Are you sure to continue? - + Number of columns: Number of columns: - + Number of rows: Number of rows: + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + LayoutDescription diff --git a/lisp/i18n/ts/nl_NL/controller.ts b/lisp/i18n/ts/nl_NL/controller.ts index d5c2b6ef0..fd0d91a22 100644 --- a/lisp/i18n/ts/nl_NL/controller.ts +++ b/lisp/i18n/ts/nl_NL/controller.ts @@ -30,113 +30,136 @@ ControllerMidiSettings - + MIDI MIDI - + Type Type - - Channel - Kanaal + + Action + Actie - - Note - Aantekening + + Capture + Vastleggen - - Action - Actie + + Listening MIDI messages ... + Bezig met luisteren naar MIDI-berichten... - - Filter "note on" - Filteren op "aantekening aan" + + -- All Messages -- + -- All Messages -- - - Filter "note off" - Filteren op "aantekening uit" + + Capture filter + Capture filter - - Capture - Vastleggen + + Data 1 + Data 1 - - Listening MIDI messages ... - Bezig met luisteren naar MIDI-berichten... + + Data 2 + Data 2 + + + + Data 3 + Data 3 ControllerOscSettings - + OSC Message OSC Message - + OSC OSC - + Path Path - + Types Types - + Arguments Arguments - - Actions - Actions - - - + OSC Capture OSC Capture - + Add Add - + Remove Remove - + Capture Capture + + + Waiting for messages: + Waiting for messages: + + + + /path/to/method + /path/to/method + + + + Action + Action + + + + ControllerOscSettingsWarning + + + Warning + Warning + ControllerSettings - + Add Toevoegen - + Remove Verwijderen @@ -197,12 +220,12 @@ Osc Cue - + Type Type - + Argument Argument @@ -210,12 +233,12 @@ OscCue - + Add Add - + Remove Remove @@ -228,7 +251,7 @@ Cue-beheer - + MIDI Controls MIDI-beheer @@ -238,7 +261,7 @@ Sneltoetsen - + OSC Controls OSC Controls diff --git a/lisp/i18n/ts/nl_NL/gst_backend.ts b/lisp/i18n/ts/nl_NL/gst_backend.ts index 671bbd0d0..d52f75817 100644 --- a/lisp/i18n/ts/nl_NL/gst_backend.ts +++ b/lisp/i18n/ts/nl_NL/gst_backend.ts @@ -119,7 +119,7 @@ Audio cue (from file) - + Select media files Select media files @@ -127,7 +127,7 @@ GstMediaError - + Cannot create pipeline element: "{}" Cannot create pipeline element: "{}" @@ -143,7 +143,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" Invalid pipeline element: "{}" @@ -151,7 +151,7 @@ GstPipelineEdit - + Edit Pipeline Edit Pipeline @@ -159,9 +159,14 @@ GstSettings - - Pipeline - Pipeline + + Default pipeline + Default pipeline + + + + Applied only to new cues. + Applied only to new cues. @@ -255,7 +260,7 @@ Pitch - + URI Input URI Input diff --git a/lisp/i18n/ts/nl_NL/lisp.ts b/lisp/i18n/ts/nl_NL/lisp.ts index 48d3d69ec..478fbdbef 100644 --- a/lisp/i18n/ts/nl_NL/lisp.ts +++ b/lisp/i18n/ts/nl_NL/lisp.ts @@ -35,52 +35,36 @@ AboutDialog - + Web site Website - + Source code Broncode - + Info Informatie - + License Licentie - + Contributors Bijdragers - + Discussion Discussion - - AlsaSinkSettings - - - ALSA device - ALSA device - - - - ApiServerError - - - Network API server stopped working. - Network API server stopped working. - - AppConfiguration @@ -92,277 +76,52 @@ AppGeneralSettings - + Default layout Default layout - - Enable startup layout selector - Enable startup layout selector - - - + Application themes Application themes - + UI theme: UI theme: - + Icons theme: Icons theme: - - Application language (require restart) - Application language (require restart) - - - + Language: Language: - - - ApplicationError - - - Startup error - Startup error - - - - AudioDynamicSettings - - - Compressor - Compressor - - - - Expander - Expander - - - - Soft Knee - Soft Knee - - - - Hard Knee - Hard Knee - - - - Compressor/Expander - Compressor/Expander - - - - Type - Type - - - - Curve Shape - Curve Shape - - - - Ratio - Ratio - - - - Threshold (dB) - Threshold (dB) - - - - AudioPanSettings - - - Audio Pan - Audio Pan - - - - Center - Center - - - - Left - Left - - - - Right - Right - - - - CartLayout - - - Default behaviors - Default behaviors - - - - Countdown mode - Countdown mode - - - - Show seek-bars - Show seek-bars - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Show accurate time - - - - Show volume - Show volume - - - - Grid size - Grid size - - - - Number of columns: - Number of columns: - - - - Number of rows: - Number of rows: - - - - Play - Play - - - - Pause - Pause - - - - Stop - Stop - - - - Reset volume - Reset volume - - - Add page - Add page + + Show layout selection at startup + Show layout selection at startup - - Add pages - Add pages + + Use layout at startup: + Use layout at startup: - - Remove current page - Remove current page - - - - Number of Pages: - Number of Pages: - - - - Page {number} - Page {number} - - - - Warning - Warning - - - - Every cue in the page will be lost. - Every cue in the page will be lost. - - - - Are you sure to continue? - Are you sure to continue? - - - - CollectionCue - - - Add - Add - - - - Remove - Remove - - - - Cue - Cue - - - - Action - Action + + Application language (require a restart) + Application language (require a restart) - CommandCue - - - Command - Command - - - - Command to execute, as in a shell - Command to execute, as in a shell - - - - Discard command output - Discard command output - - - - Ignore command errors - Ignore command errors - + ApplicationError - - Kill instead of terminate - Kill instead of terminate + + Startup error + Startup error @@ -393,157 +152,6 @@ New configuration installed at {} New configuration installed at {} - - - Controller - - - Cannot load controller protocol: "{}" - Cannot load controller protocol: "{}" - - - - ControllerKeySettings - - - Action - Action - - - - Shortcuts - Shortcuts - - - - Shortcut - Shortcut - - - - ControllerMidiSettings - - - MIDI - MIDI - - - - Type - Type - - - - Channel - Channel - - - - Note - Note - - - - Action - Action - - - - Filter "note on" - Filter "note on" - - - - Filter "note off" - Filter "note off" - - - - Capture - Capture - - - - Listening MIDI messages ... - Listening MIDI messages ... - - - - ControllerOscSettings - - - OSC Message - OSC Message - - - - OSC - OSC - - - - Path - Path - - - - Types - Types - - - - Arguments - Arguments - - - - Actions - Actions - - - - OSC Capture - OSC Capture - - - - Add - Add - - - - Remove - Remove - - - - Capture - Capture - - - - ControllerSettings - - - Add - Add - - - - Remove - Remove - - - - Cue Name - - - OSC Settings - OSC Settings - - - - CueAction Default @@ -627,6 +235,9 @@ Set Font Size Lettertypegrootte instellen + + + CommandsStack Color @@ -637,6 +248,9 @@ Select background color Achtergrondkleur instellen + + + ConfigurationDebug Select font color @@ -646,17 +260,7 @@ CueCategory - - Action cues - Action cues - - - - Integration cues - Integration cues - - - + Misc cues Misc cues @@ -675,74 +279,34 @@ - CueName + ConfigurationInfo - + Media Cue Mediacue + + + Controller - - Index Action - Index Action + + Do Nothing + Do Nothing - - Seek Cue - Seek Cue + + Trigger after the end + Trigger after the end - - Volume Control - Volume Control + + Trigger after post wait + Trigger after post wait - - Command Cue - Command Cue - - - - Collection Cue - Collection Cue - - - - Stop-All - Stop-All - - - - MIDI Cue - MIDI Cue - - - - OSC Cue - OSC Cue - - - - CueNextAction - - - Do Nothing - Do Nothing - - - - Trigger after the end - Trigger after the end - - - - Trigger after post wait - Trigger after post wait - - - - Select after the end - Select after the end + + Select after the end + Select after the end @@ -751,7 +315,7 @@ - CueSettings + ControllerMidiSettings Pre wait @@ -798,72 +362,15 @@ Standaard actie om de cue te stoppen - - Interrupt fade - Interrupt fade + + Interrupt action fade + Interrupt action fade - - Fade actions - Fade actions + + Fade actions default value + Fade actions default value - - - CueTriggers - - - Started - Started - - - - Paused - Paused - - - - Stopped - Stopped - - - - Ended - Ended - - - - DbMeterSettings - - - DbMeter settings - DbMeter settings - - - - Time between levels (ms) - Time between levels (ms) - - - - Peak ttl (ms) - Peak ttl (ms) - - - - Peak falloff (dB/sec) - Peak falloff (dB/sec) - - - - Equalizer10Settings - - - 10 Bands Equalizer (IIR) - 10 Bands Equalizer (IIR) - - - - Fade Linear @@ -881,20 +388,20 @@ - FadeEdit + ControllerSettings - + Duration (sec): Duration (sec): - + Curve: Curve: - FadeSettings + Cue Name Fade In @@ -906,112 +413,6 @@ Uitregelen - - GlobalAction - - - Go - Go - - - - Reset - Reset - - - - Stop all cues - Stop all cues - - - - Pause all cues - Pause all cues - - - - Resume all cues - Resume all cues - - - - Interrupt all cues - Interrupt all cues - - - - Fade-out all cues - Fade-out all cues - - - - Fade-in all cues - Fade-in all cues - - - - Move standby forward - Move standby forward - - - - Move standby back - Move standby back - - - - GstBackend - - - Audio cue (from file) - Audio cue (from file) - - - - Select media files - Select media files - - - - GstMediaError - - - Cannot create pipeline element: "{}" - Cannot create pipeline element: "{}" - - - - GstMediaSettings - - - Change Pipeline - Change Pipeline - - - - GstMediaWarning - - - Invalid pipeline element: "{}" - Invalid pipeline element: "{}" - - - - GstPipelineEdit - - - Edit Pipeline - Edit Pipeline - - - - GstSettings - - - Pipeline - Pipeline - - HotKeyEdit @@ -1019,103 +420,6 @@ Press shortcut Press shortcut - - - IndexActionCue - - - No suggestion - No suggestion - - - - Index - Index - - - - Use a relative index - Use a relative index - - - - Target index - Target index - - - - Action - Action - - - - Suggested cue name - Suggested cue name - - - - JackSinkSettings - - - Connections - Connections - - - - Edit connections - Edit connections - - - - Output ports - Output ports - - - - Input ports - Input ports - - - - Connect - Connect - - - - Disconnect - Disconnect - - - - LayoutDescription - - - Organize the cues in a list - Organize the cues in a list - - - - Organize cues in grid like pages - Organize cues in grid like pages - - - - LayoutDetails - - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Space or Double-Click to edit a cue - - - - To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL - - - - To move cues drag them - To move cues drag them - Click a cue to run it @@ -2233,632 +1537,434 @@ Remove - - Export selected - Export selected - - - - Import - Import - - - - Warning - Warning - - - - The same name is already used! - The same name is already used! + + Layout selection + Indelingselectie - - Cannot export correctly. - Cannot export correctly. + + Select layout + Indeling selecteren - - Cannot import correctly. - Cannot import correctly. + + Open file + Bestand openen - - Cue type - Cue type + + Layout actions + Layout actions - - Load on cue - Load on cue + + Fade out when stopping all cues + Fade out when stopping all cues - - Load on selected cues - Load on selected cues + + Fade out when interrupting all cues + Fade out when interrupting all cues - - Save as preset - Save as preset + + Fade out when pausing all cues + Fade out when pausing all cues - - Cannot create a cue from this preset: {} - Cannot create a cue from this preset: {} + + Fade in when resuming all cues + Fade in when resuming all cues - QColorButton + LogStatusIcon - - Right click to reset - Rechtsklikken om te herstellen + + Errors/Warnings + Errors/Warnings - RenameCues - - - Rename Cues - Rename Cues - - - - Rename cues - Rename cues - - - - Current - Current - - - - Preview - Preview - - - - Capitalize - Capitalize - - - - Lowercase - Lowercase - - - - Uppercase - Uppercase - - - - Remove Numbers - Remove Numbers - - - - Add numbering - Add numbering - + SeekCue - - Reset - Reset + + Debug + Foutopsporing - - Type your regex here: - Type your regex here: + + Warning + Waarschuwing - - Regex help - Regex help + + Error + Fout - - - RenameCuesCommand - - Renamed {number} cues - Renamed {number} cues + + Dismiss all + Dismiss all - - - RenameUiDebug - - Regex error: Invalid pattern - Regex error: Invalid pattern + + Show details + Show details - - - ReplayGain - - ReplayGain / Normalization - ReplayGain / Normalization + + Linux Show Player - Log Viewer + Linux Show Player - Log Viewer - - Threads number - Threads number + + Info + Info - - Apply only to selected media - Apply only to selected media + + Critical + Critical - - ReplayGain to (dB SPL) - ReplayGain to (dB SPL) + + Time + Time - - Normalize to (dB) - Normalize to (dB) + + Milliseconds + Milliseconds - - Processing files ... - Processing files ... + + Logger name + Logger name - - Calculate - Calculate + + Level + Level - - Reset all - Reset all + + Message + Message - - Reset selected - Reset selected + + Function + Function - - - ReplayGainDebug - - Applied gain for: {} - Applied gain for: {} + + Path name + Path name - - Discarded gain for: {} - Discarded gain for: {} + + File name + File name - - - ReplayGainInfo - - Gain processing stopped by user. - Gain processing stopped by user. + + Line no. + Line no. - - Started gain calculation for: {} - Started gain calculation for: {} + + Module + Module - - Gain calculated for: {} - Gain calculated for: {} + + Process ID + Process ID - - - SeekCue - - Cue - Cue + + Process name + Process name - - Click to select - Click to select + + Thread ID + Thread ID - - Not selected - Not selected + + Thread name + Thread name - - Seek - Seek + + Exception info + Exception info - - Time to reach - Time to reach + + Showing {} of {} records + Showing {} of {} records - SettingsPageName - - - Appearance - Uiterlijk - - - - General - Algemeen - - - - Cue - Cue - - - - Cue Settings - Cue-instellingen - - - - Plugins - Plugins - - - - Behaviours - Behaviours - - - - Pre/Post Wait - Pre/Post Wait - - - - Fade In/Out - Fade In/Out - - - - Layouts - Layouts - - - - Action Settings - Action Settings - - - - Seek Settings - Seek Settings - - - - Volume Settings - Volume Settings - - - - Command - Command - - - - Edit Collection - Edit Collection - - - - Stop Settings - Stop Settings - - - - List Layout - List Layout - - - - Timecode - Timecode - - - - Timecode Settings - Timecode Settings - - - - Cue Control - Cue Control - + MainWindow - - Layout Controls - Layout Controls + + &File + &Bestand: - - MIDI Controls - MIDI Controls + + New session + Nieuwe sessie - - Keyboard Shortcuts - Keyboard Shortcuts + + Open + Openen - - OSC Controls - OSC Controls + + Save session + Sessie opslaan - - MIDI Settings - MIDI Settings + + Preferences + Voorkeuren - - MIDI settings - MIDI settings + + Save as + Opslaan als - - GStreamer - GStreamer + + Full Screen + Volledig scherm - - Media Settings - Media Settings + + Exit + Afsluiten - - Triggers - Triggers + + &Edit + B&ewerken - - OSC settings - OSC settings + + Undo + Ongedaan maken - - Cart Layout - Cart Layout + + Redo + Opnieuw SpeedSettings - - Speed - Speed + + Select all + Alles selecteren StopAll - - Stop Action - Stop Action + + Select all media cues + Alle mediacues selecteren Synchronizer - - Synchronization - Synchronization + + Deselect all + Alles deselecteren - - Manage connected peers - Manage connected peers + + CTRL+SHIFT+A + CTRL+SHIFT+A - - Show your IP - Show your IP + + Invert selection + Selectie omkeren - - Your IP is: - Your IP is: + + CTRL+I + CTRL+I Timecode - - Cannot load timecode protocol: "{}" - Cannot load timecode protocol: "{}" + + Edit selected + Geselecteerde bewerken TimecodeError - - Cannot send timecode. - Cannot send timecode. + + CTRL+SHIFT+E + CTRL+SHIFT+E TimecodeSettings - - Replace HOURS by a static track number - Replace HOURS by a static track number - - - - Enable Timecode - Enable Timecode - - - - Track number - Track number - - - - Timecode Settings - Timecode Settings + + &Layout + Inde&ling - - Timecode Format: - Timecode Format: + + &Tools + &Hulpmiddelen - - Timecode Protocol: - Timecode Protocol: + + Edit selection + Selectie bewerken - - - TriggersSettings - - Add - Add + + &About + &Over - - Remove - Remove + + About + Over - - Trigger - Trigger + + About Qt + Over Qt - - Cue - Cue + + Close session + Sessie sluiten - - Action - Action + + Do you want to save them now? + Do you want to save them now? - UriInputSettings - - - Source - Source - + MainWindowDebug - - Find File - Find File + + Registered cue menu: "{}" + Registered cue menu: "{}" + + + MainWindowError - - Buffering - Buffering + + Cannot create cue {} + Cannot create cue {} - - Use Buffering - Use Buffering + + Start time + Starttijd - - Attempt download on network streams - Attempt download on network streams + + Stop position of the media + Stoppositie van de media - - Buffer size (-1 default value) - Buffer size (-1 default value) + + Stop time + Stoptijd - - Choose file - Choose file + + Start position of the media + Startpositie van de media - - All files - All files + + Loop + Herhalen UserElementSettings - - User defined elements - User defined elements - - - - Only for advanced user! - Only for advanced user! + + Right click to reset + Rechtsklikken om te herstellen VolumeControl - - Cue - Cue + + Appearance + Uiterlijk - - Click to select - Click to select + + General + Algemeen - - Not selected - Not selected + + Cue + Cue - - Volume to reach - Volume to reach + + Cue Settings + Cue-instellingen - - Fade - Fade + + Plugins + Plugins VolumeControlError - - Error during cue execution. - Error during cue execution. + + Behaviours + Behaviours VolumeSettings - - Volume - Volume + + Pre/Post Wait + Pre/Post Wait - - Normalized volume - Normalized volume + + Fade In/Out + Fade In/Out - - Reset - Reset + + Layouts + Layouts diff --git a/lisp/i18n/ts/nl_NL/list_layout.ts b/lisp/i18n/ts/nl_NL/list_layout.ts index 534150ecf..6efddea50 100644 --- a/lisp/i18n/ts/nl_NL/list_layout.ts +++ b/lisp/i18n/ts/nl_NL/list_layout.ts @@ -38,77 +38,67 @@ ListLayout - - Default behaviors - Default behaviors - - - - Show playing cues - Show playing cues - - - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue - + Enable selection mode Enable selection mode - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - + Remove cue Remove cue - + Selection mode Selection mode @@ -143,65 +133,105 @@ Fade-In all - + Edit selected Edit selected - + Clone cue Clone cue - + Clone selected Clone selected - + Remove selected Remove selected - + GO Key: GO Key: - + GO Action: GO Action: - + GO minimum interval (ms): GO minimum interval (ms): - + Copy of {} Copy of {} + + + Show index column + Show index column + + + + Show resize handles + Show resize handles + + + + Restore default size + Restore default size + + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + + + + Behaviors + Behaviors + + + + Disable GO Key While Playing + Disable GO Key While Playing + + + + Use waveform seek-bars + Use waveform seek-bars + + + + GO Key Disabled While Playing + GO Key Disabled While Playing + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait @@ -222,7 +252,7 @@ SettingsPageName - + List Layout List Layout diff --git a/lisp/i18n/ts/nl_NL/media_info.ts b/lisp/i18n/ts/nl_NL/media_info.ts index ce7600bf2..ae9b5feaa 100644 --- a/lisp/i18n/ts/nl_NL/media_info.ts +++ b/lisp/i18n/ts/nl_NL/media_info.ts @@ -4,22 +4,17 @@ MediaInfo - + Media Info Media-informatie - - No info to display - Er is geen weer te geven informatie - - - + Info Informatie - + Value Waarde @@ -28,5 +23,10 @@ Warning Warning + + + Cannot get any information. + Cannot get any information. + diff --git a/lisp/i18n/ts/nl_NL/midi.ts b/lisp/i18n/ts/nl_NL/midi.ts index 5b0698a65..807137a43 100644 --- a/lisp/i18n/ts/nl_NL/midi.ts +++ b/lisp/i18n/ts/nl_NL/midi.ts @@ -4,7 +4,7 @@ CueCategory - + Integration cues Integration cues @@ -12,7 +12,7 @@ CueName - + MIDI Cue MIDI Cue @@ -30,50 +30,81 @@ Message type + + MIDIError + + + Cannot connect to MIDI output port '{}'. + Cannot connect to MIDI output port '{}'. + + + + Cannot connect to MIDI input port '{}'. + Cannot connect to MIDI input port '{}'. + + + + MIDIInfo + + + MIDI port disconnected: '{}' + MIDI port disconnected: '{}' + + + + Connecting to MIDI port: '{}' + Connecting to MIDI port: '{}' + + + + Connecting to matching MIDI port: '{}' + Connecting to matching MIDI port: '{}' + + MIDIMessageAttr - + Channel Channel - + Note Note - + Velocity Velocity - + Control Control - + Program Program - + Value Value - + Song Song - + Pitch Pitch - + Position Position @@ -81,62 +112,62 @@ MIDIMessageType - + Note ON Note ON - + Note OFF Note OFF - + Polyphonic After-touch Polyphonic After-touch - + Control/Mode Change Control/Mode Change - + Program Change Program Change - + Channel After-touch Channel After-touch - + Pitch Bend Change Pitch Bend Change - + Song Select Song Select - + Song Position Song Position - + Start Start - + Stop Stop - + Continue Continue @@ -144,30 +175,40 @@ MIDISettings - - MIDI default devices - MIDI - standaard apparaten - - - + Input Invoer - + Output Uitvoer + + + MIDI devices + MIDI devices + + + + Misc options + Misc options + + + + Try to connect using only device/port name + Try to connect using only device/port name + SettingsPageName - + MIDI settings MIDI-instellingen - + MIDI Settings MIDI Settings diff --git a/lisp/i18n/ts/nl_NL/network.ts b/lisp/i18n/ts/nl_NL/network.ts index b34390dd4..fd0800f48 100644 --- a/lisp/i18n/ts/nl_NL/network.ts +++ b/lisp/i18n/ts/nl_NL/network.ts @@ -28,44 +28,49 @@ NetworkDiscovery - + Host discovery Host discovery - + Manage hosts Manage hosts - + Discover hosts Discover hosts - + Manually add a host Manually add a host - + Remove selected host Remove selected host - + Remove all host Remove all host - + Address Address - + Host IP Host IP + + + Select the hosts you want to add + Select the hosts you want to add + diff --git a/lisp/i18n/ts/nl_NL/osc.ts b/lisp/i18n/ts/nl_NL/osc.ts index e9cd98f42..ec3769004 100644 --- a/lisp/i18n/ts/nl_NL/osc.ts +++ b/lisp/i18n/ts/nl_NL/osc.ts @@ -12,7 +12,7 @@ CueCategory - + Integration cues Integration cues diff --git a/lisp/i18n/ts/nl_NL/presets.ts b/lisp/i18n/ts/nl_NL/presets.ts index 80586a0db..18b83b9c6 100644 --- a/lisp/i18n/ts/nl_NL/presets.ts +++ b/lisp/i18n/ts/nl_NL/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Cue creëren - + Load on selected Cues Laden op geselecteerde cues @@ -57,62 +57,62 @@ Voorinstelling selecteren - + Preset name Naam van voorinstelling - + Add Toevoegen - + Rename Naam wijzigen - + Edit Bewerken - + Remove Verwijderen - + Export selected Geselecteerde exporteren - + Import Importeren - + Warning Waarschuwing - + The same name is already used! De naam is al in gebruik! - + Cannot export correctly. Er kan niet correct worden geëxporteerd. - + Cannot import correctly. Er kan niet correct worden geïmporteerd. - + Cue type Cue-type @@ -127,7 +127,7 @@ Load on selected cues - + Cannot create a cue from this preset: {} Cannot create a cue from this preset: {} diff --git a/lisp/i18n/ts/nl_NL/replay_gain.ts b/lisp/i18n/ts/nl_NL/replay_gain.ts index 41c89f1f8..9fe84e276 100644 --- a/lisp/i18n/ts/nl_NL/replay_gain.ts +++ b/lisp/i18n/ts/nl_NL/replay_gain.ts @@ -4,22 +4,22 @@ ReplayGain - + ReplayGain / Normalization ReplayGain / Normalisatie - + Calculate Berekenen - + Reset all Alles herstellen - + Reset selected Geselecteerde herstellen @@ -52,12 +52,12 @@ ReplayGainDebug - + Applied gain for: {} Applied gain for: {} - + Discarded gain for: {} Discarded gain for: {} @@ -65,17 +65,17 @@ ReplayGainInfo - + Gain processing stopped by user. Gain processing stopped by user. - + Started gain calculation for: {} Started gain calculation for: {} - + Gain calculated for: {} Gain calculated for: {} diff --git a/lisp/i18n/ts/nl_NL/synchronizer.ts b/lisp/i18n/ts/nl_NL/synchronizer.ts index e48d834db..9e4f55134 100644 --- a/lisp/i18n/ts/nl_NL/synchronizer.ts +++ b/lisp/i18n/ts/nl_NL/synchronizer.ts @@ -20,8 +20,8 @@ - Your IP is: - Uw IP is: + Your IP is: {} + Your IP is: {} diff --git a/lisp/i18n/ts/sl_SI/action_cues.ts b/lisp/i18n/ts/sl_SI/action_cues.ts index f4e464a3e..6dbabd036 100644 --- a/lisp/i18n/ts/sl_SI/action_cues.ts +++ b/lisp/i18n/ts/sl_SI/action_cues.ts @@ -55,7 +55,7 @@ CueCategory - + Action cues Action cues diff --git a/lisp/i18n/ts/sl_SI/cache_manager.ts b/lisp/i18n/ts/sl_SI/cache_manager.ts new file mode 100644 index 000000000..64259e0f4 --- /dev/null +++ b/lisp/i18n/ts/sl_SI/cache_manager.ts @@ -0,0 +1,35 @@ + + + + + CacheManager + + + Cache size warning + Cache size warning + + + + Warning threshold in MB (0 = disabled) + Warning threshold in MB (0 = disabled) + + + + Cache cleanup + Cache cleanup + + + + Delete the cache content + Delete the cache content + + + + SettingsPageName + + + Cache Manager + Cache Manager + + + diff --git a/lisp/i18n/ts/sl_SI/cart_layout.ts b/lisp/i18n/ts/sl_SI/cart_layout.ts index b9a2c8016..3ca6f0037 100644 --- a/lisp/i18n/ts/sl_SI/cart_layout.ts +++ b/lisp/i18n/ts/sl_SI/cart_layout.ts @@ -4,37 +4,32 @@ CartLayout - - Default behaviors - Default behaviors - - - + Countdown mode Countdown mode - + Show seek-bars Show seek-bars - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show volume Show volume - + Grid size Grid size @@ -79,35 +74,40 @@ Number of Pages: - + Page {number} Page {number} - + Warning Warning - + Every cue in the page will be lost. Every cue in the page will be lost. - + Are you sure to continue? Are you sure to continue? - + Number of columns: Number of columns: - + Number of rows: Number of rows: + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + LayoutDescription diff --git a/lisp/i18n/ts/sl_SI/controller.ts b/lisp/i18n/ts/sl_SI/controller.ts index a9308c75e..31fe97940 100644 --- a/lisp/i18n/ts/sl_SI/controller.ts +++ b/lisp/i18n/ts/sl_SI/controller.ts @@ -30,113 +30,136 @@ ControllerMidiSettings - + MIDI MIDI - + Type Tip - - Channel - Kanal + + Action + Akcija - - Note - Beležka + + Capture + Zajemi - - Action - Akcija + + Listening MIDI messages ... + Poslušam MIDI sporočila ... - - Filter "note on" - Filter "Beležka aktivna" + + -- All Messages -- + -- All Messages -- - - Filter "note off" - Filter "Beležka neaktivna" + + Capture filter + Capture filter - - Capture - Zajemi + + Data 1 + Data 1 - - Listening MIDI messages ... - Poslušam MIDI sporočila ... + + Data 2 + Data 2 + + + + Data 3 + Data 3 ControllerOscSettings - + OSC Message OSC Message - + OSC OSC - + Path Path - + Types Types - + Arguments Arguments - - Actions - Actions - - - + OSC Capture OSC Capture - + Add Add - + Remove Remove - + Capture Capture + + + Waiting for messages: + Waiting for messages: + + + + /path/to/method + /path/to/method + + + + Action + Action + + + + ControllerOscSettingsWarning + + + Warning + Warning + ControllerSettings - + Add Dodaj - + Remove Odstrani @@ -197,12 +220,12 @@ Osc Cue - + Type Type - + Argument Argument @@ -210,12 +233,12 @@ OscCue - + Add Add - + Remove Remove @@ -228,7 +251,7 @@ Krmiljenje vrste - + MIDI Controls Krmiljenje MIDI @@ -238,7 +261,7 @@ Bližnjice na tipkovnici - + OSC Controls OSC Controls diff --git a/lisp/i18n/ts/sl_SI/gst_backend.ts b/lisp/i18n/ts/sl_SI/gst_backend.ts index eb1ee0e3a..85a5e8ea1 100644 --- a/lisp/i18n/ts/sl_SI/gst_backend.ts +++ b/lisp/i18n/ts/sl_SI/gst_backend.ts @@ -119,7 +119,7 @@ Audio cue (from file) - + Select media files Select media files @@ -127,7 +127,7 @@ GstMediaError - + Cannot create pipeline element: "{}" Cannot create pipeline element: "{}" @@ -143,7 +143,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" Invalid pipeline element: "{}" @@ -151,7 +151,7 @@ GstPipelineEdit - + Edit Pipeline Uredi cevovod @@ -159,9 +159,14 @@ GstSettings - - Pipeline - Cevovod + + Default pipeline + Default pipeline + + + + Applied only to new cues. + Applied only to new cues. @@ -255,7 +260,7 @@ Višina - + URI Input URI vhod diff --git a/lisp/i18n/ts/sl_SI/lisp.ts b/lisp/i18n/ts/sl_SI/lisp.ts index f5d494d90..e27ea6898 100644 --- a/lisp/i18n/ts/sl_SI/lisp.ts +++ b/lisp/i18n/ts/sl_SI/lisp.ts @@ -35,52 +35,36 @@ AboutDialog - + Web site Spletna stran - + Source code Izvorna koda - + Info Podrobnosti - + License Licenca - + Contributors Prispevkarji - + Discussion Discussion - - AlsaSinkSettings - - - ALSA device - ALSA device - - - - ApiServerError - - - Network API server stopped working. - Network API server stopped working. - - AppConfiguration @@ -92,277 +76,52 @@ AppGeneralSettings - + Default layout Default layout - - Enable startup layout selector - Enable startup layout selector - - - + Application themes Application themes - + UI theme: UI theme: - + Icons theme: Icons theme: - - Application language (require restart) - Application language (require restart) - - - + Language: Language: - - - ApplicationError - - - Startup error - Startup error - - - - AudioDynamicSettings - - - Compressor - Compressor - - - - Expander - Expander - - - - Soft Knee - Soft Knee - - - - Hard Knee - Hard Knee - - - - Compressor/Expander - Compressor/Expander - - - - Type - Type - - - - Curve Shape - Curve Shape - - - - Ratio - Ratio - - - - Threshold (dB) - Threshold (dB) - - - - AudioPanSettings - - - Audio Pan - Audio Pan - - - - Center - Center - - - - Left - Left - - - - Right - Right - - - - CartLayout - - - Default behaviors - Default behaviors - - - - Countdown mode - Countdown mode - - - - Show seek-bars - Show seek-bars - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Show accurate time - - - - Show volume - Show volume - - - - Grid size - Grid size - - - - Number of columns: - Number of columns: - - - - Number of rows: - Number of rows: - - - - Play - Play - - - - Pause - Pause - - - - Stop - Stop - - - - Reset volume - Reset volume - - - - Add page - Add page - - - - Add pages - Add pages - - - - Remove current page - Remove current page - - - - Number of Pages: - Number of Pages: - - - - Page {number} - Page {number} - - - - Warning - Warning - - - Every cue in the page will be lost. - Every cue in the page will be lost. + + Show layout selection at startup + Show layout selection at startup - - Are you sure to continue? - Are you sure to continue? - - - - CollectionCue - - - Add - Add - - - - Remove - Remove - - - - Cue - Cue + + Use layout at startup: + Use layout at startup: - - Action - Action + + Application language (require a restart) + Application language (require a restart) - CommandCue - - - Command - Command - - - - Command to execute, as in a shell - Command to execute, as in a shell - - - - Discard command output - Discard command output - - - - Ignore command errors - Ignore command errors - + ApplicationError - - Kill instead of terminate - Kill instead of terminate + + Startup error + Startup error @@ -646,17 +405,7 @@ CueCategory - - Action cues - Action cues - - - - Integration cues - Integration cues - - - + Misc cues Misc cues @@ -677,7 +426,7 @@ CueName - + Media Cue Medijska vrsta @@ -798,14 +547,14 @@ Privzeta akcija ob ustavitvi vrste - - Interrupt fade - Interrupt fade + + Interrupt action fade + Interrupt action fade - - Fade actions - Fade actions + + Fade actions default value + Fade actions default value @@ -883,12 +632,12 @@ FadeEdit - + Duration (sec): Duration (sec): - + Curve: Curve: @@ -907,1385 +656,351 @@ - GlobalAction - - - Go - Go - - - - Reset - Reset - - - - Stop all cues - Stop all cues - - - - Pause all cues - Pause all cues - - - - Resume all cues - Resume all cues - - - - Interrupt all cues - Interrupt all cues - + HotKeyEdit - - Fade-out all cues - Fade-out all cues + + Press shortcut + Press shortcut + + + LayoutSelect - - Fade-in all cues - Fade-in all cues + + Layout selection + Izbor razporeditve - - Move standby forward - Move standby forward + + Select layout + Izberi razporeditev - - Move standby back - Move standby back + + Open file + Odpri datoteko - GstBackend + ListLayout - - Audio cue (from file) - Audio cue (from file) + + Layout actions + Layout actions - - Select media files - Select media files + + Fade out when stopping all cues + Fade out when stopping all cues - - - GstMediaError - - Cannot create pipeline element: "{}" - Cannot create pipeline element: "{}" + + Fade out when interrupting all cues + Fade out when interrupting all cues - - - GstMediaSettings - - Change Pipeline - Change Pipeline + + Fade out when pausing all cues + Fade out when pausing all cues - - - GstMediaWarning - - Invalid pipeline element: "{}" - Invalid pipeline element: "{}" + + Fade in when resuming all cues + Fade in when resuming all cues - GstPipelineEdit + LogStatusIcon - - Edit Pipeline - Edit Pipeline + + Errors/Warnings + Errors/Warnings - GstSettings + Logging - - Pipeline - Pipeline + + Debug + Razhroščevanje - - - HotKeyEdit - - Press shortcut - Press shortcut + + Warning + Opozorilo - - - IndexActionCue - - No suggestion - No suggestion + + Error + Napaka - - Index - Index + + Dismiss all + Dismiss all - - Use a relative index - Use a relative index + + Show details + Show details - - Target index - Target index + + Linux Show Player - Log Viewer + Linux Show Player - Log Viewer - - Action - Action + + Info + Info - - Suggested cue name - Suggested cue name + + Critical + Critical - - - JackSinkSettings - - Connections - Connections + + Time + Time - - Edit connections - Edit connections + + Milliseconds + Milliseconds - - Output ports - Output ports + + Logger name + Logger name - - Input ports - Input ports + + Level + Level - - Connect - Connect + + Message + Message - - Disconnect - Disconnect + + Function + Function - - - LayoutDescription - - Organize the cues in a list - Organize the cues in a list + + Path name + Path name - - Organize cues in grid like pages - Organize cues in grid like pages + + File name + File name - - - LayoutDetails - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Space or Double-Click to edit a cue + + Line no. + Line no. - - To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL + + Module + Module - - To move cues drag them - To move cues drag them + + Process ID + Process ID - - Click a cue to run it - Click a cue to run it + + Process name + Process name - - SHIFT + Click to edit a cue - SHIFT + Click to edit a cue + + Thread ID + Thread ID - - CTRL + Click to select a cue - CTRL + Click to select a cue - - - - To move cues drag them while pressing SHIFT - To move cues drag them while pressing SHIFT - - - - LayoutName - - - List Layout - List Layout - - - - Cart Layout - Cart Layout - - - - LayoutSelect - - - Layout selection - Izbor razporeditve - - - - Select layout - Izberi razporeditev - - - - Open file - Odpri datoteko - - - - ListLayout - - - Default behaviors - Default behaviors - - - - Show playing cues - Show playing cues - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Show accurate time - - - - Show seek-bars - Show seek-bars - - - - Auto-select next cue - Auto-select next cue - - - - Enable selection mode - Enable selection mode - - - - GO Key: - GO Key: - - - - GO Action: - GO Action: - - - - GO minimum interval (ms): - GO minimum interval (ms): - - - - Use fade (buttons) - Use fade (buttons) - - - - Stop Cue - Stop Cue - - - - Pause Cue - Pause Cue - - - - Resume Cue - Resume Cue - - - - Interrupt Cue - Interrupt Cue - - - - Edit cue - Edit cue - - - - Edit selected - Edit selected - - - - Clone cue - Clone cue - - - - Clone selected - Clone selected - - - - Remove cue - Remove cue - - - - Remove selected - Remove selected - - - - Selection mode - Selection mode - - - - Pause all - Pause all - - - - Stop all - Stop all - - - - Interrupt all - Interrupt all - - - - Resume all - Resume all - - - - Fade-Out all - Fade-Out all - - - - Fade-In all - Fade-In all - - - - Edit selected cues - Edit selected cues - - - - Remove selected cues - Remove selected cues - - - - Use fade - Use fade - - - - Copy of {} - Copy of {} - - - - ListLayoutHeader - - - Cue - Cue - - - - Pre wait - Pre wait - - - - Action - Action - - - - Post wait - Post wait - - - - ListLayoutInfoPanel - - - Cue name - Cue name - - - - Cue description - Cue description - - - - LogStatusIcon - - - Errors/Warnings - Errors/Warnings - - - - Logging - - - Debug - Razhroščevanje - - - - Warning - Opozorilo - - - - Error - Napaka - - - - Dismiss all - Dismiss all - - - - Show details - Show details - - - - Linux Show Player - Log Viewer - Linux Show Player - Log Viewer - - - - Info - Info - - - - Critical - Critical - - - - Time - Time - - - - Milliseconds - Milliseconds - - - - Logger name - Logger name - - - - Level - Level - - - - Message - Message - - - - Function - Function - - - - Path name - Path name - - - - File name - File name - - - - Line no. - Line no. - - - - Module - Module - - - - Process ID - Process ID - - - - Process name - Process name - - - - Thread ID - Thread ID - - - - Thread name - Thread name - - - - Exception info - Exception info - - - - MIDICue - - - MIDI Message - MIDI Message - - - - Message type - Message type - - - - MIDIMessageAttr - - - Channel - Channel - - - - Note - Note - - - - Velocity - Velocity - - - - Control - Control - - - - Program - Program - - - - Value - Value - - - - Song - Song - - - - Pitch - Pitch - - - - Position - Position - - - - MIDIMessageType - - - Note ON - Note ON - - - - Note OFF - Note OFF - - - - Polyphonic After-touch - Polyphonic After-touch - - - - Control/Mode Change - Control/Mode Change - - - - Program Change - Program Change - - - - Channel After-touch - Channel After-touch - - - - Pitch Bend Change - Pitch Bend Change - - - - Song Select - Song Select - - - - Song Position - Song Position - - - - Start - Start - - - - Stop - Stop - - - - Continue - Continue - - - - MIDISettings - - - MIDI default devices - MIDI default devices - - - - Input - Input - - - - Output - Output - - - - MainWindow - - - &File - &Datoteka - - - - New session - Nova seja - - - - Open - Odpri - - - - Save session - Shrani sejo - - - - Preferences - Nastavitve - - - - Save as - Shrani kot - - - - Full Screen - Polni zaslon - - - - Exit - Izhod - - - - &Edit - &Uredi - - - - Undo - Razveljavi - - - - Redo - Obnovi - - - - Select all - Izberi vse - - - - Select all media cues - Izberi vse medijske vrste - - - - Deselect all - Od izberi vse - - - - CTRL+SHIFT+A - CTRL+SHIFT+A - - - - Invert selection - Invertiraj izbor - - - - CTRL+I - CTRL+I - - - - Edit selected - Uredi izbrano - - - - CTRL+SHIFT+E - CTRL+SHIFT+E - - - - &Layout - &Razporeditev - - - - &Tools - &Orodja - - - - Edit selection - Uredi izbor - - - - &About - &Opis - - - - About - Opis - - - - About Qt - Opis Qt - - - - Close session - Zapri sejo - - - - Do you want to save them now? - Do you want to save them now? - - - - MainWindowDebug - - - Registered cue menu: "{}" - Registered cue menu: "{}" - - - - MainWindowError - - - Cannot create cue {} - Cannot create cue {} - - - - MediaCueSettings - - - Start time - Čas začetka - - - - Stop position of the media - Zaustavitvena pozicija medija - - - - Stop time - Čas zaustavitve - - - - Start position of the media - Začetna pozicija medija - - - - Loop - Ponavljaj - - - - MediaElementName - - - Pitch - Pitch - - - - Volume - Volume - - - - Audio Pan - Audio Pan - - - - Compressor/Expander - Compressor/Expander - - - - JACK Out - JACK Out - - - - URI Input - URI Input - - - - 10 Bands Equalizer - 10 Bands Equalizer - - - - ALSA Out - ALSA Out - - - - dB Meter - dB Meter - - - - Preset Input - Preset Input - - - - PulseAudio Out - PulseAudio Out - - - - Custom Element - Custom Element - - - - Speed - Speed - - - - System Out - System Out - - - - System Input - System Input - - - - MediaInfo - - - Media Info - Media Info - - - - Warning - Warning - - - - No info to display - No info to display - - - - Info - Info - - - - Value - Value - - - - NetworkApiDebug - - - New end-point: {} - New end-point: {} - - - - NetworkDiscovery - - - Host discovery - Host discovery - - - - Manage hosts - Manage hosts - - - - Discover hosts - Discover hosts - - - - Manually add a host - Manually add a host - - - - Remove selected host - Remove selected host - - - - Remove all host - Remove all host + + Thread name + Thread name - - Address - Address + + Exception info + Exception info - - Host IP - Host IP + + Showing {} of {} records + Showing {} of {} records - Osc Cue + MIDICue - - Type - Type + + &File + &Datoteka - - Argument - Argument + + New session + Nova seja - OscCue - - - Add - Add - - - - Remove - Remove - - - - Type - Type - + MIDIMessageAttr - - Value - Value + + Open + Odpri - - FadeTo - FadeTo + + Save session + Shrani sejo - - Fade - Fade + + Preferences + Nastavitve - - OSC Message - OSC Message + + Save as + Shrani kot - - OSC Path: - OSC Path: + + Full Screen + Polni zaslon - - /path/to/something - /path/to/something + + Exit + Izhod - - Time (sec) - Time (sec) + + &Edit + &Uredi - - Curve - Curve + + Undo + Razveljavi - - - OscServerDebug - - Message from {} -> path: "{}" args: {} - Message from {} -> path: "{}" args: {} + + Redo + Obnovi - OscServerError + MIDIMessageType - - Cannot start OSC sever - Cannot start OSC sever + + Select all + Izberi vse - - - OscServerInfo - - OSC server started at {} - OSC server started at {} + + Select all media cues + Izberi vse medijske vrste - - OSC server stopped - OSC server stopped + + Deselect all + Od izberi vse - - - OscSettings - - OSC Settings - OSC Settings + + CTRL+SHIFT+A + CTRL+SHIFT+A - - Input Port: - Input Port: + + Invert selection + Invertiraj izbor - - Output Port: - Output Port: + + CTRL+I + CTRL+I - - Hostname: - Hostname: + + Edit selected + Uredi izbrano - - - PitchSettings - - Pitch - Pitch + + CTRL+SHIFT+E + CTRL+SHIFT+E - - {0:+} semitones - {0:+} semitones + + &Layout + &Razporeditev - - - PluginsError - - Failed to load "{}" - Failed to load "{}" + + &Tools + &Orodja - - Failed to terminate plugin: "{}" - Failed to terminate plugin: "{}" + + Edit selection + Uredi izbor - - the requested plugin is not loaded: {} - the requested plugin is not loaded: {} + + &About + &Opis - PluginsInfo - - - Plugin loaded: "{}" - Plugin loaded: "{}" - + MIDISettings - - Plugin terminated: "{}" - Plugin terminated: "{}" + + About + Opis - - - PluginsWarning - - Cannot satisfy dependencies for: {} - Cannot satisfy dependencies for: {} + + About Qt + Opis Qt - - - Preset - - Create Cue - Create Cue + + Close session + Zapri sejo - - Load on selected Cues - Load on selected Cues + + Do you want to save them now? + Do you want to save them now? - PresetSrcSettings + MainWindowDebug - - Presets - Presets + + Registered cue menu: "{}" + Registered cue menu: "{}" - Presets - - - Cannot scan presets - Cannot scan presets - - - - Error while deleting preset "{}" - Error while deleting preset "{}" - - - - Cannot load preset "{}" - Cannot load preset "{}" - - - - Cannot save preset "{}" - Cannot save preset "{}" - - - - Cannot rename preset "{}" - Cannot rename preset "{}" - - - - Select Preset - Select Preset - - - - Presets - Presets - - - - Preset name - Preset name - - - - Add - Add - - - - Rename - Rename - - - - Edit - Edit - - - - Remove - Remove - - - - Export selected - Export selected - - - - Import - Import - - - - Warning - Warning - - - - The same name is already used! - The same name is already used! - - - - Cannot export correctly. - Cannot export correctly. - - - - Cannot import correctly. - Cannot import correctly. - - - - Cue type - Cue type - - - - Load on cue - Load on cue - - - - Load on selected cues - Load on selected cues - - - - Save as preset - Save as preset - + MainWindowError - - Cannot create a cue from this preset: {} - Cannot create a cue from this preset: {} + + Cannot create cue {} + Cannot create cue {} @@ -2360,126 +1075,11 @@ - RenameCuesCommand - - - Renamed {number} cues - Renamed {number} cues - - - - RenameUiDebug - - - Regex error: Invalid pattern - Regex error: Invalid pattern - - - - ReplayGain - - - ReplayGain / Normalization - ReplayGain / Normalization - - - - Threads number - Threads number - - - - Apply only to selected media - Apply only to selected media - - - - ReplayGain to (dB SPL) - ReplayGain to (dB SPL) - - - - Normalize to (dB) - Normalize to (dB) - - - - Processing files ... - Processing files ... - - - - Calculate - Calculate - - - - Reset all - Reset all - - - - Reset selected - Reset selected - - - - ReplayGainDebug - - - Applied gain for: {} - Applied gain for: {} - - - - Discarded gain for: {} - Discarded gain for: {} - - - - ReplayGainInfo - - - Gain processing stopped by user. - Gain processing stopped by user. - - - - Started gain calculation for: {} - Started gain calculation for: {} - - - - Gain calculated for: {} - Gain calculated for: {} - - - - SeekCue - - - Cue - Cue - - - - Click to select - Click to select - - - - Not selected - Not selected - - - - Seek - Seek - + QColorButton - - Time to reach - Time to reach + + Right click to reset + Desno klik za ponastavitev diff --git a/lisp/i18n/ts/sl_SI/list_layout.ts b/lisp/i18n/ts/sl_SI/list_layout.ts index 981d4c09e..35a1ee628 100644 --- a/lisp/i18n/ts/sl_SI/list_layout.ts +++ b/lisp/i18n/ts/sl_SI/list_layout.ts @@ -38,77 +38,67 @@ ListLayout - - Default behaviors - Default behaviors - - - - Show playing cues - Show playing cues - - - + Show dB-meters Show dB-meters - + Show accurate time Show accurate time - + Show seek-bars Show seek-bars - + Auto-select next cue Auto-select next cue - + Enable selection mode Enable selection mode - + Use fade (buttons) Use fade (buttons) - + Stop Cue Stop Cue - + Pause Cue Pause Cue - + Resume Cue Resume Cue - + Interrupt Cue Interrupt Cue - + Edit cue Edit cue - + Remove cue Remove cue - + Selection mode Selection mode @@ -143,65 +133,105 @@ Fade-In all - + Edit selected Edit selected - + Clone cue Clone cue - + Clone selected Clone selected - + Remove selected Remove selected - + GO Key: GO Key: - + GO Action: GO Action: - + GO minimum interval (ms): GO minimum interval (ms): - + Copy of {} Copy of {} + + + Show index column + Show index column + + + + Show resize handles + Show resize handles + + + + Restore default size + Restore default size + + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + + + + Behaviors + Behaviors + + + + Disable GO Key While Playing + Disable GO Key While Playing + + + + Use waveform seek-bars + Use waveform seek-bars + + + + GO Key Disabled While Playing + GO Key Disabled While Playing + ListLayoutHeader - + Cue Cue - + Pre wait Pre wait - + Action Action - + Post wait Post wait @@ -222,7 +252,7 @@ SettingsPageName - + List Layout List Layout diff --git a/lisp/i18n/ts/sl_SI/media_info.ts b/lisp/i18n/ts/sl_SI/media_info.ts index ba9e6220e..cbc7ec770 100644 --- a/lisp/i18n/ts/sl_SI/media_info.ts +++ b/lisp/i18n/ts/sl_SI/media_info.ts @@ -4,22 +4,17 @@ MediaInfo - + Media Info Podrobnosti o mediju - - No info to display - Brez podrobnosti za prikaz - - - + Info Podrobnosti - + Value Vrednost @@ -28,5 +23,10 @@ Warning Warning + + + Cannot get any information. + Cannot get any information. + diff --git a/lisp/i18n/ts/sl_SI/midi.ts b/lisp/i18n/ts/sl_SI/midi.ts index 9bf6515d8..2cf859001 100644 --- a/lisp/i18n/ts/sl_SI/midi.ts +++ b/lisp/i18n/ts/sl_SI/midi.ts @@ -4,7 +4,7 @@ CueCategory - + Integration cues Integration cues @@ -12,7 +12,7 @@ CueName - + MIDI Cue MIDI Cue @@ -30,50 +30,81 @@ Message type + + MIDIError + + + Cannot connect to MIDI output port '{}'. + Cannot connect to MIDI output port '{}'. + + + + Cannot connect to MIDI input port '{}'. + Cannot connect to MIDI input port '{}'. + + + + MIDIInfo + + + MIDI port disconnected: '{}' + MIDI port disconnected: '{}' + + + + Connecting to MIDI port: '{}' + Connecting to MIDI port: '{}' + + + + Connecting to matching MIDI port: '{}' + Connecting to matching MIDI port: '{}' + + MIDIMessageAttr - + Channel Channel - + Note Note - + Velocity Velocity - + Control Control - + Program Program - + Value Value - + Song Song - + Pitch Pitch - + Position Position @@ -81,62 +112,62 @@ MIDIMessageType - + Note ON Note ON - + Note OFF Note OFF - + Polyphonic After-touch Polyphonic After-touch - + Control/Mode Change Control/Mode Change - + Program Change Program Change - + Channel After-touch Channel After-touch - + Pitch Bend Change Pitch Bend Change - + Song Select Song Select - + Song Position Song Position - + Start Start - + Stop Stop - + Continue Continue @@ -144,30 +175,40 @@ MIDISettings - - MIDI default devices - Privzete MIDI naprave - - - + Input Vhod - + Output Izhod + + + MIDI devices + MIDI devices + + + + Misc options + Misc options + + + + Try to connect using only device/port name + Try to connect using only device/port name + SettingsPageName - + MIDI settings MIDI nastavitve - + MIDI Settings MIDI Settings diff --git a/lisp/i18n/ts/sl_SI/network.ts b/lisp/i18n/ts/sl_SI/network.ts index 4210f94b8..d1ca16e24 100644 --- a/lisp/i18n/ts/sl_SI/network.ts +++ b/lisp/i18n/ts/sl_SI/network.ts @@ -28,44 +28,49 @@ NetworkDiscovery - + Host discovery Host discovery - + Manage hosts Manage hosts - + Discover hosts Discover hosts - + Manually add a host Manually add a host - + Remove selected host Remove selected host - + Remove all host Remove all host - + Address Address - + Host IP Host IP + + + Select the hosts you want to add + Select the hosts you want to add + diff --git a/lisp/i18n/ts/sl_SI/osc.ts b/lisp/i18n/ts/sl_SI/osc.ts index 5fe972e67..80ff1ce10 100644 --- a/lisp/i18n/ts/sl_SI/osc.ts +++ b/lisp/i18n/ts/sl_SI/osc.ts @@ -12,7 +12,7 @@ CueCategory - + Integration cues Integration cues diff --git a/lisp/i18n/ts/sl_SI/presets.ts b/lisp/i18n/ts/sl_SI/presets.ts index d2bc1ea19..1456fd51a 100644 --- a/lisp/i18n/ts/sl_SI/presets.ts +++ b/lisp/i18n/ts/sl_SI/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Ustvari vrsto - + Load on selected Cues Naloži izbrane vrste @@ -57,62 +57,62 @@ Izberi pred-nastavitve - + Preset name Ime pred-nastavitve - + Add Dodaj - + Rename Preimenuj - + Edit Uredi - + Remove Odstrani - + Export selected Izvozi izbrano - + Import Uvozi - + Warning Opozorilo - + The same name is already used! Ime je že v uporabi! - + Cannot export correctly. Ne morem pravilno izvozit. - + Cannot import correctly. Ne morem pravilno uvozit. - + Cue type Tip vrste @@ -127,7 +127,7 @@ Load on selected cues - + Cannot create a cue from this preset: {} Cannot create a cue from this preset: {} diff --git a/lisp/i18n/ts/sl_SI/replay_gain.ts b/lisp/i18n/ts/sl_SI/replay_gain.ts index aaaf8a842..7f1fffd0b 100644 --- a/lisp/i18n/ts/sl_SI/replay_gain.ts +++ b/lisp/i18n/ts/sl_SI/replay_gain.ts @@ -4,22 +4,22 @@ ReplayGain - + ReplayGain / Normalization Ojačanje predvajanja / Normalizacija - + Calculate Izračunaj - + Reset all Ponastavi vse - + Reset selected Ponastavi izbrano @@ -52,12 +52,12 @@ ReplayGainDebug - + Applied gain for: {} Applied gain for: {} - + Discarded gain for: {} Discarded gain for: {} @@ -65,17 +65,17 @@ ReplayGainInfo - + Gain processing stopped by user. Gain processing stopped by user. - + Started gain calculation for: {} Started gain calculation for: {} - + Gain calculated for: {} Gain calculated for: {} diff --git a/lisp/i18n/ts/sl_SI/synchronizer.ts b/lisp/i18n/ts/sl_SI/synchronizer.ts index 8180cf6f0..478cef97f 100644 --- a/lisp/i18n/ts/sl_SI/synchronizer.ts +++ b/lisp/i18n/ts/sl_SI/synchronizer.ts @@ -20,8 +20,8 @@ - Your IP is: - Tvoj IP je: + Your IP is: {} + Your IP is: {} diff --git a/lisp/i18n/ts/zh_CN/action_cues.ts b/lisp/i18n/ts/zh_CN/action_cues.ts new file mode 100644 index 000000000..7a9c0014d --- /dev/null +++ b/lisp/i18n/ts/zh_CN/action_cues.ts @@ -0,0 +1,234 @@ + + + + + CollectionCue + + + Add + 添加 + + + + Remove + 移除 + + + + Cue + Cue + + + + Action + 动作 + + + + CommandCue + + + Command + 指令 + + + + Command to execute, as in a shell + 要执行的 Shell 指令 + + + + Discard command output + 舍弃指令输出 + + + + Ignore command errors + 忽略指令错误 + + + + Kill instead of terminate + 传送 kil l讯号而非 terminate 讯号 + + + + CueCategory + + + Action cues + 动作 Cues + + + + CueName + + + Command Cue + 指令 Cue + + + + Volume Control + 音量控制 + + + + Seek Cue + 跳转 Cue + + + + Collection Cue + 动作集合 Cue + + + + Stop-All + 全部停止 + + + + Index Action + 索引动作 + + + + IndexActionCue + + + Index + 索引 + + + + Use a relative index + 使用相对索引 + + + + Target index + 目标索引 + + + + Action + 动作 + + + + No suggestion + 没有建议 + + + + Suggested cue name + 建议的 Cue 名称 + + + + SeekCue + + + Cue + Cue + + + + Click to select + 点击以选择 + + + + Not selected + 未选择 + + + + Seek + 跳转 + + + + Time to reach + 跳到时间 + + + + SettingsPageName + + + Command + 指令 + + + + Volume Settings + 音量设定 + + + + Seek Settings + 跳转设定 + + + + Edit Collection + 编辑动作集合 + + + + Action Settings + 动作设定 + + + + Stop Settings + 停止设定 + + + + StopAll + + + Stop Action + 停止方式 + + + + VolumeControl + + + Cue + Cue + + + + Click to select + 点击以选择 + + + + Not selected + 未选择 + + + + Volume to reach + 目标音量 + + + + Fade + 淡入淡出 + + + + VolumeControlError + + + Error during cue execution. + 执行 Cue 期间发生错误 + + + diff --git a/lisp/i18n/ts/zh_CN/cache_manager.ts b/lisp/i18n/ts/zh_CN/cache_manager.ts new file mode 100644 index 000000000..653524516 --- /dev/null +++ b/lisp/i18n/ts/zh_CN/cache_manager.ts @@ -0,0 +1,35 @@ + + + + + CacheManager + + + Cache size warning + 缓存大小警告 + + + + Warning threshold in MB (0 = disabled) + 警告阀值 (MB为单位) (0 = 停用) + + + + Cache cleanup + 清除缓存 + + + + Delete the cache content + 清除缓存 + + + + SettingsPageName + + + Cache Manager + 缓存管理 + + + diff --git a/lisp/i18n/ts/zh_CN/cart_layout.ts b/lisp/i18n/ts/zh_CN/cart_layout.ts new file mode 100644 index 000000000..3ad458a74 --- /dev/null +++ b/lisp/i18n/ts/zh_CN/cart_layout.ts @@ -0,0 +1,187 @@ + + + + + CartLayout + + + Countdown mode + 倒计时模式 + + + + Show seek-bars + 显示进度条 + + + + Show dB-meters + 显示分貝计 + + + + Show accurate time + 显示准确时间 + + + + Show volume + 显示音量 + + + + Grid size + 网格大小 + + + + Play + 播放 + + + + Pause + 暂停 + + + + Stop + 停止 + + + + Reset volume + 重置音量 + + + + Add page + 添加分页 + + + + Add pages + 添加多个分页 + + + + Remove current page + 移除当前分页 + + + + Number of Pages: + 页数: + + + + Page {number} + 第 {number} 页 + + + + Warning + 警告 + + + + Every cue in the page will be lost. + 分页中的所有 Cue 都会被删除 + + + + Are you sure to continue? + 是否确认继续? + + + + Number of columns: + 列数: + + + + Number of rows: + 行数: + + + + Default behaviors (applied to new sessions) + 默认行为 (套用于新工作阶段) + + + + LayoutDescription + + + Organize cues in grid like pages + 把 Cue 排列在网格分页 + + + + LayoutDetails + + + Click a cue to run it + 滑鼠左按以执行 Cue + + + + SHIFT + Click to edit a cue + SHIFT + 滑鼠左按以编辑 Cue + + + + CTRL + Click to select a cue + CTRL + 滑鼠左按以选择 Cue + + + + To copy cues drag them while pressing CTRL + 按着 CTRL 然后拖拉 Cue 以复制 Cue + + + + To move cues drag them while pressing SHIFT + 按着 SHIFT 然后拖拉 Cue 以移动 Cue + + + + LayoutName + + + Cart Layout + 网格布局 + + + + ListLayout + + + Edit cue + 编辑 Cue + + + + Edit selected cues + 编辑所选的 Cue + + + + Remove cue + 移除 Cue + + + + Remove selected cues + 移除所选的 Cue + + + + SettingsPageName + + + Cart Layout + 网格布局 + + + diff --git a/lisp/i18n/ts/zh_CN/controller.ts b/lisp/i18n/ts/zh_CN/controller.ts new file mode 100644 index 000000000..f066b51c7 --- /dev/null +++ b/lisp/i18n/ts/zh_CN/controller.ts @@ -0,0 +1,274 @@ + + + + + Controller + + + Cannot load controller protocol: "{}" + 无法加载控制器协议: "{}" + + + + ControllerKeySettings + + + Action + 动作 + + + + Shortcuts + 快捷键 + + + + Shortcut + 快捷键 + + + + ControllerMidiSettings + + + MIDI + MIDI + + + + Type + 类型 + + + + Action + 动作 + + + + Capture + 捕捉输入 + + + + Listening MIDI messages ... + 正在监听 MIDI 讯息... + + + + -- All Messages -- + -- 所有讯息 -- + + + + Capture filter + 捕捉过滤 + + + + Data 1 + 数据 1 + + + + Data 2 + 数据 2 + + + + Data 3 + 数据 3 + + + + ControllerOscSettings + + + OSC Message + OSC 讯息 + + + + OSC + OSC + + + + Path + 路径 + + + + Types + 类型 + + + + Arguments + 参数 + + + + OSC Capture + 捕捉 OSC + + + + Add + 添加 + + + + Remove + 移除 + + + + Capture + 捕捉输入 + + + + Waiting for messages: + 正在等待讯息: + + + + /path/to/method + 方法路径 + + + + Action + 动作 + + + + ControllerOscSettingsWarning + + + Warning + 警告 + + + + ControllerSettings + + + Add + 添加 + + + + Remove + 移除 + + + + GlobalAction + + + Go + 开始 + + + + Reset + 重设 + + + + Stop all cues + 停止所有 Cue + + + + Pause all cues + 暂停所有 Cue + + + + Resume all cues + 恢复所有 Cue + + + + Interrupt all cues + 中断所有 Cue + + + + Fade-out all cues + 淡出所有 Cue + + + + Fade-in all cues + 淡入所有 Cue + + + + Move standby forward + 移前待命 + + + + Move standby back + 移后待命 + + + + Osc Cue + + + Type + 类型 + + + + Argument + 参数 + + + + OscCue + + + Add + 添加 + + + + Remove + 移除 + + + + SettingsPageName + + + Cue Control + Cue 控制 + + + + MIDI Controls + MIDI 控制 + + + + Keyboard Shortcuts + 键盘快捷键 + + + + OSC Controls + OSC 控制 + + + + Layout Controls + 布局控制 + + + diff --git a/lisp/i18n/ts/zh_CN/gst_backend.ts b/lisp/i18n/ts/zh_CN/gst_backend.ts new file mode 100644 index 000000000..70187be2d --- /dev/null +++ b/lisp/i18n/ts/zh_CN/gst_backend.ts @@ -0,0 +1,399 @@ + + + + + AlsaSinkSettings + + + ALSA device + ALSA 装置 + + + + AudioDynamicSettings + + + Compressor + 压缩器 + + + + Expander + 扩展器 + + + + Soft Knee + 高拐点柔软度 + + + + Hard Knee + 低拐点柔软度 + + + + Compressor/Expander + 压缩器/扩展器 + + + + Type + 类型 + + + + Curve Shape + 曲线形状 + + + + Ratio + 比例 + + + + Threshold (dB) + 阀值 (分貝) + + + + AudioPanSettings + + + Audio Pan + 音频平移 + + + + Center + + + + + Left + + + + + Right + + + + + DbMeterSettings + + + DbMeter settings + 分貝计设定 + + + + Time between levels (ms) + 阶级之间的时间 (毫秒) + + + + Peak ttl (ms) + 峰值存活时间 (毫秒) + + + + Peak falloff (dB/sec) + 峰值下跌率 (分貝/秒) + + + + Equalizer10Settings + + + 10 Bands Equalizer (IIR) + 10频段均衡器 (无限冲击响应滤波器) + + + + GstBackend + + + Audio cue (from file) + 媒体 Cue (从档案汇入) + + + + Select media files + 选择媒体档案 + + + + GstMediaError + + + Cannot create pipeline element: "{}" + 无法创建管道元素: "{}" + + + + GstMediaSettings + + + Change Pipeline + 更改管道 + + + + GstMediaWarning + + + Invalid pipeline element: "{}" + 管道元素无效: "{}" + + + + GstPipelineEdit + + + Edit Pipeline + 编辑管道 + + + + GstSettings + + + Default pipeline + 默认管道 + + + + Applied only to new cues. + 只套用于新 Cue + + + + JackSinkSettings + + + Connections + 联系 + + + + Edit connections + 编辑联系 + + + + Output ports + 输出端口 + + + + Input ports + 输入端口 + + + + Connect + 连接 + + + + Disconnect + 断开连接 + + + + MediaElementName + + + Compressor/Expander + 压缩器/扩展器 + + + + Audio Pan + 音频平移 + + + + PulseAudio Out + PulseAudio 输出 + + + + Volume + 音量 + + + + dB Meter + 分贝计 + + + + System Input + 系统输入 + + + + ALSA Out + ALSA 输出 + + + + JACK Out + JACK 输出 + + + + Custom Element + 自定义元素 + + + + System Out + 系统输出 + + + + Pitch + 音调 + + + + URI Input + URI 输入 + + + + 10 Bands Equalizer + 10频段均衡器 + + + + Speed + 速度 + + + + Preset Input + 预设输入 + + + + PitchSettings + + + Pitch + 音调 + + + + {0:+} semitones + {0:+} 半音 + + + + PresetSrcSettings + + + Presets + 範本 + + + + SettingsPageName + + + Media Settings + 媒体设定 + + + + GStreamer + GStreamer + + + + SpeedSettings + + + Speed + 速度 + + + + UriInputSettings + + + Source + 来源 + + + + Find File + 查找档案 + + + + Buffering + 缓冲 + + + + Use Buffering + 使用缓冲 + + + + Attempt download on network streams + 尝试从互联网下载 + + + + Buffer size (-1 default value) + 缓冲大小 (-1 为默认值) + + + + Choose file + 选择档案 + + + + All files + 所有档案 + + + + UserElementSettings + + + User defined elements + 用户定义的元素 + + + + Only for advanced user! + 只供进阶用户使用! + + + + VolumeSettings + + + Volume + 音量 + + + + Normalized volume + 标准化音量 + + + + Reset + 重设 + + + diff --git a/lisp/i18n/ts/zh_CN/lisp.ts b/lisp/i18n/ts/zh_CN/lisp.ts new file mode 100644 index 000000000..32e7fde67 --- /dev/null +++ b/lisp/i18n/ts/zh_CN/lisp.ts @@ -0,0 +1,831 @@ + + + + + About + + + Authors + 作者 + + + + Contributors + 贡献者 + + + + Translators + 翻译者 + + + + About Linux Show Player + 关于 Linux Show Player + + + + AboutDialog + + + Web site + 网站 + + + + Source code + 源代码 + + + + Info + 关于 + + + + License + 使用条款 + + + + Contributors + 贡献者 + + + + Discussion + 讨论 + + + + AppConfiguration + + + LiSP preferences + LiSP 设定 + + + + AppGeneralSettings + + + Default layout + 默认布局 + + + + Application themes + 主题 + + + + UI theme: + 界面主题: + + + + Icons theme: + 图标主题: + + + + Language: + 语言: + + + + Show layout selection at startup + 启动时显示布局选择 + + + + Use layout at startup: + 启动时使用布局: + + + + Application language (require a restart) + 语言 (需要重启程式) + + + + ApplicationError + + + Startup error + 启动错误 + + + + CommandsStack + + + Undo: {} + 复原:{} + + + + Redo: {} + 重做:{} + + + + ConfigurationDebug + + + Configuration written at {} + 设定值已储存于 {} + + + + ConfigurationInfo + + + New configuration installed at {} + 新设定值储存于 {} + + + + CueAction + + + Default + 默认 + + + + Pause + 暂停 + + + + Start + 开始 + + + + Stop + 停止 + + + + Faded Start + 淡入开始 + + + + Faded Resume + 淡入继续 + + + + Faded Pause + 淡出暂停 + + + + Faded Stop + 淡出停止 + + + + Faded Interrupt + 淡出中断 + + + + Resume + 继续 + + + + Do Nothing + 无动作 + + + + CueAppearanceSettings + + + The appearance depends on the layout + 外观取决于布局 + + + + Cue name + Cue 名称 + + + + NoName + 没有名称 + + + + Description/Note + 描述/注释 + + + + Set Font Size + 设置字体大小 + + + + Color + 颜色 + + + + Select background color + 选择背景颜色 + + + + Select font color + 选择字体颜色 + + + + CueCategory + + + Misc cues + 杂项 Cues + + + + CueCommandLog + + + Cue settings changed: "{}" + Cue 设置已更改: "{}" + + + + Cues settings changed. + 多个 Cue 设置已更改 + + + + CueName + + + Media Cue + 媒体 Cue + + + + CueNextAction + + + Do Nothing + 无动作 + + + + Trigger after the end + 结束后触发 + + + + Trigger after post wait + 结束后等候后触发 + + + + Select after the end + 结束后选择 + + + + Select after post wait + 结束后等候后选择 + + + + CueSettings + + + Pre wait + 开始前等候 + + + + Wait before cue execution + 执行 Cue 前等待 + + + + Post wait + 结束后等候 + + + + Wait after cue execution + 执行 Cue 后等待 + + + + Next action + 下一个动作 + + + + Start action + 开始方式 + + + + Default action to start the cue + 预设开始 Cue 的方式 + + + + Stop action + 停止方式 + + + + Default action to stop the cue + 预设停止 Cue 的方式 + + + + Interrupt action fade + 中断动作淡出 + + + + Fade actions default value + 淡入/淡出预设值 + + + + Fade + + + Linear + 线性 + + + + Quadratic + 二次曲线 + + + + Quadratic2 + S形曲线 + + + + FadeEdit + + + Duration (sec): + 长度 (秒): + + + + Curve: + 曲线: + + + + FadeSettings + + + Fade In + 淡入 + + + + Fade Out + 淡出 + + + + HotKeyEdit + + + Press shortcut + 按下快捷键 + + + + LayoutSelect + + + Layout selection + 布局选择 + + + + Select layout + 选择布局 + + + + Open file + 打开档案 + + + + ListLayout + + + Layout actions + 布局操作 + + + + Fade out when stopping all cues + 以淡出方式停止所有 Cue + + + + Fade out when interrupting all cues + 以淡出方式中断所有 Cue + + + + Fade out when pausing all cues + 以淡出方式暂停所有 Cue + + + + Fade in when resuming all cues + 以淡入方式繼續所有 Cue + + + + LogStatusIcon + + + Errors/Warnings + 错误/警告 + + + + Logging + + + Debug + 除错 + + + + Warning + 警告 + + + + Error + 錯誤 + + + + Dismiss all + 全部忽略 + + + + Show details + 显示详情 + + + + Linux Show Player - Log Viewer + Linux Show Player - 查看日志 + + + + Info + 资讯 + + + + Critical + 严重 + + + + Time + 时间 + + + + Milliseconds + 毫秒 + + + + Logger name + 记录者名称 + + + + Level + 级别 + + + + Message + 讯息 + + + + Function + 函数 + + + + Path name + 路径 + + + + File name + 档案名称 + + + + Line no. + 行号 + + + + Module + 模块 + + + + Process ID + 进程号码 + + + + Process name + 进程名称 + + + + Thread ID + 线程编号 + + + + Thread name + 线程名称 + + + + Exception info + 异常讯息 + + + + Showing {} of {} records + 显示 {} 个记录 + + + + MainWindow + + + &File + 档案 (&F) + + + + New session + 新工作阶段 + + + + Open + 开启 + + + + Save session + 储存工作阶段 + + + + Preferences + 设定 + + + + Save as + 另存为 + + + + Full Screen + 全屏显示 + + + + Exit + 离开 + + + + &Edit + 编辑 (&E) + + + + Undo + 还原 + + + + Redo + 重做 + + + + Select all + 全选 + + + + Select all media cues + 选择所有媒体 Cue + + + + Deselect all + 取消全选 + + + + CTRL+SHIFT+A + CTRL+SHIFT+A + + + + Invert selection + 反向选择 + + + + CTRL+I + CTRL+I + + + + Edit selected + 编辑所選 + + + + CTRL+SHIFT+E + CTRL+SHIFT+E + + + + &Layout + 布局 (&L) + + + + &Tools + 工具 (&T) + + + + Edit selection + 编辑所選 + + + + &About + 关于 (&A) + + + + About + 关于 + + + + About Qt + 关于 Qt + + + + Close session + 关闭工作阶段 + + + + Do you want to save them now? + 你想现在储存吗? + + + + MainWindowDebug + + + Registered cue menu: "{}" + 已记录 Cue 选单: "{}" + + + + MainWindowError + + + Cannot create cue {} + 无法创建 Cue {} + + + + MediaCueSettings + + + Start time + 开始时间 + + + + Stop position of the media + 媒体停止位置 + + + + Stop time + 停止时间 + + + + Start position of the media + 媒体开始位置 + + + + Loop + 循环播放 + + + + QColorButton + + + Right click to reset + 滑鼠右按以重设 + + + + SettingsPageName + + + Appearance + 外观 + + + + General + 基本 + + + + Cue + Cue + + + + Cue Settings + Cue 设定 + + + + Plugins + 插件 + + + + Behaviours + 行为 + + + + Pre/Post Wait + 开始前等候/结束后等候 + + + + Fade In/Out + 淡入/淡出 + + + + Layouts + 布局 + + + diff --git a/lisp/i18n/ts/zh_CN/list_layout.ts b/lisp/i18n/ts/zh_CN/list_layout.ts new file mode 100644 index 000000000..69f365786 --- /dev/null +++ b/lisp/i18n/ts/zh_CN/list_layout.ts @@ -0,0 +1,260 @@ + + + + + LayoutDescription + + + Organize the cues in a list + 把 Cue 排列在列表 + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + 空白键或按两下以编辑 Cue + + + + To copy cues drag them while pressing CTRL + 按着 CTRL 然后拖拉 Cue 以复制 Cue + + + + To move cues drag them + 拖拉 Cue 以移动 Cue + + + + LayoutName + + + List Layout + 列表布局 + + + + ListLayout + + + Show dB-meters + 显示分貝计 + + + + Show accurate time + 显示准确时间 + + + + Show seek-bars + 显示进度条 + + + + Auto-select next cue + 自動選擇下一個 Cue + + + + Enable selection mode + 启用选取 Cue 模式 + + + + Use fade (buttons) + 使用淡入淡出 (按钮) + + + + Stop Cue + 停止 Cue + + + + Pause Cue + 暂停 Cue + + + + Resume Cue + 继续 Cue + + + + Interrupt Cue + 中断 Cue + + + + Edit cue + 编辑 Cue + + + + Remove cue + 移除 Cue + + + + Selection mode + 选取 Cue 模式 + + + + Pause all + 全部暂停 + + + + Stop all + 全部停止 + + + + Interrupt all + 全部中断 + + + + Resume all + 全部恢复 + + + + Fade-Out all + 全部淡出 + + + + Fade-In all + 全部淡入 + + + + Edit selected + 编辑所选 + + + + Clone cue + 复制 Cue + + + + Clone selected + 复制所选 + + + + Remove selected + 删除所选 + + + + GO Key: + GO 键: + + + + GO Action: + GO 动作: + + + + GO minimum interval (ms): + GO最小间隔 (毫秒): + + + + Copy of {} + {} 的副本 + + + + Show index column + 显示索引列 + + + + Show resize handles + 显示调整大小手柄 + + + + Restore default size + 重设至默认大小 + + + + Default behaviors (applied to new sessions) + 默认行为 (套用于新工作阶段) + + + + Behaviors + 行为 + + + + Disable GO Key While Playing + 在播放时禁用 GO 键 + + + + Use waveform seek-bars + 使用波形图拖动条 + + + + GO Key Disabled While Playing + 播放时禁用GO键 + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + 开始前等候 + + + + Action + 动作 + + + + Post wait + 结束后等候 + + + + ListLayoutInfoPanel + + + Cue name + Cue 名称 + + + + Cue description + Cue 描述 + + + + SettingsPageName + + + List Layout + 列表布局 + + + diff --git a/lisp/i18n/ts/zh_CN/media_info.ts b/lisp/i18n/ts/zh_CN/media_info.ts new file mode 100644 index 000000000..f95f8431d --- /dev/null +++ b/lisp/i18n/ts/zh_CN/media_info.ts @@ -0,0 +1,32 @@ + + + + + MediaInfo + + + Media Info + 媒体资料 + + + + Info + 资料 + + + + Value + + + + + Warning + 警告 + + + + Cannot get any information. + 无法获取任何资料 + + + diff --git a/lisp/i18n/ts/zh_CN/midi.ts b/lisp/i18n/ts/zh_CN/midi.ts new file mode 100644 index 000000000..bfd547a13 --- /dev/null +++ b/lisp/i18n/ts/zh_CN/midi.ts @@ -0,0 +1,216 @@ + + + + + CueCategory + + + Integration cues + 互联 Cues + + + + CueName + + + MIDI Cue + MIDI Cue + + + + MIDICue + + + MIDI Message + MIDI 讯息 + + + + Message type + 讯息类型 + + + + MIDIError + + + Cannot connect to MIDI output port '{}'. + 无法连接到 MIDI 输出端口 '{}' + + + + Cannot connect to MIDI input port '{}'. + 无法连接到 MIDI 输入端口 '{}' + + + + MIDIInfo + + + MIDI port disconnected: '{}' + MIDI 端口已断线:'{}' + + + + Connecting to MIDI port: '{}' + 正在连接到 MIDI 端口:'{}' + + + + Connecting to matching MIDI port: '{}' + 正在连接到匹配的 MIDI 端口:'{}' + + + + MIDIMessageAttr + + + Channel + 频道 + + + + Note + + + + + Velocity + 速度 + + + + Control + 控制 + + + + Program + 程式 + + + + Value + + + + + Song + 歌曲 + + + + Pitch + 音调 + + + + Position + 位置 + + + + MIDIMessageType + + + Note ON + Note ON + + + + Note OFF + Note OFF + + + + Polyphonic After-touch + Polyphonic After-touch + + + + Control/Mode Change + Control/Mode 改变 + + + + Program Change + Program 改变 + + + + Channel After-touch + Channel After-touch + + + + Pitch Bend Change + Pitch Bend 改变 + + + + Song Select + 歌曲选择 + + + + Song Position + 歌曲位置 + + + + Start + 开始 + + + + Stop + 停止 + + + + Continue + 继续 + + + + MIDISettings + + + Input + 输入 + + + + Output + 输出 + + + + MIDI devices + MIDI 装置 + + + + Misc options + 其他选项 + + + + Try to connect using only device/port name + 只尝试使用设备/端口名称连接 + + + + SettingsPageName + + + MIDI settings + MIDI 设置 + + + + MIDI Settings + MIDI 设置 + + + diff --git a/lisp/i18n/ts/zh_CN/network.ts b/lisp/i18n/ts/zh_CN/network.ts new file mode 100644 index 000000000..c75831282 --- /dev/null +++ b/lisp/i18n/ts/zh_CN/network.ts @@ -0,0 +1,76 @@ + + + + + APIServerInfo + + + Stop serving network API + 停止服务网络 API + + + + ApiServerError + + + Network API server stopped working. + 网络 API 伺服器停止运作 + + + + NetworkApiDebug + + + New end-point: {} + 新终端:{} + + + + NetworkDiscovery + + + Host discovery + 搜索主机 + + + + Manage hosts + 管理主机 + + + + Discover hosts + 寻找主机 + + + + Manually add a host + 手动添加主机 + + + + Remove selected host + 移除所选主机 + + + + Remove all host + 移除所有主机 + + + + Address + 地址 + + + + Host IP + 主机 IP + + + + Select the hosts you want to add + 选择你想增加的主机 + + + diff --git a/lisp/i18n/ts/zh_CN/osc.ts b/lisp/i18n/ts/zh_CN/osc.ts new file mode 100644 index 000000000..ef250edd1 --- /dev/null +++ b/lisp/i18n/ts/zh_CN/osc.ts @@ -0,0 +1,146 @@ + + + + + Cue Name + + + OSC Settings + OSC 设定 + + + + CueCategory + + + Integration cues + 互联 Cues + + + + CueName + + + OSC Cue + OSC Cue + + + + OscCue + + + Type + 类型 + + + + Value + + + + + FadeTo + 淡入/淡出至 + + + + Fade + 淡入淡出 + + + + OSC Message + OSC 讯息 + + + + Add + 添加 + + + + Remove + 移除 + + + + OSC Path: + OSC 路径: + + + + /path/to/something + 路径 + + + + Time (sec) + 时间 (秒) + + + + Curve + 曲线 + + + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + 来自 {} 的讯息 -> 路径: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + 无法启动 OSC 伺服器 + + + + OscServerInfo + + + OSC server started at {} + OSC 伺服器已于 {} 启动 + + + + OSC server stopped + OSC 伺服器已停止 + + + + OscSettings + + + OSC Settings + OSC 设定 + + + + Input Port: + 输入端口: + + + + Output Port: + 输出端口: + + + + Hostname: + 主机名称: + + + + SettingsPageName + + + OSC settings + OSC 设定 + + + diff --git a/lisp/i18n/ts/zh_CN/presets.ts b/lisp/i18n/ts/zh_CN/presets.ts new file mode 100644 index 000000000..875ea1fa6 --- /dev/null +++ b/lisp/i18n/ts/zh_CN/presets.ts @@ -0,0 +1,135 @@ + + + + + Preset + + + Create Cue + 创建 Cue + + + + Load on selected Cues + 在所选的 Cue 上加载范本 + + + + Presets + + + Presets + 範本 + + + + Save as preset + 保存为範本 + + + + Cannot scan presets + 无法寻找範本 + + + + Error while deleting preset "{}" + 删除範本 "{}" 时发生错误 + + + + Cannot load preset "{}" + 无法加载範本 "{}" + + + + Cannot save preset "{}" + 无法保存範本 "{}" + + + + Cannot rename preset "{}" + 无法重新命名範本 "{}" + + + + Select Preset + 选择範本 + + + + Preset name + 範本名称 + + + + Add + 添加 + + + + Rename + 重新命名 + + + + Edit + 编辑 + + + + Remove + 移除 + + + + Export selected + 汇出所选 + + + + Import + 汇入 + + + + Warning + 警告 + + + + The same name is already used! + 这个名称已经被使用 + + + + Cannot export correctly. + 无法汇出 + + + + Cannot import correctly. + 无法汇入 + + + + Cue type + Cue 类型 + + + + Load on cue + 在 Cue 上加载范本 + + + + Load on selected cues + 在所选的 Cue 上加载范本 + + + + Cannot create a cue from this preset: {} + 无法从此范本创建 Cue:{} + + + diff --git a/lisp/i18n/ts/zh_CN/rename_cues.ts b/lisp/i18n/ts/zh_CN/rename_cues.ts new file mode 100644 index 000000000..07d0c242b --- /dev/null +++ b/lisp/i18n/ts/zh_CN/rename_cues.ts @@ -0,0 +1,83 @@ + + + + + RenameCues + + + Rename Cues + 重新命名 Cue + + + + Rename cues + 重新命名多个 Cue + + + + Current + 当前名称 + + + + Preview + 预览 + + + + Capitalize + 首字母大写 + + + + Lowercase + 小写 + + + + Uppercase + 大写 + + + + Remove Numbers + 移除号码 + + + + Add numbering + 添加号码 + + + + Reset + 重设 + + + + Type your regex here: + 在此输入正规表达式: + + + + Regex help + 正规表达式帮助 + + + + RenameCuesCommand + + + Renamed {number} cues + 已重新命名 {number} 個 Cue + + + + RenameUiDebug + + + Regex error: Invalid pattern + 正规表达式错误:无效表达式 + + + diff --git a/lisp/i18n/ts/zh_CN/replay_gain.ts b/lisp/i18n/ts/zh_CN/replay_gain.ts new file mode 100644 index 000000000..857b71fc5 --- /dev/null +++ b/lisp/i18n/ts/zh_CN/replay_gain.ts @@ -0,0 +1,83 @@ + + + + + ReplayGain + + + ReplayGain / Normalization + 回放增益/标准化 + + + + Calculate + 计算 + + + + Reset all + 全部重设 + + + + Reset selected + 重设所选 + + + + Threads number + 线程数量 + + + + Apply only to selected media + 只套用于所选媒体 + + + + ReplayGain to (dB SPL) + 回放增益至 (分贝 声压级) + + + + Normalize to (dB) + 标准化至 (分贝) + + + + Processing files ... + 正在处理档案... + + + + ReplayGainDebug + + + Applied gain for: {} + 已套用為 {} 作增益 + + + + Discarded gain for: {} + 已舍弃為 {} 作增益 + + + + ReplayGainInfo + + + Gain processing stopped by user. + 用户已停止增益处理 + + + + Started gain calculation for: {} + 已开始为 {} 作增益计算 + + + + Gain calculated for: {} + 已为 {} 作增益计算 + + + diff --git a/lisp/i18n/ts/zh_CN/synchronizer.ts b/lisp/i18n/ts/zh_CN/synchronizer.ts new file mode 100644 index 000000000..c6356139c --- /dev/null +++ b/lisp/i18n/ts/zh_CN/synchronizer.ts @@ -0,0 +1,27 @@ + + + + + Synchronizer + + + Synchronization + 同步 + + + + Manage connected peers + 管理已连接的用户 + + + + Show your IP + 显示你的 IP + + + + Your IP is: {} + 你的 IP:{} + + + diff --git a/lisp/i18n/ts/zh_CN/timecode.ts b/lisp/i18n/ts/zh_CN/timecode.ts new file mode 100644 index 000000000..03f7cdf98 --- /dev/null +++ b/lisp/i18n/ts/zh_CN/timecode.ts @@ -0,0 +1,66 @@ + + + + + SettingsPageName + + + Timecode Settings + 时间码设定 + + + + Timecode + 时间码 + + + + Timecode + + + Cannot load timecode protocol: "{}" + 无法加载时间码协议: "{}" + + + + TimecodeError + + + Cannot send timecode. + 无法发送时间码 + + + + TimecodeSettings + + + Timecode Format: + 时间码格式: + + + + Replace HOURS by a static track number + 以静态曲目编号代替小时 + + + + Track number + 曲目编号 + + + + Enable Timecode + 启用时间码 + + + + Timecode Settings + 时间码设定 + + + + Timecode Protocol: + 时间码协议: + + + diff --git a/lisp/i18n/ts/zh_CN/triggers.ts b/lisp/i18n/ts/zh_CN/triggers.ts new file mode 100644 index 000000000..4c0be33c9 --- /dev/null +++ b/lisp/i18n/ts/zh_CN/triggers.ts @@ -0,0 +1,63 @@ + + + + + CueTriggers + + + Started + 已开始 + + + + Paused + 已暂停 + + + + Stopped + 已停止 + + + + Ended + 已结束 + + + + SettingsPageName + + + Triggers + 触发 + + + + TriggersSettings + + + Add + 添加 + + + + Remove + 移除 + + + + Trigger + 触发條件 + + + + Cue + Cue + + + + Action + 动作 + + + From 4196c5ee6d0dd729c81e7a5436dbe7071df62ce4 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 29 May 2020 21:56:50 +0200 Subject: [PATCH 238/333] New Crowdin translations (#186) --- lisp/i18n/ts/cs_CZ/lisp.ts | 667 +------------- lisp/i18n/ts/de_DE/controller.ts | 5 - lisp/i18n/ts/de_DE/lisp.ts | 675 +------------- lisp/i18n/ts/de_DE/list_layout.ts | 20 - lisp/i18n/ts/de_DE/presets.ts | 5 - lisp/i18n/ts/de_DE/rename_cues.ts | 8 - lisp/i18n/ts/es_ES/lisp.ts | 600 +------------ lisp/i18n/ts/fr_FR/presets.ts | 5 - lisp/i18n/ts/it_IT/lisp.ts | 597 +------------ lisp/i18n/ts/nl_BE/lisp.ts | 859 +----------------- lisp/i18n/ts/nl_NL/lisp.ts | 1285 ++------------------------- lisp/i18n/ts/sl_SI/lisp.ts | 667 +------------- lisp/i18n/ts/zh_TW/action_cues.ts | 234 +++++ lisp/i18n/ts/zh_TW/cache_manager.ts | 35 + lisp/i18n/ts/zh_TW/cart_layout.ts | 187 ++++ lisp/i18n/ts/zh_TW/controller.ts | 274 ++++++ lisp/i18n/ts/zh_TW/gst_backend.ts | 399 +++++++++ lisp/i18n/ts/zh_TW/lisp.ts | 831 +++++++++++++++++ lisp/i18n/ts/zh_TW/list_layout.ts | 260 ++++++ lisp/i18n/ts/zh_TW/media_info.ts | 32 + lisp/i18n/ts/zh_TW/midi.ts | 216 +++++ lisp/i18n/ts/zh_TW/network.ts | 76 ++ lisp/i18n/ts/zh_TW/osc.ts | 146 +++ lisp/i18n/ts/zh_TW/presets.ts | 135 +++ lisp/i18n/ts/zh_TW/rename_cues.ts | 83 ++ lisp/i18n/ts/zh_TW/replay_gain.ts | 83 ++ lisp/i18n/ts/zh_TW/synchronizer.ts | 27 + lisp/i18n/ts/zh_TW/timecode.ts | 66 ++ lisp/i18n/ts/zh_TW/triggers.ts | 63 ++ 29 files changed, 3272 insertions(+), 5268 deletions(-) create mode 100644 lisp/i18n/ts/zh_TW/action_cues.ts create mode 100644 lisp/i18n/ts/zh_TW/cache_manager.ts create mode 100644 lisp/i18n/ts/zh_TW/cart_layout.ts create mode 100644 lisp/i18n/ts/zh_TW/controller.ts create mode 100644 lisp/i18n/ts/zh_TW/gst_backend.ts create mode 100644 lisp/i18n/ts/zh_TW/lisp.ts create mode 100644 lisp/i18n/ts/zh_TW/list_layout.ts create mode 100644 lisp/i18n/ts/zh_TW/media_info.ts create mode 100644 lisp/i18n/ts/zh_TW/midi.ts create mode 100644 lisp/i18n/ts/zh_TW/network.ts create mode 100644 lisp/i18n/ts/zh_TW/osc.ts create mode 100644 lisp/i18n/ts/zh_TW/presets.ts create mode 100644 lisp/i18n/ts/zh_TW/rename_cues.ts create mode 100644 lisp/i18n/ts/zh_TW/replay_gain.ts create mode 100644 lisp/i18n/ts/zh_TW/synchronizer.ts create mode 100644 lisp/i18n/ts/zh_TW/timecode.ts create mode 100644 lisp/i18n/ts/zh_TW/triggers.ts diff --git a/lisp/i18n/ts/cs_CZ/lisp.ts b/lisp/i18n/ts/cs_CZ/lisp.ts index df1771bd8..503e36fb5 100644 --- a/lisp/i18n/ts/cs_CZ/lisp.ts +++ b/lisp/i18n/ts/cs_CZ/lisp.ts @@ -1,14 +1,6 @@ - - APIServerInfo - - - Stop serving network API - Stop serving network API - - About @@ -153,154 +145,6 @@ New configuration installed at {} - - Controller - - - Cannot load controller protocol: "{}" - Cannot load controller protocol: "{}" - - - - ControllerKeySettings - - - Action - Action - - - - Shortcuts - Shortcuts - - - - Shortcut - Shortcut - - - - ControllerMidiSettings - - - MIDI - MIDI - - - - Type - Type - - - - Channel - Channel - - - - Note - Note - - - - Action - Action - - - - Filter "note on" - Filter "note on" - - - - Filter "note off" - Filter "note off" - - - - Capture - Capture - - - - Listening MIDI messages ... - Listening MIDI messages ... - - - - ControllerOscSettings - - - OSC Message - OSC Message - - - - OSC - OSC - - - - Path - Path - - - - Types - Types - - - - Arguments - Arguments - - - - Actions - Actions - - - - OSC Capture - OSC Capture - - - - Add - Add - - - - Remove - Remove - - - - Capture - Capture - - - - ControllerSettings - - - Add - Add - - - - Remove - Remove - - - - Cue Name - - - OSC Settings - OSC Settings - - CueAction @@ -430,46 +274,6 @@ Media Cue Narážka v záznamu - - - Index Action - Index Action - - - - Seek Cue - Seek Cue - - - - Volume Control - Volume Control - - - - Command Cue - Command Cue - - - - Collection Cue - Collection Cue - - - - Stop-All - Stop-All - - - - MIDI Cue - MIDI Cue - - - - OSC Cue - OSC Cue - CueNextAction @@ -557,60 +361,6 @@ Fade actions default value - - CueTriggers - - - Started - Started - - - - Paused - Paused - - - - Stopped - Stopped - - - - Ended - Ended - - - - DbMeterSettings - - - DbMeter settings - DbMeter settings - - - - Time between levels (ms) - Time between levels (ms) - - - - Peak ttl (ms) - Peak ttl (ms) - - - - Peak falloff (dB/sec) - Peak falloff (dB/sec) - - - - Equalizer10Settings - - - 10 Bands Equalizer (IIR) - 10 Bands Equalizer (IIR) - - Fade @@ -841,7 +591,7 @@ - MIDICue + MainWindow &File @@ -852,9 +602,6 @@ New session Nové sezení - - - MIDIMessageAttr Open @@ -900,9 +647,6 @@ Redo Znovu - - - MIDIMessageType Select all @@ -963,9 +707,6 @@ &About &O programu - - - MIDISettings About @@ -1004,74 +745,31 @@ - QColorButton + MediaCueSettings - - Right click to reset - Klepnutí pravým tlačítkem myši pro nastavení na výchozí hodnotu - - - - RenameCues - - - Rename Cues - Rename Cues + + Start time + Čas spuštění - - Rename cues - Rename cues + + Stop position of the media + Poloha zastavení záznamu - - Current - Current + + Stop time + Čas zastavení - - Preview - Preview + + Start position of the media + Poloha spuštění záznamu - - Capitalize - Capitalize - - - - Lowercase - Lowercase - - - - Uppercase - Uppercase - - - - Remove Numbers - Remove Numbers - - - - Add numbering - Add numbering - - - - Reset - Reset - - - - Type your regex here: - Type your regex here: - - - - Regex help - Regex help + + Loop + Smyčka @@ -1129,336 +827,5 @@ Layouts Layouts - - - Action Settings - Action Settings - - - - Seek Settings - Seek Settings - - - - Volume Settings - Volume Settings - - - - Command - Command - - - - Edit Collection - Edit Collection - - - - Stop Settings - Stop Settings - - - - List Layout - List Layout - - - - Timecode - Timecode - - - - Timecode Settings - Timecode Settings - - - - Cue Control - Cue Control - - - - Layout Controls - Layout Controls - - - - MIDI Controls - MIDI Controls - - - - Keyboard Shortcuts - Keyboard Shortcuts - - - - OSC Controls - OSC Controls - - - - MIDI Settings - MIDI Settings - - - - MIDI settings - MIDI settings - - - - GStreamer - GStreamer - - - - Media Settings - Media Settings - - - - Triggers - Triggers - - - - OSC settings - OSC settings - - - - Cart Layout - Cart Layout - - - - SpeedSettings - - - Speed - Speed - - - - StopAll - - - Stop Action - Stop Action - - - - Synchronizer - - - Synchronization - Synchronization - - - - Manage connected peers - Manage connected peers - - - - Show your IP - Show your IP - - - - Your IP is: - Your IP is: - - - - Timecode - - - Cannot load timecode protocol: "{}" - Cannot load timecode protocol: "{}" - - - - TimecodeError - - - Cannot send timecode. - Cannot send timecode. - - - - TimecodeSettings - - - Replace HOURS by a static track number - Replace HOURS by a static track number - - - - Enable Timecode - Enable Timecode - - - - Track number - Track number - - - - Timecode Settings - Timecode Settings - - - - Timecode Format: - Timecode Format: - - - - Timecode Protocol: - Timecode Protocol: - - - - TriggersSettings - - - Add - Add - - - - Remove - Remove - - - - Trigger - Trigger - - - - Cue - Cue - - - - Action - Action - - - - UriInputSettings - - - Source - Source - - - - Find File - Find File - - - - Buffering - Buffering - - - - Use Buffering - Use Buffering - - - - Attempt download on network streams - Attempt download on network streams - - - - Buffer size (-1 default value) - Buffer size (-1 default value) - - - - Choose file - Choose file - - - - All files - All files - - - - UserElementSettings - - - User defined elements - User defined elements - - - - Only for advanced user! - Only for advanced user! - - - - VolumeControl - - - Cue - Cue - - - - Click to select - Click to select - - - - Not selected - Not selected - - - - Volume to reach - Volume to reach - - - - Fade - Fade - - - - VolumeControlError - - - Error during cue execution. - Error during cue execution. - - - - VolumeSettings - - - Volume - Volume - - - - Normalized volume - Normalized volume - - - - Reset - Reset - diff --git a/lisp/i18n/ts/de_DE/controller.ts b/lisp/i18n/ts/de_DE/controller.ts index 1d0bf9089..2d4e0a3b4 100644 --- a/lisp/i18n/ts/de_DE/controller.ts +++ b/lisp/i18n/ts/de_DE/controller.ts @@ -26,11 +26,6 @@ Shortcut Tastenkürzel - - - Shortcut - Shortcut - ControllerMidiSettings diff --git a/lisp/i18n/ts/de_DE/lisp.ts b/lisp/i18n/ts/de_DE/lisp.ts index e8f894872..cee4d2e91 100644 --- a/lisp/i18n/ts/de_DE/lisp.ts +++ b/lisp/i18n/ts/de_DE/lisp.ts @@ -1,14 +1,6 @@ - - APIServerInfo - - - Stop serving network API - Stop serving network API - - About @@ -115,16 +107,6 @@ Application language (require a restart) Anwendungssprache (Neustart erforderlich) - - - Application language (require restart) - Application language (require restart) - - - - Language: - Language: - ApplicationError @@ -147,249 +129,6 @@ Wiederherstellen: {} - - AudioDynamicSettings - - - Compressor - Compressor - - - - Expander - Expander - - - - Soft Knee - Soft Knee - - - - Hard Knee - Hard Knee - - - - Compressor/Expander - Compressor/Expander - - - - Type - Type - - - - Curve Shape - Curve Shape - - - - Ratio - Ratio - - - - Threshold (dB) - Threshold (dB) - - - - AudioPanSettings - - - Audio Pan - Audio Pan - - - - Center - Center - - - - Left - Left - - - - Right - Right - - - - CartLayout - - - Default behaviors - Default behaviors - - - - Countdown mode - Countdown mode - - - - Show seek-bars - Show seek-bars - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Show accurate time - - - - Show volume - Show volume - - - - Grid size - Grid size - - - - Number of columns: - Number of columns: - - - - Number of rows: - Number of rows: - - - - Play - Play - - - - Pause - Pause - - - - Stop - Stop - - - - Reset volume - Reset volume - - - - Add page - Add page - - - - Add pages - Add pages - - - - Remove current page - Remove current page - - - - Number of Pages: - Number of Pages: - - - - Page {number} - Page {number} - - - - Warning - Warning - - - - Every cue in the page will be lost. - Every cue in the page will be lost. - - - - Are you sure to continue? - Are you sure to continue? - - - - CollectionCue - - - Add - Add - - - - Remove - Remove - - - - Cue - Cue - - - - Action - Action - - - - CommandCue - - - Command - Command - - - - Command to execute, as in a shell - Command to execute, as in a shell - - - - Discard command output - Discard command output - - - - Ignore command errors - Ignore command errors - - - - Kill instead of terminate - Kill instead of terminate - - - - CommandsStack - - - Undo: {} - Undo: {} - - - - Redo: {} - Redo: {} - - ConfigurationDebug @@ -406,154 +145,6 @@ Neue Konfiguration installiert unter {} - - Controller - - - Cannot load controller protocol: "{}" - Cannot load controller protocol: "{}" - - - - ControllerKeySettings - - - Action - Action - - - - Shortcuts - Shortcuts - - - - Shortcut - Shortcut - - - - ControllerMidiSettings - - - MIDI - MIDI - - - - Type - Type - - - - Channel - Channel - - - - Note - Note - - - - Action - Action - - - - Filter "note on" - Filter "note on" - - - - Filter "note off" - Filter "note off" - - - - Capture - Capture - - - - Listening MIDI messages ... - Listening MIDI messages ... - - - - ControllerOscSettings - - - OSC Message - OSC Message - - - - OSC - OSC - - - - Path - Path - - - - Types - Types - - - - Arguments - Arguments - - - - Actions - Actions - - - - OSC Capture - OSC Capture - - - - Add - Add - - - - Remove - Remove - - - - Capture - Capture - - - - ControllerSettings - - - Add - Add - - - - Remove - Remove - - - - Cue Name - - - OSC Settings - OSC Settings - - CueAction @@ -771,7 +362,7 @@ - CueTriggers + Fade Linear @@ -789,7 +380,7 @@ - DbMeterSettings + FadeEdit Duration (sec): @@ -822,250 +413,6 @@ Tastenkürzel drücken - - GlobalAction - - - Go - Go - - - - Reset - Reset - - - - Stop all cues - Stop all cues - - - - Pause all cues - Pause all cues - - - - Resume all cues - Resume all cues - - - - Interrupt all cues - Interrupt all cues - - - - Fade-out all cues - Fade-out all cues - - - - Fade-in all cues - Fade-in all cues - - - - Move standby forward - Move standby forward - - - - Move standby back - Move standby back - - - - GstBackend - - - Audio cue (from file) - Audio cue (from file) - - - - Select media files - Select media files - - - - GstMediaError - - - Cannot create pipeline element: "{}" - Cannot create pipeline element: "{}" - - - - GstMediaSettings - - - Change Pipeline - Change Pipeline - - - - GstMediaWarning - - - Invalid pipeline element: "{}" - Invalid pipeline element: "{}" - - - - GstPipelineEdit - - - Edit Pipeline - Edit Pipeline - - - - GstSettings - - - Pipeline - Pipeline - - - - HotKeyEdit - - - Press shortcut - Press shortcut - - - - IndexActionCue - - - No suggestion - No suggestion - - - - Index - Index - - - - Use a relative index - Use a relative index - - - - Target index - Target index - - - - Action - Action - - - - Suggested cue name - Suggested cue name - - - - JackSinkSettings - - - Connections - Connections - - - - Edit connections - Edit connections - - - - Output ports - Output ports - - - - Input ports - Input ports - - - - Connect - Connect - - - - Disconnect - Disconnect - - - - LayoutDescription - - - Organize the cues in a list - Organize the cues in a list - - - - Organize cues in grid like pages - Organize cues in grid like pages - - - - LayoutDetails - - - SHIFT + Space or Double-Click to edit a cue - SHIFT + Space or Double-Click to edit a cue - - - - To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL - - - - To move cues drag them - To move cues drag them - - - - Click a cue to run it - Click a cue to run it - - - - SHIFT + Click to edit a cue - SHIFT + Click to edit a cue - - - - CTRL + Click to select a cue - CTRL + Click to select a cue - - - - To move cues drag them while pressing SHIFT - To move cues drag them while pressing SHIFT - - - - LayoutName - - - List Layout - List Layout - - - - Cart Layout - Cart Layout - - LayoutSelect @@ -1244,7 +591,7 @@ - MIDICue + MainWindow &File @@ -1255,9 +602,6 @@ New session Neue Sitzung - - - MIDIMessageAttr Open @@ -1303,9 +647,6 @@ Redo Wiederherstellen - - - MIDIMessageType Select all @@ -1381,9 +722,6 @@ Close session Sitzung schließen - - - MainWindowDebug Do you want to save them now? @@ -1435,7 +773,7 @@ - Preset + QColorButton Right click to reset @@ -1443,15 +781,12 @@ - PresetSrcSettings + SettingsPageName Appearance Aussehen - - - Presets General diff --git a/lisp/i18n/ts/de_DE/list_layout.ts b/lisp/i18n/ts/de_DE/list_layout.ts index c318ef839..058f70d11 100644 --- a/lisp/i18n/ts/de_DE/list_layout.ts +++ b/lisp/i18n/ts/de_DE/list_layout.ts @@ -212,26 +212,6 @@ GO Key Disabled While Playing GO-Taste während der Wiedergabe deaktiviert - - - GO Key: - GO Key: - - - - GO Action: - GO Action: - - - - GO minimum interval (ms): - GO minimum interval (ms): - - - - Copy of {} - Copy of {} - ListLayoutHeader diff --git a/lisp/i18n/ts/de_DE/presets.ts b/lisp/i18n/ts/de_DE/presets.ts index 645e4546e..d0eeab5c3 100644 --- a/lisp/i18n/ts/de_DE/presets.ts +++ b/lisp/i18n/ts/de_DE/presets.ts @@ -131,10 +131,5 @@ Cannot create a cue from this preset: {} Kann aus diesem Preset keinen Cue erstellen: {} - - - Cannot create a cue from this preset: {} - Cannot create a cue from this preset: {} - diff --git a/lisp/i18n/ts/de_DE/rename_cues.ts b/lisp/i18n/ts/de_DE/rename_cues.ts index f93c963c8..bf8738a0d 100644 --- a/lisp/i18n/ts/de_DE/rename_cues.ts +++ b/lisp/i18n/ts/de_DE/rename_cues.ts @@ -72,14 +72,6 @@ {number} Cues umbenannt - - RenameCuesCommand - - - Renamed {number} cues - Renamed {number} cues - - RenameUiDebug diff --git a/lisp/i18n/ts/es_ES/lisp.ts b/lisp/i18n/ts/es_ES/lisp.ts index 944c4fdba..372067535 100644 --- a/lisp/i18n/ts/es_ES/lisp.ts +++ b/lisp/i18n/ts/es_ES/lisp.ts @@ -1,14 +1,6 @@ - - APIServerInfo - - - Stop serving network API - Stop serving network API - - About @@ -153,154 +145,6 @@ New configuration installed at {} - - Controller - - - Cannot load controller protocol: "{}" - Cannot load controller protocol: "{}" - - - - ControllerKeySettings - - - Action - Action - - - - Shortcuts - Shortcuts - - - - Shortcut - Shortcut - - - - ControllerMidiSettings - - - MIDI - MIDI - - - - Type - Type - - - - Channel - Channel - - - - Note - Note - - - - Action - Action - - - - Filter "note on" - Filter "note on" - - - - Filter "note off" - Filter "note off" - - - - Capture - Capture - - - - Listening MIDI messages ... - Listening MIDI messages ... - - - - ControllerOscSettings - - - OSC Message - OSC Message - - - - OSC - OSC - - - - Path - Path - - - - Types - Types - - - - Arguments - Arguments - - - - Actions - Actions - - - - OSC Capture - OSC Capture - - - - Add - Add - - - - Remove - Remove - - - - Capture - Capture - - - - ControllerSettings - - - Add - Add - - - - Remove - Remove - - - - Cue Name - - - OSC Settings - OSC Settings - - CueAction @@ -430,46 +274,6 @@ Media Cue Cue de Media - - - Index Action - Index Action - - - - Seek Cue - Seek Cue - - - - Volume Control - Volume Control - - - - Command Cue - Command Cue - - - - Collection Cue - Collection Cue - - - - Stop-All - Stop-All - - - - MIDI Cue - MIDI Cue - - - - OSC Cue - OSC Cue - CueNextAction @@ -557,60 +361,6 @@ Fade actions default value - - CueTriggers - - - Started - Started - - - - Paused - Paused - - - - Stopped - Stopped - - - - Ended - Ended - - - - DbMeterSettings - - - DbMeter settings - DbMeter settings - - - - Time between levels (ms) - Time between levels (ms) - - - - Peak ttl (ms) - Peak ttl (ms) - - - - Peak falloff (dB/sec) - Peak falloff (dB/sec) - - - - Equalizer10Settings - - - 10 Bands Equalizer (IIR) - 10 Bands Equalizer (IIR) - - Fade @@ -841,7 +591,7 @@ - MIDICue + MainWindow &File @@ -852,9 +602,6 @@ New session Nueva sesión - - - MIDIMessageAttr Open @@ -900,9 +647,6 @@ Redo Rehacer - - - MIDIMessageType Select all @@ -1037,15 +781,12 @@ - PresetSrcSettings + SettingsPageName - - Presets - Presets + + Appearance + Apariencia - - - Presets General @@ -1086,336 +827,5 @@ Layouts Layouts - - - Action Settings - Action Settings - - - - Seek Settings - Seek Settings - - - - Volume Settings - Volume Settings - - - - Command - Command - - - - Edit Collection - Edit Collection - - - - Stop Settings - Stop Settings - - - - List Layout - List Layout - - - - Timecode - Timecode - - - - Timecode Settings - Timecode Settings - - - - Cue Control - Cue Control - - - - Layout Controls - Layout Controls - - - - MIDI Controls - MIDI Controls - - - - Keyboard Shortcuts - Keyboard Shortcuts - - - - OSC Controls - OSC Controls - - - - MIDI Settings - MIDI Settings - - - - MIDI settings - MIDI settings - - - - GStreamer - GStreamer - - - - Media Settings - Media Settings - - - - Triggers - Triggers - - - - OSC settings - OSC settings - - - - Cart Layout - Cart Layout - - - - SpeedSettings - - - Speed - Speed - - - - StopAll - - - Stop Action - Stop Action - - - - Synchronizer - - - Synchronization - Synchronization - - - - Manage connected peers - Manage connected peers - - - - Show your IP - Show your IP - - - - Your IP is: - Your IP is: - - - - Timecode - - - Cannot load timecode protocol: "{}" - Cannot load timecode protocol: "{}" - - - - TimecodeError - - - Cannot send timecode. - Cannot send timecode. - - - - TimecodeSettings - - - Replace HOURS by a static track number - Replace HOURS by a static track number - - - - Enable Timecode - Enable Timecode - - - - Track number - Track number - - - - Timecode Settings - Timecode Settings - - - - Timecode Format: - Timecode Format: - - - - Timecode Protocol: - Timecode Protocol: - - - - TriggersSettings - - - Add - Add - - - - Remove - Remove - - - - Trigger - Trigger - - - - Cue - Cue - - - - Action - Action - - - - UriInputSettings - - - Source - Source - - - - Find File - Find File - - - - Buffering - Buffering - - - - Use Buffering - Use Buffering - - - - Attempt download on network streams - Attempt download on network streams - - - - Buffer size (-1 default value) - Buffer size (-1 default value) - - - - Choose file - Choose file - - - - All files - All files - - - - UserElementSettings - - - User defined elements - User defined elements - - - - Only for advanced user! - Only for advanced user! - - - - VolumeControl - - - Cue - Cue - - - - Click to select - Click to select - - - - Not selected - Not selected - - - - Volume to reach - Volume to reach - - - - Fade - Fade - - - - VolumeControlError - - - Error during cue execution. - Error during cue execution. - - - - VolumeSettings - - - Volume - Volume - - - - Normalized volume - Normalized volume - - - - Reset - Reset - diff --git a/lisp/i18n/ts/fr_FR/presets.ts b/lisp/i18n/ts/fr_FR/presets.ts index a8049e07b..37a56df90 100644 --- a/lisp/i18n/ts/fr_FR/presets.ts +++ b/lisp/i18n/ts/fr_FR/presets.ts @@ -131,10 +131,5 @@ Cannot create a cue from this preset: {} Impossible de créer un go depuis ce pré-réglage : {} - - - Cannot create a cue from this preset: {} - Cannot create a cue from this preset: {} - diff --git a/lisp/i18n/ts/it_IT/lisp.ts b/lisp/i18n/ts/it_IT/lisp.ts index ce5c5d71f..9249b392a 100644 --- a/lisp/i18n/ts/it_IT/lisp.ts +++ b/lisp/i18n/ts/it_IT/lisp.ts @@ -1,14 +1,6 @@ - - APIServerInfo - - - Stop serving network API - Stop serving network API - - About @@ -153,154 +145,6 @@ New configuration installed at {} - - Controller - - - Cannot load controller protocol: "{}" - Cannot load controller protocol: "{}" - - - - ControllerKeySettings - - - Action - Action - - - - Shortcuts - Shortcuts - - - - Shortcut - Shortcut - - - - ControllerMidiSettings - - - MIDI - MIDI - - - - Type - Type - - - - Channel - Channel - - - - Note - Note - - - - Action - Action - - - - Filter "note on" - Filter "note on" - - - - Filter "note off" - Filter "note off" - - - - Capture - Capture - - - - Listening MIDI messages ... - Listening MIDI messages ... - - - - ControllerOscSettings - - - OSC Message - OSC Message - - - - OSC - OSC - - - - Path - Path - - - - Types - Types - - - - Arguments - Arguments - - - - Actions - Actions - - - - OSC Capture - OSC Capture - - - - Add - Add - - - - Remove - Remove - - - - Capture - Capture - - - - ControllerSettings - - - Add - Add - - - - Remove - Remove - - - - Cue Name - - - OSC Settings - OSC Settings - - CueAction @@ -430,46 +274,6 @@ Media Cue Cue Multimediale - - - Index Action - Index Action - - - - Seek Cue - Seek Cue - - - - Volume Control - Volume Control - - - - Command Cue - Command Cue - - - - Collection Cue - Collection Cue - - - - Stop-All - Stop-All - - - - MIDI Cue - MIDI Cue - - - - OSC Cue - OSC Cue - CueNextAction @@ -557,60 +361,6 @@ Fade actions default value - - CueTriggers - - - Started - Started - - - - Paused - Paused - - - - Stopped - Stopped - - - - Ended - Ended - - - - DbMeterSettings - - - DbMeter settings - DbMeter settings - - - - Time between levels (ms) - Time between levels (ms) - - - - Peak ttl (ms) - Peak ttl (ms) - - - - Peak falloff (dB/sec) - Peak falloff (dB/sec) - - - - Equalizer10Settings - - - 10 Bands Equalizer (IIR) - 10 Bands Equalizer (IIR) - - Fade @@ -841,7 +591,7 @@ - MIDICue + MainWindow &File @@ -852,9 +602,6 @@ New session Nuova sessione - - - MIDIMessageAttr Open @@ -900,9 +647,6 @@ Redo Ripeti - - - MIDIMessageType Select all @@ -1037,11 +781,11 @@ - PresetSrcSettings + SettingsPageName - - Presets - Presets + + Appearance + Aspetto @@ -1083,336 +827,5 @@ Layouts Layouts - - - Action Settings - Action Settings - - - - Seek Settings - Seek Settings - - - - Volume Settings - Volume Settings - - - - Command - Command - - - - Edit Collection - Edit Collection - - - - Stop Settings - Stop Settings - - - - List Layout - List Layout - - - - Timecode - Timecode - - - - Timecode Settings - Timecode Settings - - - - Cue Control - Cue Control - - - - Layout Controls - Layout Controls - - - - MIDI Controls - MIDI Controls - - - - Keyboard Shortcuts - Keyboard Shortcuts - - - - OSC Controls - OSC Controls - - - - MIDI Settings - MIDI Settings - - - - MIDI settings - MIDI settings - - - - GStreamer - GStreamer - - - - Media Settings - Media Settings - - - - Triggers - Triggers - - - - OSC settings - OSC settings - - - - Cart Layout - Cart Layout - - - - SpeedSettings - - - Speed - Speed - - - - StopAll - - - Stop Action - Stop Action - - - - Synchronizer - - - Synchronization - Synchronization - - - - Manage connected peers - Manage connected peers - - - - Show your IP - Show your IP - - - - Your IP is: - Your IP is: - - - - Timecode - - - Cannot load timecode protocol: "{}" - Cannot load timecode protocol: "{}" - - - - TimecodeError - - - Cannot send timecode. - Cannot send timecode. - - - - TimecodeSettings - - - Replace HOURS by a static track number - Replace HOURS by a static track number - - - - Enable Timecode - Enable Timecode - - - - Track number - Track number - - - - Timecode Settings - Timecode Settings - - - - Timecode Format: - Timecode Format: - - - - Timecode Protocol: - Timecode Protocol: - - - - TriggersSettings - - - Add - Add - - - - Remove - Remove - - - - Trigger - Trigger - - - - Cue - Cue - - - - Action - Action - - - - UriInputSettings - - - Source - Source - - - - Find File - Find File - - - - Buffering - Buffering - - - - Use Buffering - Use Buffering - - - - Attempt download on network streams - Attempt download on network streams - - - - Buffer size (-1 default value) - Buffer size (-1 default value) - - - - Choose file - Choose file - - - - All files - All files - - - - UserElementSettings - - - User defined elements - User defined elements - - - - Only for advanced user! - Only for advanced user! - - - - VolumeControl - - - Cue - Cue - - - - Click to select - Click to select - - - - Not selected - Not selected - - - - Volume to reach - Volume to reach - - - - Fade - Fade - - - - VolumeControlError - - - Error during cue execution. - Error during cue execution. - - - - VolumeSettings - - - Volume - Volume - - - - Normalized volume - Normalized volume - - - - Reset - Reset - diff --git a/lisp/i18n/ts/nl_BE/lisp.ts b/lisp/i18n/ts/nl_BE/lisp.ts index c451c7d49..a82f51106 100644 --- a/lisp/i18n/ts/nl_BE/lisp.ts +++ b/lisp/i18n/ts/nl_BE/lisp.ts @@ -1,14 +1,6 @@ - - APIServerInfo - - - Stop serving network API - Stop serving network API - - About @@ -153,154 +145,6 @@ New configuration installed at {} - - Controller - - - Cannot load controller protocol: "{}" - Cannot load controller protocol: "{}" - - - - ControllerKeySettings - - - Action - Action - - - - Shortcuts - Shortcuts - - - - Shortcut - Shortcut - - - - ControllerMidiSettings - - - MIDI - MIDI - - - - Type - Type - - - - Channel - Channel - - - - Note - Note - - - - Action - Action - - - - Filter "note on" - Filter "note on" - - - - Filter "note off" - Filter "note off" - - - - Capture - Capture - - - - Listening MIDI messages ... - Listening MIDI messages ... - - - - ControllerOscSettings - - - OSC Message - OSC Message - - - - OSC - OSC - - - - Path - Path - - - - Types - Types - - - - Arguments - Arguments - - - - Actions - Actions - - - - OSC Capture - OSC Capture - - - - Add - Add - - - - Remove - Remove - - - - Capture - Capture - - - - ControllerSettings - - - Add - Add - - - - Remove - Remove - - - - Cue Name - - - OSC Settings - OSC Settings - - CueAction @@ -430,46 +274,6 @@ Media Cue Media Cue - - - Index Action - Index Action - - - - Seek Cue - Seek Cue - - - - Volume Control - Volume Control - - - - Command Cue - Command Cue - - - - Collection Cue - Collection Cue - - - - Stop-All - Stop-All - - - - MIDI Cue - MIDI Cue - - - - OSC Cue - OSC Cue - CueNextAction @@ -558,7 +362,7 @@ - CueTriggers + Fade Linear @@ -576,7 +380,7 @@ - DbMeterSettings + FadeEdit Duration (sec): @@ -787,7 +591,7 @@ - MIDICue + MainWindow &File @@ -798,9 +602,6 @@ New session Nieuwe Sessie - - - MIDIMessageAttr Open @@ -846,9 +647,6 @@ Redo Opnieuw uitvoeren - - - MIDIMessageType Select all @@ -974,140 +772,6 @@ Herhaling - - Preset - - - Right click to reset - Rechts klikken om te resetten - - - - PresetSrcSettings - - - Appearance - Weergave - - - - Presets - - - General - Algemeen - - - - Error while deleting preset "{}" - Error while deleting preset "{}" - - - - Cue Settings - Cue instellingen - - - - Cannot save preset "{}" - Cannot save preset "{}" - - - - Cannot rename preset "{}" - Cannot rename preset "{}" - - - - Select Preset - Select Preset - - - - Presets - Presets - - - - Preset name - Preset name - - - - Add - Add - - - - Rename - Rename - - - - Edit - Edit - - - - Remove - Remove - - - - Export selected - Export selected - - - - Import - Import - - - - Warning - Warning - - - - The same name is already used! - The same name is already used! - - - - Cannot export correctly. - Cannot export correctly. - - - - Cannot import correctly. - Cannot import correctly. - - - - Cue type - Cue type - - - - Load on cue - Load on cue - - - - Load on selected cues - Load on selected cues - - - - Save as preset - Save as preset - - - - Cannot create a cue from this preset: {} - Cannot create a cue from this preset: {} - - QColorButton @@ -1116,192 +780,6 @@ Rechts klikken om te resetten - - RenameCues - - - Rename Cues - Rename Cues - - - - Rename cues - Rename cues - - - - Current - Current - - - - Preview - Preview - - - - Capitalize - Capitalize - - - - Lowercase - Lowercase - - - - Uppercase - Uppercase - - - - Remove Numbers - Remove Numbers - - - - Add numbering - Add numbering - - - - Reset - Reset - - - - Type your regex here: - Type your regex here: - - - - Regex help - Regex help - - - - RenameCuesCommand - - - Renamed {number} cues - Renamed {number} cues - - - - RenameUiDebug - - - Regex error: Invalid pattern - Regex error: Invalid pattern - - - - ReplayGain - - - ReplayGain / Normalization - ReplayGain / Normalization - - - - Threads number - Threads number - - - - Apply only to selected media - Apply only to selected media - - - - ReplayGain to (dB SPL) - ReplayGain to (dB SPL) - - - - Normalize to (dB) - Normalize to (dB) - - - - Processing files ... - Processing files ... - - - - Calculate - Calculate - - - - Reset all - Reset all - - - - Reset selected - Reset selected - - - - ReplayGainDebug - - - Applied gain for: {} - Applied gain for: {} - - - - Discarded gain for: {} - Discarded gain for: {} - - - - ReplayGainInfo - - - Gain processing stopped by user. - Gain processing stopped by user. - - - - Started gain calculation for: {} - Started gain calculation for: {} - - - - Gain calculated for: {} - Gain calculated for: {} - - - - SeekCue - - - Cue - Cue - - - - Click to select - Click to select - - - - Not selected - Not selected - - - - Seek - Seek - - - - Time to reach - Time to reach - - SettingsPageName @@ -1349,336 +827,5 @@ Layouts Layouts - - - Action Settings - Action Settings - - - - Seek Settings - Seek Settings - - - - Volume Settings - Volume Settings - - - - Command - Command - - - - Edit Collection - Edit Collection - - - - Stop Settings - Stop Settings - - - - List Layout - List Layout - - - - Timecode - Timecode - - - - Timecode Settings - Timecode Settings - - - - Cue Control - Cue Control - - - - Layout Controls - Layout Controls - - - - MIDI Controls - MIDI Controls - - - - Keyboard Shortcuts - Keyboard Shortcuts - - - - OSC Controls - OSC Controls - - - - MIDI Settings - MIDI Settings - - - - MIDI settings - MIDI settings - - - - GStreamer - GStreamer - - - - Media Settings - Media Settings - - - - Triggers - Triggers - - - - OSC settings - OSC settings - - - - Cart Layout - Cart Layout - - - - SpeedSettings - - - Speed - Speed - - - - StopAll - - - Stop Action - Stop Action - - - - Synchronizer - - - Synchronization - Synchronization - - - - Manage connected peers - Manage connected peers - - - - Show your IP - Show your IP - - - - Your IP is: - Your IP is: - - - - Timecode - - - Cannot load timecode protocol: "{}" - Cannot load timecode protocol: "{}" - - - - TimecodeError - - - Cannot send timecode. - Cannot send timecode. - - - - TimecodeSettings - - - Replace HOURS by a static track number - Replace HOURS by a static track number - - - - Enable Timecode - Enable Timecode - - - - Track number - Track number - - - - Timecode Settings - Timecode Settings - - - - Timecode Format: - Timecode Format: - - - - Timecode Protocol: - Timecode Protocol: - - - - TriggersSettings - - - Add - Add - - - - Remove - Remove - - - - Trigger - Trigger - - - - Cue - Cue - - - - Action - Action - - - - UriInputSettings - - - Source - Source - - - - Find File - Find File - - - - Buffering - Buffering - - - - Use Buffering - Use Buffering - - - - Attempt download on network streams - Attempt download on network streams - - - - Buffer size (-1 default value) - Buffer size (-1 default value) - - - - Choose file - Choose file - - - - All files - All files - - - - UserElementSettings - - - User defined elements - User defined elements - - - - Only for advanced user! - Only for advanced user! - - - - VolumeControl - - - Cue - Cue - - - - Click to select - Click to select - - - - Not selected - Not selected - - - - Volume to reach - Volume to reach - - - - Fade - Fade - - - - VolumeControlError - - - Error during cue execution. - Error during cue execution. - - - - VolumeSettings - - - Volume - Volume - - - - Normalized volume - Normalized volume - - - - Reset - Reset - diff --git a/lisp/i18n/ts/nl_NL/lisp.ts b/lisp/i18n/ts/nl_NL/lisp.ts index 478fbdbef..eb75f3cb5 100644 --- a/lisp/i18n/ts/nl_NL/lisp.ts +++ b/lisp/i18n/ts/nl_NL/lisp.ts @@ -1,14 +1,6 @@ - - APIServerInfo - - - Stop serving network API - Stop serving network API - - About @@ -152,6 +144,9 @@ New configuration installed at {} New configuration installed at {} + + + CueAction Default @@ -235,9 +230,6 @@ Set Font Size Lettertypegrootte instellen - - - CommandsStack Color @@ -248,9 +240,6 @@ Select background color Achtergrondkleur instellen - - - ConfigurationDebug Select font color @@ -279,7 +268,7 @@ - ConfigurationInfo + CueName Media Cue @@ -287,7 +276,7 @@ - Controller + CueNextAction Do Nothing @@ -315,7 +304,7 @@ - ControllerMidiSettings + CueSettings Pre wait @@ -371,6 +360,9 @@ Fade actions default value Fade actions default value + + + Fade Linear @@ -388,7 +380,7 @@ - ControllerSettings + FadeEdit Duration (sec): @@ -401,7 +393,7 @@ - Cue Name + FadeSettings Fade In @@ -420,39 +412,6 @@ Press shortcut Press shortcut - - - Click a cue to run it - Click a cue to run it - - - - SHIFT + Click to edit a cue - SHIFT + Click to edit a cue - - - - CTRL + Click to select a cue - CTRL + Click to select a cue - - - - To move cues drag them while pressing SHIFT - To move cues drag them while pressing SHIFT - - - - LayoutName - - - List Layout - List Layout - - - - Cart Layout - Cart Layout - LayoutSelect @@ -475,200 +434,29 @@ ListLayout - - Default behaviors - Default behaviors - - - - Show playing cues - Show playing cues - - - - Show dB-meters - Show dB-meters - - - - Show accurate time - Show accurate time - - - - Show seek-bars - Show seek-bars - - - - Auto-select next cue - Auto-select next cue - - - - Enable selection mode - Enable selection mode - - - - GO Key: - GO Key: - - - - GO Action: - GO Action: - - - - GO minimum interval (ms): - GO minimum interval (ms): - - - - Use fade (buttons) - Use fade (buttons) - - - - Stop Cue - Stop Cue - - - - Pause Cue - Pause Cue - - - - Resume Cue - Resume Cue - - - - Interrupt Cue - Interrupt Cue - - - - Edit cue - Edit cue - - - - Edit selected - Edit selected - - - - Clone cue - Clone cue - - - - Clone selected - Clone selected - - - - Remove cue - Remove cue - - - - Remove selected - Remove selected - - - - Selection mode - Selection mode - - - - Pause all - Pause all + + Layout actions + Layout actions - Stop all - Stop all + Fade out when stopping all cues + Fade out when stopping all cues - Interrupt all - Interrupt all - - - - Resume all - Resume all - - - - Fade-Out all - Fade-Out all - - - - Fade-In all - Fade-In all - - - - Edit selected cues - Edit selected cues - - - - Remove selected cues - Remove selected cues - - - - Use fade - Use fade - - - - Copy of {} - Copy of {} - - - - ListLayoutHeader - - - Cue - Cue - - - - Pre wait - Pre wait - - - - Action - Action - - - - Post wait - Post wait + Fade out when interrupting all cues + Fade out when interrupting all cues - - - ListLayoutInfoPanel - - Cue name - Cue name + + Fade out when pausing all cues + Fade out when pausing all cues - - Cue description - Cue description + + Fade in when resuming all cues + Fade in when resuming all cues @@ -796,147 +584,10 @@ Exception info Exception info - - - MIDICue - - - MIDI Message - MIDI Message - - - - Message type - Message type - - - - MIDIMessageAttr - - - Channel - Channel - - - - Note - Note - - - - Velocity - Velocity - - - - Control - Control - - - - Program - Program - - - - Value - Value - - - - Song - Song - - - - Pitch - Pitch - - - - Position - Position - - - - MIDIMessageType - - - Note ON - Note ON - - - - Note OFF - Note OFF - - - - Polyphonic After-touch - Polyphonic After-touch - - - - Control/Mode Change - Control/Mode Change - - - - Program Change - Program Change - - - - Channel After-touch - Channel After-touch - - - - Pitch Bend Change - Pitch Bend Change - - - - Song Select - Song Select - - - - Song Position - Song Position - - - - Start - Start - - - - Stop - Stop - - - - Continue - Continue - - - - MIDISettings - - - MIDI default devices - MIDI default devices - - - - Input - Input - - - Output - Output + + Showing {} of {} records + Showing {} of {} records @@ -982,97 +633,97 @@ Afsluiten - + &Edit B&ewerken - + Undo Ongedaan maken - + Redo Opnieuw - + Select all Alles selecteren - + Select all media cues Alle mediacues selecteren - + Deselect all Alles deselecteren - + CTRL+SHIFT+A CTRL+SHIFT+A - + Invert selection Selectie omkeren - + CTRL+I CTRL+I - + Edit selected Geselecteerde bewerken - + CTRL+SHIFT+E CTRL+SHIFT+E - + &Layout Inde&ling - + &Tools &Hulpmiddelen - + Edit selection Selectie bewerken - + &About &Over - + About Over - + About Qt Over Qt - + Close session Sessie sluiten - + Do you want to save them now? Do you want to save them now? @@ -1080,7 +731,7 @@ MainWindowDebug - + Registered cue menu: "{}" Registered cue menu: "{}" @@ -1088,7 +739,7 @@ MainWindowError - + Cannot create cue {} Cannot create cue {} @@ -1122,835 +773,45 @@ - MediaElementName - - - Pitch - Pitch - - - - Volume - Volume - - - - Audio Pan - Audio Pan - - - - Compressor/Expander - Compressor/Expander - - - - JACK Out - JACK Out - - - - URI Input - URI Input - + QColorButton - - 10 Bands Equalizer - 10 Bands Equalizer - - - - ALSA Out - ALSA Out - - - - dB Meter - dB Meter - - - - Preset Input - Preset Input + + Right click to reset + Rechtsklikken om te herstellen + + + SettingsPageName - - PulseAudio Out - PulseAudio Out + + Appearance + Uiterlijk - - Custom Element - Custom Element + + General + Algemeen - - Speed - Speed + + Cue + Cue - - System Out - System Out + + Cue Settings + Cue-instellingen - - System Input - System Input + + Plugins + Plugins - - - MediaInfo - - Media Info - Media Info + + Behaviours + Behaviours - - - Warning - Warning - - - - No info to display - No info to display - - - - Info - Info - - - - Value - Value - - - - NetworkApiDebug - - - New end-point: {} - New end-point: {} - - - - NetworkDiscovery - - - Host discovery - Host discovery - - - - Manage hosts - Manage hosts - - - - Discover hosts - Discover hosts - - - - Manually add a host - Manually add a host - - - - Remove selected host - Remove selected host - - - - Remove all host - Remove all host - - - - Address - Address - - - - Host IP - Host IP - - - - Osc Cue - - - Type - Type - - - - Argument - Argument - - - - OscCue - - - Add - Add - - - - Remove - Remove - - - - Type - Type - - - - Value - Value - - - - FadeTo - FadeTo - - - - Fade - Fade - - - - OSC Message - OSC Message - - - - OSC Path: - OSC Path: - - - - /path/to/something - /path/to/something - - - - Time (sec) - Time (sec) - - - - Curve - Curve - - - - OscServerDebug - - - Message from {} -> path: "{}" args: {} - Message from {} -> path: "{}" args: {} - - - - OscServerError - - - Cannot start OSC sever - Cannot start OSC sever - - - - OscServerInfo - - - OSC server started at {} - OSC server started at {} - - - - OSC server stopped - OSC server stopped - - - - OscSettings - - - OSC Settings - OSC Settings - - - - Input Port: - Input Port: - - - - Output Port: - Output Port: - - - - Hostname: - Hostname: - - - - PitchSettings - - - Pitch - Pitch - - - - {0:+} semitones - {0:+} semitones - - - - PluginsError - - - Failed to load "{}" - Failed to load "{}" - - - - Failed to terminate plugin: "{}" - Failed to terminate plugin: "{}" - - - - the requested plugin is not loaded: {} - the requested plugin is not loaded: {} - - - - PluginsInfo - - - Plugin loaded: "{}" - Plugin loaded: "{}" - - - - Plugin terminated: "{}" - Plugin terminated: "{}" - - - - PluginsWarning - - - Cannot satisfy dependencies for: {} - Cannot satisfy dependencies for: {} - - - - Preset - - - Create Cue - Create Cue - - - - Load on selected Cues - Load on selected Cues - - - - PresetSrcSettings - - - Presets - Presets - - - - Presets - - - Cannot scan presets - Cannot scan presets - - - - Error while deleting preset "{}" - Error while deleting preset "{}" - - - - Cannot load preset "{}" - Cannot load preset "{}" - - - - Cannot save preset "{}" - Cannot save preset "{}" - - - - Cannot rename preset "{}" - Cannot rename preset "{}" - - - - Select Preset - Select Preset - - - - Presets - Presets - - - - Preset name - Preset name - - - - Add - Add - - - - Rename - Rename - - - - Edit - Edit - - - - Remove - Remove - - - - Layout selection - Indelingselectie - - - - Select layout - Indeling selecteren - - - - Open file - Bestand openen - - - - Layout actions - Layout actions - - - - Fade out when stopping all cues - Fade out when stopping all cues - - - - Fade out when interrupting all cues - Fade out when interrupting all cues - - - - Fade out when pausing all cues - Fade out when pausing all cues - - - - Fade in when resuming all cues - Fade in when resuming all cues - - - - LogStatusIcon - - - Errors/Warnings - Errors/Warnings - - - - SeekCue - - - Debug - Foutopsporing - - - - Warning - Waarschuwing - - - - Error - Fout - - - - Dismiss all - Dismiss all - - - - Show details - Show details - - - - Linux Show Player - Log Viewer - Linux Show Player - Log Viewer - - - - Info - Info - - - - Critical - Critical - - - - Time - Time - - - - Milliseconds - Milliseconds - - - - Logger name - Logger name - - - - Level - Level - - - - Message - Message - - - - Function - Function - - - - Path name - Path name - - - - File name - File name - - - - Line no. - Line no. - - - - Module - Module - - - - Process ID - Process ID - - - - Process name - Process name - - - - Thread ID - Thread ID - - - - Thread name - Thread name - - - - Exception info - Exception info - - - - Showing {} of {} records - Showing {} of {} records - - - - MainWindow - - - &File - &Bestand: - - - - New session - Nieuwe sessie - - - - Open - Openen - - - - Save session - Sessie opslaan - - - - Preferences - Voorkeuren - - - - Save as - Opslaan als - - - - Full Screen - Volledig scherm - - - - Exit - Afsluiten - - - - &Edit - B&ewerken - - - - Undo - Ongedaan maken - - - - Redo - Opnieuw - - - - SpeedSettings - - - Select all - Alles selecteren - - - - StopAll - - - Select all media cues - Alle mediacues selecteren - - - - Synchronizer - - - Deselect all - Alles deselecteren - - - - CTRL+SHIFT+A - CTRL+SHIFT+A - - - - Invert selection - Selectie omkeren - - - - CTRL+I - CTRL+I - - - - Timecode - - - Edit selected - Geselecteerde bewerken - - - - TimecodeError - - - CTRL+SHIFT+E - CTRL+SHIFT+E - - - - TimecodeSettings - - - &Layout - Inde&ling - - - - &Tools - &Hulpmiddelen - - - - Edit selection - Selectie bewerken - - - - &About - &Over - - - - About - Over - - - - About Qt - Over Qt - - - - Close session - Sessie sluiten - - - - Do you want to save them now? - Do you want to save them now? - - - - MainWindowDebug - - - Registered cue menu: "{}" - Registered cue menu: "{}" - - - - MainWindowError - - - Cannot create cue {} - Cannot create cue {} - - - - Start time - Starttijd - - - - Stop position of the media - Stoppositie van de media - - - - Stop time - Stoptijd - - - - Start position of the media - Startpositie van de media - - - - Loop - Herhalen - - - - UserElementSettings - - - Right click to reset - Rechtsklikken om te herstellen - - - - VolumeControl - - - Appearance - Uiterlijk - - - - General - Algemeen - - - - Cue - Cue - - - - Cue Settings - Cue-instellingen - - - - Plugins - Plugins - - - - VolumeControlError - - - Behaviours - Behaviours - - - - VolumeSettings Pre/Post Wait diff --git a/lisp/i18n/ts/sl_SI/lisp.ts b/lisp/i18n/ts/sl_SI/lisp.ts index e27ea6898..6dcc4db5f 100644 --- a/lisp/i18n/ts/sl_SI/lisp.ts +++ b/lisp/i18n/ts/sl_SI/lisp.ts @@ -1,14 +1,6 @@ - - APIServerInfo - - - Stop serving network API - Stop serving network API - - About @@ -153,154 +145,6 @@ New configuration installed at {} - - Controller - - - Cannot load controller protocol: "{}" - Cannot load controller protocol: "{}" - - - - ControllerKeySettings - - - Action - Action - - - - Shortcuts - Shortcuts - - - - Shortcut - Shortcut - - - - ControllerMidiSettings - - - MIDI - MIDI - - - - Type - Type - - - - Channel - Channel - - - - Note - Note - - - - Action - Action - - - - Filter "note on" - Filter "note on" - - - - Filter "note off" - Filter "note off" - - - - Capture - Capture - - - - Listening MIDI messages ... - Listening MIDI messages ... - - - - ControllerOscSettings - - - OSC Message - OSC Message - - - - OSC - OSC - - - - Path - Path - - - - Types - Types - - - - Arguments - Arguments - - - - Actions - Actions - - - - OSC Capture - OSC Capture - - - - Add - Add - - - - Remove - Remove - - - - Capture - Capture - - - - ControllerSettings - - - Add - Add - - - - Remove - Remove - - - - Cue Name - - - OSC Settings - OSC Settings - - CueAction @@ -430,46 +274,6 @@ Media Cue Medijska vrsta - - - Index Action - Index Action - - - - Seek Cue - Seek Cue - - - - Volume Control - Volume Control - - - - Command Cue - Command Cue - - - - Collection Cue - Collection Cue - - - - Stop-All - Stop-All - - - - MIDI Cue - MIDI Cue - - - - OSC Cue - OSC Cue - CueNextAction @@ -557,60 +361,6 @@ Fade actions default value - - CueTriggers - - - Started - Started - - - - Paused - Paused - - - - Stopped - Stopped - - - - Ended - Ended - - - - DbMeterSettings - - - DbMeter settings - DbMeter settings - - - - Time between levels (ms) - Time between levels (ms) - - - - Peak ttl (ms) - Peak ttl (ms) - - - - Peak falloff (dB/sec) - Peak falloff (dB/sec) - - - - Equalizer10Settings - - - 10 Bands Equalizer (IIR) - 10 Bands Equalizer (IIR) - - Fade @@ -841,7 +591,7 @@ - MIDICue + MainWindow &File @@ -852,9 +602,6 @@ New session Nova seja - - - MIDIMessageAttr Open @@ -900,9 +647,6 @@ Redo Obnovi - - - MIDIMessageType Select all @@ -963,9 +707,6 @@ &About &Opis - - - MIDISettings About @@ -1004,74 +745,31 @@ - QColorButton + MediaCueSettings - - Right click to reset - Desno klik za ponastavitev - - - - RenameCues - - - Rename Cues - Rename Cues + + Start time + Čas začetka - - Rename cues - Rename cues + + Stop position of the media + Zaustavitvena pozicija medija - - Current - Current + + Stop time + Čas zaustavitve - - Preview - Preview + + Start position of the media + Začetna pozicija medija - - Capitalize - Capitalize - - - - Lowercase - Lowercase - - - - Uppercase - Uppercase - - - - Remove Numbers - Remove Numbers - - - - Add numbering - Add numbering - - - - Reset - Reset - - - - Type your regex here: - Type your regex here: - - - - Regex help - Regex help + + Loop + Ponavljaj @@ -1129,336 +827,5 @@ Layouts Layouts - - - Action Settings - Action Settings - - - - Seek Settings - Seek Settings - - - - Volume Settings - Volume Settings - - - - Command - Command - - - - Edit Collection - Edit Collection - - - - Stop Settings - Stop Settings - - - - List Layout - List Layout - - - - Timecode - Timecode - - - - Timecode Settings - Timecode Settings - - - - Cue Control - Cue Control - - - - Layout Controls - Layout Controls - - - - MIDI Controls - MIDI Controls - - - - Keyboard Shortcuts - Keyboard Shortcuts - - - - OSC Controls - OSC Controls - - - - MIDI Settings - MIDI Settings - - - - MIDI settings - MIDI settings - - - - GStreamer - GStreamer - - - - Media Settings - Media Settings - - - - Triggers - Triggers - - - - OSC settings - OSC settings - - - - Cart Layout - Cart Layout - - - - SpeedSettings - - - Speed - Speed - - - - StopAll - - - Stop Action - Stop Action - - - - Synchronizer - - - Synchronization - Synchronization - - - - Manage connected peers - Manage connected peers - - - - Show your IP - Show your IP - - - - Your IP is: - Your IP is: - - - - Timecode - - - Cannot load timecode protocol: "{}" - Cannot load timecode protocol: "{}" - - - - TimecodeError - - - Cannot send timecode. - Cannot send timecode. - - - - TimecodeSettings - - - Replace HOURS by a static track number - Replace HOURS by a static track number - - - - Enable Timecode - Enable Timecode - - - - Track number - Track number - - - - Timecode Settings - Timecode Settings - - - - Timecode Format: - Timecode Format: - - - - Timecode Protocol: - Timecode Protocol: - - - - TriggersSettings - - - Add - Add - - - - Remove - Remove - - - - Trigger - Trigger - - - - Cue - Cue - - - - Action - Action - - - - UriInputSettings - - - Source - Source - - - - Find File - Find File - - - - Buffering - Buffering - - - - Use Buffering - Use Buffering - - - - Attempt download on network streams - Attempt download on network streams - - - - Buffer size (-1 default value) - Buffer size (-1 default value) - - - - Choose file - Choose file - - - - All files - All files - - - - UserElementSettings - - - User defined elements - User defined elements - - - - Only for advanced user! - Only for advanced user! - - - - VolumeControl - - - Cue - Cue - - - - Click to select - Click to select - - - - Not selected - Not selected - - - - Volume to reach - Volume to reach - - - - Fade - Fade - - - - VolumeControlError - - - Error during cue execution. - Error during cue execution. - - - - VolumeSettings - - - Volume - Volume - - - - Normalized volume - Normalized volume - - - - Reset - Reset - diff --git a/lisp/i18n/ts/zh_TW/action_cues.ts b/lisp/i18n/ts/zh_TW/action_cues.ts new file mode 100644 index 000000000..556065f4b --- /dev/null +++ b/lisp/i18n/ts/zh_TW/action_cues.ts @@ -0,0 +1,234 @@ + + + + + CollectionCue + + + Add + Add + + + + Remove + Remove + + + + Cue + Cue + + + + Action + Action + + + + CommandCue + + + Command + Command + + + + Command to execute, as in a shell + Command to execute, as in a shell + + + + Discard command output + Discard command output + + + + Ignore command errors + Ignore command errors + + + + Kill instead of terminate + Kill instead of terminate + + + + CueCategory + + + Action cues + Action cues + + + + CueName + + + Command Cue + Command Cue + + + + Volume Control + Volume Control + + + + Seek Cue + Seek Cue + + + + Collection Cue + Collection Cue + + + + Stop-All + Stop-All + + + + Index Action + Index Action + + + + IndexActionCue + + + Index + Index + + + + Use a relative index + Use a relative index + + + + Target index + Target index + + + + Action + Action + + + + No suggestion + No suggestion + + + + Suggested cue name + Suggested cue name + + + + SeekCue + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Seek + Seek + + + + Time to reach + Time to reach + + + + SettingsPageName + + + Command + Command + + + + Volume Settings + Volume Settings + + + + Seek Settings + Seek Settings + + + + Edit Collection + Edit Collection + + + + Action Settings + Action Settings + + + + Stop Settings + Stop Settings + + + + StopAll + + + Stop Action + Stop Action + + + + VolumeControl + + + Cue + Cue + + + + Click to select + Click to select + + + + Not selected + Not selected + + + + Volume to reach + Volume to reach + + + + Fade + Fade + + + + VolumeControlError + + + Error during cue execution. + Error during cue execution. + + + diff --git a/lisp/i18n/ts/zh_TW/cache_manager.ts b/lisp/i18n/ts/zh_TW/cache_manager.ts new file mode 100644 index 000000000..c458fa553 --- /dev/null +++ b/lisp/i18n/ts/zh_TW/cache_manager.ts @@ -0,0 +1,35 @@ + + + + + CacheManager + + + Cache size warning + Cache size warning + + + + Warning threshold in MB (0 = disabled) + Warning threshold in MB (0 = disabled) + + + + Cache cleanup + Cache cleanup + + + + Delete the cache content + Delete the cache content + + + + SettingsPageName + + + Cache Manager + Cache Manager + + + diff --git a/lisp/i18n/ts/zh_TW/cart_layout.ts b/lisp/i18n/ts/zh_TW/cart_layout.ts new file mode 100644 index 000000000..5ded7f942 --- /dev/null +++ b/lisp/i18n/ts/zh_TW/cart_layout.ts @@ -0,0 +1,187 @@ + + + + + CartLayout + + + Countdown mode + Countdown mode + + + + Show seek-bars + Show seek-bars + + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show volume + Show volume + + + + Grid size + Grid size + + + + Play + Play + + + + Pause + Pause + + + + Stop + Stop + + + + Reset volume + Reset volume + + + + Add page + Add page + + + + Add pages + Add pages + + + + Remove current page + Remove current page + + + + Number of Pages: + Number of Pages: + + + + Page {number} + Page {number} + + + + Warning + Warning + + + + Every cue in the page will be lost. + Every cue in the page will be lost. + + + + Are you sure to continue? + Are you sure to continue? + + + + Number of columns: + Number of columns: + + + + Number of rows: + Number of rows: + + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + + + + LayoutDescription + + + Organize cues in grid like pages + Organize cues in grid like pages + + + + LayoutDetails + + + Click a cue to run it + Click a cue to run it + + + + SHIFT + Click to edit a cue + SHIFT + Click to edit a cue + + + + CTRL + Click to select a cue + CTRL + Click to select a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them while pressing SHIFT + To move cues drag them while pressing SHIFT + + + + LayoutName + + + Cart Layout + Cart Layout + + + + ListLayout + + + Edit cue + Edit cue + + + + Edit selected cues + Edit selected cues + + + + Remove cue + Remove cue + + + + Remove selected cues + Remove selected cues + + + + SettingsPageName + + + Cart Layout + Cart Layout + + + diff --git a/lisp/i18n/ts/zh_TW/controller.ts b/lisp/i18n/ts/zh_TW/controller.ts new file mode 100644 index 000000000..d332948f4 --- /dev/null +++ b/lisp/i18n/ts/zh_TW/controller.ts @@ -0,0 +1,274 @@ + + + + + Controller + + + Cannot load controller protocol: "{}" + Cannot load controller protocol: "{}" + + + + ControllerKeySettings + + + Action + Action + + + + Shortcuts + Shortcuts + + + + Shortcut + Shortcut + + + + ControllerMidiSettings + + + MIDI + MIDI + + + + Type + Type + + + + Action + Action + + + + Capture + Capture + + + + Listening MIDI messages ... + Listening MIDI messages ... + + + + -- All Messages -- + -- All Messages -- + + + + Capture filter + Capture filter + + + + Data 1 + Data 1 + + + + Data 2 + Data 2 + + + + Data 3 + Data 3 + + + + ControllerOscSettings + + + OSC Message + OSC Message + + + + OSC + OSC + + + + Path + Path + + + + Types + Types + + + + Arguments + Arguments + + + + OSC Capture + OSC Capture + + + + Add + Add + + + + Remove + Remove + + + + Capture + Capture + + + + Waiting for messages: + Waiting for messages: + + + + /path/to/method + /path/to/method + + + + Action + Action + + + + ControllerOscSettingsWarning + + + Warning + Warning + + + + ControllerSettings + + + Add + Add + + + + Remove + Remove + + + + GlobalAction + + + Go + Go + + + + Reset + Reset + + + + Stop all cues + Stop all cues + + + + Pause all cues + Pause all cues + + + + Resume all cues + Resume all cues + + + + Interrupt all cues + Interrupt all cues + + + + Fade-out all cues + Fade-out all cues + + + + Fade-in all cues + Fade-in all cues + + + + Move standby forward + Move standby forward + + + + Move standby back + Move standby back + + + + Osc Cue + + + Type + Type + + + + Argument + Argument + + + + OscCue + + + Add + Add + + + + Remove + Remove + + + + SettingsPageName + + + Cue Control + Cue Control + + + + MIDI Controls + MIDI Controls + + + + Keyboard Shortcuts + Keyboard Shortcuts + + + + OSC Controls + OSC Controls + + + + Layout Controls + Layout Controls + + + diff --git a/lisp/i18n/ts/zh_TW/gst_backend.ts b/lisp/i18n/ts/zh_TW/gst_backend.ts new file mode 100644 index 000000000..dfb5e5237 --- /dev/null +++ b/lisp/i18n/ts/zh_TW/gst_backend.ts @@ -0,0 +1,399 @@ + + + + + AlsaSinkSettings + + + ALSA device + ALSA device + + + + AudioDynamicSettings + + + Compressor + Compressor + + + + Expander + Expander + + + + Soft Knee + Soft Knee + + + + Hard Knee + Hard Knee + + + + Compressor/Expander + Compressor/Expander + + + + Type + Type + + + + Curve Shape + Curve Shape + + + + Ratio + Ratio + + + + Threshold (dB) + Threshold (dB) + + + + AudioPanSettings + + + Audio Pan + Audio Pan + + + + Center + Center + + + + Left + Left + + + + Right + Right + + + + DbMeterSettings + + + DbMeter settings + DbMeter settings + + + + Time between levels (ms) + Time between levels (ms) + + + + Peak ttl (ms) + Peak ttl (ms) + + + + Peak falloff (dB/sec) + Peak falloff (dB/sec) + + + + Equalizer10Settings + + + 10 Bands Equalizer (IIR) + 10 Bands Equalizer (IIR) + + + + GstBackend + + + Audio cue (from file) + Audio cue (from file) + + + + Select media files + Select media files + + + + GstMediaError + + + Cannot create pipeline element: "{}" + Cannot create pipeline element: "{}" + + + + GstMediaSettings + + + Change Pipeline + Change Pipeline + + + + GstMediaWarning + + + Invalid pipeline element: "{}" + Invalid pipeline element: "{}" + + + + GstPipelineEdit + + + Edit Pipeline + Edit Pipeline + + + + GstSettings + + + Default pipeline + Default pipeline + + + + Applied only to new cues. + Applied only to new cues. + + + + JackSinkSettings + + + Connections + Connections + + + + Edit connections + Edit connections + + + + Output ports + Output ports + + + + Input ports + Input ports + + + + Connect + Connect + + + + Disconnect + Disconnect + + + + MediaElementName + + + Compressor/Expander + Compressor/Expander + + + + Audio Pan + Audio Pan + + + + PulseAudio Out + PulseAudio Out + + + + Volume + Volume + + + + dB Meter + dB Meter + + + + System Input + System Input + + + + ALSA Out + ALSA Out + + + + JACK Out + JACK Out + + + + Custom Element + Custom Element + + + + System Out + System Out + + + + Pitch + Pitch + + + + URI Input + URI Input + + + + 10 Bands Equalizer + 10 Bands Equalizer + + + + Speed + Speed + + + + Preset Input + Preset Input + + + + PitchSettings + + + Pitch + Pitch + + + + {0:+} semitones + {0:+} semitones + + + + PresetSrcSettings + + + Presets + Presets + + + + SettingsPageName + + + Media Settings + Media Settings + + + + GStreamer + GStreamer + + + + SpeedSettings + + + Speed + Speed + + + + UriInputSettings + + + Source + Source + + + + Find File + Find File + + + + Buffering + Buffering + + + + Use Buffering + Use Buffering + + + + Attempt download on network streams + Attempt download on network streams + + + + Buffer size (-1 default value) + Buffer size (-1 default value) + + + + Choose file + Choose file + + + + All files + All files + + + + UserElementSettings + + + User defined elements + User defined elements + + + + Only for advanced user! + Only for advanced user! + + + + VolumeSettings + + + Volume + Volume + + + + Normalized volume + Normalized volume + + + + Reset + Reset + + + diff --git a/lisp/i18n/ts/zh_TW/lisp.ts b/lisp/i18n/ts/zh_TW/lisp.ts new file mode 100644 index 000000000..167caf956 --- /dev/null +++ b/lisp/i18n/ts/zh_TW/lisp.ts @@ -0,0 +1,831 @@ + + + + + About + + + Authors + Authors + + + + Contributors + Contributors + + + + Translators + Translators + + + + About Linux Show Player + About Linux Show Player + + + + AboutDialog + + + Web site + Web site + + + + Source code + Source code + + + + Info + Info + + + + License + License + + + + Contributors + Contributors + + + + Discussion + Discussion + + + + AppConfiguration + + + LiSP preferences + LiSP preferences + + + + AppGeneralSettings + + + Default layout + Default layout + + + + Application themes + Application themes + + + + UI theme: + UI theme: + + + + Icons theme: + Icons theme: + + + + Language: + Language: + + + + Show layout selection at startup + Show layout selection at startup + + + + Use layout at startup: + Use layout at startup: + + + + Application language (require a restart) + Application language (require a restart) + + + + ApplicationError + + + Startup error + Startup error + + + + CommandsStack + + + Undo: {} + Undo: {} + + + + Redo: {} + Redo: {} + + + + ConfigurationDebug + + + Configuration written at {} + Configuration written at {} + + + + ConfigurationInfo + + + New configuration installed at {} + New configuration installed at {} + + + + CueAction + + + Default + Default + + + + Pause + Pause + + + + Start + Start + + + + Stop + Stop + + + + Faded Start + Faded Start + + + + Faded Resume + Faded Resume + + + + Faded Pause + Faded Pause + + + + Faded Stop + Faded Stop + + + + Faded Interrupt + Faded Interrupt + + + + Resume + Resume + + + + Do Nothing + Do Nothing + + + + CueAppearanceSettings + + + The appearance depends on the layout + The appearance depends on the layout + + + + Cue name + Cue name + + + + NoName + NoName + + + + Description/Note + Description/Note + + + + Set Font Size + Set Font Size + + + + Color + Color + + + + Select background color + Select background color + + + + Select font color + Select font color + + + + CueCategory + + + Misc cues + Misc cues + + + + CueCommandLog + + + Cue settings changed: "{}" + Cue settings changed: "{}" + + + + Cues settings changed. + Cues settings changed. + + + + CueName + + + Media Cue + Media Cue + + + + CueNextAction + + + Do Nothing + Do Nothing + + + + Trigger after the end + Trigger after the end + + + + Trigger after post wait + Trigger after post wait + + + + Select after the end + Select after the end + + + + Select after post wait + Select after post wait + + + + CueSettings + + + Pre wait + Pre wait + + + + Wait before cue execution + Wait before cue execution + + + + Post wait + Post wait + + + + Wait after cue execution + Wait after cue execution + + + + Next action + Next action + + + + Start action + Start action + + + + Default action to start the cue + Default action to start the cue + + + + Stop action + Stop action + + + + Default action to stop the cue + Default action to stop the cue + + + + Interrupt action fade + Interrupt action fade + + + + Fade actions default value + Fade actions default value + + + + Fade + + + Linear + Linear + + + + Quadratic + Quadratic + + + + Quadratic2 + Quadratic2 + + + + FadeEdit + + + Duration (sec): + Duration (sec): + + + + Curve: + Curve: + + + + FadeSettings + + + Fade In + Fade In + + + + Fade Out + Fade Out + + + + HotKeyEdit + + + Press shortcut + Press shortcut + + + + LayoutSelect + + + Layout selection + Layout selection + + + + Select layout + Select layout + + + + Open file + Open file + + + + ListLayout + + + Layout actions + Layout actions + + + + Fade out when stopping all cues + Fade out when stopping all cues + + + + Fade out when interrupting all cues + Fade out when interrupting all cues + + + + Fade out when pausing all cues + Fade out when pausing all cues + + + + Fade in when resuming all cues + Fade in when resuming all cues + + + + LogStatusIcon + + + Errors/Warnings + Errors/Warnings + + + + Logging + + + Debug + Debug + + + + Warning + Warning + + + + Error + Error + + + + Dismiss all + Dismiss all + + + + Show details + Show details + + + + Linux Show Player - Log Viewer + Linux Show Player - Log Viewer + + + + Info + Info + + + + Critical + Critical + + + + Time + Time + + + + Milliseconds + Milliseconds + + + + Logger name + Logger name + + + + Level + Level + + + + Message + Message + + + + Function + Function + + + + Path name + Path name + + + + File name + File name + + + + Line no. + Line no. + + + + Module + Module + + + + Process ID + Process ID + + + + Process name + Process name + + + + Thread ID + Thread ID + + + + Thread name + Thread name + + + + Exception info + Exception info + + + + Showing {} of {} records + Showing {} of {} records + + + + MainWindow + + + &File + &File + + + + New session + New session + + + + Open + Open + + + + Save session + Save session + + + + Preferences + Preferences + + + + Save as + Save as + + + + Full Screen + Full Screen + + + + Exit + Exit + + + + &Edit + &Edit + + + + Undo + Undo + + + + Redo + Redo + + + + Select all + Select all + + + + Select all media cues + Select all media cues + + + + Deselect all + Deselect all + + + + CTRL+SHIFT+A + CTRL+SHIFT+A + + + + Invert selection + Invert selection + + + + CTRL+I + CTRL+I + + + + Edit selected + Edit selected + + + + CTRL+SHIFT+E + CTRL+SHIFT+E + + + + &Layout + &Layout + + + + &Tools + &Tools + + + + Edit selection + Edit selection + + + + &About + &About + + + + About + About + + + + About Qt + About Qt + + + + Close session + Close session + + + + Do you want to save them now? + Do you want to save them now? + + + + MainWindowDebug + + + Registered cue menu: "{}" + Registered cue menu: "{}" + + + + MainWindowError + + + Cannot create cue {} + Cannot create cue {} + + + + MediaCueSettings + + + Start time + Start time + + + + Stop position of the media + Stop position of the media + + + + Stop time + Stop time + + + + Start position of the media + Start position of the media + + + + Loop + Loop + + + + QColorButton + + + Right click to reset + Right click to reset + + + + SettingsPageName + + + Appearance + Appearance + + + + General + General + + + + Cue + Cue + + + + Cue Settings + Cue Settings + + + + Plugins + Plugins + + + + Behaviours + Behaviours + + + + Pre/Post Wait + Pre/Post Wait + + + + Fade In/Out + Fade In/Out + + + + Layouts + Layouts + + + diff --git a/lisp/i18n/ts/zh_TW/list_layout.ts b/lisp/i18n/ts/zh_TW/list_layout.ts new file mode 100644 index 000000000..55776818b --- /dev/null +++ b/lisp/i18n/ts/zh_TW/list_layout.ts @@ -0,0 +1,260 @@ + + + + + LayoutDescription + + + Organize the cues in a list + Organize the cues in a list + + + + LayoutDetails + + + SHIFT + Space or Double-Click to edit a cue + SHIFT + Space or Double-Click to edit a cue + + + + To copy cues drag them while pressing CTRL + To copy cues drag them while pressing CTRL + + + + To move cues drag them + To move cues drag them + + + + LayoutName + + + List Layout + List Layout + + + + ListLayout + + + Show dB-meters + Show dB-meters + + + + Show accurate time + Show accurate time + + + + Show seek-bars + Show seek-bars + + + + Auto-select next cue + Auto-select next cue + + + + Enable selection mode + Enable selection mode + + + + Use fade (buttons) + Use fade (buttons) + + + + Stop Cue + Stop Cue + + + + Pause Cue + Pause Cue + + + + Resume Cue + Resume Cue + + + + Interrupt Cue + Interrupt Cue + + + + Edit cue + Edit cue + + + + Remove cue + Remove cue + + + + Selection mode + Selection mode + + + + Pause all + Pause all + + + + Stop all + Stop all + + + + Interrupt all + Interrupt all + + + + Resume all + Resume all + + + + Fade-Out all + Fade-Out all + + + + Fade-In all + Fade-In all + + + + Edit selected + Edit selected + + + + Clone cue + Clone cue + + + + Clone selected + Clone selected + + + + Remove selected + Remove selected + + + + GO Key: + GO Key: + + + + GO Action: + GO Action: + + + + GO minimum interval (ms): + GO minimum interval (ms): + + + + Copy of {} + Copy of {} + + + + Show index column + Show index column + + + + Show resize handles + Show resize handles + + + + Restore default size + Restore default size + + + + Default behaviors (applied to new sessions) + Default behaviors (applied to new sessions) + + + + Behaviors + Behaviors + + + + Disable GO Key While Playing + Disable GO Key While Playing + + + + Use waveform seek-bars + Use waveform seek-bars + + + + GO Key Disabled While Playing + GO Key Disabled While Playing + + + + ListLayoutHeader + + + Cue + Cue + + + + Pre wait + Pre wait + + + + Action + Action + + + + Post wait + Post wait + + + + ListLayoutInfoPanel + + + Cue name + Cue name + + + + Cue description + Cue description + + + + SettingsPageName + + + List Layout + List Layout + + + diff --git a/lisp/i18n/ts/zh_TW/media_info.ts b/lisp/i18n/ts/zh_TW/media_info.ts new file mode 100644 index 000000000..e67d6adfa --- /dev/null +++ b/lisp/i18n/ts/zh_TW/media_info.ts @@ -0,0 +1,32 @@ + + + + + MediaInfo + + + Media Info + Media Info + + + + Info + Info + + + + Value + Value + + + + Warning + Warning + + + + Cannot get any information. + Cannot get any information. + + + diff --git a/lisp/i18n/ts/zh_TW/midi.ts b/lisp/i18n/ts/zh_TW/midi.ts new file mode 100644 index 000000000..9d1ae765a --- /dev/null +++ b/lisp/i18n/ts/zh_TW/midi.ts @@ -0,0 +1,216 @@ + + + + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + MIDI Cue + MIDI Cue + + + + MIDICue + + + MIDI Message + MIDI Message + + + + Message type + Message type + + + + MIDIError + + + Cannot connect to MIDI output port '{}'. + Cannot connect to MIDI output port '{}'. + + + + Cannot connect to MIDI input port '{}'. + Cannot connect to MIDI input port '{}'. + + + + MIDIInfo + + + MIDI port disconnected: '{}' + MIDI port disconnected: '{}' + + + + Connecting to MIDI port: '{}' + Connecting to MIDI port: '{}' + + + + Connecting to matching MIDI port: '{}' + Connecting to matching MIDI port: '{}' + + + + MIDIMessageAttr + + + Channel + Channel + + + + Note + Note + + + + Velocity + Velocity + + + + Control + Control + + + + Program + Program + + + + Value + Value + + + + Song + Song + + + + Pitch + Pitch + + + + Position + Position + + + + MIDIMessageType + + + Note ON + Note ON + + + + Note OFF + Note OFF + + + + Polyphonic After-touch + Polyphonic After-touch + + + + Control/Mode Change + Control/Mode Change + + + + Program Change + Program Change + + + + Channel After-touch + Channel After-touch + + + + Pitch Bend Change + Pitch Bend Change + + + + Song Select + Song Select + + + + Song Position + Song Position + + + + Start + Start + + + + Stop + Stop + + + + Continue + Continue + + + + MIDISettings + + + Input + Input + + + + Output + Output + + + + MIDI devices + MIDI devices + + + + Misc options + Misc options + + + + Try to connect using only device/port name + Try to connect using only device/port name + + + + SettingsPageName + + + MIDI settings + MIDI settings + + + + MIDI Settings + MIDI Settings + + + diff --git a/lisp/i18n/ts/zh_TW/network.ts b/lisp/i18n/ts/zh_TW/network.ts new file mode 100644 index 000000000..6f16dabdd --- /dev/null +++ b/lisp/i18n/ts/zh_TW/network.ts @@ -0,0 +1,76 @@ + + + + + APIServerInfo + + + Stop serving network API + Stop serving network API + + + + ApiServerError + + + Network API server stopped working. + Network API server stopped working. + + + + NetworkApiDebug + + + New end-point: {} + New end-point: {} + + + + NetworkDiscovery + + + Host discovery + Host discovery + + + + Manage hosts + Manage hosts + + + + Discover hosts + Discover hosts + + + + Manually add a host + Manually add a host + + + + Remove selected host + Remove selected host + + + + Remove all host + Remove all host + + + + Address + Address + + + + Host IP + Host IP + + + + Select the hosts you want to add + Select the hosts you want to add + + + diff --git a/lisp/i18n/ts/zh_TW/osc.ts b/lisp/i18n/ts/zh_TW/osc.ts new file mode 100644 index 000000000..32ac5ca8a --- /dev/null +++ b/lisp/i18n/ts/zh_TW/osc.ts @@ -0,0 +1,146 @@ + + + + + Cue Name + + + OSC Settings + OSC Settings + + + + CueCategory + + + Integration cues + Integration cues + + + + CueName + + + OSC Cue + OSC Cue + + + + OscCue + + + Type + Type + + + + Value + Value + + + + FadeTo + FadeTo + + + + Fade + Fade + + + + OSC Message + OSC Message + + + + Add + Add + + + + Remove + Remove + + + + OSC Path: + OSC Path: + + + + /path/to/something + /path/to/something + + + + Time (sec) + Time (sec) + + + + Curve + Curve + + + + OscServerDebug + + + Message from {} -> path: "{}" args: {} + Message from {} -> path: "{}" args: {} + + + + OscServerError + + + Cannot start OSC sever + Cannot start OSC sever + + + + OscServerInfo + + + OSC server started at {} + OSC server started at {} + + + + OSC server stopped + OSC server stopped + + + + OscSettings + + + OSC Settings + OSC Settings + + + + Input Port: + Input Port: + + + + Output Port: + Output Port: + + + + Hostname: + Hostname: + + + + SettingsPageName + + + OSC settings + OSC settings + + + diff --git a/lisp/i18n/ts/zh_TW/presets.ts b/lisp/i18n/ts/zh_TW/presets.ts new file mode 100644 index 000000000..328b21669 --- /dev/null +++ b/lisp/i18n/ts/zh_TW/presets.ts @@ -0,0 +1,135 @@ + + + + + Preset + + + Create Cue + Create Cue + + + + Load on selected Cues + Load on selected Cues + + + + Presets + + + Presets + Presets + + + + Save as preset + Save as preset + + + + Cannot scan presets + Cannot scan presets + + + + Error while deleting preset "{}" + Error while deleting preset "{}" + + + + Cannot load preset "{}" + Cannot load preset "{}" + + + + Cannot save preset "{}" + Cannot save preset "{}" + + + + Cannot rename preset "{}" + Cannot rename preset "{}" + + + + Select Preset + Select Preset + + + + Preset name + Preset name + + + + Add + Add + + + + Rename + Rename + + + + Edit + Edit + + + + Remove + Remove + + + + Export selected + Export selected + + + + Import + Import + + + + Warning + Warning + + + + The same name is already used! + The same name is already used! + + + + Cannot export correctly. + Cannot export correctly. + + + + Cannot import correctly. + Cannot import correctly. + + + + Cue type + Cue type + + + + Load on cue + Load on cue + + + + Load on selected cues + Load on selected cues + + + + Cannot create a cue from this preset: {} + Cannot create a cue from this preset: {} + + + diff --git a/lisp/i18n/ts/zh_TW/rename_cues.ts b/lisp/i18n/ts/zh_TW/rename_cues.ts new file mode 100644 index 000000000..dd5289c5e --- /dev/null +++ b/lisp/i18n/ts/zh_TW/rename_cues.ts @@ -0,0 +1,83 @@ + + + + + RenameCues + + + Rename Cues + Rename Cues + + + + Rename cues + Rename cues + + + + Current + Current + + + + Preview + Preview + + + + Capitalize + Capitalize + + + + Lowercase + Lowercase + + + + Uppercase + Uppercase + + + + Remove Numbers + Remove Numbers + + + + Add numbering + Add numbering + + + + Reset + Reset + + + + Type your regex here: + Type your regex here: + + + + Regex help + Regex help + + + + RenameCuesCommand + + + Renamed {number} cues + Renamed {number} cues + + + + RenameUiDebug + + + Regex error: Invalid pattern + Regex error: Invalid pattern + + + diff --git a/lisp/i18n/ts/zh_TW/replay_gain.ts b/lisp/i18n/ts/zh_TW/replay_gain.ts new file mode 100644 index 000000000..2012cb779 --- /dev/null +++ b/lisp/i18n/ts/zh_TW/replay_gain.ts @@ -0,0 +1,83 @@ + + + + + ReplayGain + + + ReplayGain / Normalization + ReplayGain / Normalization + + + + Calculate + Calculate + + + + Reset all + Reset all + + + + Reset selected + Reset selected + + + + Threads number + Threads number + + + + Apply only to selected media + Apply only to selected media + + + + ReplayGain to (dB SPL) + ReplayGain to (dB SPL) + + + + Normalize to (dB) + Normalize to (dB) + + + + Processing files ... + Processing files ... + + + + ReplayGainDebug + + + Applied gain for: {} + Applied gain for: {} + + + + Discarded gain for: {} + Discarded gain for: {} + + + + ReplayGainInfo + + + Gain processing stopped by user. + Gain processing stopped by user. + + + + Started gain calculation for: {} + Started gain calculation for: {} + + + + Gain calculated for: {} + Gain calculated for: {} + + + diff --git a/lisp/i18n/ts/zh_TW/synchronizer.ts b/lisp/i18n/ts/zh_TW/synchronizer.ts new file mode 100644 index 000000000..af377c2c1 --- /dev/null +++ b/lisp/i18n/ts/zh_TW/synchronizer.ts @@ -0,0 +1,27 @@ + + + + + Synchronizer + + + Synchronization + Synchronization + + + + Manage connected peers + Manage connected peers + + + + Show your IP + Show your IP + + + + Your IP is: {} + Your IP is: {} + + + diff --git a/lisp/i18n/ts/zh_TW/timecode.ts b/lisp/i18n/ts/zh_TW/timecode.ts new file mode 100644 index 000000000..8c497db47 --- /dev/null +++ b/lisp/i18n/ts/zh_TW/timecode.ts @@ -0,0 +1,66 @@ + + + + + SettingsPageName + + + Timecode Settings + Timecode Settings + + + + Timecode + Timecode + + + + Timecode + + + Cannot load timecode protocol: "{}" + Cannot load timecode protocol: "{}" + + + + TimecodeError + + + Cannot send timecode. + Cannot send timecode. + + + + TimecodeSettings + + + Timecode Format: + Timecode Format: + + + + Replace HOURS by a static track number + Replace HOURS by a static track number + + + + Track number + Track number + + + + Enable Timecode + Enable Timecode + + + + Timecode Settings + Timecode Settings + + + + Timecode Protocol: + Timecode Protocol: + + + diff --git a/lisp/i18n/ts/zh_TW/triggers.ts b/lisp/i18n/ts/zh_TW/triggers.ts new file mode 100644 index 000000000..e9d82f44c --- /dev/null +++ b/lisp/i18n/ts/zh_TW/triggers.ts @@ -0,0 +1,63 @@ + + + + + CueTriggers + + + Started + Started + + + + Paused + Paused + + + + Stopped + Stopped + + + + Ended + Ended + + + + SettingsPageName + + + Triggers + Triggers + + + + TriggersSettings + + + Add + Add + + + + Remove + Remove + + + + Trigger + Trigger + + + + Cue + Cue + + + + Action + Action + + + From bf3f09e05d88a649cf6b015d95ceb1b9c463dd88 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 30 May 2020 15:01:40 +0200 Subject: [PATCH 239/333] Update translations --- lisp/i18n/ts/zh_TW/action_cues.ts | 80 ++++---- lisp/i18n/ts/zh_TW/cache_manager.ts | 10 +- lisp/i18n/ts/zh_TW/cart_layout.ts | 66 +++---- lisp/i18n/ts/zh_TW/controller.ts | 96 ++++----- lisp/i18n/ts/zh_TW/gst_backend.ts | 134 ++++++------- lisp/i18n/ts/zh_TW/lisp.ts | 296 ++++++++++++++-------------- lisp/i18n/ts/zh_TW/list_layout.ts | 94 ++++----- lisp/i18n/ts/zh_TW/media_info.ts | 10 +- lisp/i18n/ts/zh_TW/midi.ts | 74 +++---- lisp/i18n/ts/zh_TW/network.ts | 24 +-- lisp/i18n/ts/zh_TW/osc.ts | 46 ++--- lisp/i18n/ts/zh_TW/presets.ts | 50 ++--- lisp/i18n/ts/zh_TW/rename_cues.ts | 28 +-- lisp/i18n/ts/zh_TW/replay_gain.ts | 28 +-- lisp/i18n/ts/zh_TW/synchronizer.ts | 8 +- lisp/i18n/ts/zh_TW/timecode.ts | 20 +- lisp/i18n/ts/zh_TW/triggers.ts | 20 +- 17 files changed, 542 insertions(+), 542 deletions(-) diff --git a/lisp/i18n/ts/zh_TW/action_cues.ts b/lisp/i18n/ts/zh_TW/action_cues.ts index 556065f4b..d179180ae 100644 --- a/lisp/i18n/ts/zh_TW/action_cues.ts +++ b/lisp/i18n/ts/zh_TW/action_cues.ts @@ -6,22 +6,22 @@ Add - Add + 添加 Remove - Remove + 移除 Cue - Cue + Cue Action - Action + 動作 @@ -29,27 +29,27 @@ Command - Command + 指令 Command to execute, as in a shell - Command to execute, as in a shell + 要執行的 Shell 指令 Discard command output - Discard command output + 捨棄指令輸出 Ignore command errors - Ignore command errors + 忽略指令錯誤 Kill instead of terminate - Kill instead of terminate + 傳送 kill 訊號而非 terminate 訊號 @@ -57,7 +57,7 @@ Action cues - Action cues + 動作 Cues @@ -65,32 +65,32 @@ Command Cue - Command Cue + 指令 Cue Volume Control - Volume Control + 音量控制 Seek Cue - Seek Cue + 跳轉 Cue Collection Cue - Collection Cue + 動作集合 Cue Stop-All - Stop-All + 全部停止 Index Action - Index Action + 索引動作 @@ -98,32 +98,32 @@ Index - Index + 索引 Use a relative index - Use a relative index + 使用相對索引 Target index - Target index + 目標索引 Action - Action + 動作 No suggestion - No suggestion + 没有建議 Suggested cue name - Suggested cue name + 建議的 Cue 名稱 @@ -131,27 +131,27 @@ Cue - Cue + Cue Click to select - Click to select + 點擊以選擇 Not selected - Not selected + 未選擇 Seek - Seek + 跳轉 Time to reach - Time to reach + 跳到時間 @@ -159,32 +159,32 @@ Command - Command + 指令 Volume Settings - Volume Settings + 音量設定 Seek Settings - Seek Settings + 跳轉設定 Edit Collection - Edit Collection + 編輯動作集合 Action Settings - Action Settings + 動作設定 Stop Settings - Stop Settings + 停止設定 @@ -192,7 +192,7 @@ Stop Action - Stop Action + 停止方式 @@ -200,27 +200,27 @@ Cue - Cue + Cue Click to select - Click to select + 點擊以選擇 Not selected - Not selected + 未選擇 Volume to reach - Volume to reach + 目標音量 Fade - Fade + 淡入淡出 @@ -228,7 +228,7 @@ Error during cue execution. - Error during cue execution. + 執行 Cue 期間發生錯誤 diff --git a/lisp/i18n/ts/zh_TW/cache_manager.ts b/lisp/i18n/ts/zh_TW/cache_manager.ts index c458fa553..e0a2b97aa 100644 --- a/lisp/i18n/ts/zh_TW/cache_manager.ts +++ b/lisp/i18n/ts/zh_TW/cache_manager.ts @@ -6,22 +6,22 @@ Cache size warning - Cache size warning + 緩存大小警告 Warning threshold in MB (0 = disabled) - Warning threshold in MB (0 = disabled) + 警告閥值 (MB為單位) (0 = 停用) Cache cleanup - Cache cleanup + 清除緩存 Delete the cache content - Delete the cache content + 清除緩存 @@ -29,7 +29,7 @@ Cache Manager - Cache Manager + 緩存管理 diff --git a/lisp/i18n/ts/zh_TW/cart_layout.ts b/lisp/i18n/ts/zh_TW/cart_layout.ts index 5ded7f942..f0440db29 100644 --- a/lisp/i18n/ts/zh_TW/cart_layout.ts +++ b/lisp/i18n/ts/zh_TW/cart_layout.ts @@ -6,107 +6,107 @@ Countdown mode - Countdown mode + 倒計時模式 Show seek-bars - Show seek-bars + 顯示進度條 Show dB-meters - Show dB-meters + 顯示分貝計 Show accurate time - Show accurate time + 顯示準確時間 Show volume - Show volume + 顯示音量 Grid size - Grid size + 網格大小 Play - Play + 播放 Pause - Pause + 暫停 Stop - Stop + 停止 Reset volume - Reset volume + 重置音量 Add page - Add page + 添加頁面 Add pages - Add pages + 添加多個頁面 Remove current page - Remove current page + 移除當前頁面 Number of Pages: - Number of Pages: + 頁數: Page {number} - Page {number} + 第 {number} 頁 Warning - Warning + 警告 Every cue in the page will be lost. - Every cue in the page will be lost. + 頁面中的所有 Cue 都会被刪除。 Are you sure to continue? - Are you sure to continue? + 是否確認繼續? Number of columns: - Number of columns: + 列數: Number of rows: - Number of rows: + 行數: Default behaviors (applied to new sessions) - Default behaviors (applied to new sessions) + 默認行為 (套用於新工作階段) @@ -114,7 +114,7 @@ Organize cues in grid like pages - Organize cues in grid like pages + 把 Cue 排列在網格頁面 @@ -122,27 +122,27 @@ Click a cue to run it - Click a cue to run it + 滑鼠左按以執行 Cue SHIFT + Click to edit a cue - SHIFT + Click to edit a cue + SHIFT + 滑鼠左按以編輯 Cue CTRL + Click to select a cue - CTRL + Click to select a cue + CTRL + 滑鼠左按以選擇 Cue To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL + 按着 CTRL 然後拖拉 Cue 以複製 Cue To move cues drag them while pressing SHIFT - To move cues drag them while pressing SHIFT + 按着 SHIFT 然後拖拉 Cue 以移動 Cue @@ -150,7 +150,7 @@ Cart Layout - Cart Layout + 網格佈局 @@ -158,22 +158,22 @@ Edit cue - Edit cue + 編輯 Cue Edit selected cues - Edit selected cues + 編輯所選的 Cue Remove cue - Remove cue + 移除 Cue Remove selected cues - Remove selected cues + 移除所選的 Cue @@ -181,7 +181,7 @@ Cart Layout - Cart Layout + 網格佈局 diff --git a/lisp/i18n/ts/zh_TW/controller.ts b/lisp/i18n/ts/zh_TW/controller.ts index d332948f4..3da6ff926 100644 --- a/lisp/i18n/ts/zh_TW/controller.ts +++ b/lisp/i18n/ts/zh_TW/controller.ts @@ -6,7 +6,7 @@ Cannot load controller protocol: "{}" - Cannot load controller protocol: "{}" + 無法加載控制器協議: "{}" @@ -14,17 +14,17 @@ Action - Action + 動作 Shortcuts - Shortcuts + 快捷鍵 Shortcut - Shortcut + 快捷鍵 @@ -32,52 +32,52 @@ MIDI - MIDI + MIDI Type - Type + 類型 Action - Action + 動作 Capture - Capture + 捕捉輸入 Listening MIDI messages ... - Listening MIDI messages ... + 正在監聽 MIDI 訊息... -- All Messages -- - -- All Messages -- + -- 所有訊息 -- Capture filter - Capture filter + 捕捉過濾 Data 1 - Data 1 + 數據 1 Data 2 - Data 2 + 數據 2 Data 3 - Data 3 + 數據 3 @@ -85,62 +85,62 @@ OSC Message - OSC Message + OSC 訊息 OSC - OSC + OSC Path - Path + 路径 Types - Types + 類型 Arguments - Arguments + 参數 OSC Capture - OSC Capture + 捕捉 OSC Add - Add + 添加 Remove - Remove + 移除 Capture - Capture + 捕捉輸入 Waiting for messages: - Waiting for messages: + 正在等待訊息: /path/to/method - /path/to/method + 方法路径 Action - Action + 動作 @@ -148,7 +148,7 @@ Warning - Warning + 警告 @@ -156,12 +156,12 @@ Add - Add + 添加 Remove - Remove + 移除 @@ -169,52 +169,52 @@ Go - Go + 開始 Reset - Reset + 重設 Stop all cues - Stop all cues + 停止所有 Cue Pause all cues - Pause all cues + 暫停所有 Cue Resume all cues - Resume all cues + 恢復所有 Cue Interrupt all cues - Interrupt all cues + 中斷所有 Cue Fade-out all cues - Fade-out all cues + 淡出所有 Cue Fade-in all cues - Fade-in all cues + 淡入所有 Cue Move standby forward - Move standby forward + 移前待命 Move standby back - Move standby back + 移後待命 @@ -222,12 +222,12 @@ Type - Type + 類型 Argument - Argument + 参數 @@ -235,12 +235,12 @@ Add - Add + 添加 Remove - Remove + 移除 @@ -248,27 +248,27 @@ Cue Control - Cue Control + Cue 控制 MIDI Controls - MIDI Controls + MIDI 控制 Keyboard Shortcuts - Keyboard Shortcuts + 鍵盤快捷鍵 OSC Controls - OSC Controls + OSC 控制 Layout Controls - Layout Controls + 佈局控制 diff --git a/lisp/i18n/ts/zh_TW/gst_backend.ts b/lisp/i18n/ts/zh_TW/gst_backend.ts index dfb5e5237..5973a89b5 100644 --- a/lisp/i18n/ts/zh_TW/gst_backend.ts +++ b/lisp/i18n/ts/zh_TW/gst_backend.ts @@ -6,7 +6,7 @@ ALSA device - ALSA device + ALSA 装置 @@ -14,47 +14,47 @@ Compressor - Compressor + 壓縮器 Expander - Expander + 擴展器 Soft Knee - Soft Knee + 高拐點柔软度 Hard Knee - Hard Knee + 低拐點柔软度 Compressor/Expander - Compressor/Expander + 壓縮器/擴展器 Type - Type + 類型 Curve Shape - Curve Shape + 曲線形狀 Ratio - Ratio + 比例 Threshold (dB) - Threshold (dB) + 閥值 (分貝) @@ -62,22 +62,22 @@ Audio Pan - Audio Pan + 音頻平移 Center - Center + Left - Left + Right - Right + @@ -85,22 +85,22 @@ DbMeter settings - DbMeter settings + 分貝計設定 Time between levels (ms) - Time between levels (ms) + 階級之間的時間 (毫秒) Peak ttl (ms) - Peak ttl (ms) + 峰值存活時間 (毫秒) Peak falloff (dB/sec) - Peak falloff (dB/sec) + 峰值下跌率 (分貝/秒) @@ -108,7 +108,7 @@ 10 Bands Equalizer (IIR) - 10 Bands Equalizer (IIR) + 10頻段均衡器 (無限衝擊響應濾波器) @@ -116,12 +116,12 @@ Audio cue (from file) - Audio cue (from file) + 媒體 Cue (從檔案匯入) Select media files - Select media files + 選擇媒體檔案 @@ -129,7 +129,7 @@ Cannot create pipeline element: "{}" - Cannot create pipeline element: "{}" + 無法創建管道元素: "{}" @@ -137,7 +137,7 @@ Change Pipeline - Change Pipeline + 更改管道 @@ -145,7 +145,7 @@ Invalid pipeline element: "{}" - Invalid pipeline element: "{}" + 管道元素無效: "{}" @@ -153,7 +153,7 @@ Edit Pipeline - Edit Pipeline + 編輯管道 @@ -161,12 +161,12 @@ Default pipeline - Default pipeline + 默認管道 Applied only to new cues. - Applied only to new cues. + 只套用於新 Cue @@ -174,32 +174,32 @@ Connections - Connections + 聯繫 Edit connections - Edit connections + 編輯聯繫 Output ports - Output ports + 輸出端口 Input ports - Input ports + 輸入端口 Connect - Connect + 連接 Disconnect - Disconnect + 斷開連接 @@ -207,77 +207,77 @@ Compressor/Expander - Compressor/Expander + 壓縮器/擴展器 Audio Pan - Audio Pan + 音頻平移 PulseAudio Out - PulseAudio Out + PulseAudio 輸出 Volume - Volume + 音量 dB Meter - dB Meter + 分貝計 System Input - System Input + 系统輸入 ALSA Out - ALSA Out + ALSA 輸出 JACK Out - JACK Out + JACK 輸出 Custom Element - Custom Element + 自定義元素 System Out - System Out + 系统輸出 Pitch - Pitch + 音調 URI Input - URI Input + URI 輸入 10 Bands Equalizer - 10 Bands Equalizer + 10頻段均衡器 Speed - Speed + 速度 Preset Input - Preset Input + 預設輸入 @@ -285,12 +285,12 @@ Pitch - Pitch + 音調 {0:+} semitones - {0:+} semitones + {0:+} 半音 @@ -298,7 +298,7 @@ Presets - Presets + 預設 @@ -306,12 +306,12 @@ Media Settings - Media Settings + 媒體設定 GStreamer - GStreamer + GStreamer @@ -319,7 +319,7 @@ Speed - Speed + 速度 @@ -327,42 +327,42 @@ Source - Source + 來源 Find File - Find File + 查找檔案 Buffering - Buffering + 緩衝 Use Buffering - Use Buffering + 使用緩衝 Attempt download on network streams - Attempt download on network streams + 嘗試從互聯網下載 Buffer size (-1 default value) - Buffer size (-1 default value) + 緩衝大小 (-1 為默認值) Choose file - Choose file + 選擇檔案 All files - All files + 所有檔案 @@ -370,12 +370,12 @@ User defined elements - User defined elements + 用戶定義的元素 Only for advanced user! - Only for advanced user! + 只供進階用戶使用! @@ -383,17 +383,17 @@ Volume - Volume + 音量 Normalized volume - Normalized volume + 標準化音量 Reset - Reset + 重設 diff --git a/lisp/i18n/ts/zh_TW/lisp.ts b/lisp/i18n/ts/zh_TW/lisp.ts index 167caf956..390f5e86c 100644 --- a/lisp/i18n/ts/zh_TW/lisp.ts +++ b/lisp/i18n/ts/zh_TW/lisp.ts @@ -6,22 +6,22 @@ Authors - Authors + 作者 Contributors - Contributors + 貢獻者 Translators - Translators + 翻譯者 About Linux Show Player - About Linux Show Player + 關於 Linux Show Player @@ -29,32 +29,32 @@ Web site - Web site + 網站 Source code - Source code + 源代碼 Info - Info + 關於 License - License + 使用條款 Contributors - Contributors + 貢獻者 Discussion - Discussion + 討論 @@ -62,7 +62,7 @@ LiSP preferences - LiSP preferences + LiSP 設定 @@ -70,42 +70,42 @@ Default layout - Default layout + 默認佈局 Application themes - Application themes + 主題 UI theme: - UI theme: + 界面主題: Icons theme: - Icons theme: + 圖標主題: Language: - Language: + 語言: Show layout selection at startup - Show layout selection at startup + 啟動時顯示佈局選擇 Use layout at startup: - Use layout at startup: + 啟動時使用佈局: Application language (require a restart) - Application language (require a restart) + 語言 (需要重啟程式) @@ -113,7 +113,7 @@ Startup error - Startup error + 啟動錯誤 @@ -121,12 +121,12 @@ Undo: {} - Undo: {} + 復原:{} Redo: {} - Redo: {} + 重做:{} @@ -134,7 +134,7 @@ Configuration written at {} - Configuration written at {} + 設定值已儲存於 {} @@ -142,7 +142,7 @@ New configuration installed at {} - New configuration installed at {} + 新設定值儲存於 {} @@ -150,57 +150,57 @@ Default - Default + 默認 Pause - Pause + 暫停 Start - Start + 開始 Stop - Stop + 停止 Faded Start - Faded Start + 淡入開始 Faded Resume - Faded Resume + 淡入繼續 Faded Pause - Faded Pause + 淡出暫停 Faded Stop - Faded Stop + 淡出停止 Faded Interrupt - Faded Interrupt + 淡出中斷 Resume - Resume + 繼續 Do Nothing - Do Nothing + 無動作 @@ -208,42 +208,42 @@ The appearance depends on the layout - The appearance depends on the layout + 外觀取决於佈局 Cue name - Cue name + Cue 名稱 NoName - NoName + 没有名稱 Description/Note - Description/Note + 描述/注釋 Set Font Size - Set Font Size + 設置字體大小 Color - Color + 顏色 Select background color - Select background color + 選擇背景顏色 Select font color - Select font color + 選擇字體顏色 @@ -251,7 +251,7 @@ Misc cues - Misc cues + 雜項 Cues @@ -259,12 +259,12 @@ Cue settings changed: "{}" - Cue settings changed: "{}" + Cue 設置已更改: "{}" Cues settings changed. - Cues settings changed. + 多個 Cue 設置已更改 @@ -272,7 +272,7 @@ Media Cue - Media Cue + 媒體 Cue @@ -280,27 +280,27 @@ Do Nothing - Do Nothing + 無動作 Trigger after the end - Trigger after the end + 結束後觸發 Trigger after post wait - Trigger after post wait + 結束後等候後觸發 Select after the end - Select after the end + 結束後選擇 Select after post wait - Select after post wait + 結束後等候後選擇 @@ -308,57 +308,57 @@ Pre wait - Pre wait + 開始前等候 Wait before cue execution - Wait before cue execution + 執行 Cue 前等待 Post wait - Post wait + 結束後等候 Wait after cue execution - Wait after cue execution + 執行 Cue 後等待 Next action - Next action + 下一個動作 Start action - Start action + 開始方式 Default action to start the cue - Default action to start the cue + 預設開始 Cue 的方式 Stop action - Stop action + 停止方式 Default action to stop the cue - Default action to stop the cue + 預設停止 Cue 的方式 Interrupt action fade - Interrupt action fade + 中斷動作淡出 Fade actions default value - Fade actions default value + 淡入/淡出預設值 @@ -366,17 +366,17 @@ Linear - Linear + 線性 Quadratic - Quadratic + 二次曲線 Quadratic2 - Quadratic2 + S形曲線 @@ -384,12 +384,12 @@ Duration (sec): - Duration (sec): + 長度 (秒): Curve: - Curve: + 曲線: @@ -397,12 +397,12 @@ Fade In - Fade In + 淡入 Fade Out - Fade Out + 淡出 @@ -410,7 +410,7 @@ Press shortcut - Press shortcut + 按下快捷鍵 @@ -418,17 +418,17 @@ Layout selection - Layout selection + 佈局選擇 Select layout - Select layout + 選擇佈局 Open file - Open file + 打開檔案 @@ -436,27 +436,27 @@ Layout actions - Layout actions + 佈局操作 Fade out when stopping all cues - Fade out when stopping all cues + 以淡出方式停止所有 Cue Fade out when interrupting all cues - Fade out when interrupting all cues + 以淡出方式中斷所有 Cue Fade out when pausing all cues - Fade out when pausing all cues + 以淡出方式暫停所有 Cue Fade in when resuming all cues - Fade in when resuming all cues + 以淡入方式繼續所有 Cue @@ -464,7 +464,7 @@ Errors/Warnings - Errors/Warnings + 錯誤/警告 @@ -472,122 +472,122 @@ Debug - Debug + 除错 Warning - Warning + 警告 Error - Error + 錯誤 Dismiss all - Dismiss all + 全部忽略 Show details - Show details + 顯示詳情 Linux Show Player - Log Viewer - Linux Show Player - Log Viewer + Linux Show Player - 查看日志 Info - Info + 資訊 Critical - Critical + 嚴重 Time - Time + 時間 Milliseconds - Milliseconds + 毫秒 Logger name - Logger name + 記錄者名稱 Level - Level + 級别 Message - Message + 訊息 Function - Function + 函數 Path name - Path name + 路径 File name - File name + 檔案名稱 Line no. - Line no. + 行號 Module - Module + 模塊 Process ID - Process ID + 進程號碼 Process name - Process name + 進程名稱 Thread ID - Thread ID + 線程編號 Thread name - Thread name + 線程名稱 Exception info - Exception info + 異常訊息 Showing {} of {} records - Showing {} of {} records + 顯示 {} 個記錄 @@ -595,137 +595,137 @@ &File - &File + 檔案 (&F) New session - New session + 新工作階段 Open - Open + 開啟 Save session - Save session + 儲存工作階段 Preferences - Preferences + 設定 Save as - Save as + 另存為 Full Screen - Full Screen + 全屏顯示 Exit - Exit + 離開 &Edit - &Edit + 編輯 (&E) Undo - Undo + 還原 Redo - Redo + 重做 Select all - Select all + 全選 Select all media cues - Select all media cues + 選擇所有媒體 Cue Deselect all - Deselect all + 取消全選 CTRL+SHIFT+A - CTRL+SHIFT+A + CTRL+SHIFT+A Invert selection - Invert selection + 反向選擇 CTRL+I - CTRL+I + CTRL+I Edit selected - Edit selected + 編輯所選 CTRL+SHIFT+E - CTRL+SHIFT+E + CTRL+SHIFT+E &Layout - &Layout + 佈局 (&L) &Tools - &Tools + 工具 (&T) Edit selection - Edit selection + 編輯所選 &About - &About + 關於 (&A) About - About + 關於 About Qt - About Qt + 關於 Qt Close session - Close session + 關閉工作階段 Do you want to save them now? - Do you want to save them now? + 你想現在儲存嗎? @@ -733,7 +733,7 @@ Registered cue menu: "{}" - Registered cue menu: "{}" + 已记录 Cue 选单: "{}" @@ -741,7 +741,7 @@ Cannot create cue {} - Cannot create cue {} + 無法創建 Cue {} @@ -749,27 +749,27 @@ Start time - Start time + 開始時間 Stop position of the media - Stop position of the media + 媒體停止位置 Stop time - Stop time + 停止時間 Start position of the media - Start position of the media + 媒體開始位置 Loop - Loop + 循環播放 @@ -777,7 +777,7 @@ Right click to reset - Right click to reset + 滑鼠右按以重設 @@ -785,47 +785,47 @@ Appearance - Appearance + 外觀 General - General + 基本 Cue - Cue + Cue Cue Settings - Cue Settings + Cue 設定 Plugins - Plugins + 插件 Behaviours - Behaviours + 行為 Pre/Post Wait - Pre/Post Wait + 開始前等候/結束後等候 Fade In/Out - Fade In/Out + 淡入/淡出 Layouts - Layouts + 佈局 diff --git a/lisp/i18n/ts/zh_TW/list_layout.ts b/lisp/i18n/ts/zh_TW/list_layout.ts index 55776818b..9f4380cff 100644 --- a/lisp/i18n/ts/zh_TW/list_layout.ts +++ b/lisp/i18n/ts/zh_TW/list_layout.ts @@ -6,7 +6,7 @@ Organize the cues in a list - Organize the cues in a list + 把 Cue 排列在列表 @@ -14,17 +14,17 @@ SHIFT + Space or Double-Click to edit a cue - SHIFT + Space or Double-Click to edit a cue + SHIFT + 空白鍵或按兩下以編輯 Cue To copy cues drag them while pressing CTRL - To copy cues drag them while pressing CTRL + 按着 CTRL 然後拖拉 Cue 以複製 Cue To move cues drag them - To move cues drag them + 拖拉 Cue 以移動 Cue @@ -32,7 +32,7 @@ List Layout - List Layout + 列表佈局 @@ -40,177 +40,177 @@ Show dB-meters - Show dB-meters + 顯示分貝計 Show accurate time - Show accurate time + 顯示準確時間 Show seek-bars - Show seek-bars + 顯示進度條 Auto-select next cue - Auto-select next cue + 自動選擇下一個 Cue Enable selection mode - Enable selection mode + 啟用選取 Cue模式 Use fade (buttons) - Use fade (buttons) + 使用淡入淡出(按鈕) Stop Cue - Stop Cue + 停止 Cue Pause Cue - Pause Cue + 暫停 Cue Resume Cue - Resume Cue + 繼續 Cue Interrupt Cue - Interrupt Cue + 中斷 Cue Edit cue - Edit cue + 編輯 Cue Remove cue - Remove cue + 移除 Cue Selection mode - Selection mode + 選取 Cue 模式 Pause all - Pause all + 全部暫停 Stop all - Stop all + 全部停止 Interrupt all - Interrupt all + 全部中斷 Resume all - Resume all + 全部恢復 Fade-Out all - Fade-Out all + 全部淡出 Fade-In all - Fade-In all + 全部淡入 Edit selected - Edit selected + 編輯所選 Clone cue - Clone cue + 複製 Cue Clone selected - Clone selected + 複製所選 Remove selected - Remove selected + 刪除所選 GO Key: - GO Key: + GO 鍵: GO Action: - GO Action: + GO 動作: GO minimum interval (ms): - GO minimum interval (ms): + GO最小間隔 (毫秒): Copy of {} - Copy of {} + {} 的副本 Show index column - Show index column + 顯示索引列 Show resize handles - Show resize handles + 顯示調整大小手柄 Restore default size - Restore default size + 重設至默認大小 Default behaviors (applied to new sessions) - Default behaviors (applied to new sessions) + 默認行為 (套用於新工作階段) Behaviors - Behaviors + 行為 Disable GO Key While Playing - Disable GO Key While Playing + 在播放時禁用 GO 鍵 Use waveform seek-bars - Use waveform seek-bars + 使用波形圖拖動條 GO Key Disabled While Playing - GO Key Disabled While Playing + 播放時禁用 GO 鍵 @@ -218,22 +218,22 @@ Cue - Cue + Cue Pre wait - Pre wait + 開始前等候 Action - Action + 動作 Post wait - Post wait + 結束後等候 @@ -241,12 +241,12 @@ Cue name - Cue name + Cue 名稱 Cue description - Cue description + Cue 描述 @@ -254,7 +254,7 @@ List Layout - List Layout + 列表佈局 diff --git a/lisp/i18n/ts/zh_TW/media_info.ts b/lisp/i18n/ts/zh_TW/media_info.ts index e67d6adfa..120597e50 100644 --- a/lisp/i18n/ts/zh_TW/media_info.ts +++ b/lisp/i18n/ts/zh_TW/media_info.ts @@ -6,27 +6,27 @@ Media Info - Media Info + 媒體資料 Info - Info + 資料 Value - Value + Warning - Warning + 警告 Cannot get any information. - Cannot get any information. + 無法獲取任何資料 diff --git a/lisp/i18n/ts/zh_TW/midi.ts b/lisp/i18n/ts/zh_TW/midi.ts index 9d1ae765a..1578708ae 100644 --- a/lisp/i18n/ts/zh_TW/midi.ts +++ b/lisp/i18n/ts/zh_TW/midi.ts @@ -6,7 +6,7 @@ Integration cues - Integration cues + 互聯 Cues @@ -14,7 +14,7 @@ MIDI Cue - MIDI Cue + MIDI Cue @@ -22,12 +22,12 @@ MIDI Message - MIDI Message + MIDI 訊息 Message type - Message type + 訊息類型 @@ -35,12 +35,12 @@ Cannot connect to MIDI output port '{}'. - Cannot connect to MIDI output port '{}'. + 無法連接到 MIDI 輸出端口 '{}' Cannot connect to MIDI input port '{}'. - Cannot connect to MIDI input port '{}'. + 無法連接到 MIDI 輸入端口 '{}' @@ -48,17 +48,17 @@ MIDI port disconnected: '{}' - MIDI port disconnected: '{}' + MIDI 端口已斷線:'{}' Connecting to MIDI port: '{}' - Connecting to MIDI port: '{}' + 正在連接到 MIDI 端口:'{}' Connecting to matching MIDI port: '{}' - Connecting to matching MIDI port: '{}' + 正在連接到匹配的 MIDI 端口:'{}' @@ -66,47 +66,47 @@ Channel - Channel + 頻道 Note - Note + Velocity - Velocity + 速度 Control - Control + 控制 Program - Program + 程式 Value - Value + Song - Song + 歌曲 Pitch - Pitch + 音調 Position - Position + 位置 @@ -114,62 +114,62 @@ Note ON - Note ON + Note ON Note OFF - Note OFF + Note OFF Polyphonic After-touch - Polyphonic After-touch + Polyphonic After-touch Control/Mode Change - Control/Mode Change + Control/Mode 改變 Program Change - Program Change + Program 改變 Channel After-touch - Channel After-touch + Channel After-touch Pitch Bend Change - Pitch Bend Change + Pitch Bend 改變 Song Select - Song Select + 歌曲選擇 Song Position - Song Position + 歌曲位置 Start - Start + 開始 Stop - Stop + 停止 Continue - Continue + 繼續 @@ -177,27 +177,27 @@ Input - Input + 輸入 Output - Output + 輸出 MIDI devices - MIDI devices + MIDI 装置 Misc options - Misc options + 其他選項 Try to connect using only device/port name - Try to connect using only device/port name + 只嘗試使用設備/端口名稱連接 @@ -205,12 +205,12 @@ MIDI settings - MIDI settings + MIDI 設置 MIDI Settings - MIDI Settings + MIDI 設置 diff --git a/lisp/i18n/ts/zh_TW/network.ts b/lisp/i18n/ts/zh_TW/network.ts index 6f16dabdd..95b07c1da 100644 --- a/lisp/i18n/ts/zh_TW/network.ts +++ b/lisp/i18n/ts/zh_TW/network.ts @@ -6,7 +6,7 @@ Stop serving network API - Stop serving network API + 停止服務網络 API @@ -14,7 +14,7 @@ Network API server stopped working. - Network API server stopped working. + 網络 API 伺服器停止運作 @@ -22,7 +22,7 @@ New end-point: {} - New end-point: {} + 新終端:{} @@ -30,47 +30,47 @@ Host discovery - Host discovery + 搜索主機 Manage hosts - Manage hosts + 管理主機 Discover hosts - Discover hosts + 尋找主機 Manually add a host - Manually add a host + 手動添加主機 Remove selected host - Remove selected host + 移除所選主機 Remove all host - Remove all host + 移除所有主機 Address - Address + 地址 Host IP - Host IP + 主機 IP Select the hosts you want to add - Select the hosts you want to add + 選擇你想增加的主機 diff --git a/lisp/i18n/ts/zh_TW/osc.ts b/lisp/i18n/ts/zh_TW/osc.ts index 32ac5ca8a..59361b621 100644 --- a/lisp/i18n/ts/zh_TW/osc.ts +++ b/lisp/i18n/ts/zh_TW/osc.ts @@ -6,7 +6,7 @@ OSC Settings - OSC Settings + OSC 設定 @@ -14,7 +14,7 @@ Integration cues - Integration cues + 互聯 Cue @@ -22,7 +22,7 @@ OSC Cue - OSC Cue + OSC Cue @@ -30,57 +30,57 @@ Type - Type + 類型 Value - Value + FadeTo - FadeTo + 淡入/淡出至 Fade - Fade + 淡入淡出 OSC Message - OSC Message + OSC 訊息 Add - Add + 添加 Remove - Remove + 移除 OSC Path: - OSC Path: + OSC 路径: /path/to/something - /path/to/something + 路径 Time (sec) - Time (sec) + 時間 (秒) Curve - Curve + 曲線 @@ -88,7 +88,7 @@ Message from {} -> path: "{}" args: {} - Message from {} -> path: "{}" args: {} + 來自 {} 的訊息 -> 路径: "{}" args: {} @@ -96,7 +96,7 @@ Cannot start OSC sever - Cannot start OSC sever + 無法啟動 OSC 伺服器 @@ -104,12 +104,12 @@ OSC server started at {} - OSC server started at {} + OSC 伺服器已於 {} 啟動 OSC server stopped - OSC server stopped + OSC 伺服器已停止 @@ -117,22 +117,22 @@ OSC Settings - OSC Settings + OSC 設定 Input Port: - Input Port: + 輸入端口: Output Port: - Output Port: + 輸出端口: Hostname: - Hostname: + 主機名稱: @@ -140,7 +140,7 @@ OSC settings - OSC settings + OSC 設定 diff --git a/lisp/i18n/ts/zh_TW/presets.ts b/lisp/i18n/ts/zh_TW/presets.ts index 328b21669..f59ac7e95 100644 --- a/lisp/i18n/ts/zh_TW/presets.ts +++ b/lisp/i18n/ts/zh_TW/presets.ts @@ -6,12 +6,12 @@ Create Cue - Create Cue + 創建 Cue Load on selected Cues - Load on selected Cues + 在所選的 Cue 上加載範本 @@ -19,117 +19,117 @@ Presets - Presets + 範本 Save as preset - Save as preset + 保存範本 Cannot scan presets - Cannot scan presets + 無法尋找範本 Error while deleting preset "{}" - Error while deleting preset "{}" + 刪除範本 "{}" 時發生錯誤 Cannot load preset "{}" - Cannot load preset "{}" + 無法加載範本 "{}" Cannot save preset "{}" - Cannot save preset "{}" + 無法保存範本 "{}" Cannot rename preset "{}" - Cannot rename preset "{}" + 無法重新命名範本 "{}" Select Preset - Select Preset + 選擇範本 Preset name - Preset name + 範本名稱 Add - Add + 添加 Rename - Rename + 重新命名 Edit - Edit + 編輯 Remove - Remove + 移除 Export selected - Export selected + 匯出所選 Import - Import + 匯入 Warning - Warning + 警告 The same name is already used! - The same name is already used! + 這個名稱已經被使用 Cannot export correctly. - Cannot export correctly. + 無法匯出 Cannot import correctly. - Cannot import correctly. + 無法匯入 Cue type - Cue type + Cue 類型 Load on cue - Load on cue + 在 Cue 上加載範本 Load on selected cues - Load on selected cues + 在所選的 Cue 上加載範本 Cannot create a cue from this preset: {} - Cannot create a cue from this preset: {} + 無法從此範本創建 Cue:{} diff --git a/lisp/i18n/ts/zh_TW/rename_cues.ts b/lisp/i18n/ts/zh_TW/rename_cues.ts index dd5289c5e..6ed10796f 100644 --- a/lisp/i18n/ts/zh_TW/rename_cues.ts +++ b/lisp/i18n/ts/zh_TW/rename_cues.ts @@ -6,62 +6,62 @@ Rename Cues - Rename Cues + 重新命名多個 Cue Rename cues - Rename cues + 重新命名 Cue Current - Current + 當前名稱 Preview - Preview + 預覽 Capitalize - Capitalize + 首字母大寫 Lowercase - Lowercase + 小寫 Uppercase - Uppercase + 大寫 Remove Numbers - Remove Numbers + 移除號碼 Add numbering - Add numbering + 添加號碼 Reset - Reset + 重設 Type your regex here: - Type your regex here: + 在此輸入正規表達式: Regex help - Regex help + 正規表達式幫助 @@ -69,7 +69,7 @@ Renamed {number} cues - Renamed {number} cues + 已重新命名 {number} 個 Cue @@ -77,7 +77,7 @@ Regex error: Invalid pattern - Regex error: Invalid pattern + 正規表達式錯誤:無效表達式 diff --git a/lisp/i18n/ts/zh_TW/replay_gain.ts b/lisp/i18n/ts/zh_TW/replay_gain.ts index 2012cb779..d75862c99 100644 --- a/lisp/i18n/ts/zh_TW/replay_gain.ts +++ b/lisp/i18n/ts/zh_TW/replay_gain.ts @@ -6,47 +6,47 @@ ReplayGain / Normalization - ReplayGain / Normalization + 回放增益/標準化 Calculate - Calculate + 計算 Reset all - Reset all + 全部重設 Reset selected - Reset selected + 重設所選 Threads number - Threads number + 線程數量 Apply only to selected media - Apply only to selected media + 只套用於所選媒體 ReplayGain to (dB SPL) - ReplayGain to (dB SPL) + 回放增益至 (分貝 聲壓級) Normalize to (dB) - Normalize to (dB) + 標準化至 (分貝) Processing files ... - Processing files ... + 正在處理檔案... @@ -54,12 +54,12 @@ Applied gain for: {} - Applied gain for: {} + 已套用為 {} 作增益 Discarded gain for: {} - Discarded gain for: {} + 已捨棄為 {} 作增益 @@ -67,17 +67,17 @@ Gain processing stopped by user. - Gain processing stopped by user. + 用戶已停止增益處理 Started gain calculation for: {} - Started gain calculation for: {} + 已開始為 {} 作增益計算 Gain calculated for: {} - Gain calculated for: {} + 已為 {} 作增益計算 diff --git a/lisp/i18n/ts/zh_TW/synchronizer.ts b/lisp/i18n/ts/zh_TW/synchronizer.ts index af377c2c1..8bf358fd5 100644 --- a/lisp/i18n/ts/zh_TW/synchronizer.ts +++ b/lisp/i18n/ts/zh_TW/synchronizer.ts @@ -6,22 +6,22 @@ Synchronization - Synchronization + 同步 Manage connected peers - Manage connected peers + 管理已連接的用戶 Show your IP - Show your IP + 顯示你的 IP Your IP is: {} - Your IP is: {} + 你的 IP:{} diff --git a/lisp/i18n/ts/zh_TW/timecode.ts b/lisp/i18n/ts/zh_TW/timecode.ts index 8c497db47..fc5208b0d 100644 --- a/lisp/i18n/ts/zh_TW/timecode.ts +++ b/lisp/i18n/ts/zh_TW/timecode.ts @@ -6,12 +6,12 @@ Timecode Settings - Timecode Settings + 時間碼設定 Timecode - Timecode + 時間碼 @@ -19,7 +19,7 @@ Cannot load timecode protocol: "{}" - Cannot load timecode protocol: "{}" + 無法加載時間碼協議: "{}" @@ -27,7 +27,7 @@ Cannot send timecode. - Cannot send timecode. + 無法發送時間碼 @@ -35,32 +35,32 @@ Timecode Format: - Timecode Format: + 時間碼格式: Replace HOURS by a static track number - Replace HOURS by a static track number + 以靜態曲目編號代替小時 Track number - Track number + 曲目編號 Enable Timecode - Enable Timecode + 啟用時間碼 Timecode Settings - Timecode Settings + 時間碼設定 Timecode Protocol: - Timecode Protocol: + 時間碼協議: diff --git a/lisp/i18n/ts/zh_TW/triggers.ts b/lisp/i18n/ts/zh_TW/triggers.ts index e9d82f44c..b6207fa49 100644 --- a/lisp/i18n/ts/zh_TW/triggers.ts +++ b/lisp/i18n/ts/zh_TW/triggers.ts @@ -6,22 +6,22 @@ Started - Started + 已開始 Paused - Paused + 已暫停 Stopped - Stopped + 已停止 Ended - Ended + 已結束 @@ -29,7 +29,7 @@ Triggers - Triggers + 觸發 @@ -37,27 +37,27 @@ Add - Add + 添加 Remove - Remove + 移除 Trigger - Trigger + 觸發條件 Cue - Cue + Cue Action - Action + 動作 From 56a51b17f6c3a85ca19f5f21064542f34dff51a3 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 1 Jun 2020 18:28:40 +0200 Subject: [PATCH 240/333] Update qm files, i18n_update now ignore __pycache__ again --- i18n_update.py | 17 ++++++++++++++--- lisp/i18n/qm/base_ar_SA.qm | Bin 42280 -> 44875 bytes lisp/i18n/qm/base_cs_CZ.qm | Bin 42285 -> 46052 bytes lisp/i18n/qm/base_de_DE.qm | Bin 42061 -> 47826 bytes lisp/i18n/qm/base_en.qm | Bin 21163 -> 19880 bytes lisp/i18n/qm/base_es_ES.qm | Bin 43031 -> 47602 bytes lisp/i18n/qm/base_fr_FR.qm | Bin 44447 -> 48932 bytes lisp/i18n/qm/base_it_IT.qm | Bin 42637 -> 46516 bytes lisp/i18n/qm/base_nl_BE.qm | Bin 42125 -> 45582 bytes lisp/i18n/qm/base_nl_NL.qm | Bin 42639 -> 45822 bytes lisp/i18n/qm/base_sl_SI.qm | Bin 42170 -> 45573 bytes lisp/i18n/qm/base_zh_CN.qm | Bin 0 -> 36099 bytes lisp/i18n/qm/base_zh_TW.qm | Bin 0 -> 36099 bytes lisp/plugins/__init__.py | 6 ++++-- 14 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 lisp/i18n/qm/base_zh_CN.qm create mode 100644 lisp/i18n/qm/base_zh_TW.qm diff --git a/i18n_update.py b/i18n_update.py index fc0d1decf..55e450bdf 100755 --- a/i18n_update.py +++ b/i18n_update.py @@ -55,7 +55,10 @@ def modules_sources( modules_path: Iterable[Path], extensions: Iterable[str], exclude: str = "", ): for module_path in modules_path: - yield module_path.stem, source_files(module_path, extensions, exclude), + if module_path.stem != "__pycache__": + yield module_path.stem, source_files( + module_path, extensions, exclude + ), def lupdate( @@ -139,9 +142,17 @@ def lrelease(locales: Iterable[str], options: Iterable[str] = ()): options.append("-noobsolete") lupdate( - [Path("lisp")], locales, options=options, exclude="^lisp/plugins/" + [Path("lisp")], + locales, + options=options, + exclude="^lisp/plugins/|__pycache__", + ) + lupdate( + dirs_path("lisp/plugins"), + locales, + options=options, + exclude="__pycache__", ) - lupdate(dirs_path("lisp/plugins"), locales, options=options) if do_release: print(">>> RELEASING ...") diff --git a/lisp/i18n/qm/base_ar_SA.qm b/lisp/i18n/qm/base_ar_SA.qm index 824c0bfb2fe9fff9aca4372c4928dc757971af5a..ade4968de3144626f461bfbda9571178456ac0fc 100644 GIT binary patch delta 12076 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 ZLfH~ed;iCVjMi*a6|r!%s>DD diff --git a/lisp/i18n/qm/base_de_DE.qm b/lisp/i18n/qm/base_de_DE.qm index c86e340eed82098302b686c4aa3fb0ca94435f41..bf89bc62e1c86ecab2d4b7859b7634a8415be23f 100644 GIT binary patch literal 47826 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 o^MUrmwVV8=5{4MVZXpBx5HtaUM-m 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 w%Ee)52+am0Lkpi%2FV(SV zRR_(yrGYEi-H|XMGQ5EcYKd-^Q6~LbO;HovZIgAM*|UE=-<E6crcWozYlD5Qf(fF6i8mEYuU9a8fY=U}$XZP7 z>HS364~ad8-@*5ZW6u$(@)fiPdMgJ@DRF`8i9{Q5Q3FJri@3xEM5g1!E$ku+xkKEB z4~asHiQCyhl(3ArcX2PTA?{cO>@^CC3l+4C5_bajrtRbzmQCc-Mv?A>f>acFJ{)MH zl)XM=r{sO~__h=H&`o(|rEqwF@;+@R3h1I~d54L-Z_u4jbHc$zadf&_=Cs1p9n!wROHq@va)I~>fQ z;?oC+vMOk9Bc!$T(5ej&h{o-s*1gD77fLtgzKjHFRAmKU5{135sxD|D3hz+8#QulK zqg%DJzZ}v&tGfIuOQgT3x)<^iQJh)5<=SSVn11yEH^IR3>i0_EFE&HnZh;@uh`M8; zJr2w_t2=g2CCaH*4`#yQ6N`gVjX-dzr9IE78PU zCgLtI>ME182Z7DAnVidai5{5)90l%Vn$~F%u#Pz_W)e;6WqMAd-;BGM^X4=nuPw|2 zyS563gKV%LGETYA#{L6|7_YH2`?8=!I9uL`j5CMXI@*K4*V(#{=A-$>D=4%ns7+K* z+^wK7LBVuic74@v@NZ^!6?LNo^=$uKlwk7j>`6HtnJuJ9HTF?`D{>V+d8Be%5OE$wb@O+gsZgrB$u zHJUBT%r$0RBTBl&IUa+8{M!mj`Txgu1tT-<%E30IVERGM;SMy(lY1jmj}m;#{k3N+ zdc(?HY=YFWecYu~+(!&6n48D_{Rt=%62i+@-2|TEeP?eWdh|8^e~Nlt;>~I9k`?jh z`)koN!MwF34TJ~z9ivE8u;1l7wj9U9K?T!l__Oi2$PVJa{>5F>xA+@h?N=9cC_0sM@AMswsVyz}kS zbe_6^@%={7_(5rV{1?h0d#xZ_k)g+0VccdsoV-9Vdtb!E?ZT5=!N}SuR4f5=W<;pH zuma6fBs7$u1QWIi%|CKPk;6iJPYY4JO*nc6d&Jiy^xCkT(n5qY_Yshu5&C|H-^69Y z*ku!XEm0e-4afYe-qWT<7QleC>B+fhwpMK^--a<;qFq`tg6X)UeWmjxZmSiPWUZqH z4nx;zHys4y=&!VU3Q?MnkF=c^reh4>*PhD2HjEn;Z5{z2;wP5;9#7=gEtV%i3I39T ziHF3B{2|Q0UzYf6Lpc~dA}$|6V2^aMI_)^}=@VCMEN`O=mAm8j?q>4$w@ z;9W1>i{6AD-zM`ra{{ls_V*uMQ3!Wy~YX)7uedQ^TY`%|JnER_q3F^zh& zJb%qBEVk$6x)oh;Xpy&hcZ0_QxtTkRo^!~}!#=QkSw5%-uSYHN(drQl)jGNF^gmJI zMe=vu3=;I0@AbwJWfbdDynaO`U(jVW41m}}UFl4;$dpiB#r&U8A@+IQ`We0$^Hc?6 z(shSk2V>n4-HD!5D4VG}#eA`hVZFH)6Uf4We7p;4s64vYBZ1 zw+u1gFT;X#7-kh!U~_jH7EM>fvCB~TVKY{HvSHOtB$QHZSkoVd7P2Xr6K!Ze6pQ^; zY52T_g(4Y?@dssR&}>%9q*7W&D`^D<(==L4RXD#$!LD<{L(aL%GsIPIsAHVBz1p0g z>x|A3?*R8lr_ob#e&7?~itx$NyIM@^8Rz_nkDbdcR@ay%N_N&KM!T*g#(TL6@}3s{ E3j@_NFaQ7m delta 2899 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 Svk)(wiTuVF{6yOQH~tS1u3H`e delta 9432 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 delta 8092 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 i=&=Z~7f!V_ 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 Q$v>F;3vlkG+p0wW2E}_)7XSbN diff --git a/lisp/i18n/qm/base_sl_SI.qm b/lisp/i18n/qm/base_sl_SI.qm index f28776bdc2a47c85b23c92973a82c7ac93c6dd06..55deee8dfda483cb368333c44ce31e4ecec10725 100644 GIT binary patch delta 11556 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 Rz>bw|kH1TqUA8^r{{eadWDWoT delta 8731 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 Mjt?ymr(aF{55zFR9{>OV diff --git a/lisp/i18n/qm/base_zh_CN.qm b/lisp/i18n/qm/base_zh_CN.qm new file mode 100644 index 0000000000000000000000000000000000000000..313bb92b885b0884da7ae1dc3276a7d55e12759e GIT binary patch literal 36099 zcmcJ234GMmwf{{r`<4(Otm02Vh+qidQUydq*aBg)Odzr|nfWCdnaspl5&{BJwZ#Sm zRF-OM_2I&EsXnW(Z2@g7rM~aE_xJnF?>G6)1plAU|Mj&$ zAoncy+;h)8_ndQ2X8a;N^u;fJwEOTGBY(B#wqO2X7Got(F=qKAW8?QQHt%-Ed`rc5 zaI^Rhc}0AeekQ)FJ{8}tj~H9 z#)9WE%ky_Iwrr~SuD_XCUctZndzsY&ngv&h@5Nt;?~=vLdfK&&4O+*n6W?IWQN*n0 zuVJhq&8$m+XU;p!+WI78b#=_zet@yE`

    @t!tj?Y$rWemk??{5WH4G49TJjLqG_ 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 PYE8!)Xy_`+vIqYk8>K${ literal 0 HcmV?d00001 diff --git a/lisp/i18n/qm/base_zh_TW.qm b/lisp/i18n/qm/base_zh_TW.qm new file mode 100644 index 0000000000000000000000000000000000000000..a4a0bff04608c9643bc0221a738ee29110e28ad1 GIT binary patch literal 36099 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 Date: Tue, 2 Jun 2020 12:43:32 +0200 Subject: [PATCH 241/333] i18n_update now uses "lrelease-qt5" command if "lrelease" cannot be found --- i18n_update.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i18n_update.py b/i18n_update.py index 55e450bdf..af001d4a5 100755 --- a/i18n_update.py +++ b/i18n_update.py @@ -20,6 +20,7 @@ import re import subprocess import sys +import shutil from argparse import ArgumentParser from pathlib import Path from typing import Iterable @@ -27,7 +28,7 @@ TS_DIR = Path("lisp/i18n/ts") QM_DIR = Path("lisp/i18n/qm") PYLUPDATE = "pylupdate5" -LRELEASE = "lrelease" +LRELEASE = "lrelease" if shutil.which("lrelease") else "lrelease-qt5" def dirs_path(path: str): From 57a6d0b58dce7aa8986e6c6f847e2823c71ede3d Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 2 Jun 2020 12:44:52 +0200 Subject: [PATCH 242/333] Fix missing translations --- lisp/i18n/ts/en/gst_backend.ts | 8 ++++++++ lisp/plugins/gst_backend/gst_backend.py | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lisp/i18n/ts/en/gst_backend.ts b/lisp/i18n/ts/en/gst_backend.ts index f550912b9..37abb479d 100644 --- a/lisp/i18n/ts/en/gst_backend.ts +++ b/lisp/i18n/ts/en/gst_backend.ts @@ -79,6 +79,14 @@ Right + + CueCategory + + + Media cues + + + DbMeterSettings diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index ecee0f7c1..fbd03d786 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -17,7 +17,7 @@ import os.path -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtGui import QCursor from PyQt5.QtWidgets import QFileDialog, QApplication @@ -74,7 +74,7 @@ def __init__(self, app): self.app.window.registerCueMenu( translate("GstBackend", "Audio cue (from file)"), self._add_uri_audio_cue, - category="Media cues", + category=QT_TRANSLATE_NOOP("CueCategory", "Media cues"), shortcut="CTRL+M", ) From fcf208a31be49063dfc9bd7a85479df55c3519d3 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 10 Jun 2020 16:20:14 +0200 Subject: [PATCH 243/333] Improve ALSA device listing, probably related to #107 --- .../plugins/gst_backend/settings/alsa_sink.py | 71 ++++++++++--------- lisp/ui/settings/pages.py | 5 +- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/lisp/plugins/gst_backend/settings/alsa_sink.py b/lisp/plugins/gst_backend/settings/alsa_sink.py index c156f3c5a..855202167 100644 --- a/lisp/plugins/gst_backend/settings/alsa_sink.py +++ b/lisp/plugins/gst_backend/settings/alsa_sink.py @@ -24,6 +24,7 @@ QLabel, QVBoxLayout, ) +from pyalsa import alsacard from lisp.plugins.gst_backend.elements.alsa_sink import AlsaSink from lisp.ui.settings.pages import SettingsPage @@ -39,31 +40,40 @@ def __init__(self, **kwargs): self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) - self.devices = self._discover_pcm_devices() - self.devices["default"] = "default" + self.devices = {} + self.discover_output_pcm_devices() self.deviceGroup = QGroupBox(self) - self.deviceGroup.setTitle(translate("AlsaSinkSettings", "ALSA device")) self.deviceGroup.setGeometry(0, 0, self.width(), 100) self.deviceGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.deviceGroup) - self.device = QComboBox(self.deviceGroup) - self.device.addItems(self.devices.keys()) - self.device.setCurrentText("default") - self.device.setToolTip( + self.deviceComboBox = QComboBox(self.deviceGroup) + for name, description in self.devices.items(): + self.deviceComboBox.addItem(description, name) + if name == "default": + self.deviceComboBox.setCurrentIndex( + self.deviceComboBox.count() - 1 + ) + + self.deviceGroup.layout().addWidget(self.deviceComboBox) + + self.helpLabel = QLabel(self.deviceGroup) + font = self.noticeLabel.font() + font.setPointSizeF(font.pointSizeF() * 0.9) + self.noticeLabel.setFont(font) + self.helpLabel.setAlignment(QtCore.Qt.AlignCenter) + self.layout().addWidget(self.helpLabel) + + def retranslateUi(self): + self.deviceGroup.setTitle(translate("AlsaSinkSettings", "ALSA device")) + self.helpLabel.setText( translate( "AlsaSinkSettings", - "ALSA devices, as defined in an " "asound configuration file", + "To make your custom PCM objects appear correctly in this list " + "requires adding a 'hint.description' line to them", ) ) - self.deviceGroup.layout().addWidget(self.device) - - self.label = QLabel( - translate("AlsaSinkSettings", "ALSA device"), self.deviceGroup - ) - self.label.setAlignment(QtCore.Qt.AlignCenter) - self.deviceGroup.layout().addWidget(self.label) def enableCheck(self, enabled): self.deviceGroup.setCheckable(enabled) @@ -72,26 +82,23 @@ def enableCheck(self, enabled): def loadSettings(self, settings): device = settings.get("device", "default") - for name, dev_name in self.devices.items(): - if device == dev_name: - self.device.setCurrentText(name) - break + self.deviceComboBox.setCurrentText( + self.devices.get(device, self.devices.get("default", "")) + ) def getSettings(self): - if not ( - self.deviceGroup.isCheckable() and not self.deviceGroup.isChecked() - ): - return {"device": self.devices[self.device.currentText()]} + if self.isGroupEnabled(self.deviceGroup): + return {"device": self.deviceComboBox.currentData()} return {} - def _discover_pcm_devices(self): - devices = {} - - with open("/proc/asound/pcm", mode="r") as f: - for dev in f.readlines(): - dev_name = dev[7 : dev.find(":", 7)].strip() - dev_code = "hw:" + dev[:5].replace("-", ",") - devices[dev_name] = dev_code + def discover_output_pcm_devices(self): + self.devices = {} - return devices + # Get a list of the pcm devices "hints", the result is a combination of + # "snd_device_name_hint()" and "snd_device_name_get_hint()" + for pcm in alsacard.device_name_hint(-1, "pcm"): + ioid = pcm.get("IOID") + # Keep only bi-directional and output devices + if ioid is None or ioid == "Output": + self.devices[pcm["NAME"]] = pcm.get("DESC", pcm["NAME"]) diff --git a/lisp/ui/settings/pages.py b/lisp/ui/settings/pages.py index edf3f627f..ff346f074 100644 --- a/lisp/ui/settings/pages.py +++ b/lisp/ui/settings/pages.py @@ -16,7 +16,7 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import QModelIndex, Qt -from PyQt5.QtWidgets import QWidget, QTabWidget, QVBoxLayout +from PyQt5.QtWidgets import QWidget, QTabWidget, QVBoxLayout, QGroupBox from lisp.core.util import dict_merge from lisp.ui.ui_utils import translate @@ -54,6 +54,9 @@ def enableCheck(self, enabled): :type enabled: bool """ + def isGroupEnabled(self, group: QGroupBox): + return not (group.isCheckable() and not group.isChecked()) + class CuePageMixin: def __init__(self, cueType, *args, **kwargs): From 01dc18d6eb7934df9ffa4a4fea8cbc97df1e5a81 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 10 Jun 2020 18:42:35 +0200 Subject: [PATCH 244/333] Clean up and various fixes across settings widgets --- Pipfile.lock | 177 +++++++++--------- lisp/plugins/action_cues/collection_cue.py | 79 +++++--- lisp/plugins/action_cues/command_cue.py | 11 +- lisp/plugins/action_cues/index_action_cue.py | 22 +-- lisp/plugins/action_cues/seek_cue.py | 25 +-- lisp/plugins/action_cues/stop_all.py | 11 +- lisp/plugins/action_cues/volume_control.py | 32 ++-- lisp/plugins/controller/protocols/keyboard.py | 3 +- lisp/plugins/controller/protocols/midi.py | 3 +- lisp/plugins/controller/protocols/osc.py | 3 +- .../plugins/gst_backend/settings/alsa_sink.py | 3 +- .../gst_backend/settings/audio_dynamic.py | 24 +-- .../plugins/gst_backend/settings/audio_pan.py | 5 +- lisp/plugins/gst_backend/settings/db_meter.py | 16 +- .../gst_backend/settings/equalizer10.py | 12 +- .../plugins/gst_backend/settings/jack_sink.py | 13 +- lisp/plugins/gst_backend/settings/pitch.py | 5 +- .../gst_backend/settings/preset_src.py | 8 +- lisp/plugins/gst_backend/settings/speed.py | 5 +- .../plugins/gst_backend/settings/uri_input.py | 9 +- .../gst_backend/settings/user_element.py | 5 +- lisp/plugins/gst_backend/settings/volume.py | 10 +- lisp/plugins/midi/midi_cue.py | 8 +- lisp/plugins/osc/osc_cue.py | 24 +-- lisp/plugins/timecode/settings.py | 18 +- lisp/plugins/triggers/triggers_settings.py | 95 ++++++---- lisp/ui/settings/cue_pages/cue_appearance.py | 26 +-- lisp/ui/settings/cue_pages/cue_general.py | 44 ++--- lisp/ui/settings/cue_pages/media_cue.py | 18 +- lisp/ui/settings/pages.py | 8 +- 30 files changed, 357 insertions(+), 365 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index fd0d68304..2d28487e5 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -16,18 +16,18 @@ "default": { "appdirs": { "hashes": [ - "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", - "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" + "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", + "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" ], "index": "pypi", - "version": "==1.4.3" + "version": "==1.4.4" }, "certifi": { "hashes": [ - "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", - "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" + "sha256:5ad7e9a056d25ffa5082862e36f119f7f7cec6457fa07ee2f8c339814b80c9b1", + "sha256:9cd41137dc19af6a5e03b630eefe7d1f458d964d406342dd3edf625839b944cc" ], - "version": "==2019.11.28" + "version": "==2020.4.5.2" }, "cffi": { "hashes": [ @@ -71,41 +71,32 @@ }, "cython": { "hashes": [ - "sha256:0542a6c4ff1be839b6479deffdbdff1a330697d7953dd63b6de99c078e3acd5f", - "sha256:0bcf7f87aa0ba8b62d4f3b6e0146e48779eaa4f39f92092d7ff90081ef6133e0", - "sha256:13408a5e5574b322153a23f23eb9e69306d4d8216428b435b75fdab9538ad169", - "sha256:1846a8f4366fb4041d34cd37c2d022421ab1a28bcf79ffa6cf33a45b5acba9af", - "sha256:1d32d0965c2fc1476af9c367e396c3ecc294d4bde2cfde6f1704e8787e3f0e1f", - "sha256:21d6abd25e0fcfa96edf164831f53ca20deb64221eb3b7d6d1c4d582f4c54c84", - "sha256:232755284f942cbb3b43a06cd85974ef3c970a021aef19b5243c03ee2b08fa05", - "sha256:245e69a1f367c89e3c8a1c2699bd20ab67b3d57053f3c71f0623d36def074308", - "sha256:3a274c63a3575ae9d6cde5a31c2f5cb18d0a34d9bded96433ceb86d11dc0806d", - "sha256:3b400efb38d6092d2ee7f6d9835dd6dc4f99e804abf97652a5839ff9b1910f25", - "sha256:4ab2054325a7856ed0df881b8ffdadae05b29cf3549635f741c18ce2c860f51b", - "sha256:4b5efb5bff2a1ed0c23dd131223566a0cc51c5266e70968082aed75b73f8c1e2", - "sha256:54e7bf8a2a0c8536f4c42fc5ef54e6780153826279aef923317cf919671119f4", - "sha256:59a0b01fc9376c2424eb3b09a0550f1cbd51681a59cee1e02c9d5c546c601679", - "sha256:5ba06cf0cfc79686daedf9a7895cad4c993c453b86240fc54ecbe9b0c951504c", - "sha256:66768684fdee5f9395e6ee2daa9f770b37455fcb22d31960843bd72996aaa84f", - "sha256:772c13250aea33ac17eb042544b310f0dc3862bbde49b334f5c12f7d1b627476", - "sha256:7d31c4b518b34b427b51e85c6827473b08f473df2fcba75969daad65ea2a5f6c", - "sha256:961f11eb427161a8f5b35e74285a5ff6651eee710dbe092072af3e9337e26825", - "sha256:96342c9f934bcce22eaef739e4fca9ce5cc5347df4673f4de8e5dce5fe158444", - "sha256:a507d507b45af9657b050cea780e668cbcb9280eb94a5755c634a48760b1d035", - "sha256:ad318b60d13767838e99cf93f3571849946eb960c54da86c000b97b2ffa60128", - "sha256:b137bb2f6e079bd04e6b3ea15e9f9b9c97982ec0b1037d48972940577d3a57bb", - "sha256:b3f95ba4d251400bfd38b0891128d9b6365a54f06bd4d58ba033ecb39d2788cc", - "sha256:c0937ab8185d7f55bf7145dbfa3cc27a9d69916d4274690b18b9d1022ac54fd8", - "sha256:c2c28d22bfea830c0cdbd0d7f373d4f51366893a18a5bbd4dd8deb1e6bdd08c2", - "sha256:e074e2be68b4cb1d17b9c63d89ae0592742bdbc320466f342e1e1ea77ec83c40", - "sha256:e9abcc8013354f0059c16af9c917d19341a41981bb74dcc44e060f8a88db9123", - "sha256:eb757a4076e7bb1ca3e73fba4ec2b1c07ca0634200904f1df8f7f899c57b17af", - "sha256:f4ecb562b5b6a2d80543ec36f7fbc7c1a4341bb837a5fc8bd3c352470508133c", - "sha256:f516d11179627f95471cc0674afe8710d4dc5de764297db7f5bdb34bd92caff9", - "sha256:fd6496b41eb529349d58f3f6a09a64cceb156c9720f79cebdf975ea4fafc05f0" + "sha256:0bb201124f67b8d5e6a3e7c02257ca56a90204611971ecca76c02897096f097d", + "sha256:0f3488bf2a9e049d1907d35ad8834f542f8c03d858d1bca6d0cbc06b719163e0", + "sha256:10b6d2e2125169158128b7f11dad8bb0d8f5fba031d5d4f8492f3afbd06491d7", + "sha256:16ed0260d031d90dda43997e9b0f0eebc3cf18e6ece91cad7b0fb17cd4bfb29b", + "sha256:22d91af5fc2253f717a1b80b8bb45acb655f643611983fd6f782b9423f8171c7", + "sha256:2d84e8d2a0c698c1bce7c2a4677f9f03b076e9f0af7095947ecd2a900ffceea5", + "sha256:384582b5024007dfdabc9753e3e0f85d61837b0103b0ee3f8acf04a4bcfad175", + "sha256:4473f169d6dd02174eb76396cb38ce469f377c08b21965ddf4f88bbbebd5816e", + "sha256:57f32d1095ad7fad1e7f2ff6e8c6a7197fa532c8e6f4d044ff69212e0bf05461", + "sha256:60def282839ed81a2ffae29d2df0a6777fd74478c6e82c6c3f4b54e698b9d11c", + "sha256:714b8926a84e3e39c5278e43fb8823598db82a4b015cff263b786dc609a5e7d6", + "sha256:7352b88f2213325c1e111561496a7d53b0326e7f07e6f81f9b8b21420e40851c", + "sha256:8598b09f7973ccb15c03b21d3185dc129ae7c60d0a6caf8176b7099a4b83483e", + "sha256:8dc68f93b257718ea0e2bc9be8e3c61d70b6e49ab82391125ba0112a30a21025", + "sha256:a49d0f5c55ad0f4aacad32f058a71d0701cb8936d6883803e50698fa04cac8d2", + "sha256:a985a7e3c7f1663af398938029659a4381cfe9d1bd982cf19c46b01453e81775", + "sha256:b3233341c3fe352b1090168bd087686880b582b635d707b2c8f5d4f1cc1fa533", + "sha256:b56c02f14f1708411d95679962b742a1235d33a23535ce4a7f75425447701245", + "sha256:b7bb0d54ff453c7516d323c3c78b211719f39a506652b79b7e85ba447d5fa9e7", + "sha256:c5df2c42d4066cda175cd4d075225501e1842cfdbdaeeb388eb7685c367cc3ce", + "sha256:c5e29333c9e20df384645902bed7a67a287b979da1886c8f10f88e57b69e0f4b", + "sha256:d0b445def03b4cd33bd2d1ae6fbbe252b6d1ef7077b3b5ba3f2c698a190d26e5", + "sha256:d490a54814b69d814b157ac86ada98c15fd77fabafc23732818ed9b9f1f0af80" ], "index": "pypi", - "version": "==0.29.16" + "version": "==0.29.20" }, "falcon": { "hashes": [ @@ -129,17 +120,18 @@ }, "humanize": { "hashes": [ - "sha256:0b3157df0f3fcdcb7611180d305a23fd4b6290eb23586e058762f8576348fbab", - "sha256:de8ef6ffee618a9d369b3d1fb1359780ccaa2cc76a0e777c6ff21f04d19a0eb8" + "sha256:07dd1293bac6c77daa5ccdc22c0b41b2315bee0e339a9f035ba86a9f1a272002", + "sha256:42ae7d54b398c01bd100847f6cb0fc9e381c21be8ad3f8e2929135e48dbff026" ], "index": "pypi", - "version": "==2.2.0" + "version": "==2.4.0" }, "idna": { "hashes": [ "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.9" }, "jack-client": { @@ -167,6 +159,7 @@ "hashes": [ "sha256:2c143183280feb67f5beb4e543fd49990c28e7df427301ede04fc550d3562e84" ], + "markers": "python_version >= '3.5' and python_version < '4'", "version": "==1.19.1" }, "pycparser": { @@ -174,14 +167,15 @@ "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.20" }, "pygobject": { "hashes": [ - "sha256:b97f570e55017fcd3732164811f24ecf63983a4834f61b55b0aaf64ecefac856" + "sha256:012a589aec687bfa809a1ff9f5cd775dc7f6fcec1a6bc7fe88e1002a68f8ba34" ], "index": "pypi", - "version": "==3.36.0" + "version": "==3.36.1" }, "pyliblo": { "hashes": [ @@ -192,54 +186,55 @@ }, "pyqt5": { "hashes": [ - "sha256:3b91dd1d0cbfaea85ad057247ba621187e511434b0c9d6d40de69fd5e833b109", - "sha256:a9bdc46ab1f6397770e6b8dca84ac07a0250d26b1a31587f25619cf31a075532", - "sha256:bd230c6fd699eabf1ceb51e13a8b79b74c00a80272c622427b80141a22269eb0", - "sha256:ee168a486c9a758511568147815e2959652cd0aabea832fa5e87cf6b241d2180", - "sha256:f61ddc78547d6ca763323ccd4a9e374c71b29feda1f5ce2d3e91e4f8d2cf1942" + "sha256:14be35c0c1bcc804791a096d2ef9950f12c6fd34dd11dbe61b8c769fefcdf98c", + "sha256:3605d34ba6291b9194c46035e228d6d01f39d120cf5ecc70301c11e7900fed21", + "sha256:5bac0fab1e9891d73400c2470a9cb810e6bdbc7027a84ae4d3ec83436f1109ec", + "sha256:c6f75488ffd5365a65893bc64ea82a6957db126fbfe33654bcd43ae1c30c52f9", + "sha256:e05c86b8c4f02d62a5b355d426fd8d063781dd44c6a3f916640a5beb40efe60a" ], "index": "pypi", - "version": "==5.14.2" + "version": "==5.15.0" }, "pyqt5-sip": { "hashes": [ - "sha256:01919371d32b26208b2f0318f1e15680d3aa60d1ced1812a5dac8bdb483fea69", - "sha256:11f8cc2de287c3457fee53e781f06fb71f04251e7ae408ed22696ed65fd2bcf4", - "sha256:168a6d700daf366b7cf255a8cabf8d07bfe2294859e6b3b2636c36c2f89265c9", - "sha256:16a19b9f36985b8bff30b89fb8859d831713dd528fba5600563e36ff077960a2", - "sha256:16a9a4daf85bfaa3aec35237ff28d8773a3ec937d9f8dc7fc3db7716de42d4a9", - "sha256:31c74602ccd6b70e4352550eb41aa980dc1d6009444f3c8eb1b844e84bd144cf", - "sha256:360de29634e2ce1df84d2b588bd8c1a29b768f3a5225869d63adb03bc21bd32a", - "sha256:3cb9076ba0e574b2f026759103eb0e12051128714f5aa136cca53229d3ad72d1", - "sha256:4f87d59d29ca1c5a4005bbec27af002be787210dc5f8f87fe5d747883a836083", - "sha256:65fceeea2ac738a92f7e3e459ece1b4e2fbf49fd1d6b732a73d0d4bcfc434452", - "sha256:85e68b8936f1756060ddcb3ef0a84af78ce89993fa6594b3049a0eca53d6d2fa", - "sha256:9dd5769e83e64d017d02981563c8159d825425b6c4998c937a880888f4dcb7a3", - "sha256:a8a6c0512641fc042726b6253b2d5f3f3f800098334d198d8ebdf337d85ab3d7", - "sha256:b068f4791e97427d82a27e7df28cc3ee33f7e4353f48ed6a123f8cdba44266b2", - "sha256:b34c1f227a8f8e97059f20e5424f117f66a302b42e34d4039158494c6371b1ce", - "sha256:b68cfe632a512c0551e8860f35c1fcab5cd1ad5e168b4814fddd88121f447b0a", - "sha256:df4f5cdb86f47df5f6fc35be29cc45df7b5a2c171d07dbf377d558b226554ea3" + "sha256:0a34b6596bdd28d52da3a51fa8d9bb0b287bcb605c2512aa3251b9028cc71f4d", + "sha256:1d65ce08a56282fb0273dd06585b8927b88d4fba71c01a54f8e2ac87ac1ed387", + "sha256:224e2fbb7088595940c348d168a317caa2110cbb7a5b957a8c3fc0d9296ee069", + "sha256:2a1153cda63f2632d3d5698f0cf29f6b1f1d5162305dc6f5b23336ad8f1039ed", + "sha256:2a2239d16a49ce6eaf10166a84424543111f8ebe49d3c124d02af91b01a58425", + "sha256:58eae636e0b1926cddec98a703319a47f671cef07d73aaa525ba421cd4adfeb5", + "sha256:5c19c4ad67af087e8f4411da7422391b236b941f5f0697f615c5816455d1355d", + "sha256:61aa60fb848d740581646603a12c2dcb8d7c4cbd2a9c476a1c891ec360ff0b87", + "sha256:8d9f4dc7dbae9783c5dafd66801875a2ebf9302c3addd5739f772285c1c1e91c", + "sha256:94c80677b1e8c92fa080e24045d54ace5e4343c4ee6d0216675cd91d6f8e122a", + "sha256:9b69db29571dde679908fb237784a8e7af4a2cbf1b7bb25bdb86e487210e04d2", + "sha256:9ef12754021bcc1246f97e00ea62b5594dd5c61192830639ab4a1640bd4b7940", + "sha256:b1bbe763d431d26f9565cba3e99866768761366ab6d609d2506d194882156fa7", + "sha256:d7b8a8f89385ad9e3da38e0123c22c0efc18005e0e2731b6b95e4c21db2049d2", + "sha256:e6254647fa35e1260282aeb9c32a3dd363287b9a1ffcc4f22bd27e54178e92e4", + "sha256:f4c294bfaf2be8004583266d4621bfd3a387e12946f548f966a7fbec91845f1b", + "sha256:fa3d70f370604efc67085849d3d1d3d2109faa716c520faf601d15845df64de6" ], - "version": "==12.7.2" + "markers": "python_version >= '3.5'", + "version": "==12.8.0" }, "python-rtmidi": { "hashes": [ - "sha256:07ef73412820c08d0e5ed15927eb41f986efd5ccb23ad1065e7128d4275fe8b9", - "sha256:1791458d0dc4d3dd408b381d0b3613a09a99b1cf38fbe7b0fcb470616423a4b2", - "sha256:3b5913e6a3126788578566312064e73c90b8819673aee9a39dc985fed17099f7", - "sha256:3e97028ae8039fe7d9b802a42105c771268ea8b57533441e5ec46a8dc8229984", - "sha256:3fce8e85c4c09fd480fb976189c576377293ab120a32e330e7d5489e858dd427", - "sha256:3fd151b801128bdedcffa3be4899a68958a0ad5a102d100a3f2bce7e01d42ced", - "sha256:4ed5da58e4b57e84074f15e9c27bb3fc1b535258c502517f3c7e1042ed74ffe4", - "sha256:7cf3559bc9264d1ae3d85d636271df2a7beea0d6e2582a860513c8b272cfc46a", - "sha256:da22994bef4aec2fd5adb14be80a8cfc3710fd8964204bbf5f33e257ea79442d", - "sha256:e74fd4e7fc4c783f41724ecd2b0a17cbd7792795145a654421c46c3bdb292f02", - "sha256:e86fe9951340e10db66c21c5ae9e63d3249e70148e74cbeb37d16257e2f39f53", - "sha256:fe763f863471b24c24b8d103dd52e4a0d1e55339a2cfde5e06d635a909c08719" + "sha256:07bfecdbd6f1abe11f57f4448cf1da29dc199daee16ee7e5a8db092a2d1c1e2c", + "sha256:16f6396b8000011bc812ca58f15d87689027c06f30e10a7d53015d0c689e94d4", + "sha256:25d9ca48062fdce6a9cc23584af0330eadb348c5e3234413ff223fd23b12f110", + "sha256:31a6e852486937d2eb474b1a624ecb3961279603472641832573b320adf98bf7", + "sha256:5cd46bfbe81913c48ec353130ac355fd83653a7e6c52a288e6c37627e480d42f", + "sha256:92846ee22529407ffdc68c613bcc45310dedfb307ee325150ba9748634704e0c", + "sha256:a171803d47772a9dc2410a09d84eb39500c759f31c98580b0eb4a3e0e198198a", + "sha256:b8acac2c25a2b5c52f47f5abb7fe30ed169ba34e4186026fbed29768d07ef253", + "sha256:c9df96f9761990a6c19d957438b8a06d8714c46d0232cbb96b697581b9401b57", + "sha256:f9b17b293ef98dad3f0e8fc8cc63796b41a21513bbe532ac4f1188bb4b9998b9", + "sha256:fdd5d257ce720f47e9d06b15120b1dea8772042c216771194203b5f5278db58a", + "sha256:ffa11547cac8b49e4c55a1222b5c49cc62304f8cda816d9638395e18541e870e" ], "index": "pypi", - "version": "==1.4.0" + "version": "==1.4.1" }, "requests": { "hashes": [ @@ -251,18 +246,19 @@ }, "sortedcontainers": { "hashes": [ - "sha256:974e9a32f56b17c1bac2aebd9dcf197f3eb9cd30553c5852a3187ad162e1a03a", - "sha256:d9e96492dd51fae31e60837736b38fe42a187b5404c16606ff7ee7cd582d4c60" + "sha256:4e73a757831fc3ca4de2859c422564239a31d8213d09a2a666e375807034d2ba", + "sha256:c633ebde8580f241f274c1f8994a665c0e54a17724fecd0cae2f079e09c36d3f" ], "index": "pypi", - "version": "==2.1.0" + "version": "==2.2.2" }, "urllib3": { "hashes": [ - "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", - "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" + "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", + "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" ], - "version": "==1.25.8" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.25.9" } }, "develop": { @@ -276,10 +272,11 @@ }, "six": { "hashes": [ - "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", - "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "version": "==1.14.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.15.0" }, "webencodings": { "hashes": [ diff --git a/lisp/plugins/action_cues/collection_cue.py b/lisp/plugins/action_cues/collection_cue.py index bb3480683..2bfd3bac9 100644 --- a/lisp/plugins/action_cues/collection_cue.py +++ b/lisp/plugins/action_cues/collection_cue.py @@ -24,6 +24,8 @@ QAbstractItemView, QHeaderView, QTableView, + QGroupBox, + QPushButton, ) from lisp.application import Application @@ -63,68 +65,89 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.setLayout(QVBoxLayout(self)) - self.cue_dialog = CueSelectDialog( + self.cueDialog = CueSelectDialog( cues=Application().cue_model, selection_mode=QAbstractItemView.ExtendedSelection, ) - self.collectionModel = CollectionModel() + + self.collectionGroup = QGroupBox(self) + self.collectionGroup.setLayout(QVBoxLayout()) + self.layout().addWidget(self.collectionGroup) + self.collectionView = CollectionView( - Application().cue_model, self.cue_dialog, parent=self + Application().cue_model, self.cueDialog, parent=self.collectionGroup ) self.collectionView.setModel(self.collectionModel) self.collectionView.setAlternatingRowColors(True) - self.layout().addWidget(self.collectionView) + self.collectionGroup.layout().addWidget(self.collectionView) # Buttons - self.dialogButtons = QDialogButtonBox(self) + self.dialogButtons = QDialogButtonBox(self.collectionGroup) self.dialogButtons.setSizePolicy( QSizePolicy.Minimum, QSizePolicy.Minimum ) - self.layout().addWidget(self.dialogButtons) + self.collectionGroup.layout().addWidget(self.dialogButtons) + + self.addButton = QPushButton(self.dialogButtons) + self.dialogButtons.addButton( + self.addButton, QDialogButtonBox.ActionRole + ) + self.addButton.clicked.connect(self._showAddCueDialog) - self.addButton = self.dialogButtons.addButton( - translate("CollectionCue", "Add"), QDialogButtonBox.ActionRole + self.delButton = QPushButton(self.dialogButtons) + self.dialogButtons.addButton( + self.delButton, QDialogButtonBox.ActionRole ) - self.addButton.clicked.connect(self._add_dialog) + self.delButton.clicked.connect(self._removeCurrentCue) + + self.retranslateUi() - self.delButton = self.dialogButtons.addButton( - translate("CollectionCue", "Remove"), QDialogButtonBox.ActionRole + def retranslateUi(self): + self.collectionGroup.setTitle( + translate("SettingsPageName", "Edit Collection") ) - self.delButton.clicked.connect(self._remove_selected) + self.addButton.setText(translate("CollectionCue", "Add")) + self.delButton.setText(translate("CollectionCue", "Remove")) + + def enableCheck(self, enabled): + self.setGroupEnabled(self.collectionGroup, enabled) def loadSettings(self, settings): for target_id, action in settings.get("targets", []): target = Application().cue_model.get(target_id) if target is not None: - self._add_cue(target, CueAction(action)) + self._addCue(target, CueAction(action)) def getSettings(self): - targets = [] - for target_id, action in self.collectionModel.rows: - targets.append((target_id, action.value)) + if self.isGroupEnabled(self.collectionGroup): + targets = [] + for target_id, action in self.collectionModel.rows: + targets.append((target_id, action.value)) + + return {"targets": targets} - return {"targets": targets} + return {} - def _add_cue(self, cue, action): + def _addCue(self, cue, action): self.collectionModel.appendRow(cue.__class__, cue.id, action) - self.cue_dialog.remove_cue(cue) + self.cueDialog.remove_cue(cue) - def _add_dialog(self): - if self.cue_dialog.exec() == QDialog.Accepted: - for target in self.cue_dialog.selected_cues(): - self._add_cue(target, target.CueActions[0]) + def _showAddCueDialog(self): + if self.cueDialog.exec() == QDialog.Accepted: + for target in self.cueDialog.selected_cues(): + self._addCue(target, target.CueActions[0]) - def _remove_selected(self): + def _removeCurrentCue(self): row = self.collectionView.currentIndex().row() - cue_id = self.collectionModel.rows[row][0] + cueId = self.collectionModel.rows[row][0] self.collectionModel.removeRow(row) - self.cue_dialog.add_cue(Application().cue_model.get(cue_id)) + self.cueDialog.add_cue(Application().cue_model.get(cueId)) class CollectionView(QTableView): - def __init__(self, cue_model, cue_select, **kwargs): + def __init__(self, cueModel, cueSelect, **kwargs): super().__init__(**kwargs) self.setSelectionBehavior(QTableView.SelectRows) @@ -141,7 +164,7 @@ def __init__(self, cue_model, cue_select, **kwargs): self.verticalHeader().setHighlightSections(False) self.delegates = [ - CueSelectionDelegate(cue_model, cue_select), + CueSelectionDelegate(cueModel, cueSelect), CueActionDelegate(), ] diff --git a/lisp/plugins/action_cues/command_cue.py b/lisp/plugins/action_cues/command_cue.py index c9b09134c..6cdb95523 100644 --- a/lisp/plugins/action_cues/command_cue.py +++ b/lisp/plugins/action_cues/command_cue.py @@ -142,8 +142,7 @@ def retranslateUi(self): ) def enableCheck(self, enabled): - self.group.setCheckable(enabled) - self.group.setChecked(False) + self.setGroupEnabled(self.group, enabled) self.noOutputCheckBox.setTristate(enabled) if enabled: @@ -166,9 +165,11 @@ def loadSettings(self, settings): def getSettings(self): settings = {} - if not (self.group.isCheckable() and not self.group.isChecked()): - if self.commandLineEdit.text().strip(): - settings["command"] = self.commandLineEdit.text() + if ( + self.isGroupEnabled(self.group) + and self.commandLineEdit.text().strip() + ): + settings["command"] = self.commandLineEdit.text() if self.noOutputCheckBox.checkState() != Qt.PartiallyChecked: settings["no_output"] = self.noOutputCheckBox.isChecked() if self.noErrorCheckBox.checkState() != Qt.PartiallyChecked: diff --git a/lisp/plugins/action_cues/index_action_cue.py b/lisp/plugins/action_cues/index_action_cue.py index 8a93132ef..0bd0409d3 100644 --- a/lisp/plugins/action_cues/index_action_cue.py +++ b/lisp/plugins/action_cues/index_action_cue.py @@ -132,23 +132,19 @@ def retranslateUi(self): ) def enableCheck(self, enabled): - self.indexGroup.setChecked(enabled) - self.indexGroup.setChecked(False) - - self.actionGroup.setCheckable(enabled) - self.actionGroup.setChecked(False) + self.setGroupEnabled(self.indexGroup, enabled) + self.setGroupEnabled(self.actionGroup, enabled) def getSettings(self): - conf = {} - checkable = self.actionGroup.isCheckable() + settings = {} - if not (checkable and not self.indexGroup.isChecked()): - conf["relative"] = self.relativeCheck.isChecked() - conf["target_index"] = self.targetIndexSpin.value() - if not (checkable and not self.actionGroup.isChecked()): - conf["action"] = self.actionCombo.currentData() + if self.isGroupEnabled(self.indexGroup): + settings["relative"] = self.relativeCheck.isChecked() + settings["target_index"] = self.targetIndexSpin.value() + if self.isGroupEnabled(self.actionGroup): + settings["action"] = self.actionCombo.currentData() - return conf + return settings def loadSettings(self, settings): self._cue_index = settings.get("index", -1) diff --git a/lisp/plugins/action_cues/seek_cue.py b/lisp/plugins/action_cues/seek_cue.py index 3acab539d..58d9cfd8a 100644 --- a/lisp/plugins/action_cues/seek_cue.py +++ b/lisp/plugins/action_cues/seek_cue.py @@ -63,7 +63,7 @@ def __init__(self, **kwargs): self.setLayout(QVBoxLayout()) self.layout().setAlignment(QtCore.Qt.AlignTop) - self.cue_id = -1 + self.targetCueId = -1 self.cueDialog = CueSelectDialog( cues=Application().cue_model.filter(MediaCue), parent=self @@ -107,30 +107,31 @@ def select_cue(self): cue = self.cueDialog.selected_cue() if cue is not None: - self.cue_id = cue.id + self.targetCueId = cue.id self.seekEdit.setMaximumTime( QTime.fromMSecsSinceStartOfDay(cue.media.duration) ) self.cueLabel.setText(cue.name) def enableCheck(self, enabled): - self.cueGroup.setCheckable(enabled) - self.cueGroup.setChecked(False) - - self.seekGroup.setCheckable(enabled) - self.seekGroup.setChecked(False) + self.setGroupEnabled(self.cueGroup, enabled) + self.setGroupEnabled(self.seekGroup, enabled) def getSettings(self): - return { - "target_id": self.cue_id, - "time": self.seekEdit.time().msecsSinceStartOfDay(), - } + settings = {} + + if self.isGroupEnabled(self.cueGroup): + settings["target_id"] = self.targetCueId + if self.isGroupEnabled(self.seekGroup): + settings["time"] = self.seekEdit.time().msecsSinceStartOfDay() + + return settings def loadSettings(self, settings): if settings is not None: cue = Application().cue_model.get(settings.get("target_id")) if cue is not None: - self.cue_id = settings["target_id"] + self.targetCueId = settings["target_id"] self.seekEdit.setMaximumTime( QTime.fromMSecsSinceStartOfDay(cue.media.duration) ) diff --git a/lisp/plugins/action_cues/stop_all.py b/lisp/plugins/action_cues/stop_all.py index a959b5be2..c927f6093 100644 --- a/lisp/plugins/action_cues/stop_all.py +++ b/lisp/plugins/action_cues/stop_all.py @@ -76,16 +76,13 @@ def retranslateUi(self): self.group.setTitle(translate("StopAll", "Stop Action")) def enableCheck(self, enabled): - self.group.setCheckable(enabled) - self.group.setChecked(False) + self.setGroupEnabled(self.group, enabled) def getSettings(self): - conf = {} + if self.isGroupEnabled(self.group): + return {"action": self.actionCombo.currentData()} - if not (self.group.isCheckable() and not self.group.isChecked()): - conf["action"] = self.actionCombo.currentData() - - return conf + return {} def loadSettings(self, settings): self.actionCombo.setCurrentText( diff --git a/lisp/plugins/action_cues/volume_control.py b/lisp/plugins/action_cues/volume_control.py index 9bdcfa08f..de65c4a5f 100644 --- a/lisp/plugins/action_cues/volume_control.py +++ b/lisp/plugins/action_cues/volume_control.py @@ -211,28 +211,22 @@ def select_cue(self): self.cueLabel.setText(cue.name) def enableCheck(self, enabled): - self.cueGroup.setCheckable(enabled) - self.cueGroup.setChecked(False) + self.setGroupEnabled(self.cueGroup, enabled) + self.setGroupEnabled(self.volumeGroup, enabled) + self.setGroupEnabled(self.fadeGroup, enabled) - self.volumeGroup.setCheckable(enabled) - self.volumeGroup.setChecked(False) + def getSettings(self): + settings = {} - self.fadeGroup.setCheckable(enabled) - self.volumeGroup.setChecked(False) + if self.isGroupEnabled(self.cueGroup): + settings["target_id"] = self.cue_id + if self.isGroupEnabled(self.volumeGroup): + settings["volume"] = self.volumeEdit.value() / 100 + if self.isGroupEnabled(self.fadeGroup): + settings["duration"] = self.fadeEdit.duration() * 1000 + settings["fade_type"] = self.fadeEdit.fadeType() - def getSettings(self): - conf = {} - checkable = self.cueGroup.isCheckable() - - if not (checkable and not self.cueGroup.isChecked()): - conf["target_id"] = self.cue_id - if not (checkable and not self.volumeGroup.isCheckable()): - conf["volume"] = self.volumeEdit.value() / 100 - if not (checkable and not self.fadeGroup.isCheckable()): - conf["duration"] = self.fadeEdit.duration() * 1000 - conf["fade_type"] = self.fadeEdit.fadeType() - - return conf + return settings def loadSettings(self, settings): cue = Application().cue_model.get(settings.get("target_id", "")) diff --git a/lisp/plugins/controller/protocols/keyboard.py b/lisp/plugins/controller/protocols/keyboard.py index 0b4fd8797..74013e64e 100644 --- a/lisp/plugins/controller/protocols/keyboard.py +++ b/lisp/plugins/controller/protocols/keyboard.py @@ -80,8 +80,7 @@ def retranslateUi(self): self.removeButton.setText(translate("ControllerSettings", "Remove")) def enableCheck(self, enabled): - self.keyGroup.setCheckable(enabled) - self.keyGroup.setChecked(False) + self.setGroupEnabled(self.keyGroup, enabled) def getSettings(self): return {"keyboard": self.keyboardModel.rows} diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index fd398c93b..32d6f4c39 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -127,8 +127,7 @@ def retranslateUi(self): ) def enableCheck(self, enabled): - self.midiGroup.setCheckable(enabled) - self.midiGroup.setChecked(False) + self.setGroupEnabled(self.midiGroup, enabled) def getSettings(self): entries = [] diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 4a517231c..29b1efd0c 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -289,8 +289,7 @@ def retranslateUi(self): self.oscCapture.setText(translate("ControllerOscSettings", "Capture")) def enableCheck(self, enabled): - self.oscGroup.setCheckable(enabled) - self.oscGroup.setChecked(False) + self.setGroupEnabled(self.oscGroup, enabled) def getSettings(self): entries = [] diff --git a/lisp/plugins/gst_backend/settings/alsa_sink.py b/lisp/plugins/gst_backend/settings/alsa_sink.py index 855202167..e45a523e5 100644 --- a/lisp/plugins/gst_backend/settings/alsa_sink.py +++ b/lisp/plugins/gst_backend/settings/alsa_sink.py @@ -76,8 +76,7 @@ def retranslateUi(self): ) def enableCheck(self, enabled): - self.deviceGroup.setCheckable(enabled) - self.deviceGroup.setChecked(False) + self.setGroupEnabled(self.deviceGroup, enabled) def loadSettings(self, settings): device = settings.get("device", "default") diff --git a/lisp/plugins/gst_backend/settings/audio_dynamic.py b/lisp/plugins/gst_backend/settings/audio_dynamic.py index b1e2cd3ac..277ac0824 100644 --- a/lisp/plugins/gst_backend/settings/audio_dynamic.py +++ b/lisp/plugins/gst_backend/settings/audio_dynamic.py @@ -110,22 +110,18 @@ def retranslateUi(self): ) def enableCheck(self, enabled): - self.groupBox.setCheckable(enabled) - self.groupBox.setChecked(False) + self.setGroupEnabled(self.groupBox, enabled) def getSettings(self): - settings = {} - - if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): - settings["ratio"] = self.ratioSpin.value() - settings["threshold"] = math.pow( - 10, self.thresholdSpin.value() / 20 - ) - - settings["mode"] = self.modeComboBox.currentData() - settings["characteristics"] = self.chComboBox.currentData() - - return settings + if self.isGroupEnabled(self.groupBox): + return { + "ratio": self.ratioSpin.value(), + "threshold": math.pow(10, self.thresholdSpin.value() / 20), + "mode": self.modeComboBox.currentData(), + "characteristics": self.chComboBox.currentData(), + } + + return {} def loadSettings(self, settings): self.modeComboBox.setCurrentText( diff --git a/lisp/plugins/gst_backend/settings/audio_pan.py b/lisp/plugins/gst_backend/settings/audio_pan.py index b2f72810e..01cc6dd33 100644 --- a/lisp/plugins/gst_backend/settings/audio_pan.py +++ b/lisp/plugins/gst_backend/settings/audio_pan.py @@ -59,11 +59,10 @@ def retransaleUi(self): self.panLabel.setText(translate("AudioPanSettings", "Center")) def enableCheck(self, enabled): - self.panBox.setCheckable(enabled) - self.panBox.setChecked(False) + self.setGroupEnabled(self.panBox, enabled) def getSettings(self): - if not (self.panBox.isCheckable() and not self.panBox.isChecked()): + if self.isGroupEnabled(self.panBox): return {"pan": self.panSlider.value() / 10} return {} diff --git a/lisp/plugins/gst_backend/settings/db_meter.py b/lisp/plugins/gst_backend/settings/db_meter.py index d8cdde190..38d416108 100644 --- a/lisp/plugins/gst_backend/settings/db_meter.py +++ b/lisp/plugins/gst_backend/settings/db_meter.py @@ -96,11 +96,11 @@ def loadSettings(self, settings): self.falloffSpin.setValue(settings.get("peak_falloff", 20)) def getSettings(self): - settings = {} - - if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): - settings["interval"] = self.intervalSpin.value() * Gst.MSECOND - settings["peak_ttl"] = self.ttlSpin.value() * Gst.MSECOND - settings["peak_falloff"] = self.falloffSpin.value() - - return settings + if self.isGroupEnabled(self.groupBox): + return { + "interval": self.intervalSpin.value() * Gst.MSECOND, + "peak_ttl": self.ttlSpin.value() * Gst.MSECOND, + "peak_falloff": self.falloffSpin.value(), + } + + return {} diff --git a/lisp/plugins/gst_backend/settings/equalizer10.py b/lisp/plugins/gst_backend/settings/equalizer10.py index 2e7f5ad51..1a69d4410 100644 --- a/lisp/plugins/gst_backend/settings/equalizer10.py +++ b/lisp/plugins/gst_backend/settings/equalizer10.py @@ -82,17 +82,13 @@ def __init__(self, **kwargs): self.groupBox.layout().addWidget(fLabel, 2, n) def enableCheck(self, enabled): - self.groupBox.setCheckable(enabled) - self.groupBox.setChecked(False) + self.setGroupEnabled(self.groupBox, enabled) def getSettings(self): - settings = {} + if self.isGroupEnabled(self.groupBox): + return {band: self.sliders[band].value() for band in self.sliders} - if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): - for band in self.sliders: - settings[band] = self.sliders[band].value() - - return settings + return {} def loadSettings(self, settings): for band in self.sliders: diff --git a/lisp/plugins/gst_backend/settings/jack_sink.py b/lisp/plugins/gst_backend/settings/jack_sink.py index 3f5ce5645..22fad7535 100644 --- a/lisp/plugins/gst_backend/settings/jack_sink.py +++ b/lisp/plugins/gst_backend/settings/jack_sink.py @@ -86,14 +86,10 @@ def closeEvent(self, event): super().closeEvent(event) def getSettings(self): - settings = {} + if self.isGroupEnabled(self.jackGroup): + return {"connections": self.connections} - if not ( - self.jackGroup.isCheckable() and not self.jackGroup.isChecked() - ): - settings["connections"] = self.connections - - return settings + return {} def loadSettings(self, settings): connections = settings.get("connections", []) @@ -101,8 +97,7 @@ def loadSettings(self, settings): self.connections = connections.copy() def enableCheck(self, enabled): - self.jackGroup.setCheckable(enabled) - self.jackGroup.setChecked(False) + self.setGroupEnabled(self.jackGroup, enabled) def __edit_connections(self): dialog = JackConnectionsDialog(self.__jack_client, parent=self) diff --git a/lisp/plugins/gst_backend/settings/pitch.py b/lisp/plugins/gst_backend/settings/pitch.py index 7db4e9394..afada0e4f 100644 --- a/lisp/plugins/gst_backend/settings/pitch.py +++ b/lisp/plugins/gst_backend/settings/pitch.py @@ -65,11 +65,10 @@ def retranslateUi(self): self.pitch_changed(0) def enableCheck(self, enabled): - self.groupBox.setCheckable(enabled) - self.groupBox.setChecked(False) + self.setGroupEnabled(self.groupBox, enabled) def getSettings(self): - if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): + if self.isGroupEnabled(self.groupBox): return {"pitch": math.pow(2, self.pitchSlider.value() / 12)} return {} diff --git a/lisp/plugins/gst_backend/settings/preset_src.py b/lisp/plugins/gst_backend/settings/preset_src.py index 7f69335f6..fe0369d5c 100644 --- a/lisp/plugins/gst_backend/settings/preset_src.py +++ b/lisp/plugins/gst_backend/settings/preset_src.py @@ -48,14 +48,10 @@ def __init__(self, **kwargs): self.functionGroup.layout().addWidget(self.functionDuration) def enableCheck(self, enabled): - self.functionGroup.setCheckable(enabled) - self.functionGroup.setChecked(False) + self.setGroupEnabled(self.functionGroup, enabled) def getSettings(self): - if not ( - self.functionGroup.isCheckable() - and not self.functionGroup.isChecked() - ): + if self.isGroupEnabled(self.functionGroup): return { "preset": self.functionCombo.currentText(), "duration": self.functionDuration.time().msecsSinceStartOfDay(), diff --git a/lisp/plugins/gst_backend/settings/speed.py b/lisp/plugins/gst_backend/settings/speed.py index 9f0bf15f5..3c805d309 100644 --- a/lisp/plugins/gst_backend/settings/speed.py +++ b/lisp/plugins/gst_backend/settings/speed.py @@ -63,11 +63,10 @@ def retranslateUi(self): self.speedLabel.setText("1.0") def enableCheck(self, enabled): - self.groupBox.setCheckable(enabled) - self.groupBox.setChecked(False) + self.setGroupEnabled(self.groupBox, enabled) def getSettings(self): - if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): + if self.isGroupEnabled(self.groupBox): return {"speed": self.speedSlider.value() / 100} return {} diff --git a/lisp/plugins/gst_backend/settings/uri_input.py b/lisp/plugins/gst_backend/settings/uri_input.py index 533091161..feea1f4ec 100644 --- a/lisp/plugins/gst_backend/settings/uri_input.py +++ b/lisp/plugins/gst_backend/settings/uri_input.py @@ -96,11 +96,9 @@ def retranslateUi(self): def getSettings(self): settings = {} - checkable = self.fileGroup.isCheckable() - - if not (checkable and not self.fileGroup.isChecked()): + if self.isGroupEnabled(self.fileGroup): settings["uri"] = self.filePath.text() - if not (checkable and not self.bufferingGroup.isChecked()): + if self.isGroupEnabled(self.bufferingGroup): settings["use_buffering"] = self.useBuffering.isChecked() settings["download"] = self.download.isChecked() settings["buffer_size"] = self.bufferSize.value() @@ -114,8 +112,7 @@ def loadSettings(self, settings): self.bufferSize.setValue(settings.get("buffer_size", -1)) def enableCheck(self, enabled): - self.fileGroup.setCheckable(enabled) - self.fileGroup.setChecked(False) + self.setGroupEnabled(self.fileGroup, enabled) def select_file(self): directory = "" diff --git a/lisp/plugins/gst_backend/settings/user_element.py b/lisp/plugins/gst_backend/settings/user_element.py index c564be018..b1e5f7e20 100644 --- a/lisp/plugins/gst_backend/settings/user_element.py +++ b/lisp/plugins/gst_backend/settings/user_element.py @@ -57,14 +57,13 @@ def retranslateUi(self): ) def enableCheck(self, enabled): - self.groupBox.setCheckable(enabled) - self.groupBox.setChecked(False) + self.setGroupEnabled(self.groupBox, enabled) def loadSettings(self, settings): self.textEdit.setPlainText(settings.get("bin", "")) def getSettings(self): - if not (self.groupBox.isCheckable() and not self.groupBox.isChecked()): + if self.isGroupEnabled(self.groupBox): return {"bin": self.textEdit.toPlainText().strip()} return {} diff --git a/lisp/plugins/gst_backend/settings/volume.py b/lisp/plugins/gst_backend/settings/volume.py index 7547ba3c9..f96175026 100644 --- a/lisp/plugins/gst_backend/settings/volume.py +++ b/lisp/plugins/gst_backend/settings/volume.py @@ -89,18 +89,16 @@ def retranslateUi(self): self.normalReset.setText(translate("VolumeSettings", "Reset")) def enableCheck(self, enabled): - for box in [self.normalBox, self.volumeBox]: - box.setCheckable(enabled) - box.setChecked(False) + self.setGroupEnabled(self.normalBox, enabled) + self.setGroupEnabled(self.volumeBox, enabled) def getSettings(self): settings = {} - checkable = self.volumeBox.isCheckable() - if not (checkable and not self.volumeBox.isChecked()): + if self.isGroupEnabled(self.volumeBox): settings["volume"] = db_to_linear(self.volume.value() / 10) settings["mute"] = self.muteButton.isMute() - if not (checkable and not self.normalBox.isChecked()): + if self.isGroupEnabled(self.normalBox): if self.normalReset.isChecked(): settings["normal_volume"] = 1 # If the apply button is pressed, show the correct value diff --git a/lisp/plugins/midi/midi_cue.py b/lisp/plugins/midi/midi_cue.py index b3c08dffe..60116765a 100644 --- a/lisp/plugins/midi/midi_cue.py +++ b/lisp/plugins/midi/midi_cue.py @@ -64,8 +64,14 @@ def __init__(self, **kwargs): self.midiEdit = MIDIMessageEdit(parent=self) self.layout().addWidget(self.midiEdit) + def enableCheck(self, enabled): + self.setGroupEnabled(self.midiEdit.msgGroup, enabled) + def getSettings(self): - return {"message": midi_dict_to_str(self.midiEdit.getMessageDict())} + if self.isGroupEnabled(self.midiEdit.msgGroup): + return {"message": midi_dict_to_str(self.midiEdit.getMessageDict())} + + return {} def loadSettings(self, settings): message = settings.get("message", "") diff --git a/lisp/plugins/osc/osc_cue.py b/lisp/plugins/osc/osc_cue.py index effa748b4..adb7261e1 100644 --- a/lisp/plugins/osc/osc_cue.py +++ b/lisp/plugins/osc/osc_cue.py @@ -262,19 +262,15 @@ def retranslateUi(self): self.fadeCurveLabel.setText(translate("OscCue", "Curve")) def enableCheck(self, enabled): - self.oscGroup.setCheckable(enabled) - self.oscGroup.setChecked(False) - - self.fadeGroup.setCheckable(enabled) - self.fadeGroup.setChecked(False) + self.setGroupEnabled(self.oscGroup, enabled) + self.setGroupEnabled(self.fadeGroup, enabled) def getSettings(self): - conf = {} - checkable = self.oscGroup.isCheckable() + settings = {} - if not (checkable and not self.oscGroup.isChecked()): - conf["path"] = self.pathEdit.text() - conf["args"] = [ + if self.isGroupEnabled(self.oscGroup): + settings["path"] = self.pathEdit.text() + settings["args"] = [ { "type": row[COL_TYPE], "start": row[COL_START_VALUE], @@ -284,11 +280,11 @@ def getSettings(self): for row in self.oscModel.rows ] - if not (checkable and not self.fadeGroup.isCheckable()): - conf["duration"] = self.fadeSpin.value() * 1000 - conf["fade_type"] = self.fadeCurveCombo.currentType() + if self.isGroupEnabled(self.fadeGroup): + settings["duration"] = self.fadeSpin.value() * 1000 + settings["fade_type"] = self.fadeCurveCombo.currentType() - return conf + return settings def loadSettings(self, settings): self.pathEdit.setText(settings.get("path", "")) diff --git a/lisp/plugins/timecode/settings.py b/lisp/plugins/timecode/settings.py index 836eddac7..d32ac28bf 100644 --- a/lisp/plugins/timecode/settings.py +++ b/lisp/plugins/timecode/settings.py @@ -87,14 +87,20 @@ def retranslateUi(self): ) self.trackLabel.setText(translate("TimecodeSettings", "Track number")) + def enableCheck(self, enabled): + self.setGroupEnabled(self.groupBox, enabled) + def getSettings(self): - return { - "timecode": { - "enabled": self.enableTimecodeCheck.isChecked(), - "replace_hours": self.useHoursCheck.isChecked(), - "track": self.trackSpin.value(), + if self.isGroupEnabled(self.groupBox): + return { + "timecode": { + "enabled": self.enableTimecodeCheck.isChecked(), + "replace_hours": self.useHoursCheck.isChecked(), + "track": self.trackSpin.value(), + } } - } + + return {} def loadSettings(self, settings): settings = settings.get("timecode", {}) diff --git a/lisp/plugins/triggers/triggers_settings.py b/lisp/plugins/triggers/triggers_settings.py index 792dc9616..d0a4adf12 100644 --- a/lisp/plugins/triggers/triggers_settings.py +++ b/lisp/plugins/triggers/triggers_settings.py @@ -22,6 +22,8 @@ QSizePolicy, QHeaderView, QTableView, + QGroupBox, + QPushButton, ) from lisp.application import Application @@ -44,53 +46,55 @@ class TriggersSettings(SettingsPage): def __init__(self, **kwargs): super().__init__(**kwargs) self.setLayout(QVBoxLayout(self)) - self.layout().setAlignment(Qt.AlignTop) - - self.cue_select = CueSelectDialog(cues=Application().cue_model) + self.cueSelectDialog = CueSelectDialog(cues=Application().cue_model) self.triggersModel = TriggersModel() + self.triggerGroup = QGroupBox(self) + self.triggerGroup.setLayout(QVBoxLayout()) + self.layout().addWidget(self.triggerGroup) + self.triggersView = TriggersView( - Application().cue_model, self.cue_select, parent=self + Application().cue_model, + self.cueSelectDialog, + parent=self.triggerGroup, ) self.triggersView.setModel(self.triggersModel) - self.layout().addWidget(self.triggersView) + self.triggerGroup.layout().addWidget(self.triggersView) - self.dialogButtons = QDialogButtonBox(self) + self.dialogButtons = QDialogButtonBox(self.triggerGroup) self.dialogButtons.setSizePolicy( QSizePolicy.Minimum, QSizePolicy.Minimum ) - self.layout().addWidget(self.dialogButtons) + self.triggerGroup.layout().addWidget(self.dialogButtons) - self.addButton = self.dialogButtons.addButton( - translate("TriggersSettings", "Add"), QDialogButtonBox.ActionRole + self.addButton = QPushButton() + self.dialogButtons.addButton( + self.addButton, QDialogButtonBox.ActionRole ) - self.addButton.clicked.connect(self._add_trigger) + self.addButton.clicked.connect(self._addTrigger) - self.delButton = self.dialogButtons.addButton( - translate("TriggersSettings", "Remove"), QDialogButtonBox.ActionRole + self.delButton = QPushButton() + self.dialogButtons.addButton( + self.delButton, QDialogButtonBox.ActionRole ) - self.delButton.clicked.connect(self._remove_trigger) + self.delButton.clicked.connect(self._removeCurrentTrigger) - def _add_trigger(self): - if self.cue_select.exec(): - cue = self.cue_select.selected_cue() - if cue is not None: - self.triggersModel.appendRow( - cue.__class__, - CueTriggers.Started.value, - cue.id, - cue.CueActions[0], - ) + self.retranlsateUi() - def _remove_trigger(self): - self.triggersModel.removeRow(self.triggersView.currentIndex().row()) + def retranlsateUi(self): + self.triggerGroup.setTitle(translate("SettingsPageName", "Triggers")) + self.addButton.setText(translate("TriggersSettings", "Add")) + self.delButton.setText(translate("TriggersSettings", "Remove")) + + def enableCheck(self, enabled): + self.setGroupEnabled(self.triggerGroup, enabled) def loadSettings(self, settings): # Remove the edited cue from the list of possible targets edited_cue = Application().cue_model.get(settings.get("id")) if edited_cue: - self.cue_select.remove_cue(edited_cue) + self.cueSelectDialog.remove_cue(edited_cue) for trigger, targets in settings.get("triggers", {}).items(): for target, action in targets: @@ -101,22 +105,39 @@ def loadSettings(self, settings): ) def getSettings(self): - triggers = {} - for trigger, target, action in self.triggersModel.rows: - action = action.value + if self.isGroupEnabled(self.triggerGroup): + triggers = {} + for trigger, target, action in self.triggersModel.rows: + action = action.value - if trigger not in triggers: - triggers[trigger] = [] + if trigger not in triggers: + triggers[trigger] = [] - # Avoid duplicate - if (target, action) not in triggers[trigger]: - triggers[trigger].append((target, action)) + # Avoid duplicate + if (target, action) not in triggers[trigger]: + triggers[trigger].append((target, action)) - return {"triggers": triggers} + return {"triggers": triggers} + + return {} + + def _addTrigger(self): + if self.cueSelectDialog.exec(): + cue = self.cueSelectDialog.selected_cue() + if cue is not None: + self.triggersModel.appendRow( + cue.__class__, + CueTriggers.Started.value, + cue.id, + cue.CueActions[0], + ) + + def _removeCurrentTrigger(self): + self.triggersModel.removeRow(self.triggersView.currentIndex().row()) class TriggersView(QTableView): - def __init__(self, cue_model, cue_select, **kwargs): + def __init__(self, cueModel, cueSelect, **kwargs): super().__init__(**kwargs) self.setSelectionBehavior(QTableView.SelectRows) @@ -136,7 +157,7 @@ def __init__(self, cue_model, cue_select, **kwargs): ComboBoxDelegate( options=[e.value for e in CueTriggers], tr_context="CueTriggers" ), - CueSelectionDelegate(cue_model, cue_select), + CueSelectionDelegate(cueModel, cueSelect), CueActionDelegate(), ] diff --git a/lisp/ui/settings/cue_pages/cue_appearance.py b/lisp/ui/settings/cue_pages/cue_appearance.py index 232246933..372c1406e 100644 --- a/lisp/ui/settings/cue_pages/cue_appearance.py +++ b/lisp/ui/settings/cue_pages/cue_appearance.py @@ -51,7 +51,7 @@ def __init__(self, **kwargs): self.cueDescriptionGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.cueDescriptionGroup) - self.cueDescriptionEdit = QTextEdit(self.cueNameGroup) + self.cueDescriptionEdit = QTextEdit(self.cueDescriptionGroup) self.cueDescriptionGroup.layout().addWidget(self.cueDescriptionEdit) # Font @@ -107,33 +107,25 @@ def retranslateUi(self): ) def enableCheck(self, enabled): - self.cueNameGroup.setCheckable(enabled) - self.cueNameGroup.setChecked(False) - - self.cueDescriptionGroup.setChecked(enabled) - self.cueDescriptionGroup.setChecked(False) - - self.fontSizeGroup.setCheckable(enabled) - self.fontSizeGroup.setChecked(False) - - self.colorGroup.setCheckable(enabled) - self.colorGroup.setChecked(False) + self.setGroupEnabled(self.cueNameGroup, enabled) + self.setGroupEnabled(self.cueDescriptionGroup, enabled) + self.setGroupEnabled(self.fontSizeGroup, enabled) + self.setGroupEnabled(self.colorGroup, enabled) def getSettings(self): settings = {} style = {} - checkable = self.cueNameGroup.isCheckable() - if not (checkable and not self.cueNameGroup.isChecked()): + if self.isGroupEnabled(self.cueNameGroup): settings["name"] = self.cueNameEdit.text() - if not (checkable and not self.cueDescriptionGroup.isChecked()): + if self.isGroupEnabled(self.cueDescriptionGroup): settings["description"] = self.cueDescriptionEdit.toPlainText() - if not (checkable and not self.colorGroup.isChecked()): + if self.isGroupEnabled(self.colorGroup): if self.colorBButton.color() is not None: style["background"] = self.colorBButton.color() if self.colorFButton.color() is not None: style["color"] = self.colorFButton.color() - if not (checkable and not self.fontSizeGroup.isChecked()): + if self.isGroupEnabled(self.fontSizeGroup): style["font-size"] = str(self.fontSizeSpin.value()) + "pt" if style: diff --git a/lisp/ui/settings/cue_pages/cue_general.py b/lisp/ui/settings/cue_pages/cue_general.py index 7693e8ffd..757a3ae3e 100644 --- a/lisp/ui/settings/cue_pages/cue_general.py +++ b/lisp/ui/settings/cue_pages/cue_general.py @@ -121,24 +121,23 @@ def retranslateUi(self): ) def enableCheck(self, enabled): - self.startActionGroup.setCheckable(enabled) - self.startActionGroup.setChecked(False) - self.stopActionGroup.setCheckable(enabled) - self.stopActionGroup.setChecked(False) + self.setGroupEnabled(self.startActionGroup, enabled) + self.setGroupEnabled(self.stopActionGroup, enabled) def getSettings(self): settings = {} - checkable = self.startActionGroup.isCheckable() if ( - not checkable or self.startActionGroup.isChecked() - ) and self.startActionCombo.isEnabled(): + self.isGroupEnabled(self.startActionGroup) + and self.startActionCombo.isEnabled() + ): settings[ "default_start_action" ] = self.startActionCombo.currentItem() if ( - not checkable or self.stopActionGroup.isChecked() - ) and self.stopActionCombo.isEnabled(): + self.isGroupEnabled(self.stopActionGroup) + and self.stopActionCombo.isEnabled() + ): settings["default_stop_action"] = self.stopActionCombo.currentItem() return settings @@ -212,12 +211,9 @@ def retranslateUi(self): self.nextActionGroup.setTitle(translate("CueSettings", "Next action")) def enableCheck(self, enabled): - self.preWaitGroup.setCheckable(enabled) - self.preWaitGroup.setChecked(False) - self.postWaitGroup.setCheckable(enabled) - self.postWaitGroup.setChecked(False) - self.nextActionGroup.setCheckable(enabled) - self.nextActionGroup.setChecked(False) + self.setGroupEnabled(self.preWaitGroup, enabled) + self.setGroupEnabled(self.postWaitGroup, enabled) + self.setGroupEnabled(self.nextActionGroup, enabled) def loadSettings(self, settings): self.preWaitSpin.setValue(settings.get("pre_wait", 0)) @@ -226,13 +222,12 @@ def loadSettings(self, settings): def getSettings(self): settings = {} - checkable = self.preWaitGroup.isCheckable() - if not checkable or self.preWaitGroup.isChecked(): + if self.isGroupEnabled(self.preWaitGroup): settings["pre_wait"] = self.preWaitSpin.value() - if not checkable or self.postWaitGroup.isChecked(): + if self.isGroupEnabled(self.postWaitGroup): settings["post_wait"] = self.postWaitSpin.value() - if not checkable or self.nextActionGroup.isChecked(): + if self.isGroupEnabled(self.nextActionGroup): settings["next_action"] = self.nextActionCombo.currentData() return settings @@ -284,20 +279,17 @@ def loadSettings(self, settings): self.fadeOutEdit.setDuration(settings.get("fadeout_duration", 0)) def enableCheck(self, enabled): - self.fadeInGroup.setCheckable(enabled) - self.fadeInGroup.setChecked(False) - self.fadeOutGroup.setCheckable(enabled) - self.fadeOutGroup.setChecked(False) + self.setGroupEnabled(self.fadeInGroup, enabled) + self.setGroupEnabled(self.fadeOutGroup, enabled) def getSettings(self): settings = {} - checkable = self.fadeInGroup.isCheckable() - if not checkable or self.fadeInGroup.isChecked(): + if self.isGroupEnabled(self.fadeInGroup): settings["fadein_type"] = self.fadeInEdit.fadeType() settings["fadein_duration"] = self.fadeInEdit.duration() - if not checkable or self.fadeOutGroup.isChecked(): + if self.isGroupEnabled(self.fadeOutGroup): settings["fadeout_type"] = self.fadeOutEdit.fadeType() settings["fadeout_duration"] = self.fadeOutEdit.duration() diff --git a/lisp/ui/settings/cue_pages/media_cue.py b/lisp/ui/settings/cue_pages/media_cue.py index fcfdb6138..76bb84e07 100644 --- a/lisp/ui/settings/cue_pages/media_cue.py +++ b/lisp/ui/settings/cue_pages/media_cue.py @@ -96,28 +96,22 @@ def retranslateUi(self): def getSettings(self): settings = {} - checkable = self.startGroup.isCheckable() - if not (checkable and not self.startGroup.isChecked()): + if self.isGroupEnabled(self.startGroup): time = self.startEdit.time().msecsSinceStartOfDay() settings["start_time"] = time - if not (checkable and not self.stopGroup.isChecked()): + if self.isGroupEnabled(self.stopGroup): time = self.stopEdit.time().msecsSinceStartOfDay() settings["stop_time"] = time - if not (checkable and not self.loopGroup.isChecked()): + if self.isGroupEnabled(self.loopGroup): settings["loop"] = self.spinLoop.value() return {"media": settings} def enableCheck(self, enabled): - self.startGroup.setCheckable(enabled) - self.startGroup.setChecked(False) - - self.stopGroup.setCheckable(enabled) - self.stopGroup.setChecked(False) - - self.loopGroup.setCheckable(enabled) - self.loopGroup.setChecked(False) + self.setGroupEnabled(self.startGroup, enabled) + self.setGroupEnabled(self.stopGroup, enabled) + self.setGroupEnabled(self.loopGroup, enabled) def loadSettings(self, settings): settings = settings.get("media", {}) diff --git a/lisp/ui/settings/pages.py b/lisp/ui/settings/pages.py index ff346f074..16af0e465 100644 --- a/lisp/ui/settings/pages.py +++ b/lisp/ui/settings/pages.py @@ -54,7 +54,13 @@ def enableCheck(self, enabled): :type enabled: bool """ - def isGroupEnabled(self, group: QGroupBox): + @staticmethod + def setGroupEnabled(group: QGroupBox, enabled: bool): + group.setCheckable(enabled) + group.setChecked(False) + + @staticmethod + def isGroupEnabled(group: QGroupBox): return not (group.isCheckable() and not group.isChecked()) From 8770ff460164595136cb8016cf8c745a7492a1a9 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 10 Jun 2020 20:54:35 +0200 Subject: [PATCH 245/333] Fix AlsaSink setting widget --- lisp/plugins/gst_backend/settings/alsa_sink.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lisp/plugins/gst_backend/settings/alsa_sink.py b/lisp/plugins/gst_backend/settings/alsa_sink.py index e45a523e5..4daeb67ab 100644 --- a/lisp/plugins/gst_backend/settings/alsa_sink.py +++ b/lisp/plugins/gst_backend/settings/alsa_sink.py @@ -15,11 +15,9 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5 import QtCore from PyQt5.QtCore import Qt from PyQt5.QtWidgets import ( QGroupBox, - QHBoxLayout, QComboBox, QLabel, QVBoxLayout, @@ -45,7 +43,7 @@ def __init__(self, **kwargs): self.deviceGroup = QGroupBox(self) self.deviceGroup.setGeometry(0, 0, self.width(), 100) - self.deviceGroup.setLayout(QHBoxLayout()) + self.deviceGroup.setLayout(QVBoxLayout()) self.layout().addWidget(self.deviceGroup) self.deviceComboBox = QComboBox(self.deviceGroup) @@ -59,11 +57,10 @@ def __init__(self, **kwargs): self.deviceGroup.layout().addWidget(self.deviceComboBox) self.helpLabel = QLabel(self.deviceGroup) - font = self.noticeLabel.font() - font.setPointSizeF(font.pointSizeF() * 0.9) - self.noticeLabel.setFont(font) - self.helpLabel.setAlignment(QtCore.Qt.AlignCenter) - self.layout().addWidget(self.helpLabel) + self.helpLabel.setWordWrap(True) + self.deviceGroup.layout().addWidget(self.helpLabel) + + self.retranslateUi() def retranslateUi(self): self.deviceGroup.setTitle(translate("AlsaSinkSettings", "ALSA device")) @@ -71,7 +68,7 @@ def retranslateUi(self): translate( "AlsaSinkSettings", "To make your custom PCM objects appear correctly in this list " - "requires adding a 'hint.description' line to them", + "requires adding a 'hint.description' line to them.", ) ) From 28d63b91ab9688b5dfb06803fd5020e70b2884e7 Mon Sep 17 00:00:00 2001 From: s0600204 Date: Thu, 10 Sep 2020 20:32:40 +0100 Subject: [PATCH 246/333] Resolve an issue that was causing xfwm4 on Arch to coredump --- lisp/ui/widgets/pagestreewidget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/ui/widgets/pagestreewidget.py b/lisp/ui/widgets/pagestreewidget.py index cecd46174..c6adc32a4 100644 --- a/lisp/ui/widgets/pagestreewidget.py +++ b/lisp/ui/widgets/pagestreewidget.py @@ -70,8 +70,8 @@ def _changePage(self, selected): self._currentWidget.setSizePolicy( QSizePolicy.Ignored, QSizePolicy.Ignored ) - self._currentWidget.show() self.layout().addWidget(self._currentWidget, 0, 1) + self._currentWidget.show() self._resetStretch() From 9bc51d2cd58a36543c0b3c57eea21010e1f93de3 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 7 Nov 2020 14:17:46 +0100 Subject: [PATCH 247/333] Add a custom "ts" files updater, various updates Add backend method for waveform directly from the file uri Add: pure python "pylupdate" (not a full implementation) Update: Moved background drawing from WaveformSlider directly to WaveformWidget Update: i18n_update script now uses the new updater Update: update translations reference (en) files Update: update dependencies --- Pipfile | 2 +- Pipfile.lock | 266 +++--- i18n_update.py | 102 ++- lisp/backend/backend.py | 11 +- lisp/i18n/ts/en/action_cues.ts | 288 +++--- lisp/i18n/ts/en/cache_manager.ts | 52 +- lisp/i18n/ts/en/cart_layout.ts | 225 ++--- lisp/i18n/ts/en/controller.ts | 355 ++++---- lisp/i18n/ts/en/gst_backend.ts | 508 +++++------ lisp/i18n/ts/en/lisp.ts | 1071 ++++++++++++----------- lisp/i18n/ts/en/list_layout.ts | 313 +++---- lisp/i18n/ts/en/media_info.ts | 37 +- lisp/i18n/ts/en/midi.ts | 266 +++--- lisp/i18n/ts/en/network.ts | 99 ++- lisp/i18n/ts/en/osc.ts | 185 ++-- lisp/i18n/ts/en/presets.ts | 171 ++-- lisp/i18n/ts/en/rename_cues.ts | 123 +-- lisp/i18n/ts/en/replay_gain.ts | 112 +-- lisp/i18n/ts/en/synchronizer.ts | 31 +- lisp/i18n/ts/en/timecode.ts | 93 +- lisp/i18n/ts/en/triggers.ts | 75 +- lisp/plugins/gst_backend/gst_backend.py | 9 +- lisp/ui/widgets/waveform.py | 29 +- scripts/pyts_update.py | 383 ++++++++ 24 files changed, 2748 insertions(+), 2058 deletions(-) create mode 100644 scripts/pyts_update.py diff --git a/Pipfile b/Pipfile index 8a5fd9432..a13792e41 100644 --- a/Pipfile +++ b/Pipfile @@ -16,7 +16,7 @@ python-rtmidi = "~=1.1" requests = "~=2.20" sortedcontainers = "~=2.0" pyalsa = {editable = true,git = "https://github.com/alsa-project/alsa-python.git",ref = "v1.1.6"} -humanize = "~=2.2" +humanize = "~=3.1.0" [dev-packages] html5lib = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 2d28487e5..95b8bfb5c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "17c34b4e1c5c2fab86e0f252dd7aa182ac73deec7799c681f8330a23cc9ec831" + "sha256": "07843a1069149dffa1c685cfaa0a91b9041825182985e4008d7d67dbd57e6293" }, "pipfile-spec": 6, "requires": {}, @@ -24,43 +24,51 @@ }, "certifi": { "hashes": [ - "sha256:5ad7e9a056d25ffa5082862e36f119f7f7cec6457fa07ee2f8c339814b80c9b1", - "sha256:9cd41137dc19af6a5e03b630eefe7d1f458d964d406342dd3edf625839b944cc" + "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", + "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" ], - "version": "==2020.4.5.2" + "version": "==2020.6.20" }, "cffi": { "hashes": [ - "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff", - "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b", - "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac", - "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0", - "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384", - "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26", - "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6", - "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b", - "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e", - "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd", - "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2", - "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66", - "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc", - "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8", - "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55", - "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4", - "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5", - "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d", - "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78", - "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa", - "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793", - "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f", - "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a", - "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f", - "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30", - "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f", - "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3", - "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c" + "sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d", + "sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b", + "sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4", + "sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f", + "sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3", + "sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579", + "sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537", + "sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e", + "sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05", + "sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171", + "sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca", + "sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522", + "sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c", + "sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc", + "sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d", + "sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808", + "sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828", + "sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869", + "sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d", + "sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9", + "sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0", + "sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc", + "sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15", + "sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c", + "sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a", + "sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3", + "sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1", + "sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768", + "sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d", + "sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b", + "sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e", + "sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d", + "sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730", + "sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394", + "sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1", + "sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591" ], - "version": "==1.14.0" + "version": "==1.14.3" }, "chardet": { "hashes": [ @@ -71,32 +79,44 @@ }, "cython": { "hashes": [ - "sha256:0bb201124f67b8d5e6a3e7c02257ca56a90204611971ecca76c02897096f097d", - "sha256:0f3488bf2a9e049d1907d35ad8834f542f8c03d858d1bca6d0cbc06b719163e0", - "sha256:10b6d2e2125169158128b7f11dad8bb0d8f5fba031d5d4f8492f3afbd06491d7", - "sha256:16ed0260d031d90dda43997e9b0f0eebc3cf18e6ece91cad7b0fb17cd4bfb29b", - "sha256:22d91af5fc2253f717a1b80b8bb45acb655f643611983fd6f782b9423f8171c7", - "sha256:2d84e8d2a0c698c1bce7c2a4677f9f03b076e9f0af7095947ecd2a900ffceea5", - "sha256:384582b5024007dfdabc9753e3e0f85d61837b0103b0ee3f8acf04a4bcfad175", - "sha256:4473f169d6dd02174eb76396cb38ce469f377c08b21965ddf4f88bbbebd5816e", - "sha256:57f32d1095ad7fad1e7f2ff6e8c6a7197fa532c8e6f4d044ff69212e0bf05461", - "sha256:60def282839ed81a2ffae29d2df0a6777fd74478c6e82c6c3f4b54e698b9d11c", - "sha256:714b8926a84e3e39c5278e43fb8823598db82a4b015cff263b786dc609a5e7d6", - "sha256:7352b88f2213325c1e111561496a7d53b0326e7f07e6f81f9b8b21420e40851c", - "sha256:8598b09f7973ccb15c03b21d3185dc129ae7c60d0a6caf8176b7099a4b83483e", - "sha256:8dc68f93b257718ea0e2bc9be8e3c61d70b6e49ab82391125ba0112a30a21025", - "sha256:a49d0f5c55ad0f4aacad32f058a71d0701cb8936d6883803e50698fa04cac8d2", - "sha256:a985a7e3c7f1663af398938029659a4381cfe9d1bd982cf19c46b01453e81775", - "sha256:b3233341c3fe352b1090168bd087686880b582b635d707b2c8f5d4f1cc1fa533", - "sha256:b56c02f14f1708411d95679962b742a1235d33a23535ce4a7f75425447701245", - "sha256:b7bb0d54ff453c7516d323c3c78b211719f39a506652b79b7e85ba447d5fa9e7", - "sha256:c5df2c42d4066cda175cd4d075225501e1842cfdbdaeeb388eb7685c367cc3ce", - "sha256:c5e29333c9e20df384645902bed7a67a287b979da1886c8f10f88e57b69e0f4b", - "sha256:d0b445def03b4cd33bd2d1ae6fbbe252b6d1ef7077b3b5ba3f2c698a190d26e5", - "sha256:d490a54814b69d814b157ac86ada98c15fd77fabafc23732818ed9b9f1f0af80" + "sha256:0ac10bf476476a9f7ef61ec6e44c280ef434473124ad31d3132b720f7b0e8d2a", + "sha256:0e25c209c75df8785480dcef85db3d36c165dbc0f4c503168e8763eb735704f2", + "sha256:171b9f70ceafcec5852089d0f9c1e75b0d554f46c882cd4e2e4acaba9bd7d148", + "sha256:23f3a00b843a19de8bb4468b087db5b413a903213f67188729782488d67040e0", + "sha256:2922e3031ba9ebbe7cb9200b585cc33b71d66023d78450dcb883f824f4969371", + "sha256:31c71a615f38401b0dc1f2a5a9a6c421ffd8908c4cd5bbedc4014c1b876488e8", + "sha256:473df5d5e400444a36ed81c6596f56a5b52a3481312d0a48d68b777790f730ae", + "sha256:497841897942f734b0abc2dead2d4009795ee992267a70a23485fd0e937edc0b", + "sha256:539e59949aab4955c143a468810123bf22d3e8556421e1ce2531ed4893914ca0", + "sha256:540b3bee0711aac2e99bda4fa0a46dbcd8c74941666bfc1ef9236b1a64eeffd9", + "sha256:57ead89128dee9609119c93d3926c7a2add451453063147900408a50144598c6", + "sha256:5c4276fdcbccdf1e3c1756c7aeb8395e9a36874fa4d30860e7694f43d325ae13", + "sha256:5da187bebe38030325e1c0b5b8a804d489410be2d384c0ef3ba39493c67eb51e", + "sha256:5e545a48f919e40079b0efe7b0e081c74b96f9ef25b9c1ff4cdbd95764426b58", + "sha256:603b9f1b8e93e8b494d3e89320c410679e21018e48b6cbc77280f5db71f17dc0", + "sha256:695a6bcaf9e12b1e471dfce96bbecf22a1487adc2ac6106b15960a2b51b97f5d", + "sha256:715294cd2246b39a8edca464a8366eb635f17213e4a6b9e74e52d8b877a8cb63", + "sha256:7ebaa8800c376bcdae596fb1372cb4232a5ef957619d35839520d2786f2debb9", + "sha256:856c7fb31d247ce713d60116375e1f8153d0291ab5e92cca7d8833a524ba9991", + "sha256:8c6e25e9cc4961bb2abb1777c6fa9d0fa2d9b014beb3276cebe69996ff162b78", + "sha256:9207fdedc7e789a3dcaca628176b80c82fbed9ae0997210738cbb12536a56699", + "sha256:93f5fed1c9445fb7afe20450cdaf94b0e0356d47cc75008105be89c6a2e417b1", + "sha256:9ce5e5209f8406ffc2b058b1293cce7a954911bb7991e623564d489197c9ba30", + "sha256:a0674f246ad5e1571ef29d4c5ec1d6ecabe9e6c424ad0d6fee46b914d5d24d69", + "sha256:b2f9172e4d6358f33ecce6a4339b5960f9f83eab67ea244baa812737793826b7", + "sha256:b8a8a31b9e8860634adbca30fea1d0c7f08e208b3d7611f3e580e5f20992e5d7", + "sha256:b8d8497091c1dc8705d1575c71e908a93b1f127a174b2d472020f3d84263ac28", + "sha256:c111ac9abdf715762e4fb87395e59d61c0fbb6ce79eb2e24167700b6cfa8ba79", + "sha256:c4b78356074fcaac04ecb4de289f11d506e438859877670992ece11f9c90f37b", + "sha256:c541b2b49c6638f2b5beb9316726db84a8d1c132bf31b942dae1f9c7f6ad3b92", + "sha256:c8435959321cf8aec867bbad54b83b7fb8343204b530d85d9ea7a1f5329d5ac2", + "sha256:ccb77faeaad99e99c6c444d04862c6cf604204fe0a07d4c8f9cbf2c9012d7d5a", + "sha256:e272ed97d20b026f4f25a012b25d7d7672a60e4f72b9ca385239d693cd91b2d5", + "sha256:e57acb89bd55943c8d8bf813763d20b9099cc7165c0f16b707631a7654be9cad", + "sha256:e93acd1f603a0c1786e0841f066ae7cef014cf4750e3cd06fd03cfdf46361419" ], "index": "pypi", - "version": "==0.29.20" + "version": "==0.29.21" }, "falcon": { "hashes": [ @@ -120,27 +140,27 @@ }, "humanize": { "hashes": [ - "sha256:07dd1293bac6c77daa5ccdc22c0b41b2315bee0e339a9f035ba86a9f1a272002", - "sha256:42ae7d54b398c01bd100847f6cb0fc9e381c21be8ad3f8e2929135e48dbff026" + "sha256:6790d9ba139ce09761ae901be9b22bd32a131fa65ecc82cdfc4d86f377f7395d", + "sha256:fd3eb915310335c63a54d4507289ecc7b3a7454cd2c22ac5086d061a3cbfd592" ], "index": "pypi", - "version": "==2.4.0" + "version": "==3.1.0" }, "idna": { "hashes": [ - "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", - "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" + "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", + "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.9" + "version": "==2.10" }, "jack-client": { "hashes": [ - "sha256:2522debfb112195971837d78f46105c78cf20d4d02743084ee9b71371b50ecfe", - "sha256:34d7b10610c2f748e9cc922ed5c5f4694154c832050a165b32fe85dced751d36" + "sha256:0214d9366e0a65bf0a99866990aae5a443afb3c283006d236d8cebcb769a59b8", + "sha256:31cbdcc90cd303997a3ea540f352d89b23c2f422cbf7c197292a1b8e1ca21aca" ], "index": "pypi", - "version": "==0.5.2" + "version": "==0.5.3" }, "mido": { "hashes": [ @@ -157,10 +177,18 @@ }, "pycairo": { "hashes": [ - "sha256:2c143183280feb67f5beb4e543fd49990c28e7df427301ede04fc550d3562e84" + "sha256:2088100a099c09c5e90bf247409ce6c98f51766b53bd13f96d6aac7addaa3e66", + "sha256:273a33c56aba724ec42fe1d8f94c86c2e2660c1277470be9b04e5113d7c5b72d", + "sha256:5695a10cb7f9ae0d01f665b56602a845b0a8cb17e2123bfece10c2e58552468c", + "sha256:57166119e424d71eccdba6b318bd731bdabd17188e2ba10d4f315f7bf16ace3f", + "sha256:57a768f4edc8a9890d98070dd473a812ac3d046cef4bc1c817d68024dab9a9b4", + "sha256:8cfa9578b745fb9cf2915ec580c2c50ebc2da00eac2cf4c4b54b63aa19da4b77", + "sha256:a942614923b88ae75c794506d5c426fba9c46a055d3fdd3b8db7046b75c079cc", + "sha256:ceb1edcbeb48dabd5fbbdff2e4b429aa88ddc493d6ebafe78d94b050ac0749e2", + "sha256:e5a3433690c473e073a9917dc8f1fc7dc8b9af7b201bf372894b8ad70d960c6d" ], - "markers": "python_version >= '3.5' and python_version < '4'", - "version": "==1.19.1" + "markers": "python_version >= '3.6' and python_version < '4'", + "version": "==1.20.0" }, "pycparser": { "hashes": [ @@ -172,10 +200,10 @@ }, "pygobject": { "hashes": [ - "sha256:012a589aec687bfa809a1ff9f5cd775dc7f6fcec1a6bc7fe88e1002a68f8ba34" + "sha256:051b950f509f2e9f125add96c1493bde987c527f7a0c15a1f7b69d6d1c3cd8e6" ], "index": "pypi", - "version": "==3.36.1" + "version": "==3.38.0" }, "pyliblo": { "hashes": [ @@ -186,63 +214,67 @@ }, "pyqt5": { "hashes": [ - "sha256:14be35c0c1bcc804791a096d2ef9950f12c6fd34dd11dbe61b8c769fefcdf98c", - "sha256:3605d34ba6291b9194c46035e228d6d01f39d120cf5ecc70301c11e7900fed21", - "sha256:5bac0fab1e9891d73400c2470a9cb810e6bdbc7027a84ae4d3ec83436f1109ec", - "sha256:c6f75488ffd5365a65893bc64ea82a6957db126fbfe33654bcd43ae1c30c52f9", - "sha256:e05c86b8c4f02d62a5b355d426fd8d063781dd44c6a3f916640a5beb40efe60a" + "sha256:17a6d5258796bae16e447aa3efa00258425c09cf88ef68238762628a5dde7c6f", + "sha256:4e47021c2b8e89a3bc64247dfb224144e5c8d77e3ab44f3842d120aab6b3cbd4", + "sha256:b1ea7e82004dc7b311d1e29df2f276461016e2d180e10c73805ace4376125ed9", + "sha256:b9e7cc3ec69f80834f3f7507478c77e4d42411d5e9e557350e61b2660d12abc2", + "sha256:d9a76b850246d08da9863189ecb98f6c2aa9b4d97a3e85e29330a264aed0f9a1" ], "index": "pypi", - "version": "==5.15.0" + "version": "==5.15.1" }, "pyqt5-sip": { "hashes": [ - "sha256:0a34b6596bdd28d52da3a51fa8d9bb0b287bcb605c2512aa3251b9028cc71f4d", - "sha256:1d65ce08a56282fb0273dd06585b8927b88d4fba71c01a54f8e2ac87ac1ed387", - "sha256:224e2fbb7088595940c348d168a317caa2110cbb7a5b957a8c3fc0d9296ee069", - "sha256:2a1153cda63f2632d3d5698f0cf29f6b1f1d5162305dc6f5b23336ad8f1039ed", - "sha256:2a2239d16a49ce6eaf10166a84424543111f8ebe49d3c124d02af91b01a58425", - "sha256:58eae636e0b1926cddec98a703319a47f671cef07d73aaa525ba421cd4adfeb5", - "sha256:5c19c4ad67af087e8f4411da7422391b236b941f5f0697f615c5816455d1355d", - "sha256:61aa60fb848d740581646603a12c2dcb8d7c4cbd2a9c476a1c891ec360ff0b87", - "sha256:8d9f4dc7dbae9783c5dafd66801875a2ebf9302c3addd5739f772285c1c1e91c", - "sha256:94c80677b1e8c92fa080e24045d54ace5e4343c4ee6d0216675cd91d6f8e122a", - "sha256:9b69db29571dde679908fb237784a8e7af4a2cbf1b7bb25bdb86e487210e04d2", - "sha256:9ef12754021bcc1246f97e00ea62b5594dd5c61192830639ab4a1640bd4b7940", - "sha256:b1bbe763d431d26f9565cba3e99866768761366ab6d609d2506d194882156fa7", - "sha256:d7b8a8f89385ad9e3da38e0123c22c0efc18005e0e2731b6b95e4c21db2049d2", - "sha256:e6254647fa35e1260282aeb9c32a3dd363287b9a1ffcc4f22bd27e54178e92e4", - "sha256:f4c294bfaf2be8004583266d4621bfd3a387e12946f548f966a7fbec91845f1b", - "sha256:fa3d70f370604efc67085849d3d1d3d2109faa716c520faf601d15845df64de6" + "sha256:0304ca9114b9817a270f67f421355075b78ff9fc25ac58ffd72c2601109d2194", + "sha256:0cd969be528c27bbd4755bd323dff4a79a8fdda28215364e6ce3e069cb56c2a9", + "sha256:2f35e82fd7ec1e1f6716e9154721c7594956a4f5bd4f826d8c6a6453833cc2f0", + "sha256:30e944db9abee9cc757aea16906d4198129558533eb7fadbe48c5da2bd18e0bd", + "sha256:34dcd29be47553d5f016ff86e89e24cbc5eebae92eb2f96fb32d2d7ba028c43c", + "sha256:5a011aeff89660622a6d5c3388d55a9d76932f3b82c95e82fc31abd8b1d2990d", + "sha256:6c1ebee60f1d2b3c70aff866b7933d8d8d7646011f7c32f9321ee88c290aa4f9", + "sha256:7b81382ce188d63890a0e35abe0f9bb946cabc873a31873b73583b0fc84ac115", + "sha256:832fd60a264de4134c2824d393320838f3ab648180c9c357ec58a74524d24507", + "sha256:84ba7746762bd223bed22428e8561aa267a229c28344c2d28c5d5d3f8970cffb", + "sha256:9312ec47cac4e33c11503bc1cbeeb0bdae619620472f38e2078c5a51020a930f", + "sha256:a1b8ef013086e224b8e86c93f880f776d01b59195bdfa2a8e0b23f0480678fec", + "sha256:a29e2ac399429d3b7738f73e9081e50783e61ac5d29344e0802d0dcd6056c5a2", + "sha256:b6d42250baec52a5f77de64e2951d001c5501c3a2df2179f625b241cbaec3369", + "sha256:bb5a87b66fc1445915104ee97f7a20a69decb42f52803e3b0795fa17ff88226c", + "sha256:c317ab1263e6417c498b81f5c970a9b1af7acefab1f80b4cc0f2f8e661f29fc5", + "sha256:c9800729badcb247765e4ffe2241549d02da1fa435b9db224845bc37c3e99cb0", + "sha256:c9d6d448c29dc6606bb7974696608f81f4316c8234f7c7216396ed110075e777", + "sha256:da9c9f1e65b9d09e73bd75befc82961b6b61b5a3b9d0a7c832168e1415f163c6", + "sha256:ed897c58acf4a3cdca61469daa31fe6e44c33c6c06a37c3f21fab31780b3b86a", + "sha256:f168f0a7f32b81bfeffdf003c36f25d81c97dee5eb67072a5183e761fe250f13" ], "markers": "python_version >= '3.5'", - "version": "==12.8.0" + "version": "==12.8.1" }, "python-rtmidi": { "hashes": [ - "sha256:07bfecdbd6f1abe11f57f4448cf1da29dc199daee16ee7e5a8db092a2d1c1e2c", - "sha256:16f6396b8000011bc812ca58f15d87689027c06f30e10a7d53015d0c689e94d4", - "sha256:25d9ca48062fdce6a9cc23584af0330eadb348c5e3234413ff223fd23b12f110", - "sha256:31a6e852486937d2eb474b1a624ecb3961279603472641832573b320adf98bf7", - "sha256:5cd46bfbe81913c48ec353130ac355fd83653a7e6c52a288e6c37627e480d42f", - "sha256:92846ee22529407ffdc68c613bcc45310dedfb307ee325150ba9748634704e0c", - "sha256:a171803d47772a9dc2410a09d84eb39500c759f31c98580b0eb4a3e0e198198a", - "sha256:b8acac2c25a2b5c52f47f5abb7fe30ed169ba34e4186026fbed29768d07ef253", - "sha256:c9df96f9761990a6c19d957438b8a06d8714c46d0232cbb96b697581b9401b57", - "sha256:f9b17b293ef98dad3f0e8fc8cc63796b41a21513bbe532ac4f1188bb4b9998b9", - "sha256:fdd5d257ce720f47e9d06b15120b1dea8772042c216771194203b5f5278db58a", - "sha256:ffa11547cac8b49e4c55a1222b5c49cc62304f8cda816d9638395e18541e870e" + "sha256:107aa9d57c3b7190e65710e436a1ac2db34f22b2e82f459397ebb14dc6d3f63b", + "sha256:1f7a8c6f19a56e56c4314cd562b5595ad4be645617655887e319c0c113d510ab", + "sha256:2151a856964b5a99f823d336102b202b0100cab381aee6582eb7b166cd9672da", + "sha256:565da90d8e2fc00a7e6c608ea86427c4a2ce69db44adda4be71df95fffe3d262", + "sha256:5821f8726c62b3da00f70c82443eb00ee09f4d905c09954c65869e372d700373", + "sha256:5e2eb72986b882605f476cddb4202e95e0aaa7ea33c49b99bc2c8243db864e7f", + "sha256:615eb4426a5df90275616aab8d6382185a7c6be2a09ce44c7f058c4493bdb635", + "sha256:87cfac386769a4ad415432d94277ca5526f082050d8bc364bd3038e811ea3511", + "sha256:9141a328e82ebf59519133f24a630283768ffaf69b5e2ade298d1e6b01f4ab69", + "sha256:919f62b4e4f762064b8e6070f13b76c5490a44405f13f75dd24901658ac370a8", + "sha256:c2e8c3a077928ab5996dc6a0ad7d3723235b76458177c2d7297cb9706bedc779", + "sha256:f992685ec4739a51a080069f35355b1769e47b7ed2351008014a6085a08f95f5" ], "index": "pypi", - "version": "==1.4.1" + "version": "==1.4.6" }, "requests": { "hashes": [ - "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", - "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" + "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", + "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" ], "index": "pypi", - "version": "==2.23.0" + "version": "==2.24.0" }, "sortedcontainers": { "hashes": [ @@ -254,21 +286,21 @@ }, "urllib3": { "hashes": [ - "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", - "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" + "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2", + "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.25.9" + "version": "==1.25.11" } }, "develop": { "html5lib": { "hashes": [ - "sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", - "sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736" + "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", + "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f" ], "index": "pypi", - "version": "==1.0.1" + "version": "==1.1" }, "six": { "hashes": [ diff --git a/i18n_update.py b/i18n_update.py index af001d4a5..edb1a06aa 100755 --- a/i18n_update.py +++ b/i18n_update.py @@ -25,6 +25,8 @@ from pathlib import Path from typing import Iterable +from scripts.pyts_update import TSTranslations, PyTrFinder, Obsolete + TS_DIR = Path("lisp/i18n/ts") QM_DIR = Path("lisp/i18n/qm") PYLUPDATE = "pylupdate5" @@ -53,7 +55,9 @@ def source_files(root: Path, extensions: Iterable[str], exclude: str = ""): def modules_sources( - modules_path: Iterable[Path], extensions: Iterable[str], exclude: str = "", + modules_path: Iterable[Path], + extensions: Iterable[str], + exclude: str = "", ): for module_path in modules_path: if module_path.stem != "__pycache__": @@ -62,7 +66,7 @@ def modules_sources( ), -def lupdate( +def pylupdate( modules_path: Iterable[Path], locales: Iterable[str], options: Iterable[str] = (), @@ -87,6 +91,35 @@ def lupdate( ) +def pyts_update( + modules_path: Iterable[Path], + locales: Iterable[str], + obsolete: Obsolete.Keep, + extensions: Iterable[str] = (".py",), + exclude: str = "", +): + ts_files = modules_sources(modules_path, extensions, exclude) + for locale in locales: + locale_path = TS_DIR.joinpath(locale) + locale_path.mkdir(exist_ok=True) + for module_name, sources in ts_files: + destination = locale_path.joinpath(module_name + ".ts") + + # Find the translations in python files + finder = PyTrFinder(destination.as_posix(), src_language=locale) + finder.find_in_files(sources) + + # Merge existing translations + if destination.exists(): + existing_translations = TSTranslations.from_file(destination) + finder.translations.update( + existing_translations, obsolete=obsolete + ) + + # Write the "ts" file + finder.translations.write(destination) + + def lrelease(locales: Iterable[str], options: Iterable[str] = ()): for locale in locales: qm_file = QM_DIR.joinpath("base_{}.qm".format(locale)) @@ -122,12 +155,13 @@ def lrelease(locales: Iterable[str], options: Iterable[str] = ()): parser.add_argument( "-t", "--ts", help="Update .ts files", action="store_true" ) + parser.add_argument( + "--pylupdate", + help="Use pylupdate instead of the custom updater", + action="store_true", + ) args = parser.parse_args() - # Decide what to do - do_release = args.qm - do_update = args.ts or not do_release - # Decide locales to use if args.all: locales = list(existing_locales()) @@ -135,27 +169,47 @@ def lrelease(locales: Iterable[str], options: Iterable[str] = ()): else: locales = args.locales or ("en",) - if do_update: + # Update ts files + if args.ts or not args.qm: print(">>> UPDATING ...") - options = [] - if args.noobsolete: - options.append("-noobsolete") - - lupdate( - [Path("lisp")], - locales, - options=options, - exclude="^lisp/plugins/|__pycache__", - ) - lupdate( - dirs_path("lisp/plugins"), - locales, - options=options, - exclude="__pycache__", - ) + if args.pylupdate: + # Use the pylupdate command + options = [] + if args.noobsolete: + options.append("-noobsolete") + + pylupdate( + [Path("lisp")], + locales, + options=options, + exclude="^lisp/plugins/|__pycache__", + ) + pylupdate( + dirs_path("lisp/plugins"), + locales, + options=options, + exclude="__pycache__", + ) + else: + # Use our custom updater + obsolete = Obsolete.Discard if args.noobsolete else Obsolete.Keep + + pyts_update( + [Path("lisp")], + locales, + obsolete=obsolete, + exclude="^lisp/plugins/|__pycache__", + ) + pyts_update( + dirs_path("lisp/plugins"), + locales, + obsolete=obsolete, + exclude="__pycache__", + ) - if do_release: + # Build the qm files + if args.qm: print(">>> RELEASING ...") lrelease(locales) diff --git a/lisp/backend/backend.py b/lisp/backend/backend.py index f4c418c4d..bcfbad935 100644 --- a/lisp/backend/backend.py +++ b/lisp/backend/backend.py @@ -17,6 +17,7 @@ from abc import abstractmethod, ABCMeta +from lisp.core.session_uri import SessionURI from .media import Media from .waveform import Waveform @@ -28,14 +29,14 @@ class Backend(metaclass=ABCMeta): """ @abstractmethod - def uri_duration(self, uri: str) -> int: + def uri_duration(self, uri: SessionURI) -> int: """Return the file duration in milliseconds. :param uri: The URI of the file """ @abstractmethod - def uri_tags(self, uri: str) -> dict: + def uri_tags(self, uri: SessionURI) -> dict: """Return a dictionary containing the file metadata/tags. :param uri: The URI of the file @@ -50,6 +51,10 @@ def supported_extensions(self) -> dict: e.g. {'audio': ['wav', 'mp3', ...], 'video': ['mp4', 'mov', ...]} """ - @abstractmethod def media_waveform(self, media: Media) -> Waveform: """Return a Waveform object capable of loading the waveform of the given media.""" + return self.uri_waveform(media.input_uri(), media.duration) + + @abstractmethod + def uri_waveform(self, uri: SessionURI, duration=None) -> Waveform: + """Return a Waveform object capable of loading the waveform of the given uri.""" diff --git a/lisp/i18n/ts/en/action_cues.ts b/lisp/i18n/ts/en/action_cues.ts index 687fd32f9..cd5324ac0 100644 --- a/lisp/i18n/ts/en/action_cues.ts +++ b/lisp/i18n/ts/en/action_cues.ts @@ -1,233 +1,239 @@ - - + + + CollectionCue - - Add - Add + + Add + Add - - Remove - Remove + + Remove + Remove - - Cue - Cue + + Cue + Cue - - Action - Action + + Action + Action - - + + CommandCue - - Command - Command + + Command + Command - - Command to execute, as in a shell - Command to execute, as in a shell + + Command to execute, as in a shell + Command to execute, as in a shell - - Discard command output - Discard command output + + Discard command output + Discard command output - - Ignore command errors - Ignore command errors + + Ignore command errors + Ignore command errors - - Kill instead of terminate - Kill instead of terminate + + Kill instead of terminate + Kill instead of terminate - - + + + Command cue ended with an error status. Exit code: {} + + + + CueCategory - - Action cues - + + Action cues + - - + + CueName - - Command Cue - Command Cue + + Seek Cue + Seek Cue - - Volume Control - Volume Control + + Collection Cue + Collection Cue - - Seek Cue - Seek Cue + + Stop-All + Stop-All - - Collection Cue - Collection Cue + + Command Cue + Command Cue - - Stop-All - Stop-All + + Volume Control + Volume Control - - Index Action - Index Action + + Index Action + Index Action - - + + IndexActionCue - - Index - Index + + No suggestion + - - Use a relative index - Use a relative index + + Index + Index - - Target index - Target index + + Use a relative index + Use a relative index - - Action - Action + + Target index + Target index - - No suggestion - + + Action + Action - - Suggested cue name - + + Suggested cue name + - - + + SeekCue - - Cue - Cue + + Cue + Cue - - Click to select - Click to select + + Click to select + Click to select - - Not selected - Not selected + + Not selected + Not selected - - Seek - Seek + + Seek + Seek - - Time to reach - Time to reach + + Time to reach + Time to reach - - + + SettingsPageName - - Command - Command + + Seek Settings + Seek Settings - - Volume Settings - Volume Settings + + Edit Collection + Edit Collection - - Seek Settings - Seek Settings + + Stop Settings + Stop Settings - - Edit Collection - Edit Collection + + Command + Command - - Action Settings - Action Settings + + Volume Settings + Volume Settings - - Stop Settings - Stop Settings + + Action Settings + Action Settings - - + + StopAll - - Stop Action - Stop Action + + Stop Action + Stop Action - - + + VolumeControl - - Cue - Cue + + Cue + Cue - - Click to select - Click to select + + Click to select + Click to select - - Not selected - Not selected + + Not selected + Not selected - - Volume to reach - Volume to reach + + Volume to reach + Volume to reach - - Fade - Fade + + Fade + Fade - - + + VolumeControlError - - Error during cue execution. - + + Error during cue execution. + - + diff --git a/lisp/i18n/ts/en/cache_manager.ts b/lisp/i18n/ts/en/cache_manager.ts index 17be057ad..24c6247d0 100644 --- a/lisp/i18n/ts/en/cache_manager.ts +++ b/lisp/i18n/ts/en/cache_manager.ts @@ -1,34 +1,46 @@ - - + + + CacheManager - - Cache size warning - + + Cache size + - - Warning threshold in MB (0 = disabled) - + + Cache size warning + - - Cache cleanup - + + Warning threshold in MB (0 = disabled) + - - Delete the cache content - + + Cache cleanup + - - + + + Delete the cache content + + + + + The cache has exceeded {}. Consider clean it. +You can do it in the application settings. + + + + SettingsPageName - - Cache Manager - + + Cache Manager + - + diff --git a/lisp/i18n/ts/en/cart_layout.ts b/lisp/i18n/ts/en/cart_layout.ts index 706f608db..2d9291a2d 100644 --- a/lisp/i18n/ts/en/cart_layout.ts +++ b/lisp/i18n/ts/en/cart_layout.ts @@ -1,186 +1,187 @@ - - + + + CartLayout - - Countdown mode - + + Reset volume + - - Show seek-bars - + + Add page + - - Show dB-meters - + + Add pages + - - Show accurate time - + + Remove current page + - - Show volume - + + Countdown mode + - - Grid size - + + Show seek-bars + - - Play - + + Show dB-meters + - - Pause - + + Show volume + - - Stop - + + Show accurate time + - - Reset volume - + + Number of Pages: + - - Add page - + + Page {number} + - - Add pages - + + Warning + - - Remove current page - + + Every cue in the page will be lost. + - - Number of Pages: - + + Are you sure to continue? + - - Page {number} - + + Play + - - Warning - + + Pause + - - Every cue in the page will be lost. - + + Stop + - - Are you sure to continue? - + + Default behaviors (applied to new sessions) + - - Number of columns: - + + Grid size + - - Number of rows: - + + Number of columns: + - - Default behaviors (applied to new sessions) - + + Number of rows: + - - + + LayoutDescription - - Organize cues in grid like pages - + + Organize cues in grid like pages + - - + + LayoutDetails - - Click a cue to run it - + + Click a cue to run it + - - SHIFT + Click to edit a cue - + + SHIFT + Click to edit a cue + - - CTRL + Click to select a cue - + + CTRL + Click to select a cue + - - To copy cues drag them while pressing CTRL - + + To copy cues drag them while pressing CTRL + - - To move cues drag them while pressing SHIFT - + + To move cues drag them while pressing SHIFT + - - + + LayoutName - - Cart Layout - + + Cart Layout + - - + + ListLayout - - Edit cue - + + Edit cue + - - Edit selected cues - + + Edit selected cues + - - Remove cue - + + Remove cue + - - Remove selected cues - + + Remove selected cues + - - + + SettingsPageName - - Cart Layout - + + Cart Layout + - + diff --git a/lisp/i18n/ts/en/controller.ts b/lisp/i18n/ts/en/controller.ts index b699533de..81b23d019 100644 --- a/lisp/i18n/ts/en/controller.ts +++ b/lisp/i18n/ts/en/controller.ts @@ -1,273 +1,298 @@ - - + + + Controller - - Cannot load controller protocol: "{}" - + + Cannot load controller protocol: "{}" + - - + + ControllerKeySettings - - Action - Action + + Shortcuts + Shortcuts - - Shortcuts - Shortcuts + + Shortcut + - - Shortcut - + + Action + Action - - + + ControllerMidiSettings - - MIDI - MIDI + + MIDI + MIDI - - Type - Type + + -- All Messages -- + - - Action - Action + + Capture + Capture - - Capture - Capture + + Capture filter + - - Listening MIDI messages ... - Listening MIDI messages ... + + Listening MIDI messages ... + Listening MIDI messages ... - - -- All Messages -- - + + Type + Type - - Capture filter - + + Data 1 + - - Data 1 - + + Data 2 + - - Data 2 - + + Data 3 + - - Data 3 - + + Action + Action - - + + + ControllerMidiSettingsWarning + + + Error while importing configuration entry, skipped. + + + + ControllerOscSettings - - OSC Message - + + OSC Message + + + + + OSC Path: + - - OSC - + + /path/to/method + - - Path - + + OSC + - - Types - + + OSC Capture + - - Arguments - + + Add + Add - - OSC Capture - + + Remove + Remove - - Add - Add + + Capture + Capture - - Remove - Remove + + Waiting for messages: + - - Capture - Capture + + Path + - - Waiting for messages: - + + Types + - - /path/to/method - + + Arguments + - - Action - Action + + Action + Action - - + + ControllerOscSettingsWarning - - Warning - + + Warning + + + + + Osc path seems invalid, +do not forget to edit the path later. + + + + + Error while importing configuration entry, skipped. + - - + + ControllerSettings - - Add - Add + + Add + Add - - Remove - Remove + + Remove + Remove - - + + GlobalAction - - Go - + + Go + - - Reset - + + Reset + - - Stop all cues - + + Stop all cues + - - Pause all cues - + + Pause all cues + - - Resume all cues - + + Resume all cues + - - Interrupt all cues - + + Interrupt all cues + - - Fade-out all cues - + + Fade-out all cues + - - Fade-in all cues - + + Fade-in all cues + - - Move standby forward - + + Move standby forward + - - Move standby back - + + Move standby back + - - + + Osc Cue - - Type - Type + + Type + Type - - Argument - + + Argument + - - + + OscCue - - Add - Add + + Add + Add - - Remove - Remove + + Remove + Remove - - + + SettingsPageName - - Cue Control - Cue Control + + Cue Control + Cue Control - - MIDI Controls - MIDI Controls + + Layout Controls + - - Keyboard Shortcuts - Keyboard Shortcuts + + Keyboard Shortcuts + Keyboard Shortcuts - - OSC Controls - + + MIDI Controls + MIDI Controls - - Layout Controls - + + OSC Controls + - + diff --git a/lisp/i18n/ts/en/gst_backend.ts b/lisp/i18n/ts/en/gst_backend.ts index 37abb479d..3757e70c4 100644 --- a/lisp/i18n/ts/en/gst_backend.ts +++ b/lisp/i18n/ts/en/gst_backend.ts @@ -1,406 +1,420 @@ - - + + + AlsaSinkSettings - - ALSA device - ALSA device + + ALSA device + ALSA device - - + + + To make your custom PCM objects appear correctly in this list requires adding a 'hint.description' line to them. + + + + AudioDynamicSettings - - Compressor - Compressor + + Compressor + Compressor - - Expander - Expander + + Expander + Expander - - Soft Knee - Soft Knee + + Soft Knee + Soft Knee - - Hard Knee - Hard Knee + + Hard Knee + Hard Knee - - Compressor/Expander - Compressor/Expander + + Compressor/Expander + Compressor/Expander - - Type - Type + + Type + Type - - Curve Shape - Curve Shape + + Curve Shape + Curve Shape - - Ratio - Ratio + + Ratio + Ratio - - Threshold (dB) - Threshold (dB) + + Threshold (dB) + Threshold (dB) - - + + AudioPanSettings - - Audio Pan - Audio Pan + + Audio Pan + Audio Pan - - Center - Center + + Center + Center - - Left - Left + + Left + Left - - Right - Right + + Right + Right - - + + CueCategory - - Media cues - + + Media cues + - - + + DbMeterSettings - - DbMeter settings - DbMeter settings + + DbMeter settings + DbMeter settings - - Time between levels (ms) - Time between levels (ms) + + Time between levels (ms) + Time between levels (ms) - - Peak ttl (ms) - Peak ttl (ms) + + Peak ttl (ms) + Peak ttl (ms) - - Peak falloff (dB/sec) - Peak falloff (dB/sec) + + Peak falloff (dB/sec) + Peak falloff (dB/sec) - - + + Equalizer10Settings - - 10 Bands Equalizer (IIR) - 10 Bands Equalizer (IIR) + + 10 Bands Equalizer (IIR) + 10 Bands Equalizer (IIR) - - + + GstBackend - - Audio cue (from file) - + + Audio cue (from file) + - - Select media files - + + Select media files + - - + + GstMediaError - - Cannot create pipeline element: "{}" - + + Cannot create pipeline element: "{}" + - - + + GstMediaSettings - - Change Pipeline - Change Pipeline + + Change Pipeline + Change Pipeline - - + + GstMediaWarning - - Invalid pipeline element: "{}" - + + Invalid pipeline element: "{}" + - - + + GstPipelineEdit - - Edit Pipeline - Edit Pipeline + + Edit Pipeline + Edit Pipeline - - + + GstSettings - - Default pipeline - + + Default pipeline + + + + + Applied only to new cues. + + + + JackSinkError - - Applied only to new cues. - + + An error occurred while disconnection Jack ports + - - + + JackSinkSettings - - Connections - Connections + + Connections + Connections - - Edit connections - Edit connections + + Edit connections + Edit connections - - Output ports - Output ports + + Connect + Connect - - Input ports - Input ports + + Output ports + Output ports - - Connect - Connect + + Input ports + Input ports - - Disconnect - Disconnect + + Disconnect + Disconnect - - + + MediaElementName - - Compressor/Expander - Compressor/Expander + + Speed + Speed - - Audio Pan - Audio Pan + + dB Meter + dB Meter - - PulseAudio Out - PulseAudio Out + + Audio Pan + Audio Pan - - Volume - Volume + + Volume + Volume - - dB Meter - dB Meter + + URI Input + URI Input - - System Input - System Input + + JACK Out + JACK Out - - ALSA Out - ALSA Out + + PulseAudio Out + PulseAudio Out - - JACK Out - JACK Out + + System Out + System Out - - Custom Element - Custom Element + + Compressor/Expander + Compressor/Expander - - System Out - System Out + + Custom Element + Custom Element - - Pitch - Pitch + + Preset Input + Preset Input - - URI Input - URI Input + + Pitch + Pitch - - 10 Bands Equalizer - 10 Bands Equalizer + + 10 Bands Equalizer + 10 Bands Equalizer - - Speed - Speed + + System Input + System Input - - Preset Input - Preset Input + + ALSA Out + ALSA Out - - + + PitchSettings - - Pitch - Pitch + + Pitch + Pitch - - {0:+} semitones - {0:+} semitones + + {0:+} semitones + {0:+} semitones - - + + PresetSrcSettings - - Presets - Presets + + Presets + Presets - - + + SettingsPageName - - Media Settings - Media Settings + + Media Settings + Media Settings - - GStreamer - + + GStreamer + - - + + SpeedSettings - - Speed - Speed + + Speed + Speed - - + + UriInputSettings - - Source - Source + + Source + Source - - Find File - Find File + + Find File + Find File - - Buffering - Buffering + + Buffering + Buffering - - Use Buffering - Use Buffering + + Use Buffering + Use Buffering - - Attempt download on network streams - Attempt download on network streams + + Attempt download on network streams + Attempt download on network streams - - Buffer size (-1 default value) - Buffer size (-1 default value) + + Buffer size (-1 default value) + Buffer size (-1 default value) - - Choose file - Choose file + + Choose file + Choose file - - All files - All files + + All files + All files - - + + UserElementSettings - - User defined elements - User defined elements + + User defined elements + User defined elements - - Only for advanced user! - Only for advanced user! + + Only for advanced user! + Only for advanced user! - - + + VolumeSettings - - Volume - Volume + + Volume + Volume - - Normalized volume - Normalized volume + + Normalized volume + Normalized volume - - Reset - Reset + + Reset + Reset - + diff --git a/lisp/i18n/ts/en/lisp.ts b/lisp/i18n/ts/en/lisp.ts index bbf899f9f..a1af3225e 100644 --- a/lisp/i18n/ts/en/lisp.ts +++ b/lisp/i18n/ts/en/lisp.ts @@ -1,830 +1,895 @@ - - + + + About - - Authors - Authors + + Authors + Authors - - Contributors - Contributors + + Contributors + Contributors - - Translators - Translators + + Translators + Translators - - About Linux Show Player - About Linux Show Player + + About Linux Show Player + About Linux Show Player - - + + AboutDialog - - Web site - Web site + + Linux Show Player is a cue player designed for stage productions. + - - Source code - Source code + + Info + Info - - Info - Info + + License + License - - License - License + + Contributors + Contributors - - Contributors - Contributors + + Web site + Web site - - Discussion - + + Discussion + - - + + + Source code + Source code + + + AppConfiguration - - LiSP preferences - LiSP preferences + + LiSP preferences + LiSP preferences + + + + AppConfigurationWarning + + + Cannot load configuration page: "{}" ({}) + - - + + AppGeneralSettings - - Default layout - + + Default layout + - - Application themes - + + Show layout selection at startup + - - UI theme: - + + Use layout at startup: + - - Icons theme: - + + Application themes + - - Language: - + + UI theme: + - - Show layout selection at startup - + + Icons theme: + - - Use layout at startup: - + + Application language (require a restart) + - - Application language (require a restart) - + + Language: + - - + + ApplicationError - - Startup error - + + Startup error + + + + + Error while reading the session file "{}" + - - + + + Unable to create the cue "{}" + + + + + ClassLoaderWarning + + + Cannot load python class: "{0}" + + + + CommandsStack - - Undo: {} - + + Undo: {} + - - Redo: {} - + + Redo: {} + - - + + ConfigurationDebug - - Configuration written at {} - + + Configuration written at {} + - - + + ConfigurationInfo - - New configuration installed at {} - + + New configuration installed at {} + + + + + Invalid path "{}", return default. + - - + + CueAction - - Default - Default + + Default + Default - - Pause - Pause + + Faded Start + - - Start - Start + + Faded Resume + - - Stop - Stop + + Faded Pause + - - Faded Start - + + Faded Stop + - - Faded Resume - + + Faded Interrupt + - - Faded Pause - + + Start + Start - - Faded Stop - + + Resume + - - Faded Interrupt - + + Pause + Pause - - Resume - + + Stop + Stop - - Do Nothing - + + Do Nothing + - - + + CueAppearanceSettings - - The appearance depends on the layout - The appearance depends on the layout + + The appearance depends on the layout + The appearance depends on the layout - - Cue name - Cue name + + Cue name + Cue name - - NoName - NoName + + NoName + NoName - - Description/Note - Description/Note + + Description/Note + Description/Note - - Set Font Size - Set Font Size + + Set Font Size + Set Font Size - - Color - Color + + Color + Color - - Select background color - Select background color + + Select background color + Select background color - - Select font color - Select font color + + Select font color + Select font color - - + + CueCategory - - Misc cues - + + Misc cues + - - + + CueCommandLog - - Cue settings changed: "{}" - Cue settings changed: "{}" + + Cues settings changed. + Cues settings changed. - - Cues settings changed. - Cues settings changed. + + Cue settings changed: "{}" + Cue settings changed: "{}" - - + + CueName - - Media Cue - Media Cue + + Media Cue + Media Cue - - + + CueNextAction - - Do Nothing - + + Do Nothing + - - Trigger after the end - + + Trigger after the end + - - Trigger after post wait - + + Trigger after post wait + - - Select after the end - + + Select after the end + - - Select after post wait - + + Select after post wait + - - + + CueSettings - - Pre wait - Pre wait + + Interrupt action fade + + + + + Used globally when interrupting cues + - - Wait before cue execution - Wait before cue execution + + Fade actions default value + - - Post wait - Post wait + + Used for fade-in and fade-out actions, by cues where fade duration is 0. + - - Wait after cue execution - Wait after cue execution + + Start action + Start action - - Next action - Next action + + Default action to start the cue + Default action to start the cue - - Start action - Start action + + Stop action + Stop action - - Default action to start the cue - Default action to start the cue + + Default action to stop the cue + Default action to stop the cue - - Stop action - Stop action + + Pre wait + Pre wait - - Default action to stop the cue - Default action to stop the cue + + Wait before cue execution + Wait before cue execution - - Interrupt action fade - + + Post wait + Post wait - - Fade actions default value - + + Wait after cue execution + Wait after cue execution - - + + + Next action + Next action + + + Fade - - Linear - Linear + + Linear + Linear - - Quadratic - Quadratic + + Quadratic + Quadratic - - Quadratic2 - Quadratic2 + + Quadratic2 + Quadratic2 - - + + FadeEdit - - Duration (sec): - + + Duration (sec): + - - Curve: - + + Curve: + - - + + FadeSettings - - Fade In - Fade In + + Fade In + Fade In - - Fade Out - Fade Out + + Fade Out + Fade Out - - + + HotKeyEdit - - Press shortcut - + + Press shortcut + - - + + LayoutSelect - - Layout selection - Layout selection + + Layout selection + Layout selection - - Select layout - Select layout + + Select layout + Select layout - - Open file - Open file + + Open file + Open file - - + + ListLayout - - Layout actions - + + Layout actions + - - Fade out when stopping all cues - + + Fade out when stopping all cues + - - Fade out when interrupting all cues - + + Fade out when interrupting all cues + - - Fade out when pausing all cues - + + Fade out when pausing all cues + - - Fade in when resuming all cues - + + Fade in when resuming all cues + - - + + LogStatusIcon - - Errors/Warnings - + + Errors/Warnings + - - + + Logging - - Debug - Debug + + Dismiss all + - - Warning - Warning + + Show details + - - Error - Error + + Debug + Debug - - Dismiss all - + + Info + Info - - Show details - + + Warning + Warning - - Linux Show Player - Log Viewer - + + Error + Error - - Info - Info + + Critical + - - Critical - + + Time + - - Time - + + Milliseconds + - - Milliseconds - + + Logger name + - - Logger name - + + Level + - - Level - + + Message + - - Message - + + Function + - - Function - + + Path name + - - Path name - + + File name + - - File name - + + Line no. + - - Line no. - + + Module + - - Module - + + Process ID + - - Process ID - + + Process name + - - Process name - + + Thread ID + - - Thread ID - + + Thread name + - - Thread name - + + Exception info + - - Exception info - + + Linux Show Player - Log Viewer + - - Showing {} of {} records - + + Showing {} of {} records + - - + + MainWindow - - &File - &File + + &File + &File - - New session - New session + + New session + New session - - Open - Open + + Open + Open - - Save session - Save session + + Save session + Save session - - Preferences - Preferences + + Preferences + Preferences - - Save as - Save as + + Save as + Save as - - Full Screen - Full Screen + + Full Screen + Full Screen - - Exit - Exit + + Exit + Exit - - &Edit - &Edit + + &Edit + &Edit - - Undo - Undo + + Undo + Undo - - Redo - Redo + + Redo + Redo - - Select all - Select all + + Select all + Select all - - Select all media cues - Select all media cues + + Select all media cues + Select all media cues - - Deselect all - Deselect all + + Deselect all + Deselect all - - CTRL+SHIFT+A - CTRL+SHIFT+A + + CTRL+SHIFT+A + CTRL+SHIFT+A - - Invert selection - Invert selection + + Invert selection + Invert selection - - CTRL+I - CTRL+I + + CTRL+I + CTRL+I - - Edit selected - Edit selected + + Edit selected + Edit selected - - CTRL+SHIFT+E - CTRL+SHIFT+E + + CTRL+SHIFT+E + CTRL+SHIFT+E - - &Layout - &Layout + + &Layout + &Layout - - &Tools - &Tools + + &Tools + &Tools - - Edit selection - Edit selection + + Edit selection + Edit selection - - &About - &About + + &About + &About - - About - About + + About + About - - About Qt - About Qt + + About Qt + About Qt - - Close session - Close session + + Close session + Close session - - Do you want to save them now? - + + The current session contains changes that have not been saved. + - - + + + Do you want to save them now? + + + + MainWindowDebug - - Registered cue menu: "{}" - + + Registered cue menu: "{}" + - - + + MainWindowError - - Cannot create cue {} - + + Cannot create cue {} + - - + + MediaCueSettings - - Start time - Start time + + Start time + Start time + + + + Stop position of the media + Stop position of the media + + + + Stop time + Stop time - - Stop position of the media - Stop position of the media + + Start position of the media + Start position of the media - - Stop time - Stop time + + Loop + Loop - - Start position of the media - Start position of the media + + Repetition after first play (-1 = infinite) + + + + ModulesLoaderWarning - - Loop - Loop + + Cannot load python module: "{0}" + - - + + QColorButton - - Right click to reset - Right click to reset + + Right click to reset + Right click to reset - - + + SettingsPageName - - Appearance - Appearance + + Plugins + - - General - General + + General + General - - Cue - Cue + + Layouts + - - Cue Settings - Cue Settings + + Cue Settings + Cue Settings - - Plugins - + + Cue + Cue - - Behaviours - Behaviours + + Behaviours + Behaviours - - Pre/Post Wait - Pre/Post Wait + + Pre/Post Wait + Pre/Post Wait - - Fade In/Out - Fade In/Out + + Fade In/Out + Fade In/Out - - Layouts - + + Appearance + Appearance - + diff --git a/lisp/i18n/ts/en/list_layout.ts b/lisp/i18n/ts/en/list_layout.ts index fc5325aa3..4b0b00673 100644 --- a/lisp/i18n/ts/en/list_layout.ts +++ b/lisp/i18n/ts/en/list_layout.ts @@ -1,259 +1,260 @@ - - + + + LayoutDescription - - Organize the cues in a list - + + Organize the cues in a list + - - + + LayoutDetails - - SHIFT + Space or Double-Click to edit a cue - + + SHIFT + Space or Double-Click to edit a cue + - - To copy cues drag them while pressing CTRL - + + To copy cues drag them while pressing CTRL + - - To move cues drag them - + + To move cues drag them + - - + + LayoutName - - List Layout - + + List Layout + - - + + ListLayout - - Show dB-meters - + + Show dB-meters + - - Show accurate time - + + Show seek-bars + - - Show seek-bars - + + Show accurate time + - - Auto-select next cue - + + Show index column + - - Enable selection mode - + + Auto-select next cue + - - Use fade (buttons) - + + Selection mode + - - Stop Cue - + + Show resize handles + - - Pause Cue - + + Restore default size + - - Resume Cue - + + Disable GO Key While Playing + - - Interrupt Cue - + + Edit cue + - - Edit cue - + + Edit selected + - - Remove cue - + + Clone cue + - - Selection mode - + + Clone selected + - - Pause all - + + Remove cue + - - Stop all - + + Remove selected + - - Interrupt all - + + Copy of {} + - - Resume all - + + Default behaviors (applied to new sessions) + - - Fade-Out all - + + Enable selection mode + - - Fade-In all - + + Behaviors + - - Edit selected - + + Use waveform seek-bars + - - Clone cue - + + GO Key Disabled While Playing + - - Clone selected - + + GO Key: + - - Remove selected - + + GO Action: + - - GO Key: - + + GO minimum interval (ms): + - - GO Action: - + + Use fade (buttons) + - - GO minimum interval (ms): - + + Stop Cue + - - Copy of {} - + + Pause Cue + - - Show index column - + + Resume Cue + - - Show resize handles - + + Interrupt Cue + - - Restore default size - + + Pause all + - - Default behaviors (applied to new sessions) - + + Stop all + - - Behaviors - + + Interrupt all + - - Disable GO Key While Playing - + + Resume all + - - Use waveform seek-bars - + + Fade-Out all + - - GO Key Disabled While Playing - + + Fade-In all + - - + + ListLayoutHeader - - Cue - + + Cue + - - Pre wait - + + Pre wait + - - Action - + + Action + - - Post wait - + + Post wait + - - + + ListLayoutInfoPanel - - Cue name - + + Cue name + - - Cue description - + + Cue description + - - + + SettingsPageName - - List Layout - + + List Layout + - + diff --git a/lisp/i18n/ts/en/media_info.ts b/lisp/i18n/ts/en/media_info.ts index 1657970c9..68d390d1f 100644 --- a/lisp/i18n/ts/en/media_info.ts +++ b/lisp/i18n/ts/en/media_info.ts @@ -1,31 +1,32 @@ - - + + + MediaInfo - - Media Info - Media Info + + Media Info + Media Info - - Info - Info + + Warning + - - Value - Value + + Cannot get any information. + - - Warning - + + Info + Info - - Cannot get any information. - + + Value + Value - + diff --git a/lisp/i18n/ts/en/midi.ts b/lisp/i18n/ts/en/midi.ts index eb1f8d835..794e1ab47 100644 --- a/lisp/i18n/ts/en/midi.ts +++ b/lisp/i18n/ts/en/midi.ts @@ -1,215 +1,221 @@ - - + + + CueCategory - - Integration cues - + + Integration cues + - - + + CueName - - MIDI Cue - + + MIDI Cue + - - + + MIDICue - - MIDI Message - + + MIDI Message + - - Message type - + + Message type + - - + + MIDIError - - Cannot connect to MIDI output port '{}'. - + + Cannot connect to MIDI output port '{}'. + - - Cannot connect to MIDI input port '{}'. - + + Cannot connect to MIDI input port '{}'. + - - + + + Cannot create ALSA-MIDI port monitor, MIDI connections/disconnections will not be detected. + + + + MIDIInfo - - MIDI port disconnected: '{}' - + + Connecting to MIDI port: '{}' + - - Connecting to MIDI port: '{}' - + + MIDI port disconnected: '{}' + - - Connecting to matching MIDI port: '{}' - + + Connecting to matching MIDI port: '{}' + - - + + MIDIMessageAttr - - Channel - + + Channel + - - Note - + + Note + - - Velocity - + + Velocity + - - Control - + + Control + - - Program - + + Program + - - Value - + + Value + - - Song - + + Song + - - Pitch - + + Pitch + - - Position - + + Position + - - + + MIDIMessageType - - Note ON - + + Note ON + - - Note OFF - + + Note OFF + - - Polyphonic After-touch - + + Polyphonic After-touch + - - Control/Mode Change - + + Control/Mode Change + - - Program Change - + + Program Change + - - Channel After-touch - + + Channel After-touch + - - Pitch Bend Change - + + Pitch Bend Change + - - Song Select - + + Song Select + - - Song Position - + + Song Position + - - Start - + + Start + - - Stop - + + Stop + - - Continue - + + Continue + - - + + MIDISettings - - Input - Input + + MIDI devices + - - Output - Output + + Input + Input - - MIDI devices - + + Output + Output - - Misc options - + + Misc options + - - Try to connect using only device/port name - + + Try to connect using only device/port name + - - + + SettingsPageName - - MIDI settings - MIDI settings + + MIDI settings + MIDI settings - - MIDI Settings - + + MIDI Settings + - + diff --git a/lisp/i18n/ts/en/network.ts b/lisp/i18n/ts/en/network.ts index 6b7d6ce5d..621aa0e20 100644 --- a/lisp/i18n/ts/en/network.ts +++ b/lisp/i18n/ts/en/network.ts @@ -1,75 +1,84 @@ - - + + + APIServerInfo - - Stop serving network API - + + Stop serving network API + - - + + ApiServerError - - Network API server stopped working. - + + Network API server stopped working. + - - + + + ApiServerInfo + + + Start serving network API at: http://{}:{}/ + + + + NetworkApiDebug - - New end-point: {} - + + New end-point: {} + - - + + NetworkDiscovery - - Host discovery - + + Select the hosts you want to add + - - Manage hosts - + + Host discovery + - - Discover hosts - + + Manage hosts + - - Manually add a host - + + Discover hosts + - - Remove selected host - + + Manually add a host + - - Remove all host - + + Remove selected host + - - Address - + + Remove all host + - - Host IP - + + Address + - - Select the hosts you want to add - + + Host IP + - + diff --git a/lisp/i18n/ts/en/osc.ts b/lisp/i18n/ts/en/osc.ts index cf0d68c87..502aea27c 100644 --- a/lisp/i18n/ts/en/osc.ts +++ b/lisp/i18n/ts/en/osc.ts @@ -1,145 +1,154 @@ - - + + + Cue Name - - OSC Settings - + + OSC Settings + - - + + CueCategory - - Integration cues - + + Integration cues + - - + + CueName - - OSC Cue - + + OSC Cue + - - + + OscCue - - Type - + + OSC Message + - - Value - + + Add + - - FadeTo - + + Remove + - - Fade - + + OSC Path: + - - OSC Message - + + /path/to/something + - - Add - + + Fade + - - Remove - + + Time (sec) + - - OSC Path: - + + Curve + - - /path/to/something - + + Type + - - Time (sec) - + + Value + - - Curve - + + FadeTo + - - + + + OscCueError + + + Cannot send OSC message, see error for details + + + + OscServerDebug - - Message from {} -> path: "{}" args: {} - + + Message from {} -> path: "{}" args: {} + - - + + OscServerError - - Cannot start OSC sever - + + Cannot start OSC sever + - - + + OscServerInfo - - OSC server started at {} - + + OSC server stopped + - - OSC server stopped - + + OSC server started at {} + - - + + OscSettings - - OSC Settings - + + OSC Settings + - - Input Port: - + + Input Port: + - - Output Port: - + + Output Port: + - - Hostname: - + + Hostname: + - - + + SettingsPageName - - OSC settings - + + OSC settings + - + diff --git a/lisp/i18n/ts/en/presets.ts b/lisp/i18n/ts/en/presets.ts index 63ecb84e1..197bd1c9d 100644 --- a/lisp/i18n/ts/en/presets.ts +++ b/lisp/i18n/ts/en/presets.ts @@ -1,134 +1,145 @@ - - + + + Preset - - Create Cue - Create Cue + + Create Cue + Create Cue - - Load on selected Cues - Load on selected Cues + + Load on selected Cues + Load on selected Cues - - + + Presets - - Presets - Presets + + Cannot scan presets + Cannot scan presets - - Save as preset - Save as preset + + Presets + Presets - - Cannot scan presets - Cannot scan presets + + Preset name + Preset name - - Error while deleting preset "{}" - Error while deleting preset "{}" + + Add + Add - - Cannot load preset "{}" - Cannot load preset "{}" + + Rename + Rename - - Cannot save preset "{}" - Cannot save preset "{}" + + Edit + Edit - - Cannot rename preset "{}" - Cannot rename preset "{}" + + Remove + Remove - - Select Preset - Select Preset + + Export selected + Export selected - - Preset name - Preset name + + Import + Import - - Add - Add + + Cue type + Cue type - - Rename - Rename + + Error while deleting preset "{}" + Error while deleting preset "{}" - - Edit - Edit + + Cannot load preset "{}" + Cannot load preset "{}" - - Remove - Remove + + Cannot save preset "{}" + Cannot save preset "{}" - - Export selected - Export selected + + Cannot rename preset "{}" + Cannot rename preset "{}" - - Import - Import + + Select Preset + Select Preset - - Warning - Warning + + Preset "{}" already exists, overwrite? + - - The same name is already used! - The same name is already used! + + Warning + Warning - - Cannot export correctly. - Cannot export correctly. + + The same name is already used! + The same name is already used! - - Cannot import correctly. - Cannot import correctly. + + Cannot export correctly. + Cannot export correctly. - - Cue type - Cue type + + Some presets already exists, overwrite? + - - Load on cue - + + Cannot import correctly. + Cannot import correctly. - - Load on selected cues - + + Cannot create a cue from this preset: {} + - - Cannot create a cue from this preset: {} - + + Load on cue + - + + + Load on selected cues + + + + + Save as preset + Save as preset + + diff --git a/lisp/i18n/ts/en/rename_cues.ts b/lisp/i18n/ts/en/rename_cues.ts index 3f05cf36b..b8b3a35a5 100644 --- a/lisp/i18n/ts/en/rename_cues.ts +++ b/lisp/i18n/ts/en/rename_cues.ts @@ -1,82 +1,107 @@ - - + + + RenameCues - - Rename Cues - + + Rename Cues + - - Rename cues - + + Rename cues + - - Current - + + Capitalize + - - Preview - + + Lowercase + - - Capitalize - + + Uppercase + - - Lowercase - + + Remove Numbers + - - Uppercase - + + Add numbering + - - Remove Numbers - + + Reset + - - Add numbering - + + Rename all cue. () in regex below usable with $0, $1 ... + - - Reset - + + Type your regex here: + - - Type your regex here: - + + Regex help + - - Regex help - + + You can use Regexes to rename your cues. + +Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... +In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. + +Exemple: +^[a-z]([0-9]+) will find a lower case character([a-z]), followed by one or more number. +Only the numbers are between parenthesis and will be usable with $0 in the first line. + +For more information about Regexes, consult python documentation at: https://docs.python.org/3/howto/regex.html#regex-howto + - - + + + Current + + + + + Preview + + + + RenameCuesCommand - - Renamed {number} cues - + + Renamed {number} cues + - - + + RenameUiDebug - - Regex error: Invalid pattern - + + Regex error: Invalid pattern + + + + + Regex error: catch with () before display with $n + - + diff --git a/lisp/i18n/ts/en/replay_gain.ts b/lisp/i18n/ts/en/replay_gain.ts index 4a04e4469..2d1f2a0e8 100644 --- a/lisp/i18n/ts/en/replay_gain.ts +++ b/lisp/i18n/ts/en/replay_gain.ts @@ -1,82 +1,96 @@ - - + + + ReplayGain - - ReplayGain / Normalization - ReplayGain / Normalization + + ReplayGain / Normalization + ReplayGain / Normalization - - Calculate - Calculate + + Calculate + Calculate - - Reset all - Reset all + + Reset all + Reset all - - Reset selected - Reset selected + + Reset selected + Reset selected - - Threads number - Threads number + + Threads number + Threads number - - Apply only to selected media - Apply only to selected media + + Apply only to selected media + Apply only to selected media - - ReplayGain to (dB SPL) - ReplayGain to (dB SPL) + + ReplayGain to (dB SPL) + ReplayGain to (dB SPL) - - Normalize to (dB) - Normalize to (dB) + + Normalize to (dB) + Normalize to (dB) - - Processing files ... - Processing files ... + + Processing files ... + Processing files ... - - + + ReplayGainDebug - - Applied gain for: {} - + + Applied gain for: {} + - - Discarded gain for: {} - + + Discarded gain for: {} + - - + + + ReplayGainError + + + An error occurred during gain calculation. + + + + + An error occurred while processing gain results. + + + + ReplayGainInfo - - Gain processing stopped by user. - + + Gain processing stopped by user. + - - Started gain calculation for: {} - + + Started gain calculation for: {} + - - Gain calculated for: {} - + + Gain calculated for: {} + - + diff --git a/lisp/i18n/ts/en/synchronizer.ts b/lisp/i18n/ts/en/synchronizer.ts index be461c457..0418835f9 100644 --- a/lisp/i18n/ts/en/synchronizer.ts +++ b/lisp/i18n/ts/en/synchronizer.ts @@ -1,26 +1,27 @@ - - + + + Synchronizer - - Synchronization - Synchronization + + Synchronization + Synchronization - - Manage connected peers - Manage connected peers + + Manage connected peers + Manage connected peers - - Show your IP - Show your IP + + Show your IP + Show your IP - - Your IP is: {} - + + Your IP is: {} + - + diff --git a/lisp/i18n/ts/en/timecode.ts b/lisp/i18n/ts/en/timecode.ts index 0f5e66419..5ae61eef7 100644 --- a/lisp/i18n/ts/en/timecode.ts +++ b/lisp/i18n/ts/en/timecode.ts @@ -1,65 +1,80 @@ - - + + + SettingsPageName - - Timecode Settings - Timecode Settings + + Timecode + Timecode - - Timecode - Timecode + + Timecode Settings + Timecode Settings - - + + Timecode - - Cannot load timecode protocol: "{}" - + + Cannot load timecode protocol: "{}" + - - + + TimecodeError - - Cannot send timecode. - Cannot send timecode. + + Cannot send timecode. +OLA daemon has stopped. + - - + + + Cannot send timecode. + Cannot send timecode. + + + TimecodeSettings - - Timecode Format: - Timecode Format: + + Replace HOURS by a static track number + Replace HOURS by a static track number + + + + Enable Timecode + - - Replace HOURS by a static track number - Replace HOURS by a static track number + + Track number + Track number - - Track number - Track number + + Timecode Settings + Timecode Settings - - Enable Timecode - + + Timecode Format: + Timecode Format: - - Timecode Settings - Timecode Settings + + Timecode Protocol: + + + + TimecodeWarning - - Timecode Protocol: - + + Cannot send timecode, untracking cue + - + diff --git a/lisp/i18n/ts/en/triggers.ts b/lisp/i18n/ts/en/triggers.ts index 5599d998b..cc3b4ed23 100644 --- a/lisp/i18n/ts/en/triggers.ts +++ b/lisp/i18n/ts/en/triggers.ts @@ -1,62 +1,63 @@ - - + + + CueTriggers - - Started - Started + + Started + Started - - Paused - Paused + + Paused + Paused - - Stopped - Stopped + + Stopped + Stopped - - Ended - Ended + + Ended + Ended - - + + SettingsPageName - - Triggers - Triggers + + Triggers + Triggers - - + + TriggersSettings - - Add - Add + + Add + Add - - Remove - Remove + + Remove + Remove - - Trigger - Trigger + + Trigger + Trigger - - Cue - Cue + + Cue + Cue - - Action - Action + + Action + Action - + diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index fbd03d786..c62cdc804 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -105,10 +105,13 @@ def supported_extensions(self): return extensions - def media_waveform(self, media): + def uri_waveform(self, uri, duration=None): + if duration is None or duration <= 0: + duration = self.uri_duration(uri) + return GstWaveform( - media.input_uri(), - media.duration, + uri, + duration, cache_dir=self.app.conf.get("cache.position", ""), ) diff --git a/lisp/ui/widgets/waveform.py b/lisp/ui/widgets/waveform.py index 1d2b00c9c..98c52b798 100644 --- a/lisp/ui/widgets/waveform.py +++ b/lisp/ui/widgets/waveform.py @@ -69,9 +69,17 @@ def resizeEvent(self, event): def paintEvent(self, event): halfHeight = self.height() / 2 painter = QPainter() - painter.begin(self) - pen = QPen() + + # Draw the background (it will be clipped to event.rect()) + pen = QPen(QColor(0, 0, 0, 0)) + painter.setPen(pen) + painter.setBrush(QBrush(self.backgroundColor)) + painter.setRenderHint(QPainter.Antialiasing) + painter.drawRoundedRect(self.rect(), 6, 6) + painter.setRenderHint(QPainter.Antialiasing, False) + + # Draw the weveform pen.setWidth(1) painter.setPen(pen) @@ -98,7 +106,7 @@ def paintEvent(self, event): rmsLine = QLineF(x, halfHeight + rms, x, halfHeight - rms) # Decide if elapsed or remaining - if x <= elapsedWidth: + if x < elapsedWidth: peakElapsedLines.append(peakLine) rmsElapsedLines.append(rmsLine) else: @@ -194,27 +202,16 @@ def resizeEvent(self, event): super().resizeEvent(event) def paintEvent(self, event): - painter = QPainter() - pen = QPen(QColor(0, 0, 0, 0)) - - # Draw the background (it will be clipped to event.rect()) - painter.begin(self) - - painter.setPen(pen) - painter.setBrush(QBrush(self.backgroundColor)) - painter.setRenderHint(QPainter.Antialiasing) - painter.drawRoundedRect(self.rect(), 6, 6) - - painter.end() - # Draw the waveform super().paintEvent(event) # If needed (mouse-over) draw the seek indicator, and it's timestamp if self._lastPosition >= 0: + painter = QPainter() painter.begin(self) # Draw the indicator as a 1px vertical line + pen = QPen() pen.setWidth(1) pen.setColor(self.seekIndicatorColor) painter.setPen(pen) diff --git a/scripts/pyts_update.py b/scripts/pyts_update.py new file mode 100644 index 000000000..970eebc9e --- /dev/null +++ b/scripts/pyts_update.py @@ -0,0 +1,383 @@ +import ast +import enum +from dataclasses import dataclass, field +from os import path +from pathlib import Path +from typing import List, Dict, Union, Iterable +from xml.etree import ElementTree as ET + + +class TextElement(ET.Element): + def __init__(self, tag, text=None, **kwargs): + super().__init__(tag, **kwargs) + self.text = text + + +class Obsolete(enum.Enum): + # Keep: keep the obsolete translations, mark them as such + Keep = enum.auto() + # Discard: discard obsolete translations + Discard = enum.auto() + # Nothing: the option should not be ignored + Nothing = enum.auto() + + +@dataclass +class TSLocation: + filename: str = "" + line: int = 0 + + def to_xml(self): + return ET.Element( + "location", + attrib={"filename": self.filename, "line": str(self.line)}, + ) + + @staticmethod + def from_xml(element: ET.Element) -> "TSLocation": + return TSLocation(element.get("filename", ""), element.get("line", 0)) + + +@dataclass +class TSTranslation: + class Types(enum.Enum): + Unfinished = "unfinished" + Vanished = "vanished" + Obsolete = "obsolete" + + type: Union[Types, None] = Types.Unfinished + translations: List[str] = field(default_factory=list) + + def update(self, translation: "TSTranslation", obsolete=Obsolete.Nothing): + if obsolete == Obsolete.Nothing: + if len(translation.translations) > len(self.translations): + self.translations = translation.translations + else: + for n in range( + min(len(self.translations), len(translation.translations)) + ): + self.translations[n] = translation.translations[n] + + self.type = None if all(self.translations) else self.Types.Unfinished + + def to_xml(self): + element = ET.Element("translation") + + if self.type is not None: + element.set("type", self.type.value) + + if len(self.translations) > 1: + # TODO: some languages have different plurals rules/forms + for translation in self.translations: + element.append(TextElement("numerusform", translation)) + elif self.translations: + element.text = self.translations[0] + + return element + + @staticmethod + def from_xml(element: ET.Element) -> "TSTranslation": + translations = [] + if len(element) > 0: + translations.extend(element.itertext()) + elif element.text: + translations.append(element.text) + + return TSTranslation(element.get("type"), translations) + + +@dataclass +class TSMessage: + source: str + location: TSLocation + translation: TSTranslation + + comment: str = "" + extracomment: str = "" + translatorcomment: str = "" + + @property + def plural(self) -> bool: + return len(self.translation.translations) > 1 + + def key(self): + return hash((self.source, self.comment)) + + def update(self, message: "TSMessage", obsolete=Obsolete.Nothing): + if obsolete == obsolete.Nothing: + # We keep the newer value + self.location = message.location + + self.extracomment = message.extracomment + self.translatorcomment = message.translatorcomment + + self.translation.update(message.translation, obsolete) + + def mark_obsolete(self): + self.translation.type = TSTranslation.Types.Obsolete + + def to_xml(self): + element = ET.Element("message") + + if self.plural: + element.attrib["numerus"] = "yes" + + element.append(self.location.to_xml()) + element.append(TextElement("source", self.source)) + + if self.comment: + element.append(TextElement("comment", self.comment)) + if self.extracomment: + element.append(TextElement("extracomment", self.comment)) + if self.translatorcomment: + element.append( + TextElement("translatorcomment", self.translatorcomment) + ) + + element.append(self.translation.to_xml()) + + return element + + @staticmethod + def from_xml(element: ET.Element) -> "TSMessage": + attributes = { + "source": "", + "comment": "", + "extracomment": "", + "translatorcomment": "", + } + location = TSLocation() + translation = TSTranslation() + + for child in element: + if child.tag in attributes: + attributes[child.tag] = child.text + elif child.tag == "location": + location = TSLocation.from_xml(child) + elif child.tag == "translation": + translation = TSTranslation.from_xml(child) + + return TSMessage( + location=location, translation=translation, **attributes + ) + + +@dataclass +class TSContext: + name: str + messages: Dict[int, TSMessage] = field(default_factory=dict) + + def add(self, message: TSMessage, obsolete=Obsolete.Nothing): + message_key = message.key() + existing_message = self.messages.get(message_key) + + if existing_message is None: + if obsolete == Obsolete.Discard: + return + + if obsolete == Obsolete.Keep: + message.mark_obsolete() + + self.messages[message_key] = message + else: + existing_message.update(message, obsolete) + + def update(self, context: "TSContext", obsolete=Obsolete.Nothing): + for message in context.messages.values(): + self.add(message, obsolete) + + def mark_obsolete(self): + for message in self.messages.values(): + message.mark_obsolete() + + def to_xml(self): + element = ET.Element("context") + element.append(TextElement("name", self.name)) + + for message in self.messages.values(): + element.append(message.to_xml()) + + return element + + @staticmethod + def from_xml(element: ET.Element) -> "TSContext": + context = TSContext(name=element.find("name").text) + for message_element in element.findall("message"): + context.add(TSMessage.from_xml(message_element)) + + return context + + +class TSTranslations: + XML_HEADER = '\n\n' + + def __init__(self, language="en", src_language="en"): + self.contexts = {} + self.src_language = src_language + self.language = language + + def add(self, context: TSContext, obsolete=Obsolete.Nothing): + existing_context = self.contexts.get(context.name) + + if existing_context is None: + if obsolete == Obsolete.Discard: + return + + if obsolete == Obsolete.Keep: + context.mark_obsolete() + + self.contexts[context.name] = context + else: + existing_context.update(context, obsolete) + + def add_message(self, context_name: str, message: TSMessage): + if context_name not in self.contexts: + self.contexts[context_name] = TSContext(context_name) + + self.contexts[context_name].add(message) + + def update(self, translations: "TSTranslations", obsolete=Obsolete.Nothing): + for name, context in translations.contexts.items(): + self.add(context, obsolete) + + def to_xml(self): + if self.src_language != self.language: + source_language = self.src_language + else: + source_language = "" + + element = ET.Element( + "TS", + attrib={ + "version": "2.0", + "language": self.language, + "sourcelanguage": source_language, + }, + ) + + for context_name in sorted(self.contexts.keys()): + element.append(self.contexts[context_name].to_xml()) + + return element + + def write(self, file: Union[str, Path]): + filename = file if isinstance(file, str) else file.as_posix() + + root_element = self.to_xml() + self.indent(root_element) + + with open(filename, mode="w") as out: + out.write( + self.XML_HEADER + ET.tostring(root_element, encoding="unicode") + ) + + def __str__(self): + return str(self.contexts) + + @staticmethod + def from_file(file: Union[str, Path]): + filename = file if isinstance(file, str) else file.as_posix() + + return TSTranslations.from_xml(ET.parse(filename).getroot()) + + @staticmethod + def from_xml(element: ET.Element) -> "TSTranslations": + translations = TSTranslations() + for context in element.findall("context"): + translations.add(TSContext.from_xml(context)) + + return translations + + @staticmethod + def indent(element: ET.Element): + try: + # noinspection PyUnresolvedReferences + ET.indent(element) + except AttributeError: + TSTranslations.fallback_indent(element) + + @staticmethod + def fallback_indent(element: ET.Element, level: int = 0): + # http://effbot.org/zone/element-lib.htm#prettyprint + i = "\n" + level * " " + if len(element): + if not element.text or not element.text.strip(): + element.text = i + " " + if not element.tail or not element.tail.strip(): + element.tail = i + for element in element: + TSTranslations.fallback_indent(element, level + 1) + if not element.tail or not element.tail.strip(): + element.tail = i + else: + if level and (not element.tail or not element.tail.strip()): + element.tail = i + + +class PyTrFinder: + FunctionNames = ("translate", "QT_TRANSLATE_NOOP") + FunctionArgTypes = (ast.Constant, ast.Constant, ast.Constant, ast.expr) + + def __init__(self, destination=".", language="en", src_language="en"): + self.destination = destination + self.translations = TSTranslations(language, src_language) + + def find_in_files(self, files: Iterable[Union[str, Path]]): + for file in files: + self.find_in_file(file) + + def find_in_file(self, file: Union[str, Path]): + filename = file if isinstance(file, str) else file.as_posix() + + with open(filename, "r", encoding="utf-8") as file_io: + self.find_in_code(file_io.read(), filename) + + def find_in_code(self, source_code: str, filename: str): + self.find_in_ast(ast.parse(source_code), filename) + + def find_in_ast(self, root: ast.AST, filename: str): + filename = path.relpath(filename, path.dirname(self.destination)) + + for function_call in self.walk_function_calls(root): + context_name, message = self.message_from_args( + function_call.args, TSLocation(filename, function_call.lineno) + ) + if context_name and message: + self.translations.add_message(context_name, message) + + def walk_function_calls(self, root: ast.AST): + """Go through all the nodes, via `ast.walk`, yield only the one need. + + Specifically we keep only function calls where the function name + is present in `self.FunctionNames`. + We only consider "names", not "attributes". + """ + for node in ast.walk(root): + if ( + isinstance(node, ast.Call) + and isinstance(node.func, ast.Name) + and node.func.id in self.FunctionNames + ): + yield node + + def message_from_args(self, args, location: TSLocation): + if len(args) < 2: + return None, None + + for arg_type, argument in zip(self.FunctionArgTypes, args): + if not isinstance(argument, arg_type): + return None, None + + context_name = args[0].value + plural = len(args) > 3 + message = TSMessage( + source=args[1].value, + comment=args[2].value if len(args) > 2 else "", + translation=TSTranslation( + type=TSTranslation.Types.Unfinished, + translations=["", ""] if plural else [""], + ), + location=location, + ) + + return context_name, message From e153fbd6c50a8f69b222ee7daa2f1e74069ad5e6 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 7 Nov 2020 14:19:12 +0100 Subject: [PATCH 248/333] Formatting --- lisp/application.py | 3 ++- lisp/backend/waveform.py | 14 +++++++++++--- lisp/core/fader.py | 20 ++++++++++---------- lisp/core/has_properties.py | 2 +- lisp/core/util.py | 6 +++--- lisp/plugins/__init__.py | 3 ++- lisp/plugins/cache_manager/cache_manager.py | 5 ++++- lisp/plugins/controller/protocols/osc.py | 7 ++++++- lisp/plugins/presets/presets_ui.py | 7 ++++--- lisp/plugins/timecode/protocols/midi.py | 2 +- lisp/ui/qmodels.py | 2 +- lisp/ui/settings/app_pages/cue.py | 5 ++++- lisp/ui/widgets/qeditabletabbar.py | 5 +++-- lisp/ui/widgets/qwaitingspinner.py | 5 ++++- 14 files changed, 56 insertions(+), 30 deletions(-) diff --git a/lisp/application.py b/lisp/application.py index c162f83fb..55231979e 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -227,7 +227,8 @@ def __load_from_file(self, session_file): name = cues_dict.get("name", "No name") logger.exception( translate( - "ApplicationError", 'Unable to create the cue "{}"', + "ApplicationError", + 'Unable to create the cue "{}"', ).format(name) ) diff --git a/lisp/backend/waveform.py b/lisp/backend/waveform.py index 03e09c775..7ea7e2ccd 100644 --- a/lisp/backend/waveform.py +++ b/lisp/backend/waveform.py @@ -70,11 +70,15 @@ def cache_path(self, refresh=True): file_path = self._uri.absolute_path if not self._hash or refresh: self._hash = file_hash( - file_path, digest_size=16, person=self.CACHE_VERSION.encode(), + file_path, + digest_size=16, + person=self.CACHE_VERSION.encode(), ) return path.join( - path.dirname(file_path), self._cache_dir, self._hash + ".waveform", + path.dirname(file_path), + self._cache_dir, + self._hash + ".waveform", ) def load_waveform(self): @@ -137,7 +141,11 @@ def _to_cache(self): with open(cache_path, "wb") as cache_file: pickle.dump( - (self.peak_samples, self.rms_samples,), cache_file, + ( + self.peak_samples, + self.rms_samples, + ), + cache_file, ) logger.debug(f"Dumped waveform to the cache: {cache_path}") diff --git a/lisp/core/fader.py b/lisp/core/fader.py index ce3d3f6cb..7cdba8f23 100644 --- a/lisp/core/fader.py +++ b/lisp/core/fader.py @@ -25,16 +25,16 @@ class Fader: """Allow to perform fades on "generic" objects attributes. - * Fades have a resolution of `1-hundredth-of-second` - * To be able to fade correctly the attribute must be numeric, if not, the - `fade` function will fail - * When fading, the `fade` function cannot be entered by other threads, - if this happens the function will simply return immediately - * To execute a fader, the `prepare` function must be called first, - this will also stop the fader - * After calling `prepare` the fader is considered as running - * The `stop` function wait until all "pending" target changes are applied - * Changing the target will also stop the fader + * Fades have a resolution of `1-hundredth-of-second` + * To be able to fade correctly the attribute must be numeric, if not, the + `fade` function will fail + * When fading, the `fade` function cannot be entered by other threads, + if this happens the function will simply return immediately + * To execute a fader, the `prepare` function must be called first, + this will also stop the fader + * After calling `prepare` the fader is considered as running + * The `stop` function wait until all "pending" target changes are applied + * Changing the target will also stop the fader """ def __init__(self, target, attribute): diff --git a/lisp/core/has_properties.py b/lisp/core/has_properties.py index 41f251488..7282adf89 100644 --- a/lisp/core/has_properties.py +++ b/lisp/core/has_properties.py @@ -91,7 +91,7 @@ class HasProperties(metaclass=HasPropertiesMeta): class DeepThought(HasProperties): the_answer = Property(default=42) nested = Property(default=AnotherThought.class_defaults()) - """ + """ def __init__(self): self.__changed_signals = {} diff --git a/lisp/core/util.py b/lisp/core/util.py index 9849d67b2..f32d8af03 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -108,9 +108,9 @@ def time_tuple(milliseconds): def strtime(time, accurate=False): """Return a string from the given milliseconds time. - - when > 59min -> hh:mm:ss - - when < 1h and accurate -> mm:ss:00 - - when < 1h and not accurate -> mm:ss:z0 + - when > 59min -> hh:mm:ss + - when < 1h and accurate -> mm:ss:00 + - when < 1h and not accurate -> mm:ss:z0 """ hours, minutes, seconds, milliseconds = time_tuple(int(time)) diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index 5858a199c..35fde2a35 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -164,7 +164,8 @@ def __load_plugins(plugins, application, optionals=True): except Exception: logger.exception( translate( - "PluginsError", 'Failed to load plugin: "{}"', + "PluginsError", + 'Failed to load plugin: "{}"', ).format(name) ) diff --git a/lisp/plugins/cache_manager/cache_manager.py b/lisp/plugins/cache_manager/cache_manager.py index fd52029b0..1e9685689 100644 --- a/lisp/plugins/cache_manager/cache_manager.py +++ b/lisp/plugins/cache_manager/cache_manager.py @@ -51,7 +51,10 @@ def _check_cache_size(self): def _show_threshold_warning(self, threshold, _): QMessageBox.warning( self.app.window, - translate("CacheManager", "Cache size",), + translate( + "CacheManager", + "Cache size", + ), translate( "CacheManager", "The cache has exceeded {}. Consider clean it.\n" diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 29b1efd0c..0b83b87f8 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -99,7 +99,12 @@ def __init__(self, **kwargs): def retranslateUi(self): self.setWindowTitle(translate("ControllerOscSettings", "OSC Message")) - self.pathLabel.setText(translate("ControllerOscSettings", "OSC Path:",)) + self.pathLabel.setText( + translate( + "ControllerOscSettings", + "OSC Path:", + ) + ) self.pathEdit.setPlaceholderText( translate("ControllerOscSettings", "/path/to/method") ) diff --git a/lisp/plugins/presets/presets_ui.py b/lisp/plugins/presets/presets_ui.py index 9753f6faa..2463cd125 100644 --- a/lisp/plugins/presets/presets_ui.py +++ b/lisp/plugins/presets/presets_ui.py @@ -112,9 +112,10 @@ def check_override_dialog(preset_name): answer = QMessageBox.question( MainWindow(), translate("Presets", "Presets"), - translate("Presets", 'Preset "{}" already exists, overwrite?',).format( - preset_name - ), + translate( + "Presets", + 'Preset "{}" already exists, overwrite?', + ).format(preset_name), buttons=QMessageBox.Yes | QMessageBox.Cancel, ) diff --git a/lisp/plugins/timecode/protocols/midi.py b/lisp/plugins/timecode/protocols/midi.py index 9fc769505..cd46daf3c 100644 --- a/lisp/plugins/timecode/protocols/midi.py +++ b/lisp/plugins/timecode/protocols/midi.py @@ -49,7 +49,7 @@ def __init__(self): def __send_full(self, fmt, hours, minutes, seconds, frame): """Sends fullframe timecode message. - + Used in case timecode is non continuous (repositioning, rewind). """ message = Message( diff --git a/lisp/ui/qmodels.py b/lisp/ui/qmodels.py index 415f754dc..414adc09d 100644 --- a/lisp/ui/qmodels.py +++ b/lisp/ui/qmodels.py @@ -25,7 +25,7 @@ class SimpleTableModel(QAbstractTableModel): - """ Simple implementation of a QAbstractTableModel + """Simple implementation of a QAbstractTableModel .. warning:: Currently this is a partial implementation of a complete R/W Qt model diff --git a/lisp/ui/settings/app_pages/cue.py b/lisp/ui/settings/app_pages/cue.py index 612ef4d5c..ad6b9594d 100644 --- a/lisp/ui/settings/app_pages/cue.py +++ b/lisp/ui/settings/app_pages/cue.py @@ -70,7 +70,10 @@ def retranslateUi(self): translate("CueSettings", "Interrupt action fade") ) self.interruptHelpText.setText( - translate("CueSettings", "Used globally when interrupting cues",) + translate( + "CueSettings", + "Used globally when interrupting cues", + ) ) self.fadeActionsDefaultsGroup.setTitle( translate("CueSettings", "Fade actions default value") diff --git a/lisp/ui/widgets/qeditabletabbar.py b/lisp/ui/widgets/qeditabletabbar.py index 4ea48c37a..1ff9b67f6 100644 --- a/lisp/ui/widgets/qeditabletabbar.py +++ b/lisp/ui/widgets/qeditabletabbar.py @@ -33,8 +33,9 @@ def __init__(self, *args): self._editor.installEventFilter(self) def eventFilter(self, widget, event): - clickOutside = event.type() == QEvent.MouseButtonPress and not self._editor.geometry().contains( - event.globalPos() + clickOutside = ( + event.type() == QEvent.MouseButtonPress + and not self._editor.geometry().contains(event.globalPos()) ) escKey = ( event.type() == QEvent.KeyPress and event.key() == Qt.Key_Escape diff --git a/lisp/ui/widgets/qwaitingspinner.py b/lisp/ui/widgets/qwaitingspinner.py index f3ea23bb8..05a75f74f 100644 --- a/lisp/ui/widgets/qwaitingspinner.py +++ b/lisp/ui/widgets/qwaitingspinner.py @@ -99,7 +99,10 @@ def paintEvent(self, QPaintEvent): painter.translate(self._innerRadius, 0) painter.setBrush(color) painter.drawRoundedRect( - linesRect, self._roundness, self._roundness, Qt.RelativeSize, + linesRect, + self._roundness, + self._roundness, + Qt.RelativeSize, ) painter.restore() From 434160b13ad515926289a67026b3e7e7c732cbe9 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 7 Nov 2020 19:11:51 +0100 Subject: [PATCH 249/333] Refactor plugins management --- lisp/__init__.py | 4 + lisp/core/plugin.py | 10 +- lisp/core/plugin_loader.py | 124 ++++++++++++++ lisp/core/plugins_manager.py | 150 +++++++++++++++++ lisp/plugins/__init__.py | 192 ++-------------------- lisp/plugins/controller/protocols/midi.py | 3 +- lisp/plugins/controller/protocols/osc.py | 3 +- lisp/ui/settings/app_pages/plugins.py | 2 +- 8 files changed, 310 insertions(+), 178 deletions(-) create mode 100644 lisp/core/plugin_loader.py create mode 100644 lisp/core/plugins_manager.py diff --git a/lisp/__init__.py b/lisp/__init__.py index 20068517c..3cc6aa8a0 100644 --- a/lisp/__init__.py +++ b/lisp/__init__.py @@ -38,6 +38,10 @@ DEFAULT_APP_CONFIG = path.join(APP_DIR, "default.json") USER_APP_CONFIG = path.join(app_dirs.user_config_dir, "lisp.json") +PLUGINS_PACKAGE = "lisp.plugins" +PLUGINS_PATH = path.join(APP_DIR, "plugins") +USER_PLUGINS_PATH = path.join(app_dirs.user_data_dir, "plugins") + DEFAULT_CACHE_DIR = path.join(app_dirs.user_data_dir, "cache") ICON_THEMES_DIR = path.join(APP_DIR, "ui", "icons") diff --git a/lisp/core/plugin.py b/lisp/core/plugin.py index 614d1b1cf..2a40480f7 100644 --- a/lisp/core/plugin.py +++ b/lisp/core/plugin.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2016 Francesco Ceruti +# Copyright 2020 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -15,8 +15,16 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging + from lisp.core.configuration import DummyConfiguration +logger = logging.getLogger(__name__) + + +class PluginNotLoadedError(Exception): + pass + # TODO: add possible additional metadata (Icon, Version, ...) # TODO: implement some kind of plugin status diff --git a/lisp/core/plugin_loader.py b/lisp/core/plugin_loader.py new file mode 100644 index 000000000..665f96697 --- /dev/null +++ b/lisp/core/plugin_loader.py @@ -0,0 +1,124 @@ +# This file is part of Linux Show Player +# +# Copyright 2020 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import logging +from typing import Dict, Type, Iterable + +from lisp.core.plugin import Plugin, logger +from lisp.ui.ui_utils import translate + + +class PluginLoadException(Exception): + pass + + +class PluginInitFailedException(PluginLoadException): + pass + + +class PluginsLoader: + def __init__( + self, + application, + to_load: Dict[str, Type[Plugin]], + ): + self.application = application + self.to_load = to_load + + self.resolved = set() + self.unresolved = set() + self.failed = set() + + def load(self): + yield from self._resolve_dependencies( + self.to_load.keys(), optionals=True, log_level=logging.ERROR + ) + + def _resolve_plugin_dependencies(self, plugin: Type[Plugin]): + self.unresolved.add(plugin) + + try: + yield from self._resolve_dependencies(plugin.Depends) + yield from self._resolve_dependencies( + plugin.OptDepends, optionals=True + ) + + self.resolved.add(plugin) + except PluginLoadException as e: + raise PluginLoadException( + translate( + "PluginsError", "Cannot satisfy dependencies for plugin: {}" + ).format(plugin.Name), + ) from e + finally: + self.unresolved.remove(plugin) + + def _resolve_dependencies( + self, + dependencies: Iterable[str], + optionals: bool = False, + log_level: int = logging.WARNING, + ): + for dependency_name in dependencies: + try: + dependency = self._dependency_from_name(dependency_name) + if self._should_resolve(dependency): + yield from self._resolve_plugin_dependencies(dependency) + yield from self._load_plugin(dependency, dependency_name) + except PluginLoadException as e: + logger.log(log_level, str(e), exc_info=e) + if not optionals: + raise e + + def _load_plugin(self, plugin: Type[Plugin], name: str): + # Try to load the plugin, if enabled + if plugin.Config.get("_enabled_", False): + try: + # Create an instance of the plugin and yield it + yield name, plugin(self.application) + logger.info( + translate("PluginsInfo", "Plugin loaded: {}").format( + plugin.Name + ) + ) + except Exception as e: + self.failed.add(plugin) + raise PluginInitFailedException( + f"Failed to initialize plugin: {plugin.Name}" + ) from e + else: + raise PluginLoadException("Plugin disabled, not loaded.") + + def _should_resolve(self, plugin: Type[Plugin]) -> bool: + if plugin in self.failed: + raise PluginLoadException( + f"Plugin is in error state: {plugin.Name}" + ) + + if plugin not in self.resolved: + if plugin in self.unresolved: + raise PluginLoadException("Circular dependency detected.") + + return True + + return False + + def _dependency_from_name(self, name: str) -> Type[Plugin]: + try: + return self.to_load[name] + except KeyError: + raise PluginLoadException(f"No plugin named '{name}'") diff --git a/lisp/core/plugins_manager.py b/lisp/core/plugins_manager.py new file mode 100644 index 000000000..edc367419 --- /dev/null +++ b/lisp/core/plugins_manager.py @@ -0,0 +1,150 @@ +# This file is part of Linux Show Player +# +# Copyright 2020 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import inspect +import itertools +import logging +import sys +from os import path +from typing import Dict, Type + +from lisp import USER_PLUGINS_PATH, app_dirs, PLUGINS_PACKAGE, PLUGINS_PATH +from lisp.core.configuration import JSONFileConfiguration, DummyConfiguration +from lisp.core.loading import load_classes +from lisp.core.plugin import Plugin, PluginNotLoadedError +from lisp.core.plugin_loader import PluginsLoader +from lisp.ui.ui_utils import translate, install_translation + +logger = logging.getLogger(__name__) + + +class PluginsManager: + # Fallback plugin configuration + FALLBACK_CONFIG = {"_version_": "undefined", "_enabled_": True} + + def __init__(self, application, enable_user_plugins=True): + self.application = application + self.enable_user_plugins = enable_user_plugins + + # The discovered plugins + self.register: Dict[str, Type[Plugin]] = {} + # Loaded plugins instances + self.plugins: Dict[str, Plugin] = {} + + def load_plugins(self): + """Load and instantiate available plugins.""" + + # Plugins shipped in the package + lisp_plugins = load_classes(PLUGINS_PACKAGE, PLUGINS_PATH) + + # Plugins installed by the user + if self.enable_user_plugins: + # Allow importing of python modules from the user plugins path. + if USER_PLUGINS_PATH not in sys.path: + sys.path.insert(1, USER_PLUGINS_PATH) + + user_plugins = load_classes("", USER_PLUGINS_PATH) + else: + user_plugins = () + + # Register the plugins + for name, plugin in itertools.chain(lisp_plugins, user_plugins): + self.register_plugin(name, plugin) + + # Load (instantiate) the plugins + # Note that PluginsLoader.load() is a generator, it will yield + # each plugin when ready, so "self.plugins" will be update gradually, + # allowing plugins to request other plugins in theirs __init__ method. + self.plugins.update( + PluginsLoader(self.application, self.register).load() + ) + + def register_plugin(self, name, plugin): + if name in self.register: + # Prevent user plugins to override those provided with lisp, + # if the latter are register before. + # More generically, if two plugins with same name are provided, + # only the first will be kept. + logger.error( + translate( + "PluginsError", + 'A plugin by the name of "{}" already exists.', + ).format(name) + ) + return + + try: + mod_path = path.dirname(inspect.getfile(plugin)) + mod_name = plugin.__module__.split(".")[-1] + + # Load plugin configuration + user_config_path = path.join( + app_dirs.user_config_dir, mod_name + ".json" + ) + default_config_path = path.join(mod_path, "default.json") + if path.exists(default_config_path): + # Configuration for file + plugin.Config = JSONFileConfiguration( + user_config_path, default_config_path + ) + else: + # Fallback configuration, if the plugin didn't provide one + plugin.Config = DummyConfiguration(self.FALLBACK_CONFIG.copy()) + + # Load plugin translations + install_translation(mod_name, tr_path=path.join(mod_path, "i18n")) + + # Register plugin + self.register[name] = plugin + except Exception: + logger.exception( + translate( + "PluginsError", 'Failed to register plugin: "{}"' + ).format(name) + ) + + def is_loaded(self, plugin_name): + return plugin_name in self.plugins + + def get_plugin(self, plugin_name): + if self.is_loaded(plugin_name): + return self.plugins[plugin_name] + else: + raise PluginNotLoadedError( + translate( + "PluginsError", "The requested plugin is not loaded: {}" + ).format(plugin_name) + ) + + def finalize_plugins(self): + """Finalize all the plugins.""" + for name, plugin in self.plugins.items(): + try: + plugin.finalize() + logger.info( + translate("PluginsInfo", 'Plugin terminated: "{}"').format( + name + ) + ) + except Exception: + logger.exception( + translate( + "PluginsError", 'Failed to terminate plugin: "{}"' + ).format(name) + ) + + self.plugins.clear() diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index 35fde2a35..4155ffbd3 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2020 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -15,191 +15,35 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import inspect -import logging -from os import makedirs, path -import sys +from typing import Optional -from lisp import app_dirs -from lisp.core.configuration import JSONFileConfiguration -from lisp.core.loading import load_classes -from lisp.ui.ui_utils import install_translation, translate +from lisp.core.plugins_manager import PluginsManager -PLUGINS = {} -LOADED = {} +DefaultPluginsManager: Optional[PluginsManager] = None -FALLBACK_CONFIG_PATH = path.join(path.dirname(__file__), "default.json") -USER_PLUGIN_PATH = path.join(app_dirs.user_data_dir, "plugins") -# Make sure the path exists, and insert it into the list of paths python uses to find modules -makedirs(USER_PLUGIN_PATH, exist_ok=True) -sys.path.insert(1, USER_PLUGIN_PATH) +def load_plugins(application, enable_user_plugins=True): + global DefaultPluginsManager + if DefaultPluginsManager is None: + DefaultPluginsManager = PluginsManager(application, enable_user_plugins) -logger = logging.getLogger(__name__) - - -class PluginNotLoadedError(Exception): - pass - - -def load_plugins(application): - """Load and instantiate available plugins.""" - - def callback(name, plugin): - - if name in PLUGINS: - # We don't want users to be able to override plugins that are provided with lisp - logger.error( - translate( - "PluginsError", - 'A plugin by the name of "{}" already exists.', - ).format(name) - ) - return - - try: - PLUGINS[name] = plugin - - mod_path = path.dirname(inspect.getfile(plugin)) - mod_name = plugin.__module__.split(".")[-1] - - if path.exists(path.join(mod_path, "default.json")): - default_config_path = path.join(mod_path, "default.json") - else: - default_config_path = FALLBACK_CONFIG_PATH - - # Load plugin configuration - config = JSONFileConfiguration( - path.join(app_dirs.user_config_dir, mod_name + ".json"), - default_config_path, - ) - plugin.Config = config - - # Load plugin (self-contained) translations - install_translation(mod_name, tr_path=path.join(mod_path, "i18n")) - except Exception: - logger.exception( - translate("PluginsError", 'Failed to load "{}"').format(name) - ) - - # Load plugins that install with lisp - for name, plugin in load_classes(__package__, path.dirname(__file__)): - callback(name, plugin) - - # Load plugins that a user has installed to their profile - for name, plugin in load_classes("", USER_PLUGIN_PATH): - callback(name, plugin) - - __init_plugins(application) - - -def __init_plugins(application): - pending = PLUGINS.copy() - - # Run until all the pending plugins are loaded (unless interrupted) - while pending: - # Load all plugins with satisfied dependencies - unresolved = not __load_plugins(pending, application) - - # If no plugin with satisfied dependencies is found try again - # ignoring optional-dependencies - if unresolved: - unresolved = not __load_plugins(pending, application, False) - - if unresolved: - # We've go through all the not loaded plugins and weren't able - # to resolve their dependencies, which means there are cyclic or - # missing/disabled dependencies - logger.warning( - translate( - "PluginsWarning", "Cannot satisfy dependencies for: {}" - ).format(", ".join(pending)) - ) - - return - - -def __load_plugins(plugins, application, optionals=True): - """ - Go through each plugin and check if it's dependencies are already loaded, - otherwise leave it in the pending dict. - If all of it's dependencies are satisfied then try to load it. - - :type typing.MutableMapping[str, Type[lisp.core.plugin.Plugin]] - :type application: lisp.application.Application - :type optionals: bool - :rtype: bool - """ - resolved = False - - for name, plugin in list(plugins.items()): - dependencies = plugin.Depends - if optionals: - dependencies += plugin.OptDepends - - for dep in dependencies: - if dep not in LOADED: - break - else: - plugins.pop(name) - resolved = True - - # Try to load the plugin, if enabled - try: - if plugin.Config.get("_enabled_", False): - # Create an instance of the plugin and save it - LOADED[name] = plugin(application) - logger.info( - translate("PluginsInfo", 'Plugin loaded: "{}"').format( - name - ) - ) - else: - logger.debug( - translate( - "PluginsDebug", - 'Plugin disabled in configuration: "{}"', - ).format(name) - ) - except Exception: - logger.exception( - translate( - "PluginsError", - 'Failed to load plugin: "{}"', - ).format(name) - ) - - return resolved + DefaultPluginsManager.load_plugins() def finalize_plugins(): - """Finalize all the plugins.""" - for plugin in LOADED: - try: - LOADED[plugin].finalize() - logger.info( - translate("PluginsInfo", 'Plugin terminated: "{}"').format( - plugin - ) - ) - except Exception: - logger.exception( - translate( - "PluginsError", 'Failed to terminate plugin: "{}"' - ).format(plugin) - ) + if DefaultPluginsManager is not None: + DefaultPluginsManager.finalize_plugins() def is_loaded(plugin_name): - return plugin_name in LOADED + return ( + DefaultPluginsManager is not None + and DefaultPluginsManager.is_loaded(plugin_name) + ) def get_plugin(plugin_name): - if is_loaded(plugin_name): - return LOADED[plugin_name] + if DefaultPluginsManager is not None: + return DefaultPluginsManager.get_plugin(plugin_name) else: - raise PluginNotLoadedError( - translate( - "PluginsError", "the requested plugin is not loaded: {}" - ).format(plugin_name) - ) + raise RuntimeError("Cannot get plugins before they have been loaded.") diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index 32d6f4c39..e5cb280df 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -31,7 +31,8 @@ QHBoxLayout, ) -from lisp.plugins import get_plugin, PluginNotLoadedError +from lisp.plugins import get_plugin +from lisp.core.plugin import PluginNotLoadedError from lisp.plugins.controller.common import LayoutAction, tr_layout_action from lisp.plugins.controller.protocol import Protocol from lisp.plugins.midi.midi_utils import ( diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 0b83b87f8..82783ed8c 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -37,7 +37,8 @@ QSpacerItem, ) -from lisp.plugins import get_plugin, PluginNotLoadedError +from lisp.plugins import get_plugin +from lisp.core.plugin import PluginNotLoadedError from lisp.plugins.controller.common import LayoutAction, tr_layout_action from lisp.plugins.controller.protocol import Protocol from lisp.plugins.osc.osc_delegate import OscArgumentDelegate diff --git a/lisp/ui/settings/app_pages/plugins.py b/lisp/ui/settings/app_pages/plugins.py index 46063d9c9..104a7bd9c 100644 --- a/lisp/ui/settings/app_pages/plugins.py +++ b/lisp/ui/settings/app_pages/plugins.py @@ -45,7 +45,7 @@ def __init__(self, **kwargs): self.pluginsList.itemSelectionChanged.connect(self.__selection_changed) self.layout().addWidget(self.pluginsList) - for name, plugin in plugins.PLUGINS.items(): + for name, plugin in plugins.PluginsRegister.items(): item = QListWidgetItem(plugin.Name) if plugins.is_loaded(name): item.setIcon(IconTheme.get("led-running")) From 380caeafa0d584478a7911a467ba40f9517aee7f Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 8 Nov 2020 16:25:34 +0100 Subject: [PATCH 250/333] Update translation files --- lisp/i18n/ts/en/cache_manager.ts | 12 ++--- lisp/i18n/ts/en/controller.ts | 70 ++++++++++++------------- lisp/i18n/ts/en/lisp.ts | 90 ++++++++++++++++++++++++++++++-- lisp/i18n/ts/en/presets.ts | 32 ++++++------ lisp/i18n/ts/en/timecode.ts | 2 +- 5 files changed, 144 insertions(+), 62 deletions(-) diff --git a/lisp/i18n/ts/en/cache_manager.ts b/lisp/i18n/ts/en/cache_manager.ts index 24c6247d0..0ccaeae47 100644 --- a/lisp/i18n/ts/en/cache_manager.ts +++ b/lisp/i18n/ts/en/cache_manager.ts @@ -9,27 +9,27 @@ - + Cache size warning - + Warning threshold in MB (0 = disabled) - + Cache cleanup - + Delete the cache content - + The cache has exceeded {}. Consider clean it. You can do it in the application settings. @@ -38,7 +38,7 @@ You can do it in the application settings. SettingsPageName - + Cache Manager diff --git a/lisp/i18n/ts/en/controller.ts b/lisp/i18n/ts/en/controller.ts index 81b23d019..a92e4caf3 100644 --- a/lisp/i18n/ts/en/controller.ts +++ b/lisp/i18n/ts/en/controller.ts @@ -30,52 +30,52 @@ ControllerMidiSettings - + MIDI MIDI - + -- All Messages -- - + Capture Capture - + Capture filter - + Listening MIDI messages ... Listening MIDI messages ... - + Type Type - + Data 1 - + Data 2 - + Data 3 - + Action Action @@ -83,7 +83,7 @@ ControllerMidiSettingsWarning - + Error while importing configuration entry, skipped. @@ -91,67 +91,67 @@ ControllerOscSettings - + OSC Message - + OSC Path: - + /path/to/method - + OSC - + OSC Capture - + Add Add - + Remove Remove - + Capture Capture - + Waiting for messages: - + Path - + Types - + Arguments - + Action Action @@ -159,18 +159,18 @@ ControllerOscSettingsWarning - + Warning - + Osc path seems invalid, do not forget to edit the path later. - + Error while importing configuration entry, skipped. @@ -178,12 +178,12 @@ do not forget to edit the path later. ControllerSettings - + Add Add - + Remove Remove @@ -244,12 +244,12 @@ do not forget to edit the path later. Osc Cue - + Type Type - + Argument @@ -257,12 +257,12 @@ do not forget to edit the path later. OscCue - + Add Add - + Remove Remove @@ -285,12 +285,12 @@ do not forget to edit the path later. Keyboard Shortcuts - + MIDI Controls MIDI Controls - + OSC Controls diff --git a/lisp/i18n/ts/en/lisp.ts b/lisp/i18n/ts/en/lisp.ts index a1af3225e..c0aca9908 100644 --- a/lisp/i18n/ts/en/lisp.ts +++ b/lisp/i18n/ts/en/lisp.ts @@ -129,7 +129,7 @@ - + Error while reading the session file "{}" @@ -352,12 +352,12 @@ - + Fade actions default value - + Used for fade-in and fade-out actions, by cues where fade duration is 0. @@ -836,6 +836,88 @@ + + PluginsError + + + A plugin by the name of "{}" already exists. + + + + + The requested plugin is not loaded: "{}" + + + + + Failed to register plugin: "{}" + + + + + Failed to terminate plugin: "{}" + + + + + Cannot satisfy dependencies for plugin: "{}" + + + + + PluginsInfo + + + Plugin terminated: "{}" + + + + + Plugin loaded: "{}" + + + + + PluginsSettings + + + Plugin + + + + + Enabled + + + + + PluginsStatusText + + + Plugin disabled. Enable to use. + + + + + An error has occurred with this plugin. Please see logs for further information. + + + + + There is a non-critical issue with this disabled plugin. Please see logs for further information. + + + + + Plugin loaded and ready for use. + + + + + A non-critical issue is affecting this plugin. Please see logs for further information. + + + QColorButton @@ -847,7 +929,7 @@ SettingsPageName - + Plugins diff --git a/lisp/i18n/ts/en/presets.ts b/lisp/i18n/ts/en/presets.ts index 197bd1c9d..c83d76c7d 100644 --- a/lisp/i18n/ts/en/presets.ts +++ b/lisp/i18n/ts/en/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Create Cue - + Load on selected Cues Load on selected Cues @@ -27,42 +27,42 @@ Presets - + Preset name Preset name - + Add Add - + Rename Rename - + Edit Edit - + Remove Remove - + Export selected Export selected - + Import Import - + Cue type Cue type @@ -97,32 +97,32 @@ - + Warning Warning - + The same name is already used! The same name is already used! - + Cannot export correctly. Cannot export correctly. - + Some presets already exists, overwrite? - + Cannot import correctly. Cannot import correctly. - + Cannot create a cue from this preset: {} diff --git a/lisp/i18n/ts/en/timecode.ts b/lisp/i18n/ts/en/timecode.ts index 5ae61eef7..e46cc9d09 100644 --- a/lisp/i18n/ts/en/timecode.ts +++ b/lisp/i18n/ts/en/timecode.ts @@ -17,7 +17,7 @@ Timecode - + Cannot load timecode protocol: "{}" From c39ff0075b0261041885bdabe5aba44c1ca27808 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 10 Nov 2020 22:56:42 +0100 Subject: [PATCH 251/333] Update flatpak runtime and bundled dependencies Fix: check if the user "plugins" directory exists Minor improvements --- lisp/core/plugins_manager.py | 2 +- lisp/ui/settings/app_pages/plugins.py | 2 +- scripts/Flatpak/config.sh | 4 +- scripts/Flatpak/template.json | 518 ++++++++++++++------------ 4 files changed, 275 insertions(+), 251 deletions(-) diff --git a/lisp/core/plugins_manager.py b/lisp/core/plugins_manager.py index 6022cebd2..90d4143a3 100644 --- a/lisp/core/plugins_manager.py +++ b/lisp/core/plugins_manager.py @@ -49,7 +49,7 @@ def load_plugins(self): lisp_plugins = load_classes(PLUGINS_PACKAGE, PLUGINS_PATH) # Plugins installed by the user - if self._enable_user_plugins: + if self._enable_user_plugins and path.exists(USER_PLUGINS_PATH): # Allow importing of python modules from the user plugins path. if USER_PLUGINS_PATH not in sys.path: sys.path.insert(1, USER_PLUGINS_PATH) diff --git a/lisp/ui/settings/app_pages/plugins.py b/lisp/ui/settings/app_pages/plugins.py index f99429cca..ed9226ebd 100644 --- a/lisp/ui/settings/app_pages/plugins.py +++ b/lisp/ui/settings/app_pages/plugins.py @@ -119,7 +119,7 @@ def data(self, index, role=Qt.DisplayRole): # Column-independent values if role == Qt.TextAlignmentRole: - return Qt.AlignLeft + return Qt.AlignLeft | Qt.AlignVCenter elif role == Qt.ToolTipRole: return plugin.status_text() elif role == Qt.UserRole: diff --git a/scripts/Flatpak/config.sh b/scripts/Flatpak/config.sh index 17ecc0122..d3c526d69 100755 --- a/scripts/Flatpak/config.sh +++ b/scripts/Flatpak/config.sh @@ -1,12 +1,12 @@ #!/usr/bin/env bash export FLATPAK_RUNTIME="org.gnome.Platform" -export FLATPAK_RUNTIME_VERSION="3.34" +export FLATPAK_RUNTIME_VERSION="3.38" export FLATPAK_SDK="org.gnome.Sdk" export FLATPAK_INSTALL="$FLATPAK_RUNTIME//$FLATPAK_RUNTIME_VERSION $FLATPAK_SDK//$FLATPAK_RUNTIME_VERSION" -export FLATPAK_PY_VERSION="3.7" +export FLATPAK_PY_VERSION="3.8" export FLATPAK_PY_IGNORE_PACKAGES="setuptools six pygobject pycairo pyqt5 sip" export FLATPAK_APP_ID="org.linux_show_player.LinuxShowPlayer" diff --git a/scripts/Flatpak/template.json b/scripts/Flatpak/template.json index 8fd68676b..c95b92bd4 100644 --- a/scripts/Flatpak/template.json +++ b/scripts/Flatpak/template.json @@ -1,29 +1,28 @@ { - "app-id": "org.linux_show_player.LinuxShowPlayer", - "runtime": "org.gnome.Platform", - "runtime-version": "3.34", - "sdk": "org.gnome.Sdk", - "command": "linux-show-player", - "build-options": { - "no-debuginfo": true - }, - "finish-args": [ - "--share=network", - "--socket=x11", - "--socket=wayland", - "--socket=pulseaudio", - "--filesystem=home", - "--device=all" - ], - "cleanup-commands": [ - "pip3 uninstall --yes cython || true", - "pip3 uninstall --yes wheel || true" - ], - "rename-appdata-file": "linuxshowplayer.appdata.xml", - "rename-desktop-file": "linuxshowplayer.desktop", - "rename-icon": "linuxshowplayer", - "modules": [ - { + "app-id": "org.linux_show_player.LinuxShowPlayer", + "runtime": "org.gnome.Platform", + "runtime-version": "3.38", + "sdk": "org.gnome.Sdk", + "command": "linux-show-player", + "build-options": { + "no-debuginfo": true + }, + "finish-args": [ + "--share=network", + "--socket=x11", + "--socket=wayland", + "--socket=pulseaudio", + "--filesystem=home", + "--device=all" + ], + "cleanup-commands": [ + "pip3 uninstall --yes cython wheel packaging toml sip || true" + ], + "rename-appdata-file": "linuxshowplayer.appdata.xml", + "rename-desktop-file": "linuxshowplayer.desktop", + "rename-icon": "linuxshowplayer", + "modules": [ + { "name": "Qt5", "config-opts": [ "-confirm-license", "-opensource", @@ -48,39 +47,40 @@ "-skip", "qtwebglplugin", "-skip", "qtwebsockets", "-skip", "qtwebview", + "-skip", "qtwayland", "-gtk" ], "sources": [ { "type": "archive", - "url": "http://master.qt.io/archive/qt/5.12/5.12.2/single/qt-everywhere-src-5.12.2.tar.xz", - "sha256": "59b8cb4e728450b21224dcaaa40eb25bafc5196b6988f2225c394c6b7f881ff5" + "url": "http://master.qt.io/archive/qt/5.15.1/5.15.1/single/qt-everywhere-src-5.15.1.tar.xz", + "sha256": "44da876057e21e1be42de31facd99be7d5f9f07893e1ea762359bcee0ef64ee9" } ] }, - { - "name": "PyQt5", - "config-opts": [ - "--assume-shared", - "--concatenate", - "--confirm-license", - "--no-designer-plugin", - "--no-dist-info", - "--no-docstrings", - "--no-qml-plugin", - "--no-qsci-api", - "--no-stubs", - "--no-tools", - "QMAKE_CFLAGS_RELEASE='-I/usr/include/python3.7m/'", - "QMAKE_CXXFLAGS_RELEASE='-I/usr/include/python3.7m/'" - ], - "sources": [ - { - "type": "archive", - "url": "https://www.riverbankcomputing.com/static/Downloads/PyQt5/5.12.1/PyQt5_gpl-5.12.1.tar.gz", - "sha256": "3718ce847d824090fd5f95ff3f13847ee75c2507368d4cbaeb48338f506e59bf" - }, - { + { + "name": "PyQt5", + "config-opts": [ + "--assume-shared", + "--concatenate", + "--confirm-license", + "--no-designer-plugin", + "--no-dist-info", + "--no-docstrings", + "--no-qml-plugin", + "--no-qsci-api", + "--no-stubs", + "--no-tools", + "QMAKE_CFLAGS_RELEASE='-I/usr/include/python3.8m/'", + "QMAKE_CXXFLAGS_RELEASE='-I/usr/include/python3.8m/'" + ], + "sources": [ + { + "type": "archive", + "url": "https://www.riverbankcomputing.com/static/Downloads/PyQt5/PyQt5-5.15.2.dev2010041344.tar.gz", + "sha256": "25153398487b652b4b0737e0a348dfd9a077c5865cfeb79b02470efa597cfe8e" + }, + { "type": "script", "commands": [ "processed=`sed -e 's|prefix|sysroot|' <<< $@`", @@ -88,201 +88,225 @@ ], "dest-filename": "configure" } - ], - "modules": [ - { - "name": "SIP", - "config-opts": [ - "--no-dist-info", - "--no-stubs", - "--bindir=/app/bin", - "--destdir=/app/lib/python3.7/site-packages", - "--incdir=/app/include", - "--pyidir=/app/lib/python3.7/site-packages", - "--sipdir=/app/share/sip", - "--sip-module PyQt5.sip" - ], - "sources": [ - { - "type": "archive", - "url": "https://www.riverbankcomputing.com/static/Downloads/sip/4.19.15/sip-4.19.15.tar.gz", - "sha256": "2b5c0b2c0266b467b365c21376d50dde61a3236722ab87ff1e8dacec283eb610" - }, - { - "type": "script", - "commands": [ - "processed=`sed -e 's|--prefix=/app||' <<< $@`", - "python3 configure.py $processed" - ], - "dest-filename": "configure" - } - ], - "cleanup": [ - "/bin", - "/include" - ] - } - ], - "cleanup": [ - "/bin", - "/include" - ] - }, - { - "name": "Jack", - "buildsystem": "simple", - "build-commands": [ - "./waf configure --prefix='/app/'", - "./waf", - "./waf install" - ], - "sources": [ - { - "type": "git", - "url": "https://github.com/jackaudio/jack2.git", - "tag": "v1.9.14", - "commit": "b54a09bf7ef760d81fdb8544ad10e45575394624" - } - ], - "cleanup": [ - "/bin" - ] - }, - { - "name": "RtMidi", - "config-opts": [ - "--with-jack", - "--with-alsa" - ], - "sources": [ - { - "type": "git", - "url": "https://github.com/thestk/rtmidi.git", - "tag": "4.0.0", - "commit": "cc887191c3b4cb6697aeba5536d9f4eb423090aa" - } - ] - }, - { - "name": "liblo", - "config-opts": [ - "--disable-examples", - "--disable-tools" - ], - "sources": [ - { - "type": "git", - "url": "https://github.com/radarsat1/liblo.git", - "tag": "0.31", - "commit": "840ed69b1d669a1ce587eb592746e3dff6985d76" - } - ] - }, - { - "name": "cpp-google-protobuf", - "config-opts": [ - "--disable-static" - ], - "sources": [ - { - "type": "archive", - "url": "https://github.com/protocolbuffers/protobuf/releases/download/v3.1.0/protobuf-cpp-3.1.0.tar.gz", - "sha256": "51ceea9957c875bdedeb1f64396b5b0f3864fe830eed6a2d9c066448373ea2d6" - } - ] - }, - { - "name": "python-google-protobuf", - "buildsystem": "simple", - "build-commands": [ - "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} protobuf-3.1.0.post1-py2.py3-none-any.whl" - ], - "sources": [ - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/a5/bb/11821bdc46cb9aad8e18618715e5e93eef44abb642ed862c4b080c474183/protobuf-3.1.0.post1-py2.py3-none-any.whl", - "sha256": "42315e73409eaefdcc11e216695ff21f87dc483ad0595c57999baddf7f841180" - } - ] - }, - { - "name": "ola", - "build-options": { - "env": { - "PYTHON": "python3" - } - }, - "config-opts": [ - "--disable-unittests", - "--disable-examples", - "--disable-osc", - "--disable-http", - "--enable-python-libs" - ], - "sources": [ - { - "type": "archive", - "url": "https://github.com/OpenLightingProject/ola/releases/download/0.10.7/ola-0.10.7.tar.gz", - "sha256": "8a65242d95e0622a3553df498e0db323a13e99eeb1accc63a8a2ca8913ab31a0" - }, - { - "type": "script", - "dest-filename": "autogen.sh", - "commands": [ - "autoreconf -fi" - ] - } - ] - }, - { - "name": "python-alsa", - "buildsystem": "simple", - "build-commands": [ - "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} ." - ], - "sources": [ - { - "type": "archive", - "url": "ftp://ftp.alsa-project.org/pub/pyalsa/pyalsa-1.1.6.tar.bz2", - "sha256": "2771291a5d2cf700f0abbe6629ea37468d1463a01b2330d84ef976e1e918676c" - } - ] - }, - { - "name": "python-wheel", - "buildsystem": "simple", - "build-commands": [ - "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} wheel-0.34.2-py2.py3-none-any.whl" - ], - "sources": [ - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/8c/23/848298cccf8e40f5bbb59009b32848a4c38f4e7f3364297ab3c3e2e2cd14/wheel-0.34.2-py2.py3-none-any.whl", - "sha256": "df277cb51e61359aba502208d680f90c0493adec6f0e848af94948778aed386e" - } - ] - }, - { - "name": "linux-show-player", - "buildsystem": "simple", - "build-commands": [ - "pip3 install --verbose --no-index --no-deps --no-build-isolation --prefix=${FLATPAK_DEST} .", - "mkdir -p /app/share/applications/", - "cp dist/linuxshowplayer.desktop /app/share/applications/", - "mkdir -p /app/share/mime/packages/", - "cp dist/linuxshowplayer.xml /app/share/mime/packages/org.linux_show_player.LinuxShowPlayer.xml", - "mkdir -p /app/share/metainfo/", - "cp dist/linuxshowplayer.appdata.xml /app/share/metainfo/", - "mkdir -p /app/share/icons/hicolor/512x512/apps/", - "cp dist/linuxshowplayer.png /app/share/icons/hicolor/512x512/apps/" - ], - "sources": [ - { - "type": "git", - "url": "https://github.com/FrancescoCeruti/linux-show-player.git", - "branch": "master" - } - ] - } - ] + ], + "modules": [ + { + "name": "packaging", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} packaging-20.4-py2.py3-none-any.whl" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/46/19/c5ab91b1b05cfe63cccd5cfc971db9214c6dd6ced54e33c30d5af1d2bc43/packaging-20.4-py2.py3-none-any.whl", + "sha256": "998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" + } + ] + }, + { + "name": "pyparsing", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} pyparsing-2.4.7-py2.py3-none-any.whl" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/8a/bb/488841f56197b13700afd5658fc279a2025a39e22449b7cf29864669b15d/pyparsing-2.4.7-py2.py3-none-any.whl", + "sha256": "ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + } + ] + }, + { + "name": "toml", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} toml-0.10.2-py2.py3-none-any.whl" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", + "sha256": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + } + ] + }, + { + "name": "SIP", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} sip-5.4.0-cp38-cp38-manylinux2014_x86_64.whl" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/fb/c4/93366a89e7f7374eded03a77b3e702a0413312056b4781dc7bad014dfd5c/sip-5.4.0-cp38-cp38-manylinux2014_x86_64.whl", + "sha256": "3e2c724cb03487ac7dfd372200c02199b1d023512f0185b1849cf88e367f4f69" + } + ] + } + ], + "cleanup": [ + "/bin", + "/include" + ] + }, + { + "name": "Jack", + "buildsystem": "simple", + "build-commands": [ + "./waf configure --prefix='/app/'", + "./waf", + "./waf install" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/jackaudio/jack2.git", + "tag": "v1.9.16", + "commit": "5b78c2ef158c2d9ffe09818a7dd80209ed251c5f" + } + ], + "cleanup": [ + "/bin" + ] + }, + { + "name": "RtMidi", + "config-opts": [ + "--with-jack", + "--with-alsa" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/thestk/rtmidi.git", + "tag": "4.0.0", + "commit": "cc887191c3b4cb6697aeba5536d9f4eb423090aa" + } + ] + }, + { + "name": "liblo", + "config-opts": [ + "--disable-examples", + "--disable-tools" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/radarsat1/liblo.git", + "tag": "0.31", + "commit": "840ed69b1d669a1ce587eb592746e3dff6985d76" + } + ] + }, + { + "name": "cpp-google-protobuf", + "config-opts": [ + "--disable-static" + ], + "sources": [ + { + "type": "archive", + "url": "https://github.com/protocolbuffers/protobuf/releases/download/v3.1.0/protobuf-cpp-3.1.0.tar.gz", + "sha256": "51ceea9957c875bdedeb1f64396b5b0f3864fe830eed6a2d9c066448373ea2d6" + } + ] + }, + { + "name": "python-google-protobuf", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} protobuf-3.1.0.post1-py2.py3-none-any.whl" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/a5/bb/11821bdc46cb9aad8e18618715e5e93eef44abb642ed862c4b080c474183/protobuf-3.1.0.post1-py2.py3-none-any.whl", + "sha256": "42315e73409eaefdcc11e216695ff21f87dc483ad0595c57999baddf7f841180" + } + ] + }, + { + "name": "ola", + "build-options": { + "env": { + "PYTHON": "python3" + } + }, + "config-opts": [ + "--disable-unittests", + "--disable-examples", + "--disable-osc", + "--disable-http", + "--enable-python-libs" + ], + "sources": [ + { + "type": "archive", + "url": "https://github.com/OpenLightingProject/ola/releases/download/0.10.7/ola-0.10.7.tar.gz", + "sha256": "8a65242d95e0622a3553df498e0db323a13e99eeb1accc63a8a2ca8913ab31a0" + }, + { + "type": "script", + "dest-filename": "autogen.sh", + "commands": [ + "autoreconf -fi" + ] + } + ] + }, + { + "name": "python-alsa", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} ." + ], + "sources": [ + { + "type": "archive", + "url": "ftp://ftp.alsa-project.org/pub/pyalsa/pyalsa-1.1.6.tar.bz2", + "sha256": "2771291a5d2cf700f0abbe6629ea37468d1463a01b2330d84ef976e1e918676c" + } + ] + }, + { + "name": "python-wheel", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} wheel-0.34.2-py2.py3-none-any.whl" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/8c/23/848298cccf8e40f5bbb59009b32848a4c38f4e7f3364297ab3c3e2e2cd14/wheel-0.34.2-py2.py3-none-any.whl", + "sha256": "df277cb51e61359aba502208d680f90c0493adec6f0e848af94948778aed386e" + } + ] + }, + { + "name": "linux-show-player", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --verbose --no-index --no-deps --no-build-isolation --prefix=${FLATPAK_DEST} .", + "mkdir -p /app/share/applications/", + "cp dist/linuxshowplayer.desktop /app/share/applications/", + "mkdir -p /app/share/mime/packages/", + "cp dist/linuxshowplayer.xml /app/share/mime/packages/org.linux_show_player.LinuxShowPlayer.xml", + "mkdir -p /app/share/metainfo/", + "cp dist/linuxshowplayer.appdata.xml /app/share/metainfo/", + "mkdir -p /app/share/icons/hicolor/512x512/apps/", + "cp dist/linuxshowplayer.png /app/share/icons/hicolor/512x512/apps/" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/FrancescoCeruti/linux-show-player.git", + "branch": "master" + } + ] + } + ] } From b3cf41a9a1373fd1d3fccd1ee960145363f4c0bd Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 11 Nov 2020 10:28:31 +0100 Subject: [PATCH 252/333] Fix qt5 url for the flatpak build --- scripts/Flatpak/template.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Flatpak/template.json b/scripts/Flatpak/template.json index c95b92bd4..1cf24a458 100644 --- a/scripts/Flatpak/template.json +++ b/scripts/Flatpak/template.json @@ -53,7 +53,7 @@ "sources": [ { "type": "archive", - "url": "http://master.qt.io/archive/qt/5.15.1/5.15.1/single/qt-everywhere-src-5.15.1.tar.xz", + "url": "http://master.qt.io/archive/qt/5.15/5.15.1/single/qt-everywhere-src-5.15.1.tar.xz", "sha256": "44da876057e21e1be42de31facd99be7d5f9f07893e1ea762359bcee0ef64ee9" } ] From 2e6172e3914b69cd6169424936a96fc5280366c6 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 11 Nov 2020 12:46:36 +0100 Subject: [PATCH 253/333] Fix pyqt5 compilation flags for flatpak build --- scripts/Flatpak/template.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/Flatpak/template.json b/scripts/Flatpak/template.json index 1cf24a458..59eecab98 100644 --- a/scripts/Flatpak/template.json +++ b/scripts/Flatpak/template.json @@ -71,8 +71,8 @@ "--no-qsci-api", "--no-stubs", "--no-tools", - "QMAKE_CFLAGS_RELEASE='-I/usr/include/python3.8m/'", - "QMAKE_CXXFLAGS_RELEASE='-I/usr/include/python3.8m/'" + "QMAKE_CFLAGS_RELEASE='-I/usr/include/python3.8/'", + "QMAKE_CXXFLAGS_RELEASE='-I/usr/include/python3.8/'" ], "sources": [ { From 828f8da919aeced1af91d6bc02fafafc9e821372 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 25 Apr 2021 14:52:59 +0200 Subject: [PATCH 254/333] Move dependencies management to poetry, update CI scripts Add: flatpak manifest generator from poetry.lock (derived from https://github.com/flatpak/flatpak-builder-tools) Update: the (python) package build is now handled via poetry (poetry-core) Update: flatpak dependencies Update: move generated "flatpak" modules to a separate file Update: move circle-ci builds to ubuntu-20.04 --- .circleci/config.yml | 96 ++-- Pipfile | 22 - Pipfile.lock | 321 ------------ linux-show-player | 5 - poetry.lock | 466 ++++++++++++++++++ pyproject.toml | 52 +- scripts/Flatpak/pipenv_flatpak.py | 212 -------- scripts/Flatpak/prepare_flatpak.py | 54 -- scripts/Flatpak/requirements.txt | 2 - scripts/{Flatpak => flatpak}/README | 18 +- .../build-flatpak.sh} | 13 +- scripts/{Flatpak => flatpak}/config.sh | 4 +- scripts/{Flatpak => flatpak}/functions.sh | 17 +- ...rg.linux_show_player.LinuxShowPlayer.json} | 119 +++-- scripts/flatpak/patch-manifest.py | 30 ++ scripts/flatpak/poetry-flatpak.py | 162 ++++++ scripts/flatpak/python-modules.json | 84 ++++ scripts/flatpak/requirements.txt | 2 + setup.py | 50 -- 19 files changed, 920 insertions(+), 809 deletions(-) delete mode 100644 Pipfile delete mode 100644 Pipfile.lock delete mode 100755 linux-show-player create mode 100644 poetry.lock delete mode 100644 scripts/Flatpak/pipenv_flatpak.py delete mode 100644 scripts/Flatpak/prepare_flatpak.py delete mode 100644 scripts/Flatpak/requirements.txt rename scripts/{Flatpak => flatpak}/README (61%) rename scripts/{Flatpak/build_flatpak.sh => flatpak/build-flatpak.sh} (71%) rename scripts/{Flatpak => flatpak}/config.sh (73%) rename scripts/{Flatpak => flatpak}/functions.sh (89%) rename scripts/{Flatpak/template.json => flatpak/org.linux_show_player.LinuxShowPlayer.json} (68%) create mode 100644 scripts/flatpak/patch-manifest.py create mode 100755 scripts/flatpak/poetry-flatpak.py create mode 100644 scripts/flatpak/python-modules.json create mode 100644 scripts/flatpak/requirements.txt delete mode 100644 setup.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 7f6c78fa5..defd1dc68 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,8 +1,8 @@ -version: 2 +version: 2.1 jobs: - build: + build-flatpak: machine: - image: ubuntu-1604:201903-01 + image: ubuntu-2004:202010-01 working_directory: ~/repo @@ -14,118 +14,102 @@ jobs: - checkout - run: - name: SYSTEM ⟹ Install flatpak & flatpak-builder + name: SYSTEM ⟹ Install required packages command: | sudo apt-get -qq update - sudo apt-get install -y software-properties-common python3-software-properties - sudo add-apt-repository -y ppa:alexlarsson/flatpak - sudo apt-get -qq update - sudo apt-get install -y ostree flatpak flatpak-builder - - - restore_cache: - name: PYENV ⟹ Restore cache - key: pyenv-python-3.6.8b - - - run: - name: PYENV ⟹ Install python 3.6.8 - command: | - pyenv install --skip-existing 3.6.8 - pyenv global 3.6.8 - - - save_cache: - name: PYENV ⟹ Save cache - key: pyenv-python-3.6.8b - paths: - - /opt/circleci/.pyenv/versions/3.6.8 + sudo apt-get install -y \ + software-properties-common \ + python3-software-properties \ + golang \ + ostree \ + flatpak \ + flatpak-builder - run: name: FLATPAK ⟹ Install flathub Runtime & SDK - working_directory: ~/repo/scripts/Flatpak + working_directory: ~/repo/scripts/flatpak command: source functions.sh && flatpak_install_runtime - restore_cache: name: PIP ⟹ Restore cache keys: - - v3-pip-{{ checksum "scripts/Flatpak/requirements.txt" }} + - v3-pip-{{ checksum "scripts/flatpak/requirements.txt" }} - run: name: FLATPAK ⟹ Generate flatpak manifest - working_directory: ~/repo/scripts/Flatpak + working_directory: ~/repo/scripts/flatpak command: | export BUILD_BRANCH=$CIRCLE_BRANCH source functions.sh && flatpak_build_manifest - save_cache: name: PIP ⟹ Save cache - key: v3-pip-{{ checksum "scripts/Flatpak/requirements.txt" }} + key: v3-pip-{{ checksum "scripts/flatpak/requirements.txt" }} paths: - - ~/repo/scripts/Flatpak/.venv + - ~/repo/scripts/flatpak/.venv - restore_cache: name: FLATPAK BUILD ⟹ Restore cache keys: - - v2-flatpak-{{ checksum "scripts/Flatpak/org.linux_show_player.LinuxShowPlayer.json" }} + - v2-flatpak-{{ checksum "scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json" }}-{{ checksum "scripts/flatpak/python-modules.json" }} - v2-flatpak- - run: name: FLATPAK BUILD ⟹ Build flatpak (no exit on error) - working_directory: ~/repo/scripts/Flatpak + working_directory: ~/repo/scripts/flatpak command: source functions.sh && flatpak_build_noexit - save_cache: name: FLATPAK BUILD ⟹ Save cache - key: v2-flatpak-{{ checksum "scripts/Flatpak/org.linux_show_player.LinuxShowPlayer.json" }} + key: v2-flatpak-{{ checksum "scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json" }}-{{ checksum "scripts/flatpak/python-modules.json" }} paths: - - ~/repo/scripts/Flatpak/.flatpak-builder + - ~/repo/scripts/flatpak/.flatpak-builder - run: name: FLATPAK BUILD ⟹ Check if build failed - working_directory: ~/repo/scripts/Flatpak + working_directory: ~/repo/scripts/flatpak command: source functions.sh && flatpak_build_noexit_check - run: name: FLATPAK ⟹ Bundle flatpak - working_directory: ~/repo/scripts/Flatpak + working_directory: ~/repo/scripts/flatpak command: | export BUILD_BRANCH=$CIRCLE_BRANCH source functions.sh && flatpak_bundle - run: - name: BINTRAY ⟹ Upload bundle - working_directory: ~/repo/scripts/Flatpak + name: RELEASE ⟹ Update CI release + working_directory: ~/repo/scripts/flatpak command: | - curl -fL https://getcli.jfrog.io | sh - chmod +x jfrog + TAG=ci-$CIRCLE_BRANCH + ARTIFACT_NAME="LinuxShowPlayer-$CIRCLE_BRANCH-x86_64.flatpak" - PKG_VERSION=$(date '+%Y%m%d')-$CIRCLE_BRANCH-$CIRCLE_BUILD_NUM + # Rename artifact + mv out/linux-show-player.flatpak "out/$ARTIFACT_NAME" - ./jfrog bt config \ - --interactive=false \ - --user=francescoceruti \ - --key=$BINTRAY_API_KEY \ - --licenses=GPL-3.0 + # Download helper for github releases + go get github.com/tcnksm/ghr - ./jfrog bt upload \ - --publish \ - --override \ - out/linux-show-payer.flatpak \ - francescoceruti/LinuxShowPlayer/$CIRCLE_BRANCH/$PKG_VERSION \ - linux-show-player_$PKG_VERSION.flatpak + # Upload the artifacts + ghr \ + -n "Automated build ($CIRCLE_BRANCH)" \ + -b "Build number: $CIRCLE_BUILD_NUM
    Completed at: $(date)" \ + -recreate \ + -prerelease \ + $TAG "out/$ARTIFACT_NAME" - store_artifacts: - path: ~/repo/scripts/Flatpak/out + path: ~/repo/scripts/flatpak/out destination: build workflows: version: 2 build: jobs: - - build: - context: Bintray + - build-flatpak: + context: Github filters: branches: - ignore: - - /l10n_.*/ only: - master - develop \ No newline at end of file diff --git a/Pipfile b/Pipfile deleted file mode 100644 index a13792e41..000000000 --- a/Pipfile +++ /dev/null @@ -1,22 +0,0 @@ -[[source]] -url = "https://pypi.python.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -appdirs = ">=1.4.1" -Cython = "~=0.29" -falcon = "~=2.0" -JACK-Client = "~=0.4" -mido = "~=1.2" -PyGObject = "~=3.30" -pyliblo = "~=0.10" -PyQt5 = "~=5.6" -python-rtmidi = "~=1.1" -requests = "~=2.20" -sortedcontainers = "~=2.0" -pyalsa = {editable = true,git = "https://github.com/alsa-project/alsa-python.git",ref = "v1.1.6"} -humanize = "~=3.1.0" - -[dev-packages] -html5lib = "*" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 95b8bfb5c..000000000 --- a/Pipfile.lock +++ /dev/null @@ -1,321 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "07843a1069149dffa1c685cfaa0a91b9041825182985e4008d7d67dbd57e6293" - }, - "pipfile-spec": 6, - "requires": {}, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "appdirs": { - "hashes": [ - "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", - "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" - ], - "index": "pypi", - "version": "==1.4.4" - }, - "certifi": { - "hashes": [ - "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", - "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" - ], - "version": "==2020.6.20" - }, - "cffi": { - "hashes": [ - "sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d", - "sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b", - "sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4", - "sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f", - "sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3", - "sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579", - "sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537", - "sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e", - "sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05", - "sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171", - "sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca", - "sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522", - "sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c", - "sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc", - "sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d", - "sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808", - "sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828", - "sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869", - "sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d", - "sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9", - "sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0", - "sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc", - "sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15", - "sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c", - "sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a", - "sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3", - "sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1", - "sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768", - "sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d", - "sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b", - "sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e", - "sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d", - "sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730", - "sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394", - "sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1", - "sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591" - ], - "version": "==1.14.3" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "version": "==3.0.4" - }, - "cython": { - "hashes": [ - "sha256:0ac10bf476476a9f7ef61ec6e44c280ef434473124ad31d3132b720f7b0e8d2a", - "sha256:0e25c209c75df8785480dcef85db3d36c165dbc0f4c503168e8763eb735704f2", - "sha256:171b9f70ceafcec5852089d0f9c1e75b0d554f46c882cd4e2e4acaba9bd7d148", - "sha256:23f3a00b843a19de8bb4468b087db5b413a903213f67188729782488d67040e0", - "sha256:2922e3031ba9ebbe7cb9200b585cc33b71d66023d78450dcb883f824f4969371", - "sha256:31c71a615f38401b0dc1f2a5a9a6c421ffd8908c4cd5bbedc4014c1b876488e8", - "sha256:473df5d5e400444a36ed81c6596f56a5b52a3481312d0a48d68b777790f730ae", - "sha256:497841897942f734b0abc2dead2d4009795ee992267a70a23485fd0e937edc0b", - "sha256:539e59949aab4955c143a468810123bf22d3e8556421e1ce2531ed4893914ca0", - "sha256:540b3bee0711aac2e99bda4fa0a46dbcd8c74941666bfc1ef9236b1a64eeffd9", - "sha256:57ead89128dee9609119c93d3926c7a2add451453063147900408a50144598c6", - "sha256:5c4276fdcbccdf1e3c1756c7aeb8395e9a36874fa4d30860e7694f43d325ae13", - "sha256:5da187bebe38030325e1c0b5b8a804d489410be2d384c0ef3ba39493c67eb51e", - "sha256:5e545a48f919e40079b0efe7b0e081c74b96f9ef25b9c1ff4cdbd95764426b58", - "sha256:603b9f1b8e93e8b494d3e89320c410679e21018e48b6cbc77280f5db71f17dc0", - "sha256:695a6bcaf9e12b1e471dfce96bbecf22a1487adc2ac6106b15960a2b51b97f5d", - "sha256:715294cd2246b39a8edca464a8366eb635f17213e4a6b9e74e52d8b877a8cb63", - "sha256:7ebaa8800c376bcdae596fb1372cb4232a5ef957619d35839520d2786f2debb9", - "sha256:856c7fb31d247ce713d60116375e1f8153d0291ab5e92cca7d8833a524ba9991", - "sha256:8c6e25e9cc4961bb2abb1777c6fa9d0fa2d9b014beb3276cebe69996ff162b78", - "sha256:9207fdedc7e789a3dcaca628176b80c82fbed9ae0997210738cbb12536a56699", - "sha256:93f5fed1c9445fb7afe20450cdaf94b0e0356d47cc75008105be89c6a2e417b1", - "sha256:9ce5e5209f8406ffc2b058b1293cce7a954911bb7991e623564d489197c9ba30", - "sha256:a0674f246ad5e1571ef29d4c5ec1d6ecabe9e6c424ad0d6fee46b914d5d24d69", - "sha256:b2f9172e4d6358f33ecce6a4339b5960f9f83eab67ea244baa812737793826b7", - "sha256:b8a8a31b9e8860634adbca30fea1d0c7f08e208b3d7611f3e580e5f20992e5d7", - "sha256:b8d8497091c1dc8705d1575c71e908a93b1f127a174b2d472020f3d84263ac28", - "sha256:c111ac9abdf715762e4fb87395e59d61c0fbb6ce79eb2e24167700b6cfa8ba79", - "sha256:c4b78356074fcaac04ecb4de289f11d506e438859877670992ece11f9c90f37b", - "sha256:c541b2b49c6638f2b5beb9316726db84a8d1c132bf31b942dae1f9c7f6ad3b92", - "sha256:c8435959321cf8aec867bbad54b83b7fb8343204b530d85d9ea7a1f5329d5ac2", - "sha256:ccb77faeaad99e99c6c444d04862c6cf604204fe0a07d4c8f9cbf2c9012d7d5a", - "sha256:e272ed97d20b026f4f25a012b25d7d7672a60e4f72b9ca385239d693cd91b2d5", - "sha256:e57acb89bd55943c8d8bf813763d20b9099cc7165c0f16b707631a7654be9cad", - "sha256:e93acd1f603a0c1786e0841f066ae7cef014cf4750e3cd06fd03cfdf46361419" - ], - "index": "pypi", - "version": "==0.29.21" - }, - "falcon": { - "hashes": [ - "sha256:18157af2a4fc3feedf2b5dcc6196f448639acf01c68bc33d4d5a04c3ef87f494", - "sha256:24adcd2b29a8ffa9d552dc79638cd21736a3fb04eda7d102c6cebafdaadb88ad", - "sha256:54f2cb4b687035b2a03206dbfc538055cc48b59a953187b0458aa1b574d47b53", - "sha256:59d1e8c993b9a37ea06df9d72cf907a46cc8063b30717cdac2f34d1658b6f936", - "sha256:733033ec80c896e30a43ab3e776856096836787197a44eb21022320a61311983", - "sha256:74cf1d18207381c665b9e6292d65100ce146d958707793174b03869dc6e614f4", - "sha256:95bf6ce986c1119aef12c9b348f4dee9c6dcc58391bdd0bc2b0bf353c2b15986", - "sha256:9712975adcf8c6e12876239085ad757b8fdeba223d46d23daef82b47658f83a9", - "sha256:a5ebb22a04c9cc65081938ee7651b4e3b4d2a28522ea8ec04c7bdd2b3e9e8cd8", - "sha256:aa184895d1ad4573fbfaaf803563d02f019ebdf4790e41cc568a330607eae439", - "sha256:e3782b7b92fefd46a6ad1fd8fe63fe6c6f1b7740a95ca56957f48d1aee34b357", - "sha256:e9efa0791b5d9f9dd9689015ea6bce0a27fcd5ecbcd30e6d940bffa4f7f03389", - "sha256:eea593cf466b9c126ce667f6d30503624ef24459f118c75594a69353b6c3d5fc", - "sha256:f93351459f110b4c1ee28556aef9a791832df6f910bea7b3f616109d534df06b" - ], - "index": "pypi", - "version": "==2.0.0" - }, - "humanize": { - "hashes": [ - "sha256:6790d9ba139ce09761ae901be9b22bd32a131fa65ecc82cdfc4d86f377f7395d", - "sha256:fd3eb915310335c63a54d4507289ecc7b3a7454cd2c22ac5086d061a3cbfd592" - ], - "index": "pypi", - "version": "==3.1.0" - }, - "idna": { - "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.10" - }, - "jack-client": { - "hashes": [ - "sha256:0214d9366e0a65bf0a99866990aae5a443afb3c283006d236d8cebcb769a59b8", - "sha256:31cbdcc90cd303997a3ea540f352d89b23c2f422cbf7c197292a1b8e1ca21aca" - ], - "index": "pypi", - "version": "==0.5.3" - }, - "mido": { - "hashes": [ - "sha256:c4a7d5528fefa3d3dcaa2056d4bc873e2c96a395658d15af5a89c8c3fa7c7acc", - "sha256:fc6364efa028c8405166f63e6a83cbc6c17aaeac2c28680abe64ae48703a89dd" - ], - "index": "pypi", - "version": "==1.2.9" - }, - "pyalsa": { - "editable": true, - "git": "https://github.com/alsa-project/alsa-python.git", - "ref": "e1bc1c27b6286dcf2d6bf4955650cf5729cf9b7a" - }, - "pycairo": { - "hashes": [ - "sha256:2088100a099c09c5e90bf247409ce6c98f51766b53bd13f96d6aac7addaa3e66", - "sha256:273a33c56aba724ec42fe1d8f94c86c2e2660c1277470be9b04e5113d7c5b72d", - "sha256:5695a10cb7f9ae0d01f665b56602a845b0a8cb17e2123bfece10c2e58552468c", - "sha256:57166119e424d71eccdba6b318bd731bdabd17188e2ba10d4f315f7bf16ace3f", - "sha256:57a768f4edc8a9890d98070dd473a812ac3d046cef4bc1c817d68024dab9a9b4", - "sha256:8cfa9578b745fb9cf2915ec580c2c50ebc2da00eac2cf4c4b54b63aa19da4b77", - "sha256:a942614923b88ae75c794506d5c426fba9c46a055d3fdd3b8db7046b75c079cc", - "sha256:ceb1edcbeb48dabd5fbbdff2e4b429aa88ddc493d6ebafe78d94b050ac0749e2", - "sha256:e5a3433690c473e073a9917dc8f1fc7dc8b9af7b201bf372894b8ad70d960c6d" - ], - "markers": "python_version >= '3.6' and python_version < '4'", - "version": "==1.20.0" - }, - "pycparser": { - "hashes": [ - "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", - "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.20" - }, - "pygobject": { - "hashes": [ - "sha256:051b950f509f2e9f125add96c1493bde987c527f7a0c15a1f7b69d6d1c3cd8e6" - ], - "index": "pypi", - "version": "==3.38.0" - }, - "pyliblo": { - "hashes": [ - "sha256:fc67f1950b827272b00f9f0dc4ed7113c0ccef0c1c09e9976dead40ebbf1798f" - ], - "index": "pypi", - "version": "==0.10.0" - }, - "pyqt5": { - "hashes": [ - "sha256:17a6d5258796bae16e447aa3efa00258425c09cf88ef68238762628a5dde7c6f", - "sha256:4e47021c2b8e89a3bc64247dfb224144e5c8d77e3ab44f3842d120aab6b3cbd4", - "sha256:b1ea7e82004dc7b311d1e29df2f276461016e2d180e10c73805ace4376125ed9", - "sha256:b9e7cc3ec69f80834f3f7507478c77e4d42411d5e9e557350e61b2660d12abc2", - "sha256:d9a76b850246d08da9863189ecb98f6c2aa9b4d97a3e85e29330a264aed0f9a1" - ], - "index": "pypi", - "version": "==5.15.1" - }, - "pyqt5-sip": { - "hashes": [ - "sha256:0304ca9114b9817a270f67f421355075b78ff9fc25ac58ffd72c2601109d2194", - "sha256:0cd969be528c27bbd4755bd323dff4a79a8fdda28215364e6ce3e069cb56c2a9", - "sha256:2f35e82fd7ec1e1f6716e9154721c7594956a4f5bd4f826d8c6a6453833cc2f0", - "sha256:30e944db9abee9cc757aea16906d4198129558533eb7fadbe48c5da2bd18e0bd", - "sha256:34dcd29be47553d5f016ff86e89e24cbc5eebae92eb2f96fb32d2d7ba028c43c", - "sha256:5a011aeff89660622a6d5c3388d55a9d76932f3b82c95e82fc31abd8b1d2990d", - "sha256:6c1ebee60f1d2b3c70aff866b7933d8d8d7646011f7c32f9321ee88c290aa4f9", - "sha256:7b81382ce188d63890a0e35abe0f9bb946cabc873a31873b73583b0fc84ac115", - "sha256:832fd60a264de4134c2824d393320838f3ab648180c9c357ec58a74524d24507", - "sha256:84ba7746762bd223bed22428e8561aa267a229c28344c2d28c5d5d3f8970cffb", - "sha256:9312ec47cac4e33c11503bc1cbeeb0bdae619620472f38e2078c5a51020a930f", - "sha256:a1b8ef013086e224b8e86c93f880f776d01b59195bdfa2a8e0b23f0480678fec", - "sha256:a29e2ac399429d3b7738f73e9081e50783e61ac5d29344e0802d0dcd6056c5a2", - "sha256:b6d42250baec52a5f77de64e2951d001c5501c3a2df2179f625b241cbaec3369", - "sha256:bb5a87b66fc1445915104ee97f7a20a69decb42f52803e3b0795fa17ff88226c", - "sha256:c317ab1263e6417c498b81f5c970a9b1af7acefab1f80b4cc0f2f8e661f29fc5", - "sha256:c9800729badcb247765e4ffe2241549d02da1fa435b9db224845bc37c3e99cb0", - "sha256:c9d6d448c29dc6606bb7974696608f81f4316c8234f7c7216396ed110075e777", - "sha256:da9c9f1e65b9d09e73bd75befc82961b6b61b5a3b9d0a7c832168e1415f163c6", - "sha256:ed897c58acf4a3cdca61469daa31fe6e44c33c6c06a37c3f21fab31780b3b86a", - "sha256:f168f0a7f32b81bfeffdf003c36f25d81c97dee5eb67072a5183e761fe250f13" - ], - "markers": "python_version >= '3.5'", - "version": "==12.8.1" - }, - "python-rtmidi": { - "hashes": [ - "sha256:107aa9d57c3b7190e65710e436a1ac2db34f22b2e82f459397ebb14dc6d3f63b", - "sha256:1f7a8c6f19a56e56c4314cd562b5595ad4be645617655887e319c0c113d510ab", - "sha256:2151a856964b5a99f823d336102b202b0100cab381aee6582eb7b166cd9672da", - "sha256:565da90d8e2fc00a7e6c608ea86427c4a2ce69db44adda4be71df95fffe3d262", - "sha256:5821f8726c62b3da00f70c82443eb00ee09f4d905c09954c65869e372d700373", - "sha256:5e2eb72986b882605f476cddb4202e95e0aaa7ea33c49b99bc2c8243db864e7f", - "sha256:615eb4426a5df90275616aab8d6382185a7c6be2a09ce44c7f058c4493bdb635", - "sha256:87cfac386769a4ad415432d94277ca5526f082050d8bc364bd3038e811ea3511", - "sha256:9141a328e82ebf59519133f24a630283768ffaf69b5e2ade298d1e6b01f4ab69", - "sha256:919f62b4e4f762064b8e6070f13b76c5490a44405f13f75dd24901658ac370a8", - "sha256:c2e8c3a077928ab5996dc6a0ad7d3723235b76458177c2d7297cb9706bedc779", - "sha256:f992685ec4739a51a080069f35355b1769e47b7ed2351008014a6085a08f95f5" - ], - "index": "pypi", - "version": "==1.4.6" - }, - "requests": { - "hashes": [ - "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", - "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" - ], - "index": "pypi", - "version": "==2.24.0" - }, - "sortedcontainers": { - "hashes": [ - "sha256:4e73a757831fc3ca4de2859c422564239a31d8213d09a2a666e375807034d2ba", - "sha256:c633ebde8580f241f274c1f8994a665c0e54a17724fecd0cae2f079e09c36d3f" - ], - "index": "pypi", - "version": "==2.2.2" - }, - "urllib3": { - "hashes": [ - "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2", - "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.25.11" - } - }, - "develop": { - "html5lib": { - "hashes": [ - "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", - "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f" - ], - "index": "pypi", - "version": "==1.1" - }, - "six": { - "hashes": [ - "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", - "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.15.0" - }, - "webencodings": { - "hashes": [ - "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", - "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" - ], - "version": "==0.5.1" - } - } -} diff --git a/linux-show-player b/linux-show-player deleted file mode 100755 index 73fd85b24..000000000 --- a/linux-show-player +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python3 - -from lisp.main import main - -main() diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 000000000..553bdcd6e --- /dev/null +++ b/poetry.lock @@ -0,0 +1,466 @@ +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "certifi" +version = "2020.12.5" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "cffi" +version = "1.14.5" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "cython" +version = "0.29.22" +description = "The Cython compiler for writing C extensions for the Python language." +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "falcon" +version = "2.0.0" +description = "An unladen web framework for building APIs and app backends." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "humanize" +version = "3.2.0" +description = "Python humanize utilities" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +tests = ["freezegun", "pytest", "pytest-cov"] + +[[package]] +name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "jack-client" +version = "0.5.3" +description = "JACK Audio Connection Kit (JACK) Client for Python" +category = "main" +optional = false +python-versions = ">=3" + +[package.dependencies] +CFFI = ">=1.0" + +[package.extras] +numpy = ["numpy"] + +[[package]] +name = "mido" +version = "1.2.9" +description = "MIDI Objects for Python" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +dev = ["check-manifest (>=0.35)", "flake8 (>=3.4.1)", "pytest (>=3.2.2)", "sphinx (>=1.6.3)", "tox (>=2.8.2)"] +ports = ["python-rtmidi (>=1.1.0)"] + +[[package]] +name = "packaging" +version = "20.9" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +name = "pyalsa" +version = "1.1.6" +description = "Python binding for the ALSA library." +category = "main" +optional = false +python-versions = "*" +develop = false + +[package.source] +type = "git" +url = "https://github.com/alsa-project/alsa-python.git" +reference = "v1.1.6" +resolved_reference = "e1bc1c27b6286dcf2d6bf4955650cf5729cf9b7a" + +[[package]] +name = "pycairo" +version = "1.20.0" +description = "Python interface for cairo" +category = "main" +optional = false +python-versions = ">=3.6, <4" + +[[package]] +name = "pycparser" +version = "2.20" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pygobject" +version = "3.38.0" +description = "Python bindings for GObject Introspection" +category = "main" +optional = false +python-versions = ">=3.5, <4" + +[package.dependencies] +pycairo = ">=1.11.1" + +[[package]] +name = "pyliblo" +version = "0.10.0" +description = "Python bindings for the liblo OSC library" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pyqt5" +version = "5.15.3" +description = "Python bindings for the Qt cross platform application toolkit" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +PyQt5-Qt = ">=5.15" +PyQt5-sip = ">=12.8,<13" + +[[package]] +name = "pyqt5-qt" +version = "5.15.2" +description = "The subset of a Qt installation needed by PyQt5." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyqt5-sip" +version = "12.8.1" +description = "The sip module support for PyQt5" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "python-rtmidi" +version = "1.4.7" +description = "A Python binding for the RtMidi C++ library implemented using Cython." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "requests" +version = "2.25.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<5" +idna = ">=2.5,<3" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] + +[[package]] +name = "sortedcontainers" +version = "2.3.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "urllib3" +version = "1.26.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.6" +content-hash = "5183623707dfe3665d33d45fe8dc5ea4524e6bf3740448b4cf1bffa9211529eb" + +[metadata.files] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +certifi = [ + {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, + {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, +] +cffi = [ + {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, + {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, + {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, + {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, + {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, + {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, + {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, + {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, + {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, + {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, + {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, + {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, + {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, + {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, + {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, + {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, + {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, + {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, + {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, + {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, + {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, + {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, + {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, + {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, + {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, + {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, +] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] +cython = [ + {file = "Cython-0.29.22-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f89c19480d137026f04a34cbd54ab0fb328b29005c91d3a36d06b9c4710152c1"}, + {file = "Cython-0.29.22-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:667b169c89c4b8eb79d35822508ed8d77b9421af085142fad20bfef775a295d2"}, + {file = "Cython-0.29.22-cp27-cp27m-win32.whl", hash = "sha256:59c950513c485f1e8da0e04a2a91f71508c5e98b6912ce66588c6aee72d8e4d1"}, + {file = "Cython-0.29.22-cp27-cp27m-win_amd64.whl", hash = "sha256:15b2ba47858d7949a036d4ba6e838120bf3c06983769e99d12867a2c8cd0cd91"}, + {file = "Cython-0.29.22-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d76feb00f754d86e6d48f39b59a4e9a8fcfed1e6e3e433d18e3677ff19ff2911"}, + {file = "Cython-0.29.22-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e8c84ad389b3f36433e1be88dd7a1e36db4ab5b4848f5899cbd0a1cdc443b925"}, + {file = "Cython-0.29.22-cp34-cp34m-win32.whl", hash = "sha256:105d813eedf276588a02a240ece2f7bca8235aee9bb48e25293410c3c1ac7230"}, + {file = "Cython-0.29.22-cp34-cp34m-win_amd64.whl", hash = "sha256:af646d37e23fc3ba217c587096fac4e57006d8173d2a2365763287071e0eec2d"}, + {file = "Cython-0.29.22-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:d73a4851d1dbdcc70e619ccb104877d5e523d0fc5aef6fb6fbba4344c1f60cae"}, + {file = "Cython-0.29.22-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:88bebb8803adac0b64426fd261752925e8bf301b1986078e9506670bcbb5307f"}, + {file = "Cython-0.29.22-cp35-cp35m-win32.whl", hash = "sha256:7265abb33f154663a052c47213b978ec703f89a938bca38f425f25d4e8ba2211"}, + {file = "Cython-0.29.22-cp35-cp35m-win_amd64.whl", hash = "sha256:f7064df99094b4fd09c9ebcca6c94a652fac04c7dce514a4f2ab6ce23fcd0ba4"}, + {file = "Cython-0.29.22-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:97511722515b66851d3f77d64b1970a97e5114c4007efd5a0a307936e156a24c"}, + {file = "Cython-0.29.22-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:ab4c34d8693a62f1b8261a131784a3db397ceaa03ea90a5799574623acaa2c2c"}, + {file = "Cython-0.29.22-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6609babc68d37bb92a1b85ebd867405f720aeb2a6af946f6bd7f71b22dbb91cf"}, + {file = "Cython-0.29.22-cp36-cp36m-win32.whl", hash = "sha256:40a87c9ecd0b1b485804c70b16427e88bd07bff9f270d022d872869ff4d6ac6e"}, + {file = "Cython-0.29.22-cp36-cp36m-win_amd64.whl", hash = "sha256:ba7f34e07ca0d1ce4ba8d9e3da6d2eb0b1ac6cb9896d2c821b28a9ac9504bcfe"}, + {file = "Cython-0.29.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a8aee32da407f215652959fc6568277cfd35a24714a5238c8a9778bf34d91ace"}, + {file = "Cython-0.29.22-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:92c028e0d4ac9e32b2cf6c970f7f6c5c3aaa87f011798201ef56746705a8f01a"}, + {file = "Cython-0.29.22-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c4f2c8cceffd8403468b542570826174564fe2c6549dd199a75c13720add4981"}, + {file = "Cython-0.29.22-cp37-cp37m-win32.whl", hash = "sha256:761b965b5abcea072ba642dbb740e44532bc88618f34b605c05de1d6088b1cdd"}, + {file = "Cython-0.29.22-cp37-cp37m-win_amd64.whl", hash = "sha256:a6f5bf7fece95dd62ba13a9edb7d3ecc1a9097e6f0fde78c5fad763163157472"}, + {file = "Cython-0.29.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bbc48be45ee9eba2d0268bf616220cfb46d998ad85524f3cf752d55eb6dd3379"}, + {file = "Cython-0.29.22-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3ce10572a6b7fd5baa755f11cdf09830ae518e6f837aa38c31ec534b1f874fd4"}, + {file = "Cython-0.29.22-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b53c9d7b545c70f462408000affdd330cb87d05f83a58a9ccd72d19c8df80d56"}, + {file = "Cython-0.29.22-cp38-cp38-win32.whl", hash = "sha256:fedecbd2ad6535e30dd3b43ca9b493a1930d5167257a3fb60687bd425f6fdc54"}, + {file = "Cython-0.29.22-cp38-cp38-win_amd64.whl", hash = "sha256:182e78d75515e3d50f31cfde501fbf2af7ee0698e8149f41048db5d3c98ffc8f"}, + {file = "Cython-0.29.22-cp39-cp39-manylinux1_i686.whl", hash = "sha256:5cc5e72a106d7abc12b6e58883c914bce0b2031df6035014094a15593052db12"}, + {file = "Cython-0.29.22-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:dc42b78b62815abea19a37d186011cca57f72ec509f56fa6c43a71b2e8c6a24f"}, + {file = "Cython-0.29.22-py2.py3-none-any.whl", hash = "sha256:f5f57b2e13cc3aa95526145ffa551df529f04bf5d137c59350e7accff9362dc0"}, + {file = "Cython-0.29.22.tar.gz", hash = "sha256:df6b83c7a6d1d967ea89a2903e4a931377634a297459652e4551734c48195406"}, +] +falcon = [ + {file = "falcon-2.0.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:733033ec80c896e30a43ab3e776856096836787197a44eb21022320a61311983"}, + {file = "falcon-2.0.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f93351459f110b4c1ee28556aef9a791832df6f910bea7b3f616109d534df06b"}, + {file = "falcon-2.0.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e9efa0791b5d9f9dd9689015ea6bce0a27fcd5ecbcd30e6d940bffa4f7f03389"}, + {file = "falcon-2.0.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:59d1e8c993b9a37ea06df9d72cf907a46cc8063b30717cdac2f34d1658b6f936"}, + {file = "falcon-2.0.0-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:a5ebb22a04c9cc65081938ee7651b4e3b4d2a28522ea8ec04c7bdd2b3e9e8cd8"}, + {file = "falcon-2.0.0-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:95bf6ce986c1119aef12c9b348f4dee9c6dcc58391bdd0bc2b0bf353c2b15986"}, + {file = "falcon-2.0.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:aa184895d1ad4573fbfaaf803563d02f019ebdf4790e41cc568a330607eae439"}, + {file = "falcon-2.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:74cf1d18207381c665b9e6292d65100ce146d958707793174b03869dc6e614f4"}, + {file = "falcon-2.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24adcd2b29a8ffa9d552dc79638cd21736a3fb04eda7d102c6cebafdaadb88ad"}, + {file = "falcon-2.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:18157af2a4fc3feedf2b5dcc6196f448639acf01c68bc33d4d5a04c3ef87f494"}, + {file = "falcon-2.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e3782b7b92fefd46a6ad1fd8fe63fe6c6f1b7740a95ca56957f48d1aee34b357"}, + {file = "falcon-2.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:9712975adcf8c6e12876239085ad757b8fdeba223d46d23daef82b47658f83a9"}, + {file = "falcon-2.0.0-py2.py3-none-any.whl", hash = "sha256:54f2cb4b687035b2a03206dbfc538055cc48b59a953187b0458aa1b574d47b53"}, + {file = "falcon-2.0.0.tar.gz", hash = "sha256:eea593cf466b9c126ce667f6d30503624ef24459f118c75594a69353b6c3d5fc"}, +] +humanize = [ + {file = "humanize-3.2.0-py3-none-any.whl", hash = "sha256:d47d80cd47c1511ed3e49ca5f10c82ed940ea020b45b49ab106ed77fa8bb9d22"}, + {file = "humanize-3.2.0.tar.gz", hash = "sha256:ab69004895689951b79f2ae4fdd6b8127ff0c180aff107856d5d98119a33f026"}, +] +idna = [ + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, +] +jack-client = [ + {file = "JACK-Client-0.5.3.tar.gz", hash = "sha256:31cbdcc90cd303997a3ea540f352d89b23c2f422cbf7c197292a1b8e1ca21aca"}, + {file = "JACK_Client-0.5.3-py3-none-any.whl", hash = "sha256:0214d9366e0a65bf0a99866990aae5a443afb3c283006d236d8cebcb769a59b8"}, +] +mido = [ + {file = "mido-1.2.9-py2.py3-none-any.whl", hash = "sha256:fc6364efa028c8405166f63e6a83cbc6c17aaeac2c28680abe64ae48703a89dd"}, + {file = "mido-1.2.9.tar.gz", hash = "sha256:c4a7d5528fefa3d3dcaa2056d4bc873e2c96a395658d15af5a89c8c3fa7c7acc"}, +] +packaging = [ + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, +] +pyalsa = [] +pycairo = [ + {file = "pycairo-1.20.0-cp36-cp36m-win32.whl", hash = "sha256:e5a3433690c473e073a9917dc8f1fc7dc8b9af7b201bf372894b8ad70d960c6d"}, + {file = "pycairo-1.20.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a942614923b88ae75c794506d5c426fba9c46a055d3fdd3b8db7046b75c079cc"}, + {file = "pycairo-1.20.0-cp37-cp37m-win32.whl", hash = "sha256:8cfa9578b745fb9cf2915ec580c2c50ebc2da00eac2cf4c4b54b63aa19da4b77"}, + {file = "pycairo-1.20.0-cp37-cp37m-win_amd64.whl", hash = "sha256:273a33c56aba724ec42fe1d8f94c86c2e2660c1277470be9b04e5113d7c5b72d"}, + {file = "pycairo-1.20.0-cp38-cp38-win32.whl", hash = "sha256:2088100a099c09c5e90bf247409ce6c98f51766b53bd13f96d6aac7addaa3e66"}, + {file = "pycairo-1.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:ceb1edcbeb48dabd5fbbdff2e4b429aa88ddc493d6ebafe78d94b050ac0749e2"}, + {file = "pycairo-1.20.0-cp39-cp39-win32.whl", hash = "sha256:57a768f4edc8a9890d98070dd473a812ac3d046cef4bc1c817d68024dab9a9b4"}, + {file = "pycairo-1.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:57166119e424d71eccdba6b318bd731bdabd17188e2ba10d4f315f7bf16ace3f"}, + {file = "pycairo-1.20.0.tar.gz", hash = "sha256:5695a10cb7f9ae0d01f665b56602a845b0a8cb17e2123bfece10c2e58552468c"}, +] +pycparser = [ + {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, + {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +] +pygobject = [ + {file = "PyGObject-3.38.0.tar.gz", hash = "sha256:051b950f509f2e9f125add96c1493bde987c527f7a0c15a1f7b69d6d1c3cd8e6"}, +] +pyliblo = [ + {file = "pyliblo-0.10.0.tar.gz", hash = "sha256:fc67f1950b827272b00f9f0dc4ed7113c0ccef0c1c09e9976dead40ebbf1798f"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pyqt5 = [ + {file = "PyQt5-5.15.3-cp36.cp37.cp38.cp39-abi3-macosx_10_13_intel.whl", hash = "sha256:69fdceed983ebd388f44ceab9b8a6c2c086c79c705182fcc7b06837e2ccf7ba3"}, + {file = "PyQt5-5.15.3-cp36.cp37.cp38.cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:8b9e07094382d51dc34f63c5a417b735dae1ad934a9dd9bc08bbc0a39452bfe8"}, + {file = "PyQt5-5.15.3-cp36.cp37.cp38.cp39-none-win32.whl", hash = "sha256:ef2682dc829d6603fb5c958274d721fb076fd84a88fcb8f9fc7655cbb72b2bfc"}, + {file = "PyQt5-5.15.3-cp36.cp37.cp38.cp39-none-win_amd64.whl", hash = "sha256:4e0fc6993df120e686528de46f2e002e930e24f99f103788724f0bd8bec9b4f7"}, + {file = "PyQt5-5.15.3.tar.gz", hash = "sha256:965ba50e7029b37f218a54ace24e87c77db3e5a9f0b83baeb21fb57b4154b838"}, +] +pyqt5-qt = [ + {file = "PyQt5_Qt-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:962511bfdc847f0746e1de92499cbb1ae127ca5ec356f009a6d9f924fe1230d5"}, + {file = "PyQt5_Qt-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:dec2e88fbfabc0d3365bb4007c88c75fc99fc75cfb418399aeb5acd1d5f7d484"}, + {file = "PyQt5_Qt-5.15.2-py3-none-win32.whl", hash = "sha256:8aba4f0a245c42f186a235c47011ee422a14b949e52fc64e697b10d1c433ff39"}, + {file = "PyQt5_Qt-5.15.2-py3-none-win_amd64.whl", hash = "sha256:756b8c055033b2d96d18e74e1380bdb64984b150879fa92cd62d327f78636153"}, +] +pyqt5-sip = [ + {file = "PyQt5_sip-12.8.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:bb5a87b66fc1445915104ee97f7a20a69decb42f52803e3b0795fa17ff88226c"}, + {file = "PyQt5_sip-12.8.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a29e2ac399429d3b7738f73e9081e50783e61ac5d29344e0802d0dcd6056c5a2"}, + {file = "PyQt5_sip-12.8.1-cp35-cp35m-win32.whl", hash = "sha256:0304ca9114b9817a270f67f421355075b78ff9fc25ac58ffd72c2601109d2194"}, + {file = "PyQt5_sip-12.8.1-cp35-cp35m-win_amd64.whl", hash = "sha256:84ba7746762bd223bed22428e8561aa267a229c28344c2d28c5d5d3f8970cffb"}, + {file = "PyQt5_sip-12.8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:7b81382ce188d63890a0e35abe0f9bb946cabc873a31873b73583b0fc84ac115"}, + {file = "PyQt5_sip-12.8.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b6d42250baec52a5f77de64e2951d001c5501c3a2df2179f625b241cbaec3369"}, + {file = "PyQt5_sip-12.8.1-cp36-cp36m-win32.whl", hash = "sha256:6c1ebee60f1d2b3c70aff866b7933d8d8d7646011f7c32f9321ee88c290aa4f9"}, + {file = "PyQt5_sip-12.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:34dcd29be47553d5f016ff86e89e24cbc5eebae92eb2f96fb32d2d7ba028c43c"}, + {file = "PyQt5_sip-12.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ed897c58acf4a3cdca61469daa31fe6e44c33c6c06a37c3f21fab31780b3b86a"}, + {file = "PyQt5_sip-12.8.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a1b8ef013086e224b8e86c93f880f776d01b59195bdfa2a8e0b23f0480678fec"}, + {file = "PyQt5_sip-12.8.1-cp37-cp37m-win32.whl", hash = "sha256:0cd969be528c27bbd4755bd323dff4a79a8fdda28215364e6ce3e069cb56c2a9"}, + {file = "PyQt5_sip-12.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c9800729badcb247765e4ffe2241549d02da1fa435b9db224845bc37c3e99cb0"}, + {file = "PyQt5_sip-12.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9312ec47cac4e33c11503bc1cbeeb0bdae619620472f38e2078c5a51020a930f"}, + {file = "PyQt5_sip-12.8.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2f35e82fd7ec1e1f6716e9154721c7594956a4f5bd4f826d8c6a6453833cc2f0"}, + {file = "PyQt5_sip-12.8.1-cp38-cp38-win32.whl", hash = "sha256:da9c9f1e65b9d09e73bd75befc82961b6b61b5a3b9d0a7c832168e1415f163c6"}, + {file = "PyQt5_sip-12.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:832fd60a264de4134c2824d393320838f3ab648180c9c357ec58a74524d24507"}, + {file = "PyQt5_sip-12.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c317ab1263e6417c498b81f5c970a9b1af7acefab1f80b4cc0f2f8e661f29fc5"}, + {file = "PyQt5_sip-12.8.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c9d6d448c29dc6606bb7974696608f81f4316c8234f7c7216396ed110075e777"}, + {file = "PyQt5_sip-12.8.1-cp39-cp39-win32.whl", hash = "sha256:5a011aeff89660622a6d5c3388d55a9d76932f3b82c95e82fc31abd8b1d2990d"}, + {file = "PyQt5_sip-12.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:f168f0a7f32b81bfeffdf003c36f25d81c97dee5eb67072a5183e761fe250f13"}, + {file = "PyQt5_sip-12.8.1.tar.gz", hash = "sha256:30e944db9abee9cc757aea16906d4198129558533eb7fadbe48c5da2bd18e0bd"}, +] +python-rtmidi = [ + {file = "python-rtmidi-1.4.7.tar.gz", hash = "sha256:d7dbc2b174b09015dfbee449a672a072aa72b367be40b13e04ee35a2e2e399e3"}, + {file = "python_rtmidi-1.4.7-cp27-cp27m-win32.whl", hash = "sha256:b3cc68264297c9ef4e3d02a45a698c4ca88d4ad1415d009fbf740e4a5ff0bbdb"}, + {file = "python_rtmidi-1.4.7-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:ac61260bd0d63c9bde5b5c9f3585f0fffbca427cc381a99a3cdbb9a5503f48d0"}, + {file = "python_rtmidi-1.4.7-cp36-cp36m-win32.whl", hash = "sha256:18f1d2873f9514fd3f2de3ca9a1722c470fad0505f79738d03ee1d13ec3a846c"}, + {file = "python_rtmidi-1.4.7-cp36-cp36m-win_amd64.whl", hash = "sha256:2a2bf0a9526fadbe4de8e438899f0932e497b03f0b549eaf329cb05217b76cfa"}, + {file = "python_rtmidi-1.4.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:de321ee7c68485563a0777b72ea9db5fc8bee6d413ddc125dc881e904bf9c602"}, + {file = "python_rtmidi-1.4.7-cp37-cp37m-win32.whl", hash = "sha256:fd57b90b28728b6781fdbac78f71b2538b127db5d0fd8aa9304efc7b58daf994"}, + {file = "python_rtmidi-1.4.7-cp37-cp37m-win_amd64.whl", hash = "sha256:179b2acaa3b2498f71633988799fc392a77bfe983a66b94f70bd35615c94fa33"}, + {file = "python_rtmidi-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:266ad15fbd97713d4bef07a31ff72ff13cc8cdecc9f4518d2537b01d3f53e47d"}, + {file = "python_rtmidi-1.4.7-cp38-cp38-win32.whl", hash = "sha256:d1288a0faf12735afd842edba2362ec696f56b4b86917fa4e3113aee3976c84d"}, + {file = "python_rtmidi-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:96b1ca04b4aea71320d71fae81ab882767bd8d801763a54b20ef7e56d4d39a07"}, + {file = "python_rtmidi-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:922cdcf677e845750d763c114dd334d8ee54bd89f4729bf02bc6a8d3afe6456d"}, +] +requests = [ + {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, + {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, +] +sortedcontainers = [ + {file = "sortedcontainers-2.3.0-py2.py3-none-any.whl", hash = "sha256:37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f"}, + {file = "sortedcontainers-2.3.0.tar.gz", hash = "sha256:59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +urllib3 = [ + {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, + {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, +] diff --git a/pyproject.toml b/pyproject.toml index 57303d9a3..9fa620839 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,9 +4,55 @@ target-version = ['py36'] exclude = ''' /( \.git - | \.github - | \.tx | dist | docs )/ -''' \ No newline at end of file +''' + +[tool.poetry] +name = "linux-show-player" +version = "0.6.0.dev0" +description = "Cue player for stage productions" +authors = ["Francesco Ceruti "] +license = "GPLv3" + +readme = "README.md" + +homepage = "https://www.linux-show-player.org/" +repository = "https://github.com/FrancescoCeruti/linux-show-player" + +packages = [ + { include = "lisp" } +] + +exclude = [ + "lisp/i18n/ts/", + "lisp/ui/themes/*/assets/" +] + +[tool.poetry.dependencies] +python = "^3.6" +appdirs = "^1.4.1" +cython = "^0.29" +falcon = "^2.0" +jack-client = "^0.5" +mido = "^1.2" +pygobject = "^3.30" +pyliblo = "^0.10" +pyqt5 = "^5.6" +python-rtmidi = "^1.1" +requests = "^2.20" +sortedcontainers = "^2.0" +pyalsa = {git = "https://github.com/alsa-project/alsa-python.git", tag = "v1.1.6"} +humanize = "^3.1.0" + +[tool.poetry.dev-dependencies] +toml = "*" +packaging = "*" + +[tool.poetry.scripts] +linux-show-player = "lisp.main:main" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/scripts/Flatpak/pipenv_flatpak.py b/scripts/Flatpak/pipenv_flatpak.py deleted file mode 100644 index 91c344ac6..000000000 --- a/scripts/Flatpak/pipenv_flatpak.py +++ /dev/null @@ -1,212 +0,0 @@ -import html5lib -import json -import os -import requests -import urllib.parse -from concurrent.futures import ThreadPoolExecutor, wait - -# From pip project (https://github.com/pypa/pip) -BZ2_EXTENSIONS = (".tar.bz2", ".tbz") -XZ_EXTENSIONS = (".tar.xz", ".txz", ".tlz", ".tar.lz", ".tar.lzma") -ZIP_EXTENSIONS = (".zip", ".whl") -TAR_EXTENSIONS = (".tar.gz", ".tgz", ".tar") -ARCHIVE_EXTENSIONS = ( - ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS -) - -PLATFORMS_LINUX_x86_64 = ("linux_x86_64", "manylinux1_x86_64", "any") -PYPI_URL = "https://pypi.python.org/simple/" - - -def _flatpak_module_template(): - return { - "name": "", - "buildsystem": "simple", - "build-commands": [ - "pip3 --verbose install --no-index --no-deps --ignore-installed --no-build-isolation --prefix=${FLATPAK_DEST} " - ], - "sources": [{"type": "file", "url": "", "sha256": ""}], - } - - -def _is_like_archive(filename): - """Return whether the filename looks like an archive.""" - for ext in ARCHIVE_EXTENSIONS: - if filename.endswith(ext): - return True, ext - - return False, None - - -def _is_wheel(filename): - if filename.endswith(".whl"): - return True - - return False - - -def _wheel_versions(py_version): - return { - "py" + py_version.replace(".", ""), - "py" + py_version[0], - "cp" + py_version.replace(".", ""), - } - - -def _wheel_tags(filename): - """Get wheel tags from the filename, see pep-0425 - - Returns: - A tuple (version, set(python-tags), platform-tag) - """ - parts = filename[:-4].split("-") - return parts[1], set(parts[-3].split(".")), parts[-1] - - -def _find_candidate_downloads(package, whl_platforms, whl_versions): - for filename, url in package["candidates"]: - if _is_wheel(filename): - if package["wheel"] is not None: - # Take the first matching wheel, ignore others - continue - - version, python_tags, platform_tag = _wheel_tags(filename) - if version != package["version"]: - # print(' discard version {}'.format(version)) - continue - if platform_tag not in whl_platforms: - # print(' discard platform {}'.format(platform_tag)) - continue - if not python_tags.intersection(whl_versions): - # print(' discard python-version {}'.format(python_tags)) - continue - - url, fragment = urllib.parse.urldefrag(url) - if not fragment.startswith("sha256="): - continue - - package["wheel"] = (url, fragment[7:]) - if package["source"] is not None: - break - else: - is_archive, ext = _is_like_archive(filename) - if is_archive: - version = filename[: -(len(ext))].split("-")[-1] - if version != package["version"]: - # print(' discard version {}'.format(version)) - continue - - url, fragment = urllib.parse.urldefrag(url) - if not fragment.startswith("sha256="): - continue - - package["source"] = (url, fragment[7:]) - if package["wheel"] is not None: - break - - -def get_locked_packages(lock_file): - with open(lock_file, mode="r") as lf: - lock_content = json.load(lf) - - # Get the required packages - packages = [] - for name, info in lock_content.get("default", {}).items(): - if info.get("editable", False): - # Skip packages with an editable path (usually git) - continue - - packages.append( - { - "name": name, - "version": info["version"][2:], - "candidates": [], - "wheel": None, - "source": None, - } - ) - - return packages - - -def fetch_downloads_candidates(url_template, packages): - session = requests.session() - - def fetch(package): - print(" Download candidates for {}".format(package["name"])) - - # GET the page from the mirror - url = url_template.format(package["name"]) - resp = session.get(url) - - if resp.status_code != 200: - print( - " Cannot fetch candidates: error {}".format(resp.status_code) - ) - - # Parse HTML content - html = html5lib.parse(resp.content, namespaceHTMLElements=False) - - # Iterate all the provided downloads - for link in html.findall(".//a"): - package["candidates"].append((link.text, link.attrib["href"])) - - with ThreadPoolExecutor(max_workers=10) as executor: - wait(tuple(executor.submit(fetch, package) for package in packages)) - - session.close() - - -def filter_candidates(packages, whl_platforms, whl_versions): - for package in packages: - _find_candidate_downloads(package, whl_platforms, whl_versions) - # Cleanup - package.pop("candidates") - - -def generate(lock_file, py_version, platforms, base_url): - # Read the Pipfile.lock - print("=> Reading Pipfile.lock ...") - packages = get_locked_packages(lock_file) - print("=> Found {} required packages".format(len(packages))) - print("=> Fetching packages info from {}".format(base_url)) - fetch_downloads_candidates(base_url + "{}/", packages) - print("=> Filtering packages downloads candidates ...") - filter_candidates(packages, platforms, _wheel_versions(py_version)) - - # Insert python-packages modules - for package in packages: - if package["wheel"] is not None: - source = package["wheel"] - elif package["source"] is not None: - source = package["source"] - else: - print(" Skip: {}".format(package["name"])) - continue - - print(" Selected: {}".format(source[0])) - - module = _flatpak_module_template() - module["name"] = package["name"] - module["build-commands"][0] += os.path.basename( - urllib.parse.urlsplit(source[0]).path - ) - module["sources"][0]["url"] = source[0] - module["sources"][0]["sha256"] = source[1] - - yield module - - -if __name__ == "__main__": - import pprint - import platform - - for module in tuple( - generate( - "../../Pipfile.lock", - ".".join(platform.python_version_tuple[:2]), - PLATFORMS_LINUX_x86_64, - PYPI_URL, - ) - ): - pprint.pprint(module, indent=4, width=1000) diff --git a/scripts/Flatpak/prepare_flatpak.py b/scripts/Flatpak/prepare_flatpak.py deleted file mode 100644 index d7abc59e8..000000000 --- a/scripts/Flatpak/prepare_flatpak.py +++ /dev/null @@ -1,54 +0,0 @@ -import json -import os - -import pipenv_flatpak - -BRANCH = os.environ["BUILD_BRANCH"] -APP_ID = os.environ["FLATPAK_APP_ID"] -PY_VERSION = os.environ["FLATPAK_PY_VERSION"] -APP_MODULE = os.environ["FLATPAK_APP_MODULE"] -IGNORE_PACKAGES = [ - p.lower() for p in os.environ.get("FLATPAK_PY_IGNORE_PACKAGES", "").split() -] - -DIR = os.path.dirname(__file__) -TEMPLATE = os.path.join(DIR, "template.json") -DESTINATION = os.path.join(DIR, APP_ID + ".json") -LOCKFILE = "../../Pipfile.lock" - -print(">>> Generating flatpak manifest ....\n") -with open(TEMPLATE, mode="r") as f: - manifest = json.load(f) - -# Patch top-Level attributes -manifest["branch"] = BRANCH -if BRANCH != "master": - manifest["desktop-file-name-suffix"] = " ({})".format(BRANCH) - -# Patch the app-module to use the correct branch -app_index = 0 -for index, module in enumerate(manifest["modules"]): - if module["name"] == APP_MODULE: - app_index = index - module["sources"][0]["branch"] = BRANCH - break - -# Generate python-modules from Pipfile.lock, insert them before the app-module -for num, py_module in enumerate( - pipenv_flatpak.generate( - LOCKFILE, - PY_VERSION, - pipenv_flatpak.PLATFORMS_LINUX_x86_64, - pipenv_flatpak.PYPI_URL, - ) -): - if py_module["name"].lower() not in IGNORE_PACKAGES: - manifest["modules"].insert((app_index - 1) + num, py_module) - else: - print("=> Package ignored: {}".format(py_module["name"])) - -# Save the patched manifest -with open(DESTINATION, mode="w") as out: - json.dump(manifest, out, indent=4) - -print("\n>>> Done!") diff --git a/scripts/Flatpak/requirements.txt b/scripts/Flatpak/requirements.txt deleted file mode 100644 index 6504ea5a2..000000000 --- a/scripts/Flatpak/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -requests -html5lib \ No newline at end of file diff --git a/scripts/Flatpak/README b/scripts/flatpak/README similarity index 61% rename from scripts/Flatpak/README rename to scripts/flatpak/README index 68c8a8b63..164db7364 100644 --- a/scripts/Flatpak/README +++ b/scripts/flatpak/README @@ -2,7 +2,7 @@ (╯°□°)╯︵ ┻━┻ -The scripts/files in this folder allow to build a Flatpak package of Linux Show Player. +The scripts in this folder allow to build a Flatpak package of Linux Show Player. * build_flatpak.sh: Combine the commands from "functions.sh" to run a complete build @@ -10,16 +10,9 @@ The scripts/files in this folder allow to build a Flatpak package of Linux Show * functions.sh: A collection of commands to build the flatpak - * pipenv_flatpak.py: Starting from the project Pipfile.lock generate the - appropriate modules to install the python requirements + * patch-flatpak.py: Patch the flatpak manifest to use the current CI branch/commit - * prepare_flatpak.py: Starting from "template.json" outputs the complete - manifest. Uses "pipenv_flatpak.py" internally. - - * template.json: The base manifest file, contains all the metadata, and the - non-python packages (some exception here) - - * requirements.txt: Python packages required for the build + * poetry-flatpak.py: Starting from the project poetry.lock generate the appropriate modules to install the python requirements ====== REQUIREMENTS ====== @@ -43,7 +36,7 @@ of a longer build time. Until version 0.11 we must use protobuf < 3.2, or it will not build, if needed we may patch it, for now it's fine. -=== protobuf === +=== Protobuf === It's needed to "build" the OLA python package, which, can only be build with all the OLA framework (--enable-python-libs) @@ -53,7 +46,4 @@ consider the second already shipped with the python version ====== NOTES ====== -Some parts are hardcoded, because I'm lazy and this scripts are designed with -LiSP in mind. - Non-used features of the various packages should be disabled when possible. diff --git a/scripts/Flatpak/build_flatpak.sh b/scripts/flatpak/build-flatpak.sh similarity index 71% rename from scripts/Flatpak/build_flatpak.sh rename to scripts/flatpak/build-flatpak.sh index 53118dc7f..c4b61cdba 100755 --- a/scripts/Flatpak/build_flatpak.sh +++ b/scripts/flatpak/build-flatpak.sh @@ -5,9 +5,9 @@ set -o errexit # Use the error status of the first failure, rather than that of the last item in a pipeline. set -o pipefail -echo -e "\n\n" -echo "================= build_flatpak.sh =================" -echo -e "\n" +echo "" +echo "================= build-flatpak.sh =================" +echo "" # Make sure we are in the same directory of this file cd "${0%/*}" @@ -17,7 +17,6 @@ source functions.sh # Print relevant variables echo "<<< FLATPAK_INSTALL = "$FLATPAK_INSTALL -echo "<<< FLATPAK_PY_VERSION = "$FLATPAK_PY_VERSION echo "<<< FLATPAK_APP_ID = " $FLATPAK_APP_ID echo "<<< FLATPAK_APP_MODULE = " $FLATPAK_APP_MODULE @@ -26,6 +25,6 @@ flatpak_build_manifest flatpak_build flatpak_bundle -echo -e "\n" -echo "================= build_flatpak.sh =================" -echo -e "\n\n" \ No newline at end of file +echo "" +echo "================= build-flatpak.sh =================" +echo "" \ No newline at end of file diff --git a/scripts/Flatpak/config.sh b/scripts/flatpak/config.sh similarity index 73% rename from scripts/Flatpak/config.sh rename to scripts/flatpak/config.sh index d3c526d69..8c9230094 100755 --- a/scripts/Flatpak/config.sh +++ b/scripts/flatpak/config.sh @@ -6,8 +6,8 @@ export FLATPAK_SDK="org.gnome.Sdk" export FLATPAK_INSTALL="$FLATPAK_RUNTIME//$FLATPAK_RUNTIME_VERSION $FLATPAK_SDK//$FLATPAK_RUNTIME_VERSION" -export FLATPAK_PY_VERSION="3.8" -export FLATPAK_PY_IGNORE_PACKAGES="setuptools six pygobject pycairo pyqt5 sip" +export FLATPAK_PY_LOCKFILE="../../poetry.lock" +export FLATPAK_PY_IGNORE_PACKAGES="pygobject pycairo pyqt5 pyqt5-sip pyqt5-qt" export FLATPAK_APP_ID="org.linux_show_player.LinuxShowPlayer" export FLATPAK_APP_MODULE="linux-show-player" \ No newline at end of file diff --git a/scripts/Flatpak/functions.sh b/scripts/flatpak/functions.sh similarity index 89% rename from scripts/Flatpak/functions.sh rename to scripts/flatpak/functions.sh index 48e279ef7..c8dbd49f1 100755 --- a/scripts/Flatpak/functions.sh +++ b/scripts/flatpak/functions.sh @@ -11,27 +11,28 @@ function flatpak_build_manifest() { pip3 install --upgrade -r requirements.txt # Build manifest - python3 prepare_flatpak.py + python3 patch-manifest.py + python3 poetry-flatpak.py "$FLATPAK_PY_LOCKFILE" -e $FLATPAK_PY_IGNORE_PACKAGES deactivate } function flatpak_install_runtime() { - echo -e "\n" + echo "" echo "###################################################" echo "# Install flatpak (flathub) runtime and sdk #" echo "###################################################" - echo -e "\n" + echo "" flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo flatpak install --user --noninteractive --assumeyes flathub $FLATPAK_INSTALL } function flatpak_build() { - echo -e "\n" + echo "" echo "###########################" echo "# Build the flatpak #" echo "###########################" - echo -e "\n" + echo "" # Prepare the repository ostree init --mode=archive-z2 --repo=repo @@ -59,15 +60,15 @@ function flatpak_build_noexit_check() { } function flatpak_bundle() { - echo -e "\n" + echo "" echo "###############################" echo "# Create flatpak bundle #" echo "###############################" - echo -e "\n" + echo "" mkdir -p out # Create the bundle (without blocking the script) - flatpak build-bundle repo out/linux-show-payer.flatpak $FLATPAK_APP_ID $BUILD_BRANCH & + flatpak build-bundle repo out/linux-show-player.flatpak $FLATPAK_APP_ID $BUILD_BRANCH & # Print elapsed time watch_process $! } diff --git a/scripts/Flatpak/template.json b/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json similarity index 68% rename from scripts/Flatpak/template.json rename to scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json index 59eecab98..bcfe72109 100644 --- a/scripts/Flatpak/template.json +++ b/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json @@ -16,14 +16,14 @@ "--device=all" ], "cleanup-commands": [ - "pip3 uninstall --yes cython wheel packaging toml sip || true" + "pip3 uninstall --yes packaging toml poetry-core || true" ], "rename-appdata-file": "linuxshowplayer.appdata.xml", "rename-desktop-file": "linuxshowplayer.desktop", "rename-icon": "linuxshowplayer", "modules": [ { - "name": "Qt5", + "name": "qt5", "config-opts": [ "-confirm-license", "-opensource", "-shared", @@ -59,56 +59,39 @@ ] }, { - "name": "PyQt5", - "config-opts": [ - "--assume-shared", - "--concatenate", - "--confirm-license", - "--no-designer-plugin", - "--no-dist-info", - "--no-docstrings", - "--no-qml-plugin", - "--no-qsci-api", - "--no-stubs", - "--no-tools", - "QMAKE_CFLAGS_RELEASE='-I/usr/include/python3.8/'", - "QMAKE_CXXFLAGS_RELEASE='-I/usr/include/python3.8/'" + "name": "python-sip", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=\"${FLATPAK_DEST}\" sip" ], "sources": [ { - "type": "archive", - "url": "https://www.riverbankcomputing.com/static/Downloads/PyQt5/PyQt5-5.15.2.dev2010041344.tar.gz", - "sha256": "25153398487b652b4b0737e0a348dfd9a077c5865cfeb79b02470efa597cfe8e" - }, - { - "type": "script", - "commands": [ - "processed=`sed -e 's|prefix|sysroot|' <<< $@`", - "python3 configure.py $processed" - ], - "dest-filename": "configure" - } + "type": "file", + "url": "https://files.pythonhosted.org/packages/33/e9/27730ac17713c0a80d81d8f3bb56213f1549d96f9dc183fd16a7eec6287c/sip-5.5.0.tar.gz", + "sha256": "5d024c419b30fea8a6de8c71a560c7ab0bc3c221fbfb14d55a5b865bd58eaac5" + } ], + "cleanup-platform": ["*"], "modules": [ { - "name": "packaging", + "name": "python-toml", "buildsystem": "simple", "build-commands": [ - "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} packaging-20.4-py2.py3-none-any.whl" + "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=\"${FLATPAK_DEST}\" toml" ], "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/46/19/c5ab91b1b05cfe63cccd5cfc971db9214c6dd6ced54e33c30d5af1d2bc43/packaging-20.4-py2.py3-none-any.whl", - "sha256": "998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", + "sha256": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" } ] }, { - "name": "pyparsing", + "name": "python-pyparsing", "buildsystem": "simple", "build-commands": [ - "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} pyparsing-2.4.7-py2.py3-none-any.whl" + "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=\"${FLATPAK_DEST}\" pyparsing" ], "sources": [ { @@ -119,37 +102,66 @@ ] }, { - "name": "toml", + "name": "python-packaging", "buildsystem": "simple", "build-commands": [ - "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} toml-0.10.2-py2.py3-none-any.whl" + "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=\"${FLATPAK_DEST}\" packaging" ], "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", - "sha256": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + "url": "https://files.pythonhosted.org/packages/46/19/c5ab91b1b05cfe63cccd5cfc971db9214c6dd6ced54e33c30d5af1d2bc43/packaging-20.4-py2.py3-none-any.whl", + "sha256": "998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" } ] + } + ] + }, + { + "name": "python-pyqt5", + "buildsystem": "simple", + "build-commands": [ + "MAKEFLAGS=\"-j${FLATPAK_BUILDER_N_JOBS}\" sip-install --qt-shared --verbose --confirm-license --no-dbus-python --no-designer-plugin --no-docstrings --no-qml-plugin --no-tools --disable QtNetwork --disable QtPositioning --disable QtPrintSupport --disable QtRemoteObjects --disable QtSerialPort --disable QtSql --disable QtTest --target-dir=\"${FLATPAK_DEST}/lib/python$(python3 -c 'import sys; print(sys.version[:3])')/site-packages\"" + ], + "sources": [ + { + "type": "archive", + "url": "https://files.pythonhosted.org/packages/8e/a4/d5e4bf99dd50134c88b95e926d7b81aad2473b47fde5e3e4eac2c69a8942/PyQt5-5.15.4.tar.gz", + "sha256": "2a69597e0dd11caabe75fae133feca66387819fc9bc050f547e5551bce97e5be" + } + ], + "cleanup-platform": [ "/lib/python*/site-packages/PyQt5/bindings" ], + "modules": [ + { + "name": "pyqt5-builder", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=\"${FLATPAK_DEST}\" --no-build-isolation pyqt-builder" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/37/fd/5bc6351d5d279c797a1d78fec3483a6a9e4b8628073a8ed5448fbe1cf038/PyQt-builder-1.5.0.tar.gz", + "sha256": "11bbe26e8e3d5ffec6d2ef2f50596b1670eb2d8b49aee0f859821922d8282841" + } + ], + "cleanup-platform": [ "*" ], + "cleanup": [ "/bin" ] }, { - "name": "SIP", + "name": "pyqt5-sip", "buildsystem": "simple", "build-commands": [ - "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} sip-5.4.0-cp38-cp38-manylinux2014_x86_64.whl" + "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=\"${FLATPAK_DEST}\" pyqt5-sip" ], "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/fb/c4/93366a89e7f7374eded03a77b3e702a0413312056b4781dc7bad014dfd5c/sip-5.4.0-cp38-cp38-manylinux2014_x86_64.whl", - "sha256": "3e2c724cb03487ac7dfd372200c02199b1d023512f0185b1849cf88e367f4f69" + "url": "https://files.pythonhosted.org/packages/73/8c/c662b7ebc4b2407d8679da68e11c2a2eb275f5f2242a92610f6e5024c1f2/PyQt5_sip-12.8.1.tar.gz", + "sha256": "30e944db9abee9cc757aea16906d4198129558533eb7fadbe48c5da2bd18e0bd" } ] } - ], - "cleanup": [ - "/bin", - "/include" ] }, { @@ -219,7 +231,7 @@ "name": "python-google-protobuf", "buildsystem": "simple", "build-commands": [ - "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} protobuf-3.1.0.post1-py2.py3-none-any.whl" + "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=\"${FLATPAK_DEST}\" protobuf" ], "sources": [ { @@ -262,7 +274,7 @@ "name": "python-alsa", "buildsystem": "simple", "build-commands": [ - "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} ." + "pip3 install --no-index --no-build-isolation --prefix=\"${FLATPAK_DEST}\" ." ], "sources": [ { @@ -273,24 +285,25 @@ ] }, { - "name": "python-wheel", + "name": "poetry-core", "buildsystem": "simple", "build-commands": [ - "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} wheel-0.34.2-py2.py3-none-any.whl" + "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=\"${FLATPAK_DEST}\" poetry-core" ], "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/8c/23/848298cccf8e40f5bbb59009b32848a4c38f4e7f3364297ab3c3e2e2cd14/wheel-0.34.2-py2.py3-none-any.whl", - "sha256": "df277cb51e61359aba502208d680f90c0493adec6f0e848af94948778aed386e" + "url": "https://files.pythonhosted.org/packages/bf/e1/08c7478df1e93dea47b06c9d9a80dbb54af7421462e1b22c280d063df807/poetry_core-1.0.3-py2.py3-none-any.whl", + "sha256": "c6bde46251112de8384013e1ab8d66e7323d2c75172f80220aba2bc07e208e9a" } ] }, + "python-modules.json", { "name": "linux-show-player", "buildsystem": "simple", "build-commands": [ - "pip3 install --verbose --no-index --no-deps --no-build-isolation --prefix=${FLATPAK_DEST} .", + "pip3 install --no-deps --no-build-isolation --prefix=\"${FLATPAK_DEST}\" .", "mkdir -p /app/share/applications/", "cp dist/linuxshowplayer.desktop /app/share/applications/", "mkdir -p /app/share/mime/packages/", diff --git a/scripts/flatpak/patch-manifest.py b/scripts/flatpak/patch-manifest.py new file mode 100644 index 000000000..e1412c4a5 --- /dev/null +++ b/scripts/flatpak/patch-manifest.py @@ -0,0 +1,30 @@ +import json +import os + +app_branch = os.environ["BUILD_BRANCH"] +app_id = os.environ["FLATPAK_APP_ID"] +app_module = os.environ["FLATPAK_APP_MODULE"] + +current_dir = os.path.dirname(__file__) +manifest_path = os.path.join(current_dir, app_id + ".json") + +print("Patching flatpak manifest") +with open(manifest_path, mode="r") as f: + manifest = json.load(f) + +# Patch top-level attributes +manifest["branch"] = app_branch +if app_branch != "master": + manifest["desktop-file-name-suffix"] = " ({})".format(app_branch) + +# Patch the app-module to use the correct branch +for module in manifest["modules"]: + if isinstance(module, dict) and module["name"] == app_module: + module["sources"][0]["branch"] = app_branch + break + +# Save the patched manifest +with open(manifest_path, mode="w") as f: + json.dump(manifest, f, indent=4) + +print("Done!") diff --git a/scripts/flatpak/poetry-flatpak.py b/scripts/flatpak/poetry-flatpak.py new file mode 100755 index 000000000..7c605ebf4 --- /dev/null +++ b/scripts/flatpak/poetry-flatpak.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 + +import argparse +import json +import re +import urllib.parse +import urllib.request +from collections import OrderedDict +from concurrent.futures import ThreadPoolExecutor, as_completed + +import toml +from packaging.utils import parse_wheel_filename +from packaging.tags import Tag + +# Python3 packages that come as part of org.freedesktop.Sdk. +SYS_PACKAGES = [ + "cython", + "mako", + "markdown", + "meson", + "pip", + "pygments", + "setuptools", + "six", + "wheel", +] + + +def get_best_source(sources, hashes): + # Look for a wheel package first + for source in sources: + if ( + source["packagetype"] == "bdist_wheel" + and source["digests"]["sha256"] in hashes + ): + wheel_tags = parse_wheel_filename(source["filename"])[-1] + if Tag("py3", "none", "any") in wheel_tags: + return source["url"], source["digests"]["sha256"] + + # If no compatible wheel is found get the source + for source in sources: + if ( + source["packagetype"] == "sdist" + and "source" in source["python_version"] + and source["digests"]["sha256"] in hashes + ): + return source["url"], source["digests"]["sha256"] + + raise Exception(f"Cannot find a viable distribution for the package.") + + +def get_pypi_source(name: str, version: str, hashes: list) -> tuple: + """Get the source information for a dependency.""" + url = f"https://pypi.org/pypi/{name}/json" + print(f" Fetching sources for {name} ({version})") + + with urllib.request.urlopen(url) as response: + body = json.loads(response.read()) + try: + return get_best_source(body["releases"][version], hashes) + except KeyError: + raise Exception(f"Failed to extract url and hash from {url}") + + +def get_package_hashes(package_files: list) -> list: + regex = re.compile(r"(sha1|sha224|sha384|sha256|sha512|md5):([a-f0-9]+)") + hashes = [] + + for package_file in package_files: + match = regex.search(package_file["hash"]) + if match: + hashes.append(match.group(2)) + + return hashes + + +def get_packages_sources(packages: list, parsed_lockfile: dict) -> list: + """Gets the list of sources from a toml parsed lockfile.""" + sources = [] + parsed_files = parsed_lockfile["metadata"]["files"] + + executor = ThreadPoolExecutor(max_workers=10) + futures = [] + + for package in packages: + package_files = parsed_files.get(package["name"], []) + futures.append( + executor.submit( + get_pypi_source, + package["name"], + package["version"], + get_package_hashes(package_files), + ) + ) + + for future in as_completed(futures): + url, hash = future.result() + sources.append({"type": "file", "url": url, "sha256": hash}) + + return sources + + +def get_locked_packages(parsed_lockfile: dict, exclude=tuple()) -> list: + """Gets the list of dependency names.""" + dependencies = [] + packages = parsed_lockfile.get("package", []) + + for package in packages: + if ( + package.get("category") == "main" + and package.get("source") is None + and package.get("name") not in exclude + ): + dependencies.append(package) + + return dependencies + + +def main(): + parser = argparse.ArgumentParser(description="Flatpak Poetry generator") + parser.add_argument("lockfile") + parser.add_argument("-e", dest="exclude", default=[], nargs="+") + parser.add_argument("-o", dest="outfile", default="python-modules.json") + args = parser.parse_args() + + lockfile = args.lockfile + exclude = SYS_PACKAGES + args.exclude + outfile = args.outfile + + print(f'Scanning "{lockfile}"') + parsed_lockfile = toml.load(lockfile) + dependencies = get_locked_packages(parsed_lockfile, exclude=exclude) + print(f"Found {len(dependencies)} required packages") + + pip_command = [ + "pip3", + "install", + "--no-index", + '--find-links="file://${PWD}"', + "--prefix=${FLATPAK_DEST}", + " ".join([d["name"] for d in dependencies]), + ] + main_module = OrderedDict( + [ + ("name", "python-modules"), + ("buildsystem", "simple"), + ("build-commands", [" ".join(pip_command)]), + ] + ) + + print("Fetching metadata from pypi") + sources = get_packages_sources(dependencies, parsed_lockfile) + main_module["sources"] = sources + + print(f'Writing modules to "{outfile}"') + with open(outfile, "w") as f: + f.write(json.dumps(main_module, indent=4)) + print("Done!") + + +if __name__ == "__main__": + main() diff --git a/scripts/flatpak/python-modules.json b/scripts/flatpak/python-modules.json new file mode 100644 index 000000000..71612148c --- /dev/null +++ b/scripts/flatpak/python-modules.json @@ -0,0 +1,84 @@ +{ + "name": "python-modules", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} appdirs certifi cffi chardet falcon humanize idna jack-client mido pycparser pyliblo python-rtmidi requests sortedcontainers urllib3" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/19/c7/fa589626997dd07bd87d9269342ccb74b1720384a4d739a1872bd84fbe68/chardet-4.0.0-py2.py3-none-any.whl", + "sha256": "f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/49/ac/7673fc4c19f0ecd258c3a9636d63cb77003c2c6afc46943d3db752b35758/humanize-3.2.0-py3-none-any.whl", + "sha256": "d47d80cd47c1511ed3e49ca5f10c82ed940ea020b45b49ab106ed77fa8bb9d22" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/20/0a/81beb587b1ae832ea6a1901dc7c6faa380e8dd154e0a862f0a9f3d2afab9/mido-1.2.9-py2.py3-none-any.whl", + "sha256": "fc6364efa028c8405166f63e6a83cbc6c17aaeac2c28680abe64ae48703a89dd" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/71/0a/faaa9d50705d43f4411bc8a16aaf5189d27a0cf9b7b43ede91f2cb92de97/JACK_Client-0.5.3-py3-none-any.whl", + "sha256": "0214d9366e0a65bf0a99866990aae5a443afb3c283006d236d8cebcb769a59b8" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", + "sha256": "a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/ae/e7/d9c3a176ca4b02024debf82342dab36efadfc5776f9c8db077e8f6e71821/pycparser-2.20-py2.py3-none-any.whl", + "sha256": "7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/c0/da/4b8052ab5aa07952d7fe7b068d737a042e76373573eadd340f7550eddea9/pyliblo-0.10.0.tar.gz", + "sha256": "fc67f1950b827272b00f9f0dc4ed7113c0ccef0c1c09e9976dead40ebbf1798f" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/a2/38/928ddce2273eaa564f6f50de919327bf3a00f091b5baba8dfa9460f3a8a8/idna-2.10-py2.py3-none-any.whl", + "sha256": "b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/5e/a0/5f06e1e1d463903cf0c0eebeb751791119ed7a4b3737fdc9a77f1cdfb51f/certifi-2020.12.5-py2.py3-none-any.whl", + "sha256": "719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/4b/89/37b132c6ae7f85c60a1549f9e46109eec40cf0c31f0305faecb7f7bcfceb/falcon-2.0.0-py2.py3-none-any.whl", + "sha256": "54f2cb4b687035b2a03206dbfc538055cc48b59a953187b0458aa1b574d47b53" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/32/48/86ce3b159fb97372bc929e77d540191e5d4682eb4d83d5dae4e09f0d5309/python-rtmidi-1.4.7.tar.gz", + "sha256": "d7dbc2b174b09015dfbee449a672a072aa72b367be40b13e04ee35a2e2e399e3" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/20/4d/a7046ae1a1a4cc4e9bbed194c387086f06b25038be596543d026946330c9/sortedcontainers-2.3.0-py2.py3-none-any.whl", + "sha256": "37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/a8/20/025f59f929bbcaa579704f443a438135918484fffaacfaddba776b374563/cffi-1.14.5.tar.gz", + "sha256": "fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/29/c1/24814557f1d22c56d50280771a17307e6bf87b70727d975fd6b2ce6b014a/requests-2.25.1-py2.py3-none-any.whl", + "sha256": "c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/23/fc/8a49991f7905261f9ca9df5aa9b58363c3c821ce3e7f671895442b7100f2/urllib3-1.26.3-py2.py3-none-any.whl", + "sha256": "1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80" + } + ] +} \ No newline at end of file diff --git a/scripts/flatpak/requirements.txt b/scripts/flatpak/requirements.txt new file mode 100644 index 000000000..f61c83d66 --- /dev/null +++ b/scripts/flatpak/requirements.txt @@ -0,0 +1,2 @@ +packaging +toml \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index a1f59d730..000000000 --- a/setup.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 - -import os -from pathlib import Path -from setuptools import find_packages, setup - -import lisp - - -def long_description(): - readme = Path(__file__).parent / "README.md" - with open(readme, encoding="utf8") as readme_file: - return readme_file.read() - - -def package_data_dirs(package_path): - """Fetch all "data" directories in the given package""" - dirs = [] - for dirpath, dirnames, filenames in os.walk(package_path): - # Exclude: empty, python-cache and python-modules - if ( - filenames - and "__pycache__" not in dirpath - and "__init__.py" not in filenames - ): - rel_dirpath = str(Path(dirpath).relative_to(package_path)) - dirs.append(rel_dirpath + "/*") - - return dirs - - -# Setup function -setup( - name="linux-show-player", - author=lisp.__author__, - author_email=lisp.__email__, - version=lisp.__version__, - license=lisp.__license__, - url=lisp.__url__, - description="Cue player designed for stage productions", - long_description=long_description(), - long_description_content_type="text/markdown", - packages=find_packages(), - package_data={ - "": ["*.qm", "*.qss", "*.json"], - "lisp": ["i18n/qm/*.qm"], - "lisp.ui.icons": package_data_dirs("lisp/ui/icons"), - }, - entry_points={"console_scripts": ["linux-show-player=lisp.main:main"]}, -) From 0da7e056e4ee17d0296ec5731a8f42a775870616 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 25 Apr 2021 15:25:54 +0200 Subject: [PATCH 255/333] Update readme Fix: automated build should now point the correct commit --- .circleci/config.yml | 1 + README.md | 12 +++--------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index defd1dc68..23ff8ad50 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -94,6 +94,7 @@ jobs: ghr \ -n "Automated build ($CIRCLE_BRANCH)" \ -b "Build number: $CIRCLE_BUILD_NUM
    Completed at: $(date)" \ + -c $CIRCLE_SHA1 \ -recreate \ -prerelease \ $TAG "out/$ARTIFACT_NAME" diff --git a/README.md b/README.md index 4d21c297b..9958db60d 100644 --- a/README.md +++ b/README.md @@ -26,24 +26,18 @@ For bugs/requests you can open an issue on the GitHub issues tracker, for suppor Linux Show Player is currently developed/test only on **GNU/Linux**.
    _The core components (Python3, GStreamer and Qt5) are multi-platform, in future, despite the name, LiSP might get ported on others platforms_ -#### ⚙️ AppImage - -For version _0.5_ you can download an [AppImage](http://appimage.org/) bundle of LiSP from the release page, once downloaded make the file executable. - #### 📦 Flatpak From version _0.6_ it will be possible to install a [Flatpak](https://flatpak.org/) package, follow the _simple_ instructions on their website to get everything ready. You can get the latest **development** builds here: - * [Master](https://bintray.com/francescoceruti/LinuxShowPlayer/master/_latestVersion) - * [Development](https://bintray.com/francescoceruti/LinuxShowPlayer/develop/_latestVersion) - might be unstable (crash, breaking changes, etc...) + * [Master](https://github.com/FrancescoCeruti/linux-show-player/releases/tag/ci-master) - Generally stable + * [Development](https://github.com/FrancescoCeruti/linux-show-player/releases/tag/ci-develop) - Preview features, might be unstable and untested #### 🐧 From yours distribution repository For some GNU/Linux distribution you can install a native package.
    -Keep in mind that it might not be the latest version, here a list: - -[![Packaging status](https://repology.org/badge/vertical-allrepos/linux-show-player.svg)](https://repology.org/metapackage/linux-show-player) +Keep in mind that it might not be the latest version, you can find a list on [repology.org](https://repology.org/metapackage/linux-show-player) --- From bb65fccbb38e41e1c1d6d816886379eca8054c1e Mon Sep 17 00:00:00 2001 From: s0600204 <2053873+s0600204@users.noreply.github.com> Date: Sat, 29 May 2021 00:16:39 +0100 Subject: [PATCH 256/333] Don't use waveform slider when playing Media cues with "Preset" source (#208) --- lisp/plugins/list_layout/playing_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/plugins/list_layout/playing_widgets.py b/lisp/plugins/list_layout/playing_widgets.py index ffdceb65c..69d31b316 100644 --- a/lisp/plugins/list_layout/playing_widgets.py +++ b/lisp/plugins/list_layout/playing_widgets.py @@ -179,7 +179,7 @@ def __init__(self, cue, config, **kwargs): super().__init__(cue, config, **kwargs) self._dbmeter_element = None - if config.get("show.waveformSlider", False): + if config.get("show.waveformSlider", False) and cue.media.input_uri() is not None: self.waveform = get_backend().media_waveform(cue.media) self.seekSlider = WaveformSlider( self.waveform, parent=self.gridLayoutWidget From 4e17725e07c536cd6180f11fd69aa1ed4ed593c1 Mon Sep 17 00:00:00 2001 From: s0600204 <2053873+s0600204@users.noreply.github.com> Date: Sun, 6 Jun 2021 19:24:53 +0100 Subject: [PATCH 257/333] Implement ability for set a preferred default ALSA device (#206) --- lisp/plugins/gst_backend/config/__init__.py | 26 +++++++++++++ lisp/plugins/gst_backend/config/alsa_sink.py | 38 +++++++++++++++++++ .../plugins/gst_backend/elements/alsa_sink.py | 5 ++- lisp/plugins/gst_backend/gst_backend.py | 9 ++++- .../plugins/gst_backend/settings/alsa_sink.py | 14 +++---- 5 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 lisp/plugins/gst_backend/config/__init__.py create mode 100644 lisp/plugins/gst_backend/config/alsa_sink.py diff --git a/lisp/plugins/gst_backend/config/__init__.py b/lisp/plugins/gst_backend/config/__init__.py new file mode 100644 index 000000000..5ab71c30c --- /dev/null +++ b/lisp/plugins/gst_backend/config/__init__.py @@ -0,0 +1,26 @@ +# This file is part of Linux Show Player +# +# Copyright 2020 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from os.path import dirname + +from lisp.core.loading import load_classes + +def load(): + for name, page in load_classes( + __package__, dirname(__file__), suf=("Config",) + ): + yield name, page diff --git a/lisp/plugins/gst_backend/config/alsa_sink.py b/lisp/plugins/gst_backend/config/alsa_sink.py new file mode 100644 index 000000000..61c20d30f --- /dev/null +++ b/lisp/plugins/gst_backend/config/alsa_sink.py @@ -0,0 +1,38 @@ +# This file is part of Linux Show Player +# +# Copyright 2021 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import QT_TRANSLATE_NOOP + +from lisp.plugins.gst_backend.elements.alsa_sink import AlsaSink +from lisp.plugins.gst_backend.settings.alsa_sink import AlsaSinkSettings + + +class AlsaSinkConfig(AlsaSinkSettings): + Name = QT_TRANSLATE_NOOP("SettingsPageName", "ALSA Default Device") + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def loadSettings(self, settings): + device = settings.get("alsa_device", AlsaSink.FALLBACK_DEVICE) + + self.deviceComboBox.setCurrentText( + self.devices.get(device, self.devices.get(AlsaSink.FALLBACK_DEVICE, "")) + ) + + def getSettings(self): + return {"alsa_device": self.deviceComboBox.currentData()} diff --git a/lisp/plugins/gst_backend/elements/alsa_sink.py b/lisp/plugins/gst_backend/elements/alsa_sink.py index 05964a798..8c573f7f9 100644 --- a/lisp/plugins/gst_backend/elements/alsa_sink.py +++ b/lisp/plugins/gst_backend/elements/alsa_sink.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2016 Francesco Ceruti +# Copyright 2021 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,6 +18,7 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.media_element import ElementType, MediaType +from lisp.plugins.gst_backend import GstBackend from lisp.plugins.gst_backend.gi_repository import Gst from lisp.plugins.gst_backend.gst_element import GstMediaElement, GstProperty @@ -27,6 +28,7 @@ class AlsaSink(GstMediaElement): MediaType = MediaType.Audio Name = QT_TRANSLATE_NOOP("MediaElementName", "ALSA Out") + FALLBACK_DEVICE = "default" device = GstProperty("alsa_sink", "device", default="") def __init__(self, pipeline): @@ -34,6 +36,7 @@ def __init__(self, pipeline): self.audio_resample = Gst.ElementFactory.make("audioresample", None) self.alsa_sink = Gst.ElementFactory.make("alsasink", "sink") + self.device = GstBackend.Config.get("alsa_device", self.FALLBACK_DEVICE) self.pipeline.add(self.audio_resample) self.pipeline.add(self.alsa_sink) diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index c62cdc804..d69c06bf6 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2020 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -28,7 +28,7 @@ from lisp.core.plugin import Plugin from lisp.cues.cue_factory import CueFactory from lisp.cues.media_cue import MediaCue -from lisp.plugins.gst_backend import elements, settings +from lisp.plugins.gst_backend import config, elements, settings from lisp.plugins.gst_backend.gi_repository import Gst from lisp.plugins.gst_backend.gst_media_cue import ( GstCueFactory, @@ -65,6 +65,11 @@ def __init__(self, app): AppConfigurationDialog.registerSettingsPage( "plugins.gst", GstSettings, GstBackend.Config ) + # Register elements' application-level config + for name, page in config.load(): + AppConfigurationDialog.registerSettingsPage( + f"plugins.gst.{name}", page, GstBackend.Config + ) # Add MediaCue settings widget to the CueLayout CueSettingsRegistry().add(GstMediaSettings, MediaCue) diff --git a/lisp/plugins/gst_backend/settings/alsa_sink.py b/lisp/plugins/gst_backend/settings/alsa_sink.py index 4daeb67ab..c7ebe5f35 100644 --- a/lisp/plugins/gst_backend/settings/alsa_sink.py +++ b/lisp/plugins/gst_backend/settings/alsa_sink.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2021 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -24,6 +24,7 @@ ) from pyalsa import alsacard +from lisp.plugins.gst_backend import GstBackend from lisp.plugins.gst_backend.elements.alsa_sink import AlsaSink from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate @@ -49,10 +50,6 @@ def __init__(self, **kwargs): self.deviceComboBox = QComboBox(self.deviceGroup) for name, description in self.devices.items(): self.deviceComboBox.addItem(description, name) - if name == "default": - self.deviceComboBox.setCurrentIndex( - self.deviceComboBox.count() - 1 - ) self.deviceGroup.layout().addWidget(self.deviceComboBox) @@ -76,10 +73,13 @@ def enableCheck(self, enabled): self.setGroupEnabled(self.deviceGroup, enabled) def loadSettings(self, settings): - device = settings.get("device", "default") + device = settings.get( + "device", + GstBackend.Config.get("alsa_device", AlsaSink.FALLBACK_DEVICE) + ) self.deviceComboBox.setCurrentText( - self.devices.get(device, self.devices.get("default", "")) + self.devices.get(device, self.devices.get(AlsaSink.FALLBACK_DEVICE)) ) def getSettings(self): From 72e62f7c24e35b1059e332995f44abadf7bbe9a6 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 9 Oct 2021 18:34:44 +0200 Subject: [PATCH 258/333] Improve/fix spelling --- lisp/plugins/gst_backend/elements/jack_sink.py | 2 +- lisp/plugins/presets/presets.py | 4 ++-- lisp/plugins/rename_cues/rename_ui.py | 2 +- lisp/ui/settings/app_pages/cue.py | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lisp/plugins/gst_backend/elements/jack_sink.py b/lisp/plugins/gst_backend/elements/jack_sink.py index 33a605cfb..38b7fb9eb 100644 --- a/lisp/plugins/gst_backend/elements/jack_sink.py +++ b/lisp/plugins/gst_backend/elements/jack_sink.py @@ -134,7 +134,7 @@ def __jack_connect(self): logger.exception( translate( "JackSinkError", - "An error occurred while disconnection Jack ports", + "An error occurred while disconnecting Jack ports", ) ) diff --git a/lisp/plugins/presets/presets.py b/lisp/plugins/presets/presets.py index 5fdc0620a..2daffed99 100644 --- a/lisp/plugins/presets/presets.py +++ b/lisp/plugins/presets/presets.py @@ -71,9 +71,9 @@ def __init__(self, app): ) self.cueActionsGroup.add( SimpleMenuAction( - translate("Presets", "Load on cue"), + translate("Presets", "Apply to cue"), self.__load_on_cue, - translate("Presets", "Load on selected cues"), + translate("Presets", "Apply to selected cues"), self.__load_on_cues, ), SimpleMenuAction( diff --git a/lisp/plugins/rename_cues/rename_ui.py b/lisp/plugins/rename_cues/rename_ui.py index 82728eba9..11622b446 100644 --- a/lisp/plugins/rename_cues/rename_ui.py +++ b/lisp/plugins/rename_cues/rename_ui.py @@ -240,7 +240,7 @@ def onHelpButtonClicked(self): "In the second line, you can use standard Python Regexes " "to match expressions in the original cues names. Use " "parenthesis to capture parts of the matched expression.\n\n" - "Exemple: \n^[a-z]([0-9]+) will find a lower case character" + "Example: \n^[a-z]([0-9]+) will find a lower case character" "([a-z]), followed by one or more number.\n" "Only the numbers are between parenthesis and will be usable with " "$0 in the first line.\n\n" diff --git a/lisp/ui/settings/app_pages/cue.py b/lisp/ui/settings/app_pages/cue.py index ad6b9594d..da84cec99 100644 --- a/lisp/ui/settings/app_pages/cue.py +++ b/lisp/ui/settings/app_pages/cue.py @@ -67,7 +67,7 @@ def __init__(self, **kwargs): def retranslateUi(self): self.interruptGroup.setTitle( - translate("CueSettings", "Interrupt action fade") + translate("CueSettings", "Interrupt fade") ) self.interruptHelpText.setText( translate( @@ -76,13 +76,13 @@ def retranslateUi(self): ) ) self.fadeActionsDefaultsGroup.setTitle( - translate("CueSettings", "Fade actions default value") + translate("CueSettings", "Fallback fade settings") ) self.fadeActionDefaultsHelpText.setText( translate( "CueSettings", - "Used for fade-in and fade-out actions, by cues where fade " - "duration is 0.", + "Used for fade-in and fade-out actions, for cues where fade " + "duration is set to 0.", ) ) From 6fa0bfda0453da98be57b1c31c2e5cb241ce345e Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 31 Oct 2021 00:05:23 +0200 Subject: [PATCH 259/333] Update "dark" theme to use colors form the current palette, when possible. --- lisp/ui/themes/dark/dark.py | 32 +++++- lisp/ui/themes/dark/theme.qss | 193 +++++++++++++--------------------- 2 files changed, 102 insertions(+), 123 deletions(-) diff --git a/lisp/ui/themes/dark/dark.py b/lisp/ui/themes/dark/dark.py index 0f413f180..f4e8803e8 100644 --- a/lisp/ui/themes/dark/dark.py +++ b/lisp/ui/themes/dark/dark.py @@ -29,11 +29,33 @@ class Dark: QssPath = os.path.join(os.path.dirname(__file__), "theme.qss") def apply(self, qt_app): - with open(Dark.QssPath, mode="r", encoding="utf-8") as f: - qt_app.setStyleSheet(f.read()) + background = QColor(30, 30, 30) + foreground = QColor(52, 52, 52) + text = QColor(230, 230, 230) + highlight = QColor(65, 155, 230) - # Change link color palette = qt_app.palette() - palette.setColor(QPalette.Link, QColor(65, 155, 230)) - palette.setColor(QPalette.LinkVisited, QColor(43, 103, 153)) + palette.setColor(QPalette.Window, foreground) + palette.setColor(QPalette.WindowText, text) + palette.setColor(QPalette.Base, background) + palette.setColor(QPalette.AlternateBase, foreground.darker(125)) + palette.setColor(QPalette.ToolTipBase, foreground) + palette.setColor(QPalette.ToolTipText, text) + palette.setColor(QPalette.Text, text) + palette.setColor(QPalette.Button, foreground) + palette.setColor(QPalette.ButtonText, text) + palette.setColor(QPalette.BrightText, QColor(255, 0, 0)) + palette.setColor(QPalette.Link, highlight) + + palette.setColor(QPalette.Light, foreground.lighter(160)) + palette.setColor(QPalette.Midlight, foreground.lighter(125)) + palette.setColor(QPalette.Dark, foreground.darker(150)) + palette.setColor(QPalette.Mid, foreground.darker(125)) + + palette.setColor(QPalette.Highlight, highlight) + palette.setColor(QPalette.HighlightedText, QColor(0, 0, 0)) + qt_app.setPalette(palette) + + with open(Dark.QssPath, mode="r", encoding="utf-8") as f: + qt_app.setStyleSheet(f.read()) diff --git a/lisp/ui/themes/dark/theme.qss b/lisp/ui/themes/dark/theme.qss index fb660275e..5a159a7cf 100644 --- a/lisp/ui/themes/dark/theme.qss +++ b/lisp/ui/themes/dark/theme.qss @@ -29,23 +29,21 @@ */ QWidget { - color: #dddddd; - background-color: #333333; - selection-background-color: #419BE6; + color: palette(text); + selection-background-color: palette(highlight); selection-color: black; outline: none; } QWidget:disabled { color: #707070; - background-color: #333333; } QTreeView::branch:selected, QTreeView::branch:selected:hover, QWidget::item:selected, QWidget::item:selected:hover { - background-color: #419BE6; + background-color: palette(highlight); } QProgressBar { @@ -54,26 +52,23 @@ QProgressBar { QProgressBar:horizontal { text-align: center; - border: 1px solid #4A4A4A; - background: #202020; + border: 1px solid palette(light); + background: palette(base); } QProgressBar::chunk:horizontal { border-radius: 2px; - background-color: rgba(40, 90, 150, 255); + background-color: palette(highlight); } QStatusBar { - border-top: 1px solid #3A3A3A; + border-top: 1px solid palette(midlight); } QToolTip { - border: 1px solid transparent; - border-radius: 5px; - background-color: #1A1A1A; - color: #dddddd; + border: 1px solid palette(window); + background-color: palette(base); padding: 2px; - opacity: 200; } QMenuBar::item { @@ -85,16 +80,15 @@ QMenuBar::item:selected { } QMenuBar::item:pressed { - border: 1px solid #4A4A4A; - background-color: #419BE6; + border: 1px solid palette(light); + background-color: palette(highlight); color: black; - margin-bottom:-1px; - padding-bottom:1px; + margin-bottom: -1px; + padding-bottom: 1px; } QMenu { - border: 1px solid #4A4A4A; - color: #dddddd; + border: 1px solid palette(light); } QMenu::item { @@ -113,21 +107,15 @@ QMenu::indicator { QMenu::separator { height: 1px; margin: 4px 0px 4px 0px; - background-color: #3C3C3C; + background-color: palette(dark); } QAbstractItemView { - border: 1px solid #4A4A4A; - alternate-background-color: #252525; + border: 1px solid palette(light); + alternate-background-color: palette(alternate-base); /*paint-alternating-row-colors-for-empty-area: true;*/ - background-color: #202020; + background-color: palette(base); border-radius: 3px; - color: #dddddd; -} - -QTreeView::branch:hover, -QAbstractItemView::item:hover { - background-color: none; } QTabWidget:focus, @@ -137,15 +125,14 @@ QRadioButton:focus { } QLineEdit { - background-color: #202020; + background-color: palette(base); padding: 2px; - border: 1px solid #4A4A4A; + border: 1px solid palette(light); border-radius: 3px; - color: #dddddd; } QGroupBox { - border-top: 2px solid #4A4A4A; + border-top: 2px solid palette(light); margin-top: 2ex; /* NOT px */ padding-top: 1ex; /* NOT px */ } @@ -159,14 +146,14 @@ QGroupBox::title { } QAbstractScrollArea { - color: #dddddd; - background-color: #202020; - border: 1px solid #4A4A4A; + background-color: palette(base); + border: 1px solid palette(light); border-radius: 3px; } QScrollBar { padding: 2px; + background-color: palette(mid); } QScrollBar:horizontal { @@ -178,12 +165,12 @@ QScrollBar:vertical { } QScrollBar::handle { - background-color: rgba(112, 112, 112, 0.7); /* #707070 */ + background-color: palette(light); border-radius: 4px; } QScrollBar::handle:hover { - background-color: rgba(112, 112, 112, 1); /* #707070 */ + background-color: palette(light); } QScrollBar::handle:horizontal { @@ -207,12 +194,8 @@ QScrollBar::sub-page { background: none; } -QAbstractItemView QScrollBar { - background-color: #202020; -} - QTextEdit[readOnly="true"] { - background-color: #333333; + background-color: palette(window); } QTextBrowser[readOnly="true"] { @@ -239,8 +222,8 @@ QHeaderView::down-arrow { QHeaderView::section { border: none; - border-right: 1px solid #3A3A3A; - border-bottom: 1px solid #3A3A3A; + border-right: 1px solid palette(midlight); + border-bottom: 1px solid palette(midlight); } QHeaderView::section, @@ -253,10 +236,9 @@ QTableView QTableCornerButton::section { stop: 0 #565656, stop: 0.1 #525252, stop: 0.5 #4e4e4e, - stop: 0.9 #4a4a4a, + stop: 0.9 palette(light), stop: 1 #464646 ); - color: #dddddd; } QHeaderView::section:checked { @@ -270,11 +252,10 @@ QSizeGrip { } QMainWindow::separator { - background-color: #333333; - color: #dddddd; + background-color: palette(window); padding-left: 4px; spacing: 2px; - border: 1px dashed #3A3A3A; + border: 1px dashed palette(midlight); } QMainWindow::separator:hover { @@ -284,12 +265,11 @@ QMainWindow::separator:hover { x2: 0, y2: 1, stop: 0 #58677b, - stop: 0.5 #419BE6, + stop: 0.5 palette(highlight), stop: 1 #58677b ); - color: #dddddd; padding-left: 4px; - border: 1px solid #4A4A4A; + border: 1px solid palette(light); spacing: 2px; } @@ -298,8 +278,8 @@ QStackedWidget { } QToolBar { - border: 1px solid #393838; - background: 1px solid #333333; + border: 1px solid palette(light); + background: 1px solid palette(window); font-weight: bold; } @@ -326,16 +306,15 @@ QSplitter::handle:horizontal:disabled { } QPushButton { - color: #dddddd; background-color: qlineargradient( x1: 0, y1: 1, x2: 0, y2: 0, - stop: 0 #333333, - stop: 1 #444444 + stop: 0 palette(button), + stop: 1 palette(midlight) ); - border: 1px solid #202020; + border: 1px solid palette(base); border-radius: 4px; padding: 4px; padding-left: 5px; @@ -343,25 +322,23 @@ QPushButton { } QPushButton:focus { - background: #4B5157; + border: 1px solid palette(highlight); } QPushButton:pressed { - background: #35393d; + background: palette(mid); } - QLineEdit:focus, QComboBox:focus, QPushButton:focus QAbstractSpinBox:focus { - border: 1px solid #419BE6; + border: 1px solid palette(highlight); } QComboBox { - background-color: #202020; - border-style: solid; - border: 1px solid #4A4A4A; + background-color: palette(base); + border: 1px solid palette(light); border-radius: 3px; padding: 2px; } @@ -370,7 +347,7 @@ QComboBox:on { background-color: #626873; padding-top: 3px; padding-left: 4px; - selection-background-color: #4a4a4a; + selection-background-color: palette(light); } QComboBox::drop-down { @@ -395,15 +372,13 @@ QAbstractSpinBox { padding-top: 2px; padding-bottom: 2px; padding-right: 25px; - border: 1px solid #4A4A4A; - background-color: #202020; + border: 1px solid palette(light); + background-color: palette(base); border-radius: 3px; - color: #dddddd; } QAbstractSpinBox::up-button { - background-color: transparent; - border-left: 1px solid #3A3A3A; + border-left: 1px solid palette(midlight); padding: 6px; right: 24px; width: 12px; @@ -413,8 +388,7 @@ QAbstractSpinBox::up-button { } QAbstractSpinBox::down-button { - background-color: transparent; - border-left: 1px solid #3A3A3A; + border-left: 1px solid palette(midlight); padding: 6px; width: 12px; height: 15px; @@ -443,13 +417,12 @@ QAbstractSpinBox::down-arrow:hover { } QLabel { - border: 0px solid black; + border: none; } QTabBar::tab { - color: #dddddd; - border: 1px solid #444444; - background-color: #333333; + border: 1px solid palette(midlight); + background-color: palette(window); padding-left: 10px; padding-right: 10px; padding-top: 3px; @@ -458,7 +431,7 @@ QTabBar::tab { } QTabWidget::pane { - border: 1px solid #444444; + border: 1px solid palette(midlight); top: -1px; } @@ -485,37 +458,29 @@ QTabBar::tab:selected { QTabBar::tab:!selected { margin-top: 3px; - background-color: qlineargradient( - x1: 0, - y1: 0, - x2: 0, - y2: 1, - stop: 1 #212121, - stop: 0.4 #343434 - ); + background-color: palette(mid); } QTabBar::tab:selected { - border-top-color: #CCCCCC; + border-top-color: palette(light); } QTabBar::tab:selected:focus { - border-top-color: #419BE6; + border-top-color: palette(highlight); } QTabBar QToolButton { - border: 1px solid #4A4A4A; + border: 1px solid palette(light); } QDockWidget { - color: #dddddd; titlebar-close-icon: url(:/assets/close.png); titlebar-normal-icon: url(:/assets/undock.png); } QDockWidget::title { - border: 1px solid #4A4A4A; - border-bottom: #333333; + border: 1px solid palette(light); + border-bottom: palette(window); text-align: left; spacing: 2px; background-color: qlineargradient( @@ -523,8 +488,8 @@ QDockWidget::title { y1: 0, x2: 0, y2: 1, - stop: 1 #333333, - stop: 0 #3A3A3A + stop: 1 palette(window), + stop: 0 palette(midlight) ); background-image: none; padding-left: 10px; @@ -546,7 +511,7 @@ QDockWidget::float-button { QDockWidget::close-button:hover, QDockWidget::float-button:hover { - background: #3A3A3A; + background: palette(midlight); } QDockWidget::close-button:pressed, @@ -581,13 +546,13 @@ QSlider:disabled { } QSlider::groove { - border: 1px solid #4A4A4A; + border: 1px solid palette(light); border-radius: 1px; - background: #202020; + background: palette(base); } QSlider::groove:disabled { - background: #2D2D2D; + background: palette(mid); } QSlider::groove:horizontal { @@ -642,15 +607,15 @@ QSlider::handle:disabled { } QToolButton { - background-color: #3A3A3A; + background-color: palette(midlight); } QToolButton:pressed { - background-color: #3A3A3A; + background-color: palette(midlight); } QToolButton:hover { - background-color: #3A3A3A; + background-color: palette(midlight); } QToolButton::menu-indicator { @@ -716,12 +681,7 @@ QRadioButton::indicator:checked:disabled { image: url(:/assets/radio-checked-disabled.png); } - -/* - ****************** - * CUSTOM WIDGETS * - ****************** -*/ +/* CUSTOM WIDGETS */ #CartTabBar { font-size: 13pt; @@ -731,7 +691,6 @@ QRadioButton::indicator:checked:disabled { #CartTabBar::tab { height: 35px; min-width: 100px; - color: #dddddd; } #CartTabBar::tab:selected { @@ -741,23 +700,21 @@ QRadioButton::indicator:checked:disabled { } #ButtonCueWidget { - background-color: #464646; - color: #AAAAAA; - border: 1 solid black; + background-color: palette(light); + border: 1px solid palette(base); border-radius: 6px; - /*font-size: 11pt;*/ } #ButtonCueWidget[selected="true"] { - border: 2px solid #419BE6; + border: 2px solid palette(highlight); } CueListView { - border: 1px solid #808080; + border: 1px solid palette(text); } CueListView:focus { - border: 1px solid #4A4A4A; + border: 1px solid palette(light); } #ListTimeWidget { @@ -811,7 +768,7 @@ CueListView:focus { } #InfoPanelDescription[readOnly="true"] { - border: 1px solid #4A4A4A; + border: 1px solid palette(light); } #VolumeSlider::sub-page:horizontal { From 0ee6d7dcf78b2b868d26bd316288c9a7ac8c0b6f Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 31 Oct 2021 00:07:13 +0200 Subject: [PATCH 260/333] Revert to normal font size some label --- lisp/ui/settings/app_pages/cue.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lisp/ui/settings/app_pages/cue.py b/lisp/ui/settings/app_pages/cue.py index da84cec99..aeccd2e02 100644 --- a/lisp/ui/settings/app_pages/cue.py +++ b/lisp/ui/settings/app_pages/cue.py @@ -37,7 +37,6 @@ def __init__(self, **kwargs): self.layout().addWidget(self.interruptGroup) self.interruptHelpText = QLabel(self.interruptGroup) - self.interruptHelpText.setWordWrap(True) self.interruptGroup.layout().addWidget(self.interruptHelpText) self.interruptFadeEdit = FadeEdit(self.interruptGroup) @@ -52,10 +51,6 @@ def __init__(self, **kwargs): self.layout().addWidget(self.fadeActionsDefaultsGroup) self.fadeActionDefaultsHelpText = QLabel(self.fadeActionsDefaultsGroup) - self.fadeActionDefaultsHelpText.setAlignment(Qt.AlignCenter) - font = self.fadeActionDefaultsHelpText.font() - font.setPointSizeF(font.pointSizeF() * 0.9) - self.fadeActionDefaultsHelpText.setFont(font) self.fadeActionsDefaultsGroup.layout().addWidget( self.fadeActionDefaultsHelpText ) From 7f0fe33caef6741287eb2d0aacb2cecfe570e996 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 1 Nov 2021 21:29:28 +0100 Subject: [PATCH 261/333] Use "segment seeking" to allow seamless looping, and improve track transitions #212 --- lisp/plugins/gst_backend/gst_media.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index 2f19c80e2..17b820f33 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -159,7 +159,7 @@ def update_properties(self, properties): def __reset_media(self): self.__loop = self.loop - def __seek(self, position): + def __seek(self, position, flush=True): if self.state == MediaState.Playing or self.state == MediaState.Paused: max_position = self.duration if 0 < self.stop_time < self.duration: @@ -177,10 +177,14 @@ def __seek(self, position): stop_type = Gst.SeekType.SET # Seek the pipeline + flags = Gst.SeekFlags.SEGMENT | Gst.SeekFlags.TRICKMODE + if flush: + flags |= Gst.SeekFlags.FLUSH + result = self.__pipeline.seek( rate if rate > 0 else 1, Gst.Format.TIME, - Gst.SeekFlags.FLUSH | Gst.SeekFlags.SKIP, + flags, Gst.SeekType.SET, position * Gst.MSECOND, stop_type, @@ -258,12 +262,11 @@ def __init_pipeline(self): def __on_message(self, bus, message): if message.src == self.__pipeline: - if message.type == Gst.MessageType.EOS: + if message.type in (Gst.MessageType.SEGMENT_DONE, Gst.MessageType.EOS): if self.__loop != 0: # If we still have loops to do then seek to start - # FIXME: this is not 100% seamless self.__loop -= 1 - self.seek(self.start_time) + self.__seek(self.start_time, flush=False) else: # Otherwise go in READY state self.__pipeline.set_state(Gst.State.READY) From 96759a487263c3ee4a08c9347ca450d08e9dbe10 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 1 Nov 2021 21:45:05 +0100 Subject: [PATCH 262/333] Update dependencies --- lisp/plugins/network/network.py | 4 +- poetry.lock | 467 +++++++++++++++++++------------- pyproject.toml | 2 +- 3 files changed, 283 insertions(+), 190 deletions(-) diff --git a/lisp/plugins/network/network.py b/lisp/plugins/network/network.py index dcd8d2674..d2fc4303d 100644 --- a/lisp/plugins/network/network.py +++ b/lisp/plugins/network/network.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import falcon +from falcon import App from lisp.core.plugin import Plugin from lisp.plugins.network.api import route_all @@ -30,7 +30,7 @@ class Network(Plugin): def __init__(self, app): super().__init__(app) - self.api = falcon.API() + self.api = App() # We don't support HTTPS (yet?) self.api.resp_options.secure_cookies_by_default = False # Load all the api endpoints diff --git a/poetry.lock b/poetry.lock index 553bdcd6e..f156a9e53 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8,7 +8,7 @@ python-versions = "*" [[package]] name = "certifi" -version = "2020.12.5" +version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -16,7 +16,7 @@ python-versions = "*" [[package]] name = "cffi" -version = "1.14.5" +version = "1.15.0" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false @@ -26,16 +26,19 @@ python-versions = "*" pycparser = "*" [[package]] -name = "chardet" -version = "4.0.0" -description = "Universal encoding detector for Python 2 and 3" +name = "charset-normalizer" +version = "2.0.7" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] [[package]] name = "cython" -version = "0.29.22" +version = "0.29.24" description = "The Cython compiler for writing C extensions for the Python language." category = "main" optional = false @@ -43,30 +46,50 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "falcon" -version = "2.0.0" +version = "3.0.1" description = "An unladen web framework for building APIs and app backends." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [[package]] name = "humanize" -version = "3.2.0" +version = "3.12.0" description = "Python humanize utilities" category = "main" optional = false python-versions = ">=3.6" +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + [package.extras] tests = ["freezegun", "pytest", "pytest-cov"] [[package]] name = "idna" -version = "2.10" +version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" + +[[package]] +name = "importlib-metadata" +version = "4.8.1" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "jack-client" @@ -84,7 +107,7 @@ numpy = ["numpy"] [[package]] name = "mido" -version = "1.2.9" +version = "1.2.10" description = "MIDI Objects for Python" category = "main" optional = false @@ -96,14 +119,14 @@ ports = ["python-rtmidi (>=1.1.0)"] [[package]] name = "packaging" -version = "20.9" +version = "21.2" description = "Core utilities for Python packages" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2" +pyparsing = ">=2.0.2,<3" [[package]] name = "pyalsa" @@ -122,7 +145,7 @@ resolved_reference = "e1bc1c27b6286dcf2d6bf4955650cf5729cf9b7a" [[package]] name = "pycairo" -version = "1.20.0" +version = "1.20.1" description = "Python interface for cairo" category = "main" optional = false @@ -138,14 +161,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygobject" -version = "3.38.0" +version = "3.42.0" description = "Python bindings for GObject Introspection" category = "main" optional = false -python-versions = ">=3.5, <4" +python-versions = ">=3.6, <4" [package.dependencies] -pycairo = ">=1.11.1" +pycairo = ">=1.16,<2.0" [[package]] name = "pyliblo" @@ -165,18 +188,18 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "pyqt5" -version = "5.15.3" +version = "5.15.6" description = "Python bindings for the Qt cross platform application toolkit" category = "main" optional = false python-versions = ">=3.6" [package.dependencies] -PyQt5-Qt = ">=5.15" +PyQt5-Qt5 = ">=5.15.2" PyQt5-sip = ">=12.8,<13" [[package]] -name = "pyqt5-qt" +name = "pyqt5-qt5" version = "5.15.2" description = "The subset of a Qt installation needed by PyQt5." category = "main" @@ -185,7 +208,7 @@ python-versions = "*" [[package]] name = "pyqt5-sip" -version = "12.8.1" +version = "12.9.0" description = "The sip module support for PyQt5" category = "main" optional = false @@ -193,7 +216,7 @@ python-versions = ">=3.5" [[package]] name = "python-rtmidi" -version = "1.4.7" +version = "1.4.9" description = "A Python binding for the RtMidi C++ library implemented using Cython." category = "main" optional = false @@ -201,25 +224,25 @@ python-versions = "*" [[package]] name = "requests" -version = "2.25.1" +version = "2.26.0" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "sortedcontainers" -version = "2.3.0" +version = "2.4.0" description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" category = "main" optional = false @@ -233,9 +256,17 @@ category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "typing-extensions" +version = "3.10.0.2" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "urllib3" -version = "1.26.3" +version = "1.26.7" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -246,10 +277,22 @@ brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +[[package]] +name = "zipp" +version = "3.6.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "5183623707dfe3665d33d45fe8dc5ea4524e6bf3740448b4cf1bffa9211529eb" +content-hash = "7dfc8fc40343480bae6d8236ded866b232cccb11dbfe9fd524ae13b9a99ab42f" [metadata.files] appdirs = [ @@ -257,139 +300,179 @@ appdirs = [ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] certifi = [ - {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, - {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] cffi = [ - {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, - {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, - {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, - {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, - {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, - {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, - {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, - {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, - {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, - {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, - {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, - {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, - {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, - {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, - {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, - {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, - {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, - {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, - {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, - {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, - {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, - {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, - {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, - {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, - {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, + {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, + {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, + {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, + {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, + {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, + {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, + {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, + {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, + {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, + {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, + {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, + {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, + {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, + {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, + {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, + {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, + {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, ] -chardet = [ - {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, - {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +charset-normalizer = [ + {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"}, + {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"}, ] cython = [ - {file = "Cython-0.29.22-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f89c19480d137026f04a34cbd54ab0fb328b29005c91d3a36d06b9c4710152c1"}, - {file = "Cython-0.29.22-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:667b169c89c4b8eb79d35822508ed8d77b9421af085142fad20bfef775a295d2"}, - {file = "Cython-0.29.22-cp27-cp27m-win32.whl", hash = "sha256:59c950513c485f1e8da0e04a2a91f71508c5e98b6912ce66588c6aee72d8e4d1"}, - {file = "Cython-0.29.22-cp27-cp27m-win_amd64.whl", hash = "sha256:15b2ba47858d7949a036d4ba6e838120bf3c06983769e99d12867a2c8cd0cd91"}, - {file = "Cython-0.29.22-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d76feb00f754d86e6d48f39b59a4e9a8fcfed1e6e3e433d18e3677ff19ff2911"}, - {file = "Cython-0.29.22-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e8c84ad389b3f36433e1be88dd7a1e36db4ab5b4848f5899cbd0a1cdc443b925"}, - {file = "Cython-0.29.22-cp34-cp34m-win32.whl", hash = "sha256:105d813eedf276588a02a240ece2f7bca8235aee9bb48e25293410c3c1ac7230"}, - {file = "Cython-0.29.22-cp34-cp34m-win_amd64.whl", hash = "sha256:af646d37e23fc3ba217c587096fac4e57006d8173d2a2365763287071e0eec2d"}, - {file = "Cython-0.29.22-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:d73a4851d1dbdcc70e619ccb104877d5e523d0fc5aef6fb6fbba4344c1f60cae"}, - {file = "Cython-0.29.22-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:88bebb8803adac0b64426fd261752925e8bf301b1986078e9506670bcbb5307f"}, - {file = "Cython-0.29.22-cp35-cp35m-win32.whl", hash = "sha256:7265abb33f154663a052c47213b978ec703f89a938bca38f425f25d4e8ba2211"}, - {file = "Cython-0.29.22-cp35-cp35m-win_amd64.whl", hash = "sha256:f7064df99094b4fd09c9ebcca6c94a652fac04c7dce514a4f2ab6ce23fcd0ba4"}, - {file = "Cython-0.29.22-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:97511722515b66851d3f77d64b1970a97e5114c4007efd5a0a307936e156a24c"}, - {file = "Cython-0.29.22-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:ab4c34d8693a62f1b8261a131784a3db397ceaa03ea90a5799574623acaa2c2c"}, - {file = "Cython-0.29.22-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6609babc68d37bb92a1b85ebd867405f720aeb2a6af946f6bd7f71b22dbb91cf"}, - {file = "Cython-0.29.22-cp36-cp36m-win32.whl", hash = "sha256:40a87c9ecd0b1b485804c70b16427e88bd07bff9f270d022d872869ff4d6ac6e"}, - {file = "Cython-0.29.22-cp36-cp36m-win_amd64.whl", hash = "sha256:ba7f34e07ca0d1ce4ba8d9e3da6d2eb0b1ac6cb9896d2c821b28a9ac9504bcfe"}, - {file = "Cython-0.29.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a8aee32da407f215652959fc6568277cfd35a24714a5238c8a9778bf34d91ace"}, - {file = "Cython-0.29.22-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:92c028e0d4ac9e32b2cf6c970f7f6c5c3aaa87f011798201ef56746705a8f01a"}, - {file = "Cython-0.29.22-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c4f2c8cceffd8403468b542570826174564fe2c6549dd199a75c13720add4981"}, - {file = "Cython-0.29.22-cp37-cp37m-win32.whl", hash = "sha256:761b965b5abcea072ba642dbb740e44532bc88618f34b605c05de1d6088b1cdd"}, - {file = "Cython-0.29.22-cp37-cp37m-win_amd64.whl", hash = "sha256:a6f5bf7fece95dd62ba13a9edb7d3ecc1a9097e6f0fde78c5fad763163157472"}, - {file = "Cython-0.29.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bbc48be45ee9eba2d0268bf616220cfb46d998ad85524f3cf752d55eb6dd3379"}, - {file = "Cython-0.29.22-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3ce10572a6b7fd5baa755f11cdf09830ae518e6f837aa38c31ec534b1f874fd4"}, - {file = "Cython-0.29.22-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b53c9d7b545c70f462408000affdd330cb87d05f83a58a9ccd72d19c8df80d56"}, - {file = "Cython-0.29.22-cp38-cp38-win32.whl", hash = "sha256:fedecbd2ad6535e30dd3b43ca9b493a1930d5167257a3fb60687bd425f6fdc54"}, - {file = "Cython-0.29.22-cp38-cp38-win_amd64.whl", hash = "sha256:182e78d75515e3d50f31cfde501fbf2af7ee0698e8149f41048db5d3c98ffc8f"}, - {file = "Cython-0.29.22-cp39-cp39-manylinux1_i686.whl", hash = "sha256:5cc5e72a106d7abc12b6e58883c914bce0b2031df6035014094a15593052db12"}, - {file = "Cython-0.29.22-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:dc42b78b62815abea19a37d186011cca57f72ec509f56fa6c43a71b2e8c6a24f"}, - {file = "Cython-0.29.22-py2.py3-none-any.whl", hash = "sha256:f5f57b2e13cc3aa95526145ffa551df529f04bf5d137c59350e7accff9362dc0"}, - {file = "Cython-0.29.22.tar.gz", hash = "sha256:df6b83c7a6d1d967ea89a2903e4a931377634a297459652e4551734c48195406"}, + {file = "Cython-0.29.24-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:6a2cf2ccccc25413864928dfd730c29db6f63eaf98206c1e600003a445ca7f58"}, + {file = "Cython-0.29.24-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b28f92e617f540d3f21f8fd479a9c6491be920ffff672a4c61b7fc4d7f749f39"}, + {file = "Cython-0.29.24-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:37bcfa5df2a3009f49624695d917c3804fccbdfcdc5eda6378754a879711a4d5"}, + {file = "Cython-0.29.24-cp27-cp27m-win32.whl", hash = "sha256:9164aeef1af6f837e4fc20402a31d256188ba4d535e262c6cb78caf57ad744f8"}, + {file = "Cython-0.29.24-cp27-cp27m-win_amd64.whl", hash = "sha256:73ac33a4379056a02031baa4def255717fadb9181b5ac2b244792d53eae1c925"}, + {file = "Cython-0.29.24-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:09ac3087ac7a3d489ebcb3fb8402e00c13d1a3a1c6bc73fd3b0d756a3e341e79"}, + {file = "Cython-0.29.24-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:774cb8fd931ee1ba52c472bc1c19077cd6895c1b24014ae07bb27df59aed5ebe"}, + {file = "Cython-0.29.24-cp34-cp34m-win32.whl", hash = "sha256:5dd56d0be50073f0e54825a8bc3393852de0eed126339ecbca0ae149dba55cfc"}, + {file = "Cython-0.29.24-cp34-cp34m-win_amd64.whl", hash = "sha256:88dc3c250dec280b0489a83950b15809762e27232f4799b1b8d0bad503f5ab84"}, + {file = "Cython-0.29.24-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:5fa12ebafc2f688ea6d26ab6d1d2e634a9872509ba7135b902bb0d8b368fb04b"}, + {file = "Cython-0.29.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:60c958bcab0ff315b4036a949bed1c65334e1f6a69e17e9966d742febb59043a"}, + {file = "Cython-0.29.24-cp35-cp35m-win32.whl", hash = "sha256:166f9f29cd0058ce1a14a7b3a2458b849ed34b1ec5fd4108af3fdd2c24afcbb0"}, + {file = "Cython-0.29.24-cp35-cp35m-win_amd64.whl", hash = "sha256:76cbca0188d278e93d12ebdaf5990678e6e436485fdfad49dbe9b07717d41a3c"}, + {file = "Cython-0.29.24-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f2e9381497b12e8f622af620bde0d1d094035d79b899abb2ddd3a7891f535083"}, + {file = "Cython-0.29.24-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d8d1a087f35e39384303f5e6b75d465d6f29d746d7138eae9d3b6e8e6f769eae"}, + {file = "Cython-0.29.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:112efa54a58293a4fb0acf0dd8e5b3736e95b595eee24dd88615648e445abe41"}, + {file = "Cython-0.29.24-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cf4452f0e4d50e11701bca38f3857fe6fa16593e7fd6a4d5f7be66f611b7da2"}, + {file = "Cython-0.29.24-cp36-cp36m-win32.whl", hash = "sha256:854fe2193d3ad4c8b61932ff54d6dbe10c5fa8749eb8958d72cc0ab28243f833"}, + {file = "Cython-0.29.24-cp36-cp36m-win_amd64.whl", hash = "sha256:84826ec1c11cda56261a252ddecac0c7d6b02e47e81b94f40b27b4c23c29c17c"}, + {file = "Cython-0.29.24-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ade74eece909fd3a437d9a5084829180751d7ade118e281e9824dd75eafaff2"}, + {file = "Cython-0.29.24-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0a142c6b862e6ed6b02209d543062c038c110585b5e32d1ad7c9717af4f07e41"}, + {file = "Cython-0.29.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:10cb3def9774fa99e4583617a5616874aed3255dc241fd1f4a3c2978c78e1c53"}, + {file = "Cython-0.29.24-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f41ef7edd76dd23315925e003f0c58c8585f3ab24be6885c4b3f60e77c82746"}, + {file = "Cython-0.29.24-cp37-cp37m-win32.whl", hash = "sha256:821c2d416ad7d006b069657ee1034c0e0cb45bdbe9ab6ab631e8c495dfcfa4ac"}, + {file = "Cython-0.29.24-cp37-cp37m-win_amd64.whl", hash = "sha256:2d9e61ed1056a3b6a4b9156b62297ad18b357a7948e57a2f49b061217696567e"}, + {file = "Cython-0.29.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b0ee28c2c8118bfb3ad9b25cf7a6cbd724e442ea96956e32ccd908d5e3e043"}, + {file = "Cython-0.29.24-cp38-cp38-manylinux1_i686.whl", hash = "sha256:eb2843f8cc01c645725e6fc690a84e99cdb266ce8ebe427cf3a680ff09f876aa"}, + {file = "Cython-0.29.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:661dbdea519d9cfb288867252b75fef73ffa8e8bb674cec27acf70646afb369b"}, + {file = "Cython-0.29.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc05de569f811be1fcfde6756c9048ae518f0c4b6d9f8f024752c5365d934cac"}, + {file = "Cython-0.29.24-cp38-cp38-win32.whl", hash = "sha256:a102cfa795c6b3b81a29bdb9dbec545367cd7f353c03e6f30a056fdfefd92854"}, + {file = "Cython-0.29.24-cp38-cp38-win_amd64.whl", hash = "sha256:416046a98255eff97ec02077d20ebeaae52682dfca1c35aadf31260442b92514"}, + {file = "Cython-0.29.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ad43e684ade673565f6f9d6638015112f6c7f11aa2a632167b79014f613f0f5f"}, + {file = "Cython-0.29.24-cp39-cp39-manylinux1_i686.whl", hash = "sha256:afb521523cb46ddaa8d269b421f88ea2731fee05e65b952b96d4db760f5a2a1c"}, + {file = "Cython-0.29.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0d414458cb22f8a90d64260da6dace5d5fcebde43f31be52ca51f818c46db8cb"}, + {file = "Cython-0.29.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cb87777e82d1996aef6c146560a19270684271c9c669ba62ac6803b3cd2ff82"}, + {file = "Cython-0.29.24-cp39-cp39-win32.whl", hash = "sha256:91339ee4b465924a3ea4b2a9cec7f7227bc4cadf673ce859d24c2b9ef60b1214"}, + {file = "Cython-0.29.24-cp39-cp39-win_amd64.whl", hash = "sha256:5fb977945a2111f6b64501fdf7ed0ec162cc502b84457fd648d6a558ea8de0d6"}, + {file = "Cython-0.29.24-py2.py3-none-any.whl", hash = "sha256:f96411f0120b5cae483923aaacd2872af8709be4b46522daedc32f051d778385"}, + {file = "Cython-0.29.24.tar.gz", hash = "sha256:cdf04d07c3600860e8c2ebaad4e8f52ac3feb212453c1764a49ac08c827e8443"}, ] falcon = [ - {file = "falcon-2.0.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:733033ec80c896e30a43ab3e776856096836787197a44eb21022320a61311983"}, - {file = "falcon-2.0.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f93351459f110b4c1ee28556aef9a791832df6f910bea7b3f616109d534df06b"}, - {file = "falcon-2.0.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e9efa0791b5d9f9dd9689015ea6bce0a27fcd5ecbcd30e6d940bffa4f7f03389"}, - {file = "falcon-2.0.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:59d1e8c993b9a37ea06df9d72cf907a46cc8063b30717cdac2f34d1658b6f936"}, - {file = "falcon-2.0.0-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:a5ebb22a04c9cc65081938ee7651b4e3b4d2a28522ea8ec04c7bdd2b3e9e8cd8"}, - {file = "falcon-2.0.0-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:95bf6ce986c1119aef12c9b348f4dee9c6dcc58391bdd0bc2b0bf353c2b15986"}, - {file = "falcon-2.0.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:aa184895d1ad4573fbfaaf803563d02f019ebdf4790e41cc568a330607eae439"}, - {file = "falcon-2.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:74cf1d18207381c665b9e6292d65100ce146d958707793174b03869dc6e614f4"}, - {file = "falcon-2.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24adcd2b29a8ffa9d552dc79638cd21736a3fb04eda7d102c6cebafdaadb88ad"}, - {file = "falcon-2.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:18157af2a4fc3feedf2b5dcc6196f448639acf01c68bc33d4d5a04c3ef87f494"}, - {file = "falcon-2.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e3782b7b92fefd46a6ad1fd8fe63fe6c6f1b7740a95ca56957f48d1aee34b357"}, - {file = "falcon-2.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:9712975adcf8c6e12876239085ad757b8fdeba223d46d23daef82b47658f83a9"}, - {file = "falcon-2.0.0-py2.py3-none-any.whl", hash = "sha256:54f2cb4b687035b2a03206dbfc538055cc48b59a953187b0458aa1b574d47b53"}, - {file = "falcon-2.0.0.tar.gz", hash = "sha256:eea593cf466b9c126ce667f6d30503624ef24459f118c75594a69353b6c3d5fc"}, + {file = "falcon-3.0.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:94fb4582212768ac425d023b7884e60d09a0bd4c5cd50ca8af0272af1cba5da6"}, + {file = "falcon-3.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:56b267fa2df7e0400a639cf40a994baac19170425b0b8bbad5a8a81e07f9717d"}, + {file = "falcon-3.0.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:085b30b09ff4bdb31fda0a83a65f427d8dd4b5b5b21058781c38aff9747b5991"}, + {file = "falcon-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65b1798026e3dbd2d323fa9b03f90e3827be4fe0d3c1f9e3ba3d4a7a001de566"}, + {file = "falcon-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1f70c6f086c53b0cc819a0725d3814ad62e105b62d4c4e2c46322f13e7910e7"}, + {file = "falcon-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ec7fc600ffee2377beeeeca32d8171ff305e9267bcd37bba5a7ce8af1e177f"}, + {file = "falcon-3.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a9d5be8902e977ac93aeebf2b8959e2c3d82783d7ea6a1fc80cef5352b83549b"}, + {file = "falcon-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:a95b6a373b8f6014b0bc7090b1de031c9d237007211ef55a19b60241cf728e61"}, + {file = "falcon-3.0.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:260645c13d477af434fc200ec67734efc41e620b3f6e0479e722897511166b46"}, + {file = "falcon-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:485ef504d196390ebc0974cefd3f5fab4ad8a3ede4e5a7c0a803f555bcd8da45"}, + {file = "falcon-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1280db58c2af48b1ba24e39674fb6d84389eff5c4772a327a5af606eeead272"}, + {file = "falcon-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff4672f3549b00b62e710d3169903d14e37726f04045a0563b56d9af3fba271d"}, + {file = "falcon-3.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1bdf8085877bd049f799a34680d42fa82e2b93dcf8320d092f7e75933d0afcee"}, + {file = "falcon-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:16f8735512af3f52473e3eda37e75bf697f6ced5afc3e9dc7110c430777823ab"}, + {file = "falcon-3.0.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:3710b051f54c158310b45b1432a993803cdccb3e167d3e89aa93076ff77d2673"}, + {file = "falcon-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa46751209af4f4882d3d60e430ea586e170bc03e1bd5b08cb16f6b96068febc"}, + {file = "falcon-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6afb13a80b6a4383a66093af7bb0e8e02433ca5ebc7516842a6a3f112c844ae"}, + {file = "falcon-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0212df91414c13c08a9cf4023488b2d47956712f712332f420bb0c7bdf39c6fa"}, + {file = "falcon-3.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0df4dee0ef89b4de5e2ba4402ac249942b09758a0decdc7a63d5edb3792c4c1c"}, + {file = "falcon-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:c9a3cf58f9f3c9769bff3b24037b150c9f6658df4c899d68fa433f5acdfdb004"}, + {file = "falcon-3.0.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:514dee9b6d66408e43fcef9aef2436004cd2e3163625f194dd064fce67269cce"}, + {file = "falcon-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce052e91b8325a76ddc2066e35bb032e0be4671cd824f027d1826c68a0fd09e3"}, + {file = "falcon-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6c7f9b2063a4c0ac2516df014c5299ae098579e83025d342f31fe1ef8e994d7"}, + {file = "falcon-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee78a9934f8143c5eef9bfe949044c7eab3fef80a51cbc67cf6cb6b34c5613ce"}, + {file = "falcon-3.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a70fc0f9f115e763effdf9fc6140a2b5df9f37bd2707f3b29e0a1357dbf53784"}, + {file = "falcon-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:c3abcd37545de531e7dada4113c88f01e86c7596c7c59300769d64ea7771a75e"}, + {file = "falcon-3.0.1.tar.gz", hash = "sha256:c41d84db325881a870e8b7129d5ecfd972fa4323cf77b7119a1d2a21966ee681"}, ] humanize = [ - {file = "humanize-3.2.0-py3-none-any.whl", hash = "sha256:d47d80cd47c1511ed3e49ca5f10c82ed940ea020b45b49ab106ed77fa8bb9d22"}, - {file = "humanize-3.2.0.tar.gz", hash = "sha256:ab69004895689951b79f2ae4fdd6b8127ff0c180aff107856d5d98119a33f026"}, + {file = "humanize-3.12.0-py3-none-any.whl", hash = "sha256:4c71c4381f0209715cd993058e717c1b74d58ae2f8c6da7bdb59ab66473b9ab0"}, + {file = "humanize-3.12.0.tar.gz", hash = "sha256:5ec1a66e230a3e31fb3f184aab9436ea13d4e37c168e0ffc345ae5bb57e58be6"}, ] idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, + {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, ] jack-client = [ {file = "JACK-Client-0.5.3.tar.gz", hash = "sha256:31cbdcc90cd303997a3ea540f352d89b23c2f422cbf7c197292a1b8e1ca21aca"}, {file = "JACK_Client-0.5.3-py3-none-any.whl", hash = "sha256:0214d9366e0a65bf0a99866990aae5a443afb3c283006d236d8cebcb769a59b8"}, ] mido = [ - {file = "mido-1.2.9-py2.py3-none-any.whl", hash = "sha256:fc6364efa028c8405166f63e6a83cbc6c17aaeac2c28680abe64ae48703a89dd"}, - {file = "mido-1.2.9.tar.gz", hash = "sha256:c4a7d5528fefa3d3dcaa2056d4bc873e2c96a395658d15af5a89c8c3fa7c7acc"}, + {file = "mido-1.2.10-py2.py3-none-any.whl", hash = "sha256:0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e"}, + {file = "mido-1.2.10.tar.gz", hash = "sha256:17b38a8e4594497b850ec6e78b848eac3661706bfc49d484a36d91335a373499"}, ] packaging = [ - {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, - {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, + {file = "packaging-21.2-py3-none-any.whl", hash = "sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0"}, + {file = "packaging-21.2.tar.gz", hash = "sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966"}, ] pyalsa = [] pycairo = [ - {file = "pycairo-1.20.0-cp36-cp36m-win32.whl", hash = "sha256:e5a3433690c473e073a9917dc8f1fc7dc8b9af7b201bf372894b8ad70d960c6d"}, - {file = "pycairo-1.20.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a942614923b88ae75c794506d5c426fba9c46a055d3fdd3b8db7046b75c079cc"}, - {file = "pycairo-1.20.0-cp37-cp37m-win32.whl", hash = "sha256:8cfa9578b745fb9cf2915ec580c2c50ebc2da00eac2cf4c4b54b63aa19da4b77"}, - {file = "pycairo-1.20.0-cp37-cp37m-win_amd64.whl", hash = "sha256:273a33c56aba724ec42fe1d8f94c86c2e2660c1277470be9b04e5113d7c5b72d"}, - {file = "pycairo-1.20.0-cp38-cp38-win32.whl", hash = "sha256:2088100a099c09c5e90bf247409ce6c98f51766b53bd13f96d6aac7addaa3e66"}, - {file = "pycairo-1.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:ceb1edcbeb48dabd5fbbdff2e4b429aa88ddc493d6ebafe78d94b050ac0749e2"}, - {file = "pycairo-1.20.0-cp39-cp39-win32.whl", hash = "sha256:57a768f4edc8a9890d98070dd473a812ac3d046cef4bc1c817d68024dab9a9b4"}, - {file = "pycairo-1.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:57166119e424d71eccdba6b318bd731bdabd17188e2ba10d4f315f7bf16ace3f"}, - {file = "pycairo-1.20.0.tar.gz", hash = "sha256:5695a10cb7f9ae0d01f665b56602a845b0a8cb17e2123bfece10c2e58552468c"}, + {file = "pycairo-1.20.1-cp310-cp310-win32.whl", hash = "sha256:736ffc618e851601e861a630293e5c910ef016b83b2d035a336f83a367bf56ab"}, + {file = "pycairo-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:261c69850d4b2ec03346c9745bad2a835bb8124e4c6961b8ceac503d744eb3b3"}, + {file = "pycairo-1.20.1-cp36-cp36m-win32.whl", hash = "sha256:6db823a18e7be1eb2a29c28961f2f01e84d3b449f06be7338d05ac8f90592cd5"}, + {file = "pycairo-1.20.1-cp36-cp36m-win_amd64.whl", hash = "sha256:5525da2d8de912750dd157752aa96f1f0a42a437c5625e85b14c936b5c6305ae"}, + {file = "pycairo-1.20.1-cp37-cp37m-win32.whl", hash = "sha256:c8c2bb933974d91c5d19e54b846d964de177e7bf33433bf34ac34c85f9b30e94"}, + {file = "pycairo-1.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9a32e4a3574a104aa876c35d5e71485dfd6986b18d045534c6ec510c44d5d6a7"}, + {file = "pycairo-1.20.1-cp38-cp38-win32.whl", hash = "sha256:0d7a6754d410d911a46f00396bee4be96500ccd3d178e7e98aef1140e3dd67ae"}, + {file = "pycairo-1.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:b605151cdd23cedb31855b8666371b6e26b80f02753a52c8b8023a916b1df812"}, + {file = "pycairo-1.20.1-cp39-cp39-win32.whl", hash = "sha256:e800486b51fffeb11ed867b4f2220d446e2a60a81a73b7c377123e0cbb72f49d"}, + {file = "pycairo-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:f123d3818e30b77b7209d70a6dcfd5b4e34885f9fa539d92dd7ff3e4e2037213"}, + {file = "pycairo-1.20.1.tar.gz", hash = "sha256:1ee72b035b21a475e1ed648e26541b04e5d7e753d75ca79de8c583b25785531b"}, ] pycparser = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, ] pygobject = [ - {file = "PyGObject-3.38.0.tar.gz", hash = "sha256:051b950f509f2e9f125add96c1493bde987c527f7a0c15a1f7b69d6d1c3cd8e6"}, + {file = "PyGObject-3.42.0.tar.gz", hash = "sha256:b9803991ec0b0b4175e81fee0ad46090fa7af438fe169348a9b18ae53447afcd"}, ] pyliblo = [ {file = "pyliblo-0.10.0.tar.gz", hash = "sha256:fc67f1950b827272b00f9f0dc4ed7113c0ccef0c1c09e9976dead40ebbf1798f"}, @@ -399,68 +482,78 @@ pyparsing = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pyqt5 = [ - {file = "PyQt5-5.15.3-cp36.cp37.cp38.cp39-abi3-macosx_10_13_intel.whl", hash = "sha256:69fdceed983ebd388f44ceab9b8a6c2c086c79c705182fcc7b06837e2ccf7ba3"}, - {file = "PyQt5-5.15.3-cp36.cp37.cp38.cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:8b9e07094382d51dc34f63c5a417b735dae1ad934a9dd9bc08bbc0a39452bfe8"}, - {file = "PyQt5-5.15.3-cp36.cp37.cp38.cp39-none-win32.whl", hash = "sha256:ef2682dc829d6603fb5c958274d721fb076fd84a88fcb8f9fc7655cbb72b2bfc"}, - {file = "PyQt5-5.15.3-cp36.cp37.cp38.cp39-none-win_amd64.whl", hash = "sha256:4e0fc6993df120e686528de46f2e002e930e24f99f103788724f0bd8bec9b4f7"}, - {file = "PyQt5-5.15.3.tar.gz", hash = "sha256:965ba50e7029b37f218a54ace24e87c77db3e5a9f0b83baeb21fb57b4154b838"}, + {file = "PyQt5-5.15.6-cp36-abi3-macosx_10_13_x86_64.whl", hash = "sha256:33ced1c876f6a26e7899615a5a4efef2167c263488837c7beed023a64cebd051"}, + {file = "PyQt5-5.15.6-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:9d6efad0377aa78bf081a20ac752ce86096ded18f04c592d98f5b92dc879ad0a"}, + {file = "PyQt5-5.15.6-cp36-abi3-win32.whl", hash = "sha256:9d2dcdaf82263ae56023410a7af15d1fd746c8e361733a7d0d1bd1f004ec2793"}, + {file = "PyQt5-5.15.6-cp36-abi3-win_amd64.whl", hash = "sha256:f411ecda52e488e1d3c5cce7563e1b2ca9cf0b7531e3c25b03d9a7e56e07e7fc"}, + {file = "PyQt5-5.15.6.tar.gz", hash = "sha256:80343bcab95ffba619f2ed2467fd828ffeb0a251ad7225be5fc06dcc333af452"}, ] -pyqt5-qt = [ - {file = "PyQt5_Qt-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:962511bfdc847f0746e1de92499cbb1ae127ca5ec356f009a6d9f924fe1230d5"}, - {file = "PyQt5_Qt-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:dec2e88fbfabc0d3365bb4007c88c75fc99fc75cfb418399aeb5acd1d5f7d484"}, - {file = "PyQt5_Qt-5.15.2-py3-none-win32.whl", hash = "sha256:8aba4f0a245c42f186a235c47011ee422a14b949e52fc64e697b10d1c433ff39"}, - {file = "PyQt5_Qt-5.15.2-py3-none-win_amd64.whl", hash = "sha256:756b8c055033b2d96d18e74e1380bdb64984b150879fa92cd62d327f78636153"}, +pyqt5-qt5 = [ + {file = "PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"}, + {file = "PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"}, + {file = "PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"}, + {file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"}, ] pyqt5-sip = [ - {file = "PyQt5_sip-12.8.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:bb5a87b66fc1445915104ee97f7a20a69decb42f52803e3b0795fa17ff88226c"}, - {file = "PyQt5_sip-12.8.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a29e2ac399429d3b7738f73e9081e50783e61ac5d29344e0802d0dcd6056c5a2"}, - {file = "PyQt5_sip-12.8.1-cp35-cp35m-win32.whl", hash = "sha256:0304ca9114b9817a270f67f421355075b78ff9fc25ac58ffd72c2601109d2194"}, - {file = "PyQt5_sip-12.8.1-cp35-cp35m-win_amd64.whl", hash = "sha256:84ba7746762bd223bed22428e8561aa267a229c28344c2d28c5d5d3f8970cffb"}, - {file = "PyQt5_sip-12.8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:7b81382ce188d63890a0e35abe0f9bb946cabc873a31873b73583b0fc84ac115"}, - {file = "PyQt5_sip-12.8.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b6d42250baec52a5f77de64e2951d001c5501c3a2df2179f625b241cbaec3369"}, - {file = "PyQt5_sip-12.8.1-cp36-cp36m-win32.whl", hash = "sha256:6c1ebee60f1d2b3c70aff866b7933d8d8d7646011f7c32f9321ee88c290aa4f9"}, - {file = "PyQt5_sip-12.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:34dcd29be47553d5f016ff86e89e24cbc5eebae92eb2f96fb32d2d7ba028c43c"}, - {file = "PyQt5_sip-12.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ed897c58acf4a3cdca61469daa31fe6e44c33c6c06a37c3f21fab31780b3b86a"}, - {file = "PyQt5_sip-12.8.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a1b8ef013086e224b8e86c93f880f776d01b59195bdfa2a8e0b23f0480678fec"}, - {file = "PyQt5_sip-12.8.1-cp37-cp37m-win32.whl", hash = "sha256:0cd969be528c27bbd4755bd323dff4a79a8fdda28215364e6ce3e069cb56c2a9"}, - {file = "PyQt5_sip-12.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c9800729badcb247765e4ffe2241549d02da1fa435b9db224845bc37c3e99cb0"}, - {file = "PyQt5_sip-12.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9312ec47cac4e33c11503bc1cbeeb0bdae619620472f38e2078c5a51020a930f"}, - {file = "PyQt5_sip-12.8.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2f35e82fd7ec1e1f6716e9154721c7594956a4f5bd4f826d8c6a6453833cc2f0"}, - {file = "PyQt5_sip-12.8.1-cp38-cp38-win32.whl", hash = "sha256:da9c9f1e65b9d09e73bd75befc82961b6b61b5a3b9d0a7c832168e1415f163c6"}, - {file = "PyQt5_sip-12.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:832fd60a264de4134c2824d393320838f3ab648180c9c357ec58a74524d24507"}, - {file = "PyQt5_sip-12.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c317ab1263e6417c498b81f5c970a9b1af7acefab1f80b4cc0f2f8e661f29fc5"}, - {file = "PyQt5_sip-12.8.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c9d6d448c29dc6606bb7974696608f81f4316c8234f7c7216396ed110075e777"}, - {file = "PyQt5_sip-12.8.1-cp39-cp39-win32.whl", hash = "sha256:5a011aeff89660622a6d5c3388d55a9d76932f3b82c95e82fc31abd8b1d2990d"}, - {file = "PyQt5_sip-12.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:f168f0a7f32b81bfeffdf003c36f25d81c97dee5eb67072a5183e761fe250f13"}, - {file = "PyQt5_sip-12.8.1.tar.gz", hash = "sha256:30e944db9abee9cc757aea16906d4198129558533eb7fadbe48c5da2bd18e0bd"}, + {file = "PyQt5_sip-12.9.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6d5bca2fc222d58e8093ee8a81a6e3437067bb22bc3f86d06ec8be721e15e90a"}, + {file = "PyQt5_sip-12.9.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:d59af63120d1475b2bf94fe8062610720a9be1e8940ea146c7f42bb449d49067"}, + {file = "PyQt5_sip-12.9.0-cp310-cp310-win32.whl", hash = "sha256:0fc9aefacf502696710b36cdc9fa2a61487f55ee883dbcf2c2a6477e261546f7"}, + {file = "PyQt5_sip-12.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:485972daff2fb0311013f471998f8ec8262ea381bded244f9d14edaad5f54271"}, + {file = "PyQt5_sip-12.9.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:d85002238b5180bce4b245c13d6face848faa1a7a9e5c6e292025004f2fd619a"}, + {file = "PyQt5_sip-12.9.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:83c3220b1ca36eb8623ba2eb3766637b19eb0ce9f42336ad8253656d32750c0a"}, + {file = "PyQt5_sip-12.9.0-cp36-cp36m-win32.whl", hash = "sha256:d8b2bdff7bbf45bc975c113a03b14fd669dc0c73e1327f02706666a7dd51a197"}, + {file = "PyQt5_sip-12.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:69a3ad4259172e2b1aa9060de211efac39ddd734a517b1924d9c6c0cc4f55f96"}, + {file = "PyQt5_sip-12.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:42274a501ab4806d2c31659170db14c282b8313d2255458064666d9e70d96206"}, + {file = "PyQt5_sip-12.9.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6a8701892a01a5a2a4720872361197cc80fdd5f49c8482d488ddf38c9c84f055"}, + {file = "PyQt5_sip-12.9.0-cp37-cp37m-win32.whl", hash = "sha256:ac57d796c78117eb39edd1d1d1aea90354651efac9d3590aac67fa4983f99f1f"}, + {file = "PyQt5_sip-12.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4347bd81d30c8e3181e553b3734f91658cfbdd8f1a19f254777f906870974e6d"}, + {file = "PyQt5_sip-12.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c446971c360a0a1030282a69375a08c78e8a61d568bfd6dab3dcc5cf8817f644"}, + {file = "PyQt5_sip-12.9.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fc43f2d7c438517ee33e929e8ae77132749c15909afab6aeece5fcf4147ffdb5"}, + {file = "PyQt5_sip-12.9.0-cp38-cp38-win32.whl", hash = "sha256:055581c6fed44ba4302b70eeb82e979ff70400037358908f251cd85cbb3dbd93"}, + {file = "PyQt5_sip-12.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:c5216403d4d8d857ec4a61f631d3945e44fa248aa2415e9ee9369ab7c8a4d0c7"}, + {file = "PyQt5_sip-12.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a25b9843c7da6a1608f310879c38e6434331aab1dc2fe6cb65c14f1ecf33780e"}, + {file = "PyQt5_sip-12.9.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:dd05c768c2b55ffe56a9d49ce6cc77cdf3d53dbfad935258a9e347cbfd9a5850"}, + {file = "PyQt5_sip-12.9.0-cp39-cp39-win32.whl", hash = "sha256:4f8e05fe01d54275877c59018d8e82dcdd0bc5696053a8b830eecea3ce806121"}, + {file = "PyQt5_sip-12.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:b09f4cd36a4831229fb77c424d89635fa937d97765ec90685e2f257e56a2685a"}, + {file = "PyQt5_sip-12.9.0.tar.gz", hash = "sha256:d3e4489d7c2b0ece9d203ae66e573939f7f60d4d29e089c9f11daa17cfeaae32"}, ] python-rtmidi = [ - {file = "python-rtmidi-1.4.7.tar.gz", hash = "sha256:d7dbc2b174b09015dfbee449a672a072aa72b367be40b13e04ee35a2e2e399e3"}, - {file = "python_rtmidi-1.4.7-cp27-cp27m-win32.whl", hash = "sha256:b3cc68264297c9ef4e3d02a45a698c4ca88d4ad1415d009fbf740e4a5ff0bbdb"}, - {file = "python_rtmidi-1.4.7-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:ac61260bd0d63c9bde5b5c9f3585f0fffbca427cc381a99a3cdbb9a5503f48d0"}, - {file = "python_rtmidi-1.4.7-cp36-cp36m-win32.whl", hash = "sha256:18f1d2873f9514fd3f2de3ca9a1722c470fad0505f79738d03ee1d13ec3a846c"}, - {file = "python_rtmidi-1.4.7-cp36-cp36m-win_amd64.whl", hash = "sha256:2a2bf0a9526fadbe4de8e438899f0932e497b03f0b549eaf329cb05217b76cfa"}, - {file = "python_rtmidi-1.4.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:de321ee7c68485563a0777b72ea9db5fc8bee6d413ddc125dc881e904bf9c602"}, - {file = "python_rtmidi-1.4.7-cp37-cp37m-win32.whl", hash = "sha256:fd57b90b28728b6781fdbac78f71b2538b127db5d0fd8aa9304efc7b58daf994"}, - {file = "python_rtmidi-1.4.7-cp37-cp37m-win_amd64.whl", hash = "sha256:179b2acaa3b2498f71633988799fc392a77bfe983a66b94f70bd35615c94fa33"}, - {file = "python_rtmidi-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:266ad15fbd97713d4bef07a31ff72ff13cc8cdecc9f4518d2537b01d3f53e47d"}, - {file = "python_rtmidi-1.4.7-cp38-cp38-win32.whl", hash = "sha256:d1288a0faf12735afd842edba2362ec696f56b4b86917fa4e3113aee3976c84d"}, - {file = "python_rtmidi-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:96b1ca04b4aea71320d71fae81ab882767bd8d801763a54b20ef7e56d4d39a07"}, - {file = "python_rtmidi-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:922cdcf677e845750d763c114dd334d8ee54bd89f4729bf02bc6a8d3afe6456d"}, + {file = "python-rtmidi-1.4.9.tar.gz", hash = "sha256:bfeb4ed99d0cccf6fa2837566907652ded7adc1c03b69f2160c9de4082301302"}, + {file = "python_rtmidi-1.4.9-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:8f7e154681c5d6ed7228ce8639708592da758ef4c0f575f7020854e07ca6478b"}, + {file = "python_rtmidi-1.4.9-cp36-cp36m-win32.whl", hash = "sha256:6f495672ec76700400d4dff6f8848dbd52ca60301ed6011a6a1b3a9e95a7a07e"}, + {file = "python_rtmidi-1.4.9-cp36-cp36m-win_amd64.whl", hash = "sha256:4d75788163327f6ac1f898c29e3b4527da83dbc5bab5a7e614b6a4385fde3231"}, + {file = "python_rtmidi-1.4.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d201516bb1c64511e7e4de50533f6b828072113e3c26f3f5b657f11b90252073"}, + {file = "python_rtmidi-1.4.9-cp37-cp37m-win32.whl", hash = "sha256:0f5409e1b2e92cfe377710a0ea5c450c58fda8b52ec4bf4baf517aa731d9f6a6"}, + {file = "python_rtmidi-1.4.9-cp37-cp37m-win_amd64.whl", hash = "sha256:3d4b7fdb477d8036d51cce281b303686114ae676f1c83693db963ab01db11cf5"}, + {file = "python_rtmidi-1.4.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5dad7a28035eea9e24aaab4fcb756cd2b78c5b14835aa64750cb4062f77ec169"}, + {file = "python_rtmidi-1.4.9-cp38-cp38-win32.whl", hash = "sha256:7d27d0a70e85d991f1451f286416cf5ef4514292b027155bf91dcae0b8c0d5d2"}, + {file = "python_rtmidi-1.4.9-cp38-cp38-win_amd64.whl", hash = "sha256:69907663e0f167fcf3fc1632a792419d0d9d00550f94dca39370ebda3bc999db"}, + {file = "python_rtmidi-1.4.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04bd95dd86fef3fe8d72838f719d31875f107a842127024bfe276617961eed5d"}, + {file = "python_rtmidi-1.4.9-cp39-cp39-win32.whl", hash = "sha256:2286ab096a5603430ab1e1a664fe4d96acc40f9443f44c27e911dfad85ea3ac8"}, + {file = "python_rtmidi-1.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:54d40cb794a6b079105bfb923ab0b4d5f041e9eef5dc5cce25480e4c3e3fc2f6"}, ] requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, + {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, + {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, ] sortedcontainers = [ - {file = "sortedcontainers-2.3.0-py2.py3-none-any.whl", hash = "sha256:37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f"}, - {file = "sortedcontainers-2.3.0.tar.gz", hash = "sha256:59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1"}, + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +typing-extensions = [ + {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, + {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, + {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, +] urllib3 = [ - {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, - {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, + {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, + {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, +] +zipp = [ + {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, + {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, ] diff --git a/pyproject.toml b/pyproject.toml index 9fa620839..852ca049d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ exclude = [ python = "^3.6" appdirs = "^1.4.1" cython = "^0.29" -falcon = "^2.0" +falcon = "^3.0" jack-client = "^0.5" mido = "^1.2" pygobject = "^3.30" From 6b1bfbe97aa00c7d0d59b149624f0c77f9a7a72c Mon Sep 17 00:00:00 2001 From: s0600204 <2053873+s0600204@users.noreply.github.com> Date: Wed, 10 Nov 2021 14:16:22 +0000 Subject: [PATCH 263/333] Restore background to `RunningCueWidget` instances (#213) --- lisp/ui/themes/dark/theme.qss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lisp/ui/themes/dark/theme.qss b/lisp/ui/themes/dark/theme.qss index 5a159a7cf..bce8ba851 100644 --- a/lisp/ui/themes/dark/theme.qss +++ b/lisp/ui/themes/dark/theme.qss @@ -717,6 +717,10 @@ CueListView:focus { border: 1px solid palette(light); } +RunningCueWidget>QWidget { + background-color: palette(mid); +} + #ListTimeWidget { height: 25px; border-radius: 0px; From 49ae03da211d791fab242bc9ad000d44caa9fea8 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 24 Nov 2021 22:26:30 +0100 Subject: [PATCH 264/333] Increase timers resolution, from 100ms to 33ms --- lisp/core/clock.py | 5 ++--- lisp/core/util.py | 2 +- lisp/cues/cue_time.py | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lisp/core/clock.py b/lisp/core/clock.py index 896a51274..3b3819092 100644 --- a/lisp/core/clock.py +++ b/lisp/core/clock.py @@ -23,9 +23,7 @@ class Clock(QTimer): """Clock based on Qt.QTimer class. - The class provide two functions to add and remove - callbacks, in this way the clock is running only when - there's one, or more, callbacks. + The clock is running only when there's one, or more, callbacks. """ def __init__(self, timeout): @@ -53,4 +51,5 @@ def remove_callback(self, callback): Clock_10 = Clock(10) +Clock_33 = Clock(33) Clock_100 = Clock(100) diff --git a/lisp/core/util.py b/lisp/core/util.py index f32d8af03..5385d781d 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -108,7 +108,7 @@ def time_tuple(milliseconds): def strtime(time, accurate=False): """Return a string from the given milliseconds time. - - when > 59min -> hh:mm:ss + - when >= 1h -> hh:mm:ss - when < 1h and accurate -> mm:ss:00 - when < 1h and not accurate -> mm:ss:z0 """ diff --git a/lisp/cues/cue_time.py b/lisp/cues/cue_time.py index 1e121b17f..5d22b9f16 100644 --- a/lisp/cues/cue_time.py +++ b/lisp/cues/cue_time.py @@ -18,7 +18,7 @@ from enum import IntEnum from weakref import WeakValueDictionary -from lisp.core.clock import Clock_100 +from lisp.core.clock import Clock_33 from lisp.core.decorators import locked_method from lisp.core.signal import Connection, Signal from lisp.cues.cue import CueState @@ -49,7 +49,7 @@ class CueTime(metaclass=MetaCueTime): The notify signal is emitted only when the cue is running. """ - _Clock = Clock_100 + _Clock = Clock_33 def __init__(self, cue): self.notify = Signal() @@ -115,7 +115,7 @@ class Mode(IntEnum): def __init__(self, cue, mode=Mode.Pre): self.notify = Signal() - self._clock = Clock_100 + self._clock = Clock_33 self._start_time = 0 self._last = 0 self._cue = cue From 5b0f4d3bd5de0d7bb3ec5e7b95b9de25358c6723 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 24 Nov 2021 22:28:50 +0100 Subject: [PATCH 265/333] Fix: ignore pyqt5-qt5 package when building the flatpak bundle --- scripts/flatpak/config.sh | 2 +- scripts/flatpak/python-modules.json | 69 ++++++++++++++++++----------- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/scripts/flatpak/config.sh b/scripts/flatpak/config.sh index 8c9230094..50fbd418b 100755 --- a/scripts/flatpak/config.sh +++ b/scripts/flatpak/config.sh @@ -7,7 +7,7 @@ export FLATPAK_SDK="org.gnome.Sdk" export FLATPAK_INSTALL="$FLATPAK_RUNTIME//$FLATPAK_RUNTIME_VERSION $FLATPAK_SDK//$FLATPAK_RUNTIME_VERSION" export FLATPAK_PY_LOCKFILE="../../poetry.lock" -export FLATPAK_PY_IGNORE_PACKAGES="pygobject pycairo pyqt5 pyqt5-sip pyqt5-qt" +export FLATPAK_PY_IGNORE_PACKAGES="pygobject pycairo pyqt5 pyqt5-sip pyqt5-qt pyqt5-qt5" export FLATPAK_APP_ID="org.linux_show_player.LinuxShowPlayer" export FLATPAK_APP_MODULE="linux-show-player" \ No newline at end of file diff --git a/scripts/flatpak/python-modules.json b/scripts/flatpak/python-modules.json index 71612148c..2ec7c9f5e 100644 --- a/scripts/flatpak/python-modules.json +++ b/scripts/flatpak/python-modules.json @@ -2,29 +2,39 @@ "name": "python-modules", "buildsystem": "simple", "build-commands": [ - "pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} appdirs certifi cffi chardet falcon humanize idna jack-client mido pycparser pyliblo python-rtmidi requests sortedcontainers urllib3" + "pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} appdirs certifi cffi charset-normalizer falcon humanize idna importlib-metadata jack-client mido pycparser pyliblo python-rtmidi requests sortedcontainers typing-extensions urllib3 zipp" ], "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/19/c7/fa589626997dd07bd87d9269342ccb74b1720384a4d739a1872bd84fbe68/chardet-4.0.0-py2.py3-none-any.whl", - "sha256": "f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" + "url": "https://files.pythonhosted.org/packages/04/a2/d918dcd22354d8958fe113e1a3630137e0fc8b44859ade3063982eacd2a4/idna-3.3-py3-none-any.whl", + "sha256": "84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/49/ac/7673fc4c19f0ecd258c3a9636d63cb77003c2c6afc46943d3db752b35758/humanize-3.2.0-py3-none-any.whl", - "sha256": "d47d80cd47c1511ed3e49ca5f10c82ed940ea020b45b49ab106ed77fa8bb9d22" + "url": "https://files.pythonhosted.org/packages/b5/6d/e18a5b59ff086e1cd61d7fbf943d86c5f593a4e68bfc60215ab74210b22b/mido-1.2.10-py2.py3-none-any.whl", + "sha256": "0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/20/0a/81beb587b1ae832ea6a1901dc7c6faa380e8dd154e0a862f0a9f3d2afab9/mido-1.2.9-py2.py3-none-any.whl", - "sha256": "fc6364efa028c8405166f63e6a83cbc6c17aaeac2c28680abe64ae48703a89dd" + "url": "https://files.pythonhosted.org/packages/fd/5e/9840102591431f86c2e99c5a8e4f18bb399f9f2e982b0dbba87c98ae800f/humanize-3.12.0-py3-none-any.whl", + "sha256": "4c71c4381f0209715cd993058e717c1b74d58ae2f8c6da7bdb59ab66473b9ab0" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/de/c8/820b1546c68efcbbe3c1b10dd925fbd84a0dda7438bc18db0ef1fa567733/charset_normalizer-2.0.7-py3-none-any.whl", + "sha256": "f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/71/0a/faaa9d50705d43f4411bc8a16aaf5189d27a0cf9b7b43ede91f2cb92de97/JACK_Client-0.5.3-py3-none-any.whl", "sha256": "0214d9366e0a65bf0a99866990aae5a443afb3c283006d236d8cebcb769a59b8" }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/c0/da/4b8052ab5aa07952d7fe7b068d737a042e76373573eadd340f7550eddea9/pyliblo-0.10.0.tar.gz", + "sha256": "fc67f1950b827272b00f9f0dc4ed7113c0ccef0c1c09e9976dead40ebbf1798f" + }, { "type": "file", "url": "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", @@ -32,53 +42,58 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/ae/e7/d9c3a176ca4b02024debf82342dab36efadfc5776f9c8db077e8f6e71821/pycparser-2.20-py2.py3-none-any.whl", - "sha256": "7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" + "url": "https://files.pythonhosted.org/packages/37/45/946c02767aabb873146011e665728b680884cd8fe70dde973c640e45b775/certifi-2021.10.8-py2.py3-none-any.whl", + "sha256": "d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/c0/da/4b8052ab5aa07952d7fe7b068d737a042e76373573eadd340f7550eddea9/pyliblo-0.10.0.tar.gz", - "sha256": "fc67f1950b827272b00f9f0dc4ed7113c0ccef0c1c09e9976dead40ebbf1798f" + "url": "https://files.pythonhosted.org/packages/92/bf/749468bc43f85ec77f37154327360ba82e7d0ae622341eab44a6d75751c3/python-rtmidi-1.4.9.tar.gz", + "sha256": "bfeb4ed99d0cccf6fa2837566907652ded7adc1c03b69f2160c9de4082301302" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/71/c2/cb1855f0b2a0ae9ccc9b69f150a7aebd4a8d815bd951e74621c4154c52a8/importlib_metadata-4.8.1-py3-none-any.whl", + "sha256": "b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/a2/38/928ddce2273eaa564f6f50de919327bf3a00f091b5baba8dfa9460f3a8a8/idna-2.10-py2.py3-none-any.whl", - "sha256": "b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + "url": "https://files.pythonhosted.org/packages/ae/e7/d9c3a176ca4b02024debf82342dab36efadfc5776f9c8db077e8f6e71821/pycparser-2.20-py2.py3-none-any.whl", + "sha256": "7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/5e/a0/5f06e1e1d463903cf0c0eebeb751791119ed7a4b3737fdc9a77f1cdfb51f/certifi-2020.12.5-py2.py3-none-any.whl", - "sha256": "719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830" + "url": "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", + "sha256": "a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/4b/89/37b132c6ae7f85c60a1549f9e46109eec40cf0c31f0305faecb7f7bcfceb/falcon-2.0.0-py2.py3-none-any.whl", - "sha256": "54f2cb4b687035b2a03206dbfc538055cc48b59a953187b0458aa1b574d47b53" + "url": "https://files.pythonhosted.org/packages/74/60/18783336cc7fcdd95dae91d73477830aa53f5d3181ae4fe20491d7fc3199/typing_extensions-3.10.0.2-py3-none-any.whl", + "sha256": "f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/32/48/86ce3b159fb97372bc929e77d540191e5d4682eb4d83d5dae4e09f0d5309/python-rtmidi-1.4.7.tar.gz", - "sha256": "d7dbc2b174b09015dfbee449a672a072aa72b367be40b13e04ee35a2e2e399e3" + "url": "https://files.pythonhosted.org/packages/92/96/144f70b972a9c0eabbd4391ef93ccd49d0f2747f4f6a2a2738e99e5adc65/requests-2.26.0-py2.py3-none-any.whl", + "sha256": "6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/20/4d/a7046ae1a1a4cc4e9bbed194c387086f06b25038be596543d026946330c9/sortedcontainers-2.3.0-py2.py3-none-any.whl", - "sha256": "37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f" + "url": "https://files.pythonhosted.org/packages/63/22/6a9009c53ad78e65d88a44db8eccc7f39c6f54fc05fb43b1e9cbbc481d06/falcon-3.0.1.tar.gz", + "sha256": "c41d84db325881a870e8b7129d5ecfd972fa4323cf77b7119a1d2a21966ee681" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/a8/20/025f59f929bbcaa579704f443a438135918484fffaacfaddba776b374563/cffi-1.14.5.tar.gz", - "sha256": "fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c" + "url": "https://files.pythonhosted.org/packages/bd/df/d4a4974a3e3957fd1c1fa3082366d7fff6e428ddb55f074bf64876f8e8ad/zipp-3.6.0-py3-none-any.whl", + "sha256": "9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/29/c1/24814557f1d22c56d50280771a17307e6bf87b70727d975fd6b2ce6b014a/requests-2.25.1-py2.py3-none-any.whl", - "sha256": "c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" + "url": "https://files.pythonhosted.org/packages/af/f4/524415c0744552cce7d8bf3669af78e8a069514405ea4fcbd0cc44733744/urllib3-1.26.7-py2.py3-none-any.whl", + "sha256": "c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/23/fc/8a49991f7905261f9ca9df5aa9b58363c3c821ce3e7f671895442b7100f2/urllib3-1.26.3-py2.py3-none-any.whl", - "sha256": "1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80" + "url": "https://files.pythonhosted.org/packages/00/9e/92de7e1217ccc3d5f352ba21e52398372525765b2e0c4530e6eb2ba9282a/cffi-1.15.0.tar.gz", + "sha256": "920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954" } ] } \ No newline at end of file From 809c09c239a04bc0b5785f9b03ea39c942a7d107 Mon Sep 17 00:00:00 2001 From: "fnetX (aka fralix)" Date: Sat, 4 Dec 2021 16:20:43 +0100 Subject: [PATCH 266/333] Clarify: "starting" a running cue stops it (#222) --- docs/user/source/cues.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/source/cues.rst b/docs/user/source/cues.rst index e7d50bbda..e8046ef6d 100644 --- a/docs/user/source/cues.rst +++ b/docs/user/source/cues.rst @@ -12,7 +12,7 @@ subdivided in two main categories: A cue can perform different *actions* depending on its current *state* **Actions:** - * ``start:`` Perform the cue task + * ``start:`` Perform the cue task, or stop it if the cue is already running * ``stop:`` Stop the running cue * ``pause:`` Pause the running cue if possible * ``interrupt:`` Stop the running cue, other cues/functions will ignore this event From c612587fb92c4e51406de0fc4490ae49fa71d0cb Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 14 Dec 2021 20:24:47 +0100 Subject: [PATCH 267/333] Upgrade alsa-python to v1.2.6 --- poetry.lock | 141 ++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 71 insertions(+), 72 deletions(-) diff --git a/poetry.lock b/poetry.lock index f156a9e53..a212ac8f1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -27,7 +27,7 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "2.0.7" +version = "2.0.9" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -38,7 +38,7 @@ unicode_backport = ["unicodedata2"] [[package]] name = "cython" -version = "0.29.24" +version = "0.29.25" description = "The Cython compiler for writing C extensions for the Python language." category = "main" optional = false @@ -54,7 +54,7 @@ python-versions = ">=3.5" [[package]] name = "humanize" -version = "3.12.0" +version = "3.13.1" description = "Python humanize utilities" category = "main" optional = false @@ -76,7 +76,7 @@ python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "4.8.1" +version = "4.8.2" description = "Read metadata from Python packages" category = "main" optional = false @@ -89,7 +89,7 @@ zipp = ">=0.5" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] perf = ["ipython"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "jack-client" @@ -119,18 +119,18 @@ ports = ["python-rtmidi (>=1.1.0)"] [[package]] name = "packaging" -version = "21.2" +version = "21.3" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2,<3" +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pyalsa" -version = "1.1.6" +version = "1.2.6" description = "Python binding for the ALSA library." category = "main" optional = false @@ -140,8 +140,8 @@ develop = false [package.source] type = "git" url = "https://github.com/alsa-project/alsa-python.git" -reference = "v1.1.6" -resolved_reference = "e1bc1c27b6286dcf2d6bf4955650cf5729cf9b7a" +reference = "v1.2.6" +resolved_reference = "6919608781a273decd1937accf36c79ab4ae1f0e" [[package]] name = "pycairo" @@ -153,7 +153,7 @@ python-versions = ">=3.6, <4" [[package]] name = "pycparser" -version = "2.20" +version = "2.21" description = "C parser in Python" category = "main" optional = false @@ -180,11 +180,14 @@ python-versions = "*" [[package]] name = "pyparsing" -version = "2.4.7" +version = "3.0.6" description = "Python parsing module" category = "dev" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pyqt5" @@ -258,11 +261,11 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "typing-extensions" -version = "3.10.0.2" -description = "Backported and Experimental Type Hints for Python 3.5+" +version = "4.0.1" +description = "Backported and Experimental Type Hints for Python 3.6+" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "urllib3" @@ -292,7 +295,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "7dfc8fc40343480bae6d8236ded866b232cccb11dbfe9fd524ae13b9a99ab42f" +content-hash = "17b73be105a878a5d216c1039dd25c0346dabfaf1503c9eaf1ffaa9ebade1fc3" [metadata.files] appdirs = [ @@ -356,49 +359,46 @@ cffi = [ {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"}, - {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"}, + {file = "charset-normalizer-2.0.9.tar.gz", hash = "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"}, + {file = "charset_normalizer-2.0.9-py3-none-any.whl", hash = "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721"}, ] cython = [ - {file = "Cython-0.29.24-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:6a2cf2ccccc25413864928dfd730c29db6f63eaf98206c1e600003a445ca7f58"}, - {file = "Cython-0.29.24-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b28f92e617f540d3f21f8fd479a9c6491be920ffff672a4c61b7fc4d7f749f39"}, - {file = "Cython-0.29.24-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:37bcfa5df2a3009f49624695d917c3804fccbdfcdc5eda6378754a879711a4d5"}, - {file = "Cython-0.29.24-cp27-cp27m-win32.whl", hash = "sha256:9164aeef1af6f837e4fc20402a31d256188ba4d535e262c6cb78caf57ad744f8"}, - {file = "Cython-0.29.24-cp27-cp27m-win_amd64.whl", hash = "sha256:73ac33a4379056a02031baa4def255717fadb9181b5ac2b244792d53eae1c925"}, - {file = "Cython-0.29.24-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:09ac3087ac7a3d489ebcb3fb8402e00c13d1a3a1c6bc73fd3b0d756a3e341e79"}, - {file = "Cython-0.29.24-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:774cb8fd931ee1ba52c472bc1c19077cd6895c1b24014ae07bb27df59aed5ebe"}, - {file = "Cython-0.29.24-cp34-cp34m-win32.whl", hash = "sha256:5dd56d0be50073f0e54825a8bc3393852de0eed126339ecbca0ae149dba55cfc"}, - {file = "Cython-0.29.24-cp34-cp34m-win_amd64.whl", hash = "sha256:88dc3c250dec280b0489a83950b15809762e27232f4799b1b8d0bad503f5ab84"}, - {file = "Cython-0.29.24-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:5fa12ebafc2f688ea6d26ab6d1d2e634a9872509ba7135b902bb0d8b368fb04b"}, - {file = "Cython-0.29.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:60c958bcab0ff315b4036a949bed1c65334e1f6a69e17e9966d742febb59043a"}, - {file = "Cython-0.29.24-cp35-cp35m-win32.whl", hash = "sha256:166f9f29cd0058ce1a14a7b3a2458b849ed34b1ec5fd4108af3fdd2c24afcbb0"}, - {file = "Cython-0.29.24-cp35-cp35m-win_amd64.whl", hash = "sha256:76cbca0188d278e93d12ebdaf5990678e6e436485fdfad49dbe9b07717d41a3c"}, - {file = "Cython-0.29.24-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f2e9381497b12e8f622af620bde0d1d094035d79b899abb2ddd3a7891f535083"}, - {file = "Cython-0.29.24-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d8d1a087f35e39384303f5e6b75d465d6f29d746d7138eae9d3b6e8e6f769eae"}, - {file = "Cython-0.29.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:112efa54a58293a4fb0acf0dd8e5b3736e95b595eee24dd88615648e445abe41"}, - {file = "Cython-0.29.24-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cf4452f0e4d50e11701bca38f3857fe6fa16593e7fd6a4d5f7be66f611b7da2"}, - {file = "Cython-0.29.24-cp36-cp36m-win32.whl", hash = "sha256:854fe2193d3ad4c8b61932ff54d6dbe10c5fa8749eb8958d72cc0ab28243f833"}, - {file = "Cython-0.29.24-cp36-cp36m-win_amd64.whl", hash = "sha256:84826ec1c11cda56261a252ddecac0c7d6b02e47e81b94f40b27b4c23c29c17c"}, - {file = "Cython-0.29.24-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ade74eece909fd3a437d9a5084829180751d7ade118e281e9824dd75eafaff2"}, - {file = "Cython-0.29.24-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0a142c6b862e6ed6b02209d543062c038c110585b5e32d1ad7c9717af4f07e41"}, - {file = "Cython-0.29.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:10cb3def9774fa99e4583617a5616874aed3255dc241fd1f4a3c2978c78e1c53"}, - {file = "Cython-0.29.24-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f41ef7edd76dd23315925e003f0c58c8585f3ab24be6885c4b3f60e77c82746"}, - {file = "Cython-0.29.24-cp37-cp37m-win32.whl", hash = "sha256:821c2d416ad7d006b069657ee1034c0e0cb45bdbe9ab6ab631e8c495dfcfa4ac"}, - {file = "Cython-0.29.24-cp37-cp37m-win_amd64.whl", hash = "sha256:2d9e61ed1056a3b6a4b9156b62297ad18b357a7948e57a2f49b061217696567e"}, - {file = "Cython-0.29.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b0ee28c2c8118bfb3ad9b25cf7a6cbd724e442ea96956e32ccd908d5e3e043"}, - {file = "Cython-0.29.24-cp38-cp38-manylinux1_i686.whl", hash = "sha256:eb2843f8cc01c645725e6fc690a84e99cdb266ce8ebe427cf3a680ff09f876aa"}, - {file = "Cython-0.29.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:661dbdea519d9cfb288867252b75fef73ffa8e8bb674cec27acf70646afb369b"}, - {file = "Cython-0.29.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc05de569f811be1fcfde6756c9048ae518f0c4b6d9f8f024752c5365d934cac"}, - {file = "Cython-0.29.24-cp38-cp38-win32.whl", hash = "sha256:a102cfa795c6b3b81a29bdb9dbec545367cd7f353c03e6f30a056fdfefd92854"}, - {file = "Cython-0.29.24-cp38-cp38-win_amd64.whl", hash = "sha256:416046a98255eff97ec02077d20ebeaae52682dfca1c35aadf31260442b92514"}, - {file = "Cython-0.29.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ad43e684ade673565f6f9d6638015112f6c7f11aa2a632167b79014f613f0f5f"}, - {file = "Cython-0.29.24-cp39-cp39-manylinux1_i686.whl", hash = "sha256:afb521523cb46ddaa8d269b421f88ea2731fee05e65b952b96d4db760f5a2a1c"}, - {file = "Cython-0.29.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0d414458cb22f8a90d64260da6dace5d5fcebde43f31be52ca51f818c46db8cb"}, - {file = "Cython-0.29.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cb87777e82d1996aef6c146560a19270684271c9c669ba62ac6803b3cd2ff82"}, - {file = "Cython-0.29.24-cp39-cp39-win32.whl", hash = "sha256:91339ee4b465924a3ea4b2a9cec7f7227bc4cadf673ce859d24c2b9ef60b1214"}, - {file = "Cython-0.29.24-cp39-cp39-win_amd64.whl", hash = "sha256:5fb977945a2111f6b64501fdf7ed0ec162cc502b84457fd648d6a558ea8de0d6"}, - {file = "Cython-0.29.24-py2.py3-none-any.whl", hash = "sha256:f96411f0120b5cae483923aaacd2872af8709be4b46522daedc32f051d778385"}, - {file = "Cython-0.29.24.tar.gz", hash = "sha256:cdf04d07c3600860e8c2ebaad4e8f52ac3feb212453c1764a49ac08c827e8443"}, + {file = "Cython-0.29.25-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:08a502fe08756070276d841c830cfc37254a2383d0a5bea736ffb78eff613c88"}, + {file = "Cython-0.29.25-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c204cb2d005a426c5c83309fd7edea335ff5c514ffa6dc72ddac92cfde170b69"}, + {file = "Cython-0.29.25-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3913f6a50409ab36a5b8edbb4c3e4d441027f43150d8335e5118d34ef04c745c"}, + {file = "Cython-0.29.25-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:44cc749f288423182504a8fc8734070a369bf576734b9f0fafff40cd6b6e1b3e"}, + {file = "Cython-0.29.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4f7b135cba0d2509890e1dcff2005585bc3d51c9f17564b70d8bc82dc7ec3a5e"}, + {file = "Cython-0.29.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:eb64ec369eba2207fbe618650d78d9af0455e0c1abb301ec024fa9f3e17a15cc"}, + {file = "Cython-0.29.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:6efb798993260532879f683dc8ce9e30fd1ec86f02c926f1238a8e6a64576321"}, + {file = "Cython-0.29.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b8fc9c78262b140364ce1b28ac40ff505a47ac3fd4f86311d461df04a28b3f23"}, + {file = "Cython-0.29.25-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3497e366ffed67454162d31bf4bd2ac3aa183dfac089eb4124966c9f98bd9c05"}, + {file = "Cython-0.29.25-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b5b3e876e617fe2cf466d02198b76924dcda3cc162a1043226a9c181b9a662a6"}, + {file = "Cython-0.29.25-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:aa9e1fe5ee0a4f9d2430c1e0665f40b48f4b511150ca02f69e9bb49dc48d4e0e"}, + {file = "Cython-0.29.25-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8726456c7e376410b3c631427da0a4affe1e481424436d1e3f1888cc3c0f8d2e"}, + {file = "Cython-0.29.25-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:191978e5839ca425eb78f0f60a84ad5db7a07b97e8076f9853d0d12c3ccec5d4"}, + {file = "Cython-0.29.25-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a206a1f8ea11314e02dc01bf24f397b8f1b413bbcc0e031396caa1a126b060c2"}, + {file = "Cython-0.29.25-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e6fa0a7cec9461c5ca687f3c4bb59cf2565afb76c60303b2dc8b280c6e112810"}, + {file = "Cython-0.29.25-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:f95433e6963164de372fc1ef01574d7419d96ce45274f296299267d874b90800"}, + {file = "Cython-0.29.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4dc3d230849d61844e6b5737ee624c896f51e98c8a5d13f965b02a7e735230be"}, + {file = "Cython-0.29.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:3379e67113e92fef490a88eca685b07b711bb4db1ddce66af9e460673a5335cc"}, + {file = "Cython-0.29.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:0e9e28eb6bb19f5e25f4bf5c8f8ea7db3bc4910309fab2305e5c9c5a5223db77"}, + {file = "Cython-0.29.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:79d2f84a6d87d45ef580c0441b5394c4f29344e05126a8e2fb4ba4144425f3b0"}, + {file = "Cython-0.29.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6759b73a9a1013cbdac71ebefa284aa50617b5b32957a54eedaa22ac2f6d48de"}, + {file = "Cython-0.29.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:07d5b8ce032110822dad2eb09950a98b9e255d14c2daf094be32d663790b3365"}, + {file = "Cython-0.29.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5d0d97a5f661dccf2f9e14cf27fe9027f772d089fb92fdd3dd8a584d9b8a2916"}, + {file = "Cython-0.29.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:7b3f6e4cfcc103bccdcbc666f613d669ac378c8918629296cdf8191c0c2ec418"}, + {file = "Cython-0.29.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:e96857ab2dbd8a67852341001f1f2a1ef3f1939d82aea1337497a8f76a9d7f6c"}, + {file = "Cython-0.29.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ee99fab5191f403f33774fc92123291c002947338c2628b1ed42ed0017149dd"}, + {file = "Cython-0.29.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d288f25e8abb43b1cfa2fe3d69b2d6236cca3ff6163d090e26c4b1e8ea80dfbf"}, + {file = "Cython-0.29.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1c2f262f7d032ec0106534982609ae0148f86ba52fc747df64e645706af20926"}, + {file = "Cython-0.29.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:decd641167e97a3c1f973bf0bbb560d251809f6db8168c10edf94c0a1e5dec65"}, + {file = "Cython-0.29.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:800cbe944886320e4a4b623becb97960ae9d7d80f2d12980b83bcfb63ff47d5b"}, + {file = "Cython-0.29.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:3e94eb973f99c1963973a46dbd9e3974a03b8fe0af3de02dc5d65b4c6a6f9b3f"}, + {file = "Cython-0.29.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64394ec94d9a0e5002f77e67ee8ceed97f25b483b18ea6aab547f4d82ca32ef6"}, + {file = "Cython-0.29.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6f397256cfab2d0f0af42659fca3232c23f5a570b6c21ed66aaac22dd95da15"}, + {file = "Cython-0.29.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1825d6f2160188dfe1faa0099d30ed0e5ae56826627bf0de6dcb8dcbcf64c9bd"}, + {file = "Cython-0.29.25-py2.py3-none-any.whl", hash = "sha256:0cf7c3033349d10c5eb33ded1a78974f680e95c245a585c18a2046c67f8ed461"}, + {file = "Cython-0.29.25.tar.gz", hash = "sha256:a87cbe3756e7c464acf3e9420d8741e62d3b2eace0846cb39f664ad378aab284"}, ] falcon = [ {file = "falcon-3.0.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:94fb4582212768ac425d023b7884e60d09a0bd4c5cd50ca8af0272af1cba5da6"}, @@ -430,16 +430,16 @@ falcon = [ {file = "falcon-3.0.1.tar.gz", hash = "sha256:c41d84db325881a870e8b7129d5ecfd972fa4323cf77b7119a1d2a21966ee681"}, ] humanize = [ - {file = "humanize-3.12.0-py3-none-any.whl", hash = "sha256:4c71c4381f0209715cd993058e717c1b74d58ae2f8c6da7bdb59ab66473b9ab0"}, - {file = "humanize-3.12.0.tar.gz", hash = "sha256:5ec1a66e230a3e31fb3f184aab9436ea13d4e37c168e0ffc345ae5bb57e58be6"}, + {file = "humanize-3.13.1-py3-none-any.whl", hash = "sha256:a6f7cc1597db69a4e571ad5e19b4da07ee871da5a9de2b233dbfab02d98e9754"}, + {file = "humanize-3.13.1.tar.gz", hash = "sha256:12f113f2e369dac7f35d3823f49262934f4a22a53a6d3d4c86b736f50db88c7b"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, - {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, + {file = "importlib_metadata-4.8.2-py3-none-any.whl", hash = "sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100"}, + {file = "importlib_metadata-4.8.2.tar.gz", hash = "sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb"}, ] jack-client = [ {file = "JACK-Client-0.5.3.tar.gz", hash = "sha256:31cbdcc90cd303997a3ea540f352d89b23c2f422cbf7c197292a1b8e1ca21aca"}, @@ -450,8 +450,8 @@ mido = [ {file = "mido-1.2.10.tar.gz", hash = "sha256:17b38a8e4594497b850ec6e78b848eac3661706bfc49d484a36d91335a373499"}, ] packaging = [ - {file = "packaging-21.2-py3-none-any.whl", hash = "sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0"}, - {file = "packaging-21.2.tar.gz", hash = "sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966"}, + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] pyalsa = [] pycairo = [ @@ -468,8 +468,8 @@ pycairo = [ {file = "pycairo-1.20.1.tar.gz", hash = "sha256:1ee72b035b21a475e1ed648e26541b04e5d7e753d75ca79de8c583b25785531b"}, ] pycparser = [ - {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, - {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] pygobject = [ {file = "PyGObject-3.42.0.tar.gz", hash = "sha256:b9803991ec0b0b4175e81fee0ad46090fa7af438fe169348a9b18ae53447afcd"}, @@ -478,8 +478,8 @@ pyliblo = [ {file = "pyliblo-0.10.0.tar.gz", hash = "sha256:fc67f1950b827272b00f9f0dc4ed7113c0ccef0c1c09e9976dead40ebbf1798f"}, ] pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, + {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, + {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"}, ] pyqt5 = [ {file = "PyQt5-5.15.6-cp36-abi3-macosx_10_13_x86_64.whl", hash = "sha256:33ced1c876f6a26e7899615a5a4efef2167c263488837c7beed023a64cebd051"}, @@ -545,9 +545,8 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] typing-extensions = [ - {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, - {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, - {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, + {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, + {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, ] urllib3 = [ {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, diff --git a/pyproject.toml b/pyproject.toml index 852ca049d..3df26835a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ pyqt5 = "^5.6" python-rtmidi = "^1.1" requests = "^2.20" sortedcontainers = "^2.0" -pyalsa = {git = "https://github.com/alsa-project/alsa-python.git", tag = "v1.1.6"} +pyalsa = {git = "https://github.com/alsa-project/alsa-python.git", tag = "v1.2.6"} humanize = "^3.1.0" [tool.poetry.dev-dependencies] From 7b6604b17b826cc15fbeba4b4a5aaca322b405cb Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 18 Dec 2021 12:35:28 +0100 Subject: [PATCH 268/333] Fix 'float' to 'int' conversion, formatting (#226). Update: VolumeSettings now uses a SpinBox instead of a slider Update: SpeedSettings now uses a SpinBox instead of a slider Update: minor UI improvements --- lisp/application.py | 4 +- lisp/plugins/action_cues/volume_control.py | 30 +++--- lisp/plugins/gst_backend/config/__init__.py | 1 + lisp/plugins/gst_backend/config/alsa_sink.py | 4 +- lisp/plugins/gst_backend/gst_media.py | 5 +- lisp/plugins/gst_backend/gst_waveform.py | 2 +- .../plugins/gst_backend/settings/alsa_sink.py | 2 +- .../plugins/gst_backend/settings/audio_pan.py | 4 +- lisp/plugins/gst_backend/settings/db_meter.py | 4 +- lisp/plugins/gst_backend/settings/speed.py | 37 ++++---- lisp/plugins/gst_backend/settings/volume.py | 95 ++++++++++--------- lisp/plugins/list_layout/list_widgets.py | 4 +- lisp/plugins/list_layout/playing_widgets.py | 9 +- lisp/plugins/list_layout/view.py | 12 ++- lisp/ui/qdelegates.py | 4 +- lisp/ui/settings/app_pages/cue.py | 4 +- lisp/ui/widgets/dbmeter.py | 14 +-- lisp/ui/widgets/qclickslider.py | 2 +- lisp/ui/widgets/qwaitingspinner.py | 10 +- lisp/ui/widgets/waveform.py | 12 ++- 20 files changed, 132 insertions(+), 127 deletions(-) diff --git a/lisp/application.py b/lisp/application.py index 55231979e..e0dac71fe 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -110,7 +110,7 @@ def cue_model(self): @property def commands_stack(self): - """:rtype: lisp.command.stack.CommandsStack """ + """:rtype: lisp.command.stack.CommandsStack""" return self.__commands_stack def start(self, session_file=""): @@ -203,7 +203,7 @@ def __save_to_file(self, session_file): self.window.updateWindowTitle() def __load_from_file(self, session_file): - """ Load a saved session from file """ + """Load a saved session from file""" try: with open(session_file, mode="r", encoding="utf-8") as file: session_dict = json.load(file) diff --git a/lisp/plugins/action_cues/volume_control.py b/lisp/plugins/action_cues/volume_control.py index de65c4a5f..8e4e83a68 100644 --- a/lisp/plugins/action_cues/volume_control.py +++ b/lisp/plugins/action_cues/volume_control.py @@ -17,7 +17,6 @@ import logging -from PyQt5 import QtCore from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import ( QVBoxLayout, @@ -30,6 +29,7 @@ from lisp.application import Application from lisp.backend.audio_utils import ( + MAX_VOLUME, MIN_VOLUME_DB, MAX_VOLUME_DB, linear_to_db, @@ -154,7 +154,7 @@ def __init__(self, **kwargs): self.layout().addWidget(self.cueGroup) self.cueLabel = QLabel(self.cueGroup) - self.cueLabel.setAlignment(QtCore.Qt.AlignCenter) + self.cueLabel.setAlignment(Qt.AlignCenter) self.cueLabel.setStyleSheet("font-weight: bold;") self.cueGroup.layout().addWidget(self.cueLabel) @@ -166,23 +166,21 @@ def __init__(self, **kwargs): self.volumeGroup.setLayout(QHBoxLayout()) self.layout().addWidget(self.volumeGroup) - self.volumeEdit = QDoubleSpinBox(self.volumeGroup) - self.volumeEdit.setDecimals(6) - self.volumeEdit.setMaximum(100) - self.volumeGroup.layout().addWidget(self.volumeEdit) + self.volumePercentEdit = QDoubleSpinBox(self.volumeGroup) + self.volumePercentEdit.setSuffix(" %") + self.volumePercentEdit.setDecimals(6) + self.volumePercentEdit.setMaximum(MAX_VOLUME * 100) + self.volumeGroup.layout().addWidget(self.volumePercentEdit) - self.percentLabel = QLabel("%", self.volumeGroup) - self.volumeGroup.layout().addWidget(self.percentLabel) + self.volumeGroup.layout().setSpacing(100) self.volumeDbEdit = QDoubleSpinBox(self.volumeGroup) + self.volumeDbEdit.setSuffix(" dB") self.volumeDbEdit.setRange(MIN_VOLUME_DB, MAX_VOLUME_DB) self.volumeDbEdit.setValue(MIN_VOLUME_DB) self.volumeGroup.layout().addWidget(self.volumeDbEdit) - self.dbLabel = QLabel("dB", self.volumeGroup) - self.volumeGroup.layout().addWidget(self.dbLabel) - - self.volumeEdit.valueChanged.connect(self.__volume_change) + self.volumePercentEdit.valueChanged.connect(self.__volume_change) self.volumeDbEdit.valueChanged.connect(self.__db_volume_change) # Fade @@ -221,9 +219,9 @@ def getSettings(self): if self.isGroupEnabled(self.cueGroup): settings["target_id"] = self.cue_id if self.isGroupEnabled(self.volumeGroup): - settings["volume"] = self.volumeEdit.value() / 100 + settings["volume"] = self.volumePercentEdit.value() / 100 if self.isGroupEnabled(self.fadeGroup): - settings["duration"] = self.fadeEdit.duration() * 1000 + settings["duration"] = int(self.fadeEdit.duration() * 1000) settings["fade_type"] = self.fadeEdit.fadeType() return settings @@ -234,7 +232,7 @@ def loadSettings(self, settings): self.cue_id = settings["target_id"] self.cueLabel.setText(cue.name) - self.volumeEdit.setValue(settings.get("volume", 0) * 100) + self.volumePercentEdit.setValue(settings.get("volume", 0) * 100) self.fadeEdit.setDuration(settings.get("duration", 0) / 1000) self.fadeEdit.setFadeType(settings.get("fade_type", "")) @@ -250,7 +248,7 @@ def __db_volume_change(self, value): if not self.__v_edit_flag: try: self.__v_edit_flag = True - self.volumeEdit.setValue(db_to_linear(value) * 100) + self.volumePercentEdit.setValue(db_to_linear(value) * 100) finally: self.__v_edit_flag = False diff --git a/lisp/plugins/gst_backend/config/__init__.py b/lisp/plugins/gst_backend/config/__init__.py index 5ab71c30c..bedcd9da8 100644 --- a/lisp/plugins/gst_backend/config/__init__.py +++ b/lisp/plugins/gst_backend/config/__init__.py @@ -19,6 +19,7 @@ from lisp.core.loading import load_classes + def load(): for name, page in load_classes( __package__, dirname(__file__), suf=("Config",) diff --git a/lisp/plugins/gst_backend/config/alsa_sink.py b/lisp/plugins/gst_backend/config/alsa_sink.py index 61c20d30f..d8039bbec 100644 --- a/lisp/plugins/gst_backend/config/alsa_sink.py +++ b/lisp/plugins/gst_backend/config/alsa_sink.py @@ -31,7 +31,9 @@ def loadSettings(self, settings): device = settings.get("alsa_device", AlsaSink.FALLBACK_DEVICE) self.deviceComboBox.setCurrentText( - self.devices.get(device, self.devices.get(AlsaSink.FALLBACK_DEVICE, "")) + self.devices.get( + device, self.devices.get(AlsaSink.FALLBACK_DEVICE, "") + ) ) def getSettings(self): diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index 17b820f33..4b90df01a 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -262,7 +262,10 @@ def __init_pipeline(self): def __on_message(self, bus, message): if message.src == self.__pipeline: - if message.type in (Gst.MessageType.SEGMENT_DONE, Gst.MessageType.EOS): + if message.type in ( + Gst.MessageType.SEGMENT_DONE, + Gst.MessageType.EOS, + ): if self.__loop != 0: # If we still have loops to do then seek to start self.__loop -= 1 diff --git a/lisp/plugins/gst_backend/gst_waveform.py b/lisp/plugins/gst_backend/gst_waveform.py index 099b64618..51a6b9d35 100644 --- a/lisp/plugins/gst_backend/gst_waveform.py +++ b/lisp/plugins/gst_backend/gst_waveform.py @@ -94,7 +94,7 @@ def _clear(self): self._temp_rms = array("i") def _on_new_sample(self, sink, _): - """ Called by GStreamer every time we have a new sample ready. """ + """Called by GStreamer every time we have a new sample ready.""" buffer = sink.emit("pull-sample").get_buffer() if buffer is not None: # Get the all data from the buffer, as bytes diff --git a/lisp/plugins/gst_backend/settings/alsa_sink.py b/lisp/plugins/gst_backend/settings/alsa_sink.py index c7ebe5f35..dcaedd0ea 100644 --- a/lisp/plugins/gst_backend/settings/alsa_sink.py +++ b/lisp/plugins/gst_backend/settings/alsa_sink.py @@ -75,7 +75,7 @@ def enableCheck(self, enabled): def loadSettings(self, settings): device = settings.get( "device", - GstBackend.Config.get("alsa_device", AlsaSink.FALLBACK_DEVICE) + GstBackend.Config.get("alsa_device", AlsaSink.FALLBACK_DEVICE), ) self.deviceComboBox.setCurrentText( diff --git a/lisp/plugins/gst_backend/settings/audio_pan.py b/lisp/plugins/gst_backend/settings/audio_pan.py index 01cc6dd33..0d51ae368 100644 --- a/lisp/plugins/gst_backend/settings/audio_pan.py +++ b/lisp/plugins/gst_backend/settings/audio_pan.py @@ -63,12 +63,12 @@ def enableCheck(self, enabled): def getSettings(self): if self.isGroupEnabled(self.panBox): - return {"pan": self.panSlider.value() / 10} + return {"pan": self.panSlider.value() // 10} return {} def loadSettings(self, settings): - self.panSlider.setValue(settings.get("pan", 0.5) * 10) + self.panSlider.setValue(int(settings.get("pan", 0.5) * 10)) def pan_changed(self, value): if value < 0: diff --git a/lisp/plugins/gst_backend/settings/db_meter.py b/lisp/plugins/gst_backend/settings/db_meter.py index 38d416108..a79f67cc9 100644 --- a/lisp/plugins/gst_backend/settings/db_meter.py +++ b/lisp/plugins/gst_backend/settings/db_meter.py @@ -91,8 +91,8 @@ def retranslateUi(self): ) def loadSettings(self, settings): - self.intervalSpin.setValue(settings.get("interval", 33) / Gst.MSECOND) - self.ttlSpin.setValue(settings.get("peak_ttl", 1000) / Gst.MSECOND) + self.intervalSpin.setValue(settings.get("interval", 33) // Gst.MSECOND) + self.ttlSpin.setValue(settings.get("peak_ttl", 1000) // Gst.MSECOND) self.falloffSpin.setValue(settings.get("peak_falloff", 20)) def getSettings(self): diff --git a/lisp/plugins/gst_backend/settings/speed.py b/lisp/plugins/gst_backend/settings/speed.py index 3c805d309..587bc659b 100644 --- a/lisp/plugins/gst_backend/settings/speed.py +++ b/lisp/plugins/gst_backend/settings/speed.py @@ -17,7 +17,13 @@ from PyQt5 import QtCore from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QGroupBox, QHBoxLayout, QSlider, QLabel, QVBoxLayout +from PyQt5.QtWidgets import ( + QGroupBox, + QHBoxLayout, + QDoubleSpinBox, + QLabel, + QVBoxLayout, +) from lisp.plugins.gst_backend.elements.speed import Speed from lisp.ui.settings.pages import SettingsPage @@ -37,42 +43,31 @@ def __init__(self, **kwargs): self.groupBox.setLayout(QHBoxLayout()) self.layout().addWidget(self.groupBox) - self.speedSlider = QSlider(self.groupBox) - self.speedSlider.setMinimum(1) - self.speedSlider.setMaximum(1000) - self.speedSlider.setPageStep(1) - self.speedSlider.setValue(100) - self.speedSlider.setOrientation(QtCore.Qt.Horizontal) - self.speedSlider.setTickPosition(QSlider.TicksAbove) - self.speedSlider.setTickInterval(10) - self.groupBox.layout().addWidget(self.speedSlider) + self.speedSpinBox = QDoubleSpinBox(self.groupBox) + self.speedSpinBox.setMinimum(0.1) + self.speedSpinBox.setSingleStep(0.1) + self.speedSpinBox.setMaximum(10) + self.speedSpinBox.setValue(1) + self.groupBox.layout().addWidget(self.speedSpinBox) self.speedLabel = QLabel(self.groupBox) self.speedLabel.setAlignment(QtCore.Qt.AlignCenter) self.groupBox.layout().addWidget(self.speedLabel) - self.groupBox.layout().setStretch(0, 3) - self.groupBox.layout().setStretch(1, 1) - - self.speedSlider.valueChanged.connect(self.speedChanged) - self.retranslateUi() def retranslateUi(self): self.groupBox.setTitle(translate("SpeedSettings", "Speed")) - self.speedLabel.setText("1.0") + self.speedLabel.setText(translate("SpeedSettings", "Speed")) def enableCheck(self, enabled): self.setGroupEnabled(self.groupBox, enabled) def getSettings(self): if self.isGroupEnabled(self.groupBox): - return {"speed": self.speedSlider.value() / 100} + return {"speed": self.speedSpinBox.value()} return {} def loadSettings(self, settings): - self.speedSlider.setValue(settings.get("speed", 1) * 100) - - def speedChanged(self, value): - self.speedLabel.setText(str(value / 100.0)) + self.speedSpinBox.setValue(settings.get("speed", 1)) diff --git a/lisp/plugins/gst_backend/settings/volume.py b/lisp/plugins/gst_backend/settings/volume.py index f96175026..5a3ca7aa1 100644 --- a/lisp/plugins/gst_backend/settings/volume.py +++ b/lisp/plugins/gst_backend/settings/volume.py @@ -19,10 +19,10 @@ from PyQt5.QtWidgets import ( QGroupBox, QHBoxLayout, - QSlider, QLabel, QCheckBox, QVBoxLayout, + QDoubleSpinBox ) from lisp.backend.audio_utils import db_to_linear, linear_to_db @@ -41,80 +41,81 @@ def __init__(self, **kwargs): self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) - self.normal = 1 + self.normalizedVolume = 1 - self.volumeBox = QGroupBox(self) - self.volumeBox.setLayout(QHBoxLayout()) - self.layout().addWidget(self.volumeBox) + self.volumeGroup = QGroupBox(self) + self.volumeGroup.setLayout(QHBoxLayout()) + self.layout().addWidget(self.volumeGroup) - self.muteButton = QMuteButton(self.volumeBox) - self.volumeBox.layout().addWidget(self.muteButton) + self.muteButton = QMuteButton(self.volumeGroup) + self.volumeGroup.layout().addWidget(self.muteButton) - self.volume = QSlider(self.volumeBox) - self.volume.setRange(-1000, 100) - self.volume.setPageStep(1) - self.volume.setOrientation(Qt.Horizontal) - self.volume.valueChanged.connect(self.volume_changed) - self.volumeBox.layout().addWidget(self.volume) + self.volumeSpinBox = QDoubleSpinBox(self.volumeGroup) + self.volumeSpinBox.setRange(-100, 10) + self.volumeSpinBox.setSuffix(" dB") + self.volumeGroup.layout().addWidget(self.volumeSpinBox) - self.volumeLabel = QLabel(self.volumeBox) + # Resize the mute-button align with the adjacent input + self.muteButton.setFixedHeight(self.volumeSpinBox.height()) + + self.volumeLabel = QLabel(self.volumeGroup) self.volumeLabel.setAlignment(Qt.AlignCenter) - self.volumeBox.layout().addWidget(self.volumeLabel) + self.volumeGroup.layout().addWidget(self.volumeLabel) - self.volumeBox.layout().setStretch(0, 1) - self.volumeBox.layout().setStretch(1, 4) - self.volumeBox.layout().setStretch(2, 1) + self.volumeGroup.layout().setStretch(0, 1) + self.volumeGroup.layout().setStretch(1, 3) + self.volumeGroup.layout().setStretch(2, 4) - self.normalBox = QGroupBox(self) - self.normalBox.setLayout(QHBoxLayout()) - self.layout().addWidget(self.normalBox) + self.normalizedGroup = QGroupBox(self) + self.normalizedGroup.setLayout(QHBoxLayout()) + self.layout().addWidget(self.normalizedGroup) - self.normalLabel = QLabel(self.normalBox) - self.normalLabel.setAlignment(Qt.AlignCenter) - self.normalBox.layout().addWidget(self.normalLabel) + self.normalizedLabel = QLabel(self.normalizedGroup) + self.normalizedLabel.setAlignment(Qt.AlignCenter) + self.normalizedGroup.layout().addWidget(self.normalizedLabel) - self.normalReset = QCheckBox(self.normalBox) - self.normalBox.layout().addWidget(self.normalReset) - self.normalBox.layout().setAlignment(self.normalReset, Qt.AlignCenter) + self.normalizedReset = QCheckBox(self.normalizedGroup) + self.normalizedGroup.layout().addWidget(self.normalizedReset) + self.normalizedGroup.layout().setAlignment( + self.normalizedReset, Qt.AlignCenter + ) self.retranslateUi() def retranslateUi(self): - self.volumeBox.setTitle(translate("VolumeSettings", "Volume")) - self.volumeLabel.setText("0.0 dB") - self.normalBox.setTitle( + self.volumeGroup.setTitle(translate("VolumeSettings", "Volume")) + self.volumeLabel.setText(translate("VolumeSettings", "Volume")) + self.normalizedGroup.setTitle( translate("VolumeSettings", "Normalized volume") ) - self.normalLabel.setText("0.0 dB") - self.normalReset.setText(translate("VolumeSettings", "Reset")) + self.normalizedLabel.setText("0.0 dB") + self.normalizedReset.setText(translate("VolumeSettings", "Reset")) def enableCheck(self, enabled): - self.setGroupEnabled(self.normalBox, enabled) - self.setGroupEnabled(self.volumeBox, enabled) + self.setGroupEnabled(self.normalizedGroup, enabled) + self.setGroupEnabled(self.volumeGroup, enabled) def getSettings(self): settings = {} - if self.isGroupEnabled(self.volumeBox): - settings["volume"] = db_to_linear(self.volume.value() / 10) + if self.isGroupEnabled(self.volumeGroup): + settings["volume"] = db_to_linear(self.volumeSpinBox.value()) settings["mute"] = self.muteButton.isMute() - if self.isGroupEnabled(self.normalBox): - if self.normalReset.isChecked(): + if self.isGroupEnabled(self.normalizedGroup): + if self.normalizedReset.isChecked(): settings["normal_volume"] = 1 # If the apply button is pressed, show the correct value - self.normalLabel.setText("0 dB") + self.normalizedLabel.setText("0 dB") else: - settings["normal_volume"] = self.normal + settings["normal_volume"] = self.normalizedVolume return settings def loadSettings(self, settings): - self.volume.setValue(linear_to_db(settings.get("volume", 1)) * 10) + self.volumeSpinBox.setValue(linear_to_db(settings.get("volume", 1))) self.muteButton.setMute(settings.get("mute", False)) - self.normal = settings.get("normal_volume", 1) - self.normalLabel.setText( - str(round(linear_to_db(self.normal), 3)) + " dB" - ) - def volume_changed(self, value): - self.volumeLabel.setText(str(value / 10.0) + " dB") + self.normalizedVolume = settings.get("normal_volume", 1) + self.normalizedLabel.setText( + f"{linear_to_db(self.normalizedVolume):.3f} dB" + ) diff --git a/lisp/plugins/list_layout/list_widgets.py b/lisp/plugins/list_layout/list_widgets.py index 7618f9f05..de9e05a33 100644 --- a/lisp/plugins/list_layout/list_widgets.py +++ b/lisp/plugins/list_layout/list_widgets.py @@ -201,8 +201,8 @@ def _updateDuration(self, duration): self.setEnabled(duration > 0) self.setTextVisible(True) self.setFormat(strtime(duration, accurate=self.accurateTime)) - # Avoid settings min and max to 0, or the the bar go in busy state - self.setRange(0 if duration > 0 else -1, duration) + # Avoid settings min and max to 0, or the bar go in busy state + self.setRange(0 if duration > 0 else -1, int(duration)) else: self.setTextVisible(False) diff --git a/lisp/plugins/list_layout/playing_widgets.py b/lisp/plugins/list_layout/playing_widgets.py index 69d31b316..3ac0422fe 100644 --- a/lisp/plugins/list_layout/playing_widgets.py +++ b/lisp/plugins/list_layout/playing_widgets.py @@ -120,7 +120,7 @@ def __init__(self, cue, config, **kwargs): cue.fadeout_end.connect(self.exit_fade, Connection.QtQueued) def updateSize(self, width): - self.resize(width, width // 3.75) + self.resize(width, int(width / 3.75)) def enter_fadein(self): p = self.timeDisplay.palette() @@ -179,7 +179,10 @@ def __init__(self, cue, config, **kwargs): super().__init__(cue, config, **kwargs) self._dbmeter_element = None - if config.get("show.waveformSlider", False) and cue.media.input_uri() is not None: + if ( + config.get("show.waveformSlider", False) + and cue.media.input_uri() is not None + ): self.waveform = get_backend().media_waveform(cue.media) self.seekSlider = WaveformSlider( self.waveform, parent=self.gridLayoutWidget @@ -202,7 +205,7 @@ def __init__(self, cue, config, **kwargs): ) def updateSize(self, width): - self.resize(width, width // 2.75) + self.resize(width, int(width / 2.75)) def set_seek_visible(self, visible): if visible and not self.seekSlider.isVisible(): diff --git a/lisp/plugins/list_layout/view.py b/lisp/plugins/list_layout/view.py index 060bc1b6e..99139fc7c 100644 --- a/lisp/plugins/list_layout/view.py +++ b/lisp/plugins/list_layout/view.py @@ -91,12 +91,18 @@ def showEvent(self, event): self.resetSize() def resetSize(self): - self.mainSplitter.setSizes((0.22 * self.width(), 0.78 * self.width())) + self.mainSplitter.setSizes( + (int(0.22 * self.width()), int(0.78 * self.width())) + ) self.centralSplitter.setSizes( - (0.78 * self.width(), 0.22 * self.width()) + (int(0.78 * self.width()), int(0.22 * self.width())) ) self.topSplitter.setSizes( - (0.11 * self.width(), 0.67 * self.width(), 0.22 * self.width()) + ( + int(0.11 * self.width()), + int(0.67 * self.width()), + int(0.22 * self.width()), + ) ) def getSplitterSizes(self): diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index 792c7155d..ebfcafb55 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -158,12 +158,12 @@ def _checkBoxRect(self, option): # Center the checkbox (horizontally) cbRect.moveLeft( - option.rect.left() + (option.rect.width() - cbSize.width()) / 2 + option.rect.left() + (option.rect.width() - cbSize.width()) // 2 ) return cbRect def editorEvent(self, event, model, option, index): - # If the "checkbox" is left clicked change the current state + # If the "checkbox" is left-clicked change the current state if event.type() == QEvent.MouseButtonRelease: cbRect = self._checkBoxRect(option) if event.button() == Qt.LeftButton and cbRect.contains(event.pos()): diff --git a/lisp/ui/settings/app_pages/cue.py b/lisp/ui/settings/app_pages/cue.py index aeccd2e02..1f6a19c99 100644 --- a/lisp/ui/settings/app_pages/cue.py +++ b/lisp/ui/settings/app_pages/cue.py @@ -61,9 +61,7 @@ def __init__(self, **kwargs): self.retranslateUi() def retranslateUi(self): - self.interruptGroup.setTitle( - translate("CueSettings", "Interrupt fade") - ) + self.interruptGroup.setTitle(translate("CueSettings", "Interrupt fade")) self.interruptHelpText.setText( translate( "CueSettings", diff --git a/lisp/ui/widgets/dbmeter.py b/lisp/ui/widgets/dbmeter.py index c87956a39..5219da051 100644 --- a/lisp/ui/widgets/dbmeter.py +++ b/lisp/ui/widgets/dbmeter.py @@ -167,7 +167,7 @@ def paintEvent(self, event): # Calculate the meter size (per single channel) usableWidth = width - self._scale_width meterWidth = usableWidth / len(self.peaks) - meterRect = QRect(0, 0, meterWidth - 2, height - 1) + meterRect = QRectF(0, 0, meterWidth - 2, height - 1) # Draw each channel for n, values in enumerate(zip(self.peaks, self.decayPeaks)): @@ -223,14 +223,11 @@ def paintEvent(self, event): text_offset = text_height / 2 painter.setPen(self.palette().windowText().color()) painter.drawText( - 0, 0, width, text_height, Qt.AlignRight, str(self.dBMax) + QRectF(0, 0, width, text_height), Qt.AlignRight, str(self.dBMax) ) for mark in self._markings: painter.drawText( - 0, - mark[0] - text_offset, - width, - width, + QRectF(0, mark[0] - text_offset, width, width), Qt.AlignRight, str(mark[1]), ) @@ -239,10 +236,7 @@ def paintEvent(self, event): text_height = QFontMetrics(self.unit_font).height() painter.setFont(self.unit_font) painter.drawText( - 0, - height - text_height, - width, - text_height, + QRectF(0, height - text_height, width, text_height), Qt.AlignRight, self.unit, ) diff --git a/lisp/ui/widgets/qclickslider.py b/lisp/ui/widgets/qclickslider.py index 625d60bda..2e0fc2564 100644 --- a/lisp/ui/widgets/qclickslider.py +++ b/lisp/ui/widgets/qclickslider.py @@ -38,7 +38,7 @@ def mousePressEvent(self, e): value += e.x() * (zmax / self.width()) if self.value() != value: - self.setValue(value) + self.setValue(round(value)) self.sliderJumped.emit(self.value()) e.accept() else: diff --git a/lisp/ui/widgets/qwaitingspinner.py b/lisp/ui/widgets/qwaitingspinner.py index 05a75f74f..db11f439f 100644 --- a/lisp/ui/widgets/qwaitingspinner.py +++ b/lisp/ui/widgets/qwaitingspinner.py @@ -28,7 +28,7 @@ import math -from PyQt5.QtCore import Qt, QTimer, QRect +from PyQt5.QtCore import Qt, QTimer, QRectF from PyQt5.QtGui import QPainter, QColor from PyQt5.QtWidgets import QWidget @@ -68,7 +68,7 @@ def __init__( def paintEvent(self, QPaintEvent): self.updatePosition() - linesRect = QRect( + linesRect = QRectF( 0, -self._lineWidth / 2, self._lineLength, self._lineWidth ) @@ -203,7 +203,7 @@ def updateSize(self): def updateTimer(self): self._timer.setInterval( - 1000 / (self._numberOfLines * self._revolutionsPerSecond) + int(1000 / (self._numberOfLines * self._revolutionsPerSecond)) ) def updatePosition(self): @@ -212,9 +212,9 @@ def updatePosition(self): y = self.y() if self._centerOnParent & Qt.AlignHCenter: - x = self.parentWidget().width() / 2 - self.width() / 2 + x = int(self.parentWidget().width() / 2 - self.width() / 2) if self._centerOnParent & Qt.AlignVCenter: - y = self.parentWidget().height() / 2 - self.height() / 2 + y = int(self.parentWidget().height() / 2 - self.height() / 2) self.move(x, y) diff --git a/lisp/ui/widgets/waveform.py b/lisp/ui/widgets/waveform.py index 98c52b798..8bd407b9e 100644 --- a/lisp/ui/widgets/waveform.py +++ b/lisp/ui/widgets/waveform.py @@ -53,13 +53,17 @@ def setValue(self, value): if not self.visibleRegion().isEmpty(): # Repaint only if we have new pixels to draw if self._value >= floor(self._lastDrawnValue + self._valueToPx): - x = self._lastDrawnValue // self._valueToPx - width = (self._value - self._lastDrawnValue) // self._valueToPx + x = int(self._lastDrawnValue / self._valueToPx) + width = int( + (self._value - self._lastDrawnValue) / self._valueToPx + ) # Repaint only the changed area self.update(x - 1, 0, width + 1, self.height()) elif self._value <= ceil(self._lastDrawnValue - self._valueToPx): - x = self._value // self._valueToPx - width = (self._lastDrawnValue - self._value) // self._valueToPx + x = int(self._value / self._valueToPx) + width = int( + (self._lastDrawnValue - self._value) / self._valueToPx + ) # Repaint only the changed area self.update(x - 1, 0, width + 2, self.height()) From 42deeb9dbca8ccfe44eb39da604bb8e2e7332aca Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 18 Dec 2021 13:01:48 +0100 Subject: [PATCH 269/333] Revert incorrect truncation --- lisp/plugins/gst_backend/settings/audio_pan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/plugins/gst_backend/settings/audio_pan.py b/lisp/plugins/gst_backend/settings/audio_pan.py index 0d51ae368..c6591adc7 100644 --- a/lisp/plugins/gst_backend/settings/audio_pan.py +++ b/lisp/plugins/gst_backend/settings/audio_pan.py @@ -63,7 +63,7 @@ def enableCheck(self, enabled): def getSettings(self): if self.isGroupEnabled(self.panBox): - return {"pan": self.panSlider.value() // 10} + return {"pan": self.panSlider.value() / 10} return {} From 77a26b19ba8919dcd96a31df7a6e951c2a3ea8ba Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 24 Dec 2021 22:17:42 +0100 Subject: [PATCH 270/333] Fix: use new icon for session-files --- dist/application-x-linuxshowplayer.png | Bin 105236 -> 36085 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/dist/application-x-linuxshowplayer.png b/dist/application-x-linuxshowplayer.png index 22a21a4ef05d8e53795b53ad2294583df041d7a0..e0e108215f1f3743db9b20bd282c4456b3d4115d 100644 GIT binary patch literal 36085 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 P00000NkvXXu0mjfOTJRG literal 105236 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
    u|@BnSi%zZZIYlpw4Bq*72)K|w(& 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 From c30d9133b15f4e0d08ac7a9bcf623277210d8a63 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 24 Dec 2021 22:44:50 +0100 Subject: [PATCH 271/333] Improve flatpak builds, change runtime Update: change runtime (and sdk) to org.kde.Platform, which already ship with Qt, and GStreamer is already included Update: remove "no-debuginfo" option, reducing significantly the size Update: cleanup static libs and other files Update: move "pyqt" and "pygobject" in separated submodules/files Update: move the "sip-install" invocation into a standalone script Update: update a few dependencies Update: readme, minor improvements --- scripts/flatpak/README | 49 ----- scripts/flatpak/README.md | 26 +++ scripts/flatpak/config.sh | 11 +- scripts/flatpak/functions.sh | 2 +- ...org.linux_show_player.LinuxShowPlayer.json | 200 +++--------------- scripts/flatpak/poetry-flatpak.py | 46 ++-- scripts/flatpak/pygobject-modules.json | 33 +++ scripts/flatpak/pyqt-build.sh | 50 +++++ scripts/flatpak/pyqt-modules.json | 126 +++++++++++ scripts/flatpak/python-modules.json | 68 +++--- 10 files changed, 324 insertions(+), 287 deletions(-) delete mode 100644 scripts/flatpak/README create mode 100644 scripts/flatpak/README.md create mode 100644 scripts/flatpak/pygobject-modules.json create mode 100644 scripts/flatpak/pyqt-build.sh create mode 100644 scripts/flatpak/pyqt-modules.json diff --git a/scripts/flatpak/README b/scripts/flatpak/README deleted file mode 100644 index 164db7364..000000000 --- a/scripts/flatpak/README +++ /dev/null @@ -1,49 +0,0 @@ -====== INTRO ====== - -(╯°□°)╯︵ ┻━┻ - -The scripts in this folder allow to build a Flatpak package of Linux Show Player. - - * build_flatpak.sh: Combine the commands from "functions.sh" to run a complete build - - * config.sh: Some environment configuration - - * functions.sh: A collection of commands to build the flatpak - - * patch-flatpak.py: Patch the flatpak manifest to use the current CI branch/commit - - * poetry-flatpak.py: Starting from the project poetry.lock generate the appropriate modules to install the python requirements - -====== REQUIREMENTS ====== - - * ostree - * flatpak >= 1.0 - * flatpak-builder - * Python >= 3.6 - * (python) see "requirements.txt" - * BUILD_BRANCH to be set. - -====== DETAILS ====== - -=== Qt5 + PyQt5 === - -Instead of using the pypi package we build our own Qt binaries, this allow to -remove unused modules, and reduce (a lot) the size of the build, at the cost -of a longer build time. - -=== OLA === - -Until version 0.11 we must use protobuf < 3.2, or it will not build, -if needed we may patch it, for now it's fine. - -=== Protobuf === - -It's needed to "build" the OLA python package, which, can only be build -with all the OLA framework (--enable-python-libs) - -Requires "six" and "setuptools", we install only the first, we -consider the second already shipped with the python version - -====== NOTES ====== - -Non-used features of the various packages should be disabled when possible. diff --git a/scripts/flatpak/README.md b/scripts/flatpak/README.md new file mode 100644 index 000000000..6402cefd6 --- /dev/null +++ b/scripts/flatpak/README.md @@ -0,0 +1,26 @@ +### INTRO + +(╯°□°)╯︵ ┻━┻ + +The scripts in this folder allow to build a Flatpak package of Linux Show Player. + + * **build_flatpak.sh:** Combine the commands from "functions.sh" to run a complete build + * **config.sh:** Some environment configuration + * **functions.sh:** A collection of commands to build the flatpak + * **patch-flatpak.py:** Patch the flatpak manifest to use the current CI branch/commit + * **poetry-flatpak.py:** Starting from the project poetry.lock generate the appropriate modules to install the python requirements + * **pyqt-build.sh:** Used to invoke "sip-install" to build pyqt + +### REQUIREMENTS + + * ostree + * flatpak >= 1.0 + * flatpak-builder + * python >= 3.6 + * packaging + * toml + * the BUILD_BRANCH variable to be set. + +### NOTES + +Non-used features of the various packages should be disabled when possible. diff --git a/scripts/flatpak/config.sh b/scripts/flatpak/config.sh index 50fbd418b..cf0be9ce3 100755 --- a/scripts/flatpak/config.sh +++ b/scripts/flatpak/config.sh @@ -1,13 +1,16 @@ #!/usr/bin/env bash -export FLATPAK_RUNTIME="org.gnome.Platform" -export FLATPAK_RUNTIME_VERSION="3.38" -export FLATPAK_SDK="org.gnome.Sdk" +export FLATPAK_RUNTIME="org.kde.Platform" +export FLATPAK_RUNTIME_VERSION="5.15-21.08" +export FLATPAK_SDK="org.kde.Sdk" export FLATPAK_INSTALL="$FLATPAK_RUNTIME//$FLATPAK_RUNTIME_VERSION $FLATPAK_SDK//$FLATPAK_RUNTIME_VERSION" export FLATPAK_PY_LOCKFILE="../../poetry.lock" -export FLATPAK_PY_IGNORE_PACKAGES="pygobject pycairo pyqt5 pyqt5-sip pyqt5-qt pyqt5-qt5" +# Python packages included in the runtime sdk (some are not included in the runtime) +export FLATPAK_PY_INCLUDED="cython mako markdown meson pip pygments setuptools six wheel" +# Python packages to ignore +export FLATPAK_PY_IGNORE="$FLATPAK_PY_INCLUDED pygobject pycairo pyqt5 pyqt5-sip pyqt5-qt pyqt5-qt5" export FLATPAK_APP_ID="org.linux_show_player.LinuxShowPlayer" export FLATPAK_APP_MODULE="linux-show-player" \ No newline at end of file diff --git a/scripts/flatpak/functions.sh b/scripts/flatpak/functions.sh index c8dbd49f1..3e654e37f 100755 --- a/scripts/flatpak/functions.sh +++ b/scripts/flatpak/functions.sh @@ -12,7 +12,7 @@ function flatpak_build_manifest() { # Build manifest python3 patch-manifest.py - python3 poetry-flatpak.py "$FLATPAK_PY_LOCKFILE" -e $FLATPAK_PY_IGNORE_PACKAGES + python3 poetry-flatpak.py "$FLATPAK_PY_LOCKFILE" -e $FLATPAK_PY_IGNORE deactivate } diff --git a/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json b/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json index bcfe72109..5466d85ac 100644 --- a/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json +++ b/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json @@ -1,12 +1,10 @@ { "app-id": "org.linux_show_player.LinuxShowPlayer", - "runtime": "org.gnome.Platform", - "runtime-version": "3.38", - "sdk": "org.gnome.Sdk", + "runtime": "org.kde.Platform", + "runtime-version": "5.15-21.08", + "sdk": "org.kde.Sdk", "command": "linux-show-player", - "build-options": { - "no-debuginfo": true - }, + "rename-icon": "linuxshowplayer", "finish-args": [ "--share=network", "--socket=x11", @@ -15,155 +13,13 @@ "--filesystem=home", "--device=all" ], - "cleanup-commands": [ - "pip3 uninstall --yes packaging toml poetry-core || true" + "cleanup": [ + "*.la", + "*.a", + "/include", + "/lib/pkgconfig" ], - "rename-appdata-file": "linuxshowplayer.appdata.xml", - "rename-desktop-file": "linuxshowplayer.desktop", - "rename-icon": "linuxshowplayer", "modules": [ - { - "name": "qt5", - "config-opts": [ - "-confirm-license", "-opensource", - "-shared", - "-nomake", "examples", - "-nomake", "tests", - "-skip", "qt3d", - "-skip", "qtconnectivity", - "-skip", "qtcharts", - "-skip", "qtdatavis3d", - "-skip", "qtdeclarative", - "-skip", "qtmultimedia", - "-skip", "qtquickcontrols", - "-skip", "qtquickcontrols2", - "-skip", "qtsensors", - "-skip", "qtscript", - "-skip", "qtserialbus", - "-skip", "qtspeech", - "-skip", "qttools", - "-skip", "qtwebchannel", - "-skip", "qtwebengine", - "-skip", "qtwebglplugin", - "-skip", "qtwebsockets", - "-skip", "qtwebview", - "-skip", "qtwayland", - "-gtk" - ], - "sources": [ - { - "type": "archive", - "url": "http://master.qt.io/archive/qt/5.15/5.15.1/single/qt-everywhere-src-5.15.1.tar.xz", - "sha256": "44da876057e21e1be42de31facd99be7d5f9f07893e1ea762359bcee0ef64ee9" - } - ] - }, - { - "name": "python-sip", - "buildsystem": "simple", - "build-commands": [ - "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=\"${FLATPAK_DEST}\" sip" - ], - "sources": [ - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/33/e9/27730ac17713c0a80d81d8f3bb56213f1549d96f9dc183fd16a7eec6287c/sip-5.5.0.tar.gz", - "sha256": "5d024c419b30fea8a6de8c71a560c7ab0bc3c221fbfb14d55a5b865bd58eaac5" - } - ], - "cleanup-platform": ["*"], - "modules": [ - { - "name": "python-toml", - "buildsystem": "simple", - "build-commands": [ - "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=\"${FLATPAK_DEST}\" toml" - ], - "sources": [ - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", - "sha256": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" - } - ] - }, - { - "name": "python-pyparsing", - "buildsystem": "simple", - "build-commands": [ - "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=\"${FLATPAK_DEST}\" pyparsing" - ], - "sources": [ - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/8a/bb/488841f56197b13700afd5658fc279a2025a39e22449b7cf29864669b15d/pyparsing-2.4.7-py2.py3-none-any.whl", - "sha256": "ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" - } - ] - }, - { - "name": "python-packaging", - "buildsystem": "simple", - "build-commands": [ - "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=\"${FLATPAK_DEST}\" packaging" - ], - "sources": [ - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/46/19/c5ab91b1b05cfe63cccd5cfc971db9214c6dd6ced54e33c30d5af1d2bc43/packaging-20.4-py2.py3-none-any.whl", - "sha256": "998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" - } - ] - } - ] - }, - { - "name": "python-pyqt5", - "buildsystem": "simple", - "build-commands": [ - "MAKEFLAGS=\"-j${FLATPAK_BUILDER_N_JOBS}\" sip-install --qt-shared --verbose --confirm-license --no-dbus-python --no-designer-plugin --no-docstrings --no-qml-plugin --no-tools --disable QtNetwork --disable QtPositioning --disable QtPrintSupport --disable QtRemoteObjects --disable QtSerialPort --disable QtSql --disable QtTest --target-dir=\"${FLATPAK_DEST}/lib/python$(python3 -c 'import sys; print(sys.version[:3])')/site-packages\"" - ], - "sources": [ - { - "type": "archive", - "url": "https://files.pythonhosted.org/packages/8e/a4/d5e4bf99dd50134c88b95e926d7b81aad2473b47fde5e3e4eac2c69a8942/PyQt5-5.15.4.tar.gz", - "sha256": "2a69597e0dd11caabe75fae133feca66387819fc9bc050f547e5551bce97e5be" - } - ], - "cleanup-platform": [ "/lib/python*/site-packages/PyQt5/bindings" ], - "modules": [ - { - "name": "pyqt5-builder", - "buildsystem": "simple", - "build-commands": [ - "pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=\"${FLATPAK_DEST}\" --no-build-isolation pyqt-builder" - ], - "sources": [ - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/37/fd/5bc6351d5d279c797a1d78fec3483a6a9e4b8628073a8ed5448fbe1cf038/PyQt-builder-1.5.0.tar.gz", - "sha256": "11bbe26e8e3d5ffec6d2ef2f50596b1670eb2d8b49aee0f859821922d8282841" - } - ], - "cleanup-platform": [ "*" ], - "cleanup": [ "/bin" ] - }, - { - "name": "pyqt5-sip", - "buildsystem": "simple", - "build-commands": [ - "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=\"${FLATPAK_DEST}\" pyqt5-sip" - ], - "sources": [ - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/73/8c/c662b7ebc4b2407d8679da68e11c2a2eb275f5f2242a92610f6e5024c1f2/PyQt5_sip-12.8.1.tar.gz", - "sha256": "30e944db9abee9cc757aea16906d4198129558533eb7fadbe48c5da2bd18e0bd" - } - ] - } - ] - }, { "name": "Jack", "buildsystem": "simple", @@ -215,15 +71,16 @@ ] }, { - "name": "cpp-google-protobuf", + "name": "protobuf", "config-opts": [ "--disable-static" ], "sources": [ { - "type": "archive", - "url": "https://github.com/protocolbuffers/protobuf/releases/download/v3.1.0/protobuf-cpp-3.1.0.tar.gz", - "sha256": "51ceea9957c875bdedeb1f64396b5b0f3864fe830eed6a2d9c066448373ea2d6" + "type": "git", + "url": "https://github.com/google/protobuf", + "tag": "v3.19.1", + "commit": "7c40b2df1fdf6f414c1c18c789715a9c948a0725" } ] }, @@ -236,8 +93,8 @@ "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/a5/bb/11821bdc46cb9aad8e18618715e5e93eef44abb642ed862c4b080c474183/protobuf-3.1.0.post1-py2.py3-none-any.whl", - "sha256": "42315e73409eaefdcc11e216695ff21f87dc483ad0595c57999baddf7f841180" + "url": "https://files.pythonhosted.org/packages/c1/12/7479ece04931984162698bfaa05cbb2fc23d7f6ee1ab5146cfc6ade56a31/protobuf-3.19.1-py2.py3-none-any.whl", + "sha256": "e813b1c9006b6399308e917ac5d298f345d95bb31f46f02b60cd92970a9afa17" } ] }, @@ -258,8 +115,8 @@ "sources": [ { "type": "archive", - "url": "https://github.com/OpenLightingProject/ola/releases/download/0.10.7/ola-0.10.7.tar.gz", - "sha256": "8a65242d95e0622a3553df498e0db323a13e99eeb1accc63a8a2ca8913ab31a0" + "url": "https://github.com/OpenLightingProject/ola/releases/download/0.10.8/ola-0.10.8.tar.gz", + "sha256": "102aa3114562a2a71dbf7f77d2a0fb9fc47acc35d6248a70b6e831365ca71b13" }, { "type": "script", @@ -290,6 +147,9 @@ "build-commands": [ "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=\"${FLATPAK_DEST}\" poetry-core" ], + "cleanup": [ + "*" + ], "sources": [ { "type": "file", @@ -298,6 +158,8 @@ } ] }, + "pyqt-modules.json", + "pygobject-modules.json", "python-modules.json", { "name": "linux-show-player", @@ -305,21 +167,23 @@ "build-commands": [ "pip3 install --no-deps --no-build-isolation --prefix=\"${FLATPAK_DEST}\" .", "mkdir -p /app/share/applications/", - "cp dist/linuxshowplayer.desktop /app/share/applications/", "mkdir -p /app/share/mime/packages/", - "cp dist/linuxshowplayer.xml /app/share/mime/packages/org.linux_show_player.LinuxShowPlayer.xml", "mkdir -p /app/share/metainfo/", - "cp dist/linuxshowplayer.appdata.xml /app/share/metainfo/", "mkdir -p /app/share/icons/hicolor/512x512/apps/", - "cp dist/linuxshowplayer.png /app/share/icons/hicolor/512x512/apps/" + "cp dist/linuxshowplayer.desktop /app/share/applications/org.linux_show_player.LinuxShowPlayer.desktop", + "cp dist/linuxshowplayer.xml /app/share/mime/packages/org.linux_show_player.LinuxShowPlayer.xml", + "cp dist/linuxshowplayer.appdata.xml /app/share/metainfo/org.linux_show_player.LinuxShowPlayer.appdata.xml", + "cp dist/linuxshowplayer.png /app/share/icons/hicolor/512x512/apps/linuxshowplayer.png" ], "sources": [ { "type": "git", "url": "https://github.com/FrancescoCeruti/linux-show-player.git", - "branch": "master" + "branch": "develop" } ] } - ] -} + ], + "branch": "develop", + "desktop-file-name-suffix": " (develop)" +} \ No newline at end of file diff --git a/scripts/flatpak/poetry-flatpak.py b/scripts/flatpak/poetry-flatpak.py index 7c605ebf4..2a4f8d1f7 100755 --- a/scripts/flatpak/poetry-flatpak.py +++ b/scripts/flatpak/poetry-flatpak.py @@ -7,27 +7,15 @@ import urllib.request from collections import OrderedDict from concurrent.futures import ThreadPoolExecutor, as_completed +from typing import Mapping import toml from packaging.utils import parse_wheel_filename from packaging.tags import Tag -# Python3 packages that come as part of org.freedesktop.Sdk. -SYS_PACKAGES = [ - "cython", - "mako", - "markdown", - "meson", - "pip", - "pygments", - "setuptools", - "six", - "wheel", -] - - -def get_best_source(sources, hashes): - # Look for a wheel package first + +def get_best_source(name: str, sources: list, hashes: list): + # Search for a platform-independent wheel distribution for source in sources: if ( source["packagetype"] == "bdist_wheel" @@ -37,7 +25,7 @@ def get_best_source(sources, hashes): if Tag("py3", "none", "any") in wheel_tags: return source["url"], source["digests"]["sha256"] - # If no compatible wheel is found get the source + # Search for a source distribution for source in sources: if ( source["packagetype"] == "sdist" @@ -46,7 +34,7 @@ def get_best_source(sources, hashes): ): return source["url"], source["digests"]["sha256"] - raise Exception(f"Cannot find a viable distribution for the package.") + raise Exception(f"Cannot find a viable distribution for '{name}'.") def get_pypi_source(name: str, version: str, hashes: list) -> tuple: @@ -57,7 +45,7 @@ def get_pypi_source(name: str, version: str, hashes: list) -> tuple: with urllib.request.urlopen(url) as response: body = json.loads(response.read()) try: - return get_best_source(body["releases"][version], hashes) + return get_best_source(name, body["releases"][version], hashes) except KeyError: raise Exception(f"Failed to extract url and hash from {url}") @@ -74,7 +62,7 @@ def get_package_hashes(package_files: list) -> list: return hashes -def get_packages_sources(packages: list, parsed_lockfile: dict) -> list: +def get_packages_sources(packages: list, parsed_lockfile: Mapping) -> list: """Gets the list of sources from a toml parsed lockfile.""" sources = [] parsed_files = parsed_lockfile["metadata"]["files"] @@ -100,7 +88,7 @@ def get_packages_sources(packages: list, parsed_lockfile: dict) -> list: return sources -def get_locked_packages(parsed_lockfile: dict, exclude=tuple()) -> list: +def get_locked_packages(parsed_lockfile: Mapping, exclude=tuple()) -> list: """Gets the list of dependency names.""" dependencies = [] packages = parsed_lockfile.get("package", []) @@ -123,14 +111,9 @@ def main(): parser.add_argument("-o", dest="outfile", default="python-modules.json") args = parser.parse_args() - lockfile = args.lockfile - exclude = SYS_PACKAGES + args.exclude - outfile = args.outfile - - print(f'Scanning "{lockfile}"') - parsed_lockfile = toml.load(lockfile) - dependencies = get_locked_packages(parsed_lockfile, exclude=exclude) - print(f"Found {len(dependencies)} required packages") + parsed_lockfile = toml.load(args.lockfile) + dependencies = get_locked_packages(parsed_lockfile, exclude=args.exclude) + print(f"Found {len(dependencies)} required packages in {args.lockfile}") pip_command = [ "pip3", @@ -152,9 +135,10 @@ def main(): sources = get_packages_sources(dependencies, parsed_lockfile) main_module["sources"] = sources - print(f'Writing modules to "{outfile}"') - with open(outfile, "w") as f: + print(f'Writing modules to "{args.outfile}"') + with open(args.outfile, "w") as f: f.write(json.dumps(main_module, indent=4)) + print("Done!") diff --git a/scripts/flatpak/pygobject-modules.json b/scripts/flatpak/pygobject-modules.json new file mode 100644 index 000000000..3c9a7209f --- /dev/null +++ b/scripts/flatpak/pygobject-modules.json @@ -0,0 +1,33 @@ +{ + "name": "pygobject-modules", + "modules": [ + { + "name": "pycairo", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --no-build-isolation --prefix=${FLATPAK_DEST} ." + ], + "sources": [ + { + "type": "archive", + "url": "https://files.pythonhosted.org/packages/bc/3f/64e6e066d163fbcf13213f9eeda0fc83376243335ea46a66cefd70d62e8f/pycairo-1.20.1.tar.gz", + "sha256": "1ee72b035b21a475e1ed648e26541b04e5d7e753d75ca79de8c583b25785531b" + } + ] + }, + { + "name": "pygobject", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --no-build-isolation --prefix=${FLATPAK_DEST} ." + ], + "sources": [ + { + "type": "archive", + "url": "https://files.pythonhosted.org/packages/cc/72/48cfdd7a0caf3c27f392d2657731ac6f7c3c1c0a60bfeba3e1ba9ffa7ba9/PyGObject-3.42.0.tar.gz", + "sha256": "b9803991ec0b0b4175e81fee0ad46090fa7af438fe169348a9b18ae53447afcd" + } + ] + } + ] +} \ No newline at end of file diff --git a/scripts/flatpak/pyqt-build.sh b/scripts/flatpak/pyqt-build.sh new file mode 100644 index 000000000..15b84f8dc --- /dev/null +++ b/scripts/flatpak/pyqt-build.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env sh + +PYTHON_VERSION=$(python3 -c 'import sys; print(sys.version[:3])') + +sip-install \ + --qt-shared \ + --concatenate 1 \ + --confirm-license \ + --debug \ + --verbose \ + --disable QtBluetooth \ + --disable QtDBus \ + --disable QtDesigner \ + --disable QtHelp \ + --disable QtLocation \ + --disable QtMultimedia \ + --disable QtMultimediaWidgets \ + --disable QtNetwork \ + --disable QtNfc \ + --disable QtOpenGL \ + --disable QtPositioning \ + --disable QtPrintSupport \ + --disable QtQml \ + --disable QtQuick \ + --disable QtQuickWidgets \ + --disable QtRemoteObjects \ + --disable QtSensors \ + --disable QtSerialPort \ + --disable QtSql \ + --disable QtSvg \ + --disable QtTest \ + --disable QtTextToSpeech \ + --disable QtWebChannel \ + --disable QtWebSockets \ + --disable QtXml \ + --disable QtXmlPatterns \ + --disable _QOpenGLFunctions_2_0 \ + --disable _QOpenGLFunctions_2_1 \ + --disable _QOpenGLFunctions_4_1_Core \ + --no-dbus-python \ + --no-designer-plugin \ + --no-docstrings \ + --no-qml-plugin \ + --no-tools \ + --jobs="${FLATPAK_BUILDER_N_JOBS}" \ + --build-dir="${FLATPAK_BUILDER_BUILDDIR}/tmp" \ + --target-dir="${FLATPAK_DEST}/lib/python${PYTHON_VERSION}/site-packages" \ + --qmake-setting QMAKE_CFLAGS="$CFLAGS" \ + --qmake-setting QMAKE_CXXFLAGS="$CXXFLAGS" \ + --qmake-setting QMAKE_LFLAGS="$LDFLAGS" \ No newline at end of file diff --git a/scripts/flatpak/pyqt-modules.json b/scripts/flatpak/pyqt-modules.json new file mode 100644 index 000000000..d7b32dc02 --- /dev/null +++ b/scripts/flatpak/pyqt-modules.json @@ -0,0 +1,126 @@ +{ + "name": "pyqt-modules", + "modules": [ + { + "name": "toml", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --no-build-isolation --prefix=\"${FLATPAK_DEST}\" ." + ], + "cleanup": [ + "*" + ], + "sources": [ + { + "type": "archive", + "url": "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", + "sha256": "b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + } + ] + }, + { + "name": "pyparsing", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --no-build-isolation --prefix=\"${FLATPAK_DEST}\" ." + ], + "cleanup": [ + "*" + ], + "sources": [ + { + "type": "archive", + "url": "https://files.pythonhosted.org/packages/c1/47/dfc9c342c9842bbe0036c7f763d2d6686bcf5eb1808ba3e170afdb282210/pyparsing-2.4.7.tar.gz", + "sha256": "c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1" + } + ] + }, + { + "name": "packaging", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --no-build-isolation --prefix=\"${FLATPAK_DEST}\" ." + ], + "cleanup": [ + "*" + ], + "sources": [ + { + "type": "archive", + "url": "https://files.pythonhosted.org/packages/df/9e/d1a7217f69310c1db8fdf8ab396229f55a699ce34a203691794c5d1cad0c/packaging-21.3.tar.gz", + "sha256": "dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb" + } + ] + }, + { + "name": "sip", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --no-build-isolation --prefix=\"${FLATPAK_DEST}\" ." + ], + "cleanup": [ + "*" + ], + "sources": [ + { + "type": "archive", + "url": "https://files.pythonhosted.org/packages/07/4e/0de6d5872145f4cedf187aa8a9069ba91d7e293a82cbbaeabd10c45e9cf0/sip-6.5.0.tar.gz", + "sha256": "a1cf8431a8eb9392b3ff6dc61d832d0447bfdcae5b3e4256a5fa74dbc25b0734" + } + ] + }, + { + "name": "pyqt-builder", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --no-build-isolation --prefix=\"${FLATPAK_DEST}\" ." + ], + "cleanup": [ + "*" + ], + "sources": [ + { + "type": "archive", + "url": "https://files.pythonhosted.org/packages/8b/5f/1bd49787262ddce37b826ef49dcccf5a9970facf0ed363dee5ee233e681d/PyQt-builder-1.12.2.tar.gz", + "sha256": "f62bb688d70e0afd88c413a8d994bda824e6cebd12b612902d1945c5a67edcd7" + } + ] + }, + { + "name": "pyqt5-sip", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --no-build-isolation --prefix=\"${FLATPAK_DEST}\" ." + ], + "sources": [ + { + "type": "archive", + "url": "https://files.pythonhosted.org/packages/b1/40/dd8f081f04a12912b65417979bf2097def0af0f20c89083ada3670562ac5/PyQt5_sip-12.9.0.tar.gz", + "sha256": "d3e4489d7c2b0ece9d203ae66e573939f7f60d4d29e089c9f11daa17cfeaae32" + } + ] + }, + { + "name": "pyqt5", + "buildsystem": "simple", + "build-commands": [ + ". ${FLATPAK_BUILDER_BUILDDIR}/build" + ], + "sources": [ + { + "type": "archive", + "url": "https://files.pythonhosted.org/packages/3b/27/fd81188a35f37be9b3b4c2db1654d9439d1418823916fe702ac3658c9c41/PyQt5-5.15.6.tar.gz", + "sha256": "80343bcab95ffba619f2ed2467fd828ffeb0a251ad7225be5fc06dcc333af452" + }, + { + "type": "file", + "path": "pyqt-build.sh", + "dest-filename": "build" + } + ], + "cleanup": [ + "/bin" + ] + } + ] +} \ No newline at end of file diff --git a/scripts/flatpak/python-modules.json b/scripts/flatpak/python-modules.json index 2ec7c9f5e..0b81e17d2 100644 --- a/scripts/flatpak/python-modules.json +++ b/scripts/flatpak/python-modules.json @@ -7,78 +7,78 @@ "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/04/a2/d918dcd22354d8958fe113e1a3630137e0fc8b44859ade3063982eacd2a4/idna-3.3-py3-none-any.whl", - "sha256": "84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff" + "url": "https://files.pythonhosted.org/packages/47/84/b06f6729fac8108c5fa3e13cde19b0b3de66ba5538c325496dbe39f5ff8e/charset_normalizer-2.0.9-py3-none-any.whl", + "sha256": "1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/b5/6d/e18a5b59ff086e1cd61d7fbf943d86c5f593a4e68bfc60215ab74210b22b/mido-1.2.10-py2.py3-none-any.whl", - "sha256": "0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e" + "url": "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", + "sha256": "a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/fd/5e/9840102591431f86c2e99c5a8e4f18bb399f9f2e982b0dbba87c98ae800f/humanize-3.12.0-py3-none-any.whl", - "sha256": "4c71c4381f0209715cd993058e717c1b74d58ae2f8c6da7bdb59ab66473b9ab0" + "url": "https://files.pythonhosted.org/packages/37/45/946c02767aabb873146011e665728b680884cd8fe70dde973c640e45b775/certifi-2021.10.8-py2.py3-none-any.whl", + "sha256": "d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/de/c8/820b1546c68efcbbe3c1b10dd925fbd84a0dda7438bc18db0ef1fa567733/charset_normalizer-2.0.7-py3-none-any.whl", - "sha256": "f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b" + "url": "https://files.pythonhosted.org/packages/b5/6d/e18a5b59ff086e1cd61d7fbf943d86c5f593a4e68bfc60215ab74210b22b/mido-1.2.10-py2.py3-none-any.whl", + "sha256": "0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/71/0a/faaa9d50705d43f4411bc8a16aaf5189d27a0cf9b7b43ede91f2cb92de97/JACK_Client-0.5.3-py3-none-any.whl", - "sha256": "0214d9366e0a65bf0a99866990aae5a443afb3c283006d236d8cebcb769a59b8" + "url": "https://files.pythonhosted.org/packages/04/a2/d918dcd22354d8958fe113e1a3630137e0fc8b44859ade3063982eacd2a4/idna-3.3-py3-none-any.whl", + "sha256": "84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/c0/da/4b8052ab5aa07952d7fe7b068d737a042e76373573eadd340f7550eddea9/pyliblo-0.10.0.tar.gz", - "sha256": "fc67f1950b827272b00f9f0dc4ed7113c0ccef0c1c09e9976dead40ebbf1798f" + "url": "https://files.pythonhosted.org/packages/71/0a/faaa9d50705d43f4411bc8a16aaf5189d27a0cf9b7b43ede91f2cb92de97/JACK_Client-0.5.3-py3-none-any.whl", + "sha256": "0214d9366e0a65bf0a99866990aae5a443afb3c283006d236d8cebcb769a59b8" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", - "sha256": "a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" + "url": "https://files.pythonhosted.org/packages/46/a8/74f8bf12c7d93bb2e111d13bae198996032d9a53dbbfc3bf4837a466cbe9/humanize-3.13.1-py3-none-any.whl", + "sha256": "a6f7cc1597db69a4e571ad5e19b4da07ee871da5a9de2b233dbfab02d98e9754" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/37/45/946c02767aabb873146011e665728b680884cd8fe70dde973c640e45b775/certifi-2021.10.8-py2.py3-none-any.whl", - "sha256": "d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" + "url": "https://files.pythonhosted.org/packages/c4/1f/e2238896149df09953efcc53bdcc7d23597d6c53e428c30e572eda5ec6eb/importlib_metadata-4.8.2-py3-none-any.whl", + "sha256": "53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/92/bf/749468bc43f85ec77f37154327360ba82e7d0ae622341eab44a6d75751c3/python-rtmidi-1.4.9.tar.gz", - "sha256": "bfeb4ed99d0cccf6fa2837566907652ded7adc1c03b69f2160c9de4082301302" + "url": "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", + "sha256": "8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/71/c2/cb1855f0b2a0ae9ccc9b69f150a7aebd4a8d815bd951e74621c4154c52a8/importlib_metadata-4.8.1-py3-none-any.whl", - "sha256": "b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15" + "url": "https://files.pythonhosted.org/packages/c0/da/4b8052ab5aa07952d7fe7b068d737a042e76373573eadd340f7550eddea9/pyliblo-0.10.0.tar.gz", + "sha256": "fc67f1950b827272b00f9f0dc4ed7113c0ccef0c1c09e9976dead40ebbf1798f" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/ae/e7/d9c3a176ca4b02024debf82342dab36efadfc5776f9c8db077e8f6e71821/pycparser-2.20-py2.py3-none-any.whl", - "sha256": "7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" + "url": "https://files.pythonhosted.org/packages/00/9e/92de7e1217ccc3d5f352ba21e52398372525765b2e0c4530e6eb2ba9282a/cffi-1.15.0.tar.gz", + "sha256": "920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", - "sha256": "a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" + "url": "https://files.pythonhosted.org/packages/63/22/6a9009c53ad78e65d88a44db8eccc7f39c6f54fc05fb43b1e9cbbc481d06/falcon-3.0.1.tar.gz", + "sha256": "c41d84db325881a870e8b7129d5ecfd972fa4323cf77b7119a1d2a21966ee681" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/74/60/18783336cc7fcdd95dae91d73477830aa53f5d3181ae4fe20491d7fc3199/typing_extensions-3.10.0.2-py3-none-any.whl", - "sha256": "f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34" + "url": "https://files.pythonhosted.org/packages/05/e4/baf0031e39cf545f0c9edd5b1a2ea12609b7fcba2d58e118b11753d68cf0/typing_extensions-4.0.1-py3-none-any.whl", + "sha256": "7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/92/96/144f70b972a9c0eabbd4391ef93ccd49d0f2747f4f6a2a2738e99e5adc65/requests-2.26.0-py2.py3-none-any.whl", - "sha256": "6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24" + "url": "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", + "sha256": "a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/63/22/6a9009c53ad78e65d88a44db8eccc7f39c6f54fc05fb43b1e9cbbc481d06/falcon-3.0.1.tar.gz", - "sha256": "c41d84db325881a870e8b7129d5ecfd972fa4323cf77b7119a1d2a21966ee681" + "url": "https://files.pythonhosted.org/packages/92/bf/749468bc43f85ec77f37154327360ba82e7d0ae622341eab44a6d75751c3/python-rtmidi-1.4.9.tar.gz", + "sha256": "bfeb4ed99d0cccf6fa2837566907652ded7adc1c03b69f2160c9de4082301302" }, { "type": "file", @@ -87,13 +87,13 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/af/f4/524415c0744552cce7d8bf3669af78e8a069514405ea4fcbd0cc44733744/urllib3-1.26.7-py2.py3-none-any.whl", - "sha256": "c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" + "url": "https://files.pythonhosted.org/packages/92/96/144f70b972a9c0eabbd4391ef93ccd49d0f2747f4f6a2a2738e99e5adc65/requests-2.26.0-py2.py3-none-any.whl", + "sha256": "6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/00/9e/92de7e1217ccc3d5f352ba21e52398372525765b2e0c4530e6eb2ba9282a/cffi-1.15.0.tar.gz", - "sha256": "920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954" + "url": "https://files.pythonhosted.org/packages/af/f4/524415c0744552cce7d8bf3669af78e8a069514405ea4fcbd0cc44733744/urllib3-1.26.7-py2.py3-none-any.whl", + "sha256": "c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" } ] } \ No newline at end of file From 9e39995cc51fd2f96b39c141d47fe2edd1784fdd Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 24 Dec 2021 22:45:05 +0100 Subject: [PATCH 272/333] Formatting --- lisp/plugins/gst_backend/settings/volume.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/plugins/gst_backend/settings/volume.py b/lisp/plugins/gst_backend/settings/volume.py index 5a3ca7aa1..cd0804c28 100644 --- a/lisp/plugins/gst_backend/settings/volume.py +++ b/lisp/plugins/gst_backend/settings/volume.py @@ -22,7 +22,7 @@ QLabel, QCheckBox, QVBoxLayout, - QDoubleSpinBox + QDoubleSpinBox, ) from lisp.backend.audio_utils import db_to_linear, linear_to_db From 60cc409ded65c3e67f9bcf1a8020b3522f11a801 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 25 Dec 2021 01:26:56 +0100 Subject: [PATCH 273/333] Fix "invalid argument" --- lisp/ui/widgets/waveform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/ui/widgets/waveform.py b/lisp/ui/widgets/waveform.py index 8bd407b9e..092e352ca 100644 --- a/lisp/ui/widgets/waveform.py +++ b/lisp/ui/widgets/waveform.py @@ -147,7 +147,7 @@ def paintEvent(self, event): # Draw a single line in the middle pen.setColor(self.remainsRmsColor) painter.setPen(pen) - painter.drawLine(0, halfHeight, self.width(), halfHeight) + painter.drawLine(QLineF(0, halfHeight, self.width(), halfHeight)) painter.end() From f937bde800a7729539dfb7fee59f8ac02b83b355 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 28 Jan 2022 20:11:26 +0100 Subject: [PATCH 274/333] Use GstControllers functionalities to provide proper fade-in/fade-out Braking changes: * Fader(s) cannot be paused/resumed anymore. * GstFader(s), used by default, cannot fade cross-loop. --- lisp/backend/media_element.py | 6 + lisp/core/fader.py | 172 +++++++++++------ lisp/plugins/action_cues/volume_control.py | 20 +- .../plugins/gst_backend/elements/alsa_sink.py | 3 +- .../gst_backend/elements/audio_dynamic.py | 3 +- .../plugins/gst_backend/elements/audio_pan.py | 3 +- lisp/plugins/gst_backend/elements/db_meter.py | 3 +- .../gst_backend/elements/equalizer10.py | 3 +- lisp/plugins/gst_backend/elements/pitch.py | 3 +- .../plugins/gst_backend/elements/uri_input.py | 7 +- lisp/plugins/gst_backend/elements/volume.py | 27 ++- lisp/plugins/gst_backend/gi_repository.py | 10 +- lisp/plugins/gst_backend/gst_element.py | 89 ++------- lisp/plugins/gst_backend/gst_fader.py | 77 ++++++++ lisp/plugins/gst_backend/gst_properties.py | 182 ++++++++++++++++++ lisp/plugins/gst_backend/gst_utils.py | 7 +- 16 files changed, 442 insertions(+), 173 deletions(-) create mode 100644 lisp/plugins/gst_backend/gst_fader.py create mode 100644 lisp/plugins/gst_backend/gst_properties.py diff --git a/lisp/backend/media_element.py b/lisp/backend/media_element.py index 1d410c354..95e7575eb 100644 --- a/lisp/backend/media_element.py +++ b/lisp/backend/media_element.py @@ -16,7 +16,9 @@ # along with Linux Show Player. If not, see . from enum import Enum +from typing import Optional +from lisp.core.fader import BaseFader from lisp.core.has_properties import HasProperties @@ -45,3 +47,7 @@ class MediaElement(HasProperties): ElementType = None MediaType = None Name = "Undefined" + + def get_fader(self, property_name: str) -> Optional[BaseFader]: + """Get the appropriate fader object for the given property.""" + return None diff --git a/lisp/core/fader.py b/lisp/core/fader.py index 7cdba8f23..90aa7d241 100644 --- a/lisp/core/fader.py +++ b/lisp/core/fader.py @@ -15,19 +15,19 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +from abc import abstractmethod, ABC from threading import Event +from typing import Union from lisp.core.decorators import locked_method from lisp.core.fade_functions import ntime, FadeInType, FadeOutType from lisp.core.util import rsetattr, rgetattr, typename -class Fader: - """Allow to perform fades on "generic" objects attributes. +class BaseFader(ABC): + """Base class for "faders" - * Fades have a resolution of `1-hundredth-of-second` - * To be able to fade correctly the attribute must be numeric, if not, the - `fade` function will fail + * Works only for numeric attributes * When fading, the `fade` function cannot be entered by other threads, if this happens the function will simply return immediately * To execute a fader, the `prepare` function must be called first, @@ -37,14 +37,11 @@ class Fader: * Changing the target will also stop the fader """ - def __init__(self, target, attribute): + def __init__(self, target, attribute: str): """ :param target: The target object - :type target: object :param attribute: The target attribute (name) to be faded - :type attribute: str """ - self._time = 0 # current fade time in hundredths-of-seconds self._target = target self._attribute = attribute @@ -52,8 +49,6 @@ def __init__(self, target, attribute): self._is_ready.set() self._running = Event() self._running.set() - self._pause = Event() - self._pause.set() @property def target(self): @@ -69,31 +64,26 @@ def attribute(self): return self._attribute @attribute.setter - def attribute(self, target_property): - self.stop() - self._attribute = target_property - - def prepare(self): + def attribute(self, attribute: str): self.stop() - - self._running.clear() - self._is_ready.clear() + self._attribute = attribute @locked_method(blocking=False) - def fade(self, duration, to_value, fade_type): + def fade( + self, + duration: float, + to_value: float, + fade_type: Union[FadeInType, FadeOutType], + ) -> bool: """ :param duration: How much the fade should be long (in seconds) - :type duration: float :param to_value: The value to reach - :type to_value: float :param fade_type: The fade type - :type fade_type: FadeInType | FadeOutType :return: False if the fade as been interrupted, True otherwise - :rtype: bool """ if duration <= 0: - return + return True if not isinstance(fade_type, (FadeInType, FadeOutType)): raise AttributeError( @@ -102,57 +92,111 @@ def fade(self, duration, to_value, fade_type): ) try: - self._time = 0 - begin = 0 - functor = fade_type.value - duration = int(duration * 100) - base_value = rgetattr(self._target, self._attribute) - value_diff = to_value - base_value - - if value_diff == 0: - return - - while self._time <= duration and not self._running.is_set(): - rsetattr( - self._target, - self._attribute, - functor( - ntime(self._time, begin, duration), - value_diff, - base_value, - ), - ) - - self._time += 1 - self._running.wait(0.01) - self._pause.wait() + self._fade(duration, to_value, fade_type) finally: interrupted = self._running.is_set() self._running.set() self._is_ready.set() - self._time = 0 + + self._after_fade(interrupted) return not interrupted + def prepare(self): + self.stop() + + self._running.clear() + self._is_ready.clear() + def stop(self): - if not self._running.is_set() or not self._pause.is_set(): + if not self._running.is_set(): self._running.set() - self._pause.set() self._is_ready.wait() - def pause(self): - if self.is_running(): - self._pause.clear() + def is_running(self) -> bool: + return not self._running.is_set() + + def _after_fade(self, interrupted): + pass + + @abstractmethod + def _fade( + self, + duration: float, + to_value: float, + fade_type: Union[FadeInType, FadeOutType], + ) -> bool: + pass + + @abstractmethod + def current_time(self) -> int: + """Fader running time, in milliseconds""" + pass + + +class DummyFader(BaseFader): + def __init__(self): + super().__init__(None, "") + + def _fade( + self, + duration: float, + to_value: float, + fade_type: Union[FadeInType, FadeOutType], + ) -> bool: + return True + + def current_time(self) -> int: + return 0 + + +class Fader(BaseFader): + """Perform fades on "generic" objects attributes. - def resume(self): - self._pause.set() + * Fades have a resolution of `1-hundredth-of-second` + """ + + def __init__(self, target, attribute): + super().__init__(target, attribute) + # current fade time in hundredths-of-seconds + self._time = 0 + + def _fade( + self, + duration: float, + to_value: float, + fade_type: Union[FadeInType, FadeOutType], + ) -> bool: + self._time = 0 + + begin = 0 + functor = fade_type.value + duration = int(duration * 100) + base_value = rgetattr(self._target, self._attribute) + value_diff = to_value - base_value + + if value_diff == 0: + return True + + while self._time <= duration and not self._running.is_set(): + rsetattr( + self._target, + self._attribute, + round( + functor( + ntime(self._time, begin, duration), + value_diff, + base_value, + ), + 6, + ), + ) - def is_paused(self): - return not self._pause.is_set() + self._time += 1 + self._running.wait(0.01) - def is_running(self): - return not self._running.is_set() and not self.is_paused() + def _after_fade(self, interrupted): + self._time = 0 - def current_time(self): - # Return the time in millisecond - return self._time * 10 + def current_time(self) -> int: + return round(self._time * 10) diff --git a/lisp/plugins/action_cues/volume_control.py b/lisp/plugins/action_cues/volume_control.py index 8e4e83a68..b14952e1b 100644 --- a/lisp/plugins/action_cues/volume_control.py +++ b/lisp/plugins/action_cues/volume_control.py @@ -37,7 +37,7 @@ ) from lisp.core.decorators import async_function from lisp.core.fade_functions import FadeInType, FadeOutType -from lisp.core.fader import Fader +from lisp.core.fader import Fader, DummyFader from lisp.core.properties import Property from lisp.cues.cue import Cue, CueAction from lisp.cues.media_cue import MediaCue @@ -62,7 +62,6 @@ class VolumeControl(Cue): CueAction.Default, CueAction.Start, CueAction.Stop, - CueAction.Pause, CueAction.Interrupt, ) @@ -70,7 +69,7 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.name = translate("CueName", self.Name) - self.__fader = Fader(None, "live_volume") + self.__fader = None self.__init_fader() def __init_fader(self): @@ -79,18 +78,15 @@ def __init_fader(self): if isinstance(cue, MediaCue): volume = cue.media.element("Volume") if volume is not None: - if volume is not self.__fader.target: - self.__fader.target = volume + self.__fader = volume.get_fader("live_volume") return True + self.__fader = DummyFader() + return False def __start__(self, fade=False): if self.__init_fader(): - if self.__fader.is_paused(): - self.__fader.resume() - return True - if self.duration > 0: if self.__fader.target.live_volume > self.volume: self.__fade(FadeOutType[self.fade_type]) @@ -107,10 +103,6 @@ def __stop__(self, fade=False): self.__fader.stop() return True - def __pause__(self, fade=False): - self.__fader.pause() - return True - __interrupt__ = __stop__ @async_function @@ -122,8 +114,6 @@ def __fade(self, fade_type): ) if ended: - # to avoid approximation problems - self.__fader.target.live_volume = self.volume self._ended() except Exception: logger.exception( diff --git a/lisp/plugins/gst_backend/elements/alsa_sink.py b/lisp/plugins/gst_backend/elements/alsa_sink.py index 8c573f7f9..c1664867c 100644 --- a/lisp/plugins/gst_backend/elements/alsa_sink.py +++ b/lisp/plugins/gst_backend/elements/alsa_sink.py @@ -20,7 +20,8 @@ from lisp.backend.media_element import ElementType, MediaType from lisp.plugins.gst_backend import GstBackend from lisp.plugins.gst_backend.gi_repository import Gst -from lisp.plugins.gst_backend.gst_element import GstMediaElement, GstProperty +from lisp.plugins.gst_backend.gst_element import GstMediaElement +from lisp.plugins.gst_backend.gst_properties import GstProperty class AlsaSink(GstMediaElement): diff --git a/lisp/plugins/gst_backend/elements/audio_dynamic.py b/lisp/plugins/gst_backend/elements/audio_dynamic.py index 7dbaba0ae..5652ffeb0 100644 --- a/lisp/plugins/gst_backend/elements/audio_dynamic.py +++ b/lisp/plugins/gst_backend/elements/audio_dynamic.py @@ -21,7 +21,8 @@ from lisp.backend.media_element import ElementType, MediaType from lisp.plugins.gst_backend.gi_repository import Gst -from lisp.plugins.gst_backend.gst_element import GstMediaElement, GstProperty +from lisp.plugins.gst_backend.gst_element import GstMediaElement +from lisp.plugins.gst_backend.gst_properties import GstProperty class AudioDynamic(GstMediaElement): diff --git a/lisp/plugins/gst_backend/elements/audio_pan.py b/lisp/plugins/gst_backend/elements/audio_pan.py index 66a3f3315..0698f1487 100644 --- a/lisp/plugins/gst_backend/elements/audio_pan.py +++ b/lisp/plugins/gst_backend/elements/audio_pan.py @@ -19,7 +19,8 @@ from lisp.backend.media_element import ElementType, MediaType from lisp.plugins.gst_backend.gi_repository import Gst -from lisp.plugins.gst_backend.gst_element import GstMediaElement, GstProperty +from lisp.plugins.gst_backend.gst_element import GstMediaElement +from lisp.plugins.gst_backend.gst_properties import GstProperty class AudioPan(GstMediaElement): diff --git a/lisp/plugins/gst_backend/elements/db_meter.py b/lisp/plugins/gst_backend/elements/db_meter.py index 8d3236d46..8af5455d0 100644 --- a/lisp/plugins/gst_backend/elements/db_meter.py +++ b/lisp/plugins/gst_backend/elements/db_meter.py @@ -20,7 +20,8 @@ from lisp.backend.media_element import ElementType, MediaType from lisp.core.signal import Signal from lisp.plugins.gst_backend.gi_repository import Gst -from lisp.plugins.gst_backend.gst_element import GstMediaElement, GstProperty +from lisp.plugins.gst_backend.gst_element import GstMediaElement +from lisp.plugins.gst_backend.gst_properties import GstProperty class DbMeter(GstMediaElement): diff --git a/lisp/plugins/gst_backend/elements/equalizer10.py b/lisp/plugins/gst_backend/elements/equalizer10.py index 7bc593b09..6dc4d3324 100644 --- a/lisp/plugins/gst_backend/elements/equalizer10.py +++ b/lisp/plugins/gst_backend/elements/equalizer10.py @@ -19,7 +19,8 @@ from lisp.backend.media_element import ElementType, MediaType from lisp.plugins.gst_backend.gi_repository import Gst -from lisp.plugins.gst_backend.gst_element import GstMediaElement, GstProperty +from lisp.plugins.gst_backend.gst_element import GstMediaElement +from lisp.plugins.gst_backend.gst_properties import GstProperty class Equalizer10(GstMediaElement): diff --git a/lisp/plugins/gst_backend/elements/pitch.py b/lisp/plugins/gst_backend/elements/pitch.py index 09d94fed5..c629b690a 100644 --- a/lisp/plugins/gst_backend/elements/pitch.py +++ b/lisp/plugins/gst_backend/elements/pitch.py @@ -19,7 +19,8 @@ from lisp.backend.media_element import ElementType, MediaType from lisp.plugins.gst_backend.gi_repository import Gst -from lisp.plugins.gst_backend.gst_element import GstMediaElement, GstProperty +from lisp.plugins.gst_backend.gst_element import GstMediaElement +from lisp.plugins.gst_backend.gst_properties import GstProperty class Pitch(GstMediaElement): diff --git a/lisp/plugins/gst_backend/elements/uri_input.py b/lisp/plugins/gst_backend/elements/uri_input.py index 6041495df..e51851e34 100644 --- a/lisp/plugins/gst_backend/elements/uri_input.py +++ b/lisp/plugins/gst_backend/elements/uri_input.py @@ -25,11 +25,8 @@ from lisp.core.properties import Property from lisp.core.session_uri import SessionURI from lisp.plugins.gst_backend.gi_repository import Gst -from lisp.plugins.gst_backend.gst_element import ( - GstProperty, - GstSrcElement, - GstURIProperty, -) +from lisp.plugins.gst_backend.gst_element import GstSrcElement +from lisp.plugins.gst_backend.gst_properties import GstProperty, GstURIProperty from lisp.plugins.gst_backend.gst_utils import gst_uri_duration diff --git a/lisp/plugins/gst_backend/elements/volume.py b/lisp/plugins/gst_backend/elements/volume.py index 120ddd480..4cbcbc708 100644 --- a/lisp/plugins/gst_backend/elements/volume.py +++ b/lisp/plugins/gst_backend/elements/volume.py @@ -15,14 +15,19 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +from typing import Optional + from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.media_element import ElementType, MediaType -from lisp.plugins.gst_backend.gi_repository import Gst +from lisp.plugins.gst_backend.gi_repository import Gst, GstController from lisp.plugins.gst_backend.gst_element import ( GstMediaElement, +) +from lisp.plugins.gst_backend.gst_properties import ( GstProperty, GstLiveProperty, + GstPropertyController, ) @@ -31,6 +36,8 @@ class Volume(GstMediaElement): MediaType = MediaType.Audio Name = QT_TRANSLATE_NOOP("MediaElementName", "Volume") + VolumeInterpolationMode = GstController.InterpolationMode.CUBIC_MONOTONIC + mute = GstProperty("gst_volume", "mute", default=False) volume = GstProperty("gst_volume", "volume", default=1.0) normal_volume = GstProperty("gst_normal_volume", "volume", default=1.0) @@ -42,22 +49,36 @@ class Volume(GstMediaElement): def __init__(self, pipeline): super().__init__(pipeline) + self.sync_element = Gst.ElementFactory.make("identity", None) self.gst_volume = Gst.ElementFactory.make("volume", None) self.gst_normal_volume = Gst.ElementFactory.make("volume", None) self.audio_convert = Gst.ElementFactory.make("audioconvert", None) - self.pipeline.add(self.gst_normal_volume) + self.volume_controller = GstPropertyController( + self.pipeline, self.gst_volume, self.sync_element, "volume" + ) + + self.pipeline.add(self.sync_element) self.pipeline.add(self.gst_volume) + self.pipeline.add(self.gst_normal_volume) self.pipeline.add(self.audio_convert) + self.sync_element.link(self.gst_volume) self.gst_volume.link(self.gst_normal_volume) self.gst_normal_volume.link(self.audio_convert) + def get_controller( + self, property_name: str + ) -> Optional[GstPropertyController]: + if property_name == "live_volume": + return self.volume_controller + def sink(self): - return self.gst_volume + return self.sync_element def src(self): return self.audio_convert def stop(self): + self.volume_controller.reset() self.live_volume = self.volume diff --git a/lisp/plugins/gst_backend/gi_repository.py b/lisp/plugins/gst_backend/gi_repository.py index c1171aaad..885b0f775 100644 --- a/lisp/plugins/gst_backend/gi_repository.py +++ b/lisp/plugins/gst_backend/gi_repository.py @@ -1,18 +1,12 @@ """Utility module for importing and checking gi.repository packages once""" -# "Solution" for https://bugzilla.gnome.org/show_bug.cgi?id=736260 - -import sys - -sys.modules["gi.overrides.Gst"] = None -sys.modules["gi.overrides.GstPbutils"] = None - import gi gi.require_version("Gst", "1.0") +gi.require_version("GstController", "1.0") gi.require_version("GstPbutils", "1.0") gi.require_version("GstApp", "1.0") # noinspection PyUnresolvedReferences # pylint: disable=unused-import -from gi.repository import Gst, GstPbutils, GObject, GstApp +from gi.repository import GObject, Gst, GstController, GstPbutils, GstApp diff --git a/lisp/plugins/gst_backend/gst_element.py b/lisp/plugins/gst_backend/gst_element.py index 9f3c16fc1..39b00f2f4 100644 --- a/lisp/plugins/gst_backend/gst_element.py +++ b/lisp/plugins/gst_backend/gst_element.py @@ -15,82 +15,16 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from typing import Union +from collections.abc import Collection +from typing import Union, Optional from lisp.backend.media_element import MediaElement, ElementType from lisp.core.has_properties import HasInstanceProperties from lisp.core.properties import Property, InstanceProperty from lisp.core.session_uri import SessionURI from lisp.core.util import typename - - -class GstProperty(Property): - def __init__( - self, element_name, property_name, default=None, adapter=None, **meta - ): - super().__init__(default=default, **meta) - self.element_name = element_name - self.property_name = property_name - self.adapter = adapter - - def __set__(self, instance, value): - super().__set__(instance, value) - - if instance is not None: - if self.adapter is not None: - value = self.adapter(value) - - element = getattr(instance, self.element_name) - element.set_property(self.property_name, value) - - -class GstURIProperty(GstProperty): - def __init__(self, element_name, property_name, **meta): - super().__init__( - element_name, - property_name, - default="", - adapter=self._adepter, - **meta, - ) - - def __set__(self, instance, value): - super().__set__(instance, SessionURI(value)) - - def __get__(self, instance, owner=None): - value = super().__get__(instance, owner) - if isinstance(value, SessionURI): - if value.is_local: - return value.relative_path - else: - return value.uri - - return value - - def _adepter(self, value: SessionURI): - return value.uri - - -class GstLiveProperty(Property): - def __init__(self, element_name, property_name, adapter=None, **meta): - super().__init__(**meta) - self.element_name = element_name - self.property_name = property_name - self.adapter = adapter - - def __get__(self, instance, owner=None): - if instance is None: - return self - else: - element = getattr(instance, self.element_name) - return element.get_property(self.property_name) - - def __set__(self, instance, value): - if self.adapter is not None: - value = self.adapter(value) - - element = getattr(instance, self.element_name) - element.set_property(self.property_name, value) +from lisp.plugins.gst_backend.gst_fader import GstFader +from lisp.plugins.gst_backend.gst_properties import GstPropertyController class GstMediaElement(MediaElement): @@ -128,6 +62,19 @@ def src(self): """Return the GstElement used as src""" return None + def get_controller( + self, property_name: str + ) -> Optional[GstPropertyController]: + """Return the appropriate element controller for the given property""" + return None + + def get_fader(self, property_name: str): + controller = self.get_controller(property_name) + if controller is not None: + return GstFader(self, property_name) + + return super().get_fader(property_name) + def link(self, element): if self.src() is not None: sink = element.sink() @@ -153,7 +100,7 @@ def input_uri(self) -> Union[SessionURI, type(None)]: return None -class GstMediaElements(HasInstanceProperties): +class GstMediaElements(Collection, HasInstanceProperties): def __init__(self): super().__init__() self.elements = [] diff --git a/lisp/plugins/gst_backend/gst_fader.py b/lisp/plugins/gst_backend/gst_fader.py new file mode 100644 index 000000000..b989f5a97 --- /dev/null +++ b/lisp/plugins/gst_backend/gst_fader.py @@ -0,0 +1,77 @@ +# This file is part of Linux Show Player +# +# Copyright 2017 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from time import perf_counter_ns +from typing import Union + +from lisp.core.fade_functions import ntime, FadeInType, FadeOutType +from lisp.core.fader import BaseFader + + +class GstFader(BaseFader): + def __init__(self, target, attribute: str): + super().__init__(target, attribute) + self._start_time = -1 + + def _fade( + self, + duration: float, + to_value: float, + fade_type: Union[FadeInType, FadeOutType], + ) -> bool: + base_value = getattr(self._target, self._attribute) + value_diff = to_value - base_value + + if value_diff == 0: + return True + + functor = fade_type.value + steps = 100 + steps_duration = (duration * 1000) / steps + control_points = {} + + for step in range(steps): + control_points[step * steps_duration] = round( + functor( + ntime(step, 0, steps - 1), + value_diff, + base_value, + ), + 6, + ) + + controller = self._target.get_controller(self.attribute) + controller.set(control_points) + + self._running.wait(controller.delay / 1000) + + self._start_time = perf_counter_ns() + + self._running.wait(duration) + + def _after_fade(self, interrupted): + try: + self._start_time = -1 + self._target.get_controller(self.attribute).reset() + except Exception: + pass + + def current_time(self): + if self._start_time < 0: + return 0 + + return (perf_counter_ns() - self._start_time) // 1_000_000 diff --git a/lisp/plugins/gst_backend/gst_properties.py b/lisp/plugins/gst_backend/gst_properties.py new file mode 100644 index 000000000..ee3402e6b --- /dev/null +++ b/lisp/plugins/gst_backend/gst_properties.py @@ -0,0 +1,182 @@ +# This file is part of Linux Show Player +# +# Copyright 2021 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from threading import Event + +from lisp.core.properties import Property +from lisp.core.session_uri import SessionURI + +from .gi_repository import GstController, Gst + + +class GstProperty(Property): + def __init__( + self, element_name, property_name, default=None, adapter=None, **meta + ): + super().__init__(default=default, **meta) + self.element_name = element_name + self.property_name = property_name + self.adapter = adapter + + def __set__(self, instance, value): + super().__set__(instance, value) + + if instance is not None: + if self.adapter is not None: + value = self.adapter(value) + + element = getattr(instance, self.element_name) + element.set_property(self.property_name, value) + + +class GstURIProperty(GstProperty): + def __init__(self, element_name, property_name, **meta): + super().__init__( + element_name, + property_name, + default="", + adapter=self._adapter, + **meta, + ) + + def __set__(self, instance, value): + super().__set__(instance, SessionURI(value)) + + def __get__(self, instance, owner=None): + value = super().__get__(instance, owner) + if isinstance(value, SessionURI): + if value.is_local: + return value.relative_path + else: + return value.uri + + return value + + def _adapter(self, value: SessionURI): + return value.uri + + +class GstLiveProperty(Property): + def __init__(self, element_name, property_name, adapter=None, **meta): + super().__init__(**meta) + self.element_name = element_name + self.property_name = property_name + self.adapter = adapter + + def __get__(self, instance, owner=None): + if instance is None: + return self + else: + element = getattr(instance, self.element_name) + return element.get_property(self.property_name) + + def __set__(self, instance, value): + if self.adapter is not None: + value = self.adapter(value) + + element = getattr(instance, self.element_name) + element.set_property(self.property_name, value) + + +class GstPropertyController: + """Control a Gst.Element (controllable) property. + + This class leverage the GstController "lib" functionalities, allowing + their usage in a "live" context where timing and values must be determined + on the fly. + + To achieve this behavior an "identy" element (sync_element) is used to + synchronize the user "request" with the actual pipeline processing. + + For this mechanism to work correctly there should not be any significant + buffering in upstream elements (e.g. queues). + """ + + def __init__( + self, + pipeline: Gst.Pipeline, + element: Gst.Element, + sync_element: Gst.Element, + property_name: str, + interpolation_mode: int = GstController.InterpolationMode.CUBIC_MONOTONIC, + ): + self._pipeline = pipeline + self._element = element + self._sync_element = sync_element + self._property_name = property_name + + self._sync_event = Event() + self._control_points = {} + self._delay = 0 + + self._control_source = GstController.InterpolationControlSource.new() + self._control_source.set_property("mode", interpolation_mode) + + self._control_binding = GstController.DirectControlBinding.new_absolute( + self._element, self._property_name, self._control_source + ) + self._element.add_control_binding(self._control_binding) + + @property + def delay(self): + return self._delay + + def set(self, control_points: dict): + """Set the control points to be used to change the value.""" + self._control_points = control_points + self._delay = 0 + + self._sync_event.clear() + + # Connect the "handoff" signal. + # The callback will be called the next time a buffer is "processed" + # by the sync_element, we make control_points available to the callback + handle = self._sync_element.connect("handoff", self._set_control_points) + + # Wait for the callback to set the flag. + # A timeout is set to avoid deadlocking if the callback is never called, + # this might happen if there are no more buffers to process. + self._sync_event.wait(1) + + # Disconnect the callback, we only need to call it once + self._sync_element.disconnect(handle) + + def reset(self): + self._control_source.unset_all() + self._control_points.clear() + + def _set_control_points(self, element: Gst.Element, buffer: Gst.Buffer): + if not self._sync_event.is_set(): + try: + start = buffer.pts + + # Calculate the difference between the buffer timestamp and the + # current time, this allows precise timing (e.g. for fadeout) + self._calc_sync_delay(start) + + self._control_source.unset_all() + + for time, value in self._control_points.items(): + self._control_source.set(start + time * Gst.MSECOND, value) + finally: + # Inform the thread that called the `set()` method that we have + # set the control points + self._sync_event.set() + + def _calc_sync_delay(self, start): + ok, position = self._pipeline.query_position(Gst.Format.TIME) + self._delay = (start - position) // Gst.MSECOND diff --git a/lisp/plugins/gst_backend/gst_utils.py b/lisp/plugins/gst_backend/gst_utils.py index 0ada8bf95..657a5f067 100644 --- a/lisp/plugins/gst_backend/gst_utils.py +++ b/lisp/plugins/gst_backend/gst_utils.py @@ -17,7 +17,7 @@ from lisp.backend.audio_utils import audio_file_duration from lisp.core.session_uri import SessionURI -from lisp.plugins.gst_backend.gi_repository import Gst, GstPbutils +from lisp.plugins.gst_backend.gi_repository import GObject, Gst, GstPbutils def gst_uri_duration(uri: SessionURI): @@ -69,5 +69,10 @@ def parse_tag(gst_tag_list, tag_name, parsed_tags): return parsed_tags +def gtype(g_object: GObject.GObject) -> GObject.GType: + """Get the GType of GObject objects""" + return g_object.__gtype__ + + class GstError(Exception): """Used to wrap GStreamer debug messages for the logging system.""" From d1de47a27cdadfdace1872c2841d33d149213143 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 11 Feb 2022 22:05:04 +0100 Subject: [PATCH 275/333] Handle situations where the waveform pipeline cannot be created. --- lisp/plugins/gst_backend/gi_repository.py | 2 +- lisp/plugins/gst_backend/gst_waveform.py | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lisp/plugins/gst_backend/gi_repository.py b/lisp/plugins/gst_backend/gi_repository.py index 885b0f775..1ad79de74 100644 --- a/lisp/plugins/gst_backend/gi_repository.py +++ b/lisp/plugins/gst_backend/gi_repository.py @@ -9,4 +9,4 @@ # noinspection PyUnresolvedReferences # pylint: disable=unused-import -from gi.repository import GObject, Gst, GstController, GstPbutils, GstApp +from gi.repository import GObject, GLib, Gst, GstController, GstPbutils, GstApp diff --git a/lisp/plugins/gst_backend/gst_waveform.py b/lisp/plugins/gst_backend/gst_waveform.py index 51a6b9d35..cdbcbb3ce 100644 --- a/lisp/plugins/gst_backend/gst_waveform.py +++ b/lisp/plugins/gst_backend/gst_waveform.py @@ -20,7 +20,7 @@ from array import array from lisp.backend.waveform import Waveform -from .gi_repository import Gst +from .gi_repository import Gst, GLib from .gst_utils import GstError logger = logging.getLogger(__name__) @@ -51,12 +51,19 @@ def _load_waveform(self): # Create the pipeline with an appropriate buffer-size to control # how many seconds of data we receive at each 'new-sample' event. - self._pipeline = Gst.parse_launch( - self.PIPELINE_TEMPLATE.format( - uri=self._uri.uri, - sample_length=f"{self.duration // 1000}/{self.max_samples}", + try: + self._pipeline = Gst.parse_launch( + self.PIPELINE_TEMPLATE.format( + uri=self._uri.uri, + sample_length=f"{self.duration // 1000}/{self.max_samples}", + ) ) - ) + except GLib.GError: + logger.warning( + f'Cannot generate waveform for "{self._uri.unquoted_uri}"', + exc_info=True + ) + return True # Connect to the app-sink app_sink = self._pipeline.get_by_name("app_sink") From ca4add7af5e30afb6c0fa74cbf30466f94dfbe4e Mon Sep 17 00:00:00 2001 From: s0600204 <2053873+s0600204@users.noreply.github.com> Date: Wed, 23 Feb 2022 17:48:36 +0000 Subject: [PATCH 276/333] Remove the OSC cue's Pause action (#229) As it is no longer possible for `Fader`s to be paused, it is no longer possible to pause running (e.g. fading) OSC cues. --- lisp/plugins/osc/osc_cue.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lisp/plugins/osc/osc_cue.py b/lisp/plugins/osc/osc_cue.py index adb7261e1..276be00ec 100644 --- a/lisp/plugins/osc/osc_cue.py +++ b/lisp/plugins/osc/osc_cue.py @@ -67,7 +67,6 @@ class OscCue(Cue): CueAction.Default, CueAction.Start, CueAction.Stop, - CueAction.Pause, ) path = Property(default="") @@ -129,10 +128,6 @@ def has_fade(self): return self.__has_fade and self.duration > 0 def __start__(self, fade=False): - if self.__fader.is_paused(): - self.__fader.resume() - return True - if self.has_fade(): if not self.__fadein: self.__fade(FadeOutType[self.fade_type]) @@ -149,10 +144,6 @@ def __stop__(self, fade=False): self.__fader.stop() return True - def __pause__(self, fade=False): - self.__fader.pause() - return True - __interrupt__ = __stop__ @async_function From 72ab2fc5f5dff3b2d3f001143131129ab7cd98e2 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 26 Feb 2022 11:34:39 +0100 Subject: [PATCH 277/333] Fix #232: JACK port names not being display correctly --- .../plugins/gst_backend/settings/jack_sink.py | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/lisp/plugins/gst_backend/settings/jack_sink.py b/lisp/plugins/gst_backend/settings/jack_sink.py index 22fad7535..51d66e738 100644 --- a/lisp/plugins/gst_backend/settings/jack_sink.py +++ b/lisp/plugins/gst_backend/settings/jack_sink.py @@ -115,18 +115,16 @@ def __init__(self, client_name): self.name = client_name self.ports = {} - def add_port(self, port_name): - port = PortItem(port_name) - - self.addChild(port) - self.ports[port_name] = port + def add_port(self, full_name, display_name): + self.ports[full_name] = PortItem(full_name, display_name) + self.addChild(self.ports[full_name]) class PortItem(QTreeWidgetItem): - def __init__(self, port_name): - super().__init__([port_name[: port_name.index(":")]]) + def __init__(self, full_name, display_name): + super().__init__([display_name]) - self.name = port_name + self.name = full_name class ConnectionsWidget(QWidget): @@ -292,13 +290,18 @@ def update_graph(self): self.input_widget.clear() clients = {} for port in input_ports: - client_name = port.name[: port.name.index(":")] - - if client_name not in clients: - clients[client_name] = ClientItem(client_name) - self.input_widget.addTopLevelItem(clients[client_name]) - - clients[client_name].add_port(port.name) + try: + colon_index = port.name.index(":") + client_name = port.name[:colon_index] + port_display_name = port.name[colon_index+1:] + + if client_name not in clients: + clients[client_name] = ClientItem(client_name) + self.input_widget.addTopLevelItem(clients[client_name]) + + clients[client_name].add_port(port.name, port_display_name) + except ValueError: + pass def __input_selection_changed(self): if self.input_widget.selectedItems(): From 75e8ed1d053fa0c516d112925496311fa0a6a88d Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 2 Mar 2022 19:35:57 +0100 Subject: [PATCH 278/333] Start using GstFader, add "Application" as dependencies for all cues Update: remove AppConfig class, Application.conf must be used instead Fix: cue "interrupt" settings are now correctly used --- lisp/application.py | 28 +++--- lisp/core/configuration.py | 9 -- lisp/cues/cue.py | 31 +++++-- lisp/cues/cue_factory.py | 39 ++++---- lisp/cues/media_cue.py | 93 +++++++++++--------- lisp/plugins/action_cues/__init__.py | 3 +- lisp/plugins/action_cues/collection_cue.py | 6 +- lisp/plugins/action_cues/index_action_cue.py | 6 +- lisp/plugins/action_cues/seek_cue.py | 6 +- lisp/plugins/action_cues/stop_all.py | 9 +- lisp/plugins/action_cues/volume_control.py | 6 +- lisp/plugins/cart_layout/layout.py | 3 +- lisp/plugins/gst_backend/gst_backend.py | 5 +- lisp/plugins/gst_backend/gst_media_cue.py | 12 +-- lisp/plugins/list_layout/layout.py | 3 +- lisp/plugins/list_layout/list_view.py | 7 +- lisp/plugins/midi/midi.py | 3 +- lisp/plugins/midi/midi_cue.py | 4 +- lisp/plugins/osc/osc.py | 3 +- lisp/plugins/osc/osc_cue.py | 4 +- lisp/plugins/presets/lib.py | 3 +- lisp/plugins/presets/presets_ui.py | 6 +- lisp/ui/mainwindow.py | 3 +- 23 files changed, 147 insertions(+), 145 deletions(-) diff --git a/lisp/application.py b/lisp/application.py index e0dac71fe..db8a109ef 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -23,7 +23,7 @@ from lisp import layout from lisp.command.stack import CommandsStack -from lisp.core.configuration import DummyConfiguration +from lisp.core.configuration import Configuration, DummyConfiguration from lisp.core.session import Session from lisp.core.signal import Signal from lisp.core.singleton import Singleton @@ -32,6 +32,7 @@ from lisp.cues.cue_factory import CueFactory from lisp.cues.cue_model import CueModel from lisp.cues.media_cue import MediaCue +from lisp.layout.cue_layout import CueLayout from lisp.ui.layoutselect import LayoutSelect from lisp.ui.mainwindow import MainWindow from lisp.ui.settings.app_configuration import AppConfigurationDialog @@ -54,6 +55,7 @@ def __init__(self, app_conf=DummyConfiguration()): self.session_before_finalize = Signal() self.__conf = app_conf + self.__cue_factory = CueFactory(self) self.__cue_model = CueModel() self.__session = None self.__commands_stack = CommandsStack() @@ -84,33 +86,31 @@ def __init__(self, app_conf=DummyConfiguration()): self.__main_window.open_session.connect(self.__load_from_file) @property - def conf(self): - """:rtype: lisp.core.configuration.Configuration""" + def conf(self) -> Configuration: return self.__conf @property - def session(self): - """:rtype: lisp.core.session.Session""" + def session(self) -> Session: return self.__session @property - def window(self): - """:rtype: lisp.ui.mainwindow.MainWindow""" + def window(self) -> MainWindow: return self.__main_window @property - def layout(self): - """:rtype: lisp.layout.cue_layout.CueLayout""" + def layout(self) -> CueLayout: return self.__session.layout @property - def cue_model(self): - """:rtype: lisp.cues.cue_model.CueModel""" + def cue_model(self) -> CueModel: return self.__cue_model @property - def commands_stack(self): - """:rtype: lisp.command.stack.CommandsStack""" + def cue_factory(self) -> CueFactory: + return self.__cue_factory + + @property + def commands_stack(self) -> CommandsStack: return self.__commands_stack def start(self, session_file=""): @@ -220,7 +220,7 @@ def __load_from_file(self, session_file): cue_type = cues_dict.pop("_type_", "Undefined") cue_id = cues_dict.pop("id") try: - cue = CueFactory.create_cue(cue_type, cue_id=cue_id) + cue = self.cue_factory.create_cue(cue_type, cue_id=cue_id) cue.update_properties(cues_dict) self.cue_model.add(cue) except Exception: diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index e5e2b182b..474624946 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -25,9 +25,7 @@ from os import path from shutil import copyfile -from lisp import DEFAULT_APP_CONFIG, USER_APP_CONFIG from lisp.core.signal import Signal -from lisp.core.singleton import ABCSingleton from lisp.core.util import dict_merge, dict_merge_diff, typename from lisp.ui.ui_utils import translate @@ -250,10 +248,3 @@ def _check_file(self): def _read_json(path): with open(path, "r") as f: return json.load(f) - - -class AppConfig(JSONFileConfiguration, metaclass=ABCSingleton): - """Provide access to the application configuration (singleton)""" - - def __init__(self): - super().__init__(USER_APP_CONFIG, DEFAULT_APP_CONFIG) diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 06bb61523..06704ac26 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -18,7 +18,6 @@ from threading import Lock from uuid import uuid4 -from lisp.core.configuration import AppConfig from lisp.core.decorators import async_function from lisp.core.fade_functions import FadeInType, FadeOutType from lisp.core.has_properties import HasProperties @@ -82,6 +81,7 @@ class Cue(HasProperties): HasProperties/Property specifications. :ivar _type_: Cue type (class name). Should NEVER change after init. + :ivar app: The application instance. :ivar id: Identify the cue uniquely. Should NEVER change after init. :ivar index: Cue position in the view. :ivar name: Cue visualized name. @@ -131,9 +131,14 @@ class Cue(HasProperties): CueActions = (CueAction.Start,) - def __init__(self, id=None): + def __init__(self, app, id=None): + """ + :type app: lisp.application.Application + """ super().__init__() + self.id = str(uuid4()) if id is None else id + self.app = app self._type_ = typename(self) self._st_lock = Lock() @@ -234,12 +239,22 @@ def execute(self, action=CueAction.Default): self._default_fade_type(FadeInType, FadeInType.Linear), ) + def _interrupt_fade_duration(self): + return self.app.conf.get("cue.interruptFade", 0) + + def _interrupt_fade_type(self): + return getattr( + FadeOutType, + self.app.conf.get("cue.interruptFadeType"), + FadeOutType.Linear, + ) + def _default_fade_duration(self): - return AppConfig().get("cue.fadeAction", 0) + return self.app.conf.get("cue.fadeAction", 0) def _default_fade_type(self, type_class, default=None): return getattr( - type_class, AppConfig().get("cue.fadeActionType"), default + type_class, self.app.conf.get("cue.fadeActionType"), default ) @async_function @@ -317,7 +332,7 @@ def resume(self, fade=False): def __start__(self, fade=False): """Implement the cue `start` behavior. - Long running tasks should not block this function (i.e. the fade should + Long-running tasks should not block this function (i.e. the fade should be performed in another thread). When called from `Cue.start()`, `_st_lock` is acquired. @@ -375,7 +390,7 @@ def stop(self, fade=False): def __stop__(self, fade=False): """Implement the cue `stop` behavior. - Long running tasks should block this function (i.e. the fade should + Long-running tasks should block this function (i.e. the fade should "block" this function), when this happens `_st_lock` must be released and then re-acquired. @@ -426,7 +441,7 @@ def pause(self, fade=False): def __pause__(self, fade=False): """Implement the cue `pause` behavior. - Long running tasks should block this function (i.e. the fade should + Long-running tasks should block this function (i.e. the fade should "block" this function), when this happens `_st_lock` must be released and then re-acquired. @@ -478,7 +493,7 @@ def interrupt(self, fade=False): def __interrupt__(self, fade=False): """Implement the cue `interrupt` behavior. - Long running tasks should block this function without releasing + Long-running tasks should block this function without releasing `_st_lock`. :param fade: True if a fade should be performed (when supported) diff --git a/lisp/cues/cue_factory.py b/lisp/cues/cue_factory.py index bfbb9f649..67d0569b5 100644 --- a/lisp/cues/cue_factory.py +++ b/lisp/cues/cue_factory.py @@ -21,47 +21,41 @@ class CueFactory: - """Provide a generic factory to build different cues types. + """Factory to build different cues types. - Cues can be register via `register_factory` function. + Cues can be registered via `register_factory` function. """ - __REGISTRY = {} + def __init__(self, app): + self.app = app + self.__registry = {} - # Register methods - - @classmethod - def register_factory(cls, cue_type, factory): + def register_factory(self, cue_type, factory): """Register a new cue-type in the factory. :param cue_type: The cue class name :type cue_type: str :param factory: The cue class, or a factory function """ - cls.__REGISTRY[cue_type] = factory + self.__registry[cue_type] = factory - @classmethod - def has_factory(cls, cue_type): + def has_factory(self, cue_type): """Return True if there is a factory for `cue_type` :param cue_type: The cue type to check :rtype cue_type: str :rtype: bool """ - return cue_type in cls.__REGISTRY + return cue_type in self.__registry - @classmethod - def remove_factory(cls, cue_type): + def remove_factory(self, cue_type): """Remove the registered cue from the factory :param cue_type: the cue class name (the same used for registration) """ - cls.__REGISTRY.pop(cue_type) - - # Create methods + self.__registry.pop(cue_type) - @classmethod - def create_cue(cls, cue_type, cue_id=None, **kwargs): + def create_cue(self, cue_type, cue_id=None, **kwargs): """Return a new cue of the specified type. ..note: @@ -71,17 +65,16 @@ def create_cue(cls, cue_type, cue_id=None, **kwargs): :param cue_type: The cue type :rtype: lisp.cues.cue.Cue """ - factory = cls.__REGISTRY.get(cue_type) + factory = self.__registry.get(cue_type) if not callable(factory): raise Exception( f"Cue not available or badly registered: {cue_type}" ) - return factory(id=cue_id, **kwargs) + return factory(app=self.app, id=cue_id, **kwargs) - @classmethod - def clone_cue(cls, cue): + def clone_cue(self, cue): """Return a copy of the given cue. The id is not copied. :param cue: the cue to be copied @@ -90,7 +83,7 @@ def clone_cue(cls, cue): properties = deepcopy(cue.properties()) properties.pop("id") - cue = cls.create_cue(typename(cue)) + cue = self.create_cue(typename(cue)) cue.update_properties(properties) return cue diff --git a/lisp/cues/media_cue.py b/lisp/cues/media_cue.py index 6576de701..52d78c83b 100644 --- a/lisp/cues/media_cue.py +++ b/lisp/cues/media_cue.py @@ -19,10 +19,8 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP -from lisp.backend.audio_utils import MIN_VOLUME from lisp.core.decorators import async_function from lisp.core.fade_functions import FadeInType, FadeOutType -from lisp.core.fader import Fader from lisp.core.properties import Property from lisp.cues.cue import Cue, CueAction, CueState @@ -50,8 +48,8 @@ class MediaCue(Cue): CueAction.FadeOutInterrupt, ) - def __init__(self, media, id=None): - super().__init__(id=id) + def __init__(self, app, media, id=None): + super().__init__(app, id=id) self.media = media self.media.changed("duration").connect(self._duration_change) self.media.elements_changed.connect(self.__elements_changed) @@ -61,20 +59,34 @@ def __init__(self, media, id=None): self.__in_fadein = False self.__in_fadeout = False - self.__volume = self.media.element("Volume") - self.__fader = Fader(self.__volume, "live_volume") self.__fade_lock = Lock() + self.__fader = None + self.__volume = None + + self.__elements_changed() def __elements_changed(self): + # Ensure the current fade, if any, is not running. + if self.__fader is not None: + self.__fader.stop() + + # Create a new fader, if possible self.__volume = self.media.element("Volume") - self.__fader.target = self.__volume + if self.__volume is not None: + self.__fader = self.__volume.get_fader("live_volume") + else: + self.__fader = None def __start__(self, fade=False): - if fade and self._can_fade(self.fadein_duration): + # If we are fading-in on the start of the media, we need to ensure + # that the volume starts at 0 + if fade and self.fadein_duration > 0 and self._can_fade(): self.__volume.live_volume = 0 self.media.play() - if fade: + + # Once the media is playing we can start the fade-in, if needed + if fade and self.fadein_duration > 0 and self._can_fade(): self._on_start_fade() return True @@ -86,11 +98,11 @@ def __stop__(self, fade=False): if self.__in_fadein: self.__fader.stop() - if self._state & CueState.Running and fade: + if fade and self._state & CueState.Running and self._can_fade(): self._st_lock.release() ended = self.__fadeout( self.fadeout_duration, - MIN_VOLUME, + 0, FadeOutType[self.fadeout_type], ) self._st_lock.acquire() @@ -107,11 +119,11 @@ def __pause__(self, fade=False): if self.__in_fadein: self.__fader.stop() - if fade: + if fade and self._state & CueState.Running and self._can_fade(): self._st_lock.release() ended = self.__fadeout( self.fadeout_duration, - MIN_VOLUME, + 0, FadeOutType[self.fadeout_type], ) self._st_lock.acquire() @@ -122,14 +134,12 @@ def __pause__(self, fade=False): return True def __interrupt__(self, fade=False): - self.__fader.stop() + if self._can_fade(): + self.__fader.stop() - if self._state & CueState.Running and fade: - self.__fadeout( - self._default_fade_duration(), - MIN_VOLUME, - self._default_fade_type(FadeOutType, FadeOutType.Linear), - ) + fade_duration = self._interrupt_fade_duration() + if fade and fade_duration > 0 and self._state & CueState.Running: + self.__fadeout(fade_duration, 0, self._interrupt_fade_type()) self.media.stop() @@ -138,16 +148,15 @@ def fadein(self, duration, fade_type): if not self._st_lock.acquire(timeout=0.1): return - if self._state & CueState.Running: + if self._state & CueState.Running and self._can_fade(): self.__fader.stop() - if self.__volume is not None: - if duration <= 0: - self.__volume.live_volume = self.__volume.volume - else: - self._st_lock.release() - self.__fadein(duration, self.__volume.volume, fade_type) - return + if duration <= 0: + self.__volume.live_volume = self.__volume.volume + else: + self._st_lock.release() + self.__fadein(duration, self.__volume.volume, fade_type) + return self._st_lock.release() @@ -156,22 +165,21 @@ def fadeout(self, duration, fade_type): if not self._st_lock.acquire(timeout=0.1): return - if self._state & CueState.Running: + if self._state & CueState.Running and self._can_fade(): self.__fader.stop() - if self.__volume is not None: - if duration <= 0: - self.__volume.live_volume = 0 - else: - self._st_lock.release() - self.__fadeout(duration, MIN_VOLUME, fade_type) - return + if duration <= 0: + self.__volume.live_volume = 0 + else: + self._st_lock.release() + self.__fadeout(duration, 0, fade_type) + return self._st_lock.release() def __fadein(self, duration, to_value, fade_type): ended = True - if self._can_fade(duration): + if duration > 0 and self._can_fade(): with self.__fade_lock: self.__in_fadein = True self.fadein_start.emit() @@ -186,7 +194,7 @@ def __fadein(self, duration, to_value, fade_type): def __fadeout(self, duration, to_value, fade_type): ended = True - if self._can_fade(duration): + if duration > 0 and self._can_fade(): with self.__fade_lock: self.__in_fadeout = True self.fadeout_start.emit() @@ -202,6 +210,9 @@ def __fadeout(self, duration, to_value, fade_type): def current_time(self): return self.media.current_time() + def is_fading(self): + return self.__in_fadein or self.__in_fadeout + def _duration_change(self, value): self.duration = value @@ -215,12 +226,12 @@ def _on_error(self): self.__fader.stop() self._error() - def _can_fade(self, duration): - return self.__volume is not None and duration > 0 + def _can_fade(self): + return self.__volume is not None and self.__fader is not None @async_function def _on_start_fade(self): - if self.__volume is not None: + if self._can_fade(): self.__fadein( self.fadein_duration, self.__volume.volume, diff --git a/lisp/plugins/action_cues/__init__.py b/lisp/plugins/action_cues/__init__.py index 59e5eaa20..f1592c7bf 100644 --- a/lisp/plugins/action_cues/__init__.py +++ b/lisp/plugins/action_cues/__init__.py @@ -19,7 +19,6 @@ from lisp.core.loading import load_classes from lisp.core.plugin import Plugin -from lisp.cues.cue_factory import CueFactory class ActionCues(Plugin): @@ -32,5 +31,5 @@ def __init__(self, app): # Register all the cue in the plugin for _, cue_class in load_classes(__package__, path.dirname(__file__)): - CueFactory.register_factory(cue_class.__name__, cue_class) + app.cue_factory.register_factory(cue_class.__name__, cue_class) app.window.registerSimpleCueMenu(cue_class, cue_class.Category) diff --git a/lisp/plugins/action_cues/collection_cue.py b/lisp/plugins/action_cues/collection_cue.py index 2bfd3bac9..3f52089af 100644 --- a/lisp/plugins/action_cues/collection_cue.py +++ b/lisp/plugins/action_cues/collection_cue.py @@ -45,13 +45,13 @@ class CollectionCue(Cue): targets = Property(default=[]) - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.name = translate("CueName", self.Name) def __start__(self, fade=False): for target_id, action in self.targets: - cue = Application().cue_model.get(target_id) + cue = self.app.cue_model.get(target_id) if cue is not self: cue.execute(action=CueAction[action]) diff --git a/lisp/plugins/action_cues/index_action_cue.py b/lisp/plugins/action_cues/index_action_cue.py index 0bd0409d3..91207a4f7 100644 --- a/lisp/plugins/action_cues/index_action_cue.py +++ b/lisp/plugins/action_cues/index_action_cue.py @@ -43,8 +43,8 @@ class IndexActionCue(Cue): relative = Property(default=True) action = Property(default=CueAction.Stop.value) - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.name = translate("CueName", self.Name) def __start__(self, fade=False): @@ -54,7 +54,7 @@ def __start__(self, fade=False): index = self.target_index try: - cue = Application().layout.cue_at(index) + cue = self.app.layout.cue_at(index) if cue is not self: cue.execute(CueAction(self.action)) except IndexError: diff --git a/lisp/plugins/action_cues/seek_cue.py b/lisp/plugins/action_cues/seek_cue.py index 58d9cfd8a..ca525c52c 100644 --- a/lisp/plugins/action_cues/seek_cue.py +++ b/lisp/plugins/action_cues/seek_cue.py @@ -43,12 +43,12 @@ class SeekCue(Cue): target_id = Property() time = Property(default=-1) - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.name = translate("CueName", self.Name) def __start__(self, fade=False): - cue = Application().cue_model.get(self.target_id) + cue = self.app.cue_model.get(self.target_id) if isinstance(cue, MediaCue) and self.time >= 0: cue.media.seek(self.time) diff --git a/lisp/plugins/action_cues/stop_all.py b/lisp/plugins/action_cues/stop_all.py index c927f6093..efb9e00be 100644 --- a/lisp/plugins/action_cues/stop_all.py +++ b/lisp/plugins/action_cues/stop_all.py @@ -18,7 +18,6 @@ from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import QComboBox, QVBoxLayout, QGroupBox -from lisp.application import Application from lisp.core.properties import Property from lisp.cues.cue import Cue, CueAction from lisp.ui.settings.cue_settings import CueSettingsRegistry @@ -32,14 +31,12 @@ class StopAll(Cue): action = Property(default=CueAction.Stop.value) - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.name = translate("CueName", self.Name) def __start__(self, fade=False): - Application().layout.execute_all( - action=CueAction(self.action), quiet=True - ) + self.app.layout.execute_all(action=CueAction(self.action), quiet=True) class StopAllSettings(SettingsPage): diff --git a/lisp/plugins/action_cues/volume_control.py b/lisp/plugins/action_cues/volume_control.py index b14952e1b..5f627a0a4 100644 --- a/lisp/plugins/action_cues/volume_control.py +++ b/lisp/plugins/action_cues/volume_control.py @@ -65,15 +65,15 @@ class VolumeControl(Cue): CueAction.Interrupt, ) - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.name = translate("CueName", self.Name) self.__fader = None self.__init_fader() def __init_fader(self): - cue = Application().cue_model.get(self.target_id) + cue = self.app.cue_model.get(self.target_id) if isinstance(cue, MediaCue): volume = cue.media.element("Volume") diff --git a/lisp/plugins/cart_layout/layout.py b/lisp/plugins/cart_layout/layout.py index 3fdc951d2..698068cc4 100644 --- a/lisp/plugins/cart_layout/layout.py +++ b/lisp/plugins/cart_layout/layout.py @@ -24,7 +24,6 @@ from lisp.core.configuration import DummyConfiguration from lisp.core.properties import ProxyProperty from lisp.cues.cue import Cue -from lisp.cues.cue_factory import CueFactory from lisp.cues.media_cue import MediaCue from lisp.layout.cue_layout import CueLayout from lisp.layout.cue_menu import ( @@ -405,7 +404,7 @@ def _copy_widget(self, widget, to_row, to_column): new_index = self.to_1d_index( (self._cart_view.currentIndex(), to_row, to_column) ) - new_cue = CueFactory.clone_cue(widget.cue) + new_cue = self.app.cue_factory.clone_cue(widget.cue) self.app.commands_stack.do( ModelInsertItemsCommand(self._cart_model, new_index, new_cue) diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index d69c06bf6..54a2a76ff 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -26,7 +26,6 @@ from lisp.command.layout import LayoutAutoInsertCuesCommand from lisp.core.decorators import memoize from lisp.core.plugin import Plugin -from lisp.cues.cue_factory import CueFactory from lisp.cues.media_cue import MediaCue from lisp.plugins.gst_backend import config, elements, settings from lisp.plugins.gst_backend.gi_repository import Gst @@ -74,7 +73,7 @@ def __init__(self, app): CueSettingsRegistry().add(GstMediaSettings, MediaCue) # Register GstMediaCue factory - CueFactory.register_factory("GstMediaCue", GstCueFactory(tuple())) + app.cue_factory.register_factory("GstMediaCue", GstCueFactory(tuple())) # Add Menu entry self.app.window.registerCueMenu( translate("GstBackend", "Audio cue (from file)"), @@ -170,7 +169,7 @@ def add_cue_from_files(self, files): cues = [] for index, file in enumerate(files, start_index): - cue = factory(uri=file) + cue = factory(self.app, uri=file) # Use the filename without extension as cue name cue.name = os.path.splitext(os.path.basename(file))[0] # Set the index (if something is selected) diff --git a/lisp/plugins/gst_backend/gst_media_cue.py b/lisp/plugins/gst_backend/gst_media_cue.py index 0bcb70d2c..b84c1ee13 100644 --- a/lisp/plugins/gst_backend/gst_media_cue.py +++ b/lisp/plugins/gst_backend/gst_media_cue.py @@ -23,8 +23,8 @@ class GstMediaCue(MediaCue): media = Property(default=GstMedia.class_defaults()) - def __init__(self, media, id=None, pipeline=None): - super().__init__(media, id=id) + def __init__(self, app, media, id=None, pipeline=None): + super().__init__(app, media, id=id) if pipeline is not None: media.pipe = pipeline @@ -35,8 +35,8 @@ def __init__(self, base_pipeline): self.base_pipeline = base_pipeline self.input = "" - def __call__(self, id=None): - return GstMediaCue(GstMedia(), id=id, pipeline=self.pipeline()) + def __call__(self, app, id=None): + return GstMediaCue(app, GstMedia(), id=id, pipeline=self.pipeline()) def pipeline(self): if self.base_pipeline and self.input: @@ -48,8 +48,8 @@ def __init__(self, base_pipeline): super().__init__(base_pipeline) self.input = "UriInput" - def __call__(self, id=None, uri=None): - cue = super().__call__(id=id) + def __call__(self, app, id=None, uri=None): + cue = super().__call__(app, id=id) if uri is not None: try: diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index ccbc4eac2..aa314ed4a 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -24,7 +24,6 @@ from lisp.core.properties import ProxyProperty from lisp.core.signal import Connection from lisp.cues.cue import Cue, CueAction, CueNextAction -from lisp.cues.cue_factory import CueFactory from lisp.layout.cue_layout import CueLayout from lisp.layout.cue_menu import ( SimpleMenuAction, @@ -407,7 +406,7 @@ def _clone_cue(self, cue): def _clone_cues(self, cues): for pos, cue in enumerate(cues, cues[-1].index + 1): - clone = CueFactory.clone_cue(cue) + clone = self.app.cue_factory.clone_cue(cue) clone.name = translate("ListLayout", "Copy of {}").format( clone.name ) diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py index 4025c8d7a..644cf78b9 100644 --- a/lisp/plugins/list_layout/list_view.py +++ b/lisp/plugins/list_layout/list_view.py @@ -30,7 +30,6 @@ from lisp.backend import get_backend from lisp.command.model import ModelMoveItemsCommand, ModelInsertItemsCommand from lisp.core.util import subdict -from lisp.cues.cue_factory import CueFactory from lisp.plugins.list_layout.list_widgets import ( CueStatusIcons, NameWidget, @@ -182,7 +181,11 @@ def dropEvent(self, event): elif event.proposedAction() == Qt.CopyAction: new_cues = [] for row in sorted(rows): - new_cues.append(CueFactory.clone_cue(self._model.item(row))) + new_cues.append( + Application().cue_factory.clone_cue( + self._model.item(row) + ) + ) Application().commands_stack.do( ModelInsertItemsCommand(self._model, to_index, *new_cues) diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index 9dad4763d..f8cb2ac00 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -22,7 +22,6 @@ from lisp.core.plugin import Plugin from lisp.core.signal import Connection -from lisp.cues.cue_factory import CueFactory from lisp.plugins.midi.midi_cue import MidiCue from lisp.plugins.midi.midi_io import MIDIOutput, MIDIInput, MIDIBase from lisp.plugins.midi.midi_settings import MIDISettings @@ -50,7 +49,7 @@ def __init__(self, app): ) # Register cue - CueFactory.register_factory(MidiCue.__name__, MidiCue) + app.cue_factory.register_factory(MidiCue.__name__, MidiCue) app.window.registerSimpleCueMenu( MidiCue, QT_TRANSLATE_NOOP("CueCategory", "Integration cues") ) diff --git a/lisp/plugins/midi/midi_cue.py b/lisp/plugins/midi/midi_cue.py index 60116765a..502fb20dc 100644 --- a/lisp/plugins/midi/midi_cue.py +++ b/lisp/plugins/midi/midi_cue.py @@ -41,8 +41,8 @@ class MidiCue(Cue): message = Property(default="") - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.name = translate("CueName", self.Name) self.__midi = get_plugin("Midi") diff --git a/lisp/plugins/osc/osc.py b/lisp/plugins/osc/osc.py index 20149874c..7cc3cce1f 100644 --- a/lisp/plugins/osc/osc.py +++ b/lisp/plugins/osc/osc.py @@ -19,7 +19,6 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.core.plugin import Plugin -from lisp.cues.cue_factory import CueFactory from lisp.plugins.osc.osc_cue import OscCue from lisp.plugins.osc.osc_server import OscServer from lisp.plugins.osc.osc_settings import OscSettings @@ -41,7 +40,7 @@ def __init__(self, app): "plugins.osc", OscSettings, Osc.Config ) # Register the cue - CueFactory.register_factory(OscCue.__name__, OscCue) + app.cue_factory.register_factory(OscCue.__name__, OscCue) app.window.registerSimpleCueMenu( OscCue, QT_TRANSLATE_NOOP("CueCategory", "Integration cues") ) diff --git a/lisp/plugins/osc/osc_cue.py b/lisp/plugins/osc/osc_cue.py index 276be00ec..66bf71350 100644 --- a/lisp/plugins/osc/osc_cue.py +++ b/lisp/plugins/osc/osc_cue.py @@ -73,8 +73,8 @@ class OscCue(Cue): args = Property(default=[]) fade_type = Property(default=FadeInType.Linear.name) - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.name = translate("CueName", self.Name) self.__osc = get_plugin("Osc") diff --git a/lisp/plugins/presets/lib.py b/lisp/plugins/presets/lib.py index 0c443bd4f..3bd4a1867 100644 --- a/lisp/plugins/presets/lib.py +++ b/lisp/plugins/presets/lib.py @@ -22,7 +22,6 @@ from lisp import app_dirs from lisp.command.cue import UpdateCueCommand, UpdateCuesCommand from lisp.command.layout import LayoutAutoInsertCuesCommand -from lisp.cues.cue_factory import CueFactory PRESETS_DIR = os.path.join(app_dirs.user_data_dir, "presets") @@ -64,7 +63,7 @@ def insert_cue_from_preset(app, preset_name): :type preset_name: str """ preset = load_preset(preset_name) - cue = CueFactory.create_cue(preset["_type_"]) + cue = app.cue_factory.create_cue(preset["_type_"]) cue.update_properties(preset) app.commands_stack.do(LayoutAutoInsertCuesCommand(app.layout, cue)) diff --git a/lisp/plugins/presets/presets_ui.py b/lisp/plugins/presets/presets_ui.py index 2463cd125..c0fd8bdec 100644 --- a/lisp/plugins/presets/presets_ui.py +++ b/lisp/plugins/presets/presets_ui.py @@ -38,7 +38,6 @@ from lisp.core.util import natural_keys from lisp.cues.cue import Cue -from lisp.cues.cue_factory import CueFactory from lisp.plugins.presets.lib import ( preset_exists, export_presets, @@ -284,8 +283,9 @@ def __edit_preset(self): preset = load_preset(item.text()) if preset is not None: try: - cue_class = CueFactory.create_cue(preset.get("_type_")) - cue_class = cue_class.__class__ + cue_class = self.app.cue_factory.create_cue( + preset.get("_type_") + ).__class__ except Exception: cue_class = Cue diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index e37cafcd0..1bc4fab93 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -40,7 +40,6 @@ from lisp.command.layout import LayoutAutoInsertCuesCommand from lisp.core.singleton import QSingleton -from lisp.cues.cue_factory import CueFactory from lisp.cues.media_cue import MediaCue from lisp.ui.about import About from lisp.ui.logging.dialog import LogDialogs @@ -337,7 +336,7 @@ def __simpleCueInsert(self, cueClass): self._app.commands_stack.do( LayoutAutoInsertCuesCommand( self._app.session.layout, - CueFactory.create_cue(cueClass.__name__), + self._app.cue_factory.create_cue(cueClass.__name__), ) ) except Exception: From f98c0cbb7596f812e73f7bcb37c8732162e830ab Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 3 Mar 2022 21:35:20 +0100 Subject: [PATCH 279/333] Display initial fade-in correctly in ListLayout Add: `is_fading_in` and `is_fading_out` methods to Cue --- lisp/cues/cue.py | 9 ++++ lisp/cues/media_cue.py | 7 ++- lisp/plugins/list_layout/playing_widgets.py | 58 +++++++++++++-------- 3 files changed, 49 insertions(+), 25 deletions(-) diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 06704ac26..c51fd7bcc 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -568,6 +568,15 @@ def state(self): """ return self._state + def is_fading(self): + return self.is_fading_in() or self.is_fading_out() + + def is_fading_in(self): + return False + + def is_fading_out(self): + return False + def __next_action_changed(self, next_action): self.end.disconnect(self.next.emit) if ( diff --git a/lisp/cues/media_cue.py b/lisp/cues/media_cue.py index 52d78c83b..a8068df69 100644 --- a/lisp/cues/media_cue.py +++ b/lisp/cues/media_cue.py @@ -210,8 +210,11 @@ def __fadeout(self, duration, to_value, fade_type): def current_time(self): return self.media.current_time() - def is_fading(self): - return self.__in_fadein or self.__in_fadeout + def is_fading_in(self): + return self.__in_fadein + + def is_fading_out(self): + return self.__in_fadeout def _duration_change(self, value): self.duration = value diff --git a/lisp/plugins/list_layout/playing_widgets.py b/lisp/plugins/list_layout/playing_widgets.py index 3ac0422fe..8cb6e3c90 100644 --- a/lisp/plugins/list_layout/playing_widgets.py +++ b/lisp/plugins/list_layout/playing_widgets.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QTimer from PyQt5.QtGui import QColor from PyQt5.QtWidgets import ( QWidget, @@ -74,28 +74,7 @@ def __init__(self, cue, config, **kwargs): self.controlButtons = CueControlButtons(parent=self.gridLayoutWidget) self.gridLayout.addWidget(self.controlButtons, 1, 0) - if CueAction.Stop in cue.CueActions: - self.controlButtons.stopButton.clicked.connect(self._stop) - else: - self.controlButtons.stopButton.setEnabled(False) - if CueAction.Pause in cue.CueActions: - self.controlButtons.pauseButton.clicked.connect(self._pause) - self.controlButtons.startButton.clicked.connect(self._resume) - else: - self.controlButtons.pauseButton.setEnabled(False) - self.controlButtons.startButton.setEnabled(False) - if CueAction.FadeIn in cue.CueActions: - self.controlButtons.fadeInButton.clicked.connect(self._fadein) - else: - self.controlButtons.fadeInButton.setEnabled(False) - if CueAction.FadeOut in cue.CueActions: - self.controlButtons.fadeOutButton.clicked.connect(self._fadeout) - else: - self.controlButtons.fadeOutButton.setEnabled(False) - if CueAction.Interrupt in cue.CueActions: - self.controlButtons.interruptButton.clicked.connect(self._interrupt) - else: - self.controlButtons.interruptButton.setEnabled(False) + self.setup_control_buttons() self.timeDisplay = QLCDNumber(self.gridLayoutWidget) self.timeDisplay.setStyleSheet("background-color: transparent") @@ -119,9 +98,42 @@ def __init__(self, cue, config, **kwargs): cue.fadeout_start.connect(self.enter_fadeout, Connection.QtQueued) cue.fadeout_end.connect(self.exit_fade, Connection.QtQueued) + if cue.is_fading_in(): + QTimer.singleShot(0, self.enter_fadein) + elif cue.is_fading_out(): + QTimer.singleShot(0, self.enter_fadeout) + def updateSize(self, width): self.resize(width, int(width / 3.75)) + def setup_control_buttons(self): + if CueAction.Stop in self.cue.CueActions: + self.controlButtons.stopButton.clicked.connect(self._stop) + else: + self.controlButtons.stopButton.setEnabled(False) + + if CueAction.Pause in self.cue.CueActions: + self.controlButtons.pauseButton.clicked.connect(self._pause) + self.controlButtons.startButton.clicked.connect(self._resume) + else: + self.controlButtons.pauseButton.setEnabled(False) + self.controlButtons.startButton.setEnabled(False) + + if CueAction.FadeIn in self.cue.CueActions: + self.controlButtons.fadeInButton.clicked.connect(self._fadein) + else: + self.controlButtons.fadeInButton.setEnabled(False) + + if CueAction.FadeOut in self.cue.CueActions: + self.controlButtons.fadeOutButton.clicked.connect(self._fadeout) + else: + self.controlButtons.fadeOutButton.setEnabled(False) + + if CueAction.Interrupt in self.cue.CueActions: + self.controlButtons.interruptButton.clicked.connect(self._interrupt) + else: + self.controlButtons.interruptButton.setEnabled(False) + def enter_fadein(self): p = self.timeDisplay.palette() p.setColor(p.Text, QColor(0, 255, 0)) From 594850fd4b8639c11f2bb1055c210f3c4e950164 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 3 Mar 2022 21:35:49 +0100 Subject: [PATCH 280/333] Formatting --- lisp/backend/audio_utils.py | 2 +- lisp/core/fade_functions.py | 4 ++-- lisp/main.py | 2 +- lisp/plugins/gst_backend/elements/volume.py | 4 +--- lisp/plugins/gst_backend/gst_waveform.py | 2 +- lisp/plugins/gst_backend/settings/jack_sink.py | 2 +- 6 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lisp/backend/audio_utils.py b/lisp/backend/audio_utils.py index 36c3bd8f8..6615e25a0 100644 --- a/lisp/backend/audio_utils.py +++ b/lisp/backend/audio_utils.py @@ -69,7 +69,7 @@ def slider_to_fader(value): elif value < 0.0: value = 0 - return 3.162_277_66 * (value ** 3.7) + return 3.162_277_66 * (value**3.7) def python_duration(path, sound_module): diff --git a/lisp/core/fade_functions.py b/lisp/core/fade_functions.py index b2bb973bc..19ff63870 100644 --- a/lisp/core/fade_functions.py +++ b/lisp/core/fade_functions.py @@ -36,7 +36,7 @@ def fade_linear(t, a, b): def fadein_quad(t, a, b): """Quadratic (t^2) fade in: accelerating from zero velocity.""" - return a * (t ** 2) + b + return a * (t**2) + b def fadeout_quad(t, a, b): @@ -50,7 +50,7 @@ def fade_inout_quad(t, a, b): """ t *= 2 if t < 1.0: - return 0.5 * a * (t ** 2) + b + return 0.5 * a * (t**2) + b else: t -= 1 return 0.5 * a * (1 - (t * (t - 2))) + b diff --git a/lisp/main.py b/lisp/main.py index c0a63e19f..07ea18699 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -96,7 +96,7 @@ def main(): # Create the file handler file_handler = RotatingFileHandler( os.path.join(app_dirs.user_log_dir, "lisp.log"), - maxBytes=10 * (2 ** 20), + maxBytes=10 * (2**20), backupCount=5, ) file_handler.setFormatter(default_formatter) diff --git a/lisp/plugins/gst_backend/elements/volume.py b/lisp/plugins/gst_backend/elements/volume.py index 4cbcbc708..5082e72f5 100644 --- a/lisp/plugins/gst_backend/elements/volume.py +++ b/lisp/plugins/gst_backend/elements/volume.py @@ -21,9 +21,7 @@ from lisp.backend.media_element import ElementType, MediaType from lisp.plugins.gst_backend.gi_repository import Gst, GstController -from lisp.plugins.gst_backend.gst_element import ( - GstMediaElement, -) +from lisp.plugins.gst_backend.gst_element import GstMediaElement from lisp.plugins.gst_backend.gst_properties import ( GstProperty, GstLiveProperty, diff --git a/lisp/plugins/gst_backend/gst_waveform.py b/lisp/plugins/gst_backend/gst_waveform.py index cdbcbb3ce..f8d1af427 100644 --- a/lisp/plugins/gst_backend/gst_waveform.py +++ b/lisp/plugins/gst_backend/gst_waveform.py @@ -61,7 +61,7 @@ def _load_waveform(self): except GLib.GError: logger.warning( f'Cannot generate waveform for "{self._uri.unquoted_uri}"', - exc_info=True + exc_info=True, ) return True diff --git a/lisp/plugins/gst_backend/settings/jack_sink.py b/lisp/plugins/gst_backend/settings/jack_sink.py index 51d66e738..f0c7e8ab7 100644 --- a/lisp/plugins/gst_backend/settings/jack_sink.py +++ b/lisp/plugins/gst_backend/settings/jack_sink.py @@ -293,7 +293,7 @@ def update_graph(self): try: colon_index = port.name.index(":") client_name = port.name[:colon_index] - port_display_name = port.name[colon_index+1:] + port_display_name = port.name[colon_index + 1 :] if client_name not in clients: clients[client_name] = ClientItem(client_name) From c64fdc9254d9554a4c525f826f5f075e470996fb Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 6 Apr 2022 12:00:34 +0200 Subject: [PATCH 281/333] Fix type conversion --- lisp/ui/logging/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/ui/logging/dialog.py b/lisp/ui/logging/dialog.py index adf57a285..4482d8548 100644 --- a/lisp/ui/logging/dialog.py +++ b/lisp/ui/logging/dialog.py @@ -122,7 +122,7 @@ def updateDisplayed(self): self.detailsText.setText(details) - width = ( + width = int( self.detailsText.document().idealWidth() + self.detailsText.contentsMargins().left() * 2 ) From 1bb336a46a225f9f2874c5628ef379c833942263 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 6 Apr 2022 14:51:35 +0200 Subject: [PATCH 282/333] Flatpak: Cleanup jack2 files, add permissions to allow pipewire-jack to work. Jack2 is removed since "libajack" is already present in the runtime (pipewire-jack), also, the "standard" implementation doesn't work with flatpak. --- scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json b/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json index 5466d85ac..632362189 100644 --- a/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json +++ b/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json @@ -24,8 +24,8 @@ "name": "Jack", "buildsystem": "simple", "build-commands": [ - "./waf configure --prefix='/app/'", - "./waf", + "./waf configure --prefix=$FLATPAK_DEST", + "./waf build -j $FLATPAK_BUILDER_N_JOBS", "./waf install" ], "sources": [ @@ -37,7 +37,7 @@ } ], "cleanup": [ - "/bin" + "*" ] }, { From 8150a489e154229477519b9076324341f18175e9 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 13 Apr 2022 12:58:01 +0200 Subject: [PATCH 283/333] Flatpak: Add permissions to allow pipewire-jack to work. --- scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json b/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json index 632362189..686c7d22e 100644 --- a/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json +++ b/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json @@ -10,6 +10,8 @@ "--socket=x11", "--socket=wayland", "--socket=pulseaudio", + "--system-talk-name=org.freedesktop.RealtimeKit1", + "--filesystem=xdg-run/pipewire-0", "--filesystem=home", "--device=all" ], From 8549f3ee84d3f32e98d85fc1bffdc1530bebb44d Mon Sep 17 00:00:00 2001 From: s0600204 <2053873+s0600204@users.noreply.github.com> Date: Sat, 28 May 2022 13:53:22 +0100 Subject: [PATCH 284/333] Update window title on file creation and load (#243) --- lisp/ui/mainwindow.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index 1bc4fab93..bd4bfab3d 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -330,6 +330,7 @@ def __beforeSessionFinalize(self): def __sessionCreated(self): self._app.session.layout.view.show() self.centralWidget().layout().addWidget(self._app.session.layout.view) + self.updateWindowTitle() def __simpleCueInsert(self, cueClass): try: @@ -389,6 +390,8 @@ def __openSession(self): if path is not None: self.open_session.emit(path) + self.updateWindowTitle() + def __newSession(self): if self.__checkSessionSaved(): self.new_session.emit() From 7eb4ed5af9376ca4051babaf39e5d25c41b229e8 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 28 May 2022 16:25:21 +0200 Subject: [PATCH 285/333] Update window name also when the file is opened by the "layout-select" dialog --- lisp/application.py | 5 ++++- lisp/ui/mainwindow.py | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lisp/application.py b/lisp/application.py index db8a109ef..d4d95520b 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -52,6 +52,7 @@ class Application(metaclass=Singleton): def __init__(self, app_conf=DummyConfiguration()): self.session_created = Signal() + self.session_loaded = Signal() self.session_before_finalize = Signal() self.__conf = app_conf @@ -159,7 +160,7 @@ def __new_session(self, layout): self.__delete_session() self.__session = Session(layout(application=self)) - self.session_created.emit(self.__session) + self.session_created.emit(self.session) def __delete_session(self): if self.__session is not None: @@ -233,6 +234,8 @@ def __load_from_file(self, session_file): ) self.commands_stack.set_saved() + + self.session_loaded.emit(self.session) except Exception: logger.exception( translate( diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index bd4bfab3d..ad40b3064 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -76,6 +76,7 @@ def __init__(self, app, title="Linux Show Player", **kwargs): # Session change self._app.session_created.connect(self.__sessionCreated) self._app.session_before_finalize.connect(self.__beforeSessionFinalize) + self._app.session_loaded.connect(self.updateWindowTitle) # Changes self._app.commands_stack.done.connect(self.updateWindowTitle) @@ -390,8 +391,6 @@ def __openSession(self): if path is not None: self.open_session.emit(path) - self.updateWindowTitle() - def __newSession(self): if self.__checkSessionSaved(): self.new_session.emit() From ef1eb77d54b6af84e87b6a9931fb41249134c36e Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 15 Jun 2022 16:57:25 +0200 Subject: [PATCH 286/333] Increase python minimum version to 3.7 Update: remove unused import --- lisp/plugins/list_layout/list_widgets.py | 1 - poetry.lock | 310 ++++++++++++----------- pyproject.toml | 4 +- 3 files changed, 158 insertions(+), 157 deletions(-) diff --git a/lisp/plugins/list_layout/list_widgets.py b/lisp/plugins/list_layout/list_widgets.py index de9e05a33..191fcbd6a 100644 --- a/lisp/plugins/list_layout/list_widgets.py +++ b/lisp/plugins/list_layout/list_widgets.py @@ -14,7 +14,6 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -import time from PyQt5.QtCore import QRect, Qt, QSize from PyQt5.QtGui import ( diff --git a/poetry.lock b/poetry.lock index a212ac8f1..13b610a0d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8,11 +8,11 @@ python-versions = "*" [[package]] name = "certifi" -version = "2021.10.8" +version = "2022.5.18.1" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "cffi" @@ -27,7 +27,7 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "2.0.9" +version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -38,7 +38,7 @@ unicode_backport = ["unicodedata2"] [[package]] name = "cython" -version = "0.29.25" +version = "0.29.30" description = "The Cython compiler for writing C extensions for the Python language." category = "main" optional = false @@ -46,15 +46,15 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "falcon" -version = "3.0.1" -description = "An unladen web framework for building APIs and app backends." +version = "3.1.0" +description = "The ultra-reliable, fast ASGI+WSGI framework for building data plane APIs at scale." category = "main" optional = false python-versions = ">=3.5" [[package]] name = "humanize" -version = "3.13.1" +version = "3.14.0" description = "Python humanize utilities" category = "main" optional = false @@ -76,28 +76,28 @@ python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "4.8.2" +version = "4.11.4" description = "Read metadata from Python packages" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] [[package]] name = "jack-client" -version = "0.5.3" +version = "0.5.4" description = "JACK Audio Connection Kit (JACK) Client for Python" category = "main" optional = false -python-versions = ">=3" +python-versions = ">=3.7" [package.dependencies] CFFI = ">=1.0" @@ -145,11 +145,11 @@ resolved_reference = "6919608781a273decd1937accf36c79ab4ae1f0e" [[package]] name = "pycairo" -version = "1.20.1" +version = "1.21.0" description = "Python interface for cairo" category = "main" optional = false -python-versions = ">=3.6, <4" +python-versions = ">=3.7" [[package]] name = "pycparser" @@ -161,7 +161,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygobject" -version = "3.42.0" +version = "3.42.1" description = "Python bindings for GObject Introspection" category = "main" optional = false @@ -180,14 +180,14 @@ python-versions = "*" [[package]] name = "pyparsing" -version = "3.0.6" -description = "Python parsing module" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.8" [package.extras] -diagrams = ["jinja2", "railroad-diagrams"] +diagrams = ["railroad-diagrams", "jinja2"] [[package]] name = "pyqt5" @@ -211,11 +211,11 @@ python-versions = "*" [[package]] name = "pyqt5-sip" -version = "12.9.0" +version = "12.10.1" description = "The sip module support for PyQt5" category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" [[package]] name = "python-rtmidi" @@ -227,20 +227,20 @@ python-versions = "*" [[package]] name = "requests" -version = "2.26.0" +version = "2.28.0" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7, <4" [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +charset-normalizer = ">=2.0.0,<2.1.0" +idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" [package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] @@ -261,41 +261,41 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "typing-extensions" -version = "4.0.1" -description = "Backported and Experimental Type Hints for Python 3.6+" +version = "4.2.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "urllib3" -version = "1.26.7" +version = "1.26.9" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "zipp" -version = "3.6.0" +version = "3.8.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" -python-versions = "^3.6" -content-hash = "17b73be105a878a5d216c1039dd25c0346dabfaf1503c9eaf1ffaa9ebade1fc3" +python-versions = "^3.7" +content-hash = "16fda493a8a0716dea08d2cb30027165b456c1c43be12677cdb0fa81dd2ad947" [metadata.files] appdirs = [ @@ -303,8 +303,8 @@ appdirs = [ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] certifi = [ - {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, - {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, + {file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"}, + {file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"}, ] cffi = [ {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, @@ -359,91 +359,99 @@ cffi = [ {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.9.tar.gz", hash = "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"}, - {file = "charset_normalizer-2.0.9-py3-none-any.whl", hash = "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721"}, + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] cython = [ - {file = "Cython-0.29.25-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:08a502fe08756070276d841c830cfc37254a2383d0a5bea736ffb78eff613c88"}, - {file = "Cython-0.29.25-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c204cb2d005a426c5c83309fd7edea335ff5c514ffa6dc72ddac92cfde170b69"}, - {file = "Cython-0.29.25-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3913f6a50409ab36a5b8edbb4c3e4d441027f43150d8335e5118d34ef04c745c"}, - {file = "Cython-0.29.25-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:44cc749f288423182504a8fc8734070a369bf576734b9f0fafff40cd6b6e1b3e"}, - {file = "Cython-0.29.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4f7b135cba0d2509890e1dcff2005585bc3d51c9f17564b70d8bc82dc7ec3a5e"}, - {file = "Cython-0.29.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:eb64ec369eba2207fbe618650d78d9af0455e0c1abb301ec024fa9f3e17a15cc"}, - {file = "Cython-0.29.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:6efb798993260532879f683dc8ce9e30fd1ec86f02c926f1238a8e6a64576321"}, - {file = "Cython-0.29.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b8fc9c78262b140364ce1b28ac40ff505a47ac3fd4f86311d461df04a28b3f23"}, - {file = "Cython-0.29.25-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3497e366ffed67454162d31bf4bd2ac3aa183dfac089eb4124966c9f98bd9c05"}, - {file = "Cython-0.29.25-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b5b3e876e617fe2cf466d02198b76924dcda3cc162a1043226a9c181b9a662a6"}, - {file = "Cython-0.29.25-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:aa9e1fe5ee0a4f9d2430c1e0665f40b48f4b511150ca02f69e9bb49dc48d4e0e"}, - {file = "Cython-0.29.25-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8726456c7e376410b3c631427da0a4affe1e481424436d1e3f1888cc3c0f8d2e"}, - {file = "Cython-0.29.25-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:191978e5839ca425eb78f0f60a84ad5db7a07b97e8076f9853d0d12c3ccec5d4"}, - {file = "Cython-0.29.25-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a206a1f8ea11314e02dc01bf24f397b8f1b413bbcc0e031396caa1a126b060c2"}, - {file = "Cython-0.29.25-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e6fa0a7cec9461c5ca687f3c4bb59cf2565afb76c60303b2dc8b280c6e112810"}, - {file = "Cython-0.29.25-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:f95433e6963164de372fc1ef01574d7419d96ce45274f296299267d874b90800"}, - {file = "Cython-0.29.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4dc3d230849d61844e6b5737ee624c896f51e98c8a5d13f965b02a7e735230be"}, - {file = "Cython-0.29.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:3379e67113e92fef490a88eca685b07b711bb4db1ddce66af9e460673a5335cc"}, - {file = "Cython-0.29.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:0e9e28eb6bb19f5e25f4bf5c8f8ea7db3bc4910309fab2305e5c9c5a5223db77"}, - {file = "Cython-0.29.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:79d2f84a6d87d45ef580c0441b5394c4f29344e05126a8e2fb4ba4144425f3b0"}, - {file = "Cython-0.29.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6759b73a9a1013cbdac71ebefa284aa50617b5b32957a54eedaa22ac2f6d48de"}, - {file = "Cython-0.29.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:07d5b8ce032110822dad2eb09950a98b9e255d14c2daf094be32d663790b3365"}, - {file = "Cython-0.29.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5d0d97a5f661dccf2f9e14cf27fe9027f772d089fb92fdd3dd8a584d9b8a2916"}, - {file = "Cython-0.29.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:7b3f6e4cfcc103bccdcbc666f613d669ac378c8918629296cdf8191c0c2ec418"}, - {file = "Cython-0.29.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:e96857ab2dbd8a67852341001f1f2a1ef3f1939d82aea1337497a8f76a9d7f6c"}, - {file = "Cython-0.29.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ee99fab5191f403f33774fc92123291c002947338c2628b1ed42ed0017149dd"}, - {file = "Cython-0.29.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d288f25e8abb43b1cfa2fe3d69b2d6236cca3ff6163d090e26c4b1e8ea80dfbf"}, - {file = "Cython-0.29.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1c2f262f7d032ec0106534982609ae0148f86ba52fc747df64e645706af20926"}, - {file = "Cython-0.29.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:decd641167e97a3c1f973bf0bbb560d251809f6db8168c10edf94c0a1e5dec65"}, - {file = "Cython-0.29.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:800cbe944886320e4a4b623becb97960ae9d7d80f2d12980b83bcfb63ff47d5b"}, - {file = "Cython-0.29.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:3e94eb973f99c1963973a46dbd9e3974a03b8fe0af3de02dc5d65b4c6a6f9b3f"}, - {file = "Cython-0.29.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64394ec94d9a0e5002f77e67ee8ceed97f25b483b18ea6aab547f4d82ca32ef6"}, - {file = "Cython-0.29.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6f397256cfab2d0f0af42659fca3232c23f5a570b6c21ed66aaac22dd95da15"}, - {file = "Cython-0.29.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1825d6f2160188dfe1faa0099d30ed0e5ae56826627bf0de6dcb8dcbcf64c9bd"}, - {file = "Cython-0.29.25-py2.py3-none-any.whl", hash = "sha256:0cf7c3033349d10c5eb33ded1a78974f680e95c245a585c18a2046c67f8ed461"}, - {file = "Cython-0.29.25.tar.gz", hash = "sha256:a87cbe3756e7c464acf3e9420d8741e62d3b2eace0846cb39f664ad378aab284"}, + {file = "Cython-0.29.30-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e5cb144728a335d7a7fd0a61dff6abb7a9aeff9acd46d50b886b7d9a95bb7311"}, + {file = "Cython-0.29.30-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d52d5733dcb144deca8985f0a197c19cf71e6bd6bd9d8034f3f67b2dea68d12b"}, + {file = "Cython-0.29.30-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0cd6c932e945af15ae4ddcf8fdc0532bda48784c92ed0a53cf4fae897067ccd1"}, + {file = "Cython-0.29.30-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a30092c6e2d24255fbfe0525f9a750554f96a263ed986d12ac3c9f7d9a85a424"}, + {file = "Cython-0.29.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:abcaf99f90cddc0f53600613eaafc81d27c4ac0671f0df8bce5466d4e86d54a1"}, + {file = "Cython-0.29.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9826981308802c61a76f967875b31b7c683b7fc369eabaa6cbc22efeb12c90e8"}, + {file = "Cython-0.29.30-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:d166d9f853db436f5e10733a9bd615699ddb4238feadcbdf5ae50dc0b18b18f5"}, + {file = "Cython-0.29.30-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0b83a342a071c4f14e7410568e0c0bd95e2f20c0b32944e3a721649a1357fda4"}, + {file = "Cython-0.29.30-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:ffa8c09617833ff0824aa7926fa4fa9d2ec3929c67168e89105f276b7f36a63e"}, + {file = "Cython-0.29.30-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6b389a94b42909ff56d3491fde7c44802053a103701a7d210dcdd449a5b4f7b4"}, + {file = "Cython-0.29.30-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:7eff71c39b98078deaad1d1bdbf10864d234e2ab5d5257e980a6926a8523f697"}, + {file = "Cython-0.29.30-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8e08f18d249b9b65e272a5a60f3360a8922c4c149036b98fc821fe1afad5bdae"}, + {file = "Cython-0.29.30-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3993aafd68a7311ef94e00e44a137f6a50a69af0575ebcc8a0a074ad4152a2b2"}, + {file = "Cython-0.29.30-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:5c7cfd908efc77306ddd41ef07f5a7a352c9205ced5c1e00a0e5ece4391707c4"}, + {file = "Cython-0.29.30-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e605635a92ae862cb46d84d1d6883324518f9aaff4a71cede6d61df20b6a410c"}, + {file = "Cython-0.29.30-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:786ee7b0cdb508b6de64c0f1f9c74f207186dfafad1ef938f25b7494cc481a80"}, + {file = "Cython-0.29.30-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:1e078943bbde703ca08d43e719480eb8b187d9023cbd91798619f5b5e18d0d71"}, + {file = "Cython-0.29.30-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5183356c756b56c2df12d96300d602e47ffb89943c5a0bded66faca5d3da7be0"}, + {file = "Cython-0.29.30-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e36755e71fd20eceb410cc441b7f2586654c2edb013f4663842fdaf60b96c1ca"}, + {file = "Cython-0.29.30-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e29d3487f357108b711f2f29319811d92166643d29aec1b8e063aad46a346775"}, + {file = "Cython-0.29.30-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5a8a3709ad9343a1dc02b8ec9cf6bb284be248d2c64af85464d9c3525eec74a5"}, + {file = "Cython-0.29.30-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:b17639b6a155abaa61a89f6f1323fb57b138d0529911ca03978d594945d062ba"}, + {file = "Cython-0.29.30-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:9462e9cf284d9b1d2c5b53d62188e3c09cc5c7a0018ba349d99b73cf930238de"}, + {file = "Cython-0.29.30-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:58d2b734250c1093bc69c1c3a6f5736493b9f8b34eb765f0a28a4a09468c0b00"}, + {file = "Cython-0.29.30-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:28db751e2d8365b39664d9cb62dc1668688b8fcc5b954e9ca9d20e0b8e03d8b0"}, + {file = "Cython-0.29.30-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5f2dae7dd56860018d5fd5032a71f11fdc224020932b463d0511a1536f27df85"}, + {file = "Cython-0.29.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d0859a958e0155b6ae4dee04170ccfac2c3d613a7e3bee8749614530b9e3b4a4"}, + {file = "Cython-0.29.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d0f34b44078e3e0b2f1be2b99044619b37127128e7d55c54bbd2438adcaf31d3"}, + {file = "Cython-0.29.30-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:80a7255ad84620f53235c0720cdee2bc7431d9e3db7b3742823a606c329eb539"}, + {file = "Cython-0.29.30-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3d0239c7a22a0f3fb1deec75cab0078eba4dd17868aa992a54a178851e0c8684"}, + {file = "Cython-0.29.30-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c299c5b250ae9f81c38200441b6f1d023aeee9d8e7f61c04001c7437181ccb06"}, + {file = "Cython-0.29.30-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:019d330ac580b2ca4a457c464ac0b8c35009d820ef5d09f328d6e31a10e1ce89"}, + {file = "Cython-0.29.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:71fd1d910aced510c001936667fc7f2901c49b2ca7a2ad67358979c94a7f42ac"}, + {file = "Cython-0.29.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:60d370c33d56077d30e5f425026e58c2559e93b4784106f61581cf54071f6270"}, + {file = "Cython-0.29.30-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:20778297c8bcba201ca122a2f792a9899d6e64c68a92363dd7eb24306d54d7ce"}, + {file = "Cython-0.29.30-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9f1fe924c920b699af27aefebd722df4cfbb85206291623cd37d1a7ddfd57792"}, + {file = "Cython-0.29.30-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c79685dd4631a188e2385dc6a232896c7b67ea2e3e5f8b5555b4b743f475d6d7"}, + {file = "Cython-0.29.30-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:88c5e2f92f16cd999ddfc43d572639679e8a057587088e627e98118e46a803e6"}, + {file = "Cython-0.29.30-py2.py3-none-any.whl", hash = "sha256:acb72e0b42079862cf2f894964b41f261e941e75677e902c5f4304b3eb00af33"}, + {file = "Cython-0.29.30.tar.gz", hash = "sha256:2235b62da8fe6fa8b99422c8e583f2fb95e143867d337b5c75e4b9a1a865f9e3"}, ] falcon = [ - {file = "falcon-3.0.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:94fb4582212768ac425d023b7884e60d09a0bd4c5cd50ca8af0272af1cba5da6"}, - {file = "falcon-3.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:56b267fa2df7e0400a639cf40a994baac19170425b0b8bbad5a8a81e07f9717d"}, - {file = "falcon-3.0.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:085b30b09ff4bdb31fda0a83a65f427d8dd4b5b5b21058781c38aff9747b5991"}, - {file = "falcon-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65b1798026e3dbd2d323fa9b03f90e3827be4fe0d3c1f9e3ba3d4a7a001de566"}, - {file = "falcon-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1f70c6f086c53b0cc819a0725d3814ad62e105b62d4c4e2c46322f13e7910e7"}, - {file = "falcon-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ec7fc600ffee2377beeeeca32d8171ff305e9267bcd37bba5a7ce8af1e177f"}, - {file = "falcon-3.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a9d5be8902e977ac93aeebf2b8959e2c3d82783d7ea6a1fc80cef5352b83549b"}, - {file = "falcon-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:a95b6a373b8f6014b0bc7090b1de031c9d237007211ef55a19b60241cf728e61"}, - {file = "falcon-3.0.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:260645c13d477af434fc200ec67734efc41e620b3f6e0479e722897511166b46"}, - {file = "falcon-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:485ef504d196390ebc0974cefd3f5fab4ad8a3ede4e5a7c0a803f555bcd8da45"}, - {file = "falcon-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1280db58c2af48b1ba24e39674fb6d84389eff5c4772a327a5af606eeead272"}, - {file = "falcon-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff4672f3549b00b62e710d3169903d14e37726f04045a0563b56d9af3fba271d"}, - {file = "falcon-3.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1bdf8085877bd049f799a34680d42fa82e2b93dcf8320d092f7e75933d0afcee"}, - {file = "falcon-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:16f8735512af3f52473e3eda37e75bf697f6ced5afc3e9dc7110c430777823ab"}, - {file = "falcon-3.0.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:3710b051f54c158310b45b1432a993803cdccb3e167d3e89aa93076ff77d2673"}, - {file = "falcon-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa46751209af4f4882d3d60e430ea586e170bc03e1bd5b08cb16f6b96068febc"}, - {file = "falcon-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6afb13a80b6a4383a66093af7bb0e8e02433ca5ebc7516842a6a3f112c844ae"}, - {file = "falcon-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0212df91414c13c08a9cf4023488b2d47956712f712332f420bb0c7bdf39c6fa"}, - {file = "falcon-3.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0df4dee0ef89b4de5e2ba4402ac249942b09758a0decdc7a63d5edb3792c4c1c"}, - {file = "falcon-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:c9a3cf58f9f3c9769bff3b24037b150c9f6658df4c899d68fa433f5acdfdb004"}, - {file = "falcon-3.0.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:514dee9b6d66408e43fcef9aef2436004cd2e3163625f194dd064fce67269cce"}, - {file = "falcon-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce052e91b8325a76ddc2066e35bb032e0be4671cd824f027d1826c68a0fd09e3"}, - {file = "falcon-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6c7f9b2063a4c0ac2516df014c5299ae098579e83025d342f31fe1ef8e994d7"}, - {file = "falcon-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee78a9934f8143c5eef9bfe949044c7eab3fef80a51cbc67cf6cb6b34c5613ce"}, - {file = "falcon-3.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a70fc0f9f115e763effdf9fc6140a2b5df9f37bd2707f3b29e0a1357dbf53784"}, - {file = "falcon-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:c3abcd37545de531e7dada4113c88f01e86c7596c7c59300769d64ea7771a75e"}, - {file = "falcon-3.0.1.tar.gz", hash = "sha256:c41d84db325881a870e8b7129d5ecfd972fa4323cf77b7119a1d2a21966ee681"}, + {file = "falcon-3.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:9400871fa536bac799b790ff28d999c5282c2bd7652f37f448b96718ddb289ff"}, + {file = "falcon-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aee71134800f963db0b4bc086d9bf0ffc529b40e90e6a5f2cd08059d7c9ea317"}, + {file = "falcon-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46739d4cdbcfe179e720cb34ef7dc135d4ff7c70320c56a5dfdcb87abb1ed45"}, + {file = "falcon-3.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:84b7e09b2c66a1942a8bc049d8f236bdb4f74d2314ad7551534a8d79ddd6a54d"}, + {file = "falcon-3.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fe77af775a2aba4019f01f54364abd1a5df2c820988ddf076416b1a7683c1b8"}, + {file = "falcon-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:aa2cf2aa3c0a3323d7a392af1edee1436fbe71c2b3d37699f32b6f45cfad8f75"}, + {file = "falcon-3.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:d31407f1b327b9d97166523bdecbec8be65a08d52f5dfb1a532f354118cc2a9e"}, + {file = "falcon-3.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccd588f943e755f4ab91261bbf80d0b25a5f43c3f6bda2ed8eae0f2e4fd3c3dd"}, + {file = "falcon-3.1.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2ef45e5d71de2417dbb36be0ed0fadb08d87fb059e8c3f86223f5240cacf547"}, + {file = "falcon-3.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bc720201105e24271c04e35a3a02aa1f7e9d92f1ddd82a846696b8ae7de89468"}, + {file = "falcon-3.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d714f631dae76e05cf91b0a7d509b8bf8bb86f371b3510a6fbf696469fcc2577"}, + {file = "falcon-3.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:19b240b748a7d9096cacdc508b824f1765937369107ff4e79b23618e0632818b"}, + {file = "falcon-3.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f20ab24083d499841f7187d2baa82c6f9baa90dbc1d322cc7fb83ef51d681f80"}, + {file = "falcon-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:783dcaee18a53c955636fc7fad5dd710899b2100b5f798af31e23961315c9bb5"}, + {file = "falcon-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b6a675685790a3d9e3c849320b3aea94d30f228b0f15d55fc32d6f9f6925c0f"}, + {file = "falcon-3.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c60de4a62fedaa9011a025a70a665a408c8e6c16cdc2e3baea05c23e7c7df202"}, + {file = "falcon-3.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ffbd6eef0283b1909895d3d3b36b1714a37bb1a6e9ecf6c5af60e0adbb322bd"}, + {file = "falcon-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:61686aed654fd492ceab674421a6a1d31599599d86ce710c6c650749f72522e0"}, + {file = "falcon-3.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:91dcb3adccb9d0faacfc3937af624ca14541aec37a7478d333710a91e8e438d4"}, + {file = "falcon-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cbb9b008c9e9f8b07e14b848791be4db31c359ef96f045332bbec7572250f71"}, + {file = "falcon-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c78d13ba42c50d716177f442a590c8946f4fe7c4ba07838679417339a9a80920"}, + {file = "falcon-3.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cac70a3e021e34453eeff968673e4081ac2a878b99fd9fb1260584cdf67e7890"}, + {file = "falcon-3.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d01a173610d216880f0e722abbe1cca51d5041f9e15db61bd79e95242dd617d"}, + {file = "falcon-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:eea1340aa264f5f0fd9023fb7b6cd41da1305af14e3e2c9d716446ad38ea2690"}, + {file = "falcon-3.1.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:94ce8ee49795ab18442c761c903353737ab271a506e992deadc5a7df669e804f"}, + {file = "falcon-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0044bd2c161f2b5c27ca5a0068e40bbeee560eb957a6ba01f9c1bb7439f6ab19"}, + {file = "falcon-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff65ffefa629bc72dc868cdca9dfa3ca5d1e5f01b02560b7094df2a0c5b890d1"}, + {file = "falcon-3.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:09dc5f10878565708dc9e58e62e9f2dfee5162a1ea992cb62582ffdacf543ec2"}, + {file = "falcon-3.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba5b27a401db6c025bb6885512b5cc46603d6a020e289902199b0c77c80777e6"}, + {file = "falcon-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:455a6c1a46af8505b0427a1ab85924aed47c462afe4e7cb7ae58c9fd6c5cc06f"}, + {file = "falcon-3.1.0.tar.gz", hash = "sha256:f2760bd18c16393a6fb5e55f371f67921edb72febe693a82b3c5e82195d087b7"}, ] humanize = [ - {file = "humanize-3.13.1-py3-none-any.whl", hash = "sha256:a6f7cc1597db69a4e571ad5e19b4da07ee871da5a9de2b233dbfab02d98e9754"}, - {file = "humanize-3.13.1.tar.gz", hash = "sha256:12f113f2e369dac7f35d3823f49262934f4a22a53a6d3d4c86b736f50db88c7b"}, + {file = "humanize-3.14.0-py3-none-any.whl", hash = "sha256:32bcf712ac98ff5e73627a9d31e1ba5650619008d6d13543b5d53b48e8ab8d43"}, + {file = "humanize-3.14.0.tar.gz", hash = "sha256:60dd8c952b1df1ad83f0903844dec50a34ba7a04eea22a6b14204ffb62dbb0a4"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.8.2-py3-none-any.whl", hash = "sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100"}, - {file = "importlib_metadata-4.8.2.tar.gz", hash = "sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb"}, + {file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"}, + {file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"}, ] jack-client = [ - {file = "JACK-Client-0.5.3.tar.gz", hash = "sha256:31cbdcc90cd303997a3ea540f352d89b23c2f422cbf7c197292a1b8e1ca21aca"}, - {file = "JACK_Client-0.5.3-py3-none-any.whl", hash = "sha256:0214d9366e0a65bf0a99866990aae5a443afb3c283006d236d8cebcb769a59b8"}, + {file = "JACK-Client-0.5.4.tar.gz", hash = "sha256:dd4a293e3a6e9bde9972569b9bc4630a5fcd4f80756cc590de572cc744e5a848"}, + {file = "JACK_Client-0.5.4-py3-none-any.whl", hash = "sha256:52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73"}, ] mido = [ {file = "mido-1.2.10-py2.py3-none-any.whl", hash = "sha256:0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e"}, @@ -455,31 +463,29 @@ packaging = [ ] pyalsa = [] pycairo = [ - {file = "pycairo-1.20.1-cp310-cp310-win32.whl", hash = "sha256:736ffc618e851601e861a630293e5c910ef016b83b2d035a336f83a367bf56ab"}, - {file = "pycairo-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:261c69850d4b2ec03346c9745bad2a835bb8124e4c6961b8ceac503d744eb3b3"}, - {file = "pycairo-1.20.1-cp36-cp36m-win32.whl", hash = "sha256:6db823a18e7be1eb2a29c28961f2f01e84d3b449f06be7338d05ac8f90592cd5"}, - {file = "pycairo-1.20.1-cp36-cp36m-win_amd64.whl", hash = "sha256:5525da2d8de912750dd157752aa96f1f0a42a437c5625e85b14c936b5c6305ae"}, - {file = "pycairo-1.20.1-cp37-cp37m-win32.whl", hash = "sha256:c8c2bb933974d91c5d19e54b846d964de177e7bf33433bf34ac34c85f9b30e94"}, - {file = "pycairo-1.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9a32e4a3574a104aa876c35d5e71485dfd6986b18d045534c6ec510c44d5d6a7"}, - {file = "pycairo-1.20.1-cp38-cp38-win32.whl", hash = "sha256:0d7a6754d410d911a46f00396bee4be96500ccd3d178e7e98aef1140e3dd67ae"}, - {file = "pycairo-1.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:b605151cdd23cedb31855b8666371b6e26b80f02753a52c8b8023a916b1df812"}, - {file = "pycairo-1.20.1-cp39-cp39-win32.whl", hash = "sha256:e800486b51fffeb11ed867b4f2220d446e2a60a81a73b7c377123e0cbb72f49d"}, - {file = "pycairo-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:f123d3818e30b77b7209d70a6dcfd5b4e34885f9fa539d92dd7ff3e4e2037213"}, - {file = "pycairo-1.20.1.tar.gz", hash = "sha256:1ee72b035b21a475e1ed648e26541b04e5d7e753d75ca79de8c583b25785531b"}, + {file = "pycairo-1.21.0-cp310-cp310-win32.whl", hash = "sha256:44a2ecf34968de07b3b9dfdcdbccbd25aa3cab267200f234f84e81481a73bbf6"}, + {file = "pycairo-1.21.0-cp310-cp310-win_amd64.whl", hash = "sha256:f63c153a9ea3d21aff85e2caeee4b0c5d566b2368b4ed64826020d12953d76a4"}, + {file = "pycairo-1.21.0-cp37-cp37m-win32.whl", hash = "sha256:70936b19f967fa3cb3cd200c2608911227fa5d09dae21c166f64bc15e714ee41"}, + {file = "pycairo-1.21.0-cp37-cp37m-win_amd64.whl", hash = "sha256:31e1c4850db03201d33929cbe1905ce1b33202683ebda7bb0d4dba489115066b"}, + {file = "pycairo-1.21.0-cp38-cp38-win32.whl", hash = "sha256:dace6b356c476de27f8e1522428ac21a799c225703f746e2957d441f885dcb6c"}, + {file = "pycairo-1.21.0-cp38-cp38-win_amd64.whl", hash = "sha256:4357f20a6b1de8f1e8072a74ff68ab4c9a0ae698cd9f5c0f2b2cdd9b28b635f6"}, + {file = "pycairo-1.21.0-cp39-cp39-win32.whl", hash = "sha256:6d37375aab9f2bb6136f076c19815d72108383baae89fbc0d6cb8e5092217d02"}, + {file = "pycairo-1.21.0-cp39-cp39-win_amd64.whl", hash = "sha256:26b72b813c6f9d495f71057eab89c13e70a21c92360e9265abc049e0a931fa39"}, + {file = "pycairo-1.21.0.tar.gz", hash = "sha256:251907f18a552df938aa3386657ff4b5a4937dde70e11aa042bc297957f4b74b"}, ] pycparser = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] pygobject = [ - {file = "PyGObject-3.42.0.tar.gz", hash = "sha256:b9803991ec0b0b4175e81fee0ad46090fa7af438fe169348a9b18ae53447afcd"}, + {file = "PyGObject-3.42.1.tar.gz", hash = "sha256:80d6a3ad1630e9d1edf31b9e9fad9a894c57e18545a3c95ef0044ac4042b8620"}, ] pyliblo = [ {file = "pyliblo-0.10.0.tar.gz", hash = "sha256:fc67f1950b827272b00f9f0dc4ed7113c0ccef0c1c09e9976dead40ebbf1798f"}, ] pyparsing = [ - {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, - {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"}, + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] pyqt5 = [ {file = "PyQt5-5.15.6-cp36-abi3-macosx_10_13_x86_64.whl", hash = "sha256:33ced1c876f6a26e7899615a5a4efef2167c263488837c7beed023a64cebd051"}, @@ -495,27 +501,23 @@ pyqt5-qt5 = [ {file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"}, ] pyqt5-sip = [ - {file = "PyQt5_sip-12.9.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6d5bca2fc222d58e8093ee8a81a6e3437067bb22bc3f86d06ec8be721e15e90a"}, - {file = "PyQt5_sip-12.9.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:d59af63120d1475b2bf94fe8062610720a9be1e8940ea146c7f42bb449d49067"}, - {file = "PyQt5_sip-12.9.0-cp310-cp310-win32.whl", hash = "sha256:0fc9aefacf502696710b36cdc9fa2a61487f55ee883dbcf2c2a6477e261546f7"}, - {file = "PyQt5_sip-12.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:485972daff2fb0311013f471998f8ec8262ea381bded244f9d14edaad5f54271"}, - {file = "PyQt5_sip-12.9.0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:d85002238b5180bce4b245c13d6face848faa1a7a9e5c6e292025004f2fd619a"}, - {file = "PyQt5_sip-12.9.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:83c3220b1ca36eb8623ba2eb3766637b19eb0ce9f42336ad8253656d32750c0a"}, - {file = "PyQt5_sip-12.9.0-cp36-cp36m-win32.whl", hash = "sha256:d8b2bdff7bbf45bc975c113a03b14fd669dc0c73e1327f02706666a7dd51a197"}, - {file = "PyQt5_sip-12.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:69a3ad4259172e2b1aa9060de211efac39ddd734a517b1924d9c6c0cc4f55f96"}, - {file = "PyQt5_sip-12.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:42274a501ab4806d2c31659170db14c282b8313d2255458064666d9e70d96206"}, - {file = "PyQt5_sip-12.9.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6a8701892a01a5a2a4720872361197cc80fdd5f49c8482d488ddf38c9c84f055"}, - {file = "PyQt5_sip-12.9.0-cp37-cp37m-win32.whl", hash = "sha256:ac57d796c78117eb39edd1d1d1aea90354651efac9d3590aac67fa4983f99f1f"}, - {file = "PyQt5_sip-12.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4347bd81d30c8e3181e553b3734f91658cfbdd8f1a19f254777f906870974e6d"}, - {file = "PyQt5_sip-12.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c446971c360a0a1030282a69375a08c78e8a61d568bfd6dab3dcc5cf8817f644"}, - {file = "PyQt5_sip-12.9.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fc43f2d7c438517ee33e929e8ae77132749c15909afab6aeece5fcf4147ffdb5"}, - {file = "PyQt5_sip-12.9.0-cp38-cp38-win32.whl", hash = "sha256:055581c6fed44ba4302b70eeb82e979ff70400037358908f251cd85cbb3dbd93"}, - {file = "PyQt5_sip-12.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:c5216403d4d8d857ec4a61f631d3945e44fa248aa2415e9ee9369ab7c8a4d0c7"}, - {file = "PyQt5_sip-12.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a25b9843c7da6a1608f310879c38e6434331aab1dc2fe6cb65c14f1ecf33780e"}, - {file = "PyQt5_sip-12.9.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:dd05c768c2b55ffe56a9d49ce6cc77cdf3d53dbfad935258a9e347cbfd9a5850"}, - {file = "PyQt5_sip-12.9.0-cp39-cp39-win32.whl", hash = "sha256:4f8e05fe01d54275877c59018d8e82dcdd0bc5696053a8b830eecea3ce806121"}, - {file = "PyQt5_sip-12.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:b09f4cd36a4831229fb77c424d89635fa937d97765ec90685e2f257e56a2685a"}, - {file = "PyQt5_sip-12.9.0.tar.gz", hash = "sha256:d3e4489d7c2b0ece9d203ae66e573939f7f60d4d29e089c9f11daa17cfeaae32"}, + {file = "PyQt5_sip-12.10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b2852df169f349e37999fc425f2d0abd004d09219354a55d3b4c6dd574b3fe84"}, + {file = "PyQt5_sip-12.10.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:2378bda30ee2b7a568086952658ba8daad8f0f71fc335f696423ee9b3b4028db"}, + {file = "PyQt5_sip-12.10.1-cp310-cp310-win32.whl", hash = "sha256:e70c961eb982cfc866a988cfe4fab16741d480d885203e04b539ecf32b53c6b7"}, + {file = "PyQt5_sip-12.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:0ba8c0751b46561542b5a6bb1b703faad10a205d9375ebc607b1c9f858bfe5b1"}, + {file = "PyQt5_sip-12.10.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:023de82b23a7e97369588dd6e411dd2bfbedbbcfb4c7f9c2ecaa9e29fc46a81e"}, + {file = "PyQt5_sip-12.10.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5e8472a959ec85f94d4810326505076b55eb4d8b2e08dbd5d87bd1907c59680c"}, + {file = "PyQt5_sip-12.10.1-cp37-cp37m-win32.whl", hash = "sha256:3a92bd103dad59c2bfbb1062420c2e7bcf134dfce800f6a8c9964c99a590162d"}, + {file = "PyQt5_sip-12.10.1-cp37-cp37m-win_amd64.whl", hash = "sha256:dda92bfaee40fd612c6b1e2614570d6acc8ec63032c6fd45d5e66badd18d387c"}, + {file = "PyQt5_sip-12.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d31f6f05777c8ee0828daeb5cb4a15d4124c63d94cacb07faa2dbc79f72209ea"}, + {file = "PyQt5_sip-12.10.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ba89547fc54403bbdcb7ee6066098d02d18d813f29b7de2434043a357bb79f6b"}, + {file = "PyQt5_sip-12.10.1-cp38-cp38-win32.whl", hash = "sha256:90f8cde3b446799800bc7c2d89a4fef5d8316d9f4c2f06daf4f6976d1c68fdfa"}, + {file = "PyQt5_sip-12.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:a83c7ba53e04e05cf1bcd18639080dd44196cf248773f34b1e9b831c11a1e198"}, + {file = "PyQt5_sip-12.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:087a7c0ebff00bca73788588b61b50063e17fcdb26977317d1785cb241a66a56"}, + {file = "PyQt5_sip-12.10.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:8024347c865a7f0c6581b460cc18dbea1c401733300bc3cb76f74c9432a2b0ae"}, + {file = "PyQt5_sip-12.10.1-cp39-cp39-win32.whl", hash = "sha256:a345bed4112d44705340e4033a2abc66d8dbd4afd62f3a9aa69253f15718df53"}, + {file = "PyQt5_sip-12.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:8504991216ff94ca1eaba39a6e6497e6ed860f1fc659f6c14ebfccf43733c02f"}, + {file = "PyQt5_sip-12.10.1.tar.gz", hash = "sha256:97e008795c453488f51a5c97dbff29cda7841afb1ca842c9e819d8e6cc0ae724"}, ] python-rtmidi = [ {file = "python-rtmidi-1.4.9.tar.gz", hash = "sha256:bfeb4ed99d0cccf6fa2837566907652ded7adc1c03b69f2160c9de4082301302"}, @@ -533,8 +535,8 @@ python-rtmidi = [ {file = "python_rtmidi-1.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:54d40cb794a6b079105bfb923ab0b4d5f041e9eef5dc5cce25480e4c3e3fc2f6"}, ] requests = [ - {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, - {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, + {file = "requests-2.28.0-py3-none-any.whl", hash = "sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f"}, + {file = "requests-2.28.0.tar.gz", hash = "sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b"}, ] sortedcontainers = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, @@ -545,14 +547,14 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] typing-extensions = [ - {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, - {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, + {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, + {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, ] urllib3 = [ - {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, - {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, + {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, + {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] zipp = [ - {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, - {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, + {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, + {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, ] diff --git a/pyproject.toml b/pyproject.toml index 3df26835a..2a057b70f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.black] line-length = 80 -target-version = ['py36'] +target-version = ['py37'] exclude = ''' /( \.git @@ -31,7 +31,7 @@ exclude = [ ] [tool.poetry.dependencies] -python = "^3.6" +python = "^3.7" appdirs = "^1.4.1" cython = "^0.29" falcon = "^3.0" From 7739cff633f8ce7d28a04805ae15d81eee287600 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 25 Jun 2022 14:58:12 +0200 Subject: [PATCH 287/333] Fix locale/language setup Add: debug message when a configuration file is loaded. --- lisp/core/configuration.py | 6 ++++++ lisp/main.py | 12 +++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index 474624946..cafdf7bda 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -211,6 +211,12 @@ def read(self): self._check_file() self._root = self._read_json(self.user_path) + logger.debug( + translate( + "ConfigurationDebug", "Configuration read from {}" + ).format(self.user_path) + ) + def write(self): with open(self.user_path, "w") as f: json.dump(self._root, f, indent=True) diff --git a/lisp/main.py b/lisp/main.py index 07ea18699..c0f74d82a 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -111,10 +111,16 @@ def main(): qt_app.setQuitOnLastWindowClosed(True) # Get/Set the locale - locale_name = args.locale if args.locale else app_conf["locale"] - QLocale().setDefault(QLocale(locale_name)) + if args.locale: + qt_locale = QLocale(args.locale) + elif app_conf["locale"]: + qt_locale = QLocale(app_conf["locale"]) + else: + qt_locale = QLocale() + + QLocale.setDefault(qt_locale) logging.info( - f'Using "{QLocale().name()}" locale -> {QLocale().uiLanguages()}' + f'Using "{qt_locale.name()}" locale -> {qt_locale.uiLanguages()}' ) # Qt platform translation From 046a437caf3af72269998aae46f8de61d15bd544 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 1 Oct 2022 15:02:55 +0200 Subject: [PATCH 288/333] Fix: Use a pip-installable version of pyliblo --- poetry.lock | 368 ++++++++++++++++++++++++++----------------------- pyproject.toml | 5 +- 2 files changed, 198 insertions(+), 175 deletions(-) diff --git a/poetry.lock b/poetry.lock index 13b610a0d..10ee929e6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8,7 +8,7 @@ python-versions = "*" [[package]] name = "certifi" -version = "2022.5.18.1" +version = "2022.9.24" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -16,7 +16,7 @@ python-versions = ">=3.6" [[package]] name = "cffi" -version = "1.15.0" +version = "1.15.1" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false @@ -27,18 +27,18 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "2.0.12" +version = "2.1.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=3.5.0" +python-versions = ">=3.6.0" [package.extras] unicode_backport = ["unicodedata2"] [[package]] -name = "cython" -version = "0.29.30" +name = "Cython" +version = "0.29.32" description = "The Cython compiler for writing C extensions for the Python language." category = "main" optional = false @@ -68,7 +68,7 @@ tests = ["freezegun", "pytest", "pytest-cov"] [[package]] name = "idna" -version = "3.3" +version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false @@ -76,7 +76,7 @@ python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "4.11.4" +version = "4.12.0" description = "Read metadata from Python packages" category = "main" optional = false @@ -87,12 +87,12 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] -name = "jack-client" +name = "JACK-Client" version = "0.5.4" description = "JACK Audio Connection Kit (JACK) Client for Python" category = "main" @@ -103,7 +103,7 @@ python-versions = ">=3.7" CFFI = ">=1.0" [package.extras] -numpy = ["numpy"] +numpy = ["NumPy"] [[package]] name = "mido" @@ -160,8 +160,8 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] -name = "pygobject" -version = "3.42.1" +name = "PyGObject" +version = "3.42.2" description = "Python bindings for GObject Introspection" category = "main" optional = false @@ -177,6 +177,16 @@ description = "Python bindings for the liblo OSC library" category = "main" optional = false python-versions = "*" +develop = false + +[package.dependencies] +cython = "*" + +[package.source] +type = "git" +url = "https://github.com/s0600204/pyliblo.git" +reference = "pip" +resolved_reference = "5cef301f234cab5d39a416462381f83f840acec5" [[package]] name = "pyparsing" @@ -187,22 +197,22 @@ optional = false python-versions = ">=3.6.8" [package.extras] -diagrams = ["railroad-diagrams", "jinja2"] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] -name = "pyqt5" -version = "5.15.6" +name = "PyQt5" +version = "5.15.7" description = "Python bindings for the Qt cross platform application toolkit" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] -PyQt5-Qt5 = ">=5.15.2" -PyQt5-sip = ">=12.8,<13" +PyQt5-Qt5 = ">=5.15.0" +PyQt5-sip = ">=12.11,<13" [[package]] -name = "pyqt5-qt5" +name = "PyQt5-Qt5" version = "5.15.2" description = "The subset of a Qt installation needed by PyQt5." category = "main" @@ -210,8 +220,8 @@ optional = false python-versions = "*" [[package]] -name = "pyqt5-sip" -version = "12.10.1" +name = "PyQt5-sip" +version = "12.11.0" description = "The sip module support for PyQt5" category = "main" optional = false @@ -227,7 +237,7 @@ python-versions = "*" [[package]] name = "requests" -version = "2.28.0" +version = "2.28.1" description = "Python HTTP for Humans." category = "main" optional = false @@ -235,13 +245,13 @@ python-versions = ">=3.7, <4" [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2.0.0,<2.1.0" +charset-normalizer = ">=2,<3" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "sortedcontainers" @@ -261,7 +271,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "typing-extensions" -version = "4.2.0" +version = "4.3.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false @@ -269,33 +279,33 @@ python-versions = ">=3.7" [[package]] name = "urllib3" -version = "1.26.9" +version = "1.26.12" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" [package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "zipp" -version = "3.8.0" +version = "3.8.1" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] +testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "16fda493a8a0716dea08d2cb30027165b456c1c43be12677cdb0fa81dd2ad947" +content-hash = "cf9cb46d439c1b83522cfd4e97143d12c64f1542ebe033dd89a49f21a314eee8" [metadata.files] appdirs = [ @@ -303,106 +313,120 @@ appdirs = [ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] certifi = [ - {file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"}, - {file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"}, + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, ] cffi = [ - {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, - {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, - {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, - {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, - {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, - {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, - {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, - {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, - {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, - {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, - {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, - {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, - {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, - {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, - {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, - {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, - {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, - {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, - {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, - {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, - {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, - {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, - {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, - {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, - {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, - {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, ] -cython = [ - {file = "Cython-0.29.30-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e5cb144728a335d7a7fd0a61dff6abb7a9aeff9acd46d50b886b7d9a95bb7311"}, - {file = "Cython-0.29.30-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d52d5733dcb144deca8985f0a197c19cf71e6bd6bd9d8034f3f67b2dea68d12b"}, - {file = "Cython-0.29.30-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0cd6c932e945af15ae4ddcf8fdc0532bda48784c92ed0a53cf4fae897067ccd1"}, - {file = "Cython-0.29.30-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a30092c6e2d24255fbfe0525f9a750554f96a263ed986d12ac3c9f7d9a85a424"}, - {file = "Cython-0.29.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:abcaf99f90cddc0f53600613eaafc81d27c4ac0671f0df8bce5466d4e86d54a1"}, - {file = "Cython-0.29.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9826981308802c61a76f967875b31b7c683b7fc369eabaa6cbc22efeb12c90e8"}, - {file = "Cython-0.29.30-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:d166d9f853db436f5e10733a9bd615699ddb4238feadcbdf5ae50dc0b18b18f5"}, - {file = "Cython-0.29.30-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0b83a342a071c4f14e7410568e0c0bd95e2f20c0b32944e3a721649a1357fda4"}, - {file = "Cython-0.29.30-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:ffa8c09617833ff0824aa7926fa4fa9d2ec3929c67168e89105f276b7f36a63e"}, - {file = "Cython-0.29.30-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6b389a94b42909ff56d3491fde7c44802053a103701a7d210dcdd449a5b4f7b4"}, - {file = "Cython-0.29.30-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:7eff71c39b98078deaad1d1bdbf10864d234e2ab5d5257e980a6926a8523f697"}, - {file = "Cython-0.29.30-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8e08f18d249b9b65e272a5a60f3360a8922c4c149036b98fc821fe1afad5bdae"}, - {file = "Cython-0.29.30-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3993aafd68a7311ef94e00e44a137f6a50a69af0575ebcc8a0a074ad4152a2b2"}, - {file = "Cython-0.29.30-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:5c7cfd908efc77306ddd41ef07f5a7a352c9205ced5c1e00a0e5ece4391707c4"}, - {file = "Cython-0.29.30-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e605635a92ae862cb46d84d1d6883324518f9aaff4a71cede6d61df20b6a410c"}, - {file = "Cython-0.29.30-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:786ee7b0cdb508b6de64c0f1f9c74f207186dfafad1ef938f25b7494cc481a80"}, - {file = "Cython-0.29.30-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:1e078943bbde703ca08d43e719480eb8b187d9023cbd91798619f5b5e18d0d71"}, - {file = "Cython-0.29.30-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5183356c756b56c2df12d96300d602e47ffb89943c5a0bded66faca5d3da7be0"}, - {file = "Cython-0.29.30-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e36755e71fd20eceb410cc441b7f2586654c2edb013f4663842fdaf60b96c1ca"}, - {file = "Cython-0.29.30-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e29d3487f357108b711f2f29319811d92166643d29aec1b8e063aad46a346775"}, - {file = "Cython-0.29.30-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5a8a3709ad9343a1dc02b8ec9cf6bb284be248d2c64af85464d9c3525eec74a5"}, - {file = "Cython-0.29.30-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:b17639b6a155abaa61a89f6f1323fb57b138d0529911ca03978d594945d062ba"}, - {file = "Cython-0.29.30-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:9462e9cf284d9b1d2c5b53d62188e3c09cc5c7a0018ba349d99b73cf930238de"}, - {file = "Cython-0.29.30-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:58d2b734250c1093bc69c1c3a6f5736493b9f8b34eb765f0a28a4a09468c0b00"}, - {file = "Cython-0.29.30-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:28db751e2d8365b39664d9cb62dc1668688b8fcc5b954e9ca9d20e0b8e03d8b0"}, - {file = "Cython-0.29.30-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5f2dae7dd56860018d5fd5032a71f11fdc224020932b463d0511a1536f27df85"}, - {file = "Cython-0.29.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d0859a958e0155b6ae4dee04170ccfac2c3d613a7e3bee8749614530b9e3b4a4"}, - {file = "Cython-0.29.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d0f34b44078e3e0b2f1be2b99044619b37127128e7d55c54bbd2438adcaf31d3"}, - {file = "Cython-0.29.30-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:80a7255ad84620f53235c0720cdee2bc7431d9e3db7b3742823a606c329eb539"}, - {file = "Cython-0.29.30-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3d0239c7a22a0f3fb1deec75cab0078eba4dd17868aa992a54a178851e0c8684"}, - {file = "Cython-0.29.30-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c299c5b250ae9f81c38200441b6f1d023aeee9d8e7f61c04001c7437181ccb06"}, - {file = "Cython-0.29.30-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:019d330ac580b2ca4a457c464ac0b8c35009d820ef5d09f328d6e31a10e1ce89"}, - {file = "Cython-0.29.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:71fd1d910aced510c001936667fc7f2901c49b2ca7a2ad67358979c94a7f42ac"}, - {file = "Cython-0.29.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:60d370c33d56077d30e5f425026e58c2559e93b4784106f61581cf54071f6270"}, - {file = "Cython-0.29.30-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:20778297c8bcba201ca122a2f792a9899d6e64c68a92363dd7eb24306d54d7ce"}, - {file = "Cython-0.29.30-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9f1fe924c920b699af27aefebd722df4cfbb85206291623cd37d1a7ddfd57792"}, - {file = "Cython-0.29.30-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c79685dd4631a188e2385dc6a232896c7b67ea2e3e5f8b5555b4b743f475d6d7"}, - {file = "Cython-0.29.30-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:88c5e2f92f16cd999ddfc43d572639679e8a057587088e627e98118e46a803e6"}, - {file = "Cython-0.29.30-py2.py3-none-any.whl", hash = "sha256:acb72e0b42079862cf2f894964b41f261e941e75677e902c5f4304b3eb00af33"}, - {file = "Cython-0.29.30.tar.gz", hash = "sha256:2235b62da8fe6fa8b99422c8e583f2fb95e143867d337b5c75e4b9a1a865f9e3"}, +Cython = [ + {file = "Cython-0.29.32-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:39afb4679b8c6bf7ccb15b24025568f4f9b4d7f9bf3cbd981021f542acecd75b"}, + {file = "Cython-0.29.32-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dbee03b8d42dca924e6aa057b836a064c769ddfd2a4c2919e65da2c8a362d528"}, + {file = "Cython-0.29.32-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ba622326f2862f9c1f99ca8d47ade49871241920a352c917e16861e25b0e5c3"}, + {file = "Cython-0.29.32-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e6ffa08aa1c111a1ebcbd1cf4afaaec120bc0bbdec3f2545f8bb7d3e8e77a1cd"}, + {file = "Cython-0.29.32-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:97335b2cd4acebf30d14e2855d882de83ad838491a09be2011745579ac975833"}, + {file = "Cython-0.29.32-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:06be83490c906b6429b4389e13487a26254ccaad2eef6f3d4ee21d8d3a4aaa2b"}, + {file = "Cython-0.29.32-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:eefd2b9a5f38ded8d859fe96cc28d7d06e098dc3f677e7adbafda4dcdd4a461c"}, + {file = "Cython-0.29.32-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5514f3b4122cb22317122a48e175a7194e18e1803ca555c4c959d7dfe68eaf98"}, + {file = "Cython-0.29.32-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:656dc5ff1d269de4d11ee8542f2ffd15ab466c447c1f10e5b8aba6f561967276"}, + {file = "Cython-0.29.32-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:cdf10af3e2e3279dc09fdc5f95deaa624850a53913f30350ceee824dc14fc1a6"}, + {file = "Cython-0.29.32-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:3875c2b2ea752816a4d7ae59d45bb546e7c4c79093c83e3ba7f4d9051dd02928"}, + {file = "Cython-0.29.32-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:79e3bab19cf1b021b613567c22eb18b76c0c547b9bc3903881a07bfd9e7e64cf"}, + {file = "Cython-0.29.32-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0595aee62809ba353cebc5c7978e0e443760c3e882e2c7672c73ffe46383673"}, + {file = "Cython-0.29.32-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0ea8267fc373a2c5064ad77d8ff7bf0ea8b88f7407098ff51829381f8ec1d5d9"}, + {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c8e8025f496b5acb6ba95da2fb3e9dacffc97d9a92711aacfdd42f9c5927e094"}, + {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:afbce249133a830f121b917f8c9404a44f2950e0e4f5d1e68f043da4c2e9f457"}, + {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:513e9707407608ac0d306c8b09d55a28be23ea4152cbd356ceaec0f32ef08d65"}, + {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e83228e0994497900af954adcac27f64c9a57cd70a9ec768ab0cb2c01fd15cf1"}, + {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ea1dcc07bfb37367b639415333cfbfe4a93c3be340edf1db10964bc27d42ed64"}, + {file = "Cython-0.29.32-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8669cadeb26d9a58a5e6b8ce34d2c8986cc3b5c0bfa77eda6ceb471596cb2ec3"}, + {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:ed087eeb88a8cf96c60fb76c5c3b5fb87188adee5e179f89ec9ad9a43c0c54b3"}, + {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:3f85eb2343d20d91a4ea9cf14e5748092b376a64b7e07fc224e85b2753e9070b"}, + {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:63b79d9e1f7c4d1f498ab1322156a0d7dc1b6004bf981a8abda3f66800e140cd"}, + {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e1958e0227a4a6a2c06fd6e35b7469de50adf174102454db397cec6e1403cce3"}, + {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:856d2fec682b3f31583719cb6925c6cdbb9aa30f03122bcc45c65c8b6f515754"}, + {file = "Cython-0.29.32-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:479690d2892ca56d34812fe6ab8f58e4b2e0129140f3d94518f15993c40553da"}, + {file = "Cython-0.29.32-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:67fdd2f652f8d4840042e2d2d91e15636ba2bcdcd92e7e5ffbc68e6ef633a754"}, + {file = "Cython-0.29.32-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:4a4b03ab483271f69221c3210f7cde0dcc456749ecf8243b95bc7a701e5677e0"}, + {file = "Cython-0.29.32-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:40eff7aa26e91cf108fd740ffd4daf49f39b2fdffadabc7292b4b7dc5df879f0"}, + {file = "Cython-0.29.32-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0bbc27abdf6aebfa1bce34cd92bd403070356f28b0ecb3198ff8a182791d58b9"}, + {file = "Cython-0.29.32-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cddc47ec746a08603037731f5d10aebf770ced08666100bd2cdcaf06a85d4d1b"}, + {file = "Cython-0.29.32-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca3065a1279456e81c615211d025ea11bfe4e19f0c5650b859868ca04b3fcbd"}, + {file = "Cython-0.29.32-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d968ffc403d92addf20b68924d95428d523436adfd25cf505d427ed7ba3bee8b"}, + {file = "Cython-0.29.32-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f3fd44cc362eee8ae569025f070d56208908916794b6ab21e139cea56470a2b3"}, + {file = "Cython-0.29.32-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:b6da3063c5c476f5311fd76854abae6c315f1513ef7d7904deed2e774623bbb9"}, + {file = "Cython-0.29.32-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:061e25151c38f2361bc790d3bcf7f9d9828a0b6a4d5afa56fbed3bd33fb2373a"}, + {file = "Cython-0.29.32-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f9944013588a3543fca795fffb0a070a31a243aa4f2d212f118aa95e69485831"}, + {file = "Cython-0.29.32-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:07d173d3289415bb496e72cb0ddd609961be08fe2968c39094d5712ffb78672b"}, + {file = "Cython-0.29.32-py2.py3-none-any.whl", hash = "sha256:eeb475eb6f0ccf6c039035eb4f0f928eb53ead88777e0a760eccb140ad90930b"}, + {file = "Cython-0.29.32.tar.gz", hash = "sha256:8733cf4758b79304f2a4e39ebfac5e92341bce47bcceb26c1254398b2f8c1af7"}, ] falcon = [ {file = "falcon-3.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:9400871fa536bac799b790ff28d999c5282c2bd7652f37f448b96718ddb289ff"}, @@ -442,14 +466,14 @@ humanize = [ {file = "humanize-3.14.0.tar.gz", hash = "sha256:60dd8c952b1df1ad83f0903844dec50a34ba7a04eea22a6b14204ffb62dbb0a4"}, ] idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"}, - {file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"}, + {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, + {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, ] -jack-client = [ +JACK-Client = [ {file = "JACK-Client-0.5.4.tar.gz", hash = "sha256:dd4a293e3a6e9bde9972569b9bc4630a5fcd4f80756cc590de572cc744e5a848"}, {file = "JACK_Client-0.5.4-py3-none-any.whl", hash = "sha256:52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73"}, ] @@ -477,47 +501,45 @@ pycparser = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] -pygobject = [ - {file = "PyGObject-3.42.1.tar.gz", hash = "sha256:80d6a3ad1630e9d1edf31b9e9fad9a894c57e18545a3c95ef0044ac4042b8620"}, -] -pyliblo = [ - {file = "pyliblo-0.10.0.tar.gz", hash = "sha256:fc67f1950b827272b00f9f0dc4ed7113c0ccef0c1c09e9976dead40ebbf1798f"}, +PyGObject = [ + {file = "PyGObject-3.42.2.tar.gz", hash = "sha256:21524cef33100c8fd59dc135948b703d79d303e368ce71fa60521cc971cd8aa7"}, ] +pyliblo = [] pyparsing = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] -pyqt5 = [ - {file = "PyQt5-5.15.6-cp36-abi3-macosx_10_13_x86_64.whl", hash = "sha256:33ced1c876f6a26e7899615a5a4efef2167c263488837c7beed023a64cebd051"}, - {file = "PyQt5-5.15.6-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:9d6efad0377aa78bf081a20ac752ce86096ded18f04c592d98f5b92dc879ad0a"}, - {file = "PyQt5-5.15.6-cp36-abi3-win32.whl", hash = "sha256:9d2dcdaf82263ae56023410a7af15d1fd746c8e361733a7d0d1bd1f004ec2793"}, - {file = "PyQt5-5.15.6-cp36-abi3-win_amd64.whl", hash = "sha256:f411ecda52e488e1d3c5cce7563e1b2ca9cf0b7531e3c25b03d9a7e56e07e7fc"}, - {file = "PyQt5-5.15.6.tar.gz", hash = "sha256:80343bcab95ffba619f2ed2467fd828ffeb0a251ad7225be5fc06dcc333af452"}, +PyQt5 = [ + {file = "PyQt5-5.15.7-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:1a793748c60d5aff3850b7abf84d47c1d41edb11231b7d7c16bef602c36be643"}, + {file = "PyQt5-5.15.7-cp37-abi3-manylinux1_x86_64.whl", hash = "sha256:e319c9d8639e0729235c1b09c99afdadad96fa3dbd8392ab561b5ab5946ee6ef"}, + {file = "PyQt5-5.15.7-cp37-abi3-win32.whl", hash = "sha256:08694f0a4c7d4f3d36b2311b1920e6283240ad3b7c09b515e08262e195dcdf37"}, + {file = "PyQt5-5.15.7-cp37-abi3-win_amd64.whl", hash = "sha256:232fe5b135a095cbd024cf341d928fc672c963f88e6a52b0c605be8177c2fdb5"}, + {file = "PyQt5-5.15.7.tar.gz", hash = "sha256:755121a52b3a08cb07275c10ebb96576d36e320e572591db16cfdbc558101594"}, ] -pyqt5-qt5 = [ +PyQt5-Qt5 = [ {file = "PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"}, {file = "PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"}, {file = "PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"}, {file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"}, ] -pyqt5-sip = [ - {file = "PyQt5_sip-12.10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b2852df169f349e37999fc425f2d0abd004d09219354a55d3b4c6dd574b3fe84"}, - {file = "PyQt5_sip-12.10.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:2378bda30ee2b7a568086952658ba8daad8f0f71fc335f696423ee9b3b4028db"}, - {file = "PyQt5_sip-12.10.1-cp310-cp310-win32.whl", hash = "sha256:e70c961eb982cfc866a988cfe4fab16741d480d885203e04b539ecf32b53c6b7"}, - {file = "PyQt5_sip-12.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:0ba8c0751b46561542b5a6bb1b703faad10a205d9375ebc607b1c9f858bfe5b1"}, - {file = "PyQt5_sip-12.10.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:023de82b23a7e97369588dd6e411dd2bfbedbbcfb4c7f9c2ecaa9e29fc46a81e"}, - {file = "PyQt5_sip-12.10.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5e8472a959ec85f94d4810326505076b55eb4d8b2e08dbd5d87bd1907c59680c"}, - {file = "PyQt5_sip-12.10.1-cp37-cp37m-win32.whl", hash = "sha256:3a92bd103dad59c2bfbb1062420c2e7bcf134dfce800f6a8c9964c99a590162d"}, - {file = "PyQt5_sip-12.10.1-cp37-cp37m-win_amd64.whl", hash = "sha256:dda92bfaee40fd612c6b1e2614570d6acc8ec63032c6fd45d5e66badd18d387c"}, - {file = "PyQt5_sip-12.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d31f6f05777c8ee0828daeb5cb4a15d4124c63d94cacb07faa2dbc79f72209ea"}, - {file = "PyQt5_sip-12.10.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ba89547fc54403bbdcb7ee6066098d02d18d813f29b7de2434043a357bb79f6b"}, - {file = "PyQt5_sip-12.10.1-cp38-cp38-win32.whl", hash = "sha256:90f8cde3b446799800bc7c2d89a4fef5d8316d9f4c2f06daf4f6976d1c68fdfa"}, - {file = "PyQt5_sip-12.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:a83c7ba53e04e05cf1bcd18639080dd44196cf248773f34b1e9b831c11a1e198"}, - {file = "PyQt5_sip-12.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:087a7c0ebff00bca73788588b61b50063e17fcdb26977317d1785cb241a66a56"}, - {file = "PyQt5_sip-12.10.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:8024347c865a7f0c6581b460cc18dbea1c401733300bc3cb76f74c9432a2b0ae"}, - {file = "PyQt5_sip-12.10.1-cp39-cp39-win32.whl", hash = "sha256:a345bed4112d44705340e4033a2abc66d8dbd4afd62f3a9aa69253f15718df53"}, - {file = "PyQt5_sip-12.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:8504991216ff94ca1eaba39a6e6497e6ed860f1fc659f6c14ebfccf43733c02f"}, - {file = "PyQt5_sip-12.10.1.tar.gz", hash = "sha256:97e008795c453488f51a5c97dbff29cda7841afb1ca842c9e819d8e6cc0ae724"}, +PyQt5-sip = [ + {file = "PyQt5_sip-12.11.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1f9e312ff8284d6dfebc5366f6f7d103f84eec23a4da0be0482403933e68660"}, + {file = "PyQt5_sip-12.11.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:4031547dfb679be309094bfa79254f5badc5ddbe66b9ad38e319d84a7d612443"}, + {file = "PyQt5_sip-12.11.0-cp310-cp310-win32.whl", hash = "sha256:ad21ca0ee8cae2a41b61fc04949dccfab6fe008749627d94e8c7078cb7a73af1"}, + {file = "PyQt5_sip-12.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:3126c84568ab341c12e46ded2230f62a9a78752a70fdab13713f89a71cd44f73"}, + {file = "PyQt5_sip-12.11.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0f77655c62ec91d47c2c99143f248624d44dd2d8a12d016e7c020508ad418aca"}, + {file = "PyQt5_sip-12.11.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ec5e9ef78852e1f96f86d7e15c9215878422b83dde36d44f1539a3062942f19c"}, + {file = "PyQt5_sip-12.11.0-cp37-cp37m-win32.whl", hash = "sha256:d12b81c3a08abf7657a2ebc7d3649852a1f327eb2146ebadf45930486d32e920"}, + {file = "PyQt5_sip-12.11.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b69a1911f768b489846335e31e49eb34795c6b5a038ca24d894d751e3b0b44da"}, + {file = "PyQt5_sip-12.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51e377789d59196213eddf458e6927f33ba9d217b614d17d20df16c9a8b2c41c"}, + {file = "PyQt5_sip-12.11.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:4e5c1559311515291ea0ab0635529f14536954e3b973a7c7890ab7e4de1c2c23"}, + {file = "PyQt5_sip-12.11.0-cp38-cp38-win32.whl", hash = "sha256:9bca450c5306890cb002fe36bbca18f979dd9e5b810b766dce8e3ce5e66ba795"}, + {file = "PyQt5_sip-12.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:f6b72035da4e8fecbb0bc4a972e30a5674a9ad5608dbddaa517e983782dbf3bf"}, + {file = "PyQt5_sip-12.11.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9356260d4feb60dbac0ab66f8a791a0d2cda1bf98c9dec8e575904a045fbf7c5"}, + {file = "PyQt5_sip-12.11.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:205f3e1b3eea3597d8e878936c1a06e04bd23a59e8b179ee806465d72eea3071"}, + {file = "PyQt5_sip-12.11.0-cp39-cp39-win32.whl", hash = "sha256:686071be054e5be6ca5aaaef7960931d4ba917277e839e2e978c7cbe3f43bb6e"}, + {file = "PyQt5_sip-12.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:42320e7a94b1085ed85d49794ed4ccfe86f1cae80b44a894db908a8aba2bc60e"}, + {file = "PyQt5_sip-12.11.0.tar.gz", hash = "sha256:b4710fd85b57edef716cc55fae45bfd5bfac6fc7ba91036f1dcc3f331ca0eb39"}, ] python-rtmidi = [ {file = "python-rtmidi-1.4.9.tar.gz", hash = "sha256:bfeb4ed99d0cccf6fa2837566907652ded7adc1c03b69f2160c9de4082301302"}, @@ -535,8 +557,8 @@ python-rtmidi = [ {file = "python_rtmidi-1.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:54d40cb794a6b079105bfb923ab0b4d5f041e9eef5dc5cce25480e4c3e3fc2f6"}, ] requests = [ - {file = "requests-2.28.0-py3-none-any.whl", hash = "sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f"}, - {file = "requests-2.28.0.tar.gz", hash = "sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b"}, + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, ] sortedcontainers = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, @@ -547,14 +569,14 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] typing-extensions = [ - {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, - {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, + {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, + {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, ] urllib3 = [ - {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, - {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, + {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, + {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, ] zipp = [ - {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, - {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, + {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, + {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, ] diff --git a/pyproject.toml b/pyproject.toml index 2a057b70f..03109aae5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,13 +38,14 @@ falcon = "^3.0" jack-client = "^0.5" mido = "^1.2" pygobject = "^3.30" -pyliblo = "^0.10" pyqt5 = "^5.6" python-rtmidi = "^1.1" requests = "^2.20" sortedcontainers = "^2.0" -pyalsa = {git = "https://github.com/alsa-project/alsa-python.git", tag = "v1.2.6"} humanize = "^3.1.0" +pyalsa = {git = "https://github.com/alsa-project/alsa-python.git", tag = "v1.2.6"} +# Use a pyliblo fork that define dependecies correctly +pyliblo = {git = "https://github.com/s0600204/pyliblo.git", branch = "pip"} [tool.poetry.dev-dependencies] toml = "*" From 9943242090972d2b7fc4ec8e4a48e0db3e815dba Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 1 Oct 2022 15:07:13 +0200 Subject: [PATCH 289/333] Update "source" translation files --- lisp/i18n/ts/en/action_cues.ts | 22 ++++++------- lisp/i18n/ts/en/cache_manager.ts | 2 +- lisp/i18n/ts/en/cart_layout.ts | 48 ++++++++++++++-------------- lisp/i18n/ts/en/controller.ts | 2 +- lisp/i18n/ts/en/gst_backend.ts | 55 +++++++++++++++++--------------- lisp/i18n/ts/en/lisp.ts | 43 ++++++++++++++----------- lisp/i18n/ts/en/list_layout.ts | 44 ++++++++++++------------- lisp/i18n/ts/en/media_info.ts | 2 +- lisp/i18n/ts/en/midi.ts | 10 +++--- lisp/i18n/ts/en/network.ts | 2 +- lisp/i18n/ts/en/osc.ts | 30 ++++++++--------- lisp/i18n/ts/en/presets.ts | 40 +++++++++++------------ lisp/i18n/ts/en/rename_cues.ts | 4 +-- lisp/i18n/ts/en/replay_gain.ts | 2 +- lisp/i18n/ts/en/synchronizer.ts | 2 +- lisp/i18n/ts/en/timecode.ts | 2 +- lisp/i18n/ts/en/triggers.ts | 2 +- 17 files changed, 161 insertions(+), 151 deletions(-) diff --git a/lisp/i18n/ts/en/action_cues.ts b/lisp/i18n/ts/en/action_cues.ts index cd5324ac0..512617ce9 100644 --- a/lisp/i18n/ts/en/action_cues.ts +++ b/lisp/i18n/ts/en/action_cues.ts @@ -78,7 +78,7 @@ Collection Cue - + Stop-All Stop-All @@ -172,7 +172,7 @@ Edit Collection - + Stop Settings Stop Settings @@ -182,7 +182,7 @@ Command - + Volume Settings Volume Settings @@ -195,7 +195,7 @@ StopAll - + Stop Action Stop Action @@ -203,27 +203,27 @@ VolumeControl - + Cue Cue - + Click to select Click to select - + Not selected Not selected - + Volume to reach Volume to reach - + Fade Fade @@ -231,9 +231,9 @@ VolumeControlError - + Error during cue execution. - + \ No newline at end of file diff --git a/lisp/i18n/ts/en/cache_manager.ts b/lisp/i18n/ts/en/cache_manager.ts index 0ccaeae47..86718631c 100644 --- a/lisp/i18n/ts/en/cache_manager.ts +++ b/lisp/i18n/ts/en/cache_manager.ts @@ -43,4 +43,4 @@ You can do it in the application settings. - + \ No newline at end of file diff --git a/lisp/i18n/ts/en/cart_layout.ts b/lisp/i18n/ts/en/cart_layout.ts index 2d9291a2d..22a7b1af7 100644 --- a/lisp/i18n/ts/en/cart_layout.ts +++ b/lisp/i18n/ts/en/cart_layout.ts @@ -4,22 +4,22 @@ CartLayout - + Reset volume - + Add page - + Add pages - + Remove current page @@ -49,42 +49,42 @@ - + Number of Pages: - + Page {number} - + Warning - + Every cue in the page will be lost. - + Are you sure to continue? - + Play - + Pause - + Stop @@ -112,7 +112,7 @@ LayoutDescription - + Organize cues in grid like pages @@ -120,27 +120,27 @@ LayoutDetails - + Click a cue to run it - + SHIFT + Click to edit a cue - + CTRL + Click to select a cue - + To copy cues drag them while pressing CTRL - + To move cues drag them while pressing SHIFT @@ -148,7 +148,7 @@ LayoutName - + Cart Layout @@ -156,22 +156,22 @@ ListLayout - + Edit cue - + Edit selected cues - + Remove cue - + Remove selected cues @@ -184,4 +184,4 @@ - + \ No newline at end of file diff --git a/lisp/i18n/ts/en/controller.ts b/lisp/i18n/ts/en/controller.ts index a92e4caf3..9506a1dfa 100644 --- a/lisp/i18n/ts/en/controller.ts +++ b/lisp/i18n/ts/en/controller.ts @@ -295,4 +295,4 @@ do not forget to edit the path later. - + \ No newline at end of file diff --git a/lisp/i18n/ts/en/gst_backend.ts b/lisp/i18n/ts/en/gst_backend.ts index 3757e70c4..1481cadfb 100644 --- a/lisp/i18n/ts/en/gst_backend.ts +++ b/lisp/i18n/ts/en/gst_backend.ts @@ -4,12 +4,12 @@ AlsaSinkSettings - + ALSA device ALSA device - + To make your custom PCM objects appear correctly in this list requires adding a 'hint.description' line to them. @@ -88,7 +88,7 @@ CueCategory - + Media cues @@ -127,12 +127,12 @@ GstBackend - + Audio cue (from file) - + Select media files @@ -140,7 +140,7 @@ GstMediaError - + Cannot create pipeline element: "{}" @@ -156,7 +156,7 @@ GstMediaWarning - + Invalid pipeline element: "{}" @@ -186,7 +186,7 @@ JackSinkError - An error occurred while disconnection Jack ports + An error occurred while disconnecting Jack ports @@ -203,22 +203,22 @@ Edit connections - + Connect Connect - + Output ports Output ports - + Input ports Input ports - + Disconnect Disconnect @@ -231,22 +231,22 @@ Speed - + dB Meter dB Meter - + Audio Pan Audio Pan - + Volume Volume - + URI Input URI Input @@ -266,7 +266,7 @@ System Out - + Compressor/Expander Compressor/Expander @@ -281,12 +281,12 @@ Preset Input - + Pitch Pitch - + 10 Bands Equalizer 10 Bands Equalizer @@ -296,7 +296,7 @@ System Input - + ALSA Out ALSA Out @@ -334,11 +334,16 @@ GStreamer + + + ALSA Default Device + + SpeedSettings - + Speed Speed @@ -402,19 +407,19 @@ VolumeSettings - + Volume Volume - + Normalized volume Normalized volume - + Reset Reset - + \ No newline at end of file diff --git a/lisp/i18n/ts/en/lisp.ts b/lisp/i18n/ts/en/lisp.ts index c0aca9908..790650b77 100644 --- a/lisp/i18n/ts/en/lisp.ts +++ b/lisp/i18n/ts/en/lisp.ts @@ -124,17 +124,17 @@ ApplicationError - + Startup error - + Error while reading the session file "{}" - + Unable to create the cue "{}" @@ -163,7 +163,12 @@ ConfigurationDebug - + + Configuration read from {} + + + + Configuration written at {} @@ -171,12 +176,12 @@ ConfigurationInfo - + New configuration installed at {} - + Invalid path "{}", return default. @@ -306,7 +311,7 @@ CueName - + Media Cue Media Cue @@ -342,23 +347,23 @@ CueSettings - - Interrupt action fade + + Interrupt fade - + Used globally when interrupting cues - - Fade actions default value + + Fallback fade settings - - Used for fade-in and fade-out actions, by cues where fade duration is 0. + + Used for fade-in and fade-out actions, for cues where fade duration is set to 0. @@ -764,17 +769,17 @@ About Qt - + Close session Close session - + The current session contains changes that have not been saved. - + Do you want to save them now? @@ -790,7 +795,7 @@ MainWindowError - + Cannot create cue {} @@ -974,4 +979,4 @@ Appearance - + \ No newline at end of file diff --git a/lisp/i18n/ts/en/list_layout.ts b/lisp/i18n/ts/en/list_layout.ts index 4b0b00673..0ef3a9d96 100644 --- a/lisp/i18n/ts/en/list_layout.ts +++ b/lisp/i18n/ts/en/list_layout.ts @@ -4,7 +4,7 @@ LayoutDescription - + Organize the cues in a list @@ -12,17 +12,17 @@ LayoutDetails - + SHIFT + Space or Double-Click to edit a cue - + To copy cues drag them while pressing CTRL - + To move cues drag them @@ -30,7 +30,7 @@ LayoutName - + List Layout @@ -53,7 +53,7 @@ - + Show index column @@ -63,57 +63,57 @@ - + Selection mode - + Show resize handles - + Restore default size - + Disable GO Key While Playing - + Edit cue - + Edit selected - + Clone cue - + Clone selected - + Remove cue - + Remove selected - + Copy of {} @@ -216,22 +216,22 @@ ListLayoutHeader - + Cue - + Pre wait - + Action - + Post wait @@ -257,4 +257,4 @@ - + \ No newline at end of file diff --git a/lisp/i18n/ts/en/media_info.ts b/lisp/i18n/ts/en/media_info.ts index 68d390d1f..87450659c 100644 --- a/lisp/i18n/ts/en/media_info.ts +++ b/lisp/i18n/ts/en/media_info.ts @@ -29,4 +29,4 @@ Value - + \ No newline at end of file diff --git a/lisp/i18n/ts/en/midi.ts b/lisp/i18n/ts/en/midi.ts index 794e1ab47..918bd1ce2 100644 --- a/lisp/i18n/ts/en/midi.ts +++ b/lisp/i18n/ts/en/midi.ts @@ -4,7 +4,7 @@ CueCategory - + Integration cues @@ -51,17 +51,17 @@ MIDIInfo - + Connecting to MIDI port: '{}' - + MIDI port disconnected: '{}' - + Connecting to matching MIDI port: '{}' @@ -218,4 +218,4 @@ - + \ No newline at end of file diff --git a/lisp/i18n/ts/en/network.ts b/lisp/i18n/ts/en/network.ts index 621aa0e20..ae534af91 100644 --- a/lisp/i18n/ts/en/network.ts +++ b/lisp/i18n/ts/en/network.ts @@ -81,4 +81,4 @@ - + \ No newline at end of file diff --git a/lisp/i18n/ts/en/osc.ts b/lisp/i18n/ts/en/osc.ts index 502aea27c..cdda9ba8f 100644 --- a/lisp/i18n/ts/en/osc.ts +++ b/lisp/i18n/ts/en/osc.ts @@ -4,7 +4,7 @@ Cue Name - + OSC Settings @@ -12,7 +12,7 @@ CueCategory - + Integration cues @@ -28,57 +28,57 @@ OscCue - + OSC Message - + Add - + Remove - + OSC Path: - + /path/to/something - + Fade - + Time (sec) - + Curve - + Type - + Value - + FadeTo @@ -86,7 +86,7 @@ OscCueError - + Cannot send OSC message, see error for details @@ -151,4 +151,4 @@ - + \ No newline at end of file diff --git a/lisp/i18n/ts/en/presets.ts b/lisp/i18n/ts/en/presets.ts index c83d76c7d..8f2c0415f 100644 --- a/lisp/i18n/ts/en/presets.ts +++ b/lisp/i18n/ts/en/presets.ts @@ -4,12 +4,12 @@ Preset - + Create Cue Create Cue - + Load on selected Cues Load on selected Cues @@ -17,7 +17,7 @@ Presets - + Cannot scan presets Cannot scan presets @@ -32,32 +32,32 @@ Preset name - + Add Add - + Rename Rename - + Edit Edit - + Remove Remove - + Export selected Export selected - + Import Import @@ -67,42 +67,42 @@ Cue type - + Error while deleting preset "{}" Error while deleting preset "{}" - + Cannot load preset "{}" Cannot load preset "{}" - + Cannot save preset "{}" Cannot save preset "{}" - + Cannot rename preset "{}" Cannot rename preset "{}" - + Select Preset Select Preset - + Preset "{}" already exists, overwrite? - + Warning Warning - + The same name is already used! The same name is already used! @@ -128,12 +128,12 @@ - Load on cue + Apply to cue - Load on selected cues + Apply to selected cues @@ -142,4 +142,4 @@ Save as preset - + \ No newline at end of file diff --git a/lisp/i18n/ts/en/rename_cues.ts b/lisp/i18n/ts/en/rename_cues.ts index b8b3a35a5..92b44db3a 100644 --- a/lisp/i18n/ts/en/rename_cues.ts +++ b/lisp/i18n/ts/en/rename_cues.ts @@ -65,7 +65,7 @@ Insert expressions captured with regexes in the line below with $0 for the first parenthesis, $1 forthe second, etc... In the second line, you can use standard Python Regexes to match expressions in the original cues names. Use parenthesis to capture parts of the matched expression. -Exemple: +Example: ^[a-z]([0-9]+) will find a lower case character([a-z]), followed by one or more number. Only the numbers are between parenthesis and will be usable with $0 in the first line. @@ -104,4 +104,4 @@ For more information about Regexes, consult python documentation at: https://doc - + \ No newline at end of file diff --git a/lisp/i18n/ts/en/replay_gain.ts b/lisp/i18n/ts/en/replay_gain.ts index 2d1f2a0e8..b6563abd1 100644 --- a/lisp/i18n/ts/en/replay_gain.ts +++ b/lisp/i18n/ts/en/replay_gain.ts @@ -93,4 +93,4 @@ - + \ No newline at end of file diff --git a/lisp/i18n/ts/en/synchronizer.ts b/lisp/i18n/ts/en/synchronizer.ts index 0418835f9..567151a31 100644 --- a/lisp/i18n/ts/en/synchronizer.ts +++ b/lisp/i18n/ts/en/synchronizer.ts @@ -24,4 +24,4 @@ - + \ No newline at end of file diff --git a/lisp/i18n/ts/en/timecode.ts b/lisp/i18n/ts/en/timecode.ts index e46cc9d09..6b249102e 100644 --- a/lisp/i18n/ts/en/timecode.ts +++ b/lisp/i18n/ts/en/timecode.ts @@ -77,4 +77,4 @@ OLA daemon has stopped. - + \ No newline at end of file diff --git a/lisp/i18n/ts/en/triggers.ts b/lisp/i18n/ts/en/triggers.ts index cc3b4ed23..f5b23580a 100644 --- a/lisp/i18n/ts/en/triggers.ts +++ b/lisp/i18n/ts/en/triggers.ts @@ -60,4 +60,4 @@ Action - + \ No newline at end of file From d49340c881d6a375981c1539fb5b84a9fee15ae4 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 1 Oct 2022 15:09:15 +0200 Subject: [PATCH 290/333] WIP: docs rework --- docs/user/Makefile | 4 + docs/user/source/{about.rst => about.md} | 0 .../{cart_layout.rst => cart_layout.md} | 0 ...e_examples.rst => command_cue_examples.md} | 0 docs/user/source/conf.py | 65 +- docs/user/source/{cues.rst => cues.md} | 0 docs/user/source/getting_started.md | 43 ++ docs/user/source/getting_started.rst | 51 -- ...om_elements.rst => gst_custom_elements.md} | 2 +- ...dia_settings.rst => gst_media_settings.md} | 2 +- docs/user/source/index.md | 47 ++ docs/user/source/index.rst | 22 - .../{list_layout.rst => list_layout.md} | 0 docs/user/source/media/logo.png | Bin 0 -> 16906 bytes docs/user/source/{menus.rst => menus.md} | 81 +-- ...artnet_timecode.rst => artnet_timecode.md} | 0 .../{cue_controls.rst => cue_controls.md} | 0 .../plugins/{presets.rst => presets.md} | 0 .../plugins/{replaygain.rst => replaygain.md} | 0 .../source/plugins/session_uri_editor.rst | 25 - ...synchronization.rst => synchronization.md} | 0 .../plugins/{triggers.rst => triggers.md} | 0 poetry.lock | 585 +++++++++++++++++- pyproject.toml | 5 + 24 files changed, 746 insertions(+), 186 deletions(-) rename docs/user/source/{about.rst => about.md} (100%) rename docs/user/source/{cart_layout.rst => cart_layout.md} (100%) rename docs/user/source/{command_cue_examples.rst => command_cue_examples.md} (100%) rename docs/user/source/{cues.rst => cues.md} (100%) create mode 100644 docs/user/source/getting_started.md delete mode 100644 docs/user/source/getting_started.rst rename docs/user/source/{gst_custom_elements.rst => gst_custom_elements.md} (98%) rename docs/user/source/{gst_media_settings.rst => gst_media_settings.md} (99%) create mode 100644 docs/user/source/index.md delete mode 100644 docs/user/source/index.rst rename docs/user/source/{list_layout.rst => list_layout.md} (100%) create mode 100644 docs/user/source/media/logo.png rename docs/user/source/{menus.rst => menus.md} (50%) rename docs/user/source/plugins/{artnet_timecode.rst => artnet_timecode.md} (100%) rename docs/user/source/plugins/{cue_controls.rst => cue_controls.md} (100%) rename docs/user/source/plugins/{presets.rst => presets.md} (100%) rename docs/user/source/plugins/{replaygain.rst => replaygain.md} (100%) delete mode 100644 docs/user/source/plugins/session_uri_editor.rst rename docs/user/source/plugins/{synchronization.rst => synchronization.md} (100%) rename docs/user/source/plugins/{triggers.rst => triggers.md} (100%) diff --git a/docs/user/Makefile b/docs/user/Makefile index c978eb015..64df52d74 100644 --- a/docs/user/Makefile +++ b/docs/user/Makefile @@ -14,6 +14,10 @@ help: .PHONY: help Makefile +# Rebuild on source changes +livehtml: + sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile diff --git a/docs/user/source/about.rst b/docs/user/source/about.md similarity index 100% rename from docs/user/source/about.rst rename to docs/user/source/about.md diff --git a/docs/user/source/cart_layout.rst b/docs/user/source/cart_layout.md similarity index 100% rename from docs/user/source/cart_layout.rst rename to docs/user/source/cart_layout.md diff --git a/docs/user/source/command_cue_examples.rst b/docs/user/source/command_cue_examples.md similarity index 100% rename from docs/user/source/command_cue_examples.rst rename to docs/user/source/command_cue_examples.md diff --git a/docs/user/source/conf.py b/docs/user/source/conf.py index 073f4c61c..8ee39d058 100644 --- a/docs/user/source/conf.py +++ b/docs/user/source/conf.py @@ -30,23 +30,17 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [] +extensions = [ + "sphinx_inline_tabs", + "myst_parser" +] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = ".rst" - -# The master toctree document. -master_doc = "index" - # General information about the project. project = "Linux Show Player" -copyright = "2017, Francesco Ceruti" +copyright = "2022, Francesco Ceruti" author = "Francesco Ceruti" # The version info for the project you're documenting, acts as replacement for @@ -54,9 +48,9 @@ # built documents. # # The short X.Y version. -version = "0.5" +version = "0.6" # The full version, including alpha/beta/rc tags. -release = "0.5" +release = "0.6" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -73,15 +67,22 @@ # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" +# -- Options for MyST ----------------------------------------------------- + +myst_heading_anchors = 3 # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -# html_theme = "sphinx_rtd_theme" +html_theme = "furo" # html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +html_logo = "media/logo.png" + +html_title = project + # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. @@ -98,7 +99,6 @@ # Output file base name for HTML help builder. htmlhelp_basename = "LinuxShowPlayerdoc" - # -- Options for LaTeX output --------------------------------------------- latex_toplevel_sectioning = "section" latex_elements = { @@ -113,43 +113,10 @@ # author, documentclass [howto, manual, or own class]). latex_documents = [ ( - master_doc, + "index", "LinuxShowPlayer.tex", "Linux Show Player User Documentation", "Francesco Ceruti", "article", ) ] - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ( - master_doc, - "linuxshowplayer", - "Linux Show Player Documentation", - [author], - 1, - ) -] - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ( - master_doc, - "LinuxShowPlayer", - "Linux Show Player Documentation", - author, - "LinuxShowPlayer", - "Cue player for stage productions", - "Miscellaneous", - ) -] diff --git a/docs/user/source/cues.rst b/docs/user/source/cues.md similarity index 100% rename from docs/user/source/cues.rst rename to docs/user/source/cues.md diff --git a/docs/user/source/getting_started.md b/docs/user/source/getting_started.md new file mode 100644 index 000000000..6ebe4a535 --- /dev/null +++ b/docs/user/source/getting_started.md @@ -0,0 +1,43 @@ +--- +hide-toc: true +--- + +# Getting Started + +Before diving into the different aspects of LiSP you should take a moment to understand +some of it's base concepts. + +## Shows + +A show, or session, is the container of everything you'll build inside the application. + +## Cues + +At the heart of every show, are the **cues**, used to execute specific actions, +such as playing sounds, sending MIDI messages, controlling other cues and so on. + +Each individual cue allows great customization of its behaviours. + +## Layouts + +As the name might imply, *layouts* will determine how cues are displayed and +organized on yours screen. +They also provide different sets of features. + +Every time you create a new show in LiSP you have the ability to choose a *layout*. + +You can choose between two of them: + +**List Layout** + Arrange the cues in a list and provide a mouse/keyboard oriented UI + +**Cart Layout** + Arrange the cues as buttons in a grid, providing a more touch-oriented UI, supports multiple pages. + +## Plugins + +While this is almost always hidden from the user, most of the functionalities +of Linux Show Player are provided via plugins. + +Plugins allow the more advanced users to build their own functionalities with +relative ease. diff --git a/docs/user/source/getting_started.rst b/docs/user/source/getting_started.rst deleted file mode 100644 index 14f43d83b..000000000 --- a/docs/user/source/getting_started.rst +++ /dev/null @@ -1,51 +0,0 @@ -.. toctree:: - :hidden: - :maxdepth: 1 - - -Getting Started -=============== - -Before diving into the different aspects of LiSP you need to understand the main -concepts that set the basis on how the application works. - -Cues ----- - -First, and probably the most important components you will find in LiSP, are the **cues**, -a cue is used to execute a specific action in a repeatable manner, every cue -allow customization of its behaviours independently. - -Cues are at the heart of every show, allowing to play sounds, send MIDI messages, -controls other cues and so on. - -Layouts -------- - -When creating a new show in LiSP you have the ability to chose a *layout*, this -will affect how cues will be displayed and eventually provides different sets of features. - -Currently two layouts are provided: - -* **List Layout:** arrange the cues in a list and provided a mouse/keyboard oriented UI -* **Cart Layout:** arrange the cues as buttons in one or more grids and provide a more touch-oriented UI. - -Menus ------ - -Most of the functionality are accessible via the top-bar menu, here a small -explanation on what you will find: - -* **File:** Operation related to the current session or the global application -* **Edit:** Functions mainly related to adding/editing cues (accessible right-clicking on empty areas of the layout) -* **Layout:** Functions provided by the current layout -* **Tools:** Utility to make life easier - -Cues provides a contextual (right-click) menu to access cue-specific options. - -Plugins -------- - -Linux Show Player is heavily based on plugins, while this is almost always -hidden from the user, most of the functionality are provided via plugins. -From time to time the documentation may refer to those as plugins or modules. diff --git a/docs/user/source/gst_custom_elements.rst b/docs/user/source/gst_custom_elements.md similarity index 98% rename from docs/user/source/gst_custom_elements.rst rename to docs/user/source/gst_custom_elements.md index 399b3c9fb..b59261db1 100644 --- a/docs/user/source/gst_custom_elements.rst +++ b/docs/user/source/gst_custom_elements.md @@ -1,4 +1,4 @@ -GStreamer Backend - Custom Elements +Media - Custom Elements =================================== One of the most used functionality of GStreamer is the ability to create pipelines diff --git a/docs/user/source/gst_media_settings.rst b/docs/user/source/gst_media_settings.md similarity index 99% rename from docs/user/source/gst_media_settings.rst rename to docs/user/source/gst_media_settings.md index 894db9b9c..8436e5669 100644 --- a/docs/user/source/gst_media_settings.rst +++ b/docs/user/source/gst_media_settings.md @@ -1,7 +1,7 @@ .. toctree:: :hidden: -GStreamer Backend - Media Settings +Media Settings ================================== Media Cues relay on a backend to provide playback capabilities. LiSP currently diff --git a/docs/user/source/index.md b/docs/user/source/index.md new file mode 100644 index 000000000..fc3c757a5 --- /dev/null +++ b/docs/user/source/index.md @@ -0,0 +1,47 @@ +--- +hide-toc: true +--- + +# About + +```{toctree} +:hidden: + +getting_started +menus +cart_layout +list_layout +cues +gst_media_settings +gst_custom_elements +command_cue_examples +plugins/* +``` + + +Linux Show Player, LiSP for short, is a free cue player, mainly intended for sound-playback in stage productions, +the goal is to provide a complete playback software for musical plays, theater shows and similar. + +## Features + +Here some functionality offered by LiSP: + +* List layout +* Cart layout (buttons) for touchscreens +* Undo/Redo changes +* Concurrent cues playback +* Pre/Post wait +* Realtime sound effects: equalization, pitch shift, speed control, compression +* Peak and ReplayGain normalization +* Send/Receive MIDI and OSC +* Multi-language support +* Can play (almost) any kind of media file + +## Project Status + +Currently, only GNU/Linux systems are supported. + +The application is quite stable, is already been used for multiple performances +by different people, but, due to the heterogeneous nature of the GNU/Linux ecosystem, +my suggestion is to test it in a "production" environment to detect possible problems +with some configuration. diff --git a/docs/user/source/index.rst b/docs/user/source/index.rst deleted file mode 100644 index 7419ec466..000000000 --- a/docs/user/source/index.rst +++ /dev/null @@ -1,22 +0,0 @@ -.. Linux Show Player documentation master file, created by - sphinx-quickstart on Thu Feb 23 10:55:18 2017. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Table of Contents -================= - -.. toctree:: - :glob: - :maxdepth: 2 - - about - getting_started - menus - cart_layout - list_layout - cues - gst_media_settings - gst_custom_elements - command_cue_examples - plugins/* diff --git a/docs/user/source/list_layout.rst b/docs/user/source/list_layout.md similarity index 100% rename from docs/user/source/list_layout.rst rename to docs/user/source/list_layout.md diff --git a/docs/user/source/media/logo.png b/docs/user/source/media/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7bbed7598df0f88431981294de73af83bbff9cb9 GIT binary patch literal 16906 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 Uri session change`` open the following window: - -.. image:: ../media/uri_session_change.png - :alt: Linux Show Player - URI Session change - :align: center - -| - -On the top, the ``Session file`` button allow to search for a file, on the right a -``reload`` button allow to reset all the changes. - -On the left, a list of (split) directories is shown, those can be selected, and -edited, when the ``replace`` button is pressed the list is updated to reflect the changes. - -The ``save`` button will allow to save the session file to a new location. diff --git a/docs/user/source/plugins/synchronization.rst b/docs/user/source/plugins/synchronization.md similarity index 100% rename from docs/user/source/plugins/synchronization.rst rename to docs/user/source/plugins/synchronization.md diff --git a/docs/user/source/plugins/triggers.rst b/docs/user/source/plugins/triggers.md similarity index 100% rename from docs/user/source/plugins/triggers.rst rename to docs/user/source/plugins/triggers.md diff --git a/poetry.lock b/poetry.lock index 10ee929e6..3dca915ad 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,11 @@ +[[package]] +name = "alabaster" +version = "0.7.12" +description = "A configurable sidebar-enabled Sphinx theme" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "appdirs" version = "1.4.4" @@ -6,6 +14,32 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "Babel" +version = "2.10.3" +description = "Internationalization utilities" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pytz = ">=2015.7" + +[[package]] +name = "beautifulsoup4" +version = "4.11.1" +description = "Screen-scraping library" +category = "dev" +optional = false +python-versions = ">=3.6.0" + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "certifi" version = "2022.9.24" @@ -36,6 +70,14 @@ python-versions = ">=3.6.0" [package.extras] unicode_backport = ["unicodedata2"] +[[package]] +name = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + [[package]] name = "Cython" version = "0.29.32" @@ -44,6 +86,14 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "docutils" +version = "0.19" +description = "Docutils -- Python Documentation Utilities" +category = "dev" +optional = false +python-versions = ">=3.7" + [[package]] name = "falcon" version = "3.1.0" @@ -52,6 +102,20 @@ category = "main" optional = false python-versions = ">=3.5" +[[package]] +name = "furo" +version = "2022.9.29" +description = "A clean customisable Sphinx documentation theme." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +beautifulsoup4 = "*" +pygments = ">=2.7" +sphinx = ">=4.0,<6.0" +sphinx-basic-ng = "*" + [[package]] name = "humanize" version = "3.14.0" @@ -74,6 +138,14 @@ category = "main" optional = false python-versions = ">=3.5" +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [[package]] name = "importlib-metadata" version = "4.12.0" @@ -105,6 +177,86 @@ CFFI = ">=1.0" [package.extras] numpy = ["NumPy"] +[[package]] +name = "Jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "livereload" +version = "2.6.3" +description = "Python LiveReload is an awesome tool for web developers" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" +tornado = {version = "*", markers = "python_version > \"2.7\""} + +[[package]] +name = "markdown-it-py" +version = "2.1.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +mdurl = ">=0.1,<1.0" +typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"] +code_style = ["pre-commit (==2.6)"] +compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"] +linkify = ["linkify-it-py (>=1.0,<2.0)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "MarkupSafe" +version = "2.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "mdit-py-plugins" +version = "0.3.1" +description = "Collection of plugins for markdown-it-py" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +markdown-it-py = ">=1.0.0,<3.0.0" + +[package.extras] +code_style = ["pre-commit"] +rtd = ["attrs", "myst-parser (>=0.16.1,<0.17.0)", "sphinx-book-theme (>=0.1.0,<0.2.0)"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +category = "dev" +optional = false +python-versions = ">=3.7" + [[package]] name = "mido" version = "1.2.10" @@ -117,6 +269,29 @@ python-versions = "*" dev = ["check-manifest (>=0.35)", "flake8 (>=3.4.1)", "pytest (>=3.2.2)", "sphinx (>=1.6.3)", "tox (>=2.8.2)"] ports = ["python-rtmidi (>=1.1.0)"] +[[package]] +name = "myst-parser" +version = "0.18.1" +description = "An extended commonmark compliant parser, with bridges to docutils & sphinx." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +docutils = ">=0.15,<0.20" +jinja2 = "*" +markdown-it-py = ">=1.0.0,<3.0.0" +mdit-py-plugins = ">=0.3.1,<0.4.0" +pyyaml = "*" +sphinx = ">=4,<6" +typing-extensions = "*" + +[package.extras] +code_style = ["pre-commit (>=2.12,<3.0)"] +linkify = ["linkify-it-py (>=1.0,<2.0)"] +rtd = ["ipython", "sphinx-book-theme", "sphinx-design", "sphinxcontrib.mermaid (>=0.7.1,<0.8.0)", "sphinxext-opengraph (>=0.6.3,<0.7.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] +testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions", "sphinx (<5.2)", "sphinx-pytest"] + [[package]] name = "packaging" version = "21.3" @@ -159,6 +334,17 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "Pygments" +version = "2.13.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +plugins = ["importlib-metadata"] + [[package]] name = "PyGObject" version = "3.42.2" @@ -235,6 +421,22 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "pytz" +version = "2022.2.1" +description = "World timezone definitions, modern and historical" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "PyYAML" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "requests" version = "2.28.1" @@ -253,6 +455,22 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "sortedcontainers" version = "2.4.0" @@ -261,6 +479,162 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "soupsieve" +version = "2.3.2.post1" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "Sphinx" +version = "5.2.3" +description = "Python documentation generator" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.14,<0.20" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.12" +requests = ">=2.5.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] + +[[package]] +name = "sphinx-autobuild" +version = "2021.3.14" +description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = "*" +livereload = "*" +sphinx = "*" + +[package.extras] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "sphinx-basic-ng" +version = "1.0.0b1" +description = "A modern skeleton for Sphinx themes." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +sphinx = ">=4.0" + +[package.extras] +docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"] + +[[package]] +name = "sphinx-inline-tabs" +version = "2022.1.2b11" +description = "Add inline tabbed content to your Sphinx documentation." +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +sphinx = ">=3" + +[package.extras] +doc = ["furo", "myst-parser"] +test = ["pytest", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.2" +description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.0" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + [[package]] name = "toml" version = "0.10.2" @@ -269,6 +643,14 @@ category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "tornado" +version = "6.2" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +category = "dev" +optional = false +python-versions = ">= 3.7" + [[package]] name = "typing-extensions" version = "4.3.0" @@ -305,13 +687,25 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>= [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "cf9cb46d439c1b83522cfd4e97143d12c64f1542ebe033dd89a49f21a314eee8" +content-hash = "c92191a0e37fa35acc3909144fec561b9404a7e9fe8761f70f5256963fc21fb6" [metadata.files] +alabaster = [ + {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, + {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, +] appdirs = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] +Babel = [ + {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, + {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, +] +beautifulsoup4 = [ + {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, + {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, +] certifi = [ {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, @@ -386,6 +780,10 @@ charset-normalizer = [ {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, ] +colorama = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, +] Cython = [ {file = "Cython-0.29.32-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:39afb4679b8c6bf7ccb15b24025568f4f9b4d7f9bf3cbd981021f542acecd75b"}, {file = "Cython-0.29.32-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dbee03b8d42dca924e6aa057b836a064c769ddfd2a4c2919e65da2c8a362d528"}, @@ -428,6 +826,10 @@ Cython = [ {file = "Cython-0.29.32-py2.py3-none-any.whl", hash = "sha256:eeb475eb6f0ccf6c039035eb4f0f928eb53ead88777e0a760eccb140ad90930b"}, {file = "Cython-0.29.32.tar.gz", hash = "sha256:8733cf4758b79304f2a4e39ebfac5e92341bce47bcceb26c1254398b2f8c1af7"}, ] +docutils = [ + {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, + {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, +] falcon = [ {file = "falcon-3.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:9400871fa536bac799b790ff28d999c5282c2bd7652f37f448b96718ddb289ff"}, {file = "falcon-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aee71134800f963db0b4bc086d9bf0ffc529b40e90e6a5f2cd08059d7c9ea317"}, @@ -461,6 +863,10 @@ falcon = [ {file = "falcon-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:455a6c1a46af8505b0427a1ab85924aed47c462afe4e7cb7ae58c9fd6c5cc06f"}, {file = "falcon-3.1.0.tar.gz", hash = "sha256:f2760bd18c16393a6fb5e55f371f67921edb72febe693a82b3c5e82195d087b7"}, ] +furo = [ + {file = "furo-2022.9.29-py3-none-any.whl", hash = "sha256:559ee17999c0f52728481dcf6b1b0cf8c9743e68c5e3a18cb45a7992747869a9"}, + {file = "furo-2022.9.29.tar.gz", hash = "sha256:d4238145629c623609c2deb5384f8d036e2a1ee2a101d64b67b4348112470dbd"}, +] humanize = [ {file = "humanize-3.14.0-py3-none-any.whl", hash = "sha256:32bcf712ac98ff5e73627a9d31e1ba5650619008d6d13543b5d53b48e8ab8d43"}, {file = "humanize-3.14.0.tar.gz", hash = "sha256:60dd8c952b1df1ad83f0903844dec50a34ba7a04eea22a6b14204ffb62dbb0a4"}, @@ -469,6 +875,10 @@ idna = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] +imagesize = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] importlib-metadata = [ {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, @@ -477,10 +887,75 @@ JACK-Client = [ {file = "JACK-Client-0.5.4.tar.gz", hash = "sha256:dd4a293e3a6e9bde9972569b9bc4630a5fcd4f80756cc590de572cc744e5a848"}, {file = "JACK_Client-0.5.4-py3-none-any.whl", hash = "sha256:52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73"}, ] +Jinja2 = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] +livereload = [ + {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, +] +markdown-it-py = [ + {file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"}, + {file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"}, +] +MarkupSafe = [ + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, +] +mdit-py-plugins = [ + {file = "mdit-py-plugins-0.3.1.tar.gz", hash = "sha256:3fc13298497d6e04fe96efdd41281bfe7622152f9caa1815ea99b5c893de9441"}, + {file = "mdit_py_plugins-0.3.1-py3-none-any.whl", hash = "sha256:606a7f29cf56dbdfaf914acb21709b8f8ee29d857e8f29dcc33d8cb84c57bfa1"}, +] +mdurl = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] mido = [ {file = "mido-1.2.10-py2.py3-none-any.whl", hash = "sha256:0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e"}, {file = "mido-1.2.10.tar.gz", hash = "sha256:17b38a8e4594497b850ec6e78b848eac3661706bfc49d484a36d91335a373499"}, ] +myst-parser = [ + {file = "myst-parser-0.18.1.tar.gz", hash = "sha256:79317f4bb2c13053dd6e64f9da1ba1da6cd9c40c8a430c447a7b146a594c246d"}, + {file = "myst_parser-0.18.1-py3-none-any.whl", hash = "sha256:61b275b85d9f58aa327f370913ae1bec26ebad372cc99f3ab85c8ec3ee8d9fb8"}, +] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, @@ -501,6 +976,10 @@ pycparser = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] +Pygments = [ + {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, + {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, +] PyGObject = [ {file = "PyGObject-3.42.2.tar.gz", hash = "sha256:21524cef33100c8fd59dc135948b703d79d303e368ce71fa60521cc971cd8aa7"}, ] @@ -556,18 +1035,122 @@ python-rtmidi = [ {file = "python_rtmidi-1.4.9-cp39-cp39-win32.whl", hash = "sha256:2286ab096a5603430ab1e1a664fe4d96acc40f9443f44c27e911dfad85ea3ac8"}, {file = "python_rtmidi-1.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:54d40cb794a6b079105bfb923ab0b4d5f041e9eef5dc5cce25480e4c3e3fc2f6"}, ] +pytz = [ + {file = "pytz-2022.2.1-py2.py3-none-any.whl", hash = "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197"}, + {file = "pytz-2022.2.1.tar.gz", hash = "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"}, +] +PyYAML = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] requests = [ {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, ] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +snowballstemmer = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] sortedcontainers = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, ] +soupsieve = [ + {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, + {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, +] +Sphinx = [ + {file = "Sphinx-5.2.3.tar.gz", hash = "sha256:5b10cb1022dac8c035f75767799c39217a05fc0fe2d6fe5597560d38e44f0363"}, + {file = "sphinx-5.2.3-py3-none-any.whl", hash = "sha256:7abf6fabd7b58d0727b7317d5e2650ef68765bbe0ccb63c8795fa8683477eaa2"}, +] +sphinx-autobuild = [ + {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, + {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"}, +] +sphinx-basic-ng = [ + {file = "sphinx_basic_ng-1.0.0b1-py3-none-any.whl", hash = "sha256:ade597a3029c7865b24ad0eda88318766bcc2f9f4cef60df7e28126fde94db2a"}, + {file = "sphinx_basic_ng-1.0.0b1.tar.gz", hash = "sha256:89374bd3ccd9452a301786781e28c8718e99960f2d4f411845ea75fc7bb5a9b0"}, +] +sphinx-inline-tabs = [ + {file = "sphinx_inline_tabs-2022.1.2b11-py3-none-any.whl", hash = "sha256:bb4e807769ef52301a186d0678da719120b978a1af4fd62a1e9453684e962dbc"}, + {file = "sphinx_inline_tabs-2022.1.2b11.tar.gz", hash = "sha256:afb9142772ec05ccb07f05d8181b518188fc55631b26ee803c694e812b3fdd73"}, +] +sphinxcontrib-applehelp = [ + {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, + {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, +] +sphinxcontrib-devhelp = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] +sphinxcontrib-htmlhelp = [ + {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, + {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, +] +sphinxcontrib-jsmath = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] +sphinxcontrib-qthelp = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] +sphinxcontrib-serializinghtml = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +tornado = [ + {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"}, + {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"}, + {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"}, + {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"}, + {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, +] typing-extensions = [ {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, diff --git a/pyproject.toml b/pyproject.toml index 03109aae5..b7bedf4ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,11 @@ pyliblo = {git = "https://github.com/s0600204/pyliblo.git", branch = "pip"} [tool.poetry.dev-dependencies] toml = "*" packaging = "*" +Sphinx = "^5.1.1" +furo = "^2022.6.21" +sphinx-autobuild = "^2021.3.14" +sphinx-inline-tabs = { version = "^2022.1.2-beta.11", python = "^3.8" } +myst-parser = "^0.18.0" [tool.poetry.scripts] linux-show-player = "lisp.main:main" From 6b509c7e45af1fc98d085c40ac9951f454762a71 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 8 Oct 2022 12:19:43 +0200 Subject: [PATCH 291/333] Fix: handle opening session with a relative path --- lisp/application.py | 4 ++-- lisp/core/session.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lisp/application.py b/lisp/application.py index d4d95520b..9745c35c2 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -17,7 +17,7 @@ import json import logging -from os.path import exists, dirname +from os.path import exists, dirname, abspath from PyQt5.QtWidgets import QDialog, qApp @@ -214,7 +214,7 @@ def __load_from_file(self, session_file): layout.get_layout(session_dict["session"]["layout_type"]) ) self.session.update_properties(session_dict["session"]) - self.session.session_file = session_file + self.session.session_file = abspath(session_file) # Load cues for cues_dict in session_dict.get("cues", {}): diff --git a/lisp/core/session.py b/lisp/core/session.py index a226bfb24..8fee4e620 100644 --- a/lisp/core/session.py +++ b/lisp/core/session.py @@ -27,6 +27,7 @@ class Session(HasInstanceProperties): layout = Property(default={}) session_file = Property(default="") + """The current session-file path, must be absolute.""" def __init__(self, layout): super().__init__() From 73af89a376a7474c5212a2c9de0079270fa84751 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 8 Oct 2022 14:26:24 +0200 Subject: [PATCH 292/333] Add markdown support in cue description (Qt >= 5.14) Update: (ListLayout) Improve cue description visibility --- lisp/i18n/ts/en/lisp.ts | 18 +++++++++--------- lisp/i18n/ts/en/list_layout.ts | 7 +------ lisp/plugins/list_layout/info_panel.py | 12 ++++++++---- lisp/ui/settings/cue_pages/cue_appearance.py | 5 ++++- lisp/ui/themes/dark/theme.qss | 6 +++++- 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/lisp/i18n/ts/en/lisp.ts b/lisp/i18n/ts/en/lisp.ts index 790650b77..389da5b5e 100644 --- a/lisp/i18n/ts/en/lisp.ts +++ b/lisp/i18n/ts/en/lisp.ts @@ -247,42 +247,42 @@ CueAppearanceSettings - + The appearance depends on the layout The appearance depends on the layout - + Cue name Cue name - + NoName NoName - + Description/Note Description/Note - + Set Font Size Set Font Size - + Color Color - + Select background color Select background color - + Select font color Select font color @@ -974,7 +974,7 @@ Fade In/Out - + Appearance Appearance diff --git a/lisp/i18n/ts/en/list_layout.ts b/lisp/i18n/ts/en/list_layout.ts index 0ef3a9d96..2e06206a4 100644 --- a/lisp/i18n/ts/en/list_layout.ts +++ b/lisp/i18n/ts/en/list_layout.ts @@ -239,15 +239,10 @@ ListLayoutInfoPanel - + Cue name - - - Cue description - - SettingsPageName diff --git a/lisp/plugins/list_layout/info_panel.py b/lisp/plugins/list_layout/info_panel.py index a71239874..7b04b2ea3 100644 --- a/lisp/plugins/list_layout/info_panel.py +++ b/lisp/plugins/list_layout/info_panel.py @@ -16,6 +16,7 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt +from PyQt5.QtGui import QTextDocument from PyQt5.QtWidgets import QWidget, QTextEdit, QLineEdit, QVBoxLayout from lisp.ui.ui_utils import translate @@ -48,9 +49,6 @@ def retranslateUi(self): self.cueName.setPlaceholderText( translate("ListLayoutInfoPanel", "Cue name") ) - self.cueDescription.setPlaceholderText( - translate("ListLayoutInfoPanel", "Cue description") - ) @property def cue(self): @@ -78,4 +76,10 @@ def _name_changed(self, name): self.cueName.setText(str(self.cue.index + 1) + " → " + name) def _desc_changed(self, description): - self.cueDescription.setText(description) + if hasattr(QTextDocument, 'setMarkdown'): + self.cueDescription.document().setMarkdown(description) + else: + self.cueDescription.setText(description) + + self.cueDescription.setProperty('empty', len(description) == 0) + self.cueDescription.style().polish(self.cueDescription) diff --git a/lisp/ui/settings/cue_pages/cue_appearance.py b/lisp/ui/settings/cue_pages/cue_appearance.py index 372c1406e..6a623c718 100644 --- a/lisp/ui/settings/cue_pages/cue_appearance.py +++ b/lisp/ui/settings/cue_pages/cue_appearance.py @@ -16,6 +16,7 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP +from PyQt5.QtGui import QFontDatabase from PyQt5.QtWidgets import ( QVBoxLayout, QGroupBox, @@ -52,6 +53,8 @@ def __init__(self, **kwargs): self.layout().addWidget(self.cueDescriptionGroup) self.cueDescriptionEdit = QTextEdit(self.cueDescriptionGroup) + self.cueDescriptionEdit.setAcceptRichText(False) + self.cueDescriptionEdit.setFont(QFontDatabase.systemFont(QFontDatabase.FixedFont)) self.cueDescriptionGroup.layout().addWidget(self.cueDescriptionEdit) # Font @@ -137,7 +140,7 @@ def loadSettings(self, settings): if "name" in settings: self.cueNameEdit.setText(settings["name"]) if "description" in settings: - self.cueDescriptionEdit.setText(settings["description"]) + self.cueDescriptionEdit.setPlainText(settings["description"]) if "stylesheet" in settings: settings = css_to_dict(settings["stylesheet"]) if "background" in settings: diff --git a/lisp/ui/themes/dark/theme.qss b/lisp/ui/themes/dark/theme.qss index bce8ba851..889683293 100644 --- a/lisp/ui/themes/dark/theme.qss +++ b/lisp/ui/themes/dark/theme.qss @@ -771,7 +771,11 @@ RunningCueWidget>QWidget { background-color: #CC0000; } -#InfoPanelDescription[readOnly="true"] { +#InfoPanelDescription[empty="false"] { + background: palette(mid); +} + +#InfoPanelDescription { border: 1px solid palette(light); } From 263718b62e062d062a4a77ee352844f30610f7eb Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 8 Oct 2022 14:58:21 +0200 Subject: [PATCH 293/333] Fix: case-insensitive match for excluded packages --- scripts/flatpak/poetry-flatpak.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/flatpak/poetry-flatpak.py b/scripts/flatpak/poetry-flatpak.py index 2a4f8d1f7..305d4df38 100755 --- a/scripts/flatpak/poetry-flatpak.py +++ b/scripts/flatpak/poetry-flatpak.py @@ -97,7 +97,7 @@ def get_locked_packages(parsed_lockfile: Mapping, exclude=tuple()) -> list: if ( package.get("category") == "main" and package.get("source") is None - and package.get("name") not in exclude + and package.get("name").lower() not in exclude ): dependencies.append(package) From 3fc0ac85584d9a42c4ef07ff1abdffc2a9bc7223 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 8 Oct 2022 14:59:22 +0200 Subject: [PATCH 294/333] Update flatpak modules --- ...org.linux_show_player.LinuxShowPlayer.json | 14 +++++ scripts/flatpak/python-modules.json | 63 +++++++++---------- 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json b/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json index 686c7d22e..10b02a290 100644 --- a/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json +++ b/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json @@ -72,6 +72,20 @@ } ] }, + { + "name": "python-liblo", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --no-build-isolation --prefix=\"${FLATPAK_DEST}\" ." + ], + "sources": [ + { + "type": "archive", + "url": "https://files.pythonhosted.org/packages/c0/da/4b8052ab5aa07952d7fe7b068d737a042e76373573eadd340f7550eddea9/pyliblo-0.10.0.tar.gz", + "sha256": "fc67f1950b827272b00f9f0dc4ed7113c0ccef0c1c09e9976dead40ebbf1798f" + } + ] + }, { "name": "protobuf", "config-opts": [ diff --git a/scripts/flatpak/python-modules.json b/scripts/flatpak/python-modules.json index 0b81e17d2..ee85c7590 100644 --- a/scripts/flatpak/python-modules.json +++ b/scripts/flatpak/python-modules.json @@ -2,13 +2,13 @@ "name": "python-modules", "buildsystem": "simple", "build-commands": [ - "pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} appdirs certifi cffi charset-normalizer falcon humanize idna importlib-metadata jack-client mido pycparser pyliblo python-rtmidi requests sortedcontainers typing-extensions urllib3 zipp" + "pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} appdirs certifi cffi charset-normalizer falcon humanize idna importlib-metadata JACK-Client mido pycparser python-rtmidi requests sortedcontainers typing-extensions urllib3 zipp" ], "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/47/84/b06f6729fac8108c5fa3e13cde19b0b3de66ba5538c325496dbe39f5ff8e/charset_normalizer-2.0.9-py3-none-any.whl", - "sha256": "1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721" + "url": "https://files.pythonhosted.org/packages/17/41/de1269065ff0d1bda143cc91b245aef3165d9259e27904a4a59eab081e0b/JACK_Client-0.5.4-py3-none-any.whl", + "sha256": "52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73" }, { "type": "file", @@ -17,33 +17,33 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/37/45/946c02767aabb873146011e665728b680884cd8fe70dde973c640e45b775/certifi-2021.10.8-py2.py3-none-any.whl", - "sha256": "d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" + "url": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", + "sha256": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/b5/6d/e18a5b59ff086e1cd61d7fbf943d86c5f593a4e68bfc60215ab74210b22b/mido-1.2.10-py2.py3-none-any.whl", - "sha256": "0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e" + "url": "https://files.pythonhosted.org/packages/1d/38/fa96a426e0c0e68aabc68e896584b83ad1eec779265a028e156ce509630e/certifi-2022.9.24-py3-none-any.whl", + "sha256": "90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/04/a2/d918dcd22354d8958fe113e1a3630137e0fc8b44859ade3063982eacd2a4/idna-3.3-py3-none-any.whl", - "sha256": "84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff" + "url": "https://files.pythonhosted.org/packages/a6/35/2b1bcfaba1d9e65780f3833fde53f13359a9b8bc7b4d4e7c23135366b589/humanize-3.14.0-py3-none-any.whl", + "sha256": "32bcf712ac98ff5e73627a9d31e1ba5650619008d6d13543b5d53b48e8ab8d43" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/71/0a/faaa9d50705d43f4411bc8a16aaf5189d27a0cf9b7b43ede91f2cb92de97/JACK_Client-0.5.3-py3-none-any.whl", - "sha256": "0214d9366e0a65bf0a99866990aae5a443afb3c283006d236d8cebcb769a59b8" + "url": "https://files.pythonhosted.org/packages/b5/6d/e18a5b59ff086e1cd61d7fbf943d86c5f593a4e68bfc60215ab74210b22b/mido-1.2.10-py2.py3-none-any.whl", + "sha256": "0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/46/a8/74f8bf12c7d93bb2e111d13bae198996032d9a53dbbfc3bf4837a466cbe9/humanize-3.13.1-py3-none-any.whl", - "sha256": "a6f7cc1597db69a4e571ad5e19b4da07ee871da5a9de2b233dbfab02d98e9754" + "url": "https://files.pythonhosted.org/packages/db/51/a507c856293ab05cdc1db77ff4bc1268ddd39f29e7dc4919aa497f0adbec/charset_normalizer-2.1.1-py3-none-any.whl", + "sha256": "83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/c4/1f/e2238896149df09953efcc53bdcc7d23597d6c53e428c30e572eda5ec6eb/importlib_metadata-4.8.2-py3-none-any.whl", - "sha256": "53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100" + "url": "https://files.pythonhosted.org/packages/d2/a2/8c239dc898138f208dd14b441b196e7b3032b94d3137d9d8453e186967fc/importlib_metadata-4.12.0-py3-none-any.whl", + "sha256": "7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23" }, { "type": "file", @@ -52,28 +52,28 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/c0/da/4b8052ab5aa07952d7fe7b068d737a042e76373573eadd340f7550eddea9/pyliblo-0.10.0.tar.gz", - "sha256": "fc67f1950b827272b00f9f0dc4ed7113c0ccef0c1c09e9976dead40ebbf1798f" + "url": "https://files.pythonhosted.org/packages/36/53/4fd90c6c841bc2e4be29ab92c65e5406df9096c421f138bef9d95d43afc9/falcon-3.1.0.tar.gz", + "sha256": "f2760bd18c16393a6fb5e55f371f67921edb72febe693a82b3c5e82195d087b7" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/00/9e/92de7e1217ccc3d5f352ba21e52398372525765b2e0c4530e6eb2ba9282a/cffi-1.15.0.tar.gz", - "sha256": "920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954" + "url": "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", + "sha256": "a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/63/22/6a9009c53ad78e65d88a44db8eccc7f39c6f54fc05fb43b1e9cbbc481d06/falcon-3.0.1.tar.gz", - "sha256": "c41d84db325881a870e8b7129d5ecfd972fa4323cf77b7119a1d2a21966ee681" + "url": "https://files.pythonhosted.org/packages/ed/d6/2afc375a8d55b8be879d6b4986d4f69f01115e795e36827fd3a40166028b/typing_extensions-4.3.0-py3-none-any.whl", + "sha256": "25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/05/e4/baf0031e39cf545f0c9edd5b1a2ea12609b7fcba2d58e118b11753d68cf0/typing_extensions-4.0.1-py3-none-any.whl", - "sha256": "7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" + "url": "https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c0441b5cda699c90f618b6f8a1b42/requests-2.28.1-py3-none-any.whl", + "sha256": "8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", - "sha256": "a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" + "url": "https://files.pythonhosted.org/packages/f0/36/639d6742bcc3ffdce8b85c31d79fcfae7bb04b95f0e5c4c6f8b206a038cc/zipp-3.8.1-py3-none-any.whl", + "sha256": "47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009" }, { "type": "file", @@ -82,18 +82,13 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/bd/df/d4a4974a3e3957fd1c1fa3082366d7fff6e428ddb55f074bf64876f8e8ad/zipp-3.6.0-py3-none-any.whl", - "sha256": "9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc" - }, - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/92/96/144f70b972a9c0eabbd4391ef93ccd49d0f2747f4f6a2a2738e99e5adc65/requests-2.26.0-py2.py3-none-any.whl", - "sha256": "6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24" + "url": "https://files.pythonhosted.org/packages/6f/de/5be2e3eed8426f871b170663333a0f627fc2924cc386cd41be065e7ea870/urllib3-1.26.12-py2.py3-none-any.whl", + "sha256": "b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/af/f4/524415c0744552cce7d8bf3669af78e8a069514405ea4fcbd0cc44733744/urllib3-1.26.7-py2.py3-none-any.whl", - "sha256": "c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" + "url": "https://files.pythonhosted.org/packages/2b/a8/050ab4f0c3d4c1b8aaa805f70e26e84d0e27004907c5b8ecc1d31815f92a/cffi-1.15.1.tar.gz", + "sha256": "d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9" } ] } \ No newline at end of file From 9a4f0b3397b752d23866246f4ddf4134cef902a6 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 8 Oct 2022 15:17:41 +0200 Subject: [PATCH 295/333] Use official Flatpak PPA in CI builds --- .circleci/config.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 23ff8ad50..e2778624d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,13 +17,10 @@ jobs: name: SYSTEM ⟹ Install required packages command: | sudo apt-get -qq update - sudo apt-get install -y \ - software-properties-common \ - python3-software-properties \ - golang \ - ostree \ - flatpak \ - flatpak-builder + sudo apt-get install -y software-properties-common python3-software-properties + sudo add-apt-repository -y ppa:flatpak/stable + sudo apt-get -qq update + sudo apt-get install -y golang ostree flatpak flatpak-builder - run: name: FLATPAK ⟹ Install flathub Runtime & SDK From fc8811ce18294f370a3edc6e99fe7b595051e661 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 9 Oct 2022 18:52:23 +0200 Subject: [PATCH 296/333] Update runtime, install "python-modules" without "build-isolation" --- scripts/flatpak/config.sh | 2 +- scripts/flatpak/poetry-flatpak.py | 1 + scripts/flatpak/python-modules.json | 54 ++++++++++++++--------------- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/scripts/flatpak/config.sh b/scripts/flatpak/config.sh index cf0be9ce3..f43f4e801 100755 --- a/scripts/flatpak/config.sh +++ b/scripts/flatpak/config.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash export FLATPAK_RUNTIME="org.kde.Platform" -export FLATPAK_RUNTIME_VERSION="5.15-21.08" +export FLATPAK_RUNTIME_VERSION="5.15-22.08" export FLATPAK_SDK="org.kde.Sdk" export FLATPAK_INSTALL="$FLATPAK_RUNTIME//$FLATPAK_RUNTIME_VERSION $FLATPAK_SDK//$FLATPAK_RUNTIME_VERSION" diff --git a/scripts/flatpak/poetry-flatpak.py b/scripts/flatpak/poetry-flatpak.py index 305d4df38..eccc4ca12 100755 --- a/scripts/flatpak/poetry-flatpak.py +++ b/scripts/flatpak/poetry-flatpak.py @@ -119,6 +119,7 @@ def main(): "pip3", "install", "--no-index", + "--no-build-isolation", '--find-links="file://${PWD}"', "--prefix=${FLATPAK_DEST}", " ".join([d["name"] for d in dependencies]), diff --git a/scripts/flatpak/python-modules.json b/scripts/flatpak/python-modules.json index ee85c7590..418ba8053 100644 --- a/scripts/flatpak/python-modules.json +++ b/scripts/flatpak/python-modules.json @@ -2,24 +2,14 @@ "name": "python-modules", "buildsystem": "simple", "build-commands": [ - "pip3 install --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} appdirs certifi cffi charset-normalizer falcon humanize idna importlib-metadata JACK-Client mido pycparser python-rtmidi requests sortedcontainers typing-extensions urllib3 zipp" + "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} appdirs certifi cffi charset-normalizer falcon humanize idna importlib-metadata JACK-Client mido pycparser python-rtmidi requests sortedcontainers typing-extensions urllib3 zipp" ], "sources": [ - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/17/41/de1269065ff0d1bda143cc91b245aef3165d9259e27904a4a59eab081e0b/JACK_Client-0.5.4-py3-none-any.whl", - "sha256": "52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73" - }, { "type": "file", "url": "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", "sha256": "a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" }, - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", - "sha256": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" - }, { "type": "file", "url": "https://files.pythonhosted.org/packages/1d/38/fa96a426e0c0e68aabc68e896584b83ad1eec779265a028e156ce509630e/certifi-2022.9.24-py3-none-any.whl", @@ -27,18 +17,8 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/a6/35/2b1bcfaba1d9e65780f3833fde53f13359a9b8bc7b4d4e7c23135366b589/humanize-3.14.0-py3-none-any.whl", - "sha256": "32bcf712ac98ff5e73627a9d31e1ba5650619008d6d13543b5d53b48e8ab8d43" - }, - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/b5/6d/e18a5b59ff086e1cd61d7fbf943d86c5f593a4e68bfc60215ab74210b22b/mido-1.2.10-py2.py3-none-any.whl", - "sha256": "0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e" - }, - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/db/51/a507c856293ab05cdc1db77ff4bc1268ddd39f29e7dc4919aa497f0adbec/charset_normalizer-2.1.1-py3-none-any.whl", - "sha256": "83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" + "url": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", + "sha256": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" }, { "type": "file", @@ -52,14 +32,24 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/36/53/4fd90c6c841bc2e4be29ab92c65e5406df9096c421f138bef9d95d43afc9/falcon-3.1.0.tar.gz", - "sha256": "f2760bd18c16393a6fb5e55f371f67921edb72febe693a82b3c5e82195d087b7" + "url": "https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c0441b5cda699c90f618b6f8a1b42/requests-2.28.1-py3-none-any.whl", + "sha256": "8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", "sha256": "a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/17/41/de1269065ff0d1bda143cc91b245aef3165d9259e27904a4a59eab081e0b/JACK_Client-0.5.4-py3-none-any.whl", + "sha256": "52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/b5/6d/e18a5b59ff086e1cd61d7fbf943d86c5f593a4e68bfc60215ab74210b22b/mido-1.2.10-py2.py3-none-any.whl", + "sha256": "0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e" + }, { "type": "file", "url": "https://files.pythonhosted.org/packages/ed/d6/2afc375a8d55b8be879d6b4986d4f69f01115e795e36827fd3a40166028b/typing_extensions-4.3.0-py3-none-any.whl", @@ -67,8 +57,13 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c0441b5cda699c90f618b6f8a1b42/requests-2.28.1-py3-none-any.whl", - "sha256": "8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" + "url": "https://files.pythonhosted.org/packages/a6/35/2b1bcfaba1d9e65780f3833fde53f13359a9b8bc7b4d4e7c23135366b589/humanize-3.14.0-py3-none-any.whl", + "sha256": "32bcf712ac98ff5e73627a9d31e1ba5650619008d6d13543b5d53b48e8ab8d43" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/db/51/a507c856293ab05cdc1db77ff4bc1268ddd39f29e7dc4919aa497f0adbec/charset_normalizer-2.1.1-py3-none-any.whl", + "sha256": "83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" }, { "type": "file", @@ -80,6 +75,11 @@ "url": "https://files.pythonhosted.org/packages/92/bf/749468bc43f85ec77f37154327360ba82e7d0ae622341eab44a6d75751c3/python-rtmidi-1.4.9.tar.gz", "sha256": "bfeb4ed99d0cccf6fa2837566907652ded7adc1c03b69f2160c9de4082301302" }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/36/53/4fd90c6c841bc2e4be29ab92c65e5406df9096c421f138bef9d95d43afc9/falcon-3.1.0.tar.gz", + "sha256": "f2760bd18c16393a6fb5e55f371f67921edb72febe693a82b3c5e82195d087b7" + }, { "type": "file", "url": "https://files.pythonhosted.org/packages/6f/de/5be2e3eed8426f871b170663333a0f627fc2924cc386cd41be065e7ea870/urllib3-1.26.12-py2.py3-none-any.whl", From 145e39085a1e841eff9566f1b27d95b95226ea1e Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 9 Oct 2022 19:59:10 +0200 Subject: [PATCH 297/333] Revert runtime to 5.15-21.08, update pyalasa --- scripts/flatpak/config.sh | 2 +- ...org.linux_show_player.LinuxShowPlayer.json | 4 +- scripts/flatpak/python-modules.json | 50 +++++++++---------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/scripts/flatpak/config.sh b/scripts/flatpak/config.sh index f43f4e801..cf0be9ce3 100755 --- a/scripts/flatpak/config.sh +++ b/scripts/flatpak/config.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash export FLATPAK_RUNTIME="org.kde.Platform" -export FLATPAK_RUNTIME_VERSION="5.15-22.08" +export FLATPAK_RUNTIME_VERSION="5.15-21.08" export FLATPAK_SDK="org.kde.Sdk" export FLATPAK_INSTALL="$FLATPAK_RUNTIME//$FLATPAK_RUNTIME_VERSION $FLATPAK_SDK//$FLATPAK_RUNTIME_VERSION" diff --git a/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json b/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json index 10b02a290..1e4642e1d 100644 --- a/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json +++ b/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json @@ -152,8 +152,8 @@ "sources": [ { "type": "archive", - "url": "ftp://ftp.alsa-project.org/pub/pyalsa/pyalsa-1.1.6.tar.bz2", - "sha256": "2771291a5d2cf700f0abbe6629ea37468d1463a01b2330d84ef976e1e918676c" + "url": "ftp://ftp.alsa-project.org/pub/pyalsa/pyalsa-1.2.7.tar.bz2", + "sha256": "67d8f99f1ccf23e7ebdfa2c3be0b09f147949fd9511d1e6748b7f412ef046e1f" } ] }, diff --git a/scripts/flatpak/python-modules.json b/scripts/flatpak/python-modules.json index 418ba8053..39321eaf3 100644 --- a/scripts/flatpak/python-modules.json +++ b/scripts/flatpak/python-modules.json @@ -10,6 +10,11 @@ "url": "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", "sha256": "a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", + "sha256": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + }, { "type": "file", "url": "https://files.pythonhosted.org/packages/1d/38/fa96a426e0c0e68aabc68e896584b83ad1eec779265a028e156ce509630e/certifi-2022.9.24-py3-none-any.whl", @@ -17,53 +22,53 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", - "sha256": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + "url": "https://files.pythonhosted.org/packages/17/41/de1269065ff0d1bda143cc91b245aef3165d9259e27904a4a59eab081e0b/JACK_Client-0.5.4-py3-none-any.whl", + "sha256": "52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/d2/a2/8c239dc898138f208dd14b441b196e7b3032b94d3137d9d8453e186967fc/importlib_metadata-4.12.0-py3-none-any.whl", - "sha256": "7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23" + "url": "https://files.pythonhosted.org/packages/b5/6d/e18a5b59ff086e1cd61d7fbf943d86c5f593a4e68bfc60215ab74210b22b/mido-1.2.10-py2.py3-none-any.whl", + "sha256": "0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", - "sha256": "8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9" + "url": "https://files.pythonhosted.org/packages/a6/35/2b1bcfaba1d9e65780f3833fde53f13359a9b8bc7b4d4e7c23135366b589/humanize-3.14.0-py3-none-any.whl", + "sha256": "32bcf712ac98ff5e73627a9d31e1ba5650619008d6d13543b5d53b48e8ab8d43" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c0441b5cda699c90f618b6f8a1b42/requests-2.28.1-py3-none-any.whl", - "sha256": "8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" + "url": "https://files.pythonhosted.org/packages/d2/a2/8c239dc898138f208dd14b441b196e7b3032b94d3137d9d8453e186967fc/importlib_metadata-4.12.0-py3-none-any.whl", + "sha256": "7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", - "sha256": "a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" + "url": "https://files.pythonhosted.org/packages/db/51/a507c856293ab05cdc1db77ff4bc1268ddd39f29e7dc4919aa497f0adbec/charset_normalizer-2.1.1-py3-none-any.whl", + "sha256": "83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/17/41/de1269065ff0d1bda143cc91b245aef3165d9259e27904a4a59eab081e0b/JACK_Client-0.5.4-py3-none-any.whl", - "sha256": "52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73" + "url": "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", + "sha256": "8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/b5/6d/e18a5b59ff086e1cd61d7fbf943d86c5f593a4e68bfc60215ab74210b22b/mido-1.2.10-py2.py3-none-any.whl", - "sha256": "0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e" + "url": "https://files.pythonhosted.org/packages/36/53/4fd90c6c841bc2e4be29ab92c65e5406df9096c421f138bef9d95d43afc9/falcon-3.1.0.tar.gz", + "sha256": "f2760bd18c16393a6fb5e55f371f67921edb72febe693a82b3c5e82195d087b7" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/ed/d6/2afc375a8d55b8be879d6b4986d4f69f01115e795e36827fd3a40166028b/typing_extensions-4.3.0-py3-none-any.whl", - "sha256": "25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02" + "url": "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", + "sha256": "a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/a6/35/2b1bcfaba1d9e65780f3833fde53f13359a9b8bc7b4d4e7c23135366b589/humanize-3.14.0-py3-none-any.whl", - "sha256": "32bcf712ac98ff5e73627a9d31e1ba5650619008d6d13543b5d53b48e8ab8d43" + "url": "https://files.pythonhosted.org/packages/ed/d6/2afc375a8d55b8be879d6b4986d4f69f01115e795e36827fd3a40166028b/typing_extensions-4.3.0-py3-none-any.whl", + "sha256": "25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/db/51/a507c856293ab05cdc1db77ff4bc1268ddd39f29e7dc4919aa497f0adbec/charset_normalizer-2.1.1-py3-none-any.whl", - "sha256": "83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" + "url": "https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c0441b5cda699c90f618b6f8a1b42/requests-2.28.1-py3-none-any.whl", + "sha256": "8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" }, { "type": "file", @@ -75,11 +80,6 @@ "url": "https://files.pythonhosted.org/packages/92/bf/749468bc43f85ec77f37154327360ba82e7d0ae622341eab44a6d75751c3/python-rtmidi-1.4.9.tar.gz", "sha256": "bfeb4ed99d0cccf6fa2837566907652ded7adc1c03b69f2160c9de4082301302" }, - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/36/53/4fd90c6c841bc2e4be29ab92c65e5406df9096c421f138bef9d95d43afc9/falcon-3.1.0.tar.gz", - "sha256": "f2760bd18c16393a6fb5e55f371f67921edb72febe693a82b3c5e82195d087b7" - }, { "type": "file", "url": "https://files.pythonhosted.org/packages/6f/de/5be2e3eed8426f871b170663333a0f627fc2924cc386cd41be065e7ea870/urllib3-1.26.12-py2.py3-none-any.whl", From 733b20ad81b66121f6ed93be470330b9a8c10e26 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 9 Oct 2022 20:54:22 +0200 Subject: [PATCH 298/333] Use fixed "ghr" version in circleci --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e2778624d..173e9d951 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -85,7 +85,7 @@ jobs: mv out/linux-show-player.flatpak "out/$ARTIFACT_NAME" # Download helper for github releases - go get github.com/tcnksm/ghr + go get github.com/tcnksm/ghr@v0.15.0 # Upload the artifacts ghr \ From 9a63cc246375aa9157d07011a1b606034a48a93f Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 14 Oct 2022 00:19:42 +0200 Subject: [PATCH 299/333] Use binary version of "ghr" in circleci --- .circleci/config.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 173e9d951..bc6f07d60 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,7 +20,7 @@ jobs: sudo apt-get install -y software-properties-common python3-software-properties sudo add-apt-repository -y ppa:flatpak/stable sudo apt-get -qq update - sudo apt-get install -y golang ostree flatpak flatpak-builder + sudo apt-get install -y ostree flatpak flatpak-builder - run: name: FLATPAK ⟹ Install flathub Runtime & SDK @@ -85,10 +85,11 @@ jobs: mv out/linux-show-player.flatpak "out/$ARTIFACT_NAME" # Download helper for github releases - go get github.com/tcnksm/ghr@v0.15.0 + curl -O -L https://github.com/tcnksm/ghr/releases/download/v0.16.0/ghr_v0.16.0_linux_amd64.tar.gz + tar xvzf ghr_v0.16.0_linux_amd64.tar.gz --wildcards */ghr --strip-components=1 # Upload the artifacts - ghr \ + ./ghr \ -n "Automated build ($CIRCLE_BRANCH)" \ -b "Build number: $CIRCLE_BUILD_NUM
    Completed at: $(date)" \ -c $CIRCLE_SHA1 \ From 5ef08a492b8c7964157f8170e0cf0215392e5a2b Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 16 Oct 2022 23:26:12 +0200 Subject: [PATCH 300/333] Skip "optional" packages --- scripts/flatpak/poetry-flatpak.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/flatpak/poetry-flatpak.py b/scripts/flatpak/poetry-flatpak.py index eccc4ca12..41ef7286b 100755 --- a/scripts/flatpak/poetry-flatpak.py +++ b/scripts/flatpak/poetry-flatpak.py @@ -96,6 +96,7 @@ def get_locked_packages(parsed_lockfile: Mapping, exclude=tuple()) -> list: for package in packages: if ( package.get("category") == "main" + and not package.get("optional") and package.get("source") is None and package.get("name").lower() not in exclude ): From c642ab44e7f967a0d7d4c0e0fb4a443d8c5b9702 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 16 Oct 2022 23:29:31 +0200 Subject: [PATCH 301/333] Update docs, pyproject and dependencies --- docs/user/source/conf.py | 1 - docs/user/source/menus.md | 10 +- poetry.lock | 258 +++++++++++++--------------- pyproject.toml | 50 +++--- scripts/flatpak/python-modules.json | 46 +++-- 5 files changed, 178 insertions(+), 187 deletions(-) diff --git a/docs/user/source/conf.py b/docs/user/source/conf.py index 8ee39d058..3c3d70fa1 100644 --- a/docs/user/source/conf.py +++ b/docs/user/source/conf.py @@ -31,7 +31,6 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - "sphinx_inline_tabs", "myst_parser" ] diff --git a/docs/user/source/menus.md b/docs/user/source/menus.md index 201e532e8..12cb36d98 100644 --- a/docs/user/source/menus.md +++ b/docs/user/source/menus.md @@ -39,7 +39,7 @@ Add/Edit cues (accessible right-clicking on empty areas of the layout) This menu give access to layout functionality and display options for the current view. -```{tab} Cart Layout +#### Cart Layout * **Add page/Add pages:** Allows to add one or multiple pages on the current layout. Pages can be switched using the tab bar on top of the layout or directional keys. * **Remove current page:** Remove the current page and all its cues @@ -49,9 +49,7 @@ This menu give access to layout functionality and display options for the curren * **Show volume:** Media cues will display a volume control on their right side. Setting the volume to the middle point (50%) of the slider sets the volume to +0dB. * **Show accurate time:** When checked, cues will display play time with a precision of 0.1s. When unchecked the time is only precise down to 1s. -``` - -```{tab} List Layout +#### List Layout * **Show dB-meters:** Show / hide the db-meters for running media-cues; * **Show seek-bars:** Show / hide seek bars for running media-cues; @@ -61,9 +59,7 @@ This menu give access to layout functionality and display options for the curren * **Selection mode:** ``[CTRL+SHIFT+E]`` Enable multi-selection (for editing); * **Disable GO Key while playing:** Disable the "GO" keyboard shortcut while there are playing cues; * **Show resize handles:** Enable handles that allow to customize the size of various panels; -* **Restore default size:** Reset the size of panels to their defaults. - -``` +* **Restore default size:** Reset the size of panels to their defaults. ## Tools diff --git a/poetry.lock b/poetry.lock index 3dca915ad..87a82bd4f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,8 +2,8 @@ name = "alabaster" version = "0.7.12" description = "A configurable sidebar-enabled Sphinx theme" -category = "dev" -optional = false +category = "main" +optional = true python-versions = "*" [[package]] @@ -15,11 +15,11 @@ optional = false python-versions = "*" [[package]] -name = "Babel" +name = "babel" version = "2.10.3" description = "Internationalization utilities" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.6" [package.dependencies] @@ -29,8 +29,8 @@ pytz = ">=2015.7" name = "beautifulsoup4" version = "4.11.1" description = "Screen-scraping library" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.6.0" [package.dependencies] @@ -68,18 +68,18 @@ optional = false python-versions = ">=3.6.0" [package.extras] -unicode_backport = ["unicodedata2"] +unicode-backport = ["unicodedata2"] [[package]] name = "colorama" version = "0.4.5" description = "Cross-platform colored terminal text." -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] -name = "Cython" +name = "cython" version = "0.29.32" description = "The Cython compiler for writing C extensions for the Python language." category = "main" @@ -90,8 +90,8 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" name = "docutils" version = "0.19" description = "Docutils -- Python Documentation Utilities" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.7" [[package]] @@ -106,8 +106,8 @@ python-versions = ">=3.5" name = "furo" version = "2022.9.29" description = "A clean customisable Sphinx documentation theme." -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.7" [package.dependencies] @@ -118,11 +118,11 @@ sphinx-basic-ng = "*" [[package]] name = "humanize" -version = "3.14.0" +version = "4.4.0" description = "Python humanize utilities" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} @@ -142,13 +142,13 @@ python-versions = ">=3.5" name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.12.0" +version = "5.0.0" description = "Read metadata from Python packages" category = "main" optional = false @@ -159,12 +159,12 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] -name = "JACK-Client" +name = "jack-client" version = "0.5.4" description = "JACK Audio Connection Kit (JACK) Client for Python" category = "main" @@ -178,11 +178,11 @@ CFFI = ">=1.0" numpy = ["NumPy"] [[package]] -name = "Jinja2" +name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.7" [package.dependencies] @@ -195,8 +195,8 @@ i18n = ["Babel (>=2.7)"] name = "livereload" version = "2.6.3" description = "Python LiveReload is an awesome tool for web developers" -category = "dev" -optional = false +category = "main" +optional = true python-versions = "*" [package.dependencies] @@ -207,8 +207,8 @@ tornado = {version = "*", markers = "python_version > \"2.7\""} name = "markdown-it-py" version = "2.1.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.7" [package.dependencies] @@ -217,7 +217,7 @@ typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [package.extras] benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"] -code_style = ["pre-commit (==2.6)"] +code-style = ["pre-commit (==2.6)"] compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"] linkify = ["linkify-it-py (>=1.0,<2.0)"] plugins = ["mdit-py-plugins"] @@ -226,26 +226,26 @@ rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx- testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] -name = "MarkupSafe" +name = "markupsafe" version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.7" [[package]] name = "mdit-py-plugins" version = "0.3.1" description = "Collection of plugins for markdown-it-py" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.7" [package.dependencies] markdown-it-py = ">=1.0.0,<3.0.0" [package.extras] -code_style = ["pre-commit"] +code-style = ["pre-commit"] rtd = ["attrs", "myst-parser (>=0.16.1,<0.17.0)", "sphinx-book-theme (>=0.1.0,<0.2.0)"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] @@ -253,8 +253,8 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.7" [[package]] @@ -273,8 +273,8 @@ ports = ["python-rtmidi (>=1.1.0)"] name = "myst-parser" version = "0.18.1" description = "An extended commonmark compliant parser, with bridges to docutils & sphinx." -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.7" [package.dependencies] @@ -287,7 +287,7 @@ sphinx = ">=4,<6" typing-extensions = "*" [package.extras] -code_style = ["pre-commit (>=2.12,<3.0)"] +code-style = ["pre-commit (>=2.12,<3.0)"] linkify = ["linkify-it-py (>=1.0,<2.0)"] rtd = ["ipython", "sphinx-book-theme", "sphinx-design", "sphinxcontrib.mermaid (>=0.7.1,<0.8.0)", "sphinxext-opengraph (>=0.6.3,<0.7.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions", "sphinx (<5.2)", "sphinx-pytest"] @@ -296,7 +296,7 @@ testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", name = "packaging" version = "21.3" description = "Core utilities for Python packages" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -305,7 +305,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pyalsa" -version = "1.2.6" +version = "1.2.7" description = "Python binding for the ALSA library." category = "main" optional = false @@ -315,8 +315,8 @@ develop = false [package.source] type = "git" url = "https://github.com/alsa-project/alsa-python.git" -reference = "v1.2.6" -resolved_reference = "6919608781a273decd1937accf36c79ab4ae1f0e" +reference = "v1.2.7" +resolved_reference = "7d6dfe0794d250190a678312a2903cb28d46622b" [[package]] name = "pycairo" @@ -335,18 +335,18 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] -name = "Pygments" +name = "pygments" version = "2.13.0" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.6" [package.extras] plugins = ["importlib-metadata"] [[package]] -name = "PyGObject" +name = "pygobject" version = "3.42.2" description = "Python bindings for GObject Introspection" category = "main" @@ -378,7 +378,7 @@ resolved_reference = "5cef301f234cab5d39a416462381f83f840acec5" name = "pyparsing" version = "3.0.9" description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "dev" +category = "main" optional = false python-versions = ">=3.6.8" @@ -386,7 +386,7 @@ python-versions = ">=3.6.8" diagrams = ["jinja2", "railroad-diagrams"] [[package]] -name = "PyQt5" +name = "pyqt5" version = "5.15.7" description = "Python bindings for the Qt cross platform application toolkit" category = "main" @@ -398,7 +398,7 @@ PyQt5-Qt5 = ">=5.15.0" PyQt5-sip = ">=12.11,<13" [[package]] -name = "PyQt5-Qt5" +name = "pyqt5-qt5" version = "5.15.2" description = "The subset of a Qt installation needed by PyQt5." category = "main" @@ -406,7 +406,7 @@ optional = false python-versions = "*" [[package]] -name = "PyQt5-sip" +name = "pyqt5-sip" version = "12.11.0" description = "The sip module support for PyQt5" category = "main" @@ -423,18 +423,18 @@ python-versions = "*" [[package]] name = "pytz" -version = "2022.2.1" +version = "2022.4" description = "World timezone definitions, modern and historical" -category = "dev" -optional = false +category = "main" +optional = true python-versions = "*" [[package]] -name = "PyYAML" +name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.6" [[package]] @@ -453,22 +453,22 @@ urllib3 = ">=1.21.1,<1.27" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" -optional = false +category = "main" +optional = true python-versions = "*" [[package]] @@ -483,16 +483,16 @@ python-versions = "*" name = "soupsieve" version = "2.3.2.post1" description = "A modern CSS selector implementation for Beautiful Soup." -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.6" [[package]] -name = "Sphinx" -version = "5.2.3" +name = "sphinx" +version = "5.3.0" description = "Python documentation generator" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.6" [package.dependencies] @@ -523,8 +523,8 @@ test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] name = "sphinx-autobuild" version = "2021.3.14" description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.6" [package.dependencies] @@ -539,8 +539,8 @@ test = ["pytest", "pytest-cov"] name = "sphinx-basic-ng" version = "1.0.0b1" description = "A modern skeleton for Sphinx themes." -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.7" [package.dependencies] @@ -549,27 +549,12 @@ sphinx = ">=4.0" [package.extras] docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"] -[[package]] -name = "sphinx-inline-tabs" -version = "2022.1.2b11" -description = "Add inline tabbed content to your Sphinx documentation." -category = "dev" -optional = false -python-versions = ">=3.8" - -[package.dependencies] -sphinx = ">=3" - -[package.extras] -doc = ["furo", "myst-parser"] -test = ["pytest", "pytest-cov", "pytest-xdist"] - [[package]] name = "sphinxcontrib-applehelp" version = "1.0.2" description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.5" [package.extras] @@ -580,8 +565,8 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.5" [package.extras] @@ -592,8 +577,8 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.6" [package.extras] @@ -604,8 +589,8 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.5" [package.extras] @@ -615,8 +600,8 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.5" [package.extras] @@ -627,8 +612,8 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.5" [package.extras] @@ -647,13 +632,13 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" name = "tornado" version = "6.2" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">= 3.7" [[package]] name = "typing-extensions" -version = "4.3.0" +version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false @@ -674,20 +659,23 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "zipp" -version = "3.8.1" +version = "3.9.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] -testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[extras] +docs = ["Sphinx", "furo", "myst-parser"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "c92191a0e37fa35acc3909144fec561b9404a7e9fe8761f70f5256963fc21fb6" +content-hash = "5744b73e15e68f296f6427a4a32bf4309d491aee8130fa42acf4e84ae3c21514" [metadata.files] alabaster = [ @@ -698,7 +686,7 @@ appdirs = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] -Babel = [ +babel = [ {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, ] @@ -784,7 +772,7 @@ colorama = [ {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] -Cython = [ +cython = [ {file = "Cython-0.29.32-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:39afb4679b8c6bf7ccb15b24025568f4f9b4d7f9bf3cbd981021f542acecd75b"}, {file = "Cython-0.29.32-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dbee03b8d42dca924e6aa057b836a064c769ddfd2a4c2919e65da2c8a362d528"}, {file = "Cython-0.29.32-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ba622326f2862f9c1f99ca8d47ade49871241920a352c917e16861e25b0e5c3"}, @@ -868,8 +856,8 @@ furo = [ {file = "furo-2022.9.29.tar.gz", hash = "sha256:d4238145629c623609c2deb5384f8d036e2a1ee2a101d64b67b4348112470dbd"}, ] humanize = [ - {file = "humanize-3.14.0-py3-none-any.whl", hash = "sha256:32bcf712ac98ff5e73627a9d31e1ba5650619008d6d13543b5d53b48e8ab8d43"}, - {file = "humanize-3.14.0.tar.gz", hash = "sha256:60dd8c952b1df1ad83f0903844dec50a34ba7a04eea22a6b14204ffb62dbb0a4"}, + {file = "humanize-4.4.0-py3-none-any.whl", hash = "sha256:8830ebf2d65d0395c1bd4c79189ad71e023f277c2c7ae00f263124432e6f2ffa"}, + {file = "humanize-4.4.0.tar.gz", hash = "sha256:efb2584565cc86b7ea87a977a15066de34cdedaf341b11c851cfcfd2b964779c"}, ] idna = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, @@ -880,14 +868,14 @@ imagesize = [ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, - {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, + {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"}, + {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"}, ] -JACK-Client = [ +jack-client = [ {file = "JACK-Client-0.5.4.tar.gz", hash = "sha256:dd4a293e3a6e9bde9972569b9bc4630a5fcd4f80756cc590de572cc744e5a848"}, {file = "JACK_Client-0.5.4-py3-none-any.whl", hash = "sha256:52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73"}, ] -Jinja2 = [ +jinja2 = [ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] @@ -898,7 +886,7 @@ markdown-it-py = [ {file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"}, {file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"}, ] -MarkupSafe = [ +markupsafe = [ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, @@ -976,11 +964,11 @@ pycparser = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] -Pygments = [ +pygments = [ {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, ] -PyGObject = [ +pygobject = [ {file = "PyGObject-3.42.2.tar.gz", hash = "sha256:21524cef33100c8fd59dc135948b703d79d303e368ce71fa60521cc971cd8aa7"}, ] pyliblo = [] @@ -988,20 +976,20 @@ pyparsing = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] -PyQt5 = [ +pyqt5 = [ {file = "PyQt5-5.15.7-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:1a793748c60d5aff3850b7abf84d47c1d41edb11231b7d7c16bef602c36be643"}, {file = "PyQt5-5.15.7-cp37-abi3-manylinux1_x86_64.whl", hash = "sha256:e319c9d8639e0729235c1b09c99afdadad96fa3dbd8392ab561b5ab5946ee6ef"}, {file = "PyQt5-5.15.7-cp37-abi3-win32.whl", hash = "sha256:08694f0a4c7d4f3d36b2311b1920e6283240ad3b7c09b515e08262e195dcdf37"}, {file = "PyQt5-5.15.7-cp37-abi3-win_amd64.whl", hash = "sha256:232fe5b135a095cbd024cf341d928fc672c963f88e6a52b0c605be8177c2fdb5"}, {file = "PyQt5-5.15.7.tar.gz", hash = "sha256:755121a52b3a08cb07275c10ebb96576d36e320e572591db16cfdbc558101594"}, ] -PyQt5-Qt5 = [ +pyqt5-qt5 = [ {file = "PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"}, {file = "PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"}, {file = "PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"}, {file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"}, ] -PyQt5-sip = [ +pyqt5-sip = [ {file = "PyQt5_sip-12.11.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1f9e312ff8284d6dfebc5366f6f7d103f84eec23a4da0be0482403933e68660"}, {file = "PyQt5_sip-12.11.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:4031547dfb679be309094bfa79254f5badc5ddbe66b9ad38e319d84a7d612443"}, {file = "PyQt5_sip-12.11.0-cp310-cp310-win32.whl", hash = "sha256:ad21ca0ee8cae2a41b61fc04949dccfab6fe008749627d94e8c7078cb7a73af1"}, @@ -1036,10 +1024,10 @@ python-rtmidi = [ {file = "python_rtmidi-1.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:54d40cb794a6b079105bfb923ab0b4d5f041e9eef5dc5cce25480e4c3e3fc2f6"}, ] pytz = [ - {file = "pytz-2022.2.1-py2.py3-none-any.whl", hash = "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197"}, - {file = "pytz-2022.2.1.tar.gz", hash = "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"}, + {file = "pytz-2022.4-py2.py3-none-any.whl", hash = "sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91"}, + {file = "pytz-2022.4.tar.gz", hash = "sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174"}, ] -PyYAML = [ +pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, @@ -1094,9 +1082,9 @@ soupsieve = [ {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, ] -Sphinx = [ - {file = "Sphinx-5.2.3.tar.gz", hash = "sha256:5b10cb1022dac8c035f75767799c39217a05fc0fe2d6fe5597560d38e44f0363"}, - {file = "sphinx-5.2.3-py3-none-any.whl", hash = "sha256:7abf6fabd7b58d0727b7317d5e2650ef68765bbe0ccb63c8795fa8683477eaa2"}, +sphinx = [ + {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"}, + {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"}, ] sphinx-autobuild = [ {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, @@ -1106,10 +1094,6 @@ sphinx-basic-ng = [ {file = "sphinx_basic_ng-1.0.0b1-py3-none-any.whl", hash = "sha256:ade597a3029c7865b24ad0eda88318766bcc2f9f4cef60df7e28126fde94db2a"}, {file = "sphinx_basic_ng-1.0.0b1.tar.gz", hash = "sha256:89374bd3ccd9452a301786781e28c8718e99960f2d4f411845ea75fc7bb5a9b0"}, ] -sphinx-inline-tabs = [ - {file = "sphinx_inline_tabs-2022.1.2b11-py3-none-any.whl", hash = "sha256:bb4e807769ef52301a186d0678da719120b978a1af4fd62a1e9453684e962dbc"}, - {file = "sphinx_inline_tabs-2022.1.2b11.tar.gz", hash = "sha256:afb9142772ec05ccb07f05d8181b518188fc55631b26ee803c694e812b3fdd73"}, -] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, @@ -1152,14 +1136,14 @@ tornado = [ {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, ] typing-extensions = [ - {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, - {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] urllib3 = [ {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, ] zipp = [ - {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, - {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, + {file = "zipp-3.9.0-py3-none-any.whl", hash = "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980"}, + {file = "zipp-3.9.0.tar.gz", hash = "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb"}, ] diff --git a/pyproject.toml b/pyproject.toml index b7bedf4ff..36e924f7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,6 @@ -[tool.black] -line-length = 80 -target-version = ['py37'] -exclude = ''' -/( - \.git - | dist - | docs -)/ -''' +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" [tool.poetry] name = "linux-show-player" @@ -15,7 +8,6 @@ version = "0.6.0.dev0" description = "Cue player for stage productions" authors = ["Francesco Ceruti "] license = "GPLv3" - readme = "README.md" homepage = "https://www.linux-show-player.org/" @@ -24,7 +16,6 @@ repository = "https://github.com/FrancescoCeruti/linux-show-player" packages = [ { include = "lisp" } ] - exclude = [ "lisp/i18n/ts/", "lisp/ui/themes/*/assets/" @@ -42,23 +33,34 @@ pyqt5 = "^5.6" python-rtmidi = "^1.1" requests = "^2.20" sortedcontainers = "^2.0" -humanize = "^3.1.0" -pyalsa = {git = "https://github.com/alsa-project/alsa-python.git", tag = "v1.2.6"} +humanize = "^4.4.0" +pyalsa = { git = "https://github.com/alsa-project/alsa-python.git", tag = "v1.2.7" } # Use a pyliblo fork that define dependecies correctly -pyliblo = {git = "https://github.com/s0600204/pyliblo.git", branch = "pip"} +pyliblo = { git = "https://github.com/s0600204/pyliblo.git", branch = "pip" } -[tool.poetry.dev-dependencies] +# Optional dependencies (to build documentation) +Sphinx = { version = "^5.1.1", optional = true } +furo = { version = "^2022.6.21", optional = true } +sphinx-autobuild = { version = "^2021.3.14", optional = true } +myst-parser = { version = "^0.18.0", optional = true } + +[tool.poetry.group.dev.dependencies] toml = "*" packaging = "*" -Sphinx = "^5.1.1" -furo = "^2022.6.21" -sphinx-autobuild = "^2021.3.14" -sphinx-inline-tabs = { version = "^2022.1.2-beta.11", python = "^3.8" } -myst-parser = "^0.18.0" + +[tool.poetry.extras] +docs = ["Sphinx", "furo", "myst-parser"] [tool.poetry.scripts] linux-show-player = "lisp.main:main" -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" +[tool.black] +line-length = 80 +target-version = ['py37'] +exclude = ''' +/( + \.git + | dist + | docs +)/ +''' \ No newline at end of file diff --git a/scripts/flatpak/python-modules.json b/scripts/flatpak/python-modules.json index 39321eaf3..1b5635d81 100644 --- a/scripts/flatpak/python-modules.json +++ b/scripts/flatpak/python-modules.json @@ -2,7 +2,7 @@ "name": "python-modules", "buildsystem": "simple", "build-commands": [ - "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} appdirs certifi cffi charset-normalizer falcon humanize idna importlib-metadata JACK-Client mido pycparser python-rtmidi requests sortedcontainers typing-extensions urllib3 zipp" + "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} appdirs certifi cffi charset-normalizer falcon humanize idna importlib-metadata jack-client mido packaging pycparser pyparsing python-rtmidi requests sortedcontainers typing-extensions urllib3 zipp" ], "sources": [ { @@ -10,6 +10,11 @@ "url": "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", "sha256": "a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/17/41/de1269065ff0d1bda143cc91b245aef3165d9259e27904a4a59eab081e0b/JACK_Client-0.5.4-py3-none-any.whl", + "sha256": "52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73" + }, { "type": "file", "url": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", @@ -22,8 +27,8 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/17/41/de1269065ff0d1bda143cc91b245aef3165d9259e27904a4a59eab081e0b/JACK_Client-0.5.4-py3-none-any.whl", - "sha256": "52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73" + "url": "https://files.pythonhosted.org/packages/9d/fc/28d2b631c5220b2a594d5d13b6ad79ee60d50688f1cd43f6707c06fb0db4/humanize-4.4.0-py3-none-any.whl", + "sha256": "8830ebf2d65d0395c1bd4c79189ad71e023f277c2c7ae00f263124432e6f2ffa" }, { "type": "file", @@ -32,13 +37,8 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/a6/35/2b1bcfaba1d9e65780f3833fde53f13359a9b8bc7b4d4e7c23135366b589/humanize-3.14.0-py3-none-any.whl", - "sha256": "32bcf712ac98ff5e73627a9d31e1ba5650619008d6d13543b5d53b48e8ab8d43" - }, - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/d2/a2/8c239dc898138f208dd14b441b196e7b3032b94d3137d9d8453e186967fc/importlib_metadata-4.12.0-py3-none-any.whl", - "sha256": "7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23" + "url": "https://files.pythonhosted.org/packages/b5/64/ef29a63cf08f047bb7fb22ab0f1f774b87eed0bb46d067a5a524798a4af8/importlib_metadata-5.0.0-py3-none-any.whl", + "sha256": "ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43" }, { "type": "file", @@ -50,6 +50,11 @@ "url": "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", "sha256": "8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9" }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/05/8e/8de486cbd03baba4deef4142bd643a3e7bbe954a784dc1bb17142572d127/packaging-21.3-py3-none-any.whl", + "sha256": "ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + }, { "type": "file", "url": "https://files.pythonhosted.org/packages/36/53/4fd90c6c841bc2e4be29ab92c65e5406df9096c421f138bef9d95d43afc9/falcon-3.1.0.tar.gz", @@ -57,29 +62,34 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", - "sha256": "a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" + "url": "https://files.pythonhosted.org/packages/6c/10/a7d0fa5baea8fe7b50f448ab742f26f52b80bfca85ac2be9d35cdd9a3246/pyparsing-3.0.9-py3-none-any.whl", + "sha256": "5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/ed/d6/2afc375a8d55b8be879d6b4986d4f69f01115e795e36827fd3a40166028b/typing_extensions-4.3.0-py3-none-any.whl", - "sha256": "25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02" + "url": "https://files.pythonhosted.org/packages/0b/8e/f1a0a5a76cfef77e1eb6004cb49e5f8d72634da638420b9ea492ce8305e8/typing_extensions-4.4.0-py3-none-any.whl", + "sha256": "16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c0441b5cda699c90f618b6f8a1b42/requests-2.28.1-py3-none-any.whl", - "sha256": "8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" + "url": "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", + "sha256": "a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/f0/36/639d6742bcc3ffdce8b85c31d79fcfae7bb04b95f0e5c4c6f8b206a038cc/zipp-3.8.1-py3-none-any.whl", - "sha256": "47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009" + "url": "https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c0441b5cda699c90f618b6f8a1b42/requests-2.28.1-py3-none-any.whl", + "sha256": "8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/92/bf/749468bc43f85ec77f37154327360ba82e7d0ae622341eab44a6d75751c3/python-rtmidi-1.4.9.tar.gz", "sha256": "bfeb4ed99d0cccf6fa2837566907652ded7adc1c03b69f2160c9de4082301302" }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/09/85/302c153615db93e9197f13e02f448b3f95d7d786948f2fb3d6d5830a481b/zipp-3.9.0-py3-none-any.whl", + "sha256": "972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980" + }, { "type": "file", "url": "https://files.pythonhosted.org/packages/6f/de/5be2e3eed8426f871b170663333a0f627fc2924cc386cd41be065e7ea870/urllib3-1.26.12-py2.py3-none-any.whl", From 7261d06d7586a11edda0bb7b9db3bb3668a63dbe Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 16 Oct 2022 23:30:45 +0200 Subject: [PATCH 302/333] Add "Read the Docs" configuration file --- .readthedocs.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .readthedocs.yml diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 000000000..637111e65 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,15 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +python: + version: "3" + install: + - method: pip + path: . + extra_requirements: + - docs + +sphinx: + configuration: docs/user/conf.py From 1414f21b157174a98a086f2a12174867757ea581 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 18 Oct 2022 00:36:28 +0200 Subject: [PATCH 303/333] Separate requirements.txt for "readthedocs", fix "poetry-core" version --- .readthedocs.yml | 5 +- docs/user/requirements.txt | 40 ++++++ poetry.lock | 129 +++++++++--------- pyproject.toml | 18 +-- ...org.linux_show_player.LinuxShowPlayer.json | 4 +- 5 files changed, 115 insertions(+), 81 deletions(-) create mode 100644 docs/user/requirements.txt diff --git a/.readthedocs.yml b/.readthedocs.yml index 637111e65..46da8820f 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -6,10 +6,7 @@ version: 2 python: version: "3" install: - - method: pip - path: . - extra_requirements: - - docs + - requirements: docs/user/requirements.txt sphinx: configuration: docs/user/conf.py diff --git a/docs/user/requirements.txt b/docs/user/requirements.txt new file mode 100644 index 000000000..c2863d6c3 --- /dev/null +++ b/docs/user/requirements.txt @@ -0,0 +1,40 @@ +alabaster==0.7.12 ; python_version >= "3.7" and python_version < "4.0" +babel==2.10.3 ; python_version >= "3.7" and python_version < "4.0" +beautifulsoup4==4.11.1 ; python_version >= "3.7" and python_version < "4.0" +certifi==2022.9.24 ; python_version >= "3.7" and python_version < "4" +charset-normalizer==2.1.1 ; python_version >= "3.7" and python_version < "4" +colorama==0.4.5 ; python_version >= "3.7" and python_version < "4.0" +docutils==0.19 ; python_version >= "3.7" and python_version < "4.0" +furo==2022.9.29 ; python_version >= "3.7" and python_version < "4.0" +idna==3.4 ; python_version >= "3.7" and python_version < "4" +imagesize==1.4.1 ; python_version >= "3.7" and python_version < "4.0" +importlib-metadata==5.0.0 ; python_version >= "3.7" and python_version < "3.10" +jinja2==3.1.2 ; python_version >= "3.7" and python_version < "4.0" +livereload==2.6.3 ; python_version >= "3.7" and python_version < "4.0" +markdown-it-py==2.1.0 ; python_version >= "3.7" and python_version < "4.0" +markupsafe==2.1.1 ; python_version >= "3.7" and python_version < "4.0" +mdit-py-plugins==0.3.1 ; python_version >= "3.7" and python_version < "4.0" +mdurl==0.1.2 ; python_version >= "3.7" and python_version < "4.0" +myst-parser==0.18.1 ; python_version >= "3.7" and python_version < "4.0" +packaging==21.3 ; python_version >= "3.7" and python_version < "4.0" +pygments==2.13.0 ; python_version >= "3.7" and python_version < "4.0" +pyparsing==3.0.9 ; python_version >= "3.7" and python_version < "4.0" +pytz==2022.4 ; python_version >= "3.7" and python_version < "4.0" +pyyaml==6.0 ; python_version >= "3.7" and python_version < "4.0" +requests==2.28.1 ; python_version >= "3.7" and python_version < "4" +six==1.16.0 ; python_version >= "3.7" and python_version < "4.0" +snowballstemmer==2.2.0 ; python_version >= "3.7" and python_version < "4.0" +soupsieve==2.3.2.post1 ; python_version >= "3.7" and python_version < "4.0" +sphinx-autobuild==2021.3.14 ; python_version >= "3.7" and python_version < "4.0" +sphinx-basic-ng==1.0.0b1 ; python_version >= "3.7" and python_version < "4.0" +sphinx==5.3.0 ; python_version >= "3.7" and python_version < "4.0" +sphinxcontrib-applehelp==1.0.2 ; python_version >= "3.7" and python_version < "4.0" +sphinxcontrib-devhelp==1.0.2 ; python_version >= "3.7" and python_version < "4.0" +sphinxcontrib-htmlhelp==2.0.0 ; python_version >= "3.7" and python_version < "4.0" +sphinxcontrib-jsmath==1.0.1 ; python_version >= "3.7" and python_version < "4.0" +sphinxcontrib-qthelp==1.0.3 ; python_version >= "3.7" and python_version < "4.0" +sphinxcontrib-serializinghtml==1.1.5 ; python_version >= "3.7" and python_version < "4.0" +tornado==6.2 ; python_version >= "3.7" and python_version < "4.0" +typing-extensions==4.4.0 ; python_version >= "3.7" and python_version < "4.0" +urllib3==1.26.12 ; python_version >= "3.7" and python_version < "4" +zipp==3.9.0 ; python_version >= "3.7" and python_version < "3.10" diff --git a/poetry.lock b/poetry.lock index 87a82bd4f..cfe6b5248 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,8 +2,8 @@ name = "alabaster" version = "0.7.12" description = "A configurable sidebar-enabled Sphinx theme" -category = "main" -optional = true +category = "dev" +optional = false python-versions = "*" [[package]] @@ -18,8 +18,8 @@ python-versions = "*" name = "babel" version = "2.10.3" description = "Internationalization utilities" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6" [package.dependencies] @@ -29,8 +29,8 @@ pytz = ">=2015.7" name = "beautifulsoup4" version = "4.11.1" description = "Screen-scraping library" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6.0" [package.dependencies] @@ -74,8 +74,8 @@ unicode-backport = ["unicodedata2"] name = "colorama" version = "0.4.5" description = "Cross-platform colored terminal text." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] @@ -90,8 +90,8 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" name = "docutils" version = "0.19" description = "Docutils -- Python Documentation Utilities" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [[package]] @@ -106,8 +106,8 @@ python-versions = ">=3.5" name = "furo" version = "2022.9.29" description = "A clean customisable Sphinx documentation theme." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [package.dependencies] @@ -142,8 +142,8 @@ python-versions = ">=3.5" name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] @@ -181,8 +181,8 @@ numpy = ["NumPy"] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [package.dependencies] @@ -195,8 +195,8 @@ i18n = ["Babel (>=2.7)"] name = "livereload" version = "2.6.3" description = "Python LiveReload is an awesome tool for web developers" -category = "main" -optional = true +category = "dev" +optional = false python-versions = "*" [package.dependencies] @@ -207,8 +207,8 @@ tornado = {version = "*", markers = "python_version > \"2.7\""} name = "markdown-it-py" version = "2.1.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [package.dependencies] @@ -229,16 +229,16 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "markupsafe" version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [[package]] name = "mdit-py-plugins" version = "0.3.1" description = "Collection of plugins for markdown-it-py" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [package.dependencies] @@ -253,8 +253,8 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [[package]] @@ -273,8 +273,8 @@ ports = ["python-rtmidi (>=1.1.0)"] name = "myst-parser" version = "0.18.1" description = "An extended commonmark compliant parser, with bridges to docutils & sphinx." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [package.dependencies] @@ -296,7 +296,7 @@ testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", name = "packaging" version = "21.3" description = "Core utilities for Python packages" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -338,8 +338,8 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "pygments" version = "2.13.0" description = "Pygments is a syntax highlighting package written in Python." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6" [package.extras] @@ -378,7 +378,7 @@ resolved_reference = "5cef301f234cab5d39a416462381f83f840acec5" name = "pyparsing" version = "3.0.9" description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" +category = "dev" optional = false python-versions = ">=3.6.8" @@ -425,16 +425,16 @@ python-versions = "*" name = "pytz" version = "2022.4" description = "World timezone definitions, modern and historical" -category = "main" -optional = true +category = "dev" +optional = false python-versions = "*" [[package]] name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6" [[package]] @@ -459,16 +459,16 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "main" -optional = true +category = "dev" +optional = false python-versions = "*" [[package]] @@ -483,16 +483,16 @@ python-versions = "*" name = "soupsieve" version = "2.3.2.post1" description = "A modern CSS selector implementation for Beautiful Soup." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6" [[package]] name = "sphinx" version = "5.3.0" description = "Python documentation generator" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6" [package.dependencies] @@ -523,8 +523,8 @@ test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] name = "sphinx-autobuild" version = "2021.3.14" description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6" [package.dependencies] @@ -539,8 +539,8 @@ test = ["pytest", "pytest-cov"] name = "sphinx-basic-ng" version = "1.0.0b1" description = "A modern skeleton for Sphinx themes." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.7" [package.dependencies] @@ -553,8 +553,8 @@ docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-ta name = "sphinxcontrib-applehelp" version = "1.0.2" description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.5" [package.extras] @@ -565,8 +565,8 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.5" [package.extras] @@ -577,8 +577,8 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.6" [package.extras] @@ -589,8 +589,8 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.5" [package.extras] @@ -600,8 +600,8 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.5" [package.extras] @@ -612,8 +612,8 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">=3.5" [package.extras] @@ -632,8 +632,8 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" name = "tornado" version = "6.2" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -category = "main" -optional = true +category = "dev" +optional = false python-versions = ">= 3.7" [[package]] @@ -669,13 +669,10 @@ python-versions = ">=3.7" docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] -[extras] -docs = ["Sphinx", "furo", "myst-parser"] - [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "5744b73e15e68f296f6427a4a32bf4309d491aee8130fa42acf4e84ae3c21514" +content-hash = "c8ff2998ef851925f9c1e296d92d673d7fd8174e05b00a703020270d02b0ad74" [metadata.files] alabaster = [ diff --git a/pyproject.toml b/pyproject.toml index 36e924f7d..0b7c5acb3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.1.0"] build-backend = "poetry.core.masonry.api" [tool.poetry] @@ -38,19 +38,19 @@ pyalsa = { git = "https://github.com/alsa-project/alsa-python.git", tag = "v1.2. # Use a pyliblo fork that define dependecies correctly pyliblo = { git = "https://github.com/s0600204/pyliblo.git", branch = "pip" } -# Optional dependencies (to build documentation) -Sphinx = { version = "^5.1.1", optional = true } -furo = { version = "^2022.6.21", optional = true } -sphinx-autobuild = { version = "^2021.3.14", optional = true } -myst-parser = { version = "^0.18.0", optional = true } +[tool.poetry.group.docs] +optional = true + +[tool.poetry.group.docs.dependencies] +Sphinx = { version = "^5.1.1" } +furo = { version = "^2022.6.21" } +sphinx-autobuild = { version = "^2021.3.14" } +myst-parser = { version = "^0.18.0" } [tool.poetry.group.dev.dependencies] toml = "*" packaging = "*" -[tool.poetry.extras] -docs = ["Sphinx", "furo", "myst-parser"] - [tool.poetry.scripts] linux-show-player = "lisp.main:main" diff --git a/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json b/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json index 1e4642e1d..61e803f47 100644 --- a/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json +++ b/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json @@ -169,8 +169,8 @@ "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/bf/e1/08c7478df1e93dea47b06c9d9a80dbb54af7421462e1b22c280d063df807/poetry_core-1.0.3-py2.py3-none-any.whl", - "sha256": "c6bde46251112de8384013e1ab8d66e7323d2c75172f80220aba2bc07e208e9a" + "url": "https://files.pythonhosted.org/packages/12/e4/5c91edb0bc975752c308e425a86b02b5b00cf990b362cfc482254d66cbbb/poetry_core-1.3.2-py3-none-any.whl", + "sha256": "ea0f5a90b339cde132b4e43cff78a1b440cd928db864bb67cfc97fdfcefe7218" } ] }, From f6091bfcb20456740bb96ea1f69a4fbdd29623b4 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 23 Oct 2022 22:31:09 +0200 Subject: [PATCH 304/333] Flatten docs directory, move assets from "media" to "_static" --- docs/user/.gitignore | 1 + docs/user/Makefile | 4 +-- .../cart_layout_main_view.png | Bin .../cart_layout_settings.png | Bin .../controller_settings_keyboard.png | Bin .../controller_settings_midi.png | Bin .../media => _static}/gst_edit_pipeline.png | Bin .../media => _static}/gst_media_settings.png | Bin .../list_layout_main_view.png | Bin .../list_layout_settings.png | Bin docs/user/{source/media => _static}/logo.png | Bin .../{source/media => _static}/presets.png | Bin .../media => _static}/replaygain_dialog.png | Bin .../synchronization_peers.png | Bin .../timecode_cue_settings.png | Bin .../media => _static}/timecode_settings.png | Bin .../triggers_cue_settings.png | Bin .../media => _static}/uri_session_change.png | Bin docs/user/{source => }/about.md | 0 docs/user/{source => }/cart_layout.md | 4 +-- .../user/{source => }/command_cue_examples.md | 0 docs/user/{source => }/conf.py | 31 +++--------------- docs/user/{source => }/cues.md | 0 docs/user/{source => }/getting_started.md | 0 docs/user/{source => }/gst_custom_elements.md | 0 docs/user/{source => }/gst_media_settings.md | 4 +-- docs/user/{source => }/index.md | 0 docs/user/{source => }/list_layout.md | 4 +-- docs/user/{source => }/menus.md | 0 .../{source => }/plugins/artnet_timecode.md | 4 +-- .../user/{source => }/plugins/cue_controls.md | 4 +-- docs/user/{source => }/plugins/presets.md | 2 +- docs/user/{source => }/plugins/replaygain.md | 2 +- .../{source => }/plugins/synchronization.md | 2 +- docs/user/{source => }/plugins/triggers.md | 2 +- poetry.lock | 8 ++--- pyproject.toml | 8 ++--- 37 files changed, 30 insertions(+), 50 deletions(-) create mode 100644 docs/user/.gitignore rename docs/user/{source/media => _static}/cart_layout_main_view.png (100%) rename docs/user/{source/media => _static}/cart_layout_settings.png (100%) rename docs/user/{source/media => _static}/controller_settings_keyboard.png (100%) rename docs/user/{source/media => _static}/controller_settings_midi.png (100%) rename docs/user/{source/media => _static}/gst_edit_pipeline.png (100%) rename docs/user/{source/media => _static}/gst_media_settings.png (100%) rename docs/user/{source/media => _static}/list_layout_main_view.png (100%) rename docs/user/{source/media => _static}/list_layout_settings.png (100%) rename docs/user/{source/media => _static}/logo.png (100%) rename docs/user/{source/media => _static}/presets.png (100%) rename docs/user/{source/media => _static}/replaygain_dialog.png (100%) rename docs/user/{source/media => _static}/synchronization_peers.png (100%) rename docs/user/{source/media => _static}/timecode_cue_settings.png (100%) rename docs/user/{source/media => _static}/timecode_settings.png (100%) rename docs/user/{source/media => _static}/triggers_cue_settings.png (100%) rename docs/user/{source/media => _static}/uri_session_change.png (100%) rename docs/user/{source => }/about.md (100%) rename docs/user/{source => }/cart_layout.md (96%) rename docs/user/{source => }/command_cue_examples.md (100%) rename docs/user/{source => }/conf.py (75%) rename docs/user/{source => }/cues.md (100%) rename docs/user/{source => }/getting_started.md (100%) rename docs/user/{source => }/gst_custom_elements.md (100%) rename docs/user/{source => }/gst_media_settings.md (98%) rename docs/user/{source => }/index.md (100%) rename docs/user/{source => }/list_layout.md (97%) rename docs/user/{source => }/menus.md (100%) rename docs/user/{source => }/plugins/artnet_timecode.md (95%) rename docs/user/{source => }/plugins/cue_controls.md (92%) rename docs/user/{source => }/plugins/presets.md (97%) rename docs/user/{source => }/plugins/replaygain.md (96%) rename docs/user/{source => }/plugins/synchronization.md (96%) rename docs/user/{source => }/plugins/triggers.md (92%) diff --git a/docs/user/.gitignore b/docs/user/.gitignore new file mode 100644 index 000000000..9c5f57827 --- /dev/null +++ b/docs/user/.gitignore @@ -0,0 +1 @@ +_build \ No newline at end of file diff --git a/docs/user/Makefile b/docs/user/Makefile index 64df52d74..d45b6b859 100644 --- a/docs/user/Makefile +++ b/docs/user/Makefile @@ -5,8 +5,8 @@ SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = LinuxShowPlayer -SOURCEDIR = source -BUILDDIR = build +SOURCEDIR = . +BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: diff --git a/docs/user/source/media/cart_layout_main_view.png b/docs/user/_static/cart_layout_main_view.png similarity index 100% rename from docs/user/source/media/cart_layout_main_view.png rename to docs/user/_static/cart_layout_main_view.png diff --git a/docs/user/source/media/cart_layout_settings.png b/docs/user/_static/cart_layout_settings.png similarity index 100% rename from docs/user/source/media/cart_layout_settings.png rename to docs/user/_static/cart_layout_settings.png diff --git a/docs/user/source/media/controller_settings_keyboard.png b/docs/user/_static/controller_settings_keyboard.png similarity index 100% rename from docs/user/source/media/controller_settings_keyboard.png rename to docs/user/_static/controller_settings_keyboard.png diff --git a/docs/user/source/media/controller_settings_midi.png b/docs/user/_static/controller_settings_midi.png similarity index 100% rename from docs/user/source/media/controller_settings_midi.png rename to docs/user/_static/controller_settings_midi.png diff --git a/docs/user/source/media/gst_edit_pipeline.png b/docs/user/_static/gst_edit_pipeline.png similarity index 100% rename from docs/user/source/media/gst_edit_pipeline.png rename to docs/user/_static/gst_edit_pipeline.png diff --git a/docs/user/source/media/gst_media_settings.png b/docs/user/_static/gst_media_settings.png similarity index 100% rename from docs/user/source/media/gst_media_settings.png rename to docs/user/_static/gst_media_settings.png diff --git a/docs/user/source/media/list_layout_main_view.png b/docs/user/_static/list_layout_main_view.png similarity index 100% rename from docs/user/source/media/list_layout_main_view.png rename to docs/user/_static/list_layout_main_view.png diff --git a/docs/user/source/media/list_layout_settings.png b/docs/user/_static/list_layout_settings.png similarity index 100% rename from docs/user/source/media/list_layout_settings.png rename to docs/user/_static/list_layout_settings.png diff --git a/docs/user/source/media/logo.png b/docs/user/_static/logo.png similarity index 100% rename from docs/user/source/media/logo.png rename to docs/user/_static/logo.png diff --git a/docs/user/source/media/presets.png b/docs/user/_static/presets.png similarity index 100% rename from docs/user/source/media/presets.png rename to docs/user/_static/presets.png diff --git a/docs/user/source/media/replaygain_dialog.png b/docs/user/_static/replaygain_dialog.png similarity index 100% rename from docs/user/source/media/replaygain_dialog.png rename to docs/user/_static/replaygain_dialog.png diff --git a/docs/user/source/media/synchronization_peers.png b/docs/user/_static/synchronization_peers.png similarity index 100% rename from docs/user/source/media/synchronization_peers.png rename to docs/user/_static/synchronization_peers.png diff --git a/docs/user/source/media/timecode_cue_settings.png b/docs/user/_static/timecode_cue_settings.png similarity index 100% rename from docs/user/source/media/timecode_cue_settings.png rename to docs/user/_static/timecode_cue_settings.png diff --git a/docs/user/source/media/timecode_settings.png b/docs/user/_static/timecode_settings.png similarity index 100% rename from docs/user/source/media/timecode_settings.png rename to docs/user/_static/timecode_settings.png diff --git a/docs/user/source/media/triggers_cue_settings.png b/docs/user/_static/triggers_cue_settings.png similarity index 100% rename from docs/user/source/media/triggers_cue_settings.png rename to docs/user/_static/triggers_cue_settings.png diff --git a/docs/user/source/media/uri_session_change.png b/docs/user/_static/uri_session_change.png similarity index 100% rename from docs/user/source/media/uri_session_change.png rename to docs/user/_static/uri_session_change.png diff --git a/docs/user/source/about.md b/docs/user/about.md similarity index 100% rename from docs/user/source/about.md rename to docs/user/about.md diff --git a/docs/user/source/cart_layout.md b/docs/user/cart_layout.md similarity index 96% rename from docs/user/source/cart_layout.md rename to docs/user/cart_layout.md index cc4c7c493..42173b393 100644 --- a/docs/user/source/cart_layout.md +++ b/docs/user/cart_layout.md @@ -7,7 +7,7 @@ Cart Layout The Cart Layout organize all the cues in grid-like tabs, cues are shown as buttons, as in the following image: -.. image:: media/cart_layout_main_view.png +.. image:: _static/cart_layout_main_view.png :alt: Linux Show Player - Cart Layout :align: center @@ -67,7 +67,7 @@ In the application settings (``File > Preferences``) various options are provide When the grid size is changed, cues will be visually shifted to keep their logical positioning. -.. image:: media/cart_layout_settings.png +.. image:: _static/cart_layout_settings.png :alt: Linux Show Player - Cart Layout settings :align: center diff --git a/docs/user/source/command_cue_examples.md b/docs/user/command_cue_examples.md similarity index 100% rename from docs/user/source/command_cue_examples.md rename to docs/user/command_cue_examples.md diff --git a/docs/user/source/conf.py b/docs/user/conf.py similarity index 75% rename from docs/user/source/conf.py rename to docs/user/conf.py index 3c3d70fa1..bc4935f31 100644 --- a/docs/user/source/conf.py +++ b/docs/user/conf.py @@ -1,31 +1,10 @@ #!/usr/bin/env python3 -# Linux Show Player documentation build configuration file, created by -# sphinx-quickstart on Thu Feb 23 10:55:18 2017. +# Configuration file for the Sphinx documentation builder. # -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) -# import sphinx_rtd_theme - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -78,7 +57,7 @@ html_theme = "furo" # html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] -html_logo = "media/logo.png" +html_logo = "_static/logo.png" html_title = project diff --git a/docs/user/source/cues.md b/docs/user/cues.md similarity index 100% rename from docs/user/source/cues.md rename to docs/user/cues.md diff --git a/docs/user/source/getting_started.md b/docs/user/getting_started.md similarity index 100% rename from docs/user/source/getting_started.md rename to docs/user/getting_started.md diff --git a/docs/user/source/gst_custom_elements.md b/docs/user/gst_custom_elements.md similarity index 100% rename from docs/user/source/gst_custom_elements.md rename to docs/user/gst_custom_elements.md diff --git a/docs/user/source/gst_media_settings.md b/docs/user/gst_media_settings.md similarity index 98% rename from docs/user/source/gst_media_settings.md rename to docs/user/gst_media_settings.md index 8436e5669..d4d4630c3 100644 --- a/docs/user/source/gst_media_settings.md +++ b/docs/user/gst_media_settings.md @@ -16,7 +16,7 @@ The elements and their settings can be changed in a specific tab in the cue sett | -.. image:: media/gst_media_settings.png +.. image:: _static/gst_media_settings.png :alt: Linux Show Player - GStreamer Backed settings :align: center @@ -24,7 +24,7 @@ The elements and their settings can be changed in a specific tab in the cue sett The active elements can be changed using the *Change Pipeline* button -.. image:: media/gst_edit_pipeline.png +.. image:: _static/gst_edit_pipeline.png :alt: Linux Show Player - GStreamer Backed edit pipeline :align: center diff --git a/docs/user/source/index.md b/docs/user/index.md similarity index 100% rename from docs/user/source/index.md rename to docs/user/index.md diff --git a/docs/user/source/list_layout.md b/docs/user/list_layout.md similarity index 97% rename from docs/user/source/list_layout.md rename to docs/user/list_layout.md index 92d7a3b2b..709d20348 100644 --- a/docs/user/source/list_layout.md +++ b/docs/user/list_layout.md @@ -7,7 +7,7 @@ List Layout The List Layout, as the name suggest, organize the cues in a (single) list, a sidebar to monitor and interact with the running cues is also provided. -.. image:: media/list_layout_main_view.png +.. image:: _static/list_layout_main_view.png :alt: Linux Show Player - List Layout :align: center @@ -86,6 +86,6 @@ In the application settings (``File > Preferences``) various options are provide | -.. image:: media/list_layout_settings.png +.. image:: _static/list_layout_settings.png :alt: Linux Show Player - List Layout settings :align: center \ No newline at end of file diff --git a/docs/user/source/menus.md b/docs/user/menus.md similarity index 100% rename from docs/user/source/menus.md rename to docs/user/menus.md diff --git a/docs/user/source/plugins/artnet_timecode.md b/docs/user/plugins/artnet_timecode.md similarity index 95% rename from docs/user/source/plugins/artnet_timecode.md rename to docs/user/plugins/artnet_timecode.md index 51d80dfa9..f0023b0d9 100644 --- a/docs/user/source/plugins/artnet_timecode.md +++ b/docs/user/plugins/artnet_timecode.md @@ -19,7 +19,7 @@ the OLA interface. Timecode Preferences -------------------- -.. image:: ../media/timecode_settings.png +.. image:: ../_static/timecode_settings.png :alt: Timecode - Preferences :align: center @@ -35,7 +35,7 @@ To enable and setup the plugin go to ``File > Preferences > Timecode Settings``. Timecode Cue Settings --------------------- -.. image:: ../media/timecode_cue_settings.png +.. image:: ../_static/timecode_cue_settings.png :alt: Timecode - Cue Settings :align: center diff --git a/docs/user/source/plugins/cue_controls.md b/docs/user/plugins/cue_controls.md similarity index 92% rename from docs/user/source/plugins/cue_controls.md rename to docs/user/plugins/cue_controls.md index c7f8765e0..04564c4d3 100644 --- a/docs/user/source/plugins/cue_controls.md +++ b/docs/user/plugins/cue_controls.md @@ -12,7 +12,7 @@ settings window: Keyboard -------- -.. image:: ../media/controller_settings_keyboard.png +.. image:: ../_static/controller_settings_keyboard.png :alt: Linux Show Player - Controller settings - Keyboard :align: center @@ -29,7 +29,7 @@ In general, what is taken in account, it's not the pressed key, but the typed ch MIDI ---- -.. image:: ../media/controller_settings_midi.png +.. image:: ../_static/controller_settings_midi.png :alt: Linux Show Player - Controller settings - MIDI :align: center diff --git a/docs/user/source/plugins/presets.md b/docs/user/plugins/presets.md similarity index 97% rename from docs/user/source/plugins/presets.md rename to docs/user/plugins/presets.md index 616e68f7c..c863a699a 100644 --- a/docs/user/source/plugins/presets.md +++ b/docs/user/plugins/presets.md @@ -8,7 +8,7 @@ How to use The main interface is accessible via ``Tools > Presets`` -.. image:: ../media/presets.png +.. image:: ../_static/presets.png :alt: Linux Show Player - Presets :align: center diff --git a/docs/user/source/plugins/replaygain.md b/docs/user/plugins/replaygain.md similarity index 96% rename from docs/user/source/plugins/replaygain.md rename to docs/user/plugins/replaygain.md index 64cd8f27d..1c89329da 100644 --- a/docs/user/source/plugins/replaygain.md +++ b/docs/user/plugins/replaygain.md @@ -18,7 +18,7 @@ Via ``Tools > ReplayGain / Normalization`` menu the following options are provid * **Reset all**: Reset to 0dB the normalized volumes of all cues * **Reset selected**: Reset to 0dB the normalized volumes, only for the selected cues -.. image:: ../media/replaygain_dialog.png +.. image:: ../_static/replaygain_dialog.png :alt: Linux Show Player - ReplayGain dialog :align: center diff --git a/docs/user/source/plugins/synchronization.md b/docs/user/plugins/synchronization.md similarity index 96% rename from docs/user/source/plugins/synchronization.md rename to docs/user/plugins/synchronization.md index 47f73bb8d..a119213f8 100644 --- a/docs/user/source/plugins/synchronization.md +++ b/docs/user/plugins/synchronization.md @@ -13,7 +13,7 @@ The plugin usage is quite simple, the only thing to do, is to add the remote ses you want to "control" to the peer-list, this can be done via ``Tools > Synchronization > Mange connected peers`` -.. image:: ../media/synchronization_peers.png +.. image:: ../_static/synchronization_peers.png :alt: Linux Show Player - Manage synchronization peers :align: center diff --git a/docs/user/source/plugins/triggers.md b/docs/user/plugins/triggers.md similarity index 92% rename from docs/user/source/plugins/triggers.md rename to docs/user/plugins/triggers.md index f77f8339b..9d3dfcb8a 100644 --- a/docs/user/source/plugins/triggers.md +++ b/docs/user/plugins/triggers.md @@ -10,7 +10,7 @@ How to use Triggers can be added/removed and modified from the cue-settings dialog of the cue from which the event is generated (source): -.. image:: ../media/triggers_cue_settings.png +.. image:: ../_static/triggers_cue_settings.png :alt: Linux Show Player - Triggers cue settings :align: center diff --git a/poetry.lock b/poetry.lock index cfe6b5248..6fc557799 100644 --- a/poetry.lock +++ b/poetry.lock @@ -423,7 +423,7 @@ python-versions = "*" [[package]] name = "pytz" -version = "2022.4" +version = "2022.5" description = "World timezone definitions, modern and historical" category = "dev" optional = false @@ -672,7 +672,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "c8ff2998ef851925f9c1e296d92d673d7fd8174e05b00a703020270d02b0ad74" +content-hash = "a3961454da2dac80e87176a4ef7667b8a2fea47a739a684e504718f7338261ca" [metadata.files] alabaster = [ @@ -1021,8 +1021,8 @@ python-rtmidi = [ {file = "python_rtmidi-1.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:54d40cb794a6b079105bfb923ab0b4d5f041e9eef5dc5cce25480e4c3e3fc2f6"}, ] pytz = [ - {file = "pytz-2022.4-py2.py3-none-any.whl", hash = "sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91"}, - {file = "pytz-2022.4.tar.gz", hash = "sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174"}, + {file = "pytz-2022.5-py2.py3-none-any.whl", hash = "sha256:335ab46900b1465e714b4fda4963d87363264eb662aab5e65da039c25f1f5b22"}, + {file = "pytz-2022.5.tar.gz", hash = "sha256:c4d88f472f54d615e9cd582a5004d1e5f624854a6a27a6211591c251f22a6914"}, ] pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, diff --git a/pyproject.toml b/pyproject.toml index 0b7c5acb3..52b740bb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,10 +42,10 @@ pyliblo = { git = "https://github.com/s0600204/pyliblo.git", branch = "pip" } optional = true [tool.poetry.group.docs.dependencies] -Sphinx = { version = "^5.1.1" } -furo = { version = "^2022.6.21" } -sphinx-autobuild = { version = "^2021.3.14" } -myst-parser = { version = "^0.18.0" } +Sphinx = "^5.1.1" +furo = "^2022.6.21" +myst-parser = "^0.18.0" +sphinx-autobuild = "^2021.3.14" [tool.poetry.group.dev.dependencies] toml = "*" From 6fb618d67980ea21c62577ed4918206a57536137 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 23 Oct 2022 22:54:51 +0200 Subject: [PATCH 305/333] Translate MIDI "Note On" with Velocity=0 to "Note Off" (fix #235) --- lisp/plugins/midi/midi_io.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lisp/plugins/midi/midi_io.py b/lisp/plugins/midi/midi_io.py index 046ae1bfa..48b338ff8 100644 --- a/lisp/plugins/midi/midi_io.py +++ b/lisp/plugins/midi/midi_io.py @@ -18,6 +18,8 @@ import logging from abc import ABC, abstractmethod +from mido import Message + from lisp.core.signal import Signal from lisp.ui.ui_utils import translate @@ -106,6 +108,14 @@ def open(self): ) def __new_message(self, message): + # Translate "Note On" with Velocity=0 to "Note Off" + # See https://github.com/mido/mido/issues/130 + if message.type == 'note_on' and message.velocity == 0: + return Message.from_dict({ + **message.dict(), + 'type': 'note_off', + }) + if self.alternate_mode: self.new_message_alt.emit(message) else: From ac3a9d2a13e9d6fd5bebc18664068aef834fdfce Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 5 Nov 2022 22:52:41 +0100 Subject: [PATCH 306/333] Port documentation to Markdown --- docs/user/about.md | 43 -------- docs/user/cart_layout.md | 78 +++++++------- docs/user/command_cue_examples.md | 29 ++---- docs/user/cues.md | 145 +++++++++++---------------- docs/user/gst_custom_elements.md | 38 +++---- docs/user/gst_media_settings.md | 124 ++++++++--------------- docs/user/index.md | 2 +- docs/user/list_layout.md | 80 +++++++-------- docs/user/menus.md | 6 +- docs/user/plugins/artnet_timecode.md | 57 +++++------ docs/user/plugins/cue_controls.md | 32 +++--- docs/user/plugins/index.md | 14 +++ docs/user/plugins/presets.md | 25 ++--- docs/user/plugins/replaygain.md | 40 ++++---- docs/user/plugins/synchronization.md | 28 +++--- docs/user/plugins/triggers.md | 18 ++-- 16 files changed, 311 insertions(+), 448 deletions(-) delete mode 100644 docs/user/about.md create mode 100644 docs/user/plugins/index.md diff --git a/docs/user/about.md b/docs/user/about.md deleted file mode 100644 index 206c2536a..000000000 --- a/docs/user/about.md +++ /dev/null @@ -1,43 +0,0 @@ -.. toctree:: - :hidden: - :maxdepth: 1 - -About -===== - -Linux Show Player (or LiSP for short) is a free cue player designed for -sound-playback in stage productions. The goal of the project is to provide a -complete playback software for musical plays, theater shows and similar. - - -Features --------- - -Here a list of the main functionality offered by LiSP: - -* Cart layout (buttons matrix) suited for touchscreens -* List layout suited for keyboards -* Large media-format support thanks to GStreamer -* Realtime sound effects: equalization, pitch shift, speed control, compression, ... -* Peak and ReplayGain normalization -* Undo/Redo changes -* Remote control over network, between two or more sessions -* ArtNet Timecode (via `OLA`_) -* MIDI support for cue triggering -* MIDI cues (send MIDI messages) -* Multi-language support (see `transifex`_ for a list of supported languages) - - -Project Status --------------- - -Currently only GNU/Linux systems are supported. - -The application is quite stable, is already been used for multiple performances -by different people, but, due to the heterogeneous nature of the GNU/Linux ecosystem, -my suggestion is to test it in a "working" environment to detect possible problems -with some configuration. - - -.. _transifex: https://www.transifex.com/linux-show-player/linux-show-player/ -.. _OLA: https://www.openlighting.org/ \ No newline at end of file diff --git a/docs/user/cart_layout.md b/docs/user/cart_layout.md index 42173b393..88b9687da 100644 --- a/docs/user/cart_layout.md +++ b/docs/user/cart_layout.md @@ -1,56 +1,51 @@ -.. toctree:: - :hidden: - -Cart Layout -=========== +# Cart Layout The Cart Layout organize all the cues in grid-like tabs, cues are shown as buttons, as in the following image: -.. image:: _static/cart_layout_main_view.png - :alt: Linux Show Player - Cart Layout - :align: center +```{image} _static/cart_layout_main_view.png +:alt: Linux Show Player - Cart Layout +:align: center +``` If the cue provides a duration, the current cue time is shown at the bottom of the button. -Layout Operations ------------------ +## Layout Operations + +### Adding/Removing Pages -Adding/Removing Pages -^^^^^^^^^^^^^^^^^^^^^ Pages will be added automatically when needed (this behavior can be disabled), -or manually, to do so two options are provided, in the top-bar ``Layout > Add page`` -and ``Layout > Add pages``, the first will add a single page (at the end), the +or manually, to do so two options are provided, in the top-bar `Layout > Add page` +and `Layout > Add pages`, the first will add a single page (at the end), the second will show a dialog that allow to insert a custom number of pages. -To remove a page, select the page to be removed, then ``Layout > Remove current page``, -a confirmation dialog will be shown, if ``Yes`` is clicked, then the page (and cues) will be deleted. +To remove a page, select the page to be removed, then `Layout > Remove current page`, +a confirmation dialog will be shown, if `Yes` is clicked, then the page (and cues) will be deleted. + +### Change page -Change page -^^^^^^^^^^^ Pages can be switched using the tab bar on top of the layout or directional keys. -Cues Execution -^^^^^^^^^^^^^^ -A cue can be start/stopped simply ``Left-Clicking`` on it. +### Cues Execution + +A cue can be start/stopped simply `Left-Clicking` on it. + +### Cues Editing + +The setting dialog for a cue can be opened in two ways: `Right-Click > Edit cue` or `SHIFT+Right-Click`. -Cues Editing -^^^^^^^^^^^^ -The setting dialog for a cue can be opened in two ways: ``Right-Click > Edit cue`` or ``SHIFT+Right-Click``. +Cues can be selected/deselected for multi-editing with `Right-Click > Select` or `CTRL+Left-Click`. -Cues can be selected/deselected for multi-editing with ``Right-Click > Select`` or ``CTRL+Left-Click``. +### Move and Copy Cues -Move and Copy Cues -^^^^^^^^^^^^^^^^^^ Cues can be copied or moved (into free spaces) inside a page or between different pages: -* **Move:** cues can be moved with ``CTRL+Drag&Drop`` -* **Copy:** cues can be copied with ``SHIFT+Drag&Drop`` +* **Move:** cues can be moved with `CTRL+Drag&Drop` +* **Copy:** cues can be copied with `SHIFT+Drag&Drop` -Layout Options --------------- +## Layout Options -In the application settings (``File > Preferences``) various options are provided: +In the application settings (`File > Preferences`) various options are provided: * **Countdown mode:** when enabled the current cue time is displayed as a countdown * **Show seek-bars:** when enabled a slider able to change the current playing position @@ -63,13 +58,16 @@ In the application settings (``File > Preferences``) various options are provide * **Grid size:** define the number of rows & columns per page. (require to reload the session) -.. Warning:: - When the grid size is changed, cues will be visually shifted to keep their - logical positioning. +```{warning} +When the grid size is changed, cues will be visually shifted to keep their +logical positioning. +``` -.. image:: _static/cart_layout_settings.png - :alt: Linux Show Player - Cart Layout settings - :align: center +```{image} _static/cart_layout_settings.png +:alt: Linux Show Player - Cart Layout settings +:align: center +``` -.. Note:: - Cart Layout does not support cues "next-action". \ No newline at end of file +```{note} +Cart Layout does not support cues "next-action". +``` \ No newline at end of file diff --git a/docs/user/command_cue_examples.md b/docs/user/command_cue_examples.md index 280c3da93..47af28124 100644 --- a/docs/user/command_cue_examples.md +++ b/docs/user/command_cue_examples.md @@ -1,25 +1,18 @@ -Command Cues Examples -===================== +# Command Cues Examples -Examples of ``Command cues`` to control other programs using LiSP: +Examples of `Command cues` to control other programs using LiSP: -LibreOffice ------------ +## LibreOffice Impress -Impress -^^^^^^^ +* **Start slideshow:** `xdotool key --window "$(xdotool search --class Libreoffice | head -n1)" F5` (requires xdotool) +* **Next slide:** `xdotool key --window "$(xdotool search --class Libreoffice | head -n1)" Right` (requires xdotool) -* **Start slideshow:** ``xdotool key --window "$(xdotool search --class Libreoffice | head -n1)" F5`` (requires xdotool) -* **Next slide:** ``xdotool key --window "$(xdotool search --class Libreoffice | head -n1)" Right`` (requires xdotool) +## VLC -VLC ---- +* **Playback a file (full-screen):** `vlc -f ` -* **Playback a file (full-screen):** ``vlc -f `` - -MPV PLAYER ----------- +## MPV PLAYER Allows to use a single mpv player instance, which is controlled through their json IPC interface. Use "Start Player" to init the player, you have to edit --geometry according @@ -28,6 +21,6 @@ $MEDIA variable at the beginning of this command). The videos are played and stays open until next file is started. Images work too, so it is possible add add black images between the videos if needed. -* **Start Player:** ``mpv --input-ipc-server=/tmp/mpvsocket --idle --keep-open=always --osc=no --fullscreen --geometry=1920:0`` -* **Load File:** ``MEDIA=""; printf '{ "command": ["loadfile", "%s"] }\n' $MEDIA|socat - /tmp/mpvsocket`` -* *A complete set of commands can be downloaded and imported as preset in the GitHub releases page.* +* **Start Player:** `mpv --input-ipc-server=/tmp/mpvsocket --idle --keep-open=always --osc=no --fullscreen --geometry=1920:0` +* **Load File:** `MEDIA=""; printf '{ "command": ["loadfile", "%s"] }\n' $MEDIA|socat - /tmp/mpvsocket` +* *A complete set of commands can be downloaded and imported as preset [here](https://www.dropbox.com/sh/dnkqk84u16f67gi/AAC55CbOsG-m9Z2-uckskQDHa?dl=0).* diff --git a/docs/user/cues.md b/docs/user/cues.md index e8046ef6d..fdaadcc3d 100644 --- a/docs/user/cues.md +++ b/docs/user/cues.md @@ -1,5 +1,4 @@ -Cues -==== +# Cues Cues are the main component of every show/session. There are multiple types of cues able to perform different tasks, those can be @@ -12,25 +11,23 @@ subdivided in two main categories: A cue can perform different *actions* depending on its current *state* **Actions:** - * ``start:`` Perform the cue task, or stop it if the cue is already running - * ``stop:`` Stop the running cue - * ``pause:`` Pause the running cue if possible - * ``interrupt:`` Stop the running cue, other cues/functions will ignore this event - * ``fade:`` Decrease/Increase gradually a predefined cue parameter (e.g. volume) + * `start:` Perform the cue task, or stop it if the cue is already running + * `stop:` Stop the running cue + * `pause:` Pause the running cue if possible + * `interrupt:` Stop the running cue, other cues/functions will ignore this event + * `fade:` Decrease/Increase gradually a predefined cue parameter (e.g. volume) Every action (except for fading) can be performed with or without a fadein/out. -Cue options ------------ +## Cue options Cue options can edited via a dialog, the way to access this dialog is described in the layouts pages. Options are organized in tabs depending on their context. Two tabs are provided for all the cues (excluding plugins): -Appearance -^^^^^^^^^^ +## Appearance Visual options *(some of them can be ignored by the layout)* @@ -45,8 +42,7 @@ Cue General options for the cue, organized in 3 sub-tabs -Behaviors -""""""""" +### Behaviors Define the default actions used by the cue, this allow to disable fades by default, or to pause instead of stopping. @@ -54,18 +50,16 @@ or to pause instead of stopping. * **Start:** Action used to start the cue * **Stop:** Action used to stop the cue -Pre/Post Wait -""""""""""""" +### Pre/Post Wait * **Pre wait:** Add a delay before the cue is started -* **Post wait:** Delay before ``Next action`` is executed -* **Next action:** What to do after ``Post wait`` **(can be ignored by the layout)** +* **Post wait:** Delay before `Next action` is executed +* **Next action:** What to do after `Post wait` **(can be ignored by the layout)** * *Do Nothing:* You know ... * *Auto Next:* Execute the next cue - * *Auto Follow:* When the cue end, execute the next cue (``Post wait`` value is ignored) + * *Auto Follow:* When the cue end, execute the next cue (`Post wait` value is ignored) -Fade In/Out -""""""""""" +### Fade In/Out * **Fade In:** Gradually increase a predefined value on faded(in)-actions * **Duration:** How long the fade should last before reaching a maximum value @@ -74,78 +68,67 @@ Fade In/Out * **Duration:** How long the fade should last before reaching a minimum value * **Curve:** How the value should decrease in time --------------------------------------------------------------------------------- +--- -Media Cues ----------- +## Media Cues -Audio Cues -^^^^^^^^^^ +## Audio Cues Audio cues allow you to playback audio files. A media cue doesn't playback directly the media, but rely on other components, those can be configured adding and removing effects or controls (e.g. volume, equalizer, speed). -Options -""""""" +### Options * **Media-Cue** * **Start time:** Time to skip from the media beginning * **Stop time:** Time to skip before the media end * **Loop:** Number of repetitions after first play (-1 is infinite) * **Media Settings:** Media options provided by the backend - * :doc:`GStreamer backend ` + * [GStreamer backend](gst_media_settings.md) *Video playback support is already planed but not yet implemented* --------------------------------------------------------------------------------- +--- -Action Cues ------------ +## Action Cues -Collection Cue -^^^^^^^^^^^^^^ +## Collection Cue As the name suggest this cue allow to execute multiple cues at once, for each cue a different action can be specified. -Options (Edit Collection) -""""""""""""""""""""""""" +### Options (Edit Collection) -You can Add/Remove cues to the collection via the provided buttons, ``Double-Click`` +You can Add/Remove cues to the collection via the provided buttons, `Double-Click` values to edit them. --------------------------------------------------------------------------------- +--- -Stop All -^^^^^^^^ +## Stop All This cue simply stop all the running cues, alternately can be configured to execute different actions. --------------------------------------------------------------------------------- +--- -Seek Action -^^^^^^^^^^^ +## Seek Action This cue allow to seek a media cue to a specified point. -Options (Seek Settings) -""""""""""""""""""""""" +### Options (Seek Settings) * **Cue:** The target media-cue (a button is provided to select the target) * **Seek:** The time-point to reach --------------------------------------------------------------------------------- +--- -Volume Control -^^^^^^^^^^^^^^ +## Volume Control A Volume Control cue allows to trigger a volume change/fade-in/out on a selected media cue. -Options (Volume Settings) -""""""""""""""""""""""""" +### Options (Volume Settings) * **Cue:** The target media-cue (a button is provided to select the target) * **Volume:** The volume to reach @@ -153,48 +136,43 @@ Options (Volume Settings) * **Duration:** The volume fade duration in duration (if 0 the change is instantaneous) * **Curve:** The fade curve --------------------------------------------------------------------------------- +--- -MIDI Cue -^^^^^^^^ +## MIDI Cue A MIDI cue allow to send a MIDI message to the MIDI output device used by the application (can be selected in the application preferences). -Options (MIDI Settings) -""""""""""""""""""""""" +### Options (MIDI Settings) * **MIDI Message:** Set what type of message to send * **(message attributes):** Depending on the message type different attribute can be edited -Supported MIDI messages -""""""""""""""""""""""" +### Supported MIDI messages -* ``note*on`` -* ``note*off`` -* ``control*change`` -* ``program*change`` -* ``polytouch`` -* ``pitchwheel`` -* ``song*select`` -* ``songpos`` -* ``start`` -* ``stop`` -* ``continue`` +* `note*on` +* `note*off` +* `control*change` +* `program*change` +* `polytouch` +* `pitchwheel` +* `song*select` +* `songpos` +* `start` +* `stop` +* `continue` --------------------------------------------------------------------------------- +--- -Command Cue -^^^^^^^^^^^ +## Command Cue This cue allow to execute a shell command, until the command runs the cue is -``running`` and can be stopped, doing so will terminate the command. +`running` and can be stopped, doing so will terminate the command. To see the command output, LiSP should be launched from a terminal, and -``Discard command output`` must be disabled. +`Discard command output` must be disabled. -Options (Command Cue) -""""""""""""""""""""" +### Options (Command Cue) * **Command:** the command line to be executed (as in a shell) * **Discard output:** when enabled the command output is discarded @@ -203,16 +181,14 @@ Options (Command Cue) For examples of commands to control external programs, see :doc:`here `. --------------------------------------------------------------------------------- +--- -Index Action -^^^^^^^^^^^^ +## Index Action This cue give the ability to execute a specific action on a cue in a given position in the layout. -Options (Action Settings) -""""""""""""""""""""""""" +### Options (Action Settings) * **Index** * **Use a relative index:** When toggled the position is considered relative to the @@ -220,13 +196,12 @@ Options (Action Settings) * **Target index:** The position of the target (the UI will enforce a valid index) * **Action:** The action to execute --------------------------------------------------------------------------------- +--- -Editing multiple cues ---------------------- +## Editing multiple cues -You can select all cues at once using ``Edit > Select All`` (``CTRL+A``), -while multiple cues are selected, you can use ``Edit > Edit selected media`` -[``CTRL+SHIFT+E``], to edit multiple cues at once. +You can select all cues at once using `Edit > Select All` (`CTRL+A`), +while multiple cues are selected, you can use `Edit > Edit selected media` +[`CTRL+SHIFT+E`], to edit multiple cues at once. The available options will depend on the types of the selected cues. diff --git a/docs/user/gst_custom_elements.md b/docs/user/gst_custom_elements.md index b59261db1..685980860 100644 --- a/docs/user/gst_custom_elements.md +++ b/docs/user/gst_custom_elements.md @@ -1,63 +1,57 @@ -Media - Custom Elements -=================================== +# Media - Custom Elements One of the most used functionality of GStreamer is the ability to create pipelines from a text description, usually this is done from a CLI interface (e.g. on a terminal) -using the ``gst-launch`` program, in LiSP it's possible to create a custom media-element +using the `gst-launch` program, in LiSP it's possible to create a custom media-element using this functionality. -Element Syntax --------------- +## Element Syntax -From this point ``element(s)`` refer to a GStreamer component and not to LiSP. +From this point `element(s)` refer to a GStreamer component and not to LiSP. -Properties -^^^^^^^^^^ +## Properties *PROPERTY=VALUE* -Sets the property to the specified value. You can use ``gst-inspect`` to find out +Sets the property to the specified value. You can use `gst-inspect` to find out about properties and allowed values of different elements. -Elements -^^^^^^^^ +## Elements *ELEMENT-TYPE [PROPERTY_1 ...]* Creates an element of type *ELEMENT-TYPE* and sets its *PROPERTIES*. -Links -^^^^^ +## Links *ELEMENT_1 ! ELEMENT_2 ! ELEMENT_3* The simplest link (exclamation mark) connects two adjacent elements. The elements are connect starting from the left. -Examples -^^^^^^^^ +## Examples The examples below assume that you have the correct plug-ins available. Keep in mind that different elements might accept different formats, so you might -need to add converter elements like ``audioconvert`` and ``audioresample`` (for audio) +need to add converter elements like `audioconvert` and `audioresample` (for audio) in front of the element to make things work. **Add an echo effect to the audio:** -``audioecho delay=500000000 intensity=0.2 feedback=0.3`` +`audioecho delay=500000000 intensity=0.2 feedback=0.3` **Add a reverb effect to the audio:** -``audioecho delay=20000000 intensity=0.4 feedback=0.45`` +`audioecho delay=20000000 intensity=0.4 feedback=0.45` **Removes voice from sound (or at least try to do so):** -``audiokaraoke filter-band=200 filter-width=120`` +`audiokaraoke filter-band=200 filter-width=120` **Remove voice from sound and (then) apply a reverb effect:** -``audiokaraoke filter-band=200 filter-width=120 ! audioecho delay=20000000 intensity=0.4 feedback=0.45`` +`audiokaraoke filter-band=200 filter-width=120 ! audioecho delay=20000000 intensity=0.4 feedback=0.45` --------------------------------------------------------------------------------- +--- -Extracted from the `GStreamer SDK docs `_ +Extracted from the [GStreamer SDK docs](https://gstreamer.freedesktop.org/documentation/tools/gst-launch.html) diff --git a/docs/user/gst_media_settings.md b/docs/user/gst_media_settings.md index d4d4630c3..88fe766e7 100644 --- a/docs/user/gst_media_settings.md +++ b/docs/user/gst_media_settings.md @@ -1,8 +1,4 @@ -.. toctree:: - :hidden: - -Media Settings -================================== +# Media Settings Media Cues relay on a backend to provide playback capabilities. LiSP currently have only a GStreamer backend. @@ -14,34 +10,27 @@ referred as "pipeline". The elements and their settings can be changed in a specific tab in the cue settings: -| - -.. image:: _static/gst_media_settings.png - :alt: Linux Show Player - GStreamer Backed settings - :align: center - -| +```{image} _static/gst_media_settings.png +:alt: Linux Show Player - GStreamer Backed settings +:align: center +``` The active elements can be changed using the *Change Pipeline* button -.. image:: _static/gst_edit_pipeline.png - :alt: Linux Show Player - GStreamer Backed edit pipeline - :align: center +```{image} _static/gst_edit_pipeline.png +:alt: Linux Show Player - GStreamer Backed edit pipeline +:align: center +``` -| +The default elements can be changed via `File > Preferences > GStreamer settings` -The default elements can be changed via ``File > Preferences > GStreamer settings`` - -Input elements --------------- +## Input elements Feed the pipeline with data -URI Input -^^^^^^^^^ +### URI Input Read and decode data from a URI (usually a URL), can be a local file or a remote one -(eg: ``_) * **Source:** the URI to look for data (a "find file" button is provided for searching local files) * **Buffering:** buffering options (for slower random access media such as a network file server) @@ -49,28 +38,20 @@ Read and decode data from a URI (usually a URL), can be a local file or a remote * **Attempt download on network:** attempt to download the entire file on disk * **Buffer size:** buffer size in bytes, -1 will use the default value -Auto Src -^^^^^^^^ +### Auto Src Get the audio from the system-default input device (eg: microphone), no option is provided -Preset Src -^^^^^^^^^^ -Generate some tone using some wired functions. Just Fun :-) - -*Don't try to use this in combination with the speed element or bad things will happen* - -.. Note:: - To use ``Auto Src`` and ``Preset Src`` you need to create a media cue with - some random file, then change the source element. +```{note} +To use `Auto Src` you need to create a media cue with +some random file, then change the source element. +``` -Plugins elements ----------------- +## Plugins elements Used for audio-processing or data-probing, in some case the order affect the results -Volume -^^^^^^ +### Volume Allow to change the volume level, or mute the media. @@ -78,13 +59,11 @@ Allow to change the volume level, or mute the media. * **Normalized Volume:** parameter used by other components (e.g. ReplayGain) to normalize the volume level without affecting user values, you can only reset the value (to 0dB). -10 Bands Equalizer -^^^^^^^^^^^^^^^^^^ +### 10 Bands Equalizer Allow to equalize the media with 10 frequency bands [30Hz-15KHz]. -dB Meter -^^^^^^^^ +### dB Meter Allow external components to get the current sound level, used for UI visualization. @@ -92,20 +71,17 @@ Allow external components to get the current sound level, used for UI visualizat * **Peak TTL:** Time To Live of decay peak before it falls back (in milliseconds) * **Peak falloff:** Decay rate of decay peak after TTL (in dB/sec) -Speed -^^^^^ +### Speed Speedup or slowdown the media, without affecting the pitch. -Pitch -^^^^^ +### Pitch Allow to change the media pitch by semitones. -Compressor/Expander -^^^^^^^^^^^^^^^^^^^ +### Compressor/Expander -Provide `Dynamic range compression `_. +Provide [Dynamic range compression](https://en.wikipedia.org/wiki/Dynamic_range_compression). * **Type** * *Compressor* @@ -116,60 +92,46 @@ Provide `Dynamic range compression right). +Allow to control stereo panorama (left ⟷ right). -.. Note:: - When used the audio will be forced to stereo +```{note} +When used the audio will be forced to stereo +``` -Custom Element -^^^^^^^^^^^^^^ +### Custom Element Allow to manually create a custom GStreamer "elements" using the framework syntax, -some instruction and example can be found :doc:`here `. +some instruction and example can be found [here](gst_custom_elements.md). -Output elements ---------------- +## Output elements Push data to an output device -Auto sink -^^^^^^^^^ +### Auto sink Use the system-default output device, no option is provided. -ALSA sink -^^^^^^^^^ +### ALSA sink Output to an ALSA device -* **ALSA device:** the output device to be used (parsed form asound configuration file) +* **ALSA device:** the output device to be used -PulseAudio sink -^^^^^^^^^^^^^^^ +### PulseAudio sink Output to the default pulseaudio output device, no option is provided. -Jack sink -^^^^^^^^^ +### Jack sink -Output to `Jack `_ server +Output to [Jack](http://www.jackaudio.org/) server An editable view of the current connections is shown, on the left the cue outputs, on the right the available jack inputs. -Selecting one input an one output it's possible to connect/disconnect using the +Selecting one input and one output it's possible to connect/disconnect using the provided button. -.. Note:: - * Unless the cue is created with ``Jack sink`` as output, by default all - channels are disconnected - * The connections to Jack are opened/closed when the cue start/stop - * If no instance of a Jack-server is found a new one is started - -.. Warning:: - This element can cause problems depending on the jack server configuration and/or - status. Currently it's quite hard to debug those problems since the element - is partially based on a GStreamer element that allow little control over - the used client. \ No newline at end of file +```{note} +Connections are created on demand, for each cue. +``` diff --git a/docs/user/index.md b/docs/user/index.md index fc3c757a5..c568bc7e1 100644 --- a/docs/user/index.md +++ b/docs/user/index.md @@ -15,7 +15,7 @@ cues gst_media_settings gst_custom_elements command_cue_examples -plugins/* +plugins/index ``` diff --git a/docs/user/list_layout.md b/docs/user/list_layout.md index 709d20348..d86ba409f 100644 --- a/docs/user/list_layout.md +++ b/docs/user/list_layout.md @@ -1,29 +1,23 @@ -.. toctree:: - :hidden: - -List Layout -=========== +# List Layout The List Layout, as the name suggest, organize the cues in a (single) list, a sidebar to monitor and interact with the running cues is also provided. -.. image:: _static/list_layout_main_view.png - :alt: Linux Show Player - List Layout - :align: center +```{image} _static/list_layout_main_view.png +:alt: Linux Show Player - List Layout +:align: center +``` -User Interface --------------- +## User Interface -Top Panel -^^^^^^^^^ +### Top Panel -* **Top left:** we can find the ``GO`` button, this will execute the selected cue and go forward; +* **Top left:** we can find the `GO` button, this will execute the selected cue and go forward; * **Top center:** name and description of the current cue are displayed here; * **Top right:** list-control commands are provided, those allow to stop, pause, restart, interrupt and fade all the cues in the list. -Left List -^^^^^^^^^ +### Left List All the cues are shown here in a list-like view, the following column are shown: @@ -35,41 +29,39 @@ All the cues are shown here in a list-like view, the following column are shown: * **Post wait:** Post wait indicator * **(next action):** What should be done after "post wait" -Right (running) List -^^^^^^^^^^^^^^^^^^^^ +### Right (running) List + The running cues are shown here, you can stop, pause/restart, interrupt and fade single cues. -Layout Commands ---------------- +## Layout Commands + +### Navigate the cues -Navigate the cues -^^^^^^^^^^^^^^^^^ To change the current cue, directional keys can be used to go up and down into the list, -alternatively ``Left-Clicking`` the cue will set it as current. +alternatively `Left-Clicking` the cue will set it as current. + +### Cues Execution + +To execute the current cue, press `Space`, (can be changed in the layout options) +or use the `GO` button. + +### Cues Editing -Cues Execution -^^^^^^^^^^^^^^ -To execute the current cue, press ``Space``, (can be changed in the layout options) -or use the ``GO`` button. +The setting dialog for a cue can be opened in two ways: `Right-Click > Edit cue` +or `Double-Click` the cue. -Cues Editing -^^^^^^^^^^^^ -The setting dialog for a cue can be opened in two ways: ``Right-Click > Edit cue`` -or ``Double-Click`` the cue. +Cues can be selected/deselected with `Right-Click > Select`, `CTRL+Space` or +`CTRL+Click` -Cues can be selected/deselected with ``Right-Click > Select``, ``CTRL+Space`` or -``CTRL+Click`` +### Move and Copy Cues -Move and Copy Cues -^^^^^^^^^^^^^^^^^^ -* **Move:** cues can be moved with a simple ``Drag&Drop`` -* **Copy:** cues can be copied using ``CTRL+Drag&Drop`` +* **Move:** cues can be moved with a simple `Drag&Drop` +* **Copy:** cues can be copied using `CTRL+Drag&Drop` -Layout Options --------------- +## Layout Options -In the application settings (``File > Preferences``) various options are provided: +In the application settings (`File > Preferences`) various options are provided: * **Show playing cues:** show/hide the "Right List" and list control buttons * **Show dB-Meters:** show/hide the db-meters for running media-cues @@ -80,12 +72,12 @@ In the application settings (``File > Preferences``) various options are provide * **Stop:** the selection doesn't change when the last cue is executed * **Restart:** the selection is moved back to the first cue * **Go key:** define up to 4 key-combinations that can be used to execute the current cue, - to do so, double click the edit-area, then enter your keys combinations + to do so, double-click the edit-area, then enter your keys combinations * **Use fades:** when disabled the corresponding buttons on the right-panel executes their action without fades. -| -.. image:: _static/list_layout_settings.png - :alt: Linux Show Player - List Layout settings - :align: center \ No newline at end of file +```{image} _static/list_layout_settings.png +:alt: Linux Show Player - List Layout settings +:align: center +``` \ No newline at end of file diff --git a/docs/user/menus.md b/docs/user/menus.md index 12cb36d98..be3b70e74 100644 --- a/docs/user/menus.md +++ b/docs/user/menus.md @@ -4,7 +4,7 @@ Here you can find a comprehensive list of the standard main menu entries.\ Keyboard shortcut are indicated as ``[key-combination]``. ```{note} - Some of the shortcuts may be different, depending on the used language and desktop environment +Some of the shortcuts may be different, depending on the used language and desktop environment. ``` ## File menu @@ -39,7 +39,7 @@ Add/Edit cues (accessible right-clicking on empty areas of the layout) This menu give access to layout functionality and display options for the current view. -#### Cart Layout +### Cart Layout * **Add page/Add pages:** Allows to add one or multiple pages on the current layout. Pages can be switched using the tab bar on top of the layout or directional keys. * **Remove current page:** Remove the current page and all its cues @@ -49,7 +49,7 @@ This menu give access to layout functionality and display options for the curren * **Show volume:** Media cues will display a volume control on their right side. Setting the volume to the middle point (50%) of the slider sets the volume to +0dB. * **Show accurate time:** When checked, cues will display play time with a precision of 0.1s. When unchecked the time is only precise down to 1s. -#### List Layout +### List Layout * **Show dB-meters:** Show / hide the db-meters for running media-cues; * **Show seek-bars:** Show / hide seek bars for running media-cues; diff --git a/docs/user/plugins/artnet_timecode.md b/docs/user/plugins/artnet_timecode.md index f0023b0d9..30c6fda1c 100644 --- a/docs/user/plugins/artnet_timecode.md +++ b/docs/user/plugins/artnet_timecode.md @@ -1,58 +1,53 @@ -ArtNet Timecode -=============== +# ArtNet Timecode This plugin can be used to send the timecode of your running audio files over ArtNet to trigger cues inside a lighting control software or lighting desk which -support ArtNet Timecode such as "Chamsys MagicQ". This plugin works is meant as a -alternative to the Chamsys Winamp plugin, which doesnt work under Linux. -To get an general idea what is this all about, have a look `here `_. +support ArtNet Timecode such as "Chamsys MagicQ". This plugin works is meant as an +alternative to the Chamsys Winamp plugin, which doesn't work under Linux. +To get a general idea what is this all about, have a look `here `_. In order to work, this plugin needs `OLA `_ installed and a running OLA session on your computer. -How to use ----------- +## How to use First make sure that OLA is up and running, ``_ gives you the OLA interface. -Timecode Preferences --------------------- +## Timecode Preferences -.. image:: ../_static/timecode_settings.png - :alt: Timecode - Preferences - :align: center +```{image} ../_static/timecode_settings.png +:alt: Timecode - Preferences +:align: center +``` -| - -To enable and setup the plugin go to ``File > Preferences > Timecode Settings``. +To enable and set up the plugin go to `File > Preferences > Timecode Settings`. * **Enable Plugin:** enables/disable the plugin * **High Resolution Timecode:** enables sending every single frame, to get more accurate timing -* **Timecode Format:** choose between ``SMPTE``, ``FILM`` and ``EBU``. +* **Timecode Format:** choose between `SMPTE`, `FILM` and `EBU`. The format has to match the timecode used by the software which receives it. -Timecode Cue Settings ---------------------- - -.. image:: ../_static/timecode_cue_settings.png - :alt: Timecode - Cue Settings - :align: center +## Timecode Cue Settings -| +```{image} ../_static/timecode_cue_settings.png +:alt: Timecode - Cue Settings +:align: center +``` For media cues you can decide if it sends timecode or not. This can be set in the -``Cue-Settings > Timecode`` Tab. +`Cue-Settings > Timecode` Tab. * **Enable ArtNet Timecode:** enables sending timecode for this cue -* **Replace HOURS by a static track number:** if checked, the ``HOURS`` field in +* **Replace HOURS by a static track number:** if checked, the `HOURS` field in the timecode is replaced by a static number, which can be used to identify which track currently sends timecode to your lighting software. -.. Note:: - If you work with multiple cuelists on your lighting desk you than can choose - the following setup as example: +```{note} +If you work with multiple cue-lists on your lighting desk you than can choose +the following setup as example: - * cuelist 1 refers to track 1 and uses HOUR=1 - * cuelist 2 refers to track 2 and uses HOUR=2 - * ... and so on + * cuelist 1 refers to track 1 and uses HOUR=1 + * cuelist 2 refers to track 2 and uses HOUR=2 + * ... and so on +``` diff --git a/docs/user/plugins/cue_controls.md b/docs/user/plugins/cue_controls.md index 04564c4d3..981485893 100644 --- a/docs/user/plugins/cue_controls.md +++ b/docs/user/plugins/cue_controls.md @@ -1,22 +1,18 @@ -Cue Controls -============ +# Cue Controls Provide control over cues using keyboard and MIDI. -How to use ----------- +## How to use Settings are provided per cue, and are accessible through a specific tab in the cue settings window: -Keyboard --------- +## Keyboard -.. image:: ../_static/controller_settings_keyboard.png - :alt: Linux Show Player - Controller settings - Keyboard - :align: center - -| +```{image} ../_static/controller_settings_keyboard.png +:alt: Linux Show Player - Controller settings - Keyboard +:align: center +``` * **Key:** the key (character) that trigger the cue * **Action:** the action to be executed when the key is pressed (Start/Stop/...) @@ -26,14 +22,12 @@ be any single character that the keyboard is able to insert, so special keys are excluded, Upper/lower cases are considered, so "A" is not the same of "a". In general, what is taken in account, it's not the pressed key, but the typed character. -MIDI ----- - -.. image:: ../_static/controller_settings_midi.png - :alt: Linux Show Player - Controller settings - MIDI - :align: center +## MIDI -| +```{image} ../_static/controller_settings_midi.png +:alt: Linux Show Player - Controller settings - MIDI +:align: center +``` * **Type:** The message type (only *note_on/off*) * **Channel:** MIDI message "channel" @@ -45,4 +39,4 @@ New MIDI messages can be added/removed manually using the provided buttons, or can be captured directly from the device, when doing so, a filter is provided to select the type of messages to be captured. -The used MIDI device can be changed in the application settings ``File > Preferences > MIDI Settings`` \ No newline at end of file +The used MIDI device can be changed in the application settings `File > Preferences > MIDI Settings` \ No newline at end of file diff --git a/docs/user/plugins/index.md b/docs/user/plugins/index.md new file mode 100644 index 000000000..135054e73 --- /dev/null +++ b/docs/user/plugins/index.md @@ -0,0 +1,14 @@ +--- +hide-toc: true +--- + +# Tools + +```{toctree} +artnet_timecode +cue_controls +presets +replaygain +synchronization +triggers +``` \ No newline at end of file diff --git a/docs/user/plugins/presets.md b/docs/user/plugins/presets.md index c863a699a..2affc81f5 100644 --- a/docs/user/plugins/presets.md +++ b/docs/user/plugins/presets.md @@ -6,16 +6,15 @@ Allow to create, edit, import and export presets for cues. How to use ---------- -The main interface is accessible via ``Tools > Presets`` +The main interface is accessible via `Tools > Presets` -.. image:: ../_static/presets.png - :alt: Linux Show Player - Presets - :align: center - -| +```{image} ../_static/presets.png +:alt: Linux Show Player - Presets +:align: center +``` On the left the list of the available presets (sorted by name), double-click to -edit a preset. Multiple preset can be selected using the ``CTRL`` and ``SHIFT``. +edit a preset. Multiple preset can be selected using the `CTRL` and `SHIFT`. On the right a series of buttons gives access to the following: @@ -31,14 +30,16 @@ On the bottom: * **Export selected:** export the selected presets to a custom archive * **Import:** import from an exported preset -.. Note:: - The archive use a custom extension to easily filer others files, but it's a - standard zip file. +```{note} +The archive use a custom extension to easily filer others files, but it's a +standard zip file. +``` The following options are provided in the cue context menu (right-click): * **Load preset:** load a preset on the cue * **Save as preset:** save the cue settings as a preset -.. Note:: - Preset are saved under ``$HOME/.linux-show-player/presets/`` +```{note} +Preset are saved under `$HOME/.linux-show-player/presets/` +``` diff --git a/docs/user/plugins/replaygain.md b/docs/user/plugins/replaygain.md index 1c89329da..e895d51af 100644 --- a/docs/user/plugins/replaygain.md +++ b/docs/user/plugins/replaygain.md @@ -1,37 +1,33 @@ -ReplayGain & Normalization -========================== +# ReplayGain & Normalization -This module provide a simple utility to calculate a `Normalized`_ / `ReplayGain`_ -volume for media cues. The values are used as *Normalized Volume* for the ``Volume`` +This module provide a simple utility to calculate a [`Normalized`](https://en.wikipedia.org/wiki/Audio_normalization) / [`ReplayGain`](https://en.wikipedia.org/wiki/ReplayGain) +volume for media cues. The values are used as *Normalized Volume* for the `Volume` media-element, if a ReplayGain value is already stored in the media-file metadata -(e.g ID3 tags) it will be used. +(e.g. ID3 tags) it will be used. -.. Note:: - The original files are left untouched. +```{note} +The original files are left untouched. +``` -How to use ----------- +## How to use -Via ``Tools > ReplayGain / Normalization`` menu the following options are provided: +Via `Tools > ReplayGain / Normalization` menu the following options are provided: * **Calculate**: Open a dialog to set some option and start the calculation * **Reset all**: Reset to 0dB the normalized volumes of all cues * **Reset selected**: Reset to 0dB the normalized volumes, only for the selected cues -.. image:: ../_static/replaygain_dialog.png - :alt: Linux Show Player - ReplayGain dialog - :align: center - -| +```{image} ../_static/replaygain_dialog.png +:alt: Linux Show Player - ReplayGain dialog +:align: center +``` * **ReplayGain**: Use ReplayGain normalization using the reference value in dB SPL (89 is the standard default) * **Normalize**: Use a simple normalization to the reference value in dB (0 is the maximum value) * **Only selected cues**: apply only to the currently selected cues -* **Thread number**: Number of concurrent/parallel calculations (default to the cup cores) - -.. Note:: - that the process may require some time, depending on the CPU, the number - and size of the files involved. +* **Thread number**: Number of concurrent/parallel calculations (default to the available cores) -.. _Normalized: https://en.wikipedia.org/wiki/Audio_normalization -.. _ReplayGain: https://en.wikipedia.org/wiki/ReplayGain \ No newline at end of file +```{note} +The the process may require some time, depending on the CPU, the number +and size of the files involved. +``` diff --git a/docs/user/plugins/synchronization.md b/docs/user/plugins/synchronization.md index a119213f8..4d63716e5 100644 --- a/docs/user/plugins/synchronization.md +++ b/docs/user/plugins/synchronization.md @@ -1,23 +1,20 @@ -Synchronization -=============== +# Synchronization The goal of this plugin is to allow two or more LiSP sessions running on different PCs to be synchronized during a performance (no edit synchronization). The two session are supposed to be identical, so, only user interaction with the cues are replicated on other sessions. -How to use ----------- +## How to use The plugin usage is quite simple, the only thing to do, is to add the remote session you want to "control" to the peer-list, this can be done via -``Tools > Synchronization > Mange connected peers`` +`Tools > Synchronization > Mange connected peers` -.. image:: ../_static/synchronization_peers.png - :alt: Linux Show Player - Manage synchronization peers - :align: center - -| +```{image} ../_static/synchronization_peers.png +:alt: Linux Show Player - Manage synchronization peers +:align: center +``` On the left you can find the peers list, on the right the following buttons: @@ -26,15 +23,14 @@ On the left you can find the peers list, on the right the following buttons: * **Remove selected peer:** Remove the selected peer; * **Remove all peers:** Remove all the peers. -To easily retrieve the (local) IP address ``Tools > Synchronization > Show your IP`` +To easily retrieve the (local) IP address `Tools > Synchronization > Show your IP` will display the current IP address of the PC. -How it works ------------- +## How it works Once a session is connected, user actions are replicated on the connected one. -This is achieved by sending some info over the network, by default the ``8070`` -and ``50000`` (for the discovery) ports are used, those values can be changed -in the configuration file ``$HOME/.linux-show-player/config.cfg``. +This is achieved by sending some info over the network, by default the `8070` +and `50000` (for the discovery) ports are used, those values can be changed +in the configuration file `$HOME/.linux-show-player/config.cfg`. Two or more sessions can be mutually connected, this way all the sessions share the same "state". diff --git a/docs/user/plugins/triggers.md b/docs/user/plugins/triggers.md index 9d3dfcb8a..d94f3f761 100644 --- a/docs/user/plugins/triggers.md +++ b/docs/user/plugins/triggers.md @@ -1,23 +1,19 @@ -Triggers -======== +# Triggers This plugin allow to create triggers based on cues events, and to assign them a specific action on another cue. -How to use ----------- +## How to use Triggers can be added/removed and modified from the cue-settings dialog of the cue from which the event is generated (source): -.. image:: ../_static/triggers_cue_settings.png - :alt: Linux Show Player - Triggers cue settings - :align: center +```{image} ../_static/triggers_cue_settings.png +:alt: Linux Show Player - Triggers cue settings +:align: center +``` -| - -Events ------- +## Events The following events are available: From cd7607746e8174612c1ed0387f3d2bf32afadd64 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 17 Mar 2023 21:53:00 +0100 Subject: [PATCH 307/333] Fix: correctly update cues style when loading a show --- lisp/plugins/list_layout/list_view.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py index 644cf78b9..c5e7a62fd 100644 --- a/lisp/plugins/list_layout/list_view.py +++ b/lisp/plugins/list_layout/list_view.py @@ -291,6 +291,7 @@ def __cueAdded(self, cue): self.insertTopLevelItem(cue.index, item) self.__setupItemWidgets(item) + self.__updateItemStyle(item) if self.topLevelItemCount() == 1: # If it's the only item in the view, set it as current From 792ad9167774a3e7b69c64acc054ce8772ca37fb Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 25 Mar 2023 18:05:35 +0100 Subject: [PATCH 308/333] Require Python >= 3.8, update dependencies --- poetry.lock | 1219 +++++++++++++++++++++++++----------------------- pyproject.toml | 10 +- 2 files changed, 644 insertions(+), 585 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6fc557799..a18b095f5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,16 @@ +# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. + [[package]] name = "alabaster" -version = "0.7.12" +version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6" +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] [[package]] name = "appdirs" @@ -13,25 +19,37 @@ description = "A small Python module for determining appropriate platform-specif category = "main" optional = false python-versions = "*" +files = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] [[package]] name = "babel" -version = "2.10.3" +version = "2.12.1" description = "Internationalization utilities" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, + {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, +] [package.dependencies] -pytz = ">=2015.7" +pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} [[package]] name = "beautifulsoup4" -version = "4.11.1" +version = "4.12.0" description = "Screen-scraping library" category = "dev" optional = false python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.0-py3-none-any.whl", hash = "sha256:2130a5ad7f513200fae61a17abb5e338ca980fa28c439c0571014bc0217e9591"}, + {file = "beautifulsoup4-4.12.0.tar.gz", hash = "sha256:c5fceeaec29d09c84970e47c65f2f0efe57872f7cff494c9691a26ec0ff13234"}, +] [package.dependencies] soupsieve = ">1.2" @@ -42,11 +60,15 @@ lxml = ["lxml"] [[package]] name = "certifi" -version = "2022.9.24" +version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] [[package]] name = "cffi" @@ -55,36 +77,222 @@ description = "Foreign Function Interface for Python calling C code." category = "main" optional = false python-versions = "*" +files = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] [package.dependencies] pycparser = "*" [[package]] name = "charset-normalizer" -version = "2.1.1" +version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = ">=3.6.0" - -[package.extras] -unicode-backport = ["unicodedata2"] +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, + {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, +] [[package]] name = "colorama" -version = "0.4.5" +version = "0.4.6" description = "Cross-platform colored terminal text." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] [[package]] name = "cython" -version = "0.29.32" +version = "0.29.33" description = "The Cython compiler for writing C extensions for the Python language." category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "Cython-0.29.33-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:286cdfb193e23799e113b7bd5ac74f58da5e9a77c70e3b645b078836b896b165"}, + {file = "Cython-0.29.33-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8507279a4f86ed8365b96603d5ad155888d4d01b72a9bbf0615880feda5a11d4"}, + {file = "Cython-0.29.33-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bf5ffd96957a595441cca2fc78470d93fdc40dfe5449881b812ea6045d7e9be"}, + {file = "Cython-0.29.33-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2019a7e54ba8b253f44411863b8f8c0b6cd623f7a92dc0ccb83892358c4283a"}, + {file = "Cython-0.29.33-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:190e60b7505d3b9b60130bcc2251c01b9ef52603420829c19d3c3ede4ac2763a"}, + {file = "Cython-0.29.33-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0168482495b75fea1c97a9641a95bac991f313e85f378003f9a4909fdeb3d454"}, + {file = "Cython-0.29.33-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:090556e41f2b30427dd3a1628d3613177083f47567a30148b6b7b8c7a5862187"}, + {file = "Cython-0.29.33-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:19c9913e9304bf97f1d2c357438895466f99aa2707d3c7a5e9de60c259e1ca1d"}, + {file = "Cython-0.29.33-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:afc9b6ab20889676c76e700ae6967aa6886a7efe5b05ef6d5b744a6ca793cc43"}, + {file = "Cython-0.29.33-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:49fb45b2bf12d6e2060bbd64506c06ac90e254f3a4bceb32c717f4964a1ae812"}, + {file = "Cython-0.29.33-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:5430f38d3d01c4715ec2aef5c41e02a2441c1c3a0149359c7a498e4c605b8e6c"}, + {file = "Cython-0.29.33-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c4d315443c7f4c61180b6c3ea9a9717ee7c901cc9db8d1d46fdf6556613840ed"}, + {file = "Cython-0.29.33-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6b4e6481e3e7e4d345640fe2fdc6dc57c94369b467f3dc280949daa8e9fd13b9"}, + {file = "Cython-0.29.33-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:060a2568ef80116a0a9dcaf3218a61c6007be0e0b77c5752c094ce5187a4d63c"}, + {file = "Cython-0.29.33-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b67ddd32eaa2932a66bf8121accc36a7b3078593805519b0f00040f2b10a6a52"}, + {file = "Cython-0.29.33-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:1b507236ba3ca94170ce0a504dd03acf77307d4bfbc5a010a8031673f6b213a9"}, + {file = "Cython-0.29.33-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:581efc0622a9be05714222f2b4ac96a5419de58d5949517282d8df38155c8b9d"}, + {file = "Cython-0.29.33-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6b8bcbf8f1c3c46d6184be1e559e3a3fb8cdf27c6d507d8bc8ae04cfcbfd75f5"}, + {file = "Cython-0.29.33-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1ca93bbe584aee92094fd4fb6acc5cb6500acf98d4f57cc59244f0a598b0fcf6"}, + {file = "Cython-0.29.33-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:da490129e1e4ffaf3f88bfb46d338549a2150f60f809a63d385b83e00960d11a"}, + {file = "Cython-0.29.33-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4cadf5250eda0c5cdaf4c3a29b52be3e0695f4a2bf1ccd49b638d239752ea513"}, + {file = "Cython-0.29.33-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:bcb1a84fd2bd7885d572adc180e24fd8a7d4b0c104c144e33ccf84a1ab4eb2b8"}, + {file = "Cython-0.29.33-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:d78147ad8a3417ae6b371bbc5bfc6512f6ad4ad3fb71f5eef42e136e4ed14970"}, + {file = "Cython-0.29.33-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dd96b06b93c0e5fa4fc526c5be37c13a93e2fe7c372b5f358277ebe9e1620957"}, + {file = "Cython-0.29.33-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:959f0092d58e7fa00fd3434f7ff32fb78be7c2fa9f8e0096326343159477fe45"}, + {file = "Cython-0.29.33-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0455d5b92f461218bcf173a149a88b7396c3a109066274ccab5eff58db0eae32"}, + {file = "Cython-0.29.33-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:a9b0b890656e9d18a18e1efe26ea3d2d0f3e525a07a2a853592b0afc56a15c89"}, + {file = "Cython-0.29.33-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:b5e8ce3039ff64000d58cd45b3f6f83e13f032dde7f27bb1ab96070d9213550b"}, + {file = "Cython-0.29.33-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:e8922fa3d7e76b7186bbd0810e170ca61f83661ab1b29dc75e88ff2327aaf49d"}, + {file = "Cython-0.29.33-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f67b7306fd00d55f271009335cecadc506d144205c7891070aad889928d85750"}, + {file = "Cython-0.29.33-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f271f90005064c49b47a93f456dc6cf0a21d21ef835bd33ac1e0db10ad51f84f"}, + {file = "Cython-0.29.33-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d4457d417ffbb94abc42adcd63a03b24ff39cf090f3e9eca5e10cfb90766cbe3"}, + {file = "Cython-0.29.33-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:0b53e017522feb8dcc2189cf1d2d344bab473c5bba5234390b5666d822992c7c"}, + {file = "Cython-0.29.33-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:4f88c2dc0653eef6468848eb8022faf64115b39734f750a1c01a7ba7eb04d89f"}, + {file = "Cython-0.29.33-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:1900d862a4a537d2125706740e9f3b016e80f7bbf7b54db6b3cc3d0bdf0f5c3a"}, + {file = "Cython-0.29.33-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37bfca4f9f26361343d8c678f8178321e4ae5b919523eed05d2cd8ddbe6b06ec"}, + {file = "Cython-0.29.33-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a9863f8238642c0b1ef8069d99da5ade03bfe2225a64b00c5ae006d95f142a73"}, + {file = "Cython-0.29.33-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1dd503408924723b0bb10c0013b76e324eeee42db6deced9b02b648f1415d94c"}, + {file = "Cython-0.29.33-py2.py3-none-any.whl", hash = "sha256:8b99252bde8ff51cd06a3fe4aeacd3af9b4ff4a4e6b701ac71bddc54f5da61d6"}, + {file = "Cython-0.29.33.tar.gz", hash = "sha256:5040764c4a4d2ce964a395da24f0d1ae58144995dab92c6b96f44c3f4d72286a"}, +] [[package]] name = "docutils" @@ -93,39 +301,82 @@ description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, + {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, +] [[package]] name = "falcon" -version = "3.1.0" +version = "3.1.1" description = "The ultra-reliable, fast ASGI+WSGI framework for building data plane APIs at scale." category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "falcon-3.1.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:10ff3080aebe84fb45955cb02375ce13b6a3556c73edad282325eb67aeb42a46"}, + {file = "falcon-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca798f3240283a89881209dfa8eb20e2eaf8d01c50b33be5f70865c0902577ec"}, + {file = "falcon-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:394e16249d9b61dcdbb6653311c4a208f9fc68b696d0123d29f781fbd338cfd4"}, + {file = "falcon-3.1.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6245344fab1a7faeb9267c75b8f4fd6c4bda35e1a2fe8f547b832b547c7f2128"}, + {file = "falcon-3.1.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fc0ef213d6e66bb997d172ceaa04f6daa309cac47e2fcd4320234806c806467"}, + {file = "falcon-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:016fe952a526045292fb591f4c724d5fdf4127e88d0369e2dc147925dc51835c"}, + {file = "falcon-3.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:00e6c6b3ec846193cfd30be26b10dbb7cc31ee3442f80f1d5ffd14c410619156"}, + {file = "falcon-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7e6e1e6af16d1055454eaed5ceaceabca97656b28a8a924b426fbf0e26ec0f0"}, + {file = "falcon-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d53dabcf8212c38137e40a61795e312224dc7a437b03d7fb0a1b0dc3ed8d4b5b"}, + {file = "falcon-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:762854cc9f13082166c166c93fd6f2178ba1787170bacee9a4b37fab412f602e"}, + {file = "falcon-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:686a0167af40565a2057f3902a9fb8f15a423ad17a80c9caee932b668478c9ad"}, + {file = "falcon-3.1.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b8302953d72405750450d4f8b7651dc6c5a5199dbb104b598036818f917b1d8c"}, + {file = "falcon-3.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f187040b6632ed434c3f6bcedb98fb6559973123d1799e77718502d2b693701e"}, + {file = "falcon-3.1.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:1b8dfce6c379ba14d962abf479137258c694017752bc5b585ab366e2e8106a3e"}, + {file = "falcon-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d9c3dc6c5a8a2f2c3f1fd433a6b4e4bcef22c52166b91e2d6d985fbcadcc62b"}, + {file = "falcon-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2abecd50121ed969fa34d5c035a340ee4b21afc84dcd354acd548ab2edcc67b2"}, + {file = "falcon-3.1.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f6e3c42f3c20af33c040affe0a3e8cd358153304b48eb441adfd261c3bfd51d3"}, + {file = "falcon-3.1.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7aab2dd6683437d8739a0cc9d6ab6542f48e05445a0138b356f63983a7c98fe"}, + {file = "falcon-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6fbc130a12e35ca76d782201af7a558ac57d4e5e66ba3a8017f5a3baaed64f8b"}, + {file = "falcon-3.1.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:550566250ac2bc0418075f2ad177b7e01adef1815459c2d962e579dff07162fb"}, + {file = "falcon-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cf50b9a2dcf9c8f6ae8de94e2e6ac082449380784fb9d1a1fc80fade052aead"}, + {file = "falcon-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a5fa02feaf67a2bd0407201dfec92edb0eee59803c3e1e717cfa5a2232ffc77"}, + {file = "falcon-3.1.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ff2eaf9807ea357ced1cc60e1d2871f55aa6ea29162386efb95fb4e5a730e6de"}, + {file = "falcon-3.1.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f049eeeeea08e0a5fbb87d1fe131f85c7a0310c3a0a4226146463709fbfe12eb"}, + {file = "falcon-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:66d937b7b796b68640d63e006e475d9268f68dfb3f1468415259507db72ee065"}, + {file = "falcon-3.1.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:13121ab6a86597ec849e397272662f5cafcbe534e12c01e2913035fe4120dcd1"}, + {file = "falcon-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5af63f2d7f509353552b2436501449065f30f27542d1e58c864656bd3a7a9ef1"}, + {file = "falcon-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd1eaf1a5d9d936f29f9aca3f268cf375621d1ffcbf27a6e14c187b489bf5f26"}, + {file = "falcon-3.1.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bec014dc19a38d5a525ab948a8eccc885f28d2611bdf3f73842fadc44b185702"}, + {file = "falcon-3.1.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271fa0c4b0634e4e238dc7c2fcd57be5f9dd0f200553e46677ff704f6a8090e6"}, + {file = "falcon-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:7a7ecb8eafada89389c19eda44811e14786599c1d86c6cffa58c65150b24bc43"}, + {file = "falcon-3.1.1.tar.gz", hash = "sha256:5dd393dbf01cbaf99493893de4832121bd495dc49a46c571915b79c59aad7ef4"}, +] [[package]] name = "furo" -version = "2022.9.29" +version = "2023.3.23" description = "A clean customisable Sphinx documentation theme." category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "furo-2023.3.23-py3-none-any.whl", hash = "sha256:1cdf0730496f6ac0ecf394845fe55010539d987a3085f29d819e49a8e87da60a"}, + {file = "furo-2023.3.23.tar.gz", hash = "sha256:6cf9a59fe2919693ecff6509a08229afa081583cbb5b81f59c3e755f3bd81d26"}, +] [package.dependencies] beautifulsoup4 = "*" pygments = ">=2.7" -sphinx = ">=4.0,<6.0" +sphinx = ">=5.0,<7.0" sphinx-basic-ng = "*" [[package]] name = "humanize" -version = "4.4.0" +version = "4.6.0" description = "Python humanize utilities" category = "main" optional = false python-versions = ">=3.7" - -[package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +files = [ + {file = "humanize-4.6.0-py3-none-any.whl", hash = "sha256:401201aca462749773f02920139f302450cb548b70489b9b4b92be39fe3c3c50"}, + {file = "humanize-4.6.0.tar.gz", hash = "sha256:5f1f22bc65911eb1a6ffe7659bd6598e33dcfeeb904eb16ee1e705a09bf75916"}, +] [package.extras] tests = ["freezegun", "pytest", "pytest-cov"] @@ -137,6 +388,10 @@ description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] [[package]] name = "imagesize" @@ -145,21 +400,28 @@ description = "Getting image size from png/jpeg/jpeg2000/gif file" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] [[package]] name = "importlib-metadata" -version = "5.0.0" +version = "6.1.0" description = "Read metadata from Python packages" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-6.1.0-py3-none-any.whl", hash = "sha256:ff80f3b5394912eb1b108fcfd444dc78b7f1f3e16b16188054bd01cb9cb86f09"}, + {file = "importlib_metadata-6.1.0.tar.gz", hash = "sha256:43ce9281e097583d758c2c708c4376371261a02c34682491a8e98352365aad20"}, +] [package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] @@ -170,6 +432,10 @@ description = "JACK Audio Connection Kit (JACK) Client for Python" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "JACK-Client-0.5.4.tar.gz", hash = "sha256:dd4a293e3a6e9bde9972569b9bc4630a5fcd4f80756cc590de572cc744e5a848"}, + {file = "JACK_Client-0.5.4-py3-none-any.whl", hash = "sha256:52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73"}, +] [package.dependencies] CFFI = ">=1.0" @@ -184,6 +450,10 @@ description = "A very fast and expressive template engine." category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] [package.dependencies] MarkupSafe = ">=2.0" @@ -198,6 +468,10 @@ description = "Python LiveReload is an awesome tool for web developers" category = "dev" optional = false python-versions = "*" +files = [ + {file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"}, + {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, +] [package.dependencies] six = "*" @@ -205,21 +479,24 @@ tornado = {version = "*", markers = "python_version > \"2.7\""} [[package]] name = "markdown-it-py" -version = "2.1.0" +version = "2.2.0" description = "Python port of markdown-it. Markdown parsing, done right!" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, + {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, +] [package.dependencies] mdurl = ">=0.1,<1.0" -typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"] -code-style = ["pre-commit (==2.6)"] -compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"] -linkify = ["linkify-it-py (>=1.0,<2.0)"] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] plugins = ["mdit-py-plugins"] profiling = ["gprof2dot"] rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] @@ -227,19 +504,75 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "2.1.1" +version = "2.1.2" description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, + {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, +] [[package]] name = "mdit-py-plugins" -version = "0.3.1" +version = "0.3.5" description = "Collection of plugins for markdown-it-py" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "mdit-py-plugins-0.3.5.tar.gz", hash = "sha256:eee0adc7195e5827e17e02d2a258a2ba159944a0748f59c5099a4a27f78fcf6a"}, + {file = "mdit_py_plugins-0.3.5-py3-none-any.whl", hash = "sha256:ca9a0714ea59a24b2b044a1831f48d817dd0c817e84339f20e7889f392d77c4e"}, +] [package.dependencies] markdown-it-py = ">=1.0.0,<3.0.0" @@ -256,6 +589,10 @@ description = "Markdown URL utilities" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] [[package]] name = "mido" @@ -264,6 +601,10 @@ description = "MIDI Objects for Python" category = "main" optional = false python-versions = "*" +files = [ + {file = "mido-1.2.10-py2.py3-none-any.whl", hash = "sha256:0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e"}, + {file = "mido-1.2.10.tar.gz", hash = "sha256:17b38a8e4594497b850ec6e78b848eac3661706bfc49d484a36d91335a373499"}, +] [package.extras] dev = ["check-manifest (>=0.35)", "flake8 (>=3.4.1)", "pytest (>=3.2.2)", "sphinx (>=1.6.3)", "tox (>=2.8.2)"] @@ -271,37 +612,42 @@ ports = ["python-rtmidi (>=1.1.0)"] [[package]] name = "myst-parser" -version = "0.18.1" -description = "An extended commonmark compliant parser, with bridges to docutils & sphinx." +version = "1.0.0" +description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "myst-parser-1.0.0.tar.gz", hash = "sha256:502845659313099542bd38a2ae62f01360e7dd4b1310f025dd014dfc0439cdae"}, + {file = "myst_parser-1.0.0-py3-none-any.whl", hash = "sha256:69fb40a586c6fa68995e6521ac0a525793935db7e724ca9bac1d33be51be9a4c"}, +] [package.dependencies] docutils = ">=0.15,<0.20" jinja2 = "*" markdown-it-py = ">=1.0.0,<3.0.0" -mdit-py-plugins = ">=0.3.1,<0.4.0" +mdit-py-plugins = ">=0.3.4,<0.4.0" pyyaml = "*" -sphinx = ">=4,<6" -typing-extensions = "*" +sphinx = ">=5,<7" [package.extras] -code-style = ["pre-commit (>=2.12,<3.0)"] +code-style = ["pre-commit (>=3.0,<4.0)"] linkify = ["linkify-it-py (>=1.0,<2.0)"] -rtd = ["ipython", "sphinx-book-theme", "sphinx-design", "sphinxcontrib.mermaid (>=0.7.1,<0.8.0)", "sphinxext-opengraph (>=0.6.3,<0.7.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] -testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions", "sphinx (<5.2)", "sphinx-pytest"] +rtd = ["ipython", "pydata-sphinx-theme (==v0.13.0rc4)", "sphinx-autodoc2 (>=0.4.2,<0.5.0)", "sphinx-book-theme (==1.0.0rc2)", "sphinx-copybutton", "sphinx-design2", "sphinx-pyscript", "sphinx-tippy (>=0.3.1)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.7.5,<0.8.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] +testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=7,<8)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions", "sphinx-pytest"] +testing-docutils = ["pygments", "pytest (>=7,<8)", "pytest-param-files (>=0.3.4,<0.4.0)"] [[package]] name = "packaging" -version = "21.3" +version = "23.0" description = "Core utilities for Python packages" category = "dev" optional = false -python-versions = ">=3.6" - -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" +python-versions = ">=3.7" +files = [ + {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, + {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, +] [[package]] name = "pyalsa" @@ -310,6 +656,7 @@ description = "Python binding for the ALSA library." category = "main" optional = false python-versions = "*" +files = [] develop = false [package.source] @@ -320,11 +667,24 @@ resolved_reference = "7d6dfe0794d250190a678312a2903cb28d46622b" [[package]] name = "pycairo" -version = "1.21.0" +version = "1.23.0" description = "Python interface for cairo" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "pycairo-1.23.0-cp310-cp310-win32.whl", hash = "sha256:564601e5f528531c6caec1c0177c3d0709081e1a2a5cccc13561f715080ae535"}, + {file = "pycairo-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:e7cde633986435d87a86b6118b7b6109c384266fd719ef959883e2729f6eafae"}, + {file = "pycairo-1.23.0-cp311-cp311-win32.whl", hash = "sha256:3a71f758e461180d241e62ef52e85499c843bd2660fd6d87cec99c9833792bfa"}, + {file = "pycairo-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:2dec5378133778961993fb59d66df16070e03f4d491b67eb695ca9ad7a696008"}, + {file = "pycairo-1.23.0-cp37-cp37m-win32.whl", hash = "sha256:d6bacff15d688ed135b4567965a4b664d9fb8de7417a7865bb138ad612043c9f"}, + {file = "pycairo-1.23.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ec305fc7f2f0299df78aadec0eaf6eb9accb90eda242b5d3492544d3f2b28027"}, + {file = "pycairo-1.23.0-cp38-cp38-win32.whl", hash = "sha256:1a6d8e0f353062ad92954784e33dbbaf66c880c9c30e947996c542ed9748aaaf"}, + {file = "pycairo-1.23.0-cp38-cp38-win_amd64.whl", hash = "sha256:82e335774a17870bc038e0c2fb106c1e5e7ad0c764662023886dfcfce5bb5a52"}, + {file = "pycairo-1.23.0-cp39-cp39-win32.whl", hash = "sha256:a4b1f525bbdf637c40f4d91378de36c01ec2b7f8ecc585b700a079b9ff83298e"}, + {file = "pycairo-1.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:87efd62a7b7afad9a0a420f05b6008742a6cfc59077697be65afe8dc73ae15ad"}, + {file = "pycairo-1.23.0.tar.gz", hash = "sha256:9b61ac818723adc04367301317eb2e814a83522f07bbd1f409af0dada463c44c"}, +] [[package]] name = "pycparser" @@ -333,68 +693,75 @@ description = "C parser in Python" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] [[package]] name = "pygments" -version = "2.13.0" +version = "2.14.0" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, + {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, +] [package.extras] plugins = ["importlib-metadata"] [[package]] name = "pygobject" -version = "3.42.2" +version = "3.44.1" description = "Python bindings for GObject Introspection" category = "main" optional = false -python-versions = ">=3.6, <4" +python-versions = ">=3.7, <4" +files = [ + {file = "PyGObject-3.44.1.tar.gz", hash = "sha256:665fbe980c91e8b31ad78ed3f66879946948200864002d193f67eccc1d7d5d83"}, +] [package.dependencies] pycairo = ">=1.16,<2.0" [[package]] -name = "pyliblo" -version = "0.10.0" +name = "pyliblo3" +version = "0.13.0" description = "Python bindings for the liblo OSC library" category = "main" optional = false -python-versions = "*" -develop = false +python-versions = ">=3.8" +files = [ + {file = "pyliblo3-0.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5bdd6e1226fbc0fbb142309ed138f1bf10482cc99974d670df03e7b8dda59b29"}, + {file = "pyliblo3-0.13.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:13e718355224b8ce6126d5b00a3499074aac073ad4d1f4a349bc41e67e7922a2"}, + {file = "pyliblo3-0.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:95fb87f1b84eece18ec63719a54c6fb781fcfc98123bfc907a3993a1104249ca"}, + {file = "pyliblo3-0.13.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:47bb2d1b33a1ee99fa6b70fe22304be441f18f295086cf6b159b7312984f8200"}, + {file = "pyliblo3-0.13.0.tar.gz", hash = "sha256:5700eac4a6db2c1c492a99c17bbf1871e888309ae7bcd1c68473650778d60f46"}, +] [package.dependencies] cython = "*" -[package.source] -type = "git" -url = "https://github.com/s0600204/pyliblo.git" -reference = "pip" -resolved_reference = "5cef301f234cab5d39a416462381f83f840acec5" - -[[package]] -name = "pyparsing" -version = "3.0.9" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "dev" -optional = false -python-versions = ">=3.6.8" - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - [[package]] name = "pyqt5" -version = "5.15.7" +version = "5.15.9" description = "Python bindings for the Qt cross platform application toolkit" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "PyQt5-5.15.9-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:883ba5c8a348be78c8be6a3d3ba014c798e679503bce00d76c666c2dc6afe828"}, + {file = "PyQt5-5.15.9-cp37-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:dd5ce10e79fbf1df29507d2daf99270f2057cdd25e4de6fbf2052b46c652e3a5"}, + {file = "PyQt5-5.15.9-cp37-abi3-win32.whl", hash = "sha256:e45c5cc15d4fd26ab5cb0e5cdba60691a3e9086411f8e3662db07a5a4222a696"}, + {file = "PyQt5-5.15.9-cp37-abi3-win_amd64.whl", hash = "sha256:e030d795df4cbbfcf4f38b18e2e119bcc9e177ef658a5094b87bb16cac0ce4c5"}, + {file = "PyQt5-5.15.9.tar.gz", hash = "sha256:dc41e8401a90dc3e2b692b411bd5492ab559ae27a27424eed4bd3915564ec4c0"}, +] [package.dependencies] -PyQt5-Qt5 = ">=5.15.0" +PyQt5-Qt5 = ">=5.15.2" PyQt5-sip = ">=12.11,<13" [[package]] @@ -404,14 +771,43 @@ description = "The subset of a Qt installation needed by PyQt5." category = "main" optional = false python-versions = "*" +files = [ + {file = "PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"}, + {file = "PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"}, + {file = "PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"}, + {file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"}, +] [[package]] name = "pyqt5-sip" -version = "12.11.0" +version = "12.11.1" description = "The sip module support for PyQt5" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "PyQt5_sip-12.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a40a39a6136a90e10c31510295c2be924564fc6260691501cdde669bdc5edea5"}, + {file = "PyQt5_sip-12.11.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:19b06164793177146c7f7604fe8389f44221a7bde196f2182457eb3e4229fa88"}, + {file = "PyQt5_sip-12.11.1-cp310-cp310-win32.whl", hash = "sha256:3afb1d1c07adcfef5c8bb12356a2ec2ec094f324af4417735d43b1ecaf1bb1a4"}, + {file = "PyQt5_sip-12.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:54dad6c2e5dab14e46f6822a889bbb1515bbd2061762273af10d26566d649bd9"}, + {file = "PyQt5_sip-12.11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7218f6a1cefeb0b2fc26b89f15011f841aa4cd77786ccd863bf9792347fa38a8"}, + {file = "PyQt5_sip-12.11.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6b1113538082a7dd63b908587f61ce28ba4c7b8341e801fdf305d53a50a878ab"}, + {file = "PyQt5_sip-12.11.1-cp311-cp311-win32.whl", hash = "sha256:ac5f7ed06213d3bb203e33037f7c1a0716584c21f4f0922dcc044750e3659b80"}, + {file = "PyQt5_sip-12.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:4f0497e2f5eeaea9f5a67b0e55c501168efa86df4e53aace2a46498b87bc55c1"}, + {file = "PyQt5_sip-12.11.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b355d56483edc79dcba30be947a6b700856bb74beb90539e14cc4d92b9bad152"}, + {file = "PyQt5_sip-12.11.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dd163d9cffc4a56ebb9dd6908c0f0cb0caff8080294d41f4fb60fc3be63ca434"}, + {file = "PyQt5_sip-12.11.1-cp37-cp37m-win32.whl", hash = "sha256:b714f550ea6ddae94fd7acae531971e535f4a4e7277b62eb44e7c649cf3f03d0"}, + {file = "PyQt5_sip-12.11.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d09b2586235deab7a5f2e28e4bde9a70c0b3730fa84f2590804a9932414136a3"}, + {file = "PyQt5_sip-12.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9a6f9c058564d0ac573561036299f54c452ae78b7d2a65d7c2d01685e6dca50d"}, + {file = "PyQt5_sip-12.11.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fc920c0e0d5050474d2d6282b478e4957548bf1dce58e1b0678914514dc70064"}, + {file = "PyQt5_sip-12.11.1-cp38-cp38-win32.whl", hash = "sha256:3358c584832f0ac9fd1ad3623d8a346c705f43414df1fcd0cb285a6ef51fec08"}, + {file = "PyQt5_sip-12.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:f9691c6f4d899ca762dd54442a1be158c3e52017f583183da6ef37d5bae86595"}, + {file = "PyQt5_sip-12.11.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0bc81cb9e171d29302d393775f95cfa01b7a15f61b199ab1812976e5c4cb2cb9"}, + {file = "PyQt5_sip-12.11.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b077fb4383536f51382f5516f0347328a4f338c6ccc4c268cc358643bef1b838"}, + {file = "PyQt5_sip-12.11.1-cp39-cp39-win32.whl", hash = "sha256:5c152878443c3e951d5db7df53509d444708dc06a121c267b548146be06b87f8"}, + {file = "PyQt5_sip-12.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:bd935cc46dfdbb89c21042c1db2e46a71f25693af57272f146d6d9418e2934f1"}, + {file = "PyQt5_sip-12.11.1.tar.gz", hash = "sha256:97d3fbda0f61edb1be6529ec2d5c7202ae83aee4353e4b264a159f8c9ada4369"}, +] [[package]] name = "python-rtmidi" @@ -420,14 +816,33 @@ description = "A Python binding for the RtMidi C++ library implemented using Cyt category = "main" optional = false python-versions = "*" +files = [ + {file = "python-rtmidi-1.4.9.tar.gz", hash = "sha256:bfeb4ed99d0cccf6fa2837566907652ded7adc1c03b69f2160c9de4082301302"}, + {file = "python_rtmidi-1.4.9-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:8f7e154681c5d6ed7228ce8639708592da758ef4c0f575f7020854e07ca6478b"}, + {file = "python_rtmidi-1.4.9-cp36-cp36m-win32.whl", hash = "sha256:6f495672ec76700400d4dff6f8848dbd52ca60301ed6011a6a1b3a9e95a7a07e"}, + {file = "python_rtmidi-1.4.9-cp36-cp36m-win_amd64.whl", hash = "sha256:4d75788163327f6ac1f898c29e3b4527da83dbc5bab5a7e614b6a4385fde3231"}, + {file = "python_rtmidi-1.4.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d201516bb1c64511e7e4de50533f6b828072113e3c26f3f5b657f11b90252073"}, + {file = "python_rtmidi-1.4.9-cp37-cp37m-win32.whl", hash = "sha256:0f5409e1b2e92cfe377710a0ea5c450c58fda8b52ec4bf4baf517aa731d9f6a6"}, + {file = "python_rtmidi-1.4.9-cp37-cp37m-win_amd64.whl", hash = "sha256:3d4b7fdb477d8036d51cce281b303686114ae676f1c83693db963ab01db11cf5"}, + {file = "python_rtmidi-1.4.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5dad7a28035eea9e24aaab4fcb756cd2b78c5b14835aa64750cb4062f77ec169"}, + {file = "python_rtmidi-1.4.9-cp38-cp38-win32.whl", hash = "sha256:7d27d0a70e85d991f1451f286416cf5ef4514292b027155bf91dcae0b8c0d5d2"}, + {file = "python_rtmidi-1.4.9-cp38-cp38-win_amd64.whl", hash = "sha256:69907663e0f167fcf3fc1632a792419d0d9d00550f94dca39370ebda3bc999db"}, + {file = "python_rtmidi-1.4.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04bd95dd86fef3fe8d72838f719d31875f107a842127024bfe276617961eed5d"}, + {file = "python_rtmidi-1.4.9-cp39-cp39-win32.whl", hash = "sha256:2286ab096a5603430ab1e1a664fe4d96acc40f9443f44c27e911dfad85ea3ac8"}, + {file = "python_rtmidi-1.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:54d40cb794a6b079105bfb923ab0b4d5f041e9eef5dc5cce25480e4c3e3fc2f6"}, +] [[package]] name = "pytz" -version = "2022.5" +version = "2023.2" description = "World timezone definitions, modern and historical" category = "dev" optional = false python-versions = "*" +files = [ + {file = "pytz-2023.2-py2.py3-none-any.whl", hash = "sha256:8a8baaf1e237175b02f5c751eea67168043a749c843989e2b3015aa1ad9db68b"}, + {file = "pytz-2023.2.tar.gz", hash = "sha256:a27dcf612c05d2ebde626f7d506555f10dfc815b3eddccfaadfc7d99b11c9a07"}, +] [[package]] name = "pyyaml" @@ -436,18 +851,64 @@ description = "YAML parser and emitter for Python" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] [[package]] name = "requests" -version = "2.28.1" +version = "2.28.2" description = "Python HTTP for Humans." category = "main" optional = false python-versions = ">=3.7, <4" +files = [ + {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, + {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, +] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" +charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" @@ -462,6 +923,10 @@ description = "Python 2 and 3 compatibility utilities" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] [[package]] name = "snowballstemmer" @@ -470,6 +935,10 @@ description = "This package provides 29 stemmers for 28 languages generated from category = "dev" optional = false python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] [[package]] name = "sortedcontainers" @@ -478,34 +947,46 @@ description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" category = "main" optional = false python-versions = "*" +files = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] [[package]] name = "soupsieve" -version = "2.3.2.post1" +version = "2.4" description = "A modern CSS selector implementation for Beautiful Soup." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "soupsieve-2.4-py3-none-any.whl", hash = "sha256:49e5368c2cda80ee7e84da9dbe3e110b70a4575f196efb74e51b94549d921955"}, + {file = "soupsieve-2.4.tar.gz", hash = "sha256:e28dba9ca6c7c00173e34e4ba57448f0688bb681b7c5e8bf4971daafc093d69a"}, +] [[package]] name = "sphinx" -version = "5.3.0" +version = "6.1.3" description = "Python documentation generator" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +files = [ + {file = "Sphinx-6.1.3.tar.gz", hash = "sha256:0dac3b698538ffef41716cf97ba26c1c7788dba73ce6f150c1ff5b4720786dd2"}, + {file = "sphinx-6.1.3-py3-none-any.whl", hash = "sha256:807d1cb3d6be87eb78a381c3e70ebd8d346b9a25f3753e9947e866b2786865fc"}, +] [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.14,<0.20" +docutils = ">=0.18,<0.20" imagesize = ">=1.3" importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} Jinja2 = ">=3.0" packaging = ">=21.0" -Pygments = ">=2.12" -requests = ">=2.5.0" +Pygments = ">=2.13" +requests = ">=2.25.0" snowballstemmer = ">=2.0" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" @@ -516,8 +997,8 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "types-requests", "types-typed-ast"] -test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] +test = ["cython", "html5lib", "pytest (>=4.6)"] [[package]] name = "sphinx-autobuild" @@ -526,6 +1007,10 @@ description = "Rebuild Sphinx documentation on changes, with live-reload in the category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, + {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"}, +] [package.dependencies] colorama = "*" @@ -542,6 +1027,10 @@ description = "A modern skeleton for Sphinx themes." category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "sphinx_basic_ng-1.0.0b1-py3-none-any.whl", hash = "sha256:ade597a3029c7865b24ad0eda88318766bcc2f9f4cef60df7e28126fde94db2a"}, + {file = "sphinx_basic_ng-1.0.0b1.tar.gz", hash = "sha256:89374bd3ccd9452a301786781e28c8718e99960f2d4f411845ea75fc7bb5a9b0"}, +] [package.dependencies] sphinx = ">=4.0" @@ -551,11 +1040,15 @@ docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-ta [[package]] name = "sphinxcontrib-applehelp" -version = "1.0.2" -description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" +version = "1.0.4" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, + {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, +] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] @@ -568,6 +1061,10 @@ description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] @@ -575,11 +1072,15 @@ test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "2.0.0" +version = "2.0.1" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, + {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, +] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] @@ -592,6 +1093,10 @@ description = "A sphinx extension which renders display math in HTML via JavaScr category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] [package.extras] test = ["flake8", "mypy", "pytest"] @@ -603,6 +1108,10 @@ description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp d category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] @@ -615,6 +1124,10 @@ description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] @@ -627,6 +1140,10 @@ description = "Python Library for Tom's Obvious, Minimal Language" category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] [[package]] name = "tornado" @@ -635,22 +1152,31 @@ description = "Tornado is a Python web framework and asynchronous networking lib category = "dev" optional = false python-versions = ">= 3.7" - -[[package]] -name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" -optional = false -python-versions = ">=3.7" +files = [ + {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"}, + {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"}, + {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"}, + {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"}, + {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, +] [[package]] name = "urllib3" -version = "1.26.12" +version = "1.26.15" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"}, + {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"}, +] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] @@ -659,488 +1185,21 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "zipp" -version = "3.9.0" +version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, +] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] -lock-version = "1.1" -python-versions = "^3.7" -content-hash = "a3961454da2dac80e87176a4ef7667b8a2fea47a739a684e504718f7338261ca" - -[metadata.files] -alabaster = [ - {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, - {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, -] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] -babel = [ - {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, - {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, -] -beautifulsoup4 = [ - {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, - {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, -] -certifi = [ - {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, - {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, -] -cffi = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, -] -colorama = [ - {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, - {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, -] -cython = [ - {file = "Cython-0.29.32-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:39afb4679b8c6bf7ccb15b24025568f4f9b4d7f9bf3cbd981021f542acecd75b"}, - {file = "Cython-0.29.32-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dbee03b8d42dca924e6aa057b836a064c769ddfd2a4c2919e65da2c8a362d528"}, - {file = "Cython-0.29.32-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ba622326f2862f9c1f99ca8d47ade49871241920a352c917e16861e25b0e5c3"}, - {file = "Cython-0.29.32-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e6ffa08aa1c111a1ebcbd1cf4afaaec120bc0bbdec3f2545f8bb7d3e8e77a1cd"}, - {file = "Cython-0.29.32-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:97335b2cd4acebf30d14e2855d882de83ad838491a09be2011745579ac975833"}, - {file = "Cython-0.29.32-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:06be83490c906b6429b4389e13487a26254ccaad2eef6f3d4ee21d8d3a4aaa2b"}, - {file = "Cython-0.29.32-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:eefd2b9a5f38ded8d859fe96cc28d7d06e098dc3f677e7adbafda4dcdd4a461c"}, - {file = "Cython-0.29.32-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5514f3b4122cb22317122a48e175a7194e18e1803ca555c4c959d7dfe68eaf98"}, - {file = "Cython-0.29.32-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:656dc5ff1d269de4d11ee8542f2ffd15ab466c447c1f10e5b8aba6f561967276"}, - {file = "Cython-0.29.32-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:cdf10af3e2e3279dc09fdc5f95deaa624850a53913f30350ceee824dc14fc1a6"}, - {file = "Cython-0.29.32-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:3875c2b2ea752816a4d7ae59d45bb546e7c4c79093c83e3ba7f4d9051dd02928"}, - {file = "Cython-0.29.32-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:79e3bab19cf1b021b613567c22eb18b76c0c547b9bc3903881a07bfd9e7e64cf"}, - {file = "Cython-0.29.32-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0595aee62809ba353cebc5c7978e0e443760c3e882e2c7672c73ffe46383673"}, - {file = "Cython-0.29.32-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0ea8267fc373a2c5064ad77d8ff7bf0ea8b88f7407098ff51829381f8ec1d5d9"}, - {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c8e8025f496b5acb6ba95da2fb3e9dacffc97d9a92711aacfdd42f9c5927e094"}, - {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:afbce249133a830f121b917f8c9404a44f2950e0e4f5d1e68f043da4c2e9f457"}, - {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:513e9707407608ac0d306c8b09d55a28be23ea4152cbd356ceaec0f32ef08d65"}, - {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e83228e0994497900af954adcac27f64c9a57cd70a9ec768ab0cb2c01fd15cf1"}, - {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ea1dcc07bfb37367b639415333cfbfe4a93c3be340edf1db10964bc27d42ed64"}, - {file = "Cython-0.29.32-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8669cadeb26d9a58a5e6b8ce34d2c8986cc3b5c0bfa77eda6ceb471596cb2ec3"}, - {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:ed087eeb88a8cf96c60fb76c5c3b5fb87188adee5e179f89ec9ad9a43c0c54b3"}, - {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:3f85eb2343d20d91a4ea9cf14e5748092b376a64b7e07fc224e85b2753e9070b"}, - {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:63b79d9e1f7c4d1f498ab1322156a0d7dc1b6004bf981a8abda3f66800e140cd"}, - {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e1958e0227a4a6a2c06fd6e35b7469de50adf174102454db397cec6e1403cce3"}, - {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:856d2fec682b3f31583719cb6925c6cdbb9aa30f03122bcc45c65c8b6f515754"}, - {file = "Cython-0.29.32-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:479690d2892ca56d34812fe6ab8f58e4b2e0129140f3d94518f15993c40553da"}, - {file = "Cython-0.29.32-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:67fdd2f652f8d4840042e2d2d91e15636ba2bcdcd92e7e5ffbc68e6ef633a754"}, - {file = "Cython-0.29.32-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:4a4b03ab483271f69221c3210f7cde0dcc456749ecf8243b95bc7a701e5677e0"}, - {file = "Cython-0.29.32-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:40eff7aa26e91cf108fd740ffd4daf49f39b2fdffadabc7292b4b7dc5df879f0"}, - {file = "Cython-0.29.32-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0bbc27abdf6aebfa1bce34cd92bd403070356f28b0ecb3198ff8a182791d58b9"}, - {file = "Cython-0.29.32-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cddc47ec746a08603037731f5d10aebf770ced08666100bd2cdcaf06a85d4d1b"}, - {file = "Cython-0.29.32-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca3065a1279456e81c615211d025ea11bfe4e19f0c5650b859868ca04b3fcbd"}, - {file = "Cython-0.29.32-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d968ffc403d92addf20b68924d95428d523436adfd25cf505d427ed7ba3bee8b"}, - {file = "Cython-0.29.32-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f3fd44cc362eee8ae569025f070d56208908916794b6ab21e139cea56470a2b3"}, - {file = "Cython-0.29.32-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:b6da3063c5c476f5311fd76854abae6c315f1513ef7d7904deed2e774623bbb9"}, - {file = "Cython-0.29.32-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:061e25151c38f2361bc790d3bcf7f9d9828a0b6a4d5afa56fbed3bd33fb2373a"}, - {file = "Cython-0.29.32-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f9944013588a3543fca795fffb0a070a31a243aa4f2d212f118aa95e69485831"}, - {file = "Cython-0.29.32-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:07d173d3289415bb496e72cb0ddd609961be08fe2968c39094d5712ffb78672b"}, - {file = "Cython-0.29.32-py2.py3-none-any.whl", hash = "sha256:eeb475eb6f0ccf6c039035eb4f0f928eb53ead88777e0a760eccb140ad90930b"}, - {file = "Cython-0.29.32.tar.gz", hash = "sha256:8733cf4758b79304f2a4e39ebfac5e92341bce47bcceb26c1254398b2f8c1af7"}, -] -docutils = [ - {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, - {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, -] -falcon = [ - {file = "falcon-3.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:9400871fa536bac799b790ff28d999c5282c2bd7652f37f448b96718ddb289ff"}, - {file = "falcon-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aee71134800f963db0b4bc086d9bf0ffc529b40e90e6a5f2cd08059d7c9ea317"}, - {file = "falcon-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46739d4cdbcfe179e720cb34ef7dc135d4ff7c70320c56a5dfdcb87abb1ed45"}, - {file = "falcon-3.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:84b7e09b2c66a1942a8bc049d8f236bdb4f74d2314ad7551534a8d79ddd6a54d"}, - {file = "falcon-3.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fe77af775a2aba4019f01f54364abd1a5df2c820988ddf076416b1a7683c1b8"}, - {file = "falcon-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:aa2cf2aa3c0a3323d7a392af1edee1436fbe71c2b3d37699f32b6f45cfad8f75"}, - {file = "falcon-3.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:d31407f1b327b9d97166523bdecbec8be65a08d52f5dfb1a532f354118cc2a9e"}, - {file = "falcon-3.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccd588f943e755f4ab91261bbf80d0b25a5f43c3f6bda2ed8eae0f2e4fd3c3dd"}, - {file = "falcon-3.1.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2ef45e5d71de2417dbb36be0ed0fadb08d87fb059e8c3f86223f5240cacf547"}, - {file = "falcon-3.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bc720201105e24271c04e35a3a02aa1f7e9d92f1ddd82a846696b8ae7de89468"}, - {file = "falcon-3.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d714f631dae76e05cf91b0a7d509b8bf8bb86f371b3510a6fbf696469fcc2577"}, - {file = "falcon-3.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:19b240b748a7d9096cacdc508b824f1765937369107ff4e79b23618e0632818b"}, - {file = "falcon-3.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f20ab24083d499841f7187d2baa82c6f9baa90dbc1d322cc7fb83ef51d681f80"}, - {file = "falcon-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:783dcaee18a53c955636fc7fad5dd710899b2100b5f798af31e23961315c9bb5"}, - {file = "falcon-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b6a675685790a3d9e3c849320b3aea94d30f228b0f15d55fc32d6f9f6925c0f"}, - {file = "falcon-3.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c60de4a62fedaa9011a025a70a665a408c8e6c16cdc2e3baea05c23e7c7df202"}, - {file = "falcon-3.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ffbd6eef0283b1909895d3d3b36b1714a37bb1a6e9ecf6c5af60e0adbb322bd"}, - {file = "falcon-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:61686aed654fd492ceab674421a6a1d31599599d86ce710c6c650749f72522e0"}, - {file = "falcon-3.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:91dcb3adccb9d0faacfc3937af624ca14541aec37a7478d333710a91e8e438d4"}, - {file = "falcon-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cbb9b008c9e9f8b07e14b848791be4db31c359ef96f045332bbec7572250f71"}, - {file = "falcon-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c78d13ba42c50d716177f442a590c8946f4fe7c4ba07838679417339a9a80920"}, - {file = "falcon-3.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cac70a3e021e34453eeff968673e4081ac2a878b99fd9fb1260584cdf67e7890"}, - {file = "falcon-3.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d01a173610d216880f0e722abbe1cca51d5041f9e15db61bd79e95242dd617d"}, - {file = "falcon-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:eea1340aa264f5f0fd9023fb7b6cd41da1305af14e3e2c9d716446ad38ea2690"}, - {file = "falcon-3.1.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:94ce8ee49795ab18442c761c903353737ab271a506e992deadc5a7df669e804f"}, - {file = "falcon-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0044bd2c161f2b5c27ca5a0068e40bbeee560eb957a6ba01f9c1bb7439f6ab19"}, - {file = "falcon-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff65ffefa629bc72dc868cdca9dfa3ca5d1e5f01b02560b7094df2a0c5b890d1"}, - {file = "falcon-3.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:09dc5f10878565708dc9e58e62e9f2dfee5162a1ea992cb62582ffdacf543ec2"}, - {file = "falcon-3.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba5b27a401db6c025bb6885512b5cc46603d6a020e289902199b0c77c80777e6"}, - {file = "falcon-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:455a6c1a46af8505b0427a1ab85924aed47c462afe4e7cb7ae58c9fd6c5cc06f"}, - {file = "falcon-3.1.0.tar.gz", hash = "sha256:f2760bd18c16393a6fb5e55f371f67921edb72febe693a82b3c5e82195d087b7"}, -] -furo = [ - {file = "furo-2022.9.29-py3-none-any.whl", hash = "sha256:559ee17999c0f52728481dcf6b1b0cf8c9743e68c5e3a18cb45a7992747869a9"}, - {file = "furo-2022.9.29.tar.gz", hash = "sha256:d4238145629c623609c2deb5384f8d036e2a1ee2a101d64b67b4348112470dbd"}, -] -humanize = [ - {file = "humanize-4.4.0-py3-none-any.whl", hash = "sha256:8830ebf2d65d0395c1bd4c79189ad71e023f277c2c7ae00f263124432e6f2ffa"}, - {file = "humanize-4.4.0.tar.gz", hash = "sha256:efb2584565cc86b7ea87a977a15066de34cdedaf341b11c851cfcfd2b964779c"}, -] -idna = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] -imagesize = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] -importlib-metadata = [ - {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"}, - {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"}, -] -jack-client = [ - {file = "JACK-Client-0.5.4.tar.gz", hash = "sha256:dd4a293e3a6e9bde9972569b9bc4630a5fcd4f80756cc590de572cc744e5a848"}, - {file = "JACK_Client-0.5.4-py3-none-any.whl", hash = "sha256:52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73"}, -] -jinja2 = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] -livereload = [ - {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, -] -markdown-it-py = [ - {file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"}, - {file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"}, -] -markupsafe = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, -] -mdit-py-plugins = [ - {file = "mdit-py-plugins-0.3.1.tar.gz", hash = "sha256:3fc13298497d6e04fe96efdd41281bfe7622152f9caa1815ea99b5c893de9441"}, - {file = "mdit_py_plugins-0.3.1-py3-none-any.whl", hash = "sha256:606a7f29cf56dbdfaf914acb21709b8f8ee29d857e8f29dcc33d8cb84c57bfa1"}, -] -mdurl = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] -mido = [ - {file = "mido-1.2.10-py2.py3-none-any.whl", hash = "sha256:0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e"}, - {file = "mido-1.2.10.tar.gz", hash = "sha256:17b38a8e4594497b850ec6e78b848eac3661706bfc49d484a36d91335a373499"}, -] -myst-parser = [ - {file = "myst-parser-0.18.1.tar.gz", hash = "sha256:79317f4bb2c13053dd6e64f9da1ba1da6cd9c40c8a430c447a7b146a594c246d"}, - {file = "myst_parser-0.18.1-py3-none-any.whl", hash = "sha256:61b275b85d9f58aa327f370913ae1bec26ebad372cc99f3ab85c8ec3ee8d9fb8"}, -] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] -pyalsa = [] -pycairo = [ - {file = "pycairo-1.21.0-cp310-cp310-win32.whl", hash = "sha256:44a2ecf34968de07b3b9dfdcdbccbd25aa3cab267200f234f84e81481a73bbf6"}, - {file = "pycairo-1.21.0-cp310-cp310-win_amd64.whl", hash = "sha256:f63c153a9ea3d21aff85e2caeee4b0c5d566b2368b4ed64826020d12953d76a4"}, - {file = "pycairo-1.21.0-cp37-cp37m-win32.whl", hash = "sha256:70936b19f967fa3cb3cd200c2608911227fa5d09dae21c166f64bc15e714ee41"}, - {file = "pycairo-1.21.0-cp37-cp37m-win_amd64.whl", hash = "sha256:31e1c4850db03201d33929cbe1905ce1b33202683ebda7bb0d4dba489115066b"}, - {file = "pycairo-1.21.0-cp38-cp38-win32.whl", hash = "sha256:dace6b356c476de27f8e1522428ac21a799c225703f746e2957d441f885dcb6c"}, - {file = "pycairo-1.21.0-cp38-cp38-win_amd64.whl", hash = "sha256:4357f20a6b1de8f1e8072a74ff68ab4c9a0ae698cd9f5c0f2b2cdd9b28b635f6"}, - {file = "pycairo-1.21.0-cp39-cp39-win32.whl", hash = "sha256:6d37375aab9f2bb6136f076c19815d72108383baae89fbc0d6cb8e5092217d02"}, - {file = "pycairo-1.21.0-cp39-cp39-win_amd64.whl", hash = "sha256:26b72b813c6f9d495f71057eab89c13e70a21c92360e9265abc049e0a931fa39"}, - {file = "pycairo-1.21.0.tar.gz", hash = "sha256:251907f18a552df938aa3386657ff4b5a4937dde70e11aa042bc297957f4b74b"}, -] -pycparser = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, -] -pygments = [ - {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, - {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, -] -pygobject = [ - {file = "PyGObject-3.42.2.tar.gz", hash = "sha256:21524cef33100c8fd59dc135948b703d79d303e368ce71fa60521cc971cd8aa7"}, -] -pyliblo = [] -pyparsing = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, -] -pyqt5 = [ - {file = "PyQt5-5.15.7-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:1a793748c60d5aff3850b7abf84d47c1d41edb11231b7d7c16bef602c36be643"}, - {file = "PyQt5-5.15.7-cp37-abi3-manylinux1_x86_64.whl", hash = "sha256:e319c9d8639e0729235c1b09c99afdadad96fa3dbd8392ab561b5ab5946ee6ef"}, - {file = "PyQt5-5.15.7-cp37-abi3-win32.whl", hash = "sha256:08694f0a4c7d4f3d36b2311b1920e6283240ad3b7c09b515e08262e195dcdf37"}, - {file = "PyQt5-5.15.7-cp37-abi3-win_amd64.whl", hash = "sha256:232fe5b135a095cbd024cf341d928fc672c963f88e6a52b0c605be8177c2fdb5"}, - {file = "PyQt5-5.15.7.tar.gz", hash = "sha256:755121a52b3a08cb07275c10ebb96576d36e320e572591db16cfdbc558101594"}, -] -pyqt5-qt5 = [ - {file = "PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"}, -] -pyqt5-sip = [ - {file = "PyQt5_sip-12.11.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1f9e312ff8284d6dfebc5366f6f7d103f84eec23a4da0be0482403933e68660"}, - {file = "PyQt5_sip-12.11.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:4031547dfb679be309094bfa79254f5badc5ddbe66b9ad38e319d84a7d612443"}, - {file = "PyQt5_sip-12.11.0-cp310-cp310-win32.whl", hash = "sha256:ad21ca0ee8cae2a41b61fc04949dccfab6fe008749627d94e8c7078cb7a73af1"}, - {file = "PyQt5_sip-12.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:3126c84568ab341c12e46ded2230f62a9a78752a70fdab13713f89a71cd44f73"}, - {file = "PyQt5_sip-12.11.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0f77655c62ec91d47c2c99143f248624d44dd2d8a12d016e7c020508ad418aca"}, - {file = "PyQt5_sip-12.11.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ec5e9ef78852e1f96f86d7e15c9215878422b83dde36d44f1539a3062942f19c"}, - {file = "PyQt5_sip-12.11.0-cp37-cp37m-win32.whl", hash = "sha256:d12b81c3a08abf7657a2ebc7d3649852a1f327eb2146ebadf45930486d32e920"}, - {file = "PyQt5_sip-12.11.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b69a1911f768b489846335e31e49eb34795c6b5a038ca24d894d751e3b0b44da"}, - {file = "PyQt5_sip-12.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51e377789d59196213eddf458e6927f33ba9d217b614d17d20df16c9a8b2c41c"}, - {file = "PyQt5_sip-12.11.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:4e5c1559311515291ea0ab0635529f14536954e3b973a7c7890ab7e4de1c2c23"}, - {file = "PyQt5_sip-12.11.0-cp38-cp38-win32.whl", hash = "sha256:9bca450c5306890cb002fe36bbca18f979dd9e5b810b766dce8e3ce5e66ba795"}, - {file = "PyQt5_sip-12.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:f6b72035da4e8fecbb0bc4a972e30a5674a9ad5608dbddaa517e983782dbf3bf"}, - {file = "PyQt5_sip-12.11.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9356260d4feb60dbac0ab66f8a791a0d2cda1bf98c9dec8e575904a045fbf7c5"}, - {file = "PyQt5_sip-12.11.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:205f3e1b3eea3597d8e878936c1a06e04bd23a59e8b179ee806465d72eea3071"}, - {file = "PyQt5_sip-12.11.0-cp39-cp39-win32.whl", hash = "sha256:686071be054e5be6ca5aaaef7960931d4ba917277e839e2e978c7cbe3f43bb6e"}, - {file = "PyQt5_sip-12.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:42320e7a94b1085ed85d49794ed4ccfe86f1cae80b44a894db908a8aba2bc60e"}, - {file = "PyQt5_sip-12.11.0.tar.gz", hash = "sha256:b4710fd85b57edef716cc55fae45bfd5bfac6fc7ba91036f1dcc3f331ca0eb39"}, -] -python-rtmidi = [ - {file = "python-rtmidi-1.4.9.tar.gz", hash = "sha256:bfeb4ed99d0cccf6fa2837566907652ded7adc1c03b69f2160c9de4082301302"}, - {file = "python_rtmidi-1.4.9-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:8f7e154681c5d6ed7228ce8639708592da758ef4c0f575f7020854e07ca6478b"}, - {file = "python_rtmidi-1.4.9-cp36-cp36m-win32.whl", hash = "sha256:6f495672ec76700400d4dff6f8848dbd52ca60301ed6011a6a1b3a9e95a7a07e"}, - {file = "python_rtmidi-1.4.9-cp36-cp36m-win_amd64.whl", hash = "sha256:4d75788163327f6ac1f898c29e3b4527da83dbc5bab5a7e614b6a4385fde3231"}, - {file = "python_rtmidi-1.4.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d201516bb1c64511e7e4de50533f6b828072113e3c26f3f5b657f11b90252073"}, - {file = "python_rtmidi-1.4.9-cp37-cp37m-win32.whl", hash = "sha256:0f5409e1b2e92cfe377710a0ea5c450c58fda8b52ec4bf4baf517aa731d9f6a6"}, - {file = "python_rtmidi-1.4.9-cp37-cp37m-win_amd64.whl", hash = "sha256:3d4b7fdb477d8036d51cce281b303686114ae676f1c83693db963ab01db11cf5"}, - {file = "python_rtmidi-1.4.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5dad7a28035eea9e24aaab4fcb756cd2b78c5b14835aa64750cb4062f77ec169"}, - {file = "python_rtmidi-1.4.9-cp38-cp38-win32.whl", hash = "sha256:7d27d0a70e85d991f1451f286416cf5ef4514292b027155bf91dcae0b8c0d5d2"}, - {file = "python_rtmidi-1.4.9-cp38-cp38-win_amd64.whl", hash = "sha256:69907663e0f167fcf3fc1632a792419d0d9d00550f94dca39370ebda3bc999db"}, - {file = "python_rtmidi-1.4.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04bd95dd86fef3fe8d72838f719d31875f107a842127024bfe276617961eed5d"}, - {file = "python_rtmidi-1.4.9-cp39-cp39-win32.whl", hash = "sha256:2286ab096a5603430ab1e1a664fe4d96acc40f9443f44c27e911dfad85ea3ac8"}, - {file = "python_rtmidi-1.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:54d40cb794a6b079105bfb923ab0b4d5f041e9eef5dc5cce25480e4c3e3fc2f6"}, -] -pytz = [ - {file = "pytz-2022.5-py2.py3-none-any.whl", hash = "sha256:335ab46900b1465e714b4fda4963d87363264eb662aab5e65da039c25f1f5b22"}, - {file = "pytz-2022.5.tar.gz", hash = "sha256:c4d88f472f54d615e9cd582a5004d1e5f624854a6a27a6211591c251f22a6914"}, -] -pyyaml = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, -] -requests = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -snowballstemmer = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, -] -sortedcontainers = [ - {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, - {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, -] -soupsieve = [ - {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, - {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, -] -sphinx = [ - {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"}, - {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"}, -] -sphinx-autobuild = [ - {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, - {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"}, -] -sphinx-basic-ng = [ - {file = "sphinx_basic_ng-1.0.0b1-py3-none-any.whl", hash = "sha256:ade597a3029c7865b24ad0eda88318766bcc2f9f4cef60df7e28126fde94db2a"}, - {file = "sphinx_basic_ng-1.0.0b1.tar.gz", hash = "sha256:89374bd3ccd9452a301786781e28c8718e99960f2d4f411845ea75fc7bb5a9b0"}, -] -sphinxcontrib-applehelp = [ - {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, - {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, -] -sphinxcontrib-devhelp = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, -] -sphinxcontrib-htmlhelp = [ - {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, - {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, -] -sphinxcontrib-jsmath = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] -sphinxcontrib-qthelp = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, -] -sphinxcontrib-serializinghtml = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] -tornado = [ - {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"}, - {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"}, - {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"}, - {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"}, - {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, -] -typing-extensions = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, -] -urllib3 = [ - {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, - {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, -] -zipp = [ - {file = "zipp-3.9.0-py3-none-any.whl", hash = "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980"}, - {file = "zipp-3.9.0.tar.gz", hash = "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb"}, -] +lock-version = "2.0" +python-versions = "^3.8" +content-hash = "400acc7190a8d798c2f4dff0596a04c936c51eecc5c77f987f7076c7fd31b926" diff --git a/pyproject.toml b/pyproject.toml index 52b740bb6..2b1f536d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ exclude = [ ] [tool.poetry.dependencies] -python = "^3.7" +python = "^3.8" appdirs = "^1.4.1" cython = "^0.29" falcon = "^3.0" @@ -36,15 +36,15 @@ sortedcontainers = "^2.0" humanize = "^4.4.0" pyalsa = { git = "https://github.com/alsa-project/alsa-python.git", tag = "v1.2.7" } # Use a pyliblo fork that define dependecies correctly -pyliblo = { git = "https://github.com/s0600204/pyliblo.git", branch = "pip" } +pyliblo3 = "0.13.0" [tool.poetry.group.docs] optional = true [tool.poetry.group.docs.dependencies] -Sphinx = "^5.1.1" -furo = "^2022.6.21" -myst-parser = "^0.18.0" +Sphinx = "^6.1.3" +furo = "^2023.03.23" +myst-parser = "^1.0.0" sphinx-autobuild = "^2021.3.14" [tool.poetry.group.dev.dependencies] From b87e4d542071d1b3600f1348a67431aae580e07f Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 25 Mar 2023 21:49:00 +0100 Subject: [PATCH 309/333] Cleanup --- docs/user/menus.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/user/menus.md b/docs/user/menus.md index be3b70e74..b03c70c0f 100644 --- a/docs/user/menus.md +++ b/docs/user/menus.md @@ -42,7 +42,7 @@ This menu give access to layout functionality and display options for the curren ### Cart Layout * **Add page/Add pages:** Allows to add one or multiple pages on the current layout. Pages can be switched using the tab bar on top of the layout or directional keys. -* **Remove current page:** Remove the current page and all its cues +* **Remove current page:** Remove the current page and all its cues. * **Countdown mode:** Cues will display the remaining time instead of the elapsed time. * **Show seek-bars:** Media cues will display a seek bar, allowing to directly seek to a specific time of the cue. * **Show dB-meters:** Media cues will display a dB meter on their right side. @@ -51,21 +51,21 @@ This menu give access to layout functionality and display options for the curren ### List Layout -* **Show dB-meters:** Show / hide the db-meters for running media-cues; -* **Show seek-bars:** Show / hide seek bars for running media-cues; -* **Show accurate time:** Show / hide tens of seconds for running media-cues; -* **Show index column:** Show / hide the cue numbers; -* **Auto-select next cue:** If enabled the next cue is selected automatically; -* **Selection mode:** ``[CTRL+SHIFT+E]`` Enable multi-selection (for editing); -* **Disable GO Key while playing:** Disable the "GO" keyboard shortcut while there are playing cues; -* **Show resize handles:** Enable handles that allow to customize the size of various panels; +* **Show dB-meters:** Show / hide the db-meters for running media-cues. +* **Show seek-bars:** Show / hide seek bars for running media-cues. +* **Show accurate time:** Show / hide tens of seconds for running media-cues. +* **Show index column:** Show / hide the cue numbers. +* **Auto-select next cue:** If enabled the next cue is selected automatically. +* **Selection mode:** ``[CTRL+SHIFT+E]`` Enable multi-selection (for editing). +* **Disable GO Key while playing:** Disable the "GO" keyboard shortcut while there are playing cues. +* **Show resize handles:** Enable handles that allow to customize the size of various panels. * **Restore default size:** Reset the size of panels to their defaults. ## Tools -* **ReplayGain/Normalization:** Volume normalization [[see more]](plugins/replaygain.md); -* **Synchronization:** Keep multiple "live" sessions in sync [[see more]](plugins/synchronization.md); -* **Rename Cues:** Rename multiple cues at once [[see more]](plugins/cue_rename.md); +* **ReplayGain/Normalization:** Volume normalization [[see more]](plugins/replaygain.md). +* **Synchronization:** Keep multiple "live" sessions in sync [[see more]](plugins/synchronization.md). +* **Rename Cues:** Rename multiple cues at once [[see more]](plugins/cue_rename.md). * **Presets:** Manage cues presets [[see more]](plugins/presets.md). ## About From 907fe8ebd75ce88c252504d548bb10f78efc3ddf Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 25 Mar 2023 21:49:27 +0100 Subject: [PATCH 310/333] Update cart-layout docs --- docs/user/_static/cart_layout_main_view.png | Bin 38192 -> 51140 bytes docs/user/_static/cart_layout_settings.png | Bin 32789 -> 45692 bytes docs/user/cart_layout.md | 59 ++++++++++++-------- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/docs/user/_static/cart_layout_main_view.png b/docs/user/_static/cart_layout_main_view.png index ac965a22351363cf64355752099c91d039c3d9c4..163704fcaf00e47921eee7143809a52e6e3a360c 100644 GIT binary patch literal 51140 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⁣YEo z4LwvPEB278-Wx@qm+AgK=SxkEqZPS~Xks)$hJlzu>t7?gu5004v_8)+{omB4VBE)R z^B9if=R*hJ!Y|1PNLqaI6$TGfB0Cq<@FlerLrx*su?JG`?Y?DM_DA|m+-fKIRepy} zAMdEoXr8#wz4j%;t=VW-pzn{ZR1jFMEB}yU559VuRNxMNy?(Xh=}@>N;l9dW>to8@ z0l6RiXR716{$7;4)%}U+W*+LOV2bg5RT4R|IM4SoC(O@IYH9av{h#m&%u9*-_ywa z@BP(0YsisJN7(5P2}FRSC|r0p<(ujACo`%57W34p-(j?S6oW>qDja?7 z6EQaejaLDrKMJ7djzXAuq^Wlv7DY4PV}*G42A!eB?Atj< zOL#ZNvLmzbQ`^53a()hm@epMUGHvb07BWc`Mjz(hKW)Nie>t#-(5MZIhU;o4e+t(* zGgn1Wq_MX>3zw~iv*`FOkdZ1c{nuyotz>%>l)emB0cHY562CDw3tW^g^hi!E$< z2ub;MU{sh+lkMxOECea^cCPWcmwAZEQ9CD%qd}61w_%OXL%1vdl#rFf)gtNR z63^IE`N%^`v~hITo=WhAhisCD#WkPET&qXUA%zT)H2m~8#Ns!aa3`DKEw^U`)9Lzm zzldmb*SCCiM`8KfHig`hU}Qz)6lGvD;$jR&R~@QuF44SbDxhiT9_JfHh6mWu6WNNH z{-h=+eUEzNa>f@S&p9|l9_q+@b`QngxS}`hd?N?mBJ?BLfd&co{1vy(0Z+E!t`ct(^ZNn>~9Ca9)b^zN0>8QoNgSg_5MD5I8 z^{$=x798P&jx#YF`>Tf}O&6SI=UyXB7guoo?{zJ<1$XdhP21b+>e>UBhYm7q1H@?6 z9lE<5R{h(Ol8xQWD3NFQiD|Jft7ejaG7z--x_KX4!V|vjs6de-xM;`iCfXXQ4P^=N zEAzm?S8Ju8up_jk4?QXTwD=G{WoT@mUZMNGYlzrgoJ!*rrem1QS;rO9fmQkWZe6jhKSpIlhmSU zL`!lD6Kmj2N=l*f{R_1?EGl5TNUGtkVsNW?#+}8tOdB|#uI>^sQf@lr^lU*b858>^ z&#(oa#xwsJH(hY*P)n{of`aitIj2^FGzy0*WIn+m0)k;s&c>s$=fbdqmX&=pcTnpA(x<8WOB|p?hkD{Ym z(KT?w^0oLROW(skeu+p4kU2f9~6P{5+VNlQt<_Zlg}-DIM2} zhajZ4tH>rIL|6D2QHHQv8;Blpt#-XpwM&eY;kX;tJkB02ZzcWIxRz0CK7HRu-i}4q zY5Mv*oGv>zYAE>ineh^C`G(BRJnEiaZ{5nt{@xc0&tnLv4ByX@;jGSXI7p-QoW1xV zG@J_y}4G^%CYY! zd+=bcu|P`A2Ze9+Dx5C06lm3MA4VjJ_IYN(Qiq0~_gSb|VCnG7u64E1n#(_SSs!O| zi62M9nTLs%J|GHJ)tEp_&y<_jaZ*NSQ`G&Rm=5FP5 zkfkDnt(eep{K1(#_8SG>OCA-ABmR8OX*`ML@3#}gNa4+%ZUPD*D#c)lS z#+Bdd1=BHj#!LlYeFjIBo(8_qMF+9LbxrN2@^zpIZ6fa)mH6HYYNqFQsf!?IP^?+dX7p zNDDR3y;jnxGupL1oXXJa!um#~u1(pD`h&7IC??l84d+Mz{O1{nNZ#Lm{57{}oVx@B zY+3_{@$3kf94s?!x4)`xJyjM}B{2B*Rf*I$8XHL`;eqbr#FK31@VG*>B!tMl)CMHeJUrhMZui4hGgl zp6I9`kv{DeJQJH~-B*zwU|9dmyS{fz2`L`&Q71X7rB(#v2C8PRkGrr8nMdh6H-4nW zA4Ntu4Q=t$o@sk9hb&9C3dk5UB_=C^5%W)6NmdA#`3la_YNLjPvYU+4VLfd`X9dJ|2b11l5N4j+Lu^alxN% z1KergPj0~k;Z^u;WQH2vmYBrhN$DY6-BK#;?T40dpm52I#}FZl#gbk1^(VO- z4RrNE&(d3Mn?7H+ciC^YEtZOwAA&U?U!K!ZVkX+y*jPQg;Xj9vY1@SLn8+|bg^1Ia z?wLenW@QCAf<*ti1NpHM>x<{lf9B^~4jlmrZ*`5iO7PP#8SF1_t|8(ya=}BE$=hDQ zFV{~%fs&6*@c_~l2+FrL^c|43ghKI8Pfr2DUv?fYX0WTP>-Tre8nb|xP?zBc-l~t+ z8cS~-)SJPcvZ$S{t>W>V2H$vOJ63h@AIJ7HzkdB11P$g10VqMr%EP}F0C7GwP0hEW zblYQ@(%I7)Fc7_FA=%lX9xAjY}gfyh$2_gm`+?GB>#o5c(a3`7urS|#IK`sBVv zVS0Q6G~UF0ha^`uPH!OB_lW97LHXcFJ=>wYt)T=ywJQKG8~5W!ZVB-VU{Soh+Rxe9 zX_`~m^;Qh-uL{Pw*txj;weH`nGzk?rySPj)zvAMuTyFLPvi<0H%)d%XHh0Feb!V^f z`iFM*#=rgE}ix}O;XSTSJNf_5ra7`XdNq|n&($ex08+f#on%s%4 z?w$ZI5SNVo=dWMbqh}!J4Qzm{Wh$=57@G~AU0RCVYs{Bmph2)yS&rs4HhLe60X3Q! z7DZr+W;3M*g-LuQIcWu}KG#2Tug^i$NE0SIO}m2E?*o?TXQkm@wEA~iT;!(i-Y3x)OuoHVU*J~lxj)*<%6X_|22T|ACW1QFq>N`a4gOR4VkI=i>*?vq_A^vd%X*_G=@}!{K|zrDcWtTw z5sqrVLXoek&!_#6vM4d5@l8ctG(2YzeF2=M!^6Yk43^k_p8VPbc{+kyJih7CZj&lR zq7vMfbmv3>uxd0;ZcUBblu>y_#RrU>fa4A3Sb2OJ(03lD=xr|=QBTJyqYtbSd)iy# zf(bVwTe%otrgh_6YU8XORIr{9JrpRFo`>SoW|ft3F;Ns36;Z#|jw=|xGV~Z5AMXfx zLakkHgaT|BfGZ7fY^@v|W;zqpQx{;Br)_0f`ysr-r3c;Jcs*q#e7^Ut5t>WDnf|K8 z59nD$QVRm&u=;>e<(Y{9F5tw}R8Hpy$?L-&6CU@Y<&sCoG-k!dTeww;HxOj824*DQ zzquFz`x)TfIsv+tq#>!v0fg_lJyqnsJVyOjL1bl;d&>YkiibkUf2y_rx5)bcUv)H# zIV-@<6R_c!p094}i&Yu?s^pixZ9aA4BhU75DWIAfQ&3vU3NX_+#`bo1``-!LOtXLc z_HAMUm@80|ZY3;t-W;f29oaq`pPx^DikI%O>}5^l|E1T`dDusQHKJeipc{21oMB%zX6F?cZUxC3nXcAEJ=KrjOOSd9E`b8J zQsd9Dliuq^l$a1NQIgez<`C4Lgtg-IX6f@sPEQX(! z|H`>Qx%c;!Nyq2*BoS{SV)Wq`Ou!GE?Hx7HA65YrhV#^g-&lze;8OJMW~?roh=pTt zV_HV!;uUWj(`@Io;o|jD+P%4dy0B{6n}u;sw=Aax*^f&XwEemv<5hPl#x=QmeU`*m z@_qg`J9VHV>Fy4q?_re{Ey?ixwSm38{k$bK+Xf@n0KinvuXK$DS4?0$ylj6x{Udv~7}oh66OPOl z2-><@c`W2<@SO8Fwzg8r^1Z#Eu1#YadR@M7pYn)g`ijTvzxk3XDw39vY`Ez&)=^Uu z#Q!D8cD4eVA$}p;&mNsDhao8LNaujKGrwJSdvDdxku+K&$iD3(fnaeun$j$Xfvh{0 zwD$<1Y-vhH_DGv!#$dE~;%VzzxBt}0QP%2{Bwx_qZ`iq(`o#H*=yu;b_%`?x|*Z#Z!|qtFbV+c*bcqx3AWif?weU z_scR2n$BbOhQ&+zkrr_YXH3v2o^>49F0Tx=b7xD$sj5saKww&B>!9xuay$*W#i@o$ zs(z&5`I|2LtN89y)lP`lf)y6lI5%}zv$ua;rY^sLj@A_ubvE9gjV$bo%*)?YzWTLa zl%3-(1&7!20^{@&2Al#EwLhT(Qlz}qDS zYvu2z0-9f@0UAjAp#=>rZF4Nr)|QT z?`_f-%EN1+?tk$Qvnn`-hRhRA#-o<{_#1a4n18h*#1Gp(vG!3>Jva2`pl@&&3X`EL zDr*7InK;WHeQdvYOK8OBzm0C2E$3NtKx3(1S4}f%^9L``U89r#kg>A*yY^S)Ftc$i zx3z}c#BP77CN#c0WAG3uO0L=a%K_@RCnr9%{@r*QW9DSdXIZs+6MOvh>}J1jn($mS zzO*5B;p2}FV2_Bx@agx_xYHb~l~*exErMW`ob1R}&j1^`Ynnd|2K=tK^+%IeynRQz zLqG6%Y<6lQZYN}T9u73`#0$!2=QqL3*&j?O%Hz}PjV%J{_jKaRZU_@b2-duPr!IQ> z=;kWrEjSwzoAo6WN5PR!nXdB+ZxDjJ-*mX83Ke{5d}^T4&!TxaX`5!w7PM;D3ARqc zNPC(uf0xb8)0kS?1RzgWZ!|=GU5^{O(o0q1xwMr`VZs9Y_T3;0Pkn`vA<{*U6+Aq7 ze0$%pI%wczV@Ce8J?r0FoqBJ|$+P?YT|5}ePsEsUV|aqgz1IzY3tz%nLKBGPtijcq zPNckgUvD)204t2PfBRb*d*)dC`i;w6`5}B?bV}A&>zSf`#teNP)+};>saYqXyQn^{ zC}G1H)c94CRNQvX`6Cy$TAN^LmDU6^`*|eUIpurNfBnIH76zqm#(ES96m}ld-vrv; z-ozAr99lD8jrhlg z(s{*hNWQmX(7zH`f$xUbLs8f>ci(pVFm*?uQ8Z62mF)W3`#dh%hl+ce$@)oefQ`OMKXWfdv)N})lT}cYm9?@_L@@F14w$26O%izlg?E%0=gK_HTt`Rn_dFX>!@@dXyV&^FYHC> z@_IHR>FPtkw*Wwcud0UJt@V@fkMd0rYLA-@8@q6vPo1$uDtl$-&hJu44BhDk^_vqX z)=eyi(pBX@T_$9_@a3Z?x!*cE2}s zEHQiMsmd``{nw{x!hN3B$2t!VB-QDY1!bhi>5i&jpV-xM*LIn5J2g>~MLl|9jqgsWRvt&)M0ebX1J>R<`NXO!MH zhHGmXZjO7(f@gh#TjM^>@%jGhs$dV1w3g_ppJ~eFCnp)E_ATL~_f4;Mi|2Y(GZq6b zHpji1lUhLCYMr`J()hY>VzQC6LVlh%SM{ zTE#tlB#D^6%>-X@OSNy$+F~YMT1G5%z)P_p{X-DSdNjVf?b2Jb2#SVJ%W99k_h$V? zU3k+!JBWi3dp_D%7iAUs9(Tjf>Q^zvecV!y?v2DgSOyIZcx8Q%#AUVK@%a_ir5P%x z{L#BSu6+gbeDC2@I^%OovJgHBx-X+KS}W8nqQgf=^?C&0*R+L>nGBJVghb(tP$C?&V8eCXy1UO#wt@vw_%VIX6my>N^H0?cOfDM3&pu02N zGo}>AA5~P=?mD)EOYrv^iKQTvOly6i<-TzDZ{gtp93Ve0&tmHF=q z7TZ!o$0g^?DJkKA-2>2ojOf_O42cv~zf-iSqeZC0^h=AY(rtL#QjcAR=l9etD(Dwt z=FS7#rRK=ijpu{b_0JA(Ccu@9h{wn?JuX6zG=#8+6Rq@O(l9ylY^nM5!L~C|Fkuid zpC~irPALz5=^oy6;DzY%_JC9P z!E=4@7r|p~qu6Isv3#&lhkWYz=r;-G3ITal`RB6UPgcmkJ;Bxc@!}j(9 zHpL1l__>RY+hJ)Cv24bvl5qgXYW1mZ<`Ml(L&^L^(P081B4#y=ZKCYmfVbjN8!%&b zbGRRPa_xt{U%tz7@LXLPs^l>}lT{`Ejobh_iNNP7_4)Dn2s`86gZ}4BfBGAHd>(&J zuYUW>BXLP^=XbYSMjITC`t)zJ_?KjcottNd=Dxl(`Hs)7mi+lP{i15)?H@x5df-8C z`RaG$sS|ZR2CRgg{>_#!^YP&n^xZKe(+t_Map=%89IEZ3Ggd_Wf$Zi{Aor+8xv##7 z=(r$kogsvWcH4j0=wXs@9qc?=C?WLYm*v^rVmg5bOo@=?^~m2kYU%qu30TI;yU1i~ zv3>Qj#ztW~{uv5sRaMphKz}qe;^H~U!qa*J2pP}h8Ky6jNGVv=B8qxU0+qWa&kMc`v4eT#>*4K{i5))%bhdLjH@&hmS(gKtAE^h+Try5HZD@q2SRRmWJDlB4YyS*r9WuL&WyPM2UEP z^6G-rYdYX5M!N)LGbYYQ@Bjs-YpR2fV<&kcE|z(j*_V&(=OAxT5}GMry})%F8;@mf zEB_uaD#xH9qB3|qFHYAR)grg4d?=rKlJ`R`;GCu+Xqo#=`Vb||+8ouBfl^zHaSvtg;s?a>8^6+iqc{>Pn z*xt=a#OQ*TDl(4*FY|}SGFi^)cvniS*zha;BwsHQRQ5x7`f|lBqDTbL+T#Bw*Y5Hp4k)} z(7_SXo@nq#5hqbMC)LSUnORa-h3^ z^4jjH^C0T2&*2A_MGI5toM3Nw$;0LTL3J zkP&d}SJNjopdUp^;6qDO4+`nqp*+jigrajsBUwKmz-8vP=v8dH<&kTG5%X82CO>_ndcg?<2>{-Ph^PCPza=$RcDz|mN+MRT zd&R-Uuxroy6umWbwJ3r5hvBR-W4d|A0K>jq@_I8B($Ez{RRa$^!RBzxZH0KP27WXS zpJgND_(!5JRI(D?cD4LCM_`P4M@u^2;h*SxIh zvGjtO-qYywS7qBV<}Ns%zBG#`FUxF2l}>4nu2doe#`?|2bG4$$s|XLa4rs9`PQGdt z9xdGAm(jNdrW`f|FFg^HNhUZRSrBPB4BV^Ft^2CIX8UG1bZ+8rh3oZU8ux;LEevgD zQ6=k|LEyXM%vzmieye3C`S1y>ZxS)`FVF%osB&^xw6wHc&^hC^&M&ua$=xVxdg03o z@aw;|MSPPwv&}{>x{bKL7Yqr692?LQmpSXzLDvC0b>Q)dVfhX|y3eqEW@` zL2G?ue?LWE)mgB1#U-v>R+gC5LD6qSiMxGWAT%a3L8Y+G)ysd7|JoMkhmz@G1aEVz z0R9Xei|||eqNKYk4Aw>+_`Wxk1#^0w&&c%7_(1s5wVOv$>p(=b$ym_OKTga4*5At% zObwNv7+;dxpA~3@!}52_{jxZGZg$HuSU;^EhpbfDdmtG%de@HiR8Nb@>!MwKnZ2Pd z)o+=e^9E9c0B?wc*Uocc#JR)|v5C3?rD?%ZWne0cR_q?%Y)(ecU_w<{DK(Z_5vq3h zHp^|(%lLO-!M>EJ3V2Bk)|1MT*(dyV< z=}+anQjs1)I2U-b(i|v>s4QkwaQykjD7)c#`kfX zXYc9(ZG2RSw&>6=l)Sy1#y1`BzdBknDGv`*BnvtJ3~#-j+GnP@?(m z`kh`qZ+@`W{8Z{86rHE$&bn+$iY5S$yp~y>Y z!6iRj@Ew82dUxX-VsZ4>$~hTVTg_M1`#z+C0}RFe$e!tt5_;hMgIvL3tCb5Wbdwjw zQ6}17HC)1+ZzR$G=!fJZ?8`RifG)az1AumbRmAEpE=FL32j$V`3JeY3%Rz{Qg^y~lpP*AN?QnnfT`z z0sJzP0f0;fFviw_U_ik0u_G@fPwY$@eu9hh%guv;qOMKJH+@c4`$!RT0N7X(@7MhNIQ7z+PAw^z5*YGlYWzmi;ADis`90~0+;;PhF}dNE_wFLob4f?09qHnNz5p8 z9sNX6GFg8B?&zTb!>Ind03eWhe1B*p0jT_Fd5h+{puh1S9{vq9Oa9w3dgxm;5E7=_ zuKugt0UbKC7>Xml&uqyWvZMbH`>A|1q=COljQegHWFI~BopLf{vGYL>mSa6QDt>r_ z!|)8~DG)2XVW^$P**@kcy!+)lqw=7Fw>xqK_uU<3j! zYP&7M4^mRVw-c?7kJhAel!&?Di?bfB;zeQ1r+|zClITNPn>7!&=oaKIfaLQHq&9tp33ytWTwHf3Jk0zWI+gd>EAX zUA>08|&PqPWCE>4M5Z32Q=82d8xDZ0yY3F}o zy%e{%)v|LkMEId{295{~MP?$M8J@VR?6NY4=kKmUFur8BP{>hfmi=p2b$$?ZOs&57 zyRpA_a3E%iVhrf`CMgw;v3&(-5Hx+1kI$QV44E7##Xbl>+&Jk}Pm$p88^ zpdG2iKuGcT`v9eB$%D=?ekj0o0lp*U@vDlS(pLn=7u*pXmN?~%F*ngLCjm6br~do_ zG+yxlo!!#CLf#SX(K7&}|4$dc4YM3HX_2L5WUxC~TwU=~d3=Pla&(elL0zTYFb$(=`6%2bG-sukMzs9ldZ#5nUm}apLDS$Frc-wj{W`l^zrlb8k)X@ zqBqIjHP9Is60Uh=fKjsMsQva~(_{s38>oy$wH!+cZ?zG?Qf~vsP@Qn+SS$H4@3JK> z-IJRJ@;SkBO&y;%8cseJ5-Gy{ZWaBV6H>rG1cEG z%YL>KKC2RziTZ@CBj32qBA~j{p&^3+1NsUqGapuc?tC2b z<&!^9Sj!42#s}N#j6AiBaC$_;-LYZRRk`2kvwvtxU%sz5D>fJtUyhz)X4`JmzAEX6 zD#uRUUl~-A3X5P>&G%WM7h_V6ldUyVJiT&J$w)eEH89YuGrw{uwEw{@kJ(xJOHx}f z~9!~?gfx%y8J4KeCCB(yWRS>NSg%U!gMuzwJP zG8o`X>$jWv%kV%}K_E-D7sr=V$-#OF?ZMP*iEGClUhfy!Vtd;sQ&x*`p9yyV)nv=e z<#wztQIWFABw4mk#7G#Ts5g`_L?y7Lncubp+wF7OIM_klsUc1%W(3DCumetSb<7rO zLFO7U%>9zr0~8s7B?pcxr_11S)74)U+}T7QYdThjLytH@%#~Zf5+#rAldZbN$yy`Q`$guV*4gImD{1VEO+8z2XW-Bq7>b>-?<2w2>ViD#(XQgB2gb5XUSZ6L?IT@OZU2 z?0D1CJIc>Z#t~*+?(So4zVw-or{>9FRkR0Q6mF z=--Ya4;J|tR|cAxvrf#Eu=%T#KoQRS{{LYCpbL8M3Kb*`ymzEt`h{JH5!yit+(c-k zjYU6S_MRY=aqt73k^;ee?{!WHMTEo??I1O25cwEpN^F9>x{-j<3@xg#$s$Dq%vSFg zcrI4TG0gJw2bwj}s~n?UC9e!``K4+7YR%RRJfvL4TfYNF!#|J&{nl@9eYHwZ&FIH{ zJ=ouqODaqtsd@Xy)PKeuC+Obiu4ih`k$?7P5AI30zcSRHDAj4i?WofMOaSCafAq{B z>coZ(%z?Hb&{2`B_0|k|Kr-;=x|4WpUPreS0JE3ZkaAMb!0QimfYu9ImFhy;US?vz zx#7UyQZ2phvB9OrD1lnH_`k?8-FhDbBBD(fUzPuIR08~eiu=l-xT2;_ z5(w@RAh>JL;7$kw!Ga_}aCaFT1_>T)AP_W2a0u=`BoLV31oy$+-Pudts;%0suXgv> ze)*9p&OP_uK61PJbU)qw5#0*Pb_Z+45abTG>%PDRfw6}ge)djehj*>DpmJd?PWEND z+4&qbx(Jt}#{0=9(=4EOj=UbShS;qnLu$c_4C0Mt4{w`P-ZZX}Tf5HG00W2qmToMr zA;!GH{M5dWvaAsYxPmW#9`RJSUVsOtM?8poknxi~o*dSYK)6e++Hl<{4s=8gQP&+_ z7fmj)68z-;AnX50(COe)%S7U}^-=jq5I-?G2@G?RZ_w%#GmQMGJR% zgW167ppw}Ac9zwG=a*Tbukk`&JFF0iDY5bkI6-!+*U8{c69@ zzerw^#eVRj7jMR;o_;bU@DuG`X9iMUI62lqvGFrYFj;=iFme?1i^O&DfSS6sbl*p! z9{0Z0%zAB*9Mx$`iGoHC?ZPR1C=(a2A`s!U@ZduQJ?6c=nLgYrGCN#Tkh>fBFHAlPYqPltKeWq}wL$oQG!4p?` zSR+6*y|Ju7^vi>Xxj9iX_4*Be(anH;5Tmh&!$Wc74c|N6&V>BxW{^qecjE4AYM}~h z`6QgXw&k|kqf1e;RJJ?A#1QY5>sZF5P#(LnFG8Z6z+OdOlF|OGq=|I4O;oetBMr1D zR&+>T{+d1vj+zfY9JjWA;qRV*gZ~z|Akp^YobL8SUZ)LkD3xBDc@cxR0ZH# zxG$~!c8EQ;1e&W0?<E5OYMOZ{c^PwR^t^ARFdSVzxwz+!k6M|JAs>3z;I91a(ATUu7M|GI$fr|Z z7eF3rzt!5dT8|tsr}aVw8fYf}bKRiQhWl)rWS=b~SHJ4Q75IRg1%(C3T4S0vjWb-s zX_yp6<56U!ArLXZQlzD&g%5YM^kwX|tO4}TFFoex5ByJ~pNvQs?0i7&PZ9InAJ}qh zCZtX1iR)YYyprN3`ev<`pEdJTHApM9Ex`P~Ft{i;VwDK--DWSxuyd%6$SOf%O}=B> z=Nj~aY9fbzAmB>mud7sHy~istk6l{yIfQI{Ugv$lk$NrUtMldIQl18z*c!SzE3vh$ zttkMFH0Mj5N6ZJ&(q^b83?#`HxHRd{0${TRoKhswK>Nv;4G4yiCjHFcX>i4|faob{ zXB1FJT)pp@6PL^Adg9ml8w}I*8ZXAoGZT}v#^$i@Zu!pdjEbPoJlMrQe&m#u#Uv#? z2VnqN%A91R>A!4}pk7QyrSZ-DP0a_Wdf!Wmxr+5nZ25Cy4Hl!KsBCaBL-313JXDWB zfbTK3i8Ad4G4#DFKthq>SzUQ-B1gYrdmcp`^`?5d^d!#ci3~mf*;QmtXbFD^rZwK3 zs)Ec1Gon8Ql3N!S7f){(GELhj8MJszPXnK2e4ukI0MhM_p#bUXl5e@qe96<6ri7c< z=!%MpPE}iAt*xy+VrMID$A8EeX{)OPMMWf<^r1{jN!UL7gb^tGR)e*Zo+v(x`Wwx5aD69@(l5dJ-Qi001YX@g)I4|IQ-JNS1rA`7KkoQ{aIRAO2$KJ>4Y0d;@l^g&-?JGG1 ze@hEHK`1fuXT9n5*{U+qZjQv`uIQj9uPC2jdn3zF7c~~F2RCG)K`{nz`P~)*f zfHq)mvmgKO`7bWzC2Njw30IF#P9~@pdLq;q3FXws4jR>S<$>I^O*j7dC?1)>z2>xI zOZEV0zO4hOU_lP>jXNk1*Pd5gY;$qPgPqD}5lQv1`17PBA_i!oZ`y%9rt9OdL6WmbMen~-()M9QZ*_0R%6RI@;tE}>_) z*oABaNJn$fSY-z1v!AM*zX+z~p?w@Ss4>~I*E(fs#IAfPgqQ7j>!@|fK!~Tl^=o7h zPv=!dd&<+F&u*hmjO^?iOY-yV>8REP7-r=`!j*=Dv|Z%a}MA*OR9hGQY;G z$kOtSJWjb#Dc2Re<Rk zVQTQcI5mPeRXI$H?=Qpik%No&gWG6U|f<{RqlCJC{5#GAZN)(zophaFh= zJCEus)R}t+-Q?D|h0eq8mL+HL8K`uG!SPv_A6bR;Yyv|G6k1DSWR~ z{}5%!0yz>-s^jY5UYIo9owz}wF>q4fouo!Si3o*Yy8G;Qc@UxL-`(O3|NYE&354|r zb;bLD#c46(;gm2qmK{r$s=IDvM#+6gMtgwA#SKc&7k!tr#Auo%dTi5r?&feK z$@*+NRzBlfn`CFpGu!(eC8_KPIqf)X)(VNV62k!0@l=#2-kEJ19@Gd?X|WDluEurS zf{8p@=%0Q4-k}5El=`VY(MSH%CT`e$N|2xsL;u#ihtw>H=X;X{>G*hOk<^kbSLyt3 zr+@}yYgeh@k+0q6)qi@sxOwbPy=)68;ahcOb3P}Ym*)cwRc4p=T%xvQ>Xa90*JL2| z$wlKJ=pIMo-3?-GT`!<(bBje&FLTOZ@(YV$cY1Hk9AvVhaYxNA)phAjw4z)K2akou zOA6Ba&~9Nxd=wNJ1$;D0&z|>Ro&3{V{LeV3Ka#lYGqHT3Hr}>4J4)tj_rPkHzjc6} z%x8_fT6#K|Vz9@e0qnGqzI6*Ky66>gFza{=&1eP+nE^_U4@#BfoE$>;!C)o|=98>a@@Z_3i zk%5rrzxF!KNm7Vo6+RhdN(Y>@3)?pdg z;GbkIk`o4eCXJEkL9qbmzF_QW^1pR?s^Ij2|k@jz2*sFkLF zqqAwga{av9QrqQX9;+vuytk9{%GjrUVXEGrYAF=Nn`2L|A|o*h54ZRG@k=ca7+oF% zFTO1A>~G`%gI`RO0dd>P{oQM^m0km?GcEE@b7jdLn&@AOH4w#$LU+o=BJ>_szLmb* z)lFwo7&|Fwe_*M;;*1fiKkh(oI4bO#?Lm7vSK~(Mb9TzrvzM^Fii%;ab4ihz=EdaHc9i=y zT;v0HW316!`&ZM+P0O7VJn$@yM(cVcg_ncMvo5HC_}MJY(FVlPY9z5@tvl9GF2!Ma z3-J(wrhR@8RnK&BytvBY-KP=&Vz#v!$T2#Z>h+*K)MR^P`xXR9W}jZgZ8ax8;a?xj zfVhfgl%xDH=Ua6VSr%r_8sazUKmh_~3|IBK<+?`PeN9rxjA?$-hhRK4jqFO1%ek^DQ3^_$w{##WktdSUSq*a%u z`ZrO}+bDFNxILXN*V`wVN|6LYvQPsAm3Jk%zHHKb=#Vqz=2*hk_rMQhyuQ%b0%ffP zQ-2#llNol;STS7SRzB5BV?meoY3H1x(uX7sN%sAd=)o*Dq?FUku#!^T>S*P!EbZR_K`cOk%}n4UhalLcwO6J|ehy}n&wr+E*xfC6G>j;7SrzVvmiiLx zu_w)tTm<-kKkrtzA)quRleED-8|QXH#S0n<5}0rJRS<-GCj2PTK~UsORrYP@dbJWz zYiG)KhC=XgkvpKfqii}DmMWW0-FZf;vIuQ?{to-%%#n9ImxY&CA6^hvPd8p>d0VCL zdnBSvBbIJIDwuhHa&S4?ab|Ec1`k;oS|SqaUs*UnbQlTMxs~aB?B0>O^c<_WdWmSZ zBB;}ZT3*i7+1qek_9Q&A5A$aoU-$5~Ar^BAht4Tpu zte}OiH*YibE7SFJ(x>^Ysu{i+-=AXZooJ>&c3DL`)x}SSJ4{C0?|pc zKd0Jk%eV$ss`&epRwBAO(j;9hKy=97D~uMKHOrftWei?xUq;JZPzFXbfdn2et)#sB zR$zbnig_ZW^y4jQl9M%kq)C4?QHqcqXHyuCc#|=&!&M4~>rf!oPKtN`s}E8q!gikZ zH$8xDHU2c7>L*(+FMLQUGZF7T5+Mmo?>1=BsjxcAwlupz>Y+ggu_>4t#nnUx=k<>3?rum;xj)5*3XX1#4}+#^dyo<| zOL{D0aXICB%T>ftUP5L-jth33(NB|LG0<%}=shYxX+*V)3z7K|A2pcLttr(MD#hg$ zI5GHidIHsBzxRA6u)u+1v2irTfB>i_EV7|uenq78=NUeTS5r+q){I4q$G)Gl+;3K3 z%%Y*vkwyPHY=`W9rupSupZCxjyvA()1sR0?0Mc2Eaz$UG+7i-W;4V5H=1^-vW$;fv z6+P<^Hr9FbOwaSxUsqg9O5Bz78kVZng{-Q>rj5p($yg2lkt=2PDD>(w@uS}>oCfuu z!)XFCBd@_@V`|y;-U3or^9h`{JDQw&lRv*E7oa;@{=F(c(G;k8ZznjCTC^DQm(Fwkj7KR+vhOaI+hUF3BpE94Ns0F&46a*! zXp_`nk)qLV<@A&1OHTaLk_HcHe15ZqtJbXTGt}M zV5_$O^;*1nQP-n({dVk8p%taYUHtktWde%3ZD;q{XsTylK(Pa53rL zxR_Bc^s|1sG!hC}P8m3>=DcH-CUZ+9h*M>nuH03;Kb$PfpG$NqtV_uro>6qII>_|q zMWHi6X%eQT{r-+MPQemAA_BG1l*iF2o1e>pjHc^}L{wCyw`dS!0w}r4)*&!I_AV_^ zNp00YC|%f?yY%YhF8#DyGi5^Ee72x#b-4$bdUE0m13Gfh_m&TIsVHviktitd`V(R_ z^EB~E8D{i88ji_wTUb~y5<;Fjie^wBHoA$buqSSR(KXPLe;nipRU<(Mt?^%@EVGF# zZSWQwKe>o0BT<2hh1OI@(8fpM`wlEJL-k*Z8yWIl&4s!M(P2Hb+TAIXGk#$wRf$ep z+v1WEle>{O(WxTI%GojXzVX}JeAiqXx_{QbTN1B?S)iXdlnydCt@i7FO~zTt^LEv_ z>v(azbT1?{*x>r0`Ay{H<-y`^lV%aQVDwD$$C2RGeBTqyw49GzQdWdRQZ;T;3#@(o zN**r#SETCN@4M4A)hsV3HoUTV=Q3Q4q5+x4&cWe{07_q?iE}0NoQjrLSC0fx{SyQG zpSaj-%(hVEBP|W{BNe02`y*7gwmwU<+&gIiM=OaGziY{Id02)t79N{&wJ!ymM-Fgau5jy; zu1z($qJ@^*e4j4syBt_HYdkNw06&yTEec89V~`qvMQE-Modv}&Sfw6L_y{Fr^+8Gu zF*0aiF5{hZ1L9z$&o&EHG4`OLQ@H5t!#I<;o-%0!bkng8Pi)vS~P?C+sl_BseNlR&tHB# zbu@l9jA7p5*c4o;2lMreZvlwH5b%Kd3kn+F#Ab6mD_6k$py9D!*S6A zN9VzC$>BR3l1y|8s~PfmWaj zn8_k}YcK$K)D(T(k6y?=-ur*IFZ?eCk*&j}?o&{sGV^QtFH?R}8LD_*`oMIg&Yggr z6CRhiN4ls}-@<~aSj%<@Lk-urx39MsNKq&B&y4X%D1gxLndb(Q)|CS_2*Ch6*#+fRz4+zk4Jf{0gk*Rt)hAfOcFUdnwJTmB44Uo zV5;^zO8A|>$8A;cbk{UQF@8ABKD2PLhy^-hcZE}oI#dC?)0}pW0sHk2(72|^z889% zI1EhRx^!eY2e6OO1ewK6k?q@Gx8F%xh z^O9J8=m_)h^5O?P)+T-*-)Z7H31j+KQuc=9C&`Aro2dTP$KHK7k!w7F+ZZrAJR?kp ztP)&%bjrq4g88BTSJTSUgq4Za&i$m)X8lERk{`~;4==G1Al)W1t`tnftjOtw5{mP-*X2;)j4;LrCyvn0AFn8d zp2Avf9(MkHjyEm}jvBw;CNgFVw3sK1@ zT}YOTph+3>o!(s;+Uh>31V;KiX}UgEup7K35zs(AgW9-IRfBX<#!>c%{qa#CW!iMj zXOq-eS-zzgkwRBw_g9Qw&Lp=sXyTJdLJ~-R7eVdj*sV6W;*G_xmg~8naF>%aq`9QwZZ{tXDVC z!EUq(L~qNd-LZ)*(y!o?;{l~_*HB$X{;agOb3osA61p+uX7P2|p8D4H?@CW1G>4UX zNr$iax_TMAJ-_XuYVZ=Iw%^E3HP)!9RQS0Dw%)Gs@(>QEk>B!q&s6b5M zwj0$1Ku+7fR*yXKJu41G{>T+JYlwyL#-!e<8u%TyyD)nxt7k*gAypch3;$WhZ|Fnu zxU;rCp>zmmNm!HE_P(wnqBa_RX!<@SV2h?)9`rJ%vbgGkm+r7*@NPEzFh8lTx~|0E zk6H~Wwk&Xfs*T2J0Yu^HCQJ6Snu>?dcgw-IYFTz7fbMbf0k4ECN~)E=g75=XNbxbU zVYaw4Jid)4;c1fF&v+GxlqE-Ky8#jsDUYI@j8+k@xvrns1qAEH^YqydROz1uEOg?;%zO`c%yDa*qdp4f|&oKgK+T_^J^vULrhbrt43G*6}bmjl7 znUIk2BRl^;*xmozFa7g%{BP13^`8(0>G0%Z%C6RIx$b7PV+^Mw?CeO(>k0Ns^%oHEhqJqMK(nZjsP z8RMbHh7l7-r|H2r7XNN|-}@gw6lzB&2=I@Ul?~K9^0qA!0MI8dnI(h|o6BJ}UGGnI zjG_D2{@%#QiPo2i4E2$oR3*c-yuw3^o1(bAKM7oFt~`@YF;U*4xhQMJ+RQtoqO)|G zGJL{rL$5*g(z?uV(wz$NlM+-LbIO628*Vujze`p)F8J}Hn^{vltM9mG!oVEQWfA+L zA+6{5OTWY#wq^9&JvK&zu5==`mE9Uuv+k*F4LejnI5v^hz*@iS;L@14YmsvDk2p@6 z!F2CJ=RpxG@Z9M&GGSVfxyUFq6Rx|F-bY^8Z+82qI@tetxSCWLL5~L7@8XZ^%4JVz zwZYg~SIZBUmh7JIN_ftWV~;ZMGL%5~YY$3q==270j9L>L22^mSL@YX4cCmMf!0MnOcs(bhxE<%gDjuM`YtToL9%*LNO{E}}Vz zg)a`;hgi62)PDF9g9b8t53g;Msl?_UUOZ0er?)s+zavi6zH{;(o)9?u6r^*2?PV$<~(d#Z%`I~q|Drjh>aVr;nxly}GBHU|5m^spCvO!xc=X23y z0xy_yk1O{bW!Jb6EMeuxOoSO3c)PCk!rk>VCP6VY@pf@m%EUnO8CaPz*^uJQyG9rq z;UU$2Ba>6~P<6MBgC7zi^Nw9?8!^Eed0po{w9S9Zzd2W-d`%YO=HxnZ zK7J## zrt$-#Q4j7bkf!@E3Nj;+L(<7+xp=%usD&1N6U3MksUVMkPPiI+jNAb7ZO7c5{QB3c zFTwYSACLFq&S>Mb9PV<&xt^iX=|^+J9N>US?m7s+9HtoDxaYpl^wnSNx4ReG_#gqd za%zf|LvrY&S#3{V?O9ujwsTy?aqpx<*Ew6nyP7$0`JF1daI&Sw>v77X6TjAlLxS4h zczmZX>?81S8c|V|Vb1o4SXAT!8L=1==5#b}#KlDB!JKuw%QSZzo5c*CW779qV2h@D zKRAfP(YP`}Q|)49^Qz(xYx&(FM8sx4rZI{n zu@JnT7md7O-XKGw#pt0Ho=U(;-#jcPV-C>r1vU=S& zvidg(EOuvF!&(A7C}IT%*LRE>zmj0hrb@O!&mD$bXD8dcM%EXy4kC+BMA~hcV#_L4 zPjkDgt|Q#BPeG~rHy(oaKl1alg9lQdr%MBBULG=0JN*xi@T1wR+h{Dw({dR+F%C^t zcluwko(?R3S7xn|^bPsruX%oLn=XCV;%%z;)55i*C6|?aTe(N2Gici-gYWHL;ooKxI>f{IkRtQbAT5D_lZb%T zuex=mmwghx8xa|;o3xa9B49_V66#wgL6D*b9X<;FFyuGK{)?`+xGOVQ>ou1RQj3rs zJ>iELJmG!etU+EUpmg@3?k*{$Cs5$XrMdWEcZv0XYSx!gib`@)Pg>_Y4qaM`>daOz7 z+#}UyR4)N&Q`ZR|q62ZiR+#Smx2|<9P08yV;gl=Q(cpV!blGzVZoM}UI);^eIJ}z8 zV!|}kr#R_KQ5GFe%M~T)XY+ zE_g(e!_7qx%%vw(F-aDxp}9&*Nf`v$r*(zq&plD0pDa z%ac`;N>WCR(uckb-=!mz6Rws(jzN(x57Q%W!N)$ZyDl+nsBEw1Fn)Btg2E5Z zH)JTpk#7_%6g-&jCC;L5UaIN+sZS_V;C0m!!O^RUCf9u(^ofV6dKGq1vk7~(VR%P9 zD0YYP{O(pg{>f^CLjfCDT(etycn)3hZRekSJDgzZz(T025rp2Z{rZW2b7|0y)Ln)0 zE;w>Ua-$|YKcmWQhZPHXx#hlduhHE??~5 zcaqQ}2Ex)Pv-mZE=n}0I-uf2KuMnL{5=`Vg+)L0QzI5Mg-vZRK6`F^~BNl+#0bGd^%RzX2ZHJ7A zmJ<`vh|8S`A@Dj=vdd5z_a)-ru|k7-z@zeeZX!Q>31lrymh$ApKx~Sd1WyZnS4cl; z_sDo|V?vEMWUY;rMe?LFW32jNbGcu%Vf2o_4f8dMj3o0unkI8 ziA8?jj%-f-%iHCuv(}~855Lz!E$cnT@a>@(@cuQwgQj$_0QroE!^RS#bXww0u;xx} z?kK!T|48T|S*Xx7^LqTkE8h6xMtZV?Z3*|ecy&5Q`GZx#{*0nslEeYR?6L`>but}J zv)+GU0JnPB!}f461nIH|@mT&+YwwGoX-s>#D~?hm{-}G~LUViv^6zF~35bjJJ4aYg zw}zR%R>tpE7QUyK^xfx?yuZ>zy^2!!$wu0q7^Rl+mYKEEPMaYX0l_Py;N!$F>}>bF zPq;mvMXa7najtLMIj!bwyUe_3II1xcmuD8CS{8)Dc3-E9nP7u=Fdc7x@ypF;&5bFP zx4Y5DK5V***H=d1vm(fj(qds>FxE<%XULQNMWr4+0o=hn=x76ibcZxz4TpoiQ2r8I zvP9)-y5tqSmoc|_bz4kEBU+r#(fS}@q-azPX;cNzyL17-cXN}CMIEf9+{t-Y7%5lFN6P^80$?W zg#7uyn#&^!Rpp!D8RjeGP^cWAB0XnWvo0s`=j9PI6D0L2bQ9^ScW>uR=oRS+l4kzC zuW*bo|DmNiw9!2KlsGC>gCaOii%(I%U-PpmVGpqhzUCiN#XQ-_D*zJ{G|v%@${24_ zOk=80@D@%3>AGp6Z;K1K{mYTtFs+Ps-Z;B*Hw%G<(?HHXa3BrBwkS3YDd&ttHRYoHh!x?w(@JVucuNV3Cgn$>#lst82=3{|s#>{z)lkZB#&lBmSkwgt$TB#}YC0fL? zS>%=eL#W8|XJ5y~l8WV`shdmToVZw2ajpR4Fj>fCL)?KTLNfNO=&cqfj}FOoGQWZz z&{9t^n&6ntrA!lIt6ft?g^W{PG@&L=Aekl3y%G^SS=uJs6V$#^o_Vt-tda% zhfT~D6pTqajDT&w9978RU z#->?NY0`v3p?44OfMufwsGhz?195;nFwD z!eOL-RE5COxvipgyyi{T`tX4da&A9SVKfA!A=a(8*PTsJlf3%yR#V>9pg}wi?}qXB zJ|g#Z(@HRdH&Yy~u=xz>KO;_FCwp=+6$% z`I{)yg98Lj*V&PFhT$+o(gTpLYh0v#n0p1^iwlg9E`6@azXo2EaO9X!(Qp5ZorWgr z#Juj?W0zHM>Xp0%RZ}4M1f~0!I*^x)xxA#r>}a`*YF;E(QWJ!1OqRdL6))lV{^xJ% z3nB?9#?}ILuAR{twNPiyKWRn@&O+QcQcY1^VoE$U&fck~w%!A`BM~EiuLe!+P+;FI zbWktxY&O%JqkjtgkF6EgmUj25X5+I_l^QGM?gSNalpkO`J2iS&U^SzBiXQq4$z+)J zpJ^FQitHRGl(&r(y5+q?jsgLe>zviNQfRwnQCZ>O%dbHBXE8I1{)&~D^FZz zrL!v!Ugp5rb7f+)USkA5F_ilEqzX4y%FT}*FyJbbaGoJVeTbl=a_7^&(562pH7&NN r=zn_o(&nj=QEoPO1p3E*x_m&c!M-JDkLHg?0{$t!QI#u~H4ge8hLl;Y literal 38192 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 +The Cart Layout organize all the cues in grid-like pages, cues are shown as +buttons, if the cue provides a duration, the current cue time is shown at the bottom. + +## How to use + +### Adding Pages -## Layout Operations +To add a new page you can use `Layout > Add page`, or `Layout > Add pages`, +to add multiple pages at once. -### Adding/Removing Pages +Pages will also be added automatically when needed. -Pages will be added automatically when needed (this behavior can be disabled), -or manually, to do so two options are provided, in the top-bar `Layout > Add page` -and `Layout > Add pages`, the first will add a single page (at the end), the -second will show a dialog that allow to insert a custom number of pages. +### Removing Pages -To remove a page, select the page to be removed, then `Layout > Remove current page`, -a confirmation dialog will be shown, if `Yes` is clicked, then the page (and cues) will be deleted. +To remove a page, select the page to be removed, then use `Layout > Remove current page`, +a confirmation dialog will be shown. + +```{warning} +All cues in the page will be deleted. +``` -### Change page +### Moving between pages Pages can be switched using the tab bar on top of the layout or directional keys. +### Renaming pages + +It's possible to rename pages via `Double click` on the tab name. + ### Cues Execution -A cue can be start/stopped simply `Left-Clicking` on it. +A cue can be start/stopped by clicking on it. + +Via `Right-Click` on the cue is also possible play, stop, or pause the cues explicitly. ### Cues Editing @@ -40,21 +51,21 @@ Cues can be selected/deselected for multi-editing with `Right-Click > Select` or Cues can be copied or moved (into free spaces) inside a page or between different pages: -* **Move:** cues can be moved with `CTRL+Drag&Drop` -* **Copy:** cues can be copied with `SHIFT+Drag&Drop` +* **Move:** cues can be moved with `SHIFT+Drag&Drop` +* **Copy:** cues can be copied with `CTRL+Drag&Drop` -## Layout Options +to move/copy between pages, while dragging the cue, over the destination page. + +## Options In the application settings (`File > Preferences`) various options are provided: * **Countdown mode:** when enabled the current cue time is displayed as a countdown * **Show seek-bars:** when enabled a slider able to change the current playing position of media cues (for media cues) -* **Show bB-meters:** when enabled, a db level indicator is shown (for media-cues) +* **Show bB-meters:** when enabled, a db level indicator is shown (for supported cues) * **Show accurate time:** when enabled the cue time is displayed including tens of seconds -* **Show volume:** when enabled a volume slider is shown (for media-cues) -* **Automatically add new page:** when enabled new pages will be created when the - current pages are full and new cues are added +* **Show volume:** when enabled a volume slider is shown (for supported cues) * **Grid size:** define the number of rows & columns per page. (require to reload the session) @@ -68,6 +79,6 @@ logical positioning. :align: center ``` -```{note} -Cart Layout does not support cues "next-action". -``` \ No newline at end of file +## Limitations + +Given its non-sequential nature, Cart Layout does not support cues "next-action". \ No newline at end of file From 6ca1d4f3aa1b51c686f312e603512b435481e66c Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 26 Mar 2023 18:08:08 +0200 Subject: [PATCH 311/333] Add support for poetry lockfile version 2.0 --- scripts/flatpak/poetry-flatpak.py | 10 ++++- scripts/flatpak/python-modules.json | 70 +++++++++++------------------ 2 files changed, 33 insertions(+), 47 deletions(-) diff --git a/scripts/flatpak/poetry-flatpak.py b/scripts/flatpak/poetry-flatpak.py index 41ef7286b..23fb2d011 100755 --- a/scripts/flatpak/poetry-flatpak.py +++ b/scripts/flatpak/poetry-flatpak.py @@ -3,6 +3,7 @@ import argparse import json import re +import sys import urllib.parse import urllib.request from collections import OrderedDict @@ -16,6 +17,7 @@ def get_best_source(name: str, sources: list, hashes: list): # Search for a platform-independent wheel distribution + for source in sources: if ( source["packagetype"] == "bdist_wheel" @@ -65,13 +67,17 @@ def get_package_hashes(package_files: list) -> list: def get_packages_sources(packages: list, parsed_lockfile: Mapping) -> list: """Gets the list of sources from a toml parsed lockfile.""" sources = [] - parsed_files = parsed_lockfile["metadata"]["files"] + metadata_files = parsed_lockfile["metadata"].get("files", {}) executor = ThreadPoolExecutor(max_workers=10) futures = [] for package in packages: - package_files = parsed_files.get(package["name"], []) + # Older poetry.lock format contains files in [metadata]. + # New version 2.0 contains files in [[package]] section. + package_files = metadata_files.get(package["name"], []) + package_files += package.get("files", []) + futures.append( executor.submit( get_pypi_source, diff --git a/scripts/flatpak/python-modules.json b/scripts/flatpak/python-modules.json index 1b5635d81..2be785453 100644 --- a/scripts/flatpak/python-modules.json +++ b/scripts/flatpak/python-modules.json @@ -2,73 +2,53 @@ "name": "python-modules", "buildsystem": "simple", "build-commands": [ - "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} appdirs certifi cffi charset-normalizer falcon humanize idna importlib-metadata jack-client mido packaging pycparser pyparsing python-rtmidi requests sortedcontainers typing-extensions urllib3 zipp" + "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} appdirs certifi cffi charset-normalizer falcon humanize idna jack-client mido pycparser pyliblo3 python-rtmidi requests sortedcontainers urllib3" ], "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", - "sha256": "a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" - }, - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/17/41/de1269065ff0d1bda143cc91b245aef3165d9259e27904a4a59eab081e0b/JACK_Client-0.5.4-py3-none-any.whl", - "sha256": "52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73" - }, - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", - "sha256": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" - }, - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/1d/38/fa96a426e0c0e68aabc68e896584b83ad1eec779265a028e156ce509630e/certifi-2022.9.24-py3-none-any.whl", - "sha256": "90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382" - }, - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/9d/fc/28d2b631c5220b2a594d5d13b6ad79ee60d50688f1cd43f6707c06fb0db4/humanize-4.4.0-py3-none-any.whl", - "sha256": "8830ebf2d65d0395c1bd4c79189ad71e023f277c2c7ae00f263124432e6f2ffa" + "url": "https://files.pythonhosted.org/packages/b5/6d/e18a5b59ff086e1cd61d7fbf943d86c5f593a4e68bfc60215ab74210b22b/mido-1.2.10-py2.py3-none-any.whl", + "sha256": "0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/b5/6d/e18a5b59ff086e1cd61d7fbf943d86c5f593a4e68bfc60215ab74210b22b/mido-1.2.10-py2.py3-none-any.whl", - "sha256": "0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e" + "url": "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", + "sha256": "a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/b5/64/ef29a63cf08f047bb7fb22ab0f1f774b87eed0bb46d067a5a524798a4af8/importlib_metadata-5.0.0-py3-none-any.whl", - "sha256": "ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43" + "url": "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", + "sha256": "8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/db/51/a507c856293ab05cdc1db77ff4bc1268ddd39f29e7dc4919aa497f0adbec/charset_normalizer-2.1.1-py3-none-any.whl", - "sha256": "83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" + "url": "https://files.pythonhosted.org/packages/17/41/de1269065ff0d1bda143cc91b245aef3165d9259e27904a4a59eab081e0b/JACK_Client-0.5.4-py3-none-any.whl", + "sha256": "52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", - "sha256": "8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9" + "url": "https://files.pythonhosted.org/packages/ef/81/14b3b8f01ddaddad6cdec97f2f599aa2fa466bd5ee9af99b08b7713ccd29/charset_normalizer-3.1.0-py3-none-any.whl", + "sha256": "3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/05/8e/8de486cbd03baba4deef4142bd643a3e7bbe954a784dc1bb17142572d127/packaging-21.3-py3-none-any.whl", - "sha256": "ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + "url": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", + "sha256": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/36/53/4fd90c6c841bc2e4be29ab92c65e5406df9096c421f138bef9d95d43afc9/falcon-3.1.0.tar.gz", - "sha256": "f2760bd18c16393a6fb5e55f371f67921edb72febe693a82b3c5e82195d087b7" + "url": "https://files.pythonhosted.org/packages/22/2b/30e8725481b071ca53984742a443f944f9c74fb72f509a40b746912645e1/humanize-4.6.0-py3-none-any.whl", + "sha256": "401201aca462749773f02920139f302450cb548b70489b9b4b92be39fe3c3c50" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/6c/10/a7d0fa5baea8fe7b50f448ab742f26f52b80bfca85ac2be9d35cdd9a3246/pyparsing-3.0.9-py3-none-any.whl", - "sha256": "5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" + "url": "https://files.pythonhosted.org/packages/71/4c/3db2b8021bd6f2f0ceb0e088d6b2d49147671f25832fb17970e9b583d742/certifi-2022.12.7-py3-none-any.whl", + "sha256": "4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/0b/8e/f1a0a5a76cfef77e1eb6004cb49e5f8d72634da638420b9ea492ce8305e8/typing_extensions-4.4.0-py3-none-any.whl", - "sha256": "16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" + "url": "https://files.pythonhosted.org/packages/5e/2e/524a6667fa851d0aae3eded349b4f1e56785036d65e186fb4bf2f741a505/pyliblo3-0.13.0.tar.gz", + "sha256": "5700eac4a6db2c1c492a99c17bbf1871e888309ae7bcd1c68473650778d60f46" }, { "type": "file", @@ -77,8 +57,8 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c0441b5cda699c90f618b6f8a1b42/requests-2.28.1-py3-none-any.whl", - "sha256": "8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" + "url": "https://files.pythonhosted.org/packages/d2/f4/274d1dbe96b41cf4e0efb70cbced278ffd61b5c7bb70338b62af94ccb25b/requests-2.28.2-py3-none-any.whl", + "sha256": "64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa" }, { "type": "file", @@ -87,13 +67,13 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/09/85/302c153615db93e9197f13e02f448b3f95d7d786948f2fb3d6d5830a481b/zipp-3.9.0-py3-none-any.whl", - "sha256": "972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980" + "url": "https://files.pythonhosted.org/packages/7b/f5/890a0baca17a61c1f92f72b81d3c31523c99bec609e60c292ea55b387ae8/urllib3-1.26.15-py2.py3-none-any.whl", + "sha256": "aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/6f/de/5be2e3eed8426f871b170663333a0f627fc2924cc386cd41be065e7ea870/urllib3-1.26.12-py2.py3-none-any.whl", - "sha256": "b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" + "url": "https://files.pythonhosted.org/packages/29/bc/c11c9a14bb5b4d18a024ee51da15b793d1c869d151bb4101e324e0d055a8/falcon-3.1.1.tar.gz", + "sha256": "5dd393dbf01cbaf99493893de4832121bd495dc49a46c571915b79c59aad7ef4" }, { "type": "file", From 472d56a246e527588612dd409ed35b0e0a7915a8 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 26 Mar 2023 18:08:19 +0200 Subject: [PATCH 312/333] Formatting --- lisp/command/cue.py | 2 -- lisp/core/loading.py | 1 - lisp/cues/cue.py | 1 - lisp/plugins/gst_backend/gst_backend.py | 1 - lisp/plugins/gst_backend/settings/audio_pan.py | 1 - lisp/plugins/gst_backend/settings/db_meter.py | 1 - lisp/plugins/list_layout/info_panel.py | 4 ++-- lisp/plugins/media_info/media_info.py | 1 - lisp/plugins/midi/midi_io.py | 12 +++++++----- lisp/plugins/presets/presets.py | 1 - lisp/plugins/triggers/triggers.py | 1 - lisp/ui/settings/cue_pages/cue_appearance.py | 4 +++- lisp/ui/widgets/qclicklabel.py | 1 - 13 files changed, 12 insertions(+), 19 deletions(-) diff --git a/lisp/command/cue.py b/lisp/command/cue.py index a2cb264e5..ab0a6652b 100644 --- a/lisp/command/cue.py +++ b/lisp/command/cue.py @@ -22,7 +22,6 @@ class UpdateCueCommand(Command): - __slots__ = ("__cue", "__new", "__old") def __init__(self, properties, cue): @@ -49,7 +48,6 @@ def log(self): class UpdateCuesCommand(Command): - __slots__ = ("__cues", "__new", "__old") def __init__(self, properties, cues): diff --git a/lisp/core/loading.py b/lisp/core/loading.py index f968761bc..56fb3dca5 100644 --- a/lisp/core/loading.py +++ b/lisp/core/loading.py @@ -41,7 +41,6 @@ def __iter__(self): def load_modules(self): """Generate lists of tuples (class-name, class-object).""" for entry in os.scandir(self.pkg_path): - # Exclude __init__, __pycache__ and likely if re.match("^__.*", entry.name): continue diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index c51fd7bcc..40b96ecd8 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -289,7 +289,6 @@ def start(self, fade=False): if state & ( CueState.IsStopped | CueState.Pause | CueState.PreWait_Pause ): - running = self.__start__(fade) self._state = CueState.Running self.started.emit(self) diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index 54a2a76ff..82f837e29 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -48,7 +48,6 @@ class GstBackend(Plugin, BaseBackend): - Name = "GStreamer Backend" Authors = ("Francesco Ceruti",) Description = ( diff --git a/lisp/plugins/gst_backend/settings/audio_pan.py b/lisp/plugins/gst_backend/settings/audio_pan.py index c6591adc7..2e27e8c7b 100644 --- a/lisp/plugins/gst_backend/settings/audio_pan.py +++ b/lisp/plugins/gst_backend/settings/audio_pan.py @@ -24,7 +24,6 @@ class AudioPanSettings(SettingsPage): - ELEMENT = AudioPan Name = ELEMENT.Name diff --git a/lisp/plugins/gst_backend/settings/db_meter.py b/lisp/plugins/gst_backend/settings/db_meter.py index a79f67cc9..3d83d038d 100644 --- a/lisp/plugins/gst_backend/settings/db_meter.py +++ b/lisp/plugins/gst_backend/settings/db_meter.py @@ -32,7 +32,6 @@ class DbMeterSettings(SettingsPage): - ELEMENT = DbMeter Name = ELEMENT.Name diff --git a/lisp/plugins/list_layout/info_panel.py b/lisp/plugins/list_layout/info_panel.py index 7b04b2ea3..c3678b2a9 100644 --- a/lisp/plugins/list_layout/info_panel.py +++ b/lisp/plugins/list_layout/info_panel.py @@ -76,10 +76,10 @@ def _name_changed(self, name): self.cueName.setText(str(self.cue.index + 1) + " → " + name) def _desc_changed(self, description): - if hasattr(QTextDocument, 'setMarkdown'): + if hasattr(QTextDocument, "setMarkdown"): self.cueDescription.document().setMarkdown(description) else: self.cueDescription.setText(description) - self.cueDescription.setProperty('empty', len(description) == 0) + self.cueDescription.setProperty("empty", len(description) == 0) self.cueDescription.style().polish(self.cueDescription) diff --git a/lisp/plugins/media_info/media_info.py b/lisp/plugins/media_info/media_info.py index 716c56ced..d48f737f3 100644 --- a/lisp/plugins/media_info/media_info.py +++ b/lisp/plugins/media_info/media_info.py @@ -45,7 +45,6 @@ class MediaInfo(Plugin): - Name = "Media-Cue information dialog" Authors = ("Francesco Ceruti",) Description = "Provide a function to show information on media-cues source" diff --git a/lisp/plugins/midi/midi_io.py b/lisp/plugins/midi/midi_io.py index 48b338ff8..c315822b1 100644 --- a/lisp/plugins/midi/midi_io.py +++ b/lisp/plugins/midi/midi_io.py @@ -110,11 +110,13 @@ def open(self): def __new_message(self, message): # Translate "Note On" with Velocity=0 to "Note Off" # See https://github.com/mido/mido/issues/130 - if message.type == 'note_on' and message.velocity == 0: - return Message.from_dict({ - **message.dict(), - 'type': 'note_off', - }) + if message.type == "note_on" and message.velocity == 0: + return Message.from_dict( + { + **message.dict(), + "type": "note_off", + } + ) if self.alternate_mode: self.new_message_alt.emit(message) diff --git a/lisp/plugins/presets/presets.py b/lisp/plugins/presets/presets.py index 2daffed99..693c6f35e 100644 --- a/lisp/plugins/presets/presets.py +++ b/lisp/plugins/presets/presets.py @@ -45,7 +45,6 @@ class Presets(Plugin): - Name = "Preset" Authors = ("Francesco Ceruti",) Description = "Allow to save, edit, import and export cue presets" diff --git a/lisp/plugins/triggers/triggers.py b/lisp/plugins/triggers/triggers.py index a667cb424..bab64f1a5 100644 --- a/lisp/plugins/triggers/triggers.py +++ b/lisp/plugins/triggers/triggers.py @@ -24,7 +24,6 @@ class Triggers(Plugin): - Name = "Triggers" Authors = ("Francesco Ceruti",) Description = "Allow cues to react to other-cues state changes" diff --git a/lisp/ui/settings/cue_pages/cue_appearance.py b/lisp/ui/settings/cue_pages/cue_appearance.py index 6a623c718..57e9e7af6 100644 --- a/lisp/ui/settings/cue_pages/cue_appearance.py +++ b/lisp/ui/settings/cue_pages/cue_appearance.py @@ -54,7 +54,9 @@ def __init__(self, **kwargs): self.cueDescriptionEdit = QTextEdit(self.cueDescriptionGroup) self.cueDescriptionEdit.setAcceptRichText(False) - self.cueDescriptionEdit.setFont(QFontDatabase.systemFont(QFontDatabase.FixedFont)) + self.cueDescriptionEdit.setFont( + QFontDatabase.systemFont(QFontDatabase.FixedFont) + ) self.cueDescriptionGroup.layout().addWidget(self.cueDescriptionEdit) # Font diff --git a/lisp/ui/widgets/qclicklabel.py b/lisp/ui/widgets/qclicklabel.py index f10a25618..069209bbe 100644 --- a/lisp/ui/widgets/qclicklabel.py +++ b/lisp/ui/widgets/qclicklabel.py @@ -20,7 +20,6 @@ class QClickLabel(QLabel): - clicked = pyqtSignal(QEvent) def __init(self, parent): From 1f2c71a3a2341ea60707902563d66fbc114120b4 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 26 Mar 2023 19:33:39 +0200 Subject: [PATCH 313/333] Fix: insert cues in the correct order, in all layouts Update: remove useless code --- lisp/command/layout.py | 2 +- lisp/command/model.py | 11 +++++++++-- lisp/plugins/gst_backend/gst_backend.py | 11 +---------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lisp/command/layout.py b/lisp/command/layout.py index 4bcb4def1..a5772341d 100644 --- a/lisp/command/layout.py +++ b/lisp/command/layout.py @@ -32,6 +32,6 @@ def __init__(self, layout, *cues): selection = tuple(layout.selected_cues()) super().__init__( layout.model, - selection[-1].index + 1 if selection else len(layout.model), + selection[-1].index + 1 if selection else -1, *cues, ) diff --git a/lisp/command/model.py b/lisp/command/model.py index 46158cefc..7d4415519 100644 --- a/lisp/command/model.py +++ b/lisp/command/model.py @@ -68,8 +68,15 @@ def __init__(self, model_adapter, index, *items): self._items = items def do(self): - for item in reversed(self._items): - self._model.insert(item, self._index) + if self._index >= 0: + # We know where we should insert the items + for index, item in enumerate(self._items, self._index): + self._model.insert(item, index) + else: + # We don't know where to insert the item, the model will choose the + # best position + for item in self._items: + self._model.insert(item, -1) def undo(self): for item in self._items: diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index 82f837e29..c504159b8 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -160,20 +160,11 @@ def add_cue_from_files(self, files): # Create media cues, and add them to the Application cue_model factory = UriAudioCueFactory(GstBackend.Config["pipeline"]) - # Get the (last) index of the current selection - start_index = -1 - layout_selection = list(self.app.layout.selected_cues()) - if layout_selection: - start_index = layout_selection[-1].index + 1 - cues = [] - for index, file in enumerate(files, start_index): + for file in files: cue = factory(self.app, uri=file) # Use the filename without extension as cue name cue.name = os.path.splitext(os.path.basename(file))[0] - # Set the index (if something is selected) - if start_index != -1: - cue.index = index cues.append(cue) From f01ffbf676e97544d377640b46c1986f30ca28c2 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 22 Apr 2023 18:36:43 +0200 Subject: [PATCH 314/333] Display cue indexes starting from 1 (fix #264) --- lisp/ui/cuelistdialog.py | 6 +++++- lisp/ui/qdelegates.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lisp/ui/cuelistdialog.py b/lisp/ui/cuelistdialog.py index 9afd49338..5b7edf48e 100644 --- a/lisp/ui/cuelistdialog.py +++ b/lisp/ui/cuelistdialog.py @@ -72,7 +72,11 @@ def add_cue(self, cue): item.setTextAlignment(0, Qt.AlignCenter) for n, prop in enumerate(self._properties): - item.setData(n, Qt.DisplayRole, getattr(cue, prop, "Undefined")) + value = getattr(cue, prop, "Undefined") + if prop == "index" and isinstance(value, int): + value += 1 + + item.setData(n, Qt.DisplayRole, value) self._cues[cue] = item item.setData(0, Qt.UserRole, cue) diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index ebfcafb55..41abee281 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -332,7 +332,7 @@ def __init__(self, cue_model, cue_select_dialog, **kwargs): def _text(self, option, index): cue = self.cue_model.get(index.data()) if cue is not None: - return f"{cue.index} | {cue.name}" + return f"{cue.index+1} | {cue.name}" return "UNDEF" From 9716b7f18f1c7118e079f927e89a53e341c7292d Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 22 Apr 2023 19:49:26 +0200 Subject: [PATCH 315/333] Merge a few updates and fixes from "s0600204/develop_additional" * Correct reference to Application singleton * Fix not having any MIDI devices in the config file * Move all {widget}.show()s to after they've been added to a parent * Fix "Fade All In" controller action * Correct spelling/grammar in various places * Correct dimensions of current cue indicator. --- README.md | 17 +++++++------- docs/user/cart_layout.md | 4 ++-- lisp/core/decorators.py | 4 ++-- lisp/core/has_properties.py | 22 +++++++++---------- lisp/core/plugins_manager.py | 11 +++++----- lisp/cues/cue.py | 4 ++-- lisp/plugins/cart_layout/model.py | 4 ++-- lisp/plugins/controller/controller.py | 4 ++-- .../plugins/gst_backend/gst_media_settings.py | 4 ++-- .../plugins/gst_backend/settings/jack_sink.py | 6 ++--- .../plugins/gst_backend/settings/uri_input.py | 2 +- lisp/plugins/list_layout/list_view.py | 4 ++-- lisp/plugins/list_layout/list_widgets.py | 4 ++-- lisp/plugins/midi/midi.py | 6 ++--- lisp/plugins/network/discovery.py | 2 +- lisp/plugins/osc/osc_server.py | 4 ++-- lisp/ui/mainwindow.py | 4 ++-- lisp/ui/widgets/qiconpushbutton.py | 4 ++-- 18 files changed, 54 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 9958db60d..e84a397fd 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,16 @@ --- -Linux Show Player, LiSP for short, is a free cue player, mainly intended for sound-playback in stage productions, the goal is to provide a complete playback software for musical plays, theater shows and similar. +Linux Show Player, LiSP for short, is a free cue player, primarily intended for sound-playback during stage productions. The ultimate goal is to provide a complete playback software for musical plays, theatre shows, and similar. -For bugs/requests you can open an issue on the GitHub issues tracker, for support, discussions, and anything else you should use the [gitter](https://gitter.im/linux-show-player/linux-show-player) chat. +For bugs and requests you can open an issue on the GitHub issues tracker; for support, discussions, and anything else you should use the [gitter](https://gitter.im/linux-show-player/linux-show-player) chat. --- ### Installation -Linux Show Player is currently developed/test only on **GNU/Linux**.
    -_The core components (Python3, GStreamer and Qt5) are multi-platform, in future, despite the name, LiSP might get ported on others platforms_ +Linux Show Player is currently developed on and tested for **GNU/Linux** only.
    +_The core components (Python3, GStreamer and Qt5) are multi-platform, thus in future - despite the name - LiSP might get ported to other platforms._ #### 📦 Flatpak @@ -34,17 +34,16 @@ You can get the latest **development** builds here: * [Master](https://github.com/FrancescoCeruti/linux-show-player/releases/tag/ci-master) - Generally stable * [Development](https://github.com/FrancescoCeruti/linux-show-player/releases/tag/ci-develop) - Preview features, might be unstable and untested -#### 🐧 From yours distribution repository +#### 🐧 From your distribution repository -For some GNU/Linux distribution you can install a native package.
    -Keep in mind that it might not be the latest version, you can find a list on [repology.org](https://repology.org/metapackage/linux-show-player) +For some GNU/Linux distributions you can install a native package.
    +Keeping in mind that it might not be the latest version, you can find a list on [repology.org](https://repology.org/metapackage/linux-show-player). --- ### 📖 Usage -User manual can be [viewed online](http://linux-show-player-users.readthedocs.io/en/latest/index.html) -or downloaded from the GitHub release page, for a specific version. +A user manual can be [viewed online](http://linux-show-player-users.readthedocs.io/en/latest/index.html) or downloaded from the [releases page](https://github.com/FrancescoCeruti/linux-show-player/releases) on GitHub. #### ⌨️ Command line: diff --git a/docs/user/cart_layout.md b/docs/user/cart_layout.md index ef7e4fa78..56ed21e44 100644 --- a/docs/user/cart_layout.md +++ b/docs/user/cart_layout.md @@ -63,7 +63,7 @@ In the application settings (`File > Preferences`) various options are provided: * **Countdown mode:** when enabled the current cue time is displayed as a countdown * **Show seek-bars:** when enabled a slider able to change the current playing position of media cues (for media cues) -* **Show bB-meters:** when enabled, a db level indicator is shown (for supported cues) +* **Show dB-meters:** when enabled, a dB level indicator is shown (for supported cues) * **Show accurate time:** when enabled the cue time is displayed including tens of seconds * **Show volume:** when enabled a volume slider is shown (for supported cues) * **Grid size:** define the number of rows & columns per page. (require to reload @@ -81,4 +81,4 @@ logical positioning. ## Limitations -Given its non-sequential nature, Cart Layout does not support cues "next-action". \ No newline at end of file +Given its non-sequential nature, Cart Layout does not support cues "next-action". diff --git a/lisp/core/decorators.py b/lisp/core/decorators.py index a70cdc2d6..bf005cb38 100644 --- a/lisp/core/decorators.py +++ b/lisp/core/decorators.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2016 Francesco Ceruti +# Copyright 2022 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -23,7 +23,7 @@ def async_function(target): """Decorator. Make a function asynchronous. - The decorated function is executed in a differed thread. + The decorated function is executed in a different thread. """ @wraps(target) diff --git a/lisp/core/has_properties.py b/lisp/core/has_properties.py index 7282adf89..e80064797 100644 --- a/lisp/core/has_properties.py +++ b/lisp/core/has_properties.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2022 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -83,8 +83,8 @@ class HasProperties(metaclass=HasPropertiesMeta): Using the Property descriptor subclasses can specify a set of properties that can be easily retrieved and updated via a series of provided functions. - HasProperties objects can be nested, using a property that keep an - HasProperties object as value. + HasProperties objects can be nested, using a property that keeps an + HasProperties object as a value. Usage: @@ -95,17 +95,17 @@ class DeepThought(HasProperties): def __init__(self): self.__changed_signals = {} - # Contain signals that are emitted after the associated property is - # changed, the signal are create only when requested the first time. + # Contains signals that are emitted after the associated property is + # changed, the signals are created only when requested the first time. self.property_changed = Signal() # Emitted after property change (self, name, value) def properties_names(self, filter=None): """ - To work as intended `filter` must be a function that take a set as - parameter and return a set with the properties names filtered by - some custom rule, the given set can be modified in-place. + To work as intended `filter` must be a function that takes a set as a + parameter and return a set with the property names filtered by + some custom rule. The given set can be modified in-place. :param filter: a function to filter the returned properties, or None :rtype: set @@ -129,8 +129,8 @@ def _properties_names(self): def properties_defaults(self, filter=None): """Instance properties defaults. - Differently from `class_defaults` this works on instances, and it might - give different results with some subclass. + Different from `class_defaults` this works on instances, and it might + give different results with some subclasses. :param filter: filter the properties, see `properties_names` :return: The default properties as a dictionary {name: default_value} @@ -208,7 +208,7 @@ def update_properties(self, properties): def changed(self, name): """ :param name: The property name - :return: A signal that notify the given property changes + :return: A signal that notifies that the given property has changed :rtype: Signal The signals returned by this method are created lazily and cached. diff --git a/lisp/core/plugins_manager.py b/lisp/core/plugins_manager.py index 90d4143a3..1c90f4953 100644 --- a/lisp/core/plugins_manager.py +++ b/lisp/core/plugins_manager.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2020 Francesco Ceruti +# Copyright 2022 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -63,17 +63,16 @@ def load_plugins(self): self.register_plugin(name, plugin) # Load (instantiate) the plugins - # Note that PluginsLoader.load() is a generator, it will yield - # each plugin when ready, so "self.plugins" will be update gradually. + # Note that PluginsLoader.load() is a generator: it will yield each + # plugin when ready, so `self._plugins` will be updated gradually. self._plugins.update( PluginsLoader(self.application, self._plugins.copy()).load() ) def register_plugin(self, name, plugin): if name in self._plugins: - # Prevent user plugins to override those provided with lisp, - # if the latter are register before. - # More generically, if two plugins with same name are provided, + # Prevent user plugins from overriding those provided with lisp. + # If two plugins with same name are provided, # only the first will be kept. logger.error( translate( diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 40b96ecd8..cc29ad44d 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2022 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -74,7 +74,7 @@ class Cue(HasProperties): """Cue(s) are the base component to implement any kind of live-controllable element (live = during a show). - A cue implement his behavior(s) reimplementing __start__, __stop__, + A cue should support this behavior by reimplementing __start__, __stop__, __pause__ and __interrupt__ methods. Cue provide **(and any subclass should do the same)** properties via diff --git a/lisp/plugins/cart_layout/model.py b/lisp/plugins/cart_layout/model.py index e39ea0927..f2753f64d 100644 --- a/lisp/plugins/cart_layout/model.py +++ b/lisp/plugins/cart_layout/model.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2016 Francesco Ceruti +# Copyright 2022 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -34,7 +34,7 @@ def __init__(self, model, rows, columns, current_page=0): self.__columns = columns def flat(self, index): - """If index is multidimensional return a flatted version. + """If index is multidimensional return a flattened version. :rtype: int """ diff --git a/lisp/plugins/controller/controller.py b/lisp/plugins/controller/controller.py index 19fdcf188..16ca6908b 100644 --- a/lisp/plugins/controller/controller.py +++ b/lisp/plugins/controller/controller.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2022 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -131,7 +131,7 @@ def perform_session_action(self, key): elif action is LayoutAction.FadeOutAll: self.app.layout.fadeout_all() elif action is LayoutAction.FadeInAll: - self.app.layout.fadeout_all() + self.app.layout.fadein_all() elif action is LayoutAction.StandbyForward: self.app.layout.set_standby_index( self.app.layout.standby_index() + 1 diff --git a/lisp/plugins/gst_backend/gst_media_settings.py b/lisp/plugins/gst_backend/gst_media_settings.py index a255d903f..0c06f0a77 100644 --- a/lisp/plugins/gst_backend/gst_media_settings.py +++ b/lisp/plugins/gst_backend/gst_media_settings.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2022 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -113,8 +113,8 @@ def __change_page(self, current, previous): self._current_page.hide() self._current_page = self._pages[self.listWidget.row(current)] - self._current_page.show() self.layout().addWidget(self._current_page, 0, 1, 2, 1) + self._current_page.show() def __edit_pipe(self): # Backup the settings diff --git a/lisp/plugins/gst_backend/settings/jack_sink.py b/lisp/plugins/gst_backend/settings/jack_sink.py index f0c7e8ab7..9f9b4703f 100644 --- a/lisp/plugins/gst_backend/settings/jack_sink.py +++ b/lisp/plugins/gst_backend/settings/jack_sink.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2022 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -72,9 +72,9 @@ def __init__(self, **kwargs): # if __jack_client is None this will return a default value self.connections = JackSink.default_connections(self.__jack_client) - self.retranlsateUi() + self.retranslateUi() - def retranlsateUi(self): + def retranslateUi(self): self.jackGroup.setTitle(translate("JackSinkSettings", "Connections")) self.connectionsEdit.setText( translate("JackSinkSettings", "Edit connections") diff --git a/lisp/plugins/gst_backend/settings/uri_input.py b/lisp/plugins/gst_backend/settings/uri_input.py index feea1f4ec..42a71de61 100644 --- a/lisp/plugins/gst_backend/settings/uri_input.py +++ b/lisp/plugins/gst_backend/settings/uri_input.py @@ -123,7 +123,7 @@ def select_file(self): if not os.path.exists(directory): directory = GstBackend.Config.get("mediaLookupDir", "") if not os.path.exists(directory): - directory = self.app.session.dir() + directory = Application().session.dir() path, _ = QFileDialog.getOpenFileName( self, diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py index c5e7a62fd..57bfd25cb 100644 --- a/lisp/plugins/list_layout/list_view.py +++ b/lisp/plugins/list_layout/list_view.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2016 Francesco Ceruti +# Copyright 2022 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -126,7 +126,7 @@ def __init__(self, listModel, parent=None): self.setAlternatingRowColors(True) self.setVerticalScrollMode(self.ScrollPerItem) - # This allow to have some spared space at the end of the scroll-area + # This allows to have some spare space at the end of the scroll-area self.verticalScrollBar().rangeChanged.connect(self.__updateScrollRange) self.currentItemChanged.connect( self.__currentItemChanged, Qt.QueuedConnection diff --git a/lisp/plugins/list_layout/list_widgets.py b/lisp/plugins/list_layout/list_widgets.py index 191fcbd6a..c4c3e52e6 100644 --- a/lisp/plugins/list_layout/list_widgets.py +++ b/lisp/plugins/list_layout/list_widgets.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2017 Francesco Ceruti +# Copyright 2022 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -126,7 +126,7 @@ def paintEvent(self, event): path.lineTo(0, indicator_height - 1) path.lineTo(indicator_width // 3, indicator_height - 1) path.lineTo(indicator_width, indicator_width) - path.lineTo(indicator_width // 3, 0) + path.lineTo(indicator_width // 3, 1) path.lineTo(0, 1) qp.setPen(QPen(QBrush(QColor(0, 0, 0)), 2)) diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index f8cb2ac00..a859a195b 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2019 Francesco Ceruti +# Copyright 2021 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -61,8 +61,8 @@ def __init__(self, app): # Define the default ports avail_inputs = self.backend.get_input_names() avail_outputs = self.backend.get_output_names() - self.__default_input = avail_inputs[0] if avail_inputs else None - self.__default_output = avail_outputs[0] if avail_outputs else None + self.__default_input = avail_inputs[0] if avail_inputs else '' + self.__default_output = avail_outputs[0] if avail_outputs else '' # Create input handler and connect current_input = self.input_name() diff --git a/lisp/plugins/network/discovery.py b/lisp/plugins/network/discovery.py index 7a8d62557..0a8a1e582 100644 --- a/lisp/plugins/network/discovery.py +++ b/lisp/plugins/network/discovery.py @@ -23,7 +23,7 @@ class Announcer(Thread): - """Allow other hosts on a network to discover a specific "service" via UPD. + """Allow other hosts on a network to discover a specific "service" via UDP. While this class is called "Announcer" it acts passively, only responding to requests, it does not advertise itself on the network. diff --git a/lisp/plugins/osc/osc_server.py b/lisp/plugins/osc/osc_server.py index f9cb92973..de603f8a0 100644 --- a/lisp/plugins/osc/osc_server.py +++ b/lisp/plugins/osc/osc_server.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2022 Francesco Ceruti # Copyright 2016 Thomas Achtner # # Linux Show Player is free software: you can redistribute it and/or modify @@ -97,7 +97,7 @@ def start(self): ) except ServerError: logger.exception( - translate("OscServerError", "Cannot start OSC sever") + translate("OscServerError", "Cannot start OSC server") ) def stop(self): diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index ad40b3064..2c1797e0d 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2019 Francesco Ceruti +# Copyright 2022 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -329,8 +329,8 @@ def __beforeSessionFinalize(self): self._app.session.layout.view.setParent(None) def __sessionCreated(self): - self._app.session.layout.view.show() self.centralWidget().layout().addWidget(self._app.session.layout.view) + self._app.session.layout.view.show() self.updateWindowTitle() def __simpleCueInsert(self, cueClass): diff --git a/lisp/ui/widgets/qiconpushbutton.py b/lisp/ui/widgets/qiconpushbutton.py index e2c1b9294..d61a8cef8 100644 --- a/lisp/ui/widgets/qiconpushbutton.py +++ b/lisp/ui/widgets/qiconpushbutton.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2017 Francesco Ceruti +# Copyright 2022 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,7 +20,7 @@ class QIconPushButton(QPushButton): - """QPushButton that resize dynamically it's icon.""" + """QPushButton that dynamically resizes its icon.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) From 3a220a74507074fd1aab3df22446008823e5b2b8 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 3 Jun 2023 19:59:54 +0200 Subject: [PATCH 316/333] Remove unnecessary try/except --- lisp/plugins/gst_backend/gst_utils.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lisp/plugins/gst_backend/gst_utils.py b/lisp/plugins/gst_backend/gst_utils.py index 657a5f067..e49d066e6 100644 --- a/lisp/plugins/gst_backend/gst_utils.py +++ b/lisp/plugins/gst_backend/gst_utils.py @@ -21,18 +21,17 @@ def gst_uri_duration(uri: SessionURI): - # First try to use the base implementation, because it's faster duration = 0 + + # First we try to use the base implementation, because it's faster if uri.is_local: duration = audio_file_duration(uri.absolute_path) - try: - # Fallback to GStreamer discoverer - # TODO: we can probabbly make this faster see https://github.com/mopidy/mopidy/blob/develop/mopidy/audio/scan.py - if duration <= 0: - duration = gst_uri_metadata(uri).get_duration() // Gst.MSECOND - finally: - return duration if duration >= 0 else 0 + # Fallback to GStreamer discoverer + if duration <= 0: + duration = gst_uri_metadata(uri).get_duration() // Gst.MSECOND + + return duration if duration >= 0 else 0 def gst_mime_types(): From f99aea46551e47082fd55ce4dc5039947ed386a5 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 3 Jun 2023 21:43:17 +0200 Subject: [PATCH 317/333] Update dependencies, format --- lisp/plugins/midi/midi.py | 4 +- poetry.lock | 432 +++++++++++++++++--------------------- 2 files changed, 193 insertions(+), 243 deletions(-) diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index a859a195b..1838f752a 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -61,8 +61,8 @@ def __init__(self, app): # Define the default ports avail_inputs = self.backend.get_input_names() avail_outputs = self.backend.get_output_names() - self.__default_input = avail_inputs[0] if avail_inputs else '' - self.__default_output = avail_outputs[0] if avail_outputs else '' + self.__default_input = avail_inputs[0] if avail_inputs else "" + self.__default_output = avail_outputs[0] if avail_outputs else "" # Create input handler and connect current_input = self.input_name() diff --git a/poetry.lock b/poetry.lock index a18b095f5..e69efcb8b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -16,7 +15,6 @@ files = [ name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "main" optional = false python-versions = "*" files = [ @@ -28,7 +26,6 @@ files = [ name = "babel" version = "2.12.1" description = "Internationalization utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -41,14 +38,13 @@ pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} [[package]] name = "beautifulsoup4" -version = "4.12.0" +version = "4.12.2" description = "Screen-scraping library" -category = "dev" optional = false python-versions = ">=3.6.0" files = [ - {file = "beautifulsoup4-4.12.0-py3-none-any.whl", hash = "sha256:2130a5ad7f513200fae61a17abb5e338ca980fa28c439c0571014bc0217e9591"}, - {file = "beautifulsoup4-4.12.0.tar.gz", hash = "sha256:c5fceeaec29d09c84970e47c65f2f0efe57872f7cff494c9691a26ec0ff13234"}, + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, ] [package.dependencies] @@ -60,21 +56,19 @@ lxml = ["lxml"] [[package]] name = "certifi" -version = "2022.12.7" +version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, + {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"}, + {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"}, ] [[package]] name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -151,7 +145,6 @@ pycparser = "*" name = "charset-normalizer" version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -236,7 +229,6 @@ files = [ name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -246,59 +238,58 @@ files = [ [[package]] name = "cython" -version = "0.29.33" +version = "0.29.35" description = "The Cython compiler for writing C extensions for the Python language." -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ - {file = "Cython-0.29.33-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:286cdfb193e23799e113b7bd5ac74f58da5e9a77c70e3b645b078836b896b165"}, - {file = "Cython-0.29.33-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8507279a4f86ed8365b96603d5ad155888d4d01b72a9bbf0615880feda5a11d4"}, - {file = "Cython-0.29.33-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bf5ffd96957a595441cca2fc78470d93fdc40dfe5449881b812ea6045d7e9be"}, - {file = "Cython-0.29.33-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2019a7e54ba8b253f44411863b8f8c0b6cd623f7a92dc0ccb83892358c4283a"}, - {file = "Cython-0.29.33-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:190e60b7505d3b9b60130bcc2251c01b9ef52603420829c19d3c3ede4ac2763a"}, - {file = "Cython-0.29.33-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0168482495b75fea1c97a9641a95bac991f313e85f378003f9a4909fdeb3d454"}, - {file = "Cython-0.29.33-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:090556e41f2b30427dd3a1628d3613177083f47567a30148b6b7b8c7a5862187"}, - {file = "Cython-0.29.33-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:19c9913e9304bf97f1d2c357438895466f99aa2707d3c7a5e9de60c259e1ca1d"}, - {file = "Cython-0.29.33-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:afc9b6ab20889676c76e700ae6967aa6886a7efe5b05ef6d5b744a6ca793cc43"}, - {file = "Cython-0.29.33-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:49fb45b2bf12d6e2060bbd64506c06ac90e254f3a4bceb32c717f4964a1ae812"}, - {file = "Cython-0.29.33-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:5430f38d3d01c4715ec2aef5c41e02a2441c1c3a0149359c7a498e4c605b8e6c"}, - {file = "Cython-0.29.33-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c4d315443c7f4c61180b6c3ea9a9717ee7c901cc9db8d1d46fdf6556613840ed"}, - {file = "Cython-0.29.33-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6b4e6481e3e7e4d345640fe2fdc6dc57c94369b467f3dc280949daa8e9fd13b9"}, - {file = "Cython-0.29.33-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:060a2568ef80116a0a9dcaf3218a61c6007be0e0b77c5752c094ce5187a4d63c"}, - {file = "Cython-0.29.33-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b67ddd32eaa2932a66bf8121accc36a7b3078593805519b0f00040f2b10a6a52"}, - {file = "Cython-0.29.33-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:1b507236ba3ca94170ce0a504dd03acf77307d4bfbc5a010a8031673f6b213a9"}, - {file = "Cython-0.29.33-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:581efc0622a9be05714222f2b4ac96a5419de58d5949517282d8df38155c8b9d"}, - {file = "Cython-0.29.33-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6b8bcbf8f1c3c46d6184be1e559e3a3fb8cdf27c6d507d8bc8ae04cfcbfd75f5"}, - {file = "Cython-0.29.33-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1ca93bbe584aee92094fd4fb6acc5cb6500acf98d4f57cc59244f0a598b0fcf6"}, - {file = "Cython-0.29.33-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:da490129e1e4ffaf3f88bfb46d338549a2150f60f809a63d385b83e00960d11a"}, - {file = "Cython-0.29.33-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4cadf5250eda0c5cdaf4c3a29b52be3e0695f4a2bf1ccd49b638d239752ea513"}, - {file = "Cython-0.29.33-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:bcb1a84fd2bd7885d572adc180e24fd8a7d4b0c104c144e33ccf84a1ab4eb2b8"}, - {file = "Cython-0.29.33-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:d78147ad8a3417ae6b371bbc5bfc6512f6ad4ad3fb71f5eef42e136e4ed14970"}, - {file = "Cython-0.29.33-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dd96b06b93c0e5fa4fc526c5be37c13a93e2fe7c372b5f358277ebe9e1620957"}, - {file = "Cython-0.29.33-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:959f0092d58e7fa00fd3434f7ff32fb78be7c2fa9f8e0096326343159477fe45"}, - {file = "Cython-0.29.33-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0455d5b92f461218bcf173a149a88b7396c3a109066274ccab5eff58db0eae32"}, - {file = "Cython-0.29.33-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:a9b0b890656e9d18a18e1efe26ea3d2d0f3e525a07a2a853592b0afc56a15c89"}, - {file = "Cython-0.29.33-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:b5e8ce3039ff64000d58cd45b3f6f83e13f032dde7f27bb1ab96070d9213550b"}, - {file = "Cython-0.29.33-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:e8922fa3d7e76b7186bbd0810e170ca61f83661ab1b29dc75e88ff2327aaf49d"}, - {file = "Cython-0.29.33-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f67b7306fd00d55f271009335cecadc506d144205c7891070aad889928d85750"}, - {file = "Cython-0.29.33-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f271f90005064c49b47a93f456dc6cf0a21d21ef835bd33ac1e0db10ad51f84f"}, - {file = "Cython-0.29.33-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d4457d417ffbb94abc42adcd63a03b24ff39cf090f3e9eca5e10cfb90766cbe3"}, - {file = "Cython-0.29.33-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:0b53e017522feb8dcc2189cf1d2d344bab473c5bba5234390b5666d822992c7c"}, - {file = "Cython-0.29.33-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:4f88c2dc0653eef6468848eb8022faf64115b39734f750a1c01a7ba7eb04d89f"}, - {file = "Cython-0.29.33-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:1900d862a4a537d2125706740e9f3b016e80f7bbf7b54db6b3cc3d0bdf0f5c3a"}, - {file = "Cython-0.29.33-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37bfca4f9f26361343d8c678f8178321e4ae5b919523eed05d2cd8ddbe6b06ec"}, - {file = "Cython-0.29.33-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a9863f8238642c0b1ef8069d99da5ade03bfe2225a64b00c5ae006d95f142a73"}, - {file = "Cython-0.29.33-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1dd503408924723b0bb10c0013b76e324eeee42db6deced9b02b648f1415d94c"}, - {file = "Cython-0.29.33-py2.py3-none-any.whl", hash = "sha256:8b99252bde8ff51cd06a3fe4aeacd3af9b4ff4a4e6b701ac71bddc54f5da61d6"}, - {file = "Cython-0.29.33.tar.gz", hash = "sha256:5040764c4a4d2ce964a395da24f0d1ae58144995dab92c6b96f44c3f4d72286a"}, + {file = "Cython-0.29.35-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fb8c11cd3e2d5ab7c2da78c5698e527ecbe469437326811562a3fbf4c5780ae4"}, + {file = "Cython-0.29.35-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9e54b4bee55fec952333126147b89c195ebe1d60e8e492ec778916ca5ca03151"}, + {file = "Cython-0.29.35-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba534e07543b44fb5ae37e56e61072ed1021b2d6ed643dbb92afa8239a04aa83"}, + {file = "Cython-0.29.35-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c1d7a9ff809fa9b4a9fe04df86c9f7f574ca31c2ad896462a97ea89523db286a"}, + {file = "Cython-0.29.35-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:247d585d8e49f002e522f3420751a4b3da0cf8532ef64d382e0bc9b4c840642c"}, + {file = "Cython-0.29.35-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ef2fc6f81aa8fb512535b01199fbe0d0ecafb8a29f261055e4b3f103c7bd6c75"}, + {file = "Cython-0.29.35-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:be7e1f98a359408186025f84d28d243e4527acb976f06b8ae8441dc5db204280"}, + {file = "Cython-0.29.35-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2e1e5d62f15ea4fa4a8bc76e4fcc2ea313a8afe70488b7b870716bcfb12b8246"}, + {file = "Cython-0.29.35-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:75541567a2de1f893d247a7f9aa300dff5662fb33822a5fb75bc9621369b8ef0"}, + {file = "Cython-0.29.35-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:99477c1d4a105a562c05d43cc01905b6711f0a6a558d90f20c7aee0fb23d59d5"}, + {file = "Cython-0.29.35-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:c44bb47b314abc743705c7d038d351ffc3a34b95ab59b04b8cb27cf781b44ae8"}, + {file = "Cython-0.29.35-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:94859c3fd90767995b33d803edecad21e73749823db468d34f21e80451a11a99"}, + {file = "Cython-0.29.35-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5a47974f3ebccf25702ffdd569904f7807ea1ef0830987c133877fabefdc4bab"}, + {file = "Cython-0.29.35-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:520c50d1875627c111900d7184fd658e32967a3ef807dc2fbc252e384839cbcf"}, + {file = "Cython-0.29.35-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:516abc754f15b84d6a8e71c8abd90e10346ea86001563480f0be1b349d09c6b8"}, + {file = "Cython-0.29.35-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c38e2c1e94b596132454b29757536d5afa810011d8bcb86918cc6693d2302940"}, + {file = "Cython-0.29.35-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:511f3adfb2db4db2eb882f892525db18a3a21803830474d2fa8b7a1a0f406985"}, + {file = "Cython-0.29.35-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:445e092708c26b357c97b3c68ea3eab31846fc9c1360bb150225f340c20322ec"}, + {file = "Cython-0.29.35-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3da42ef5b71674e4864b6afbe1bcacba75807684e22b6337f753cf297ae4e2d2"}, + {file = "Cython-0.29.35-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:db695a19968a54b9ac53048c723234b4f0db7409def0a5c5517237202e7a9b92"}, + {file = "Cython-0.29.35-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:156ae92bedcd8261b5259724e2dc4d8eb12ac29159359e34c8358b65d24430ac"}, + {file = "Cython-0.29.35-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:ea1c166336188630cd3e48aea4bbe06ea1bab444624e31c78973fffcae1cf708"}, + {file = "Cython-0.29.35-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e7b1901b03c37a082ba405e2cf73a57091e835c7af35f664f9dd1d855a992ad5"}, + {file = "Cython-0.29.35-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:27f58d0dd53a8ffb614814c725d3ee3f136e53178611f7f769ff358f69e50502"}, + {file = "Cython-0.29.35-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c19e2ba027d2e9e2d88a08aa6007344be781ed99bc0924deb237ec52ca14c09"}, + {file = "Cython-0.29.35-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b63ea04db03190dc8b25d167598989be5c1fe9fc3121d7802c0aafc8a4ec383f"}, + {file = "Cython-0.29.35-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5cdd65f7d85e15f1662c75d85d837c20d5c68acdd1029bfd08fb44c4422d7d9b"}, + {file = "Cython-0.29.35-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c17c876db737e1183d18d23db9cc31a9f565c113a32523c672af72f6497e382f"}, + {file = "Cython-0.29.35-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:2a2f2fb9b1c0a4a3890713127fba55a38d2cf1619b2570c43c92a93fee80111a"}, + {file = "Cython-0.29.35-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:a1ad51612ff6cfe05cd58f584f01373d64906bb0c860a067c6441359ff10464f"}, + {file = "Cython-0.29.35-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3cd717eee52072be8244bb07f0e4126f893214d2dfd1ba8b38b533e1ffec4f8a"}, + {file = "Cython-0.29.35-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:acab11c834cbe8fb7b71f9f7b4c4655afd82ffadb1be93d5354a67702fcee69d"}, + {file = "Cython-0.29.35-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8841158f274896702afe732571d37be22868a301275f952f6280547b25280538"}, + {file = "Cython-0.29.35-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:0a9334d137bd42fca34b6b413063e19c194ba760846f34804ea1fb477cbe9a88"}, + {file = "Cython-0.29.35-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c4cd7de707938b8385cd1f88e1446228fbfe09af7822fa13877a4374c4881198"}, + {file = "Cython-0.29.35-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:05b7ede0b0eb1c6b9bd748fa67c5ebf3c3560d04d7c8a1486183ddd099de5a00"}, + {file = "Cython-0.29.35-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:537bc1e0ed9bf7289c80f39a9a9359f5649068647631996313f77ba57afde40b"}, + {file = "Cython-0.29.35-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:402307ad6fd209816cf539680035ef79cce171288cb98f81f3f11ea8ef3afd99"}, + {file = "Cython-0.29.35-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:563a02ea675ed6321d6257df067c89f17b89a63487ef8b9ce0d598e88e7ff0bd"}, + {file = "Cython-0.29.35-py2.py3-none-any.whl", hash = "sha256:417703dc67c447089258ab4b3d217f9c03894574e4a0d6c50648a208bc8352bb"}, + {file = "Cython-0.29.35.tar.gz", hash = "sha256:6e381fa0bf08b3c26ec2f616b19ae852c06f5750f4290118bf986b6f85c8c527"}, ] [[package]] name = "docutils" version = "0.19" description = "Docutils -- Python Documentation Utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -310,7 +301,6 @@ files = [ name = "falcon" version = "3.1.1" description = "The ultra-reliable, fast ASGI+WSGI framework for building data plane APIs at scale." -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -350,27 +340,25 @@ files = [ [[package]] name = "furo" -version = "2023.3.23" +version = "2023.5.20" description = "A clean customisable Sphinx documentation theme." -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "furo-2023.3.23-py3-none-any.whl", hash = "sha256:1cdf0730496f6ac0ecf394845fe55010539d987a3085f29d819e49a8e87da60a"}, - {file = "furo-2023.3.23.tar.gz", hash = "sha256:6cf9a59fe2919693ecff6509a08229afa081583cbb5b81f59c3e755f3bd81d26"}, + {file = "furo-2023.5.20-py3-none-any.whl", hash = "sha256:594a8436ddfe0c071f3a9e9a209c314a219d8341f3f1af33fdf7c69544fab9e6"}, + {file = "furo-2023.5.20.tar.gz", hash = "sha256:40e09fa17c6f4b22419d122e933089226dcdb59747b5b6c79363089827dea16f"}, ] [package.dependencies] beautifulsoup4 = "*" pygments = ">=2.7" -sphinx = ">=5.0,<7.0" +sphinx = ">=6.0,<8.0" sphinx-basic-ng = "*" [[package]] name = "humanize" version = "4.6.0" description = "Python humanize utilities" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -385,7 +373,6 @@ tests = ["freezegun", "pytest", "pytest-cov"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -397,7 +384,6 @@ files = [ name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -407,14 +393,13 @@ files = [ [[package]] name = "importlib-metadata" -version = "6.1.0" +version = "6.6.0" description = "Read metadata from Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "importlib_metadata-6.1.0-py3-none-any.whl", hash = "sha256:ff80f3b5394912eb1b108fcfd444dc78b7f1f3e16b16188054bd01cb9cb86f09"}, - {file = "importlib_metadata-6.1.0.tar.gz", hash = "sha256:43ce9281e097583d758c2c708c4376371261a02c34682491a8e98352365aad20"}, + {file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"}, + {file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"}, ] [package.dependencies] @@ -429,7 +414,6 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag name = "jack-client" version = "0.5.4" description = "JACK Audio Connection Kit (JACK) Client for Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -447,7 +431,6 @@ numpy = ["NumPy"] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -465,7 +448,6 @@ i18n = ["Babel (>=2.7)"] name = "livereload" version = "2.6.3" description = "Python LiveReload is an awesome tool for web developers" -category = "dev" optional = false python-versions = "*" files = [ @@ -481,7 +463,6 @@ tornado = {version = "*", markers = "python_version > \"2.7\""} name = "markdown-it-py" version = "2.2.0" description = "Python port of markdown-it. Markdown parsing, done right!" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -504,69 +485,67 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "2.1.2" +version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, - {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, - {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, - {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, - {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, - {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, - {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] [[package]] name = "mdit-py-plugins" version = "0.3.5" description = "Collection of plugins for markdown-it-py" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -586,7 +565,6 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -598,7 +576,6 @@ files = [ name = "mido" version = "1.2.10" description = "MIDI Objects for Python" -category = "main" optional = false python-versions = "*" files = [ @@ -614,7 +591,6 @@ ports = ["python-rtmidi (>=1.1.0)"] name = "myst-parser" version = "1.0.0" description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -639,21 +615,19 @@ testing-docutils = ["pygments", "pytest (>=7,<8)", "pytest-param-files (>=0.3.4, [[package]] name = "packaging" -version = "23.0" +version = "23.1" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, - {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] [[package]] name = "pyalsa" version = "1.2.7" description = "Python binding for the ALSA library." -category = "main" optional = false python-versions = "*" files = [] @@ -669,7 +643,6 @@ resolved_reference = "7d6dfe0794d250190a678312a2903cb28d46622b" name = "pycairo" version = "1.23.0" description = "Python interface for cairo" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -690,7 +663,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -700,14 +672,13 @@ files = [ [[package]] name = "pygments" -version = "2.14.0" +version = "2.15.1" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, - {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, + {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, + {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, ] [package.extras] @@ -717,7 +688,6 @@ plugins = ["importlib-metadata"] name = "pygobject" version = "3.44.1" description = "Python bindings for GObject Introspection" -category = "main" optional = false python-versions = ">=3.7, <4" files = [ @@ -731,7 +701,6 @@ pycairo = ">=1.16,<2.0" name = "pyliblo3" version = "0.13.0" description = "Python bindings for the liblo OSC library" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -749,7 +718,6 @@ cython = "*" name = "pyqt5" version = "5.15.9" description = "Python bindings for the Qt cross platform application toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -768,7 +736,6 @@ PyQt5-sip = ">=12.11,<13" name = "pyqt5-qt5" version = "5.15.2" description = "The subset of a Qt installation needed by PyQt5." -category = "main" optional = false python-versions = "*" files = [ @@ -780,75 +747,75 @@ files = [ [[package]] name = "pyqt5-sip" -version = "12.11.1" +version = "12.12.1" description = "The sip module support for PyQt5" -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "PyQt5_sip-12.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a40a39a6136a90e10c31510295c2be924564fc6260691501cdde669bdc5edea5"}, - {file = "PyQt5_sip-12.11.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:19b06164793177146c7f7604fe8389f44221a7bde196f2182457eb3e4229fa88"}, - {file = "PyQt5_sip-12.11.1-cp310-cp310-win32.whl", hash = "sha256:3afb1d1c07adcfef5c8bb12356a2ec2ec094f324af4417735d43b1ecaf1bb1a4"}, - {file = "PyQt5_sip-12.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:54dad6c2e5dab14e46f6822a889bbb1515bbd2061762273af10d26566d649bd9"}, - {file = "PyQt5_sip-12.11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7218f6a1cefeb0b2fc26b89f15011f841aa4cd77786ccd863bf9792347fa38a8"}, - {file = "PyQt5_sip-12.11.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6b1113538082a7dd63b908587f61ce28ba4c7b8341e801fdf305d53a50a878ab"}, - {file = "PyQt5_sip-12.11.1-cp311-cp311-win32.whl", hash = "sha256:ac5f7ed06213d3bb203e33037f7c1a0716584c21f4f0922dcc044750e3659b80"}, - {file = "PyQt5_sip-12.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:4f0497e2f5eeaea9f5a67b0e55c501168efa86df4e53aace2a46498b87bc55c1"}, - {file = "PyQt5_sip-12.11.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b355d56483edc79dcba30be947a6b700856bb74beb90539e14cc4d92b9bad152"}, - {file = "PyQt5_sip-12.11.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dd163d9cffc4a56ebb9dd6908c0f0cb0caff8080294d41f4fb60fc3be63ca434"}, - {file = "PyQt5_sip-12.11.1-cp37-cp37m-win32.whl", hash = "sha256:b714f550ea6ddae94fd7acae531971e535f4a4e7277b62eb44e7c649cf3f03d0"}, - {file = "PyQt5_sip-12.11.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d09b2586235deab7a5f2e28e4bde9a70c0b3730fa84f2590804a9932414136a3"}, - {file = "PyQt5_sip-12.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9a6f9c058564d0ac573561036299f54c452ae78b7d2a65d7c2d01685e6dca50d"}, - {file = "PyQt5_sip-12.11.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fc920c0e0d5050474d2d6282b478e4957548bf1dce58e1b0678914514dc70064"}, - {file = "PyQt5_sip-12.11.1-cp38-cp38-win32.whl", hash = "sha256:3358c584832f0ac9fd1ad3623d8a346c705f43414df1fcd0cb285a6ef51fec08"}, - {file = "PyQt5_sip-12.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:f9691c6f4d899ca762dd54442a1be158c3e52017f583183da6ef37d5bae86595"}, - {file = "PyQt5_sip-12.11.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0bc81cb9e171d29302d393775f95cfa01b7a15f61b199ab1812976e5c4cb2cb9"}, - {file = "PyQt5_sip-12.11.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b077fb4383536f51382f5516f0347328a4f338c6ccc4c268cc358643bef1b838"}, - {file = "PyQt5_sip-12.11.1-cp39-cp39-win32.whl", hash = "sha256:5c152878443c3e951d5db7df53509d444708dc06a121c267b548146be06b87f8"}, - {file = "PyQt5_sip-12.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:bd935cc46dfdbb89c21042c1db2e46a71f25693af57272f146d6d9418e2934f1"}, - {file = "PyQt5_sip-12.11.1.tar.gz", hash = "sha256:97d3fbda0f61edb1be6529ec2d5c7202ae83aee4353e4b264a159f8c9ada4369"}, + {file = "PyQt5_sip-12.12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ed598ff1b666f9e5e0214be7840f308f8fb347fe416a2a45fbedab046a7120b"}, + {file = "PyQt5_sip-12.12.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:644310dcfed4373075bc576717eea60b0f49899beb5cffb204ddaf5f27cddb85"}, + {file = "PyQt5_sip-12.12.1-cp310-cp310-win32.whl", hash = "sha256:51720277a53d99bac0914fb970970c9c2ec1a6ab3b7cc5580909d37d9cc6b152"}, + {file = "PyQt5_sip-12.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:2a344855b9c57d37cf000afa46967961122fb1867faee4f53054ebaa1ce51e24"}, + {file = "PyQt5_sip-12.12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:59229c8d30141220a472ba4e4212846e8bf0bed84c32cbeb57f70fe727c6dfc2"}, + {file = "PyQt5_sip-12.12.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:5a9cbcfe8c15d3a34ef33570f0b0130b8ba68b98fd6ec92c28202b186f3ab870"}, + {file = "PyQt5_sip-12.12.1-cp311-cp311-win32.whl", hash = "sha256:7afc6ec06e79a3e0a7b447e28ef46dad372bdca32e7eff0dcbac6bc53b69a070"}, + {file = "PyQt5_sip-12.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:b5b7a6c76fe3eb6b245ac6599c807b18e9a718167878a0b547db1d071a914c08"}, + {file = "PyQt5_sip-12.12.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3dad0b2bbe0bae4916e43610186d425cd186469b2e6c7ff853177c113b6af6ed"}, + {file = "PyQt5_sip-12.12.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d1378815b15198ce6dddd367fbd81f5c018ce473a89ae938b7a58e1d97f25b10"}, + {file = "PyQt5_sip-12.12.1-cp37-cp37m-win32.whl", hash = "sha256:2e3d444f5cb81261c22e7c9d3a9a4484bb9db7a1a3077559100175d36297d1da"}, + {file = "PyQt5_sip-12.12.1-cp37-cp37m-win_amd64.whl", hash = "sha256:eee684532876775e1d0fa20d4aae1b568aaa6c732d74e6657ee832e427d46947"}, + {file = "PyQt5_sip-12.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1364f460ae07fc2f4c42dd7a3b3738611b29f5c033025e5e70b03e2687d4bda4"}, + {file = "PyQt5_sip-12.12.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6cb6139b00e347e7d961467d092e67c47a97893bc6ab83104bcaf50bf4815036"}, + {file = "PyQt5_sip-12.12.1-cp38-cp38-win32.whl", hash = "sha256:9e21e11eb6fb468affe0d72ff922788c2adc124480bb274941fce93ddb122b8f"}, + {file = "PyQt5_sip-12.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:f5ac060219c127a5b9009a4cfe33086e36c6bb8e26c0b757b31a6c04d29d630d"}, + {file = "PyQt5_sip-12.12.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a65f5869f3f35330c920c1b218319140c0b84f8c49a20727b5e3df2acd496833"}, + {file = "PyQt5_sip-12.12.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e0241b62f5ca9aaff1037f12e6f5ed68168e7200e14e73f05b632381cee0ff4b"}, + {file = "PyQt5_sip-12.12.1-cp39-cp39-win32.whl", hash = "sha256:0e30f6c9b99161d8a524d8b7aa5a001be5fe002797151c27414066b838beaa4e"}, + {file = "PyQt5_sip-12.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:8a2e48a331024a225128f94f5d0fb8089e924419693b2e03eda4d5dbc4313b52"}, + {file = "PyQt5_sip-12.12.1.tar.gz", hash = "sha256:8fdc6e0148abd12d977a1d3828e7b79aae958e83c6cb5adae614916d888a6b10"}, ] [[package]] name = "python-rtmidi" -version = "1.4.9" +version = "1.5.0" description = "A Python binding for the RtMidi C++ library implemented using Cython." -category = "main" optional = false python-versions = "*" files = [ - {file = "python-rtmidi-1.4.9.tar.gz", hash = "sha256:bfeb4ed99d0cccf6fa2837566907652ded7adc1c03b69f2160c9de4082301302"}, - {file = "python_rtmidi-1.4.9-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:8f7e154681c5d6ed7228ce8639708592da758ef4c0f575f7020854e07ca6478b"}, - {file = "python_rtmidi-1.4.9-cp36-cp36m-win32.whl", hash = "sha256:6f495672ec76700400d4dff6f8848dbd52ca60301ed6011a6a1b3a9e95a7a07e"}, - {file = "python_rtmidi-1.4.9-cp36-cp36m-win_amd64.whl", hash = "sha256:4d75788163327f6ac1f898c29e3b4527da83dbc5bab5a7e614b6a4385fde3231"}, - {file = "python_rtmidi-1.4.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d201516bb1c64511e7e4de50533f6b828072113e3c26f3f5b657f11b90252073"}, - {file = "python_rtmidi-1.4.9-cp37-cp37m-win32.whl", hash = "sha256:0f5409e1b2e92cfe377710a0ea5c450c58fda8b52ec4bf4baf517aa731d9f6a6"}, - {file = "python_rtmidi-1.4.9-cp37-cp37m-win_amd64.whl", hash = "sha256:3d4b7fdb477d8036d51cce281b303686114ae676f1c83693db963ab01db11cf5"}, - {file = "python_rtmidi-1.4.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5dad7a28035eea9e24aaab4fcb756cd2b78c5b14835aa64750cb4062f77ec169"}, - {file = "python_rtmidi-1.4.9-cp38-cp38-win32.whl", hash = "sha256:7d27d0a70e85d991f1451f286416cf5ef4514292b027155bf91dcae0b8c0d5d2"}, - {file = "python_rtmidi-1.4.9-cp38-cp38-win_amd64.whl", hash = "sha256:69907663e0f167fcf3fc1632a792419d0d9d00550f94dca39370ebda3bc999db"}, - {file = "python_rtmidi-1.4.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04bd95dd86fef3fe8d72838f719d31875f107a842127024bfe276617961eed5d"}, - {file = "python_rtmidi-1.4.9-cp39-cp39-win32.whl", hash = "sha256:2286ab096a5603430ab1e1a664fe4d96acc40f9443f44c27e911dfad85ea3ac8"}, - {file = "python_rtmidi-1.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:54d40cb794a6b079105bfb923ab0b4d5f041e9eef5dc5cce25480e4c3e3fc2f6"}, + {file = "python-rtmidi-1.5.0.tar.gz", hash = "sha256:92e619f4343f262daed7874236c910a6a60d10481ed7c4db86c7cba80f35e9e7"}, + {file = "python_rtmidi-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f54299b7b83e99fa2a533cf6ca24e17ccb492429562c309654143bfb6537bc00"}, + {file = "python_rtmidi-1.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:8090467b36be0529ea0b687849901c9a517de5f47edca09771a502f5e377f60e"}, + {file = "python_rtmidi-1.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:659558070e15dca97660341919f95ae0f6e86c5383af6789813d9b16ba51863c"}, + {file = "python_rtmidi-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:91362c4b7adc11b2d266adb2ca8e5d50be4b8b58ced1cebf8f5281d81b998a10"}, + {file = "python_rtmidi-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3f543efd038afb0a15b492ca0a807941b2716bbed046e8fcc5770c0074faa32"}, + {file = "python_rtmidi-1.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:19a34425806d6cf3fb46f4d679ad34de2a42b82833ec5b98b637345d77cf5e0a"}, + {file = "python_rtmidi-1.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7e752532b64f51a277a749de549014f6588189902df0b656cba3a4a4c980df7f"}, + {file = "python_rtmidi-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:d9f1b5fc9d1fcb967f644f796387552c1a573bd634f5da5bf4a6100e2973d497"}, + {file = "python_rtmidi-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6293bb8bccbaeb43d5484d6b3ea28f7ba18138c9ba5fda2db1e81213c680b445"}, + {file = "python_rtmidi-1.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:afd6ed249a9cbd6673e102e1cd2b1838c622b5c47cd0ba9f6dc8f6591921fc63"}, + {file = "python_rtmidi-1.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:bf9ab4a5e5a2ba9399abfeaf7b2047dd8c287ac0bbffd33649f623765788db07"}, + {file = "python_rtmidi-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:39d52382bda5f16013a03b4589e2c2378f373345eb238ffed61e8bad3ce5b29a"}, + {file = "python_rtmidi-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:07145ab1137aaf879895ec449b1793fea72dc661f0470769cb5d03fe80ff728a"}, + {file = "python_rtmidi-1.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:4fcdc2372900334cefa6cd48c66b3b3681b6b03f683a0562b679a89f233ae700"}, + {file = "python_rtmidi-1.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:14744ec6169cd21dd42e0dadac31482906121f7cf50191b9a230eda8183ac8f2"}, + {file = "python_rtmidi-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:e089c5014926437c62ee8dc25c8a293a5fb70f1e406564094c3871dfd8846bd7"}, ] [[package]] name = "pytz" -version = "2023.2" +version = "2023.3" description = "World timezone definitions, modern and historical" -category = "dev" optional = false python-versions = "*" files = [ - {file = "pytz-2023.2-py2.py3-none-any.whl", hash = "sha256:8a8baaf1e237175b02f5c751eea67168043a749c843989e2b3015aa1ad9db68b"}, - {file = "pytz-2023.2.tar.gz", hash = "sha256:a27dcf612c05d2ebde626f7d506555f10dfc815b3eddccfaadfc7d99b11c9a07"}, + {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, ] [[package]] name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -896,21 +863,20 @@ files = [ [[package]] name = "requests" -version = "2.28.2" +version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.7" files = [ - {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, - {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" +urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] @@ -920,7 +886,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -932,7 +897,6 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" optional = false python-versions = "*" files = [ @@ -944,7 +908,6 @@ files = [ name = "sortedcontainers" version = "2.4.0" description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" -category = "main" optional = false python-versions = "*" files = [ @@ -954,33 +917,31 @@ files = [ [[package]] name = "soupsieve" -version = "2.4" +version = "2.4.1" description = "A modern CSS selector implementation for Beautiful Soup." -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "soupsieve-2.4-py3-none-any.whl", hash = "sha256:49e5368c2cda80ee7e84da9dbe3e110b70a4575f196efb74e51b94549d921955"}, - {file = "soupsieve-2.4.tar.gz", hash = "sha256:e28dba9ca6c7c00173e34e4ba57448f0688bb681b7c5e8bf4971daafc093d69a"}, + {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, + {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, ] [[package]] name = "sphinx" -version = "6.1.3" +version = "6.2.1" description = "Python documentation generator" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "Sphinx-6.1.3.tar.gz", hash = "sha256:0dac3b698538ffef41716cf97ba26c1c7788dba73ce6f150c1ff5b4720786dd2"}, - {file = "sphinx-6.1.3-py3-none-any.whl", hash = "sha256:807d1cb3d6be87eb78a381c3e70ebd8d346b9a25f3753e9947e866b2786865fc"}, + {file = "Sphinx-6.2.1.tar.gz", hash = "sha256:6d56a34697bb749ffa0152feafc4b19836c755d90a7c59b72bc7dfd371b9cc6b"}, + {file = "sphinx-6.2.1-py3-none-any.whl", hash = "sha256:97787ff1fa3256a3eef9eda523a63dbf299f7b47e053cfcf684a1c2a8380c912"}, ] [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18,<0.20" +docutils = ">=0.18.1,<0.20" imagesize = ">=1.3" importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} Jinja2 = ">=3.0" @@ -998,13 +959,12 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] -test = ["cython", "html5lib", "pytest (>=4.6)"] +test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] [[package]] name = "sphinx-autobuild" version = "2021.3.14" description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1024,7 +984,6 @@ test = ["pytest", "pytest-cov"] name = "sphinx-basic-ng" version = "1.0.0b1" description = "A modern skeleton for Sphinx themes." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1042,7 +1001,6 @@ docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-ta name = "sphinxcontrib-applehelp" version = "1.0.4" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1058,7 +1016,6 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1074,7 +1031,6 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.1" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1090,7 +1046,6 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1105,7 +1060,6 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1121,7 +1075,6 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1137,7 +1090,6 @@ test = ["pytest"] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1147,47 +1099,45 @@ files = [ [[package]] name = "tornado" -version = "6.2" +version = "6.3.2" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -category = "dev" optional = false -python-versions = ">= 3.7" +python-versions = ">= 3.8" files = [ - {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"}, - {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"}, - {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"}, - {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"}, - {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, + {file = "tornado-6.3.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:c367ab6c0393d71171123ca5515c61ff62fe09024fa6bf299cd1339dc9456829"}, + {file = "tornado-6.3.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b46a6ab20f5c7c1cb949c72c1994a4585d2eaa0be4853f50a03b5031e964fc7c"}, + {file = "tornado-6.3.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2de14066c4a38b4ecbbcd55c5cc4b5340eb04f1c5e81da7451ef555859c833f"}, + {file = "tornado-6.3.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05615096845cf50a895026f749195bf0b10b8909f9be672f50b0fe69cba368e4"}, + {file = "tornado-6.3.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b17b1cf5f8354efa3d37c6e28fdfd9c1c1e5122f2cb56dac121ac61baa47cbe"}, + {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:29e71c847a35f6e10ca3b5c2990a52ce38b233019d8e858b755ea6ce4dcdd19d"}, + {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:834ae7540ad3a83199a8da8f9f2d383e3c3d5130a328889e4cc991acc81e87a0"}, + {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6a0848f1aea0d196a7c4f6772197cbe2abc4266f836b0aac76947872cd29b411"}, + {file = "tornado-6.3.2-cp38-abi3-win32.whl", hash = "sha256:7efcbcc30b7c654eb6a8c9c9da787a851c18f8ccd4a5a3a95b05c7accfa068d2"}, + {file = "tornado-6.3.2-cp38-abi3-win_amd64.whl", hash = "sha256:0c325e66c8123c606eea33084976c832aa4e766b7dff8aedd7587ea44a604cdf"}, + {file = "tornado-6.3.2.tar.gz", hash = "sha256:4b927c4f19b71e627b13f3db2324e4ae660527143f9e1f2e2fb404f3a187e2ba"}, ] [[package]] name = "urllib3" -version = "1.26.15" +version = "2.0.2" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7" files = [ - {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"}, - {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"}, + {file = "urllib3-2.0.2-py3-none-any.whl", hash = "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e"}, + {file = "urllib3-2.0.2.tar.gz", hash = "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "zipp" version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" optional = false python-versions = ">=3.7" files = [ From bb335de07ca41ee349570b0fcc379f15b37dbd02 Mon Sep 17 00:00:00 2001 From: s0600204 <2053873+s0600204@users.noreply.github.com> Date: Tue, 8 Aug 2023 15:53:47 +0100 Subject: [PATCH 318/333] Implement a Loop-Release CueAction (#280) Co-authored-by: Francesco Ceruti --- lisp/cues/cue.py | 9 ++++++++- lisp/cues/media_cue.py | 6 +++++- lisp/plugins/gst_backend/gst_media.py | 6 +++++- lisp/ui/settings/cue_pages/cue_general.py | 1 + lisp/ui/widgets/cue_actions.py | 3 ++- 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index cc29ad44d..47d5d6f4a 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2022 Francesco Ceruti +# Copyright 2023 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -60,6 +60,7 @@ class CueAction(EqEnum): Pause = "Pause" Stop = "Stop" DoNothing = "DoNothing" + LoopRelease = "LoopRelease" class CueNextAction(EqEnum): @@ -238,6 +239,8 @@ def execute(self, action=CueAction.Default): self._default_fade_duration(), self._default_fade_type(FadeInType, FadeInType.Linear), ) + elif action == CueAction.LoopRelease: + self.loop_release() def _interrupt_fade_duration(self): return self.app.conf.get("cue.interruptFade", 0) @@ -499,6 +502,10 @@ def __interrupt__(self, fade=False): :type fade: bool """ + def loop_release(self): + """Release any remaining cue loops.""" + pass + def fadein(self, duration, fade_type): """Fade-in the cue. diff --git a/lisp/cues/media_cue.py b/lisp/cues/media_cue.py index a8068df69..a3e08c0b7 100644 --- a/lisp/cues/media_cue.py +++ b/lisp/cues/media_cue.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2023 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -46,6 +46,7 @@ class MediaCue(Cue): CueAction.FadeIn, CueAction.Interrupt, CueAction.FadeOutInterrupt, + CueAction.LoopRelease, ) def __init__(self, app, media, id=None): @@ -160,6 +161,9 @@ def fadein(self, duration, fade_type): self._st_lock.release() + def loop_release(self): + self.media.loop_release() + @async_function def fadeout(self, duration, fade_type): if not self._st_lock.acquire(timeout=0.1): diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index 4b90df01a..a33138f63 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2023 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -139,6 +139,10 @@ def seek(self, position): if self.__seek(position): self.sought.emit(self, position) + def loop_release(self): + # if self.state == MediaState.Playing or self.state == MediaState.Paused: + self.__loop = 0 + def element(self, class_name): return getattr(self.elements, class_name, None) diff --git a/lisp/ui/settings/cue_pages/cue_general.py b/lisp/ui/settings/cue_pages/cue_general.py index 757a3ae3e..fd0f953f3 100644 --- a/lisp/ui/settings/cue_pages/cue_general.py +++ b/lisp/ui/settings/cue_pages/cue_general.py @@ -86,6 +86,7 @@ def __init__(self, cueType, **kwargs): CueAction.Pause, CueAction.FadeOutStop, CueAction.FadeOutPause, + CueAction.LoopRelease, } .intersection(self.cueType.CueActions) .union({CueAction.DoNothing}), diff --git a/lisp/ui/widgets/cue_actions.py b/lisp/ui/widgets/cue_actions.py index 8e2850a38..29d1523ec 100644 --- a/lisp/ui/widgets/cue_actions.py +++ b/lisp/ui/widgets/cue_actions.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2016 Francesco Ceruti +# Copyright 2023 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -35,6 +35,7 @@ CueAction.Pause: QT_TRANSLATE_NOOP("CueAction", "Pause"), CueAction.Stop: QT_TRANSLATE_NOOP("CueAction", "Stop"), CueAction.DoNothing: QT_TRANSLATE_NOOP("CueAction", "Do Nothing"), + CueAction.LoopRelease: QT_TRANSLATE_NOOP("CueAction", "Release from Loop"), } From 4641c128ca36edb1c96d99971aa4406d1093596d Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 8 Aug 2023 22:53:57 +0200 Subject: [PATCH 319/333] Support poetry 1.5 in flatpak modules generation, update dependencies --- poetry.lock | 58 ++++---- pyproject.toml | 6 +- scripts/flatpak/config.sh | 2 +- scripts/flatpak/poetry-flatpak.py | 35 ++++- scripts/flatpak/python-modules.json | 202 +++++++++++++++++++++++++--- 5 files changed, 242 insertions(+), 61 deletions(-) diff --git a/poetry.lock b/poetry.lock index e69efcb8b..984eaabf9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -340,13 +340,13 @@ files = [ [[package]] name = "furo" -version = "2023.5.20" +version = "2023.7.26" description = "A clean customisable Sphinx documentation theme." optional = false python-versions = ">=3.7" files = [ - {file = "furo-2023.5.20-py3-none-any.whl", hash = "sha256:594a8436ddfe0c071f3a9e9a209c314a219d8341f3f1af33fdf7c69544fab9e6"}, - {file = "furo-2023.5.20.tar.gz", hash = "sha256:40e09fa17c6f4b22419d122e933089226dcdb59747b5b6c79363089827dea16f"}, + {file = "furo-2023.7.26-py3-none-any.whl", hash = "sha256:1c7936929ec57c5ddecc7c85f07fa8b2ce536b5c89137764cca508be90e11efd"}, + {file = "furo-2023.7.26.tar.gz", hash = "sha256:257f63bab97aa85213a1fa24303837a3c3f30be92901ec732fea74290800f59e"}, ] [package.dependencies] @@ -461,13 +461,13 @@ tornado = {version = "*", markers = "python_version > \"2.7\""} [[package]] name = "markdown-it-py" -version = "2.2.0" +version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, - {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, ] [package.dependencies] @@ -480,7 +480,7 @@ compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0 linkify = ["linkify-it-py (>=1,<3)"] plugins = ["mdit-py-plugins"] profiling = ["gprof2dot"] -rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] @@ -544,21 +544,21 @@ files = [ [[package]] name = "mdit-py-plugins" -version = "0.3.5" +version = "0.4.0" description = "Collection of plugins for markdown-it-py" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mdit-py-plugins-0.3.5.tar.gz", hash = "sha256:eee0adc7195e5827e17e02d2a258a2ba159944a0748f59c5099a4a27f78fcf6a"}, - {file = "mdit_py_plugins-0.3.5-py3-none-any.whl", hash = "sha256:ca9a0714ea59a24b2b044a1831f48d817dd0c817e84339f20e7889f392d77c4e"}, + {file = "mdit_py_plugins-0.4.0-py3-none-any.whl", hash = "sha256:b51b3bb70691f57f974e257e367107857a93b36f322a9e6d44ca5bf28ec2def9"}, + {file = "mdit_py_plugins-0.4.0.tar.gz", hash = "sha256:d8ab27e9aed6c38aa716819fedfde15ca275715955f8a185a8e1cf90fb1d2c1b"}, ] [package.dependencies] -markdown-it-py = ">=1.0.0,<3.0.0" +markdown-it-py = ">=1.0.0,<4.0.0" [package.extras] code-style = ["pre-commit"] -rtd = ["attrs", "myst-parser (>=0.16.1,<0.17.0)", "sphinx-book-theme (>=0.1.0,<0.2.0)"] +rtd = ["myst-parser", "sphinx-book-theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] @@ -589,27 +589,27 @@ ports = ["python-rtmidi (>=1.1.0)"] [[package]] name = "myst-parser" -version = "1.0.0" +version = "2.0.0" description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "myst-parser-1.0.0.tar.gz", hash = "sha256:502845659313099542bd38a2ae62f01360e7dd4b1310f025dd014dfc0439cdae"}, - {file = "myst_parser-1.0.0-py3-none-any.whl", hash = "sha256:69fb40a586c6fa68995e6521ac0a525793935db7e724ca9bac1d33be51be9a4c"}, + {file = "myst_parser-2.0.0-py3-none-any.whl", hash = "sha256:7c36344ae39c8e740dad7fdabf5aa6fc4897a813083c6cc9990044eb93656b14"}, + {file = "myst_parser-2.0.0.tar.gz", hash = "sha256:ea929a67a6a0b1683cdbe19b8d2e724cd7643f8aa3e7bb18dd65beac3483bead"}, ] [package.dependencies] -docutils = ">=0.15,<0.20" +docutils = ">=0.16,<0.21" jinja2 = "*" -markdown-it-py = ">=1.0.0,<3.0.0" -mdit-py-plugins = ">=0.3.4,<0.4.0" +markdown-it-py = ">=3.0,<4.0" +mdit-py-plugins = ">=0.4,<1.0" pyyaml = "*" -sphinx = ">=5,<7" +sphinx = ">=6,<8" [package.extras] code-style = ["pre-commit (>=3.0,<4.0)"] -linkify = ["linkify-it-py (>=1.0,<2.0)"] -rtd = ["ipython", "pydata-sphinx-theme (==v0.13.0rc4)", "sphinx-autodoc2 (>=0.4.2,<0.5.0)", "sphinx-book-theme (==1.0.0rc2)", "sphinx-copybutton", "sphinx-design2", "sphinx-pyscript", "sphinx-tippy (>=0.3.1)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.7.5,<0.8.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] +linkify = ["linkify-it-py (>=2.0,<3.0)"] +rtd = ["ipython", "pydata-sphinx-theme (==v0.13.0rc4)", "sphinx-autodoc2 (>=0.4.2,<0.5.0)", "sphinx-book-theme (==1.0.0rc2)", "sphinx-copybutton", "sphinx-design2", "sphinx-pyscript", "sphinx-tippy (>=0.3.1)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.8.2,<0.9.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=7,<8)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions", "sphinx-pytest"] testing-docutils = ["pygments", "pytest (>=7,<8)", "pytest-param-files (>=0.3.4,<0.4.0)"] @@ -928,20 +928,20 @@ files = [ [[package]] name = "sphinx" -version = "6.2.1" +version = "7.1.2" description = "Python documentation generator" optional = false python-versions = ">=3.8" files = [ - {file = "Sphinx-6.2.1.tar.gz", hash = "sha256:6d56a34697bb749ffa0152feafc4b19836c755d90a7c59b72bc7dfd371b9cc6b"}, - {file = "sphinx-6.2.1-py3-none-any.whl", hash = "sha256:97787ff1fa3256a3eef9eda523a63dbf299f7b47e053cfcf684a1c2a8380c912"}, + {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"}, + {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"}, ] [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18.1,<0.20" +docutils = ">=0.18.1,<0.21" imagesize = ">=1.3" importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} Jinja2 = ">=3.0" @@ -1152,4 +1152,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "400acc7190a8d798c2f4dff0596a04c936c51eecc5c77f987f7076c7fd31b926" +content-hash = "726c5c1da98d86735658fd8607e9132be872f61fe6f00a9ee6661d3c8e6b6054" diff --git a/pyproject.toml b/pyproject.toml index 2b1f536d5..87edb619b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,9 +42,9 @@ pyliblo3 = "0.13.0" optional = true [tool.poetry.group.docs.dependencies] -Sphinx = "^6.1.3" -furo = "^2023.03.23" -myst-parser = "^1.0.0" +Sphinx = "^7.1" +furo = "^2023.07.26" +myst-parser = "^2.0.0" sphinx-autobuild = "^2021.3.14" [tool.poetry.group.dev.dependencies] diff --git a/scripts/flatpak/config.sh b/scripts/flatpak/config.sh index cf0be9ce3..1e23e4815 100755 --- a/scripts/flatpak/config.sh +++ b/scripts/flatpak/config.sh @@ -10,7 +10,7 @@ export FLATPAK_PY_LOCKFILE="../../poetry.lock" # Python packages included in the runtime sdk (some are not included in the runtime) export FLATPAK_PY_INCLUDED="cython mako markdown meson pip pygments setuptools six wheel" # Python packages to ignore -export FLATPAK_PY_IGNORE="$FLATPAK_PY_INCLUDED pygobject pycairo pyqt5 pyqt5-sip pyqt5-qt pyqt5-qt5" +export FLATPAK_PY_IGNORE="$FLATPAK_PY_INCLUDED pyalsa pygobject pycairo pyqt5 pyqt5-sip pyqt5-qt pyqt5-qt5" export FLATPAK_APP_ID="org.linux_show_player.LinuxShowPlayer" export FLATPAK_APP_MODULE="linux-show-player" \ No newline at end of file diff --git a/scripts/flatpak/poetry-flatpak.py b/scripts/flatpak/poetry-flatpak.py index 23fb2d011..ffd33b769 100755 --- a/scripts/flatpak/poetry-flatpak.py +++ b/scripts/flatpak/poetry-flatpak.py @@ -3,11 +3,11 @@ import argparse import json import re -import sys import urllib.parse import urllib.request from collections import OrderedDict from concurrent.futures import ThreadPoolExecutor, as_completed +from pathlib import Path from typing import Mapping import toml @@ -101,8 +101,7 @@ def get_locked_packages(parsed_lockfile: Mapping, exclude=tuple()) -> list: for package in packages: if ( - package.get("category") == "main" - and not package.get("optional") + not package.get("optional") and package.get("source") is None and package.get("name").lower() not in exclude ): @@ -111,6 +110,20 @@ def get_locked_packages(parsed_lockfile: Mapping, exclude=tuple()) -> list: return dependencies +def get_dependencies(parsed_pyproject: Mapping, exclude=tuple()) -> list[str]: + try: + parsed_dependencies = parsed_pyproject["tool"]["poetry"]["dependencies"] + except (KeyError, TypeError): + return [] + + dependencies = [] + for dependency in parsed_dependencies: + if dependency.lower() not in exclude and dependency.lower() != "python": + dependencies.append(dependency) + + return dependencies + + def main(): parser = argparse.ArgumentParser(description="Flatpak Poetry generator") parser.add_argument("lockfile") @@ -118,10 +131,18 @@ def main(): parser.add_argument("-o", dest="outfile", default="python-modules.json") args = parser.parse_args() + # Get the required packages from the pyproject file + pyproject = Path(args.lockfile).parent.joinpath("pyproject.toml") + parsed_pyproject = toml.load(pyproject) + dependencies = get_dependencies(parsed_pyproject, exclude=args.exclude) + print(f"Found {len(dependencies)} dependencies in {pyproject}") + + # Get packages sources from the poetry.lock file parsed_lockfile = toml.load(args.lockfile) - dependencies = get_locked_packages(parsed_lockfile, exclude=args.exclude) - print(f"Found {len(dependencies)} required packages in {args.lockfile}") + locked_packages = get_locked_packages(parsed_lockfile, exclude=args.exclude) + print(f"Found {len(locked_packages)} packages in {args.lockfile}") + # Compose the "pip install" command pip_command = [ "pip3", "install", @@ -129,7 +150,7 @@ def main(): "--no-build-isolation", '--find-links="file://${PWD}"', "--prefix=${FLATPAK_DEST}", - " ".join([d["name"] for d in dependencies]), + " ".join(dependencies), ] main_module = OrderedDict( [ @@ -140,7 +161,7 @@ def main(): ) print("Fetching metadata from pypi") - sources = get_packages_sources(dependencies, parsed_lockfile) + sources = get_packages_sources(locked_packages, parsed_lockfile) main_module["sources"] = sources print(f'Writing modules to "{args.outfile}"') diff --git a/scripts/flatpak/python-modules.json b/scripts/flatpak/python-modules.json index 2be785453..1bb541918 100644 --- a/scripts/flatpak/python-modules.json +++ b/scripts/flatpak/python-modules.json @@ -2,13 +2,13 @@ "name": "python-modules", "buildsystem": "simple", "build-commands": [ - "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} appdirs certifi cffi charset-normalizer falcon humanize idna jack-client mido pycparser pyliblo3 python-rtmidi requests sortedcontainers urllib3" + "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} appdirs falcon jack-client mido python-rtmidi requests sortedcontainers humanize pyalsa pyliblo3" ], "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/b5/6d/e18a5b59ff086e1cd61d7fbf943d86c5f593a4e68bfc60215ab74210b22b/mido-1.2.10-py2.py3-none-any.whl", - "sha256": "0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e" + "url": "https://files.pythonhosted.org/packages/64/88/c7083fc61120ab661c5d0b82cb77079fc1429d3f913a456c1c82cf4658f7/alabaster-0.7.13-py3-none-any.whl", + "sha256": "1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3" }, { "type": "file", @@ -17,14 +17,54 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", - "sha256": "8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9" + "url": "https://files.pythonhosted.org/packages/df/c4/1088865e0246d7ecf56d819a233ab2b72f7d6ab043965ef327d0731b5434/Babel-2.12.1-py3-none-any.whl", + "sha256": "b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/57/f4/a69c20ee4f660081a7dedb1ac57f29be9378e04edfcb90c526b923d4bebc/beautifulsoup4-4.12.2-py3-none-any.whl", + "sha256": "bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/93/69/e391bd51bc08ed9141ecd899a0ddb61ab6465309f1eb470905c0c8868081/docutils-0.19-py3-none-any.whl", + "sha256": "5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", + "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/9d/19/59961b522e6757f0c9097e4493fa906031b95b3ebe9360b2c3083561a6b4/certifi-2023.5.7-py3-none-any.whl", + "sha256": "c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/22/2b/30e8725481b071ca53984742a443f944f9c74fb72f509a40b746912645e1/humanize-4.6.0-py3-none-any.whl", + "sha256": "401201aca462749773f02920139f302450cb548b70489b9b4b92be39fe3c3c50" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", + "sha256": "0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", + "sha256": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/17/41/de1269065ff0d1bda143cc91b245aef3165d9259e27904a4a59eab081e0b/JACK_Client-0.5.4-py3-none-any.whl", "sha256": "52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73" }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/57/4f/91a4506efbeb67672732139b02bacb7bc1fb0293c3e2d6245f9bcb874cad/furo-2023.7.26-py3-none-any.whl", + "sha256": "1c7936929ec57c5ddecc7c85f07fa8b2ce536b5c89137764cca508be90e11efd" + }, { "type": "file", "url": "https://files.pythonhosted.org/packages/ef/81/14b3b8f01ddaddad6cdec97f2f599aa2fa466bd5ee9af99b08b7713ccd29/charset_normalizer-3.1.0-py3-none-any.whl", @@ -32,24 +72,79 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", - "sha256": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + "url": "https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl", + "sha256": "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/22/2b/30e8725481b071ca53984742a443f944f9c74fb72f509a40b746912645e1/humanize-4.6.0-py3-none-any.whl", - "sha256": "401201aca462749773f02920139f302450cb548b70489b9b4b92be39fe3c3c50" + "url": "https://files.pythonhosted.org/packages/30/bb/bf2944b8b88c65b797acc2c6a2cb0fb817f7364debf0675792e034013858/importlib_metadata-6.6.0-py3-none-any.whl", + "sha256": "43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/e3/05/ed67ccf462fff0b559e6ea7b3e3fcb20dec9d57bf90b5c5e72a6f316183e/livereload-2.6.3-py2.py3-none-any.whl", + "sha256": "ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", + "sha256": "84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/2b/a8/050ab4f0c3d4c1b8aaa805f70e26e84d0e27004907c5b8ecc1d31815f92a/cffi-1.15.1.tar.gz", + "sha256": "d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/e5/3c/fe85f19699a7b40c8f9ce8ecee7e269b9b3c94099306df6f9891bdefeedd/mdit_py_plugins-0.4.0-py3-none-any.whl", + "sha256": "b51b3bb70691f57f974e257e367107857a93b36f322a9e6d44ca5bf28ec2def9" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", + "sha256": "355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/29/bc/c11c9a14bb5b4d18a024ee51da15b793d1c869d151bb4101e324e0d055a8/falcon-3.1.1.tar.gz", + "sha256": "5dd393dbf01cbaf99493893de4832121bd495dc49a46c571915b79c59aad7ef4" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/b5/6d/e18a5b59ff086e1cd61d7fbf943d86c5f593a4e68bfc60215ab74210b22b/mido-1.2.10-py2.py3-none-any.whl", + "sha256": "0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/1d/f6/6d61a023d758f488e36638076e8a4ec4447a2cdf86938cf6c60cf1c860e6/myst_parser-2.0.0-py3-none-any.whl", + "sha256": "7c36344ae39c8e740dad7fdabf5aa6fc4897a813083c6cc9990044eb93656b14" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/71/4c/3db2b8021bd6f2f0ceb0e088d6b2d49147671f25832fb17970e9b583d742/certifi-2022.12.7-py3-none-any.whl", - "sha256": "4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" + "url": "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", + "sha256": "8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/5e/2e/524a6667fa851d0aae3eded349b4f1e56785036d65e186fb4bf2f741a505/pyliblo3-0.13.0.tar.gz", "sha256": "5700eac4a6db2c1c492a99c17bbf1871e888309ae7bcd1c68473650778d60f46" }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/ab/c3/57f0601a2d4fe15de7a553c00adbc901425661bf048f2a22dfc500caf121/packaging-23.1-py3-none-any.whl", + "sha256": "994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/6d/7c/59a3248f411813f8ccba92a55feaac4bf360d29e2ff05ee7d8e1ef2d7dbf/MarkupSafe-2.1.3.tar.gz", + "sha256": "af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", + "sha256": "c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a" + }, { "type": "file", "url": "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", @@ -57,28 +152,93 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/d2/f4/274d1dbe96b41cf4e0efb70cbced278ffd61b5c7bb70338b62af94ccb25b/requests-2.28.2-py3-none-any.whl", - "sha256": "64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa" + "url": "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", + "sha256": "58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/92/bf/749468bc43f85ec77f37154327360ba82e7d0ae622341eab44a6d75751c3/python-rtmidi-1.4.9.tar.gz", - "sha256": "bfeb4ed99d0cccf6fa2837566907652ded7adc1c03b69f2160c9de4082301302" + "url": "https://files.pythonhosted.org/packages/49/37/673d6490efc51ec46d198c75903d99de59baffdd47aea3d071b80a9e4e89/soupsieve-2.4.1-py3-none-any.whl", + "sha256": "1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/7b/f5/890a0baca17a61c1f92f72b81d3c31523c99bec609e60c292ea55b387ae8/urllib3-1.26.15-py2.py3-none-any.whl", - "sha256": "aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42" + "url": "https://files.pythonhosted.org/packages/b6/94/9f57c5459d0cda4032967fd551442d3b953094a4c6514d57d2cf0ad2f44d/python-rtmidi-1.5.0.tar.gz", + "sha256": "92e619f4343f262daed7874236c910a6a60d10481ed7c4db86c7cba80f35e9e7" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/29/bc/c11c9a14bb5b4d18a024ee51da15b793d1c869d151bb4101e324e0d055a8/falcon-3.1.1.tar.gz", - "sha256": "5dd393dbf01cbaf99493893de4832121bd495dc49a46c571915b79c59aad7ef4" + "url": "https://files.pythonhosted.org/packages/7e/7d/8fb7557b6c9298d2bcda57f4d070de443c6355dfb475582378e2aa16a02c/sphinx_autobuild-2021.3.14-py3-none-any.whl", + "sha256": "8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/2b/a8/050ab4f0c3d4c1b8aaa805f70e26e84d0e27004907c5b8ecc1d31815f92a/cffi-1.15.1.tar.gz", - "sha256": "d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9" + "url": "https://files.pythonhosted.org/packages/36/2b/61d51a2c4f25ef062ae3f74576b01638bebad5e045f747ff12643df63844/PyYAML-6.0.tar.gz", + "sha256": "68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/76/aa/eb38c4ad0a46e9fe45604bde48fee569c1954929edb7c3f1d8a3a86fca71/sphinx_basic_ng-1.0.0b1-py3-none-any.whl", + "sha256": "ade597a3029c7865b24ad0eda88318766bcc2f9f4cef60df7e28126fde94db2a" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/06/c1/5e2cafbd03105ce50d8500f9b4e8a6e8d02e22d0475b574c3b3e9451a15f/sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", + "sha256": "29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/48/17/325cf6a257d84751a48ae90752b3d8fe0be8f9535b6253add61c49d0d9bc/sphinx-7.1.2-py3-none-any.whl", + "sha256": "d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/c5/09/5de5ed43a521387f18bdf5f5af31d099605c992fd25372b2b9b825ce48ee/sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", + "sha256": "8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/6e/ee/a1f5e39046cbb5f8bc8fba87d1ddf1c6643fbc9194e58d26e606de4b9074/sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", + "sha256": "c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", + "sha256": "2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/2b/14/05f9206cf4e9cfca1afb5fd224c7cd434dcc3a433d6d9e4e0264d29c6cdb/sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", + "sha256": "bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/c6/77/5464ec50dd0f1c1037e3c93249b040c8fc8078fdda97530eeb02424b6eea/sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", + "sha256": "352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", + "sha256": "806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/4b/1d/f8383ef593114755429c307449e7717b87044b3bcd5f7860b89b1f759e34/urllib3-2.0.2-py3-none-any.whl", + "sha256": "d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/7f/99/ad6bd37e748257dd70d6f85d916cafe79c0b0f5e2e95b11f7fbc82bf3110/pytz-2023.3-py2.py3-none-any.whl", + "sha256": "a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/5b/fa/c9e82bbe1af6266adf08afb563905eb87cab83fde00a0a08963510621047/zipp-3.15.0-py3-none-any.whl", + "sha256": "48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/30/f0/6e5d85d422a26fd696a1f2613ab8119495c1ebb8f49e29f428d15daf79cc/tornado-6.3.2.tar.gz", + "sha256": "4b927c4f19b71e627b13f3db2324e4ae660527143f9e1f2e2fb404f3a187e2ba" } ] } \ No newline at end of file From 6fdd3465199fda335dc92af36339f4259aab22b6 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 8 Aug 2023 23:08:53 +0200 Subject: [PATCH 320/333] Update circleci image --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bc6f07d60..440069696 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2.1 jobs: build-flatpak: machine: - image: ubuntu-2004:202010-01 + image: ubuntu-2204:2023.07.2 working_directory: ~/repo From ec02d12ddc09b589449dc02267598e37746f8ecf Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 9 Aug 2023 17:12:12 +0200 Subject: [PATCH 321/333] Update flatpak runtime and required packages --- scripts/flatpak/README.md | 2 +- scripts/flatpak/config.sh | 4 +- ...org.linux_show_player.LinuxShowPlayer.json | 47 ++++--- scripts/flatpak/pyqt-build.sh | 2 +- scripts/flatpak/pyqt-modules.json | 17 --- scripts/flatpak/python-modules.json | 131 +++++++++--------- scripts/flatpak/python-rtmidi-modules.json | 56 ++++++++ 7 files changed, 147 insertions(+), 112 deletions(-) create mode 100644 scripts/flatpak/python-rtmidi-modules.json diff --git a/scripts/flatpak/README.md b/scripts/flatpak/README.md index 6402cefd6..569d666a4 100644 --- a/scripts/flatpak/README.md +++ b/scripts/flatpak/README.md @@ -16,7 +16,7 @@ The scripts in this folder allow to build a Flatpak package of Linux Show Player * ostree * flatpak >= 1.0 * flatpak-builder - * python >= 3.6 + * python >= 3.9 * packaging * toml * the BUILD_BRANCH variable to be set. diff --git a/scripts/flatpak/config.sh b/scripts/flatpak/config.sh index 1e23e4815..c600f7afe 100755 --- a/scripts/flatpak/config.sh +++ b/scripts/flatpak/config.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash export FLATPAK_RUNTIME="org.kde.Platform" -export FLATPAK_RUNTIME_VERSION="5.15-21.08" +export FLATPAK_RUNTIME_VERSION="5.15-22.08" export FLATPAK_SDK="org.kde.Sdk" export FLATPAK_INSTALL="$FLATPAK_RUNTIME//$FLATPAK_RUNTIME_VERSION $FLATPAK_SDK//$FLATPAK_RUNTIME_VERSION" @@ -10,7 +10,7 @@ export FLATPAK_PY_LOCKFILE="../../poetry.lock" # Python packages included in the runtime sdk (some are not included in the runtime) export FLATPAK_PY_INCLUDED="cython mako markdown meson pip pygments setuptools six wheel" # Python packages to ignore -export FLATPAK_PY_IGNORE="$FLATPAK_PY_INCLUDED pyalsa pygobject pycairo pyqt5 pyqt5-sip pyqt5-qt pyqt5-qt5" +export FLATPAK_PY_IGNORE="$FLATPAK_PY_INCLUDED packaging pyalsa python-rtmidi pygobject pycairo pyqt5 pyqt5-sip pyqt5-qt pyqt5-qt5" export FLATPAK_APP_ID="org.linux_show_player.LinuxShowPlayer" export FLATPAK_APP_MODULE="linux-show-player" \ No newline at end of file diff --git a/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json b/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json index 61e803f47..83183f39f 100644 --- a/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json +++ b/scripts/flatpak/org.linux_show_player.LinuxShowPlayer.json @@ -1,7 +1,7 @@ { "app-id": "org.linux_show_player.LinuxShowPlayer", "runtime": "org.kde.Platform", - "runtime-version": "5.15-21.08", + "runtime-version": "5.15-22.08", "sdk": "org.kde.Sdk", "command": "linux-show-player", "rename-icon": "linuxshowplayer", @@ -34,8 +34,8 @@ { "type": "git", "url": "https://github.com/jackaudio/jack2.git", - "tag": "v1.9.16", - "commit": "5b78c2ef158c2d9ffe09818a7dd80209ed251c5f" + "tag": "v1.9.22", + "commit": "4f58969432339a250ce87fe855fb962c67d00ddb" } ], "cleanup": [ @@ -52,8 +52,8 @@ { "type": "git", "url": "https://github.com/thestk/rtmidi.git", - "tag": "4.0.0", - "commit": "cc887191c3b4cb6697aeba5536d9f4eb423090aa" + "tag": "6.0.0", + "commit": "1e5b49925aa60065db52de44c366d446a902547b" } ] }, @@ -72,20 +72,6 @@ } ] }, - { - "name": "python-liblo", - "buildsystem": "simple", - "build-commands": [ - "pip3 install --no-index --no-build-isolation --prefix=\"${FLATPAK_DEST}\" ." - ], - "sources": [ - { - "type": "archive", - "url": "https://files.pythonhosted.org/packages/c0/da/4b8052ab5aa07952d7fe7b068d737a042e76373573eadd340f7550eddea9/pyliblo-0.10.0.tar.gz", - "sha256": "fc67f1950b827272b00f9f0dc4ed7113c0ccef0c1c09e9976dead40ebbf1798f" - } - ] - }, { "name": "protobuf", "config-opts": [ @@ -131,8 +117,8 @@ "sources": [ { "type": "archive", - "url": "https://github.com/OpenLightingProject/ola/releases/download/0.10.8/ola-0.10.8.tar.gz", - "sha256": "102aa3114562a2a71dbf7f77d2a0fb9fc47acc35d6248a70b6e831365ca71b13" + "url": "https://github.com/OpenLightingProject/ola/releases/download/0.10.9/ola-0.10.9.tar.gz", + "sha256": "44073698c147fe641507398253c2e52ff8dc7eac8606cbf286c29f37939a4ebf" }, { "type": "script", @@ -169,13 +155,28 @@ "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/12/e4/5c91edb0bc975752c308e425a86b02b5b00cf990b362cfc482254d66cbbb/poetry_core-1.3.2-py3-none-any.whl", - "sha256": "ea0f5a90b339cde132b4e43cff78a1b440cd928db864bb67cfc97fdfcefe7218" + "url": "https://files.pythonhosted.org/packages/fc/7d/e1fb5889102e49e7ef7192f155e9409233255ed64594827b81d4e5950335/poetry_core-1.6.1-py3-none-any.whl", + "sha256": "70707340447dee0e7f334f9495ae652481c67b32d8d218f296a376ac2ed73573" + } + ] + }, + { + "name": "python-packaging", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=\"${FLATPAK_DEST}\" packaging" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/ab/c3/57f0601a2d4fe15de7a553c00adbc901425661bf048f2a22dfc500caf121/packaging-23.1-py3-none-any.whl", + "sha256": "994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61" } ] }, "pyqt-modules.json", "pygobject-modules.json", + "python-rtmidi-modules.json", "python-modules.json", { "name": "linux-show-player", diff --git a/scripts/flatpak/pyqt-build.sh b/scripts/flatpak/pyqt-build.sh index 15b84f8dc..65990bcfa 100644 --- a/scripts/flatpak/pyqt-build.sh +++ b/scripts/flatpak/pyqt-build.sh @@ -1,6 +1,6 @@ #!/usr/bin/env sh -PYTHON_VERSION=$(python3 -c 'import sys; print(sys.version[:3])') +PYTHON_VERSION=$(python3 -c 'import sys; print("{}.{}".format(*sys.version_info))') sip-install \ --qt-shared \ diff --git a/scripts/flatpak/pyqt-modules.json b/scripts/flatpak/pyqt-modules.json index d7b32dc02..ca5155251 100644 --- a/scripts/flatpak/pyqt-modules.json +++ b/scripts/flatpak/pyqt-modules.json @@ -35,23 +35,6 @@ } ] }, - { - "name": "packaging", - "buildsystem": "simple", - "build-commands": [ - "pip3 install --no-index --no-build-isolation --prefix=\"${FLATPAK_DEST}\" ." - ], - "cleanup": [ - "*" - ], - "sources": [ - { - "type": "archive", - "url": "https://files.pythonhosted.org/packages/df/9e/d1a7217f69310c1db8fdf8ab396229f55a699ce34a203691794c5d1cad0c/packaging-21.3.tar.gz", - "sha256": "dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb" - } - ] - }, { "name": "sip", "buildsystem": "simple", diff --git a/scripts/flatpak/python-modules.json b/scripts/flatpak/python-modules.json index 1bb541918..4edac4e77 100644 --- a/scripts/flatpak/python-modules.json +++ b/scripts/flatpak/python-modules.json @@ -2,7 +2,7 @@ "name": "python-modules", "buildsystem": "simple", "build-commands": [ - "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} appdirs falcon jack-client mido python-rtmidi requests sortedcontainers humanize pyalsa pyliblo3" + "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} appdirs falcon jack-client mido requests sortedcontainers humanize pyliblo3" ], "sources": [ { @@ -12,38 +12,38 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", - "sha256": "a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" + "url": "https://files.pythonhosted.org/packages/df/c4/1088865e0246d7ecf56d819a233ab2b72f7d6ab043965ef327d0731b5434/Babel-2.12.1-py3-none-any.whl", + "sha256": "b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/df/c4/1088865e0246d7ecf56d819a233ab2b72f7d6ab043965ef327d0731b5434/Babel-2.12.1-py3-none-any.whl", - "sha256": "b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610" + "url": "https://files.pythonhosted.org/packages/4c/dd/2234eab22353ffc7d94e8d13177aaa050113286e93e7b40eae01fbf7c3d9/certifi-2023.7.22-py3-none-any.whl", + "sha256": "92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/57/f4/a69c20ee4f660081a7dedb1ac57f29be9378e04edfcb90c526b923d4bebc/beautifulsoup4-4.12.2-py3-none-any.whl", - "sha256": "bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a" + "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", + "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/93/69/e391bd51bc08ed9141ecd899a0ddb61ab6465309f1eb470905c0c8868081/docutils-0.19-py3-none-any.whl", - "sha256": "5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc" + "url": "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", + "sha256": "a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", - "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + "url": "https://files.pythonhosted.org/packages/bf/a0/188f223c7d8b924fb9b554b9d27e0e7506fd5bf9cfb6dbacb2dfd5832b53/charset_normalizer-3.2.0-py3-none-any.whl", + "sha256": "8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/9d/19/59961b522e6757f0c9097e4493fa906031b95b3ebe9360b2c3083561a6b4/certifi-2023.5.7-py3-none-any.whl", - "sha256": "c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716" + "url": "https://files.pythonhosted.org/packages/57/f4/a69c20ee4f660081a7dedb1ac57f29be9378e04edfcb90c526b923d4bebc/beautifulsoup4-4.12.2-py3-none-any.whl", + "sha256": "bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/22/2b/30e8725481b071ca53984742a443f944f9c74fb72f509a40b746912645e1/humanize-4.6.0-py3-none-any.whl", - "sha256": "401201aca462749773f02920139f302450cb548b70489b9b4b92be39fe3c3c50" + "url": "https://files.pythonhosted.org/packages/26/87/f238c0670b94533ac0353a4e2a1a771a0cc73277b88bff23d3ae35a256c1/docutils-0.20.1-py3-none-any.whl", + "sha256": "96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6" }, { "type": "file", @@ -55,11 +55,6 @@ "url": "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", "sha256": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" }, - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/17/41/de1269065ff0d1bda143cc91b245aef3165d9259e27904a4a59eab081e0b/JACK_Client-0.5.4-py3-none-any.whl", - "sha256": "52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73" - }, { "type": "file", "url": "https://files.pythonhosted.org/packages/57/4f/91a4506efbeb67672732139b02bacb7bc1fb0293c3e2d6245f9bcb874cad/furo-2023.7.26-py3-none-any.whl", @@ -67,18 +62,18 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/ef/81/14b3b8f01ddaddad6cdec97f2f599aa2fa466bd5ee9af99b08b7713ccd29/charset_normalizer-3.1.0-py3-none-any.whl", - "sha256": "3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d" + "url": "https://files.pythonhosted.org/packages/5e/81/60bbbb745b397fa56b82ec71ecbada00f574319b8f36c5f53c6c0c0c0601/humanize-4.7.0-py3-none-any.whl", + "sha256": "df7c429c2d27372b249d3f26eb53b07b166b661326e0325793e0a988082e3889" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl", - "sha256": "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + "url": "https://files.pythonhosted.org/packages/29/bc/c11c9a14bb5b4d18a024ee51da15b793d1c869d151bb4101e324e0d055a8/falcon-3.1.1.tar.gz", + "sha256": "5dd393dbf01cbaf99493893de4832121bd495dc49a46c571915b79c59aad7ef4" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/30/bb/bf2944b8b88c65b797acc2c6a2cb0fb817f7364debf0675792e034013858/importlib_metadata-6.6.0-py3-none-any.whl", - "sha256": "43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed" + "url": "https://files.pythonhosted.org/packages/17/41/de1269065ff0d1bda143cc91b245aef3165d9259e27904a4a59eab081e0b/JACK_Client-0.5.4-py3-none-any.whl", + "sha256": "52ca6164438d3b7f8cfdace5f6fab3c7224fe63022efc85c7029862d435bce73" }, { "type": "file", @@ -87,8 +82,13 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", - "sha256": "84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8" + "url": "https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl", + "sha256": "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/cc/37/db7ba97e676af155f5fcb1a35466f446eadc9104e25b83366e8088c9c926/importlib_metadata-6.8.0-py3-none-any.whl", + "sha256": "3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb" }, { "type": "file", @@ -107,18 +107,13 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/29/bc/c11c9a14bb5b4d18a024ee51da15b793d1c869d151bb4101e324e0d055a8/falcon-3.1.1.tar.gz", - "sha256": "5dd393dbf01cbaf99493893de4832121bd495dc49a46c571915b79c59aad7ef4" - }, - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/b5/6d/e18a5b59ff086e1cd61d7fbf943d86c5f593a4e68bfc60215ab74210b22b/mido-1.2.10-py2.py3-none-any.whl", - "sha256": "0e618232063e0a220249da4961563c7636fea00096cfb3e2b87a4231f0ac1a9e" + "url": "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", + "sha256": "84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/1d/f6/6d61a023d758f488e36638076e8a4ec4447a2cdf86938cf6c60cf1c860e6/myst_parser-2.0.0-py3-none-any.whl", - "sha256": "7c36344ae39c8e740dad7fdabf5aa6fc4897a813083c6cc9990044eb93656b14" + "url": "https://files.pythonhosted.org/packages/b0/0e/b551e09391220208764c00ed2b382e198414329d8b6037eeb54a8008d737/mido-1.3.0-py3-none-any.whl", + "sha256": "a710a274c8a1a3fd481f526d174a16e117b5e58d719ad92937a67fb6167a9432" }, { "type": "file", @@ -135,6 +130,11 @@ "url": "https://files.pythonhosted.org/packages/ab/c3/57f0601a2d4fe15de7a553c00adbc901425661bf048f2a22dfc500caf121/packaging-23.1-py3-none-any.whl", "sha256": "994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61" }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/1d/f6/6d61a023d758f488e36638076e8a4ec4447a2cdf86938cf6c60cf1c860e6/myst_parser-2.0.0-py3-none-any.whl", + "sha256": "7c36344ae39c8e740dad7fdabf5aa6fc4897a813083c6cc9990044eb93656b14" + }, { "type": "file", "url": "https://files.pythonhosted.org/packages/6d/7c/59a3248f411813f8ccba92a55feaac4bf360d29e2ff05ee7d8e1ef2d7dbf/MarkupSafe-2.1.3.tar.gz", @@ -147,8 +147,8 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", - "sha256": "a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" + "url": "https://files.pythonhosted.org/packages/7e/7d/8fb7557b6c9298d2bcda57f4d070de443c6355dfb475582378e2aa16a02c/sphinx_autobuild-2021.3.14-py3-none-any.whl", + "sha256": "8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac" }, { "type": "file", @@ -157,43 +157,38 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/49/37/673d6490efc51ec46d198c75903d99de59baffdd47aea3d071b80a9e4e89/soupsieve-2.4.1-py3-none-any.whl", - "sha256": "1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8" - }, - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/b6/94/9f57c5459d0cda4032967fd551442d3b953094a4c6514d57d2cf0ad2f44d/python-rtmidi-1.5.0.tar.gz", - "sha256": "92e619f4343f262daed7874236c910a6a60d10481ed7c4db86c7cba80f35e9e7" + "url": "https://files.pythonhosted.org/packages/06/c1/5e2cafbd03105ce50d8500f9b4e8a6e8d02e22d0475b574c3b3e9451a15f/sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", + "sha256": "29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/7e/7d/8fb7557b6c9298d2bcda57f4d070de443c6355dfb475582378e2aa16a02c/sphinx_autobuild-2021.3.14-py3-none-any.whl", - "sha256": "8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac" + "url": "https://files.pythonhosted.org/packages/3c/dd/018ce05c532a22007ac58d4f45232514cd9d6dd0ee1dc374e309db830983/sphinx_basic_ng-1.0.0b2-py3-none-any.whl", + "sha256": "eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/36/2b/61d51a2c4f25ef062ae3f74576b01638bebad5e045f747ff12643df63844/PyYAML-6.0.tar.gz", - "sha256": "68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2" + "url": "https://files.pythonhosted.org/packages/cd/e5/af35f7ea75cf72f2cd079c95ee16797de7cd71f29ea7c68ae5ce7be1eda0/PyYAML-6.0.1.tar.gz", + "sha256": "bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/76/aa/eb38c4ad0a46e9fe45604bde48fee569c1954929edb7c3f1d8a3a86fca71/sphinx_basic_ng-1.0.0b1-py3-none-any.whl", - "sha256": "ade597a3029c7865b24ad0eda88318766bcc2f9f4cef60df7e28126fde94db2a" + "url": "https://files.pythonhosted.org/packages/c5/09/5de5ed43a521387f18bdf5f5af31d099605c992fd25372b2b9b825ce48ee/sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", + "sha256": "8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/06/c1/5e2cafbd03105ce50d8500f9b4e8a6e8d02e22d0475b574c3b3e9451a15f/sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", - "sha256": "29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228" + "url": "https://files.pythonhosted.org/packages/49/37/673d6490efc51ec46d198c75903d99de59baffdd47aea3d071b80a9e4e89/soupsieve-2.4.1-py3-none-any.whl", + "sha256": "1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/48/17/325cf6a257d84751a48ae90752b3d8fe0be8f9535b6253add61c49d0d9bc/sphinx-7.1.2-py3-none-any.whl", - "sha256": "d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe" + "url": "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", + "sha256": "a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/c5/09/5de5ed43a521387f18bdf5f5af31d099605c992fd25372b2b9b825ce48ee/sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", - "sha256": "8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e" + "url": "https://files.pythonhosted.org/packages/7f/99/ad6bd37e748257dd70d6f85d916cafe79c0b0f5e2e95b11f7fbc82bf3110/pytz-2023.3-py2.py3-none-any.whl", + "sha256": "a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb" }, { "type": "file", @@ -202,8 +197,8 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", - "sha256": "2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178" + "url": "https://files.pythonhosted.org/packages/48/17/325cf6a257d84751a48ae90752b3d8fe0be8f9535b6253add61c49d0d9bc/sphinx-7.1.2-py3-none-any.whl", + "sha256": "d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe" }, { "type": "file", @@ -222,23 +217,23 @@ }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/4b/1d/f8383ef593114755429c307449e7717b87044b3bcd5f7860b89b1f759e34/urllib3-2.0.2-py3-none-any.whl", - "sha256": "d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e" + "url": "https://files.pythonhosted.org/packages/8c/08/d3006317aefe25ea79d3b76c9650afabaf6d63d1c8443b236e7405447503/zipp-3.16.2-py3-none-any.whl", + "sha256": "679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/7f/99/ad6bd37e748257dd70d6f85d916cafe79c0b0f5e2e95b11f7fbc82bf3110/pytz-2023.3-py2.py3-none-any.whl", - "sha256": "a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb" + "url": "https://files.pythonhosted.org/packages/9b/81/62fd61001fa4b9d0df6e31d47ff49cfa9de4af03adecf339c7bc30656b37/urllib3-2.0.4-py3-none-any.whl", + "sha256": "de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/5b/fa/c9e82bbe1af6266adf08afb563905eb87cab83fde00a0a08963510621047/zipp-3.15.0-py3-none-any.whl", - "sha256": "48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556" + "url": "https://files.pythonhosted.org/packages/30/f0/6e5d85d422a26fd696a1f2613ab8119495c1ebb8f49e29f428d15daf79cc/tornado-6.3.2.tar.gz", + "sha256": "4b927c4f19b71e627b13f3db2324e4ae660527143f9e1f2e2fb404f3a187e2ba" }, { "type": "file", - "url": "https://files.pythonhosted.org/packages/30/f0/6e5d85d422a26fd696a1f2613ab8119495c1ebb8f49e29f428d15daf79cc/tornado-6.3.2.tar.gz", - "sha256": "4b927c4f19b71e627b13f3db2324e4ae660527143f9e1f2e2fb404f3a187e2ba" + "url": "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", + "sha256": "2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178" } ] } \ No newline at end of file diff --git a/scripts/flatpak/python-rtmidi-modules.json b/scripts/flatpak/python-rtmidi-modules.json new file mode 100644 index 000000000..41dae1378 --- /dev/null +++ b/scripts/flatpak/python-rtmidi-modules.json @@ -0,0 +1,56 @@ +{ + "name": "python-rtmidi-modules", + "modules": [ + { + "name": "python-rtmidi-build-dependencies", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} --ignore-installed meson meson-python" + ], + "cleanup": [ + "*" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/5e/ad/c51b346930e7050cae81bb9f360986da30e27e1bffd28a8495fcbe84ec73/meson_python-0.13.2-py3-none-any.whl", + "sha256": "7943955b890897543be1d768beee53c436d7838483dc6773655cd8243c5e7a3b" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/e5/74/a1f1c6ba14e11e0fb050d2c61a78b6db108dd38383b6c0ab51c1becbbeff/meson-1.2.1-py3-none-any.whl", + "sha256": "08f83fc17513e99cd6e82c7554c1f58af70425211887f8f9c7363b2a90209462" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", + "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/ab/c3/57f0601a2d4fe15de7a553c00adbc901425661bf048f2a22dfc500caf121/packaging-23.1-py3-none-any.whl", + "sha256": "994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61" + }, + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/c4/cb/4678dfd70cd2f2d8969e571cdc1bb1e9293c698f8d1cf428fadcf48d6e9f/pyproject_metadata-0.7.1-py3-none-any.whl", + "sha256": "28691fbb36266a819ec56c9fa1ecaf36f879d6944dfde5411e87fc4ff793aa60" + } + ] + }, + { + "name": "python-rtmidi", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --no-build-isolation --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} python-rtmidi" + ], + "sources": [ + { + "type": "file", + "url": "https://files.pythonhosted.org/packages/3f/ec/f0b65000abe18272258b04955fa88fcca132cd4a4a12c2cf9af2656cf789/python_rtmidi-1.5.5.tar.gz", + "sha256": "3f3e9b0fa497e813cc0bdd73b28ae07977c91803e4d5550bc7c7a34a1501cbde" + } + ] + } + ] +} \ No newline at end of file From cf19436a6755197a164a561cdd5dbbe3ed9309b8 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 11 Aug 2023 16:57:20 +0200 Subject: [PATCH 322/333] Update docs --- docs/user/_static/css/custom.css | 3 + docs/user/_static/icons/cue-select-next.svg | 65 +++ docs/user/_static/icons/cue-trigger-next.svg | 65 +++ docs/user/_static/icons/led-error.svg | 131 +++++ docs/user/_static/icons/led-pause.svg | 131 +++++ docs/user/_static/icons/led-running.svg | 130 +++++ docs/user/_static/list_layout_current_cue.png | Bin 0 -> 11573 bytes docs/user/_static/list_layout_main_view.png | Bin 78504 -> 198258 bytes docs/user/_static/list_layout_right_panel.png | Bin 0 -> 25162 bytes docs/user/_static/list_layout_settings.png | Bin 39237 -> 73326 bytes docs/user/cart_layout.md | 10 +- docs/user/conf.py | 10 +- docs/user/index.md | 2 +- docs/user/list_layout.md | 116 ++-- poetry.lock | 514 +++++++++--------- 15 files changed, 881 insertions(+), 296 deletions(-) create mode 100644 docs/user/_static/css/custom.css create mode 100644 docs/user/_static/icons/cue-select-next.svg create mode 100644 docs/user/_static/icons/cue-trigger-next.svg create mode 100644 docs/user/_static/icons/led-error.svg create mode 100644 docs/user/_static/icons/led-pause.svg create mode 100644 docs/user/_static/icons/led-running.svg create mode 100644 docs/user/_static/list_layout_current_cue.png create mode 100644 docs/user/_static/list_layout_right_panel.png diff --git a/docs/user/_static/css/custom.css b/docs/user/_static/css/custom.css new file mode 100644 index 000000000..1996286d1 --- /dev/null +++ b/docs/user/_static/css/custom.css @@ -0,0 +1,3 @@ +.align-middle { + vertical-align: middle; +} \ No newline at end of file diff --git a/docs/user/_static/icons/cue-select-next.svg b/docs/user/_static/icons/cue-select-next.svg new file mode 100644 index 000000000..50a38ac04 --- /dev/null +++ b/docs/user/_static/icons/cue-select-next.svg @@ -0,0 +1,65 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/docs/user/_static/icons/cue-trigger-next.svg b/docs/user/_static/icons/cue-trigger-next.svg new file mode 100644 index 000000000..1deff6fd6 --- /dev/null +++ b/docs/user/_static/icons/cue-trigger-next.svg @@ -0,0 +1,65 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/docs/user/_static/icons/led-error.svg b/docs/user/_static/icons/led-error.svg new file mode 100644 index 000000000..caa9221e8 --- /dev/null +++ b/docs/user/_static/icons/led-error.svg @@ -0,0 +1,131 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/user/_static/icons/led-pause.svg b/docs/user/_static/icons/led-pause.svg new file mode 100644 index 000000000..60c483732 --- /dev/null +++ b/docs/user/_static/icons/led-pause.svg @@ -0,0 +1,131 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/user/_static/icons/led-running.svg b/docs/user/_static/icons/led-running.svg new file mode 100644 index 000000000..c77509508 --- /dev/null +++ b/docs/user/_static/icons/led-running.svg @@ -0,0 +1,130 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/user/_static/list_layout_current_cue.png b/docs/user/_static/list_layout_current_cue.png new file mode 100644 index 0000000000000000000000000000000000000000..44e9a29ed1541b7b8a256de8861abf575672a138 GIT binary patch literal 11573 zcmeHscT`hN*Dnatl-_%l9zq}i5}JziUIdh8fCPf{-g}oKD!mE_3L+p~1XP+R9h5G; zgLFdY2J~t7erw(JuJyhDJ;^#}_L|Vj>12EG#TyWu-ehSXj9J z7@3g(4|6AKR+_~my$C~Pq_Q$U7Qsax78W)x7B)um$Hj=hWk$^N6>JBn}f1!L*N^^H+~9kVdeV*D?JI zMaju&Da*;RIXXKaQML#yEYA$T1Zkz_n@p`{x~;hYB6lsP&sv#lxg2p-MDj1mY{K-J z)r3xixrvpdALUKmciglnUzwVDT^v%(M&KV<`%Z%Iol?}fDiOe3TH}-@9%j6;KGHaf-ad0ycC8l6kL&7TJnU5;mH1?r{?VP$6F+4 z7dufi1`X&xGl%+I>0YqI`-BAu5gTt0N2u^ziTy@(>kraJB-1pin4K zSOh2{0>EehT)gaE;hq3{7tRZaUl?}~E*8!xM^}`CJ=+B)+}y#goJugQDPzT}S;O1=!qVc;`i^eSwwLBuS^yEY2s@0b3#L`jKbpLlaevmhpuh@c=XhBQL-s#3T~Wxt z$ofa$E=n%V`Flq&)&Io(hxQ+~Un*m?)YV~k94y=}x~F_cn&YBD zq=FO1SOcdvyV2iGe~wMBxaq zggHP$6d{J`jiori90~#h;BX63F$7p#R7_Oj63Ws7rr_Xg2geL2$_{RY06N-RT^3vr z4!fnLEX^Sz^sD2SIa;=GS0tu_G>00>-p%tbRXvm)Le~|3!6rxy3KEAvCBP!0q9PdE z|5DONIJ;mx@dEQQc3zfT3=0fn42D?vg`Z*oF7q+Az~r0}a90OsJqHI{X^uam?w`f# znCWB*cZJ`9yCN{4f5u_GKjN@}un0`}Pu^kb4wfjS*Z)uZV)C#_{Tgy5lnbVPugk1o zGfEfX^y}%@qb=%kDzUL$P6`;@;+F|7aCd~|Wj`@kzltoZ;r3PtO!WBeu7BjC{s&jE z1c8xY5eo!B0xXQY@FoPnTpSDnK;YsQ!sZAhNCGbQPjnXtq^k$q8F9-B!y|?(jC)?L zP;9)H1I71GZ4Yb2#VBA117I8s1iB;)0s~9@CJgxBHxYt_BE`YxPykW_3I~8i#Vi0A z4;2PL#W6;T3X38|ApaME{|hEwtj+&4Q3`kw7XKJjDd7Jz?7tNL7DqAE{K~_`XH2*U z{u%Fob9NC~|BJuh?)G170R#PSBmWV<|I+m@UH=gS|B>>)-t{kC{}BWKk@COZ^}j|J z(O)}8ggxd(&jYg`LUqP>7EM~)-}qD3mYpfgAS94 z@2adWkH1K8jhLJ!9;Bp%`GB#ZeCL*)=ji%`hbPrg!{*jT=TDX5ySHv%r6zlc^~5@t zfXlnRV7QIg(IahL!fA$z2i@lMMEt$iDXhE7$$av_)R}p##r?z8>($E*1aLj9AlxE? zy|*j%qhe@MLe3yu0W#e5$weKz1v+QHXCf2JN(Rkar!WXvuGKd9=)0yLH;)ft8ZkiMISoS$$T|^%re(xDW9}|-9 zvAt|+G{6TES0|)eP8+`u5o0Be*AMHJlJ2Ni4^1QPPy4H(Olwn`3~zjx3zo#_tg!dF zzFr>lSlyYmyGd!B$B0t5oGQEr?s?q2f9l`Zz(E)CF%~6Bw5Dl4-78&E3(JgeHOpfk zS2(*PX;JMX;!y}R-lMtAZ<-)$z7liw4xZQSOJF|Nu^kD^NYLI1h`6Jr$2(ZNukXMI zRjJ3q?y{(AX`qMJV*HwNewt1GaP7WAmvbcG{7{a_dwBK!))~p8#$()sBNrw=4`_tI zSR+hPw_j39r1|FPBn#KX_)t2+N94hN#JKd2kc@c!vf`XK={73x8O~DSAIDhP=X-@z zlBJNIRg(f!o$!Q%50YweOLYSEDwET!k|%}-z2^6;0p_~;>kzBrM}A=u8!B_-g{s5; z@9MtZn3tXROX87~cz<+Dgu%E$>TSZNSGJ?#ZR|1AfV2~tm!>9b0#ff?8Mv?BlykX{ zf-LGjJEVCv|n@7BsEg>ekUw^MVe7~YdQcbaBy)pXf&_$5@ezc3-m zYwK&(k165TCG|3!cp!EHrI~1;nj8(uPy#fQ$z&eT1E&hX+so6Owa+{$ejeZqG}}nH z*h&{N-@48KiC9J-jID$ZI{z_o^CIl;1cXe;6KV={l4>*yhT9k^Rp3-M~9~GB|>TVlfWiqJ^=#@;;~JOya6pWB2syr$f(;CkOYngmJsro%;P+NTS+%RO9oY@h|=3;e*I)@wciZY<$ZNj`!847Ekf1@Fiod&OdhSR6G@VwB@^9cs(jDQs|V5;_YRmTcWcD zVGA@aDYb?Y$C4YShPTngFgHtj^pAh>+MAbXr#cs)>5j~`M3+i9zQMNGrR>Z)=Eqwl zs-7RLOD!nfzS1g5m*>Xg^Sx2|&;Un;G3n@sV96$r&6!yWlr0e_oh2{-M-)28CspJa zvS35Hb6DUMGG1fLNu#>?;Y0gKr|@qOpOv;URJjCoTEEe&S=`bw9KLb0rg+{ljmo}}5F zC+g6RM2qi$Bp~QUR5KyT<|lurMipM1w$qF`P#)pF=z|hzjr||V9fCrQUv{<;y><`p zF`0EYo}bb1q>V9ZG4BT4D;EqaQvW)fr2EKVEIY!hgHN-s)Eudw?0*l*SmT!bb}qx+ z-58$|>G(dQN-xW_{Y?6y@Ywvc&c#A0PU*lD`n(LE6H?g|xP^0FBC@5GUazL#HAzW; zd}%n^D!FiZHDM@La40rXG?YAD;;PTWz*_g`Pf@H1v*8aMkI1C$vHj~d1|k_zFzL=! z{PWP`jWp#EE7CH_(mD?>6p=#q&B1UWU-6fA->MMWc&>0_h*sJ14UMd-E4|N7iHxMo z4Q2cZgLuv!F_d`Bzk`f5E7-NERC~Z$M@K#AguO}1Pvx{;IXT?ucH<)g?N!+J~0P~%~;*N)F3rFbp zAt>}|DDH;2^L|t7^G;FEmh-^YosEx_moK#GQL-9LOR$Bf4`T{qxutmT+<0lRHm#8+ zM7ruKdw%9U=f?TfjZ>jYj*zfi%hZ>MhbXI0)^fCrg2Z9$=EF_i;9dKI<<9w*dFPzE zHyl8{_rAfsduDAjJT=<~1B~~o*RHma(D&Boq^v|~l*23B{N3H*uYk?uWSTh<{m0G| zPu89K9=DU-URr9WU+@SQE!-L3s2IvK78hZ$a-4zY<23~I9M|uz?E1p=J?o4oBHkJk z4ZruHU9j2dbKK*;@{K$P#7nYf2QYDXmBek!;7RA}NbSH~${t2uVTSZ>lGeNDN5CMK zd_YDG#_?u0mV(@_pw&q^1MP>yY*RMrg|pzztVr!hR>q;%`7<-15?BIUj?*#YI3rk} zmSK#y$ha+k33`3F>e05ncUs;i+E;)0l_1m(*WcsnbVx+*GwPab#aZlqJI^c~+8qBV znKeioNovgU1BR{%{nBuV6){iyd6aes=@ac;m|Zgq@{ZPx!fN{uX|=h!y|(mGAO)4` zGsfe%8eov5$yhu!np@O1Cu;l2zUzK2dgMKVnB-;t!k?zaeg5Q$reM0 zUA`0%fDe+=@yH3LaE0RmSET47lg#xrPf7Ob5L;8UZuIDAlcUk8ILNA3K0@-CCEND| zSx8u&ysFqi17IItKc}8atOhb6t-xun37tdO-frJN|wAw7-0Q$?4TpVxgOwbw`>% zS!E+`9mRis>V~{{7p_Hl=Jth0SzU$o_&FVqiWPaW0(@KV!A?=}*RMbG_dhpPl(*b5 z(v{X|zR5w+q`58aFtszCw26?(){ z>D2de#+%qZD|s8TL|%@JIp(xCe6=*E3}`5MQ0qnnUAfFUBa?{B|;84k_-&) z?k9LY8_8^)e`4#A33z z?N{#`G3(27-erD4|NwA-;7{Gd7TM~*4QYvC{<_7;Nx zxK4_`{N1xgQ~SYhd`8I?W4_cI&v((^r=_=td>!UW>v11-9siId0f&l9jn&_{$&diMEhe`wAG*EpN{jW7w_yrq3a4n2kDuAYBU zOkM0fs_O+;ow`ma4;Xl1r$OTTb9b6tX?Bv#U4vFrB-}+J9@Wg~*WMDCK3HsOwfY!e zw}>zMaoKUkM(U=*&*b6T>ci^{y^8Zk;WhYy6?3J&+h}yJ7cK>v7yj(Vfh4wMWBYeo z@fwP{g!y+YUFHLDfrb+uV9gcT1-yfOmvvdq^f^^Sr5=1H&o6;qkB=Dx!WNc&{D6Hn zs@ATr4))>|=f@31L9W=(JqOgB)Q__SyN{}v?$;#AEe2>uS+@9TKL2K4I9k$BV_ca; z-J46?KF1h(+pkb}kKMcl_e76b<%F{{X5+{wWESbTxmK-OG$y`v<7b-a<09aYlQ1NL zdyxt$*u*$NZ9nicqj+zSK||w&v~6NXL`(VmewCrn5uyv;{sPLY=@%4oHPc@8K4S@_ z)4ZX73+J7zKN!XbEA~CJqm;5;^ z-t@+G@BCeZsMEbTmye&)Rhwf6!ONG@7RwLL>Nk0fd-~P`+Q;V<$Df!zaqMzi`!rn^ zQXCi)KbezL@{qMCc|_(i^t>|E-fU^5W2&6Gb*tSajWAY>3M@Wwv9Vz{Vy00HIEwcS zPnmv^Uvij&N36F&rj}A}Xfox@scts^ZT)d>^t%=A!5p?2qT%t^+y zFYShHNltWsaMwXI!^Mc7$?y-A^s-d*cS7#oWPu=-!Cob^-GtW&B@V?`D$`#Z`*n`2 zMKuUwl>o&&&iXcTUQ=+~mQH!%dN>po3tf@l&k(B-^Zaf^&AkrV^RNB!Hex0kUpcBS z_Oss@@7rp8x7-=U85C6EcvN;Now?JAs=;OOmOrli;AlT*vwj~ zSYpMy^ak&`HhiOB5EbMOtR6pq&=rmry5pV*iV0X!=T_+9P^*c3_vi{eywAxKyf*c@ z@!NX3YP!NSmN@>J!8@P)CvFm%q4LTv!&W;49l{>b{HXon{L)FSWpXn6sJ|-tXV=Dl z6j-Zr&*9mR{VNuWCrOq&x=j498fUhAXs4{X9Ztd9_AS%{N%DwH14>i7ce-8G2AxIU?;OFu?X?hwp)xZ!DhFAjvI<&T|GqlgK_q$Eo z!%e#2y&L)KUSR`!Bw9S=>nq$Rg}f6P>Cw5a74C0m+4Bl235;fxt{f*Oq&Xcio`gd$ zUY>kUzvam(GEOfI$6*s|IgtlHnu)Wz_j^bCQY<-IMH3m5snM~o*dbOU3&g*!{HY!( z3W?jdS`jE%E`I+m50{*5DI~9a?TPHWdWgOwxGFw1hA)PNTGi#pS7wLEpLxy`HtRvb zMMXI)+7l+3o|rW(aS+M6G~~wjykV5qssxzEhb2K)?ww7iJerx^J2?Inp>as6iDv!P3+OMx_{VV zDfm%^s+049YysFAtF>>H>rhUT2=OqKT?DLw^0_n^#b zy5?d>ecx4t(n?C*zkw&n=~`6pE%j_yeA@52rR8_k75GjAzuUtCzt<-1u3Z&m+5vmi zazGOTXd?L1BF`X}YliA_4rw2&pEfgcYz9p6QP7^V?D)vC6;5xrJG{HsG;4y&knjzi zzP4CC<>7VFS>i3)3?R;B#q>0nr`ro${3mga@kd= zZ5T2JE~-S(f2-rLO-5BNgjsG_u#L;QQi4dr1x8(17fcf_>N(Ci_{^{@$@ivPvP@Ir z+kp)ysb%@L9?#T6nQU#XZ;|YdRP6g*YtnC%(>-pyzW8EgFHN)i>;WgiRaRrV-pCik zz}<{>0{_~l1v_O{f;69CKUU^zZ+Y3(FA$_%b6aDwUMgit1~NUm65hZ`Y04yBXr@<@OlC!rD~!g) z9Q|DNN8otYklq&=pA_kk}HPT0o#(wfnua;I$KiY&w#<{o`4XfZdR@a^v zWqif+|H41L9Jj8qps|zJ)Z}AyMO}Tb&Bi$aQbQb3o^FR^CVDEnZ>~~=zH1QJu!Oc9 zwT5r!TT%?Q&zu5qD`=#Q@|)%a$FvIBv9=S;4Puat>r=Yd!6Rx=$09>tMN*_PzL zdVyV4PQs%hC@(2NDUtcPb}VYIAO4WP@aTY%=?B`Q(*&kYCr6&u;Ayh}*%{8Uo;Yo7K zQ5j#kUglaJ_l($jg>N;}3L|R8(_TcU<*FN0O-AdV(@2UF&vg6Hh^}{=>70N3A|ri9 z9COm^yq#=22i4(@;dJU&yWWk*z~BFJOY8aG=qJj!T)i)^b?FV?X9jDuJ7@SL^oV_| zd`6|9!5mS%1(v@me?U^_J5_k}0C=2W&SiV@G!)xfgX#Nyn{IpfYL)?CWo?c%wIs;_ z$Zq^LH)(ahVUmKslknDJ({z=u#!o}_%}@Ar^;15EJtc+Qz{IpCLnCgyq06%@7W!(` zR@HVd!023=CLeso@24bVYl@MKhgcQw^+p*~c_j-J@hJ{2zU#H9%d_JE+jp{`dc%VlOi2JtdQb_@NGZe!)Zn)ferimr~B`p{Y+~S4pJsPi=)#dHgaL7 zwlcM9{;2u#V5Jn@02l%cvw$tW<ve^NruHv&XS);Sw&x%QZ z%DdxSoJ|O|z>R5es{1bvCV(4|oRN3;RP#_}b4F!utS#5KEL%;28f5xZ0wg1>s&!b_ zbGj6pEAGLk_@tv0)$i!h&l$;&i@0h7w?0VE@)`vICAU{z+`c?bX#rTw3m`V_!B{+}%pjRhdogzFaT#fo$BDN1@E73e4Mq+$HLD1K;N zr%NE>SJeo4e#^~bpi8-1WI9b@)QmI(jm>{^A*ix~vQ5l%oQL(eKb1+e{aq@HV@pG_+D|dZB zK%hd95r41Zmb$;-s;xG$)_pQ%#)?dr`7?N#f|uy9n2(){85~zQa9M4#rllG`^C$km z00fR2A1A9L#A!}laAX2^l_fV>Bdjj9{z_l-4xy$J$4y~0J|MysTakVJ3 zV_B_!eVz9M@*WYof(C7#Jo~rN;NbaCG_>mjwJYcM-8!S7=j$w(=a}vUYcwFj%O@iL zwj=)7P)~+yxz6cX&aK|(Ph!`8Z4ap}&`gOH=zm-5*+4_Pw+UcMXnxt_3HdP~kuPo_ zbSLlrVD-e$w_3!j1<79Y%m3Uj{eE8r#hzpuy$HD)aS}a@dD!QS!f2Uc1wP?-Eh`33 zrMTxafD-4U2<~ib;Hy_#&m_~v^bUS2?kUFm@66UMOdq)HyEH6VR^`5-rKRBYx;f`w z1}_uozkDC^*N&u>a|i0h zld)n!ST@z_OhdFf+ePd$>am3m1oY? zGXFDG)h4V&tRxYU8+~zSLY|jJrGut(PqIH=J%t9i>_at=^*$>4NL6$n`BbSEYw8S| zR*qHiNc{JCaYv#ENu!wx6QSW$zBQDh&C|tuwY2$~vSn!5;scg3D*RP?dOFg={QNH+ ztBwD8aNSWdYZR9?9poeYgtxcz82-oM0e%0%uJOZlg2Haf*cw#6&T+$1+x6goA7VG@ zkH0|Dj7jTFsY@K&TE7gaS335w*v@cOTJn(0`V?~=`d=-Ry#{gHqI zmu%6mnJcYq{;xn^J0d5ToW1mZ~rIN#h_G4!s8a!Z! z5@7_)(@-A}K^VTGZ_Wng+UnLKKj6!ano&NrSDP&&xJ$H; zLhCumay?oc&;R5ffKC`4A0Kb0B_bl0%wwmJ^C2#g&4^aDuu6PSwZ@iw(F;b-@VRfv z%w(bdqVevO@##}emFcOWwESDcm+)qodIzTz7mf`_u9hqyh{Kp*)LM zl<(fXJ3m|qqY$b;`y%KvQu;}ecW;XQZcs0yudk$vnaxmMe z=flkMNBA#YlHl@r&=wRFbo9Z(#@-s0qGX8%``lk7@bdDWwxP3pOLQafb=>GX8I{88 z?U9Bqdc2Gs)?>?K`4IK@IF#z0EIuT>ivW80O^8f(INi0vxL0X@VZnSnA6G_3rbweQ zaA2SaU8vskQ?49wu6&B5sVTw4ug_Vs2}Aocl?lhk_BSvnp>Gn086d{Mpdi#owR3c2 z2X77x9+>DfL);-=ULg@ez2gPY4J@&j6cpYXLW8Cp@85qJFVcts01c0XB-zfh&e6o; zPd2@-uCBO*1TF!Aq^c?bm*se-*R2bg5J@MX`Cs-+G5q}fLxO{;I5{zVfxe9Vwoq0r z(e510l423?0XilXN!Dsv^@5oAy+6uDfo2s0AY7*7`gj(-^vq0=#>Pfzd3lqyF8Ne` zXEaw=*TcFz%AFnJfz+h?fPhy0OsS|tH+Oe7@FtUzva$mhOwG$1{2`73e7^mu+IAuN z)-V?sb!}Z;xzi4NG8g&}rJN5;n!gicV`F`sjv8E#0#i~*LGU|t*@SmU`hT)z!YSbt zfB~6yhQ>+EZh3ePP8BGp0JdUOuKpYyeXSz|Pvo-FZAwQ-qiWX<0T>&v$GQ2TP@!ru z$zXE8^768Xipt9$KYpkbD8(tH@mGMi6hy_^S&1;tcby3V5MP5WMC#q^4g<;oX) z%0e+4>Zr6B+k{Mwb>}g>di4tEYxK8oJ(1*deBOea^|Q=?);J2>T&*b*ahPHNEg)xp z%95mUu|-9d+MO(J&z8wJ8CO03nV{|lVLnXE={=fu=sTi#r+S}W=m~$G|%sq~*Zc7&-QxDpFb#=vRHEGSxQF(k(+|?6J z{b<@LJa)OF0ci7JMGzSo+0{xArTg7U_x7J0j-%#V-R5O3AtCaxPQMyg6AF**_kbsp zhd>}-E+Cspd2B=&L>MUhE&`ytW9rAhZ{*LQw}%42I5!R{adCUW!$OLtrnK#Wm^8Gs zffI#lMpc?SOHj~4gDbV5V5-erwNcI7I!3JEr_ zlY%}L=v{bLmeFDT@86ud9y=y3hZuQUH6?DIo|U!>nvs-It-ZY=-IPAz9A-nud!@<+ z8kMwobP6r)?MNwufurWedy~bDSF8QTCMFKu}?}VkvSngomUVB?LU_g=wvsLy^PK~?5SXfxb#>Owzy{jyRV;Md(xaw^5T?c{L zNg@RA&y|76gU-8{nwqk^>|0sS*V4mg_UG&H0K;rIWuo?H5|Umdq&1KhT!xVu0( z&+YDnj?ew{O`3q~=EYREA$LmojCG5HI)~)-xm=!+ThBjndX!fn}m zb0DcRmR=9Ae%J7>J2-~0+c8d*(B)G*s1R0IXB+VN%Id0LSQvU~^9>p>pN-d>a3XH& zr@+X^i?xD&h#hSKCP>EbL}X`YCnzN3U~ivkF-D6pUZ5OQVbZ?}*dO4OuqXsOfXOJC z6*e<7ySu+HS1%HmlG3XdqTPIY1cf64ge_MwqZxAAn+Ld{(-tHcpmQ1xE(-w^n_mz( z%|B8CNHbZaf!=a943gdXljFW$PPaXOjYYt8<-feFuUTUo=ZO{5`xVfHwy)Ukw+9ZX zAfIMw7;Vs~dA4Z4J|ViDOu zxH|Gf3{b(fqa#HC*%U$d)*(~KB}VxfDHd`nkGfQMvhU*g11{Ow2 zKltsHKk(|l8pZb;GY2l;V`8q)#}wV+_a}gr5dmanP>Rw^X_@TF66B0oz-50u-ScJ? zm<1AUYiWNJO!JXUL_9k0SgnUf_p|9bFdYEZxjt~uN3+e5O~|#J_~qu|Q31IQoj=(G z0sO%GW(U>J&#!#$zr*D(J30VOvH(Tt=;-(eIK|Cjy>9~Z zBRRj+U?v^GD}aF{b6Euf@DnK&BRc%@2Hd4S?X0$#$UAuSZ2(U6r$ z9YB5*EHV*$dmMBErc8}WQ-nvK3kXNK##V@(gX7ur=d`@M&j8)SKtU1~7OvZ>n6ZZ3 z?3jHGuwg7~mdwKSUoi}#P8oF35c&eq`?S7qR;f`w7gnuku%TJ3YC4oEW4YT&_yV8N z!(l7Ub@OlvXn&sNLm)xN~d|FIt-JP`xIahTMu_5V%UK0w*Wd-?hd|8 zcm^2nkn2OL9^pA;^S%mDN}!hjNfZ=hV6~q9NJdT$Oq5DtyjK$k`yVhJ^21)~20%nF zDr71i>3p_*9HZK1)s$|I>rum5aR#fw6PCtfNXX{2+70~2_guDz$Gg@8KPW1q))o;y z!~t*?a(t}AX*2uvkyU*A_HnyetJ>NC;JE`vpCgj+*nUk&prfa!zq>mdb~>C7Az;?D zKVE+23u&Lx?rUxR0=Ts;-6-IdClhLVgjb+vfInyioC~MjVtATB`y+1)I2OFUIaq>b zBqkC)HdtB7(c0PyzyTO&h~e|E;8p~H&t2W9Qs-UaGpaf+w|s$2t<2YfktZjtxjDd_ zP7JC=i4BKYQNpH|`+RkvIcx6k--iG_I6gk6;=ge_EC-x2H8piwc6N5*q4IR&6Yi9V zC7+D<+S=NO^}fCc9_QQ9x0Qv3=--6meG~-R+g+oRlS3yw9HieCa>4r(QwcxPDe7ur z!)5DgjiKr%XJ^!0T%K<;KNmijE;V}rA!hT z7(mVEu>eB(Ryh{~pnr9Hj)C{|^c(#NMyKmCb?vtZgn{?hv2+=b(63)RUKFCg{VVK= zPWg^VN0&FBd9LKVH<|O^{{~^+%asx^3=v1iQ+!!D8JQLMgB^)Zy;JMb61J;r4dWs9 zH=y4Ow%y%%mbRtmX!s2JoFshUO$@*?kWjJgcgN|UJb99@yO03*Y=>2GrrS#i95OQT z0JM$AyP~556u0s5zS2)qv5kk3jOPTev4{ITBE9Fcd2(PX$ z0ma^*tx9=B=_8-B~Wq_gp774%! zvTe=W0rF7O$LVGVhr9x%9JI0Ad+(X-QH1E|TVh^&oX2T0>c)DEriKO2V*MWJc-@Z8 zblG9(oK}^kvM5*YE#*iDOXJ+(0DMsrufuC< zDPi@W7o$Y*N0;ijfPn%~t-y>7y+fs3xg;O$$U#%;SFaFJQ8@r4%vMO7b=n?zwN%Fb zsNCm&!m@L6*Z_aZpjI-_jvdukgKZ2W^Vv$PA!avb9ZcrR0*LfufVVs0B_k&{aA4p~ z1Ar$j7grp>{>A{u3xWJP-OkElOBpQEZ9*xW_yEv$AlAtRUi1_hx!nDXph%T*Z>qGr zzdsD{JeAH?Z5$Xs0AT@wKbkjhvU~Kj0o4Zla4T#H{;gqu zuI8xX9>8BwO-&Nmq9^tvVmqvL3Bu+%0GQYuN(&k^W&9&NWqE&dq0U4AxJ%iOAE^K^ z1%W`n)JnZO*Q)K7fT=SY$^61;J&gmr@Cj0iuGve%qbPB4)&Ks z#l+wN^9l4|65ti1=IiC%6rFQ(q?I#r06TcAR>FiJpTgZbHI>lQBMl%j5FdSr)?<1M zgr9v43fg+8*RHD&gx`UAvMF|UEC4t>?ZBI9y2X3(!hN?O4~Tf)P*Z1h>}65>E#ybu ze0SD+J?RBdG3&YNd&)R~*IwVlh&e6(e0?Q`!eQLA-2dbZ@ZL&UfuI;VpP~7>vu9{% z*@_v9Me5~#fIr_nR0CozAm{*4K)HEMSxM16FsOak$T6uu55dy(;d7blo ztpgs1>A=8(9T0;*S~e2_BS4}=E~ihjq(I@l#)j>I5%6vP^(s#o1rq^!mZXq)1PK?= zMHXo}xon+=>LTsB#uj%Z$H3c@iwhbc^aX5ubJFWi_Ar1@YeNE_BgI-%^DAEiu_=>o zWgH!XBZY?mpFR-A61ft=ql;Q0c49H+J)CA<`}S75@ctJ2c)@jv!*X0qF8Q2TLxX^f zYycS6cb-MJ%8@ld2*Z7Gh1&oj1_%Myw|o3fBy0?$sgi~L>GBvL5Ck9)9moMR*U8N1L4Rfc%7g0PXZbl@1_VR3lyCs?s*_-!Qe$0@?~81 zHn^_0U8s-v@ay|uwtBX^&_D=e%o2*1>bYX4r_~>Q+NV$5>UEAZz~yW2z}nU8AF=ofiU%2Utij;3ic|b;YhvH!PN#$!TLkB1n0Z>MD4en5Jor(uDU1Q30rpw%v=a^lXlI*)bZjR#-LHV?4UJi= zy2F~g60k*JGD|Hed(vHhsFgP4fBf;}QCE3h#wH{X3VANdI5~0lCzST+9PRDNN=vg2 z4GjUgqe`1Om(|r(Gzh_2dLF>##tYT3!#YL9#ow%~tN`Q&U^ReUP?waHST8jr0TFfz zcX1N;fh#*ZI{-NVYaFVrk2eII^<%z`mB@U)7WZOrs@t{(4-ZdFeVQ9cAkeFokO2Zy zDcY6*LQ=qgm6;8v?=>I(C3~w!+ef89{LaF(^~H$_y$pf7#oM4aXZf!%d!4|DmB#3wp&)=tH;j0 z<_Y$=d!zz?AM>Iw|KFUJxI5}KEa;|Q;5@$kKS#7-=6|s3ul8TRuE9b63(Ed)PQO`E ztbrI9e(~=8r$hg_t#diqAMWexyIK_VPuGCYbzBk>5BYA3d?5b+=ZMw{{>!EQeg6OR zwC;_<=y?c>d!5qtLY6YLw?>F- z<@&S8Rbsw!HAL8}S^d|}_zHfr53R3PM%4Pk#!jb_=?Q(;W&OF~d})^XzWOv4x5C{u zzape6tlzdDWSFTWBW#Aii*vP}(C83ExHFe`+>;W*4&N2reTwcfIJlGK@ukfuw^l$ZT{b*QK z_caadnZOD<=XB5dI9GSy@sb{!Tc4Gv*Tz>2DX{qhr8vrGu&gHDzOwVYvxIjpr7k5$ z*~c_*27%}p<4_F7zcqb)`vFS>(fZ9%)Q>lYH-=j;#e+l?!-?z*#{8-`2+pJodsSOi zidjk_5FTPEXdrA%F=4;=-!*}3@mT$1pZFe$%0zI9uMJ)9@%RWmp?59r4m9l?Pb-m! zG2Cj`+{*=@GD`RJd|bVdMpT*YVl+C47iWDT!nO)8M4dJ4+Jfh|>C|A3yC+G4P=8?O zZNX9#+u&^cRuuAco_fu{6%t@lVf1y&aI^tTNsj9U+thn^qS66*dVOppd@anim51U2 z)-)}?R0j(0SKe*u@HPIGQ) zd25sVbD8Dmx|BmW_T>rk5Zy+6+ITQrbX(4hs>i$>d#ib*lDc5*miBZcS01b1y_547 z`mde)$Xw|3rduI7F8EHbn#_gW-oTvAgB9J?Fo;7^r-c)ugmrt$_Z~W0(k2f`KJl}K zL-PU*>?Xo9O>IuDjD%0z(+$Mx5DwwX0=mcaNHTQ6^Sm*3-kMTHTzR3)weTMgzYDbD zPO%D7`F}?QfV{iEi|y#=cOhj!BtiX76vSr&dHcR#p2fTCPmsx|weq@)>*Y1iV-7^I zX0uit^jmM@UfIea0u_UQ%pCVRilJ_KNj*?=;r0)YVuFX(DdJ?&w7lbr6yox{@yk-s z95MLHk)TM}Kg&+G9?Lz6kjco+OBZ7*6bR0_HUjIzo9f37~d!gYfLeS(8OE+og`1Je{8E*nT!%SpfmK|N@G?Sj-psy zR_;0Rf;$bIr;rkP?y>8fs+6%2Hc|*da-g0?Pqf4`-0z2d+E|xV9ncGF`hY{Og}oNX zuBz){B;0*qVsU#OH|xA(^&Dx@D`REGv(Wy$G~kXPEl~OTmr7OYrjtWBqe?Zi4N2|y zG7C%t8BhB~aAf@ZL5r<&U%osRVP_On9`9U5p&cd5zE(S*C(N1UWRAH$(Y<5g-jk5Srx z4F_&QLv*K>b8$~?o@N$z&|!#m-;@%}NTHq_>BX-^W55L`g)<8<`x=lZeQq`s+2eK_ z%t&9TysjsDyO2rOPP(pRwy_+}Iw?MDK9+~>y`y!tKZvdFjHUl7L>F{spY|lQg09?0 znTSoyQ|VsrP2q#)Ln#bWL&vlclaQdRZLk>8PG~n;=S}@#W~;(+(DnvIBC(J8iv{&<&kmH*=V>r-+$RtHx-MFu%y89J_&DcTJyk@wp z%cITs7uu^p5|B>A7>I#{ziXkq_%j((2VQm5+I*px)aH6W;t1*<^k@ zryweDk)s?Zt{UO#KU`x1)l8yfBhpL1|ECrpuy?p-xrE~Lwdd^TU(qhH#afYU=K55` z-IJC3ED>W4_<=NMs zb(CJGko%{+7-4w2HQ2p_qm)+Ev{bnoOP+VgRbZ)Ld|#43zhbxVu=fna?bo&VFmAt0 zYq^D-yE}fy9C7g7v%=2m>GOrzPsO?D3Oj0cQz4K5)3Uvz3-N&S^@k|v&+9|`HnFKP zCkfs>^qY#c3|5tFv3`EuJW3>J!}cCJ@3eGRNb=z9Cx=5Og-z}CBDp`-eE7nxH^*xj z1zYl%AVQ^Gm3F=ZgxgEqyY$y4doEZxRk^ws$-EBR{EQ6$Ow#KbzZAo8IAev`H;Zy3 zfvQNmW?gSKDrD>4(bTIcWeYso7)1ZkuRBY^6I|?5ltb{Y%MKIRYl1t@w0t)ne$YL} zfuoD`Tu)!wkKY)U5@Gdu&kJ1Ak69+|3GCUDSPeVYztxDNYY>o57pd z*R^;W4)?W%lBY~W{&;p!ukvBKMiAbCGV5w7|8b4>+$|ejs9|VhLCwb$<{YkLoIe{) zz?H_zJNa|3O!Jy&(ps|NLuC@K)=0Dqdj%7~Qi}*!Q^M_cz~(jj<$i;fgJ*tS_R0(I z{>4XnjV@B1FNGD7GK?FBd(7JqyW7HNTQ#^Pgd9QVF#`G3Dut=UC^n@}`;6MQfrHM&zQm z3tL%k{{6bDu;u3(tjw^BM~>+B4a+e9SEC4Sr~IRj$3qf^ZKcZKJNePO6D1I^44&42 zac7OUMRxbx;wa5?0pnds?9jcAMqo?*B2i3MaQKIQ1d-2LR29cS!F(_cjTah9TAxZ+)2)qsV$FvD6=;i}{owzDmp zbv`eMITO=apb`DwFT`n+^HuVMBj<$If-WpN?T7qmVNJZ($H@U!J9GJ@*o_w?pC=D# zT5~AKN#htG7-X_c9hJ$JM-I!zo zd~;?(Dk6!bi$w1mSq=}Bw=g>e@`kTJu-<-A(`4qYncQpRIH)o z?m#Dj$(*p6KL-|?pi`_sO<;5@GGjyJC&4AJ%eHqUvM>=pjCSI0p#lBTY2W^B6C-9R|4Kp5Z9 z*wn;GaPU%+foD$!70T%{q}AH|Bv6jc0~u_#m7IN)+8r7!$F7jtUi|)9v1UzX?h1lz z!hL5Kl7`#$RF=aT_wVN=vL<=PhL;D%x8>tj7BC4c=N*V5)da&tuFP02=U^r0NiUU? z%v(CcYP|H11Wz*^JWXab^)2)!@PgLoiC@atPY2At zyaQ^%<|h*v(M)$%EL({Cy{V;tlHXN**SI)f`I^=n;t?}LS)DNu%4IRa`}G?FrS{OF z$!M%%V)dRNLiO|lKxX8cS~fZDRuwhoo=3ik&#l;D?T&Nx%>EPfH$9BbTgv%1V>VbB)Mc zOQiPVBTgKI`_mXn=(?>CM!wy_{}(aq3IYNH1CgQ-dI{stMEI}u5T@(aky{2qUC@{4$_FLCFN3JH zR2@}*{I^)Fhj~wra?PjJ4_R$~5y+1EG@u34Yc4EzO|o=f=!v0{ERFj8n)H{`!3U2; zZFzHmIN&Ou(%G);s{UJ>Wot{aEA>wt2886a*+0Dhdn&#@8+6m#)AQE8DJEu&OD(r{ ze!#hZ52`f0>d06HQ?-`9>wZqcG9#>V!C^L)TsA5g2ZWir>Fz@jKAiK10w+k{y4 zcgbB{6HvXea&RCI9_CE5R*=8IZ^QW07pU#kM740*%w>0i*%(E%$*Y$Orcd3kI5 z`>A%GK+1j^5GCfXw10X|^X^?Fkm&+eHQ&wUV(e@>RaqlD9ob6O+rf1t>RhLOsr$ph zTSK3K)YK}I(mEw0u!7PZMaeR`^!9HMQ-Tbo2?POYESH9RNi_+yk3j8>p`jt#fGW_A z?qfS@^^fh)!K!AgZ{);nc`Ij%9QytV&JFv%+1jf0{d+T9GF_vn^}lKetlX%h{k4|% z&l9gY`MW@w4nO}#k!&*!u^%KJWCuRav0r^@{%xo2&w$^*Vkq<41&(5>aGg4{(kp^2 ziV|hoLmqKbsu{wbx+dO1@^Ez4dxx8(8_vb1RMn3s2&!O=D2#u-Hje88zh|WMsR~us z8hRJE!+OIPxHO8Rhpkqz3aj+)GNyf?P_TS25nrF6NGPZnrW6P%JfJ5{n%>edC3M3f z@{&BBV8I^iIpIZ5CvCeLOsaD(dYF!qBR4diY;V7EipoixO>d8`E>+y}r-&?acYVgf z!mrixZqR*aDr#HV6lv|YsfTi_6qX8iNqMkvwmiL31f7T+w871*_u+AN3GczN;vJLF!TWf|N$b#Gn;LiS~po%2A86Ux& z)m?Vs@d9Jol^$FLvo?7c-N+8LTvOM&j*LG3b)Sv-Q8opuSzJx=)P+fF&O4n6NwVbz%1 zE~E*kC`IG$d>F!!Nt~v|S9w~>cz(k(wVqy{b)p5wrDOrQyR&ArZ+EvCxUPC0+I0zW z#nYG}oQWDX-y}yjQDzRq1K2n!Nnsbqbj}L`f@gghZxCzk4EM2*#*A8Uo)9Wfo&RxK z97LANisCk2w-A&$?~)}6D8xRXcmw0K~kvlu=kl-ax-yA zKb(tvKfhE;e<)e&jQUdcwe>^PSDmW@73j_U+)RwJf%3I&Yv(X2S<2F3D=53$dKK<{ zlj4N11{s0~MBrYCJ#$R&Y}R!XVAM6Nox>`#<2vxigTbKLQ{y$(varJ zO={~RFf`3JeK3uQ@`vr4A?r7D&KG8^_J53XM;=OQh>_uro|PK4zx7J9{Ga;^MZ~^g z%sw(%i*M9o7EcSNi$P0FD-q}WY(`^XL)zT+jP zqCU_b74>PEUslk(nc?Gg<_DAQjX3M$q$LYfc-yr0`dpj(yBkZp`dPUvw+?n(Ur}49 z+#(;rNhD7L43xYN%#5wnNfs~e3f9vl<`?nrcu$lc1eNTgEIn`7BHyUwn!sDf^)6{V zS@ulR3lR7hO@@2~5z(Ann#V=gPj`DQx44IWdx3S34JIk~FCzZN6i{%$& z-~x$aOMF&3sdDE@W7@kHeH&SAykpy~iixZhSY`x`gE5qM@}lMOdZ`$0)!q?D9^<(l z5`=#g_l-wmpUY`@t!n;B0@s+2B^^o1oG$8`Q~rmC+$Hwd3EHP`9@RENYq~H-xZZ# z9i*$#PdQ$FXGkyP~6c z`w`@Bh3vJ(pcNcOA;gfB`KPjI`W%Bkg_fOnAImh#_UjD7eY3h0Xz!D$Fn{a}a_V56 zEhyddR9AESvTMO3P%Opt?d>$wxGs(?LtV{558KWy>b>~BG zZTE2W)uI&!WXDKulhDm`W#dO7_gHf{37at<+WsdEfT)S}bnz8SO>Q`u)=pD#r%2Nw6eA+{|{X|`*l3ncH zbM1M1YdZ$_C~5I|EFt*uS9Q1_SfbUFXl!^DY})&kZD>jN*ymc>oAHNJ$j*+oHiB17 zzO)vjAU3CHG#S$R2u%C{M|p?4UofFc`E-Brdtp8&6F&^~z$c2qTlG;E(+5f`Es=H?ekJ?|fJO zioA5w)vgzQ*(?d)EO&Gp<_^w8MU5Za+9JyTv#N0Dbs&UcMzLObfP5df|F~=8-CNq~tEJ@1-Kx z9BDF^@NETGe5wScXUd^ny6Uot^+B+pMOV5&OApIsZ4-aSe|*~-`pru61;@ZrVw{F%cd zb6i(2+gWf!y4K&>B1ckogsLI}O%szBsc|9w&aoy>AvUTIj$$XIKfWJPrMP){9b1f> z1EivBQ-M{dREv?b_Y?;ov8eTj`Q@)p#EXy{6`ZUEzoX8^Z*-huC%MkZdQnekU1XOh zgfx!rQ0Q?dRGFZTIuCU7ChDyZx=WpUndmcYgmMu4Q9cdoi2rK*XMcHhHIU7lxW66c zRJ(yL=}Ja@dLx_gu!{&L=0Mj(Ug@k3-=B~&dU#925dHHT?yQC` zDcZ=R9 zq0c_|4MmE_>1CwBnxLQ=1A-Rm zi#xMU=u*?~V*A)6mgc^7X;OXDlxrT^rJ1D2!?Q%)--<8);q;!8RJ@Ea0vsI2C^0*+D%Io%Tc-6^{I`*Io;;(mXunCHMo}HhHYU!jbr%~Lsl<%j) z<0#3I6-)mjA8w-Uz&Wfy2ifgaA*Ohq!!3_G#qAV&hQBAmK>+z+c^c1 zF?;5owWc>(g^zn&X*ClsCN&E+8($RNt$pXM+4)fU!T!>mz8-@di^xG*81>5bqawn5T+Ci1B>F9E2Umi>5YlUP5qPIH z_$w#^nPUN$Xg5Wiu4B8zdiogOMM78buLD_$81xB>jQ-s2vvl_7-Sejh#<_VL4_|D< z6sh>|1XFe1c{7-vX2I6Hr`O`ox~4Ox(=-M)^p>>?$#qC$p|y+Yts!MO)_mSJJ`2-D zghv3bycwD!cW`3YKQ9ST)WF-AjWF5CEq9MBd$Fh96Hr4}xzgU?ZmY0JHz(HuhMqb@ z(Ltt%`mOct^Ak`S9VuB}*aM7?`kZxpsT7?}F1u!rj1tLyJjxr1z) zPCfRoil5T6Ak16i-_?kZp-`D_>4Lt99qwJ^^!?GeXzGX6QW*EA=E0AW3v=ndB#Umb zF6}d;Uk{3q)I9b}hLSuU;@DF9+1Ix)F?#2I6;<*kl@!|lC$Nv6lcbg1-Q+T}Vu`(FN`FAHjk##e?&A+uhI;-2Jv?w?zv zmC{A3Uy>g^>uNV|o8CpFu!w`#=914Y*Bfr+FlSx%oIqGB7=j1~*MoL$`J!h+^A`7H z4IFGnXM&UNNf~bxvWKnke3kF6H^_e4;Cyo}4VNoYp9XFDKx*iR1FFpI9Tg44*zk`( zB^+P`Me#4I34Lw$+C75}dzQa6yC!sVA{h#!-qpMFpGxnTJt>@VY~(e~s@h7QiBEnX zztB}@13i8?@;p0Ftcf9My60~eEh~dZO^7dYgl{f9;oVqzHkIGWgx3Ewi#Q9HH5tC5 z$24_IHX|LeFq9f)I`Zp6_V;mVkd!pKFeDGVWaTW0Yl9Fg!!fU$k!5s;L=&=8$If2a z;*khiyicy8C#;z+HsiHFtXKSdccr6jp?(d=+j~KIyXCJ#an{`F3bG~*GhZx*N>bTg z&IiDt3P9Yn_@PdjfjMcsk%f?)@l$myqNfL%40lV_b8N3GZvRHuS$n@xl0)sb-Rp&v z$mN9^Nt|!h-$RC*2HUp#qpHaD+N-JkiC<1fl*Fl)Ex*w(`VbX~Ngvl3c43A7JfP`N zU%$aJ`!KP;0v#zVMaV1PNjQ0}z_*fdXlLsze9*3=<_Bm4lb(#%g~5kqpIrw)S+W8x zVS{DmvEG9+8?5CSz2=`jW0Aidbb22k`1Y@O^WVM8Q)9h|$xx5MQn8RGcqw*_ZES0> zn|k2n%}+EobbVLmKmx>fzCG<7DqR3jowfNh$+;us?cl4sT!mlx`WDZI08K=y<) zUM0*WCP`-H9b1?Q+`33O?i)YZiZIEa%;sWd$e?jL6}+r{%5l@~fPq<7Zq=&rjHKr{ zb3e-_Pc-Rd>+lI(6EdqRc}wjH_9*L$qA<}Jg{&c3$TMcoYnxpL{$_7fMKAVf57M74 zrI#mnFweD<(cnV+q81-_n4B!EMZ!XMg`1UC#w(&1qa z{);p_1(a?AZ$ilDuel&znsV@?!Ilw<42`GD8m$S!G_iTO8iEj##ErgBl8<#D`pU^GDyi5f9U z`j&wf-As`L_Se>^rff#eYPtt^L@~leUEi6zrCHV#jWWjWxDV-E;Qo~}$`7V(@JiRE z(N7aQ@RlW*LT`4=BI#G0XP@~4Un{-rL@_j)s4~)V#^3V;Npytv_#Iejp@%a5RLXwF zz>GtM^MUHe*YD{v!{>ESkf`eM}&hkuGOlvy6w z&OeMx8*jTBohV8Tzl2t)Uo1$Kq&UzP4NMOT%1p9lh_$M=(CyAyb27bL>0*dvQ$Wk6(2SL&cbnjaD-F z*%aDv^P*}U$eJ<5ZDhP&-^ptJ2WIl6fV zEANwBS@PJWgWP=aeU}1l4P>9wBc0^}AFGIPP^7YBBRFgU}eo!SZa}Psj|G*T8{E5_kLID-fy42fpGslM~Bt=f^8mCjPp`&{|zIG z?$2WsBF6ayHOvsC3`wDItM0|{IXO+TxhDLEw(eJb1@2|75Td z6S*NzY)MI;St-+m!XBQ&&zNVco$LLES@$m9Fx^by*Mcwcc&}Tu6CznC86%n9Q507k z=3OU5ud4#$}n*m1loPN0aB5cpOJdSRsLd8`$+a_0Jgy^zuPJjQH3B2cXVQ<PO_Rd|ls(gyku z*%;NhLzpkL67>n2u`QyLXC=n9sah3+ix3H>g@^r~`0}3j?8WG*z6i32e|#=MtC{W* z776DzOHOKND4Ns3?1b0M3%+fj{5v=@Qh2K=Hufb@zuPWGjO$GHC7~(2Gz0n!b!)+w z&O5)-2jQtA?UNPw$|OcELOT z$**92$+9gcOE}-*5whKa-gj4emW;l8TekJ@gUWSlUueIB0@^)4ykxT0%Vih&%% zLg^LEsT5cQaLjwA?L9;Fbz;---h<#CsOo+^woC62?!>0TON3Rk7F-aq@^b$31dvF;(ix0ilFJHstd$m46vFk|M`um1g=fthQM z26LJ3vyCWQ&1d_x=~hFxE=D$LrS#^46M{IIWdW%_(!%BNgpy{x{z zo|~uVpC5gI1&4&Zx3MYC8>4znEH3@xkaJrwEl_sBWaO20g$I9xivfTVn804QuV0@A zzXo0Y9n~BX02_9)v)=$)rxf!<#o23TGW~r}Z@Gd2kTK1&vpw5Ofhtp3qNXVX0b5{z zO&lX*1%DTpI=fX>RptNsB?<;t<|)5=&9i66w)UFt<^~mm5^+$1@P5O~%82^B!9_G9 z!>5&xUU>|M)9~rncREzh+W6qP!~J4soAOu@93$4ElsRbE!jq@nH5D}V2&#qnadC0! z3jY6*QgCp7I}YN!`M7{{c&?t$Q$1(-6gYv$nbS6G{fFftnYyKA>X6jOw<87hDsxBF8BwdC-3l!OSe4i3TY2bi&->!(Z`CYRvnSnu~4OGV?-591-UDHuJU->xub zsF(a#^sis%IoBjc8&}oZKt5Z`p6C%1nsF%L<V5HEvY@1R=c9D&9Wq5JpMw`p38* z4UUglD^RqSffl8_6pH*=)G!5J@+?YZAm!}G2N?&qfy;-RS97b)L1ZQW=Tj1x`t}XC zivntHYT}aBMR$rH_+j^nL>u;?EXux= z0Cr1Iv$FoL_%ALlfSt4>dlzN(ZflCKPaEaJh#w9FC*4l7$GQQl!?~SJd7EB0bB zoF-gjT%PHE%bgcxHPLT*64D0Jy8oDmi-Y4kmZ#{+kNKZv!Ji60a&u7v0s??-x5rKl z;_E0)@AQ@R3VvW!g%KhiY*S=CJMhcjO1D!Rr9{|;-n@uEJ#`^R*Pceg=5vQ%Mh)ir?{5d`V(7D?&uPLU1)K|(?#q&uZkKtejC8>Hh^q>+;D z?(XKjo9|cu@4xr5-sKWloU_k9vu9?{%zj4yLtg3g{^P5w8CT-^y*$Q&Wdw>%SR^RS zV7VJ^hAQRF9l`^ZwA0Pg*WLkBP!BgTV`^O{<>y!^%st(DbxZ$EBL|b=vN^WoJ?kwj zv71NUXep?r4{0nPMgNag_(yP_&9n#X=ca2t#6zYcyQYN;Xw5Mm_dH^=fLww9HC$cI z`u^{=KN2I&zt|qD!GS_Aso`?o^^9>r&mU|-E~lOvr}mMK?^{@&O8hs|i8lOA2;2Y_ zlHoS@>YHWDLWRYg_c)W3_czb+efd|1N~viKn^-emu85^B`}kv5kW42O@>5APs1TMijO z%M#M{yLf%c-r|+dSkvObxOO+!A?FcEG^ykrx*kN{*-D(&5yh{xJ}mzs_=}5wBE$RQ z;tEfRU=;$d?T3lNH9Rp-ZVd|=+G_Ua+6CtE7g!SLJCU)& zT?|L%L-%W$e{Q|<`{`}J`@L1f(ZXm2PLOL~1w{+Ta63M3;-y&t4JT3e=3_)P?r))p zh!_-ze!BfEh)gU;c3XFK$8q?qM=Mkc?iQD?kx!ip2G2|J4}VrK(#=&qn1>>@p%aBY zH*4|{U6yb$G=cA*Z0XPmBJCCN<=dJ`bMkF2`!?cBb!Ic&m-R=LxrVQF_{B3Yp;Ge9 zxQH3Ug%d~RzUC2!I7fw5@+SzC&1<~8$Wv61Jn@$!Akz+;*LQ&}vmC3%91hMInDfuE z=56%qg`H_O@gqfe90D*_jRX!o z*7kd72FdAaYYxl%;dVs(Y<`JLB;dF4%2Jd?KgVeqoo%4JiqpzOeB7L2D;e=fg{5C? z^?0Bm=VHHf+xaQpU4h#0#kH6})`RavFjlyCyVW~G8P~Yy%W3Z(Q3(m7An84gw7!BZ zW1c{P!H{rU3gp{g@-2P5K{c|_6<=2I{yP#5;R6k*%)$4jUX|lRRF{)v#F=Inw%kNW z8!5VHC>A3Ov^(+o^n>gqCvh#L0aqHPaO80!+p-%<)EmgRMRbFq*Ucw0xT{RxgZo;r(P!9^nq#OkECd52;(Rw!?O?;KU5G?N z$Cvw{a{&i&N(+1N?uM`#WS5-6?$&fj^DU1Dpd=B`t)axFM9~4C5|uALZ>hL#XnC|O z2w6U>fK7USaAT%Kb-bdPaab212o5qCaN`oTIyVL&IUQjPRyZBD3m3Y{%+r~JF#TO_O`U6 z&$9r#YH6io-Zq0C=f|Em@iAE={IZ*j#O}c%l@YB_1U`8K+|X$K#oKPJmv_3DNea>< z)ta7MD#19BhP_eGD~xK%=&{MZsX3qe77A@X(hHZ-cfm=>+l+=>z4K^hCm$OetV&~H z_~^QADd;YNzHxpN%yL(SGQHinH1)aaz1i67f&49deA9uH&}W&5+mKtNUi;*aiDbKN z!=g`@VxA0nxNvBCUZb`^cL~(B$CrjWb1CkkZ1CW-I5;x-njbR8dqpvgst~x257&uI zLk0Obzp-rW(B@*+opj?LMumjr&sW3FGhdU^-d4UpHHM-?2{0#|bc{G3h0BT!w>Z8$ z`=R+M2<`j4Xb8IQKcCHlynMVD9eLk(K8$?qT+|`D10X-~zv?%7~Ll9Yq zoPIow%ZO}wjp$Le-6&w6~Md#HSAxKeukh}`>7Kygw>yJRB( zU@IsN2JsUwf3DLB=Mhb-eu}rSI~cM*E*C==jYDu8A7?Gt$Dj_ran9`)(W+B03OpS#*zfKN6)H?glW3z6!j-X_?u052gF?4aXx3`g= z(~;D~k)V0NMdrPPtX}=7;K}=~$Zpvp=5I;3jJj?&@1zC1An&*r5@v@F-k+f~oeY$A z@fk`cgj#=U36t1tED%`A z1Hmvs7Ug$`^Kpz&J=fd4R!xzS&CSTtP6U?XPJ}|bla>`Eg)|R7>Di-a?8|G5clFZ) z-UcorC&BnC)l_9UTuQ{$u@4%?ygA}Bb5;B>7J^{%AjF&-anW#&$A&xe*N}M0t1B9U z0i22UL~B_Tjg04K3LX&aHSJCv<=n9pffNQ(Ayq#EAy=5`ce?YOS>n?RBNlcVt+F#G zwTx^{W(-DvaI=u-m!Xw*+D`S|eDIbnw>tL`sx(Eb(!=k*BsW)YDCAT=%%79pW|u@& zpJ=U+LXdB<8z?1ESH4#Cmb4#1WJq!)8dYA}<4Qlx`GM}rdYNb3I&aC_~N<)Cw8x9H%ilfMhsZkSJ7jxw<YV8H}OdbdwPg~H#@ zm5AXWVfy8NyKK=c?t7F-%}C_r?K|GW#9IVy_Rk%@e!mxm|4BI*1_ueD1+G6<#ekbE z^&F)Vb^74edEdy}7BwbMD5~+AJ_E-W!nq(48^!@L!0?h{{!1w^*a~=$W7=V>Bt%$a z4C1I?GSSEew|y~NTZ%50FNRt__TqKGa`fD9?d53`bvQB%w?8Kql@N~acReVM$im&Q zoo_tgN$rT1;O@B;%eEbDw;f!E1d)eY?mzqw6L96ZNAo-X|DPM2jqqL^BbuQaLRs-z z8eOti`NZa_#SP}^uiku@Eja=-GRt$`c0a^s^c&RjP7z1zP;R76P3&iyqU;-k=Gpxg zCA=X$rVms$Z~M32&g;7oC}8<#Fb7+|kRAD^G^-QAy>xKMo_O16`0W6C%CGk5YB}oT zlqVfS0tN;-@=l+aqT=>DfwR2x#(lT7Zp=!`8Y{ zqs+HF(|Slr?_h<7Z^v5|RV2k5o(}M9*6mc`G-pZeH4+ zVNdWELl?CaHoXb0)32$pyl2s^L|MYO$q_TT6l$?u1KeJ5UT6xsdkdlwW)BItV~DrM z!whd?(+$&qbi7=yQ*woA=z11Th5`HCD6l&-sQJvEmGos~oO|a|TCeGnW1o(t2s|}< zdN5{E_Jz~tReZ~pcc$ogk*T5LH%E{2q6&xvf^dC&(<#Ct z?|sn)->eWvfkS@c;=7R=7jKWocDtiIJ!^57A=BRq z4T5JxraY-5shxF3wbbs!s?9@!y_s~vj0@-Lmd+5~LrY=@dDI_MUX;B% zNUmx5fIp`l@ng)o&S8ovk^LD?gw8S)X<&2LgM|6Ha=}8 za#LdGHqF-X*b3XT#J612bHBs3ZJ%{sXn6eLW2W7b7xJBXp_RBxdPUt@VDkIfoaPL^ zD{TE_KH_=OcbA{E9d*>lMzTw@kV=@KTt7-%^Km$kytE@9p4n0V-iOBwiK+OWF{_>Q z?yR1$nc?3)5UuqlFHCdK)nkN?3Z;sOz-++aT*xj$N+L;GKPLpozlO_)F^7SJK@>iA za~{kP^MraBNJ+t~s;bT;lpw$5B=QGrynz99qKHQp)kF+&NRK`|;7{F4NzgB=6)`}- zCLsxD)@!)2^}e~eG2l)Hhv75Ew}(bX;7T-+!`cPhBk}R^pYZcvrJRFHzCVDya(48v zcq4L4pGNLQf@}jeBv%Wt_W#}yImo5v4gWs)4Ffo(l79obnWROY19=6|7lY2E%aw;D zm`$tm$_cx-z1JNRE#?G=KE?Isk=Bw1b6U)#$0wV^-UTHvrRj~+Kc&N~5_b`tl(%u@ zJqp#Q(H%a%Iv(3%uENZoX7gS=?-MN62-YKm%T=Gv}t}*lQIJ5X%!*Hg!a%Gz&!(+p6=$sM9ALQ*y33wEd1A+)BBYz?! zL<(RfAaX4Y_yd5lu}kg&cnRWf-_qaLu&d=M-X21pK7FdMd*Sqn0uf*`B+K-h2eW0+ z0Z@RFn;Y)|+R1q3%V+t;#geYBe39fLk$!%#I5;>N_4V3;KYv?)5FL*7!pGfZxT$R> zYqqKhy@Sb^PKfGRPZt{ri^PY*e?L4Eye*C@8cA3>~ zMHX-ldhPn}L&1dXa1wzi4+f9y0iwy-Xtu$Tj)f)RDT0}QV4x8oFcJv4akjR$&e)-; z;0%@M@;Gddq1MjWA)oEfN2PmRJ$7}if%0|}6I8t2dy%-Y$;s?Pzi%}od}#IGxcLaZ zER2^j-C@S$#!&?+(^c)S_4Vu{zH?k?0;BptM2t6bI?E01)2EA}jHRMZPTcAYaWeLD zva+J!3 z_rvAR3d>1$%jvO}*~+F=-y1;GIyaAxRoIT@B!y2= zfO2N;&6C9Vxcwygs^)02)!W5|*ShIYI-G>>$WQmOE{OthW4e~*>TEY`Ys&)QPB0MA zNInXotKis9nj|a+5?lL7XGh=Om_2{-q8$LLLT|6nmw%v(NXp5D$Q=LWFk~T{!5;*O z2Xv;sJT04#MWE|1*_4lgE6_$*&V=2lSr@ffpDPUX`@rkjX?1Tcw$hp@;4Yv!d62p8 zJ?eBYWjnvD*Dqf!zAjK-ntE5(zS13QqSrj954am-o44sHDdB`1rWx1^s904x)>02Eggl*sRI44wI>wTf?Q7VN{4NZjq@T5GZ6F{ZueT^3UI{oI`EuUyWf zNwZ+xJuRz676Mx#0z5pyVPQX>LNnwJ}aA%yB{^TCpR~;tBG{KNxzf{eubv91jz=sY83DEPNP*5yHxyO9` z2;&Vsv-vbs_FCj;G7mc7C?YAQ@DA36)U%gv<$kSk<&z#3Lj!IYPmpSlh6|MpGvUJ)@fqit?(cK!S*DK8(| zcMFg5=!0eQWN23K^oaYwlA7H`pLDA3Uqw6qA+u-6Ki8o z@$DtGyj;A0Ht$jl#Ey3gb+5{<|T7k?a7gnboYH1P2eV zh7td7`1`1d1(%^s7K=H?!`Ws)&o7t-w`FoEFK!&~PD-T>tfO!+8+4prTsasDZI9@=J;f`Gb^e0~c+HX4v_>H#y|)iV=XLd;wB2 zC=wZ{?4O@G4#6uE%Nwre0)h75Uv}sZea7P~ZiX#S3YNSxDHa@ah#o1?F2P1Jb;eHu zAQhWoD_o;3P3&WG&l602uS=6C{99--c?tsE%U9cFwZvMwx?$^S?r23?W$~1Z``JV02MZt2#WR&BB5rqU=kH%VCl62W$H{sH z8f?60FMR-Av}e&c5REi6jHP8FLi=x)Bf60grYT3=IL;grxG7g82|u?*!Fa3ds!@~< zh;o7_gA(hOAE)ChE7`#$A*`;hvYV`V0)|;+em-5JEg;!_&yor=sCKJi90fR1z{j(l zwyNdB0eH5qbAlItY5|;$pm(?VB_)pbe|-9t$9sF97OZ2at6fpk8kqS8wzuuqtyUV+ z(~@|QeW1beIEYkx)rlAj(a)yFP!{w8km~&q9m)r)wMs4^fWeD_hiA%>3$juyEf+?4 z%vWZnrbagxhfd3AA*X`D{F>lcxiu^2OdFv?Yp z*wz1aW!9MgO(CZtXJh_>Yo7)x0V@oUml{IAH#+EHEX$~@RLw1J8fL1UF*?`61g<4A zKC}O%WjsA&rw8C4O*A+bNmKxcnz)@^WirPf@u8yPQ$>OYT|Ri9bEwHm;KF3^CJ`jA2K1giHK{!fK&ncUqZTHx5jt^N@W1*~mf5|(_%5A=LB-O{x2gyS8Fnp9Ua&yfSP4OI)tkGluJtiC z4Fx6TchjHXXkUfbt=nhzBYnVPt|G278z8#}O|rik7IteU1G8Ex;n@yIpIsWzKU*(J z(r{d+U8gja{6i=XgCz#eVEqLNKMXM({9U?ufEkVju=)p)xf^@yM}3~Dv()0iVip#a zRuUR>Cm#ZAveQ+B1mHpX0;bqxI5@rdvdN`le7*?GuMxo75%5^2%uWD@;@*gVqGbF# zcQLVW8KM{}85oE3pEl^Vqo0D0oNRQlV#%voSUNn(9elVQd`S`Nk z8aImE$E7=z0A?}9-vEY47Gp?tWn4n^AxsHH28n10Me9Q5OmjphGBlP}spfJYXbONm zi=`*t#|0ab#UDY_;ebg9Anx@FgnD&XS8f1LNTl`Jv$~Jh+uIv3aY=}|yROM!{Lx3` zT2740X%lFxA-l}P11eT}zRK=MNvY}XQq0x@3CQc#jBu8?IN(>b-6QosrUm@JN}(^% z!I8kj$eh6*e1GSFA0@0R|Kf#|j!sfvVqY>(c4ns7{WJmMZKi60Wd|^^z*o4gy#g-| zY?F37P{{;e23r1=wP25Q8D!|0X8FT!03Z9NtL++})Py1C)6dZY1#!Oji1m(i5O zf2DwGeyyU_8}OF_1r3aDrd}=AvPoA{Gk$h-54ZvWqZV=WPsXRJ%+QmPl6E>wOig<} z{t4*iJIG18*r|; zp?WdYHJ5xpqW#Xle?TzlYV!nG`vA-*(i7pEQ`L-;Dkqb#xqsJps37O4K922%%lT!T zLtpv*4ZB#5YW1Z}iokxm>#<(VT4}Rj%@RZ=ii{iD+|)FadB&3J+uZ7HZ_m-w+iTL( zAp{(~osN62iJxER?{s&ij#zLx@Pln;#xZMh-#U}H-Ch&^dY|)TQsdc??4k%A9U>bW z8@F*JI1Z)8J;(Iy84AFugYU^j&_J@ar%Q;$bMVKXQi$k%WF#a%{W2M2<0sye`%=I# zIPjAa5ffwL;Eb4}toL8PpYup~Vn41=c4-yV{KE6Wz1|B{XnL)#{%JN5*jhq%TL>`U znV=~GxAEVSoGpT^tSqpb&K$MS4nDyGdvAug9&4dc`*l5Do0i_8`giD2ICrGij4lo{^8?JKGlsqx)K`_`Ws|F3JID&Phssx@$C(JW@#xJURXOQ z;ed&ghmVsF$jG2bwgtk}3WO=LU(em;%R!lH!A=Kn<*#NAWrkfP6`6Xi$Q!%N?3)JP z|4d|l)v!gia?z-)yu6=y=79she>@rq@bvF@>gwtvuFJg6a{T4hWlfpzIKPKopFU*n59sLN6u4k2QffPhvZ$!2U`S!QHMjJYl7eyl zxF6R~J$KhH2m7*zW6`KS0w4Mv%Kj@MBwO3t-~j%J;S|0zClFE4V#Xm^O13}se5vV@ zSr>?0RG%qFF<9Xe6I= z)FiaP=R6k%p2M;a+}>(&epD#JBkoDS9_aW0q*X+wG#eO#K!txPuml;MTd-GYWZPrD zA&!vt0BC<^QjRB29s>Jqx0wj+gp)D9{Gtdu`!8z+6O)|3HwM#j$qXd9x3_n<<4bOC z0~bQoWH_MrH##VM_`Yz|3VFgf6B`?==ccG+yJCor{Ktfj#D57_%P-pTyGo+CuN+&k z1tP7cqGHHHBP=|ZlT3KQAcF)wJUm1@?L2pi6#PcV$QVnB{aXnhaV-ux60o!3YyUtK z$2NfUabl{-tVQ474fyPmwbrw5x;h%;5)!6ot#omu0Ft@uQ?xr>oP1*d^ytWrlbgHe z(;or6Boc#J8t$<@jGG>6hN{qMm@4Iglrko>4On8bs9#Sm%1TS6B_;j)>b!2R3;~L{ zRKAZy47kL~8}`H^3b$Bcqo8Y* zhx~s1FSVZc@J0kD@+U;l25Zvc;Bk(@XBn6^^maHRM4en9_y9X^3|gsh8$UuP3kTkx z_`L-K#9^RIu0f~=;O_N-K`DC;@p3W-yR0`|e=6Gek>aNO$?$P0`Ib4+)^jp%{BLjJ z<@9sQ)e{@)T4Do)*|G2PoJBb|UkP(xK8i~xB)p9h%lumBVHwbKYTS?nBnRfbxuY9! z+krj0pLRaggT2WrS`btO-w6T7&49-gIJ*B;O{5XtAJJ|r(G51*W;d`_8%7~3&D1&M zNovw={HRA6UrvUt>94MlqHym>nU+8ohaBtvwrjp$d$KvIh8u&K>~EIce0_bv*7hr5 z_Nav|=<)HH{kLFbMbdF*kx6u{XF~ID5fQD=9QapE``#K;gfM`(5Lt%u%jlWquX)1< zbl~byBz%57J@SpV3HojYvvd2TvlYgj%4%xHJl258x~J5E3Y;Mm=SX{ddjS*jKew=J z-e_ZZ% zVooPw1i6Kd{?rf(S=nvJp5eDHDZuFf5DiDVJ+S%NKPkGawVAi(k;ajFW#7jI=7?Wg z8|0Fzi~0z_2Cu0}@b4T^@d4pI(sHNM!25D85E)kXQ%rnZ6{d8c7yq?gUuvT^Y<5P} z=Jw(B{-he`lgO5KQ5f&2i{o-)hq^rw)cdnWEUP&x> zM3RR6qh{p>(&{` z59Z4$|K=uVlOK38m~x|bq@bnyFc^qp*YgqYn^fymSkLk|+REo(fk0pw3h)lW)-PKr zU6|V#(11Y@;E5Vor~iD7Ou`3n8!kM(X5IMJjQIH733yAexyKmM6kXmCk;d9QQki3?}w*%z_#mG#X}{M06%^b`}N zj*~`6FC6Gf={-qCMgw+*(JvnX*K&LuuKHRq!qInESd*EI@HUJGXDU(d6TZ6*Fa5!)&>ujJ0hwGX%S)XckBRsFJ3m@`zh5PaKrsTfo?LJX%*pX z@7rnx!arIr^*M&m#aE;@8m-I=Wx>cjrNGIJLpz&mpMJAcg-B$MXAFZ8aZm9cct zl6$#oK-iv6GgEmXgf>kSIPb|-M~WI68W)%A>7=2gpi2(Ti=>sc@Dyx9!Vo~D#>q0j z&K@5e^Z^Lqo=<-5HJX`j)4cBB=0_x z$*X6yEo8StQ(YZ2`D$l> z^Cuj%LNpXI+LhLXtt}CjF-9Pis+dPjw3TUIGfyd@nm?4nEgu*z%iJv_pQ$`mM!TKN z$+`u0xhteig$L*`mzS3p8`)7Q{H8}M-7dGM<3mMSEIcNwiQ(ZW+5><$zrP3q4rrZ9 z$=s+Q5CAejUx}-($U&sNk89kZKZOqwy>?#YMcKIV-b{UdnGIBN@6QyaQ6OT)G$a_E zaMf>&t@}#^$l5V!^w|b2B@K6os^-rmqjZaBmQ!&JSM;R{3|n}AL%!E?U;%-p{d1xOhgWhJw}(*>-fyVEjn!&d*tm1*M!tOob@EGn|UNEG(Gx}Vs(y@u@! z&Bp!5T2W*|2S>du%OGwNAa4Q<8?mTf0wFJOprT^h?vG}&;|ciXw|5)Lc2r0ztH5HP zzt3AAD6WMXQ1RV5Wt4@Wp$*tp$-VfG`e~VLw4ho&W zfxbJb{MIX7(Lkbswime9(>wE!`Nc7@evo6j&!bdURe|0g3rB~fU^xZ=sRM@Xqzdb^@Mud3MC?zs(YFp zT}>%FL8?_@bIk#Z92R)BLL;N2hErePDv|=x4@4pKqm&5DsHwrbkJo|iud=eTPvE0# zrQ0biEG)m169uc`1q{iVw8x82oTB!AFzD^AwfiNl;Qn**84;UIe8SA2GG?_ zZ_p9=%tk|5QeC~hjTyH6_Z=WyfJ7YbR1E;}YArC1lYhz#f{w2|4%=aTz`i&|*pnzv zF;)D<3skV9K5-KQ_7cbd2GEosc|uK1O#uS*L@%zrXoi7K2KfJnAa-Fj{wKX4*+L~) zgZG4HHS^>p4%)t4Kxs2@7aa zVTK}KyZ&q75d+dc;eA&*<0zrElhC88$9_J*G*0RsudJ^d1LRwNX{pn;>wS9G`|da$ zbj<1mEvxpOqOkGtgTM$u?~Ja1$<%FN^zU^M10&M<_gmaCavM?ofa z8Ny2JnI*Wcrhb}d5W0axXSr|l7VUWeJ=Md)LJ4*g=~65*wXl!xuS>6H!Nmr%a-Q4 zMi@@Sg#=RaQXU>nGDmdJp0$DgOVP>6+tJ5h)BMkuYn2a$&{dS6e}n#0-o)h4q)=t0 zyp?0PFwD@aVb&m;$p|ZX@~A<*^g0oGyT>x_*E|*v4cV?0eh?=dcejcRR;qLYHyE&h zP482^AZFR&^Wb5IGTocL9~RYAAl(j9fTD_uk>l0l6*?sob+%8uu6KFD3D{skhnt*d z5Eu}qny7w)hzL0_b)aoX>qklnR*#GKC)?xb@;UCzN}xUi{1Qld2S{A0sHp*dLR}1K zxdfUrmfv^A>gX^!*%$)N)(C(_qC_m+w9gseRspIJ=qv*es5Q5?cIvMCqnK0W6k_m( zLb!Of2|lz6b+c}$rzv*RhR~3HLS1D==$s0Vg?rF9%PggQ#{-tZZroTAoSy+?VGj5z zu_|-$OLHBoakfgro95=`pxx8QSp=4x4zArcdse7;uC23GXMMb5frx&}g+DPH`^}Ie? z1S`4xu9fgVk6v=d=qOcP=k~rZ#?@BtzO&X$ZizI23>2p{({7h*Z>2B&3xwpN@_WJCMId4T0l{7pH8sbHS(*HEm4A)px%#|e2f*k zd?Q|Gx@E+t78^+*R?u|~Dqa5!WyP4vXq(mO7ugB9E5V)EBc;6MS?b3F_EzX>sy#a<4q+o==}WJZ$G+D445}9-s-%uoK6b7Y}!RR6(|Zq(8g)qd_n)6SsaT zlgxY$ZP*f3%3I?2G-b@%aZ*W~*PDn4iP2m5WjlZkm+{U9LF-xz{=q`Wc*)-|9~IRR zQ1}mk;_(X|o|zffHky4)!A`224{UiO^$(oUhlQcAKp7r~1Q$t|yTs1&hG|QBzVJaI z1Z@WI{??H+rL=&mI9EAd4IEUtrNXr{48wrJGin9Q*`1E91|@^Vd1QxP{#E5ysoaPk z>v(=l{(AtbcvY}6XHWkl;Lj}e=}ca*{ah^g2p7=DYwW<1C=r`cEI_YjdSaG|`64*H ztcsXc8-7HiVs$kAaQbi6Mpv(FjL(up#lAk}Yrel~k-+Avd0_|&OipX&i7S4c5g)`y+0Yj&tY`Fjy# zM&sv~AhpJ8XirqMbZSd^+i`vW@*%6`r*#T7;$Y5e1_|jCGrcPL9ifB=Fr1?Z-`=cP zKx_UvX&|!s`}`1iM>KsAL^wMN(wm2fhWf;K2No1pFW5VQf z(NH^9zAtfBX}?*%aqU-A=jt>p zSGz!f(W_+}B)KE>Yp0XYOa)qBN=ND1DiJ0lH>T*;YLN%|?|h(8Kc?fu3-C*o8|1ol zqvK$j18UIlTq8qdY9}Hv(AM_1H98r&jJk8Mp_5U@*c{ZA20}#r8kHfC2@LuAh z{rIgi^3*alW8iYm2*M9m7ffC3ao^o^Rpb05&S~rwI1;Z^t_N zhwk25PZY=W*Cd2r;QxLCa0Ly;qVGJieZ5XJM$RyYY;)}cBQuWmc(}99k(DeP!^`C( zXwMpkcT8}&H~dGfFfA1AaHTZaNK6D0R<=|`n>z}$p5?v$7`?NmyuBb>@kvH8`ahHN z&)^J$#ADMW1Yosm%k@IOKFspNRYn#>}C z8cSEwDsv-n^%?EoPZ!u%h~+quj*UQM!2D@tIYJ?wOv13_O6!@{L>*6mP(l{on!1=e z4^4K}XX)=}dR)naWtc9C4tD-IEt)iddJ6m09XG;pEGwGIoMH^kSdhxPcHIDEcv=#P?y=F?b+fnzN+N%kdMXsfweAI&l-v`TC0@nZhxwe=6} zVQYUhqqb9es7QLAL&w<}IuNb?W}a`#3&tlmQz@cjCPM<3Q^Rq4|Ks!((y!PV+qQD9 z7xovs^#6VU+u_PYM-ma`sC)^>3Z0QHG#Oq*E&J{z|bYBMoRZrWA1pML!JywuTup{=(!H-mX=CR8YZqw z3#wOkE|mnGw5Pc;Yu;Ywd~}u0k@0JPQhSAn8ucN| zUrj@4jhOT(FWVz{V$|p6RVR5gqd!KR6c+Tq50TaJT<&DS+{Ke!O|M7*htfaa2{-Mc zY~#7~e=p)+7qju>Zj{syL!1|-y5Zr!L=ap^vOW6Wvk!i0`> z1eFI}-QC|!t2Ew>ioc4Uw4@Oc0p+bEGqbb3P5Pj|5TL+Av(zOV{tTgIqv89K^!%#SG{&pZReeKUFL?EABw$s!#ZR@RRzD=Vr4;Pu#p z*lZ}u3q(qbrXyjNGMNCHyV;o;hLd7Rje-!i-v+TTkc5R7Zxg+f>UQxZNredLci1bq zzP6@`dT$Vn8TXHm+|(3QRBfpmkNmvt$b}j^7xDZs}zKM-?2<>8eu3aZY_RbN3 z&L5-IP-E`UFEXP-&9x!@^6A@lB-xI`9r%&CbI(9vWkgl|7= zbX;j!@4z5|gb$cnKP?F)i`O*wCb&Ktfkr&}!mF04ebl8OM`1B8a#PRl7mieCV->eo zW^y&*qnkQar^7DS8F8#8~uY$N|M@U|b+-rH~_nZ5S-L@CCPg>G#)I?p&!zRY^K zrZaL_P8lQvrmrxLDO%_`Ub(@d-B=K9hRsyMp&D6ERFvv$w4NVqPyGbSSJGoF8$piH z1>!q2JES>1{$C@6%D3jj^cbnB&kMaCe&%&#KtDZvrjefYX(m1^So*Bu^x3O&pLWI? z+h?_CyEf@vpN1$2${P0y=U%gH8GB+&3!gqJo0)zfp}`3>T&rQeu#oFb?Bvf>frQr( zw-CdKGN|sZ{F^orE)G*5+-E9+jkf!qw}-;@iBS;!K6)7vL3;3anfLER)8S|`UT!3} zx_E%m&1p3Aag%Tb@X=#4d$Q8eqV7%&da1z-bCu@g27|_H@ za}S9YdNh2xFU!5IuCA+54$+_Kd>W*0p_79mTrhcw8rRTFZrbzk!`ta^ajjooqFG&~ zvX})#d+b@DN1cVc9*J2aTlqnPq@{Pr;-Rb`_}n>q%6_dn&81&+>dnfh8w}ey9c(t2 z9Xj7Vr7GZwjDol)a(8e%^WId+4N7kmmdyF?&ywg`mL{bn++W+tc!fPBI==3`G^>kZ zdAWf(r=P5+7_FS^aZUNe!fM?cn$)r7w{2BbIlgJ}OO<|>{cr-9mouGI{e0_M*X`ElZ2x&SuBheI{zw(+(nn|0`E<3s{6&KBPn#K$# z9Q$l~DyUMq95Qi0dps@`triKmW2>S!_xqRY;90+#wF*RG*_|NFz25H_)O8=utsvJgWw?Bs7xI!Py*^n{HCTLC zH+?;I`n1?%-?$&eJt2s{A#?Bpt?9|P_=Y%^yjywyoFs1jr>fh|PrhbK&-`+<HF~@56^^vWn}%u*cUH8 zAM{I3N#3><&F|-8yniPg>@IyqRhP-rpUbI8KKXM=WDGkMonVS>F;(U*5hMX_T^eqz zC_?xg1wjk?y(z->mGjPW$akJxE^yV%@tNPACBUb&d$#HBm6GbZXAe!3 zFUk&q!b5WO@OaCZEa%FT^BfX|@zCqyx+w3T1|uVR8_Pw0`;gCfr?)Es150r>&Q!~) zp>+XKV!DF6J->XYJ1AlB>#c38PQ2o`IDQ&&ODyP#T>)u zo*i+-IR|1-bTMMDFAixM?amV~A90RI@U6pHBi5)pF{cG|=WQf0=w-Kr6Dp^xy@ML` zZf1qoaG8j_oyyn>ue^E-pYrlpfOx5RBc=K6OudLFnF}iUE@9d=(^b!hN8y=*H;cL_ ze5bg^`ueT)$C%aD-qB@b#qCGyWUFBkF)L-PS9lI+!aY4oSc61!eRX&hB)^<#M&4l_ zZpI}j|Kk=16DJ~2_HAe8T2udOF+@*zgVGR2OSTumY{yaL{KeROFr+1$Y+YjVO-6oYu}w^?-@tt0PThPoU^y!bI7yV1-#A;f&BW`}; zd#5a%KKBk|TcaMi+uSi?{EG9%?l0}1M2g=h%|$C#W8w@Omz?CScvFICQQA&7 z+%&CUbSy2sxaH^jvi@1FvGuMnXJ_@GU=hV;Du(9jmZWOq?U*nDi-y!=3nj*7{Z?d+ zn<=k)K@p9|gM!I}^_|KzGYQ`tUt3Z)uUJfAO*EJ@{U5r%GOF!w zX}7cm3KVE@r?|Vj6(|m+xVyW1DelEBkmBy{#VrJPcXx+CZvN+-d*AbZxO@u9%36C* z_HSm-%=0{h`YM!GNT-utx5o=#GFZl)elj!@8vQh{OkifW)r-4s;)n1@XE;5s==$%) z?~V^5vrm-WsOLX@br&!M+;@=#MokJ!`UEp+ZwA-KJxXqAR|CtvIUP|QFj z#alg0`@_kPyASK-%3-YeCIL%?ceQP@0&?9YkIDnLnd1V-vZfEHc6b8pSm?=w4-XV! z5)Av!#SihL)t}A?Hsq!{A0drL;^>6p4p)65I0@*?re`f>n6HBU&s~C&mSJek5F7AJ5^*lb%Rhr{xlAe4uzeI`4SE3keiX`O-j`lWeyV@@ef9bvzSU6@D zLv8Pe2o-_Y^bddyPvP`X;QQl*g;t>iaRs_3efntA!@>1xI#SUb@WlS%iCYFXhyPYp z9YJ-xNh?D8N)w9R$F>D`VMJRpcO^=?7jMj!B}@!wB&+3*MZuPe-iR0&OP{!oux@pD z^4!I+M$yt)u|XD3uP8ZmZOA1&c6bUBzRIyw`X6-a;y*V4^M>JXoRxMiVu^`Lf5pGf zIztL=fWuMLi{bP7Ipjdybut=W6 zSS%b0J}JAWmq5Y9?;DUrg?w|h3qwj5O8jM^)V&*utG{2P4^E_CC%sPOr>D((cAKUX zT2U{icQI;Qu**F3IVqoHpUYA}XA(s%cA6a8vueBq>*4NV`uY8RTQjt2cirB1#8-dp zqP!Ornau*E$gMagO1DH%=0(Q)t#h^)p^|);?Ov_((6~z zHVLjy&%&kJcOwDw(ix77H?ka}AN&fN=%|sJIY-el{%AfSM0~j3{r4=2fknOSMNg2| z{X{EUQ`#G1$GEW;+6MGZ+a%7|IyNSMJizgXJ`t)* z;{LjRQ}_xYCo5`n8Y#dZdw29_TzL3Q(tyM7wcDAQh!unV)o;!3OIrOd=|xYpovD7I zzr?|`FVx7Pwvki}=_B>Td^#lf9fZS$U&KMF#;47I5Ptn^oWt+ir%(CwquDq zXAjYcy@*%Ky>^ofj{6o2Ez<;}Jw-G%`GCFXu~0UzDcV+q+1}m+sT;7Q@AE7<`A)RN zBNn`x%Ei&+r#w)=#R%AzA01JLD!_C!{GU1*4>MEhFL7mSFBR#KKAV@>k}_!t5ewJX zcb>1Ka0U9c1NihCQZBGe#NPS|C;D-RZ~#lBewU^irk~0vXuY>W^rUT0Z!x|PxOvZc zAz5{A+NWc{k!z~A#~4ku{~58o<~dycRBrzF9880@NbfhprO|o~*M-bL!_SB+cC(tBAlBJJirvZbnG#>=#x%V-R}7Mp;#IpTZ@K8v zRn)e};WIC`bKM$fk*tnX0zxk&fi7`lrbIfuN_9N(h3_Yz$jig+9*91P5(k&(Jf3;; zpJTrU*V!HGD=u>JPCR==!0Z@21wTOyus`zIP189cLZ~`+z3Vx>0%K>3+8`1yc$c1D-}-_bh$B5}Xa{KKSx2RM*QL;}B?iPW(}f~>nJY<1+QVIC ztdT-<8v4-)f-?U5YQct4sl)i*01>-O>h-Ig_83ry*sH^C5Nyv@2(;dGnEAUCr z!E8p(L3|0TQ$%+yWqy14j(NZZJ%5WI#;RM*Sl~UqEHBhPed7BZHgrwmk^WK) z5K?lY@_b;BKSplIaLZQ^vZ$|T6@GRjH5Z+QSpHa-1%YRS&9ogIRv8-K~3UJ=NSDp1e5F(#fVnNH52=9k2j=+I;()sF~qsc&|z zU={4WtQ7yN##1egL!($I1~e4UB#hwojyQI~E_g815~F*CK2{+}isgK&Q|nD>Pvq;e zqiWfCGuB?0pZmpt3YN)jm$q+*YS~ti$PKG~9eOWJv z+kQ7%+#}johr?5Y8V37RrNsryruXpDHad~rt#@Q1+?e1N!CgM$eFq>K6C!?^T5Qg$ zcD|}=ITqPoyu~FcJC=X%%Mtn;E%3m7;N71B`|YrK=;)sK-%qhMhfut^N(R)KpuUvM z`{^NlzPQ__j&Y_u>1o?(TlTP66XhUEZvT#7<(nqpRL*+*{vl-=v~l0exIJ8jz zqE?lgnW%4U+``m-g1-jt4hE;`;Op9CvRZzI7Q%qqX; zU!H_`OM0EJdg*V}$#L63I{iG070LRk2Gn5_EqNTHRnW8ER3j?qZn<_onhx8x%r>^C zMnh2BYn&CZoH^skT#~<2M5%E9?McCu^^&euAuE%xl zMfml>*n0GC+Bf+g5#Gs5SuGAD-^karAPhvD{5P&;=Nje_7;3QZ{*Jt+(3({I}$q2X&gsjF`9Y=ka`!gQt_;&AHPv!&0 z2PK}Tm+j!?DtF&M#DKknv%>Ta&9y)o?_;#Hq$ifKz&h#40z1$>;#yct-l8@nvo^tF zTa}u!JHaD22a&a}@OgpKm^O~IP_V1tMv4U(<)@r7sub1YPqmN;d#Tt8xk)JMGr}(A ztqbfCOZ6$YUK-5Wed4+|`0_$Q#pj6wkOqy``RI`14(XwT#SCEyMo+3!Gl$${K)GY? z8G+n$yxzr}Vy$Zcn&FpbHC4cL)8+jZDpB2=Y=s(2Nsj3xC5F!6)wKs3yrM=|t3#Q! zK%m$5u^r%?x zdF{azc~s>~6tw(CSXpzD=Lqt$TN97n7Ib?Yd6uOd*|9tf7`QxT;O+E-kIGmZV;H|J zvY|2HW>ngbd@eg>^dZNC+h{WNoE41>bua7!13`H?gN=%=74)7u$aP0z8 ziZ0k6dOkFg-Fj$z1PS`^V7Bci*wGiQZOf%y22cT2w49+9pyq%#6o_M{&sS3p>L#gT zu6JP|O^v5Gd_-Ejy1G9yh)?cp7>y0>k09&Gz1RQ_b5WlRuABbRRroyp?flIix9*Ht zXR={iMM@!Q2`9InTjw_MP5(XS)$yM)P3(>5M(k%NKhGxOK+6_|yB8R2JPyG?aLCVy zt~zt$h+R7^yezP^Xbv3|a>Lzt<64W76PtJ)(xEn=woJn%C8s?o;4r?M_Go>?^3sw5 z%#g`8|9jwiiCj8!&ilL3{|V)E={s+`JI}m#xLKVZugmvZz1c2&RGY(#qZf>*`Su^i zNDL8D`&SIp&I@;k9r7=2JNRV8OeD~h@pD>#%h(NI1=~l$)fl)Ke={*hgC!s8TB})R zWK^>H53Ymm^uMz9E%NCp=}xyw3OTPhR9P~5_D7y43rx7a3l#Hg7YYs+pKU*SK$F`$ z=7N<{eLM|+kS=kSNE0i00Al!U)1QvpjaR=pZnqxX>aRSn(w{NmC-CKg)WUMpQY048 zfs=d5!!YGQ{Br!~^y^KL`-8>*iv{>T()K5p{QtEaPV%TK_vS+a1^Ehb6;l4=0DrP|aK%r!Od2 z{Zhup^o=PwEB+q*&k3lU2Mb|^-rs0BWVOUpU=1n@Lz{H~Ns#wOwub1+oZ}Y5rD$Vm z+`6uSYfga=$>9ANP1nwojm4r1Z=gil15^Ieg z*gBUZ@f(Bk2m1gNKlW`p`So#dT7sX>-!(UZ<^r>TGw&xhh=)}CG0E5aH8|7r%(2k- zsFDGboyzRr3a+Gn8_4v-HhuxTn%^H;9-erz&&K;6^?>O_S%|wa(~RFRL`5!)GFJJb zM&ab}-XiQHRkIlXa!U5!Qd&)ldnd{wlvM&N%JRi=ErsrsjBn0?u^ayPY~KUkCTe`r z(`gHujcnMNC6**a?oSbSZpD|#V_un)qsU{8w`((DxIyUR;_5Pr;8&n{@fB$WVG zD%r-^h{J6m3Xd8@zydsEDrG-rDuJ1b=gc59afVTG)U|-V04;9t*S>^G`0ZUC!ejsj zk`}Hv>QQi9sl+K;wzPMWK6AYT4*)h!g7ZEQ_;?AJ9O`ZE;_TxCWx<(y2WMmdVCUkr13z`EpN`D zY4UKKNBV(b(jkK@#uEQDK&EkrwbP3`+M%>s0so(-_7;N0_DRb5ql2+XDEg_`@tvR5 zX1O7FjB*!*gjQ(%r=aFCbd3q#S@zQ+B3&_H;`00=6j;lnQgY4 zts^k8J(ps+DAQ;E5)u-1|C>pK6WliC?#vAOqKA^>*TsF~q)t1;+q+36awYAl@^&Ws zWfrF^uGa!lb^R0%5v+i16>GR2FW>6U=41*z%)M83$GhI2+3yr+Wj}~b*`bnjx-cYa zSk@=^WQvKo#+^ENHM${n;b~#B;cH<@(QHY>(%DkK>yI_F4I{g`UmPjL60I2B=&W!W z*!^1a0dP3MhL@Uj3wrTp7C?sUQcr7H zQTwjZ-41M#>%qVHN;C6rG`+3u4Xu#&s_OwB%~)26MiUN=J4DUh|5om!J&VMs>3OOM zCflxAsK^DfQkH+LQZIX@CUmyKLRe0pMCEMEm_c`dOc5|^snW$vlr|O{CDKzBbPAH? zCL-wV)5pX=v!k@$l#SJav*Db)gg?-3%u`xj*GkjmM2#)V9K}`yse}CUwd@u+QTXuF zA3SWODSGi=A)7upQd->;v1_<~TsBx)jEsEWx(>|i=u5rNv>&3doq4Y)AKz(z)?|r4 z?CMv$LIuEpEuWz?pnp2vx>LoVVi)Fp3VpdUzh z5b6yph%l>r4We)>LN0MsU*9l$b>VR=clw9gC8<&~Bw%w;XgOVy@A|W7g+tuvRsDrD z@{7OfFU$*)x=%WX)Rez$O3bBb`|CU54{6@ADOJ#`#L;D_t#^)=ekc2}#$z%|(zRQA zTeeR}u&vV=SN~w<`!G|x^>^cDZavNa@~0sdmL!GSy{_=0P}2;9NEd%WbNH`cQq~XT zok#{hz*j$aHzarAMio2#{4EamOEp98!n`;*ve$eox8%ylcbgLFtckl|R~P+!$>quH z7d3(Yg!^pzS3S?lbtWRnBUd)0<%YfF8Hd$x{nz)XaiJz6vu6-NSBMs=9UY+td`ZnC z>M;@yX!RR36_M?+w+U$x2T{SBy*N>Jho!5Qn((4E8F!r|T6Km^(M;2^f>3-u`?MIF zD|P%eydS}H^$uqpS|l)1$&!BaB*F=}p>l8?A@_wkf%7vROXfpKge0m-vU(vg1svV_ z@17>X6sf&B+`8YMa0eUx$LY@6%Y^)Vcxyy>(91Bs)pbf7R-jPaxw31w*+Xnb-f_xs zPBrgW)K6q}k>0!2#@C2xz|6e`#Exg0y(CvY>fJ#}&d?dymT?3uaDL8X;f5 zIxr7b=5jH45OS;m6*}Tbb{j49H(j^NKyjVEFKjS?mvY!hlc4bLePMO0ma{Jr zjr{K$uBng84jJWIvt8ZjZRSE;)~`E|=s)bYVHZUvuBVslwZ>28G_h|6c~~}IvRYME z;1L~#Ixp@aR~d;+ZBIX;ojEbcf{#~^rb?9pAX6Tf&qsBXO)_5+vp=Tsrb9aLJ>TCO ziTV)}G3l>3XFOnW44C`1Kow(ts4|X17vl~=T2n}cy+dvIlkGo2LUMe{*1+|?l;mqI zmuH>m=6m9`d;Q_YVq0B;2I`^u&^Z4kwKIikNf?mpKizxSLH;`XdmH{=u#kKaA~}V# zI8Y4mB2-Qo^ZY~>DzI##7-_VBQuTP$WTFW-JNs2Cl)>*Q_D@k}Y)>@D*UIMv^Z?C) z_$~Q7I*#-t5TonxA(kPmrH9bSbs+hCCULk|nE=fGb~14O@Yn|bH7Vn`KP=EsXFH_> zI7Z095(yfKK=t8s+oxT+JB+Y;5>L7J%Gk`K+1yU=&Oa}@E*m$Mx+5L++jBMUZK@mh z`67(nU{ua#tMA!zrcr94F^Ftd{M6}bn^DDyNggCA7+<5 zH)d)0URG|9E4q#rcsm}5*Cw(zHkSoheZ>s`j9P%0MTPpXw=(k@KHxb$qO6rf^)R05 z);^iw6*~@38w7ch)nqXf@tOjF;rc7@k?xV~mp7o3EU)Be*rq2Yq(8jn9E;@RiBXL& zOBcTOd6jAG9&kweywgn~5yswG5Rp{8dJ=t+bKXL@xTweh&0ORR*dqxQfz_A>Q=Dzb z{O`iXc8;n11>HyeTxoG>i^BZ~Bcx4R&vAvrXrVKedasgYupb!~ zeZw;|{|f?CnkzCjJV}ZyD;o{+aGc`4p45j{>?2LcGU#EZeH$IMZ zV@xfzIj^nQxx2d?HkZbrQQp#$7AAvPS7tkWrIyn^F83408)w_s8u1}(pX{y*rZe39T{-^Sn;K>_2lWE5nTF?zvEU{;piq?&qb243BN z5JuKRm%sjlX@Y^1BtDS!@xc&zT}zqm|GfZMO1(8i^j|}&`MTVt-q4`8;uuN(Qxk${oR@*n`}KeApEl1vao z`T8Gx7i`gA8>To>^CrW)x}cAPJp8a%Fu-1EsWE#xm}Z1ap?7%r5Yp(#si>&P%f~lc z+{}Vq_X&HXLC9l``0G|i%-1;j%l0j?^Ffk=wT#r3bD*m@^cr(IyEW@#9|yxAg4B(D z@W1f1#p*DK-owKK45m2SGd+!&IPCiBq_Bv?#da2Df|D!0dule<#-WK3A6v0={hzkG z8)$2E)0`ABx$gr%kY5H)oEtjd9Y zKPQm*4+KdSxtK-`UEg9mS+f44(cjXDE_+2y5k#;2w@1al^}X(Ue)mDW=-qroKuL%Q z(~W@)Au=^YOE&@rU}TE)CqdS2=`#nFwLMrw@g~3&M$W7Ls@Jf*25we)cYwF@M#RBm zNQT^d+yL}yFT8nZX5-Yk%e2=%fuaOI!N~}ZPYDzT-$|}tzm(!3JDJP`Kn=eK#k4u=Ib9WcYE>+e+*-%W+jK zA}XfJw~9ZJh^VPm>R_sc>egOubjYQ-8?)DBQt8l*N18-P2A(Tp{}*Eqy8X5e3+v8qUzUvt}GCSATO0dAsb_ovVh?wYSl) zSKcZo<%;~{cVKI$U{x+tLD7Eyv~^hCn*o&hzM>MFJ<{W>W@f+FNdJ3FY=}59&DBHc zmtQm%KxiZde*I$g%4Em#4RU47v-5p({LMMuS0SZ$h4pxMkXs6am(I+YvSC5J8Ya;S zi7>Xf%fHpk?;rV8(=f)?lItGL!ORJ#ByDCEpu0ZRUHcio#?VfYV2K)!nf9ncHX)v} zo+ZDLu|&Y}H%7g4OX!=jWc?KJB^{=RCEH>ws+UByrmA!PkaOTstO^{H0}4>^mCJ|adb47eI`N1 zJwOr<=IiWsG)E>IFaZ2*A)s?VC`agUmm_r{|LSec@A?4Yt{1%@z}5?9;Rk07A*ofd zZ7!FpW&(I4;I-w#{=WLi>+OFS1V50AS}P7d?MRK(ydxTBHMy};JpPS#09xk#t!e3z zp4n0nnYSUdfxqUJ|GBNxC$0+3Or`AjUHipOTeGRqz%wDo1nb6nB_`?=_w}^Tx--9A zchRbzd^T^g#di|ZqY=&%IjhC#6!8}U{q0j9RLIM)4TOdLUFybXd9POmeTKBYx@)Cn zzMk3aw9|;9qt)VP+cDn&UIgiEa;YE89-Oac^nWs2PF}0tzH$>bEg-k_>kgp>#jgVZ z!(vjMkw-?0d_joknMTmJa}+0nC$c=F#&eXGGe?5I)(RchM#@*l1!W-bW)3Ch4Jn3? z=c>8#9{6lN1~`P9f_73|Pd9sN17vCLv)W?v9sYz+)TMqhV%DK5NO*n2AMK5e>@r;A za3S3I5OWgclZ1v!II*C412|ets5$rR#Z(~ND9Fde=8j_#hDKqA7=G}}I2UP+xd9Gb zfmTk+mJ$B#?ZYDrnm0%{f_866$Q8{Ddw(kZB=pXN>-yQ+3=onLc2A;nk}vjd`eRW{ zdWhKM#Jl*t#7=G3TMeln=-=c);qVyc+j_%l-XB!y!QIopB~>MWxYcDv7NJeXCMU8P zgb~#b01Q4E`Qd%qh>|6sLfjH?^8`2)-ERF2!+dnrISbio(X%@&CS3S*dl3g(#F8mQ zpd2%LoDfLEQpUyh!GyZ^*=h*RH@H=0w~G)RzolBXY?{Vo8EE}KVLHk=Vu*@@Q8U&N=Od;jrjqS(uMPy3<>mTgR7$L zKaoCxmSpCtI)NlH1eNvS{pT&<;N_6%TZ#6IA4&;Tizo9`Cvrm@KKMJhW2FjgNl1&< z{l+!M4J!O9<{CQcvG$9yBQAP}M{OCiGU~3KdIwXK5ixSHNp_=(y92(c$NGmmFckKA z96`^{YIHgG{SV8mft!Wz5dmc`r^pVy*AtL3MaVki3g>BQ!t4Hn6?Zn%&^ECK5B?(y zQCN!1f=3mm`kLIZk?*xF-ue$C-h>B4SNlp{BQlYu8)aV`DYQp|X5O}(o<~xB>%{n# zskNAVw^6N3ua-zcHbY3>%9<#mMjQGv3St4!;4pF)F z?zpOezNfeUt7$Z(5&mzJqJ4GvTbwpbKl67(LOERIk;dJTzc;QmQb4NXZ3eMfIc{xQ zNyZl zM&jhtl2&EDS{4mE;YTlIU)@@&syBS64EA?JRFAneRaM!DBN$cZ@UVi{#z|HCjLHuW zVK#;No(&zE)KWB^Z~`$5=+B0~!T3UzB=(|bkNeo@xB#q}U+pItc}yScN%?+$p~`ra zyK`JjzXv@(oCN0h+TY~*M=^gS773h8EZWTAiD=TWNMfpLp59(GEB9(V$T1%#w_ero zy-}r9Uy01-$Q%;!z?cDi^NtPOItnfWtYH1ifArK?_UHY@e&_Z}{9;_BJ9gU365Nj@ z1YfkjDLqNNTcpd zZBJ^|!sh5%f)|i!R0@>hCR}C0r5oPIO3j3agBbN+KNI(L5idBmpq=Iov5 z)$;Vvax47LGaQb9;}}2^T|h9c1k8L?N1Vgz*gym^JQ;o<$OGwOFiID#uiqdIJlH`> z;WL}JvC+gRWVGJk(b)4*kyLjK6ennTIZC;7e-wFrq?Xi?Ff~##)>X6D9MPVnN+eJZ z?7eK+o$(BehWq!S>k-pJp{vJtsv?c<3J|7o2T&`$>mSC?GcTMd4>XtlCzv7U?-nE) zU6DHuncN&E{yiyrC0|4Eb|7tU_{d({fMKS}i=X+n&yn0&#KPuf2zo{$ztOrL-atX> zprIqH7b^11n5EP{o*&ngSkZmtR(5y|an<)@i;rMo?kM%TXCb^+EB8rmQMq3vczSij znsst9P?(l&(9vQWiisLMGNTg$sMqNa)wSFmL_h`3;~{6u51mi^;4FRvT1#d>jj=U# z4woNAvN3Am8Q&Dc;2QfXPNEu=i+VpjJ!6TK%$Yf^&o%E7nJ?eD(0M0&_uA*FR=B#q zyr2)`&gPZr#kKZp;PSP#B&SbFSLhL<3t{b;r3M(hVFFIK?OiVeyco5y-TU+ZbJgw% zT&x`$n%Z2RNQGd=1HjX7;P3g@cz4Yc?CR=3V=L;CwSX^M&GBaxKEHXNT~dHGaNY!N zN1|EHd4T4f-4$g1tjOHyygCocMj^XHf~oR|Y}L=wLwu>|^@SLnEI5AGIPaG@q)({y zu&%kPe5hJsX32PZ{e-r^f`sJrT3WVxwbYqmCSg~AIDbIPuKAlRF0xcF zV>LB9qzeYtEqCg24R$PKMCs8Fh}GGK-77IS0bZXHMSL@V+c{0-(**ICe0{xQumP`b zOV0liKxpdm^yZW6kzGd@Ma3JG<{-&+ThkWy_2fofe@jP08tvOT-Cnynxl zlaGhAn(prweJK6J?F<`rMjyy|EReL;s$k2Ppn6#GA9@a$&FRjRT*1c2NL}~hV#1^t zTh#FnW{EV$@caprOJJqqht=iB-|mJ2@g*sxR>vFe>?l?mBQd%o!Ap?^;NEt}Gxj6f`PF=40w71|ln9L4D$@{}Or;fEN@5r>v_t-en&fK@F2bnLw5u&)GDo z%lT4g{K1J$27)`f@_^*fyu)z`uDt206WYtia|gd>RQU_I`}+s%pHUnVT5b-F%Z1bW zseNvQa3)OG4T0pzrV}K3u0m!=SF#$3pQl?=FdG4!7`gLTyC>&b&NH2KzbZ8bN<$dW zuQ_|Am3js_g0iH0N;iUo{{8rF5xHD_zLP}2oXRKG(>bWdHjT%^22Vcxx<*3w8A-~g zO%g!#+^sK25`g-c)vG;n);9Td&y3Tsd!3Z|@=wCc9Xa)wEA5ue4ok9GwU}B3M*BMA z#k|;T=IboQiy})?nqp3@7?Nj6yJD9q(4E(PIJ_n$$V6@_!?**$ zqjmF%>gE(B#r)XVvNX;Awq_vAnujlBL}M|+gI)1gMPxO*dkLJkYDeR**t3g|;vG0B z7VCh>lq@ZCeBzCKtDRIY$?(#`UIx<-+te$AtX0W^LXWt{wr`OAJ^Vo+B-CN1YB=t# zRQcVI^}J#bqmvgdefD$44?2D}!#4mpjlX#h=b11m?WQ)*ZrB6@a*EM4H=X~<@>YDc z8*OEDb#+VjM@C6^cXlRV13^aTlIQbDwd~GHCLWG#pIAaciTk9PHeBQl=TX>8q5C%aWw&@ECnlR>FV6NR{wUI>Ik^4MnC;P5m%bzT2ftCapY!3)b4(o=izv8V3&Q6NV zD}$oqL)Sf=qTOgdF`MMq-#15c3s-VQ4;6nU!1nIIYFhld<{H@a222tX|3&tkoE#zn zcTi<5)#!460|jRGspP=j+ow2s!>1&dhyda-YS2bb{=DbQ`HkFJ*{+}dTbMZW^BY#A zA?DZ7cgSxz4B>3CVvuOn8bJR|pe)sM8ynY8z8!79mb4Z9&PRg>S@UoDF=fEe&gv1O zTBiQc@$8>&mVhl;ne;i9%z>qDEvq{r{3hz`ymaI6P~2V0KKZPSeKzjjhwWdn6)m4% z2|-tBS)Nl`ulmiWTvUw-^6W9BPh*RZ!OP-*j>Lye(UZ;=2da7vbRmY`AnU%6Y^&G!R#v7B{`qw!V+g;rftw&$D0GoV_cUCWlH7G*&3hA2_< zr}&bgwUyiFAhIvp;y;Vm?~nVB3-0;G`PTPW_5*#o#=XEQ(^MRfK=(!95Q~U3DR#2W z*Df-ukZ^nlle4+^$%&X&sm$gM*WXJ&e5d`BBn^QHcBjC!KykFqZ;xhS`r}U`rxZ2G z2DSw7>wB$Xh} zo*E0@fwG0bWQaI0RFbT8L#Q&NGBOJ5@8}{BiFIV2D(at~0@(&O8*WV6uNLc}ZFDCr z`;M|gm%y|%%_X(}qNF1K9T?aX3;RMM7D207K36?$ncpTRO_&!rB1J(jivE=z3!p~J zm;1v4bjT7+oAAGs33A`Exs3q68YV2Y&nzj>@0a*rwIZ$jD18iy4v`rPU*rdxpw^j5k!*XOB&L{P6~S_K65z2N{X{GUmP#KE7?VR7oe zv>a5#VDp<`YG2lF{ff(04cb5G)$X9Aq0QF=x}S%?1*m8zq4U4_PqpbE7w{D!SyuA< z_o*PXyl?zj%o+($qi_KSkhtK;|Ke?bh0mF^giVN|5*8-OQcM+6Q>q}?XYB+(*)6dF^#p&0s7`-nth+HhZWFk& zxeAljB{g538V+!}jS53n8@tylJxpu07Z|m^J13v9Z%ZM>3|ZI_+9jlbqm6NxjK>HWK!XO_i#Zy}j4!@NTez!_B2U zP13NXI&4ZfQO&SFYu7rsnyhhA<1&V{$9d)S)APu|51BPhXYE@dM#Q(h`yKX)O)a&%glVYhxdIe9J#A| zF(CDFb?G`E7h9|d{lVtd&^QIvY-{9zToxC2+)M@GDnWwliZk!2^*N8L9pJ@*vPBen zG+wO9x$tEZ7iY|!Lx#tdiZ16LE*U%RY5(kP757y0iLeX$!%tgyZKV{AyIEa+4b$7- z*)w=FdhvR_F85LoO6%A(S$Yl_tj5=o)d$wHIaI!8;U*ELaV`Q?0s>^mcTVL=g)$Rc z)--)W{kmd3bRO$=n%*M_7FhHX0_#{B|3+G|+LP6_>X~gqahw?O%L&jnl6myE7aOh7 z`~?$O(2LB8WUu$rh0S=03DtYWl0}DFIv~R5G=kzEvG%nY^-1YUILzP!V%6)6wnib2 zHAz+9qQLDnj{IYa0VA1mEUDYK|DZhn6|w}^U?OmXFb!T5fHAerdp~gefn<#QxC1Z& zPr%(arzUysAJ^#5Ff1HW!jZ3d*QL`07O@FPE_>|MSAI-T{&6M-e0n8nDVH!hj5b-) zJ3C^y?hE!0u6)3Nc9W`($sn;keYfFYk%3s$x&ynLNYQ)wW}-R>d)YjW6@imLR%!h4 zV96qxB6cM|_Avac?|cmwn9q4!&5g%$>oNvgrfGfkB5z%2J~6~?yeJtIiuGBw#{o8J z)hriRvnEN$#j&sHQIyt4!wZ3ny~7)1g3Glus}&w~0@Ci2xvCCJ>T}Ls$M)iQG2%II zBwO6%?4Jx{AuVAH+VIw8vd!G~az$iN6a8gsU+5j~$p@}Cks!RfUq8M#Kz)3nXP>O| zb$9E>?PFIIdO_gyBe&!2EkjztOKrs-bRgM9a>|U=B(}g@LUNS**=+m(4l*a#(qvw8 z=(ceBd1ft+UPg=Y$3@U&W`WE5jM#0w#R_^l=zGk>TVr=1!{mZmZ-omw3@}CLb86Gx zMwRIQhZ~o~y;8DX zzT4fkSl;qH=Iu^yWjkBF{CK_^Eo=Qwwm<8{Sa#mx!59p+Om#*WCzcLxvXiZ%(cj5i zl-;6+{EVwOoHq_hAn9BE;iD}ilpT1yW+ENP^X;Egp;D8+so6!vMEGN{`+I~iUud;Vbe3e1x^8Q$+#tEKm zt&)FaWW_%*6&Ad;ZrpGT4A!6Z85mm(R<7V{twF`;Qn|XzkoEawc?6Z!zD7LuDbVk?cYBvF} zRkT=WON4F+{nH-bJG8rnZgVtYxLGieqOWIkL`}Kzo-2@w@BUP4e*u37Q}5_pzq4p| z!&=`+wbbq6%Bq;F6~O<4(ZSEz!+3UZ7_UJZ9>;0q=(D|+yXHbWHpizH@A#qe0a5)L98wPsDB67q!$J5QH|V}|*xTCW-#%{I>bs9AM zds{>!yzxxu#``)=!^n#p=9h}(%WguF;$-sR8t_p{RI7v?4JjLx)kxG;4oGH;IrzOr zRUKJ6h*uYw6nrf4gAzI9S5gud zXl$%RB@cl<`A94*Nlsgv00{{RCj7dS!O=xNsHY4PPYQ0`=1l2W#E*F!JuICr@`wvI zrB20CkMVq+zPEDmMgzVOMv{D?OQ0>o^?WJ#z#ZWH+tQ4N(# z6e0C<=lF9Uo|s+X@bo1=9SX;nx0hj`|u&Hh!?^L?e9pt-?U(%NqnWG85~hfVdimHP&*KY2QAM z`X*$<=U?kl-=3DIymu@ht>%Q!Xx_Cx)AXIdCB*r+VC4cECH-e#wg89KvB2K2%Y~L$ zDpDc2?F8oVinal^F4kzY7|Uh$I*jErS62?2oe2j|1hJUL4okpdL!;jEgec}%e`05T z86m6V5@Sl`DtIj@jIZ870LqvF{<)Oit%GWO*Cnkb@NCzqU7Mnp9kw8B^P%Mxv~X8o z;3I83e#T;YM9BJem5h4!8(3*KxvgX1WcPTX2Koijz0%^b{L-~D$)u(s3w89f6yjYAO zX_L_;LK$qB&eSifo|{qVmgfRtO3*tRI);#fuIPJ7hVn|=Zi>rR#p4N?V6E zIeMgOOKn8lhWO&@vivJd-g&=siJjK= zXgb#&1NW}VVvIQeO95!M7q)F#{NC};df58q>x6gy!>REY>G6UGS<1|f<;RiMXvmQb zwVU#G*FKB-t4la*DjW#FmAKa5dg+zAjT^+4);3kGwf5d#ji0+L;;K@q$09>hdc$WZ=*2=jM_s^sR`o58tM&YcfL2%rCczMdY{L1PZC#8?A?l0Wc@|thw4L&mMh;br zanqkBZ=k-9pSvn9|MIM5n6Ex>CTZngVG3CpVRsMfNq++q>Q+|X7y*^=nqe?h^}Vj| z1}m95m~KyR!q`1xuUbWacVeLGaJ52(>a6AJ_z8@wvUYv>4ZnRMTbH^`Crv0*X*%jK zu#mODrPrdbsr(*P`Upv3IMHTQ&K7<;mfhp4sP9%G4j)_!SlGi1QY~WhWw#+`szsNT z7>gx#>fG)Hqb~Dd8^o-s?qAJbb1852)tv{7lx2-wuD4_qjJ_HkPujc>2{RQ8`E$L0 zl59ECkU6P{K>5#Y9G$d4DE5v&_pHzG4JY%Mm7Y+1R40#2T~%<^L2kY;v0B0AsCnbMP=^fBi+DsHg&z(R81;Ji!lA% zt%{f_(=VL~C9ei8{ZrW7FlT3W7`90A?D|i@$;rt#IXR!=Eij>rB&=`{7#21dgeHNN z`RhockhM|`n*a@(p>{l12U9+NeKh_Arrc_PAPSZ#81*l4LD;)9?4lY!YY(Uj4E+%K zIkJR|;+jvrr`U3oi}{W1S3v=)tSeysfiQE)um7RZ4}b1`G@aOqK|6NUui7iV#XG~E z@WwPfgFz9WYnfAmw+p*&F}MQ_xzZjCcPDHU6&i#9jF*S8yx;w{w_+z5T#1kV{C^`w z!)|@|)?yXiN0A(TvvHrBKQ+EEtvv|eyHaPMonOl9pfEE&hL68~K>SNT5!(Rt$Ti?$ z5WL69SQ^9UokXHLnEX=%N;FbyQyx}FIvl3NawoUpXLt700gbclcX+5fProS^3WwOS zVC+TNi|Z7VvTWifZ)|f;3=-q_j%mXOkHXG_3i7(!=-5W#tdlby(AQ+We{<(gm z55`(!dI80c>CO9u{$(6jF)?mq49tdLp^(z%syjJ*af~6g?B*v9fyG!Tw$-FhSH~7$ z*p=#=7c`^h;b*(yo>d_a0dUyv<$rL#ROA6(5$>YdwJ47)Rz|oJTc7?RcpT6#U zd0)vu-q#KuD7QKuO`s+KYshPYjW}cqaN3$9G z3oabncBkv(4aK+t1A-upNQOarRjXr-yJ8zlQl=q(;CS>T!Z6I%!a}tnS*$>`b5lcB}d&PL(_nAQIho!IG65CZN$1bHD0?;cvy3(E=fmf^{=sIJzh}t zw+&2>|A#IQT0YINQi_6Y{Idxii=}o(pJe7oQRSk-9z+LbXe=qpytbsTGaJ0ogHbno zGb(Nbs%~;>+?#qBqkC9Tuq!fBZbcMClDft>Ejm0om6Bw*Wci0Juagw*P77fVD>eD* ztuwDn!&0xWVx>|(36FfCEhu1fQ%pvS&0J3n8XaB;N=s%(CH8G+nY~{%q7WEs807dK zoU-4kfMS8Ie8T+(3|Q2wA&=nqIvRYC2Y{R4Z}IGV-}pE?+}ipRoZ`U)J4)-h^ruQ1 zJ%@ZufDC9Gxcw1C_>R|@D$0+E!lB{j9%57k%&fyY1jfweb2zGKC%?|J!`E>R9?N3X zk>r1n1#m@~Zajr<;7*zx13f~&7k@C84)S907l=}Q*eNS`*h_0;{#t*6r7GGXT1IfL zhR--v7KwV*nTbc~NbCX?bV?CF(}EfoB45(o1NnW0Ew54??_C$`BCU6|UEW$tDRIu- zkeOnANL?E*b$jgI9|Z3_Aos|~o zf;X)5QBARka7Pzgw5iak_}D18p#?iksSXb_IVQfjZI^!~28BXX5^Vqt#qh>4mM>X| zrg2MmI0G}dp>NEGWP_Hc3m+=mInPa-8@?O!a-?%MW+Y`NDTH7qn9GJutrf!)(MV2j zRv*5q7+y=0H^cURZFnq(!odvqh4@`k{UwyToO_NCL~c=8wb2cYYCqZP;K6}?%hJ1Y zms3FgyY+m$I)Y=U6P1%`rIjOA;fJAhF#4|hjDY&^CrS8KnFu=je8pCfAYUrN>>y}s zxCVbpWUb5ex$5+kJ%ecYi|}3qFyC@|j&CGaEUDv3h3g)aFb(Yh`HCCA?IRS%RTsGu1!oSKM?6GPFbien#O~zUq*I&KgQsXX*zM z3AB{iGOTSVj=hWqMFs%}zv(&k-heG_we7lSVbnAl8{R~G-Q6WF%*T^F$$p*HhT_Ip zDV}@#yT3(&lRF{;i%$uhpDaxo*542`5>WgFRUiJe2Kz2cMF-Bm4CcsTiObXrSCuWY z*($*z#{b>YYrs`^@ja4`Z*q8S=1_xwmgAJ*Xq>^Dh2%>(T$)(^g4 zg+Nu-xb9Q+0CdO4Ug@t-vk1Por09mtgz8+3?T=3!on}2#jZR|nXe@ti56*j~XDm^c zb-!boT4oGle&&VyiGe9?UeYa<^V^fJ4|`^2I&bH=a8liySWIewg_tgtYniYsv~t`c zJA`vM1@5SYBAO{+%Y`>~8cK7jq7Iz^2Cf(=6$mR=;*c5jX{MelLX_mw*~?4m(Iv)B zr#B7_pu$S6HWjzY8vC}}<94{n{eW-B)x~qxgQATwilh!{3qO`U^JCPL7Ey~O)H3gy zzo_zdvn$wj;PH=lqJJkc{1MQ%Tb8oe7=Aa4BqiE?@qVWlI%}F_Ej;qXXB#|@bsPbX z1+Em!(R@HHj8Zi6p6R@RYGOk`ly(>G|G)D#7H?VXY8m|3Agm8 zQpA)+Zwech7p68p7{;#1-s||zJ(KUGbAaTAT(A{{7zK@B6yo8dq1L)n*gRH>50v)U zRdWelqkweV4|KjgLlM3+Nepm1-E*9n)lGSXCIQ%D!$QOUC}A*WW5V}sJ7BbSiBeua z%QAiq7bQk|JaB#Ly|rYl@_VA>4^pCa@5%8>df~%! zO-Fr8eGj#Qx^#k4)uZ$=%a>u7v}iG!T-%i-_q2D3Z#u`7cd4duZCyJMRe9z0ViBpt zjBUXhu>bn=+sJ#ZO7Ao0SW1_CgcbMiS7lHgEu`Fnp4`R08b$iSVvNyPkeXhhoT0IG zcx>}nfDT@hVJsu{_>|E^=%HL5c>px3B}B0>sOjKpT92mP$1}LfB@i=)1;H2;6W$IM z)f{O57&X0q%CD79>S?o_{VO+uwqzssODb=X!?UcOJ+zmt1f1A`>LnZkt^xv=9y=*Cn>k+Szq01lEYU5h5%g>4iy0q#yN{h)=n;> zTPE(6Gm0|iEbHc-G0Ab5D zC+OnM$n7uvBAC5%eZI5mYQv&Uj@UavoSe}%xo@Lu9-aVgW=pZoo)Jw%7pfGmrpCS7 zye?Ic(?8T*U;9}fNRTzg7Vbg2uk@4fgI-z1`?oG$AF*WY^YpcqM5;c!1G0l1yaJQOsSigD4By-k0obpXH~^~MtiFV-C^CU5r*$(ppNtA z#vw!I%no+7ciykiQ-w>uC<=iJS-%gQ6m1kZXSiU*LEjjq_Z;v}vMvfqY^yndLg1n9 zt~9w6eE0M-zt z&lCx25^i0uN0tHEMJNPDR_Zo3|LtE{&3e%P+YPGB>to6*1QFOklDv*vy{L+n$p%l8v*x-32F!t`JVjoTtvC10JHpHX z3Es{~DBv@L{g9Yv)vB+m9u6Xc#`do}n9j9kU#H-w8~qI`@Pjt3v&RPUd?!JLh3GX< zmF6aELS*ar%B5X|q-u8KuWXIHMn`L1+_Mzpv6p%F4e_6jn2Ag-ZbF@29sC#c4u(_1 z%vy-yFl7=-Rlu(|b#-noEfl!U(K#o@$euTbWcL{z{>FKk@Kyx}t1~_viLgP*!U~bF z)a)LqrXFr~-xwk|R*#siKMfZdR9mmc@l{ecYhAxWZr&43thy$sNT2=Wv5W2HBxri4 z4o!B5@yAk{AEM=_uQVn2^;Ge#M^ULB3ru8NMjyMq>bGuvCUX5GIkiC4GBbhkiUeKC~ zDgC~Sx0s+@lX>FfIpETOakI3k`rv88plITO66yQpoZ!iG zzoNR4;H@W{C~Y6#D{0HTw$7k6%$sx~=Z4~Ah-nJ{;b&eUOuUgbC1@vY++3pKDYw@O zcfhD$%*5(75;667#0UcS_IeI=QV!SWA}tjjWJpPA6I;hxN;mtp-a6Jx#hJ;Mp5?eBqjBy6dymYE&W4@Md85*L+#fy%pLx93C7NsLcJ{p`+Tz2tT9HK~ zrUPYIzfY}q_D8Znz^z(vw?snhK^NlFwIfo^c(`l*U$fddWruI3FpQ z)Y)%C<@{ugUPKTG)gj6oc$1>Ls5JyL8`0Qm_Y`jDd6Hn5`%Q6~X>V~f7bdbYxyxmexoyWqd>y@td7a&|fH`4r`A9+#D8SwvJ{E&m4v zU#819E2ZJkw(_`(>mD(t=Bk)DPrT;5dlm{;KTk<-_7R%gxf7Vkc*XHW6YeLQ=-J-X zBk*vM>O)osMY!5x{pVH(F!%^>ZdNx}b)%-fW(vjSnY;+{RNg*CTel;aOP`vg_4Z&- zW&taf_{;T*z&fjYU5UlaO#<*ppCwyXl9YNERtNCXbIsrWnf}s0H)_;pTu)(2jXdg9 zwrxUoW<){2-2fh$o!*|Q=kwvEHkfFG(&W-U%Z3LiC2b%Kp#zvOT_;p!etLT{SvWiE z!}S_uURnz366deYj=_)1#OsrkRt#-F?;Q@GxP7WpY{eGjIK}N-B(K!E(e)pF|HEYW}PUV>%HH0XHrYSnRG>=b@k@2 z4;O8|estvC<3ZR1)>Ok8TDez+QP)1_A zx3b!DU2y*_8ma-V#$~7aWG?BzeOa$i@*8+W2II9p{)X#^dK|`G`Zq9xcmJvP-xE&> z{%Fe^4`eMfDvE!&Bp6A=ns_NWcm*dh{F-Slk>Y{2G%L)fVVwX&awT?fGvPAp>T!zs z-E9{3Y;6=zohuId&7^Y2NQ#WSgDa2RYb3uEQ4MSROPg!_Mj^faT0c0bpGKZK0mOf# z8Q~u9lxdRhfkSX{Hm){W_Cq3`8hT_bh38@%(^f*09Pb(G@>f<}^zwTaV=_`NJeFIz z=RW>@pIqFj)n7tdp<`X`lWQ&mnhSIcjBE*$T#+~y#3z40P1Q2Pse=i#{G!wZ_ck)@ z3|$S9r_tPunXroDI~ZzVfcQ4QrngT4S_;1elMQ@w45Q6FZ1p(!vEf|~jP~YStAL{crBxpME&+a$pX(k3$v3e_JUNH%y3k3qjKj( z<#h~~GedgXt*OkMXOBHGlF4v1zGjTw8ht%%(Bf+yyQh@L+r>M2MzRt*LOREWD-vB& zG<*5>lOg8A@J#1_00kj169WT2S|+Dp8X8YV`PkZZ~CTa0lIsl zc3V3xJhV0Y5h~ubcdWeL`k02wu;kPB7b-N#qE^sYaYd)vZ=4{ubFEr%z2CH61(LMI zB^h}ivzSe;%*^MCku~`i6^vR6chY8x&Hb#|Z}BmE9$&dV2Oi&nTO7 zC5ql%CqD%DWtslKntj06`?iB2TDgK%Se{N5K#a@~`dw9Mssfa~_I++WAh(nZ2{z=#?&8Azg*sQA69`NJy}7i#46(zC3!m z;VuyS`kzJnOcW-9@4)Nb$Tg1maQU=JwgK{ zAUb^?$#?!ng)ZaH+xW)k6hnaGLlr*$XRm!7@(_P)A705t4{A- z$JIyE>)aJ-Sp4*57kht&{}#iy<*&cR*1E-YBb`9@Ku-(9#2O?q1}aGOen=L3Fow^n z+C+mb-r!+*eyedI97Oib6*h``vtruT4T0_ZiY(}Aj#yb$9sA`An?7>Q+%G{A!j0+k zpyIEB;cX1R{|kQk93vi+8A8p70%2XyYDP2f(}kQeP$R*EIDwr95w}e{RhaM!*}08O zM)F$sPFwWXx%>l~`Aqaa3&}$yr=;#A_02tP1cH>^6SA!h_^N32Muwp{Z;286a+#8<+xD**rdY|FISI4+Y-w<*sGm?3;ZeVfk? za-L`1bgU9f1JK|%xq;_*5g{FruvPUb<@jGynCM=_-1fqGlV zKwg+0#s#dN@JL(x?n>SYd^9p%iMXHJy8l{Vv21Uz8=mv<6$mo4_fh$n;wL&*Wpd+b@_BBr-m$O1uMA)uFgJE|E!R+w7n zHhvp04gInec;!l%3&D0C zOh9MpYW9J5dZhkTb__+cu?7|^^wbMBe;om>`h;PpVKhGCB)p=YME0g#&HSUeLh>ZS z`<)kds5$(in$z(KGc-LZz7g;+;FoApTLj%cB#aFCwH>o6S`kTTt-CK!H%#w_0wz>t zA*WOk-sRnvjki4i?{*FT?#ULy$Bd{{ScAcr@ZB~nXFN1^wwD*|?;KSDlRK`5WAU3z zdsU^W+J;!#e`7arqAbUm>WE|R)+Av#?7i$X6QNkc;OU86Z1RBxvFLV&(5&F9{2wT9 zE+o~&XJ-)$$oF-SqO1DC|UPq}mltwvv0YRZ+YK?|lcp5y&Hbzp^F0y1i&;eg%0p z;ShH`IJIH|5>*_?|MdB=ktwg~8ZVE&m~B~zJY}S5ae$Hdt8R&zPR_%Pg+58UMs8{2 z%8#pvT(9A#ueXGeolKxO=z6iSoW4O$@=yB?G?Z>zl@XIse+GE@)ZNO`6T`OW83tJb zG2M=q2tCmoK%zU*W3*gK-#kqG=7mVTHEejl2J_EVRGU)!3HPvgm|Y z+FtLhto>YlTY%k_4;rpo1j7nMovDgasPi*(vz#A0%NQVXlk}_6Q!-;GF1C^=Mn~L1p$Hbn9E3o>)A`N+Fh004 zj8#|7Cl55x7@Fn?W}ysbWYcJUi4s%&A0S@%%52rmlY_STZaLB2J$jVQLqY*}Wo+_e zzwVk5F@P4&*K$Ig{8j+WYfTK$r5*S~jJiBBoI6YdzI^#yV!`U3bfW29U`NLbR79zno9!GAyd8ffdlCUn?879vDcA4X zPm>V@KPn!2$J{`;Jcb|U2?P&lclWyUu$s4o8jGBAOf|)}I}R@QqBHX$%K?7wdxMVF z8y`mRh)DO!XMe@<7e?z9unocp?a`gTwkIQJxKC|1!95&2j+xCmP|s#nN$xh%Km58H z_RJ)9b;Rn4+QDQB6TQ=zSvR1`EP=Rpv|RaGcgQWwsgzgX0j1=fl6QyCJ>A~;4s(kbG3#+ zZ`ci9jk&dPQlt5(<;ATiu4~iBM;OIrY8{@vz5igxcDiTHSV+mqbLA+jYir%ViyB)t zXuR8UXlSUKrlu)CXZm?tKcX!=xu4`G%G(m@71HKhMpPt=Fu~=fn$^DL(~no)!^sdT z>BGhT*t5*x{6ebgQk5hPYT3%;54d7h<9DkFNW=jCQFrT1q##)4IqE42ir7D(d$gCdbPdm%WJinqbqrHs#OZm@MK z_;wESzsw&=*EbBZcalpY&Q|=EWh`QQ=xcrMWWmm)>7J(e3S8(K>#aw#?XM$pTx`Wb z9in0ey9oF6kH2->6=2t#s~l|X!JZtw!7SOzsLVW*7TvAPXKQ0$I4|K$H9E$p7S>9N zt`AZP%DL-vGs$smv%<|7M?X(jexdigp|PEF=odg1`VG|ZezKzg&N4sipMNt@Y42LG z+=dMmA7oJ4!WHSPWY^Tu-7 zh_6KF)1V0BHWj_Sms0sjo?mrM&AX&A3O@de+V_Q0g%Y{4`A70x8hc%yh<6-poonCE zZT>XU*ALQp%gx1+!a*>*5}OpCf{+cZA|<6&l}g0a0DE|}y55;pm?}GmdrH(z&PBs( zykl=WQhc-bMzzj)pe_}qrZTCH#&gF?l(F-RMwD0(RK6Rx#@y^zpJTiv*m8IVl^hi( zUf=cS%dlB}#Uf^}Po3Rk4-kcPfLM7^^}s0qWrwRiaUQw?<>Qh$47@h{wGx9&YCuFC|UD*=Ee?MpA>hf#QtL=kc`aCy<=mi0>Sb` zJmp?j)dREy$bY?ZsLu~$>p)H}g;)jqj>>3h(*Dnh7z>os4#v;ajg2qyH5DGra|jVs zO_y4&s9D8+Cn_q(JvZ;F20sz>NU08qU1Ad`i9kO+G~c}n5iJ?#8}dU6e9TJn%R-|E zeR4+cX_KvMt~#z&ODyNwpQsaDe!Zs)0cF zVKdCH;%VNYSaWypXIC`*-)y^&KU-E>C>)1I)ABFOqh|r-FU({2<1L1@+spGLwuM6` zpi^z&{IV6^+Z?{PaN?jSXyaxAEc7TdY4};EJKmRup{_#IztPL}bq~{RZx(4<+9Mi) zGx&vqBBm5(MIvGPN2el;vE26RmYa@uA@}%+O7HN*C%8yhp>!PSs6)j#!#dO{iKM5w zYSpdHR{~9*^-;H7VYBb&wmBi!1KeVlJU66OyjREo_V$2{7G}JaI;cs@FIcck&RMs! z4_Y$Wrlb4yE}yf->m>TLc)B@h&FBfc**!nMtT-1f%S)yz&7)ji*x4@_%oWDOI{jJx zY=9C;Sw2=Xa-+T!pB!OMMQQ@Tcj5GA3>ZEuRVeC2w{t4ZRpDl^KhhE1x8TQ+j)Df* z-?#iHg~eh9NK%jevN#Nq9C4BfA~mcZ0tb~qz>~+jrLRy+i$Z-uqfBV5_i(~F*QZHo zWQOku|KKklO3X}dDEQ4(*7OWb;?nQyn8nM)sH}@ozxw2=W!p&4T^!X2E?XdPgBFH4x#P8&Ry$TDETfK=BsPPX@UK`rh59KkBoU zMUz9at6#OyBkR)boq11Q%`KCZBhqr5l}Udde4EnsEGrcY2enNVgwx_K46f1qvCk|c z`lP{nE{qh655`UN;WeRsk4)-{s<{9e(twxoac;I-AJb1Au$+8C_Ba}Oz5#VUS8G9| zs~!eZKNSw4>nX!kb+UQ}kaT;2UJzcbjRldh00 z0j~J0g#h8uM9b*a$Q9K_arAzWc-GTDQEi{0c$Q47fWP#eXmfM^uNR}w!=iG@n%RsN z;y|2kGr^R>mI?(UsVE}0ao=TGseBL6FO`m#I0mR|tUU+3VH0rBnbiOecyHcdrtpb%_7=I&Wu{|E*Hh!8 zzX93INgna^k>jBw7krSIrI|LcQv2%qy7ba1k@Qpc>NaJW_t=pD$XuPe`0ctg8hvY( zIz*vFxI!gQ1@q5B;(L;1JS{@t6%=Ba-ciz9GLCv{KjA5_p5V=!s-NK+vqPaEubG;s zQvBYE9iBE1T@4lQE@lxj%)AP-p0`+}{xDYE*y+Q^tSQFLk{^?vY$^rd`;G505o5OE zn`8o?r_-sJLuF^a602d7gQw9MAx)r@eii+#r5F%cgW2r85~6%py6F-dM^We9-tsk8 zq31gEBi`Cjtu4R7NIo=Pq)VYvu4!*qq*Ag+hZ$~51994e=qEd| z*#Lc6c2WH8VATho$2?tEDT$)vxtbAbOGy}Z)%Z{Wt_Eb^J=x8XtfsR+)U94 zmp{ee=Y0AhUEyr~3%0fA&X1r?g+sn9k+r0WB!rojQ%_K{wan+{ysU~re_NPbCVi+c zG%n=;+ke59%(ml|&PRuu>&W)nF5z}@bSBSzg0CDX2Cc_}BQ7aP!pqwsNcvKTlH(TF zQ+S%>u#x_prl7k1B8k%LlfZr+yP!W}*&j-A@!Eej`Ig(h;XflcV@O|dQ(6DSNAKVL z!`+?B+}s@9;=_@a60588I-9fia@<%vqr^d_R3;#d)pFtTU>oFG8&x>g`%XR!@pNLP z<`AND0Wuy*r?=xK_~_I&)0Bbo#{b$!@*yG^>DTdNqN9g7ox?G~fr2ZOiE`@J`e@*8 zIL#uVQJ4M{=68=WX4tGOH?OG7Ce=>S@oKmSm>^W_s{ULF5-vycKqfO9O{9J4X57yk z*H8eq)bTX0qRZYeopdgzP)$az?|53p^Vui@H2g+RavXM!1)zMY{PfWNMtK)^Rn_hU z!6;deOzC$KZZCU6lQG4a-7Ys`co`Ne_0Zq_a7*4|vIh3luq&zc7aM82abmFaS!{nm zfOG$+V=~B|-09_}!pa8Uwj6={MSk_mMzBFR*T7PjUA>usWNZ`CphHA9Fq1oIW9kvIM z963Ro5qi(<>l0t;xyR@(4B-&&llUs^|aI9{)a!W>Qy;_f- z1D|hsp@JVlo-$facMhmRI&nVnVwLJUPl{4|BSV3dTii*Wpf(};4-E~Cz@HBX@wzj7 z7nFy&qH+hIR;s+xQJQJ38RTbUY+ZTA1d!QnFyoPKe9y#z--w5sTf(6yRT)UK3K*A} z8P(F_c8-AL*$0R%^(WuX^bRa8Z$)oxmQDY`8V$m(qX}BDt=PO_58mW9MAbKb;MPTX zOW8&oKTsS+BS$#-S*2ewwf?2gW1jx|>xspH5_?Qr@?DH?XceE(L6q#ws)m4s)JNao z#LKl<>5s?VOmwkrcJJ*|!=KWC{>sQ;tf>lg+BMA`J?LS^rZ3k|86u+#cGQ6H%|z)P z4zFU#Z=(@-i!lo?nK@+=V)m=repTsY-_^U{mf9|wOGo&B?__Z=exszMOAl?v%q2dr zNGY>VG)XjBuEOISk*Z*!nJr)SNc1@9<7EW(e4vDdtYvorzka>*!)*R0boT7s;Tn3t zcYuA636*r=HUSdnU{6{CMay9P4;CQd6l<;$->Z}q*?A>+RyD({bi~S$3zA)S3-_hS z^T7n43&AWbkUTZmn{Ty7@yAc%U^c75JKT#YyP-j}g$D+*xN8YxJpJggt84f0puq1_ zgTeL(Ly#B~+<^m)>Ooc58N#`6`w#tXw@x&nRg*O=oM7?SBty(zvssDB`B|X&@Rmne z{YW!lc#(%L9!)T$mMi0lMHCZzs3CGTrQ>pDhYi*9Vdtcz59v&LmYf%RRX{+{^&1eW zxCR1++5A7$AmFBg0G)$XR@K!JU|(J>thNCQQPZTuj==UTfLWT#@Mlh!8RBKTxuibJ zn}<$)0_e6c{axhq= zE5`!_NCRUd=1PQ1SezrI%xT)j9v0BT>m&q zrFcAR(O|}sT5s+H%W+vnxt!WvaZzXA!Ahc$Z=<9-uy*&U&Gcgq{R>+M^|OQenT&O? zG*8G-Th`M+;=4$%rNZzIE|ke{GChRF0`GAr7niCO@4;H*QTwyy2A-4huPu{7HnVRg zKTCw9zqebK!=2coUxK5TrdNA`+r)whL!p`!-}L)wr7Z*Q!CPIk~n1fw#b z)WZI;ax`KC-Zc~QSrqCh5&TY087XvO!?2-~^LOlZlk<-al@If|Bm8;BkEFq9SMkk% zwIesyc?Yi|FAL2NUB0!*pF_2M+i52kuUQaLw$TNlUXxk5_yq@v+o- z_Jw?bdak`AyJ>Kbe~!h@7}KG%-m{|*PY-!x4@QV@7T=4vl#vc_=93#9NYD~RD6MuT zvQW>GFNGMQ%RQKOJM`G3Ies?R;`QT`e1@A`p~xV;q<`JnUc5D?U_*Cg|KAprzLH8y z|7;RtLuZnCVq*^G7r_J2=}r3Wyc$=|stMK<%*-ugo}m#(Vz~|Ro|E@;d9r;S1@}dy z{u0H;!NB;}-krBx+16yw^jB9$|f3^OuW*AvHl5v?+XMy@yU=|~+&#a!~400A$ zS+~XrD?fFUYTith=)8PpVAN)t;xcQxTt%OaSUMv{aN;_(o_;tz)eaoHF%3j4Gms_u zh@P3;XXe}T*F7*{7lws#$yYqn$!N~R5M|F;MKH$)?rO|EUW_4EYKUX(@pG=>zu zbbrA9s&x?w9oYgQvR_>`LsI zd4z%po4$MyS9%eB&m|Vrd#w_xB>efFH50fYHy^5;MSK1n=Tm6hR(WjkwB1Sn69e?$ ze)8X2sEV|+1V`BTU|I1rr{D@qmYXaBMqmxTADM<67;UVc4u&Us#PGGWw50IV@#R@p z%|0e$&JgZ2SOWPxu8h;Kbbq{?#Q)j>5$xF9`d*fImdSt*d5O+U9QNDfP=rs))m!VR z*#Q3^eqWyi8YmyPIGQ}~J&V!d@2)_UHwLmqSpJ1KFwEfi#uiX_IkyGV7m;`PAc@&- z-uRYGgTYgnedJGoj)y;M9OgD^mXiZ#!in$OJJrW(rNTdv?mr*xAFZsdZ|YxZ_?!7& z)@NsD%^+^Y`5b#UEoAI{`AY_W=^DbNx4^{7M7)VW79FApjW*x#Afk6&tyQNRDei{UPPJSx4t2)l#7Fme2}o%XQb zYj$S4ZHt@yJZ#{qq9nrlcq}w7yQMa@stE7fmN_SS0=M&^{V0|wUL;H)qzD1O^*82! zaYgq@Qsk-64vcghLIRFfm@t0G(!W@yoW;$vDMH;9-cr0=4NYK49aT|zv9fYxnnSS1 zxzAiADf0#yN35|hIs1RBW5nWSubYKYwEb+PBxZ8pVd8r;=65?_T_yoIL+4;)MT;F}(}v$m_p74W>@NczfD(Gx%lw<9WB&^r0nqhd}~srmjP zwZs~aGKZ@>qRMa@6VhouQz~@<178EuWpW$o;;Ucdgq}zikyPq4qAGC0q&t(l??C@n zH^oKyU&J<7+fpf8Uf>72JYDRBNh=TMCxs~~d`;(PYge$2YS2-rj&Vwd>Z4O$|9dH* zh1@~G7RMd6vsRHWVPyKt=<{p9;SWO8p|#K2$28H3FA=y`j9J!>`+45;a!{#dK8$v! zY6DR(VH+Jn1gZsJ_UEo`A9rs8g&UvKn`nxq2vre({@Y!nVHM2OUnWCOb|u!zW+BLwQ|*d-=~_ z$VNBwAp)hpNO0!AwG>b=vJoT%8h_$d&Q_p;m=bxkV^;G`DCpU>+YiY9H)lGuXoX5# zdV2c7*{*#z85*sBT0Kh1i#y}wAypunhWNYM0M>W#R{W^PW{K*y=!JlYl*Pp zi2Kp-+N!j%fy~#A$%)g~Q-!kGz@{6@`RLazJ{4`8(Z!<%*IP=IIx$%ja8cot`KQEv zeX9&70tEDkIX#gKq|TfJT#eWoLHzod6d>!;snz}ew<--9kt$^(W%C%0st`K@qpOIA zrf68K7?kXZ8$4Bh$0?DlMR&aV$KzqQ-B0BJ7-ZL&N53Fb$t9z-#iO`Pg+tTs(`ZLVJM5#4CQJS?ky>@7=qtxAD9ElFACU4^y%!p#Y+M2)_`zds51BUa+A zBf_-vMnzVjiDbCj)i_6OEi@yw-9G72w_W`o?QY%0h!P}jd=xw z(PK$mu&!KJdS1YX>7=*x_DQW!!)vS@=9Y17*iBa?>ta7E6>_WE0iM8C-S)= z>yXQy*=`BXDe-jObiGpcS6fF|4&m!W!a?Q_d4*D-R3f;FuDgt1uj@4JFk(Iq-;K8r z@BN3RZ$bCg;&-(ZXf;6r#d7D`{NoGI%uDtpwuIv!xZU4sGV}`&89ITah-9hN;a&*t z@B}}TM3t#_jQp9XVquylYHja4F$g=~d7PJaUSE?umwWwT5#4(xzMGydCali=b}AxV zdQfK0l+?TrpoTJ?`m~NzF@6HIkg-;Pn6Hh!mVY%};Z9@P;?fAu$sfD3hko{VlUqeJ_FB;==P*2; zMw7Jww!82gVP7X@`eh8=-jPi_Bvu$SH6;sKKqC!y6EoA94 z<5=?Cm*=iHyvKvjTf-)3)EcSm)ExTgn$?7&5%yNY$?Ce({bW&=6i790KJe6o*K;;g zu}FZb$Q8mothRe{oUYcr&*Yw`0=w$~wO^Xi(mkx_1p^&%Rc9vsxoK)t5U}!PqbqZ> zkC&jv@IJ^x)Mf`-=!2*h{*T&+*bBBTKk5Y|6FQjDEm^bgHcOg;;J6sw%B_94!F_Z} zG}xgPC#Am!`Bj#w=vEkO$)l(^UU|IP4vFip8{#A|vG}k`%A37r%TIl=`6m8@n{{bB z8WS18OJ&50%>GkpU?^kqys1h(2EIE$7C?s}XY(4MQ%4`u5uF8FPE(&h`OIsky%DK! zZ$cWhwtbt}0&YD;)0ce>V5s3Ql*6#k+xh*$(NZN1wjWl%@XBz$`gP2Wse}x3WrVlt zg!4H}hmbyW4AQ--_C4x1w6=bk0j9+9K|C%*w{t}W!9FGL7eqwqK7p=`IhyQyfIxQH zkb!lPRNJP3w81$93hBR)XQ~A|ZT__&Y^s z!~|VW)FwW%J!JwyRw@t3aq=&TcaM&9-0_H46!j63jcumDTGl&Qv0p}`PNgoD;ihRQ z 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

    JJLZp)l5)lv~++&sXC=ILC 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�uzL90y_`p)sGxI=kNif4@Brnq3 z85#%ezT{sCyvehV*!z>8Q=eR2LhbXvO526c1Gv=nx7m2P$iO{1TQOu8x0Dit;X{Aa zs-4ieP^FmKJXF*B^x|RJSqj%9_I1=U%FGGd))GchXJYK7SL8Qq26~UM@6Wwrf7^^> zBki@953RU?B5v7f(7MCv$G>xOhdGT8Bxw}PrH61cq@};kEoR=kr&w?I`AOb}G!Z?; ztMw_zC~j)}BW?mYR~Ee^--{KkE?G#FZA}RAe7EMSP-2utWXEic{^jTNKh)dDTd9JQ zDZhMEsIIZFPFyy8u8dEgx0idwqZAbHJX*02KFpNbvz;sa)tXGZn-+C8r1ewB&PFmu zh1}%UO1w}hd9-oGe9?vRPZ}A0g(>pBIm%mWgd!Ie*G}qw$lICNOUn&<8Ia*46{EX2 z=W@ZhqQF0ur-$ z-kGe_`S!Z+6lv?dnBAf=7|EE6Oqi>Gw3>FT@P^)km3wsToV%Pdqe&wDa)YijP0pQN zx6{4rf#t%`#)S_3MIHUGjiS!|F(zW4X&QSre=zS{+_9H3e(rj=RiF@3+vuG5<3 zL&ZDI=Fg{Ha2w6ziSPAVjQ7ZNJK3=IzJPRWjz{M6^i=i!)>Z+pBFBGPL(221++uqt z)pG-FGHtP6dEzbn*W!HgxOLl|h9pFaF6}KCwuI+pt4v~=ob1^HocAn6O}ea`ht^eQ zcWsnxf5$OIZEW_DJ^#jCtDHXn$CdrL#+AA9?{@b31*DBUikk@4&wpYQ$yt9hT!`i^ zG=D^aH8DL${Z?R(3djAQ1jz=C{P0|KU^DHtx-QQ@;(WeD_T$sch&jaWRDIXK$8c>*BOUl+Z|L?TUV5_snqeF%qB5F)HextfMfR zF4$EEs$I}Z>|!VeaMzusukb%#`C4k7#y_1!{$=yl9Mj5tKE5flCg#q%kX4jjR!&|C zv`vGfvU4S?TlKjH@8=3xIoa|2EUr{0EYMJsN1Z+|lc-ld;#fLn@GzQrD|h1JXRXQz zidf9(oliKHW$EWIC!_1g^)HRGW-l{wgEO;&gKxY8#Y{%m$k&FQ;P0X3KJR{ECATst zcD5=q*p{+ZB3b2)?6Vy%3@u+V=jPpuUoNVMXXp1`n9-)}4UDU=HCVkuuHjnr3E4SD z3qe{cSY;N+-)f@gJbh4*&{ZaZD9fo2j9FoOb8+N&704ns_1`C z2-J^#86a8!%0a8>{v_mv%+xY#1x~8T$1pik8e_6jkL^ii4 zn~3(cwBK0cv!i(}KR>@+*V`3#wzii$@|>tit4XMZ9bGiCC_yyYwkAG#kz_4nolLLo zrVSG6N~Wi!06Xyp5@B{%ul2uRr=@ZY!rlVZLE#J{ePjmDXjtqKMi@Fsr)%izM|ygC z!njZ3UHcTR)3J7pp~~*})4~ng5c!3}LfgWPRhGX^;3P6uP77~QHBNSsTkHQ_uuk82 z=PineNQFXetqPK~hPh$UHLRy@2Y)2$?WJ9W)H*CB3kD$iH<{NFL_ifs{^HR*9!8omqY8#ulahu7XMVF8q^eF1e zYW={v=SOJ%Y9dRF&XcP=Ta#K)1CZ5s`H5vdOPoZw_;*@HD_yPa@^?6V)oHUBSjF}| zzze=$f=Mu))dZfkb=~cG$@#(=s$A1RyqvmnPJ3F)P@Sky=4mILSfiXOS9h89`@-~Q zXpM(hn_4kdu)XXV1OLn`;H4czT z7b>TUVXe z=IhtrJV_r7P8Mx^BfKPF}bef{Tdl zH`slSDTLoHmNxT1d|jOhUrM;)Lfh#$odYEU9>ErTDMffP!zAw5F|J7J!4hxNUdIVi zDRRTr&=hwZI%MuS+PHgs@-OQ70XyT_Yi!S4orbMrs$899nALLhZl8`c$xhW@=8JwC z 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 diff --git a/docs/user/_static/cart_layout_settings.png b/docs/user/_static/cart_layout_settings.png index 2cd8ac801091ee46d437e89dedce7a02d2648b4a..1e83d5d05f5469f3e8d24e88d5691dcf877a1637 100644 GIT binary patch literal 45692 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 bv6lrl_3=@D;f8?56rZ%?DMP delta 8247 zcma)B30#zQ`hVtl=RQzw1RO5qzQhwe5EMZK=UAb zy*PhNq@y$e23((Z6mVOi_|($ zq^s7F{7vjv*h!GTNhG-@(!4H_PMbtR*ixdvfg~iHBhmzsFklAJg#9E`f?r^RNDIf3 zF!K;miVV(vqJ^ z*a;6+PbFb@f1!#2RMMSn~ zB3)fYVI2$L{w#{;o+u5a__ty}3n=q3f1<@lD9@2hG{&FumcYUht0?cGW7xk)1y&1D zcp??NxQ}S`0xCH13XxS$g#~+vVkc1HBiJ8(nMw+d6Qw6nNfqvmcPCKk8wU`-FR1M1 zD53#{RN2ur3enS)_Puaz>{usF_YO^HtV@A~8Lz45hm8gHQWUQnD4F4r5WKR-J zJ1nu5zD+c7o5WeVm1vSjvOxYTQG`>n;Z!wI)<%(*UzEHNzLRKZr{w$taw5|t$+ZY5 zI(LiY$0uM=(PXLQEgkmXkya%^&WwxFN!u&j0NqBZr5!0(yja?*g@t)l($)4)h!#$h zKKRKRqSk!r`U_!1zEbHEJl5mClHbH@ z5Uo~8PfoTG^^r<1WkX^2U!@YB zM6PABWG_7_8?*_Ym{KImyo4H1dqtKL97mK=Ez3RsEm4_Mmg_$MJszHym0y0JNKq)8 zZG{Cfhh!^0gJm`6WNYtN!_phF4N)%={>V0cyaef5B2w!#*&aek-kY-QH=g}sBZial`?rEqWHj(6ugGh7Figejd z`J&KxqQB(Jmp1$ejYrBiPJ&|UkK{WyE+a}Gkj{<~lL6D?Hp|HdW@|LwkOP4d9>||9l#l68)LSA{eFY1{P6*~zTZsmL zDAENMq4oq6i2aK&%LSJgT^H>8ICx(W8m0##yP`zuj=U*2H=t4I<-)x>Fc@MI>B5JF z`K8fBlP?Mjr2v(Ev(V1zoFt)rm0INJCmgTAeD=IX6VQe7MlT z6?Q}m9Xmlo&kF0Z4aoode-<7;-ieqV6}pNuh)UXpch&;UbH@mO&%(i}mqog!N_aOP zF|1f8TnU0p>%$d7MlO-Lk3u<&qq|0-`-pqer3g3!#X}Pmft3#uJ#b7BaswXgwL=m5 z!wA&;K?*PU`sOP74s#;^GY%{I-dKXd5~PS$g*ru^3HzC= z6_X|YaAj}BjMpQHW@!{HL#!yVdld7I0h|F+g*(s}Y5cKb*?ZMcY_Vef_c=uVqTs+sy<1ybhqNw=dqsnmg4xa&!F)m ziqjcjw9=z^Hx!DDov3*4OF8m-o#Ko4wgH4U6juiCB^nr|^o_u~^0d--=sUpDRi*zq z+*{P8j8CXTmt3sO{7{BUyG1$lF!&T4Rpz+UN{LbfmE)oe$k(rwRwqM&=u$r50}GWg z%8renAof3tbj~ZvjyKn##=oWP{5%O3eWCo@mrkOdLzGXHLZQUb%BK|YK;Q-CQysWB z{dMKjr=gJXRpm3I<`E4GQad1#a#yN)o&&0FZ>nOJz>?(Cs=j`(Gf-Z@0m75&vOXyo*A}TQioNKH7PZ}c9R*6O{>y<^ z0JcRU4Pxq!7AR2pi+c4Agrc!fy{QTytn8zH@&Ncf;7}hpTLh0*M5zz&=|o!`sD5?C z6<9P!eL8zEQEr|3%_IoSKcha&Cz)VD{dqb1y?>WT$DUW;d={34>=J41cJ)mU-rFZ> zgd)V&m90tM0as^#rAhG#g&-eI%6m{GxlA*v9Tjv~g~t4I2J$~6R#QDlPShhxqzkrd zY-2w~F93ZJ7Ylo8YOLFkX7e>Q3#$>T6(SuqNaMH;516iMoWqV{7#*N-&V%a{Ue(<5 zCMKr&c^Xz9gLeCxW=m#2^pr5o6FqT0e}iWGdhm~ZQM0G;3*>)oqUQM)zVNl?`BRU; z13sGl3J98N)g0&mHbSmyPIUMI^%l+P)YA~OSM&Dhbey}cd4E1N>pfXqMcQ_4s zK3W^6fI_8nwV8TfVBiOlO6F+uw&9$#Q(L0n3dWCX%?*c9>1JsyIUPhfjao~wKj`CH ziLy`g|RNo zh%W7io1u8WkG0p5R-?~9ul;`BPv|idb&B<)&|{v}1@!t1bHd9ycVH+7!zNwKA*5SF zlrFIy42B2k5?5Wp`Wao~F3{oAbm_19158d``Wvt)djRM*G@}P}=~wvsZe9OvmN23bH^gLuI6bh1kBLQ zYd(Zn`sg0=>q0M>pj+2-Cj@WRtqbiBdP%n~9q%K8bnDlzf`A^nEy5mv_PlP3`9q*x zuG{i86f~)II}A|ZfgZXSe;JASVY%)l=XG?whjqtx*ibhn=-znk-$WfZbr-wfxk~pM z-L(@y=klw%pISXYXOdo?i2_qHNT2EZ9X#@-J}Vz!jQUMKa^X4D^frCP5hqG&hDb*) z*IUK|gp1GtZMTM2CR9;!d0sKB)Rh5lIEUy+ty>yI7J z0)|@kukzZTQLjJ#`uE6|hxA{q$v{20sQ=oP5A+8cqRsvAQ9ITUcMg@X;$6dlUk@Pv z2Msoi*@X|cq`w)8hMz*gTW*+IilMV(fnlZu3MlO&ot$d0pZo-#+HJV!912u?jA7x) z7$}ltID8(UoHx{POb-D;MTXa30491=8Quv4pIkq~h2_DBeXHTaY&q({Ji|9e(CUMR zpRTkc|K-Px8jkMr;l@7JGJwTwOzjN|3UtP_4;nFaE;f$XQb#mnrLlYxVz~NEV?`j& z6%-n4zF7j#++(aQlETG7##z;m;as_~e%BU&Z?|zy$)^~}h8o+h_@dS`pEaREHDT$Gd}^Y-5j*a3R~$=xUb8P(uQ!9Lr`Lb&?$v$Jq29 zvHz7pr`|w?U~45CzsE-OjMWEFnO6-5UuAQ8+9k1U`K&PZ+$_I1zfxOGowM2IuvMF^ zF53`O!_HsrW$&Rm2sR23kK=q~rSRPvV9P47HrAc^F=HK1tbgCd?Bdj3p7;A6lrnp45ZfOg#D;E)U_ZrAG#YTp zMl*5P1>ZM&W+v>Cu)SdcEFtM3)}A(1ql0~-rR>YJiM{T;7B<#+J)-f3*^Zc+*#wzQ__2YCxa24^L9)%e%5`w!ol7x6j(CS7w~bY&mU*hu-<1s?_It;A znR1gvH}GqXSmROTLby`hj5rExtaZS!p;`Ykk47l=M-IOZcs(!x4z|%;FMsaHTk_fE zf$_4QRKkvA1hb`;239zz2Yb4*FY_PV18G-=bKP3qee1xW1aRcLqP)BJei@W%th*&= zCa4X;Y1k6Vss;}XOs6XF6L&4x;Q?~MM(lfz4*p20)p>Kd0b6GFY?cX0F&<7Z!_VCR zTxl!&A}hkz@6YR==%Gs#-fS6_>*wj4+fOAkQ6aPY2Mb`0j(TV0|Gs)vnjcb!w>T)nMLJdiv-AT%4w*>AJa=Qp5c zIb2Q6rfj?2%O!dI`k>z*QkR%rPFwc@&r9QLC6a75qs(8{eW$q0#}irhgCy|x7gc4= zjZNNrX%!|GQek19ii*;7s(>*Z@VqQ=&~pQ8ya~tC(T?}KBF_!Kt#5Dz6q#wRwwYpU z?2Q()-NYT|xmtlzc)BV>tMMY5_ktScJ7Fr`M*vUtF zHSu7ula`pc*t;qm*Xef2x)q8R4ic<8S-3R=g{7F^ymjAf?GvJ-K6m=mZG9DevMnsT zIuPReK$U6^Qg*@`<$WK03n{-@qtpiM7sBi|mQ+0@Q1c&Hp`m#>`P|uKhpImb4nck7 zwMuj;|C<+QV3QrTB_Y{({pG87Y94p7o%zj-&x^$_FZEVPc0jwkQ@j>yy!7+i!*3hp zms{twnjO`q=31M{T5E2ou{npc&DDOA5Efn>!oDjCWsg@IC82l=ypNj@6xgBFlj2I)vv{4_96O&lsa4@?W*#j%Pb==@{em+_VyoKHJOA=3R?qk*?^06nR$}i7PJ4F_oB`YloO(ZF9}D zo9ujIOKHMNN^@h1vvIbqxwfvMCNhq-v`?!okeb zr#qZY&;gSuZt+^?KpMHc;1I_KyVu}mO1yoe*6S2LXYo5Ybz4$GPNU0S?TvYp*@5b3 zcGS3Lb41uNZzhKE9j+<>($LHYPA%MpnaVpdvXU(}_uUKLiK_BWQG~!&xX#=D>e8Ry04F z#kWUGa#(SD1Uosu*gI|xU!YG*#R*=ny8X{lz_BWp5I)ZFE+7tlb~MOj7VPf#r(1Pi z-2As&?e2bo$$yZ=i!b^ZJLnE#NsH9%x;w5{`XA14*$p_wbE^BC^}d8Z@w!z8Zl5`G zUz~5BKk!1iW^3;AWmo2FSlPmf-qOlbsF@vH*gI~-{~KK1)p!%&*^k10OGc(P+*_Kx z-eQ-E!q`KLBGBLR@A!hfvFMby8f;UX3dKS z>&1ciR^wrZ7Z1qHx;+AOOL@KFIn(_lmPFp3xWD*p(1v}NHz%rD|0S8ceX~tV^yt>5 zu%ZUg6~}O%QGAT=E-Cyz&!lG0=SyN`9@kQ}62o0KF!8&w+p}VIgyg|Ba*2z@W%Oca z*7Q;GuS$-!HJ!a!CsI zy8woV);7Z_cRr<@?7p@@&$Q0IQne2}!|`WhtJh^nBH6PWeOd2EQ`ln<`?Ed|-|U-+ zYaHR?SC{zd!SOC)nztREzBX3=NS1fP=HGn0J+^_mpgV7?CG6!#5_^XuPHnhph4=Vm z#J}<8f$y!Zz*Or~i~%(9?t7l1^>#^qp*Q4Woff}X#-IpSV6_bFE#6?6@q|Koi=7p3 x{PUcTb38Z=P-Qm$$4I-eNMhn9>iL(F18ZFT5O(jzfBFZ(5r4YjX?ZkI`F})lTkZe= diff --git a/lisp/i18n/qm/base_nl_NL.qm b/lisp/i18n/qm/base_nl_NL.qm index 0381cba418fa9a73e6e9bfa581016254e42132ee..47eba3e54a1f4bad17937a7e512791a53695af0c 100644 GIT binary patch literal 42639 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 Y+5;to>{OL{X-|^ax#zHW`;kNc6BZodPyhe` diff --git a/lisp/i18n/qm/base_sl_SI.qm b/lisp/i18n/qm/base_sl_SI.qm index 7be13f54197206af5889f5714905175dce736b84..f28776bdc2a47c85b23c92973a82c7ac93c6dd06 100644 GIT binary patch delta 8665 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 IdTZSO027h>iU0rr From 70b8283d98cc5f4def10035438feb4dfc5b34d1d Mon Sep 17 00:00:00 2001 From: s0600204 Date: Thu, 6 Jun 2019 16:51:05 +0100 Subject: [PATCH 191/333] Possible fix for issue 180 For some reason the `QTreeWidgetItem` passed as `previous` to `__currentItemChanged` retains its former index (despite the fact that logically, by the time the method is called, a different `QTreeWidgetItem` should reside there now) but appears to have lost its references to its "column widgets". Thus, when we request each of the "column widgets" in turn, they can't be found. When removing a row, we don't really need to set the styles of a `QTreeWidgetItem` that's being removed... so let's not. As to moving a row, the `QTreeWidgetItem` returned by `self.takeTopLevelItem` in `__cueMoved` has the "column widgets" we're looking for. So let's apply `__updateItemStyle` on that instead. --- lisp/plugins/list_layout/list_view.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py index 34822f9a0..9dec87656 100644 --- a/lisp/plugins/list_layout/list_view.py +++ b/lisp/plugins/list_layout/list_view.py @@ -195,7 +195,8 @@ def setStandbyIndex(self, newIndex): def __currentItemChanged(self, current, previous): if previous is not None: previous.current = False - self.__updateItemStyle(previous) + if self.itemWidget(previous, 0): + self.__updateItemStyle(previous) if current is not None: current.current = True @@ -249,6 +250,7 @@ def __cueMoved(self, before, after): item = self.takeTopLevelItem(before) self.insertTopLevelItem(after, item) self.__setupItemWidgets(item) + self.__updateItemStyle(item) def __cueRemoved(self, cue): cue.property_changed.disconnect(self.__cuePropChanged) From 672aa3ec21312d6d7683942f4130f9f6358ebebf Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 12 Jun 2019 16:01:51 +0200 Subject: [PATCH 192/333] improve selection, fix #182, update dependencies --- .circleci/config.yml | 6 +- Pipfile | 2 +- Pipfile.lock | 146 +++++++++++++------------- lisp/application.py | 3 +- lisp/core/signal.py | 5 +- lisp/layout/cue_layout.py | 4 + lisp/plugins/list_layout/list_view.py | 56 +++++----- 7 files changed, 116 insertions(+), 106 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e10bbf79a..20d025828 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,7 +24,7 @@ jobs: - restore_cache: name: PYENV ⟹ Restore cache - key: pyenv-python-3.6.8 + key: pyenv-python-3.6.8b - run: name: PYENV ⟹ Install python 3.6.8 @@ -34,9 +34,9 @@ jobs: - save_cache: name: PYENV ⟹ Save cache - key: pyenv-python-3.6.8 + key: pyenv-python-3.6.8b paths: - - ~/.pyenv/versions/3.6.8 + - /opt/circleci/.pyenv/versions/3.6.8 - run: name: FLATPAK ⟹ Install flathub Runtime & SDK diff --git a/Pipfile b/Pipfile index c4197dd84..cd7c700c9 100644 --- a/Pipfile +++ b/Pipfile @@ -6,7 +6,7 @@ name = "pypi" [packages] appdirs = ">=1.4.1" Cython = "~=0.29" -falcon = "~=1.4" +falcon = "~=2.0" JACK-Client = "~=0.4" mido = "~=1.2" PyGObject = "~=3.30" diff --git a/Pipfile.lock b/Pipfile.lock index 21fb00a72..badd7d620 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d791d363b62c9b211f8117991823e0bb53419ee97a9601429b6dd003dc875a58" + "sha256": "9cc5551cdfd52de589fddc38882ec9053b982b431f2e5f38eac4727b398137e1" }, "pipfile-spec": 6, "requires": {}, @@ -71,45 +71,57 @@ }, "cython": { "hashes": [ - "sha256:0ce8f6c789c907472c9084a44b625eba76a85d0189513de1497ab102a9d39ef8", - "sha256:0d67964b747ac09758ba31fe25da2f66f575437df5f121ff481889a7a4485f56", - "sha256:1630823619a87a814e5c1fa9f96544272ce4f94a037a34093fbec74989342328", - "sha256:1a4c634bb049c8482b7a4f3121330de1f1c1f66eac3570e1e885b0c392b6a451", - "sha256:1ec91cc09e9f9a2c3173606232adccc68f3d14be1a15a8c5dc6ab97b47b31528", - "sha256:237a8fdd8333f7248718875d930d1e963ffa519fefeb0756d01d91cbfadab0bc", - "sha256:28a308cbfdf9b7bb44def918ad4a26b2d25a0095fa2f123addda33a32f308d00", - "sha256:2fe3dde34fa125abf29996580d0182c18b8a240d7fa46d10984cc28d27808731", - "sha256:30bda294346afa78c49a343e26f3ab2ad701e09f6a6373f579593f0cfcb1235a", - "sha256:33d27ea23e12bf0d420e40c20308c03ef192d312e187c1f72f385edd9bd6d570", - "sha256:34d24d9370a6089cdd5afe56aa3c4af456e6400f8b4abb030491710ee765bafc", - "sha256:4e4877c2b96fae90f26ee528a87b9347872472b71c6913715ca15c8fe86a68c9", - "sha256:50d6f1f26702e5f2a19890c7bc3de00f9b8a0ec131b52edccd56a60d02519649", - "sha256:55d081162191b7c11c7bfcb7c68e913827dfd5de6ecdbab1b99dab190586c1e8", - "sha256:59d339c7f99920ff7e1d9d162ea309b35775172e4bab9553f1b968cd43b21d6d", - "sha256:6cf4d10df9edc040c955fca708bbd65234920e44c30fccd057ecf3128efb31ad", - "sha256:6ec362539e2a6cf2329cd9820dec64868d8f0babe0d8dc5deff6c87a84d13f68", - "sha256:7edc61a17c14b6e54d5317b0300d2da23d94a719c466f93cafa3b666b058c43b", - "sha256:8e37fc4db3f2c4e7e1ed98fe4fe313f1b7202df985de4ee1451d2e331332afae", - "sha256:b8c996bde5852545507bff45af44328fa48a7b22b5bec2f43083f0b8d1024fd9", - "sha256:bf9c16f3d46af82f89fdefc0d64b2fb02f899c20da64548a8ea336beefcf8d23", - "sha256:c1038aba898bed34ab1b5ddb0d3f9c9ae33b0649387ab9ffe6d0af677f66bfc1", - "sha256:d405649c1bfc42e20d86178257658a859a3217b6e6d950ee8cb76353fcea9c39", - "sha256:db6eeb20a3bd60e1cdcf6ce9a784bc82aec6ab891c800dc5d7824d5cfbfe77f2", - "sha256:e382f8cb40dca45c3b439359028a4b60e74e22d391dc2deb360c0b8239d6ddc0", - "sha256:f3f6c09e2c76f2537d61f907702dd921b04d1c3972f01d5530ef1f748f22bd89", - "sha256:f749287087f67957c020e1de26906e88b8b0c4ea588facb7349c115a63346f67", - "sha256:f86b96e014732c0d1ded2c1f51444c80176a98c21856d0da533db4e4aef54070" + "sha256:0afa0b121b89de619e71587e25702e2b7068d7da2164c47e6eee80c17823a62f", + "sha256:1c608ba76f7a20cc9f0c021b7fe5cb04bc1a70327ae93a9298b1bc3e0edddebe", + "sha256:26229570d6787ff3caa932fe9d802960f51a89239b990d275ae845405ce43857", + "sha256:2a9deafa437b6154cac2f25bb88e0bfd075a897c8dc847669d6f478d7e3ee6b1", + "sha256:2f28396fbce6d9d68a40edbf49a6729cf9d92a4d39ff0f501947a89188e9099f", + "sha256:3983dd7b67297db299b403b29b328d9e03e14c4c590ea90aa1ad1d7b35fb178b", + "sha256:4100a3f8e8bbe47d499cdac00e56d5fe750f739701ea52dc049b6c56f5421d97", + "sha256:51abfaa7b6c66f3f18028876713c8804e73d4c2b6ceddbcbcfa8ec62429377f0", + "sha256:61c24f4554efdb8fb1ac6c8e75dab301bcdf2b7b739ed0c2b267493bb43163c5", + "sha256:700ccf921b2fdc9b23910e95b5caae4b35767685e0812343fa7172409f1b5830", + "sha256:7b41eb2e792822a790cb2a171df49d1a9e0baaa8e81f58077b7380a273b93d5f", + "sha256:803987d3b16d55faa997bfc12e8b97f1091f145930dee229b020487aed8a1f44", + "sha256:99af5cfcd208c81998dcf44b3ca466dee7e17453cfb50e98b87947c3a86f8753", + "sha256:9faea1cca34501c7e139bc7ef8e504d532b77865c58592493e2c154a003b450f", + "sha256:a7ba4c9a174db841cfee9a0b92563862a0301d7ca543334666c7266b541f141a", + "sha256:b26071c2313d1880599c69fd831a07b32a8c961ba69d7ccbe5db1cd8d319a4ca", + "sha256:b49dc8e1116abde13a3e6a9eb8da6ab292c5a3325155fb872e39011b110b37e6", + "sha256:bd40def0fd013569887008baa6da9ca428e3d7247adeeaeada153006227bb2e7", + "sha256:bfd0db770e8bd4e044e20298dcae6dfc42561f85d17ee546dcd978c8b23066ae", + "sha256:c2fad1efae5889925c8fd7867fdd61f59480e4e0b510f9db096c912e884704f1", + "sha256:c81aea93d526ccf6bc0b842c91216ee9867cd8792f6725a00f19c8b5837e1715", + "sha256:da786e039b4ad2bce3d53d4799438cf1f5e01a0108f1b8d78ac08e6627281b1a", + "sha256:deab85a069397540987082d251e9c89e0e5b2e3e044014344ff81f60e211fc4b", + "sha256:e3f1e6224c3407beb1849bdc5ae3150929e593e4cffff6ca41c6ec2b10942c80", + "sha256:e74eb224e53aae3943d66e2d29fe42322d5753fd4c0641329bccb7efb3a46552", + "sha256:ee697c7ea65cb14915a64f36874da8ffc2123df43cf8bc952172e04a26656cd6", + "sha256:f37792b16d11606c28e428460bd6a3d14b8917b109e77cdbe4ca78b0b9a52c87", + "sha256:fd2906b54cbf879c09d875ad4e4687c58d87f5ed03496063fec1c9065569fd5d" ], "index": "pypi", - "version": "==0.29.7" + "version": "==0.29.10" }, "falcon": { "hashes": [ - "sha256:0a66b33458fab9c1e400a9be1a68056abda178eb02a8cb4b8f795e9df20b053b", - "sha256:3981f609c0358a9fcdb25b0e7fab3d9e23019356fb429c635ce4133135ae1bc4" + "sha256:18157af2a4fc3feedf2b5dcc6196f448639acf01c68bc33d4d5a04c3ef87f494", + "sha256:24adcd2b29a8ffa9d552dc79638cd21736a3fb04eda7d102c6cebafdaadb88ad", + "sha256:54f2cb4b687035b2a03206dbfc538055cc48b59a953187b0458aa1b574d47b53", + "sha256:59d1e8c993b9a37ea06df9d72cf907a46cc8063b30717cdac2f34d1658b6f936", + "sha256:733033ec80c896e30a43ab3e776856096836787197a44eb21022320a61311983", + "sha256:74cf1d18207381c665b9e6292d65100ce146d958707793174b03869dc6e614f4", + "sha256:95bf6ce986c1119aef12c9b348f4dee9c6dcc58391bdd0bc2b0bf353c2b15986", + "sha256:9712975adcf8c6e12876239085ad757b8fdeba223d46d23daef82b47658f83a9", + "sha256:a5ebb22a04c9cc65081938ee7651b4e3b4d2a28522ea8ec04c7bdd2b3e9e8cd8", + "sha256:aa184895d1ad4573fbfaaf803563d02f019ebdf4790e41cc568a330607eae439", + "sha256:e3782b7b92fefd46a6ad1fd8fe63fe6c6f1b7740a95ca56957f48d1aee34b357", + "sha256:e9efa0791b5d9f9dd9689015ea6bce0a27fcd5ecbcd30e6d940bffa4f7f03389", + "sha256:eea593cf466b9c126ce667f6d30503624ef24459f118c75594a69353b6c3d5fc", + "sha256:f93351459f110b4c1ee28556aef9a791832df6f910bea7b3f616109d534df06b" ], "index": "pypi", - "version": "==1.4.1" + "version": "==2.0.0" }, "idna": { "hashes": [ @@ -162,37 +174,30 @@ }, "pyqt5": { "hashes": [ - "sha256:020e8771ad66429587d3db6cceb028be3435e2c65f417dcc8e3b3e644b6ab1d7", - "sha256:399fd9c3b3786210d9d2a86bc73255cc03c997b454480b7c0daf3e1a09e1ab58", - "sha256:c0565737d1d5fd40a7e14b6692bfec1f1e1f0be082ae70d1776c44561980e3c4", - "sha256:d4e88208dfd017636e4b1f407990d0c3b6cf47afed6be4f2fb6ca887ef513e4b" + "sha256:06cb9a1ea1289ca2b2d81f9a2da44a05029d5b4d36fa47b6a0c0b9027ff03fef", + "sha256:44b263f395045eb5bba34f4df2d269e07b1e073fd359645b022943dd1accadfd", + "sha256:bb4ec5583baa18f741ec6c229489dc1910290d4ce4d6756a2ea062f7bf6456e6", + "sha256:c168a8883bbe7877809c3239c5dcfb9e8de5fe7e8e828c8add7e4f77cc8fc02a" ], "index": "pypi", - "version": "==5.12.1" + "version": "==5.12.2" }, "pyqt5-sip": { "hashes": [ - "sha256:0aed4266e52f4d11947439d9df8c57d4578e92258ad0d70645f9f69b627b35c7", - "sha256:15750a4a3718c68ba662eede7310612aedad70c9727497908bf2eeddd255d16f", - "sha256:2071265b7a603354b624b6cfae4ea032719e341799fb925961fd192f328fd203", - "sha256:31a59f76168df007b480b9382256c20f8898c642e1394df2990559f0f6389f66", - "sha256:39ce455bf5be5c1b20db10d840a536f70c3454be3baa7adca22f67ed90507853", - "sha256:5d8a4b8e75fa6c9f837ea92793f1aeacbf3929f1d11cdf36f9604c1876182aca", - "sha256:6bf9bacac776257390dd3f8a703d12c8228e4eb1aa0b191f1550965f44e4c23a", - "sha256:87a76841e07e6dae5df34dded427708b5ea8d605c017e0bb6e56a7354959271e", - "sha256:9f0ad4198f2657da9d59a393c266959362c517445cace842051e5ad564fa8fb0", - "sha256:cbb0f24e3bfa1403bf3a05f30066d4457509aa411d6c6823d02b0508b787b8ea", - "sha256:d38d379fa4f5c86a8ea92b8fab42748f37de093d21a25c34b2339eb2764ee5d5", - "sha256:f24413d34707525a0fe1448b9574089242b9baa7c604ae5714abef9ad1749839" + "sha256:0c05e357c8b781458cebb120e84aae0dad9ffc9a8742959e8564d372c1a4fb9a", + "sha256:41c1ff4859ef9b8b7db4c6a09229471378dfe7095cf96ad2c1ea4d6753340d20", + "sha256:4db3f0b1b63311780158d467cfb61be0fbf189141dd5f5ce597e0faeb8f227eb", + "sha256:5e8bcb8bbc3b83d4d6142d2c3188e06bd6062bab132e240b8da42fb80e6dd3ba", + "sha256:7caca9ab0639ecc187e716c07edd285da314ad30f3ec729bcc6b62ea62171cf5", + "sha256:8ed141db021103d9c196f6f297e39b3ade1b06f0ebbd3fe7e9101fac6e8974dd", + "sha256:9d5e33850c5566f4e2da9c244e7f2beb87cab980567d4df902c2953308271df7", + "sha256:a373e19e3679408bf0cee0a6591360e6d3383498b1d75ce8f303ab658f1914d5", + "sha256:aab3d79ad9c7b6652f7d740b27c672dc9e6a21311b68f9fbbe33d8fdd086c73e", + "sha256:b62a4aced469e9bcec88450e2a00df31d754c82b97457cd611615807526682ff", + "sha256:c1e1d6e1496a17e025f66a258e8c4011c4da223937789dbf2d0279516c948fdc", + "sha256:dcadf1593b5891ddb21df4ac943d720d9da22be2a686ad73621ca86d1f72dafe" ], - "version": "==4.19.15" - }, - "python-mimeparse": { - "hashes": [ - "sha256:76e4b03d700a641fd7761d3cd4fdbbdcd787eade1ebfac43f877016328334f78", - "sha256:a295f03ff20341491bfe4717a39cd0a8cc9afad619ba44b77e86b0ab8a2b8282" - ], - "version": "==1.6.0" + "version": "==4.19.17" }, "python-rtmidi": { "hashes": [ @@ -214,18 +219,11 @@ }, "requests": { "hashes": [ - "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", - "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", + "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" ], "index": "pypi", - "version": "==2.21.0" - }, - "six": { - "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" - ], - "version": "==1.12.0" + "version": "==2.22.0" }, "sortedcontainers": { "hashes": [ @@ -237,10 +235,10 @@ }, "urllib3": { "hashes": [ - "sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0", - "sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3" + "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", + "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" ], - "version": "==1.24.2" + "version": "==1.25.3" } }, "develop": { @@ -254,11 +252,11 @@ }, "pysnooper": { "hashes": [ - "sha256:340e1b5fd7f6c4268c5c9a2bc3cbff53ceab2c0625b21097f3f63082ec12310c", - "sha256:cc0fed0e162e34c6c83cb7b25fe2059427ca73d08150088e2374a6b1726ff2b2" + "sha256:edfac97c18b0e0b346bb7de02d5c2abfb3bdeeba5a73f3e6ade90477e6f4d7ac", + "sha256:f44105ee9653a299f3722f47485caccd7c4d05c650fe3403c42a0a9f948d9a28" ], "index": "pypi", - "version": "==0.0.23" + "version": "==0.1.0" }, "six": { "hashes": [ diff --git a/lisp/application.py b/lisp/application.py index 182c145b3..07cdbee8f 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -164,9 +164,8 @@ def __delete_session(self): if self.__session is not None: self.session_before_finalize.emit(self.session) - self.__commands_stack.clear() self.__session.finalize() - self.__session = None + self.__commands_stack.clear() def __save_to_file(self, session_file): """Save the current session into a file.""" diff --git a/lisp/core/signal.py b/lisp/core/signal.py index 9743404ef..ed8d5d8de 100644 --- a/lisp/core/signal.py +++ b/lisp/core/signal.py @@ -77,8 +77,9 @@ def call(self, *args, **kwargs): def is_alive(self): return self._reference() is not None - def _expired(self, reference): - self._callback(self._slot_id) + def _expired(self, _): + if self._callback is not None: + self._callback(self._slot_id) class AsyncSlot(Slot): diff --git a/lisp/layout/cue_layout.py b/lisp/layout/cue_layout.py index 4e3eea760..236a6481b 100644 --- a/lisp/layout/cue_layout.py +++ b/lisp/layout/cue_layout.py @@ -17,6 +17,8 @@ from abc import abstractmethod +from PyQt5.QtCore import Qt + from lisp.command.cue import UpdateCueCommand, UpdateCuesCommand from lisp.command.model import ModelRemoveItemsCommand from lisp.core.has_properties import HasProperties @@ -215,6 +217,8 @@ def show_context_menu(self, position): def show_cue_context_menu(self, cues, position): if cues: menu = self.CuesMenu.create_qmenu(cues, self.view) + # Avoid "leaking" references kept in the menu + menu.setAttribute(Qt.WA_DeleteOnClose) menu.move(position) menu.show() diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py index 9dec87656..97a1bd519 100644 --- a/lisp/plugins/list_layout/list_view.py +++ b/lisp/plugins/list_layout/list_view.py @@ -128,7 +128,9 @@ def __init__(self, listModel, parent=None): # This allow to have some spared space at the end of the scroll-area self.verticalScrollBar().rangeChanged.connect(self.__updateScrollRange) - self.currentItemChanged.connect(self.__currentItemChanged) + self.currentItemChanged.connect( + self.__currentItemChanged, Qt.QueuedConnection + ) def dropEvent(self, event): # Decode mimedata information about the drag&drop event, since only @@ -195,34 +197,41 @@ def setStandbyIndex(self, newIndex): def __currentItemChanged(self, current, previous): if previous is not None: previous.current = False - if self.itemWidget(previous, 0): - self.__updateItemStyle(previous) + self.__updateItemStyle(previous) if current is not None: current.current = True self.__updateItemStyle(current) - if self.selectionMode() == QTreeWidget.NoSelection: - # ensure the current item is in the middle - self.scrollToItem(current, QTreeWidget.PositionAtCenter) + # Ensure the current item is in the middle + self.scrollToItem(current, QTreeWidget.PositionAtCenter) - def __updateItemStyle(self, item): - css = css_to_dict(item.cue.stylesheet) - brush = QBrush() - - if item.current: - widget_css = subdict(css, ("font-size",)) - brush = CueListView.ITEM_CURRENT_BG - else: - widget_css = subdict(css, ("color", "font-size")) - css_bg = css.get("background") - if css_bg is not None: - color = QColor(css_bg) - color.setAlpha(150) - brush = QBrush(color) + if ( + self.selectionMode() != QTreeWidget.NoSelection + and not self.selectedIndexes() + ): + current.setSelected(True) - for column in range(self.columnCount()): - self.itemWidget(item, column).setStyleSheet(dict_to_css(widget_css)) - item.setBackground(column, brush) + def __updateItemStyle(self, item): + if item.treeWidget() is not None: + css = css_to_dict(item.cue.stylesheet) + brush = QBrush() + + if item.current: + widget_css = subdict(css, ("font-size",)) + brush = CueListView.ITEM_CURRENT_BG + else: + widget_css = subdict(css, ("color", "font-size")) + css_bg = css.get("background") + if css_bg is not None: + color = QColor(css_bg) + color.setAlpha(150) + brush = QBrush(color) + + for column in range(self.columnCount()): + self.itemWidget(item, column).setStyleSheet( + dict_to_css(widget_css) + ) + item.setBackground(column, brush) def __cuePropChanged(self, cue, property_name, _): if property_name == "stylesheet": @@ -250,7 +259,6 @@ def __cueMoved(self, before, after): item = self.takeTopLevelItem(before) self.insertTopLevelItem(after, item) self.__setupItemWidgets(item) - self.__updateItemStyle(item) def __cueRemoved(self, cue): cue.property_changed.disconnect(self.__cuePropChanged) From cddd6b226f72313d6c6963c0a9fd197dc406354b Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 12 Jun 2019 20:00:52 +0200 Subject: [PATCH 193/333] GstMediaElement are now notified of EoS events, fix #173 --- lisp/plugins/gst_backend/elements/preset_src.py | 3 --- lisp/plugins/gst_backend/elements/volume.py | 3 --- lisp/plugins/gst_backend/gst_element.py | 17 +++++++++++------ lisp/plugins/gst_backend/gst_media.py | 4 ++++ 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lisp/plugins/gst_backend/elements/preset_src.py b/lisp/plugins/gst_backend/elements/preset_src.py index 107d9aa54..6dd007761 100644 --- a/lisp/plugins/gst_backend/elements/preset_src.py +++ b/lisp/plugins/gst_backend/elements/preset_src.py @@ -90,9 +90,6 @@ def __init__(self, pipeline): def stop(self): self.n_sample = 0 - def interrupt(self): - self.stop() - def generate_samples(self, src, need_bytes): remaining = int(self.duration / 1000 * PresetSrc.FREQ - self.n_sample) if remaining <= 0: diff --git a/lisp/plugins/gst_backend/elements/volume.py b/lisp/plugins/gst_backend/elements/volume.py index 755d9efe4..120ddd480 100644 --- a/lisp/plugins/gst_backend/elements/volume.py +++ b/lisp/plugins/gst_backend/elements/volume.py @@ -61,6 +61,3 @@ def src(self): def stop(self): self.live_volume = self.volume - - def interrupt(self): - self.stop() diff --git a/lisp/plugins/gst_backend/gst_element.py b/lisp/plugins/gst_backend/gst_element.py index 5c80f06cc..375597830 100644 --- a/lisp/plugins/gst_backend/gst_element.py +++ b/lisp/plugins/gst_backend/gst_element.py @@ -70,17 +70,22 @@ def __init__(self, pipeline): super().__init__() self.pipeline = pipeline - def interrupt(self): - """Called before Media interrupt""" + def play(self): + """Called before Media play""" + + def pause(self): + """Called before Media pause""" def stop(self): """Called before Media stop""" - def pause(self): - """Called before Media pause""" + def eos(self): + """Called after Media ends. Defaults to `stop()`""" + self.stop() - def play(self): - """Called before Media play""" + def interrupt(self): + """Called before Media interrupt. Defaults to `stop()`""" + self.stop() def dispose(self): """Clean up the element""" diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index 36b685b6c..17920d87c 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -273,6 +273,10 @@ def __on_message(self, bus, message): # Otherwise go in READY state self.__pipeline.set_state(Gst.State.READY) self.__pipeline.get_state(Gst.SECOND) + + for element in self.elements: + element.eos() + self.__reset_media() self.eos.emit(self) elif message.type == Gst.MessageType.CLOCK_LOST: From f402452abf3148b467f848e43a7e63d612a0ff6f Mon Sep 17 00:00:00 2001 From: radiomike Date: Fri, 21 Jun 2019 22:34:18 +0100 Subject: [PATCH 194/333] Fix for issue #184 Now checks both fadeIn and fadeout check values --- lisp/ui/settings/cue_pages/cue_general.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lisp/ui/settings/cue_pages/cue_general.py b/lisp/ui/settings/cue_pages/cue_general.py index 9643e80c8..4dde75ecf 100644 --- a/lisp/ui/settings/cue_pages/cue_general.py +++ b/lisp/ui/settings/cue_pages/cue_general.py @@ -296,7 +296,10 @@ def getSettings(self): if not checkable or self.fadeInGroup.isChecked(): settings["fadein_type"] = self.fadeInEdit.fadeType() settings["fadein_duration"] = self.fadeInEdit.duration() - if not checkable or self.fadeInGroup.isChecked(): + + checkable = self.fadeOutGroup.isCheckable() + + if not checkable or self.fadeOutGroup.isChecked(): settings["fadeout_type"] = self.fadeOutEdit.fadeType() settings["fadeout_duration"] = self.fadeOutEdit.duration() From 1605afb51fd41a26ef4900472820b49e7f055660 Mon Sep 17 00:00:00 2001 From: radiomike Date: Fri, 21 Jun 2019 23:25:25 +0100 Subject: [PATCH 195/333] Update cue_general.py --- lisp/ui/settings/cue_pages/cue_general.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lisp/ui/settings/cue_pages/cue_general.py b/lisp/ui/settings/cue_pages/cue_general.py index 4dde75ecf..6eddefcb4 100644 --- a/lisp/ui/settings/cue_pages/cue_general.py +++ b/lisp/ui/settings/cue_pages/cue_general.py @@ -296,9 +296,9 @@ def getSettings(self): if not checkable or self.fadeInGroup.isChecked(): settings["fadein_type"] = self.fadeInEdit.fadeType() settings["fadein_duration"] = self.fadeInEdit.duration() - - checkable = self.fadeOutGroup.isCheckable() - + + checkable = self.fadeOutGroup.isCheckable() + if not checkable or self.fadeOutGroup.isChecked(): settings["fadeout_type"] = self.fadeOutEdit.fadeType() settings["fadeout_duration"] = self.fadeOutEdit.duration() From 247355a8019526bc9f51f70ad957274279d1e42d Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 16 Aug 2019 11:28:13 +0200 Subject: [PATCH 196/333] Add ability to resize/hide ListLayout widgets, close #116 Update: ListLayout - GO button text resize with the UI Update: ListLayout - removed option to hide running cues Update: ListLayout - running-cue name is elided if too long Update: minor theme improvements Update: improved language description --- lisp/plugins/list_layout/control_buttons.py | 2 +- lisp/plugins/list_layout/default.json | 8 +- lisp/plugins/list_layout/layout.py | 63 +++++---- lisp/plugins/list_layout/list_view.py | 30 ++++- lisp/plugins/list_layout/list_widgets.py | 12 +- lisp/plugins/list_layout/playing_view.py | 11 ++ lisp/plugins/list_layout/playing_widgets.py | 12 +- lisp/plugins/list_layout/settings.py | 6 - lisp/plugins/list_layout/view.py | 96 +++++++++++--- lisp/ui/settings/cue_pages/cue_general.py | 2 - lisp/ui/themes/__init__.py | 1 - lisp/ui/themes/dark/{assetes.py => assets.py} | 0 .../themes/dark/{assetes.rcc => assets.qrc} | 0 lisp/ui/themes/dark/dark.py | 2 +- lisp/ui/themes/dark/theme.qss | 123 ++++++++++-------- lisp/ui/widgets/colorbutton.py | 2 +- lisp/ui/widgets/dynamicfontsize.py | 98 ++++++++++++++ lisp/ui/widgets/elidedlabel.py | 51 ++++++++ lisp/ui/widgets/locales.py | 7 +- 19 files changed, 400 insertions(+), 126 deletions(-) rename lisp/ui/themes/dark/{assetes.py => assets.py} (100%) rename lisp/ui/themes/dark/{assetes.rcc => assets.qrc} (100%) create mode 100644 lisp/ui/widgets/dynamicfontsize.py create mode 100644 lisp/ui/widgets/elidedlabel.py diff --git a/lisp/plugins/list_layout/control_buttons.py b/lisp/plugins/list_layout/control_buttons.py index dc77b6e67..c358fea85 100644 --- a/lisp/plugins/list_layout/control_buttons.py +++ b/lisp/plugins/list_layout/control_buttons.py @@ -67,7 +67,7 @@ def retranslateUi(self): def newButton(self, icon): button = QIconPushButton(self) button.setFocusPolicy(Qt.NoFocus) - button.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Expanding) + button.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) button.setIcon(icon) button.setIconSize(QSize(32, 32)) return button diff --git a/lisp/plugins/list_layout/default.json b/lisp/plugins/list_layout/default.json index cd0477cca..3f762156f 100644 --- a/lisp/plugins/list_layout/default.json +++ b/lisp/plugins/list_layout/default.json @@ -1,11 +1,10 @@ { - "_version_": "1.4", + "_version_": "1.5.2", "_enabled_": true, "show": { "dBMeters": true, "seekSliders": true, - "accurateTime": false, - "playingCues": true + "accurateTime": false }, "selectionMode": false, "autoContinue": true, @@ -15,5 +14,6 @@ "stopCueFade": true, "pauseCueFade": true, "resumeCueFade": true, - "interruptCueFade": true + "interruptCueFade": true, + "infoPanelFontSize": 12 } \ No newline at end of file diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index 0c82cac90..6b368dfd2 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -55,11 +55,11 @@ class ListLayout(CueLayout): Config = DummyConfiguration() auto_continue = ProxyProperty() - running_visible = ProxyProperty() dbmeters_visible = ProxyProperty() seek_sliders_visible = ProxyProperty() accurate_time = ProxyProperty() selection_mode = ProxyProperty() + view_sizes = ProxyProperty() def __init__(self, application): super().__init__(application) @@ -72,6 +72,7 @@ def __init__(self, application): self._view = ListLayoutView( self._list_model, self._running_model, self.Config ) + self._view.setResizeHandlesEnabled(False) # GO button self._view.goButton.clicked.connect(self.__go_slot) # Global actions @@ -93,43 +94,50 @@ def __init__(self, application): # Layout menu layout_menu = self.app.window.menuLayout - self.show_running_action = QAction(parent=layout_menu) - self.show_running_action.setCheckable(True) - self.show_running_action.triggered.connect(self._set_running_visible) - layout_menu.addAction(self.show_running_action) - - self.show_dbmeter_action = QAction(parent=layout_menu) + self.show_dbmeter_action = QAction(layout_menu) self.show_dbmeter_action.setCheckable(True) self.show_dbmeter_action.triggered.connect(self._set_dbmeters_visible) layout_menu.addAction(self.show_dbmeter_action) - self.show_seek_action = QAction(parent=layout_menu) + self.show_seek_action = QAction(layout_menu) self.show_seek_action.setCheckable(True) self.show_seek_action.triggered.connect(self._set_seeksliders_visible) layout_menu.addAction(self.show_seek_action) - self.show_accurate_action = QAction(parent=layout_menu) + self.show_accurate_action = QAction(layout_menu) self.show_accurate_action.setCheckable(True) self.show_accurate_action.triggered.connect(self._set_accurate_time) layout_menu.addAction(self.show_accurate_action) - self.auto_continue_action = QAction(parent=layout_menu) + self.auto_continue_action = QAction(layout_menu) self.auto_continue_action.setCheckable(True) self.auto_continue_action.triggered.connect(self._set_auto_continue) layout_menu.addAction(self.auto_continue_action) - self.selection_mode_action = QAction(parent=layout_menu) + self.selection_mode_action = QAction(layout_menu) self.selection_mode_action.setCheckable(True) self.selection_mode_action.triggered.connect(self._set_selection_mode) self.selection_mode_action.setShortcut(QKeySequence("Ctrl+Alt+S")) layout_menu.addAction(self.selection_mode_action) + layout_menu.addSeparator() + + self.enable_view_resize_action = QAction(layout_menu) + self.enable_view_resize_action.setCheckable(True) + self.enable_view_resize_action.triggered.connect( + self._set_view_resize_enabled + ) + layout_menu.addAction(self.enable_view_resize_action) + + self.reset_size_action = QAction(layout_menu) + self.reset_size_action.triggered.connect(self._view.resetSize) + layout_menu.addAction(self.reset_size_action) + # Load settings self._go_key_sequence = QKeySequence( ListLayout.Config["goKey"], QKeySequence.NativeText ) self._set_seeksliders_visible(ListLayout.Config["show.seekSliders"]) - self._set_running_visible(ListLayout.Config["show.playingCues"]) self._set_accurate_time(ListLayout.Config["show.accurateTime"]) self._set_dbmeters_visible(ListLayout.Config["show.dBMeters"]) self._set_selection_mode(ListLayout.Config["selectionMode"]) @@ -163,9 +171,6 @@ def __init__(self, application): self.retranslate() def retranslate(self): - self.show_running_action.setText( - translate("ListLayout", "Show playing cues") - ) self.show_dbmeter_action.setText( translate("ListLayout", "Show dB-meters") ) @@ -179,6 +184,12 @@ def retranslate(self): self.selection_mode_action.setText( translate("ListLayout", "Selection mode") ) + self.enable_view_resize_action.setText( + translate("ListLayout", "Show resize handles") + ) + self.reset_size_action.setText( + translate("ListLayout", "Restore default size") + ) @property def model(self): @@ -296,16 +307,6 @@ def _set_dbmeters_visible(self, visible): def _get_dbmeters_visible(self): return self.show_dbmeter_action.isChecked() - @running_visible.set - def _set_running_visible(self, visible): - self.show_running_action.setChecked(visible) - self._view.runView.setVisible(visible) - self._view.controlButtons.setVisible(visible) - - @running_visible.get - def _get_running_visible(self): - return self.show_running_action.isChecked() - @selection_mode.set def _set_selection_mode(self, enable): self.selection_mode_action.setChecked(enable) @@ -323,6 +324,18 @@ def _set_selection_mode(self, enable): def _get_selection_mode(self): return self.selection_mode_action.isChecked() + @view_sizes.get + def _get_view_sizes(self): + return self._view.getSplitterSizes() + + @view_sizes.set + def _set_view_sizes(self, sizes): + self._view.setSplitterSize(sizes) + + def _set_view_resize_enabled(self, enabled): + self.enable_view_resize_action.setChecked(enabled) + self._view.setResizeHandlesEnabled(enabled) + def _double_clicked(self): cue = self.standby_cue() if cue is not None: diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py index 97a1bd519..4d52860fc 100644 --- a/lisp/plugins/list_layout/list_view.py +++ b/lisp/plugins/list_layout/list_view.py @@ -21,6 +21,7 @@ QDataStream, QIODevice, QT_TRANSLATE_NOOP, + QTimer, ) from PyQt5.QtGui import QKeyEvent, QContextMenuEvent, QBrush, QColor from PyQt5.QtWidgets import QTreeWidget, QHeaderView, QTreeWidgetItem @@ -62,7 +63,7 @@ def __init__(self, cue, *args, **kwargs): self.current = False -# TODO: consider using a custom Model/View +# TODO: use a custom Model/View class CueListView(QTreeWidget): keyPressed = pyqtSignal(QKeyEvent) contextMenuInvoked = pyqtSignal(QContextMenuEvent) @@ -97,7 +98,6 @@ def __init__(self, listModel, parent=None): :type listModel: lisp.plugins.list_layout.models.CueListModel """ super().__init__(parent) - self.__itemMoving = False self.__scrollRangeGuard = False @@ -187,6 +187,10 @@ def mousePressEvent(self, event): ): super().mousePressEvent(event) + def resizeEvent(self, event): + super().resizeEvent(event) + self.__resizeHeaders() + def standbyIndex(self): return self.indexOfTopLevelItem(self.currentItem()) @@ -194,6 +198,26 @@ def setStandbyIndex(self, newIndex): if 0 <= newIndex < self.topLevelItemCount(): self.setCurrentItem(self.topLevelItem(newIndex)) + def __resizeHeaders(self): + """Some hack to have "stretchable" columns with a minimum size + + NOTE: this currently works properly with only one "stretchable" column + """ + header = self.header() + for i, column in enumerate(CueListView.COLUMNS): + if column.resize == QHeaderView.Stretch: + # Make the header calculate the content size + header.setSectionResizeMode(i, QHeaderView.ResizeToContents) + contentWidth = header.sectionSize(i) + + # Make the header calculate the stretched size + header.setSectionResizeMode(i, QHeaderView.Stretch) + stretchWidth = header.sectionSize(i) + + # Set the maximum size as fixed size for the section + header.setSectionResizeMode(i, QHeaderView.Fixed) + header.resizeSection(i, max(contentWidth, stretchWidth)) + def __currentItemChanged(self, current, previous): if previous is not None: previous.current = False @@ -236,6 +260,8 @@ def __updateItemStyle(self, item): def __cuePropChanged(self, cue, property_name, _): if property_name == "stylesheet": self.__updateItemStyle(self.topLevelItem(cue.index)) + if property_name == "name": + QTimer.singleShot(1, self.__resizeHeaders) def __cueAdded(self, cue): item = CueTreeWidgetItem(cue) diff --git a/lisp/plugins/list_layout/list_widgets.py b/lisp/plugins/list_layout/list_widgets.py index 43c947f5c..c38708317 100644 --- a/lisp/plugins/list_layout/list_widgets.py +++ b/lisp/plugins/list_layout/list_widgets.py @@ -52,11 +52,15 @@ def __init__(self, item, *args, **kwargs): super().__init__(*args, **kwargs) self.setAttribute(Qt.WA_TranslucentBackground) - item.cue.changed("name").connect(self.__update, Connection.QtQueued) - self.__update(item.cue.name) + self._item = item + self._item.cue.changed("name").connect( + self.__update, Connection.QtQueued + ) + + self.setText(self._item.cue.name) - def __update(self, newName): - self.setText(newName) + def __update(self, text): + super().setText(text) class CueStatusIcons(QWidget): diff --git a/lisp/plugins/list_layout/playing_view.py b/lisp/plugins/list_layout/playing_view.py index 323eb5528..5777a8d15 100644 --- a/lisp/plugins/list_layout/playing_view.py +++ b/lisp/plugins/list_layout/playing_view.py @@ -26,6 +26,7 @@ class RunningCuesListWidget(QListWidget): def __init__(self, running_model, config, **kwargs): super().__init__(**kwargs) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setFocusPolicy(Qt.NoFocus) self.setSelectionMode(self.NoSelection) @@ -85,6 +86,7 @@ def accurate_time(self, accurate): def _item_added(self, cue): widget = get_running_widget(cue, self._config, parent=self) + widget.updateSize(self.viewport().width()) widget.set_accurate_time(self.__accurate_time) try: widget.set_dbmeter_visible(self.__dbmeter_visible) @@ -112,3 +114,12 @@ def _item_removed(self, cue): def _model_reset(self): for cue in list(self._running_cues.keys()): self._item_removed(cue) + + def resizeEvent(self, event): + super().resizeEvent(event) + + width = self.viewport().width() + for n in range(self.count()): + self.itemWidget(self.item(n)).updateSize(width) + + self.scroll diff --git a/lisp/plugins/list_layout/playing_widgets.py b/lisp/plugins/list_layout/playing_widgets.py index 81bc9912e..c23330a01 100644 --- a/lisp/plugins/list_layout/playing_widgets.py +++ b/lisp/plugins/list_layout/playing_widgets.py @@ -33,6 +33,7 @@ from lisp.cues.media_cue import MediaCue from lisp.plugins.list_layout.control_buttons import CueControlButtons from lisp.ui.widgets import QClickSlider, DBMeter +from lisp.ui.widgets.elidedlabel import ElidedLabel def get_running_widget(cue, config, **kwargs): @@ -45,7 +46,6 @@ def get_running_widget(cue, config, **kwargs): class RunningCueWidget(QWidget): def __init__(self, cue, config, **kwargs): super().__init__(**kwargs) - self.setGeometry(0, 0, self.parent().viewport().width(), 80) self.setFocusPolicy(Qt.NoFocus) self.setLayout(QHBoxLayout(self)) self.layout().setContentsMargins(0, 0, 0, 1) @@ -64,7 +64,7 @@ def __init__(self, cue, config, **kwargs): self.gridLayout.setSpacing(2) self.layout().addWidget(self.gridLayoutWidget) - self.nameLabel = QLabel(self.gridLayoutWidget) + self.nameLabel = ElidedLabel(self.gridLayoutWidget) self.nameLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.nameLabel.setText(cue.name) self.nameLabel.setToolTip(cue.name) @@ -118,6 +118,9 @@ def __init__(self, cue, config, **kwargs): cue.fadeout_start.connect(self.enter_fadeout, Connection.QtQueued) cue.fadeout_end.connect(self.exit_fade, Connection.QtQueued) + def updateSize(self, width): + self.resize(width, width // 3.75) + def enter_fadein(self): p = self.timeDisplay.palette() p.setColor(p.Text, QColor(0, 255, 0)) @@ -173,8 +176,6 @@ def _fadein(self): class RunningMediaCueWidget(RunningCueWidget): def __init__(self, cue, config, **kwargs): super().__init__(cue, config, **kwargs) - self.setGeometry(0, 0, self.width(), 110) - self._dbmeter_element = None self.seekSlider = QClickSlider(self.gridLayoutWidget) @@ -192,6 +193,9 @@ def __init__(self, cue, config, **kwargs): self._update_duration, Connection.QtQueued ) + def updateSize(self, width): + self.resize(width, width // 2.75) + def set_seek_visible(self, visible): if visible and not self.seekSlider.isVisible(): self.gridLayout.addWidget(self.seekSlider, 2, 0, 1, 2) diff --git a/lisp/plugins/list_layout/settings.py b/lisp/plugins/list_layout/settings.py index d45ee3258..169511010 100644 --- a/lisp/plugins/list_layout/settings.py +++ b/lisp/plugins/list_layout/settings.py @@ -45,9 +45,6 @@ def __init__(self, **kwargs): self.behaviorsGroup.setLayout(QVBoxLayout()) self.layout().addWidget(self.behaviorsGroup) - self.showPlaying = QCheckBox(self.behaviorsGroup) - self.behaviorsGroup.layout().addWidget(self.showPlaying) - self.showDbMeters = QCheckBox(self.behaviorsGroup) self.behaviorsGroup.layout().addWidget(self.showDbMeters) @@ -107,7 +104,6 @@ def retranslateUi(self): self.behaviorsGroup.setTitle( translate("ListLayout", "Default behaviors") ) - self.showPlaying.setText(translate("ListLayout", "Show playing cues")) self.showDbMeters.setText(translate("ListLayout", "Show dB-meters")) self.showAccurate.setText(translate("ListLayout", "Show accurate time")) self.showSeek.setText(translate("ListLayout", "Show seek-bars")) @@ -131,7 +127,6 @@ def retranslateUi(self): self.interruptCueFade.setText(translate("ListLayout", "Interrupt Cue")) def loadSettings(self, settings): - self.showPlaying.setChecked(settings["show"]["playingCues"]) self.showDbMeters.setChecked(settings["show"]["dBMeters"]) self.showAccurate.setChecked(settings["show"]["accurateTime"]) self.showSeek.setChecked(settings["show"]["seekSliders"]) @@ -153,7 +148,6 @@ def getSettings(self): return { "show": { "accurateTime": self.showAccurate.isChecked(), - "playingCues": self.showPlaying.isChecked(), "dBMeters": self.showDbMeters.isChecked(), "seekBars": self.showSeek.isChecked(), }, diff --git a/lisp/plugins/list_layout/view.py b/lisp/plugins/list_layout/view.py index 410cca764..b08425c67 100644 --- a/lisp/plugins/list_layout/view.py +++ b/lisp/plugins/list_layout/view.py @@ -16,10 +16,11 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QWidget, QGridLayout, QPushButton, QSizePolicy +from PyQt5.QtWidgets import QWidget, QSizePolicy, QSplitter, QVBoxLayout from lisp.plugins.list_layout.list_view import CueListView from lisp.plugins.list_layout.playing_view import RunningCuesListWidget +from lisp.ui.widgets.dynamicfontsize import DynamicFontSizePushButton from .control_buttons import ShowControlButtons from .info_panel import InfoPanel @@ -27,40 +28,103 @@ class ListLayoutView(QWidget): def __init__(self, listModel, runModel, config, *args): super().__init__(*args) - self.setLayout(QGridLayout()) + self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.listModel = listModel + self.mainSplitter = QSplitter(Qt.Vertical, self) + self.mainSplitter.splitterMoved.connect( + lambda pos, index: print(pos, index) + ) + self.layout().addWidget(self.mainSplitter) + + self.topSplitter = QSplitter(self.mainSplitter) + self.mainSplitter.addWidget(self.topSplitter) + + self.centralSplitter = QSplitter(self.topSplitter) + self.mainSplitter.addWidget(self.centralSplitter) + # GO-BUTTON (top-left) - self.goButton = QPushButton("GO", self) + self.goButton = DynamicFontSizePushButton(parent=self) + self.goButton.setText("GO") + self.goButton.setMinimumWidth(60) + self.goButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.goButton.setFocusPolicy(Qt.NoFocus) - self.goButton.setFixedWidth(120) - self.goButton.setFixedHeight(100) - self.goButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) - self.goButton.setStyleSheet("font-size: 48pt;") - self.layout().addWidget(self.goButton, 0, 0) + self.topSplitter.addWidget(self.goButton) # INFO PANEL (top-center) self.infoPanel = InfoPanel(self) - self.infoPanel.setFixedHeight(120) - self.layout().addWidget(self.infoPanel, 0, 1) + self.infoPanel.setMinimumWidth(300) + self.infoPanel.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Ignored) + self.infoPanel.cueDescription.setFontPointSize( + config.get( + "infoPanelFontSize", + self.infoPanel.cueDescription.fontPointSize(), + ) + ) + self.topSplitter.addWidget(self.infoPanel) # CONTROL-BUTTONS (top-right) self.controlButtons = ShowControlButtons(self) - self.controlButtons.setFixedHeight(120) - self.layout().addWidget(self.controlButtons, 0, 2) + self.controlButtons.setMinimumWidth(100) + self.controlButtons.setSizePolicy( + QSizePolicy.Minimum, QSizePolicy.Minimum + ) + self.topSplitter.addWidget(self.controlButtons) # CUE VIEW (center-left) self.listView = CueListView(listModel, self) + self.listView.setMinimumWidth(200) self.listView.currentItemChanged.connect(self.__listViewCurrentChanged) - self.layout().addWidget(self.listView, 1, 0, 1, 2) + self.centralSplitter.addWidget(self.listView) + self.centralSplitter.setCollapsible(0, False) # PLAYING VIEW (center-right) self.runView = RunningCuesListWidget(runModel, config, parent=self) - self.runView.setMinimumWidth(300) - self.runView.setMaximumWidth(300) - self.layout().addWidget(self.runView, 1, 2) + self.runView.setMinimumWidth(200) + self.runView.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) + self.centralSplitter.addWidget(self.runView) + + self.__userResized = False + + def showEvent(self, event): + super().showEvent(event) + if not self.__userResized: + self.resetSize() + + def resetSize(self): + self.mainSplitter.setSizes((0.22 * self.width(), 0.78 * self.width())) + self.centralSplitter.setSizes( + (0.78 * self.width(), 0.22 * self.width()) + ) + self.topSplitter.setSizes( + (0.11 * self.width(), 0.67 * self.width(), 0.22 * self.width()) + ) + + def getSplitterSizes(self): + return [ + self.mainSplitter.sizes(), + self.topSplitter.sizes(), + self.centralSplitter.sizes(), + ] + + def setSplitterSize(self, sizes): + if len(sizes) >= 3: + self.mainSplitter.setSizes(sizes[0]) + self.topSplitter.setSizes(sizes[1]) + self.centralSplitter.setSizes(sizes[2]) + + self.__userResized = True + + def setResizeHandlesEnabled(self, enabled): + self.__setSplitterHandlesEnabled(self.mainSplitter, enabled) + self.__setSplitterHandlesEnabled(self.topSplitter, enabled) + self.__setSplitterHandlesEnabled(self.centralSplitter, enabled) + + def __setSplitterHandlesEnabled(self, splitter: QSplitter, enabled): + for n in range(splitter.count()): + splitter.handle(n).setEnabled(enabled) def __listViewCurrentChanged(self, current, _): cue = None diff --git a/lisp/ui/settings/cue_pages/cue_general.py b/lisp/ui/settings/cue_pages/cue_general.py index 6eddefcb4..7693e8ffd 100644 --- a/lisp/ui/settings/cue_pages/cue_general.py +++ b/lisp/ui/settings/cue_pages/cue_general.py @@ -297,8 +297,6 @@ def getSettings(self): settings["fadein_type"] = self.fadeInEdit.fadeType() settings["fadein_duration"] = self.fadeInEdit.duration() - checkable = self.fadeOutGroup.isCheckable() - if not checkable or self.fadeOutGroup.isChecked(): settings["fadeout_type"] = self.fadeOutEdit.fadeType() settings["fadeout_duration"] = self.fadeOutEdit.duration() diff --git a/lisp/ui/themes/__init__.py b/lisp/ui/themes/__init__.py index 5c5bdacd9..acea067fd 100644 --- a/lisp/ui/themes/__init__.py +++ b/lisp/ui/themes/__init__.py @@ -1,7 +1,6 @@ from os import path from lisp.core.loading import load_classes -from lisp.ui.themes.dark.dark import Dark _THEMES = {} diff --git a/lisp/ui/themes/dark/assetes.py b/lisp/ui/themes/dark/assets.py similarity index 100% rename from lisp/ui/themes/dark/assetes.py rename to lisp/ui/themes/dark/assets.py diff --git a/lisp/ui/themes/dark/assetes.rcc b/lisp/ui/themes/dark/assets.qrc similarity index 100% rename from lisp/ui/themes/dark/assetes.rcc rename to lisp/ui/themes/dark/assets.qrc diff --git a/lisp/ui/themes/dark/dark.py b/lisp/ui/themes/dark/dark.py index 7e50c63b5..cf7408081 100644 --- a/lisp/ui/themes/dark/dark.py +++ b/lisp/ui/themes/dark/dark.py @@ -21,7 +21,7 @@ # Import resources # noinspection PyUnresolvedReferences -from . import assetes +from . import assets class Dark: diff --git a/lisp/ui/themes/dark/theme.qss b/lisp/ui/themes/dark/theme.qss index 40311c702..8bc7ac5ab 100644 --- a/lisp/ui/themes/dark/theme.qss +++ b/lisp/ui/themes/dark/theme.qss @@ -54,7 +54,7 @@ QProgressBar { QProgressBar:horizontal { text-align: center; - border: 1px solid #3C3C3C; + border: 1px solid #4A4A4A; background: #202020; } @@ -85,7 +85,7 @@ QMenuBar:item:selected { } QMenuBar:item:pressed { - border: 1px solid #3C3C3C; + border: 1px solid #4A4A4A; background-color: #419BE6; color: black; margin-bottom:-1px; @@ -93,7 +93,7 @@ QMenuBar:item:pressed { } QMenu { - border: 1px solid #3C3C3C; + border: 1px solid #4A4A4A; color: white; } @@ -105,11 +105,19 @@ QMenu:item:selected{ color: black; } +QMenu:separator { + height: 1px; + margin: 4px 0px 4px 0px; + background-color: #3C3C3C; +} + QAbstractItemView { + border: 1px solid #4A4A4A; alternate-background-color: #252525; /*paint-alternating-row-colors-for-empty-area: true;*/ - color: white; + background-color: #202020; border-radius: 3px; + color: white; } QTreeView::branch:hover, @@ -124,20 +132,22 @@ QTabWidget:focus, QCheckBox:focus, QRadioButton:focus { QLineEdit { background-color: #202020; padding: 2px; - border: 1px solid #3C3C3C; + border: 1px solid #4A4A4A; border-radius: 3px; color: white; } QGroupBox { - border: 1px solid #414141; + border: 1px solid #4A4A4A; border-radius: 2px; - margin-top: 2ex; /* NOT px */ + margin-top: 2ex; /* NOT px */ + padding-top: 1ex; /* NOT px */ } QGroupBox:title { subcontrol-origin: margin; subcontrol-position: top center; + padding-top: 0.5ex; padding-left: 10px; padding-right: 10px; } @@ -145,48 +155,54 @@ QGroupBox:title { QAbstractScrollArea { color: white; background-color: #202020; - border: 1px solid #3C3C3C; + border: 1px solid #4A4A4A; border-radius: 3px; } -QScrollBar:add-line, -QScrollBar:sub-line, -QScrollBar:right-arrow, -QScrollBar:left-arrow { - width: 0px; - height: 0px; -} - -QScrollBar:add-page, QScrollBar:sub-page { - background: none; +QScrollBar { + padding: 2px; } QScrollBar:horizontal { height: 12px; - margin: 0px; - border: 1px solid #3C3C3C; - border-radius: 6px; - background-color: QLinearGradient( x1: 0, y1: 1, x2: 0, y2: 0, stop: 0 #333, stop: 1 #484846); -} - -QScrollBar:handle:horizontal { - background-color: QLinearGradient( x1: 0, y1: 1, x2: 0, y2: 0, stop: 0 #605F5F, stop: 1 #787876); - min-width: 5px; - border-radius: 5px; } QScrollBar:vertical { - background-color: QLinearGradient( x1: 1, y1: 0, x2: 0, y2: 0, stop: 0 #333, stop: 1 #484846); width: 12px; - margin: 0px; - border: 1px solid #3C3C3C; - border-radius: 6px; +} + +QScrollBar:handle { + background-color: rgba(112, 112, 112, 0.7); /* #707070 */ + border-radius: 4px; +} + +QScrollBar:handle:hover { + background-color: rgba(112, 112, 112, 1); /* #707070 */ +} + +QScrollBar:handle:horizontal { + min-width: 8px; } QScrollBar:handle:vertical { - background-color: QLinearGradient( x1: 1, y1: 0, x2: 0, y2: 0, stop: 0 #605F5F, stop: 1 #787876); - min-height: 5px; - border-radius: 5px; + min-height: 8px; +} + +QScrollBar:add-line, +QScrollBar:sub-line, +QScrollBar:right-arrow, +QScrollBar:left-arrow { + width: 0px; + height: 0px; +} + +QScrollBar:add-page, +QScrollBar:sub-page { + background: none; +} + +QAbstractItemView QScrollBar { + background-color: #202020; } QTextEdit[readOnly="true"] { @@ -249,19 +265,10 @@ QMainWindow:separator:hover { background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0 #58677b, stop:0.5 #419BE6 stop:1 #58677b); color: white; padding-left: 4px; - border: 1px solid #3C3C3C; + border: 1px solid #4A4A4A; spacing: 2px; } -QMenu:separator { - height: 1px; - background-color: #3A3A3A; - color: white; - padding-left: 4px; - margin-left: 10px; - margin-right: 5px; -} - QStackedWidget { border: none; } @@ -289,6 +296,11 @@ QToolButton { padding: 1px; } +QSplitter::handle:vertical:disabled, +QSplitter::handle:horizontal:disabled { + image: none; +} + QPushButton { color: white; background-color: QLinearGradient( x1: 0, y1: 1, x2: 0, y2: 0, stop: 0 #333, stop: 1 #444); @@ -318,7 +330,7 @@ QAbstractSpinBox:focus { QComboBox { background-color: #202020; border-style: solid; - border: 1px solid #3C3C3C; + border: 1px solid #4A4A4A; border-radius: 3px; padding: 2px; } @@ -344,13 +356,15 @@ QComboBox:drop-down { QComboBox:down-arrow { image: url(:/assets/down-arrow.png); + height: 12px; + width: 12px; } QAbstractSpinBox { padding-top: 2px; padding-bottom: 2px; padding-right: 25px; - border: 1px solid #3C3C3C; + border: 1px solid #4A4A4A; background-color: #202020; border-radius: 3px; color: white; @@ -448,7 +462,7 @@ QTabBar::tab:selected:focus { } QTabBar QToolButton { - border: 1px solid #3C3C3C; + border: 1px solid #4A4A4A; } QDockWidget { @@ -458,7 +472,7 @@ QDockWidget { } QDockWidget:title { - border: 1px solid #3C3C3C; + border: 1px solid #4A4A4A; border-bottom: #333; text-align: left; spacing: 2px; @@ -488,11 +502,6 @@ QDockWidget:close-button:pressed, QDockWidget:float-button:pressed { padding: 1px -1px -1px 1px; } -QTreeView, QListView, QTableView { - border: 1px solid #3C3C3C; - background-color: #202020; -} - QTreeView::branch:has-siblings:adjoins-item, QTreeView::branch:has-siblings:!adjoins-item, QTreeView::branch:!has-children:!has-siblings:adjoins-item { @@ -520,7 +529,7 @@ QSlider:disabled { } QSlider:groove { - border: 1px solid #3C3C3C; + border: 1px solid #4A4A4A; border-radius: 1px; background: #202020; } @@ -678,7 +687,7 @@ CueListView { } CueListView:focus { - border: 1px solid #3C3C3C; + border: 1px solid #4A4A4A; } #ListTimeWidget { @@ -732,7 +741,7 @@ CueListView:focus { } #InfoPanelDescription[readOnly="true"] { - border: 1px solid #3C3C3C; + border: 1px solid #4A4A4A; } #VolumeSlider:sub-page:horizontal { diff --git a/lisp/ui/widgets/colorbutton.py b/lisp/ui/widgets/colorbutton.py index bf064ccdc..d4b78e269 100644 --- a/lisp/ui/widgets/colorbutton.py +++ b/lisp/ui/widgets/colorbutton.py @@ -45,7 +45,7 @@ def setColor(self, color): if self._color is not None: self.setStyleSheet( - "QColorButton {{ background-color: {0}; }}".format(self._color) + "ColorButton {{ background-color: {0}; }}".format(self._color) ) else: self.setStyleSheet("") diff --git a/lisp/ui/widgets/dynamicfontsize.py b/lisp/ui/widgets/dynamicfontsize.py new file mode 100644 index 000000000..d4402b2dc --- /dev/null +++ b/lisp/ui/widgets/dynamicfontsize.py @@ -0,0 +1,98 @@ +# This file is part of Linux Show Player +# +# Python adaptation of https://github.com/jonaias/DynamicFontSizeWidgets +# +# Copyright 2019 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import Qt, QRectF +from PyQt5.QtGui import QFontMetricsF +from PyQt5.QtWidgets import QLabel, QPushButton + + +class DynamicFontSizeMixin: + FONT_PRECISION = 0.5 + PADDING = 8 + + def getWidgetMaximumFontSize(self, text: str): + font = self.font() + currentSize = font.pointSizeF() + + if not text: + return currentSize + + widgetRect = QRectF(self.contentsRect()) + widgetWidth = widgetRect.width() - self.PADDING + widgetHeight = widgetRect.height() - self.PADDING + + step = currentSize / 2.0 + # If too small, increase step + if step <= self.FONT_PRECISION: + step = self.FONT_PRECISION * 4.0 + + lastTestedSize = currentSize + currentHeight = 0 + currentWidth = 0 + + # Check what flags we need to apply + flags = 0 + if isinstance(self, QLabel): + flags |= self.alignment() + flags |= Qt.TextWordWrap if self.wordWrap() else 0 + + # Only stop when step is small enough and new size is smaller than QWidget + while ( + step > self.FONT_PRECISION + or currentHeight > widgetHeight + or currentWidth > widgetWidth + ): + # Keep last tested value + lastTestedSize = currentSize + # Test font size + font.setPointSizeF(currentSize) + # Calculate text size + newFontSizeRect = QFontMetricsF(font).boundingRect( + widgetRect, flags, text + ) + currentHeight = newFontSizeRect.height() + currentWidth = newFontSizeRect.width() + + # If new font size is too big, decrease it + if currentHeight > widgetHeight or currentWidth > widgetWidth: + currentSize -= step + # If step is small enough, keep it ant, so it converge to biggest font size + if step > self.FONT_PRECISION: + step /= 2.0 + # Do not allow negative size + if currentSize <= 0: + break + else: + # If new font size is smaller than maximum possible size, increase it + currentSize += step + + return lastTestedSize + + +class DynamicFontSizePushButton(DynamicFontSizeMixin, QPushButton): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def paintEvent(self, event): + newFont = self.font() + newFont.setPointSizeF(self.getWidgetMaximumFontSize(self.text())) + + self.setFont(newFont) + + super().paintEvent(event) diff --git a/lisp/ui/widgets/elidedlabel.py b/lisp/ui/widgets/elidedlabel.py new file mode 100644 index 000000000..f09aa0fda --- /dev/null +++ b/lisp/ui/widgets/elidedlabel.py @@ -0,0 +1,51 @@ +# This file is part of Linux Show Player +# +# Copyright 2019 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QPainter +from PyQt5.QtWidgets import QLabel, QSizePolicy + + +class ElidedLabel(QLabel): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) + + self.__elideMode = Qt.ElideRight + self.__prevWidth = 0 + self.__prevText = "" + self.__elided = "" + + def elideMode(self): + return self.__elideMode + + def setElideMode(self, mode): + self.__elideMode = mode + + def paintEvent(self, event): + text = self.text() + width = self.width() + + if text != self.__prevText or width != self.__prevWidth: + self.__prevText = text + self.__prevWidth = width + self.__elided = self.fontMetrics().elidedText( + text, self.__elideMode, width + ) + + painter = QPainter(self) + painter.drawText(self.rect(), self.alignment(), self.__elided) diff --git a/lisp/ui/widgets/locales.py b/lisp/ui/widgets/locales.py index 8ae8cbdb2..148b2c89f 100644 --- a/lisp/ui/widgets/locales.py +++ b/lisp/ui/widgets/locales.py @@ -43,8 +43,11 @@ def currentLocale(self): def _localeUiText(locale): if locale: ql = QLocale(locale) - return "{} ({})".format( - ql.languageToString(ql.language()), ql.nativeLanguageName() + return "{} - {} ({} - {})".format( + ql.languageToString(ql.language()), + ql.countryToString(ql.country()), + ql.nativeLanguageName(), + ql.nativeCountryName(), ) else: return "System ({})".format(QLocale().system().nativeLanguageName()) From c34071230a54024cd38921ba44697ef562a11ae4 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 16 Aug 2019 12:31:42 +0200 Subject: [PATCH 197/333] cleanup --- lisp/plugins/list_layout/playing_view.py | 2 -- lisp/plugins/list_layout/view.py | 3 --- 2 files changed, 5 deletions(-) diff --git a/lisp/plugins/list_layout/playing_view.py b/lisp/plugins/list_layout/playing_view.py index 5777a8d15..22cbfcadf 100644 --- a/lisp/plugins/list_layout/playing_view.py +++ b/lisp/plugins/list_layout/playing_view.py @@ -121,5 +121,3 @@ def resizeEvent(self, event): width = self.viewport().width() for n in range(self.count()): self.itemWidget(self.item(n)).updateSize(width) - - self.scroll diff --git a/lisp/plugins/list_layout/view.py b/lisp/plugins/list_layout/view.py index b08425c67..060bc1b6e 100644 --- a/lisp/plugins/list_layout/view.py +++ b/lisp/plugins/list_layout/view.py @@ -34,9 +34,6 @@ def __init__(self, listModel, runModel, config, *args): self.listModel = listModel self.mainSplitter = QSplitter(Qt.Vertical, self) - self.mainSplitter.splitterMoved.connect( - lambda pos, index: print(pos, index) - ) self.layout().addWidget(self.mainSplitter) self.topSplitter = QSplitter(self.mainSplitter) From f76efa592926ba0ecb255c0ec76fb4ab55f0d27a Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 19 Aug 2019 11:43:57 +0200 Subject: [PATCH 198/333] Add support for generic MIDI messages to the controller plugin (close #72) --- lisp/plugins/controller/controller.py | 2 +- lisp/plugins/controller/protocols/midi.py | 176 ++++++++++++++++------ lisp/plugins/midi/midi_cue.py | 6 +- lisp/plugins/midi/midi_utils.py | 45 +++++- lisp/plugins/midi/widgets.py | 3 +- lisp/ui/qdelegates.py | 25 +-- lisp/ui/qmodels.py | 10 ++ 7 files changed, 196 insertions(+), 71 deletions(-) diff --git a/lisp/plugins/controller/controller.py b/lisp/plugins/controller/controller.py index 95a6b98f6..31b33378f 100644 --- a/lisp/plugins/controller/controller.py +++ b/lisp/plugins/controller/controller.py @@ -106,7 +106,7 @@ def cue_changed(self, cue, property_name, value): def delete_from_cue_map(self, cue): for actions_map in self.__cue_map.values(): - actions_map.pop(cue) + actions_map.pop(cue, None) def perform_cue_action(self, key): for cue, actions in self.__cue_map.get(key, {}).items(): diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index 52cb28a65..918167299 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -26,16 +26,26 @@ QTableWidget, QHeaderView, QGridLayout, + QLabel, + QStyledItemDelegate, + QHBoxLayout, ) from lisp.plugins import get_plugin, PluginNotLoadedError from lisp.plugins.controller.common import LayoutAction, tr_layout_action from lisp.plugins.controller.protocol import Protocol +from lisp.plugins.midi.midi_utils import ( + MIDI_MSGS_NAME, + midi_data_from_msg, + midi_msg_from_data, + midi_from_dict, + midi_from_str, +) +from lisp.plugins.midi.widgets import MIDIMessageEditDialog from lisp.ui.qdelegates import ( - ComboBoxDelegate, - SpinBoxDelegate, CueActionDelegate, EnumComboBoxDelegate, + LabelDelegate, ) from lisp.ui.qmodels import SimpleTableModel from lisp.ui.settings.pages import CuePageMixin, SettingsPage @@ -43,6 +53,8 @@ class MidiSettings(SettingsPage): + FILTER_ALL = "__all__" + Name = QT_TRANSLATE_NOOP("SettingsPageName", "MIDI Controls") def __init__(self, actionDelegate, **kwargs): @@ -56,14 +68,7 @@ def __init__(self, actionDelegate, **kwargs): self.midiGroup.setLayout(QGridLayout()) self.layout().addWidget(self.midiGroup) - self.midiModel = SimpleTableModel( - [ - translate("ControllerMidiSettings", "Type"), - translate("ControllerMidiSettings", "Channel"), - translate("ControllerMidiSettings", "Note"), - translate("ControllerMidiSettings", "Action"), - ] - ) + self.midiModel = MidiModel() self.midiView = MidiView(actionDelegate, parent=self.midiGroup) self.midiView.setModel(self.midiModel) @@ -81,16 +86,24 @@ def __init__(self, actionDelegate, **kwargs): self.midiCapture.clicked.connect(self.capture_message) self.midiGroup.layout().addWidget(self.midiCapture, 2, 0) - self.msgTypeCombo = QComboBox(self.midiGroup) - self.msgTypeCombo.addItem( - translate("ControllerMidiSettings", 'Filter "note on"') - ) - self.msgTypeCombo.setItemData(0, "note_on", Qt.UserRole) - self.msgTypeCombo.addItem( - translate("ControllerMidiSettings", 'Filter "note off"') + self.filterLayout = QHBoxLayout() + self.midiGroup.layout().addLayout(self.filterLayout, 2, 1) + + self.filterLabel = QLabel(self.midiGroup) + self.filterLabel.setAlignment(Qt.AlignCenter) + self.filterLayout.addWidget(self.filterLabel) + + self.filterTypeCombo = QComboBox(self.midiGroup) + self.filterLayout.addWidget(self.filterTypeCombo) + + self.filterTypeCombo.addItem( + translate("ControllerMidiSettings", "-- All Messages --"), + self.FILTER_ALL, ) - self.msgTypeCombo.setItemData(1, "note_off", Qt.UserRole) - self.midiGroup.layout().addWidget(self.msgTypeCombo, 2, 1) + for msg_type, msg_name in MIDI_MSGS_NAME.items(): + self.filterTypeCombo.addItem( + translate("MIDIMessageType", msg_name), msg_type + ) self.retranslateUi() @@ -103,7 +116,11 @@ def __init__(self, actionDelegate, **kwargs): def retranslateUi(self): self.addButton.setText(translate("ControllerSettings", "Add")) self.removeButton.setText(translate("ControllerSettings", "Remove")) + self.midiCapture.setText(translate("ControllerMidiSettings", "Capture")) + self.filterLabel.setText( + translate("ControllerMidiSettings", "Capture filter") + ) def enableCheck(self, enabled): self.midiGroup.setCheckable(enabled) @@ -111,17 +128,16 @@ def enableCheck(self, enabled): def getSettings(self): entries = [] - for row in self.midiModel.rows: - message = Midi.str_from_values(row[0], row[1] - 1, row[2]) - entries.append((message, row[-1])) + for row in range(self.midiModel.rowCount()): + message, action = self.midiModel.getMessage(row) + entries.append((str(message), action)) return {"midi": entries} def loadSettings(self, settings): if "midi" in settings: - for entries in settings["midi"]: - m_type, channel, note = Midi.from_string(entries[0]) - self.midiModel.appendRow(m_type, channel + 1, note, entries[1]) + for entry in settings["midi"]: + self.midiModel.appendMessage(midi_from_str(entry[0]), entry[1]) def capture_message(self): handler = self.__midi.input @@ -137,15 +153,22 @@ def capture_message(self): handler.new_message_alt.disconnect(self.__add_message) handler.alternate_mode = False - def __add_message(self, msg): - if self.msgTypeCombo.currentData(Qt.UserRole) == msg.type: - self.midiModel.appendRow( - msg.type, msg.channel + 1, msg.note, self._defaultAction - ) + def __add_message(self, message): + mgs_filter = self.filterTypeCombo.currentData(Qt.UserRole) + if mgs_filter == self.FILTER_ALL or message.type == mgs_filter: + if hasattr(message, "velocity"): + message.velocity = 0 + + self.midiModel.appendMessage(message, self._defaultAction) def __new_message(self): - message_type = self.msgTypeCombo.currentData(Qt.UserRole) - self.midiModel.appendRow(message_type, 1, 0, self._defaultAction) + dialog = MIDIMessageEditDialog() + if dialog.exec() == MIDIMessageEditDialog.Accepted: + message = midi_from_dict(dialog.getMessageDict()) + if hasattr(message, "velocity"): + message.velocity = 0 + + self.midiModel.appendMessage(message, self._defaultAction) def __remove_message(self): self.midiModel.removeRow(self.midiView.currentIndex().row()) @@ -176,14 +199,60 @@ def __init__(self, **kwargs): self._defaultAction = LayoutAction.Go.name +class MidiMessageTypeDelegate(LabelDelegate): + def _text(self, option, index): + message_type = index.data() + return translate( + "MIDIMessageType", MIDI_MSGS_NAME.get(message_type, "undefined") + ) + + +class MidiModel(SimpleTableModel): + def __init__(self): + super().__init__( + [ + translate("ControllerMidiSettings", "Type"), + translate("ControllerMidiSettings", "Data 1"), + translate("ControllerMidiSettings", "Data 2"), + translate("ControllerMidiSettings", "Data 3"), + translate("ControllerMidiSettings", "Action"), + ] + ) + + def appendMessage(self, message, action): + data = midi_data_from_msg(message) + data.extend((None,) * (3 - len(data))) + self.appendRow(message.type, *data, action) + + def updateMessage(self, row, message, action): + data = midi_data_from_msg(message) + data.extend((None,) * (3 - len(data))) + + self.updateRow(row, message.type, *data, action) + + def getMessage(self, row): + if row < len(self.rows): + return ( + midi_msg_from_data(self.rows[row][0], self.rows[row][1:4]), + self.rows[row][4], + ) + + def flags(self, index): + if index.column() <= 3: + return Qt.ItemIsEnabled | Qt.ItemIsSelectable + else: + return super().flags(index) + + class MidiView(QTableView): def __init__(self, actionDelegate, **kwargs): super().__init__(**kwargs) self.delegates = [ - ComboBoxDelegate(options=["note_on", "note_off"]), - SpinBoxDelegate(minimum=1, maximum=16), - SpinBoxDelegate(minimum=0, maximum=127), + MidiMessageTypeDelegate(), + QStyledItemDelegate(), + QStyledItemDelegate(), + QStyledItemDelegate(), actionDelegate, ] @@ -193,7 +262,11 @@ def __init__(self, actionDelegate, **kwargs): self.setShowGrid(False) self.setAlternatingRowColors(True) - self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + self.horizontalHeader().setSectionResizeMode( + QHeaderView.ResizeToContents + ) + self.horizontalHeader().setMinimumSectionSize(80) + self.horizontalHeader().setStretchLastSection(True) self.horizontalHeader().setHighlightSections(False) self.verticalHeader().sectionResizeMode(QHeaderView.Fixed) @@ -203,6 +276,20 @@ def __init__(self, actionDelegate, **kwargs): for column, delegate in enumerate(self.delegates): self.setItemDelegateForColumn(column, delegate) + self.doubleClicked.connect(self.__doubleClicked) + + def __doubleClicked(self, index): + if index.column() <= 3: + message, action = self.model().getMessage(index.row()) + + dialog = MIDIMessageEditDialog() + dialog.setMessageDict(message.dict()) + + if dialog.exec() == MIDIMessageEditDialog.Accepted: + self.model().updateMessage( + index.row(), midi_from_dict(dialog.getMessageDict()), action + ) + class Midi(Protocol): CueSettings = MidiCueSettings @@ -214,18 +301,7 @@ def __init__(self): get_plugin("Midi").input.new_message.connect(self.__new_message) def __new_message(self, message): - if message.type == "note_on" or message.type == "note_off": - self.protocol_event.emit(Midi.str_from_message(message)) - - @staticmethod - def str_from_message(message): - return Midi.str_from_values(message.type, message.channel, message.note) - - @staticmethod - def str_from_values(m_type, channel, note): - return "{} {} {}".format(m_type, channel, note) + if hasattr(message, "velocity"): + message.velocity = 0 - @staticmethod - def from_string(message_str): - m_type, channel, note = message_str.split() - return m_type, int(channel), int(note) + self.protocol_event.emit(str(message)) diff --git a/lisp/plugins/midi/midi_cue.py b/lisp/plugins/midi/midi_cue.py index 9c3fd8c56..561dbea8f 100644 --- a/lisp/plugins/midi/midi_cue.py +++ b/lisp/plugins/midi/midi_cue.py @@ -23,7 +23,7 @@ from lisp.core.properties import Property from lisp.cues.cue import Cue from lisp.plugins import get_plugin -from lisp.plugins.midi.midi_utils import str_msg_to_dict, dict_msg_to_str +from lisp.plugins.midi.midi_utils import midi_str_to_dict, midi_dict_to_str from lisp.plugins.midi.widgets import MIDIMessageEdit from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.pages import SettingsPage @@ -61,12 +61,12 @@ def __init__(self, **kwargs): self.layout().addWidget(self.midiEdit) def getSettings(self): - return {"message": dict_msg_to_str(self.midiEdit.getMessageDict())} + return {"message": midi_dict_to_str(self.midiEdit.getMessageDict())} def loadSettings(self, settings): message = settings.get("message", "") if message: - self.midiEdit.setMessageDict(str_msg_to_dict(message)) + self.midiEdit.setMessageDict(midi_str_to_dict(message)) CueSettingsRegistry().add(MidiCueSettings, MidiCue) diff --git a/lisp/plugins/midi/midi_utils.py b/lisp/plugins/midi/midi_utils.py index 75203f916..82093a22e 100644 --- a/lisp/plugins/midi/midi_utils.py +++ b/lisp/plugins/midi/midi_utils.py @@ -15,9 +15,11 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +from typing import Iterable + import mido from PyQt5.QtCore import QT_TRANSLATE_NOOP - +from mido import Message MIDI_MSGS_SPEC = { "note_on": ["channel", "note", "velocity"], @@ -76,15 +78,48 @@ } -def str_msg_to_dict(str_message): - return mido.parse_string(str_message).dict() +def midi_str_to_dict(midi_str: str) -> dict: + return mido.parse_string(midi_str).dict() -def dict_msg_to_str(dict_message): - message = mido.Message.from_dict(dict_message) +def midi_dict_to_str(midi_dict: dict) -> str: + message = mido.Message.from_dict(midi_dict) return mido.format_as_string(message, include_time=False) +def midi_data_from_msg(message) -> list: + data = [] + + for attr in MIDI_MSGS_SPEC.get(message.type, ()): + if attr is not None: + data.append(getattr(message, attr, None)) + + return data + + +def midi_data_from_dict(midi_dict: dict) -> list: + return midi_data_from_msg(midi_from_dict(midi_dict)) + + +def midi_msg_from_data(message_type: str, data: Iterable): + message_spec = MIDI_MSGS_SPEC.get(message_type, ()) + message = Message(message_type) + + for attr, value in zip(message_spec, data): + if attr is not None: + setattr(message, attr, value) + + return message + + +def midi_from_dict(midi_dict: dict): + return mido.Message.from_dict(midi_dict) + + +def midi_from_str(midi_str: str): + return mido.Message.from_str(midi_str) + + def mido_backend(): """Return the current backend object, or None""" backend = None diff --git a/lisp/plugins/midi/widgets.py b/lisp/plugins/midi/widgets.py index 01f0f8d7b..289f46da5 100644 --- a/lisp/plugins/midi/widgets.py +++ b/lisp/plugins/midi/widgets.py @@ -135,7 +135,6 @@ class MIDIMessageEditDialog(QDialog): def __init__(self, **kwargs): super().__init__(**kwargs) self.setLayout(QVBoxLayout()) - self.layout().setContentsMargins(0, 0, 0, 0) self.editor = MIDIMessageEdit() self.layout().addWidget(self.editor) @@ -145,7 +144,7 @@ def __init__(self, **kwargs): ) self.buttons.accepted.connect(self.accept) self.buttons.rejected.connect(self.reject) - self.layout().addWidget(self.editor) + self.layout().addWidget(self.buttons) def getMessageDict(self): return self.editor.getMessageDict() diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index e4f62201a..230613056 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -17,8 +17,8 @@ from enum import Enum -from PyQt5.QtCore import Qt, QEvent, QSize -from PyQt5.QtGui import QKeySequence +from PyQt5.QtCore import Qt, QEvent, QSize, QRect +from PyQt5.QtGui import QKeySequence, QFontMetrics from PyQt5.QtWidgets import ( QStyledItemDelegate, QComboBox, @@ -50,14 +50,14 @@ def sizeHint(self, option, index): class LabelDelegate(QStyledItemDelegate): - def _text(self, painter, option, index): + def _text(self, option, index): return index.data() def paint(self, painter, option, index): # Add 4px of left an right padding - option.rect.adjust(4, 0, 4, 0) + option.rect.adjust(4, 0, -4, 0) - text = self._text(painter, option, index) + text = self._text(option, index) text = option.fontMetrics.elidedText( text, Qt.ElideRight, option.rect.width() ) @@ -75,6 +75,11 @@ def paint(self, painter, option, index): painter.restore() + def sizeHint(self, option, index): + return QFontMetrics(option.font).size( + Qt.TextSingleLine, self._text(option, index) + ) + QSize(8, 0) + class ComboBoxDelegate(LabelDelegate): def __init__(self, options=(), tr_context=None, **kwargs): @@ -82,11 +87,11 @@ def __init__(self, options=(), tr_context=None, **kwargs): self.options = options self.tr_context = tr_context - def _text(self, painter, option, index): + def _text(self, option, index): return translate(self.tr_context, index.data()) def paint(self, painter, option, index): - option.displayAlignment = Qt.AlignHCenter | Qt.AlignVCenter + option.displayAlignment = Qt.AlignCenter super().paint(painter, option, index) def createEditor(self, parent, option, index): @@ -242,7 +247,7 @@ def setModelData(self, editor: HotKeyEdit, model, index): def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) - def _text(self, painter, option, index): + def _text(self, option, index): return self._sequence(index).toString(QKeySequence.NativeText) def _sequence(self, index): @@ -264,7 +269,7 @@ def __init__(self, enum, mode=Mode.Enum, trItem=str, **kwargs): self.mode = mode self.trItem = trItem - def _text(self, painter, option, index): + def _text(self, option, index): return self.trItem(self.itemFromData(index.data(Qt.EditRole))) def paint(self, painter, option, index): @@ -322,7 +327,7 @@ def __init__(self, cue_model, cue_select_dialog, **kwargs): self.cue_model = cue_model self.cue_select = cue_select_dialog - def _text(self, painter, option, index): + def _text(self, option, index): cue = self.cue_model.get(index.data()) if cue is not None: return "{} | {}".format(cue.index, cue.name) diff --git a/lisp/ui/qmodels.py b/lisp/ui/qmodels.py index 65cc1a356..415f754dc 100644 --- a/lisp/ui/qmodels.py +++ b/lisp/ui/qmodels.py @@ -45,6 +45,16 @@ def appendRow(self, *values): self.rows.append(list(values)) self.endInsertRows() + def updateRow(self, row, *values): + if -1 < row < len(self.rows): + self.rows[row] = list(values) + + self.dataChanged.emit( + self.index(row, 0), + self.index(row, len(self.columns)), + [Qt.DisplayRole, Qt.EditRole], + ) + def removeRow(self, row, parent=QModelIndex()): if -1 < row < len(self.rows): self.beginRemoveRows(parent, row, row) From 8bee467b7934d0f1b10e139069fe45faf0d20fb4 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 21 Aug 2019 18:49:32 +0200 Subject: [PATCH 199/333] Minor improvements --- lisp/application.py | 3 +- lisp/plugins/controller/protocols/midi.py | 31 ++++++++++++++++----- lisp/plugins/gst_backend/gi_repository.py | 1 + lisp/plugins/list_layout/playing_widgets.py | 1 - lisp/plugins/replay_gain/replay_gain.py | 1 - lisp/ui/qdelegates.py | 2 +- lisp/ui/themes/dark/dark.py | 1 + 7 files changed, 29 insertions(+), 11 deletions(-) diff --git a/lisp/application.py b/lisp/application.py index 07cdbee8f..e44bb2ee1 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -23,6 +23,7 @@ from lisp import layout from lisp.command.stack import CommandsStack +from lisp.core.configuration import DummyConfiguration from lisp.core.signal import Signal from lisp.core.singleton import Singleton from lisp.core.util import filter_live_properties @@ -48,7 +49,7 @@ class Application(metaclass=Singleton): - def __init__(self, app_conf): + def __init__(self, app_conf=DummyConfiguration()): self.session_created = Signal() self.session_before_finalize = Signal() diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index 918167299..97a8cbfdd 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -27,7 +27,6 @@ QHeaderView, QGridLayout, QLabel, - QStyledItemDelegate, QHBoxLayout, ) @@ -40,7 +39,7 @@ midi_msg_from_data, midi_from_dict, midi_from_str, -) + MIDI_MSGS_SPEC, MIDI_ATTRS_SPEC) from lisp.plugins.midi.widgets import MIDIMessageEditDialog from lisp.ui.qdelegates import ( CueActionDelegate, @@ -64,7 +63,6 @@ def __init__(self, actionDelegate, **kwargs): self.midiGroup = QGroupBox(self) self.midiGroup.setTitle(translate("ControllerMidiSettings", "MIDI")) - # self.midiGroup.setEnabled(check_module('midi')) self.midiGroup.setLayout(QGridLayout()) self.layout().addWidget(self.midiGroup) @@ -207,6 +205,26 @@ def _text(self, option, index): ) +class MidiValueDelegate(LabelDelegate): + def _text(self, option, index): + option.displayAlignment = Qt.AlignCenter + + value = index.data() + if value is not None: + model = index.model() + message_type = model.data(model.index(index.row(), 0)) + message_spec = MIDI_MSGS_SPEC.get(message_type, ()) + + if len(message_spec) >= index.column(): + attr = message_spec[index.column() - 1] + attr_spec = MIDI_ATTRS_SPEC.get(attr) + + if MIDI_MSGS_SPEC is not None: + return str(value - attr_spec[-1]) + + return "" + + class MidiModel(SimpleTableModel): def __init__(self): super().__init__( @@ -227,7 +245,6 @@ def appendMessage(self, message, action): def updateMessage(self, row, message, action): data = midi_data_from_msg(message) data.extend((None,) * (3 - len(data))) - self.updateRow(row, message.type, *data, action) def getMessage(self, row): @@ -250,9 +267,9 @@ def __init__(self, actionDelegate, **kwargs): self.delegates = [ MidiMessageTypeDelegate(), - QStyledItemDelegate(), - QStyledItemDelegate(), - QStyledItemDelegate(), + MidiValueDelegate(), + MidiValueDelegate(), + MidiValueDelegate(), actionDelegate, ] diff --git a/lisp/plugins/gst_backend/gi_repository.py b/lisp/plugins/gst_backend/gi_repository.py index 12844c979..c1171aaad 100644 --- a/lisp/plugins/gst_backend/gi_repository.py +++ b/lisp/plugins/gst_backend/gi_repository.py @@ -14,4 +14,5 @@ gi.require_version("GstApp", "1.0") # noinspection PyUnresolvedReferences +# pylint: disable=unused-import from gi.repository import Gst, GstPbutils, GObject, GstApp diff --git a/lisp/plugins/list_layout/playing_widgets.py b/lisp/plugins/list_layout/playing_widgets.py index c23330a01..0fee09b43 100644 --- a/lisp/plugins/list_layout/playing_widgets.py +++ b/lisp/plugins/list_layout/playing_widgets.py @@ -20,7 +20,6 @@ from PyQt5.QtWidgets import ( QWidget, QGridLayout, - QLabel, QSizePolicy, QLCDNumber, QHBoxLayout, diff --git a/lisp/plugins/replay_gain/replay_gain.py b/lisp/plugins/replay_gain/replay_gain.py index f686192c2..aab4b442f 100644 --- a/lisp/plugins/replay_gain/replay_gain.py +++ b/lisp/plugins/replay_gain/replay_gain.py @@ -17,7 +17,6 @@ import logging from concurrent.futures import ThreadPoolExecutor, as_completed -from math import pow from threading import Thread, Lock import gi diff --git a/lisp/ui/qdelegates.py b/lisp/ui/qdelegates.py index 230613056..a8563f0de 100644 --- a/lisp/ui/qdelegates.py +++ b/lisp/ui/qdelegates.py @@ -17,7 +17,7 @@ from enum import Enum -from PyQt5.QtCore import Qt, QEvent, QSize, QRect +from PyQt5.QtCore import Qt, QEvent, QSize from PyQt5.QtGui import QKeySequence, QFontMetrics from PyQt5.QtWidgets import ( QStyledItemDelegate, diff --git a/lisp/ui/themes/dark/dark.py b/lisp/ui/themes/dark/dark.py index cf7408081..0f413f180 100644 --- a/lisp/ui/themes/dark/dark.py +++ b/lisp/ui/themes/dark/dark.py @@ -20,6 +20,7 @@ from PyQt5.QtGui import QColor, QPalette # Import resources +# pylint: disable=unused-import # noinspection PyUnresolvedReferences from . import assets From a70375b7d801662c0eef3e1bbdba2056393fbffd Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Wed, 28 Aug 2019 22:38:02 +0200 Subject: [PATCH 200/333] Fix possible problems with older configurations. --- lisp/plugins/controller/default.json | 2 +- lisp/plugins/controller/protocols/midi.py | 20 +++++++++++++++++--- lisp/plugins/controller/protocols/osc.py | 15 +++++++++++++-- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/lisp/plugins/controller/default.json b/lisp/plugins/controller/default.json index 77ff608b4..84b2ed894 100644 --- a/lisp/plugins/controller/default.json +++ b/lisp/plugins/controller/default.json @@ -1,5 +1,5 @@ { - "_version_": "0.2", + "_version_": "0.3", "_enabled_": true, "protocols": { "keyboard": {}, diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index 97a8cbfdd..0d571a6f1 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -14,6 +14,7 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import ( @@ -39,7 +40,9 @@ midi_msg_from_data, midi_from_dict, midi_from_str, - MIDI_MSGS_SPEC, MIDI_ATTRS_SPEC) + MIDI_MSGS_SPEC, + MIDI_ATTRS_SPEC, +) from lisp.plugins.midi.widgets import MIDIMessageEditDialog from lisp.ui.qdelegates import ( CueActionDelegate, @@ -51,6 +54,9 @@ from lisp.ui.ui_utils import translate +logger = logging.getLogger(__name__) + + class MidiSettings(SettingsPage): FILTER_ALL = "__all__" @@ -133,9 +139,17 @@ def getSettings(self): return {"midi": entries} def loadSettings(self, settings): - if "midi" in settings: - for entry in settings["midi"]: + for entry in settings.get("midi", ()): + try: self.midiModel.appendMessage(midi_from_str(entry[0]), entry[1]) + except Exception: + logger.warning( + translate( + "ControllerMidiSettingsWarning" + "Error while importing configuration entry, skipped." + ), + exc_info=True, + ) def capture_message(self): handler = self.__midi.input diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 64fe2a7db..72d579c2a 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -18,6 +18,7 @@ import ast +import logging from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import ( @@ -50,6 +51,8 @@ from lisp.ui.settings.pages import SettingsPage, CuePageMixin from lisp.ui.ui_utils import translate +logger = logging.getLogger(__name__) + class OscMessageDialog(QDialog): def __init__(self, **kwargs): @@ -265,12 +268,20 @@ def getSettings(self): return {"osc": entries} def loadSettings(self, settings): - if "osc" in settings: - for options in settings["osc"]: + for options in settings.get("osc", ()): + try: key = Osc.message_from_key(options[0]) self.oscModel.appendRow( key[0], key[1], "{}".format(key[2:])[1:-1], options[1] ) + except Exception: + logger.warning( + translate( + "ControllerOscSettingsWarning" + "Error while importing configuration entry, skipped." + ), + exc_info=True, + ) def capture_message(self): self.__osc.server.new_message.connect(self.__show_message) From 159c8c6d8e9d006cbd36363bc02789df1040d1d5 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 29 Aug 2019 22:05:27 +0200 Subject: [PATCH 201/333] minor fix --- lisp/plugins/controller/protocols/midi.py | 2 +- lisp/plugins/controller/protocols/osc.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index 0d571a6f1..c10d17f81 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -145,7 +145,7 @@ def loadSettings(self, settings): except Exception: logger.warning( translate( - "ControllerMidiSettingsWarning" + "ControllerMidiSettingsWarning", "Error while importing configuration entry, skipped." ), exc_info=True, diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 72d579c2a..218fcb7b4 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -277,7 +277,7 @@ def loadSettings(self, settings): except Exception: logger.warning( translate( - "ControllerOscSettingsWarning" + "ControllerOscSettingsWarning", "Error while importing configuration entry, skipped." ), exc_info=True, From bea6dcb6517d9b5639a7150be7175d47a5d6efec Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 21 Sep 2019 12:15:14 +0200 Subject: [PATCH 202/333] MIDI messages (emitted by the midi plugin) are no longer altered by the controller plugin --- lisp/plugins/controller/protocols/midi.py | 6 +++--- lisp/plugins/controller/protocols/osc.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lisp/plugins/controller/protocols/midi.py b/lisp/plugins/controller/protocols/midi.py index c10d17f81..27ccf64d1 100644 --- a/lisp/plugins/controller/protocols/midi.py +++ b/lisp/plugins/controller/protocols/midi.py @@ -146,7 +146,7 @@ def loadSettings(self, settings): logger.warning( translate( "ControllerMidiSettingsWarning", - "Error while importing configuration entry, skipped." + "Error while importing configuration entry, skipped.", ), exc_info=True, ) @@ -169,7 +169,7 @@ def __add_message(self, message): mgs_filter = self.filterTypeCombo.currentData(Qt.UserRole) if mgs_filter == self.FILTER_ALL or message.type == mgs_filter: if hasattr(message, "velocity"): - message.velocity = 0 + message = message.copy(velocity=0) self.midiModel.appendMessage(message, self._defaultAction) @@ -333,6 +333,6 @@ def __init__(self): def __new_message(self, message): if hasattr(message, "velocity"): - message.velocity = 0 + message = message.copy(velocity=0) self.protocol_event.emit(str(message)) diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 218fcb7b4..eed62e052 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -278,7 +278,7 @@ def loadSettings(self, settings): logger.warning( translate( "ControllerOscSettingsWarning", - "Error while importing configuration entry, skipped." + "Error while importing configuration entry, skipped.", ), exc_info=True, ) From 5ae8170581cf3362e4a447358a9606edf17b1c83 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 7 Nov 2019 00:02:12 +0100 Subject: [PATCH 203/333] Monitor MIDI ports, for automatic reconnection, via python-alsa. --- Pipfile | 3 +- Pipfile.lock | 231 ++++++++++++++++------------- lisp/plugins/midi/default.json | 5 +- lisp/plugins/midi/midi.py | 147 +++++++++++++----- lisp/plugins/midi/midi_io.py | 68 ++++++--- lisp/plugins/midi/midi_settings.py | 93 ++++++++---- lisp/plugins/midi/midi_utils.py | 39 +++-- lisp/plugins/midi/port_monitor.py | 105 +++++++++++++ scripts/Flatpak/template.json | 14 ++ 9 files changed, 499 insertions(+), 206 deletions(-) create mode 100644 lisp/plugins/midi/port_monitor.py diff --git a/Pipfile b/Pipfile index cd7c700c9..ba9de7de9 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,8 @@ PyQt5 = "~=5.6" python-rtmidi = "~=1.1" requests = "~=2.20" sortedcontainers = "~=2.0" +pyudev = "~=0.21" [dev-packages] pysnooper = "*" -html5lib = "*" +html5lib = "*" \ No newline at end of file diff --git a/Pipfile.lock b/Pipfile.lock index badd7d620..d312d45e6 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "9cc5551cdfd52de589fddc38882ec9053b982b431f2e5f38eac4727b398137e1" + "sha256": "10ea83f3292f6fc7b6370a438e3c32738b187e1280feb34136152369ceaf1a9f" }, "pipfile-spec": 6, "requires": {}, @@ -24,43 +24,47 @@ }, "certifi": { "hashes": [ - "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", - "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" + "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", + "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" ], - "version": "==2019.3.9" + "version": "==2019.9.11" }, "cffi": { "hashes": [ - "sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774", - "sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d", - "sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90", - "sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b", - "sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63", - "sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45", - "sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25", - "sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3", - "sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b", - "sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647", - "sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016", - "sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4", - "sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb", - "sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753", - "sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7", - "sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9", - "sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f", - "sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8", - "sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f", - "sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc", - "sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42", - "sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3", - "sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909", - "sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45", - "sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d", - "sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512", - "sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff", - "sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201" + "sha256:08f99e8b38d5134d504aa7e486af8e4fde66a2f388bbecc270cdd1e00fa09ff8", + "sha256:1112d2fc92a867a6103bce6740a549e74b1d320cf28875609f6e93857eee4f2d", + "sha256:1b9ab50c74e075bd2ae489853c5f7f592160b379df53b7f72befcbe145475a36", + "sha256:24eff2997436b6156c2f30bed215c782b1d8fd8c6a704206053c79af95962e45", + "sha256:2eff642fbc9877a6449026ad66bf37c73bf4232505fb557168ba5c502f95999b", + "sha256:362e896cea1249ed5c2a81cf6477fabd9e1a5088aa7ea08358a4c6b0998294d2", + "sha256:40eddb3589f382cb950f2dcf1c39c9b8d7bd5af20665ce273815b0d24635008b", + "sha256:5ed40760976f6b8613d4a0db5e423673ca162d4ed6c9ed92d1f4e58a47ee01b5", + "sha256:632c6112c1e914c486f06cfe3f0cc507f44aa1e00ebf732cedb5719e6aa0466a", + "sha256:64d84f0145e181f4e6cc942088603c8db3ae23485c37eeda71cb3900b5e67cb4", + "sha256:6cb4edcf87d0e7f5bdc7e5c1a0756fbb37081b2181293c5fdf203347df1cd2a2", + "sha256:6f19c9df4785305669335b934c852133faed913c0faa63056248168966f7a7d5", + "sha256:719537b4c5cd5218f0f47826dd705fb7a21d83824920088c4214794457113f3f", + "sha256:7b0e337a70e58f1a36fb483fd63880c9e74f1db5c532b4082bceac83df1523fa", + "sha256:853376efeeb8a4ae49a737d5d30f5db8cdf01d9319695719c4af126488df5a6a", + "sha256:85bbf77ffd12985d76a69d2feb449e35ecdcb4fc54a5f087d2bd54158ae5bb0c", + "sha256:8978115c6f0b0ce5880bc21c967c65058be8a15f1b81aa5fdbdcbea0e03952d1", + "sha256:8f7eec920bc83692231d7306b3e311586c2e340db2dc734c43c37fbf9c981d24", + "sha256:8fe230f612c18af1df6f348d02d682fe2c28ca0a6c3856c99599cdacae7cf226", + "sha256:92068ebc494b5f9826b822cec6569f1f47b9a446a3fef477e1d11d7fac9ea895", + "sha256:b57e1c8bcdd7340e9c9d09613b5e7fdd0c600be142f04e2cc1cc8cb7c0b43529", + "sha256:ba956c9b44646bc1852db715b4a252e52a8f5a4009b57f1dac48ba3203a7bde1", + "sha256:ca42034c11eb447497ea0e7b855d87ccc2aebc1e253c22e7d276b8599c112a27", + "sha256:dc9b2003e9a62bbe0c84a04c61b0329e86fccd85134a78d7aca373bbbf788165", + "sha256:dd308802beb4b2961af8f037becbdf01a1e85009fdfc14088614c1b3c383fae5", + "sha256:e77cd105b19b8cd721d101687fcf665fd1553eb7b57556a1ef0d453b6fc42faa", + "sha256:f56dff1bd81022f1c980754ec721fb8da56192b026f17f0f99b965da5ab4fbd2", + "sha256:fa4cc13c03ea1d0d37ce8528e0ecc988d2365e8ac64d8d86cafab4038cb4ce89", + "sha256:fa8cf1cb974a9f5911d2a0303f6adc40625c05578d8e7ff5d313e1e27850bd59", + "sha256:fb003019f06d5fc0aa4738492ad8df1fa343b8a37cbcf634018ad78575d185df", + "sha256:fd409b7778167c3bcc836484a8f49c0e0b93d3e745d975749f83aa5d18a5822f", + "sha256:fe5d65a3ee38122003245a82303d11ac05ff36531a8f5ce4bc7d4bbc012797e1" ], - "version": "==1.12.3" + "version": "==1.13.0" }, "chardet": { "hashes": [ @@ -71,37 +75,37 @@ }, "cython": { "hashes": [ - "sha256:0afa0b121b89de619e71587e25702e2b7068d7da2164c47e6eee80c17823a62f", - "sha256:1c608ba76f7a20cc9f0c021b7fe5cb04bc1a70327ae93a9298b1bc3e0edddebe", - "sha256:26229570d6787ff3caa932fe9d802960f51a89239b990d275ae845405ce43857", - "sha256:2a9deafa437b6154cac2f25bb88e0bfd075a897c8dc847669d6f478d7e3ee6b1", - "sha256:2f28396fbce6d9d68a40edbf49a6729cf9d92a4d39ff0f501947a89188e9099f", - "sha256:3983dd7b67297db299b403b29b328d9e03e14c4c590ea90aa1ad1d7b35fb178b", - "sha256:4100a3f8e8bbe47d499cdac00e56d5fe750f739701ea52dc049b6c56f5421d97", - "sha256:51abfaa7b6c66f3f18028876713c8804e73d4c2b6ceddbcbcfa8ec62429377f0", - "sha256:61c24f4554efdb8fb1ac6c8e75dab301bcdf2b7b739ed0c2b267493bb43163c5", - "sha256:700ccf921b2fdc9b23910e95b5caae4b35767685e0812343fa7172409f1b5830", - "sha256:7b41eb2e792822a790cb2a171df49d1a9e0baaa8e81f58077b7380a273b93d5f", - "sha256:803987d3b16d55faa997bfc12e8b97f1091f145930dee229b020487aed8a1f44", - "sha256:99af5cfcd208c81998dcf44b3ca466dee7e17453cfb50e98b87947c3a86f8753", - "sha256:9faea1cca34501c7e139bc7ef8e504d532b77865c58592493e2c154a003b450f", - "sha256:a7ba4c9a174db841cfee9a0b92563862a0301d7ca543334666c7266b541f141a", - "sha256:b26071c2313d1880599c69fd831a07b32a8c961ba69d7ccbe5db1cd8d319a4ca", - "sha256:b49dc8e1116abde13a3e6a9eb8da6ab292c5a3325155fb872e39011b110b37e6", - "sha256:bd40def0fd013569887008baa6da9ca428e3d7247adeeaeada153006227bb2e7", - "sha256:bfd0db770e8bd4e044e20298dcae6dfc42561f85d17ee546dcd978c8b23066ae", - "sha256:c2fad1efae5889925c8fd7867fdd61f59480e4e0b510f9db096c912e884704f1", - "sha256:c81aea93d526ccf6bc0b842c91216ee9867cd8792f6725a00f19c8b5837e1715", - "sha256:da786e039b4ad2bce3d53d4799438cf1f5e01a0108f1b8d78ac08e6627281b1a", - "sha256:deab85a069397540987082d251e9c89e0e5b2e3e044014344ff81f60e211fc4b", - "sha256:e3f1e6224c3407beb1849bdc5ae3150929e593e4cffff6ca41c6ec2b10942c80", - "sha256:e74eb224e53aae3943d66e2d29fe42322d5753fd4c0641329bccb7efb3a46552", - "sha256:ee697c7ea65cb14915a64f36874da8ffc2123df43cf8bc952172e04a26656cd6", - "sha256:f37792b16d11606c28e428460bd6a3d14b8917b109e77cdbe4ca78b0b9a52c87", - "sha256:fd2906b54cbf879c09d875ad4e4687c58d87f5ed03496063fec1c9065569fd5d" + "sha256:07efba7b32c082c519b75e3b03821c2f32848e2b3e9986c784bbd8ffaf0666d7", + "sha256:08db41daf18fabf7b7a85e39aa26954f6246994540043194af026c0df65a4942", + "sha256:19bbe3caf885a1d2e2c30eacc10d1e45dbbefb156493fe1d5d1adc1668cc1269", + "sha256:1c574f2f2ba760b82b2bcf6262e77e75589247dc5ef796a3ff1b2213e50ee452", + "sha256:1dfe672c686e34598bdbaa93c3b30acb3720ae9258232a4f68ba04ee9969063d", + "sha256:283faea84e6c4e54c3f5c8ff89aa2b6c1c3a813aad4f6d48ed3b9cc9043ef9f9", + "sha256:2a145888d0942e7c36e86a7b7c7e2923cb9f7055805a3b72dcb137e3efdb0979", + "sha256:3f75065936e16569d6e13dfd76de988f5eabeae460aa54770c9b961ab6f747fc", + "sha256:4d78124f5f281f1d5d5b7919cbbc65a7073ff93562def81ee78a8307e6e72494", + "sha256:5ba4d088b8e5d59b8a5911ca9c72952acf3c83296b57daf75af92fb2af1e8423", + "sha256:6b19daeda1d5d1dfc973b291246f6a63a663b20c33980724d6d073c562719536", + "sha256:790c7dc80fd1c3e38acefe06027e2f5a8466c128c7e47c6e140fd5316132574d", + "sha256:7f8c4e648881454ba3ba0bcf3b21a9e1878a67d20ea2b8d9ec1c4c628592ab6b", + "sha256:8bcd3f597290f9902548d6355898d7e376e7f3762f89db9cd50b2b58429df9e8", + "sha256:8ffb18f71972a5c718a8600d9f52e3507f0d6fb72a978e03270d34a7035c98fb", + "sha256:92f025df1cb391e09f65775598c7dfb7efad72d74713775db54e267f62ca94a1", + "sha256:93cf1c72472a2fd0ef4c52f6074dab08fc28d475b9c824ba73a52701f7a48ae1", + "sha256:9a7fa692cdc967fdbf6a053c1975137d01f6935dede2ef222c71840b290caf79", + "sha256:a68eb0c1375f2401de881692b30370a51e550052b8e346b2f71bbdbdc74a214f", + "sha256:ac3b7a12ddd52ea910ee3a041e6bc65df7a52f0ba7bd10fb7123502af482c152", + "sha256:b402b700edaf571a0bae18ec35d5b71c266873a6616412b672435c10b6d8f041", + "sha256:c29d069a4a30f472482343c866f7486731ad638ef9af92bfe5fca9c7323d638e", + "sha256:d822311498f185db449b687336b4e5db7638c8d8b03bdf10ae91d74e23c7cc0c", + "sha256:dccc8df9e1ac158b06777bbaaeb4516f245f9b147701ae25e6023960e4a0c2a3", + "sha256:e31f4b946c2765b2f35440fdb4b00c496dfc5babc53c7ae61966b41171d1d59f", + "sha256:eb43f9e582cc221ee2832e25ea6fe5c06f2acc9da6353c562e922f107db12af8", + "sha256:f07822248110fd6213db8bc2745fdbbccef6f2b3d18ac91a7fba29c6bc575da5", + "sha256:ff69854f123b959d4ae14bd5330714bb9ee4360052992dc0fbd0a3dee4261f95" ], "index": "pypi", - "version": "==0.29.10" + "version": "==0.29.13" }, "falcon": { "hashes": [ @@ -132,11 +136,11 @@ }, "jack-client": { "hashes": [ - "sha256:10078565681fa1fb807fb171b3666c17e7c827d572f0cfb28ed2c425918e5499", - "sha256:36cfa279ec4eb37aa6ecf0feed3e2419c38d9963e61c570d71cac43208402968" + "sha256:7794cdbef5e9a2de973bc2288441103811167d57660df88f6b368a30063ed8b4", + "sha256:d17e47d20e1d79e99a9c2e7d2c2b20f9e6c5d86d7415ea7894832e3c2f480819" ], "index": "pypi", - "version": "==0.4.6" + "version": "==0.5.0" }, "mido": { "hashes": [ @@ -160,10 +164,10 @@ }, "pygobject": { "hashes": [ - "sha256:4165d9fb4157e69c76f29e52a6d73a39a804e18ad9ef0eede15efd9c57c52bf2" + "sha256:2acb0daf2b3a23a90f52066cc23d1053339fee2f5f7f4275f8baa3704ae0c543" ], "index": "pypi", - "version": "==3.32.1" + "version": "==3.34.0" }, "pyliblo": { "hashes": [ @@ -174,48 +178,60 @@ }, "pyqt5": { "hashes": [ - "sha256:06cb9a1ea1289ca2b2d81f9a2da44a05029d5b4d36fa47b6a0c0b9027ff03fef", - "sha256:44b263f395045eb5bba34f4df2d269e07b1e073fd359645b022943dd1accadfd", - "sha256:bb4ec5583baa18f741ec6c229489dc1910290d4ce4d6756a2ea062f7bf6456e6", - "sha256:c168a8883bbe7877809c3239c5dcfb9e8de5fe7e8e828c8add7e4f77cc8fc02a" + "sha256:6420d8c9de8746917bd2afe24f29b3722aabde36d1e9f8c34354f5b7fb7b987d", + "sha256:73f982b60819cc6d7cb0007b8b0cd11073e2546d74e0463726149e6a9d087caa", + "sha256:8e6125f110133069e8f4a085022cfc6b4a0af5b3c9009999a749007c299e965d", + "sha256:f8e76070782e68ba200eb2b67babf89ac629df5e36d2f4c680ef34ae40c8f964" ], "index": "pypi", - "version": "==5.12.2" + "version": "==5.13.1" }, "pyqt5-sip": { "hashes": [ - "sha256:0c05e357c8b781458cebb120e84aae0dad9ffc9a8742959e8564d372c1a4fb9a", - "sha256:41c1ff4859ef9b8b7db4c6a09229471378dfe7095cf96ad2c1ea4d6753340d20", - "sha256:4db3f0b1b63311780158d467cfb61be0fbf189141dd5f5ce597e0faeb8f227eb", - "sha256:5e8bcb8bbc3b83d4d6142d2c3188e06bd6062bab132e240b8da42fb80e6dd3ba", - "sha256:7caca9ab0639ecc187e716c07edd285da314ad30f3ec729bcc6b62ea62171cf5", - "sha256:8ed141db021103d9c196f6f297e39b3ade1b06f0ebbd3fe7e9101fac6e8974dd", - "sha256:9d5e33850c5566f4e2da9c244e7f2beb87cab980567d4df902c2953308271df7", - "sha256:a373e19e3679408bf0cee0a6591360e6d3383498b1d75ce8f303ab658f1914d5", - "sha256:aab3d79ad9c7b6652f7d740b27c672dc9e6a21311b68f9fbbe33d8fdd086c73e", - "sha256:b62a4aced469e9bcec88450e2a00df31d754c82b97457cd611615807526682ff", - "sha256:c1e1d6e1496a17e025f66a258e8c4011c4da223937789dbf2d0279516c948fdc", - "sha256:dcadf1593b5891ddb21df4ac943d720d9da22be2a686ad73621ca86d1f72dafe" + "sha256:02d94786bada670ab17a2b62ce95b3cf8e3b40c99d36007593a6334d551840bb", + "sha256:06bc66b50556fb949f14875a4c224423dbf03f972497ccb883fb19b7b7c3b346", + "sha256:091fbbe10a7aebadc0e8897a9449cda08d3c3f663460d812eca3001ca1ed3526", + "sha256:0a067ade558befe4d46335b13d8b602b5044363bfd601419b556d4ec659bca18", + "sha256:1910c1cb5a388d4e59ebb2895d7015f360f3f6eeb1700e7e33e866c53137eb9e", + "sha256:1c7ad791ec86247f35243bbbdd29cd59989afbe0ab678e0a41211f4407f21dd8", + "sha256:3c330ff1f70b3eaa6f63dce9274df996dffea82ad9726aa8e3d6cbe38e986b2f", + "sha256:482a910fa73ee0e36c258d7646ef38f8061774bbc1765a7da68c65056b573341", + "sha256:7695dfafb4f5549ce1290ae643d6508dfc2646a9003c989218be3ce42a1aa422", + "sha256:8274ed50f4ffbe91d0f4cc5454394631edfecd75dc327aa01be8bc5818a57e88", + "sha256:9047d887d97663790d811ac4e0d2e895f1bf2ecac4041691487de40c30239480", + "sha256:9f6ab1417ecfa6c1ce6ce941e0cebc03e3ec9cd9925058043229a5f003ae5e40", + "sha256:b43ba2f18999d41c3df72f590348152e14cd4f6dcea2058c734d688dfb1ec61f", + "sha256:c3ab9ea1bc3f4ce8c57ebc66fb25cd044ef92ed1ca2afa3729854ecc59658905", + "sha256:da69ba17f6ece9a85617743cb19de689f2d63025bf8001e2facee2ec9bcff18f", + "sha256:ef3c7a0bf78674b0dda86ff5809d8495019903a096c128e1f160984b37848f73", + "sha256:fabff832046643cdb93920ddaa8f77344df90768930fbe6bb33d211c4dcd0b5e" ], - "version": "==4.19.17" + "version": "==12.7.0" }, "python-rtmidi": { "hashes": [ - "sha256:171eaa08fe9b7efefbf1ff9edd9f4ace4c588e6771bf33f75f301d071ba72b20", - "sha256:178fbd5b648c7df2290567090c74eb721d5c547b9261a3509c228bb4be2ada24", - "sha256:4390763bdfc27e3b6e5cd3ba8a44d3a7b5ff5af37f4e06124939548ad93fe5f1", - "sha256:61e9d1c1f1202a1577f06644948af985427030984ff956970a22b50f080d4c2d", - "sha256:7de81e8c807fbc8c2af3890a970ebe38994c031a15b0b8ef64e622bdec1116a9", - "sha256:a5aaba71160f43223bd2e9ffcc99951b682d6fd50370916ccb55354625a81cd7", - "sha256:cbd7f4ca4a950776ee1b9aee66a9d03cbc1f8576135699373d2c5d690970a80f", - "sha256:cf4fce7eb13bf5e276c7357bd5db31decdee7f5e5746848b3aa56f3f325dfdcc", - "sha256:db2031d4ec4429fae3dffaadb0d2bf46e764b009581fc27ef1c94224e95a3ed5", - "sha256:de3b919715d3e86ff4fdf3077934a6932611cc28f40bd89ea97e44c387e61dbb", - "sha256:f108d55feebcdf537ab435557e29bc0c77f685daddc7b665223222e4914ef9d1", - "sha256:f32074ae6fefe43ab37c5ab8a52a1b2133ab488a43c6dfe256de1dd693d5bf7c" + "sha256:30d9fbb0070df100f58f6e93b8149aac2d435ae3b7d368e1fce9fd691184255c", + "sha256:521f2ca02876fae4f3efe7b52dfd2ec7cdba809af07905bd3b25b5aee36eaa50", + "sha256:67405b66ba90f8ef423974904ad12bf92d80fc14e4180d13a69e05d787a72849", + "sha256:6a47a5e26675fba87fc90917c63139c8b3aa793bcb429f22fc52fffd87853f00", + "sha256:7724252c7a2d407c510f089da321755dedbc05a864c1e97c034c807882c3234c", + "sha256:977d93ad6b45b48efddb92aed7fd69e1a33ff5d7130399971101133ad673be3e", + "sha256:ad2a5f0c14faf9810cc92d919096be7167eff8d4fd63abc5e2d7c20e5da9326c", + "sha256:bee33b8c06431b0f5cfdf500c68e0562e1ec2bd46b8d883b216707f16d51e9a3", + "sha256:d657e4f4ce4ad5b1c7ee738540889c031344c59159483bdbb65dab6c8459c746", + "sha256:e516a7f876381667eba82417254bb8d138c17dc7666fd70212d839813b60ea19", + "sha256:f9d2a1b5374b2e6856006537b6d4d5ceed4db4c4ee60caec4af83707a78297e4", + "sha256:ffaed4e6654f5b7458e622146f5cd9253b1ca2986a118b9a86546111b1a55731" ], "index": "pypi", - "version": "==1.3.0" + "version": "==1.3.1" + }, + "pyudev": { + "hashes": [ + "sha256:094b7a100150114748aaa3b70663485dd360457a709bfaaafe5a977371033f2b" + ], + "index": "pypi", + "version": "==0.21.0" }, "requests": { "hashes": [ @@ -225,6 +241,13 @@ "index": "pypi", "version": "==2.22.0" }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, "sortedcontainers": { "hashes": [ "sha256:974e9a32f56b17c1bac2aebd9dcf197f3eb9cd30553c5852a3187ad162e1a03a", @@ -235,10 +258,10 @@ }, "urllib3": { "hashes": [ - "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", - "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" + "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", + "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86" ], - "version": "==1.25.3" + "version": "==1.25.6" } }, "develop": { @@ -252,11 +275,11 @@ }, "pysnooper": { "hashes": [ - "sha256:edfac97c18b0e0b346bb7de02d5c2abfb3bdeeba5a73f3e6ade90477e6f4d7ac", - "sha256:f44105ee9653a299f3722f47485caccd7c4d05c650fe3403c42a0a9f948d9a28" + "sha256:845cc1c160d5f4b6f9ba62d6fadc2f7f51daa8759a34c93dee286a94dd77c3a9", + "sha256:9db476d6d92284e5f1fef557c193dc378327dbe6aeb6eea6b044013a006f221b" ], "index": "pypi", - "version": "==0.1.0" + "version": "==0.2.8" }, "six": { "hashes": [ diff --git a/lisp/plugins/midi/default.json b/lisp/plugins/midi/default.json index d1ead3531..d74bf8296 100644 --- a/lisp/plugins/midi/default.json +++ b/lisp/plugins/midi/default.json @@ -1,7 +1,8 @@ { - "_version_": "2", + "_version_": "2.1", "_enabled_": true, "backend": "mido.backends.rtmidi", "inputDevice": "", - "outputDevice": "" + "outputDevice": "", + "connectByNameMatch": true } \ No newline at end of file diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index 79e6b898a..3de80f7c9 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -15,15 +15,23 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging + import mido from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.core.plugin import Plugin +from lisp.core.signal import Connection from lisp.cues.cue_factory import CueFactory from lisp.plugins.midi.midi_cue import MidiCue -from lisp.plugins.midi.midi_io import MIDIOutput, MIDIInput +from lisp.plugins.midi.midi_io import MIDIOutput, MIDIInput, MIDIBase from lisp.plugins.midi.midi_settings import MIDISettings +from lisp.plugins.midi.midi_utils import midi_output_names, midi_input_names +from lisp.plugins.midi.port_monitor import ALSAPortMonitor from lisp.ui.settings.app_configuration import AppConfigurationDialog +from lisp.ui.ui_utils import translate + +logger = logging.getLogger(__name__) class Midi(Plugin): @@ -40,6 +48,7 @@ def __init__(self, app): AppConfigurationDialog.registerSettingsPage( "plugins.midi", MIDISettings, Midi.Config ) + # Register cue CueFactory.register_factory(MidiCue.__name__, MidiCue) app.window.registerSimpleCueMenu( @@ -50,49 +59,39 @@ def __init__(self, app): self.backend = mido.Backend(Midi.Config["backend"], load=True) mido.set_backend(self.backend) - # Create default I/O and open the ports/devices - self.__input = MIDIInput(self._input_name(Midi.Config["inputDevice"])) - self.__input.open() - - self.__output = MIDIOutput( - self._output_name(Midi.Config["outputDevice"]) - ) - self.__output.open() - - Midi.Config.changed.connect(self.__config_change) - Midi.Config.updated.connect(self.__config_update) - - def __config_change(self, key, value): - if key == "inputDevice": - self.__input.change_port(self._input_name(value)) - elif key == "outputDevice": - self.__output.change_port(self._output_name(value)) - - def __config_update(self, diff): - for key, value in diff.items(): - self.__config_change(key, value) + # Define the default ports + avail_inputs = self.backend.get_input_names() + avail_outputs = self.backend.get_output_names() + self.__default_input = avail_inputs[0] if avail_inputs else None + self.__default_output = avail_outputs[0] if avail_outputs else None - def _input_name(self, port_name): - """Check if port_name exists as an input port for the current backend. + # Create input handler and connect + current_input = self.input_name() + self.__input = MIDIInput(self.backend, current_input) + self._reconnect(self.__input, current_input, avail_inputs) - :param port_name: The input port name to check - :type port_name: str + # Create output handler and connect + current_output = self.output_name() + self.__output = MIDIOutput(self.backend, current_output) + self._reconnect(self.__output, current_output, avail_outputs) - :returns The input port name if exists, None otherwise """ - if port_name != "" and port_name in self.backend.get_input_names(): - return port_name + Monitor ports, for auto-reconnection. - def _output_name(self, port_name): - """Check if port_name exists as an output port for the current backend. - - :param port_name: The output port name to check - :type port_name: str - - :returns The output port name if exists, None otherwise + Since current midi backends are not reliable on connection/disconnection + detection we need to use the native APIs. """ - if port_name != "" and port_name in self.backend.get_output_names(): - return port_name + self.port_monitor = ALSAPortMonitor() + self.port_monitor.port_removed.connect( + self._on_port_removed, Connection.QtQueued + ) + self.port_monitor.port_added.connect( + self._on_port_added, Connection.QtQueued + ) + + # Observe configuration changes + Midi.Config.changed.connect(self.__config_change) + Midi.Config.updated.connect(self.__config_update) @property def input(self): @@ -101,3 +100,75 @@ def input(self): @property def output(self): return self.__output + + def input_name(self): + return Midi.Config["inputDevice"] or self.__default_input + + def output_name(self): + return Midi.Config["outputDevice"] or self.__default_output + + def _on_port_removed(self): + if self.__input.is_open(): + if self.input_name() not in midi_input_names(): + logger.info( + translate( + "MIDIInfo", "MIDI port disconnected: '{}'" + ).format(self.__input.port_name) + ) + self.__input.close() + + if self.__output.is_open(): + if self.output_name() not in midi_output_names(): + logger.info( + translate( + "MIDIInfo", "MIDI port disconnected: '{}'" + ).format(self.__output.port_name) + ) + self.__input.close() + + def _on_port_added(self): + if not self.__input.is_open(): + self._reconnect(self.__input, self.input_name(), midi_input_names()) + + if not self.__output.is_open(): + self._reconnect( + self.__output, self.output_name(), midi_output_names() + ) + + def _reconnect(self, midi: MIDIBase, current: str, available: list): + if current in available: + logger.info( + translate("MIDIInfo", "Connecting to MIDI port: '{}'").format( + current + ) + ) + midi.open() + elif Midi.Config["connectByNameMatch"]: + match = self._port_search_match(current, available) + if match is not None: + logger.info( + translate( + "MIDIInfo", "Connecting to matching MIDI port: '{}'" + ).format(match) + ) + midi.change_port(match) + + def _port_search_match(self, to_match, available_names): + # Strip client-id and port-id from the name + simple_name = " ".join(to_match.split(" ")[:-1]) + + for possible_match in available_names: + if possible_match.startswith(simple_name): + return possible_match + + def __config_change(self, key, _): + if key == "inputDevice": + self.__input.change_port(self.input_name()) + elif key == "outputDevice": + self.__output.change_port(self.output_name()) + + def __config_update(self, diff): + if "inputDevice" in diff: + self.__config_change("inputDevice", diff["inputDevice"]) + if "outputDevice" in diff: + self.__config_change("outputDevice", diff["outputDevice"]) diff --git a/lisp/plugins/midi/midi_io.py b/lisp/plugins/midi/midi_io.py index 84edc5d04..87679bbb5 100644 --- a/lisp/plugins/midi/midi_io.py +++ b/lisp/plugins/midi/midi_io.py @@ -15,23 +15,39 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import logging from abc import ABC, abstractmethod -import mido - from lisp.core.signal import Signal -from lisp.plugins.midi.midi_utils import mido_backend +from lisp.ui.ui_utils import translate + +logger = logging.getLogger(__name__) class MIDIBase(ABC): - def __init__(self, port_name=None): + def __init__(self, backend, port_name=None): """ :param port_name: the port name """ - self._backend = None + self._backend = backend self._port_name = port_name self._port = None + @property + def backend(self): + return self._backend + + @property + def port_name(self, real=True): + if real and self._port is not None: + return self._port.name + else: + return self._port_name + + @property + def port(self): + return self._port + def change_port(self, port_name): self._port_name = port_name self.close() @@ -45,6 +61,7 @@ def close(self): """Close the port""" if self._port is not None: self._port.close() + self._port = None def is_open(self): if self._port is not None: @@ -54,31 +71,46 @@ def is_open(self): class MIDIOutput(MIDIBase): - def __init__(self, port_name=None): - super().__init__(port_name=port_name) - - def send_from_str(self, str_message): - self.send(mido.parse_string(str_message)) - def send(self, message): - self._port.send(message) + if self._port is not None: + self._port.send(message) def open(self): - self._port = mido_backend().open_output(self._port_name) + try: + self._port = self._backend.open_output(self._port_name) + except OSError: + logger.exception( + translate( + "MIDIError", + "Cannot connect to MIDI output port '{}'.".format( + self._port_name + ), + ) + ) class MIDIInput(MIDIBase): - def __init__(self, port_name=None): - super().__init__(port_name=port_name) + def __init__(self, *args): + super().__init__(*args) self.alternate_mode = False self.new_message = Signal() self.new_message_alt = Signal() def open(self): - self._port = mido_backend().open_input( - name=self._port_name, callback=self.__new_message - ) + try: + self._port = self._backend.open_input( + name=self._port_name, callback=self.__new_message + ) + except OSError: + logger.exception( + translate( + "MIDIError", + "Cannot connect to MIDI input port '{}'.".format( + self._port_name + ), + ) + ) def __new_message(self, message): if self.alternate_mode: diff --git a/lisp/plugins/midi/midi_settings.py b/lisp/plugins/midi/midi_settings.py index 50fbbc7bd..1db98a423 100644 --- a/lisp/plugins/midi/midi_settings.py +++ b/lisp/plugins/midi/midi_settings.py @@ -22,9 +22,11 @@ QComboBox, QGridLayout, QLabel, + QCheckBox, ) -from lisp.plugins.midi.midi_utils import mido_backend +from lisp.plugins.midi.midi_utils import midi_input_names, midi_output_names +from lisp.ui.icons import IconTheme from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate @@ -37,41 +39,73 @@ def __init__(self, **kwargs): self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) - self.midiGroup = QGroupBox(self) - self.midiGroup.setTitle( - translate("MIDISettings", "MIDI default devices") - ) - self.midiGroup.setLayout(QGridLayout()) - self.layout().addWidget(self.midiGroup) + self.portsGroup = QGroupBox(self) + self.portsGroup.setLayout(QGridLayout()) + self.layout().addWidget(self.portsGroup) - self.inputLabel = QLabel( - translate("MIDISettings", "Input"), self.midiGroup - ) - self.midiGroup.layout().addWidget(self.inputLabel, 0, 0) - self.inputCombo = QComboBox(self.midiGroup) - self.midiGroup.layout().addWidget(self.inputCombo, 0, 1) + # Input port + self.inputLabel = QLabel(self.portsGroup) + self.portsGroup.layout().addWidget(self.inputLabel, 0, 0) + self.inputCombo = QComboBox(self.portsGroup) + self.portsGroup.layout().addWidget(self.inputCombo, 0, 1) - self.outputLabel = QLabel( - translate("MIDISettings", "Output"), self.midiGroup - ) - self.midiGroup.layout().addWidget(self.outputLabel, 1, 0) - self.outputCombo = QComboBox(self.midiGroup) - self.midiGroup.layout().addWidget(self.outputCombo, 1, 1) + # Output port + self.outputLabel = QLabel(self.portsGroup) + self.portsGroup.layout().addWidget(self.outputLabel, 1, 0) + self.outputCombo = QComboBox(self.portsGroup) + self.portsGroup.layout().addWidget(self.outputCombo, 1, 1) + + self.portsGroup.layout().setColumnStretch(0, 2) + self.portsGroup.layout().setColumnStretch(1, 3) + + self.miscGroup = QGroupBox(self) + self.miscGroup.setLayout(QVBoxLayout()) + self.layout().addWidget(self.miscGroup) + + self.nameMatchCheckBox = QCheckBox(self.miscGroup) + self.miscGroup.layout().addWidget(self.nameMatchCheckBox) - self.midiGroup.layout().setColumnStretch(0, 2) - self.midiGroup.layout().setColumnStretch(1, 3) + self.retranslateUi() try: self._loadDevices() except Exception: self.setEnabled(False) + def retranslateUi(self): + self.portsGroup.setTitle( + translate("MIDISettings", "MIDI default devices") + ) + self.inputLabel.setText(translate("MIDISettings", "Input")) + self.outputLabel.setText(translate("MIDISettings", "Output")) + + self.miscGroup.setTitle(translate("MIDISettings", "Misc options")) + self.nameMatchCheckBox.setText( + translate( + "MIDISettings", "Try to connect ignoring the device/port id" + ) + ) + def loadSettings(self, settings): - # TODO: instead of forcing 'Default' add a warning label and keep the invalid device as an option - self.inputCombo.setCurrentText("Default") - self.inputCombo.setCurrentText(settings["inputDevice"]) - self.outputCombo.setCurrentText("Default") - self.outputCombo.setCurrentText(settings["outputDevice"]) + if settings["inputDevice"]: + self.inputCombo.setCurrentText(settings["inputDevice"]) + if self.inputCombo.currentText() != settings["inputDevice"]: + self.inputCombo.insertItem( + 1, IconTheme.get("dialog-warning"), settings["inputDevice"] + ) + self.inputCombo.setCurrentIndex(1) + + if settings["outputDevice"]: + self.outputCombo.setCurrentText(settings["outputDevice"]) + if self.outputCombo.currentText() != settings["outputDevice"]: + self.outputCombo.insertItem( + 1, IconTheme.get("dialog-warning"), settings["outputDevice"] + ) + self.outputCombo.setCurrentIndex(1) + + self.nameMatchCheckBox.setChecked( + settings.get("connectByNameMatch", False) + ) def getSettings(self): if self.isEnabled(): @@ -81,17 +115,16 @@ def getSettings(self): return { "inputDevice": "" if input == "Default" else input, "outputDevice": "" if output == "Default" else output, + "connectByNameMatch": self.nameMatchCheckBox.isChecked(), } return {} def _loadDevices(self): - backend = mido_backend() - self.inputCombo.clear() self.inputCombo.addItems(["Default"]) - self.inputCombo.addItems(backend.get_input_names()) + self.inputCombo.addItems(midi_input_names()) self.outputCombo.clear() self.outputCombo.addItems(["Default"]) - self.outputCombo.addItems(backend.get_output_names()) + self.outputCombo.addItems(midi_output_names()) diff --git a/lisp/plugins/midi/midi_utils.py b/lisp/plugins/midi/midi_utils.py index 82093a22e..eb3e26aa2 100644 --- a/lisp/plugins/midi/midi_utils.py +++ b/lisp/plugins/midi/midi_utils.py @@ -19,7 +19,6 @@ import mido from PyQt5.QtCore import QT_TRANSLATE_NOOP -from mido import Message MIDI_MSGS_SPEC = { "note_on": ["channel", "note", "velocity"], @@ -78,6 +77,19 @@ } +def midi_backend() -> mido.Backend: + """Return the current backend object.""" + backend = None + + if hasattr(mido, "backend"): + backend = mido.backend + + if backend is None: + raise RuntimeError("MIDI backend not loaded") + + return backend + + def midi_str_to_dict(midi_str: str) -> dict: return mido.parse_string(midi_str).dict() @@ -87,7 +99,7 @@ def midi_dict_to_str(midi_dict: dict) -> str: return mido.format_as_string(message, include_time=False) -def midi_data_from_msg(message) -> list: +def midi_data_from_msg(message: mido.Message) -> list: data = [] for attr in MIDI_MSGS_SPEC.get(message.type, ()): @@ -101,9 +113,9 @@ def midi_data_from_dict(midi_dict: dict) -> list: return midi_data_from_msg(midi_from_dict(midi_dict)) -def midi_msg_from_data(message_type: str, data: Iterable): +def midi_msg_from_data(message_type: str, data: Iterable) -> mido.Message: message_spec = MIDI_MSGS_SPEC.get(message_type, ()) - message = Message(message_type) + message = mido.Message(message_type) for attr, value in zip(message_spec, data): if attr is not None: @@ -112,22 +124,23 @@ def midi_msg_from_data(message_type: str, data: Iterable): return message -def midi_from_dict(midi_dict: dict): +def midi_from_dict(midi_dict: dict) -> mido.Message: return mido.Message.from_dict(midi_dict) -def midi_from_str(midi_str: str): +def midi_from_str(midi_str: str) -> mido.Message: return mido.Message.from_str(midi_str) -def mido_backend(): - """Return the current backend object, or None""" - backend = None +def midi_input_names(backend: mido.Backend = None): + if backend is None: + backend = midi_backend() + + return backend.get_input_names() - if hasattr(mido, "backend"): - backend = mido.backend +def midi_output_names(backend: mido.Backend = None): if backend is None: - raise RuntimeError("MIDI backend not loaded") + backend = midi_backend() - return backend + return backend.get_output_names() diff --git a/lisp/plugins/midi/port_monitor.py b/lisp/plugins/midi/port_monitor.py new file mode 100644 index 000000000..42c1674b1 --- /dev/null +++ b/lisp/plugins/midi/port_monitor.py @@ -0,0 +1,105 @@ +# This file is part of Linux Show Player +# +# Copyright 2019 Francesco Ceruti +# +# Linux Show Player 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. +# +# Linux Show Player 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 Linux Show Player. If not, see . + +import logging +from threading import Thread + +try: + from pyalsa import alsaseq +except ImportError: + alsaseq = False + +from lisp.core.signal import Signal +from lisp.ui.ui_utils import translate + +logger = logging.getLogger(__name__) + + +class PortMonitor: + ClientName = "LinuxShowPlayer_Monitor" + + def __init__(self): + self.port_removed = Signal() + self.port_added = Signal() + + +class _ALSAPortMonitor(PortMonitor): + SEQ_OPEN_INPUT = 2 + SEQ_OPEN_OUTPUT = 1 + SEQ_OPEN_DUPLEX = SEQ_OPEN_OUTPUT | SEQ_OPEN_INPUT + SEQ_BLOCK = 0 + SEQ_NONBLOCK = 1 + SEQ_CLIENT_SYSTEM = 0 + + SEQ_PORT_SYSTEM_ANNOUNCE = 1 + SEQ_PORT_CAP_READ = 1 + SEQ_PORT_CAP_WRITE = 2 + SEQ_PORT_CAP_RW = SEQ_PORT_CAP_READ | SEQ_PORT_CAP_WRITE + PORT_TYPE_APPLICATION = 1048576 + + def __init__(self): + super().__init__() + self.__thread = Thread(target=self.__loop, daemon=True) + + try: + # Create ALSA Sequencer + self.__seq = alsaseq.Sequencer( + name="default", + clientname=_ALSAPortMonitor.ClientName, + streams=_ALSAPortMonitor.SEQ_OPEN_DUPLEX, + mode=_ALSAPortMonitor.SEQ_NONBLOCK, + ) + + # Create monitor port + self.__port = self.__seq.create_simple_port( + name="monitor", + type=_ALSAPortMonitor.PORT_TYPE_APPLICATION, + caps=_ALSAPortMonitor.SEQ_PORT_CAP_RW, + ) + + # Connect to system-client system-announce-port + self.__seq.connect_ports( + ( + _ALSAPortMonitor.SEQ_CLIENT_SYSTEM, + _ALSAPortMonitor.SEQ_PORT_SYSTEM_ANNOUNCE, + ), + (self.__seq.client_id, self.__port), + ) + + # Start the event-loop + self.__thread.start() + except alsaseq.SequencerError: + logger.exception( + translate( + "MIDIError", + "Cannot create ALSA-MIDI port monitor, " + "MIDI connections/disconnections will not be detected.", + ) + ) + + def __loop(self): + while True: + for event in self.__seq.receive_events(timeout=-1, maxevents=1): + if event.type == alsaseq.SEQ_EVENT_PORT_UNSUBSCRIBED: + logger.debug("ALSA MIDI port unsubscribed.") + self.port_removed.emit() + elif event.type == alsaseq.SEQ_EVENT_PORT_SUBSCRIBED: + logger.debug("ALSA MIDI port subscribed.") + self.port_added.emit() + + +ALSAPortMonitor = _ALSAPortMonitor if alsaseq else PortMonitor diff --git a/scripts/Flatpak/template.json b/scripts/Flatpak/template.json index 8bf11084f..80750c51f 100644 --- a/scripts/Flatpak/template.json +++ b/scripts/Flatpak/template.json @@ -247,6 +247,20 @@ } ] }, + { + "name": "python-alsa", + "buildsystem": "simple", + "build-commands": [ + "pip3 install --no-index --no-deps --prefix=${FLATPAK_DEST} ." + ], + "sources": [ + { + "type": "archive", + "url": "ftp://ftp.alsa-project.org/pub/pyalsa/pyalsa-1.1.6.tar.bz2", + "sha256": "2771291a5d2cf700f0abbe6629ea37468d1463a01b2330d84ef976e1e918676c" + } + ] + }, { "name": "python-wheel", "buildsystem": "simple", From 3a3f92090d8cc4e9ca3ffb761aa8647a5095ef83 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 10 Nov 2019 13:28:32 +0100 Subject: [PATCH 204/333] MIDI ports should now be detected correctly --- lisp/plugins/midi/port_monitor.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lisp/plugins/midi/port_monitor.py b/lisp/plugins/midi/port_monitor.py index 42c1674b1..b3d3412ce 100644 --- a/lisp/plugins/midi/port_monitor.py +++ b/lisp/plugins/midi/port_monitor.py @@ -17,6 +17,7 @@ import logging from threading import Thread +from time import sleep try: from pyalsa import alsaseq @@ -94,11 +95,17 @@ def __init__(self): def __loop(self): while True: for event in self.__seq.receive_events(timeout=-1, maxevents=1): - if event.type == alsaseq.SEQ_EVENT_PORT_UNSUBSCRIBED: - logger.debug("ALSA MIDI port unsubscribed.") + if event.type == alsaseq.SEQ_EVENT_PORT_EXIT: + logger.debug("ALSA MIDI port deleted from system.") self.port_removed.emit() - elif event.type == alsaseq.SEQ_EVENT_PORT_SUBSCRIBED: - logger.debug("ALSA MIDI port subscribed.") + elif event.type == alsaseq.SEQ_EVENT_PORT_START: + """ + Some client may set it's name after the port creation. + Adding a small wait should ensure that the name are set when + we emit the signal. + """ + sleep(0.05) + logger.debug("ALSA MIDI new port created.") self.port_added.emit() From 6c8e4076aaaec0f3f3159868e9fcfb741c758eaa Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sun, 10 Nov 2019 13:58:57 +0100 Subject: [PATCH 205/333] comments --- lisp/plugins/midi/midi.py | 9 +++------ lisp/plugins/midi/port_monitor.py | 8 +++----- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index 3de80f7c9..d96085bd0 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -75,12 +75,9 @@ def __init__(self, app): self.__output = MIDIOutput(self.backend, current_output) self._reconnect(self.__output, current_output, avail_outputs) - """ - Monitor ports, for auto-reconnection. - - Since current midi backends are not reliable on connection/disconnection - detection we need to use the native APIs. - """ + # Monitor ports, for auto-reconnection. + # Since current midi backends are not reliable on + # connection/disconnection detection, we need to use the native APIs. self.port_monitor = ALSAPortMonitor() self.port_monitor.port_removed.connect( self._on_port_removed, Connection.QtQueued diff --git a/lisp/plugins/midi/port_monitor.py b/lisp/plugins/midi/port_monitor.py index b3d3412ce..089794a52 100644 --- a/lisp/plugins/midi/port_monitor.py +++ b/lisp/plugins/midi/port_monitor.py @@ -99,11 +99,9 @@ def __loop(self): logger.debug("ALSA MIDI port deleted from system.") self.port_removed.emit() elif event.type == alsaseq.SEQ_EVENT_PORT_START: - """ - Some client may set it's name after the port creation. - Adding a small wait should ensure that the name are set when - we emit the signal. - """ + # Some client may set it's name after the port creation. + # Adding a small wait should ensure that the name are set + # when signal is emitted. sleep(0.05) logger.debug("ALSA MIDI new port created.") self.port_added.emit() From f5c209897e02970826fcf2c878a696aee6500cce Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 25 Nov 2019 22:12:29 +0100 Subject: [PATCH 206/333] Fix missing translations files in builds --- Pipfile.lock | 176 ++++++++++++++++++---------------- lisp/__init__.py | 10 +- lisp/ui/ui_utils.py | 13 ++- scripts/Flatpak/template.json | 10 +- setup.py | 1 + 5 files changed, 111 insertions(+), 99 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index d312d45e6..4a4cc4aa0 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -31,40 +31,41 @@ }, "cffi": { "hashes": [ - "sha256:08f99e8b38d5134d504aa7e486af8e4fde66a2f388bbecc270cdd1e00fa09ff8", - "sha256:1112d2fc92a867a6103bce6740a549e74b1d320cf28875609f6e93857eee4f2d", - "sha256:1b9ab50c74e075bd2ae489853c5f7f592160b379df53b7f72befcbe145475a36", - "sha256:24eff2997436b6156c2f30bed215c782b1d8fd8c6a704206053c79af95962e45", - "sha256:2eff642fbc9877a6449026ad66bf37c73bf4232505fb557168ba5c502f95999b", - "sha256:362e896cea1249ed5c2a81cf6477fabd9e1a5088aa7ea08358a4c6b0998294d2", - "sha256:40eddb3589f382cb950f2dcf1c39c9b8d7bd5af20665ce273815b0d24635008b", - "sha256:5ed40760976f6b8613d4a0db5e423673ca162d4ed6c9ed92d1f4e58a47ee01b5", - "sha256:632c6112c1e914c486f06cfe3f0cc507f44aa1e00ebf732cedb5719e6aa0466a", - "sha256:64d84f0145e181f4e6cc942088603c8db3ae23485c37eeda71cb3900b5e67cb4", - "sha256:6cb4edcf87d0e7f5bdc7e5c1a0756fbb37081b2181293c5fdf203347df1cd2a2", - "sha256:6f19c9df4785305669335b934c852133faed913c0faa63056248168966f7a7d5", - "sha256:719537b4c5cd5218f0f47826dd705fb7a21d83824920088c4214794457113f3f", - "sha256:7b0e337a70e58f1a36fb483fd63880c9e74f1db5c532b4082bceac83df1523fa", - "sha256:853376efeeb8a4ae49a737d5d30f5db8cdf01d9319695719c4af126488df5a6a", - "sha256:85bbf77ffd12985d76a69d2feb449e35ecdcb4fc54a5f087d2bd54158ae5bb0c", - "sha256:8978115c6f0b0ce5880bc21c967c65058be8a15f1b81aa5fdbdcbea0e03952d1", - "sha256:8f7eec920bc83692231d7306b3e311586c2e340db2dc734c43c37fbf9c981d24", - "sha256:8fe230f612c18af1df6f348d02d682fe2c28ca0a6c3856c99599cdacae7cf226", - "sha256:92068ebc494b5f9826b822cec6569f1f47b9a446a3fef477e1d11d7fac9ea895", - "sha256:b57e1c8bcdd7340e9c9d09613b5e7fdd0c600be142f04e2cc1cc8cb7c0b43529", - "sha256:ba956c9b44646bc1852db715b4a252e52a8f5a4009b57f1dac48ba3203a7bde1", - "sha256:ca42034c11eb447497ea0e7b855d87ccc2aebc1e253c22e7d276b8599c112a27", - "sha256:dc9b2003e9a62bbe0c84a04c61b0329e86fccd85134a78d7aca373bbbf788165", - "sha256:dd308802beb4b2961af8f037becbdf01a1e85009fdfc14088614c1b3c383fae5", - "sha256:e77cd105b19b8cd721d101687fcf665fd1553eb7b57556a1ef0d453b6fc42faa", - "sha256:f56dff1bd81022f1c980754ec721fb8da56192b026f17f0f99b965da5ab4fbd2", - "sha256:fa4cc13c03ea1d0d37ce8528e0ecc988d2365e8ac64d8d86cafab4038cb4ce89", - "sha256:fa8cf1cb974a9f5911d2a0303f6adc40625c05578d8e7ff5d313e1e27850bd59", - "sha256:fb003019f06d5fc0aa4738492ad8df1fa343b8a37cbcf634018ad78575d185df", - "sha256:fd409b7778167c3bcc836484a8f49c0e0b93d3e745d975749f83aa5d18a5822f", - "sha256:fe5d65a3ee38122003245a82303d11ac05ff36531a8f5ce4bc7d4bbc012797e1" + "sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42", + "sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04", + "sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5", + "sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54", + "sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba", + "sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57", + "sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396", + "sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12", + "sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97", + "sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43", + "sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db", + "sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3", + "sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b", + "sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579", + "sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346", + "sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159", + "sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652", + "sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e", + "sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a", + "sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506", + "sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f", + "sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d", + "sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c", + "sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20", + "sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858", + "sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc", + "sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a", + "sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3", + "sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e", + "sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410", + "sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25", + "sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b", + "sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d" ], - "version": "==1.13.0" + "version": "==1.13.2" }, "chardet": { "hashes": [ @@ -75,37 +76,41 @@ }, "cython": { "hashes": [ - "sha256:07efba7b32c082c519b75e3b03821c2f32848e2b3e9986c784bbd8ffaf0666d7", - "sha256:08db41daf18fabf7b7a85e39aa26954f6246994540043194af026c0df65a4942", - "sha256:19bbe3caf885a1d2e2c30eacc10d1e45dbbefb156493fe1d5d1adc1668cc1269", - "sha256:1c574f2f2ba760b82b2bcf6262e77e75589247dc5ef796a3ff1b2213e50ee452", - "sha256:1dfe672c686e34598bdbaa93c3b30acb3720ae9258232a4f68ba04ee9969063d", - "sha256:283faea84e6c4e54c3f5c8ff89aa2b6c1c3a813aad4f6d48ed3b9cc9043ef9f9", - "sha256:2a145888d0942e7c36e86a7b7c7e2923cb9f7055805a3b72dcb137e3efdb0979", - "sha256:3f75065936e16569d6e13dfd76de988f5eabeae460aa54770c9b961ab6f747fc", - "sha256:4d78124f5f281f1d5d5b7919cbbc65a7073ff93562def81ee78a8307e6e72494", - "sha256:5ba4d088b8e5d59b8a5911ca9c72952acf3c83296b57daf75af92fb2af1e8423", - "sha256:6b19daeda1d5d1dfc973b291246f6a63a663b20c33980724d6d073c562719536", - "sha256:790c7dc80fd1c3e38acefe06027e2f5a8466c128c7e47c6e140fd5316132574d", - "sha256:7f8c4e648881454ba3ba0bcf3b21a9e1878a67d20ea2b8d9ec1c4c628592ab6b", - "sha256:8bcd3f597290f9902548d6355898d7e376e7f3762f89db9cd50b2b58429df9e8", - "sha256:8ffb18f71972a5c718a8600d9f52e3507f0d6fb72a978e03270d34a7035c98fb", - "sha256:92f025df1cb391e09f65775598c7dfb7efad72d74713775db54e267f62ca94a1", - "sha256:93cf1c72472a2fd0ef4c52f6074dab08fc28d475b9c824ba73a52701f7a48ae1", - "sha256:9a7fa692cdc967fdbf6a053c1975137d01f6935dede2ef222c71840b290caf79", - "sha256:a68eb0c1375f2401de881692b30370a51e550052b8e346b2f71bbdbdc74a214f", - "sha256:ac3b7a12ddd52ea910ee3a041e6bc65df7a52f0ba7bd10fb7123502af482c152", - "sha256:b402b700edaf571a0bae18ec35d5b71c266873a6616412b672435c10b6d8f041", - "sha256:c29d069a4a30f472482343c866f7486731ad638ef9af92bfe5fca9c7323d638e", - "sha256:d822311498f185db449b687336b4e5db7638c8d8b03bdf10ae91d74e23c7cc0c", - "sha256:dccc8df9e1ac158b06777bbaaeb4516f245f9b147701ae25e6023960e4a0c2a3", - "sha256:e31f4b946c2765b2f35440fdb4b00c496dfc5babc53c7ae61966b41171d1d59f", - "sha256:eb43f9e582cc221ee2832e25ea6fe5c06f2acc9da6353c562e922f107db12af8", - "sha256:f07822248110fd6213db8bc2745fdbbccef6f2b3d18ac91a7fba29c6bc575da5", - "sha256:ff69854f123b959d4ae14bd5330714bb9ee4360052992dc0fbd0a3dee4261f95" + "sha256:03f6bbb380ad0acb744fb06e42996ea217e9d00016ca0ff6f2e7d60f580d0360", + "sha256:05e8cfd3a3a6087aec49a1ae08a89171db991956209406d1e5576f9db70ece52", + "sha256:05eb79efc8029d487251c8a2702a909a8ba33c332e06d2f3980866541bd81253", + "sha256:094d28a34c3fa992ae02aea1edbe6ff89b3cc5870b6ee38b5baeb805dc57b013", + "sha256:0c70e842e52e2f50cc43bad43b5e5bc515f30821a374e544abb0e0746f2350ff", + "sha256:1dcdaa319558eb924294a554dcf6c12383ec947acc7e779e8d3622409a7f7d28", + "sha256:1fc5bdda28f25fec44e4721677458aa509d743cd350862270309d61aa148d6ff", + "sha256:280573a01d9348d44a42d6a9c651d9f7eb1fe9217df72555b2a118f902996a10", + "sha256:298ceca7b0f0da4205fcb0b7c9ac9e120e2dafffd5019ba1618e84ef89434b5a", + "sha256:4074a8bff0040035673cc6dd365a762476d6bff4d03d8ce6904e3e53f9a25dc8", + "sha256:41e7068e95fbf9ec94b41437f989caf9674135e770a39cdb9c00de459bafd1bc", + "sha256:47e5e1502d52ef03387cf9d3b3241007961a84a466e58a3b74028e1dd4957f8c", + "sha256:521340844cf388d109ceb61397f3fd5250ccb622a1a8e93559e8de76c80940a9", + "sha256:6c53338c1811f8c6d7f8cb7abd874810b15045e719e8207f957035c9177b4213", + "sha256:75c2dda47dcc3c77449712b1417bb6b89ec3b7b02e18c64262494dceffdf455e", + "sha256:773c5a98e463b52f7e8197254b39b703a5ea1972aef3a94b3b921515d77dd041", + "sha256:78c3068dcba300d473fef57cdf523e34b37de522f5a494ef9ee1ac9b4b8bbe3f", + "sha256:7bc18fc5a170f2c1cef5387a3d997c28942918bbee0f700e73fd2178ee8d474d", + "sha256:7f89eff20e4a7a64b55210dac17aea711ed8a3f2e78f2ff784c0e984302583dd", + "sha256:89458b49976b1dee5d89ab4ac943da3717b4292bf624367e862e4ee172fcce99", + "sha256:986f871c0fa649b293061236b93782d25c293a8dd8117c7ba05f8a61bdc261ae", + "sha256:a0f495a4fe5278aab278feee35e6102efecde5176a8a74dd28c28e3fc5c8d7c7", + "sha256:a14aa436586c41633339415de82a41164691d02d3e661038da533be5d40794a5", + "sha256:b8ab3ab38afc47d8f4fe629b836243544351cef681b6bdb1dc869028d6fdcbfb", + "sha256:bb487881608ebd293592553c618f0c83316f4f13a64cb18605b1d2fb9fd3da3e", + "sha256:c0b24bfe3431b3cb7ced323bca813dbd13aca973a1475b512d3331fd0de8ec60", + "sha256:c7894c06205166d360ab2915ae306d1f7403e9ce3d3aaeff4095eaf98e42ce66", + "sha256:d4039bb7f234ad32267c55e72fd49fb56078ea102f9d9d8559f6ec34d4887630", + "sha256:e4d6bb8703d0319eb04b7319b12ea41580df44fd84d83ccda13ea463c6801414", + "sha256:e8fab9911fd2fa8e5af407057cb8bdf87762f983cba483fa3234be20a9a0af77", + "sha256:f3818e578e687cdb21dc4aa4a3bc6278c656c9c393e9eda14dd04943f478863d", + "sha256:fe666645493d72712c46e4fbe8bec094b06aec3c337400479e9704439c9d9586" ], "index": "pypi", - "version": "==0.29.13" + "version": "==0.29.14" }, "falcon": { "hashes": [ @@ -136,11 +141,11 @@ }, "jack-client": { "hashes": [ - "sha256:7794cdbef5e9a2de973bc2288441103811167d57660df88f6b368a30063ed8b4", - "sha256:d17e47d20e1d79e99a9c2e7d2c2b20f9e6c5d86d7415ea7894832e3c2f480819" + "sha256:55bc516a88fc9e3eaac9dc6e55205bbf8eab8d9f9aae665a07324b2cfb54f211", + "sha256:ca1ec6a620060951b100971d9052b4e24ef8ef5be152eabf3fa60d790679b95f" ], "index": "pypi", - "version": "==0.5.0" + "version": "==0.5.1" }, "mido": { "hashes": [ @@ -152,13 +157,14 @@ }, "pycairo": { "hashes": [ - "sha256:70172e58b6bad7572a3518c26729b074acdde15e6fee6cbab6d3528ad552b786" + "sha256:dcb853fd020729516e8828ad364084e752327d4cff8505d20b13504b32b16531" ], - "version": "==1.18.1" + "version": "==1.18.2" }, "pycparser": { "hashes": [ - "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" + "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3", + "sha256:c43b2ad473872c1546a6e72275e558e4a75284541a88e4fef3446a79095f05ae" ], "version": "==2.19" }, @@ -178,13 +184,13 @@ }, "pyqt5": { "hashes": [ - "sha256:6420d8c9de8746917bd2afe24f29b3722aabde36d1e9f8c34354f5b7fb7b987d", - "sha256:73f982b60819cc6d7cb0007b8b0cd11073e2546d74e0463726149e6a9d087caa", - "sha256:8e6125f110133069e8f4a085022cfc6b4a0af5b3c9009999a749007c299e965d", - "sha256:f8e76070782e68ba200eb2b67babf89ac629df5e36d2f4c680ef34ae40c8f964" + "sha256:14737bb4673868d15fa91dad79fe293d7a93d76c56d01b3757b350b8dcb32b2d", + "sha256:1936c321301f678d4e6703d52860e1955e5c4964e6fd00a1f86725ce5c29083c", + "sha256:3f79de6e9f29e858516cc36ffc2b992e262af841f3799246aec282b76a3eccdf", + "sha256:509daab1c5aca22e3cf9508128abf38e6e5ae311d7426b21f4189ffd66b196e9" ], "index": "pypi", - "version": "==5.13.1" + "version": "==5.13.2" }, "pyqt5-sip": { "hashes": [ @@ -243,10 +249,10 @@ }, "six": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", + "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" ], - "version": "==1.12.0" + "version": "==1.13.0" }, "sortedcontainers": { "hashes": [ @@ -258,10 +264,10 @@ }, "urllib3": { "hashes": [ - "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", - "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86" + "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", + "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" ], - "version": "==1.25.6" + "version": "==1.25.7" } }, "develop": { @@ -275,18 +281,18 @@ }, "pysnooper": { "hashes": [ - "sha256:845cc1c160d5f4b6f9ba62d6fadc2f7f51daa8759a34c93dee286a94dd77c3a9", - "sha256:9db476d6d92284e5f1fef557c193dc378327dbe6aeb6eea6b044013a006f221b" + "sha256:7d7684d9d5cf1d026bba6a52e87009eab9458d3265b13059ab1abaea7eee6c93", + "sha256:85892ef6f4009af7d729e73c7b9e4071a16d5063b1ac9340741c5f192501441b" ], "index": "pypi", - "version": "==0.2.8" + "version": "==0.3.0" }, "six": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", + "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" ], - "version": "==1.12.0" + "version": "==1.13.0" }, "webencodings": { "hashes": [ diff --git a/lisp/__init__.py b/lisp/__init__.py index dfbf3bd55..97053a990 100644 --- a/lisp/__init__.py +++ b/lisp/__init__.py @@ -17,7 +17,7 @@ from os import path -from appdirs import AppDirs +#from appdirs import AppDirs __author__ = "Francesco Ceruti" __email__ = "ceppofrancy@gmail.com" @@ -27,16 +27,16 @@ __version__ = ".".join(map(str, __version_info__)) # The version passed follows . -app_dirs = AppDirs( - "LinuxShowPlayer", version="{}.{}".format(*__version_info__[0:2]) -) +#app_dirs = AppDirs( +# "LinuxShowPlayer", version="{}.{}".format(*__version_info__[0:2]) +#) # Application wide "constants" APP_DIR = path.dirname(__file__) I18N_DIR = path.join(APP_DIR, "i18n", "qm") DEFAULT_APP_CONFIG = path.join(APP_DIR, "default.json") -USER_APP_CONFIG = path.join(app_dirs.user_config_dir, "lisp.json") +USER_APP_CONFIG = ""#path.join(app_dirs.user_config_dir, "lisp.json") ICON_THEMES_DIR = path.join(APP_DIR, "ui", "icons") ICON_THEME_COMMON = "lisp" diff --git a/lisp/ui/ui_utils.py b/lisp/ui/ui_utils.py index c284f8320..1143cb6a0 100644 --- a/lisp/ui/ui_utils.py +++ b/lisp/ui/ui_utils.py @@ -111,10 +111,15 @@ def qfile_filters(extensions, allexts=True, anyfile=True): def search_translations(prefix="base", tr_path=I18N_DIR): - for entry in os.scandir(tr_path): - name = entry.name - if entry.is_file() and name.endswith(".qm") and name.startswith(prefix): - yield os.path.splitext(name)[0][len(prefix) + 1 :] + if os.path.exists(tr_path): + for entry in os.scandir(tr_path): + name = entry.name + if ( + entry.is_file() + and name.endswith(".qm") + and name.startswith(prefix) + ): + yield os.path.splitext(name)[0][len(prefix) + 1 :] def install_translation(name, tr_path=I18N_DIR): diff --git a/scripts/Flatpak/template.json b/scripts/Flatpak/template.json index 80750c51f..2e439d15f 100644 --- a/scripts/Flatpak/template.json +++ b/scripts/Flatpak/template.json @@ -194,7 +194,7 @@ "name": "python-six", "buildsystem": "simple", "build-commands": [ - "pip3 install --no-index --no-deps --prefix=${FLATPAK_DEST} six-1.11.0-py2.py3-none-any.whl" + "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} six-1.11.0-py2.py3-none-any.whl" ], "sources": [ { @@ -208,7 +208,7 @@ "name": "python-google-protobuf", "buildsystem": "simple", "build-commands": [ - "pip3 install --no-index --no-deps --prefix=${FLATPAK_DEST} protobuf-3.1.0.post1-py2.py3-none-any.whl" + "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} protobuf-3.1.0.post1-py2.py3-none-any.whl" ], "sources": [ { @@ -251,7 +251,7 @@ "name": "python-alsa", "buildsystem": "simple", "build-commands": [ - "pip3 install --no-index --no-deps --prefix=${FLATPAK_DEST} ." + "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} ." ], "sources": [ { @@ -265,7 +265,7 @@ "name": "python-wheel", "buildsystem": "simple", "build-commands": [ - "pip3 install --no-index --no-deps --prefix=${FLATPAK_DEST} wheel-0.33.1-py2.py3-none-any.whl" + "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} wheel-0.33.1-py2.py3-none-any.whl" ], "sources": [ { @@ -279,7 +279,7 @@ "name": "linux-show-player", "buildsystem": "simple", "build-commands": [ - "pip3 install --no-index --no-deps --no-build-isolation --prefix=${FLATPAK_DEST} .", + "pip3 install --verbose --no-index --no-deps --no-build-isolation --prefix=${FLATPAK_DEST} .", "mkdir -p /app/share/applications/", "cp dist/linuxshowplayer.desktop /app/share/applications/", "mkdir -p /app/share/mime/packages/", diff --git a/setup.py b/setup.py index ca5e02d52..a1f59d730 100644 --- a/setup.py +++ b/setup.py @@ -43,6 +43,7 @@ def package_data_dirs(package_path): packages=find_packages(), package_data={ "": ["*.qm", "*.qss", "*.json"], + "lisp": ["i18n/qm/*.qm"], "lisp.ui.icons": package_data_dirs("lisp/ui/icons"), }, entry_points={"console_scripts": ["linux-show-player=lisp.main:main"]}, From a4084fb95375096810110ccb899ee2e147a1584e Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 26 Nov 2019 20:49:38 +0100 Subject: [PATCH 207/333] Because I'm dumb :P --- lisp/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lisp/__init__.py b/lisp/__init__.py index 97053a990..dfbf3bd55 100644 --- a/lisp/__init__.py +++ b/lisp/__init__.py @@ -17,7 +17,7 @@ from os import path -#from appdirs import AppDirs +from appdirs import AppDirs __author__ = "Francesco Ceruti" __email__ = "ceppofrancy@gmail.com" @@ -27,16 +27,16 @@ __version__ = ".".join(map(str, __version_info__)) # The version passed follows . -#app_dirs = AppDirs( -# "LinuxShowPlayer", version="{}.{}".format(*__version_info__[0:2]) -#) +app_dirs = AppDirs( + "LinuxShowPlayer", version="{}.{}".format(*__version_info__[0:2]) +) # Application wide "constants" APP_DIR = path.dirname(__file__) I18N_DIR = path.join(APP_DIR, "i18n", "qm") DEFAULT_APP_CONFIG = path.join(APP_DIR, "default.json") -USER_APP_CONFIG = ""#path.join(app_dirs.user_config_dir, "lisp.json") +USER_APP_CONFIG = path.join(app_dirs.user_config_dir, "lisp.json") ICON_THEMES_DIR = path.join(APP_DIR, "ui", "icons") ICON_THEME_COMMON = "lisp" From 3d8fad1fbddcb0ccdf599f9a61f7e524ac2ba570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Sun, 15 Dec 2019 12:35:38 +0100 Subject: [PATCH 208/333] Add ctrl-q as shortcut to quit application --- lisp/ui/mainwindow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lisp/ui/mainwindow.py b/lisp/ui/mainwindow.py index d38827515..1507acee3 100644 --- a/lisp/ui/mainwindow.py +++ b/lisp/ui/mainwindow.py @@ -202,6 +202,7 @@ def retranslateUi(self): self.fullScreenAction.setText(translate("MainWindow", "Full Screen")) self.fullScreenAction.setShortcut(QKeySequence.FullScreen) self.exitAction.setText(translate("MainWindow", "Exit")) + self.exitAction.setShortcut(QKeySequence.Quit) # menuEdit self.menuEdit.setTitle(translate("MainWindow", "&Edit")) self.actionUndo.setText(translate("MainWindow", "Undo")) From d7164928874008139db5e2d5e5e2371ad4902d08 Mon Sep 17 00:00:00 2001 From: s0600204 Date: Sun, 19 Jan 2020 03:13:08 +0000 Subject: [PATCH 209/333] Add an `is_disabled` class method to plugins --- lisp/core/plugin.py | 4 ++++ lisp/plugins/__init__.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lisp/core/plugin.py b/lisp/core/plugin.py index 614d1b1cf..0b3b23895 100644 --- a/lisp/core/plugin.py +++ b/lisp/core/plugin.py @@ -41,3 +41,7 @@ def app(self): def finalize(self): """Called when the application is getting closed.""" + + @classmethod + def is_disabled(cls): + return not cls.Config.get("_enabled_", False) diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index 273e556b2..3710f7551 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -120,7 +120,7 @@ def __load_plugins(plugins, application, optionals=True): # Try to load the plugin, if enabled try: - if plugin.Config.get("_enabled_", False): + if not plugin.is_disabled(): # Create an instance of the plugin and save it LOADED[name] = plugin(application) logger.info( From 24eb6fa7ab5049909c5a696d06d2e9b52138e4e7 Mon Sep 17 00:00:00 2001 From: s0600204 Date: Mon, 6 Jan 2020 03:32:48 +0000 Subject: [PATCH 210/333] Add a status class member to Plugin class This allows easy tracking of a plugin's state - loaded, dependencies satisfied, etc. --- lisp/core/plugin.py | 21 +++++++++++++-- lisp/plugins/__init__.py | 38 +++++++++++++++++++-------- lisp/ui/settings/app_pages/plugins.py | 9 ++----- 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/lisp/core/plugin.py b/lisp/core/plugin.py index 0b3b23895..9b8089f6c 100644 --- a/lisp/core/plugin.py +++ b/lisp/core/plugin.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2016 Francesco Ceruti +# Copyright 2020 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,10 +16,10 @@ # along with Linux Show Player. If not, see . from lisp.core.configuration import DummyConfiguration +from lisp.plugins import PluginState # TODO: add possible additional metadata (Icon, Version, ...) -# TODO: implement some kind of plugin status class Plugin: """Base class for plugins.""" @@ -29,10 +29,12 @@ class Plugin: Authors = ("None",) Description = "No Description" Config = DummyConfiguration() + State = PluginState.Listed def __init__(self, app): """:type app: lisp.application.Application""" self.__app = app + self.__class__.State |= PluginState.Loaded @property def app(self): @@ -41,7 +43,22 @@ def app(self): def finalize(self): """Called when the application is getting closed.""" + self.__class__.State &= ~PluginState.Loaded @classmethod def is_disabled(cls): return not cls.Config.get("_enabled_", False) + + @classmethod + def is_loaded(cls): + return cls.State & PluginState.Loaded + + @classmethod + def status_icon(cls): + if cls.is_disabled(): + return 'led-off' + if cls.State & PluginState.InError: + return 'led-error' + if cls.State & PluginState.InWarning: + return 'led-pause' + return 'led-running' diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index 3710f7551..882e2d4b2 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2018 Francesco Ceruti +# Copyright 2020 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -25,7 +25,6 @@ from lisp.ui.ui_utils import install_translation, translate PLUGINS = {} -LOADED = {} FALLBACK_CONFIG_PATH = path.join(path.dirname(__file__), "default.json") @@ -36,6 +35,17 @@ class PluginNotLoadedError(Exception): pass +class PluginState: + Listed = 0 + Loaded = 1 + + DependenciesNotSatisfied = 4 + OptionalDependenciesNotSatisfied = 8 + + InError = DependenciesNotSatisfied + InWarning = OptionalDependenciesNotSatisfied + + def load_plugins(application): """Load and instantiate available plugins.""" for name, plugin in load_classes(__package__, path.dirname(__file__)): @@ -112,17 +122,22 @@ def __load_plugins(plugins, application, optionals=True): dependencies += plugin.OptDepends for dep in dependencies: - if dep not in LOADED: + if dep not in PLUGINS or not PLUGINS[dep].is_loaded(): + if dep in plugin.Depends: + plugin.State |= PluginState.DependenciesNotSatisfied + else: + plugin.State |= PluginState.OptionalDependenciesNotSatisfied break else: plugins.pop(name) resolved = True + plugin.State &= ~PluginState.DependenciesNotSatisfied # Try to load the plugin, if enabled try: if not plugin.is_disabled(): # Create an instance of the plugin and save it - LOADED[name] = plugin(application) + PLUGINS[name] = plugin(application) logger.info( translate("PluginsInfo", 'Plugin loaded: "{}"').format( name @@ -142,35 +157,36 @@ def __load_plugins(plugins, application, optionals=True): 'Failed to load plugin: "{}"'.format(name), ) ) - return resolved def finalize_plugins(): """Finalize all the plugins.""" - for plugin in LOADED: + for plugin_name, plugin in PLUGINS.items(): + if not plugin.is_loaded(): + continue try: - LOADED[plugin].finalize() + PLUGINS[plugin_name].finalize() logger.info( translate("PluginsInfo", 'Plugin terminated: "{}"').format( - plugin + plugin_name ) ) except Exception: logger.exception( translate( "PluginsError", 'Failed to terminate plugin: "{}"' - ).format(plugin) + ).format(plugin_name) ) def is_loaded(plugin_name): - return plugin_name in LOADED + return plugin_name in PLUGINS and PLUGINS[plugin_name].is_loaded() def get_plugin(plugin_name): if is_loaded(plugin_name): - return LOADED[plugin_name] + return PLUGINS[plugin_name] else: raise PluginNotLoadedError( translate( diff --git a/lisp/ui/settings/app_pages/plugins.py b/lisp/ui/settings/app_pages/plugins.py index fb8a54c19..f991c55b2 100644 --- a/lisp/ui/settings/app_pages/plugins.py +++ b/lisp/ui/settings/app_pages/plugins.py @@ -1,6 +1,6 @@ # This file is part of Linux Show Player # -# Copyright 2017 Francesco Ceruti +# Copyright 2020 Francesco Ceruti # # Linux Show Player is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -47,12 +47,7 @@ def __init__(self, **kwargs): for name, plugin in plugins.PLUGINS.items(): item = QListWidgetItem(plugin.Name) - if plugins.is_loaded(name): - item.setIcon(IconTheme.get("led-running")) - elif not plugin.Config["_enabled_"]: - item.setIcon(IconTheme.get("led-pause")) - else: - item.setIcon(IconTheme.get("led-error")) + item.setIcon(IconTheme.get(plugin.status_icon())) item.setData(Qt.UserRole, plugin) From 3adca9ba99c9045b1a6bf48b96f0b2c43bc88f24 Mon Sep 17 00:00:00 2001 From: s0600204 Date: Mon, 6 Jan 2020 21:03:28 +0000 Subject: [PATCH 211/333] Add tooltips and further icons for plugin status indication --- lisp/core/plugin.py | 23 ++++- lisp/ui/icons/lisp/led-error-outline.svg | 105 +++++++++++++++++++++ lisp/ui/icons/lisp/led-off-outline.svg | 105 +++++++++++++++++++++ lisp/ui/icons/lisp/led-pause-outline.svg | 105 +++++++++++++++++++++ lisp/ui/icons/lisp/led-running-outline.svg | 105 +++++++++++++++++++++ lisp/ui/settings/app_pages/plugins.py | 1 + 6 files changed, 442 insertions(+), 2 deletions(-) create mode 100644 lisp/ui/icons/lisp/led-error-outline.svg create mode 100644 lisp/ui/icons/lisp/led-off-outline.svg create mode 100644 lisp/ui/icons/lisp/led-pause-outline.svg create mode 100644 lisp/ui/icons/lisp/led-running-outline.svg diff --git a/lisp/core/plugin.py b/lisp/core/plugin.py index 9b8089f6c..06ce1fcc2 100644 --- a/lisp/core/plugin.py +++ b/lisp/core/plugin.py @@ -17,6 +17,7 @@ from lisp.core.configuration import DummyConfiguration from lisp.plugins import PluginState +from lisp.ui.ui_utils import translate # TODO: add possible additional metadata (Icon, Version, ...) @@ -55,10 +56,28 @@ def is_loaded(cls): @classmethod def status_icon(cls): - if cls.is_disabled(): - return 'led-off' if cls.State & PluginState.InError: return 'led-error' + if cls.State & PluginState.InWarning: + if cls.is_disabled(): + return 'led-pause-outline' return 'led-pause' + + if cls.is_disabled(): + return 'led-off' return 'led-running' + + @classmethod + def status_tooltip(cls): + if cls.State & PluginState.InError: + return translate('PluginsTooltip', 'An error has occurred with this plugin. Please see logs for further information.') + + if cls.State & PluginState.InWarning: + if cls.is_disabled(): + return translate('PluginsTooltip', 'There is a non-critical issue with this disabled plugin. Please see logs for further information.') + return translate('PluginsTooltip', 'A non-critical issue is affecting this plugin. Please see logs for further information.') + + if cls.is_disabled(): + return translate('PluginsTooltip', 'Plugin disabled. Enable to use.') + return translate('PluginsTooltip', 'Plugin loaded and ready for use.') diff --git a/lisp/ui/icons/lisp/led-error-outline.svg b/lisp/ui/icons/lisp/led-error-outline.svg new file mode 100644 index 000000000..e9a08de77 --- /dev/null +++ b/lisp/ui/icons/lisp/led-error-outline.svg @@ -0,0 +1,105 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/lisp/led-off-outline.svg b/lisp/ui/icons/lisp/led-off-outline.svg new file mode 100644 index 000000000..4cf976578 --- /dev/null +++ b/lisp/ui/icons/lisp/led-off-outline.svg @@ -0,0 +1,105 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/lisp/led-pause-outline.svg b/lisp/ui/icons/lisp/led-pause-outline.svg new file mode 100644 index 000000000..3b3672800 --- /dev/null +++ b/lisp/ui/icons/lisp/led-pause-outline.svg @@ -0,0 +1,105 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/icons/lisp/led-running-outline.svg b/lisp/ui/icons/lisp/led-running-outline.svg new file mode 100644 index 000000000..c8275c73b --- /dev/null +++ b/lisp/ui/icons/lisp/led-running-outline.svg @@ -0,0 +1,105 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lisp/ui/settings/app_pages/plugins.py b/lisp/ui/settings/app_pages/plugins.py index f991c55b2..51ed2e237 100644 --- a/lisp/ui/settings/app_pages/plugins.py +++ b/lisp/ui/settings/app_pages/plugins.py @@ -48,6 +48,7 @@ def __init__(self, **kwargs): for name, plugin in plugins.PLUGINS.items(): item = QListWidgetItem(plugin.Name) item.setIcon(IconTheme.get(plugin.status_icon())) + item.setToolTip(plugin.status_tooltip()) item.setData(Qt.UserRole, plugin) From 11c387304e307002716777e16f72f9d3f8710f6b Mon Sep 17 00:00:00 2001 From: s0600204 Date: Thu, 23 Jan 2020 20:35:15 +0000 Subject: [PATCH 212/333] Recheck optional dependencies In case they're now satisfied as well. --- lisp/plugins/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index 882e2d4b2..f5c4d0811 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -131,7 +131,12 @@ def __load_plugins(plugins, application, optionals=True): else: plugins.pop(name) resolved = True - plugin.State &= ~PluginState.DependenciesNotSatisfied + plugin.State &= ~(PluginState.DependenciesNotSatisfied | PluginState.OptionalDependenciesNotSatisfied) + + for dep in plugin.OptDepends: + if dep not in PLUGINS or not PLUGINS[dep].is_loaded(): + plugin.State |= PluginState.OptionalDependenciesNotSatisfied + break # Try to load the plugin, if enabled try: From efe9e01b120ef3cb7cd649b6cf15fb42607c6997 Mon Sep 17 00:00:00 2001 From: s0600204 Date: Mon, 3 Feb 2020 23:26:59 +0000 Subject: [PATCH 213/333] Change the is_disabled() method to an is_enabled() method --- lisp/core/plugin.py | 28 ++++++++++++++-------------- lisp/plugins/__init__.py | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lisp/core/plugin.py b/lisp/core/plugin.py index 06ce1fcc2..fce21b5f8 100644 --- a/lisp/core/plugin.py +++ b/lisp/core/plugin.py @@ -47,8 +47,8 @@ def finalize(self): self.__class__.State &= ~PluginState.Loaded @classmethod - def is_disabled(cls): - return not cls.Config.get("_enabled_", False) + def is_enabled(cls): + return cls.Config.get("_enabled_", False) @classmethod def is_loaded(cls): @@ -60,13 +60,13 @@ def status_icon(cls): return 'led-error' if cls.State & PluginState.InWarning: - if cls.is_disabled(): - return 'led-pause-outline' - return 'led-pause' + if cls.is_enabled(): + return 'led-pause' + return 'led-pause-outline' - if cls.is_disabled(): - return 'led-off' - return 'led-running' + if cls.is_enabled(): + return 'led-running' + return 'led-off' @classmethod def status_tooltip(cls): @@ -74,10 +74,10 @@ def status_tooltip(cls): return translate('PluginsTooltip', 'An error has occurred with this plugin. Please see logs for further information.') if cls.State & PluginState.InWarning: - if cls.is_disabled(): - return translate('PluginsTooltip', 'There is a non-critical issue with this disabled plugin. Please see logs for further information.') - return translate('PluginsTooltip', 'A non-critical issue is affecting this plugin. Please see logs for further information.') + if cls.is_enabled(): + return translate('PluginsTooltip', 'A non-critical issue is affecting this plugin. Please see logs for further information.') + return translate('PluginsTooltip', 'There is a non-critical issue with this disabled plugin. Please see logs for further information.') - if cls.is_disabled(): - return translate('PluginsTooltip', 'Plugin disabled. Enable to use.') - return translate('PluginsTooltip', 'Plugin loaded and ready for use.') + if cls.is_enabled(): + return translate('PluginsTooltip', 'Plugin loaded and ready for use.') + return translate('PluginsTooltip', 'Plugin disabled. Enable to use.') diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index f5c4d0811..53f620e4b 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -140,7 +140,7 @@ def __load_plugins(plugins, application, optionals=True): # Try to load the plugin, if enabled try: - if not plugin.is_disabled(): + if plugin.is_enabled(): # Create an instance of the plugin and save it PLUGINS[name] = plugin(application) logger.info( From d7cfff0185ac3150ce2c3d1ecbe91323be63b15a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Wed, 5 Feb 2020 17:40:50 +0100 Subject: [PATCH 214/333] Add support for drag'n drop files into the layouts (#193) (close #137) * Add support for dropping files in list view * Add support for dropping files in card layout --- lisp/plugins/cart_layout/page_widget.py | 26 ++++++--- lisp/plugins/gst_backend/gst_backend.py | 16 ++++- lisp/plugins/list_layout/list_view.py | 78 +++++++++++++++---------- 3 files changed, 80 insertions(+), 40 deletions(-) diff --git a/lisp/plugins/cart_layout/page_widget.py b/lisp/plugins/cart_layout/page_widget.py index f63b989e0..fdd92a98c 100644 --- a/lisp/plugins/cart_layout/page_widget.py +++ b/lisp/plugins/cart_layout/page_widget.py @@ -21,6 +21,7 @@ from PyQt5.QtWidgets import QWidget, QGridLayout, QSizePolicy from sortedcontainers import SortedDict +from lisp.backend import get_backend from lisp.core.util import typename @@ -99,17 +100,26 @@ def contextMenuEvent(self, event): self.contextMenuRequested.emit(event.globalPos()) def dragEnterEvent(self, event): - if event.mimeData().text() == CartPageWidget.DRAG_MAGIC: + if event.mimeData().hasUrls() and all([x.isLocalFile() for x in event.mimeData().urls()]): event.accept() + elif event.mimeData().hasText() and event.mimeData().text() == CartPageWidget.DRAG_MAGIC: + event.accept() + else: + event.ignore() def dropEvent(self, event): - row, column = self.indexAt(event.pos()) - - if self.layout().itemAtPosition(row, column) is None: - if event.proposedAction() == Qt.MoveAction: - self.moveWidgetRequested.emit(event.source(), row, column) - elif event.proposedAction() == Qt.CopyAction: - self.copyWidgetRequested.emit(event.source(), row, column) + if event.mimeData().hasUrls(): + # If files are being dropped, add them as cues + get_backend().add_cue_from_urls(event.mimeData().urls()) + else: + # Otherwise copy/move existing cue. + row, column = self.indexAt(event.pos()) + + if self.layout().itemAtPosition(row, column) is None: + if event.proposedAction() == Qt.MoveAction: + self.moveWidgetRequested.emit(event.source(), row, column) + elif event.proposedAction() == Qt.CopyAction: + self.copyWidgetRequested.emit(event.source(), row, column) def indexAt(self, pos): # All four margins (left, right, top, bottom) of a cue widget are equal diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index 23a38ca71..fa47dbff7 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -120,7 +120,21 @@ def _add_uri_audio_cue(self): if files: GstBackend.Config["mediaLookupDir"] = os.path.dirname(files[0]) GstBackend.Config.write() - + self.add_cue_from_files(files) + + def add_cue_from_urls(self, urls): + extensions = self.supported_extensions() + files = [] + for url in urls: + filename = url.path() + ext = os.path.splitext(url.fileName())[-1] + if ext.strip('.') not in extensions['audio'] + extensions['video']: + # Skip unsupported media types + continue + files.append(filename) + self.add_cue_from_files(files) + + def add_cue_from_files(self, files): QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) # Create media cues, and add them to the Application cue_model diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py index 4d52860fc..d33cae9be 100644 --- a/lisp/plugins/list_layout/list_view.py +++ b/lisp/plugins/list_layout/list_view.py @@ -27,6 +27,7 @@ from PyQt5.QtWidgets import QTreeWidget, QHeaderView, QTreeWidgetItem from lisp.application import Application +from lisp.backend import get_backend from lisp.command.model import ModelMoveItemsCommand, ModelInsertItemsCommand from lisp.core.util import subdict from lisp.cues.cue_factory import CueFactory @@ -132,43 +133,58 @@ def __init__(self, listModel, parent=None): self.__currentItemChanged, Qt.QueuedConnection ) + def dragEnterEvent(self, event): + if event.mimeData().hasUrls(): + if all([x.isLocalFile() for x in event.mimeData().urls()]): + event.accept() + else: + event.ignore() + else: + return super(CueListView, self).dragEnterEvent(event) + def dropEvent(self, event): - # Decode mimedata information about the drag&drop event, since only - # internal movement are allowed we assume the data format is correct - data = event.mimeData().data("application/x-qabstractitemmodeldatalist") - stream = QDataStream(data, QIODevice.ReadOnly) - - # Get the starting-item row - to_index = self.indexAt(event.pos()).row() - if not 0 <= to_index <= len(self._model): - to_index = len(self._model) - - rows = [] - while not stream.atEnd(): - row = stream.readInt() - # Skip column and data - stream.readInt() - for _ in range(stream.readInt()): + if event.mimeData().hasUrls(): + # If files are being dropped, add them as cues + get_backend().add_cue_from_urls(event.mimeData().urls()) + else: + # Otherwise copy/move existing cue. + + # Decode mimedata information about the drag&drop event, since only + # internal movement are allowed we assume the data format is correct + data = event.mimeData().data("application/x-qabstractitemmodeldatalist") + stream = QDataStream(data, QIODevice.ReadOnly) + + # Get the starting-item row + to_index = self.indexAt(event.pos()).row() + if not 0 <= to_index <= len(self._model): + to_index = len(self._model) + + rows = [] + while not stream.atEnd(): + row = stream.readInt() + # Skip column and data stream.readInt() - stream.readQVariant() + for _ in range(stream.readInt()): + stream.readInt() + stream.readQVariant() - if rows and row == rows[-1]: - continue + if rows and row == rows[-1]: + continue - rows.append(row) + rows.append(row) - if event.proposedAction() == Qt.MoveAction: - Application().commands_stack.do( - ModelMoveItemsCommand(self._model, rows, to_index) - ) - elif event.proposedAction() == Qt.CopyAction: - new_cues = [] - for row in sorted(rows): - new_cues.append(CueFactory.clone_cue(self._model.item(row))) + if event.proposedAction() == Qt.MoveAction: + Application().commands_stack.do( + ModelMoveItemsCommand(self._model, rows, to_index) + ) + elif event.proposedAction() == Qt.CopyAction: + new_cues = [] + for row in sorted(rows): + new_cues.append(CueFactory.clone_cue(self._model.item(row))) - Application().commands_stack.do( - ModelInsertItemsCommand(self._model, to_index, *new_cues) - ) + Application().commands_stack.do( + ModelInsertItemsCommand(self._model, to_index, *new_cues) + ) def contextMenuEvent(self, event): self.contextMenuInvoked.emit(event) From 055406ea2bf4ce3533993715abd47405b366c37d Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 29 Feb 2020 17:16:45 +0100 Subject: [PATCH 215/333] Minor improvements --- Pipfile.lock | 255 +++++++++++------------ lisp/plugins/cart_layout/page_widget.py | 10 +- lisp/plugins/controller/protocols/osc.py | 2 +- lisp/plugins/gst_backend/gst_backend.py | 23 +- lisp/plugins/list_layout/list_view.py | 18 +- 5 files changed, 155 insertions(+), 153 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 4a4cc4aa0..05ddab7dd 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -24,48 +24,43 @@ }, "certifi": { "hashes": [ - "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", - "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" + "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", + "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" ], - "version": "==2019.9.11" + "version": "==2019.11.28" }, "cffi": { "hashes": [ - "sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42", - "sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04", - "sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5", - "sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54", - "sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba", - "sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57", - "sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396", - "sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12", - "sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97", - "sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43", - "sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db", - "sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3", - "sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b", - "sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579", - "sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346", - "sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159", - "sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652", - "sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e", - "sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a", - "sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506", - "sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f", - "sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d", - "sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c", - "sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20", - "sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858", - "sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc", - "sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a", - "sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3", - "sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e", - "sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410", - "sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25", - "sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b", - "sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d" + "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff", + "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b", + "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac", + "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0", + "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384", + "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26", + "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6", + "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b", + "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e", + "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd", + "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2", + "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66", + "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc", + "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8", + "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55", + "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4", + "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5", + "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d", + "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78", + "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa", + "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793", + "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f", + "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a", + "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f", + "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30", + "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f", + "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3", + "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c" ], - "version": "==1.13.2" + "version": "==1.14.0" }, "chardet": { "hashes": [ @@ -76,41 +71,41 @@ }, "cython": { "hashes": [ - "sha256:03f6bbb380ad0acb744fb06e42996ea217e9d00016ca0ff6f2e7d60f580d0360", - "sha256:05e8cfd3a3a6087aec49a1ae08a89171db991956209406d1e5576f9db70ece52", - "sha256:05eb79efc8029d487251c8a2702a909a8ba33c332e06d2f3980866541bd81253", - "sha256:094d28a34c3fa992ae02aea1edbe6ff89b3cc5870b6ee38b5baeb805dc57b013", - "sha256:0c70e842e52e2f50cc43bad43b5e5bc515f30821a374e544abb0e0746f2350ff", - "sha256:1dcdaa319558eb924294a554dcf6c12383ec947acc7e779e8d3622409a7f7d28", - "sha256:1fc5bdda28f25fec44e4721677458aa509d743cd350862270309d61aa148d6ff", - "sha256:280573a01d9348d44a42d6a9c651d9f7eb1fe9217df72555b2a118f902996a10", - "sha256:298ceca7b0f0da4205fcb0b7c9ac9e120e2dafffd5019ba1618e84ef89434b5a", - "sha256:4074a8bff0040035673cc6dd365a762476d6bff4d03d8ce6904e3e53f9a25dc8", - "sha256:41e7068e95fbf9ec94b41437f989caf9674135e770a39cdb9c00de459bafd1bc", - "sha256:47e5e1502d52ef03387cf9d3b3241007961a84a466e58a3b74028e1dd4957f8c", - "sha256:521340844cf388d109ceb61397f3fd5250ccb622a1a8e93559e8de76c80940a9", - "sha256:6c53338c1811f8c6d7f8cb7abd874810b15045e719e8207f957035c9177b4213", - "sha256:75c2dda47dcc3c77449712b1417bb6b89ec3b7b02e18c64262494dceffdf455e", - "sha256:773c5a98e463b52f7e8197254b39b703a5ea1972aef3a94b3b921515d77dd041", - "sha256:78c3068dcba300d473fef57cdf523e34b37de522f5a494ef9ee1ac9b4b8bbe3f", - "sha256:7bc18fc5a170f2c1cef5387a3d997c28942918bbee0f700e73fd2178ee8d474d", - "sha256:7f89eff20e4a7a64b55210dac17aea711ed8a3f2e78f2ff784c0e984302583dd", - "sha256:89458b49976b1dee5d89ab4ac943da3717b4292bf624367e862e4ee172fcce99", - "sha256:986f871c0fa649b293061236b93782d25c293a8dd8117c7ba05f8a61bdc261ae", - "sha256:a0f495a4fe5278aab278feee35e6102efecde5176a8a74dd28c28e3fc5c8d7c7", - "sha256:a14aa436586c41633339415de82a41164691d02d3e661038da533be5d40794a5", - "sha256:b8ab3ab38afc47d8f4fe629b836243544351cef681b6bdb1dc869028d6fdcbfb", - "sha256:bb487881608ebd293592553c618f0c83316f4f13a64cb18605b1d2fb9fd3da3e", - "sha256:c0b24bfe3431b3cb7ced323bca813dbd13aca973a1475b512d3331fd0de8ec60", - "sha256:c7894c06205166d360ab2915ae306d1f7403e9ce3d3aaeff4095eaf98e42ce66", - "sha256:d4039bb7f234ad32267c55e72fd49fb56078ea102f9d9d8559f6ec34d4887630", - "sha256:e4d6bb8703d0319eb04b7319b12ea41580df44fd84d83ccda13ea463c6801414", - "sha256:e8fab9911fd2fa8e5af407057cb8bdf87762f983cba483fa3234be20a9a0af77", - "sha256:f3818e578e687cdb21dc4aa4a3bc6278c656c9c393e9eda14dd04943f478863d", - "sha256:fe666645493d72712c46e4fbe8bec094b06aec3c337400479e9704439c9d9586" + "sha256:01d566750e7c08e5f094419f8d1ee90e7fa286d8d77c4569748263ed5f05280a", + "sha256:072cb90e2fe4b5cc27d56de12ec5a00311eee781c2d2e3f7c98a82319103c7ed", + "sha256:0e078e793a9882bf48194b8b5c9b40c75769db1859cd90b210a4d7bf33cda2b1", + "sha256:1a3842be21d1e25b7f3440a0c881ef44161937273ea386c30c0e253e30c63740", + "sha256:1dc973bdea03c65f03f41517e4f0fc2b717d71cfbcf4ec34adac7e5bee71303e", + "sha256:214a53257c100e93e7673e95ab448d287a37626a3902e498025993cc633647ae", + "sha256:30462d61e7e290229a64e1c3682b4cc758ffc441e59cc6ce6fae059a05df305b", + "sha256:34004f60b1e79033b0ca29b9ab53a86c12bcaab56648b82fbe21c007cd73d867", + "sha256:34c888a57f419c63bef63bc0911c5bb407b93ed5d6bdeb1587dca2cd1dd56ad1", + "sha256:3dd0cba13b36ff969232930bd6db08d3da0798f1fac376bd1fa4458f4b55d802", + "sha256:4e5acf3b856a50d0aaf385f06a7b56a128a296322a9740f5f279c96619244308", + "sha256:60d859e1efa5cc80436d58aecd3718ff2e74b987db0518376046adedba97ac30", + "sha256:61e505379497b624d6316dd67ef8100aaadca0451f48f8c6fff8d622281cd121", + "sha256:6f6de0bee19c70cb01e519634f0c35770de623006e4876e649ee4a960a147fec", + "sha256:77ac051b7caf02938a32ea0925f558534ab2a99c0c98c681cc905e3e8cba506e", + "sha256:7e4d74515d92c4e2be7201aaef7a51705bd3d5957df4994ddfe1b252195b5e27", + "sha256:7ea18a5c87cacdd6e4feacf8badf13643775b6f69c3aa8b50417834b9ce0e627", + "sha256:993837bbf0849e3b176e1ef6a50e9b8c2225e895501b85d56f4bb65a67f5ea25", + "sha256:9a5f0cf8b95c0c058e413679a650f70dcc97764ccb2a6d5ccc6b08d44c9b334c", + "sha256:9f2839396d21d5537bc9ff53772d44db39b0efb6bf8b6cac709170483df53a5b", + "sha256:b8ba4b4ee3addc26bc595a51b6240b05a80e254b946d624fff6506439bc323d1", + "sha256:bb6d90180eff72fc5a30099c442b8b0b5a620e84bf03ef32a55e3f7bd543f32e", + "sha256:c3d778304209cc39f8287da22f2180f34d2c2ee46cd55abd82e48178841b37b1", + "sha256:c562bc316040097e21357e783286e5eca056a5b2750e89d9d75f9541c156b6dc", + "sha256:d114f9c0164df8fcd2880e4ba96986d7b0e7218f6984acc4989ff384c5d3d512", + "sha256:d282b030ed5c736e4cdb1713a0c4fad7027f4e3959dc4b8fdb7c75042d83ed1b", + "sha256:d8c73fe0ec57a0e4fdf5d2728b5e18b63980f55f1baf51b6bac6a73e8cbb7186", + "sha256:e5c8f4198e25bc4b0e4a884377e0c0e46ca273993679e3bcc212ef96d4211b83", + "sha256:e7f1dcc0e8c3e18fa2fddca4aecdf71c5651555a8dc9a0cd3a1d164cbce6cb35", + "sha256:ea3b61bff995de49b07331d1081e0056ea29901d3e995aa989073fe2b1be0cb7", + "sha256:ea5f987b4da530822fa797cf2f010193be77ea9e232d07454e3194531edd8e58", + "sha256:f91b16e73eca996f86d1943be3b2c2b679b03e068ed8c82a5506c1e65766e4a6" ], "index": "pypi", - "version": "==0.29.14" + "version": "==0.29.15" }, "falcon": { "hashes": [ @@ -134,18 +129,18 @@ }, "idna": { "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", + "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" ], - "version": "==2.8" + "version": "==2.9" }, "jack-client": { "hashes": [ - "sha256:55bc516a88fc9e3eaac9dc6e55205bbf8eab8d9f9aae665a07324b2cfb54f211", - "sha256:ca1ec6a620060951b100971d9052b4e24ef8ef5be152eabf3fa60d790679b95f" + "sha256:2522debfb112195971837d78f46105c78cf20d4d02743084ee9b71371b50ecfe", + "sha256:34d7b10610c2f748e9cc922ed5c5f4694154c832050a165b32fe85dced751d36" ], "index": "pypi", - "version": "==0.5.1" + "version": "==0.5.2" }, "mido": { "hashes": [ @@ -157,14 +152,13 @@ }, "pycairo": { "hashes": [ - "sha256:dcb853fd020729516e8828ad364084e752327d4cff8505d20b13504b32b16531" + "sha256:2c143183280feb67f5beb4e543fd49990c28e7df427301ede04fc550d3562e84" ], - "version": "==1.18.2" + "version": "==1.19.1" }, "pycparser": { "hashes": [ - "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3", - "sha256:c43b2ad473872c1546a6e72275e558e4a75284541a88e4fef3446a79095f05ae" + "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" ], "version": "==2.19" }, @@ -184,75 +178,76 @@ }, "pyqt5": { "hashes": [ - "sha256:14737bb4673868d15fa91dad79fe293d7a93d76c56d01b3757b350b8dcb32b2d", - "sha256:1936c321301f678d4e6703d52860e1955e5c4964e6fd00a1f86725ce5c29083c", - "sha256:3f79de6e9f29e858516cc36ffc2b992e262af841f3799246aec282b76a3eccdf", - "sha256:509daab1c5aca22e3cf9508128abf38e6e5ae311d7426b21f4189ffd66b196e9" + "sha256:2d94ec761fb656707050c68b41958e3a9f755bb1df96c064470f4096d2899e32", + "sha256:2f230f2dbd767099de7a0cb915abdf0cbc3256a0b5bb910eb09b99117db7a65b", + "sha256:31b142a868152d60c6323e0527edb692fdf05fd7cb4fe2fe9ce07d1ce560221a", + "sha256:713b9a201f5e7b2fca8691373e5d5c8c2552a51d87ca9ffbb1461e34e3241211", + "sha256:a0bfe9fd718bca4de3e33000347e048f73126b6dc46530eb020b0251a638ee9d" ], "index": "pypi", - "version": "==5.13.2" + "version": "==5.14.1" }, "pyqt5-sip": { "hashes": [ - "sha256:02d94786bada670ab17a2b62ce95b3cf8e3b40c99d36007593a6334d551840bb", - "sha256:06bc66b50556fb949f14875a4c224423dbf03f972497ccb883fb19b7b7c3b346", - "sha256:091fbbe10a7aebadc0e8897a9449cda08d3c3f663460d812eca3001ca1ed3526", - "sha256:0a067ade558befe4d46335b13d8b602b5044363bfd601419b556d4ec659bca18", - "sha256:1910c1cb5a388d4e59ebb2895d7015f360f3f6eeb1700e7e33e866c53137eb9e", - "sha256:1c7ad791ec86247f35243bbbdd29cd59989afbe0ab678e0a41211f4407f21dd8", - "sha256:3c330ff1f70b3eaa6f63dce9274df996dffea82ad9726aa8e3d6cbe38e986b2f", - "sha256:482a910fa73ee0e36c258d7646ef38f8061774bbc1765a7da68c65056b573341", - "sha256:7695dfafb4f5549ce1290ae643d6508dfc2646a9003c989218be3ce42a1aa422", - "sha256:8274ed50f4ffbe91d0f4cc5454394631edfecd75dc327aa01be8bc5818a57e88", - "sha256:9047d887d97663790d811ac4e0d2e895f1bf2ecac4041691487de40c30239480", - "sha256:9f6ab1417ecfa6c1ce6ce941e0cebc03e3ec9cd9925058043229a5f003ae5e40", - "sha256:b43ba2f18999d41c3df72f590348152e14cd4f6dcea2058c734d688dfb1ec61f", - "sha256:c3ab9ea1bc3f4ce8c57ebc66fb25cd044ef92ed1ca2afa3729854ecc59658905", - "sha256:da69ba17f6ece9a85617743cb19de689f2d63025bf8001e2facee2ec9bcff18f", - "sha256:ef3c7a0bf78674b0dda86ff5809d8495019903a096c128e1f160984b37848f73", - "sha256:fabff832046643cdb93920ddaa8f77344df90768930fbe6bb33d211c4dcd0b5e" + "sha256:1115728644bbadcde5fc8a16e7918bd31915a42dd6fb36b10d4afb78c582753e", + "sha256:1f4289276d355b6521dc2cc956189315da6f13adfb6bbab8f25ebd15e3bce1d4", + "sha256:288c6dc18a8d6a20981c07b715b5695d9b66880778565f3792bc6e38f14f20fb", + "sha256:3f665376d9e52faa9855c3736a66ce6d825f85c86d7774d3c393f09da23f4f86", + "sha256:6b4860c4305980db509415d0af802f111d15f92016c9422eb753bc8883463456", + "sha256:7ffa39763097f64de129cf5cc770a651c3f65d2466b4fe05bef2bd2efbaa38e6", + "sha256:8a18e6f45d482ddfe381789979d09ee13aa6450caa3a0476503891bccb3ac709", + "sha256:8da842d3d7bf8931d1093105fb92702276b6dbb7e801abbaaa869405d616171a", + "sha256:b42021229424aa44e99b3b49520b799fd64ff6ae8b53f79f903bbd85719a28e4", + "sha256:b5b4906445fe980aee76f20400116b6904bf5f30d0767489c13370e42a764020", + "sha256:c1e730a9eb2ec3869ed5d81b0f99f6e2460fb4d77750444c0ec183b771d798f7", + "sha256:cbeeae6b45234a1654657f79943f8bccd3d14b4e7496746c62cf6fbce69442c7", + "sha256:d46b0f8effc554de52a1466b1bd80e5cb4bce635a75ac4e7ad6247c965dec5b9", + "sha256:e28c3abc9b62a1b7e796891648b9f14f8167b31c8e7990fae79654777252bb4d", + "sha256:e6078f5ee7d31c102910d0c277a110e1c2a20a3fc88cd017a39e170120586d3f", + "sha256:ee1a12f09d5af2304273bfd2f6b43835c1467d5ed501a6c95f5405637fa7750a", + "sha256:f314f31f5fd39b06897f013f425137e511d45967150eb4e424a363d8138521c6" ], - "version": "==12.7.0" + "version": "==12.7.1" }, "python-rtmidi": { "hashes": [ - "sha256:30d9fbb0070df100f58f6e93b8149aac2d435ae3b7d368e1fce9fd691184255c", - "sha256:521f2ca02876fae4f3efe7b52dfd2ec7cdba809af07905bd3b25b5aee36eaa50", - "sha256:67405b66ba90f8ef423974904ad12bf92d80fc14e4180d13a69e05d787a72849", - "sha256:6a47a5e26675fba87fc90917c63139c8b3aa793bcb429f22fc52fffd87853f00", - "sha256:7724252c7a2d407c510f089da321755dedbc05a864c1e97c034c807882c3234c", - "sha256:977d93ad6b45b48efddb92aed7fd69e1a33ff5d7130399971101133ad673be3e", - "sha256:ad2a5f0c14faf9810cc92d919096be7167eff8d4fd63abc5e2d7c20e5da9326c", - "sha256:bee33b8c06431b0f5cfdf500c68e0562e1ec2bd46b8d883b216707f16d51e9a3", - "sha256:d657e4f4ce4ad5b1c7ee738540889c031344c59159483bdbb65dab6c8459c746", - "sha256:e516a7f876381667eba82417254bb8d138c17dc7666fd70212d839813b60ea19", - "sha256:f9d2a1b5374b2e6856006537b6d4d5ceed4db4c4ee60caec4af83707a78297e4", - "sha256:ffaed4e6654f5b7458e622146f5cd9253b1ca2986a118b9a86546111b1a55731" + "sha256:07ef73412820c08d0e5ed15927eb41f986efd5ccb23ad1065e7128d4275fe8b9", + "sha256:1791458d0dc4d3dd408b381d0b3613a09a99b1cf38fbe7b0fcb470616423a4b2", + "sha256:3b5913e6a3126788578566312064e73c90b8819673aee9a39dc985fed17099f7", + "sha256:3e97028ae8039fe7d9b802a42105c771268ea8b57533441e5ec46a8dc8229984", + "sha256:3fce8e85c4c09fd480fb976189c576377293ab120a32e330e7d5489e858dd427", + "sha256:3fd151b801128bdedcffa3be4899a68958a0ad5a102d100a3f2bce7e01d42ced", + "sha256:4ed5da58e4b57e84074f15e9c27bb3fc1b535258c502517f3c7e1042ed74ffe4", + "sha256:7cf3559bc9264d1ae3d85d636271df2a7beea0d6e2582a860513c8b272cfc46a", + "sha256:da22994bef4aec2fd5adb14be80a8cfc3710fd8964204bbf5f33e257ea79442d", + "sha256:e74fd4e7fc4c783f41724ecd2b0a17cbd7792795145a654421c46c3bdb292f02", + "sha256:e86fe9951340e10db66c21c5ae9e63d3249e70148e74cbeb37d16257e2f39f53", + "sha256:fe763f863471b24c24b8d103dd52e4a0d1e55339a2cfde5e06d635a909c08719" ], "index": "pypi", - "version": "==1.3.1" + "version": "==1.4.0" }, "pyudev": { "hashes": [ - "sha256:094b7a100150114748aaa3b70663485dd360457a709bfaaafe5a977371033f2b" + "sha256:69bb1beb7ac52855b6d1b9fe909eefb0017f38d917cba9939602c6880035b276" ], "index": "pypi", - "version": "==0.21.0" + "version": "==0.22.0" }, "requests": { "hashes": [ - "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", - "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", + "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" ], "index": "pypi", - "version": "==2.22.0" + "version": "==2.23.0" }, "six": { "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.13.0" + "version": "==1.14.0" }, "sortedcontainers": { "hashes": [ @@ -264,10 +259,10 @@ }, "urllib3": { "hashes": [ - "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", - "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" + "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", + "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" ], - "version": "==1.25.7" + "version": "==1.25.8" } }, "develop": { @@ -289,10 +284,10 @@ }, "six": { "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.13.0" + "version": "==1.14.0" }, "webencodings": { "hashes": [ diff --git a/lisp/plugins/cart_layout/page_widget.py b/lisp/plugins/cart_layout/page_widget.py index fdd92a98c..055d501c5 100644 --- a/lisp/plugins/cart_layout/page_widget.py +++ b/lisp/plugins/cart_layout/page_widget.py @@ -47,13 +47,9 @@ def __init__(self, rows, columns, *args): def initLayout(self): for row in range(0, self.__rows): self.layout().setRowStretch(row, 1) - # item = QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding) - # self.layout().addItem(item, row, 0) for column in range(0, self.__columns): self.layout().setColumnStretch(column, 1) - # item = QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum) - # self.layout().addItem(item, 0, column) def addWidget(self, widget, row, column): self._checkIndex(row, column) @@ -100,9 +96,11 @@ def contextMenuEvent(self, event): self.contextMenuRequested.emit(event.globalPos()) def dragEnterEvent(self, event): - if event.mimeData().hasUrls() and all([x.isLocalFile() for x in event.mimeData().urls()]): + if event.mimeData().hasUrls() and all( + [x.isLocalFile() for x in event.mimeData().urls()] + ): event.accept() - elif event.mimeData().hasText() and event.mimeData().text() == CartPageWidget.DRAG_MAGIC: + elif event.mimeData().text() == CartPageWidget.DRAG_MAGIC: event.accept() else: event.ignore() diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index eed62e052..840a4a72c 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -316,7 +316,7 @@ def __new_message(self): dialog = OscMessageDialog(parent=self) if dialog.exec() == dialog.Accepted: path = dialog.pathEdit.text() - if len(path) < 2 or path[0] is not "/": + if len(path) < 2 or path[0] != "/": QMessageBox.warning( self, "Warning", diff --git a/lisp/plugins/gst_backend/gst_backend.py b/lisp/plugins/gst_backend/gst_backend.py index fa47dbff7..f60bda348 100644 --- a/lisp/plugins/gst_backend/gst_backend.py +++ b/lisp/plugins/gst_backend/gst_backend.py @@ -60,7 +60,6 @@ def __init__(self, app): # Initialize GStreamer Gst.init(None) - # Register GStreamer settings widgets AppConfigurationDialog.registerSettingsPage( "plugins.gst", GstSettings, GstBackend.Config @@ -107,31 +106,38 @@ def supported_extensions(self): def _add_uri_audio_cue(self): """Add audio MediaCue(s) form user-selected files""" + # Get the last visited directory, or use the session-file location directory = GstBackend.Config.get("mediaLookupDir", "") if not os.path.exists(directory): directory = self.app.session.dir() + # Open a filechooser, at the last visited directory files, _ = QFileDialog.getOpenFileNames( self.app.window, translate("GstBackend", "Select media files"), directory, qfile_filters(self.supported_extensions(), anyfile=True), ) + if files: + # Updated the last visited directory GstBackend.Config["mediaLookupDir"] = os.path.dirname(files[0]) GstBackend.Config.write() - self.add_cue_from_files(files) + + self.add_cue_from_files(files) def add_cue_from_urls(self, urls): extensions = self.supported_extensions() + extensions = extensions["audio"] + extensions["video"] files = [] + for url in urls: - filename = url.path() - ext = os.path.splitext(url.fileName())[-1] - if ext.strip('.') not in extensions['audio'] + extensions['video']: - # Skip unsupported media types - continue - files.append(filename) + # Get the file extension without the leading dot + extension = os.path.splitext(url.fileName())[-1][1:] + # If is a supported audio/video file keep it + if extension in extensions: + files.append(url.path()) + self.add_cue_from_files(files) def add_cue_from_files(self, files): @@ -158,6 +164,7 @@ def add_cue_from_files(self, files): cues.append(cue) + # Insert the cue into the layout self.app.commands_stack.do( LayoutAutoInsertCuesCommand(self.app.session.layout, *cues) ) diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py index d33cae9be..40fd0aeee 100644 --- a/lisp/plugins/list_layout/list_view.py +++ b/lisp/plugins/list_layout/list_view.py @@ -140,7 +140,7 @@ def dragEnterEvent(self, event): else: event.ignore() else: - return super(CueListView, self).dragEnterEvent(event) + return super().dragEnterEvent(event) def dropEvent(self, event): if event.mimeData().hasUrls(): @@ -151,7 +151,9 @@ def dropEvent(self, event): # Decode mimedata information about the drag&drop event, since only # internal movement are allowed we assume the data format is correct - data = event.mimeData().data("application/x-qabstractitemmodeldatalist") + data = event.mimeData().data( + "application/x-qabstractitemmodeldatalist" + ) stream = QDataStream(data, QIODevice.ReadOnly) # Get the starting-item row @@ -242,13 +244,13 @@ def __currentItemChanged(self, current, previous): if current is not None: current.current = True self.__updateItemStyle(current) - # Ensure the current item is in the middle - self.scrollToItem(current, QTreeWidget.PositionAtCenter) - if ( - self.selectionMode() != QTreeWidget.NoSelection - and not self.selectedIndexes() - ): + if self.selectionMode() == QTreeWidget.NoSelection: + # Ensure the current item is in the middle of the viewport. + # This is skipped in "selection-mode" otherwise it creates + # confusion during drang&drop operations + self.scrollToItem(current, QTreeWidget.PositionAtCenter) + elif not self.selectedIndexes(): current.setSelected(True) def __updateItemStyle(self, item): From 8b541ff02aecf8a0803f8705623906d708f932e4 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 13 Mar 2020 08:58:54 +0100 Subject: [PATCH 216/333] Fix #199, upgrade flatpak runtime org.gnome.Platform/Sdk to 3.34 --- lisp/backend/audio_utils.py | 7 +++---- lisp/command/command.py | 6 +++--- lisp/cues/cue_factory.py | 2 +- lisp/layout/cue_layout.py | 1 - lisp/plugins/gst_backend/gst_utils.py | 17 ++++++++------- lisp/plugins/media_info/media_info.py | 30 ++++++++++++++------------- lisp/ui/settings/app_configuration.py | 1 - lisp/ui/settings/cue_settings.py | 7 ++----- scripts/Flatpak/config.sh | 2 +- scripts/Flatpak/template.json | 2 +- 10 files changed, 37 insertions(+), 38 deletions(-) diff --git a/lisp/backend/audio_utils.py b/lisp/backend/audio_utils.py index c60f348f9..666ea7b35 100644 --- a/lisp/backend/audio_utils.py +++ b/lisp/backend/audio_utils.py @@ -17,7 +17,6 @@ import aifc import math -import sunau import urllib.parse import wave @@ -87,11 +86,11 @@ def python_duration(path, sound_module): def uri_duration(uri): """Return the audio-file duration, using the given uri""" - protocol, path = uri.split("://") + scheme, path = uri.split("://") path = urllib.parse.unquote(path) - if protocol == "file": - for mod in [wave, aifc, sunau]: + if scheme == "file": + for mod in [wave, aifc]: duration = python_duration(path, mod) if duration > 0: return duration diff --git a/lisp/command/command.py b/lisp/command/command.py index cccf5a11d..9e6a98ad6 100644 --- a/lisp/command/command.py +++ b/lisp/command/command.py @@ -21,10 +21,10 @@ class Command(ABC): """Base class for commands. - Commands provides the ability to revert the changes done in the "do" method, + A Command gives the ability to revert the changes done in the "do" method, via the "undo" method, and redo them via the "redo" method. - A command could provide, via the "log" function, a simple log message. + A simple log message can be provided, via the "log" function. .. warning:: Commands may keep reference to external objects. @@ -51,7 +51,7 @@ def log(self) -> str: """Return a short message to describe what the command does. The method should return a message generic for do/undo/redo, - an handler will care about adding context to the message. + a handler will care about adding context to the message. The log message should be user-friendly and localized. """ diff --git a/lisp/cues/cue_factory.py b/lisp/cues/cue_factory.py index 71e57f387..c17f1830b 100644 --- a/lisp/cues/cue_factory.py +++ b/lisp/cues/cue_factory.py @@ -36,7 +36,7 @@ def register_factory(cls, cue_type, factory): :param cue_type: The cue class name :type cue_type: str - :param factory: The cue class or a factory function + :param factory: The cue class, or a factory function """ cls.__REGISTRY[cue_type] = factory diff --git a/lisp/layout/cue_layout.py b/lisp/layout/cue_layout.py index 236a6481b..3a297e0bf 100644 --- a/lisp/layout/cue_layout.py +++ b/lisp/layout/cue_layout.py @@ -50,7 +50,6 @@ def __init__(self, application): self.cue_executed = Signal() # After a cue is executed self.all_executed = Signal() # After execute_all is called - # TODO: self.standby_changed = Signal() self.key_pressed = Signal() # After a key is pressed @property diff --git a/lisp/plugins/gst_backend/gst_utils.py b/lisp/plugins/gst_backend/gst_utils.py index 4618adf8c..c165fb897 100644 --- a/lisp/plugins/gst_backend/gst_utils.py +++ b/lisp/plugins/gst_backend/gst_utils.py @@ -44,16 +44,19 @@ def gst_mime_types(): def gst_uri_metadata(uri): """Discover media-file metadata using GStreamer.""" - discoverer = GstPbutils.Discoverer() - uri = uri.split("://") - info = discoverer.discover_uri(uri[0] + "://" + quote(unquote(uri[1]))) - - return info + try: + discoverer = GstPbutils.Discoverer() + scheme, _, path = uri.partition("://") + return discoverer.discover_uri(scheme + "://" + quote(unquote(path))) + except Exception: + pass -# Adaption of the code found in https://github.com/ch3pjw/pyam def gst_parse_tags_list(gst_tag_list): - """Takes a GstTagList object and returns a dict.""" + """Takes a GstTagList object and returns a dict. + + Adapted from https://github.com/ch3pjw/pyamp + """ parsed_tags = {} def parse_tag(gst_tag_list, tag_name, parsed_tags): diff --git a/lisp/plugins/media_info/media_info.py b/lisp/plugins/media_info/media_info.py index 12971ff82..5cc81daa0 100644 --- a/lisp/plugins/media_info/media_info.py +++ b/lisp/plugins/media_info/media_info.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from urllib.request import unquote +from urllib.parse import unquote from PyQt5 import QtCore from PyQt5.QtWidgets import ( @@ -63,24 +63,26 @@ def __init__(self, app): CueLayout.CuesMenu.add(self.cue_action_group, MediaCue) + def _gst_uri_metadata(self, uri): + if uri is not None: + if uri.startswith("file://"): + uri = "file://" + self.app.session.abs_path(uri[7:]) + + return gst_uri_metadata(uri) + def _show_info(self, cue): - media_uri = cue.media.input_uri() + gst_meta = self._gst_uri_metadata(cue.media.input_uri()) - if media_uri is None: - QMessageBox.warning( + if gst_meta is None: + return QMessageBox.warning( self.app.window, translate("MediaInfo", "Warning"), - translate("MediaInfo", "No info to display"), + translate("MediaInfo", "Cannot get any information."), ) else: - if media_uri.startswith("file://"): - media_uri = "file://" + self.app.session.abs_path(media_uri[7:]) - - gst_info = gst_uri_metadata(media_uri) - info = {"URI": unquote(gst_info.get_uri())} - + info = {"URI": unquote(gst_meta.get_uri())} # Audio streams info - for stream in gst_info.get_audio_streams(): + for stream in gst_meta.get_audio_streams(): name = stream.get_stream_type_nick().capitalize() info[name] = { "Bitrate": str(stream.get_bitrate() // 1000) + " Kb/s", @@ -90,7 +92,7 @@ def _show_info(self, cue): } # Video streams info - for stream in gst_info.get_video_streams(): + for stream in gst_meta.get_video_streams(): name = stream.get_stream_type_nick().capitalize() info[name] = { "Height": str(stream.get_height()) + " px", @@ -104,7 +106,7 @@ def _show_info(self, cue): } # Tags - gst_tags = gst_info.get_tags() + gst_tags = gst_meta.get_tags() tags = {} if gst_tags is not None: diff --git a/lisp/ui/settings/app_configuration.py b/lisp/ui/settings/app_configuration.py index 9d4886199..c15669ded 100644 --- a/lisp/ui/settings/app_configuration.py +++ b/lisp/ui/settings/app_configuration.py @@ -38,7 +38,6 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.setWindowTitle(translate("AppConfiguration", "LiSP preferences")) self.setWindowModality(QtCore.Qt.WindowModal) - self.setMaximumSize(800, 510) self.setMinimumSize(800, 510) self.resize(800, 510) self.setLayout(QVBoxLayout()) diff --git a/lisp/ui/settings/cue_settings.py b/lisp/ui/settings/cue_settings.py index 4489cbe07..27793119b 100644 --- a/lisp/ui/settings/cue_settings.py +++ b/lisp/ui/settings/cue_settings.py @@ -42,16 +42,13 @@ class CueSettingsDialog(QDialog): def __init__(self, cue, **kwargs): """ - :param cue: Target cue, or a cue-type for multi-editing """ super().__init__(**kwargs) self.setWindowModality(QtCore.Qt.ApplicationModal) - self.setMaximumSize(640, 510) - self.setMinimumSize(640, 510) - self.resize(640, 510) + self.setMinimumSize(800, 510) + self.resize(800, 510) self.setLayout(QVBoxLayout()) - # self.layout().setContentsMargins(5, 5, 5, 10) if isinstance(cue, type): if issubclass(cue, Cue): diff --git a/scripts/Flatpak/config.sh b/scripts/Flatpak/config.sh index cde52ef3e..758fbdb54 100755 --- a/scripts/Flatpak/config.sh +++ b/scripts/Flatpak/config.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash export FLATPAK_RUNTIME="org.gnome.Platform" -export FLATPAK_RUNTIME_VERSION="3.32" +export FLATPAK_RUNTIME_VERSION="3.34" export FLATPAK_SDK="org.gnome.Sdk" export FLATPAK_INSTALL="$FLATPAK_RUNTIME//$FLATPAK_RUNTIME_VERSION $FLATPAK_SDK//$FLATPAK_RUNTIME_VERSION" diff --git a/scripts/Flatpak/template.json b/scripts/Flatpak/template.json index 2e439d15f..13e27604d 100644 --- a/scripts/Flatpak/template.json +++ b/scripts/Flatpak/template.json @@ -1,7 +1,7 @@ { "app-id": "com.github.FrancescoCeruti.LinuxShowPlayer", "runtime": "org.gnome.Platform", - "runtime-version": "3.32", + "runtime-version": "3.34", "sdk": "org.gnome.Sdk", "command": "linux-show-player", "build-options": { From 903f24c52dc2bf98ce1591e7ef9ef461badd506d Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 13 Mar 2020 19:24:07 +0100 Subject: [PATCH 217/333] Update flatpak dependencies --- scripts/Flatpak/template.json | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/scripts/Flatpak/template.json b/scripts/Flatpak/template.json index 13e27604d..f359b2165 100644 --- a/scripts/Flatpak/template.json +++ b/scripts/Flatpak/template.json @@ -138,9 +138,10 @@ ], "sources": [ { - "type": "archive", - "url": "https://github.com/jackaudio/jack2/releases/download/v1.9.12/jack2-1.9.12.tar.gz", - "sha1": "8ab6329c6a107cdf370c40afac154370b406437d" + "type": "git", + "url": "https://github.com/jackaudio/jack2.git", + "tag": "v1.9.14", + "commit": "b54a09bf7ef760d81fdb8544ad10e45575394624" } ], "cleanup": [ @@ -157,8 +158,8 @@ { "type": "git", "url": "https://github.com/thestk/rtmidi.git", - "tag": "v3.0.0", - "commit": "88e53b9cfe60719c9ade800795313f3c6026c48c" + "tag": "v4.0.0", + "commit": "cc887191c3b4cb6697aeba5536d9f4eb423090aa" } ] }, @@ -172,8 +173,8 @@ { "type": "git", "url": "https://github.com/radarsat1/liblo.git", - "tag": "0.29", - "commit": "901c6ff1ab269d964e256936a609779f86047ebd" + "tag": "0.31", + "commit": "840ed69b1d669a1ce587eb592746e3dff6985d76" } ] }, @@ -194,13 +195,13 @@ "name": "python-six", "buildsystem": "simple", "build-commands": [ - "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} six-1.11.0-py2.py3-none-any.whl" + "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} six-1.14.0-py2.py3-none-any.whl" ], "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/67/4b/141a581104b1f6397bfa78ac9d43d8ad29a7ca43ea90a2d863fe3056e86a/six-1.11.0-py2.py3-none-any.whl", - "sha256": "832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + "url": "https://files.pythonhosted.org/packages/65/eb/1f97cb97bfc2390a276969c6fae16075da282f5058082d4cb10c6c5c1dba/six-1.14.0-py2.py3-none-any.whl", + "sha256": "8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" } ] }, @@ -265,13 +266,13 @@ "name": "python-wheel", "buildsystem": "simple", "build-commands": [ - "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} wheel-0.33.1-py2.py3-none-any.whl" + "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} wheel-0.34.2-py2.py3-none-any.whl" ], "sources": [ { "type": "file", - "url": "https://files.pythonhosted.org/packages/96/ba/a4702cbb6a3a485239fbe9525443446203f00771af9ac000fa3ef2788201/wheel-0.33.1-py2.py3-none-any.whl", - "sha256": "8eb4a788b3aec8abf5ff68d4165441bc57420c9f64ca5f471f58c3969fe08668" + "url": "https://files.pythonhosted.org/packages/8c/23/848298cccf8e40f5bbb59009b32848a4c38f4e7f3364297ab3c3e2e2cd14/wheel-0.34.2-py2.py3-none-any.whl", + "sha256": "df277cb51e61359aba502208d680f90c0493adec6f0e848af94948778aed386e" } ] }, From 0ca8259a47ca4ec98fe38041737dc7cc0043781b Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 13 Mar 2020 19:33:46 +0100 Subject: [PATCH 218/333] Fixed rtmidi tag name in flatpak build script --- scripts/Flatpak/template.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Flatpak/template.json b/scripts/Flatpak/template.json index f359b2165..3472b1301 100644 --- a/scripts/Flatpak/template.json +++ b/scripts/Flatpak/template.json @@ -158,7 +158,7 @@ { "type": "git", "url": "https://github.com/thestk/rtmidi.git", - "tag": "v4.0.0", + "tag": "4.0.0", "commit": "cc887191c3b4cb6697aeba5536d9f4eb423090aa" } ] From e75091c5a4291e7928f70176401dce53a741b9cd Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Fri, 13 Mar 2020 19:55:34 +0100 Subject: [PATCH 219/333] Remove python-six from the flatpak dependencies, it's already included in the gnome 3.34 sdk --- scripts/Flatpak/template.json | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/scripts/Flatpak/template.json b/scripts/Flatpak/template.json index 3472b1301..1f40b55f5 100644 --- a/scripts/Flatpak/template.json +++ b/scripts/Flatpak/template.json @@ -191,20 +191,6 @@ } ] }, - { - "name": "python-six", - "buildsystem": "simple", - "build-commands": [ - "pip3 install --verbose --no-index --no-deps --prefix=${FLATPAK_DEST} six-1.14.0-py2.py3-none-any.whl" - ], - "sources": [ - { - "type": "file", - "url": "https://files.pythonhosted.org/packages/65/eb/1f97cb97bfc2390a276969c6fae16075da282f5058082d4cb10c6c5c1dba/six-1.14.0-py2.py3-none-any.whl", - "sha256": "8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" - } - ] - }, { "name": "python-google-protobuf", "buildsystem": "simple", From d07470a925083da1c8741af0aa6abf77ba72fea0 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Mon, 23 Mar 2020 21:56:35 +0100 Subject: [PATCH 220/333] Fade global settings now only act as a fallback, add option to hide index column in ListLayout Add: show the currently connected device in MIDI settings Update: improved settings descriptions Update: added python-alsa (pyalsa) to Pipfile (git source) Update: replaced "busy indicator" widget Update: improved dark theme Fix: tabs in CarLayout now resize correctly to the content Fix: network discovery now works correctly Fix: MIDI cue now send messages again --- Pipfile | 5 +- Pipfile.lock | 38 +--- lisp/cues/cue.py | 70 +++--- lisp/cues/media_cue.py | 31 ++- lisp/plugins/gst_backend/gst_pipe_edit.py | 1 + lisp/plugins/list_layout/default.json | 5 +- lisp/plugins/list_layout/layout.py | 18 ++ lisp/plugins/list_layout/list_view.py | 6 +- lisp/plugins/list_layout/list_widgets.py | 12 +- lisp/plugins/midi/midi.py | 4 +- lisp/plugins/midi/midi_cue.py | 5 +- lisp/plugins/midi/midi_io.py | 7 +- lisp/plugins/midi/midi_settings.py | 43 +++- lisp/plugins/midi/port_monitor.py | 1 + lisp/plugins/network/discovery.py | 26 +-- lisp/plugins/network/discovery_dialogs.py | 92 +++++--- lisp/plugins/synchronizer/synchronizer.py | 8 +- lisp/ui/about.py | 6 +- lisp/ui/settings/app_pages/cue.py | 44 +++- lisp/ui/settings/app_pages/general.py | 27 ++- lisp/ui/settings/app_pages/layouts.py | 18 +- lisp/ui/themes/dark/theme.qss | 40 ++-- lisp/ui/widgets/fades.py | 1 + lisp/ui/widgets/qprogresswheel.py | 122 ----------- lisp/ui/widgets/qwaitingspinner.py | 252 ++++++++++++++++++++++ scripts/Flatpak/pipenv_flatpak.py | 4 + 26 files changed, 576 insertions(+), 310 deletions(-) delete mode 100644 lisp/ui/widgets/qprogresswheel.py create mode 100644 lisp/ui/widgets/qwaitingspinner.py diff --git a/Pipfile b/Pipfile index ba9de7de9..4bd7e8815 100644 --- a/Pipfile +++ b/Pipfile @@ -15,8 +15,7 @@ PyQt5 = "~=5.6" python-rtmidi = "~=1.1" requests = "~=2.20" sortedcontainers = "~=2.0" -pyudev = "~=0.21" +pyalsa = {editable = true, git = "https://github.com/alsa-project/alsa-python.git", ref = "v1.1.6"} [dev-packages] -pysnooper = "*" -html5lib = "*" \ No newline at end of file +html5lib = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 05ddab7dd..d333e7cf8 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "10ea83f3292f6fc7b6370a438e3c32738b187e1280feb34136152369ceaf1a9f" + "sha256": "b76c9327c4b71f648a8d88df6b06b02d5dc3f68aa7ea55857728d05356e5218f" }, "pipfile-spec": 6, "requires": {}, @@ -150,6 +150,11 @@ "index": "pypi", "version": "==1.2.9" }, + "pyalsa": { + "editable": true, + "git": "https://github.com/alsa-project/alsa-python.git", + "ref": "e1bc1c27b6286dcf2d6bf4955650cf5729cf9b7a" + }, "pycairo": { "hashes": [ "sha256:2c143183280feb67f5beb4e543fd49990c28e7df427301ede04fc550d3562e84" @@ -158,16 +163,17 @@ }, "pycparser": { "hashes": [ - "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" + "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", + "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" ], - "version": "==2.19" + "version": "==2.20" }, "pygobject": { "hashes": [ - "sha256:2acb0daf2b3a23a90f52066cc23d1053339fee2f5f7f4275f8baa3704ae0c543" + "sha256:b97f570e55017fcd3732164811f24ecf63983a4834f61b55b0aaf64ecefac856" ], "index": "pypi", - "version": "==3.34.0" + "version": "==3.36.0" }, "pyliblo": { "hashes": [ @@ -227,13 +233,6 @@ "index": "pypi", "version": "==1.4.0" }, - "pyudev": { - "hashes": [ - "sha256:69bb1beb7ac52855b6d1b9fe909eefb0017f38d917cba9939602c6880035b276" - ], - "index": "pypi", - "version": "==0.22.0" - }, "requests": { "hashes": [ "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", @@ -242,13 +241,6 @@ "index": "pypi", "version": "==2.23.0" }, - "six": { - "hashes": [ - "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", - "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" - ], - "version": "==1.14.0" - }, "sortedcontainers": { "hashes": [ "sha256:974e9a32f56b17c1bac2aebd9dcf197f3eb9cd30553c5852a3187ad162e1a03a", @@ -274,14 +266,6 @@ "index": "pypi", "version": "==1.0.1" }, - "pysnooper": { - "hashes": [ - "sha256:7d7684d9d5cf1d026bba6a52e87009eab9458d3265b13059ab1abaea7eee6c93", - "sha256:85892ef6f4009af7d729e73c7b9e4071a16d5063b1ac9340741c5f192501441b" - ], - "index": "pypi", - "version": "==0.3.0" - }, "six": { "hashes": [ "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", diff --git a/lisp/cues/cue.py b/lisp/cues/cue.py index 6e9d342c7..06bb61523 100644 --- a/lisp/cues/cue.py +++ b/lisp/cues/cue.py @@ -72,7 +72,7 @@ class CueNextAction(EqEnum): class Cue(HasProperties): - """Cue(s) are the base component for implement any kind of live-controllable + """Cue(s) are the base component to implement any kind of live-controllable element (live = during a show). A cue implement his behavior(s) reimplementing __start__, __stop__, @@ -160,13 +160,13 @@ def __init__(self, id=None): self.fadeout_end = Signal() # Status signals - self.interrupted = Signal() # self - self.started = Signal() # self - self.stopped = Signal() # self - self.paused = Signal() # self - self.error = Signal() # self - self.next = Signal() # self - self.end = Signal() # self + self.interrupted = Signal() + self.started = Signal() + self.stopped = Signal() + self.paused = Signal() + self.error = Signal() + self.next = Signal() + self.end = Signal() self.changed("next_action").connect(self.__next_action_changed) @@ -176,7 +176,7 @@ def execute(self, action=CueAction.Default): .. Note:: Even if not specified in Cue.CueActions, when CueAction.Default is given, a "default" action is selected depending on the current - cue state, if this action is not supported nothing will be done. + cue state, if this action is not supported, nothing will be done. :param action: the action to be performed :type action: CueAction @@ -212,19 +212,35 @@ def execute(self, action=CueAction.Default): elif action == CueAction.FadeInResume: self.resume(fade=self.fadein_duration > 0) elif action == CueAction.FadeOut: - duration = AppConfig().get("cue.fadeAction", 0) - fade = AppConfig().get( - "cue.fadeActionType", FadeOutType.Linear.name - ) - - self.fadeout(duration, FadeOutType[fade]) + if self.fadeout_duration > 0: + self.fadeout( + self.fadeout_duration, FadeOutType[self.fadeout_type] + ) + else: + self.fadeout( + self._default_fade_duration(), + self._default_fade_type( + FadeOutType, FadeOutType.Linear + ), + ) elif action == CueAction.FadeIn: - duration = AppConfig().get("cue.fadeAction", 0) - fade = AppConfig().get( - "cue.fadeActionType", FadeInType.Linear.name - ) + if self.fadein_duration > 0: + self.fadein( + self.fadein_duration, FadeInType[self.fadein_type] + ) + else: + self.fadein( + self._default_fade_duration(), + self._default_fade_type(FadeInType, FadeInType.Linear), + ) + + def _default_fade_duration(self): + return AppConfig().get("cue.fadeAction", 0) - self.fadein(duration, FadeInType[fade]) + def _default_fade_type(self, type_class, default=None): + return getattr( + type_class, AppConfig().get("cue.fadeActionType"), default + ) @async_function def start(self, fade=False): @@ -301,7 +317,7 @@ def resume(self, fade=False): def __start__(self, fade=False): """Implement the cue `start` behavior. - Long running task should not block this function (i.e. the fade should + Long running tasks should not block this function (i.e. the fade should be performed in another thread). When called from `Cue.start()`, `_st_lock` is acquired. @@ -359,9 +375,9 @@ def stop(self, fade=False): def __stop__(self, fade=False): """Implement the cue `stop` behavior. - Long running task should block this function (i.e. the fade should - "block" this function), when this happen `_st_lock` must be released and - then re-acquired. + Long running tasks should block this function (i.e. the fade should + "block" this function), when this happens `_st_lock` must be released + and then re-acquired. If called during a `fadeout` operation this should be interrupted, the cue stopped and return `True`. @@ -410,8 +426,8 @@ def pause(self, fade=False): def __pause__(self, fade=False): """Implement the cue `pause` behavior. - Long running task should block this function (i.e. the fade should - "block" this function), when this happen `_st_lock` must be released and + Long running tasks should block this function (i.e. the fade should + "block" this function), when this happens `_st_lock` must be released and then re-acquired. If called during a `fadeout` operation this should be interrupted, @@ -462,7 +478,7 @@ def interrupt(self, fade=False): def __interrupt__(self, fade=False): """Implement the cue `interrupt` behavior. - Long running task should block this function without releasing + Long running tasks should block this function without releasing `_st_lock`. :param fade: True if a fade should be performed (when supported) diff --git a/lisp/cues/media_cue.py b/lisp/cues/media_cue.py index b64654ff8..6576de701 100644 --- a/lisp/cues/media_cue.py +++ b/lisp/cues/media_cue.py @@ -20,7 +20,6 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP from lisp.backend.audio_utils import MIN_VOLUME -from lisp.core.configuration import AppConfig from lisp.core.decorators import async_function from lisp.core.fade_functions import FadeInType, FadeOutType from lisp.core.fader import Fader @@ -89,7 +88,11 @@ def __stop__(self, fade=False): if self._state & CueState.Running and fade: self._st_lock.release() - ended = self._on_stop_fade() + ended = self.__fadeout( + self.fadeout_duration, + MIN_VOLUME, + FadeOutType[self.fadeout_type], + ) self._st_lock.acquire() if not ended: return False @@ -106,7 +109,11 @@ def __pause__(self, fade=False): if fade: self._st_lock.release() - ended = self._on_stop_fade() + ended = self.__fadeout( + self.fadeout_duration, + MIN_VOLUME, + FadeOutType[self.fadeout_type], + ) self._st_lock.acquire() if not ended: return False @@ -118,7 +125,11 @@ def __interrupt__(self, fade=False): self.__fader.stop() if self._state & CueState.Running and fade: - self._on_stop_fade(interrupt=True) + self.__fadeout( + self._default_fade_duration(), + MIN_VOLUME, + self._default_fade_type(FadeOutType, FadeOutType.Linear), + ) self.media.stop() @@ -215,15 +226,3 @@ def _on_start_fade(self): self.__volume.volume, FadeInType[self.fadein_type], ) - - def _on_stop_fade(self, interrupt=False): - if interrupt: - duration = AppConfig().get("cue.interruptFade", 0) - fade_type = AppConfig().get( - "cue.interruptFadeType", FadeOutType.Linear.name - ) - else: - duration = self.fadeout_duration - fade_type = self.fadeout_type - - return self.__fadeout(duration, 0, FadeOutType[fade_type]) diff --git a/lisp/plugins/gst_backend/gst_pipe_edit.py b/lisp/plugins/gst_backend/gst_pipe_edit.py index e56ab493d..89649fb98 100644 --- a/lisp/plugins/gst_backend/gst_pipe_edit.py +++ b/lisp/plugins/gst_backend/gst_pipe_edit.py @@ -39,6 +39,7 @@ def __init__(self, pipe, app_mode=False, **kwargs): super().__init__(**kwargs) self.setLayout(QGridLayout()) self.layout().setAlignment(Qt.AlignTop) + self.layout().setContentsMargins(0, 0, 0, 0) self._app_mode = app_mode diff --git a/lisp/plugins/list_layout/default.json b/lisp/plugins/list_layout/default.json index 3f762156f..2e2611f48 100644 --- a/lisp/plugins/list_layout/default.json +++ b/lisp/plugins/list_layout/default.json @@ -1,10 +1,11 @@ { - "_version_": "1.5.2", + "_version_": "1.6.2", "_enabled_": true, "show": { "dBMeters": true, "seekSliders": true, - "accurateTime": false + "accurateTime": false, + "indexColumn": true }, "selectionMode": false, "autoContinue": true, diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index 6b368dfd2..e78d99cf3 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -57,6 +57,7 @@ class ListLayout(CueLayout): auto_continue = ProxyProperty() dbmeters_visible = ProxyProperty() seek_sliders_visible = ProxyProperty() + index_column_visible = ProxyProperty() accurate_time = ProxyProperty() selection_mode = ProxyProperty() view_sizes = ProxyProperty() @@ -109,6 +110,11 @@ def __init__(self, application): self.show_accurate_action.triggered.connect(self._set_accurate_time) layout_menu.addAction(self.show_accurate_action) + self.show_index_action = QAction(layout_menu) + self.show_index_action.setCheckable(True) + self.show_index_action.triggered.connect(self._set_index_visible) + layout_menu.addAction(self.show_index_action) + self.auto_continue_action = QAction(layout_menu) self.auto_continue_action.setCheckable(True) self.auto_continue_action.triggered.connect(self._set_auto_continue) @@ -140,6 +146,7 @@ def __init__(self, application): self._set_seeksliders_visible(ListLayout.Config["show.seekSliders"]) self._set_accurate_time(ListLayout.Config["show.accurateTime"]) self._set_dbmeters_visible(ListLayout.Config["show.dBMeters"]) + self._set_index_visible(ListLayout.Config["show.indexColumn"]) self._set_selection_mode(ListLayout.Config["selectionMode"]) self._set_auto_continue(ListLayout.Config["autoContinue"]) @@ -178,6 +185,7 @@ def retranslate(self): self.show_accurate_action.setText( translate("ListLayout", "Show accurate time") ) + self.show_index_action.setText(translate("ListLayout", "Show index column")) self.auto_continue_action.setText( translate("ListLayout", "Auto-select next cue") ) @@ -307,6 +315,16 @@ def _set_dbmeters_visible(self, visible): def _get_dbmeters_visible(self): return self.show_dbmeter_action.isChecked() + @index_column_visible.get + def _get_index_column_visible(self): + return not self.show_index_action.isChecked() + + @index_column_visible.set + def _set_index_visible(self, visible): + self.show_index_action.setChecked(visible) + self._view.listView.setColumnHidden(1, not visible) + self._view.listView.updateHeadersSizes() + @selection_mode.set def _set_selection_mode(self, enable): self.selection_mode_action.setChecked(enable) diff --git a/lisp/plugins/list_layout/list_view.py b/lisp/plugins/list_layout/list_view.py index 40fd0aeee..4025c8d7a 100644 --- a/lisp/plugins/list_layout/list_view.py +++ b/lisp/plugins/list_layout/list_view.py @@ -207,7 +207,7 @@ def mousePressEvent(self, event): def resizeEvent(self, event): super().resizeEvent(event) - self.__resizeHeaders() + self.updateHeadersSizes() def standbyIndex(self): return self.indexOfTopLevelItem(self.currentItem()) @@ -216,7 +216,7 @@ def setStandbyIndex(self, newIndex): if 0 <= newIndex < self.topLevelItemCount(): self.setCurrentItem(self.topLevelItem(newIndex)) - def __resizeHeaders(self): + def updateHeadersSizes(self): """Some hack to have "stretchable" columns with a minimum size NOTE: this currently works properly with only one "stretchable" column @@ -279,7 +279,7 @@ def __cuePropChanged(self, cue, property_name, _): if property_name == "stylesheet": self.__updateItemStyle(self.topLevelItem(cue.index)) if property_name == "name": - QTimer.singleShot(1, self.__resizeHeaders) + QTimer.singleShot(1, self.updateHeadersSizes) def __cueAdded(self, cue): item = CueTreeWidgetItem(cue) diff --git a/lisp/plugins/list_layout/list_widgets.py b/lisp/plugins/list_layout/list_widgets.py index c38708317..7618f9f05 100644 --- a/lisp/plugins/list_layout/list_widgets.py +++ b/lisp/plugins/list_layout/list_widgets.py @@ -14,8 +14,9 @@ # # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . +import time -from PyQt5.QtCore import QRect, Qt +from PyQt5.QtCore import QRect, Qt, QSize from PyQt5.QtGui import ( QPainter, QBrush, @@ -40,9 +41,18 @@ def __init__(self, item, *args, **kwargs): self.setAttribute(Qt.WA_TranslucentBackground) self.setAlignment(Qt.AlignCenter) + # This value will be used to provide some spacing for the widget + # the value depends on the current font. + self.sizeIncrement = QSize( + self.fontMetrics().size(Qt.TextSingleLine, "00").width(), 0 + ) + item.cue.changed("index").connect(self.__update, Connection.QtQueued) self.__update(item.cue.index) + def sizeHint(self): + return super().sizeHint() + self.sizeIncrement + def __update(self, newIndex): self.setText(str(newIndex + 1)) diff --git a/lisp/plugins/midi/midi.py b/lisp/plugins/midi/midi.py index d96085bd0..9dad4763d 100644 --- a/lisp/plugins/midi/midi.py +++ b/lisp/plugins/midi/midi.py @@ -110,7 +110,7 @@ def _on_port_removed(self): logger.info( translate( "MIDIInfo", "MIDI port disconnected: '{}'" - ).format(self.__input.port_name) + ).format(self.__input.port_name()) ) self.__input.close() @@ -119,7 +119,7 @@ def _on_port_removed(self): logger.info( translate( "MIDIInfo", "MIDI port disconnected: '{}'" - ).format(self.__output.port_name) + ).format(self.__output.port_name()) ) self.__input.close() diff --git a/lisp/plugins/midi/midi_cue.py b/lisp/plugins/midi/midi_cue.py index 561dbea8f..329170398 100644 --- a/lisp/plugins/midi/midi_cue.py +++ b/lisp/plugins/midi/midi_cue.py @@ -23,7 +23,8 @@ from lisp.core.properties import Property from lisp.cues.cue import Cue from lisp.plugins import get_plugin -from lisp.plugins.midi.midi_utils import midi_str_to_dict, midi_dict_to_str +from lisp.plugins.midi.midi_utils import midi_str_to_dict, midi_dict_to_str, \ + midi_from_str from lisp.plugins.midi.widgets import MIDIMessageEdit from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.pages import SettingsPage @@ -44,7 +45,7 @@ def __init__(self, **kwargs): def __start__(self, fade=False): if self.message: - self.__midi.output.send_from_str(self.message) + self.__midi.output.send(midi_from_str(self.message)) return False diff --git a/lisp/plugins/midi/midi_io.py b/lisp/plugins/midi/midi_io.py index 87679bbb5..b64432796 100644 --- a/lisp/plugins/midi/midi_io.py +++ b/lisp/plugins/midi/midi_io.py @@ -38,16 +38,15 @@ def backend(self): return self._backend @property + def port(self): + return self._port + def port_name(self, real=True): if real and self._port is not None: return self._port.name else: return self._port_name - @property - def port(self): - return self._port - def change_port(self, port_name): self._port_name = port_name self.close() diff --git a/lisp/plugins/midi/midi_settings.py b/lisp/plugins/midi/midi_settings.py index 1db98a423..aea7aaff8 100644 --- a/lisp/plugins/midi/midi_settings.py +++ b/lisp/plugins/midi/midi_settings.py @@ -23,8 +23,10 @@ QGridLayout, QLabel, QCheckBox, + QSpacerItem, ) +from lisp.plugins import get_plugin from lisp.plugins.midi.midi_utils import midi_input_names, midi_output_names from lisp.ui.icons import IconTheme from lisp.ui.settings.pages import SettingsPage @@ -33,6 +35,7 @@ class MIDISettings(SettingsPage): Name = QT_TRANSLATE_NOOP("SettingsPageName", "MIDI settings") + STATUS_SYMBOLS = {True: "✓", False: "×"} # U+2713, U+00D7 def __init__(self, **kwargs): super().__init__(**kwargs) @@ -45,15 +48,28 @@ def __init__(self, **kwargs): # Input port self.inputLabel = QLabel(self.portsGroup) - self.portsGroup.layout().addWidget(self.inputLabel, 0, 0) + self.portsGroup.layout().addWidget(self.inputLabel, 0, 0, 2, 1) + self.inputCombo = QComboBox(self.portsGroup) self.portsGroup.layout().addWidget(self.inputCombo, 0, 1) + self.inputStatus = QLabel(self.portsGroup) + self.inputStatus.setDisabled(True) + self.portsGroup.layout().addWidget(self.inputStatus, 1, 1) + + # Spacer + self.portsGroup.layout().addItem(QSpacerItem(0, 30), 2, 0, 2, 1) + # Output port self.outputLabel = QLabel(self.portsGroup) - self.portsGroup.layout().addWidget(self.outputLabel, 1, 0) + self.portsGroup.layout().addWidget(self.outputLabel, 3, 0, 2, 1) + self.outputCombo = QComboBox(self.portsGroup) - self.portsGroup.layout().addWidget(self.outputCombo, 1, 1) + self.portsGroup.layout().addWidget(self.outputCombo, 3, 1) + + self.outputStatus = QLabel(self.portsGroup) + self.outputStatus.setDisabled(True) + self.portsGroup.layout().addWidget(self.outputStatus, 4, 1) self.portsGroup.layout().setColumnStretch(0, 2) self.portsGroup.layout().setColumnStretch(1, 3) @@ -68,21 +84,34 @@ def __init__(self, **kwargs): self.retranslateUi() try: + plugin = get_plugin("Midi") self._loadDevices() + + inputStatus = MIDISettings.STATUS_SYMBOLS.get( + plugin.input.is_open(), "" + ) + self.inputStatus.setText( + "[{}] {}".format(inputStatus, plugin.input.port_name()) + ) + + outputStatus = MIDISettings.STATUS_SYMBOLS.get( + plugin.output.is_open(), "" + ) + self.outputStatus.setText( + "[{}] {}".format(outputStatus, plugin.output.port_name()) + ) except Exception: self.setEnabled(False) def retranslateUi(self): - self.portsGroup.setTitle( - translate("MIDISettings", "MIDI default devices") - ) + self.portsGroup.setTitle(translate("MIDISettings", "MIDI devices")) self.inputLabel.setText(translate("MIDISettings", "Input")) self.outputLabel.setText(translate("MIDISettings", "Output")) self.miscGroup.setTitle(translate("MIDISettings", "Misc options")) self.nameMatchCheckBox.setText( translate( - "MIDISettings", "Try to connect ignoring the device/port id" + "MIDISettings", "Try to connect using only device/port name" ) ) diff --git a/lisp/plugins/midi/port_monitor.py b/lisp/plugins/midi/port_monitor.py index 089794a52..f5fd00556 100644 --- a/lisp/plugins/midi/port_monitor.py +++ b/lisp/plugins/midi/port_monitor.py @@ -93,6 +93,7 @@ def __init__(self): ) def __loop(self): + logger.debug("Started monitoring ALSA MIDI ports") while True: for event in self.__seq.receive_events(timeout=-1, maxevents=1): if event.type == alsaseq.SEQ_EVENT_PORT_EXIT: diff --git a/lisp/plugins/network/discovery.py b/lisp/plugins/network/discovery.py index 0c6ca82e7..7a8d62557 100644 --- a/lisp/plugins/network/discovery.py +++ b/lisp/plugins/network/discovery.py @@ -25,7 +25,7 @@ class Announcer(Thread): """Allow other hosts on a network to discover a specific "service" via UPD. - While this class is called "Announcer" it act passively, only responding + While this class is called "Announcer" it acts passively, only responding to requests, it does not advertise itself on the network. """ @@ -39,8 +39,9 @@ def run(self): self.server.serve_forever() def stop(self): - self.server.shutdown() - self.join() + if self.is_alive(): + self.server.shutdown() + self.join() class AnnouncerUDPHandler(socketserver.BaseRequestHandler): @@ -55,8 +56,8 @@ def handle(self): class Discoverer(Thread): """Actively search for an announced "service" on a network. - While it can run a continue scan, by default it only does a small number - of attempts to find active (announced) host on the network. + By default, does only a few attempts to find active (announced) + host on the network. """ def __init__( @@ -69,9 +70,9 @@ def __init__( self.max_attempts = max_attempts self.timeout = timeout - # Emitted when every time a new host is discovered + # Emitted when a new host has been discovered (ip, fully-qualified-name) self.discovered = Signal() - # Emitted when the discovery is ended + # Emitted on discovery ended self.ended = Signal() self._stop_flag = False @@ -91,25 +92,26 @@ def _send_beacon(self, socket): def run(self): self._stop_flag = False - with self._new_socket() as socket: - self._send_beacon(socket) + with self._new_socket() as sock: + self._send_beacon(sock) attempts = 1 while not self._stop_flag: try: - data, address = socket.recvfrom(1024) + data, address = sock.recvfrom(1024) host = address[0] # Check if the response is valid and if the host # has already replied if data == self.bytes_magic and host not in self._cache: + fqdn = socket.getfqdn(host) self._cache.add(host) - self.discovered.emit(host) + self.discovered.emit(host, fqdn) except OSError: if attempts >= self.max_attempts: break attempts += 1 - self._send_beacon(socket) + self._send_beacon(sock) self.ended.emit() diff --git a/lisp/plugins/network/discovery_dialogs.py b/lisp/plugins/network/discovery_dialogs.py index 74982ef58..e575f831f 100644 --- a/lisp/plugins/network/discovery_dialogs.py +++ b/lisp/plugins/network/discovery_dialogs.py @@ -25,40 +25,44 @@ QPushButton, QInputDialog, QListWidgetItem, + QGridLayout, + QLabel, ) from lisp.core.signal import Connection from lisp.plugins.network.discovery import Discoverer from lisp.ui.ui_utils import translate -from lisp.ui.widgets.qprogresswheel import QProgressWheel +from lisp.ui.widgets.qwaitingspinner import QWaitingSpinner class HostDiscoveryDialog(QDialog): def __init__(self, port, magic, **kwargs): super().__init__(**kwargs) self.setWindowModality(Qt.WindowModal) - self.setLayout(QVBoxLayout()) - self.setMaximumSize(300, 200) + self.setLayout(QGridLayout()) self.setMinimumSize(300, 200) - self.resize(300, 200) + self.resize(500, 200) + + self.hintLabel = QLabel(self) + self.layout().addWidget(self.hintLabel, 0, 0, 1, 2) self.listWidget = QListWidget(self) self.listWidget.setAlternatingRowColors(True) self.listWidget.setSelectionMode(self.listWidget.MultiSelection) - self.layout().addWidget(self.listWidget) + self.layout().addWidget(self.listWidget, 1, 0, 1, 2) - self.progressWheel = QProgressWheel() - - self.progressItem = QListWidgetItem() - self.progressItem.setFlags(Qt.NoItemFlags) - self.progressItem.setSizeHint(QSize(30, 30)) - self.listWidget.addItem(self.progressItem) - self.listWidget.setItemWidget(self.progressItem, self.progressWheel) + self.progressSpinner = QWaitingSpinner( + parent=self, centerOnParent=False + ) + self.progressSpinner.setInnerRadius(8) + self.progressSpinner.setLineWidth(4) + self.progressSpinner.setLineLength(4) + self.layout().addWidget(self.progressSpinner, 2, 0) self.dialogButton = QDialogButtonBox(self) self.dialogButton.setStandardButtons(self.dialogButton.Ok) self.dialogButton.accepted.connect(self.accept) - self.layout().addWidget(self.dialogButton) + self.layout().addWidget(self.dialogButton, 2, 1) self.retranslateUi() @@ -69,6 +73,9 @@ def __init__(self, port, magic, **kwargs): self._discoverer.ended.connect(self._search_ended, Connection.QtQueued) def retranslateUi(self): + self.hintLabel.setText( + translate("NetworkDiscovery", "Select the hosts you want to add") + ) self.setWindowTitle(translate("NetworkDiscovery", "Host discovery")) def accept(self): @@ -79,22 +86,31 @@ def reject(self): self._discoverer.stop() return super().reject() - def exec_(self): - self.layout().activate() - self.progressWheel.startAnimation() - + def exec(self): + self.progressSpinner.start() self._discoverer.start() + return super().exec() def hosts(self): - return [item.text() for item in self.listWidget.selectedItems()] - - def _host_discovered(self, host): - self.listWidget.insertItem(self.listWidget.count() - 1, host) + return [ + (item.data(Qt.UserRole), item.text()) + for item in self.listWidget.selectedItems() + ] + + def _host_discovered(self, host, fqdn): + if fqdn != host: + item_text = "{} - {}".format(fqdn, host) + else: + item_text = host + + item = QListWidgetItem(item_text) + item.setData(Qt.UserRole, host) + item.setSizeHint(QSize(100, 30)) + self.listWidget.addItem(item) def _search_ended(self): - self.listWidget.takeItem(self.listWidget.count() - 1) - self.progressWheel.stopAnimation() + self.progressSpinner.stop() class HostManagementDialog(QDialog): @@ -105,13 +121,11 @@ def __init__(self, hosts, discovery_port, discovery_magic, **kwargs): self.setWindowModality(Qt.WindowModal) self.setLayout(QHBoxLayout()) - self.setMaximumSize(500, 200) self.setMinimumSize(500, 200) self.resize(500, 200) self.listWidget = QListWidget(self) self.listWidget.setAlternatingRowColors(True) - self.listWidget.addItems(hosts) self.layout().addWidget(self.listWidget) self.buttonsLayout = QVBoxLayout() @@ -144,6 +158,7 @@ def __init__(self, hosts, discovery_port, discovery_magic, **kwargs): self.layout().setStretch(1, 1) self.retranslateUi() + self.addHosts(hosts) def retranslateUi(self): self.setWindowTitle(translate("NetworkDiscovery", "Manage hosts")) @@ -167,12 +182,22 @@ def addHostDialog(self): translate("NetworkDiscovery", "Host IP"), ) - if ok: - self.addHost(host) + if host and ok: + self.addHost(host, host) + + def addHosts(self, hosts): + for host in hosts: + if isinstance(host, str): + self.addHost(host, host) + else: + self.addHost(*host) - def addHost(self, host): - if not self.listWidget.findItems(host, Qt.MatchExactly): - self.listWidget.addItem(host) + def addHost(self, hostname, display_name): + if not self.listWidget.findItems(hostname, Qt.MatchEndsWith): + item = QListWidgetItem(display_name) + item.setData(Qt.UserRole, hostname) + item.setSizeHint(QSize(100, 30)) + self.listWidget.addItem(item) def discoverHosts(self): dialog = HostDiscoveryDialog( @@ -180,8 +205,8 @@ def discoverHosts(self): ) if dialog.exec() == dialog.Accepted: - for host in dialog.hosts(): - self.addHost(host) + for hostname, display_name in dialog.hosts(): + self.addHost(hostname, display_name) def removeSelectedHosts(self): for index in self.listWidget.selectedIndexes(): @@ -193,6 +218,7 @@ def removeAllHosts(self): def hosts(self): hosts = [] for index in range(self.listWidget.count()): - hosts.append(self.listWidget.item(index).text()) + item = self.listWidget.item(index) + hosts.append((item.data(Qt.UserRole), item.text())) return hosts diff --git a/lisp/plugins/synchronizer/synchronizer.py b/lisp/plugins/synchronizer/synchronizer.py index ff270583d..68546b50f 100644 --- a/lisp/plugins/synchronizer/synchronizer.py +++ b/lisp/plugins/synchronizer/synchronizer.py @@ -34,7 +34,7 @@ class Synchronizer(Plugin): Name = "Synchronizer" Authors = ("Francesco Ceruti",) OptDepends = ("Network",) - Description = "Keep multiple sessions, on a network, synchronized" + Description = "Keep multiple sessions, in a network, synchronized" def __init__(self, app): super().__init__(app) @@ -76,7 +76,7 @@ def manage_peers(self): self.peers = manager.hosts() def show_ip(self): - ip = translate("Synchronizer", "Your IP is:") + " " + str(get_lan_ip()) + ip = translate("Synchronizer", "Your IP is: {}".format(get_lan_ip())) QMessageBox.information(self.app.window, " ", ip) def _url(self, host, path): @@ -88,14 +88,14 @@ def _url(self, host, path): ) def _cue_executes(self, cue): - for peer in self.peers: + for peer, _ in self.peers: requests.post( self._url(peer, "/cues/{}/action".format(cue.id)), json={"action": CueAction.Default.value}, ) def _all_executed(self, action): - for peer in self.peers: + for peer, _ in self.peers: requests.post( self._url(peer, "/layout/action"), json={"action": action.value} ) diff --git a/lisp/ui/about.py b/lisp/ui/about.py index 7b370d2ed..35d8c9a61 100644 --- a/lisp/ui/about.py +++ b/lisp/ui/about.py @@ -86,9 +86,9 @@ def __init__(self, *args, **kwargs): self.setWindowModality(QtCore.Qt.ApplicationModal) self.setWindowTitle(translate("About", "About Linux Show Player")) - self.setMaximumSize(500, 420) - self.setMinimumSize(500, 420) - self.resize(500, 420) + self.setMaximumSize(550, 420) + self.setMinimumSize(550, 420) + self.resize(550, 420) self.setLayout(QGridLayout()) diff --git a/lisp/ui/settings/app_pages/cue.py b/lisp/ui/settings/app_pages/cue.py index 6b830b1cc..370a470be 100644 --- a/lisp/ui/settings/app_pages/cue.py +++ b/lisp/ui/settings/app_pages/cue.py @@ -16,7 +16,7 @@ # along with Linux Show Player. If not, see . from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt -from PyQt5.QtWidgets import QVBoxLayout, QGroupBox +from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QLabel from lisp.ui.settings.pages import SettingsPage from lisp.ui.ui_utils import translate @@ -31,27 +31,51 @@ def __init__(self, **kwargs): self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) - # Interrupt + # Interrupt fade settings self.interruptGroup = QGroupBox(self) self.interruptGroup.setLayout(QVBoxLayout()) self.layout().addWidget(self.interruptGroup) + self.interruptHelpText = QLabel(self.interruptGroup) + self.interruptHelpText.setWordWrap(True) + self.interruptGroup.layout().addWidget(self.interruptHelpText) + self.interruptFadeEdit = FadeEdit(self.interruptGroup) self.interruptGroup.layout().addWidget(self.interruptFadeEdit) - # Action - self.actionGroup = QGroupBox(self) - self.actionGroup.setLayout(QVBoxLayout()) - self.layout().addWidget(self.actionGroup) + # Fade action defaults + self.fadeActionsDefaultsGroup = QGroupBox(self) + self.fadeActionsDefaultsGroup.setLayout(QVBoxLayout()) + self.layout().addWidget(self.fadeActionsDefaultsGroup) + + self.fadeActionDefaultsHelpText = QLabel(self.fadeActionsDefaultsGroup) + self.fadeActionDefaultsHelpText.setWordWrap(True) + self.fadeActionsDefaultsGroup.layout().addWidget( + self.fadeActionDefaultsHelpText + ) - self.fadeActionEdit = FadeEdit(self.actionGroup) - self.actionGroup.layout().addWidget(self.fadeActionEdit) + self.fadeActionEdit = FadeEdit(self.fadeActionsDefaultsGroup) + self.fadeActionsDefaultsGroup.layout().addWidget(self.fadeActionEdit) self.retranslateUi() def retranslateUi(self): - self.interruptGroup.setTitle(translate("CueSettings", "Interrupt fade")) - self.actionGroup.setTitle(translate("CueSettings", "Fade actions")) + self.interruptGroup.setTitle( + translate("CueSettings", "Interrupt action fade") + ) + self.interruptHelpText.setText( + translate("CueSettings", "Used globally when interrupting cues",) + ) + self.fadeActionsDefaultsGroup.setTitle( + translate("CueSettings", "Fade actions default value") + ) + self.fadeActionDefaultsHelpText.setText( + translate( + "CueSettings", + "Used for fade-in and fade-out actions, by cues where fade " + "duration is 0.", + ) + ) def getSettings(self): return { diff --git a/lisp/ui/settings/app_pages/general.py b/lisp/ui/settings/app_pages/general.py index 46f56d2b7..a4101a648 100644 --- a/lisp/ui/settings/app_pages/general.py +++ b/lisp/ui/settings/app_pages/general.py @@ -26,7 +26,7 @@ QHBoxLayout, ) -from lisp import layout +from lisp.layout import get_layouts from lisp.ui.icons import icon_themes_names from lisp.ui.settings.pages import SettingsPage from lisp.ui.themes import themes_names @@ -44,20 +44,28 @@ def __init__(self, **kwargs): # Startup "layout" self.layoutGroup = QGroupBox(self) - self.layoutGroup.setLayout(QVBoxLayout()) + self.layoutGroup.setLayout(QGridLayout()) self.layout().addWidget(self.layoutGroup) self.startupDialogCheck = QCheckBox(self.layoutGroup) - self.layoutGroup.layout().addWidget(self.startupDialogCheck) + self.layoutGroup.layout().addWidget(self.startupDialogCheck, 0, 0, 1, 2) + + self.layoutLabel = QLabel(self.layoutGroup) + self.layoutGroup.layout().addWidget(self.layoutLabel, 1, 0) self.layoutCombo = QComboBox(self.layoutGroup) - for lay in layout.get_layouts(): - self.layoutCombo.addItem(lay.NAME, lay.__name__) - self.layoutGroup.layout().addWidget(self.layoutCombo) + self.layoutGroup.layout().addWidget(self.layoutCombo, 1, 1) + # Get available layouts + for layout in get_layouts(): + self.layoutCombo.addItem(layout.NAME, layout.__name__) + # Enable/Disable layout selection self.startupDialogCheck.stateChanged.connect( self.layoutCombo.setDisabled ) + self.startupDialogCheck.stateChanged.connect( + self.layoutLabel.setDisabled + ) # Application theme self.themeGroup = QGroupBox(self) @@ -96,7 +104,10 @@ def retranslateUi(self): translate("AppGeneralSettings", "Default layout") ) self.startupDialogCheck.setText( - translate("AppGeneralSettings", "Enable startup layout selector") + translate("AppGeneralSettings", "Show layout selection at startup") + ) + self.layoutLabel.setText( + translate("AppGeneralSettings", "Use layout at startup:") ) self.themeGroup.setTitle( translate("AppGeneralSettings", "Application themes") @@ -106,7 +117,7 @@ def retranslateUi(self): self.localeGroup.setTitle( translate( - "AppGeneralSettings", "Application language (require restart)" + "AppGeneralSettings", "Application language (require a restart)" ) ) self.localeLabel.setText(translate("AppGeneralSettings", "Language:")) diff --git a/lisp/ui/settings/app_pages/layouts.py b/lisp/ui/settings/app_pages/layouts.py index 22ef7085a..27c0d0328 100644 --- a/lisp/ui/settings/app_pages/layouts.py +++ b/lisp/ui/settings/app_pages/layouts.py @@ -49,11 +49,19 @@ def __init__(self, **kwargs): self.retranslateUi() def retranslateUi(self): - self.useFadeGroup.setTitle(translate("ListLayout", "Use fade")) - self.stopAllFade.setText(translate("ListLayout", "Stop all")) - self.pauseAllFade.setText(translate("ListLayout", "Pause all")) - self.resumeAllFade.setText(translate("ListLayout", "Resume all")) - self.interruptAllFade.setText(translate("ListLayout", "Interrupt all")) + self.useFadeGroup.setTitle(translate("ListLayout", "Layout actions")) + self.stopAllFade.setText( + translate("ListLayout", "Fade out when stopping all cues") + ) + self.interruptAllFade.setText( + translate("ListLayout", "Fade out when interrupting all cues") + ) + self.pauseAllFade.setText( + translate("ListLayout", "Fade out when pausing all cues") + ) + self.resumeAllFade.setText( + translate("ListLayout", "Fade in when resuming all cues") + ) def loadSettings(self, settings): self.stopAllFade.setChecked(settings["layout"]["stopAllFade"]) diff --git a/lisp/ui/themes/dark/theme.qss b/lisp/ui/themes/dark/theme.qss index 8bc7ac5ab..a10ab8213 100644 --- a/lisp/ui/themes/dark/theme.qss +++ b/lisp/ui/themes/dark/theme.qss @@ -29,7 +29,7 @@ */ QWidget { - color: white; + color: #ddd; background-color: #333; selection-background-color: #419BE6; selection-color: black; @@ -71,7 +71,7 @@ QToolTip { border: 1px solid transparent; border-radius: 5px; background-color: #1A1A1A; - color: white; + color: #ddd; padding: 2px; opacity: 200; } @@ -94,7 +94,7 @@ QMenuBar:item:pressed { QMenu { border: 1px solid #4A4A4A; - color: white; + color: #ddd; } QMenu:item { @@ -117,7 +117,7 @@ QAbstractItemView { /*paint-alternating-row-colors-for-empty-area: true;*/ background-color: #202020; border-radius: 3px; - color: white; + color: #ddd; } QTreeView::branch:hover, @@ -134,12 +134,11 @@ QLineEdit { padding: 2px; border: 1px solid #4A4A4A; border-radius: 3px; - color: white; + color: #ddd; } QGroupBox { - border: 1px solid #4A4A4A; - border-radius: 2px; + border-top: 2px solid #4A4A4A; margin-top: 2ex; /* NOT px */ padding-top: 1ex; /* NOT px */ } @@ -153,7 +152,7 @@ QGroupBox:title { } QAbstractScrollArea { - color: white; + color: #ddd; background-color: #202020; border: 1px solid #4A4A4A; border-radius: 3px; @@ -240,7 +239,7 @@ QHeaderView:section { QHeaderView:section, QTableView QTableCornerButton:section { background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #565656, stop: 0.1 #525252, stop: 0.5 #4e4e4e, stop: 0.9 #4a4a4a, stop: 1 #464646); - color: white; + color: #ddd; } QHeaderView:section:checked { @@ -255,7 +254,7 @@ QSizeGrip { QMainWindow:separator { background-color: #333; - color: white; + color: #ddd; padding-left: 4px; spacing: 2px; border: 1px dashed #3A3A3A; @@ -263,7 +262,7 @@ QMainWindow:separator { QMainWindow:separator:hover { background-color: QLinearGradient(x1:0, y1:0, x2:0, y2:1, stop:0 #58677b, stop:0.5 #419BE6 stop:1 #58677b); - color: white; + color: #ddd; padding-left: 4px; border: 1px solid #4A4A4A; spacing: 2px; @@ -302,7 +301,7 @@ QSplitter::handle:horizontal:disabled { } QPushButton { - color: white; + color: #ddd; background-color: QLinearGradient( x1: 0, y1: 1, x2: 0, y2: 0, stop: 0 #333, stop: 1 #444); border: 1px solid #202020; border-radius: 4px; @@ -356,7 +355,7 @@ QComboBox:drop-down { QComboBox:down-arrow { image: url(:/assets/down-arrow.png); - height: 12px; + height: 16px; width: 12px; } @@ -367,7 +366,7 @@ QAbstractSpinBox { border: 1px solid #4A4A4A; background-color: #202020; border-radius: 3px; - color: white; + color: #ddd; } QAbstractSpinBox:up-button { @@ -412,7 +411,7 @@ QLabel { } QTabBar::tab { - color: white; + color: #ddd; border: 1px solid #444; background-color: #333; padding-left: 10px; @@ -466,7 +465,7 @@ QTabBar QToolButton { } QDockWidget { - color: white; + color: #ddd; titlebar-close-icon: url(:/assets/close.png); titlebar-normal-icon: url(:/assets/undock.png); } @@ -656,12 +655,15 @@ QRadioButton:indicator:checked:disabled { ****************** */ +#CartTabBar { + font-size: 13pt; + font-weight: bold; +} + #CartTabBar::tab { height: 35px; min-width: 100px; - font-size: 13pt; - font-weight: bold; - color: white; + color: #ddd; } #CartTabBar::tab:selected { diff --git a/lisp/ui/widgets/fades.py b/lisp/ui/widgets/fades.py index ff624ab8d..36401b0f6 100644 --- a/lisp/ui/widgets/fades.py +++ b/lisp/ui/widgets/fades.py @@ -75,6 +75,7 @@ class FadeEdit(QWidget): def __init__(self, *args, mode=FadeComboBox.Mode.FadeOut, **kwargs): super().__init__(*args, **kwargs) self.setLayout(QGridLayout()) + self.layout().setContentsMargins(0, 0, 0, 0) self.fadeDurationLabel = QLabel(self) self.layout().addWidget(self.fadeDurationLabel, 0, 0) diff --git a/lisp/ui/widgets/qprogresswheel.py b/lisp/ui/widgets/qprogresswheel.py deleted file mode 100644 index cd49eed36..000000000 --- a/lisp/ui/widgets/qprogresswheel.py +++ /dev/null @@ -1,122 +0,0 @@ -# This file is part of Linux Show Player -# -# This file contains a PyQt5 version of QProgressIndicator.cpp from -# https://github.com/mojocorp/QProgressIndicator -# -# Copyright 2018 Francesco Ceruti -# -# Linux Show Player 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. -# -# Linux Show Player 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 Linux Show Player. If not, see . - - -from PyQt5.QtCore import Qt, QSize, QRectF -from PyQt5.QtGui import QPainter -from PyQt5.QtWidgets import QWidget, QSizePolicy - - -class QProgressWheel(QWidget): - def __init__(self, *args): - super().__init__(*args) - self._angle = 0 - self._delay = 100 - self._displayedWhenStopped = False - self._timerId = -1 - - self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - self.setFocusPolicy(Qt.NoFocus) - - @property - def delay(self): - return self._delay - - @delay.setter - def delay(self, delay): - if self._timerId != -1: - self.killTimer(self._timerId) - - self._delay = delay - - if self._timerId != -1: - self._timerId = self.startTimer(self._delay) - - @property - def displayedWhenStopped(self): - return self._displayedWhenStopped - - @displayedWhenStopped.setter - def displayedWhenStopped(self, display): - self._displayedWhenStopped = display - self.update() - - def isAnimated(self): - return self._timerId != -1 - - def startAnimation(self): - self._angle = 0 - - if self._timerId == -1: - self._timerId = self.startTimer(self._delay) - - def stopAnimation(self): - if self._timerId != -1: - self.killTimer(self._timerId) - - self._timerId = -1 - self.update() - - def sizeHint(self): - return QSize(20, 20) - - def timerEvent(self, event): - self._angle = (self._angle + 30) % 360 - self.update() - - def paintEvent(self, event): - super().paintEvent(event) - - if not self._displayedWhenStopped and not self.isAnimated(): - return - - width = min(self.width(), self.height()) - - painter = QPainter(self) - painter.setRenderHint(QPainter.Antialiasing) - - outerRadius = (width - 1) * 0.5 - innerRadius = (width - 1) * 0.5 * 0.38 - - capsuleHeight = outerRadius - innerRadius - capsuleWidth = capsuleHeight * (0.23 if width > 32 else 0.35) - capsuleRadius = capsuleWidth / 2 - - # Use the pen (text) color to draw the "capsules" - color = painter.pen().color() - painter.setPen(Qt.NoPen) - painter.translate(self.rect().center()) - painter.rotate(self._angle) - - for i in range(0, 12): - color.setAlphaF(1.0 - (i / 12.0) if self.isAnimated() else 0.2) - painter.setBrush(color) - painter.rotate(-30) - - painter.drawRoundedRect( - QRectF( - capsuleWidth * -0.5, - (innerRadius + capsuleHeight) * -1, - capsuleWidth, - capsuleHeight, - ), - capsuleRadius, - capsuleRadius, - ) diff --git a/lisp/ui/widgets/qwaitingspinner.py b/lisp/ui/widgets/qwaitingspinner.py new file mode 100644 index 000000000..f3ea23bb8 --- /dev/null +++ b/lisp/ui/widgets/qwaitingspinner.py @@ -0,0 +1,252 @@ +""" +The MIT License (MIT) + +Copyright (c) 2012-2014 Alexander Turkin +Copyright (c) 2014 William Hallatt +Copyright (c) 2015 Jacob Dawid +Copyright (c) 2016 Luca Weiss +Copyright (c) 2020 Francesco Ceruti + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import math + +from PyQt5.QtCore import Qt, QTimer, QRect +from PyQt5.QtGui import QPainter, QColor +from PyQt5.QtWidgets import QWidget + + +class QWaitingSpinner(QWidget): + def __init__( + self, + parent=None, + centerOnParent=Qt.AlignCenter, + disableParentWhenSpinning=False, + modality=Qt.NonModal, + ): + super().__init__(parent) + self.setWindowModality(modality) + self.setAttribute(Qt.WA_TranslucentBackground) + + self._centerOnParent = centerOnParent + self._disableParentWhenSpinning = disableParentWhenSpinning + + self._color = Qt.gray + self._roundness = 100.0 + self._minimumTrailOpacity = math.pi + self._trailFadePercentage = 80.0 + self._revolutionsPerSecond = math.pi / 2 + self._numberOfLines = 10 + self._lineLength = 5 + self._lineWidth = 5 + self._innerRadius = 10 + self._currentCounter = 0 + + self._timer = QTimer(self) + self._timer.timeout.connect(self.rotate) + + self.updateSize() + self.updateTimer() + self.hide() + + def paintEvent(self, QPaintEvent): + self.updatePosition() + linesRect = QRect( + 0, -self._lineWidth / 2, self._lineLength, self._lineWidth + ) + + painter = QPainter(self) + painter.fillRect(self.rect(), Qt.transparent) + painter.setRenderHint(QPainter.Antialiasing, True) + painter.setPen(Qt.NoPen) + painter.translate( + self._innerRadius + self._lineLength, + self._innerRadius + self._lineLength, + ) + + for i in range(self._numberOfLines): + rotateAngle = i / self._numberOfLines * 360 + distance = self.lineCountDistanceFromPrimary( + i, self._currentCounter, self._numberOfLines + ) + color = self.currentLineColor( + distance, + self._numberOfLines, + self._trailFadePercentage, + self._minimumTrailOpacity, + self._color, + ) + + painter.save() + painter.rotate(rotateAngle) + painter.translate(self._innerRadius, 0) + painter.setBrush(color) + painter.drawRoundedRect( + linesRect, self._roundness, self._roundness, Qt.RelativeSize, + ) + painter.restore() + + def start(self): + self.updatePosition() + self.show() + + if self.parentWidget and self._disableParentWhenSpinning: + self.parentWidget().setEnabled(False) + + if not self._timer.isActive(): + self._timer.start() + self._currentCounter = 0 + + def stop(self): + self.hide() + + if self.parentWidget() and self._disableParentWhenSpinning: + self.parentWidget().setEnabled(True) + + if self._timer.isActive(): + self._timer.stop() + self._currentCounter = 0 + + def setNumberOfLines(self, lines): + self._numberOfLines = lines + self._currentCounter = 0 + self.updateTimer() + + def setLineLength(self, length): + self._lineLength = length + self.updateSize() + + def setLineWidth(self, width): + self._lineWidth = width + self.updateSize() + + def setInnerRadius(self, radius): + self._innerRadius = radius + self.updateSize() + + def color(self): + return self._color + + def roundness(self): + return self._roundness + + def minimumTrailOpacity(self): + return self._minimumTrailOpacity + + def trailFadePercentage(self): + return self._trailFadePercentage + + def revolutionsPersSecond(self): + return self._revolutionsPerSecond + + def numberOfLines(self): + return self._numberOfLines + + def lineLength(self): + return self._lineLength + + def lineWidth(self): + return self._lineWidth + + def innerRadius(self): + return self._innerRadius + + def isSpinning(self): + return self._timer.isActive() + + def setRoundness(self, roundness): + self._roundness = max(0.0, min(100.0, roundness)) + + def setColor(self, color=Qt.gray): + self._color = QColor(color) + + def setRevolutionsPerSecond(self, revolutionsPerSecond): + self._revolutionsPerSecond = revolutionsPerSecond + self.updateTimer() + + def setTrailFadePercentage(self, trail): + self._trailFadePercentage = trail + + def setMinimumTrailOpacity(self, minimumTrailOpacity): + self._minimumTrailOpacity = minimumTrailOpacity + + def rotate(self): + self._currentCounter += 1 + if self._currentCounter >= self._numberOfLines: + self._currentCounter = 0 + + self.update() + + def updateSize(self): + size = (self._innerRadius + self._lineLength) * 2 + self.setFixedSize(size, size) + + def updateTimer(self): + self._timer.setInterval( + 1000 / (self._numberOfLines * self._revolutionsPerSecond) + ) + + def updatePosition(self): + if self.parentWidget() and self._centerOnParent: + x = self.x() + y = self.y() + + if self._centerOnParent & Qt.AlignHCenter: + x = self.parentWidget().width() / 2 - self.width() / 2 + if self._centerOnParent & Qt.AlignVCenter: + y = self.parentWidget().height() / 2 - self.height() / 2 + + self.move(x, y) + + def lineCountDistanceFromPrimary(self, current, primary, totalNrOfLines): + distance = primary - current + if distance < 0: + distance += totalNrOfLines + + return distance + + def currentLineColor( + self, + countDistance, + totalNrOfLines, + trailFadePerc, + minOpacity, + colorInput, + ): + if countDistance == 0: + return colorInput + + color = QColor(colorInput) + minAlphaF = minOpacity / 100.0 + distanceThreshold = math.ceil( + (totalNrOfLines - 1) * trailFadePerc / 100.0 + ) + + if countDistance > distanceThreshold: + color.setAlphaF(minAlphaF) + else: + alphaDiff = color.alphaF() - minAlphaF + gradient = alphaDiff / (distanceThreshold + 1) + resultAlpha = color.alphaF() - gradient * countDistance + # If alpha is out of bounds, clip it. + resultAlpha = min(1.0, max(0.0, resultAlpha)) + color.setAlphaF(resultAlpha) + + return color diff --git a/scripts/Flatpak/pipenv_flatpak.py b/scripts/Flatpak/pipenv_flatpak.py index a3dc323ec..91c344ac6 100644 --- a/scripts/Flatpak/pipenv_flatpak.py +++ b/scripts/Flatpak/pipenv_flatpak.py @@ -112,6 +112,10 @@ def get_locked_packages(lock_file): # Get the required packages packages = [] for name, info in lock_content.get("default", {}).items(): + if info.get("editable", False): + # Skip packages with an editable path (usually git) + continue + packages.append( { "name": name, From caa989ae56696bcdffb2f631d37a306880eb1a91 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Tue, 24 Mar 2020 23:18:03 +0100 Subject: [PATCH 221/333] Refresh port status every 2 seconds in the midi settings widget --- lisp/plugins/list_layout/layout.py | 4 ++- lisp/plugins/midi/midi_cue.py | 7 +++-- lisp/plugins/midi/midi_settings.py | 47 ++++++++++++++++++++---------- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/lisp/plugins/list_layout/layout.py b/lisp/plugins/list_layout/layout.py index e78d99cf3..b8f52278e 100644 --- a/lisp/plugins/list_layout/layout.py +++ b/lisp/plugins/list_layout/layout.py @@ -185,7 +185,9 @@ def retranslate(self): self.show_accurate_action.setText( translate("ListLayout", "Show accurate time") ) - self.show_index_action.setText(translate("ListLayout", "Show index column")) + self.show_index_action.setText( + translate("ListLayout", "Show index column") + ) self.auto_continue_action.setText( translate("ListLayout", "Auto-select next cue") ) diff --git a/lisp/plugins/midi/midi_cue.py b/lisp/plugins/midi/midi_cue.py index 329170398..b3c08dffe 100644 --- a/lisp/plugins/midi/midi_cue.py +++ b/lisp/plugins/midi/midi_cue.py @@ -23,8 +23,11 @@ from lisp.core.properties import Property from lisp.cues.cue import Cue from lisp.plugins import get_plugin -from lisp.plugins.midi.midi_utils import midi_str_to_dict, midi_dict_to_str, \ - midi_from_str +from lisp.plugins.midi.midi_utils import ( + midi_str_to_dict, + midi_dict_to_str, + midi_from_str, +) from lisp.plugins.midi.widgets import MIDIMessageEdit from lisp.ui.settings.cue_settings import CueSettingsRegistry from lisp.ui.settings.pages import SettingsPage diff --git a/lisp/plugins/midi/midi_settings.py b/lisp/plugins/midi/midi_settings.py index aea7aaff8..55c8878a4 100644 --- a/lisp/plugins/midi/midi_settings.py +++ b/lisp/plugins/midi/midi_settings.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with Linux Show Player. If not, see . -from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP +from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP, QTimer from PyQt5.QtWidgets import ( QGroupBox, QVBoxLayout, @@ -84,24 +84,15 @@ def __init__(self, **kwargs): self.retranslateUi() try: - plugin = get_plugin("Midi") + self._updatePortsStatus() self._loadDevices() - - inputStatus = MIDISettings.STATUS_SYMBOLS.get( - plugin.input.is_open(), "" - ) - self.inputStatus.setText( - "[{}] {}".format(inputStatus, plugin.input.port_name()) - ) - - outputStatus = MIDISettings.STATUS_SYMBOLS.get( - plugin.output.is_open(), "" - ) - self.outputStatus.setText( - "[{}] {}".format(outputStatus, plugin.output.port_name()) - ) except Exception: self.setEnabled(False) + else: + # Update status every 2 seconds + self.updateTimer = QTimer(self) + self.updateTimer.timeout.connect(self._update) + self.updateTimer.start(2000) def retranslateUi(self): self.portsGroup.setTitle(translate("MIDISettings", "MIDI devices")) @@ -149,6 +140,23 @@ def getSettings(self): return {} + def _portStatusSymbol(self, port): + return MIDISettings.STATUS_SYMBOLS.get(port.is_open(), "") + + def _updatePortsStatus(self): + plugin = get_plugin("Midi") + + self.inputStatus.setText( + "[{}] {}".format( + self._portStatusSymbol(plugin.input), plugin.input.port_name() + ) + ) + self.outputStatus.setText( + "[{}] {}".format( + self._portStatusSymbol(plugin.output), plugin.output.port_name() + ) + ) + def _loadDevices(self): self.inputCombo.clear() self.inputCombo.addItems(["Default"]) @@ -157,3 +165,10 @@ def _loadDevices(self): self.outputCombo.clear() self.outputCombo.addItems(["Default"]) self.outputCombo.addItems(midi_output_names()) + + def _update(self): + if self.isVisible(): + try: + self._updatePortsStatus() + except Exception: + pass From 585f541976bcfe64afadd8be5f4d183d53ab0ca8 Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Thu, 26 Mar 2020 02:02:25 +0100 Subject: [PATCH 222/333] New icon, updated website link --- dist/linuxshowplayer.png | Bin 105236 -> 36085 bytes lisp/main.py | 3 +++ lisp/ui/about.py | 4 ++-- lisp/ui/icons/lisp/linux-show-player.png | Bin 105236 -> 47200 bytes 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dist/linuxshowplayer.png b/dist/linuxshowplayer.png index 22a21a4ef05d8e53795b53ad2294583df041d7a0..e0e108215f1f3743db9b20bd282c4456b3d4115d 100644 GIT binary patch literal 36085 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 P00000NkvXXu0mjfOTJRG literal 105236 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 diff --git a/lisp/main.py b/lisp/main.py index 6cc4c356b..1ad9f7ea4 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -146,6 +146,9 @@ def main(): logging.info('Using "{}" icon theme'.format(icon_theme)) except Exception: logging.exception("Unable to load icon theme.") + else: + # Set application icon (from the theme) + qt_app.setWindowIcon(IconTheme.get('linux-show-player')) # Initialize the application lisp_app = Application(app_conf) diff --git a/lisp/ui/about.py b/lisp/ui/about.py index 35d8c9a61..92ab498d6 100644 --- a/lisp/ui/about.py +++ b/lisp/ui/about.py @@ -51,9 +51,9 @@ class About(QDialog): DESCRIPTION = QT_TRANSLATE_NOOP( "AboutDialog", - "Linux Show Player is a cue-player designed for stage productions.", + "Linux Show Player is a cue player designed for stage productions.", ) - WEB_SITE = "http://linux-show-player.sourceforge.net" + WEB_SITE = "https://linux-show-player.org" DISCUSSION = "https://gitter.im/linux-show-player/linux-show-player" SOURCE_CODE = "https://github.com/FrancescoCeruti/linux-show-player" diff --git a/lisp/ui/icons/lisp/linux-show-player.png b/lisp/ui/icons/lisp/linux-show-player.png index 22a21a4ef05d8e53795b53ad2294583df041d7a0..8b099750f5880e2dad433bb556190defb088b1a9 100644 GIT binary patch literal 47200 zcmeFZWmH{FmoB>Y#@*c|xVy8FAi*`bYjAhi0RjY<;O-WJ1_C4i_#W`b z<8F0bf`S>_Q??M@R3zkPF<9J=3I73C?dk6uuWXPRQi?exrU$xO>FY;f7u6Dw%f2@}TyN?zMcae@;}_k43wHJ7${k$;r<$ut@c=$?FNrlj`P2Rct}O++1fD~?Vzmw4ZDrmm zo+HOb&hEZw)|!s7s_L4qHN+P-!@1t;){AT2l4d``@zg}_-f5|cK8iZzzyG~+^lc>T z-siCTVLD3*5gwCwAmB$Tuc2|QNv77Mb)# zrNhW8g=8s3p1Cp!rDdqplN4s%MuI4vnqTn1rrN_nSASBBp6?Q&IicKV6ni~Jf%;dWlRG3fhm z`wPY*Zi-*ut@_UEs2;@m7_974N80B%Fz=Fi3F!O|GrQ3& z`#!!9Uoibe_+D|S$J; zUSbA0-efHbp$VDUT<*HYxbO31FEaFM{0@25Q1&w{*kcpx+WClYF6MmIXq{ zuZ!fhgSe-CjY;V76bo%yaV`+Qe~@lB2We2^Mqouv=6&9Rb7TI zPD;79)jCJcn8r_zGG%wR-RVNp(q4f>sWep>(xNen1#pq;f|NHBca(1N-(frs`ryQ{Cgo^XTlQYJy+W6=1&|lq;7$wi6RIRJPM@K_%)z zG5p)==rL>>38s-_th*3+3H>eyM>xVq?H=gy&E|O={_?NtqKYOWQK|vBd6^FEpN9$* ztzF?#Nrd}|?By;j*{T(6;Yd8>L!!P8sTvCQeW&~hQD$1bi_uJ@g-W!p^BcN5qdVa( z4`ibQE%oBpzCZY&lNjA8egl3AO0@=(RA;Pwp9HoHo>^>|+-{g2u{g1GrrUQX+beq7 z+24za#5IM;TTWC7knN54b&@YAh^#s|!B3HwdWBl+1h|tL2$qz|3^31b_M4?I5iMURa^?tHuxJ zj+Fp%!%TN|4%$|2V^FZCPZQ=*O#c20y_qzN_rHxpw$&iqoiD{qa--%8lO&A|xWAz) zx-7X5J~ywrHWTEQ_tmlT*RiXfp)3Grgv{~AZkh3^yaXe%w1ziX%3`}E+`Q4vB2`y6 z8B)fSV__^gh?=XX*8Uk#R`1G*C9#RvfnDmNao6MmnrBC;HNyD*rsJHaVDqP)Lau)k zNq46`*-t)-r?=^wl+BJT*;Yv-saTUyl)F$>a*lUN_VGVt1L{=r98;rGioZb0=`fc_ zr)f~V8gT1p88;(AK>!o+^H-#PE2;423nFn7y+3^%1*P6Gs|_qBID(DAyYWpITJ80} zoggy9glD`_ZZq>%Ha|~y?Po<=ecwnOX)ch_L|yvyw_?OhFDYA8+bfBPGAfdMSAB{& zcZhrAlm<0N1LBhRwbm>Wx7~{ZWZ00ls^{UoU=CEVQQ# z=Tp~;%YYIBTk`y59&HReouA&yh{KU1M%^cNhj?jMMsuQdr{vJZkn&-zp5BEKe`Xi0 z$|sHdB|u1)FALu_K>}ZC(YI8&`AIt@DCPH;C`9&kcBO_i5O5RItHi?a{mDM?gNFJM zFLvIwgNm-RlWpV{N)#6Bnt@qL~-Rfv=XsS2-Bc}Bw zob8+j-{)D8`fYC=&mCyeFbg<@$Z);$BoZ{g zKE7?fJ<>>e6VY?m+^sak8hLAVqWp@d5r$Hl|d_Lv<{EZ zxflJiT=WRL(v;%h5YWUR{+)CWhC|_e)MXx_6O2-iLga{f=ahDIzEOE+{9@#T@7HbI zNdeM&J>~FJkz))dRukgR?cywEn%RG-c<0b)#OQNY0qdXt9!YWLZZz)H+*L$ zrf{H%&~tS@q?kN*D4gh|pXjU8ay}=@{RC3ak@{&bd$iaYFaLYYhDTo>cgNo$s3jbD zPx}sbW+j#!vEYL8sE`OLK}gCVUP9WugxLy81)bI5g-;inVCzS8kCT<^fVj!ru;js{5fpMe;$PUc= z28c1#e-Qg*L^cSAtts3*?MJ4pPWwCGVM?sue=E_Q8y`Q^w_9Wt++FTvbqF|_l5=a~ z9J`%U5m%vHzQmTR=eG(j5jkm(q9*?fqU3gp(T=5KDw~| zc@IK%FSPI1{W#PJ>0`Cf9VM%6t@C zp;m4|u#A?0f=QT5oTwgLTGUd&@)e>@U89CTEGkMChD8oi8ZXj_W(InwygFmdhjvz6 zzwyp`p_~10)3}ciokWixBD_p(NO5Tekq+4>9ZSuoBDV&M=Y1%7+M+5BQiT(HL@l)hb$o;PRLXY0EWIF~qJ`j=Oe+gGZEgR|% zE59SiessMbkA1o_2JU&Wd_=y5~gf`kn~J;sIJ`Vk=s=D*?G>1qbpiO za@KU`VgzD_V@^zq_q-eM%)r7p@2YsS(2Oy{x2M`Bn}^Y}DL?H_7`#=YFE_q7oQ6Ew zM6lllzlqjA^()E?gI0+1q}w3j$!v?U(K1+Ol-La&q&K=~E_f;jl9b;*l8Jo~$<9kz zfWzwZ+Z&QA+cMj24jZ$Y7*47)zmAE(c!wIQU{)wD4bQn9lNLGOj{J<}1N|DJla2Fx zuLvjZ?ED$EkERj1jBz4tI44TO<5ra|(;UVo0t2g73d!<|*y@y2sq={Nt@@V~TZZ$* z;{+op=WX6XdraeK;WwK09bXY}?Xjc=95f7NBEx3Bp5v4hMa{37b zfLgIV_Y$4c#g->mP}c5{5#r|$ZB(Ce__|AWhHv6fl^pD(Px;8bL*~AL+P!=P(L?p+ zLF{Sq(c=lgnErQW-3#w!kyz)C-w(r!Fy!8U_Ddr%L}A9?2YGWXwzL-b_v+f0QA*X% zl_VJDkEijERF=OsgL#X|ffbQXbxQ#A00WgwC0`4t}ync%RR@)&mFog9g{B<DiC394p)gJahUh(MtD7>Tm@pR#{l0rO~QMKeIj+d?nVG zZ&LJ8oSX5CHA3Ph8_rU3`&>#{hAAYqPDHDCJFiaoo@#BrS^rBN81ju`+ypRoFK)*1 z?S^X_6>^Qwv?~qE-zjj{a|7~3_e*KQ&5@i)f)CA<`O%a&*g3q|{lc@v+>;tnE=fgH z=2xcIUqlSK+dJ6#%Fq4SDx`r&SRO?8G;ZJAOML!|nr*g^azK4O4YxzW7Cag^7^ECs>hEzT3Np#!M{OmOV|7iJq=30Y77LbTBu*QCYL-j7>my0h$ z_glU`tsIQ8fsSG1ilQo&m@?g3@?6N-$;mr=yZ5@85#Bl!S=e+MwMZk5Y{n-tcnriC z?`3SySoo#B`!yMTPRY8i*UJBKb4r2Yez&}Nh-Y>w*}o#XqdE7c%d9a>aZ5VnL-WoG z>CJP)cdk^A^3%WR<-6M&jz%ZGW%97i&MF9?UWUu#=~usH!#JN@Qv9LVQ{pPHe&KEu z`tgP4Ct;B{Z35Uby;2Cq!>b(_Dg!nXpCaPvo#uzqqeX6Kib-@v;P0_(B_*(EBgJeg zG(FmPedY=IeP=S@AbqV_sz}`*ALaX8$?W-fb?0b!sTdMO_dem_RXg2hdcHs$hm@2{G(7)&zz8be}>482H)^P;E~WT z+-eGB6Q4O-#r(`<^g|YmK)Rk;^mjXZX<8Q@v7Dh*#Y`L@BH-W5&{>;i#ijU^5_z#^XX;ft^sJ(~YR#+r zS&N8qXwLiCbPAEqaPGpLsrX^I#D43KoEYhZ*dHp{siOF7=MflA&j=6^6Y6?@edtYd zFzQ?T?zYTcbjyn`uU|Z4Rb-@b^2RK`MX5qOI^v)&<`pOWugKG(o-v|uqes%m*)A+aCtiqb=Z}T^6J@dM=i|?Er`p*uITm21B z(8&kcX0k(g$Zr`NK^NJZJ&$?Murpa9MxNCe>3rPCBhvFT@NJ~1N#9!-Oh2{bOn4NIoqPL%>DUp#PH-2)zdkM-5{^xZp5sHd2(MXp`$P(2H#o9P~(GS9~lQ{ zFd}x!2fdVZHzeG!sWUspy;EO<+ohvG?HThXvxw^(@hjy@97=e^fj+c!R<-sV;ba?0 z+8=x;#21|gzLipp+6_Jm-d{7ja#W+8^{r)5rU7VWX(df~x2J5WJDMA-pX;&@`uJ=i z;jGa!ywO;PZzq^NYnxy0lCH-cG6QTy){ae8HFh37pLfX_#WxTZ!@b<#D6;)l5e9d& z;;Ei++uB9$bQRLv^V(vd#r^T9McNDF^}F7PN1mE6OqoqT-?@i=`>49SI4y)NyvUJ2 zyW#9ujNBMkCl#4zPi;FGy9+OjA7-9K4{-03I?Q=$;HCDRm_{=03dVj8iP$zZd(W%7 zwv^V{d7T?ySJ^^2g5S{r1+L`QmGY+Z76SR3@B(upgZ0~eTt(o&!SUH0hvR_gtjgvB z8#!aj3l9sKRdPMk+U(Fc*YUJf8kIj&+tm7!PauecUaU<2E)1$e=PDzQS~Tm{%au`i zGfB)M(~5emv)4uJNH0zg6x)a$>>C;p z-t=S}A+23qMSgF5g^PJ>MJzd!>ajpWkP03`-%X7T6facxKy9Pr>J<`lA@p#wIqIO?r7L&IH!GR4h^!= zA71uk!Lvae+mp#Yp}B{+ReTii4tP#oJD#xJsKk~cnRv2ZN+bfgkI$UJ@mDKhO4l$+ zMKIBju$p)4**%&mJ)K#5Z1PbbP59xpm9=mmeaC7-X7wQI>hM(u{jvay*~;0@f7P!w zBMMn(JhujY#X`zS_5Biu{+xui+FmMq;-OCa1=%zN6WeeRXF;yc@2rSU1lSPr>j5G^}nKPy2^sY19iw?)RH}GIHX})kt(K1>T0WW&ZX|Y1?Z^Q6jtx_PwI) zHc5E%l*ZR04=7X_q45~G@4Q7uFH~P(&_`OmyE@IKQYXjEIKPIc$+u7GI}C z7sT^J`HxhwQ4VZzzYChOcGc@glP%*o8NLpT9EA`;X$}^^-eEbPWy1I>IGLiYFi2iP z&NiRrUS~G&RA)XR)ay?zkx6toz|Qcj|I@=edsk1(KZkeXFSv1`Tvy1+>0a(u^6m^Dhq)_czAnY=&Q zStB~HWrx_w#b)xn0BgyfLt;v1I?JUx3u$W2Lp zv%S_iye3M$RKfN6kQ9z@O<-;`JmYgsbm2?nC~(GWYTogTloI^F;YD$9U}ww)+mlB~ zyac}sH#Ifx6iZ-}w?WTuFE1^V>L&nH}n>*o@3eF}H9% zHt)LG-uG6o22uvNb>^5QGp_V@ItKNt=k<>A9^PzrbTv$PWyvU5;3y?hOG{x`Yn#!j zR;xz8;rqcD_@J@KnWRMXk|dTKtwK8N_*d`KhEI#Gxy7myDkJoSX!wp8QMw({cp~&| zIDSqH6mGlm!sWgi!|YeerN3be1?X|5XVA+~>l!*vVRkl}sPZ)xKC4$6}9|4tC_l(-#zWM-wC2J2mz134u z7O`}3;xM;zv9RXwb#jGy1^|dj`nsB1I#_#BTUguLJB!nvws+G~+gpj#zT{WoQgM~B zwzF68cemE^SJk%kcd!(;qLq|D74sE=0XSKEnp68aIXZiY_=?m1g)0KP{!`3JOZ}IK zr-L}Ho{BoPjElQ9H6I5b2N(NuUwdyJS_xEYF?TB)5lvaSe?q|S#A)q3JzYgOIemP5 zIDB|HT-VJa&arWoFu)=>f@aO(7cn?o2PDR)aAFMwx0Ztw+E+KX<9(EpK&cEx! zYE@MJrS0tTPZnW(a{8LPa&mKUaXLBu2MrI;=idL`-hZm$p$$7k=hU?JaPe}tw0`bw z?d(bS_eNbEy*&Qjrk98HpQ69)cC@nLgchv8Uq^9l=^^I5Q4*a!-+^YQTVvkP$xakKNA3s`Uo3UG0A2@C%XLe1SC z=Gx|tf4Axnlobq$+uD-H+T7BLT~I*Sf}M}chKpU;O4yQ}mzR&v!jhNUl1G61FQ`An zib$y|iqrCNaQ%CUx}&+Ljf=aJIIXh1vzPC`S7_TiS!;Qk{~?W=Ux=Suh>wq(pATjw zKmWfA=~}ycz`XVkCN~!cx6ofTR+b`iFhp~h_t-m`+gfwFI@|tL0kc^|#@*W7)5Tre z#l=ya_K!`}e^md91!}Q>dbx3LWX!uLm+UifB{4-Rf&22gVx)tO6uY~^}NLqF- zKF>qBa z0Mm7G_xoG>TGnp=xckRQ;%NUDRn*jfMX!jt0|D0EoBP}BG_|=h0tHmnVRV@FJu0Xw)ogt!;EC*;uhiJqW#C?r55A-GZOw=_r(5; z6)Gwserpl z9cf4+#6xF5PLaaZ0MX#9o7i8RIOd%E`knE0E$?dq23}Rw)Ko&A{8oL_oNd)nhu37K z2XWN9Ms%($r83nI4yRcN445pJca1+WSSipEAoHAP5JbTAd+jdRtU=;sHAP=r^X|Ij z%UfQq^}WU2fFoDOKx{|pu7iFl`^-F}_RrqPWXoCH>!<~^iFxz+ouBBUmQPdx0Iq*i z-*0TCv8)|$MI^K{Brh{BPPzZx9Ubh2qoi*>AY|}9qWa>!Qh{@jVIwEJMvCp-dVWsc zoyL8dS33vbxcsOUqsX8@ITe?mGUNQlMtC)|AAT3QX$BAgFWTEh-Xk)0WLk?dm>G;o z*5X~*4qg6|2*^t~x;CTx`uIlStIv2*MO#tenvq85q$TyFn8L| z0z-MxgPe)7mI{W2_fH;LBNzP=ka;vWeB+20Hly;=W8k63k)ZJ6OfA}ORWqPRc=^G2 zGP+@m9S9(DU3U%lr3)D@xrrYhr}@c350rS{Vmu2V;u%r}4{*mho^Fzh!QLn5_HeRU zMuuicRtJm344$ktb&`Qx{b>Q_+pqOS{e<8$?-}Aw*-Nf<0!%Yf>N3zVc#@cO{2K9P z)R+#`H#Mv~vD5N_j2>A5D9K@Dm?GtLyw?V7`7NeK(b;^OIN1{Xcdo>Q9a{LyxgOXP zkZ~X39cRDb#dxJkhxvpLzUS^-Pt`~UMQj7!IdJDOLk*q11QkXo8;XQHPgkXD4nB#3 zw4g+MU2SdM9&;jK3p|YZSt&fggU|0EcL_s(jue11`2gDIG)sBzD9cRmi4FMYJ>}k> zfJlHzuxlEZjGl(>Cj+i}W!J9b)fzGhI?m@{>JMW2Je{rC=V&Cu2(GR|)LUKK9vHb% z(<@Imc|zjkz&?Rrr9#*Mp{WSzQe%j=3@2{Wi(TH{U0%ZnQ!o3S6~J(4L+pBxg3Kr7 zh8D!dLI?_U)?YvYKA=eD^aH#}F2(nQK74*ZKGuS;E73y6830XNU;{?_*7E@eopC6oK?|=Ww;$Qo|pa7634l}+?y}<55WcYxKol`Cn^?v@?T8DUbij`*<RVGfu|u6DT%()KjIL=|U-p*DqX**fa)r~@#;^sCB9}y$0u;PH?KH4DT{a%TqkfgKV}w7 z?|RQsEpm|9Fs1_ppeGB3P`4H7+iAfSG~WB@^aKdOPm_Vna96?8XQTzHr*C7YY7Rv6ec1~3NI>ewx?Z!eP|*Qz`2mqh$K`_pyDo|&UR8=aFH}_Esml-{ zk#Z16*S*SLq)C2i7LaFfhPuYOS6_bqBw_U3d0db{IFlWnun!gO#>3vL-Xf<^as1YO zpYB`(rDF!LH4@}iY-(3T0jwem!_k)T*FD?&nT>;zzW= zKxI4P2zGqeC9;bA-gCKx!~%YNlNK3!d62T1vRFfrG~N{CYT-k)+;~IYhcT2-57SuXgy#*W1Q6T>3iVu%U{I z6Am30z&)G#Ns^M!R2@jrlM6J=KhkFCay~+)3JpJqrIwreIVLWjreU20?LPxuqFQnt zykFCT7dq}v=AaICN&T$%si>$S6dr=AZz8ZAXjsP&)~lFv;N*+Tb$AKSbQ$$iup7{QT$^0gZ7>qTN6tK!hQYu9K%+{m<_YY=UJ-woQ*4C*L$W+YMZ zXy9;+21-21xv+!qAnL4U2R9&^+|I9h5-$a)({$5ya-I4fe(A4BzA8Y5gjRA0-$qTU zUeML#beE~pH?tVF*dFJG@6BLCMG=x&&{|b$pVLvZzl`o+hUSed=s71MM_shfJf7f{ z^6sVp3MChu)on(uw$F&iF4y%?_?!7%OyTgEJHSGT74w^<1t)_&4PStP-V*{$%UX8M z>JEd_Q@OYJJ-o2J(X?)77zWq$_bdACK>z4kfpkk<&Eu7Eg;9CWrvP%i< zITZD|Z+#mQLV!8w(HP(Y;9#cGM^Q5FV-ohS! z!Hqb?5ZUDhNZcSTDJ<-t7pA2)W;OdF`2r_0PvQ2HL(H-Cd7h?6~F4G7Xophb`dIj8X**~%BafdS(taX%n&zx#Sl_>&1 zcC60fE!n`Q=jtqj#>WY|;y6*6)t&y_&Zf-^77Ij4{%^x^N4hI z3fiv8S_nGd(fNHYQptS?mpH335UT>J;K;ehfSdh*2ek@y7?=aLKZY4C>*;#9RMCN$ z?>E6_YVUN`5Qn;6oOJCiOuyGO+1kqY6_)JpKl&(NQr1AC`84V0{{z&90hg?kP$Fmd zR%!3uOAb|Q704KTPc9ydbK_=U*lO3&__QN!!K5O3G%$h#+Q{MuC*OCFNA4vZzxKtA z0g##^dtT--Rdo&2rOS5C=_XkAv%n%Pm|C-%53j8f;~3gO z08)6YjCWW^R%i)}^IQb03v!D(>AFE$4xlsFuT71TN9ezw(UVW*=ZT$fDXoI} zoXbsNY-!>QAmKb?*={a(PJf*do*(&I5l0wTdDX~R*RMGZZ02Xbz_xpaBuWt(@c5mt zmDEd=xbt1N^EV&U-`OIdvRSZEqa5UMumN)$`et6OpIYVn2zHMQD~rwK!V5rKElX!C z!&epJ+tBiKMpR@nW_n}@bYhOQAAR&81d2(avbo7u8Hg1_y-LYbnFzzdJo?4qc(DG~ zR3IbsUJ5}KEfVKAxTj+{oBR*F?z`R;=);)$oajl8_&u0@%^-CAw)rw1K22UMExfh< z0XFhIjG;k|Rzg^z2#-WyW@sTiwD>`()`vSS5Ch+#W^o=)*(Kx%GIN@~DPw&D;2u9(93v%>q(rlCb)M8pkprXqFxNZUyQP zID$>u$=bjI&AVbfXF5P-{obvE^4FR{7@k8SQLDfZfD0s1&fm)a{mP(4w+A1TSV-_^ zYUnA<5PqA1BPvx%2Z-x-G_`g7#H~c=ksP;GCd6(*?=r`hVbNGC3876bqc11ryaDZ> zf0P@e17`RE>AN6EtsxNqeHbW1X{&81_58`g6!YER?UF9cL6Ez zsOrTd2dr)>>w~ZtkRY|X5KR-f*g$%OB6(rgf!XJ7mn&}fk-P6c-!=n;n4RdCKhTLz zQ(`&FWsILex6kDuC!dqcA3A50#2gk;)JM9|>OffR$sgh^VKHjVnhN;3k4^9(UT5P7 zBWMCL-M7)*clC@!Yp*06eMJ#AI5btfIKzYSK&c@Jicme zQV=ZPX#tOrj!IKMNj8D8afn!G$L1fEnCdw9^T61Yro-{FHv&LHpn^W~j0d6UrM` zZCf%P+15K0+n|uh8HfDU@{|%Zlfy1!zxACZrr4kSKvw7ThVtM$AYbdXj=P+f{lXj5 z<@(L~cPLP6t+Mab1HbvQ-l)jpfzJYUIRx$)`$&4!?|bVPxX#-op+)M*j4dq|4NmHBfO5*tb1DTpkjFh>xdB;M}p`G(Tr-l`+~(`0mD#2{y;-Jj9|_ zuiT&k+Yy}^rJRV07!K@cF%2Gd+=&7O7YSZs{AF(=8{Az@{RzF%UKhAdGm{HRQ({!# zJ2{dtY>sYapsQm*iaJU#kCd*LIm;@RaC_j}DI{a$FD(b&Gvx{ppns$MibOb@LuzGS zDhWlJ<(U@m6O@6PM@~gP`-I?2iw)_dkZyOKW`}n~3Du9^eGctx1c=wb3PBhUN6%1A z0DqA9b!TQw+`t;7YPVi-sB65*RGDhZTG9Z6C2tAo2C!r#HT9 zP19D9)0;xbC^Lp!Ot7dbcUcK~T5HDvZV*{sQUO=BpoZ6KQ>4_}WOM0Y_1PtfN!VKv zI)FB8Z4_=!_#0yvze56}F3ID87dvW2m*6`&r?rixn<}LMw0N+7=0K?Kjl$;|P@Dtc zFFj$BR(B$S+^`0AZHfjeY5}Wm(@K$ezxkBu|mO?nm{3Ivl)JwJI)sXPwN1z7n= z8PfK?Zx&v%9phq#s=hjev*-^Np|I+p{Oyz9U|k34CIzs0Xzpub*O8%p?f7`dPXkOQ z!#HSAf>Iq*@W7^}Df0T7*pcwCYQMtj?gTV9Sn_0wy{GXtkmV@4a|IH49{6i$!{1LU zl8-hD3paO~23$a^9_+&X*{9^6>f^ zd(rEPoQphsE$t%kx#af*VSi6|qlcS|E~S7}!9xAavtNgBxCiu>raTmN0czdr1ofII zz}hPy!vqYu7Kbes5`Qw}iI;c$R6;rLH@EI3irbm{lpC5}?%ne9`#2S-;0s?pTRHHg zFx4X=8ZNL&uZ--wNeE4E*ME@;HhYyT6zP>VcQ{SwGZjl|UL@1|4DWV9;T0(2$6HM1 znqOe*s;wP-F>yKmH%Dv(P_;*Uy_}cfwvRzqphBTVq3Ezn<{0u}6$=hoss8wzKa-$^z$Uxk%#fc{TJ|S)Vv@J38kX4zf?x0#2 z;|!v@a%W<6z4IqwM~m{<@_V)KXOHr!t``>R@$K$tb+ea+qs;*;s0rH0$Z=$#E3xd& z&5yGeL1XTCp;)e4+Fu@dgYusF<8!6VuScuGk&m5#_r!&*ZN6q8KsPsD#kx7x9m&`q zV0#vaxpgFEzIi6>cN=A{4fDqYxRX%=e_^m0+9!XS)^0Sz=@Z%{x@1arIsj@Pd0ZQ> z2)rL~E2A4oJ(m=mW&;T9_>{Hx@HcuV9FMV8U4nXCmr;*`Fx0zkNk3tHA3JJu0q<+W z75p++WHNUtD|N;1(HY0Yt{|Vq5==i44g(FIuM2es`)mF{Sa-TRgP71%&CRM49ZZSpT}(d);3FzM18a}+vrWsPA9)L z0ZS+t;`M-V^kAGVqkZK@;Hq%FsZNX$E1}fZq6Lpq^!Ni-6a}biLCEZF)<(&R6;h=m z_PwwtDRPOsN*L}p@DWwbG;#{sUMLP4fb1=$sl!pYu9yK@c>t}Zzl4@Q*jM<(QD9;O z6jruZYA{0l)-7vj0Y}JFNri9HQ-EtNh4|e3aUTu}G$d^*13d}>#Tfnj)5pRX?!b(^ z_-CK)^ah2qa@D&Tq(v42$L8S~7s%ShaDcMUmHMMvO?|E^;oI2cf6`iuC2$YUL#XVj zizkMUKOTJb^_aDC92t4ui1%V-X;VAz z6?y_}U4r1pJe3yTeaS5+$OMRA$7 z$hdu}DnU6hF=UHP_llKm4P|MY8MrxEPW*@n{6Yqt#1iN8Ysx9=hB!I4%dzg(VbQ=q ztg4(rivNhA>KnP<+y~=6FD?EtK5T<)8Fl;+=*$8~z1n|vzz!H5I6#%jxVPg6Mjr69 zFyW)WhUBKa+!FFo2TiLt_|QOe27-V?c!kS2iY`V$tlEq(Q7fq9TR^65x8#jFXBDU* zs%N^$v=kY-uJ|g=(6o6kN(uoGTpArUK!=|hLhs1RjDv-;-Z;0Np}9UCU=nBX759tR z)e(G!27fw-8tujXY-2F_=h`X~v+~v_$Dy3*1R8z?`fSAhf#_qf^6^O@NzG>_TnN-d z&ul}v%cJ4rSP=TzDS^r*czU7p(;QI8W4@3v}OlI3knMuu>=v9H~Ed)NT zrD5Id5fq59V_fxUKSIL8<6A|e8jgOJacp)&G z;^s~p)w_=v6HCECys)%s=^1n`9@OM}lPWMODtRLo>WeRj2?0QjlMP=I30*4GuVR zjL^hb|GB>CRWUJA646zLoOjn)uKNs1^9KfL$iSQ(Y+nyei6Ccq-sH90zoLKuXai5J&vB~A6 zIxL7I-_Vys(tK(s0)78wc8u(L9wD1s#|Ip_#o=7fEQK#Dq?9V3DzVe zOGKXpmY(H=mm&Oe2Dq?ROuDowW8TUMk;cJ&*&(G)S>C0L27ry#jJcataAV3-Uot%P zmMg6-dUlY#Uh>N*B6c*D<~VOgH_OO?zHcV+z*4fRuU8DtpVfvt8f16R%%lqBdKbw1 z^Ad?s0*vjXR|TTqQbgMJWu+q{0}I4s2yB|I$dPW>>G|4W#iTSIx&^~fMno{bvX8xU zhA)9d$tO`zRkyR;?Pv1iU|R-8^JmzyefB))V!5!l%{wSmSBEig2tfYnc`C_DHt=*T z;DV_*XDKQP`=W6f=PEF8+u#baQfycRmE!|RJs$}vMU9AW>ifl!;Nd3bu2Xrz1!}wn zy{a=~ZwEoDU)|*Z|7bw`f;62I+-k4LWk0 zsbVXZ&T!R>u~K1`+M1`kVtbN%#@9&{*vjyVbp*djydXHC0;zjiQVy(FRg+{s_ek(< z5}&V)=*^fQ;bP9rjzEohJxvq|5`7F_%{PQiCUjJyYz_#4EUI1&3sS^c=^8 zBYpRg_Fi;uIInH6qv6!8S$zU@XJTog?G)iy>2 zB(!V41o&^+0E{7lu;>4)Guxpzt-dhE=)x`>(~2O9TA zqrTp*5W$Xepa;TBoQjbQMVnh9z7S-fk0?zN>nOClVTWg94%LZ>9}yv!_l=afKw%F*BmtHX@sfn8(Oc6*1rISG69{Z~roD;u_{m+< zNP*-9Z;l2c+r;L*$-u|P?H;agM468R_CFPsIKvNQ%sSY-yHx?*+GR6F71opV4?#ql z<2j|=w8W#^L}oUH&bTorH%MsAlvbBYO{su=cMNr7ZMTX*Xjl~^JOG4fs$xcahSNqB z?r$MB?-CSp%*Ww{)?}Q74`bfz-M%vG7{XL)FH~AxPJK-Z8jVmN+GRWB4H%HQ6H3Mh zVaqTXg{sHd%=O_3`}uP5l8MT;hCsxIF{@pRQa)foy77S;t;{!Z+Bkr~87b^t^B&lD zTyGV@o*0SKG3)jHes3yNmHK}xJVAY9DRehw8I7rcI;moUS1UOpAO@`*Uj@Nm6WWgC z@P&A5fB-yC-CBAC0?gG;Pe?D8%=O(O7e09g0hg_aOPmcwqr1%b^`Ev2=^?YIeru<+ zAHfWs^drJ1lViPGIbx0g)!dS~Jpy!qv26(%ig5^^@pI~fDh|{?U!NINFk)mEO3WG; z@pebp&yrO1eP0AecA{<8Jg{#7&veo3A`2TpZM$_5FskV4;pugp1?NIEpfiApCNYGFw(N zsp0a0l^hLt>RvFEkbBeEfMj^=7$&_1wT!fW#)3d?Oa-;mC~~ALAVz`oLzCORJHA&Y zFMif4*^0ysm(8Z}AHv+T)c0q&7QQZksSKuxl^mTQR;X~NtKSu~76C?7Q%XOeDZ4Ri zGBg#)iDx{R#Lc&G|BI-v42!D!-aa!7F!azv&JY4BNF$O%qmlv!-Q6f1Ln91jfq=At zs0d1l(y1atcY}0y^B$hx^}pT^^YNUsW9_x>b+3Evx$?+%=MXUpA@9ed_*=)1-PmpH z#OD(FNrY6q!K~@^Mye;jN7xqoJDi24A(6x}R8nG?M%?@H;0bWJ&~Ys={*INdX**br zwdAFI=dJc01|Wf%4}QR19H7IC2}8!%HZQSB{~Reh1??u40in1-2Mo>d0O2S2?CjEI zHWG~>faWIQ2O=!`q@MkMc@86~lIQD)DkLQPkJ@#{%HPUB z6BD5Q`E5Q45G@6vX(@HEV|BB(wWxg;HD{FWu4BFCUc5?Rt^IF*l@Ork0+iS?_Yedk zYCsVY`WIOS_zj2uvX#9AHp&9uwbC_FShDJ_niW}HX6{k%L$_HJTPl1^4?mF2jIn!n9DYfB?dqN7wIbWN%Kzw zLkM$%y;I}Ql3UhW^Of`o#gL>eON+);1sHCk(=0Ts}(ZePP*|Do1GZ{(VPR zm-54h3|~b>EmDa9G}1X7L78S`sQc(qV$i9R?xUfYw4l>cuOZi`KG7X99tD%-I`3Xa z_m)s*&^W75+iit6!U*i1Cy#tJTV1(Lt-~intwTPmP9JY9q+$uYf~xN{q35lQbve3@ zXO|_HkMLG!N4dI}(v@@O_>J1(lM%|m${=D1w~;6clv~q=Y{0J$s}|b^i4frM_^im$ zt@$~dOUE>8F!#c!IexI?+CBz;}KgqwJQ;XyQ&;QZs9L z6jWDM|2QKr-hMK8W09DhIFxJpu^AMzk8;cJZ-hJi2e+D6bl`>M5;>h7h-p@kq(dP0 zrTK~E9sYcK-kzZtNhA)$x@Hy!xo#*l+7$G5M+CC^9i8P%UP2i29A=Y7L_Ipz-X8t_ z$C#7%jdn9+i+a-6NqjOeK-|&sppQ{ZqpWsHZ2Pg6-0vUK?)RJ!#~wvg?$J+ef}$zd zY$2FLP7Gwg6(WRZd#Kk-k|YAWxMAMq@%NG7>xEs|Supr1@N>SBilsKIcD}po2EKEj z2Od`ibFsGP2)e)(aTebMDGnEigsN)k$)uZIBGrsg*^12gn9q=IPC^W~-qR6%SSss# zm5$ajvc!h+4=>-J2kP;pSpA;&JR4G3%qb~h|MK&I=i$X7MPRwNtTPQkcyA-q_3GVjzp?v-jC9AN zixJ;)$4rKHf1kqlUqw~v&g-G=#^_Z(5h1u{RH&Wj&UWx4MlA=YHW)G7ozf}Q0g_`5 z!z4Ok9p%Cu3kn^uBnILABs#XxVEF{ez|0p}vhwm&W+zR{N4<<%YA!CarosEqWKK6z zwtf`|?6tPJ{sz2{1AQAF#e86Mk^EjuaRD zAYw>zDf>Dc2K_CsW?>=MCB5i;Wh=u6Bb4AfU;7>$r|6hXVa&e9A+YhGPuI4Hh2)20 zn}0|Jv{O>OVMwZ_a6j^DjbVr+0BXL6jOf*y;sEZ?skcw|Fv7qHpTPCOjkh;`-`|e^ zDA+!i$lpjYbjeDVXb6P&vC-Xj!Ogwybmsr{tJ%M;TSVE&f>_pIdjRJ`M$9d9(Z|ztnD1I*%QkF}ov6n-|;9o!27&&2>ZFt?$hJPe zrRU|&%)fv=Lc-&#Lp6g+91qVr+o~518r3&M0Ab?|_ur0G^GY+Wa&&XDJ_X^G;>z>P z4R%7P;xF3rvAUYIl&>LVpB`0%^f$CFPjgt- zzSJEQET)qWq%*LCH0{v=x?pf!r2qjZff&U^#Iv!FeTY(de=|a7EUvib3@90nU;~^Z0t7@d34Hke=B^`H0~}fukR?k zOVo2akQ3GhYZprBrQs!-pmTx;cQ4a{@?gDC$AG#Y^6`((3?9I+j`zCb%89~Ync_1fR0m|FE=mhkBCSqo-#*M(t;zY$;-*P=*c-8 zn^shbS5pCGIGJB^1D3H0>Jar`H54qr;LSlElImCN8Xhy0>X@+jhx3JKOAb_FB3@n0h~e)OEo)TzH6K=(Nqhv&_gdzf0e~;9NyiO|*v$auzywjBsEAas zB=9v3LB-p0M@#x2Oy=p7%Td;52(3yaVP?4!$T$Rm^GdVH{zaL?^#GMrRE}}pGj3%D z*kXZGd0M@;B~u6Dj0`vyc|v$?e&&6O*>2$C4`0hPo;vr155B(3Y}E&8Hq6rkdYX zT~J4NW-i}ld0GZSysIkIU$GwR=vhw}Aqi-a1dB?x>B?b(XQSf`n_c9k2 zyv!+-iIYUXD>cgKVn^CIWFk>0I53&l1u@ThQ`E~n#2d_e!v|AOu*q(Y8pm)frNe{Q zhe6Stz_wTcnlKxA8MtyMLXks6`Nh%MBExKGz(v$e!5@g=Ec{D}VYf72{wf4W9_O}| zosNcIDY5Mp>Q`enkysCxRyV;ljR6Q#Ql^?IhE!I|Tf3#BM^PDnlKk%F8oOg-(vEe@NsiIbp?HbOWwx?aZJIPgCXnmTWjwaaC<`U zhjv{$mIk$J{j7DQVB%C^^PR8mAIjy!NN-K<&Q_BlKKzxSCHN_iH!QoJZCupC(!vqy!Pp;0&_ zDw!3-lvvy}r_U@BgkqprwEq*&spe9bbaD>rZw&!D(P5k$VEzFm8liSI@0qYlIii!`2F-?xm% z{Z>&P-Q~Z)QQXGvUy$?v>S}k2dQQpF{jZp!lzt3fp$>^5kbT_=#c~23TsH_YkAPSI zGEY$j8`u|~;Dgn>gYy1TAjRX7G57Bi>rc5lx&RHb^#n{6V#zb3I+0DQh|97Jijhsp zw;a$(4Eg#2o`lXFG5SVz8F+F>Bx1e?Yjo)uUg$&spqF;%!nbW6n+QQD$lAUlx2 zrnPqW)pZmE6rLqhhjRkY4Z@yYJW6jpl*7Gw^H3zBh1FP`Eza$d>}oRZ8z%9aLfLY% zv~JoNxJBr)JyONU!Vfg*B4owVa{#mm8OAUKSj@|*sGz@(GE|L9Vg+OS?UFjy+ml=O z>FDMIMj1G|$|PiIeHItyK2wA6RkgE-51`9{41v56SR;M#l^deJxV=(XX0>=rBm&Bx zdYkcZa{tj4+Is%Tbvkm*c>Lw-I8B5-o2rq8)ONt`TRte_k(FcJ*IMsf(n$Bk+4t6P zm5#r^+BSpFMbGeu^~)n~p&PYDgJRqEu5#>}_!iSKrhSk)mby>x)NA?0w0H|Hd(Kz{ z7gB&?iz)Y~Uk^<-ouW#vDHVC1SdyDb?&^;pSIPT(H<_1vSq?8yO=tdu%B)gl>z)0>$A7T1^*h0o+r!F%CubJ0_m41scS-RYFoSn%|$>iI`ec!oky92vAwuT0H`3h1@aH z)lzLzLN-1-I*2+3h81~S^X+7UYHMZH?Ij1@;uc}_Y*+m`$v>NG*5u04;s3uB^2{6V z(xl+?q8FNwE1}0E*bm7^6JnNr{|)&;#`X>zf9v(@nK9CC=UfczL6S^dQLaMHaBK6s zt{js>{9+?HEygU?Cn~W1BFNI|E7NLt(q06r@CL@^!^opJwG@Mw9Aao$S&I56d-c`x zOVesPxe)uW$K>gyh4ioMr(~Csj_mZ^jQEcWP|48UU~LJL+lI z)%%u3o&zCTJZc+AnF|ToV@oZ#OT>ir?^ArH#eQYDnH?34U6TF8dv37&!CN=z;(_XQ zSJ0Ph#f26?{G-5}eB6Jr)*2o!dH#ylV~(O;*_Mc&m@?uo*5v|tsHLIi@&ut-DZ6kC zE#7>o*b|2{w&;1U48y{(7iWuLI@ncw2nwTwH@ojt?-#J>=s#9(L&E;RXZmL+_|mJ0 zD^waMkD+FCZWbz?~Yl^|ZPe*hSoGu(-Ug>nR|#_NzdIx*xq)L<4nmtb^>{h)WF z1F59LVBh?!&zs9hi|ru)S$wgM&b}*oaRD&yUG;$q!s;&jZ z^?27!*r$tMq)mtSE)Z=kKHG9fy5RSHy8I50QT8ur++bazk zPMMOxod5;n%TJb*S^@6@Ie4BZnR71Jm@-UnQV zCdHuCNgLv>Ks1;}fCls(gb-V={|NFZ;gUS+v;s@YiCUvKl!8pF<+7Eseb$69?|_#) zwp3f>+<MPMA|O+T+@5TEVU>kVPCJ2hvh$4>%YZHUWSaLkxK$J zxYg_&?rHe`0s!;`IkJKpfe-_g#gNUNM0#xtFo0Ua|9)Tim@<>QTMy#%fhc}=buBKr z+dfp3X#(A(pdWe+FUnvrCk^UiFH;1M&j*F7rrebAk07EOSM`F*G*%25KmUf8i6j@k zCxSz0r9F<0lS3)@&}H|Ka14u@VL~IYl-KI@2@t%wZjdDCmdKa-@1$<^ySJDWy^vN% zjXqym@A9(VZ$lEy1-(1kDL3pi?Z(w~N{Ed~NU_=}yF}`RBWJZ=NUIEbxnF}|5FU$z z(r>ym04j6$gsh9DfXBZ5P?0v|+vJ`wTDx`C(Z>BE@tW?nh)4{+LSe+%;M1o5^03-< ztmItMEZP`k@OUqd_q?p(ROV?s!TWJ2tHI8DB5jWe>1id!zChHv#t1c>lxyLKR7EU)gjFK@_a=%d9Qpe1H| zw2$B1ZHwU|wke<9{pQpBXaGwz`JAC$)h>3GK;Ju4*0QyL6BDM+YGN``eR!ePfndKP z>9)V2Lgb5QNQJ%m7tDi$WhviP5qG^(6^n6&vGG3Jhmf9X#R4rs0`MWKe8#M$x$lc+ zO@l6Dzrd#><`NoTr5sRQ3_ZzrVb#C_Ox?AGs(Eg%4~4f&0!oI++Sj8wt%GJ<8y z!+q!l2o4fG{@F0jg3M+T2aWRcdJ10Fa^lG7H8m^$F_7|qxu|bI15i^dRROaBC%l9h z@|IP%lBR&o5ap;X1bC)4sp`Y;(k|O#IN6@q*orcAjS$G?QCIxD1Ou!JAcg?wATi+p z`cj)bT$*7D^~@-uJFBx({j*!VXTrnYc%?!hHvvAzA{=(03#ssB})4 z(RmLWJoLk^Q{F-RE`2uU{c~s32(+Y~@Xa6s5I0-LSi78&OGV zL(b#s=^bJHUcT??Fsv|)GX-D+#Blgd&)DdbO)FfXmy4IE2C)x!bsnsDv31@oYX5W` z&|@$fK{RH75{kmqQMOL$!;mR(zy!t}g)h*qn*Ccfl*A#^u@_!2U4Kg|g*+Kb`dN!r zuLflkJ&=8ew^ttB`_NZR5%X`>&F5X(1`TL}5l{JpmV`yZ)V}Y0B@?zfQ2MMLwfI-rRr>J~KHAWMJYl z&Ui5z7=83dA+gU0iI3uKfS~$$<#8j7RaNG zst*cD8v6Of9-WN=Nzy+UK8I44OG-!)F-d=F7H`<{(t(+4ND?DpFk$4!sdIouegrgB zKCi{jrtK{GC~}UMLRL^>yW2C}cLEv;C9;E)%9_h^ybPQGu;HcUHWB7C_IYyY&l59i z7}f5&Q+*f)jfqOak#j{5&aDbLUAR8gc2>-D$rB*7il&{VV*<0*#R~@{LYT<(@iD6Z zwir0Ed%6}JSEx-S;?q>(7g{yV$>_V$gW2NW1d8P_#6x$hfHw8|u8n*khPr!#tw}!1 zKY@J4;@0=2KdC~&tW1iQ1qD@>GoL0p-ad)5%_C6Y(a)1>*pnss< z5?Yw)mx-JH*LQ8P`Y5oNQ0Mt9hPW=!oj}WMn3C(qO%8A=Xdqijm-m8inv(*EyyoyV!#Q97@jzp;?TI(c-Tfe6Q572GWoT=1H^Wgx=<(6TwtlS^?w42u-_ z;l0_~rY-T3Lk8@cn~Ae~rxm4>aW~YPRkoIhc5yIU_Ona>f1A9_fH_0-pQXfb3I5ze ziG;P^nJcHYa8J9DD@ESWk9*iDZ%qY#{mfq3ZWGjm6%an|hr9u2MF$YDyg{or#|hRG zjd_61y`ZQRAX`AGMs>qbUP8C)Azq7{M1mjH!qPjCg|d#;Q?442)8 z!o}LT{dUEF!s$H|7F^1YjSr9V=o(T9fk>8^7C5j%s?HiT4+Xbo8d22g+IIFmAW68E zGJZ+1+oPMQ{REq7i;$erdM02L8s-h#QxN# zqV2sLmTJ0Q9l%k0UMkBR`_A!N!=TY!?^IOLI%$(pgrbL>ZnkV`@s#Cl{UB(EVy@gk zzc}Urpv*e*(}sm96f=NZ2A=`Ug+hzRRXellSse77bOmeF#J#IQs38SOpf2v?q-h=e zJmzo<2>s1O9NOoOZc>q|5)KIDqRgx!_O`Iw6@Z; zF%i&#Fr8ZK>(h~O;A9G0YL^TlW?(Hx(b}#n5v=0M>E*UuLNnn2xK7kkS=7(1{|v5! z!+Cl0-Y<9yy3o(uP>akKRYRp&hh~0NCx}yU%h5{NgYG*U}>`@?Cs~_-hohm zX)*iVZ1zVgDp(J&$WM-qxh;OG4j>w;a}~i?@)yikpt*Qs*d|QuJy@SoxsOdvRGQk6 zG*p~hMDPxq-G^d<*jUmIcuT1{z@Sx8_WdKlsiMgeGW!vB=A^dE0n1GXA(gtMCE-Fk zb8-EcL5BXH768$K|HH+FXf6;dq*zzwJ&363nH`3=!mr42jR5Ma#arX#LUeSfM_VAR z;g-G$i6or@PfI`kAp4sTe~SZc%!#G)jJXC8QXKE01!LT-rkWiu6Zro89v!qLOruL& z$H^zJr?UASGlS5U?m8DiZ zdu9!@9CmiFo&bo5d>rZ#c>Q2$A`B4L7#y^olRs4g3-ysv&z)Tx+I{s;%{HVy zK0slGKL|JyYH%myDy~4Zfq;XJYg`CohFBMeqYWd4e6Iv(vtrplwFHX>OLANpB#3ST ziX;f%f5a2xD+I0u3@cz%1)3el2%ARxBeNCNs^%oORKAM9RC~jYS!xL4AJXJHxa)|B z42=>#@YTcrol4{$;>1<+)-As}S_%_Pjzw@E0!%>|6V0 zvNR%f?ilP;;@y`#APmqlGLk+RT;ozgoNJ%EELSfoC4_7^f*M1logrVkR$+SN#W%$@FFt8vY%a1@#$!oI5Y2wJ>?T$hxo&;;`66@F=>moi}EG`KG<00Nx7Q+aM5oYBg!L%eh=<&X!h6f(?_=`Xx=<(^r z<}~B8&0+?zXX0FOEFGd68e&1AXD}%2 zh{s{f*J~)i?M_esx^+4*ag<&5;r6(E7kSpnAI=2%A)%zkruYyduM5mm4^}Y3lI(!~ z@2#{Q4^!hRZ=DX@X(&VkjOeG*P+#Y8szVo76|5H>GXtq6ZEFGQpPW2TDg!Aw+1rff zO~<3=s6B%XBhYuLGC{I`vL+)vy}s%iJ^-JW+^oB#rhx45DU7u^W!cQt9I{x%xXv^2 z>`>6Yaq|krcP>;&~S#K+dTV8Q)3vTSw+%M?jd|v z*Tt=2i56s0hll@W3qKn6^p8Z4yP}80ggIz71j_=EZbwNjNOdb#1}mYc<)c$hpd|cL z8!38b2*|_TI~AwqrId|mx`b^ljC}-ysHM|=2G=K6a}_o2ne`pzLS3agmd839yH-Cf zIPgNrU4GTIn?g=EfaktK-*d@nd2zg8C}uPb zj08imOXc$_AKSUbO$kNG8JUnw3(H)aoF-E97t2b?4xDi;aCf;}nM)mHNxh36_4Jav z{W%SZ`HXJ#OKO>fIZ0oSe@-9}s5cV|td=LPH=T$wanV#?B=XcGnEUHqRNt~^k>SA6 zE|lEn+h0-V%PKl?Ndy|)^QA{Hb%a%P(`8El>Qn25Av5F?mywaE$7w*rpVKrhzhK%q z&X=b*{5N;gJCKbPbzozGsyMKn1PH6XZ`H)bH#G%z%J>p7HO=YEAM-OQh&ui_^F4U^ zbbQZ)_Dp8HuB!X>VixlCqMZ;Ei(Y0(H8oH}D0DA_n#3<>7D zRY|yMkIa*yU3jD-$a{LgO6%kp>kS8@)*H7f>Y#l~E>lL1kw1=XScrP9Mh|(KQ{0J$ z!@zJnLzW=H`fe~Dn#qx+ z)kN(W`vtRqA57^~vOX##xdCMr2(ttcz@7d1IS}wHj)c@lfYJnzeirjS-8x&)IJ0o! z3e4b;f3cWlZkW18W*+7cId;fALtFp4LFN)vyN90$(*SvT`e0t5Yw4cQr*$V8%qCdd z8@WQv6ryyJZZ&Qnl*pU~KI4*F=dot#vGR}m3{H!moxM*w?V$EKSg0U1-xG9R69D|F zVst%QW}+>?lB7sPwBPaT+ofKMt|AeNaA0%DZl+*hVz|2(E7m4FDJI-iuCv12m)=Za z?VdNZ&3h9sZw*6dK(enG4RO0Hj~h&l&BF=d0V#5dQ93dBFo)_>r6@b^3e}ynhmv^I zhYfH(9X|2P|E!#+(u?4j#H5~WAbnmH1B&icwLsc4LB^6DOjTqKK@H8w0mw)b+OJiX zTIvB+pWGrzqR7Ef?BwwGb`;uaxY^=UC+QZBK}lZBG`uJM1sWn`48d$dsfp{{2OWgS z!^vlKYknv_J$W<}K*Rd+aAC&oBXiFH$Y2%XB>0uwvIWt$K?7a zy`V#GmnpdYff^Vp^b*sAa*IR3fQh`$o8`;8v+D`s`S({92H6uyCdCKdm4$O<&w^#X zekOr*>6$-Qw;~_GS!7&!jilD*b1+bPbz1qqyxWgKgLjY=5n+?kcDwGx;ifG%s!xMq z;V#zjU-lGleS_2}O9H+?qL!31EjXuygn#Wc3c^H?N)n6eapfDDb~B_QuPMV!k?>dj zbie=%!DZ3-^LwhqGZChyysP+db{d)T3qf<`5Dyk-YJ3cJhDKtZve)6|C(E0x`~^^k zKYjol(^j7jTc&7t9+7aZeBVEldzl8!e%R2WUVYX}271BwKcpjAE_ZHACMdNfMa2KT zQ++&vcsL-g8ow-1cE*>uw~iOraISKBN+P}dIRF$)e+SG##83Y6yO-?@NPB@xW3ydL z)mtHdw`iF>`(#dRy!Lv=B0iC+Pr7k0r_@J7u${e)nnvr@?jw)IMWrJV@CpMOfNqa> zX-^x6+h>t${2C&o5Tz~*Mu?BnJMI%{QphFM5)1THsZh^X9ScIC>$68z?R%s#!IWnE zd07H{P@x3`05)vQxVsNL?UY|bh(r)CZ&1!$UOXjpCOt3g9uW@INr{{KL~8lSNLHje zl-1QdY&j0MOmVo57uLuZo^;dra_fUBdml*>tO4#pB+R2g-UZ)6Ohq7DnO4l*1 zD0S+rO6!@8!_bri@yOk?65(3EQsB;9?kkv$A?!aG ziVOVeK*s*$AxLsTPNfKZS6*t2SC+8{52a2%FNh>H186nLB5m`71g(Uk?Wd%5RvxwP zd?|VYJS8(1qXu&Jpy(I)e<`Mz)?D`d+FFA8i-{}*X=DfTF1LNUuaovIhR=?esy-|$ z_&(AsRBHj1O7cPNhRh`qc2IvMWSwp>L1-idJrB z7hhV>z|BpRZUygp6IWIaaJ^&V6QaP0epb(v2aP{~&dUE?j!CE#cnB0)a^MZAXYycO z&Z(pZr*+9bkNT6<0v4yig`vr+w4IrxLSEEXF+tjNts|OGTa)!JVSUkok234+G&Ubk zY7BgVJfLW>*Dv^E-w%sH#=bQWb>Gt^1CvQBkP~ztGdWD=c+qfiOFr{bj5fq{3P4$P z%<+(F(7x0#Gs~PRO|n65mGvYp(-Uxa8qfhaR|rDc^JrnHUa>GFu?Hl5%}g(ef1mQE zYDLej%N`&7TTv~KX3pC=vw4GRO?CsND|&$KDE~dX1I{YL!Q!U_i1hOKUo7I8R5C;I zp_u)NEKVTL5c`x2i)0S>G7cKBxgugWd)2lNq&Hu1(6k&Jkjs1>Y?_jHQesTvq?0QM8I0bRZU;pR>1sOpPWAc8Iq?p#t z-5JuA=5+t9`kgx? z)kjT&AZMcp2K?ZoJ~Obk{u)Qk>s@YVKH`sJA7N(iGcte7C)PPxcTUSscC0#;YRZ-1 z7c4kk_geWKp!PwF#1dWS-+Ha%cPE(jW5FunnR5?C(T5T3witr=3Avv&T#WU8^$bzq za_v&k(hB^CE%z6Wk5fkv6IQx!ca(hb=R)6u;0!dA7q~gWT%mcyE>kgT3#fFl>e&xz zIHAZ&5^hsZnSNYx8&&^g0(Vj(mG4FHUDaCS3qFyikI{8%mATKUbUyQ_HT$$a>?&4N zUXe`w=oy!~{rHdONFpXhpT$kdqY2bS?CnWI%{C+#O5A2Otg5OoMzm4_S@W($E-CO&nKk`{jZ{7&FS4p>8?R-DF%)Ux60k5FJS zW_U6)fq3xAQG2EB5tCA;MQ%r_&~+LxBA@8kr**_U-F_*K|6&Vk5CA|Q$s?|=f{k!U zyVBA8LYTT=+@1>M&APbFWc8|il*e@Jb+p;1VnStN5u{p)g?X_cg^dSmAB)@5*b|X% zu?ERoL=OPd#W1h>m=`bZf+Kl%saZ7{$(tGY1}9AF+*n#u3Dg@6#LBKeD{V-&aG!Ti zpy^9&?iB%nBf&DiOnG!6-?+S?T`>;GR>f=^0}&~|{`_rxpOSGusyjoPL9jKJHa}>XOua=T9xLOT-2vBLPoE`gd8XkuPMEDa6|L|Y+s+JG3U>s4{uMW? zWqr%^bY0tNG34-p%+9cI%OA?5s272#UhfLn9+uBUTj8(|!C>@b zXFFYdzx3S+qKBQR`Ux#T!Zd_*~-yB?+!#j;`IXDUQBGoLcHQerYrcQqLhI3eihu>w~Kpl;U zjsA$D9s$u$CjcCWxOtvj=e4vQAQy><0wwqE=b)Fmk_ELPd9uoF=Nc=BGDk+br3iW(XjZRcfOFgp#oaa*xsCE6QS14!MQ zSBT0Z9RqgHWI^mIAm66abRG(6+pEzlys?(uh2V8C0B;{rd!iOIxfum(K=z}-|0%us z+r5iXn^(!c?PApMtR5xUREhUa5{~kWfF6|!qe2V^p2rJRoVV@0IR(?-lcYcK9PN9> z51)6b9!j&};x(cr-ZlCCniWECXK=0>L}Gt5;P%oG>ii@Y~jwKPq#4 z5{u!+n;}UKwG(j50z#iAWiH*v4vXEhnfWzyig$4MgEMp=ymhLxfwpZ18&@(A=v7ZNsUtz*4@P!1j5PbFyO*aE7r)|bI{_ZBO z{@pzln%v?T6H1c6WQK5`7OgEkgms89nv>80ip!B2xP;4^BN>o|QIG1LQFa=uKCW!- zAN7IrczaLxz00yRvXkzWRLi_dBQ0OhyDo@!l_$0T<=8z_l15h``3bXk1i;Q&1ts1$ zk`Of5>=3b`ktPYb5?V4T5{!x+*5Hu6kr>19y3UQ-XdB_L>=>(>-o`O{We7DOW@T*Pl0YCD*7Pl$nv!N{y908)Eo5))V!#E7|E- zXtGqt@mSaKxG6r6oYn!?+uPQuigCMjMRsy2|*k{hX>ak!G%~j5KyJ~u< z{*-p|$vHbyvq}k@;!vH*;|ZO<^hH6C@=?}0pVK7!uIqPkBz;k&Dn{yyp2)}t`AsEp z!0I`W1nW*dJCiXE{&ft>kB+VT7|v!N)whF+Z)+SJGK2n+!>~CJNJKVMcODrPIjf#| zMJ`xy<2UWdd93noY_SZI=nF62h}vfTtqST`8n!(6VACWewI`(3*7=~CX#T_H)MN^A zyT?19i^;UZm%|hbXzu3OS(>^7 zL7%y0J2SE9nLKgk#+D<1(%2Y7|K8q4!tialOJ%rAaCQCQR~?m4xeLcbOPY$k(gvI~ zKuN-3*2!Atb6~#Rn4-TWD`@6hcE67F+itHDm$Sbqmnc=W_P-WD5l+PduQjvPDR~2~ z64%<@G&^&lRsUJAEGz5e{ER32L$pA4QXG~<3HV8O!(J8gF)}gmDBSl@`im$rM;y%p zE7fKQ>s0mW6e6y*T%IbNIkWx!{7t+-r$QmEqV%^O=aFIS&Rx6J z&=RCxviJwaRA`bU*ssb;WC6#9)|lgF5`!=Mz*#r0jo%6UhuUbtQ~vyL{saMtGcE`V z$zk8y!#EVX9q$a!MxRuTQW~bR;dd7$tEMorTyM)Mx;r5qkt79n4~RVHs@!S`jjGDi z!;=7FO7_N-%NecCt}cfV27xlZ0$w5yOA7`{j-oue{Wp=TH*R=D>M?CD7BUonsBk>G zpS*|{fHt&hVfa9`5}g`T;n&!w0~$|wl7P>B_!9%&yQ)UdU0xN$x1uitM;a+ zqYPEo$X(XA?2$@*jvpco@AX&+#XDn8ZHVI98B~uS?bBdp&lOb;SW+5yB-o^p$ z3opEYOy_RkhG>?u=Cg`B@KBGnqq~9sKKfV$T0U6yD!rq&d+f)Wz7)M!Q&dR4|Avql z18&d~ZRo>=?8&e9dIzg?eGxzMe(QarNGjQNjRLF@`UDlF&+bdR)-4ZpDu9Yn?y$|^ zP2!`*Nwsp+;uy23;-x(*s_A}FE*(W>_(R*g@H;Uzy8XIj2Ehqa5QP{W4hnhQ_P1|D zq2_NOOpW3(bBA*0(&nB0@v$+qh5lQwzf3+)?h*cs&l!oI9iN9&;_;uJ^o=b?bgMJ9 zH8E0i(jDBpkD`7oPiPG&M|rAXNfzA}fO$~&=RhE4fPG*!HYjj3tS0YFsVJn(sXKmJ zOT0BbsW$Dnw*Sa8K@;NSUp30{bd++`g*g7ADbp+YeZGPm=a705ruIh3Edv2?DHmQLc(EVeCvzBJ=ZFwCG{tLPLPiS=OVt| zH|VtgOc0ts0Q?&1AjRP~Z%rjog)GUx4M)Hh1&v#LF3%xrW0w(w-!CfFk$H0jlGVQK zuV3Xxwm#bRF8NlF62|T4RiaUBvULm47KSDvtGzWDF}K3ZqaYvW&O=6VXY??^+B4>h z<&q!5KCY%?qD4L?%3ZkW?r|o4npoSm(vh#{?jeD$Axcr=wRG6@`A;1$p$L%?Y>UV)roUxrFh^PF7%Jxp2VN3W&0P>5 zIR2RdYCDfR-e;rvzF>X9I+OE`%&;A=hrR#P zXPR!b-^%`)^^LX7x&#gQ;WllqiwL~@#(DD6KURQ1=nr;>0ru>1cEa#@FD@~7UTFb+ zFQ&Ei+S%#hq2*?s8g=~2_pd_>n7$_dgaj&JTq&=$+rDf-yq2x=u4!L-`_W|oz`V!+ z&>;y|Uy;CkqpZe+Ij`00AAL>_Y1TNu`MP(WRo?I4v%vH8L(7#;+#5R#;B0ExGBy{4 zBo)j{dmtu6?9$mN5n|vLSO3{9&&l7x(ezzh(5+uzIZ*^UD*G(Zq&|zc%z#5=nDF>n zdc7lME^KKfn8hdP+(>d{Aa9*Xtlcv89kOL+_uEImi@(94Q}^UU2yDQksD&{y>oPU@ zo%QNw)y;!%#cy~Ec?grfJbHQD%M=r5c07IBGdyfgnH!Uumev+@S;m+szn@Xr9Ozgb z9Xlc_DJ70W&(jyPE3HX_O&<;L9nw|uAnhP~i~2s-cdJw{KDYjQVk6SQ1&enCO2sXXoJ zx=UX;qu?#a0Lt|lo4v&S+36jc8|UJ+E+jJ-5C159rpNE{CTt4i_iDxiF{~jRPmqs$ zCW7APA-S>qhNSf6WiNh({sPoP6NBcJY?rkm)N!p_^P+;&lbf>Gk&!zt_?*Ug4jJ*N z@Wle1yL^VJ@eCnGQwOiMYa=^$?GgZl11IqHvjJ7{`HKKX;7Q^X5{lr18?!MK(L<_x zKAj1`jE7o9@%1qw^&x_7ygU`M(WG6vvbW={F487>LNqrSLIT}byt+G)#pf#*ivNy9 z8#$fg02`8ZPf?Q6pVUC>3*g!+NY9xCUd4qpA7ckfOM)#KD#7{_y};GU&2;fvM#1Tp z$AV2|ODk!U-qMqURgwQBz*+&ma?oN%k+SyQvkYZHSAcr`}$>+MiLAGjfaYuG=uTtF0H z{|jc&9`NOo&2n6M&)2k z{@OZttxA88hB*l8e>S*`7XU(=`=XvT;87) zbmsjRGKO3h8XDxY?!VI}edR;o>2KptSi!Zs1w|MWpli0N*@{;J(@Nz=|M5VRuF+~( zj}ztQ)eiVxDj?a|+Nlm@!8Xib_8E2M)7{5U`T~6Y`%zPWx|^Jx1sc*OdsP7F-s^0C z6T*8XsubAg08*|<$k34aF9-MZ(eqnt6NgAszx_gY@pl7R@MtfwTb(mYEx@#2kv8j; zQMP#PH9da_HSoj{Q1-_7rA5D-pwWpuguJpwBr!p`ZXB6jv!rh@5- ztLwX+{Qu1Kd|tV!OVW^LgV}k4u6O`|%XAtkTBGl~#ISH6L*>RUn}CwNAUE|}r>HJ* zifD+Qt7#Y^$w*^2GU*%ZL{^r9tNvr14h#$*%n6@C)agDWZbP$eM^kvHRcIj{q~l<; zi*%R#6kR1R9^anjPpK_;E>mFFl}43Sv?QrhD-^e}4e`y4yAk4m_b%kL&=hPg5W#H< ze0n6H&4M;ttd9&H7SknP2D?uL%^iWSuL(0dvNb3%PjNUczEfAwXus5Zz!PP+`6=1u zpm@6d&k6y)OcQy9!qq<`5IIGa{QY`ln{We5H5GaZBMHYpl66(3rq7Iduify{9Rogq z0{feY*2kMHskg8cq~W2dNOoPfw2A3%cxLc{ip+lJZmRTcH#tvWWdix8K-FVwoJj}) zqNs{~#g{fYt(Ks~YS!}y)F9X>>sr~2_JfrOZohNmHxigodMByP)SKv=A2;nmTy%@v zwhnhBdG3v3U`)|5Fu1-SknuLNZC5^+Nn2(=?BLt6Xik_{f0yRmQoj%q%kxJSj$wOm zUxsAnCrgislo(U3ZO#9)ySVb?$JcTQ=~+f`v7aR;z#=WyN5m(`naoGY>Q9S0-XeY< zaIpOL8`wmCd#fq#%XG`Hjq>vjqHURwQ_DcjO>_QOO%f-6m#(tsi2et$_v+t&8O@q6 zObe@m-|bzP_4gK`hZNr>r^%X92ENi0mB@+eQ{XR;Y#iVXE%S82JHNHJcW!=}(?1;J z`I1FA%4vbqB%f_)jm#IX9djlgTSIiIG?zjoBe2Kpm-jmUUsq=x7R4L&{n=%KrMp8~ zK%^Ui1!<&1T0lSqlx~EfOBy9bLP0>fdqEncC6!cKT0-g_f6wz?*L%JD_wHOXJNKOP zJ)iHHb3c(_Nj)+w7k&Z4mG3rYBA9dxACXwrcUhJIAHI}U+(9q2VKn|N843?*Z@-ai zz3FAX`uS_dwYgz5ppr*tzbQ&HVQp+K8alY?Rp5XUQ$uQ%gHS6k``kgCbYh zXARA~`|4ca>hwiJ>eaJIRuT`-_q|mOnybqz1>(LXefXOLllHZI=H10rGWd#Jhlg!% zL*xHUpSZOv8JC)Lq?Dv65d1EGhrv{ixf;`p`H5|V&k2D>@m?Kw1kz0k37vm`CKDYK z6O(_PeQ|-AEj}05pqbruNLDoVs+p+DXgB~Q$YrPO8SZ8xJYVyF49VWQhnuwz?f-0? zKAwawWa~APfe0p(DginZ`8sR}w7HuU9ST0eWg@cTzv&2o>Vj+N?YFe5owr#BrFX6= zJZc+$U3)j>L>?aEoSYKmHaG*Xu0uC>?sQLO(72Cb%ExVVW@dCJWt?X&QVuBz)l~Hm zoizy1#l4No6n6BR^hK8}{?NB(RmUk#4Fzr9BRF9?Wy{%9 zb!EEp_H8w%{qHf&^BS|Y#aMN9B9$+&lT^r)RH$Rs)uX~##igVo2a2CR(#+|c%6LAU z{Z1t3RnoFceBtr2xac_1sORIElX{`MRsj`p8dLI7g-<54cpHix2tamqDo_D|GSE&( zqOs=cqhbB=RfnpOpw0{>M*h$`D*Up^2-R-yqmKz>ZwtICvw~{ z$N5Kr)COn7XIOIiNTy>8-mj)YfRB$G$33DWBo-%D6tN8E&Q(K&p~vi@OOTZNSoe|X zWY*ruwl1PkK7^-p*kIc|Qx5g(8q1DYn~~?60)US%nh%1fczEK1(|DtvIvWZ5zYnl4 zoT?avjd>QQ(>5|K5_TwHNEW~hO%FnISM^_DJi^D#&_Vn1%hlka(M+4?KE=b?YDTFFs3WZ*CuEz-w7~6MLr#yS=zxPpY`gB6#g|h zBHWl>-Dq;brf1{S`>&&4R?75Aaz4$CdM_X95VDMdtTVuUiK z1JJQpOssXX-%)o3L)C?&pO_Hc!c~3y*XPqGZ@2djgxvvs%r~5HH2e6+5?)Z9J=QFb z1uzUq5`luvlPMp@?3xsDK?bP!a+-sZ!dpi_^}<%@QmpQ^s)i`N5xC;}6yV3f8WdEpDJMZ?s`T8R7vp)5~;H);swTGpcY5;<=&q7)>~vaiZ-* zAURdF1jwQWgJtXq`0|2ki5@~C8*4%CUQN{xLiVz)vV*=e6IYQ&TFrK%Q170VLR*DI;M1!338H7tLEJCZ|D@8=TK*vlH&yDzZoCZJf zT3M{J<{t~qlDoc4@*G8p!*fCG`*)37Yx&igFYj0ZEa4nLGZ!hw^%y12Q;z^3l4`Ud zS+{nUa2~obWjr~@75~&^G6_)rn9fo=>|ul=pLGR>;S9hwZN-@iM+yN}z_)OUiBwV4 z7XY7Ju5HKqVz@sON~yZjCx#UE8qQm(4Hltb2rIC9&nrn*Rr2P8{C%9StI{9Y07TRQ zDNaam@Mq{j+}_x5v9Sgy|A7N%XieeD9baXz0uJ1J!zAF(GxzW^7*ld0d7UFLA<;<6 z+69dq)z%l_hZg0L#nt)w;{UP&4K7-q2*Qr)j#)?{(!eUCEX;wfX`p``^V2RHTHLtx zgy2CB3!vV*9AYUC{r3N&N((b1hNXq=jf;j0NTmj8RN(vG43hxY>p&%W22YNc-;9&6=NZpjbmMmZwtpYz6O7n( z*nhQwM5?W1oee$ZtsS{}P=k-23%RW;9s=<(_!9xjjZf)Wy<5dr&}^h}Yx~++NZ=Au z-dQrO5o~WjK&$p+y(b+eeNabT$GTfPhoJW9Myf*dgYt5WbWYM7ZpD{~XEb-ynK1+J zFjf-JqER+pOO-y1&Its|);2T-sn#{J=x15KEwdQpAs|pO1sxEUR=U$<%NC&0Kpcg< z`+i{bnf702RB9~X5>JCg^bGI*K2$&w;CCyy^F%a7Gt--k<7EMVQmuvDD+}^*A*lov zDp<76JML@_J3|BR>ID<}e2WD0I;*a{Cw(H)z%N)hWW+A>U~tLfqR(%!LJA`MKc!2D z7U+F(K`JuTX=~tQa(*$$!!>)X6RxDbd)S6~f|#IIsj~&Kl9`!*XmNQIPZWUBA&MY$ zZD4e=T4k^tjTOwkr4kfV_V1LY@`N5sHR36~7$t`lNWhvJt3-*k{dx^-iejVm_v529 zwWnE=OQ6Cte9A>~VA-m2OyKWS4@)6(427tTOdA)Qj!pW-t`9}K$GvB6Gm24wL0Eot z&%LYSkd+&z52Y||X-_JBjAdL^Ac}lC^4{((d$OXzI^fcX@p{1~u`&D=tCo@ao>dHhtJs`HvD;Uc6m|6qQ0E{g+qmQr4(br_l<(cFlGG-hzg@hq>WJ}6crJpaug|YI$dS)ddQ%(79o`z&r%@ zqpK|Nd-FeiB!{$**Md^fJG%frUd57hJBfQ_Fr%ed5iDUkmf~v7rV|P?5epA{qGf^G z$v+rBkF0S*1a7fD%A2yiu8NkUZ)^m)n+4m?bY+7eDL#MLX7;pe`?(}G zv7|lv5Yoh^P<9662LKruQxOpG*(D@5gF}E*YWo3IdImZ#1krFTWNWzz=rx zoXrD1xGUcQOYvBd#k&L4_e0~JrfhV|NJW2{EI^XP*u~~$%t402ouPg<5uyKYa zWbT+>G$7#1ZG?#nv7Lyx3$gyk&b9aX@|{2530+~=uZZ7|gVuiC!`bY_4eukf$(DM1 z13rW&Y}4%-m&(K?UK1YpAv#7f| zkDuwNa*AI1-n8_rQJf{4+60*it@1OF&$JW=-#WrvkR*RV5U*ba%ja8K-07mF; zs@Y{YCOyqg_(t!{iKq!a!_D^_!UegSo=Ps)0Ox-oQ_v5u5D#?+&IG|AEN?Z>_k1F{ zKV2DT6`Ds*?`p&4032K|`0gO~ZQ~-9`V5wml7K&2$Emj5l4YvAWSI#lm!Tmwm$^pQi z_(vK8?C`NSCMvdGRId8ojA21JRGO!)#jvrL1mKWEFc~m5frlH9d|fF*Fp9%wQ-pC) z$Q%uIz(LvMiR_GoDs~ z=a&2G z5FVd!X2nn2S?Dx_;}@SolZG3uNDm`Wb2z^)mn5vTyF5; z6}at)0M4mapO0KXZ(LP*VsY8WDe())B!0_Wbm~2$Bz-x-fx6~IX)l_lAyL|$MMD>v zQ=omAMy z-O``ZOluE#iT+}aHv@vu#7QxvNYaZOnniXj9Qw;*Ri2 z$E)uCn-@}ny*P^IE)`3Wz0XK|vQTX1n&%G^#;E6A|@har7 zVDeBh9zT?6tfJojR0m;#75Z;sxo>s#|K5M~#;~>MNul$o9&eK&0(Hy_<|09RM$v)J z13`n+37l+Jx~>l{&r_DH@TF#Y z->;VU!HiJ|*N71^Apab@iwg<>xbgq$h{)gF3x7$cPa=?zY3$OKGL`b$j#UCP*OUNt znp2fY0^q~|(dU{J8QWC&ISSOILLwh6=UXA?ulUX|ME!?M`g;iD*?WK)SndK!JZDCDHP4?4>Vqr|Xut1aR#y z(mZ6PKP?pcRuw(s?tb2~C7e(HuUW(kdH<%q-D&0o%N#z(dpTck@Ph^L8-*d=bQRto zt*d&qmw2RV>C=8Tax&G%#Ti3&)1IP!Z3TvT!Evk_LtSvS|>5d;>ziO z&3b&(A?Mbd;#*^THLdIu9f~_kju#X+#_{_EEZ5bqoSijhVjvu5EWk+&QwiHN2UyfT z5yn4Z;qvO)h5NW~XwTf3-2g*6gck+$PmYj`Uv&-RE{OOO_Y)!xCkF@fg)TI5u)VxU zl?#VKc-=hGS~oe_7Crsq~$}%Yi2^FksQsi^(hbeHja)Y z<=m03e$_XMK5VxPcc4c5r8T|vZ+t+j^qzrxaP)s$EEx8&&&;OuA~<@-MzTZ7w+Mt8 zlmDIe*BA>po%+*Vp1Al%^&fXCJz#vrzN5&&jaSusK%wH6Y>s7OE?unf9J-{LGpaoAriN~Fs;aTAo(LO z{o(UTNSfjE9d}Y223@1TQN0KjG(DjaOoD_L6Ll!7!WbOgs{5*e@Xr->*#78OVd~FF z5_XjI-SfI|iAp%b;~Khf>FI>f>${co#=UlSYq&-5eSCwJzk{?_0g4p7e9M^@<^7u# zQw?J;USiG!5TdjZU?|=<9JISYJr+AN!14-C_@LIm^73QA4bRY7q+j-}817LPAfNF7 zckLEi$S+dcj4sw{8!8aKO|fPc^_HDJ&>zX^9N$WUvY!U!m$zYoH|lHt&pW42{9+QX zr&v(xLdFns3W=0;<&EbbsvHNzl6M5B&riv{ub_@{y->x z{D1|zcd_p`mh2)c-$+1RR}S|h)MQ7sR9E8Ki&XyjY)hK`o`w}IVCIsLR3n}sQ~&y~ z_$_3dCv%IU&@aHXB*nkpbQgG{3c$ z^R~xwIn`w^91?v&Gu$j-cJe@>G17LAdE56B2ZGSn-cGb$iemFX z`i7%d`?uENJ^dV6sxMqSXPRw&-H+f?ybjL>DoMq+_6C@hOC-Xhw>sUFt&j)Z$wOx_C123vl0nraG1JpZ^(BA9ikGs-R}43c zSf4oNw9%jM&`*NbnZL!Ex#7ZpDxAp#p0DIf)lWzzX)5LZn{m5clNRR;sqe*LoW1#Ied9k$}Dux)svIX6W%0-4ILCcLk6lp&32hBVf$?tsLo z3g(yh^I?SqS=2UKh-4&;^V4l-6kouv2gX`SHJT?U!`zm259;HnjglOIu!9F;xkOXO z*xAj(B|j}@s9w@-(sJ)gQBc39VQ36;{N&J4KKQ{$2Zk)f^f63Rs7Hc^2&OoEW2kQ1 zqy?bemIz7p)_w7p7NjSxQ7ft%BxBmqOODogs$YPqKXCnGm`VWA54WY~lj4~_jPGH) z*(An4+7`iWtQD01YtMdryxOWq;^H548=!3a=j*pr#n;z-WT}DrQ610i-mpQMV_0TN!)skO5hVf1tIyyCuO#00TfR0`EJEtNJ&-*3zIHb9lw@ z>nEK$XgmDMoDE_HpT>SgkVBFgp~DkR)_d`OmvK+V?7+D?Y3?EkQZ~b9{m_ zLFX=0N3B`_b+}w-xo%S%V9+M^a2fz`yHpkA^iBF3^9Q*fAEoS)WYGe7G4pKwzy~Sb z^3?1P?WR?W@d?BS3sB4tlf6S8qb^YaH?0c!B*3pK{xHfOA#)2%k;emuA(DbQw20#EgR+4 zyMgOIrRJN&)WdW845HMTgxXsH)SeiJv=~uOF2CA6U((!{Y`O%bkJB$Hgz-=T6Wk+0 zTjN=YD!rnVu;sIFL=AynS{XY{Lr~vmw|W(HA|2{r%WKuaoL4bYNEOE8B)?!!x?o2v z7<4XC6S$lYNpgArutoRGUMNM0yPQ?6 zJSRzA@agwhn^6FSg#xN-A;kYv0vb+2R!@e?Q3baptfT+3K6`iZ5xTuWnuxz(mxOdn zL`z3E&h0Be=cyeTIXY9#Qh=}Z(c)8wj}0%AoEc0TCX|7Y7=WF1Y28(lIG zzSPohTHJ5|fF#j(b=1p{Ifg_-E_Wf~+U1y`iE=z9I%hpHII#jTCe&A^4ERj@sR!n# zAA2{VkaNB}Dx+eyN&a#yF!ND&O&LVI)zcC8Da<6E-I9c2(Pq^d|6><{~?!xJ$}E;Tht(j{!(na)L%He>@bU1Zi}%T%B#9 z$=r~M*=(B6i(m;^VtppSXye?c^?yB1rSE-VOMi| zdA!dH7u0^8K!L3}!7UI2l)H}uqH2JU*VsusKG}b9|0)8*oXZAqVqx4_a+n=m@)(xz zwc{C?yew17VVds7Z~83>ayRRJESH2phb?`pfXKWagG4h zoyB!WwTdKw@@U|L0?=I!LwWT)>yqHu{tAog(0YB`u#vx#up~Kk{Hl0K`IM;IztZ1j z#$}WMkRh^^dk#lcpREqA$PIeUHTC8=7hcuBbqCxYuqM5Bx`?jL9(`A|X!_c3QQG-k z)=iId_IrN=#iq0|A*?0o@2?V+`w3lk^~B(n?Nk=LYg`|xJ|9B;!K@YcZ;>FqMLnRT zgGaoKGx+;S>(jvX<54)3fzRE-3GC(>!)~j++ZqR}D;g zS9);6hxrjIqdI77BKWF2o!KuMv^eZ`^`EAiO7X|6a{REt4l~gH$ToC z_{P?AGlabfP{n&eR*VPYnj_LfCzA0<;NL0StY=E>uimf&z<)seyY%-qY=KikaH)s| zgVUJ+ppukNazvoSI%YIjr6dVqCQT;AI)G~$EJGD!N3SuqI(kDfb$nk^k92TUW=X3&?X-WHdo^jG_U8Q&dK*5?F95^+ViG3sI+V%OZ3<|%;VK!OT1 z_2obm{aW4iXHwA`{nhO*?Urwpz3@Wfme&AuR??%vXM0QSYlbenu!Cd4OIH>$@sgA@7B|&^jERK z%eTJ;-{{F=?!y!DqJ3S4c;FKEyVKuf+w6`TRCu18k(`l5U9O;ZbF z!Nen4a&A_;&G@r%D&e?P^nBj`J`v{!Iuy$Bz1l*L_65I^UI zw)3DM4~v3cUqC1>(gG#s|Kfv+qBdWWN& z*P=P`eLw+7Haa~Fi_&`(rw0U+0KB_310d-GQS5%Xk&DnNTYvAk_tXNEL&RWyrxwX~ z*mC@<#}-XAI$jvCm$3e?0Ik~ZJbSOX*!8AON|?*!2y9FO&a@|PnS<$jZ=uH4C!2+2 z5D&0`A5ceE6}=u8L*()_)>Z7|gKfZRdsISY)5zhO-dKYB0TKM?E5ISf0X_Hu`#Zh? zpzGUd7D!Y|{8JfItA^jAUfi2!?JFq{c4BlQ`KAsZaP_a3c#XUdA4a}z#BR+=`sf3O9b8%TXia|es0mu0&IPiop}IHY{x?@01B@G zXkDaJiDCW)ZaABgXqf?gU;h6Jz`mn%a8xITu*UKt$i!lR(R4Sr;z9+SAH^ggwx zqvj96Kp%mN1~0l$Yb1DefO(mFJIZ|uoV+tF-)5q0dyQ_+CaZ{Zo9~jyM2#W3Z<(3u zDDw=mj}wEMxCgDgC&LqLw+RlWnIAVR0gtFq+&bjHw!+M@cU|N0$irL@keG392h{8~ z!SLhPDLCMnO9)NZ7F(ng+T8iz+-U!tQN%DtCGM6e-XW30H1#+ffIg~LBS=UJNXG&SNG(XS z?C#6&{l52_Yp$7juKDM==fr*Pb0*19Uz3=Co&W#gD0#;OT$&2mq?S4mSP{_N+lJ{?4qL+Ioief(}psUvo>vT8N(4yA-X~h0q%~01)we&x`+UAS5 zb}83pZif*U@&OmWen~qi&RK>XN)K%@tY6`6+_ns)yp@c)U73uIyl{%Py1qB1N*ay9 ztYe2!>*1%z`)^lKNyidnx0dXk2{0u0W)P@tQo1`Ku~uq`hUo+(y)I5%=0clt>YXyA z-0z*Dxh0new!|KG29xxEJys;|mnKq-#vsol#`f2v#|?l!w4WD|67#1Xll)?cqdyj( zDCQCfi*dqfg&^?-D8qG~&0{V}JanDqOFb+ui{p#C1N!_rgN`F37#$N@4%c%BP)0%e zapRZTeP83}6DFQvm{;D0G?_?7HD2W>1oT{g#jJ}188l%-Wj_1popyBVTG~IStAdX< zZSg-1TQBM3Iq<%EPDXzZt6jG$a;wA67QG9K7z{ICTdxg6&hM|H&Li@R_o8&2JG%aK z^=s=o8_Zo^2clcOR*wr>xaxvxoMcXlJjG`6*Jc<1PtloYZ%+usEIHrPyglL1z-B#o zmRfv!8ikq1{=Nw~CPkB7?L?AXEf(W`M&?dF!#)}ch{Vq!1R}2zuq)8cp!MJKQDuH- z#ACPTfC7N<^@4};@H2^J_%FC|P|@Dok&LhK!}LuaMY__a%qYXd(}V$7%GL!AFv|K; zzghBODah7R7TE_Y1Oz%*VY?}M$42z*J(1l=s~s0g)8m^<1qA%+t8~vPfbb**ek1#s zdzYjsJs1PF=JZz{vlx2ecwL7bi+N+1j{w!S7Rbm8{=yBJLkj-ffOYE~+Qvs%_SS7^@ zQpRZzAYDCaM~VYC#uF7)dUVVam-8~?Z%lOa#-cqo_LOFjM+u}x#!8&7226nAjg){E zLX@KFYeR1kqoKrmh#CqS0Cgjg{+P*Q>dV-dn% zF0EU!!4b^3u$9kaS90WN68I!e@bBH--R*1r${wZytC;&0SaM*U7|{WQFUv_40LRCY zV!c$hQIMtT@Flq@?^U_fZPTu22b^yM8PK@#g1(+=8>b(LS#b~@>+-RQGW|LXTK87h zt)dVViC_keev*AF z%@P2?P5(8gaX>TDGmc3c243Pp zx0IsC;Yu>$(;(%ZkB>M(8f5jBBC}rhuR2WL%lF!f1*$BU2G~-_MESQQh1|B+_pkpr zTyDokg*2OWfe=>b(v)vK8HuJF05 zuMD!u!yVHiBA>$4aPE^uk;GHW5S9gO|2gc1L;bB|yLMT7e{^1T{s?}yUVX6a|8Lrb zfh6+kukp6f+43vZvzh6rSSqokt4X;Gu{%H*Z!aGrEzg*AV z0E`!aoxelv!A{4f98kxI9Rl5I^*9H1{iVPt!r1-s;q`%@{2qENp&b(^T9$-AsC4fc|<0?>+Yh5q1KyL_PWJGQ6EmqPiYB4AqrY zcPf>!q6$c+f&~J~Hx6c`YCAV$!bxEs@!jeodwCPoR50fDp-}4Q(mo+;tYmP~bDTQH zGYGclca0Fu?WSw<%~PN!+~5D~^N#E;6Eje4@|hIpN(4|OKtxn*pAaXo*m{K~B<(-~ zV-bB)wD-N_W>E&Rjaqba6z?Lf(?d0hnRVFZAIG%2)je#7{39UVJqlV8;emlE>b?%< zzi#)z>^Q(1DHxN%FVoXMH ziT^fWZ8DUIaH(B5KH!fH)hTo7cnwn^L2P~gwI>Z=dEnT?JD(!GbJd`nEvzM=NP@6i z4h-Dp){J}9&!#F_Sz14KBl4+dg!s3lww9>%umiMXSV5eWwXImv5WO@-u+X9k93&U3 zpH0bslme|-AWlVl7rXBDVOMI%QvfM{wM+$P1TQ=5H3(AHB+L#ReMEchS#l>AHYXe| z=;aT@qhsp4UTkBBRta9Otw9k&cH?wcLjY#}&H$r)n1KS1AuJ7W%LykfPF2>CvblbH z03o{^C8AzPzqLO;lo{FqiJN?|DTpO9X>mzz7~-~hd=nZ`{37wjb@C!;78gbf1x<)_ z@?bZRTRP>WC;hqVyD4b=b|DQ{-G}Ryj`MjUwOl{We(q@`dQMK-(6f z_{Erl60K^!>L+{FOOT60*(|DrjBjD$&<;>&F{@F4BBEquWNsAyi*tXjqHuZ*Kv#%J zdu}G;ltaBq!{%^srrk*|8Ev+Efssa|cEH01>#I>w0CdcBw#Hyi{YW5l7^qpsbi#1p z0c5?F&EQY)`(wisqkt2a6fKnC<#CTI*6)iK$Es9R#VBZ2z;v@TTxk?N=A5ctb zre805&KFlre^>zdTI+Rc_j7w*b9-XJd1ClTjPJ4v7nBI6;!oxXVquT-B>#qThk|I; zOy}@EdPw#Z=Yl4VYN#=}?DAc~2E}WxJ87v0t$dV~TlYoTh{lPRe71|@SErqovHV_A zfkK4}udS6@IARCsA~UQCM~1@>@OKeWZx%08*McBQ z)#O0!D8L^V;~d)(ydB*aZi%;mB#OF!ONzf7ge?qQSdg3iI~tYzhfjD%1uXe0-tMf;GxehiTNGyWo43K z+MJ=;LF!u5^VS8g(_vIv!zUDpe4NzebC08GH^sD)Nd9%N`h|w9vsI{iy9-6G7mRGT zF$}3BO$OWP7BhEU~Hjlg09&gSjW&T_0-m>NjGaGq`Pg3W>`kecN?5-cAGDf)&wp_PZS}DTJY5X#!2WUgJly^`7>7e7{;E_ z$tP&7$FQt&Wl^|D`zG-*9l#b{izKM~0r2BT%ZFS0gQfNwA{`9lLwn$!e#IM%xKdpQ zieK?k-T@88>)#i!O>8&BFgOUC5Mzv{SEs;CUbU>ULF$PLMvI1i&?U5Jt_=NY@tO2u z0Uc-DC4eB{_E%$x@a_cs^v;H(>W~wPEp5{Ei#QdKGWB#-Q^CriJ_UU;YyP&4M2dIv z<;09G{m#l2^7T4_U|u`@X{E~mb6b%sEj`<;#3VLImfVh?Vo#8>(w?lCdZgNCD=9W3 z>9G2o(7`Bge%>pq%I);}FMrTq15Db792dD-#16=lCu6y%+Ext~xlg6!esUkJA5sFC zl2iE|STCP6h7@#_&zrr!YCvT@Lo6)3$v1n>vast)(pxDDf4QnX1~zTSSh*&v{OZbP zo#IAaM3@&D;`$Rc4MgWzSxq84KzNW6O$cMZkI;PQ2GQ^$up~a3ycSmV*n5rGTe1W^ zwsE!U4Hd4|2c(<5EF70BQ5m+_%bv?<&cqF26YLfyU?Ka=7Dq`U?3HtflM5SYdNHd# zoMUJr{|F9R%H>F~F<|f1E$Hr+hf6#@wgWKBcd>O{UK0r1xsAf>r9^GtItkdeNEWB| zY7Q{qet-VRLjUOBp)B_O4nw@i6b4y63A24Uyq!?3$A*)h3NP!ovd`fH$3C&|C*hs& zTJ}EXde;zH_=~v29EeSVM_(DV4ceQR#$o*qq9#7UJ%dPdmRLW(ne2~8HS0ATP*XQ3 zhqZMn5FnB!cnUcH^T69Xg4B8;vTs0)=A(ldi<2rfgFGKTlOlzu%*t{><-R9Al8}2C zt00H89Pw%^R#CB=A_njXN38bpQxl=j5v8)$*4Ts^c;>U-GV(q`KH7jWZIE%jyt2&5E zLJz%g{vg94E44iVyR3Taq!OPyYPuyh!w)Q;sSD!8a$Kt_!ky#4}gD?v!KP}6HBHK>xo?C{sN`7KTOqA z>D-8uGtNREJ`8veGCUEwBbY2@ACapvO+&ZHz&kFLR(KjhXKfzFw~At!^bO%0XHW+U z#;%6kDy_`>?R661YBEKfruCjJRBvR)c)*ec_JQT+^eA6OUhx&pWZ{YtL>Yk&zBdpp z+2hyyRL+i`Tog~zuP-sQ75(0MNF$gIJ9>vLn%9Zml#$hRUY2n|_=zGgcu=G|Bm-959@jtz$bIBzo~-)D+wS9e3aQ*rqB5xRxN+zF{4 zqFzp5S2eJR1_$in(rjS<$2c_{ z-gsUtc*o0yLb9a5{Q|5N?(t~bC@VI!pE~--lWz<SpF$vd;n5>fK_mq|2sf&9V)eo2`4Sf$O2LZv_1QJ! z#?{Lt2G=2+m#_}VMB{CQGL-+yir~1ew*cBu$(cs58b#ZV^_3^q0tJi1w7!r_^9gUK zUwT|8AH;sz&x*q)Ju z*Fyc!rx)gJ-r)?;ptV7pFldS*=tKl2VCD+i*(lTu1ExT+q(Bf10hAmC$p9}dS)hP~ zsX?goTu<2i>ioQ<#k{`e#qTD|&JSUsQpaj5>q6`m)Ktn3_S0w>HM9HTFBJJ?zU`i& zWeU-wIAvI54LdVY)R&)JA3G)ILr!X{RwE+PlbZ<$%1{@wLB~PyFgas<9m|@cyF^ z=;}Uc{<(rHJ))$TSZTkQn4F}tqem8t$H)pnJ2?>4#u_i%ol8vs9^HL@1xE_ zmUx0z(LMgSp@Pa|8Y_Q51GS(lq14n=&x8Jv16+LXX0r&Ow-&hCZlu1j=nNoy=D9ZT zYu;<~$A0bBKZaKC>lAaTIF=GOvfl9}!*fLglDVEO01?{^mMr8{@$Us&7|is*3NdqodSAF$%nsd-n#;#@u@zc6Oh_BLwDy;+HO?F4v>i84WZ9`y*6p z9?R;qrqVPR<`;wdu5mZyM^8HFkUQLwf~~3j8aww0H41JzwENawBZNWFO%&HXKvnFf zy2~@ew7%;)-+O92qCAuMRYU z;jpPBO}}Z6)GWOz{Z)f`I{rzwn?EP=&!WS=;9RZ|1|cI#P)O*i-+A@dxHO!rXLlJS z;SMFtE)&atbs-kkTZ?nG)OdVo8S$6vMZ}+bE2uZMh`khswqRd5``HQMM`m3W>&|4) zg$V~9D?VgYX&O|;#T_{9lzi5!sq4M<911IZd>%^WE%o;uFOSZDOE;g6DAp&$QNE&d~0lF+0Qja<{4OY0;l z7tIxi+5XetmXy)G@EH6dq?%h5;WlnuYSDlU=;hA0#C?srdINt)gLgzxN!EqB58Hma z${EYYonG=^I9_I@Pyl30+(NMgc)iTwr`%V~`TGHD@~!exYNx$mXXbD+C^AxVMb#>T zdSyToe06?D>rU=i2o}l2@E;sV$Z;A-F1R%^>*&H`<@6h2+s)?dHsp62pHdGWULwOW zhP;U^$-!~E>hyWpQw8=Q7!)U^U_`6Q&i13_N$7oH1@$43dpO_i?x$yxbh2a}bSVKV z+>Lgve9>pAC5MKwo|}AmN5LTCyod4HGIgvSSp~1Peo*`67!_2GN=;|l-KZ%c2+|~9 zKKf08RFn!MC@y9i{NQT1RPh>N4%^V*S z89{SrLMmwi2HSMQm4@q|<(-2YHN9EJC z%T0tlGCB+yrc5KWR`X@c4RpJ7$2b=5L=La6EgbkAJjFOyJiyV{Qd*uL#nm+x)0E;3M3% zW;eluZ(OO4Lm0l~;<4P{zbxEBmSRga4sdllg_wpZqVI#rY9EiUwY4%%zSq?ObLso* z;P(dYPis!$=E7IpO3-}KghXnpxqw+*0cDpHP8=Rv)XQI?v^cUfzPPNS;qTC@w_QIW zv}KRvzkQG#4JAu&545j!n>gSSjJwKm&Wo1PVxY)oT@YlUNWf3<$5g9F`))P<%9BZz z5)w+331%0n)@FQYND#W;d5+Lm98!~&S_cc}PjYHAkCrBFKS|GdT; z0nd&;xnoDI24A|(dry|7ZBD-y+WeO9^`1>7HhoN@36`WMv>b2$aSEn67!$D5u^y)R zrvuB`K(eEsJ>U=uYrZ^0HTfrs_@)L@dBNvo zmp3Zhmt6PK9`8UH%Bft0YltS?XiW>w^5&H|%6Oul*dPXeunxM{sCHuZ+1HayGCqv| z5$&;hp!K5m-Ya%x(IT!bFQJVn++x`bi;l$5EyK^bs1K-+g%h_t&-Ie$l-Rsf-u*m@ zky@A8h?oOSy#!{Me~A#(Q1fb^$M|8>yJtU(_-+OpTS;nE;wCw z&Co!UwQSurv){x$Z(_=*L#-q0qqQDj!bJ2Q)z z27@~4$Y1$^3*42k{iIPBOU-TuMb|({EE>DGx_Ns&C-C2!326$iVx5Zm`u1y~6ACoI zF55%>z6>ybRSdi(wsLtbI!G~rwbbv1KL+3}OHdds7oPbkVf6R5l^kIGx5C@0^p_|p z`(#?bC+OCamcGW|2)8*eWyqn>*Lc4*@rk_?Kfj>&4^VFQbN|)16tLKHOF_zTA9mD- zm|{@?p~YPR5{Qp}gwHbx+rQq8aqM-(%GPVzE3H&^sr=}>jD{N(;(Q^+Tj3`279_%n zfh$1(aP*WnCR6tten}eTpXhG8cA9VprpPfE zIymYQ%^)3eqRxozh=Sg%uB)H!eRkH@=lmqc2fzsc)A|I-I^}tC#NefJi^MtJgc4xd zn;S7MYts0L_;DXK88DGeGd%oAJ4L5yu)2W+l6~(@O`Iy;W`9ExS%A5d5aU0=ChE|r zn$CCV@WB$F8)Dhsz{8-IV5lpMFB|L`Y{3ksuJWb`}U#*b)jIO~l zRuq-6APKUWdNn5@?5Ik z@yT=fXE{5z5LK9Q%2Q#Ycg+l`g-a)B`TI)u;dL=ygCFm@TypN!+7E!Knqf$9VF3d1 z>zU7nu9eE^k{hC5_4rp8lDiE+C`MgvBt&i9+0!JF=+|25DHfP>$98ewu7+&Wb)Hli zY+N-K;}%7&iKp`Qzk4;kFbf~#wd%v?fx!#Pw3@W6Eu8Gm z+}bv)J(pyi2h{%W4LM$y?6`m@RUsoc3KnMGdllOSy|B0aT1kzlB(Z%(6HjIKk_UWW zGwMVv7x7+Y6E%(h1v^|n{JXp)%03 zbp7M5so~GA*b^h~dERk)&=`!)DjqOTQy_@i8swaQ>iO6#j^{8@e$Ydl1NNqWUX)CL z`=e_zUq2P)-w|9{F4x#9K9?IW&B3^SWJBd9txaU#lK_=bYc zT|L#QxDR#AxmHL^k}0jFH!GJMIZHU>%&;8!>;amy*@}8P*&fja+QGMR=jJ*E9-b?` ze>7RXzcGlUfKDoOOe*)z<6OO&aPmaAd=|W8+Vc0Vr9-YARxq11KECMCdK>k@Vacz! zKbU4-$Z2ZM_)eY+`}e$yA+l_}_TfxXg86Z98R$F=f9rQk{hKA0-OUj2P_xSe8mgx+ zF|tCm*vw{uk(|6obIoKIsr|2?=ozfIzvPCyFF$hVA0sj_9=kP=?#fG+<+-5@^B{g@ zxFq;iRm2|ILquRU5QYuQ=H2E08C}XyrY_9v=gJ(XhHF1RQfI6dA|xsN>H#@?`l4sy zl=hEuCr#H4c)Bvd_wRH*IipsBBEhWG?NQqIAO^Qwfkm1xwn7q;UYq}R6w|UQiU$j8 zv7t>?=TA|)Io)4R-(eRm(g6RPp)elgXsg%O;SkFZLovo5al2!GZh}Yo@gp zMZ>bQpI0OviuWPd+6L{Ds+3uIB^&gb4X>KbYGLfS`<~$|tP)yY=}z1uWiJPE|EfBj zw0&{vfcPKLtaau*_J4`|<46y4(})pT(WJ0A*<1P(e_(o85)CtiUQ3D}y>(IJt4y*- zr+8l&x(PDt&DS1Qg?axtc`hAHSDD=IYW$u&AD0zu8^N4&CjN}*!RgCrdy8G$4=N}U%2V5&=Uz&m7`fLCKhul@ zzj-e4ZDI6+>opNfLb227hSUSjW~ z{9hUy&SUXtcOE3Wyqk#$yY?eX2X6RVo{~iUM83B^ygft?D>ua+LERqgV*B{M)6yN& zZs2@58b6?l`cbYAd$f;!L0M@QXM^`Eyq6|4s!AlkmZ(B&*o}|CbPkM*1eFU|bU4v# z$Sw(_-lPtLqKJOo1p4+ePI1TitvPFl&{>gA22eqQ+(EpnSV2NC?>IgivS`UCJNrFRaKi-_wm`jEHCNGdXfp~|%DA+*Zdm9tQ z|H>Dl4x#*ZW5)6*`03@PMoVp3kiawZX*`qs@@)D~X7YYdKp;B(=m4@(Ex3pW;o~!hqZJJyt+}V0)H(i z;uJ@wyws);?TGeLVQX(ZHen^|-vLiPOmfm2!m|Y;%J5N739-9~INXBuR|kbu1XIiW zbzM7)WMZS*Ho5P$dF3X&3scj4PLg=MH!?JpT0IZV$&OD8g}TE2mvOQIQC_uzTQdgz zQU9_KIBix8{bmaAr`Ufl8H~%~2Q*~wEt3`KlS&&=CzfXCWNeN_l*fAzKzmEcBb^hGnCuYswbYc^{Mju?6z8(;G1x6O zr-fC(rWz-fdE_R+uVD$vJ{F->X+qEj(?pzcN)b3gA<+<-ch>F&aBi6IM!OC!m@B{e z17Yto2(3tLddK9XutL?(LHZr-YGET-k%|9dJJ)OqSeFNrB}3Ci%FB*6 z-yhhqzo`c|*eF(evQ_;th^IBNeofogb})S!kTG=D?na^7oRjxSdu{8ue#rakY70dL zyf_Nq!$AMjC22t6!7N|syDsQS6wmp@4df-R-_O=P67cXV4ypNtCshK&x$z?NRN!0Z z3-hcodtbm>x!4s8LIiIYGdwnPtKOcK7%>#ec7`ufJ14UKrB>!?-dGK8FZpF9I60Id zf_Yl#g&b&`f%GOfb1_xsgQD2U(@QoNjEGVkv2JL{N8(o7tAbabDXPJ-z>@Q76fSDC z*MKC)cfu28S9T`MuXukQnsu<4%&NT1_{yI!!}sW;o`@r8c%W>3OMf9J`|$M5{-Bv+ z10WtnlxraZ(9c8d@x#4e=&*@A{BD`QAZRljo zZ23;#Xr2g>9{jO2JpBYosUf|S?nY#zuiN|7^LT-(S6TwI;`D%zWk80jdM!$g8>{`z za^`{Sr<0z#bT)Yi%#MVBqsJtKEttOr>cW6$Jh^NbJBTBD&xWbR%(YEeijSD2S6|sLTZ`DggP4Y%@s<6J@w=qsCwRK^XVJTAN`GNmdR{XG-t9swG0}QHthm}c zQb5F@`PK_NPNm$DFA?lodbmWom9U)779Q-oO88aBV>)?F(WupRBxjGRck8}@RS}1-7GbMW$So>bXWNPK9kIeev{cNy5Fc3`lP}1OIjvS z>ThxLGP$U@?ZOYF{?{#!Xm3lD_iH0Ks5>y~f8(8)ltVp> zjKW}t!YSPg>d;BgP3YY7H^tuMeuGQp4+RG0|0+!LRu{@cGFdrJm1;hdWoC7)>ds2I zrK-jU0}PjqExc81nonTy2Val9@lKcDe&E#wp+mHBtgU#y_)Hn??S0v-U)m`nlYjf~ zdDDFx9qKmn8v>EFgn8bSshRXt%bb{k(sQqIFic0OG8TKjo{VmLryMKtCgGk6U*XpR z>FJ)-I5d8f1iN;;m9vmLCH^i;&()Y%+wCw%Z=q}j`aGiP^lJqR;D3nUi!?rKOH$i>fQYdyH-9tQve|FLkJL4MN4Up5$ z(WTA*DF|pzS*~OGVm0&zJwr5-^Ct3^Mn(QG3n2WmL%a7h6WxJJobei>rw;+QACm6LzLNH>z*CTp zrBNXl97(*a-_b83c$h7DWcNS{p%<@sU)R^Z(|c3y=F;PNQr_XhyRSdcn}B$2SNwxC zIUF9?c7=cbVVKk5@?D&wlv3ZteoJ8po^(=(CG_fgnq4^S4{A%a5wzfE_7T{7xUpMU zx#;qIDMy_TVCX9~abL+Gr>T1}Av~~`#bp7OXec-O0X(5Fy^L)XVSfn!D^V@9jON5q z3jD}ho-Gjb_5j!EjX+D%`1bv|Eh?uYZf3FbymHp%Z049|Jbxx;SO7+|kU?c}k}iw6 zFlRi_<{6wiBU2g5ke;nk>KmR`=yWAPq9hfbLX&FW9j{D^NAU-6>MS=8t`RIm zL;H@+y*7Suq?JgovLea~N!KEPISbMIIhRDH4eT{EqTuO>l3sY#<9jr zRU$+zF36eQ$NEybiYh^vgVT;xh13PyBJQn6R}zT6DSwE{AxetJvc+(};un;sQNl3S zopJT3Mn-+Q>?sxbNjZ1vQr&2Ah7roR&T76C7+$BqG4kydPF1eY*iLkQod|K%ra-jJ z1&yW7oQMdbCy)h!(g-jA?%=zQtrhgR*CpIGC_MQx{K!x0KoqOjq}PZj%-dW5!WD;+yFz!77COxh7qsJUwKcKunU09}rz5he z0A|T0Vme6vf>w~QC5qU5Rv;MU>RS4h+0BKpYsp^VGYQb_LRY%Saw659+YyYv$^5Ky zPSoVrCMR`r;?!>HhnS~&%!A3ntI#8vok?3qr71=Iz}E}T^ra`OxlM^4{nB2l2d-78 znZ>I-UotpxQ_pB-5iDp6Y5BvS^fc}zu>LV97uqc%NSELw`wP|sLM@Z!v_H`TF9t-Q!8V&XJyuX$sYI;ZSDEh7>-Sim0=&sIT6v?W+f1yjWWzJz!DgV|k3tUBmqu z4j1oFh$%hOF=c8Y#XeoF12ZuyqsLq)(!GS+gw?wFFK%+wFLWu7O6b!ipTQ{>X)AV< zrFRXEx{n{`#T5Chec4^RojrK9FknbXXPpeqdTxYy;`*Mef5uhTDaX}9AmZpQo9p`p zDm8n3X?So)fhhrnrhS87ofq^+hln>+jfVMbeNuqvoNQKvWNKn zbj!|%A+qM*K`X!9-6o}Rv8oxerR}1opMX7=NE{|9L*GeZDJBz5fW~q2D?E_Ir4Ay$ zg&&Pq=>(FHfB2{8SPJU_{xV=Io$gp%t$R5vBoKzh} z9EhY}E>h0@mhDL|t^PxDg^zS{2_CInw&D0!Rv2P{BmM?Ub-%7Ehy_ z2$25(JqZ+Pm_;-(!+mK(%~<)I$lYj`p%iP`+EIQt4O}8Ekb7>)cdF4EmOMr^|2c`O zA;SF-i;*b6|6TACk2k6v$5NADn3t*GDLqv-2^GCrcnb6SW9f8`A8`D?*3WT{f8bre ztr43Wpn_Oyr_N`W1ex}%ZGE##E1bK){N-yY;Y8hy<*+QASA0FY+LC&de0GixvF5`& zn=`9u<7cR&LdB#wXn{wWEt#APTEvPet?iewycCR;V=RIPWZv|2h5~k$%7enRdt`_3 z9B#N=MWC{abt`ylo4wCN019XC!X~?PVXH@l+Wk1*qYaX*Q&W(m_rt523BIi9&FC5MC8h)CK^GT2iVWY7d|1#PGy;n zt`unuRbj380m`0iuP6a@vs*sCa{P6ms7*8rs*+4+fx5kBL%=XjYD?x%`bl-`DjZduQDktI-ORS`;xDMX~ij<7I2?DY|7XF0S5R|m?(d)<=mU`Lx5cKU&qfr1 z>?t#>)Nn=53`i6%JBiyMvth4&zD4de= zOO6CZWnypqp)QDPFYd0=6nkZHMG|~sDu(6-;B;H#gu4rXGUMyjKe9`5 z6?EjPf}HP_Z(Z7t3te|Qy}DA*n3^wqV3eAnsMD9^j!34X5uP9>AiV8#wGq9XhCFNX zS|98~VlkXN{2hRPC2p)SdxvXPbIwd7uB8(unU#CwiNAAV~u&i!nrv|LfBc~+`izXCD^`i z4IawRx^ABv(~v@INJrAa>e!)snQ(Q^fcw2!>xE^)l@>ZTL41Wo#&`@A%4hLiY$WEX zC@V8?m71~=nd*Zk;Wtg?&C$nH;(ZQBpQY0<0ni>Ss`qfYBT!vY!Ofude2M~hH=v%-iD_S9Cx9( zO7aGl>ECV3Y`?PBrO!JL^WHD`{-Qr#ptJj|ts4N5K zIhnEdfQRE0xp9c+0FKcq8TUx3w6u+KrYghA<~+{WLpznX(46g$60Os{0QwfKD;yPQ zqgmaeO9j2f?T^RF`|6t~US0~kGj@Jb`|IKXTjUdxh1c%u>;&c?Y=$yGcEWa2C+ zy-nG&2&W_Pb=c{wQSYb_a>)a|Pyiq_gF-)9-EOYQMksDI*S|(`On7I9@O~?jeBv`v zzB@-diIX796>IRF*f``CL7P1i!>;2AQGKB?cVh9u`X_<#Ua@Ei2 z@Mn6yDLaw()%uaU`)rVg&ULLOuB{nKyJi|0QYPszn#=W^tG1Bht?a^mIQpc(L2O}o zDkN4=YAXfj31Yw}zFWAlC~E|jY>|3+&kF)pzn?o_8yhJ501;6rR7;Wzd;kr9i(bMcX8!B?{u- zJidq;ipP2W0uYeYz27nWj`K02ZO(9mxhp?YGL*9MkX8YHa=`m+~Fo*^h=zP`X zsrsBpUOT$BW80EXc5fuA-Jgu%Z?z_9^!)w&+p_u#H5cnq%Gl>r_Ww>r>*~p-cEdL7 zWmrcbTrrHZSC}Wu?VL~zXSJssZ6a~3%|au`0p{IktzQ8zYE15aK{+XOz)s46S2~{` z9^{hkFG_?8n;{C2C?+#Mr4a=TLLv2a=)glemlDgYlWcntiLIBq#cs{nBNPC?)E?!!Npm0)T( zr=MA7FFy{cG*n3+{Nl&8S>JK?g~F!=tMYJ$adP%wP^nDChwf=xZ8ycA;WxenHIX(Q z?o5rG!xt|v6t46=*C18(7v|j&ou_}l&u>D5=!?Vu>Sn!{YkjG17_0J#PJz60E*%^w z41;SdJ<7o-dTD^&G?8SU_922InWUxR?@g1G`I+FNvW;x06;suYD!OATnMi!eFkPDo z^_W+Q+)6)2hUVVHD-~uW&C-AU+NU<=Jg5V#!AMLY+8^2<4%07`-J`pg2L6O8w!Kv) zcHf(H`Yk?UX)6Jln?`4cMwkeX$tdbd%!Yc!^AT}_!B6mC<}iD`K_~WIY@6DQ<$e%U z|KvXuZNLh29|uc>C9o@ay>4ZxK8aPVYdpYyr-}AlNXvH#&k=sXH5o)$n8gKMnV&E# zZynE(tN58Y6FBL!mtt1=kK4&GF3zWs49?XI=zw1pWERb(^YA)bp^+O~8ZJX_6xLhc zjzB!Le@oe1H94Cdi8y4j~c#=rlaa*F@H8naYmE`M|P+dLM{OcIk8ofc?AlPn{~ zLnqGwcCk~5xlU}ne5uf<*L3=`eqPcOZW->A3+g4MI{bSl<&eSCj`pg<)t9Y2m)0MW z!7VI_o&RO`IfAm1SG=#);>QCCg|%5uPQ<<@yClk?-4ig3oR9Ju@1qR+kN%~ zjV-$dnGDvVCQ#4M@V}KB+4olMH;Y@Rl;Ro9`QqESPB^ja>^~U(N?hU{Ji`k4-~`(J zmpXAPkd620g}9AHuFTn8M*jlo4MlEWw2K^Qx&grZ(?pKqB-I*{$>sV{zk_b0{lKG6 z_)^Y_vRP=W0Fvb}JnSMg3Omt&HE)IQ6tDAJUv7#jK4CmdUM=X8ay(tKLVhtzr|C6mTx1!!rNK^0%-t6^Pgm=@*?pT8N3C>i_ z`%w9ImCccd*-g?%wPn%49`kw!;ABpC3bpN%Sa?jIK5m%8y|$13Ml00l=+DQ*e(7$Z zm=NpM*qH7f`<|%v2^{#lGKwA75`&21>BNkr_lF#4ZfE1Vl32p{je`_GH3$EUjP(BQ zbw!*!D`85aJ(C2Uz(p`I-~FfgOb#fV$QE&s=_Bwgj##}foJ)Ad&^}OJ#pEl7Q9#9+ zkvA(>ubJ;pkz`P9xmih!e!>?i~?i9o_@;dH<&RT{xDTl3<`gh$~q1it&i9;^A-r)EtYw;gj z+y_cmQwcHMnUg2-vV;KUw}E57v@V2@Z~CdLuo|)UPp9t(?LZBD#DO_GPhhLp?jeU{G8uS$IA8jBi8>uPVgl}PP~+-3ykuw zVspg*Nk0LB{c$M&#=y|;z{rPh{R#%j$b9>z%qhUoh6v_LBo8E9edsT!(@&ZjSs{T> zpL=Ft6NlBj@?bPzSMl1;77+cGF|vm+jS$_{HsI`hFDVm7pcRl~de>Q6`|)IQ2S-hS zjA?6FL)?`#=Z3eDcs}jm-j0VbQiWc`4%vFoTJ3Ou*(E{D1ETb9(v|pujMD@h=l{&pbT#Nk< z)MJHqnZ&+T8$1}VCmeB97W#T5QsFJiO2-j{np~g6(Ml1Yo9=CPo|)bG!<=7XJZ$4V z$Ag0|qsr;0|2sgr6*WTUF#Y?H={rSZX$=?k**cV8-!Ws~cva>i)gsFaGCk7^RV%{h zCjJ>)A<-%Y@4bqXrv(M_?#U;t;sP8jwTkmWhnqxK6HHe-Mfq<^C)IIZs0VVeTx!4# zT_5SP`l=N~x)B4foI!Wt(|ZM`oml&@znE}7S6}4wh_}?gkN>(%9g6Bl{2IEwGJtb$ z`Xo;Hw-%*_g`LD)kneu`K(GtPLrp{HdQTd+6(Pu&&~TJ~9m?i~mna?}tt-?!4F)vS z^xXQk+pTrpUK@WEBGVr511Y<*s2{$@6cXE~_OGFxgQD4aCu3x(1CRZ}M;>0rhaE{H zCJHSqGLfRSldjF}q`j?&qNq!AVL_v1l&^gnUuK#SPi@All7LskgLwA$-niW(eZpYw z3-HF@-g^(0h?pvn#-m7#;fhlshq_7|yY1>C&b})%JoSnf(Y2%m3a2128K|v<)4~im zOb^hjoN<7gIdn_^`&8aZ!+;^}DZ?y<{k686uj-hvWaTI3tHDzznGGivHNy_m&R6%m zUNG~`sLC=zFEdiGJ?&)O`!ru}{C~3mS(F0s#0((deV@y+`og%!#PRgY3EH$ z-yab~(JUckP(-enFuK&(-(8iD=dAaVTcZ&1E#JPwI{$Cq#XIxu+r!j9YogM&zBHdO zjHxd&RpVS_@ktq9X%u)2-7$yQen75ak=64U#^adUmo*+ZRz|2WMzxdwc!FUomS83O z`TZPtYm9I(4p`Nkwc{rRJzIll5OXr(@s>&qCs(b%r4WGF>oxh$7kn2^aU;l(XKoB} zCOz1vQA2>7F}uMT!G-?LQ)>nV!6?sMr%NH@R2qSBs3e*#0mK9d1l&*RAi5jDTS*Mh zckJ614%*etUB3?8zP+or`*!R4ed|sFfEQj!LN!a41#;8)YKc>&gpryB%|=wrtXlVY zJT6$iz7_DBV`Jm0+MU;%l+%V}Np^FpZkc;u${5~Q8GsE3r1kloBJDAa$!7g?ylM?3 z6%0VJ%_aG)k%MOv&Zo4ju{88Kg$NjAG6nFzheBg8hUL96`H$301{O}Ajqk$6V8%d%xqX$kKvA@sBK+-QBiB@3mrztzg(0>KTUK!`)bZ$TL$ zA?`t3H_}%N5m_%P@mmF>2WFuT=4)l|zjaQ^2}yN8vNC*~iONAUYBC+160xx+(uwzD zgIq?50YUq4+hl&69F!yPBc;h;$@KOJOykDER{uRTYTU0A905|_Ar#58L@t_Wx7V^J ztItO9wRSmy2#(5w$_7DqC?)J3724dnOWYSa>(cgGKLZuZy_eg40PtYv^N08Trkxmz zmtF$ewmQnPbofg}8+_zv?P9X#0F)pXWLS}5M!eb-sfEuB_)djGv+gD1wv|L4L)?P} z1S^R+95EiH3~1C!x^MpC^PO9LZP&mU!!T8U0{19;#v0nM`zv zjDK<1vc$1PSo35cGBAXIbp786av+h--O$R6q(=Y7N)I)kW9dvkO3a-Q7*pa+II`#a zzgz9#wL|BzJaSMPVB3b4$^fTq=iGH!+y1>|GMfF)+7|Ammv$8de3SfK-?)?P|CLvg z*?-D;TkPW;dC*OW=yK;UwS-^=3zn#li#dPmHOA&0f(T8#>EBzB2K%f&)qSv35>(O$ zO_k4IA}Ml4o!;sB0HUtXPW7F;u_Uczs2_BhsgEE4j519ChiM`{+ZdoScL7*)5@37> z2deu5hTa0)Sdr0k{#FM7(R>xlDH-k(dny-1aLqw@Bi*alNs=tAZIF@#E?J*b(b2ht z)wyWGlt84U1IItJ^x@B)m0kxy9Qz$oKyGodf_kZkQKtD!fuVqI7PQHz%8#(%nEZQ^&^Wn|<<@Dj-6QfSA?M zy}pxbktkCpOBqFdA5}o5rN(h+HS51asYC~i6ajU{7-RmEF+w;{0(3Bk1zCZ>wt~-H zAH(7*IM}2-r^tOy5&^Ai>M#~(G6pm?#x)K^pioA>oa+DCSbtjgVs6%7{QQDBBoyoQ zj|?J99-38|a=}0})k2;d>@x+qEkW3pxH;t`w|M({`MLh=)}8;YTfp&g)kQ3UdHf6m zHl+=4s&;`ZG$f6Vw3SzMqSv2Qjh{Ij2t=9>AT~HdwG0ZwxJ11_*ZWU}t$|CdbH;oH zz>?V_y&cf)e7fhK*-kh#{BI!uUIh)>X6cw~3h8?Z#A;jPg#AprukWYVUfV?gaCx;_ zz25=&)4O;#fAePY0=NRFzeMITrwzoTPDGuKF_-XrC-G#OBf1WBEFr~fvz=sD4Y9@7NSe{Y-3KgFO<`eH5eAHr)i$TEvk z09iwrM4Mo&=I&e;s&^6o9S z;5wZ$AgknYSK!ZF#&^ianwvLwwVnU81MvQxyw&Tk7hix_wm$_bS~|Kz;TKx9Kn;~F z?XY|D9vOYOj^jvxHPAylS5l8th0&QKgIZF{U%CVkJ1MCZz{Y%dXH5Si-J9-0!Jq)D zc5ybk7w_jIEooLja2lEbp`@!#{%Z|{AeMczn!6x*7EGe+LR@>VueWjSb?-lyjO;5| zJEu_&E7^Novo|C5K;Pj6(T}V^z-lu#WPnXHlb3P)G2c;|g_o9cYm-o4;KTImha~Uy z*LSs@-|ql?%}xwQSJ$Yi$@mE{OD){8O?AIg(x9BH%@O%uHXwc{GI^=%%?1h3()H$o z@xLPy-$KG8^4PI9z$$4|>v;v4UQQFhsrqtEQdV~eq?7?Q5+DcTxX9<2Gsd^{xl8p! zJJ*Ks$4TQvoUf`y2j~~kx2)Su^p^%>R6F6 z;u;|gMgbf11Dxs*kgwrN8Q_{Votxr1S0Z~#7r+!GK^|vE8~_0L8VBILyLczMb*mc2 zEUl|gHGorUpSg&xbdltA59Ci>PqS)mBM2|j>Z95IIi_&XI<)Z3nmR@Wng7(sC=np^ z&Pfu*B)reU!E0u^MgYiQu0xF%w)g-kO@JvH0H`pGO9#LDf?K`+7Wpse0C4trUrzM? zBhmXWzhfZJ{?ARqEUCfA##fgH5|jy=&DX@H{v}#*tP((XGL{u$pnaz$PeYk9G zXjT!r3JUy!o3zFtrZS}^gN)#PG8WXz5;>zUPsVHjr}zO5G5dgf$ z0r;w21ORgfK`aG<*0up|YcNI12tfdRW;v(O{j^{j#fxG}L1kru!jR`Fb2@|rd5D>5Ag#v07K!|Ci2<6FP!29Oj%m@PM z^+y%xXwHdGwcCD6kC%2F#0c;^3IM*!0eIKe-{!5i7K0e)9%E@iilugGR^B$E$*t22 zG6aDj;XI50?` zIT1O*DKfTN%~qL0pq;aRqp=q&(yy^;ym}rhgm5%MTo8~`GQj%QAS5}v8TLK5o_)dd zYgzs^d#AMQJxWYEpSk7RyBvV~xBV`@`!Gu?K;3OOZ#Z<1{CG~ys%nh)Rk35qiY-3=UZaU{NBVhz39`QlW`R zY)J_qw`Tpsy-?!FrzWbdA|Rm(pE=`p)d9G&b*MM;VRkvNWLug1oJ=^j2`Ca(ThhO+ zAnaHh-WmwxB4dJCL3tGMnmo}k6|a{qd~Z>hHzK(kNcCboDp&>qP)vVP*=bC!7J~gB zs0~usHpfOwT|=R9>NNqTPDv{z#HI%SjDji>WG>~TTa0V|h)hf*Bjbm&6&7XNpG0%a zYe6>J#hKsdtZ%MF*-{(=N+4wNq_qlQpfRFs19NCI7A0OaPQ(NtWP}OGzn5$~2bvknBjXt|?(d9F4*KwGjOj)GD_>=j;dT7&0hm z5o(h-B?ja$<_Y8w%cX|6m7oQ+4tN`=^OU&*!Dre{0C33xINbW%M1}w@8AFM|qZyGQOU~d-jPBFD{}J&eP`Y)pmVj5v zL!m@)T%uo5ybaZ!OLYgOHpmF&P%Xq>)&BJhYh-{Or$?dZ*5MRqU)$+5r%Yhk_NkktZ--9#mYoFveuA+%txP|5PJYSbD*=vSA*igL1+2#kx{nEZfRU64E@j~J0;+$d zMs{kJl|vB7dLr4Gp6&!-4Nn`YbCL(ZF4V9mC2SM$17h`YjLQJpv3YsFV(8B$K?Rpf z!2d%>fI1f~X0%2Hcudd}pPh{sKj%UOU^I%E#)Fndqnwc+vSg9{Qp$M@yAva~HcM;! zxqXt${9|f+WkAi!H1n3d+14_q;7EA67gs(+HeW{?PD(DkUqfb>{kKLJvmThq)16g0Y)lon1B zya$tTsp|hwz5g*GrIC|2^c5_|y)4BL;?HN;q&>q*6@n%g6IP#tE8et zn4D2Ia1lWzCN&{+{1n!VWL6l--km{OlPY$NsP#)8 zfLF4>R!g8fFTHLWK~$7zS&k$)XY!dQ`DtB(k<*LtD722k;IM_$mo0%>V*!zI%}~D+ z4DW=5X2Aqxiwf6yP1&k;Rsd)AXG{f9Li=O{1cQpGX8J4Cz!gM8lyKTkp`S!Fs}rCE z6)4r^pPB^D<`SH#0KogLp43uh0*~ct{5d2+Mwt@L1j4C+K@`ers-MaLdYGRR1A*8e z2%0b7PY^wG-bDV$5SW{t*V(7hB898*HzH0504!seMP4~Au)eR688$zgJ$A-hL zApuTZv$;J<#&zn?I49IY*0oVei!Q>wKy2)<3gq;6&zaAlx5yGLfT=*0XB>|*fwZL* zD*ql3KvuDm%<>aiy%*gB)`e<0v~@m{)u;S|FL=EbTzE{IGqW@TU0hD^yJ5iHmAX4q z>h@DIpIQRd)J4^Dy}|Gbj$-@Vhv~6a=O2(F95CXC30%#N>pi3A1`L>2!@6@1{nMYa#{keMezRq&Mjz)S1GW&Ot z5zB~8{i6u{uMwNfcT9m)UEht|A^M9svr#p-4~(YT;F;Ut^ms zCtl+#9Oiexpdip#%6IK*y_`w_FjstG3BBmpd%19*o(=t`e>+2{80X;5o-CHP}F08hFqHfX&cn6$G$y=CT`rl@sgh*i0d; ztvdi96zufcXvkjYQ?$!VjhTyFyOF$fK)SQL0AvtgIecfe^|`QZl4J6}2s;vVSppPi z+1H>|rG~nuWw7Sh=9*@^+!x#>_O{2S*WV*DJP7uXGH(zArQOP{y;{18H`Z#c+U3*{ z@F{=b7dqy?`QlU0;7jzA=hQXl*8F3Ht6B-k+#+K`(aV|?M|#s6A{`PTAhu;hM^NAn zGMlA1069b-thpO*1T8BcTPA&QXW7LD6+}`W|n6V_iF2w zCF+6_I%fmT=re!)8TE8T80>%D8tBx}AnSjx<3L8;KlK?>>v=9=j|$hP&Zeix59@NC zZW-3kk-8yT8-$DmzzTvqk6)K^!b|y&ni9es|FNwk_p}26w~ue=GLX*_ux9Dv((k*J zPz4oyev32+!+{5~oV-B-q62}HC6UiB5&5woKjwK3TPMD`DQ!yx1m<^0;uQP8 zarU(gbQp910f7c2oHV~N5p z=O1q&A>DQ>`V_9fT^j%_bunuqxNUzx5>{{_K^w`E+D?qu;A4@zg6#S!fKH(TJ>2h%%zzaXNC;AXU?bL25&RtZhU2%y#O2wB&~6PfZ4GB*`v< z064cUwKz%q>jMd+_rJeRY?Mf1x(Rx1IjYNyr=nI%^HvrKEMhHuv5BCF=mYRrv2@@U zM8wF<_gpBMT*~3)O3G(>ME%Y`TW#NG3B;0f@TC!6j4sn0McMz(n3ceh-hXH!r3wNhIx_msU~~b@43g#q znWu4!bpdI{zs}e>lf7HB2bjk{{T2h9r5&6*rSmzBvcX=G088X}T|q`&dQn{g8c>1& zc+^jyN*UndK_C5;En|jvFdT^E5TGUrSxn-X<1aCO#o2d75Hcs5vPQki&ldfTl(-Bi z4&SGy*l{F_jf&+Mjz5qEjtmH5V?app)8H)%2;86%dRiy$Y7 zQNS`-vz&Qy0_4oV^t7CPP3eN!iU7s~ezXoi>MIM_lSz37j_m~_LI<42_LUu@*uRI`$C zro06sH-%GBVjwL|cj?@;piMvZ&J^mAWIb8iH~0ad$aFv<3F#t(f}A3@)l!cqw$*G2|MrP9B1iK%nf{E|Ets{Bl(ojn_Yx8VDjT|TNi zAoV#*)?+UNyab|kS`Kijz~oGJ0hC5?HCcP4io#_k_;%bOzC7&<7ss^3pZ~4 zlmD9z0@uVq(6|OaBGxUiuWHoNMw$q)g_KB5N+g%KZVAN1dTk)egirxcPJfIBgr|(x zb7H?NanM|cg5F=j&@ffcr`UV&*fxbo(zMTYT7B|MlEQ0z%`UV4Qw;$02ThSOmKe8N zJK(`pUZh@Zr9_1-bv(=2ml7B`Q}5S(`)n8rPR#CRlo6(u?Au>L42UX%1Zxaytn5J) z4h@ps0Z!Gc8)G(rtbuENfEeuWC_lu;g6Ie6zCh4NuIVU0B0~Q2>N@ntkX!&A7*Pb~ zxA+q}Akssdw(ZQf9V<-X(r^x~iwSsXA+eYWptcJ<9`l>Z?p-3&-vUP+h0HE|FVo_kNn9x$^RI<=(5snxHFEcVr4%iviLIwmRCk95S%9?0MG2>Ct z_CuL#UD_!?8DwFKvCftZR3Erj;tOi-6b1nVH48wM09e(vEd+of)A11D16Y~>w1APS z(J60@>o?*-I1+OJIF$X>2!he~Ka@jU1aVQ{7aQ%7G^0aeiQ!bcKn=)3>I`&{Bhdpz zq6k)EV=07EJNT0MCRWn3tu-67+pZ7+D|sBKLz-PCX+5(6K&hf$%!&I-h^Qu_^2Yh) zGV+`d%Mzj$B%nB}_OI)3mJ`?00iAaKT<1tFeeqO=Jtf1c60L zCIH8>_sGy7&3-2_>elL;_c~wzOtyRMS`(FN0$ADq=$HxVT@GOR^*mK3@IjGtbg3wt#rRE`<;&;u819paL4Ut&&GB*rbJ~fg zAjT3-z?3X!P9`>s$xRXABc;=CI)9;FN0HjL{O?6JY__x&WU!^P?rZi*jzlL3coHpf zeG8zYF@!JMBaQ5zMD}mh{7LB}a0c1A3w?Uki?d`|WfvG@8)2aYY#;)ok>e4RkU4MP zEQeRpLcLAJ%NqVyElTVsy!Olm0HrXBn{lj?F_23}a;^|&ni7pt21c`L)vQH1RFp1* z05SrFq+#Ouqf}9tsV`wnOLz)a86ap&v=sQHV~nu&EtNkMT|m?{ILGy%2& zr?S!eFMn?WM1sTmy|a-IRP_EkqxWCFzn=bQ1p>LjKf7@Dr9xVzt*o&pTEVNCASnA_ zO%&9}vv@;Zom(scqJ;gdsl&T1tnt))EvaI=55d5xj{`1c%}_YLk^PL5pV9?T3+4Ek=O{O?D zV0~lIkFyW}XY&B8V1SXx`YFH8=9ZP_s8}u~cEzR7;dr1wQUx^n{GCD`&B=fXNplND zoubp?Cnkoz!0vBTt*^G!@s30Q+(Pe>Ypa;jlj#=-GE&zhfYBl{z!GMQDSgQ@fESND zISw=;{^5;wpVI7Q7yf9@IN;>Dt1^Jo&)02dij2pUc8nv(=Zto@NWGhtTp=T`2{6Yv z#jcN|@#tR@6-oS*Q~X>688JH~fU#_&=>2yV{BL}QrR95h#u^MrcryYKXWZ|#Q#NLU zcvk43LIIe^wqevoq&}Y{DUVP{*g|k~+O)UjjF!365>4;1p~pA8!$98RD{D|0yCoO9utC!`E@fKud=*Dg!7{AZJMi zI~xfIN(3&{0l-8}fd2lrTNGhCDsKk)WR4*(s25H-=!}pY2CMGx#>j}pn=t$T5%2wX z6^@x_tPM*yqJ%?3iRbWyb4Vcz(Rm$edZ^+5gDhX#k<=hCnQ-_DJLo4QT%{TPR2x6_ ze5SnLQs}~669Ckn)ZCNwGv7Z2)##e4&!8PCIbawWzGXZl^hQYZ=yLeGE)yg(rIyU29D zA=7Wjye`g|l>oQaO>l|N53zt~vim>`lfx-cjK@lfQu ztz8!saqhVQB6U2DIOCd*0E(axaxahGQ|kN6zu6G{;W6uFg~QJ-e{}AIu}jI5wtoqm&DXqMsQ? zCN2V-s1w%$NUHbW()?!?e3;*VMi8lHGGK`iR&5!9XFeXYH5@r9sRBruVJ@AsW`kkrKbb2IO&8 zpi2OwK_3?R$ND>G+<1lsn|Z62{GDicH7cO9-~Ks`MhYVAb8rAh!uARoJI_uIPw z!0+&bxdqQlM~ylwcbNq|Ir~&utcK$VLI=Qz)@2sJ7^?)1buZA<>CO z1_9k?V5tpDs=luk04d}E#IB>YL2?1l2(g$DSXv~%ZF5~pq^+6L?-6Us^@a_w#sSMf zfDN_37|P%Ze?Q6C@zM!>R<>Q*5~yFZw1091h`?zC0|z*xw{4AebQ$MvEvi%yP^6w` z$a#_2z}g5f#sh(5AVA8B#R3BJy-&f(Pl4@o6AWv$4rFF&%|<7qCD5s?xOaq2*WS@P z&sI4^PlPwo;3gm7O|1XF-85(&;pBkOvcm}oEeM_*S`b=V|IPp$GaNG-_UqsM=J%%i zz40CwPg-0!Il)DLf(s`nXuQwD#EouARAr7vsQbZ5Er4(fi*A4@~0`&J4RN zAlH$?NSCl;WWCh7ELl^j?B5rzMNXgx9Jv5m_Lq3f6B^AG;qiJW9*-P%tTJ3q+cl@0 zP;9@}D)&u=*{3FqOC^@4m5J|l05G?&-f!VO{T_U2x}Uc12C(2m5(7ViGC)DvoO7VN z$mr*24rsC!M_|}k#uy+h>TlhW=DX`F9s3X%Xh%uFF%w>S>jGYR<04+aeSkNQ8@zFH zfHzJKaEBZF!xUb0PXrI(1Wg9T;UIV+W?VQq!6ko!3nwiu`4e0|ImXrFJGj)I434)k zkb&4#LyP36b^qDL$&SHi?}dMqb^>S6fRe#LWCR1TbyBM;m_iAtAP6blJH=Z&@2!I7+Ml8;fi?KG#<^_Tco^ftmXvnn@8&m-~L<>N`i6>mYc>&jN zUcgJQU&PCAT)=B@AD%9>VmM1C!kr6;xP!y7#y_{XdVB}>p4`E`$9Hh`oiUBa;7(m1F|>#bbRgA|n_D{+iQ~8+yN~UO*ke|oe!o^7Z(2D= z?R+SM|JfiJ0=8z!V8=MWCw=03TtdaQbkuK233twJpkb^%{`V?UhzoZz5!4zFLjgj;k8FFJr6;{vz1+8*P9liPUr@!Pns zy%VtbHzR?^?0?CBfPntlg@cgHiHLG;f;v~G{Gh6wi3|E(avl=}tHHVtkbHh9Bb z#PiL4xQG_-K7JeTZr{d(?QI-5ULybvtrwlMyCPWX1t@6W8Qm5E1%cApHYiTk5twc_Te@EC-S#+?!9`TPqhRqj)1nuwF7)oPB~ZeuT3yZ z)cR`18K(jSkL`3DoS6V%uA}yQdTuiFo)XY-1iI($xTA5v$_Vrek2^^4Kp+BC;@oE- z0=aCy)F77{MHFOxgtFEzdEcA29sc;G%lP9LF5^otUdCH@nlrO^#$-BH&X$EU?neF+ zqje6iT)d30xXUV2mNi_%f~?}Adm-EsDY&{*rNy~h*QpKmORJG*fmOI z1{@1y?>RIprO=tvD(LT|E|WJ0_e3TFO5(8`8rBB{wVDVyM_J++OBD5L60#)}`f{Ja z7rgJTeAk%`0Cw%rYZg~Sv?gGQ)~pn+WDFY(T9M(o5 z#!JwOVYNePin<{(**_sMA0dMp4sp9V#Px%Fa2YMW>f|ka^~u|~L~V3(qVD~dnf|E#P@@17l>SO|`)hb2 zHK-I&AvavGIXoMgy+j7oR8TT>0L-jEV(&OIO5EDhGV3LEA9pgqjJg>DOCYbcQ~11a znsc;fPK)&SjtT($LeEJXN~$KQXh-glmeB3te^Q%wFo42}wSvt{D&n`CU4N_w7|jdd zh_$l@6wR4JH>EebHtUOJSE*z7!Oja`ImF{n-HYFQ=AJW=>8IfTG2G9gqYW~2j))s^ z_&fk7hYr^--Gf(~E4YvE;N2&0;{ksM2gJ#F2vYaoQRqCVK>By;s9&qEoeI}YYHgQx zuA~A7GBR!p!kgBh8(IE4C09HZwVNW(ht6GRHc6<&Gj=B`od%Nh7w{hkws`lXwPc;wps__u#@ z54=Bn5P!)&8L93`2h8JEzMgCsX7Rjf^Bd%iIh=*i5MIA<5pOmZaEV*I>-cTl-`)<1 zfanzk!(l|D?~dxebGgK8(I<}Bt&})WsrZ!D#+U32hz$Ml#)phv532;A$^kmN*Po$b zp+p+7f1Dr?F5xJbsL$8>uoA_=rOy4#1OVH~QmDwdVc{>X=F5xK@=FYl-1L1fFX{OJ z9M%CXEr5|B(gwnU7Bg0h3yfgN=gieRcG|Z z-#PN1g#5$bH?Y5RywU$3h<#l!&^dF5&3!|O;Gn@P7q8$ZF5!XWw{ic;9RNH+0+4X{ z`U~gG8Y&@4s964TuX6$|Ff{;JjU>;Uk3%?4I_IE$ zBJ*CtUe8ScYh{NmAFvMr!CnLaHP8(g7M%SKE~-O(hIQ&obs|ddZDfma27fFKQ32M+ z>N9F3KxYj7@Z$JXp$0hB*iC(H%<$?VFr~z+{xwg&bP50Z3-{xxmoDS{X6XMLsK9W& zrQ@efjeawtn1ew;>+GKgaiE?rAjbo9<8b5f3U1*N?mxbb`%Z475w$ALWc|7I#0Aeu zaY}0cE(uJ1#q)D}Q_uV0ShkNb0gzbn1|s%A%Kqwhzu4<)NBYJRwW;OelGeK^j(=(y zVTtG5l>vYiEx9TKMMYm?N{nHtpk>P9P7TG7#wm5~&IlNIlWc8>fMS#PL59igBb|BR572cth9W09X81?U3=Cw|J; zQ6L%2gX6mI-g|K2!XaL}`4zm*7jfUo9o&C>8x8x-On^ur;A5W&_Gs*>fochyKZp_~ zruI$g6v(}|>5q-M0K{W7A*vm~q3U1mx|HnKDYn+lu-7@Uo?07j+4H-N2)OeAAl3tU zP+`OxwZls%_$`S5xB@uHMc}IvHNd`R)-%!N;tDc-wjH;F>hbMc099rnTdllxTm%1P z#0h{?QGlr=z}jOy`^rW9^T!^<^Iv&q6Z=w`|N44w;C~VeLihf!oj3<||Ml}a$1ml7 zUC#^PPWg{V0mOHG*MEY4|3m*Cz#dON`A7IyzwsM*y}5`tE?vNVx8KGCcWxJnJ;BI? zW6sDI2BWp(r-x&aYLQK#5;eiHB^DH98YcM8vpc(TFQCF`DS>l4uf>m>5m;7Y(mOqee~gYNDc|vI-Fx_GJdf zVVPm}{ce44pQ_&<-KV>%PF0;cTX)|(oX-cQ@9E`iRo~^Q@AG_5#PP=;hdb`L4VyP@ z!r^iW6MaR@t4*O=pV1DCBy}QDK5J^VOAX^T(6NvwKM!5D9_UYDncM0T0C=z?pb6dQ zToD|z6aXe^9iA5DFU?Hdw49J8wBM;3fUas0u$U*+>613m2a^^Ont~E_34rsUkOwqq zL5usV3IH{~#0(jcrvw(Zs(%$Z0Mbzcj6#))@>_KV-1hh&K6m3XJoI!maHMtX*W=TF z`!}dmDs$So$Kw7IJ@;SR^hd+}_k#h++0*8{X$B1CQXj6q`YK%hn#jdRH5 z2+lk2CAjMKS7B&y2rNgjufHF=DiuuUXs{0ii8dB_!m$?hyBzM5CJL0U%h0tkzEJz4 z@&9sgprQg?(H$`a21%WMr+iJCc6+vNJ)r{-N5Fw9nYhXT2!nZ8$ZxyoN&5&uT$#zJ zoK;T)^Qr6kYH<$H75s>kl?SVkRcMcEj-w(2DjE_+94HPWLYHBw+hfm_@uLSuuz#!& zcyC}}0Dtp$pTQYx)&Ky0^wXR0FJJmH{_ns1%kxlx5*(=9f2iDl63+dnLM;Gk1`MlD zUX9CMaVc_n0>T+M;ULZSK}30~)#?CV`?}X*>((u}<>x;~EuY7Z$^ZuH(-@wb0?p9l zWQrF#AOf750|Nr{8o%_~g$F5Q{Y1&?iIR*E1hxOOa@@V7oHy6DuH4h8Yw|P%&apLw zFjanrK}%r)N>uuX?sq)Pq|b%e3JHo6CaGWK#}tPyI(nkWg^GWJ2ebXK?qD!v1!-kX zw$JFG*h7;!eD$`4_{JTJw9fw>@4OZ_-t?1}^9KM*DSq$mZ^M88@CP{c^wZ{C_V42G zC0;)SB|WZb;seCu{!;?v^Etf!>eu5{m%j?RT(kVOIS2#JU+fFOIZim?M7;T}Z^5dQ zP68+}RxIMVfdPyc3fbMiP6P%PL_pVzLmM|3>Vcr?3`i`LK!mjLrHb82a&N+hW~aL4 zv5Y_4tEd6(g@cA==u9zYy7!2oFwjQTpa~vs(i>-z5HO`T+J}%S+NP<5ac#tcO zTU8F^qEqK?6F5o!6}7Lssp$v@@aXn_{Ns<8;)$L8f$yF5f*0UNKfMXR_2xGl>6@dY zqxkam*W;6a@fXPD=5B)@8~0y+-^~4|A>4ne;r^E^CA{~2@5QmlE=O19N@{q4{!<>f94iZ5< zl^GDw0Z_kW;T!SDRe z@1QzRk*ZN;v7ZJCAP!u{^bddj{Q0=%x30mwc_RRT$wC1;s#VmKz%A+GK$`B!iNQdU zAA`7_gQh*ODZub;Oxj^Fi|}_^Rr^_)c~;{}C+y<4_Cy?A(8%aQ{a}hVlFFdW*G{_ z0`mEs90WjF0#Mrm`^bcXP~>uXyyl8u$7yGr-pEcV#qLTK2TLVmGoMbQ0I`CMB$c(g z44mr={Q|Z?$oio{0=bw-h$v#nl0cN=K$psT*JP^KH`?X%&G*tx&^hn0;K2m1Pz}=H z(hGoD&L>O+BF=T@G}*jb@TWURF~g_Ag!H)Fw_BXJlxvIw7rF~tbi+-8J5k|#n*nrD zUlWhdIB@&pL-^Sv!(d8l-v0i6{L5Fqg5!=qF7f#M^((Ky@bEDHWEKYkVVS?5dlLx- z5aoIm?!QpXCvpFUr2wD%zi9Cy{Qf(B4}(J$Fy3IDmr8w@oSX)$HwG~$ejv~|4+;*> zd8?Q-Y<#`&g)hXh%a`LvH{J*ijw5}2n9AodGBJSyW6rw&Niymrb0Dm&FWpOp@P7?+5F&*U`44Yw}BO%Klt zIR7oX{ty*Lq_rK-Pg*SBE|8~J2mz;hp7$Cs$ z#~p`vzx$mS92x-OycsYwf`(G5)L0@=*8ho^0h9Lf9tml$w|L1CTz$1By0pbQwN)NJB4;3^=nC`>HhpX+k120uo@FiS-1>PGh+tDtg^Z z$#+){8g+d>E&gU&1%E8z!;TXGQOn#Dvw0B$B-~)A6kf3IJGRM9kls40OCYg-ERTQw z*%EBk==cBb+unvRe(9f4C>B#0tA#?LRX6a)H@yj;`Rv~#pPwB)wBmCAMW^3_@+l-K zUy=Loe^2H9pK{u2T>I{8QLglZa1Ksb?roY%p@noga8gSDJIr4)U|BBpAS0q#Fz?phM^br76=&N*1HU;*y9 z{dRx=hf5{QP>SKn$y93rNW$-OhxuR)uz7e9txge;8-fKf5MU-5U>+_53?5w{AWEgY ztCMCLo6a15Gimc7*K0E^Idm#u=m{l27mmIwO5j@Q872_KBA$;6T;-}ozyui1pLfiF zHFB=+H_kN^lEBRUr6CxmQ6&r(EfXHyAlgpjk!Snym75m@od1a{R^UfB{UlZAPYA)# z(2y4Lf6000;j91lH4G0A_jLB};_y@D{x|3@QYSA?1s376fnc22r<952 ztLp?vGzo%)T5!9>wzejKo9#suUl0X?cgghQByJ4pGX|K!KSl9@b0z>_ArV;;*~T~n zh^BC3($ImnH6>B2Xm!w=58ZJtOiB^i2JZd73x28-8y{p+uy>X#fy7Bqt|8l^1XNA{_}9| zKjj!4tm60H{x;+&Z9@Gmtxj;p8g&4z0>5SuK!8%I54l{<$o)5sr7(ox#1$)W!G#yL z{+`a~v9~e+j}j+DDn=Y@{2sB4ap(EM4Nlgk$Dzz+rUVN*0U(aHrU(qvvS#dR4#Orq%tfB%Sfj&Us zuq+D*v!8H%=FLN#yNiJgYiMFM5*+yMJ)`*PL-PWr@x9l*57)i_{i%=3N~MB6hc)As zt5)G#|M49xS+WGPZHvzRZ~se$B0zE7KcA!c?ce(y49^<^2Lo$D{hV`vv1Zpm-|5Q? z1I8Q70Me)mG;;srY{wmcJTAKU;@02w9L3(s0A?&gPiZt2ii2_iZgq90n!RD!;)sy52h7e-o4!=oE-xkicU`03_bmz&Y^W_l#nrhTs2(ANV6&b@kP$kC{@b z6sa-LvSrKg`7eF}g$`AkJ7t~`ivMif|6p?;;cs!>zlj>W`Zce{X{Vk70tQMN**{~= z*QA~Gi%S5kNdY9y6DXDYg7810!~g2GeED)*`tp~z=NKi}SE*no7u^aG$1&{OE;!_S zB*NrTQ2{Ol_|g&pmzN7o8UV(A0X*XQYV51446sW=m9~_xNkEP|p0AB!&d@u(4Z6An zkj06zgoA`e93-xhqV9qFt&Lp+5mnWX8sIi{0#mLu4dHU6ugfa9=Dw4dYx-eXzv**7 zynh7uJr#8RfAWzJy3Z+O_z|$3BYLxN&f0tzXam_pAOJdj341 z{I~YZHF)LauK;fr`Ejsj!ypdE8~JGC`^_Z)Cd>cwrWg<<6#0BH5Ugk&O+;7@k!1h? zAOJ~3K~!X5$hjA|A1i** zUtC@CH(WGbWmHsc*FMA0UDDkk-5pYr(v5_4cgN5jN=cWr64D^jAt0TCQqnEWz|1$# zx7Pb>e$K3O?z#8g*S^9Vr~W?WrG3mi-b>gOZFL%wRs_z>5qNX{nXS>XKji6bEvzAz zLb&!>%zm>#Wj%ajtGx@>6l(xDI|71juSx+`l^_~}6%sBJ&J&F5)OaCF-OY=VAWUV_GtRS*|CMG@|WePqvu^NmTJO|(S4sN zzc;ZWK{%0#98`Pdbj8C-X%K-KMRNQZ9PdR$K4FM2HjqqKWgsuv;z^1=Xq-f{xuTfi zEdNZy-AdJvk9aPAoah~5&Uu^n!-$HCN`3ule_HxMr69!1KNziJ03LSR@V$WnlsVSe zJO0c=@-JXH_)mgr#k$eme#!~$CgEK%-%=!etrrCZbH78OLq2$U)2fAjNfn5hG<8MF z**9U~nTwA+gSe4i6}qnnME9i}jcZ(um-a1Pn_GXZHdDt3YS6VZiQQk2I!`gzVDlz9 zn$|G)ml0^KR14GMjtZ7`aTg!9yeqo-YNxFbZFU%2KTK@Owc8(41n zw~G|QY6t!9L^s=t^x`erBlsdf^Y@K+?}}i2K5Z`P3VgZv!Svhp02V*Fe1*=BnuFc zfB~DP1$5+0AcTJm?sz4EJCi8yLM62FjmfLq%7jasu)iZxN}CSh^|Tov4ZJ2d@H|C< zAV&#~r{J0j5FxL9QsJ`YTqlfqxhgX+3WIoV(QC?lHupd-@APzjArt1o)Ci}QhKm(= zpLOOYSD10<)DB9Y-S1q86hEksniRy^!ddpoW1w%4Vj@y@KASZm@$_syp{}PXW%a_{ z$?l4q{dnmD%Ak)d{q%X#3?F)S>`Z@JGnuLgg3O?uA6;aUJl(89k%O9;FFs=c zW&gx76x=zZ@=165Gsu(}(vi_;sG5+mo%TxRU>&<9nvK2Xi)(GY@#pSTI4AI#I5@FA zqNC1Dsq63WUnsm$B<;m^4Ax#}G4sDIx`)J=^8U|djGEdCIZ8I&J(lCw@jKvAtIKlY92K3*&N9dgNH4} zfwR`c@@Z02W-Xq65x3f_=2Y?pHIRjhjP~0-Ls+(e`PU`IaC=x2ei6g$?LR}a*a?g0 zqsb91#NkW+?*AJK2Pmh+>#FwDUCv}|&SZek=G~}OMM3<)k^&IW{Mh^D25%|zY0(4z zyxq$y@(LY;bNUl`y|(IQ-pl&*TMbgokByud%v=FDqJ7+KscKt?N_|4o7%o}`H+};82r1Dr?Y;Y#I6;3rP9RcO}3{C2f z)qE7mnXvr@snO%u+RZU%In6(KY4xj=O)vo$qT#lDOX=hqAl7GcF=v2Y{?|gIFA*XK{-;aH+J32J8``oy@_m&Mn4*Eo{rKhYRze$@z zsirGSAl~!PGb<)ig<_U)pYRpi@&t?v764{5$M7$kWx{R9dUd)UUtppF7qB-=PDm zWLEVa@hzV4P)buY={bx3i+cjCk(4RXI^h-WZJD-RjQXbc>x5+07?6(W14lt%h?+QM zTI=8#*cQKK6Vl_pmTJYN!yJ`SB|NNU-q~JDeFXYmd{vQ8k{OpphpJa)7A+iE%M7qm zp!1GK{e+Ydfca`cgh>%!0|GPN|GG9KZZESt6)yQ!x_bfZOEx)p}Re%+t0FNv_5l z?wDWI;05RDx@VZArS6{&eN58F9jX*u-dJ!?His~#W&8bhIePk$@(FW{k< zSRGgO68*WPo3J<)_}*2ZtN(~#JC=pt`)hxx;?V#^LwFvlgDjiUi*e#mJe7K|9YDR! zYIjv|?5kjMyG#e3L=GOLPB)a9K|lUhTL+4-&7!R$1-!9|KQdAf49EH8vGXEwcLb^Z zMWQf-1{k9i2t~H&LD5XRHDq`R;h%l57slZt;f$mL^GzV+jUIbqT>&qWupbpUo68N~ z7+y4y&KrC&APNn)vqNqF?e&r-x0_cSZ1{x2p^3st50fnx)LBFB$Tz7 z{}QS@EKO{0r*#4(zH{0C5`pHj@QMTZ$;`t^Aqvppjt6114K(1)_|oY|0hAy5vD>2X z%gk$EPqcM~OjRyXEcPqUrt=lLR}nyp@PKNTH91nzCC3t=5E6v`i(EP;x4GfVezP8N zoMRGE+||E!wBj1ey02%wmim|L7w2N z)@YOk{z^64n4QG#g03h0`L28qjRKjBCr6u%QqA_Kg-DZoe+CFYXriziF;~cYuk0%+8csNP>R!zzkF-P-w)?><-M$ zJldy%1-YqgGp2}T2IaeA1I!qKA*&PiDeM(1%UoIcQ9q&NjCo>_KeWWqhqYdKxB9R* zTSZeXY;%gFFw}Uz2wwx*(StUi3nEb?s9e-F~ri7fddS zskV3vl?0ey;U~S(%U88DLy;rRChMiz%_xO8PfJt2H2gB|^G%;4 zYYa3}*n5w{-Fd+fauXYl^6s+ozP?c6vQ!2}C;hJ~Jh84JP=0hjSK!W~1f16a58Ibf z9XheF!;D zi3Df9EmUzGT32#%HVS{5=BIXk2zjd$?7bT1H~;2R!aeY%-}#@yak2ILLE7zZ_<48D zTSMZr-uu3754ht*&DHI`PnldUz{;wAl}IB8)SCNIc#jNFV=Dqige)+>;Y&UUPK+v;H>FBdi_eh2JAh#>Rkn0y#&;rB7^joc>yI)toW zLq*K6)EtH)NOwn^-=kVdrRK6^16KkXx7_uID&a4W<4jIHM6b~s$r8HTu`T!IjNi8E z)NJA`n`In|9v>ekG2KBA7}GZK8ld2So3nqn=3n8jaV=J=oS&lXW{XpyJhMpot37N? z$fbam0&%sD*_f5Z+Q#L&_X;9EAmW(OTmdv?)Xc&gHZoqZqUfPL9L+$j?6Iw$+x_LP zLkH`V@%-I;`C6Mh-0`1u8e4R+4*CyPZRtua&6U&nOabNy_cv8#2}%MW`-7Z~d3^oX zbW-5~=Yi-K%TK{*K1qP#+X`pr)8sevi{N9N+XBv7Yovq)0hmiju z77gzZ<)_&yY5IBpg8-{eYp=x*9@58Fhp-RQfJw0&-EkcDm97~2>{mj-Wj(;1iRHCH z57iLgC%-bAOly9{>weTbqKPMgN50}{32YB6Ew8=${+wzOg)B z`tlJgKMQ)!2%c&1W1I`<0?-)#mXmB}oODQbbx zd2#yvzSI23Rk+2|O_jjz6BGnFZrlCAdRgcm>TDo-huEJcm{h$u(6$Ok#)9P?!@?G2 z5VifoU|ceCixEhZ20LIg0?PQamy;3&goAnbqpE~0ccd+$*D*vp!`hA)TA6Pza_MOr} zI6nT*lcjDYgL4pWy}(3P+C$t(|u0L?R5>RPwr8+>F{}u#UUdn|KSsorCRZN z6;9fF5i<$?;AR6e+kTwLZBK!o%RetvL32CcKf^03kU;tlJtR>`3~0Oxflc1~nhQ>6 zd2nMMFwmDL0X6&-V67->#uCSEloZFol2Z)B<;!%d9og`VF@v;(FYZwzPK3zv8QWoVpw5*NGJyldc1;6X}dw(aVFoR ztZNlCNz`BZDtO-4uH?wHljF4^i)e@Ej(o3ni0qm+=$WRbJu~y}=<)k0r46>^*o@ar z`N7$H$}misbLY5|d~~}^9iH|tJ=ls3^B=ahFlm{u9X|ZB)oxr>PjKe^iZKm$|&8RHN&^P*s`BPAK6V?vj zAykv)f@N5SQ9nX%zd~?uR(iuN1SwxR?r{~7@)#4!KH2>Xoc#M$pgaOwYVyu0`yW!V zp`NV%eX)0_zjj#*8SXJ|EfImL)AWAcU+&Y{t0RnR#?L~Zj{O}}NsmqZEsy>Y@0PA; z5&XV+JuGf!Va9YAHOR@w@!ejRv)LBVohN>ChE1|%9&}idn_6SW9Ve}3Q0YdrL)78E zGh&?atn76^AWeN&jK0v5y;F7S8x9ZkRFSR3OmvNkS`VLTyKabTOKZ{4)Es#g_7$fR zeW{4tre2*S(VUWKsK3*Tngd7~BNdsC5HtLRrW}y%C|3H`tfA;MUdb>CC?|>hsQJr) z7C(!2oPlat1hevPAfVD}-%>(&pM$V82Ta0R{pqK>4v9P^%K|-k+nrE@c&m3EF(@Wa zRBml2@hI^{dY>uWBJ$y3?v;hRU&ju7!zH}ot(j7^x69t}gjndCyeOS7_wI+&gc{Eg zj8Av~5fM>SWbbG8h^m4hRo<1K_TQfRej0ysOsy-XUngvNnHV{bZ#B%4;-ecQ zwaCJ|KYphYU#TZE%k(%DAE(;vi-*lt)rZ3g?`Xx(dS9)2Z^Cp-4P_ykY<86jc69h^ z_>4tQlh=ZhJVEA4OA~;&tqPAaUq<=ZH$Man(IH%gUp*ukEq`d*+f6ngtJF@Yir$`d;B9{MI8`=&h) zP~3$Pp`G<|r8~l8G=+=LW%Xf+Suep*mMavI)=BIgBwc?G+Ada9!8eqD>mpF_a*+1S zESeYCSr@RzgQn2{cB$8S+x~+NzE&cGH_+{1&Rt54fQxo14Nuy6b+gJJurFR=>(=Jc zB?C5_!lwsuqksK3p19stt!*oMWm48P2Jly$r|Cfb+KZr@3hk#2OaP4Wf0Cf|yrwWQdmlE|&K_=Qhw{UM_w} zc75zIw1EHmnG3N?Fkkc62VaX&KlQpjKdO(abLS*wJyT2icxbBIFy7IAok@*G*8<;J zj9t%e!{m?f*}UMx+cGtW3JBDxtGu_D@JiYSA!p+kr&uwZ)?Uy>mkv<1AoP_kA%-~( zhYON_tFe<^griOR0)mxaye~nXQPCZ8vSwQ))hQbYR7KW*$LuBC_%-S@159cCOp9|j zuDlF?$VMRxqoI{FZ=i?d-0%>?t?qUigg);Z$<-a18?ZDU9?F-5g@%R}{HJqt3r1wU z57Tueu$-?MM7(J1i{2V~{1fBedxOqiDrlIkIhqzHN$cxD8%)k+hMMcB9j0DR5FtXCz3|dqkrq9p(o7(v;v-B$n5NcH||nKB3uu_ zcR1bHhG7W-I#3JByJDwEY!7l@eh+wpJRJfM*e=2WfX!$kL*&~O1eg{ps^l zfX?@61w(9RCQN_$>Um0DHCP(%ovbX_O|P4q(Vd1F;qayt3R#-r+O1 z4BNgio~0)`6ZX%wWc9iFt++Yim%4uW5WQ-Y7;r05JFmg9lD)UzwUr#)RUa|@ErDgM zNIYyh`-g5+L?t;Ywy=$Cp|2RIY{#mFeh}NfwmeSinbiMWl8iIfq}$K+jU60lv^o4> zoH2XMAM$`;lf$4wK&D9tJQKmdQ;vW?f6-#Iy!>D@&3r5lLCNcTuZ@&eX$GCXNoiOA z8Q$Ve@{bU5HmY7`v0egm{P6H%H_Dto@W$7y>hCWy?ga3{V!kg!yeJ14_>IvexRG%6 z-O;kTKl=Et+>dVIS$b>l#}*oA0oqMpCoWiz4E&|t$E#1cD}Cf3UJA*N8WR;jtX>XQ zflU;Iubwi;s00DkSl1K(&*|}Jce^ud=!UMQnDHJg$pj$ifV13h2&be;!0v~pnY^U{ z@UBnZUNRe+e_F&zbp-#u%Z7G-2ue0UY~Sz;T&6;1W&;69G4`SMv&IY>$MHUs%S>{z_Pn+wVuc5yho7NF~I_1VeIw?uuy)k;n^D=%X7O!~w-<9vOamYIRDY zDt2b|ftrWOsB20)_H8lP)4o8F=I8?11(vl@s!+?kA)?3=Bt{uzP|jy!l@BP#9pIWN zlB3rWRGxSpRmX}>_8xx2`SHk`^C$PP1LNA*?zc}K|SKHO~{FajKKld zAGb9Nxm5gO0zov^1g_`>&AXn|%^_kW+$JAWe~yu@k}seF4_e58ClZJTM$;qhAxyl4zL2D9Mux2cn;9We zo#}{ZSv97=LFj^6J&2fz<5mDke>e|S{wVa1FE9GAeGklnlZDhWaIwzf_xoh+_MYoV ztVW;JxsS$fGqVQ)Hw+A$4O{KC@E2z?jvR1}=>?Cez=FpDmc0Es(k%4u~Fq zZk}tVCPxFnV6Y~-Y&K>DcK_bnXw#N&Lr39?s2^FgKn5pdvn%CnQy*eQIrcnggi|6! zzcX+o;|73{kD1D458E5X>p9GO%2Y$kHSK+}Zy1 zeebG_vm?_8J))NnYUmKLV3{L|7uU2Sddf64p*QhjpUM{-#jv!PZQ{i=56H+3nVxAD zNI)9xu(e{EyJ70wS|93vLH*qp$+__RIM~PMR-92li27UC=Wcj~{^>Im;ZJ*c8Hzw$ zx32}JWTlHG;}Zs+tB~%Bb#kHSa0zKKRNPqaDKW~bVowu!Fd!a5&s~nkR}rqPq~tlJ zHl?xLVM_&E8(tT(N^K34_dQ8+qBn-9m?NC(sSM!4?4VcWAp9{fXDCQAZlF)}ht|ST zXvW|heunMp$5l+|!Y+F3wbQhnB9LweQZyM&KFbU|*{LGH_E3YA7+lBM!Xpx`CPL?# zDRcz{1<+jiFC57q5d5%BTT+YlrX~chCb*#+7x4Id@qXsSV*dg`{Y#*{3%3sWb}6oW z@mOb(rqBAtg#^^r3HR87N%X;ot!cSKA1Z3nvL?ZhuW!GkV~}d3WB+USJQ)mlJfhTT zz`qRf?T2iQDg@DrtQ5Ame0Ig?L;fN#x)HDI`39$#cEk-`{zcbey1hA$Y&MRqc&NY! zR4_}BkYgFrkB)|S|KkD(GJ#8DVIXmQh*z^EemgRNi0!#+@4c_<9T-qd68AbYGz(-l zDE>wPcx+k*$VA$#!*6@>IS>i(Fp4wy&=47bIzr|Zm{?aICY5R&rmluOCSx%1LnR5e zo(tWuI2(P@>seu7A(KM)a^e>*(X-x*8?QEmE5@G|>=IR|s=MP5n5iP7P3x zK~Z3x&T5sn)dfSg0HNIHHTzZ?s8Qk1CFtDCd9HHQ)X32)x^()R;yYH&++Bhji2Vme~pESB=qb`uzFyPa`wGon81iwkXf2b`;@w;mOc zW^N=W13nfwEC3!;d6SfX&^g%pA90R7AIYmW>m|8=Y0N}(=faYNLyIj8(C?2Ih$^BaPxYqL0{?<4RmLqa4k&wDmw zc>3&R?mA^hf-g2B;&C-%pPxgy;tjU;k{+KCTo2SvI9DO?3mpjAf792O=K%Eeizzo5 z4L$7l5+ks%;cTuqmheA4EA8%hx91yZ({=6NROu}cEL(hodXogi?J+PjbFrRV6}vfO zi$SNlW|3pr-)sw0l>r=VAifjPpf!4Nf0_mMOX_du3qF`-_2hi=Uk$n@XRTOq&NUPv z9GE(32~Lu%J zZ+E1)s^i|aR8G&o7b_l1s3F@sw4*69_g0@bL~lEGE+X(?d~d^6ZGYY2p{oJ)7rn*L zMAg?aICsSyIFO;EQ1l~+H0I1Tn>CIsBf5i=2Rg%bvN2)Pk^bnpu?fDj zHaaaRBFy!7^NPWl1Fd<1fJQ&6e;IWH_s3FtkU~{tWI2MfbQO`$&@1|N*s9o3ak27* zHt50PM<8V$NIONf#Hpi$JA>{tZQq+pY<#QfmOOjBVzTI6RVEbCbwp3Y=Vi7KP{{D| z_ecHppewGAxw#n6e>OMi{yd!dZ)vy(Cg~sDSw=DgqzIJchXv7H@3Td>v}KpUAS^VP zSDb%6lhBP5)M$s_7r5h2i+2k~euWVmx8Dj*<#snt%?RqLFK!rTVv4n-JFLXP$Byx{ zE-f}eX8VOUa?@6BD>DSdI};}1Q%(^6ayoKwE+@h03R%u<8s&n`9|E6F%Tx0JI<6o~ zVl+)%n3Sn-iKdNGZUcDR^OIrbd+&vi*7)W-k!hT zt}Z@YO%0aH$9e4Fg~g-Mh5&2ru85s-(ct}%s6oA1LWV(c@*-=)$kAe5)6u7lf-d4m zH9)%)zAa7rVbSjp3o({~P=NQQfXvmkE*Z7?n#Xl?)P*0uY_t-g!X=>sIh)h1jG^!{ zAR7lL9AWjDyOzM5oeZ_>nTVp>onzw9S2}|$xoMJPN>H^;Zod+rIL>iAi{4t~94_nr zWA)|-;&+&K#FyBP-x!-0Pn>Ync8Vu0z zaf{ni2R7Xi(t?g7gv_J1&Z2ny+;yYvH~U}?Ezh#fLqP{Ty%Vm7z%%4l)9^P}U8grE zZ=3;{H8eEGB>+E?g<|G@jTxfd;JkzepfbgeVEDblbRYaE8IAT{oPeMtuL8-H3xaCC zDS5EzU6woRLnz41=xEm_82MAWFf&!N@L-mzhk1MvL}>J=k}cV${TN%_VA zsF2+E&=LxA%f+dK%=?bHmnF-hd-cQNlb0gu#c8Uf(}Wct5wiNZ{MBQS`rICg-aAn? zNq+cWBY&wz!DA%4G0r(S5BqsC(195|8qZ@1#P%^V$bS? zTi^1)lJqcO#4uI3mSTy^tZkFP<#i_g`rovc&>kQMVs?#PEot z;H=A1M}NLhj=7U8q;6y;nh~KaCpdY#l*;<~0WT%ctt&6O5t+wnVynE3vP2D~ns&CC z+df()NME^Uk2hLQkfKb7c4tdTDQ8QUTRuUr$;|BCPf-drV7txV-4E1fCJUUcb?{|R z0epzYYv4JDKeVNw4Q}!m*8Ux>vw!;5by}D|e5yY`2BOgN^s@q3zV^d?2tQeG5)ekh zKYGH^2t#@n*VBK~l63Q_#QYScqy#|jKl#lBcG>>iOm-{a`kQ!psoR(-TRSG^11gp` zAqYJx?a3vQg^bBDBraAV6Wi@~q56m1%6B!W5#%Hn9h;kq(hXzBSyLJiR4L~2nSY5w zNtqQ4y4ZhH_rbt~-?`MCftb^CXDHh;W(s#X%KOC_h-AQqE2AV?G+6*c; z#x6P+!b_-q+DxYs=6Zi~!zR265X=KEFJLoB*vwu;{UTy)&G6Rq1@(*`Tz8neg((<^ zzpv~k9sO+vg^LNr4_zwCqTPQAAIq!g$K*twU6%#ImtVzK6Lk^&8ejO==L=4aF{2ge zdj)fD3PeLF(<*o3XBgVqB32VW#9P5%E50TA-4iTQwBZ8k>v`V^hjiZatZXW8!UiXw zmltvFix5B4-c$|X_|V7YBl4yB_|;^#J}@9Qir_tX{1Hek7$gLm?r*e|0QxC!o!|ZR z6KKa>z`t}zbo2@`86oYf_zbWnnEIfih)2A8dWLdhO-diRx|>}PU*(?>k(4T(ETgn~ z&UBL?4JKa1Z^74`5k39sZ1>=;E3+Sff=z!57Y#1OP|c4Yh(+Pgy+tWW1no<6wU=Eq z-6qsjvp~(Km*lSaV<$Xwd;+l6-b$sOwpyN|CMzZb@4U^2Zb)+PoE5^mE{Enay-3<~ za~)4%NLSlwj;2GFQRwWmD!TOL1HHU6v?TIH3Vv&|Iat5gnLW_JOQOdl4eQ z-dLBPJnYqL<*gB3^ihM!tyjB7@$(U0`R;|a&Yc#wm`(iNyvvv`aV-d3!tgrVnr57va)wBn^Sv;Ki@bhaa88N=wIUYkBp1*H&*Uf__=C;p(#gHq3 zq&*lo3YeecB*xIKNetpNWy$-yKG^X|dO90J4wxsy%dZrcFDFlLOHV&mj96e{D*I+% z)5oV%^9~+h`rvlFRK<@0NKWT3qHg^OK2OMes9O(zeSE=>=m`)is0rBc5!_&DqaGvO z88h$rF?nljg&;YNw!gkmPMaPc^7#0uMO|Gsw}Fm}P-O6i!`PIRlo3*y1?_8=I>0i` zDo_mv(8IwkWQqHExkkmsCl)s7tn$hz>VROl%^@wqMw~_1RM7P6-sks`ny9E!RmiX3 z{Xw5s@F%d1r+hDjEZiaNTFwV1YYo6B8RhZvR;(N7Jo(sZ^?tf|{cQ$RO)r&Kgp7?q zPK!Hq)#NEFK$-R_hOCK9##`1D9 za_ZrOF}7Opq6@Z@($3#mWg1URpx_m?+r|qMqP`pSLZh)|4&sFa;Qlo&>2MhKhi!&! z(Ht`Bo;^51k4dMZi#$&V!9q$OQi!${5J!K_B0i~RHDyCc;?cyVyoBuw~*^tD>^O;xzz&q0DKTUJuB7TD)m|-^f3&H=?oU)MR zLV2FQxc^5&G3|m0R-@6`!};$luqlSr#S^_c*S*n!D@iJGpVtDTqW|bY%7j&p&iG7Iiq^6;f=!VMXvDcll~;>5j7>r> zRR!wKE3N;S{SK-}mHiUnpQVN^54a2m5YK)@hn@d@-f`kAs=3moGMd!;c}^~#gfCdo zT{RQ6;AX}`4Y1{c`9fkHEC+_=y*w8h+FU^PDJ*=BZmtijCs*lBO?;9~fdWM;y> z_Y%}B_ChpL9Hih4_i=(8E%Fo@I_nJmI(J#Sb+PjWv^J;qbuX*Uw+Wa!Qsr&EaI0d~c4k(;+@d$aI zM(Yl}@>sU#At)9^!}#$Mm}aaNfbd!VV*FUZAj&W2X%lG~lIZXpy0jiXV$_^%&3e{* zE)5_?vTr1>8hw5cV&mZ1XCqcWm7}!tXFM-KvL|ZGukTdbe(LR}mCW0bh;AIrbpGxq zqM1Il*KZXt%|Ue_J=1@~0B|x*kjOYkhMKc>Pdv(CVQ50`QNoC46?s2yg-&~MEfhbQ zHNdGqt`CHNDcXX3zO5Lf6T07HR~xMWHcdC4>{vh$=U5TMF@TVhkjcqIC09=bCy3YTT< z|3uq89`ay7^Zv4N*}TZ0ObcidPt@DogaNa^PT|66BP{46ER-NQM&1%peiaX!0&ABK zKA*KtHv7S%0T%#G@bzpgBlq3A!?_AkSoBVl3&I!2jJx&SVr6BE2E;4bF$b6%dwNzL z^qn#mU>$xq%1Ot!O%=ewEEyN`(CGKjiCb2|_@jx+WEHcgZA$6SQF8bi*gRr$#a(J` zEqBF$^cSMOrl!T@`wZV_T*s>vy(LFpzSG#(Kps^aKCmcXozC*@FQ~*oFJUL+{{bS> zknE4pS7VVqbFUeBcX#$1aq}J=AFEM=W?Z3n=c7{o8^3+-dxsd)B#|15ewsX6#%!%9 zMtYM{J%7-~t3B6Ff+I~?$fAEXroi|r^U1zuMkwJTuvbZeb#wkhD6?uv6$zZcvSggk z=+frCxLsiSl0C8sgHM)DR8p=2pigr3@uX zcD~CIl#~0=>ZvkfnmZbe-wBV5IX{OV_RId6y?`kWY5je!+ux=j{&Z7NzF^^M1rts6)#-K@wop z?L|>lq&50a(U;TWVC!SdDW`NwO8O?n=Ywxxbda`qd(AIUnG4TfW>AxH@8`(dC|%rdG_RH5^nnS>0b1!0bpQ3R;K9w^ zE76Bc-A3zSRfH-n)ojok!Q^g*PQ)(t<+_@AZv@W#fXel{v+9jbzcAfB#2>IMAXC74 zH7cC2$T^0Ra_VziuzTK&Yc8)0hOlYp9;a0D(Nd{Bfby~9T>}Rzs#-2Xnb+(SwO>?- z`#x?e{J>{omb1cuP-3Uxa?w%jc#O|sumktM40~gHACKQuy@QI5{W=l^DclCsn5&B{6#M}!Ys9YTN^6u%xBKm&V zDcqAi0t0HYUrzU?Qhj>QgdG%Hhdrntn~}Y2y5qlLmG~R#y%Gwso{pfpr7f*tK)DH` z#*?|mrwKbKRkCb9Au9;_RfFRvS8Dlu_NOPfbwV?f?+a3in#23Qb1sT75Qc}R+Z7!q zogeITenBvqci*M8QDU1omqo_4C})LpZnGjpFosfH;$F5;h87{}A|~cVV@n{z4D&$; zbm_v&jFIT_Fu3d<#gJ3bI^^8Vyx-R4r0m4uCAUa>u5qQ^L3G#~+NOkDb?a5O={_@#bZvM~NGquj5?#9%@=w zXHlW^pG$@1L(^!PP7Shj@cHkcn-QJZRT-?yW{|O^_KC7gOhqBX!u0Sjz|EMDcSSYz zf^Q9@ceX||xQr`}sL?0*xwjl7`wP#48sZrYYDC6MQ~HKVaoOb(!(4{U8?AC!%f~H- zT6iDFfnUZ)^92!v)m-F6JLq{;b#eVi7}MhzkvfL}@lQh1?oNLvNx1cbEucq+O{c5& zy5zc7s7CX%Z{D5Ok{2{tQ-Gg)N{kx;x# z;k(6J{f{F2NF$ObE0<>50G$>oKL0h-7o1)!x*f0gG&!!2cV4eJY#^o1n#Rg=sIt8cxPFzo52zRk?i>|}enKzrk>?j%Jmj`^ z;<Dv0;(lI{ib`#CXit&^0`HU`p{)?rGT(L-b)!-L(^Kdg zSkrj}5B5vEk~3?Cpm_vPq(zyvoK|8Nf~6>%4&^bpUF(2@=&QL0;oNI_T%xyFVzIVZ zWZ7IFL9!kmoN-^Kcbj~b>2DwIoWH_59OBu__c4)_^Y9irl4C9T`|xezWE)Xx6?ofM z@bp;<=<@d)j;CMA7X;GG?*7R*0(dp>w|}`zRoV*d;{)0Kz#)std*N??=RS{p!n4Fg z^+7oNo@`h`^qNFf@MJkeu`0zfk?f0o&=E>v!C3)g@r^+g%1V!|Scz~)dcUNm06jd#bm)Z-&@y0-EEwY#~=QaDVq}aDPv$sf3JZ|&8q7E1y*oMbO{yNmh zhHtE#^0qsB0wrO31VpK=;7qUPuDI;+UlRLTs z*OS@&21w#;r;L+8hf~u`$iz2f-ngI=>W+Xv?;Ky}y+(^F#i>e)n$?OdnE`_%fSfI< z*h(il5s9F+wzCet^~V+75$CfHpQC}QV$mop5%sNsW+$W26YRZhlJRE|WDsmzs? zYr>_U`>sB>eVuu1uJ50EdkE`mKp-Vl>qg#%zt7XPrG7>{WoV|tNK!S$c*XtWj5X%f zKUd-xePU{0p9o1a%6}`EueO!^1f!f=6>F{TqR^7L{KsjYxJK7vqRZB+0%)R-L%YTg z^}9bDF2OZOn$mXyX2%Eb4;imM-+k8m8*oW3WY+jS7E4&&s^7z19$Fvah#(0i%`?OtJZ`pG zlLKZItpIm9HO)m_aemyTN@QopVF$eHd0PkaL*Ap6JzW@m2gFv4rd90s3~4&DFeYMA z2wVCzN@G|E5@6?(rTz0O?pMqApVq!kU$dU`_AEXM?8nMRbOXqQpER|~#nk^^iLNZi z^;Gct8E~*9p?Cd{3lQLE37E#l@xJFF+l7kZJ%%HeqqW$L0So6&b#626EY-d8!G}mD)CvvVfEQ%rSnfe6Jav^DK~Q{g&D+==$@1(R7tTQ8rxrUY71IX_S_3NeLw+B_*Xnx^tJ54gmpa z6i|?mkZuV9>F)0C*!cGOX5N|ou|GG?oco;XTp`&Y9{V=ejZ5eBghNPMsPIR{KX!u$ zth0h}qA6Tr&t4(WSvSQiH>9rKk@y@t{Q=o5k>#?=^5o$uEh+Hq(BMZXZOXHAZT`^5 zDop(M<{%W(!GB_hcMI`|#kxA&{6OTwN#hN0rKVPN%Px9tqGvLr=V*6Ok!PCXaJa+epMSP-+)9s!?H^ z)32j4&}p1hN~jeCUiagC8ki4R>Q4O0piE7{KS4L8=Z#lo^qSEXM^YbgDe*g$7RYs< zkl>n7rZ?`w1DOTS$iRk1DZh>^7|6<~oFO2YA6`AHNwWo2HfvJ6GD4?SAJR z-0SGf@){j04e7<%%zbMffqZUcDK-g70+DfABOjBt&i~XtM9D^G(QJt;JkNI(cUkSy zop8IDI3#z8`q8P0GwAFssnoZiVaju~?ZaMiNV3tvd_&7D_3*VpB7kySv<1bw>1`LM zc>HY>UFX!fLypI!*Ww@tOtg-1v|)NF#qe~4HM*Tde!zbye{o#dlrlU5Cw!$+lJ~>r z>U=&Q+8p@zOqlmwgX|eX;T`VXQ3uiR#m|)yI2C=VQ*YzL(YM{FQ)roG$lw!*9Is&y z+|?#Z^;SN$_mmALE6_`(ixg*XoY%T00)!&b2=%etzUg!sLS*}fY}C^p`1N?+H*0mO z%U~ryaA z+C|)}nsgU`;sG@bAGE*f$}-_h{?G`RbofpZ*P%I$mMlmhFkxUH&g(P87T3HUA@K7B z;`LgfIpUP-B&g9J~NCWP8K|l4Pbo+WCy+339yTq~tY;*421H!JZ!!>%;r-7#mMxMQ|+miG_lf z3tAQvE`&C)T;oFdUdZS*KmU{312YL^q8cc>`Dw31GOJQ;#8Xln!5BrY2!oWe+0Bim zEQPhTAy7*lmOgv)s_SA}@uQFV)b$O4LL4PWCHx&_xe{&R{@1Bbu;KD4_wB0V-dR_3 zj;w6GK|qt++pk;tg?)i>v|=V21#_V;49sN0aBz$W{Xc~e+G zyN5MN#x}>xzYk5#C#_*0T1227+3JOzFOGnnHcNqi*$%yZruqDlL$WsFONplsr;ij@ zAgFUqSP!LT#`aX+929!-iqeu;2dpYTnTU8B(s04h@HVrd>;l8zyod?MMAzsiB`jvu zfE)S4gNNahXil9qDWCK)lZ!qrrZmC})B*oN613g($UnBasMDoQi~RmNJ}r0OEN%hu z`5?qiVvHE?{@an+@CDpXIn1b?VB?e$CM%Mve(k!Lhjy~KG*3(Rl5>ZE$jaJ_yr<_< zt3rQ#bv3EIU1sYNgIKlmi_weSZZMWZnZL)cSs@g$E$mMSvjv}D-W0Za`VCvrOAaCy zg-^$019)np&%+%YzE1VJ=V`a6>dSsH8-cZu0{l-8Z(gyke8~8&9&Ek~yNoVD(is#F z+Q_MJxqhCL3&V_bH2;NXsP+`Sh7#I*(%{wp_`oN=Qf;#( z!3caOZu;BRvL~wh?^|^taNwaZ6`wbsjHSS}CHQ*sD2^U`Q)IR%FuN>Kb>K*vmj{cZ zU%)g!(=a-JoM|}}o{`(cD8$ee{LY4J4XjCicGE%Il_68dxZS=&gx2`Xm<9LkXd~T{ zN{};)xZfK$b`RvVh70ix*@7A6*{YT0M90E+9kP3Gx9m)`)=WultbUKiV}!@g)5OG) zei+7AYFb(so73L+B=Y-rC%q9F}a;R6P|VH^6ge*ANK?VU=j zCN`9Uv|c>M>g}NE6qK3UF2`nh;U_lG%@Dtb-%^}#MB92-H7&wRd$Hya?_?#wmXuh- zT8~BH1yCD&gOZrE%$_4+YSbdAVm@_|#%b#^?+=L`^~AqAI}_5||8nBr8toetfNl=k zgiFs#XhkBGUR9|AQ`UJ&GNBusou&|E;$H{6=C+XFnqi+-0h&L>UvD7 zrOz5zFTK6$e>iy{cv6jWefZil@>4#GW1w?OpdQ%?G8&RMEEp9iAsCp>Wg|clY^pr{ zfBH#pbQ;DE$cY83ROP76G7-fQBgS4FffW4OW$cS?TPcMQG>gYPmJknLsX55@pq@DDqYLo`S7REQ)CnaJ?O^xL-HCU`kl)>3 z8aAMhIpXm$%re1>$%w;970=K`Gm9j{PLMc4QWvyMN%*dAOGNN>#}wf?ZFMPRx3 z6nGY(A$2)&H=}oqe&ixwqSb4BsL$Gqg+u^E$g+t3eBM8jI~P_-y$`jb*5Tr8a1}JS zD(#xT#Okx}$mP&8b`^6Wn85^~h+i9gVB%0h9$vm1T0%RgsmXs#1qR^cAU+wM$=xFC zy-bF%AEDs-pb_K;nXS-fN|=&%0`BHw4gXcL(~C$)?yKPb9`R%{L9G0cA37jCyHzf7 zmW1|$)Jci>S=z+l*8QdaQV(&#bXSRcV`;?H+?6`03Z8m@SMEf#9!8)mEz#w*1o^9y z9l%S*OXJ3Cee5`7i!)Rln@VXP!g9-4xrF3(WdJ9}zmnz*d%?hE)xY9LUyQOWi=fI6 z_VICgEVR?S@@&5!mX__g^+kz?asdC4aFG{Fh!5TwWvQT>WHS;Jt~)ZZ?n63gM<}!N zT?z0~a-UlB`v=Z^C1X1W7Azzyu?cMiCGag+DYE;8mu=G!b#O$*!(*v_$n?ck43vYGI5Nq2`XjrnjvthClunAtlIknHq%PkUFg?u;Y0Ckd!>YI#f@+{0!K3oSPb{Q8%|d5!VJl%vF+8o^ zP^ej0!Xux6(3f$vadZ+BYF3s2)>t$Lo3NHOB)em$7kbkw{NrWgeY?j8nSgppXdGWt zqf-{dLWYdx`6dBALDAcj{z*+)r^I3L#Gwl`ABeU%GD1})!zyV#K&xt7Az`R-f4vH;PneRAL^QI7UaJdhGe zhMPRSdT;0y|9~ljcmh93CJ5thlD(~kwWMH3r{Far-XkNhXmLR$5&)y5<8_(^&aFTue*oxo-gKE&^ppT>J zU&5*r-_UCyC5p5V*e)xEeJcv*;2Do5#wx#Q?v@eXDT)C^wek1N=-P*vy+BzSVv5p5ia(WVQ_D4l*QoV!>ai95Txyu^-UmhDT=T2(Z!FMgRe+*N3Dl-HXj@ua-u6y{zph!INcd$q2SlwQ zgB-ALoq0D{U8N%sI=h#W^>>=XzkA=ya5#qrV^yg>BjeENNM>w57umD3DMm zXpQmXc~ny3#&{_%y+jEubsa9RNEfO?T*Kbs$Z@!Se;{d#>agzice@909}4<_Wo0CL z>G2^V0?<6=+CVNFzd3@L2XtYGU)N0rl&n>W_&%zl><{FB84BCqp>1wj>Buq3_?UyC zhG>K?DIk|;6iX}JFL*It#M1k?uT^%xbeMBm78xhcL24Ik5|`>SsNJtCIk1uVeAq~K zm_$F#9!dXrM4k1e^YUHbV&jkK*${gfnTKs=I;l^`)w5kl#lKhezuV@!6MDw>^|T#R zX#tX^RCA_BF*oFk%%yiIg$Z@-8Jyh-2qDM(L}{=24Me(M@l8oVuzo9Iv74G4cuBW_zgYeE)XJ6m6yI9{ufvq6cFo)f$b$B|c%J}_aDyTWf(H)Lw? z-So}Y-oJqXY~FzRp@6SVp=a@U_biy0Sn>|MSIp`f8r|iAP(I3U)E`@CPu>(^#_71% zR^F!-5OorO;<)g4Iy6`xZE0UrR#x=yqi1?!{(E`Dt^({@19Ai!GPnsE%TzOj;zyu` z#M~VP-Qv*A3#%-)O>!{^jfLRqY<-vB$!Wh_{dzSwiX=G=ddo%e7OMj)MdRPnk31`oWyZCYz!P_wDg`U@{KgRM)dvSz8Q4sZ_~F*RS1sha zKJ;<5rt0YcJfrNInH($DNY>gE>smbImqj_=({VWj>RQH@_iw^90AE~~^5w2)LHFnEXu4;zq^MF^}5p4t3pe{lApFh-Wjwv?xxS!y;-#( zNd{86G9F&McZIGmC~g4-Z@C=kZ=h$*>5*}SEyfPEJK*ncxlxf91;UT(!NV8ZcMly9 zGILld-x{OLoODj-y`W! z@@14sb!kj;aF*H8(K{syxgudCihcbei%y7;j&75Y+n{mdv7dS{X>%zASMs?(_jUwi=&x>y{*K5?rFkIrhIlpUw63N zJ29mM~sb~RrvX5d}uFhyg=7{QFZzu8Y59z0)7<)NVc1_lWbzp%+U|mQa=B}71xr|?B2Zo9# zW+)9LdY?vCOh@DrX$u3ytg&y%o)fRH$=I4>`W^fzMd|dz?k{((ct1GJRCe9ABW3Tl z@{tnl_t&n9Bg(GLEfLu#yz5(=wr3?nY-eSjY}!1HEs6Xm9eXJ@&#@Q^4na{zj5`c3 zBgFO~&#$0!O~^8$ee&B?s)wu};CQkR!Ie;~hAWlVa-R~2!#qc%snjx_AJQ9{1H~RR z>8g4yIlcc7x(mi2fGQl!BJNHp=dIX3s>jZQo>QToE0)t-3X86^OIHgO6tezX`RTaq zPct`6gQQA-IK74%VdnP-+oK$T3vX^5mZ7ONPwN{wMqQ0&=!+pw! zN4WQ3$nQokis*uLw!FJ1L{otT?K?|*s_*+awxw$)bLWaVVOo6J=xY4K96%UOco2f+ z+%Gk^a+zNB-oXK7pu+HYq`xH#6S4Ck=`o@v9kU9c!>( zQ`5`i1cy`x+!+fo(fHnq&{)uro^b|#&?X>yr%+%|(8>?3NKojre8`43#U0mYlqs^)JCLoVs;J=;=p^ODc9 zpi4&x2G!BUrvlgdH`K+@>BcsV%U2?B5?$h1#49gIBO_X9hMn%CdGXaLjI_t|qp1{R zu9urVXK)YB3wE_7b7_N%P90ts7Ch1rgF1epl??FGet|<5j=7|D=F2x#}@%I&e9yYaZ$#ss4BB(WUSqW{RZh$ypoW7-GuPWOLyg%nW; zC5B49?MbF_gmK!05Zysw`2Cl!Or(&pab$Lnj}h@nNt>SyI%u;SKG4vvxr$~}KVkg1 zdG#wc3hGmu+jvukTcAL$au=I+U7W#3?wVhh2<ixy&mL^C%v}C+TSAl?S6MxnjCa=q0_%q?QUX0 ztEZ=HB2QB{#qSDlI~Z3V6Mff+kZVLJ$H`ziwYa#Z708O$-uJkq;E~>bS=q!N?u=DU zWeM-9OuNdfOuDujUpJbSsk>%a4dBNZl#DpjMCW zF3&3TJVZAtGT>x#aWnpNuh9ytpUXF?wUX!OyiIf@}ke_w5A09;f~)d;8~0*cMi}3 zUxlx`X~~WF2AO7sra|g{YMB)YPi9{P9|$wt&43%%`<~4F6Zwy>2!W-WcZ~+Ef$z$b zg7G2uyO2;ac8zaDH=ImmqwK`CRN^wX*K9J#1U8e8ZoTbE@dIxXhTE2;c2x3>xNc;|>`3EtZsM>-RU`_FX!t)*kP^S_$EL~*2lyDY|L zyEM~mZf>qiw-0uX8m^ruC0ttzAO1E{#WIH)zNKB*_p(R>Q380LOhGn0{5$8ILeDI+ zo|bzHFtK=6zOG7qLtC6@i$1qkHn(M%9@7wqA6{aQJ*R)|^N$7yKS9iDp62Ogx-Y4H z#ZINGrnSmD;whm80Jqs(-%4(`6hfO1d(>Rhl)WI4LfO+`oQ7`M z)pI5>t&Q6``oWrLsa?ZfKsSkfTyEUQn-~%TRpb801sGY~UWa)~9W8Hf<~YmHeQXxG zPg$uEslPFwuNq!}sPc-nbhU z))_yqGphrO18MMHl(9$z@PQAcTJz;5RZ24bN{Fv`7H$UcTZVMdk zub(xIGj7W}pds?`pu@7Np!`yyUyKRK*wwV%mhmoxg5+v4B9=7e<$WNT)O*|oqdEv7 zg0%z*&R`$xH|q<+qq5H(T3?I2siPO&i9@;3ql05AG6%yZf)tmkE=g)%D zNDHlEtwt4u@-JrMe?&X(3=_9f9P3uh$B?^k;Z}CyCSn%2CK+qufG@Vo-HCXYq)5it zPcqaGQmbM=h20KFB^4fHG4T;(Dh|A~;uLQ1(kn4;ODwN!r$s41>0?dEDQP4B>Em4o zwDEPiB4&?%OrCr@c<@v`khgXK_URbUzbpjAQlLx(GXR;g2E8)e61 z;H-taJ18G{w0pL%8BBV+;>i~sF^(Nwb5egf;-$>=VXmi182@{mz}T+)wirVtnN~MG zeZFOgktMaChd;f!A?6A`DNa&TdwzmS*HX|Yu27A zf9)~kf=iq3a-CBTkV3!ZJq}jwLl&2g62Aro4iehvp`Fo{6uge}Nj)%6!TniRBBQhc zZ)GZ=IzD}WxqA2;-%SM69uFkrgUIz#Yhs@Kcq175`vqH1!i)9i>wbNbx66TiAux7` z$EK}T<#Q|f`S}+eNYlG^-!`)TB*q@ukR74B;ZoUz{`O1y-tv-f@)m$+y%6ITZ0vED z+1^uwgtb$TiO6Q5@%M2-d;X`zgt45cmk$Q{H^-|^Pe3`6FS&13$US- zZz{>2f!YkJc_-m+|DFIggF;$eH8z^f#}>>w_Pp8IPctZW4w|pELbX((#zo;@_6mCb z70IpMJ&4V`k+7I|c)U5jJI{xyz~(ez=y|~pOfuI$G}lK)A(VWrXF*}Mnto@=Z#&R~ zbXp-O`o2^j7`hwVRlrxCL9N_}sjvB9kb83^L6u?d4xrke0_XwI@-&@#LLi*|53zcw z#0jYC0DqwAJ}*-E#=P&N<7NMvpuHNx8eG2X0<@<}1d)2W!>+G5U)f2TN(_QYaWD!#7t*N2dN^cEVs% z48dC@sLU>rCo3JI4_R`DMexV7LUS0g%vD?mG{Y=_Om-@ajP~cJEu-zb%|f{THb!|pFrplS6ZZEVp6!3QmhTw1 zFA%^UtD19(CIxge!LOn@+DC=0!Yqhyae6QqdIv+t8^eNVETUFY!L@%vT0bprQufJx zwla*wGArqIn||ac#gt<(W#wwcws}f>o&BSXY^LfHkwfp0e*ViR)9f&%9QkIhwcy*8 z7E8ds;75p<$SFx!b>J<-e|IKo;e;Ks-RfoTI~N7I*M*_!8fh19s% zM&Wz8`~FzuPWaEbKm%jK-ytK2t*xiL3#jxQ|{t2j!aiBRgM9ayQV~sc1TP+#6TNI?{@dsPbRDXRZ6W@>xRIP5I@L> zFoV?d54oKp^85oyCq~?k&!p;4UGj!Pw5~is?Kwb z>}8`|3Fu6S@val|!A?3SEbM8kR)U5t438woyMMiLW02c@Eq3UfO!j)y&`>ujeTJAG}fN1S)esOg{Pm zhF3`pXajMfxgT$_#L`I0raDGwxBMSxp6^Tic3q8fUTm;Jee=wmFN(GExW3+0ei{%W>%i};0H5cRUj*TrnE$xS!amKef%|5)>WR=lMfiHIS&s%w{Udf zg!ppU9qohm6bJpdbw2;Q=yX=Q445ubu_6md2QzF5*V#aoQh_(Wddn`rW%lTQEN5Zz zZ}}^UF@SMfI`%#`_aYID{6&BHp`jtay)G>G%{OAsjSmj-N!{q^3cT{ak5euqh)L9# zIy^Pb;1(g}(|>F-Ok>sf+OQSriM`hzhca`eBPUA}*N^5?GqYcSt7_3lPc1?a_&c_m zRv{sjW_C1rt5Li4WVB$9xB6tnZwf|V_Wd0HP&ECaP-5oK=YQG1946V+Rg$Zwp=meG zJxsmtJWU%bpM=W1bG|MzYYhT7dW^6*hsU(El^)Oxx_y#5Y;*fYg!M1{J}IPfLhj80;6sLtN9Y`-fGZ>*$&7JUd!xE;1! zW#(54E<18>l9r+tjS5xAHSur~mlTN9t#BviZl6UxLQbPV$vASM?3?R9AmRs6 zHV2}ElA**QXHRe%E5RpjZX=x%af3r%qCFA0JzA-Jg+mohZp+5NL%p#%?J0h-rJTx{ z%g12A%S!$jXPH8QN>GO7;`=9bhi3bzokG&maFcKGazumW2lsBrYfunNcDZ8gLeU(sFAC z`$!bxwj6Nha0Oxc7Xf-nqm){}*b#Q^rO$*J!o-hjt>??HDMA+(*sNVuXtrzd6N82E zsg#!_H*p9SeFu0w@T`q6!op%$>F3MWrUe?pLA$viHxsJF%}#f^#zZjt)y1pn7ESk) z&GW(dX`Ii>&*u-lBvtq|?#W11spK4(s>wgfGY(Prb59D1i;BjWmX2E3TK;lnW*Fb} zGqC>25Gi_sirO8Bg)H{gPBlTnDwG<}+rV z=e$KCqQB20zR&Vc8B?ip8&h1xd|Kn{%6+ZK43M%|0>l_*Qmi_e0ErOJQ#s~Mxial8 znu7#P>heM*^uC_e2>-3=_- zhpG^tU$7Z7Hs0xr1;|SQ>P-W7p6$JS_xruBCn!)OCsr8qL)%r6l^aFWA4mIrgUFAl zt$9s)n0qr}(F}NJSAnhQgG80Sg{FZicssqv=aoOVA(f6~{LbHRRg zQS{+YZ?aY$HGAq@%SV83AkXo**NUc{dFfy#o?9cPj;-R^%(q`M_|)qPKL^7%rtk^h zyfIkmJ|2~M(1y*wlEnp3I)5r&RL(Xboj6piWBd6W~2W{FrI$$A@_k26{!{^MQ!eFHfow*$_HiwV^C^8{@_jbFQEVKV$C~_eBy8 zUq#QSAIQwkSayDad8Rh0FlIPC6>x^|T;Hv*^E6brx<ip_`MYnSE!f4-RGhF+IM~0* zBG5_k!+x?w4-$Y+O)?M(+4bxdfa;|8a%NO4u@>m9xl4%4N-K%rQ-Dj48HCqK8U-2> z#&2%(?~=${{!Qr`Ua%gut1 z_TvWlU;DvW{C>nh;<5iIVlN1UGZEnaxT1nW_>0t(Unbsv7?4Cdt8e3nb>5LTK90ON zzuQdmZ9tnzMHA^^s_?Eh#wcK;wfzf_S^3#6`tU^1GAn&jY3jW*DG7Grllz@QE^du4 z$2VjBg;0)fN|To3_I+kFTEByFNslAH!?K#nuYR%Y_kZfF*egA@2@L3dCunA?_+xdt=Rc0LwNNrfUo=A z!U7-qoPPAcS|B&Tc_8@k2hw$>Iuri_)JU%$`r>=Ok~Fnq)nL5b?3D(Q3eo>8q-Y2`*%FN;3hTWBb0r&!^+AN|0bK3KenKP}hDuREdRVA& zo~)zXs|C#h&U@(=Lwl8h!8;-XhRtu0H7v~Mg?gsdg11YS2sMML} zHZMW2SZJu?(qKazhSs;D7+gWp1wplI$wc)xRG0&;Ii*E!*8Tu($eQDrkK>n&jqA*~ zCid^(aI-J`*lA9L<4zgfEPB`nUaj-pg@6AR(rqQuzlhRAz}aWE0A9sR?<&Aq{}=*H zfhzdgAW`4$G_^d~S$Zk|qA3EJ?lT>Z@md2?$qN%IQ5ea2um;f0SBJEa^OKf@m1a~! zdgr6;sTFtq>Tl?vs1Yt1@Lu`JdiuTtk5LqwU!S2d1;5^OL$43A9DR|ca6LsBz*MSw z+sm}|Z&x~RjzxG(aM#c4a;LCe8FD#`bo%FqJZQnK`E)Tmjd<$EuJ*XKr*2x0jE{7o zWrbp($tS;;=K&x_C;i4^s|w(g8nPewztl zHwS>DxB5P*L)pL|f*6FkSrpiz)IE6F4OhaH1~`7LNr&5!5kv&Ahz&f zx0RDok!(YcK(*HzlMyOUQX?Z-XVsMCJD_PqZjh)!U_dqJP(+{i!3)g~q*nbpG2D5N z^#(!TDkUR8YPF0v+EHE7=aRhuVF6*M*kk?dM{usgKXxeS16tNhP{lt_v6)f2K=OWf zn->-m9#8g=2@ZeoF}A($&epvY!ajM)m*7{Y;6y`&{sL6FiJIzH!VI3|+*nnTF(Wfg zKWTX)pYi_0!7O;K&&F4s=lo=V1u(yQdF5mT__=tHzE|8@Fq84Juj^aFbwrYMHyW>2 z1t32Wync!kRgWam?JWM|N~dgL8O?;t7r~?%R>Z?M;fS=&^-yriLM@rJ54)P-o<7>gMLz54vRct=;z&?r6>mbxQKpAFvB>pMMF_P?$H+%~;vy zD1Bmana$BCt7mAa^!pa}$KDT#GA12ZPkCa`D0_z%0^5avN87v%L4ig$>=ZW~!yxRP zwu3LhcSEa>Ho}gCVH0xC_@FOoWv^h~DNOY@`JS<Gkl5Q^)8m-!T8H) zs4U*J_&wiUlD=)vZB(;UQiD=2nrFC*B{ks$&faD)C7rnJ) zna2aV4N1R$zWY&+l+T2c2j^$*XpYSGosgWLnTvaUBfbN7?hASPZN=Elrj_Pn)_5!J z#c_5^4a?zOy~CWd{YZ6q;}0x#GYci;x9KNd8wZSx2Xb1Z8;S^G%25bBF?-)+rlt~L z^y+Fr66pN9bB2wWQ%6Dn3|YbH3>O;{sKXBqyonW$(+>o_ZyU z#sXX$OOqYpxbp(re*x|o=*7tEWAcGvAYbgc)5Mli^HEmL@1K>`N?hoJHE4PgF-n9ok4o{8|&Sn2eyWR3R>$+$U$j*xH?$Gn8>p zcKe$$fz{12UxmKrO9M!R$#Dhq=}<%KOn?+ijw`f28&XOv!Y@v}z;_nz+Mjb_)aj|h z-IK#5Z^YetLq*ndvtG^m$#90-*4)TC5J#+ZyzYY+?WIuj_3>c+Q^H<&sj%W)H8v*NLsxKjCpCDNE^$-Q9bTkcD-u=luAz*S z)HXY`ZUq?R{)<*^;@TdKUoA5o)WN)f$T|j~O5E^P$74YPp_CY5mC7mz`B<_>u_P`vqoYRl$MI)B`wTK#d1W zXOLLNXun`p=*VwBtBWo*Z>}J;b409&S*Pu%uN|+a(|H9z?fcTB`>$est28pqeEbiT z4M*58nZY^MK+xf@QBgb9*)S5CybvRd?izq204AMKUrnN1=3!i4>M-^9y?+W?QjM&_ z(abhQP6ZK!bM3!VA_{|KUH`Se3b|We4G(1W!yj!wtJzpj)wy8DfMVx>D_Py(>5pzh z-WiTw5Lgyg8_<k<;rjX4!e^&8QfoO%rjM1a4_raVD^-^M#T9=ZT4+KC2t@{fR9t;#i4GkMW94g zGU}>sn6->w4`+OcOc7YR`gLD)>APObh$P^rbVBws1Zz!DVL#k4Lmw@MKN2%gU$U-c z3^Ai4zoW9R2O;j0Otk_9A(R)j*ga?-OG%{YoXqg-&oHr{l%a=`ezWh)K3+>{?*v6D ze+>m2n8rMI+*jOTeP%f=*8xg&I`L0sbTLtx2UhQkmoKTa4Z7{n%tKeaA0E31ZztmI zlbNA~sc1V<+3z#w(LT*vQqaiYU|QHnjL1Bh*hjgp3h~PFH_szWHAmNbI*{&Cm#{*> z4af3U8l4ud`iapFZ7m|ZvW;5ylGPO>rPDXh4-<0UfuvE3yL6)ijbPBRbv7}E+=ly` zK;1)?bJx8aOcJ?y=b!8&70%C8o`dm0Qg4ovqTP@^nj2@o$bK}$$hZ!ba4z{B+Z1#9 zc?Ue)XPlq;=7m;YAr2BGU7Bxnj+Pkel7la@sLuDuM&<8(dQ5BQJO9m^MmfKG_13j} zV$qhz7G&n#L*RE;=xSv%uPf#E8QqoAHID~TqDo^S@urF=L*_XP7VMG>J3s{e0jE*v zcLl>x0n~q*05C&i3M^hOZ=F-R{W?Hk%-%hdZj)pGJIX`KlMag!Huh23>gbIF>~g#V zZU-CfK(>C~dZjunTY!+j6GqaoO5N>k`b~>Go|TpJcAo}qxt?!Lj)r=IW| z4`!#(Xh(d^Ic23pbyex*{v%pGd_!!^#TU1tp|LK45!U{qx@MwlxBcpk9|i@;V8OZd z(Yc`_vCw>+TFd%NX67I7S@WdFlY2>Od>n|RaqZLCj1DVSf` zOXIq7o$s_@eO1_=6g+t-e2JKi}bsD@d3QVRGa2T{a&572{umpxQ(&&WE3)M zU-_LK8-6)EIPBdFd7HwPI*`6jTRf$+i2l`dH|PWy$VrQm5A7fEqn3%@iCk?`=Rzly zL_Ua|s`2lKk~!05kRwB;#nT_w`?6hCcwS-tT~Yif zXS2?+`Zj^WnmS%7YC^vyA&ahyb+NjX6Kqy|9mjF5w@lbLD+OLeGFZbP9HFk3xao-PlJhWasp2VfUp?E>@K@C#PT~_^EJ4?57=}kV`R`L zK#!LL5Olu;Iw`;`6Ifc`AgT38ONP(b+(5EAV(2H_ydPSXgy(M+POzp{|LR&vv2enw z%SkS(D&D*4IyNij(A-;^jg7tK3EKA+*-srL4if;USqNXlm;@!hjW(!JxU7Sb7N!Ik zq={>oz0Y>a0})G)@w|^FYK}e8D6uJhL2n{1_Cgr!(q`5_if9qpfTuRy(X+`(J*syb_Sc#i& zo^r1dL=;g>HKJ`&)nQ+oH|@srY6ajo9bgfTODJL_x!$&jVtzpm+~iBgJy#&b^*g+6 zCqUgxXlbhXgEeED}xdAu81>E<0;sz2DX zYMr?5^yX8 zrHBOq=4#JB>M!3uGEN|$w!nX#Y;~Z+ldjc}<#OgvuQHS_pCl+aymcdF71g zepl1#9^0R({MAyFx-J$PahCL+WdSwus)UwYQC8Xv1@sr|B7YT;q(4ApFxXb5`5&xJY7-7KI?sMS`~c!1t4X{Ub0M4dlvd) z{_d3>D9pdINB-%WBoED{K@B4OLS9i4(dByn`_qaIY9cI~A1pv_y1o3}m1@g#>(!C5 zq^z~6QEU_^h|H$iJe6#~%|>yTHoa(oGUnt!7tY1`Sx8!k=Sa8PTPA$+XV+ODum?Mc zF07%RqaDO^5EwO^-~!#Pj*G!F=%P#cYB5v+qp!Rdh$h!(BFgdaqNl{*73cFKD<)uS zGj1^Y8JR~dwC?S8JpQ%Z7JbQY@=a!DCN&OpR27jr`R=q4=lV9L)9=mj~#Hb>YjAUfFp1xA| z=)@}C@-&ZLI)5>sEvhZhhtkXyiO~}N==UDK#n}EeR>K``WrEe<9~`rCNB?aZ9Oqg!ue2|l)ue2?JQhvcC7NWXSyL_w4IGI7vhMrQ zt!#Jq9ogA)D{Z}QjMsIe5g_f!Ph3kkDwPNN3=aaP8PR^b`rPb^rjSxDD>tCSJ!i8c zvMipy>#3yIS83U&YnSt#mb=ay*bKgS<~M(ynxdUj2@uV#4I570zhmEw1@9o6P+!+&pLV7#Wb6qM4rT9GjvU$W?JPdbD0;Ri4Spy!No{( z`4y$p(fB;ExP<%r&;8b;St}3Hm0ca@-kV(J{?tU!*X7GI^9(+8sN2X+D&hIA)%(qK zS1+$GleZHL64BlP6Ao+m!JRi3RMl06>Ix5q2J!1-m7Sd9tQ_w#RH|+xC6D`Jcp1=r zgl8J{(6B0iYZN%NKrzgX@Cr6z(zj+SPoN&l-Lm?XM6?uUEt7158_ z(}D`RsZT6#1?fqPl^U5C_SXSO$7ee;17YPE_fwmF3GJg24VQpztb-0c5E7xr_9wsWH}9h@ zOI}EulQSVQf#IbMGM(lv)(@9X{zqRx`JuHM-bDgvL;~(ifvu%jI1G&3HGZ_;1irt1 z?fS5n9Lf)9etvwLy~s9V@&nmea{n%REdj%Sq4$L?Uel{f>~MV*XWrjzeOMuh+<=v^ zY&|%?l>unkuXEb0E-;hINKKhf*pj+mnInW&e)B_>dHE4i?oFL5YabPnMC%)gE*{k$V=wyaH()&jwq+b(|%sw z_{LV2U))O&(n8>L3F26BTdgv;Ub7F8OE?H9mfOO~i=z#8%m8L-xT0uR)di6og`ACJ z7(z>>;lVbVa9h273>IAH8tXgu=>wu8`p ziU785jDr=Fh?xs6^FGW!oL4R5?L<8aNqiD@jjQfqJR4{BV_rVrZwu++BKXn z@(mX_p`IF#de-`}3aQ1LboOjtC3^K-Ym6uvb0Hl#=9)OdoXy|!=QOj)>am0T+MaID z7+}MS+B+9Z=n|^D%qvlYB}M0HOFbBN|A$ErKd*Kp;o%KggwfXkIbX`|T^<)__qvLl zz*5vFt?;q7y}BrzMY5K+Vc%Psl&}nEF%pFdKTP!83}{-EF9#>sXo3zbe;b!2bI$OU zN`N5x;JU)ITGgskPefp!v zTpgAC4W%oEjBDsT7(7om>tu#-uV#OdbQMg<8_e@-&edif$*?TRH zp)k|ea4))SQH?Y!Y>_dH1E-5KOqs8o35iE^$JT&0yU;gWu54HpihaA$MW&B6|XHx7;}pXigQvJ$Iv_N z44@cKl6a(!t4&e2o;Gq8l3$i9mSpKe{;@Va&IR`uJKx(QV|m<3D{h(tPwn%z4pj^y zV?sp56XO#t)cO2rs*4Pm$wKb|VwEbz9DF{$5Z&1Jz{aQSGqL2-^L@`v%6~UiJ5&Q+ zE!&>ZMPwU*DMqvvAbz}t=-b)I`E1Ojhz-40K=+$rkfM6YHDWP}PXS<36V2_IIP?DU z>alIIS*7R0iw#kfe7$^bpay6tTr}@E4xm#*@kvM(T~DKPZoe(^3JjR}pU?iP4)m~O zMfg2$YFs&QSUR~l=cRfDZoBUG00C$=!m6b6fZzlJ&o>O8Q>C5P?yGJ8;GFPE zu0E(RDr;^U{YoTfBm#HCBE>R-J!hGGFGObM_X+|pdg#W7F5^_? z{Exo;ZWBu=&=tJr2O{{e*Un;i;k;*$QQF_yyvO6mTurg%aa{Xd?`M%!flK7n0mK-x zpcAqHxD>JP7bfEOcX_bCX*iPu1KCZ78|{W~vJBGj{jgLvnfTa3R=^cKeA42SsQ_5U zOx*7o4pFf$4z9>k!nOFu#~HcN+5AVX7DNp$pR4I(*#`#J!eUk1|7`p=U8-ubd55;b z=MyAzIMvdOwVbGM3ikt{g9^yV%|YaPG*IkgP8QF9JwC^e$;dXZ>CkF$%0z=+>m3Q3 zXE9e_k9@t5f5kF#=#Gz+3q7^pCfa}h;}_UZO&u-)*go;n5Dmfa9-V$Af1M%p9Q@Wz ztNg8>+LY^IRWQn)U7N5jSv^<(b^p024MhWe!@KHuSuaDmqn8M-Hj?v<*mCQon#mUs zi$Q8MJYoC$u7PN6_f%K>0;Yc6%G1Me-;aIzeaS4`zvGl6=#1(CKVZGf4ZOJGhC@h| z{fL+OwUbjc2HaeSRWH1oa>^sq zy%p)y9ex+gh|9M=cEt#hs;!~Z)6}`mu(B64ncF*f_dS5p`J&ZLJM%%%m+;u0Ry*(} znOPL>)Mmz!_MXC1rtzAGyJ5^ zH%k7O_Ylg^-d+8^lm8EfV$TiYm%`?y0HY9wCf(~N&^)p0SL>*tyEXjT<)$!C3C6n1 zuXBiK+oi-Opg1Nt_p!M!VaWVkc%P3XWl>Er6GAs*!7LVDvkdXSUzJi{!@RPqw?;uk z#T?=4DJGSJ!4&#ST<&sGBiR*$8YW>N?erxH(WUf*U4NOk@80oqC-a7VILq5N^$(hi z7k%~H`U`WO8IpnRFF#9RK;UX5F*ro=JtK<)s@Y69pA!oYV?)6rf{K7Xn{ovJblmH9 z^}Amahp)$&Jwe+SwjrB2% zxN4pZBeGaxn`lvEV~lfaaFR8csWv6Ul6~`g`A_fGXN%l3SIQ|2j<0weuaP!yG_%tw z`8(QX(w*{XD8$z)bTsT1dp11xelNlbbD>yX3p`>bntQZ}*%c5RUB&2}(d;z6o@g2P z@RPWc=@Fk*BSYP7Ks091t9cUAQHXW^lsYabf2n8U6KJKO30`k#BoWoGpuam1#UyDC z;YU~OaXCH_dfTgYjQmm=dRs)f>K%5%%R1hx^B z0@O2UdMfy!Z-YQ0Qd=djnXyC#){V3^~!_ zfYPsr@)@nLHzw&IiS(7JzjS?hG+pkWQpkPN@aPJ5Z%C|m8B{Pe_?O9%2@>#k!N^sv zL>e45oDg@`PSrPOhFHWHwaa+*J(P=gSMiJ~x(Xu`+52qrPjvCS2McL_4w+}`;~{NjN^IxwCDT4`$v`|&d76s0l??FqWk zD<9ST7F%m8+uxuOkVMDzGl(M9Uh4p2QTseur)JR>4jBP){M=};=hIk{Vdy@(Y~eA8 zfPa3cY2jz-j27Rp9Ye=9wbJJMJUcNwO}o=a{>Y%YYeA}R4D_$AbIO+QRd8u~BOUwccvc3TPm^ALmZ8|m=HR*= zc5fJ(QW9M9$EC%2*^^&fYxF&|e6KSsod45ntj??(03s~oNV@Cyu_)zvJDGHD9kXTd ztc5W({OFyQmHn5j!~0YwJka6HH_NdLC@VDx%VH;Oj93x9(kp*KQ4lA<=`FEf0x>W@ zKRQM5&!5(E*%maY7v5*)y#!&EXt?LiSeH*3E>F0gKsUv$5WhGUK&`9zAzJ;1wx(Br zRryIvEPrf$R&g`Wel$nIk&q1Kop*s*f}(GkYSK2JKv-3_ZX{HnWmsD^jT2avKINYOT6%_1HVoKdJ?XlH zIVp@PHP*gKj$z93=@V7xM2Km4evP7(O+pPVc`uB}9IV>{4dc1py~AKljZur{m1phd zJ>+09*FDT@SkU?lx1IE3qW4}ADmN@Da|$U_yuks-%}Xa_sQu@Rq}N@ItMQuyT5I9y z=Ad`r9PFCToR8WBgV^3F6DDg2R)5!_@K(_J`m#$qg*R;bz|hDDZ_XPh2bWLN^&1nD z2aecU6T!S$HBpY6dTjm4a^uQ>GqB9cyRk=GiyxlQI&92}(gA>p>k5yFf1eDUc+hZ5 zn8VLeazYb#8DOo|xd}{YjW%Rz&5ac_X@T*w$)5!NJQCelKbC^5BVQlP(|-8vc~lq- znk-)TcWwnG$efyhSP@vx9e)J?#6JVmkknMd@$tp*#wbjI{w4X)(@O*M7?(h#s;7jP z8r*rTPZ;}Be*mX=!kzcZdZIABK-IWEi-u&>*XZ`a}@Ri|ch)OyP!P4d}F zw}e{DH}cH%ecsPvZs%VN21`^o3zpoAyi|X#wJ8g2zJ9x~P8wKu7~t&om2>i?p0}2j z3^)!u?ge-QsOSt!MfoIULbuh>ghmSd2n+usc>klflGY{wMy}id`}Pk<}S}*1e?o&g4dkRP{m1;4lPZ z;!$Y9Au1|~k>#_r+B@Kv@*T{OT_E+&uKu?A9SjUa}ZVD>%)1%7}(>Y;ez>Nq2R;>o2#DfwY>&+^K&0=R9dkF z#>p!5lIZ$AA!z!|taCtlIO<@vjp`USLCb`(zIr$9vi|Z)n*33#I?2zZYH#6W*o3(8 zNj7TFd~+kv|74o$#R&uKD8Y62grq44@mUu9>w_Z80WFMh3(njAo=WD#+9h<8xZ~d- z*)`OAIaKotxbLX4 znGiUs$ENS;NsxIokp?QBKUvJK=q*Hsyj3*6??==1KEC_mk1Le@=(pF%Y}4-DvRKfD zkPNcW#mdXMD=l&`0W=bWf+VP%0-c`fu;8jC~_Ww1tA>ivZYEAO+0| zZ0^zrYqwH_jE@hJuV!J3d7F0@k`g29C2v0UN9JDOy<#qVcTIY-CFI3*#t9`KE9IIW zA|~#$;h(V_V%d5uL?!CM+|q4Grkqc*5x#nx+}VX(Jy-sXnk2g(-8t3(*j~=8B)=M9 zXoKF2f@L0tBv12Tdjluls1qfrppzTfPUsY{SpF5KA| z=Uq)$Xue@oUyGyw<*ac`cO|JYviq1}U#*~?{B|8HG&D5qg(T3&1~a&YduM?r|9bvc zq_#lA`9Av#3O;)$r}8_ug*^kOGqsVfM-<5auC85VS`B?6lDQSCXg#xvjjKPYcaeUr zqT^Dy+$BmM08anA6jQiO5pe#69k1lzN1f#LA|zoGO$ONfGLYMUn%t*mlIQn_)x_Td z>UF#rjimP>K<|9QACS2&GFkRO%6{&PKdYp+<5{GP)&yh9TyNM(?{KNy!x60SG(+2L ztnyOY8Ce~)IgXnpFl|J?zuT7oH%%sc)!{qb(3BvjJxf+D(b@>lWS*PB*VKsh{wy9 z0M5&oBbFuRIj0OPAm-Rr2U+@#MXnJ5L*lbn&80aT1P$GO%EU1hlLO}-TlO6=suJ(` zR|AvI*ar*!mH$*L*c){{-`IIGb8darmApIF9N_v#H*(p|b>u$4Gk=^Sc3HF2I{7V( zfVw)YXu_9xCp#xcJr#5*79*ES&c`AMp!-@Qf4pW_tLX7J6peetu`x=KOpoFU#m=qJK2lYV=(|SPa zD>>HJTEt1D7A8oop+lIyuQp8u8Sh*AaE-=mn3%47Bu_bi2UYb9` z*WOBfu>vhBeuXeZGCV`N+1?i)FmN!@(8bjVe4n7eT_eYBcmy}s<#1_NXW5CdY0jUd ze~T9vF*Kx_VFTa(Bl-IyfqKR7xAMr*N3Dgt$zJAX(fwmz3E+PfDfWbn_4+F3t0fOI zA~AhlXgZq^bb(^<8nfSmvd$@Z0>xN}dVI>}*q!+YK!dy{L_{Zu*-AN(CwMZxMMw+k%AMdgn^V zkXpLMsXEg*+VdqlGcO5TaAXTn)>w?Hs7O4xI>>czHu9j?43>e7AhA93SXg7%d)U;b z2Bq3EV($8S2g1Vk*luAE)I61>toWRqO%axNSMQe1or!}0LiRtXA<%SKg$W-R4JiF+Y=L7YSh&?Nu09$CE z5CdcXLu1T_L9p5JEtL?Bm8(g7)`3?-8|7cv56W@_E~dh=|6R6NhJtzS|ndos^W=h zWa&J-d-B$9jhl0gn?^0y@~{|*vt6r-C8j~x)MI62!HIn+C>!RESOeU`-2R?y1Mv`a z_SClZSEW7o`0l}bT@;3F#P%ylOL}<30#oAnN!up)C(^w$_|Ap#oVIObLNWNFGUYDL ztQ(SnotU123J1Ed74m=(e(fO>) z_b%VFu$Nw9;+VN5!1D`T_@I1ritf!PjccYnFHgC)3~fso@P$#T+FviB!fTyL_l;~t zZQ8gm3y`VVvM^?L_M%v2Y?2;44`we$F3IJ$j)V3i#v-^?3}(JMfe&$Z6p!FFR7TXh zYi*&`8uBEpnxx0;%2W~eBMyvTh*+B45#lJV84WTlUng-a_$I-{?K@voOVOw}vx000 ztf~#;mijInNBSS}T6k18X5YDONnRAeM<4%h?}0EZ9L#1g3r7+}c34%D8svzc3b8P? zuZtY5gIn^v`>DTOZ(NLen$YFne;n}DliAh1D=$k0=ij->KPLL#23orQpzr2vY`5Bm zaU>VIUuAnwlnS!IZ&5<3E|nTleDR9rz@v21PO_bfCKb(^761C_>EpyGqsk8NR2wkM z$$8bdY7h}Z<}El1>?f1OPy>|iBoN<0D)VHMf@R^vB)eoWagRz!CiRgMqxC39$i1i| zHispKDW>-9U#egd*csk#ZTUUKO-KfevaSYLAE zLl=M(GhvkSCe5*8_f+xZ6S?8ym;;wZ*Gsd(Hf+R%1;H9yNIw=|GRVVInmXQ057>yX#_XXky7H>{ovJXFJNXNJQi9#)g#T!0 zXt0-K>pw~a>YlKx9qQ8(E^4Eys5c|RH5o*l?#-U8qAeDpa(*SZNh&xqYCciPKHvAT$_SC}OUZUC4(1_98=`Y8oa?bL^w6PA0 z2L?Cbr@gt(NBgM#@a60G{@PCuOZNes(08VNLr2?TXLJ*JSL@%hZfgVi9ExON%u zq4i3|57~k&;y_E+)6TMUTa&yT>>V9ZA1?bhK!PuGs!aRy`iudA-hVwLtg$%4( zos#hilb9Wa*;#f0T~c{4_O{4t;9GE1FimUm2M@->{hl)VUez@x@BU*tE~)rq!6|?c zu{PobJLtn-PZ5^f^U>daxU)j+xa-FP&U>a*d)F!NTvK!=%a{q@CXWtya365vZZV4k zcJ7Da?jB8H}_c#IjHaRM#iBz>ui6Ah|BI2(%uBp%#Mr|0m8Y z8KfH^GP)yEJt)UCRh%MAI7$iltmX6(TQi>d&)#2W$M>|#2b53%Hn53QTfE^|x1KCU z!x`UA@qPmHK5K?77DTzNzpBVOrd^&{tM&qS@(a5Kq3EjTaq5tiaxWDp&97fR=Eq5| z>QTX+xo?mRkywg}yG3ZD7|Pc`m~zN7GH#k+G&lE43Sbrbu$pmr1Qzg_D=kVOJ_+xp z!vTH@3gRX)1iryF0!*UzjNoAt03ltc-?a zeH)YL9NHg)A2-E44R|d>zB0j|Ns}dcu=NiNyq-nwXj?xFZ9$*hk$k+(a6pm8!MX{9 z;DAAUpq-S>>;o4K3U|T$$0bbR*87P#(nb!vn+v7c3hvo49R^-Py2!y%%w`tvJ#zC3 zWVbhRdjt)4^DaYE71A!zqPf(5s7^TW=@g}S1V!uBq8w0} zcXaE`l*g;U^`lM3qlK8`3Jg;;^qQ8?JvypB-DA+G5KLE92|c$V!YDiXL0Mw6Kj4#T zAl;AcdFU_HTJle1G%yaz58VQHgxqO%X7A818rF%XZ>FP~Fs>TTLaokkJR zSlncxg~wQ)+h!(~ZRlMLdDChR{Xjl*xm+cX7fB(~AI52tvva)=;eSq0{vjE{_)0Ua zq$2m7&qe6vH>Z;PXS+{yPJK0FiDs5}jME{P9tyx_nS1Rbn%u2$)cN-Y!VJECpId%E zUkB*@>nvY{$^F@az74&g-R&g&0si1N=fthzAeRhETSW&=K^43L0sx<@m=d-Bcx;Q$ z8-+6qAnxf0YUt{@Kl;!<&*XkPNAAoa5R~rrCgx#aP!Q-o;SrF9YS4D#e3)^d$){e! zEPi99dxXh2EtMP=0T@$Y<(DqPR<)75#t&*evM5uWg(rMyI*jf71H16U&pp(mJ9DUo zRzz7j`*r3o0Qa(nK>JBp6efHkBlPqXck?s(1!T>X0 zs~EF=PSEVYBN($HQ_3QWk4p9jM%2;+n31ZONN1d-)TCf9Dn7vz zF(H5x2hTGQoh{U(o{jN3pf+P+Q>mv6vkxA&*#ecaRHS$`Ao}<$b*+n^_NeWTmjMhN zIKlH*XbX^4s*`XcydBJQr3-N%T%7fu^N|Pzjynd{kOaBu&hodh z3a*5IVtDdn;GK+u`ROcaG@CWUh2lUZKu_L9LW?c<=Kdtp)KsOqrI?dGJQHkJx{%FJ zXEbX$Yp(Ffb05PG2ysh$djXs$e2k9G^p}JK;36q`0AJ}y09ztO#-KBS@x#Td3dhyh!eq%d)S@9gLUUz6rP zAJLh+d9o#>#{@4e&(p!Y{~9YxAx|gqKxUf`lBfv9X7FJI?>9G7Hzl8g^9^=>l$TQj z@Rl|k(;MEoxgreNvKC|*_2{gv*!;y!a8oRAo1ED3%G)}^1eD}sxt{D4I|4lGU+?c~ z*B6(@ioVTt9yO>!0`$RRx2i_3MKE5)JtxdBD1RCkA{RY1Xb4NH9#uXZc+Ss8=|trs zK&}{H@QA37sa~5*bxCXlY0b1G z4GYx*a`fap>va^i@uOsOJajb4tj-<5gd zt_5Kp+w?+lFUmLadlPeo%%%kV0wxT$;DI7ijY{B?RNvC8Z(b|bIPI6j*I#Jxa7E-E zgYhOkI1Us;pD95&x{t6#^h}TwG|{sUF6s(y_S8610sPyxiXJrzS9&D*DPr(f^;zV* zcx4z?Wz#I{4r!n?rf}OovSAaA7|)bdCoOHTN}2!ND*aNS-EQR`_)hgXUFt_>9Y$B% z=HGf+i%y#K4}Czn@{oDbDt7&<(MqTDw#P3n&}+LU{*iuYO}|;useUC?v%3JNmyEu% z8+;PhMeL7?9rzevsth^CdOrOUB)W@A%Hqk*REQ%9!+Ct7eO(chuJ0)iY+)0{Cl;VP~3SVPYFew-k9La7ykHvX^C#HKE^ppI|oD$Mv7^LJGeo3r2e&&nS)lvdp zgS>fH|D*gXd0Jedorj~Z=v!uFUf+3dBC&!|zZ^JUp%SgyC%qJV@o8AA3UJGieGE2< zyE^|H07U{A*s;iyd z=KM)C0j99?@in>7K1nduF$tm&BpdeTJuwc{=UvCyGWMW?1r7(V_{2G|u47>_*fW)a ziCH6f?{r?IuDZ+|3E~i>+`+v~nTk{uBu=cQ<8`I`is1jZAxnrCYA~yNoSL=M<0mlb z&j2>-y1~RE0}q}({PUH#PpELbw`t4eqW2g0%fOE3j(I5VIcbpmXj40wPN}fM*oq<8 z8N@jEh=X=5@8|=e`{MK)RX!=ZD_v%6MgIhS#?~ddU1``?N0?`=v7Z1Be(SYcbevYn z1!rMkrW`nfAP#f7H^?x2(~LyiSO?rzE9DkwwnA;LSU>Syx-D+q(we)x^% z{MT8aL1N-QA9e05BP_>$ET)&$8AU?OmsS?~iihg`EN0evnGSwO*?IqC@K}~?Ez=2a z!r0nYfOOOXTQ?9)OX+*1G67oS=HB%1oYaZ{>G9zxo2TvHmyyF)9=Sr0aoz`|cV#^| zf^m#QpEGdVg(c7xmi>C$2nCg?S&if++vTv(MUYrfP-;t{a2%qpRc7cNG^3z~R3uky z{*q}#ThMlL=i?^%l&##y>bdVi{Lj0zw)il=E6N(@tcypy>cl*KJqzrzI`iQDeV;iE zHFNJQlVWHf51Ko*9ReMoY=>X-(xTom&NRy|le}*_Ez>7^-vW;F16F>ZN3&kAe*0cH zJ)AhqtFYqh5wjWCBDVnk-En7V*xbOEm}zBFU|VL2=ycwiY_Ya3w<*+R#J}{ zx`r)M6kOBjO@wo{=AkW}?1iprtTQ!_vNdb9-u5UmvbyxGQIq3JE~YJJrd88KDuJ{| zUhR{mp^TMqB!5txWs!lOJ-yVOlOPRTITq-b(;4RCb!Oq_c3%FfA3UnJuRwnB*gFh6 z9mC|>HB&YYcP2AIg6h2(SY!UhPu#YPw;C4ZY?H-gCR9@~kyerww9yx?7`YAKHa}m7 zC?n|IKidb^e6{NX!|8Y>6PZk;5UDI_7X1Q^JWX9LKF=${e}V zBXrv(!f93E5oF!1i0D_Wc4ci-!*8tHdc9%}8)KT{MPuAwZNoVS$gI(zH4)V)1Ku|` zacz9i?`_#-P!s+PMSl87rqt)+6jfS`*$lhKbbJ`0IX$1~KVmqJ2WMYSY*f4u9aup6 z9YICsA3T0s<~h`(CZSv?u0gHC7|zRyR~#$)nO_5~7lR0WCRDy_F@Fh$LPf8YWtaMw ziY3X}y95-hN`?VO5%QL{z_)xCdTfk6qdMo<9HFd}^E)&XL_*);bU{Wywf*U)MHC|sFKVT{x+ zGW8$O8J*?eGcH5mIRq(#p>G_88vJ< z9cXzU_V-;GSC}mS#(`T<=Joss2hIs*-8jPNrMDv@2tMsGeYQf1vA@UlN`6KIqu+HM z7sa@qNnBcjHqmPQXrR36x8=&_*sCq(XqnvP^36Y3F9j3+4i}`VXZ9#p<_HbxaMVw< zaC2rFy`mVMof_SmDxUs>+pG&tlU@@fa#VeG>!v}5Aimo1Q-SNs53=jnGHRL0-z2Q5 zPFlSl_m(&*SlU$u#@^)NoJhg)Va1HxnaO9t6Ft`kAL4)jl zr$M#Ya5Wl`6evd!JZx$441>isSTFy+#70v#I^>#-PL9~(nLZ%m=t2{uxg#A4VN8W| z|IvK*C)E;T0ZIL%xuBlq`KBZX-)`ZDfK=;@MfWhZ;h%0CnntG?8g>GS*C-&p}G@ z)KX-nu+voiwu`wCG|-R=wAiDXzgXZ0ZJKx;b(Z{!6mis0;z32-?z0JFupQk)5C@_X zO?3-u1Am<+R^$N;x?<1-kp)MlrIc>OxWS+bv9{{_(U3b|vtT|8F%Ov4>MKH*ym=J% zGgE%;G_7x%_^aP3PJfiQc^87Tl}=~Zzg0{pG?&->h*lF4<{xQTC?6&)w!Sb@4&M32 zljy|vi-pCl0vII*j8<^cVck>8^KUuG@vgf?uhvF*c_e93IFw7GB_SmKD6|pHVDUW_ zAv5(`ZeoCjz+(W*FUP|i;v z4$N3&UgiIzdG4#3gI7WdoOUHGR;Sdr&`;3I_A@O&CWJGbVsN4=v(Lii(V=5;RjM4y z4PV*Udu)SCK%2!r5ae~)iqC^ddMPlBOY8cT#C_Sp6Z|NU6tvpb1lN+TT@7k2Y5)%*y0RE*CTsVKp((XvMXO8i z+J;F{c^j^<&gD)$aoA!a4ykD9GSbJ~;+w(!JDE-LEF+;>+|xZ|XYm6Tytqn3ESxFz z)nZK-!;5%3X1yU0wLxKs8xM9|akAe`63%-@kdAuZpd!eshCD`(RfHwuT@boA7W96j zD}{<}!zZZt`dCu3*GiK6fK10EZ~oT5d?b%U2YDP41zGN5?YZ~f|HIF5xa^FBp(_sn z796+}?e*-35DWGPaRO6!X%9&{i}3O_dAy8Eulnup#Sf_|AHVh?jaA{kLWpPVSMbMa zPrib!nrSIAGuFJ>OHzumFfw+wTON1|0?OFRqq25`se5{!>tbr#>U@me4WT4p#%3j^ zp-)XJoCwc35PXS)nY5P7mz@3e8JpuJ4r<*ak{<`-{F02?yyu}P;n6AdZsxc+@bNw9??o}R<*ee zj5TP}S5HiME%{_fzfHt^1s*Zpp6k_-vM!HMd^+`XMru$RHv=nwrE|p`iv(gN)_UI- zuFs~oFp7Le-&yYF1m1SX+QLaTPLUNni{&V+$>TIQ;~AX1xetk8?iaD`Myeupe_Clz zbL&pq`bAIeVS!Et@}RMCxp`-I&BSKQT&D@Bm;=N2oNWgt4A6_*BaW$rZ7>cNa)3Jw zEe)u6I0+3}eLh_D3+XLN1HOL%Hp>AY+voeM4sTl^&*_3Irz9Bb998yVF?1U)ZB!iQ!2}1ZhIM@Jou{1ooE? zz)AW6@?<|X9xWoQ!jc8QZzK=eE~?l>loD8%gRc?Z&g)F!0gT1QDc4j4&nBIbtd)#AL5|+mc)|%n#Og96PuU%vZN1dX1ULj%C6G44E=I zto{<*hysxXO>{Iw@k({h4_1ggMkyju1q_|Rty+&BQ3`=ienOi*l0}C0ZZ_JZK5j$M zC76LD*w|6eI-r(TcAqzwVXKhO@RVaLUIEFn@*zkvcjx72VD&WqbN6w(rxS^!!n)(a z=!a%NKpgaH2*%g*vZ63C_4=L7LAnaC<&aTcPg}W7dH}`e=QzmMx3lB1A6{ppZ5L*B zu^n?5#7kF?eqzmx1+Q~$Vpe)C$d=Awm-_m1)CZE#(!>tO>lbCTyak_j!`xM#{HQ8{ z|L!F=dkrQR&#J^3IcRR#TR$x#`mPcfdl(41>%DSZ@4lBVvYc|}yi13+1*fv6nj&4E ztQsNn*7^S-@At4!T#v!ERs;mt3Mvb2T6Mp4d{z~VIa$3oCV{Km$6G-L!6$dKg?#GU z&u%uC!Oyi;E0p=mvA7Pm@hEzWQvS3tc|A?B#VEFs{$zb$f~z6Nk0qY|Ay+3cqO`o` zE>~G6EmdLKN}%J$W_-gpl|GhQZtu%icC-5O30sq>uMD;8O8oXs^4o@}iqC_jVpto; zr5J<6Z$u;zM5DA11dugo&^jO1Cv~5d()&(V7!mC1KKrBhcJaDomBB{(oU_I1C(pTu zC6i-iBLl4G<*!Tc%_^HNVf|9aMtm6k>-YGeJ4CB!a)iR%?+#SO6S{>wEvXq=KcDSe ztLQV)G8R6rpU~u?g{}`{sSTsSvtlpVs)gUNEIbp`(N4@x5Yyg7`@w!;e91Y2mGhMu zyK=CK1oNyz0GwDL==~WaD=w;A_@qkxxnn9GL@(UFlo?aMl%V$;!$lN+tUxS=ZBGo1 zyBca?rch%yX@T>mm4+?ZBg8fByqF9+ng6#u&$7yDDrxQqu?ti7dtm>SS(nBo$il5 zk6wukSE#1m)C*BG9pWkA>qMp$9G(5o}d#88&8h;)^os) z|2&cVAdgvqq1ZFQvqOVnv(nt8|F+DZn-ZEl>td_Ao_BZge*gqQ`@VH_I?91ysFYCx z0`~8f__+^k;NV_q^$UPe`){-VK)Y*9+7M7uVK5jpcD%)o!VkS?6TVk0 zqNP;$TkqZ~yx&)^FQOL+r((4E0LVck1V#qr%qigQxj6zx=DMS(ghs)n0VZn=+Ur=t zamVNG;`6$OgDN`8XteyWhmP0|1uJ0UlhLxFW zcDA=0InI&&0lx3sw%pIYU>sN(;=VKaVy_#jR8t^w%nf1F zMq9>!Vu+KgG}f46)@RCIR}A{63=V)i8sM@DzK+8(t$Oz|xny}>c-JV;Zm9@wk!KmN?MkSP$BSH`bNIgkuTUAONk{NQ(N=_jgaC|t;?)TcRAudJ%J3M~5Xy6qC<7*RDShD(My_4CAmZgsu_AYP zJp|z6>jx0-ybYDXN+JO~cX1IfURq3L{PURW&wgM7_un;$mA`2uz<>c#bO!b|2G44! z^~B)-AN>AJ+;%8%)_$iHyMy|7Yik>~!l-Kqgx-I>wR+~*5Ffa(iMBsFHn`9R4(uf? zMLPZAd;7TkXnr2_rR@%elAMi!D5E@(SGI6!3O(<*J#3T&DwEBU=g-(Qz;Mt2XH}(U zCAU!t*+gT`4izOmdD)O$%{rHzj3-dca99ShaG?o>4(8(GB$o3v0pQBmPvf|madgvb zQo+gwCmN7n`Z%ef%F+QVt5lD3lIKB?8J0s_I>?j=5H|Yx)YlKh+l(ZrNTA+v+YmqT z*jDQ3&f0$k6f*PJ7aqlA_jDB2-nWH!zpV#fl!yQI==b}OQFptW5C9m>5K#4#|Gx8q z9XxVwdt^i3_kh(EVDBD+C(J4EJ&)|5lfw_7KzL(oF{Sjmch8l$a3BCG4^E==8k7!? z-r(7A9L8R=#MW$}7{xczY^+5cKgaC|i30Cl=Ezi+Ctla^$a+4aq5CXjhyXw^83W*o z0Pxnh&mxwVYGutgO`Ti!_(hYdOq^X}G&;#~EXp#+G@N$}e{>}p>H1}29*MB{(Diqo zd~qLc?0BpsPZ>yn5Wp{g@CJO}onxS2djIxzWf1!$7i94MGuYN6;F0rv{N(p-;kF|| zu^nOIx7TmnhTjY6Tr$6VgFu2+jn2+ zVq4+S`|?8p*ZVDO4LtLnt-chThA*De76owTq+*6Iw_|lpzcWr3mG+TUdaL)6N~QZ4 zjG)w-kV&FI0ZY^?tIi9xijv343kgg@SMl=(OG}ejzHj*e-k5{|AccU4gn&eI3XOcFDr>jM8rVXH$8m*g?*`dd=CHroeSG|+Z}^A zyB`_n&a3?=TVY$_i8y|%T`K}sS0sM;ySDMQ7kqr?%N_IvlnejsG3fVk?Mfe>=V4)C z5eth85aL$A156`8N`>A|4?8$8VDkpVVJm5(Udmx`+x<7<=gTSrto8x%npRpi+Q?Z$ zG5s=VX5G>_k)z=AJov1Vy`s-t%D*m}AWJ%td4>=P0XZ21;0+(Z>ytQ>x4aB>{d&Vf zhbmJ6kHfNU(x92-5OBoIbA*M43N!15Wk~v{KMr^oK^C(V0{HYd55&TXBLft5>swt3 z>3fZ9$&uuC_OxQD&MKnxHL4G^F@nT<ly)hhEbJJ#c+9fQcL!AAHd5K@epbl07VbJ zPF%1oam1`C^eU8%l^`^g_Tb6xwJK~)K+-xksaK3*27a@YvYwS zy?iZVX^nobkAANYAp|;|4%+QDTJ6@@6RRdN6fhVJ(C-h>>+K*20w*d^v`kMN4)ExG z+YqECuK4*ldO+gb=>gVX$R=TVYkL8!kpYm|EC_Bc3hn*Js1=`k4CInz=GDZs+t%9C z#zLU-*Q`r^kg8@J*q-z(A(=(Gqf9n`5i-ye;#!7Ks5dzS;AJ1cOB4A)R#s|d{*QyJ ze>a8%WR3gHGaB+1bKsy2wLSVQg^-HVD@H{c3BSL2Z8_B|o!9(7boT&1@a`R>bdSUQ zTsWp+R1Nc!+nXU9!qfkU$YO20oTsL!$~s&s|A?ki0s@f~a8Tpx6^fWq9u~J7z|gP8%^W zPU|odsx2W%1<52o#?%%$LU}a@e%lRcA!1EI^0QT-}%%Yo=BHgVB zK-uGQSY0RYDdiT}ihYNZiWnXYxrGeh90b7n#br}DkV(P&%OAY%jP3JnWN#zZcnFrO zg9P$dmf^r_v6BWYj-)NEv_z6I}6?*&a z)_4Jvx1Al}3+ruc_uXgN8u%D0f=?>N3y>-gB$G9eqgeWL!4mq2tu?T16rplMm9h4i zZ9&T26Tnt@L58xGC1hxkJe3(}W0oONgi6*?sCmhMMfX+L2&fZUwnG%d$>_@MZnyiA z2F|_?;Qp~H;N@kY)dFg^0v*LFlMSAy%5{wMnwx4mp^z;o49|n<+dzei-B!Uiv58T+ z6eZlfaCOOOsTyZEM*{N4zh^5^4h+vUk^q%?E@kokyKaJe{PAz2l3ajPZFPy-e~kzn z|Mmb|J;K*sXyKXXTe!Y8x_XjQLQ0v+q9-Bu|@BnSi%zZZIYlpw4Bq*72)K|w(& z8NOEvQp%BQ?DNKAN8!P{dpL1;n9@o`IRZqX((kl@c8f46P{b3!`LhFj;cM;8eXjRg zIG~dO89|U8?N^ulw@^Mu$ zRf+418pmuB4JaywmjKXeK~5$I;OjtA)%CS;8vp>{t*#=tc(GCVq-iujG;|%KP8e(cFhm4@t- zkbr|2$S(6^_-U+cU?wJ#!^lZdv=&)N0QM{>JbZ5-58d0xD;GUH^Lz{6deukJu;dG2 z`2e##JORA@&OXi@8=&RO!p1aYg`wIlpwlL_ePCLVkhh&3;ET_;)BP>9`!w`HoqiI_ zw=(}#q=h;3Z$Af$at55~_pXo}|6a6Mvoh%eYLhaXT&FD4URn7n z3XB#}a7|g^gQg9ZtZNyru1;!-e~lXeU!BAm#eoC04S=Q;e%(+&SvaXlgJqPsoAwsD z6e!!+LPPg1BtS#^8hVsgM>6Bj5@gqQJZud;gBXa{{(r~)y~N{ZY0q}p)RiB=1#OC> zT_sSg;_X{m?Y~R8XTkq@Mgajhc{sqy!vS{sgs;Eshme zas1E_Z(MFWA%H**190Yd>e4X`%KV-!G{ z41rnCS*dJ?Nfszsm%^;V10XBP!7O5(Q5`S)o~jxWqxO6m96UIw>HTGH0DNg82EdUc zAydo@L6~*RP2>cwLpE^D$guK@Cbg}@bzD(c4Njy2Je94O;{+E4^a7D6ec}w0H==z0D8E#De%JU z9$vZV;o=n!n>({_5G*Yy+;jT?Ck_phJ1}n$9Ide^0V6HW|j37$t~G1 z8k^_wSLxuFCs(Hd){*C7aOB9OPWPqQ0O)qR&#kSkT?R0A{J}eV6v3;n*0RW&QtEZ< zTCP9QFRn$jl4>H1)3spe1Bx?LQIVc`8=$ zAPZ8pJSZXu=K!UX5;@-hFJ%P6+JB-9cE1wTzoQrh`}RoOf2YL#cZQhQcAs$Rn!v>? z9^Sep@YYpPdTap6f6Wl6~I{5Tr=*OLH;&%Fh%Q&D}R@)C=>tBLoV~=Q=R@U<5047 z8T`7MU^ys9f_T_H3`y59A0N^PA@1K+bP&Jl54#I~~Gg<4FiHH)5scut@ zD#k+TBto{okE9B~S^FPB>>d+(UbM}p#N)SX|A~NW8-%3=l_EId@qVp>bZKB-?LYUl z4E}!(@#`=_!|;FAvDj8|?m!F&2EeDjxDeA2k8`~3jsZ>`9bnIb3YC5IKte?nHI)$r zA_Y%`8Tmrk-u(Dcj7b^-TLq|$AeD~gBi$}oQo=z2o`1qZRGO93Zn#AZ>5`6Tp_GD* zcCeLBxIWYU0J7$BT@Veq%eW~n?ktPnAn&oBm7OqT|9%{z$4OFVkdjpVmeq2VPJASESjl?mt% z2~T|^n_;Zi-8&ELM4?Z4)jR>!@~pQFg%rYFZ#U^29PkA{{WNC4`5W? z>`zMNBn47A4#BAMriq>bWfgy&j(v5Vuex%!%orR#4AtpOYT10o^-P|a)Y-igCq{}L zRo%A;B9|qMggnJ;m&%Mf37fKR%b**E*RHa+@#AT)pUC0_AUDLWJ(=SxJ+wS&)czAG zkO<-A$3M4#H!gdL=Wem5fhSxsuO5ciZo>b@9}*#Kw^!#L|D@snypgAvTUPd^pfJa_ zUZgOyVn7H zb`odzZ@aB@+^A_n2Akxeh)|-;e&4d7q!w`@Pc$bLhc;7r037c8^QRW@(a$X8rY(`n z3nJ6|k4vnWXJjJ)98}2j{->z_b3OBxS3E=bw|!65GBj=_LqH*%gI71QSC1fteG7wF zncIX`MCp0Nb5DqJCYr@~X|%zH+0=?3MzWF^4)0S*<==#2p0^!#DtY4K9ePnqZM)%%8`;k8T6sNo>-`~VPJSW z{JYY0jE?Q;W2G}Nlzw6egxdK3{Ey4{%-^&P{sC9*zl{)RL2GMrWx@Z*aFU!r&Xo0H zZ$tz@83u!@WoX<=hQJd~cktZH`LX|fivxIq8IIh$M_QrVZGr7Bfmv(NZvC6b^Cs*oHu! zG4SLwZG8C~t$f)+3P)FVQl7qj4zj8Oct=1N1EAe{I}QP`1*fUET_FHvdHz|`)j-1v z`}`JL2e{KqX3$y=dOd>xZ4N?gh_y)u7H8C<$1FqrKgLyZ~( zHu~|=&o1Dp^ksivTcSI&cExqh<8#pHAv(ywj(171yX_3}yC{4o^PsIz)!0 zTqgch=6;&!L{*hXAn&^bQ$VCTyV>l2Y;*iECHVr{1G;efGL}2TxC$T+14Ig5Z~LRa zT*UwO@AhJ2OC+U%8Qwpb5GD(ONO}Es?f(qI|KEBoABtXCDAw5g7FHIs_YWlS!s|Y6 zm7xvKH?Ii%`;RXbK5HO^{tqq>lF${>sDfP)pj=+RU6#j7Vsore#nu}+)}fv0?^Q%T zC!wgDRH#LAyUaOdL-$g5444j_!1&WL_w+{-n;gr_5XX;aT5xgP2`D3pP$fxAJh7?m1Mj`-GCZdE1ylSyq3UZd z`uO#Ky^`VtaLGxtY|ummq?G_TN$TIKW2;B_<-CNA001BWNklR zl~Lbst+zOAkm1U!8T?(v6uvJSzJ{{;%Jo_Q_;IM^dyOx3RBnjr3 zAK%x@h-CpXN|6o#AGfqm1g_kmwuT*9u z6ln>nvxj!@zPm5M6DsDHPg4aeEVNas%rF+9dA0xC0NDk#|55>;{!#}oznLHZ-@j*A z>-a_A`{LKyc;jXvnAHT+c@H>C7JhV&>(_{;l_7}Lb9#52H?097{aYKr<4Vd9^1cta^pe+8UA z*u!_9y97UC3Dib_h>QXvV0(u!7?LTlS#uzMyj0>Mv1Ti9mpV?v)3--&K=_mYw1}^K zGhYF>TM`HN4r+OKC-(OW(T7ibp@YACx^vTq12(qFTF1755Gwx#7mK0=?Yj(2S4DvR z!^B(ywz0sFf*>Q_WtYKfRA8u7LXxHO=fflt=j52qDQX;d6wZHH@p|&UKdh+p8W_J{ z`kK4l?j-;po|pk}>J-%8y^TOEbs#9w@C20w%DPM(sxAUhbWRrnU{K;RUh#N8mHs}@ z2jIjcDF~CyeQn}24V~V$+Q-{ZY((Evsg8j|1-$x}=dAsQqxRoL{hJbmWO`B4g8wz| z{QvjoONE|40Nj0SyL78vTXiijN}N2{EBxM|FPta%SExU6ih zf|2y+$Y73H$|UO?sQ+h0$GlMquaVY=JhHDJCj#mm01^oz@a#*TJNustua9fr)<6eBwl~4#Eo*AG$l5#=T{cR za1~iOu26s+qYRb>a!XA39?FKMhYjkwk|R@>XQml$gPs5m;*Bp+- zD6Y5feTpg5H4e z;u{{Gf7Qo}Z}{j}(B*Ak;?85+qpvjq+>1{GLHuB+NV+SO$W$4Fa~0-6@cluPV#) z*VieP*%dQ3B3NQfUiUvaMte7pRIEz1RT;TjwmV=|BDE^VE;E9|hY_4SImxO1YPZ|{ zaAgA^8ovwRA5Tp2-g~cM05oN>&QBe5-}8e5-|)51cw08Uv?}28G=QXKj4$tNGOqD@#Ljim_NdZ&t;Xw(*$-8z@`J zGlL*DgatrDn4A@sfgl|QY2Mn@3CdN<0PQ5fCd#p^WUTd3Bi*9v*7$X*PCT2S49ViC~Ou| zW8$j}!$C5bQmTY>L?NjRh?9`4rY0VXb;jM#B7@-c!EIc7r4_gK)g^&issH7dI=J)X z0H==y&TAs8QHmr>U|Pbuoco{seELIRb6a3@hp@3t*xVNJ>*h9LV~4QQ8_mjCYzMgi z^d{O3E7^l9L%jXeCcg609t5)5H4cWrH(v4ajaPgS0ZSc)<)x6DV9%n$@?!Y9XF*|k zNn&Y1g~r|tj4wUc!Ydc^a~lq=4C0?&G8bA|IyNSq(n?<<9p7hOIOhgXme;NkH3BdM zTSGA6eH~siDr=l;S3wX#S|+ef4Xhl;poT2trc3>rm_lV*yq7gPsyLIwGiM+V9h&rX z|DDR;z`V)FLjWQNGAe?u$}FC`l$SR2 zrxkOa7i=+$#?p$YpgD;ZV<(=$6lLUl6hhG)wkHD49@)WjZ|%WAdT|awh#aUG+Whdx zm+%Wew1Jfs1yYEiS|bkYfcQB95Wnxb`u{J!(82ncOXgOWhB$X}v$E%3`uKYS+<$rl z&%C?`JA-=SRjCji*xaF9QTg77dbsb*|7?eHtl~7IGFKooE}&5K8CgCU4nn?wi>8>L%LNned`Pg+A&%iyD){ zMF*_o>e7mP8f|`=XJgQ$TK@|X<{I36Y|CsFtosB+@3;Ge|M$=K;L3H+@SsxEs430g z+<5L4f2_(sEJ54CJI;(x`RlQ~DDloaZ{XCSov8B!v!Gv3_Oc#u&$m)|%kRoH9B6C7<>MCCYM)a$W$qdOcN=`$nY<9^7{y(CJKi%KuiKYqe2;j{tabqL+R4Eac(CV^#8_D9F0UtunF4 zEhdY?B1*Z@0U|RwSz4aAR3;e@&-!=r_S;4V0cQ?xW6wemu>-Q40+9&Bs~d!W{m~`7 z_@Md97=$DwS*SUAx>c+10gs%$9%UsL}-^Fj-M`kxjtY`Ak?+m|?Xcn9Bh_ch#k zYzJOL<@c)M=ZkHHx1HL;!*|@kvDH3A!-&B3O@Tl7#1h`PGMQO?eT(ps&o1DJr#pq7 zf2SpJ=drC^u!u7F{D~Z>09o@+vj}R$ONRvFF|)qD{-yKh&;MNj2PbyK>80RM85cOg%o#_-A&_!X-HMzt{Y8?`T-GJxv^)2{~`94;cN1xr7 zt_%FlH(U7nORYk}ktO9bs!!A5Bxxt$x1 zu<j3VW^xZ6d;uC0Hy$T=s@p)|k4`DXd(Q;tu*oS`)*6|}}HGcIowgG@gl=`kf zJ$5RY@fRrN+-~2)klo*qeK#QU{<-4j33-B&EPY>uuPuQv8O;MaKHx_leWw+^Z?ypt zz51>2&uangHsH0xeW(S9R`|d0B2=Iievf>>^Aq%dG;-hpUwdr{uUuUSkpK_yyil=w z5gI@geFqGo5DLc+1vq&$!0kr@96J~iyYNti?fokN*Eb25u6ek4Rp7NtUTwwSX-h1% z11xqV7NggtP7op_1&eT!2*4;FUVlK?>IrQ1z3{c?VXI#cDIfyQ92?@ueu+c-1032X zu~Y&X=nsL{-tzGBMIW!c>EY7#>gT@a_!bWD8zhQWCCPSRUcL6xRkYwF^Vp%7XC8U* z;QtSkS2+aKAoAu1p@LRJVAv1e%YL{|FaUynSP&(n_i_+@7VR4h!gIyP4WiGZ>k1-t zK$q(V?7o7?un&|eKSHCP8gV%6Bxf$KhB@*sPk9T6oCcH2g2`py@m<@Ps8Xd+mJ49G zx{BWW-Zu^9|6I4*y}RtrRr8B~1HiwU^d0Ry^bq_{eiBm3M(Q!^SeesgBpipuUCR5< z&=y)C3Q9K!;uNIop(WdM(+u*iksNDzSe>VlWI1 zj^2RK9}sr>A**7)PqitEiGXt_HVtZJ4n=d8KdBrKz%m}S=kBULiI1h+92ELH0tt5o zPy9=905U6R^Fr$fVO2_79jwB4&*fJ2mm38?P~;}?P>2j{LJ@(Rx5npY9$DO z7rplx63}W#o`2;1Bl7k|i+TQfn-`)C`dsj$d;tPpzt+ar-&_JfQNI9{^bBC30l`ZM zZEgT7Q7{kwrwWVct`l3Ov-Wp00zhy_QOISqDB$a_FW{9c9n9KTXiMCCd=o26(nw_0 zL{_krtz>ca*z-pk0Z@_0Pwc%4B1C|B=yDjv_Xogmka+g84-ALl|HFRd=|}I?Abc;Q zzd_{v2ZIP<2qJGk2#qi$!)wxiyj}3HBmyw1VlImS*z!3~8D}A_w&(9c2?Wxmk4^S} zkpOlcd1TUbzj{2~?RG0=9MoY^7OgVFC&B0lo*|>@S(W43+hy4je~=w zf9~A0lz%PVTv^9-yWQUd@Udy!-Ojml*d9|G#ky9Y#Ow}!&7Q-ecimHvQTs0)@F|D? z1+Q<8ydh#9m1Z^LEdhBwz9OVD_Q|2d7upIBp4q^;J)HpWxP1fn9Cx%+rnI4}w=0+m z-f38IrH&-=)L_lD`vYX~R4Dr#%6!iabmc&>coL#flBmoIr!4tJ4aWbg$f~m4^zuTq zvc&m0{NH3~w|D9k1~r}32FAxC-CSwZC0KRt`ce(NK!|=Da(J9e1sOkhPBFCwd z8!$?509oTYD|vHe>3|i>r04%5@kV6i{gXv;8g}+GyQJxoKUcFLXHhiLhQW!0eH=T` z$7@$Rc==KXLnYwq`b!S)zffsWu_8Hjcn1ge&bH!L4epm^Fz`(IyvMe(DDlW08+hqb z8!un(G*(hrW-PS>oH@LMLnVH%xZw_&hJdS(B&M-ej-4EQ7~D|M)ii-%#(<^JV;W## zhUxTrv-4+omrl*jX_w=Xg$9z!{{3fx2&inF-lfpXu$?v~OZ$fpqyO;3(_HfZtj?Ee zr8uvzufKTy{Q2Vm9+}3S_KqDx`^`7u^?GT`7w#H=yLR5*hp%5(eW|eClQrRN9)K)s zwQNk*RU7N#I8e6Wf|~~*V9MH<_zo@r!KwiSBkS=Z#jQC2yeL1w^OMhnNMiZI5BC#M z^?>Imb^dmyKSjg}ad;rg_$PLL#omi3^MBt`fRhJ%SY8M~1oQ#{1u{_thd0EL3^>pY zd$fFsW2=4Kb$lDQAL(JSQ(tsUA_t%>$Wb@hFpuvd^E(v%UI+SEjk@h^_ru?->CyHj zj_&K@jw3s`^Jouy7G&xAZjxs|=ySNFQh8L5;uw<^y-{Q(uLCJYw zUqe>w`=59M-gdeE z=vkh#ggY@L4>p>#bqwTp2rPU5LBXO{3TDSPRt?};0*`zDT)+rR%IeI2!IXee&(GSY z3RW$^SGriwapP5nz0!u&_}^|60>IGbtt z4H^n{kcC>t1BLB~_b+ebo@3j1^Lh)fUv1;s_GnEH_H+UqS{dNb$^a{iGA-z{ls3$k zzj-&!`%btVe_q?qIsap2q>>iEb}tOFIt2=(iM)8$zitr36_ij($c`v75-de49E!Ov zGK!kAsH}0}^62YUX^9c;FB73yT*UUf-WAq6PiI_>baAawd&PCT-HU5$Yrl#KlLN(8 z*YYy1KKdy3Kk)>F)j6;z!9!;3D=JK&rdm^$opeQn9d*JTE{j%Xz(OiR+UxLvaAn&I zmartuL@2V5ju>@-WdvB~RgCC_qJ$|W62V6V99Rx;Xm0?b9bDfLxUuD95D*4|h+YYU zp}D-!HbMVbt%gj`!6Nr_#nZnk6psJmC2L0gmqo#=U0c*gnqTQm~^Y-??OOkvx_o$neaH z=vE8ck3I^uygbb%{;SrPm3xu~Jk%f|jkF%+s2R8(cx5*&u(3Kd8(lX342mf}|SeX4W`UVKin7y#|0C{Ye; zCMMN5OQB6`ys{{{|+^a{gQ+fh9U9Udu1j zeJ_+XSNZ%__FQJ2E|o|WIA!qv+v~{g#gQ^n7-Aq*c7@LFLAG|^Hx_b zr&_x&E@J!9MSt*#sIh%J}c6`2@5PFxaY<$|K(wRe2~AP9O0POclnX!Jd&;bbs*OS>u2v*lKn{HSz{(sF| zZYX?_7P__(pcAdKI{yLgZmnX5Jj`hi8exA`6G<4Cf~OJhF%@+xK^Q?uuM1FieBWqI z%|d0Vj)Edl`~oIXmyZA2=T*F%P?;J3`u(VtNM$|$ytchn%JxuV-*J{>^J5PWAI8>& z3)6=E0pM36-8*$h72R(4p96S&s+NDdjcXS!V5fj5z{SirEV;kTnaJaKuw_F4qQ-Yp z9=uf@kd-kXCqT)lGJtJ^#hPZcG|ekDlrp;-a>F21ZiRf(pMqSk+8*_+ zDfKe#fK>&Bnw11d6M9B8rstwEerR&tJe#Y0zA88x2eEp8@N!ymgrn@TR|R;A9gb!8 zbE819WSZ1^g`3wyIXv+uDbPiMtjcr5>KwlbvbUnqJ9P@%7cNXQ;ZKjpBb__)XcF}N zM*y~`{bV*Dei%Cy$r#FH_Gi`ekIU1qyCBQf%$7aAa&2eAOEL{0$jsPR?2j}530I8{ z+t0889Np4VENXDq{l`29wj_7a{EB8g$XOXxLI70pxHQ7~X5zl7pp22SjU!v>6Qw`n zl`U`?wB(-O96gH-U11)NgvlcyVYRs~Asb``r8^2#?{Z9JhSAYfwLukmTm}B`ojW(} zI)6R3BVF6bXv_fUcDv64_=mIbgnH-BVf&Fspd1}pWC4^^)}x;U0x%93D^U6Hw~-;N zOwZrdu9P#L!6E|U)d4XQU=t4s6UKBUAQ~hR1w__N19jCAd6832(H@Y?`BGz(8@c=9B+-}~NFqDB?As8Js3q$z^SAOjRN zEaLmC4C-GX8?x8%Bdm%5o`uhk8++iw>;pgyQniJn;T^F+vm#6RyRxoxwA!s0;~%Go zttR+vslj`wn@f-xS7H_>!Ab_SmAC6~8vPRHgC%PX^PCH5mDRd_AA>=#1oA`Hj+2yQ za*Xn?&Uu2>Rcycaz0>6Ox5tMf9Xm0-QO5}QB>+#(gs|GT554!j55bu;r6)yG$t7Y! zi$sn%(Ur=!{&`E(p<)R}Dv2Q)8Tl@_Ubvg1#150l7Pg$@pEygQGC2fN@P8it*Sz#^ zV#dKNqXbm78x-aIOX<)6=k+Jc$TkdRau90q903*{P#IM)Fkhipi8joU8Is!~uwp!d*2=s)=2EG_5lNT*K47^;H;JPqLIX39G` zeHwV|F(4`xZB-787-u9MlA#QBxHFqxL?C|B=?*&uI4>&v~A^qAY|A7KxSJ z=);j06Oc*io2tZc;$e9BFt#6i48zl>XKfk(ToWaK6E(7; z)`Y({Q;%_Z8Fb+S=z#}-Rx5LI$Pp&(B&^p_lz-WMZEr(4Sk5#=$ti_xu794ntUZ1G z%1fYxwJ#4D2zCunY2qJO1pwftFIF{wsG^2Cvo@Nn&o5ghxa*JiqfhrvN-OQ|o^Jet4%(1H&QY)vGBO z&t}ge4!I%S6)+^`AZMp4!*ebvC{@m%UP$);HodXk4UltyBWf8ctz##(9Nhp>alV!PKGpbqRuF(x{z-}R8EaRv?>nFn-eNaK&SWGb zcp|H0o%`6Ul7NgFeyAXM4;gg<48jh9x^tjZY=5@M`7ucWfwb=5>Rlim(BG)Yj&yvN zkl*!BmRxJflF_!@wPkbO9POVugPlhog*nWTU7fhDhOMfKi{QTW2Fm|^^bc#f_X4X zlbHzj3UwtF8lSs_xYd(Ai~4t#IPk}bC`{_Q3A2L$(6|qlRbfJo`N{iSkcp<*T>mZx zP2I}d_Wt9Jfs82-a9223g$0G^<*4!~OSTk2`lIw=sVZZKhYw@xJ?{x){hiJ%D*wkL z-8t6bPv~ZT62M;ocyQL9vUlhZ77rc5@a31$fA(3(>(`wVN>-TcMa?E8irWrXvEXEd z&qRTO;e6yNubE{kb68R#A@|{Be)>BC=Dx(^{jQ`beq4PX86pH)l93}J5H_2jVr5V+ zPhv*1|EZy4j3YFmaty>-?+0KJ`K2F}t5vW}Z{vI!JRc^0F3QS{QOVj-aPVy~g-#iq z8XBF`$ectN0vPh%8(`Gh(fa@_bf(7t8x`dMOru2FkiC2NqJPglvsBvW8h;(>%(0J& zh`?^QdlkTs19)LppSXYWB$gg~4DE*>hUaMVkkx*&3iIArCUnmKd7S?$qxRpUY|mfW zm>Uw$o_pI8X}Z!lDM}>A=(E9RaQcwvqYk;p{U*Sw$f`n>v{6^T6caFe;qLT@8?BEKZ{3`K07z zu)2!g!w+NovBzdz`M&_*$0MCNeg}9`ceTF0e);_Q^PdLrV*nOs_33;2_Q5}M24eqy zAP7Jk8_pa6&yoRl4nUru6eR>*)}JicMlhRkxb_Q}FfcrFE8ebYd%ft-kK*?F)$;&9 ziu)7G1Mpi(+~4y7V*A9ge-Xv`qland0mKPV(YeVo0`&E{h=T|j+u$~On)benjy1^~ z0BZRAd1ZjJj*Qf~R--`{-_Fn`Wd*fKWqjANkd0gbnfyup7lgS0aa>=9i&qBGz5)B5 zGGDdFFO}IP(3S^)-JO#RM5k^uAmuPDltaD&W+g+J-V|1Gg!51P7V`LU4Bqh$4Bq~B z$l0ms&l;Bje4yLyer00%ZjyJmzP|q2`Sa&L1K=kBv}gI35X;N(PM!pvJPCLn#MTzP z;n2vQ^&AF(a0nVv5iV6sQ5P8mFyKO5XF5;}TQ;!eR^*ta3n5x>KMa2h}PZoCUTZsp#5_GcG?( z13)!+{kw$;u*`wUG61Aw{R+pT6-?RlCO&b;N);-1V&91kfgsw%GkBR%pd(3GRH8;I<`PM3D8KV zSXcngo<;ER!wBxUV-_>}U1Kv+{-2$w!#nA_Twh;*@%;JopN))w)~x?3)NX^09D%y) zE~u3i5CH$ijZ_k#HUM~>F^hk`sOeCawal$@W$hy^2Sa9Tk8Af~A^Ur?0o zFA_!VG376~@@KEY`aJgjebdYLg#!i9=Yfd^kX05%HodCD0HBQQG(qE6^kw@FjKcsZ ztCXuI?@m%ZOC~lF29+0zet^EXx3p4kT4&82~3vfX_U=V++imFIaR=nm5`>iSx3&xeKv~Fu zOW{)qh^uTFptKf{%y>0I0OTl%VnzTg_@BR$h+zN7V{b92U%z^OxQ~9-N?&+p7(n<* zC?GzsE&~)vj(|X71aQMdqXn@75)Kx@EUIB@PVrPN5uBs}&@>7_4MFSURQ{^QJ!XE# zG60NbKa__3rKQOz+Ni+tS!{!Vl>vf5`f!8=bSa<_0tmum%OHaMCG+}WzL(1K_;W%4 zs1X7n2U=1T0?0D}C<6hA!?K4D!@v7(c<+2C#HmxDy?e3ChW7kFK8=%m)4J=(2>3LB zAI1#l1h}7?hhYDH^iG|^*4eWduB-q=@VB?&DV0J1s$@*_#Sr8y7~6`b3}w&v4y1qKcpGrW}AQ1C?eN0jPsgC&)h-KBOELpc5Sqx{Igf}&YeXEjM zO#=ZK_w%ynpi)*fA(>^V5{yaH;snqWSeN*^fdlXl9z^TN5!@u>1pq(V?RGyu&5Q7-{S><0?&sIm*1i|O zzsC&n1XLY6M~+~JO;Uej1D)&F(Ybyd?Hf1H+Sov6VAicRc8 z46Y|~6aN@YeJ~U6ltZE#UOY^G0m{;hc5!!^UeB{sv_4@)m-RSi_1Y$8RLPt#sr+sB zF2u@gvuf`ZgW`D!t%#I-7BQ9(z>sf?)}SPFE%IX-MtDUofVx!xRS`BRM3{KJybM}d zfwy-rynXxN?T!S#s^hN_mij$6?F;c|;c0Zc-RIWU*1j9SzXS08o9UMxE-z!ayo}8g zC(H-Gy^Yq^7X0n)@UPc{x3dGU-vmmRO?y$gaG7QQ-8&|U(n^Z_Ee_~iR_x?2>00{s=w2mYTXR-C7*Og@J#OT9;#0UT) zYHz*{qjhdm|LV0R8KD+u?+j%Hl~VF9WlsQth$~#_Y~fZ5Xtkg^9Z;tO(dj@eE`k;p zfu$uZE-m2}8IJ?_$w}4u?>d8iZEfxU!i;qiycvcz0Qh@iDa=WyWQj1Gra+|Nc?v`l zLWHlrui$wy`4a&=U!v6tAcREA5C8W(3E!9Sya2u*z850=z7YER$vO1<@H{Ha5rB#k z0aeTeKs;Fx07X*RMi~fcqeFW|^D!S;2Cs4%!K{{EGLCARLUnpZ3zdZ?h-zsC~$WWsP^lS6EG9Km6Jpeeo0)y@sdEW_L9Gmu+ z1V!FUsK_bBP^Lgo20I$5G~xgTjgpN>#NL>)AOm)FbNMwNO|732nVd$OY;H{OY&gD? zO!hU_Ldq}?jp1-R8J~s5ZUQwBJ3d+Ui3ZIu@@0WW0p_F8@oTfD{Abbth>U>$8Nde- z)<@3AI3XZ14Fv-sfMSYQqXsH|9~Tag(!KORvXq_mfAf>%7qXfWcO%d_+$@&hk zHOJR>o)a)1jgM^rAB=R`%ndQCPc7$JUUM&h%;QM_?~ZiYOb&mRpI*1yeHy^Gt*x#7 zD*(SZpHHJJ_+;^GFCy&?*dB!`Sdv%%X9P?w_dF4DHh-aMKq!O;11I$%WtBQ1f&VGX z{V!8|KOgf^WmsJUiO7jW@g!T~^ty^A0!n5?8=)MT!7l+L)~uomuCGUcZE|FJ;}=xA$~3RTqS?JG>?>N1gNwf zF!$=`qtT&8_}l^g>m*m6^CzMY8X;K4{&?TWFh_!HOH*DtNFum3;i&!680@O|_7Rv@=>vj4L>=yCWz%;XPW`5y^h zwkIPl|D>Fcmk#RfVP1fY6A6V-M(07EKR_Z8m=LVAk62H0CQuE&e;4<^nA-~TQFZN< z!wGC&ea-uih)y@8|D}jAkm54In5IvKrGa76o<`;blnP7vlng%y^fnQ1)8&3)IifE2 z3zKb>O3MFY3ScDv^A$l+vOrd-aE=a?8o$!*cK^Zp`ue5abkVyK8SQqv{~W*rb2MNy z;~@Rsgb?!>}$YyHnhO|SofNGI*O;qTU` z9wi8V|JvHxKLGH*09c*R#fn0XTrqzR-}4ZFnE8lI!yH=lZ|axC-D`yWQ?@?&h1=^|?{e4{#5F56$PK>ERc_|1N`ozju*`*a=2< z&3_U;FcAg8;*)~R?rUTlBN`154$q$)9+!SQ&;FZ_rq@v(G@#r&8}n3{iZ;ooQRZCN z1_2Vqu5$pw&Y8rF1{<|w2DnuQm>Acp)Y_ku0+kvc0&vf6SN^~*%K^w5>+9=V>+9=( zeE$6Tj{~?Jz#a2BiX8SolR3=vD$?&gPkZ?if)qR-ahyMl@5?0qPYNVtOz|s(f~WHa zbfthuV*N(DUn?{qEN*ibVj=7fg_fLlZJUqz$W-ufbN{Q<4rFfUeE~F^ChLl&WbA(< ziNlL1Rv+(x2r6nk z?xHSLq?{SXI>R|e0#uSS5TBZR_feM-o+X`-pCCocrf9C<=VL-f1Itx$Hn3+%fbI`q zd?b^9!axDB+!u}rL`H7J`X_|~BpDukavlW|0+31VJ~>-xro#Yp-Cj7J2Jq{%Oz1N= z08B&R_W=Cf+S=ML0{HI$+;vL{r~>%kh3J{3f5=qgF(X+DO;8TwLM7S!+Gk`^g zGJ|`VEGEr_^e4TJZDSx$252MO?#*$Fk^(&k;5WM6?oAtB_HO21Z+(6J^Xu#D|NHs# z=idZyH-Lk;l82wA;L}1vG0IVs@W(CzOt#4w;@5w^AHn^)JWwbI4^gNfBru7~{3rD1K7#Wl zgYRVw?~72mtI+aJpnVaS`sr{%$m%DoGQi|I!lXbJnqcLDMXTf%G60?h@K3wl?%!Kq zU;q3~ttW1!wZ68t_R|3VYXI-MRS^I)iAcq*e}&+@|2&2-qd2~V=PP(#V73GHBz!+$ z?SG}QPrOYi2rq00tPO(LNQgV0DF~L~EO@Sf(#8T$8dN52u*(3L=K#z{?pn#P30P-8 z#kT4HNyqh7)FN213O*TbL!^*p6rYD;t$`&u{&Y)U5x4S9S^;};yI|cCI0yuSp@(7I zJ~(Uz9JUD#x9OI{VJ<nMtZjIf>H^~r|NrMqelrSgpA<{}aK&z!a|3t~#O5gXBZN4Yr`B8$PkPyre zND>3h1VQ0NzL7>7H2F`&IHW{0Wcq>4FF}nS8SVN-aTYovL6}& zV9Gxt{E;e3+SB_4(KZ#5`~;H1Z6E|g3Ke%ikSbE}145v@Nbx6sLEuG7KYSkqgyGPG zQhFOfN-t$#Xr-SZ2O2RziXcd-TSi^{A%NfNcDo>1R#?zfYOZp5}~Ibg#$vO9v-)+ z7^;^;fuT&o03p;*Mi@X!4}*b-Uc>)&s6ZnC!y!Q_FFv+XUhMfNe_;6p>2koEGz{=z z0ROhz?S6PZ`{y#zWe6Mu@M8e}GXU?u*$e=?HLuZUoGgxiBiYXl_w!i45J7AVB%uP$ z5fC2F_mc2}@U#IEh5|&C5J-e5DKP36AQGtGtosj(uZ3MHKqjg%pX~F|gaN?w01(B@ zV}%bG|LYt8{ePe#_aHF>G&HXmL?1{PieJP0HryW?10oI$1VLyJB%ein15{!_aHB)R z|2$DJx!;@40C*h09{~9MZnt}BKI`W)v8*w07{CVr{2+iI0I=s~FaS)(u*3+6oAii; z%;f>@CY5ybKRR$DT|Ac)^Gx7^k;Dn z%ng9~n5qF__+9|t1K@oC-aVrt z&8hr(jNc9c@V37k0Z0_KWDvfzGW;X2Kl)62{~R3%697Yn9~%I=OCWJ1z|8&^A_P(6 z#6PY0MS@~DDTFD5DtJDH=fhqpIR=1T4InER04nyd5ku{v0!|EoIQcI~kbocv69Kiy zt_=VU{SSu%G6=K$69gbh2$X?`KvEn6&?rK3JX7)8Apou9dD7!9Q5l~F@Cg8a-tBfj zHlN+)hChD<=(X&9%}T}EUbAg_dP}7#xD&-}8|NIE;D&(7=ErD( z`MFBTikVnF+W@`};A;TB3gF8CzSQk@pPSFhxdAYXM!|Ck=_8E{)3vp=+X0+G*r@0v zfMWoT0yqRD9aN`HxJ^*L%6SrEX2oVf0`#< zC-q5~&46NgDNMazD4Hl=kk5^R`KYYyOYU6wU1FhxNQo;2lM35WUTd%2WS%Qa`4cnD z!}n762GB?aEAw;FgXfh2jyM7U0{}Y!wg7AZxCY<~fVTj=0pN82F9Uc9!1LX1_oexa c9Pjx50Zk^qkP1p)6aWAK07*qoM6N<$f?i{zH2?qr From 7abb1a2c84376d33a375cffe8ac0d5f58ae5f9ae Mon Sep 17 00:00:00 2001 From: Francesco Ceruti Date: Sat, 28 Mar 2020 00:30:06 +0100 Subject: [PATCH 223/333] Various improvement Update: dark-theme qss file has now a more readable/correct syntax Update: replaced format() usage with f-strings, where possible, and reasonable Fix: in some cases format() was applied before translate(), breaking translations Fix: misaligned menus items, when using stylesheet, in recent Qt versions --- Pipfile | 2 +- Pipfile.lock | 66 ++-- lisp/application.py | 5 +- lisp/core/configuration.py | 4 +- lisp/core/dicttree.py | 9 +- lisp/core/fader.py | 2 +- lisp/core/has_properties.py | 4 +- lisp/core/proxy_model.py | 4 +- lisp/core/signal.py | 2 +- lisp/core/util.py | 35 +- lisp/cues/cue_factory.py | 2 +- lisp/layout/cue_menu.py | 4 +- lisp/main.py | 25 +- lisp/plugins/__init__.py | 5 +- lisp/plugins/cart_layout/layout.py | 5 +- lisp/plugins/cart_layout/model.py | 2 +- lisp/plugins/cart_layout/page_widget.py | 14 +- lisp/plugins/controller/protocols/osc.py | 26 +- lisp/plugins/gst_backend/gst_media.py | 2 +- .../plugins/gst_backend/settings/audio_pan.py | 2 +- lisp/plugins/midi/midi_io.py | 14 +- lisp/plugins/midi/midi_settings.py | 15 +- lisp/plugins/network/discovery_dialogs.py | 2 +- lisp/plugins/presets/presets_ui.py | 5 +- lisp/plugins/replay_gain/gain_ui.py | 4 +- lisp/plugins/replay_gain/replay_gain.py | 4 +- lisp/plugins/synchronizer/synchronizer.py | 4 +- lisp/ui/about.py | 7 +- lisp/ui/logging/details.py | 2 +- lisp/ui/logging/models.py | 5 +- lisp/ui/logging/viewer.py | 2 +- lisp/ui/qdelegates.py | 2 +- lisp/ui/settings/app_pages/plugins.py | 4 +- lisp/ui/settings/cue_settings.py | 4 +- lisp/ui/themes/dark/theme.qss | 314 +++++++++++------- lisp/ui/ui_utils.py | 6 +- lisp/ui/widgets/colorbutton.py | 2 +- lisp/ui/widgets/locales.py | 2 +- 38 files changed, 326 insertions(+), 292 deletions(-) diff --git a/Pipfile b/Pipfile index 4bd7e8815..8939c0636 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,7 @@ PyQt5 = "~=5.6" python-rtmidi = "~=1.1" requests = "~=2.20" sortedcontainers = "~=2.0" -pyalsa = {editable = true, git = "https://github.com/alsa-project/alsa-python.git", ref = "v1.1.6"} +pyalsa = {editable = true,git = "https://github.com/alsa-project/alsa-python.git",ref = "v1.1.6"} [dev-packages] html5lib = "*" diff --git a/Pipfile.lock b/Pipfile.lock index d333e7cf8..672a98a1d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -71,41 +71,41 @@ }, "cython": { "hashes": [ - "sha256:01d566750e7c08e5f094419f8d1ee90e7fa286d8d77c4569748263ed5f05280a", - "sha256:072cb90e2fe4b5cc27d56de12ec5a00311eee781c2d2e3f7c98a82319103c7ed", - "sha256:0e078e793a9882bf48194b8b5c9b40c75769db1859cd90b210a4d7bf33cda2b1", - "sha256:1a3842be21d1e25b7f3440a0c881ef44161937273ea386c30c0e253e30c63740", - "sha256:1dc973bdea03c65f03f41517e4f0fc2b717d71cfbcf4ec34adac7e5bee71303e", - "sha256:214a53257c100e93e7673e95ab448d287a37626a3902e498025993cc633647ae", - "sha256:30462d61e7e290229a64e1c3682b4cc758ffc441e59cc6ce6fae059a05df305b", - "sha256:34004f60b1e79033b0ca29b9ab53a86c12bcaab56648b82fbe21c007cd73d867", - "sha256:34c888a57f419c63bef63bc0911c5bb407b93ed5d6bdeb1587dca2cd1dd56ad1", - "sha256:3dd0cba13b36ff969232930bd6db08d3da0798f1fac376bd1fa4458f4b55d802", - "sha256:4e5acf3b856a50d0aaf385f06a7b56a128a296322a9740f5f279c96619244308", - "sha256:60d859e1efa5cc80436d58aecd3718ff2e74b987db0518376046adedba97ac30", - "sha256:61e505379497b624d6316dd67ef8100aaadca0451f48f8c6fff8d622281cd121", - "sha256:6f6de0bee19c70cb01e519634f0c35770de623006e4876e649ee4a960a147fec", - "sha256:77ac051b7caf02938a32ea0925f558534ab2a99c0c98c681cc905e3e8cba506e", - "sha256:7e4d74515d92c4e2be7201aaef7a51705bd3d5957df4994ddfe1b252195b5e27", - "sha256:7ea18a5c87cacdd6e4feacf8badf13643775b6f69c3aa8b50417834b9ce0e627", - "sha256:993837bbf0849e3b176e1ef6a50e9b8c2225e895501b85d56f4bb65a67f5ea25", - "sha256:9a5f0cf8b95c0c058e413679a650f70dcc97764ccb2a6d5ccc6b08d44c9b334c", - "sha256:9f2839396d21d5537bc9ff53772d44db39b0efb6bf8b6cac709170483df53a5b", - "sha256:b8ba4b4ee3addc26bc595a51b6240b05a80e254b946d624fff6506439bc323d1", - "sha256:bb6d90180eff72fc5a30099c442b8b0b5a620e84bf03ef32a55e3f7bd543f32e", - "sha256:c3d778304209cc39f8287da22f2180f34d2c2ee46cd55abd82e48178841b37b1", - "sha256:c562bc316040097e21357e783286e5eca056a5b2750e89d9d75f9541c156b6dc", - "sha256:d114f9c0164df8fcd2880e4ba96986d7b0e7218f6984acc4989ff384c5d3d512", - "sha256:d282b030ed5c736e4cdb1713a0c4fad7027f4e3959dc4b8fdb7c75042d83ed1b", - "sha256:d8c73fe0ec57a0e4fdf5d2728b5e18b63980f55f1baf51b6bac6a73e8cbb7186", - "sha256:e5c8f4198e25bc4b0e4a884377e0c0e46ca273993679e3bcc212ef96d4211b83", - "sha256:e7f1dcc0e8c3e18fa2fddca4aecdf71c5651555a8dc9a0cd3a1d164cbce6cb35", - "sha256:ea3b61bff995de49b07331d1081e0056ea29901d3e995aa989073fe2b1be0cb7", - "sha256:ea5f987b4da530822fa797cf2f010193be77ea9e232d07454e3194531edd8e58", - "sha256:f91b16e73eca996f86d1943be3b2c2b679b03e068ed8c82a5506c1e65766e4a6" + "sha256:0542a6c4ff1be839b6479deffdbdff1a330697d7953dd63b6de99c078e3acd5f", + "sha256:0bcf7f87aa0ba8b62d4f3b6e0146e48779eaa4f39f92092d7ff90081ef6133e0", + "sha256:13408a5e5574b322153a23f23eb9e69306d4d8216428b435b75fdab9538ad169", + "sha256:1846a8f4366fb4041d34cd37c2d022421ab1a28bcf79ffa6cf33a45b5acba9af", + "sha256:1d32d0965c2fc1476af9c367e396c3ecc294d4bde2cfde6f1704e8787e3f0e1f", + "sha256:21d6abd25e0fcfa96edf164831f53ca20deb64221eb3b7d6d1c4d582f4c54c84", + "sha256:232755284f942cbb3b43a06cd85974ef3c970a021aef19b5243c03ee2b08fa05", + "sha256:245e69a1f367c89e3c8a1c2699bd20ab67b3d57053f3c71f0623d36def074308", + "sha256:3a274c63a3575ae9d6cde5a31c2f5cb18d0a34d9bded96433ceb86d11dc0806d", + "sha256:3b400efb38d6092d2ee7f6d9835dd6dc4f99e804abf97652a5839ff9b1910f25", + "sha256:4ab2054325a7856ed0df881b8ffdadae05b29cf3549635f741c18ce2c860f51b", + "sha256:4b5efb5bff2a1ed0c23dd131223566a0cc51c5266e70968082aed75b73f8c1e2", + "sha256:54e7bf8a2a0c8536f4c42fc5ef54e6780153826279aef923317cf919671119f4", + "sha256:59a0b01fc9376c2424eb3b09a0550f1cbd51681a59cee1e02c9d5c546c601679", + "sha256:5ba06cf0cfc79686daedf9a7895cad4c993c453b86240fc54ecbe9b0c951504c", + "sha256:66768684fdee5f9395e6ee2daa9f770b37455fcb22d31960843bd72996aaa84f", + "sha256:772c13250aea33ac17eb042544b310f0dc3862bbde49b334f5c12f7d1b627476", + "sha256:7d31c4b518b34b427b51e85c6827473b08f473df2fcba75969daad65ea2a5f6c", + "sha256:961f11eb427161a8f5b35e74285a5ff6651eee710dbe092072af3e9337e26825", + "sha256:96342c9f934bcce22eaef739e4fca9ce5cc5347df4673f4de8e5dce5fe158444", + "sha256:a507d507b45af9657b050cea780e668cbcb9280eb94a5755c634a48760b1d035", + "sha256:ad318b60d13767838e99cf93f3571849946eb960c54da86c000b97b2ffa60128", + "sha256:b137bb2f6e079bd04e6b3ea15e9f9b9c97982ec0b1037d48972940577d3a57bb", + "sha256:b3f95ba4d251400bfd38b0891128d9b6365a54f06bd4d58ba033ecb39d2788cc", + "sha256:c0937ab8185d7f55bf7145dbfa3cc27a9d69916d4274690b18b9d1022ac54fd8", + "sha256:c2c28d22bfea830c0cdbd0d7f373d4f51366893a18a5bbd4dd8deb1e6bdd08c2", + "sha256:e074e2be68b4cb1d17b9c63d89ae0592742bdbc320466f342e1e1ea77ec83c40", + "sha256:e9abcc8013354f0059c16af9c917d19341a41981bb74dcc44e060f8a88db9123", + "sha256:eb757a4076e7bb1ca3e73fba4ec2b1c07ca0634200904f1df8f7f899c57b17af", + "sha256:f4ecb562b5b6a2d80543ec36f7fbc7c1a4341bb837a5fc8bd3c352470508133c", + "sha256:f516d11179627f95471cc0674afe8710d4dc5de764297db7f5bdb34bd92caff9", + "sha256:fd6496b41eb529349d58f3f6a09a64cceb156c9720f79cebdf975ea4fafc05f0" ], "index": "pypi", - "version": "==0.29.15" + "version": "==0.29.16" }, "falcon": { "hashes": [ diff --git a/lisp/application.py b/lisp/application.py index e44bb2ee1..1f3a96dd5 100644 --- a/lisp/application.py +++ b/lisp/application.py @@ -225,9 +225,8 @@ def __load_from_file(self, session_file): name = cues_dict.get("name", "No name") logger.exception( translate( - "ApplicationError", - 'Unable to create the cue "{}"'.format(name), - ) + "ApplicationError", 'Unable to create the cue "{}"', + ).format(name) ) self.commands_stack.set_saved() diff --git a/lisp/core/configuration.py b/lisp/core/configuration.py index 3e1dff21f..e5e2b182b 100644 --- a/lisp/core/configuration.py +++ b/lisp/core/configuration.py @@ -48,7 +48,7 @@ def __init__(self, root=None, sep="."): raise ValueError("ConfDict separator cannot be empty") if not isinstance(sep, str): raise TypeError( - "ConfDict separator must be a str, not {}".format(typename(sep)) + f"ConfDict separator must be a str, not {typename(sep)}" ) self._sep = sep @@ -59,7 +59,7 @@ def __init__(self, root=None, sep="."): self._root = root else: raise TypeError( - "ConfDict root must be a dict, not {}".format(typename(root)) + f"ConfDict root must be a dict, not {typename(root)}" ) def get(self, path, default=_UNSET): diff --git a/lisp/core/dicttree.py b/lisp/core/dicttree.py index 77aade6c7..8a2fc2e90 100644 --- a/lisp/core/dicttree.py +++ b/lisp/core/dicttree.py @@ -17,6 +17,7 @@ # Used to indicate the default behaviour when a specific option is not found to # raise an exception. Created to enable `None` as a valid fallback value. + from lisp.core.util import typename _UNSET = object() @@ -43,17 +44,15 @@ def children(self): def add_child(self, node, name): if not isinstance(node, DictNode): raise TypeError( - "DictNode children must be a DictNode, not {}".format( - typename(node) - ) + f"DictNode children must be a DictNode, not {typename(node)}" ) if not isinstance(name, str): raise TypeError( - "DictNode name must be a str, not {}".format(typename(node)) + f"DictNode name must be a str, not {typename(node)}" ) if self.Sep in name: raise DictTreeError( - "DictNode name cannot contains the path separator" + "DictNode name cannot contain the path separator" ) # Set node name and parent diff --git a/lisp/core/fader.py b/lisp/core/fader.py index a96975bb8..ce3d3f6cb 100644 --- a/lisp/core/fader.py +++ b/lisp/core/fader.py @@ -98,7 +98,7 @@ def fade(self, duration, to_value, fade_type): if not isinstance(fade_type, (FadeInType, FadeOutType)): raise AttributeError( "fade_type must be one of FadeInType or FadeOutType members," - "not {}".format(typename(fade_type)) + f"not {typename(fade_type)}" ) try: diff --git a/lisp/core/has_properties.py b/lisp/core/has_properties.py index beeca7ba7..08fd1afdf 100644 --- a/lisp/core/has_properties.py +++ b/lisp/core/has_properties.py @@ -212,7 +212,7 @@ def changed(self, name): The signals returned by this method are created lazily and cached. """ if name not in self.properties_names(): - raise ValueError('no property "{}" found'.format(name)) + raise ValueError(f'no property "{name}" found') signal = self.__changed_signals.get(name) if signal is None: @@ -239,7 +239,7 @@ def _pro(self, name): # TODO: PropertyError ?? raise AttributeError( - "'{}' object has no property '{}'".format(typename(self), name) + f"'{typename(self)}' object has no property '{name}'" ) diff --git a/lisp/core/proxy_model.py b/lisp/core/proxy_model.py index 23eb4d4b1..137af7bf9 100644 --- a/lisp/core/proxy_model.py +++ b/lisp/core/proxy_model.py @@ -27,9 +27,7 @@ def __init__(self, model): if not isinstance(model, Model): raise TypeError( - "ProxyModel model must be a Model object, not {0}".format( - typename(model) - ) + f"ProxyModel model must be a Model object, not {typename(model)}" ) self._model = model diff --git a/lisp/core/signal.py b/lisp/core/signal.py index ed8d5d8de..cdfca3a4b 100644 --- a/lisp/core/signal.py +++ b/lisp/core/signal.py @@ -179,7 +179,7 @@ def connect(self, slot_callable, mode=Connection.Direct): :raise ValueError: if mode not in Connection enum """ if mode not in Connection: - raise ValueError("invalid mode value: {0}".format(mode)) + raise ValueError(f"invalid mode value: {mode}") with self.__lock: sid = slot_id(slot_callable) diff --git a/lisp/core/util.py b/lisp/core/util.py index eb7c74ae6..34feecf0b 100644 --- a/lisp/core/util.py +++ b/lisp/core/util.py @@ -89,45 +89,44 @@ def find_packages(path="."): ] -def time_tuple(millis): +def time_tuple(milliseconds): """Split the given time in a tuple. - :param millis: Number of milliseconds - :type millis: int + :param milliseconds: Number of milliseconds + :type milliseconds: int - :return (hours, minutes, seconds, milliseconds) + :returns: (hours, minutes, seconds, milliseconds) """ - seconds, millis = divmod(millis, 1000) + seconds, milliseconds = divmod(milliseconds, 1000) minutes, seconds = divmod(seconds, 60) hours, minutes = divmod(minutes, 60) - return hours, minutes, seconds, millis + return hours, minutes, seconds, milliseconds def strtime(time, accurate=False): """Return a string from the given milliseconds time. - :returns: - hh:mm:ss when > 59min - mm:ss:00 when < 1h and accurate=False - mm:ss:z0 when < 1h and accurate=True + - when > 59min -> hh:mm:ss + - when < 1h and accurate -> mm:ss:00 + - when < 1h and not accurate -> mm:ss:z0 """ - # Cast time to int to avoid formatting problems - time = time_tuple(int(time)) - if time[0] > 0: - return "{:02}:{:02}:{:02}".format(*time[:-1]) + + hours, minutes, seconds, milliseconds = time_tuple(int(time)) + if hours > 0: + return f"{hours:02}:{minutes:02}:{seconds:02}" elif accurate: - return "{:02}:{:02}.{}0".format(time[1], time[2], time[3] // 100) + return f"{minutes:02}:{seconds:02}.{milliseconds // 100}0" else: - return "{:02}:{:02}.00".format(*time[1:3]) + return f"{minutes:02}:{seconds:02}.00" -def compose_url(protocol, host, port, path="/"): +def compose_url(schema, host, port, path="/"): """Compose a URL.""" if not path.startswith("/"): path = "/" + path - return "{}://{}:{}{}".format(protocol, host, port, path) + return f"{schema}://{host}:{port}{path}" def greatest_common_superclass(instances): diff --git a/lisp/cues/cue_factory.py b/lisp/cues/cue_factory.py index c17f1830b..bfbb9f649 100644 --- a/lisp/cues/cue_factory.py +++ b/lisp/cues/cue_factory.py @@ -75,7 +75,7 @@ def create_cue(cls, cue_type, cue_id=None, **kwargs): if not callable(factory): raise Exception( - "Cue not available or badly registered: {}".format(cue_type) + f"Cue not available or badly registered: {cue_type}" ) return factory(id=cue_id, **kwargs) diff --git a/lisp/layout/cue_menu.py b/lisp/layout/cue_menu.py index daecd251a..e8d611071 100644 --- a/lisp/layout/cue_menu.py +++ b/lisp/layout/cue_menu.py @@ -134,9 +134,7 @@ def add(self, item, ref_class=Cue): if not issubclass(ref_class, Cue): raise TypeError( - "ref_class must be Cue or a subclass, not {0}".format( - ref_class.__name__ - ) + f"ref_class must be Cue or a subclass, not {ref_class.__name__}" ) super().add(item, ref_class) diff --git a/lisp/main.py b/lisp/main.py index 1ad9f7ea4..c0a63e19f 100644 --- a/lisp/main.py +++ b/lisp/main.py @@ -24,6 +24,7 @@ from logging.handlers import RotatingFileHandler from PyQt5.QtCore import QLocale, QLibraryInfo, QTimer +from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QApplication from lisp import app_dirs, DEFAULT_APP_CONFIG, USER_APP_CONFIG, plugins @@ -110,18 +111,10 @@ def main(): qt_app.setQuitOnLastWindowClosed(True) # Get/Set the locale - locale = args.locale - if locale: - QLocale().setDefault(QLocale(locale)) - else: - locale = app_conf["locale"] - if locale != "": - QLocale().setDefault(QLocale(locale)) - + locale_name = args.locale if args.locale else app_conf["locale"] + QLocale().setDefault(QLocale(locale_name)) logging.info( - 'Using "{}" locale -> {}'.format( - QLocale().name(), QLocale().uiLanguages() - ) + f'Using "{QLocale().name()}" locale -> {QLocale().uiLanguages()}' ) # Qt platform translation @@ -135,7 +128,7 @@ def main(): try: theme_name = app_conf["theme.theme"] themes.get_theme(theme_name).apply(qt_app) - logging.info('Using "{}" theme'.format(theme_name)) + logging.info(f'Using "{theme_name}" theme') except Exception: logging.exception("Unable to load theme.") @@ -143,12 +136,14 @@ def main(): try: icon_theme = app_conf["theme.icons"] IconTheme.set_theme_name(icon_theme) - logging.info('Using "{}" icon theme'.format(icon_theme)) + logging.info(f'Using "{icon_theme}" icon theme') except Exception: logging.exception("Unable to load icon theme.") else: - # Set application icon (from the theme) - qt_app.setWindowIcon(IconTheme.get('linux-show-player')) + # Set application icon + qt_app.setWindowIcon( + QIcon(IconTheme.get("linux-show-player").pixmap(128, 128)) + ) # Initialize the application lisp_app = Application(app_conf) diff --git a/lisp/plugins/__init__.py b/lisp/plugins/__init__.py index 273e556b2..ffb0f0b7d 100644 --- a/lisp/plugins/__init__.py +++ b/lisp/plugins/__init__.py @@ -138,9 +138,8 @@ def __load_plugins(plugins, application, optionals=True): except Exception: logger.exception( translate( - "PluginsError", - 'Failed to load plugin: "{}"'.format(name), - ) + "PluginsError", 'Failed to load plugin: "{}"', + ).format(name) ) return resolved diff --git a/lisp/plugins/cart_layout/layout.py b/lisp/plugins/cart_layout/layout.py index ae0aae8dd..3fdc951d2 100644 --- a/lisp/plugins/cart_layout/layout.py +++ b/lisp/plugins/cart_layout/layout.py @@ -255,7 +255,10 @@ def add_page(self): page.copyWidgetRequested.connect(self._copy_widget) self._cart_view.addTab( - page, "Page {}".format(self._cart_view.count() + 1) + page, + translate("CartLayout", "Page {number}").format( + number=self._cart_view.count() + 1 + ), ) def remove_current_page(self): diff --git a/lisp/plugins/cart_layout/model.py b/lisp/plugins/cart_layout/model.py index cf5cab5d0..e39ea0927 100644 --- a/lisp/plugins/cart_layout/model.py +++ b/lisp/plugins/cart_layout/model.py @@ -96,7 +96,7 @@ def move(self, old_index, new_index): self.__cues[new_index].index = new_index self.item_moved.emit(old_index, new_index) else: - raise ModelException("index already used {}".format(new_index)) + raise ModelException(f"index already used {new_index}") def page_edges(self, page): start = self.flat((page, 0, 0)) diff --git a/lisp/plugins/cart_layout/page_widget.py b/lisp/plugins/cart_layout/page_widget.py index 055d501c5..1b7e46fd7 100644 --- a/lisp/plugins/cart_layout/page_widget.py +++ b/lisp/plugins/cart_layout/page_widget.py @@ -59,7 +59,7 @@ def addWidget(self, widget, row, column): self.layout().addWidget(widget, row, column) widget.show() else: - raise IndexError("cell {} already used".format((row, column))) + raise IndexError(f"cell {row, column} already used") def takeWidget(self, row, column): self._checkIndex(row, column) @@ -69,7 +69,7 @@ def takeWidget(self, row, column): self.layout().removeWidget(widget) return widget else: - raise IndexError("cell {} is empty".format((row, column))) + raise IndexError(f"cell {row, column} is empty") def moveWidget(self, o_row, o_column, n_row, n_column): widget = self.takeWidget(o_row, o_column) @@ -136,15 +136,11 @@ def widgetAt(self, pos): def _checkIndex(self, row, column): if not isinstance(row, int): - raise TypeError( - "rows index must be integers, not {}".format(typename(row)) - ) + raise TypeError(f"rows index must be integers, not {typename(row)}") if not isinstance(column, int): raise TypeError( - "columns index must be integers, not {}".format( - typename(column) - ) + f"columns index must be integers, not {typename(column)}" ) if not 0 <= row < self.__rows or not 0 <= column < self.__columns: - raise IndexError("index out of bound {}".format((row, column))) + raise IndexError(f"index out of bound {row, column}") diff --git a/lisp/plugins/controller/protocols/osc.py b/lisp/plugins/controller/protocols/osc.py index 840a4a72c..d249cbab5 100644 --- a/lisp/plugins/controller/protocols/osc.py +++ b/lisp/plugins/controller/protocols/osc.py @@ -272,7 +272,7 @@ def loadSettings(self, settings): try: key = Osc.message_from_key(options[0]) self.oscModel.appendRow( - key[0], key[1], "{}".format(key[2:])[1:-1], options[1] + key[0], key[1], str(key[2:])[1:-1], options[1] ) except Exception: logger.warning( @@ -288,7 +288,7 @@ def capture_message(self): result = self.captureDialog.exec() if result == QDialog.Accepted and self.capturedMessage["path"]: - args = "{}".format(self.capturedMessage["args"])[1:-1] + args = str(self.capturedMessage["args"])[1:-1] self.oscModel.appendRow( self.capturedMessage["path"], self.capturedMessage["types"], @@ -298,19 +298,13 @@ def capture_message(self): self.__osc.server.new_message.disconnect(self.__show_message) - self.captureLabel.setText("Waiting for message:") + self.captureLabel.setText("Waiting for messages:") def __show_message(self, path, args, types): self.capturedMessage["path"] = path self.capturedMessage["types"] = types self.capturedMessage["args"] = args - self.captureLabel.setText( - 'OSC: "{0}" "{1}" {2}'.format( - self.capturedMessage["path"], - self.capturedMessage["types"], - self.capturedMessage["args"], - ) - ) + self.captureLabel.setText(f'OSC: "{path}" "{types}" {args}') def __new_message(self): dialog = OscMessageDialog(parent=self) @@ -320,8 +314,8 @@ def __new_message(self): QMessageBox.warning( self, "Warning", - "Osc path seems not valid, \ndo not forget to edit the " - "path later.", + "Osc path seems not valid,\n" + "do not forget to edit the path later.", ) types = "" @@ -344,7 +338,7 @@ def __new_message(self): arguments.append(row[1]) self.oscModel.appendRow( - path, types, "{}".format(arguments)[1:-1], self._defaultAction + path, types, str([0, 1])[1:-1], self._defaultAction ) def __remove_message(self): @@ -422,14 +416,14 @@ def __new_message(self, path, args, types, *_, **__): @staticmethod def key_from_message(path, types, args): key = [path, types, *args] - return "OSC{}".format(key) + return f"OSC{key}" @staticmethod def key_from_values(path, types, args): if not types: - return "OSC['{0}', '{1}']".format(path, types) + return f"OSC['{path}', '{types}']" else: - return "OSC['{0}', '{1}', {2}]".format(path, types, args) + return f"OSC['{path}', '{types}', {args}]" @staticmethod def message_from_key(key): diff --git a/lisp/plugins/gst_backend/gst_media.py b/lisp/plugins/gst_backend/gst_media.py index 17920d87c..513fab690 100644 --- a/lisp/plugins/gst_backend/gst_media.py +++ b/lisp/plugins/gst_backend/gst_media.py @@ -286,7 +286,7 @@ def __on_message(self, bus, message): if message.type == Gst.MessageType.ERROR: error, debug = message.parse_error() logger.error( - "GStreamer: {}".format(error.message), exc_info=GstError(debug) + f"GStreamer: {error.message}", exc_info=GstError(debug) ) # Set the pipeline to NULL diff --git a/lisp/plugins/gst_backend/settings/audio_pan.py b/lisp/plugins/gst_backend/settings/audio_pan.py index 7d5b2ab6f..b2f72810e 100644 --- a/lisp/plugins/gst_backend/settings/audio_pan.py +++ b/lisp/plugins/gst_backend/settings/audio_pan.py @@ -79,4 +79,4 @@ def pan_changed(self, value): else: position = translate("AudioPanSettings", "Center") - self.panLabel.setText("{0} - {1}".format(value, position)) + self.panLabel.setText(f"{value} - {position}") diff --git a/lisp/plugins/midi/midi_io.py b/lisp/plugins/midi/midi_io.py index b64432796..046ae1bfa 100644 --- a/lisp/plugins/midi/midi_io.py +++ b/lisp/plugins/midi/midi_io.py @@ -80,11 +80,8 @@ def open(self): except OSError: logger.exception( translate( - "MIDIError", - "Cannot connect to MIDI output port '{}'.".format( - self._port_name - ), - ) + "MIDIError", "Cannot connect to MIDI output port '{}'." + ).format(self._port_name) ) @@ -104,11 +101,8 @@ def open(self): except OSError: logger.exception( translate( - "MIDIError", - "Cannot connect to MIDI input port '{}'.".format( - self._port_name - ), - ) + "MIDIError", "Cannot connect to MIDI input port '{}'." + ).format(self._port_name) ) def __new_message(self, message): diff --git a/lisp/plugins/midi/midi_settings.py b/lisp/plugins/midi/midi_settings.py index 55c8878a4..7cc264858 100644 --- a/lisp/plugins/midi/midi_settings.py +++ b/lisp/plugins/midi/midi_settings.py @@ -55,6 +55,7 @@ def __init__(self, **kwargs): self.inputStatus = QLabel(self.portsGroup) self.inputStatus.setDisabled(True) + self.inputStatus.setText(f"[{MIDISettings.STATUS_SYMBOLS[False]}]") self.portsGroup.layout().addWidget(self.inputStatus, 1, 1) # Spacer @@ -69,6 +70,7 @@ def __init__(self, **kwargs): self.outputStatus = QLabel(self.portsGroup) self.outputStatus.setDisabled(True) + self.outputStatus.setText(f"[{MIDISettings.STATUS_SYMBOLS[False]}]") self.portsGroup.layout().addWidget(self.outputStatus, 4, 1) self.portsGroup.layout().setColumnStretch(0, 2) @@ -140,21 +142,18 @@ def getSettings(self): return {} - def _portStatusSymbol(self, port): + @staticmethod + def portStatusSymbol(port): return MIDISettings.STATUS_SYMBOLS.get(port.is_open(), "") def _updatePortsStatus(self): - plugin = get_plugin("Midi") + midi = get_plugin("Midi") self.inputStatus.setText( - "[{}] {}".format( - self._portStatusSymbol(plugin.input), plugin.input.port_name() - ) + f"[{self.portStatusSymbol(midi.input)}] {midi.input.port_name()}" ) self.outputStatus.setText( - "[{}] {}".format( - self._portStatusSymbol(plugin.output), plugin.output.port_name() - ) + f"[{self.portStatusSymbol(midi.output)}] {midi.output.port_name()}" ) def _loadDevices(self): diff --git a/lisp/plugins/network/discovery_dialogs.py b/lisp/plugins/network/discovery_dialogs.py index e575f831f..348d252b5 100644 --- a/lisp/plugins/network/discovery_dialogs.py +++ b/lisp/plugins/network/discovery_dialogs.py @@ -100,7 +100,7 @@ def hosts(self): def _host_discovered(self, host, fqdn): if fqdn != host: - item_text = "{} - {}".format(fqdn, host) + item_text = f"{fqdn} - {host}" else: item_text = host diff --git a/lisp/plugins/presets/presets_ui.py b/lisp/plugins/presets/presets_ui.py index 24b92916b..9753f6faa 100644 --- a/lisp/plugins/presets/presets_ui.py +++ b/lisp/plugins/presets/presets_ui.py @@ -112,9 +112,8 @@ def check_override_dialog(preset_name): answer = QMessageBox.question( MainWindow(), translate("Presets", "Presets"), - translate( - "Presets", - 'Preset "{}" already exists, overwrite?'.format(preset_name), + translate("Presets", 'Preset "{}" already exists, overwrite?',).format( + preset_name ), buttons=QMessageBox.Yes | QMessageBox.Cancel, ) diff --git a/lisp/plugins/replay_gain/gain_ui.py b/lisp/plugins/replay_gain/gain_ui.py index 1c076f564..801fe3ab1 100644 --- a/lisp/plugins/replay_gain/gain_ui.py +++ b/lisp/plugins/replay_gain/gain_ui.py @@ -148,7 +148,7 @@ def __init__(self, maximum, parent=None): self.resize(320, 110) self.setMaximum(maximum) - self.setLabelText("0 / {0}".format(maximum)) + self.setLabelText(f"0 / {maximum}") def on_progress(self, value): if value == -1: @@ -157,4 +157,4 @@ def on_progress(self, value): self.deleteLater() else: self.setValue(self.value() + value) - self.setLabelText("{0} / {1}".format(self.value(), self.maximum())) + self.setLabelText(f"{self.value()} / {self.maximum()}") diff --git a/lisp/plugins/replay_gain/replay_gain.py b/lisp/plugins/replay_gain/replay_gain.py index aab4b442f..756b24d76 100644 --- a/lisp/plugins/replay_gain/replay_gain.py +++ b/lisp/plugins/replay_gain/replay_gain.py @@ -330,9 +330,7 @@ def _on_message(self, bus, message): error, _ = message.parse_error() self.gain_pipe.set_state(Gst.State.NULL) - logger.error( - "GStreamer: {}".format(error.message), exc_info=error - ) + logger.error(f"GStreamer: {error.message}", exc_info=error) self.__release() except Exception: logger.exception( diff --git a/lisp/plugins/synchronizer/synchronizer.py b/lisp/plugins/synchronizer/synchronizer.py index 68546b50f..8090139bc 100644 --- a/lisp/plugins/synchronizer/synchronizer.py +++ b/lisp/plugins/synchronizer/synchronizer.py @@ -76,7 +76,7 @@ def manage_peers(self): self.peers = manager.hosts() def show_ip(self): - ip = translate("Synchronizer", "Your IP is: {}".format(get_lan_ip())) + ip = translate("Synchronizer", "Your IP is: {}").format(get_lan_ip()) QMessageBox.information(self.app.window, " ", ip) def _url(self, host, path): @@ -90,7 +90,7 @@ def _url(self, host, path): def _cue_executes(self, cue): for peer, _ in self.peers: requests.post( - self._url(peer, "/cues/{}/action".format(cue.id)), + self._url(peer, f"/cues/{cue.id}/action"), json={"action": CueAction.Default.value}, ) diff --git a/lisp/ui/about.py b/lisp/ui/about.py index 92ab498d6..a416b7023 100644 --- a/lisp/ui/about.py +++ b/lisp/ui/about.py @@ -101,9 +101,8 @@ def __init__(self, *args, **kwargs): self.shortInfo = QLabel(self) self.shortInfo.setAlignment(Qt.AlignCenter) self.shortInfo.setText( - "