2015-11-29 13:02:36 +01:00
|
|
|
#!/usr/bin/python3
|
|
|
|
|
2017-10-04 15:03:38 +02:00
|
|
|
# @begin:license
|
|
|
|
#
|
2019-01-12 01:20:46 +01:00
|
|
|
# Copyright (c) 2015-2019, Benjamin Niemann <pink@odahoda.de>
|
2017-10-04 15:03:38 +02:00
|
|
|
#
|
|
|
|
# This program 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 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
#
|
|
|
|
# @end:license
|
|
|
|
|
2019-06-02 01:12:45 +02:00
|
|
|
import asyncio
|
2019-06-10 17:26:21 +02:00
|
|
|
import functools
|
2015-11-29 13:02:36 +01:00
|
|
|
import logging
|
|
|
|
import os
|
2016-11-12 18:05:34 +01:00
|
|
|
import pprint
|
2015-11-29 13:02:36 +01:00
|
|
|
import sys
|
2019-06-02 18:40:13 +02:00
|
|
|
import textwrap
|
2015-11-29 13:02:36 +01:00
|
|
|
import traceback
|
2018-05-26 14:47:14 +02:00
|
|
|
import types
|
2019-06-02 01:12:45 +02:00
|
|
|
from typing import Any, Optional, Callable, Sequence, List, Type
|
2015-11-29 13:02:36 +01:00
|
|
|
|
2019-06-02 16:05:31 +02:00
|
|
|
from PyQt5.QtCore import Qt
|
2016-09-04 22:55:59 +02:00
|
|
|
from PyQt5 import QtCore
|
2019-06-02 01:12:45 +02:00
|
|
|
from PyQt5 import QtGui
|
2016-09-04 22:55:59 +02:00
|
|
|
from PyQt5 import QtWidgets
|
2015-11-29 13:02:36 +01:00
|
|
|
|
2016-07-02 20:45:40 +02:00
|
|
|
from noisicaa import audioproc
|
2016-09-19 19:30:06 +02:00
|
|
|
from noisicaa import instrument_db
|
2016-08-28 00:25:05 +02:00
|
|
|
from noisicaa import node_db
|
2018-05-26 14:47:14 +02:00
|
|
|
from noisicaa import core
|
2019-01-04 01:31:33 +01:00
|
|
|
from noisicaa import lv2
|
2019-03-03 16:36:28 +01:00
|
|
|
from noisicaa import editor_main_pb2
|
2018-05-26 14:47:14 +02:00
|
|
|
from noisicaa import runtime_settings as runtime_settings_lib
|
2019-06-02 18:40:13 +02:00
|
|
|
from noisicaa import exceptions
|
2015-11-29 13:02:36 +01:00
|
|
|
from ..constants import EXIT_EXCEPTION, EXIT_RESTART, EXIT_RESTART_CLEAN
|
2019-06-02 01:12:45 +02:00
|
|
|
from . import editor_window
|
2018-12-27 00:34:14 +01:00
|
|
|
from . import audio_thread_profiler
|
2019-01-14 04:02:26 +01:00
|
|
|
from . import device_list
|
2016-06-25 20:48:52 +02:00
|
|
|
from . import project_registry
|
2016-07-17 00:17:25 +02:00
|
|
|
from . import pipeline_perf_monitor
|
2016-11-27 21:31:15 +01:00
|
|
|
from . import stat_monitor
|
2019-06-02 16:05:31 +02:00
|
|
|
from . import settings_dialog
|
2019-06-02 20:40:45 +02:00
|
|
|
from . import instrument_list
|
2019-06-02 16:53:49 +02:00
|
|
|
from . import instrument_library
|
2017-11-26 03:01:46 +01:00
|
|
|
from . import ui_base
|
2019-06-10 17:26:21 +02:00
|
|
|
from . import open_project_dialog
|
2016-06-25 20:48:52 +02:00
|
|
|
|
2015-11-29 13:02:36 +01:00
|
|
|
logger = logging.getLogger('ui.editor_app')
|
|
|
|
|
|
|
|
|
|
|
|
class ExceptHook(object):
|
2018-05-26 14:47:14 +02:00
|
|
|
def __init__(self, app: 'EditorApp') -> None:
|
2015-11-29 13:02:36 +01:00
|
|
|
self.app = app
|
|
|
|
|
2018-05-26 14:47:14 +02:00
|
|
|
def __call__(
|
|
|
|
self, exc_type: Type[BaseException], exc_value: BaseException, tb: types.TracebackType
|
|
|
|
) -> None:
|
2019-06-02 18:40:13 +02:00
|
|
|
if issubclass(exc_type, exceptions.RestartAppException):
|
2016-06-24 01:11:25 +02:00
|
|
|
self.app.quit(EXIT_RESTART)
|
2015-11-29 13:02:36 +01:00
|
|
|
return
|
2019-06-02 18:40:13 +02:00
|
|
|
if issubclass(exc_type, exceptions.RestartAppCleanException):
|
2016-06-24 01:11:25 +02:00
|
|
|
self.app.quit(EXIT_RESTART_CLEAN)
|
2015-11-29 13:02:36 +01:00
|
|
|
return
|
|
|
|
|
|
|
|
msg = ''.join(traceback.format_exception(exc_type, exc_value, tb))
|
|
|
|
|
|
|
|
logger.error("Uncaught exception:\n%s", msg)
|
2016-09-04 01:36:32 +02:00
|
|
|
self.app.crashWithMessage("Uncaught exception", msg)
|
2015-11-29 13:02:36 +01:00
|
|
|
|
|
|
|
|
2018-06-03 16:51:41 +02:00
|
|
|
class QApplication(QtWidgets.QApplication):
|
|
|
|
def __init__(self) -> None:
|
|
|
|
super().__init__(['noisicaä'])
|
|
|
|
|
|
|
|
self.setQuitOnLastWindowClosed(False)
|
|
|
|
|
|
|
|
|
|
|
|
class EditorApp(ui_base.AbstractEditorApp):
|
2018-05-26 14:47:14 +02:00
|
|
|
def __init__(
|
|
|
|
self, *,
|
2018-06-03 16:51:41 +02:00
|
|
|
qt_app: QtWidgets.QApplication,
|
2018-05-26 14:47:14 +02:00
|
|
|
process: core.ProcessBase,
|
2018-06-03 16:51:41 +02:00
|
|
|
paths: Sequence[str],
|
2018-05-26 14:47:14 +02:00
|
|
|
runtime_settings: runtime_settings_lib.RuntimeSettings,
|
|
|
|
settings: Optional[QtCore.QSettings] = None
|
|
|
|
) -> None:
|
2017-11-26 03:01:46 +01:00
|
|
|
self.__context = ui_base.CommonContext(app=self)
|
|
|
|
|
2018-06-03 16:51:41 +02:00
|
|
|
super().__init__()
|
2016-06-23 00:49:45 +02:00
|
|
|
|
2018-06-03 16:51:41 +02:00
|
|
|
self.paths = paths
|
|
|
|
self.qt_app = qt_app
|
|
|
|
self.process = process
|
2015-11-29 13:02:36 +01:00
|
|
|
self.runtime_settings = runtime_settings
|
|
|
|
|
|
|
|
if settings is None:
|
2016-09-04 22:55:59 +02:00
|
|
|
settings = QtCore.QSettings('odahoda.de', 'noisicaä')
|
2015-11-29 13:02:36 +01:00
|
|
|
if runtime_settings.start_clean:
|
|
|
|
settings.clear()
|
|
|
|
self.settings = settings
|
|
|
|
self.dumpSettings()
|
|
|
|
|
2019-06-02 18:40:13 +02:00
|
|
|
self.new_project_action = None # type: QtWidgets.QAction
|
|
|
|
self.open_project_action = None # type: QtWidgets.QAction
|
|
|
|
self.restart_action = None # type: QtWidgets.QAction
|
|
|
|
self.restart_clean_action = None # type: QtWidgets.QAction
|
|
|
|
self.crash_action = None # type: QtWidgets.QAction
|
|
|
|
self.about_action = None # type: QtWidgets.QAction
|
|
|
|
self.aboutqt_action = None # type: QtWidgets.QAction
|
|
|
|
self.show_settings_dialog_action = None # type: QtWidgets.QAction
|
|
|
|
self.show_instrument_library_action = None # type: QtWidgets.QAction
|
2018-12-27 00:34:14 +01:00
|
|
|
self.profile_audio_thread_action = None # type: QtWidgets.QAction
|
2019-06-02 18:40:13 +02:00
|
|
|
self.dump_audioproc_action = None # type: QtWidgets.QAction
|
|
|
|
self.show_pipeline_perf_monitor_action = None # type: QtWidgets.QAction
|
|
|
|
self.show_stat_monitor_action = None # type: QtWidgets.QAction
|
|
|
|
self.quit_action = None # type: QtWidgets.QAction
|
|
|
|
|
2019-06-09 02:29:36 +02:00
|
|
|
self.project_registry = None # type: project_registry.ProjectRegistry
|
2019-06-02 18:40:13 +02:00
|
|
|
self.__audio_thread_profiler = None # type: audio_thread_profiler.AudioThreadProfiler
|
2019-03-03 16:36:28 +01:00
|
|
|
self.audioproc_client = None # type: audioproc.AbstractAudioProcClient
|
2018-05-26 14:47:14 +02:00
|
|
|
self.audioproc_process = None # type: str
|
|
|
|
self.node_db = None # type: node_db.NodeDBClient
|
|
|
|
self.instrument_db = None # type: instrument_db.InstrumentDBClient
|
2019-01-04 01:31:33 +01:00
|
|
|
self.urid_mapper = None # type: lv2.ProxyURIDMapper
|
2018-05-26 14:47:14 +02:00
|
|
|
self.__clipboard = None # type: Any
|
2018-06-03 16:51:41 +02:00
|
|
|
self.__old_excepthook = None # type: Callable[[Type[BaseException], BaseException, types.TracebackType], None]
|
2019-06-02 01:12:45 +02:00
|
|
|
self.__windows = [] # type: List[editor_window.EditorWindow]
|
2019-06-02 18:40:13 +02:00
|
|
|
self.__pipeline_perf_monitor = None # type: pipeline_perf_monitor.PipelinePerfMonitor
|
|
|
|
self.__stat_monitor = None # type: stat_monitor.StatMonitor
|
2018-06-03 16:51:41 +02:00
|
|
|
self.default_style = None # type: str
|
2019-06-02 20:40:45 +02:00
|
|
|
self.instrument_list = None # type: instrument_list.InstrumentList
|
2019-01-14 04:02:26 +01:00
|
|
|
self.devices = None # type: device_list.DeviceList
|
2019-06-02 01:12:45 +02:00
|
|
|
self.setup_complete = None # type: asyncio.Event
|
2019-06-02 16:05:31 +02:00
|
|
|
self.__settings_dialog = None # type: settings_dialog.SettingsDialog
|
2019-06-02 16:53:49 +02:00
|
|
|
self.__instrument_library_dialog = None # type: instrument_library.InstrumentLibraryDialog
|
2019-06-02 16:05:31 +02:00
|
|
|
|
2018-12-23 17:23:55 +01:00
|
|
|
self.__player_state_listeners = core.CallbackMap[str, audioproc.EngineNotification]()
|
|
|
|
|
2017-11-26 03:01:46 +01:00
|
|
|
@property
|
2018-04-15 09:28:04 +02:00
|
|
|
def context(self) -> ui_base.CommonContext:
|
|
|
|
return self.__context
|
2017-11-26 03:01:46 +01:00
|
|
|
|
2018-05-26 14:47:14 +02:00
|
|
|
async def setup(self) -> None:
|
2018-06-03 16:51:41 +02:00
|
|
|
logger.info("Installing custom excepthook.")
|
|
|
|
self.__old_excepthook = sys.excepthook
|
|
|
|
sys.excepthook = ExceptHook(self) # type: ignore
|
|
|
|
|
2019-06-02 01:12:45 +02:00
|
|
|
self.setup_complete = asyncio.Event(loop=self.process.event_loop)
|
2016-08-28 00:25:05 +02:00
|
|
|
|
2019-06-02 16:05:31 +02:00
|
|
|
self.default_style = self.qt_app.style().objectName()
|
2019-06-02 01:12:45 +02:00
|
|
|
style_name = self.settings.value('appearance/qtStyle', '')
|
|
|
|
if style_name:
|
|
|
|
# TODO: something's wrong with the QtWidgets stubs...
|
|
|
|
self.qt_app.setStyle(QtWidgets.QStyleFactory.create(style_name)) # type: ignore
|
2016-06-25 20:48:52 +02:00
|
|
|
|
2019-06-02 18:40:13 +02:00
|
|
|
self.new_project_action = QtWidgets.QAction("New", self.qt_app)
|
|
|
|
self.new_project_action.setShortcut(QtGui.QKeySequence.New)
|
|
|
|
self.new_project_action.setStatusTip("Create a new project")
|
|
|
|
self.new_project_action.setEnabled(False)
|
|
|
|
self.new_project_action.triggered.connect(self.__newProject)
|
|
|
|
|
|
|
|
self.open_project_action = QtWidgets.QAction("Open", self.qt_app)
|
|
|
|
self.open_project_action.setShortcut(QtGui.QKeySequence.Open)
|
|
|
|
self.open_project_action.setStatusTip("Open an existing project")
|
|
|
|
self.open_project_action.setEnabled(False)
|
|
|
|
self.open_project_action.triggered.connect(self.__openProject)
|
|
|
|
|
|
|
|
self.restart_action = QtWidgets.QAction("Restart", self.qt_app)
|
|
|
|
self.restart_action.setShortcut("F5")
|
|
|
|
self.restart_action.setShortcutContext(Qt.ApplicationShortcut)
|
|
|
|
self.restart_action.setStatusTip("Restart the application")
|
|
|
|
self.restart_action.triggered.connect(self.__restart)
|
|
|
|
|
|
|
|
self.restart_clean_action = QtWidgets.QAction("Restart clean", self.qt_app)
|
|
|
|
self.restart_clean_action.setShortcut("Ctrl+Shift+F5")
|
|
|
|
self.restart_clean_action.setShortcutContext(Qt.ApplicationShortcut)
|
|
|
|
self.restart_clean_action.setStatusTip("Restart the application in a clean state")
|
|
|
|
self.restart_clean_action.triggered.connect(self.__restartClean)
|
|
|
|
|
|
|
|
self.crash_action = QtWidgets.QAction("Crash", self.qt_app)
|
|
|
|
self.crash_action.triggered.connect(self.__crash)
|
|
|
|
|
|
|
|
self.about_action = QtWidgets.QAction("About", self.qt_app)
|
|
|
|
self.about_action.setStatusTip("Show the application's About box")
|
|
|
|
self.about_action.triggered.connect(self.__about)
|
|
|
|
|
|
|
|
self.aboutqt_action = QtWidgets.QAction("About Qt", self.qt_app)
|
|
|
|
self.aboutqt_action.setStatusTip("Show the Qt library's About box")
|
|
|
|
self.aboutqt_action.triggered.connect(self.qt_app.aboutQt)
|
|
|
|
|
2019-06-02 16:05:31 +02:00
|
|
|
self.show_settings_dialog_action = QtWidgets.QAction("Settings", self.qt_app)
|
|
|
|
self.show_settings_dialog_action.setStatusTip("Open the settings dialog.")
|
|
|
|
self.show_settings_dialog_action.setEnabled(False)
|
|
|
|
self.show_settings_dialog_action.triggered.connect(self.__showSettingsDialog)
|
|
|
|
|
2019-06-02 16:53:49 +02:00
|
|
|
self.show_instrument_library_action = QtWidgets.QAction("Instrument Library", self.qt_app)
|
|
|
|
self.show_instrument_library_action.setStatusTip("Open the instrument library dialog.")
|
|
|
|
self.show_instrument_library_action.setEnabled(False)
|
|
|
|
self.show_instrument_library_action.triggered.connect(self.__showInstrumentLibrary)
|
|
|
|
|
2019-06-02 18:40:13 +02:00
|
|
|
self.profile_audio_thread_action = QtWidgets.QAction("Profile Audio Thread", self.qt_app)
|
|
|
|
self.profile_audio_thread_action.setEnabled(False)
|
|
|
|
self.profile_audio_thread_action.triggered.connect(self.__profileAudioThread)
|
|
|
|
|
|
|
|
self.dump_audioproc_action = QtWidgets.QAction("Dump AudioProc", self.qt_app)
|
|
|
|
self.dump_audioproc_action.setEnabled(False)
|
|
|
|
self.dump_audioproc_action.triggered.connect(self.__dumpAudioProc)
|
|
|
|
|
|
|
|
self.show_pipeline_perf_monitor_action = QtWidgets.QAction(
|
|
|
|
"Pipeline Performance Monitor", self.qt_app)
|
|
|
|
self.show_pipeline_perf_monitor_action.setEnabled(False)
|
|
|
|
self.show_pipeline_perf_monitor_action.setCheckable(True)
|
|
|
|
|
|
|
|
self.show_stat_monitor_action = QtWidgets.QAction("Stat Monitor", self.qt_app)
|
|
|
|
self.show_stat_monitor_action.setEnabled(False)
|
|
|
|
self.show_stat_monitor_action.setCheckable(True)
|
|
|
|
|
2019-06-02 16:05:31 +02:00
|
|
|
self.quit_action = QtWidgets.QAction("Quit", self.qt_app)
|
|
|
|
self.quit_action.setShortcut(QtGui.QKeySequence.Quit)
|
|
|
|
self.quit_action.setShortcutContext(Qt.ApplicationShortcut)
|
|
|
|
self.quit_action.setStatusTip("Quit the application")
|
|
|
|
self.quit_action.triggered.connect(self.quit)
|
|
|
|
|
2019-06-02 01:12:45 +02:00
|
|
|
logger.info("Creating initial window...")
|
|
|
|
win = await self.createWindow()
|
2019-06-10 17:26:21 +02:00
|
|
|
tab_page = win.addProjectTab()
|
2016-01-01 17:58:08 +01:00
|
|
|
|
2019-06-02 01:12:45 +02:00
|
|
|
progress = win.createSetupProgress()
|
|
|
|
try:
|
2019-06-02 20:40:45 +02:00
|
|
|
progress.setNumSteps(5)
|
2018-12-27 00:34:14 +01:00
|
|
|
|
2019-06-02 18:40:13 +02:00
|
|
|
logger.info("Creating StatMonitor.")
|
|
|
|
self.__stat_monitor = stat_monitor.StatMonitor(context=self.context)
|
|
|
|
self.show_stat_monitor_action.setChecked(self.__stat_monitor.isVisible())
|
|
|
|
self.show_stat_monitor_action.toggled.connect(
|
|
|
|
self.__stat_monitor.setVisible)
|
|
|
|
self.__stat_monitor.visibilityChanged.connect(
|
|
|
|
self.show_stat_monitor_action.setChecked)
|
|
|
|
|
2019-06-02 20:43:26 +02:00
|
|
|
logger.info("Creating SettingsDialog...")
|
|
|
|
self.__settings_dialog = settings_dialog.SettingsDialog(context=self.context)
|
|
|
|
|
2019-06-02 01:12:45 +02:00
|
|
|
with progress.step("Scanning projects..."):
|
2019-06-09 02:29:36 +02:00
|
|
|
self.project_registry = project_registry.ProjectRegistry(context=self.context)
|
|
|
|
await self.project_registry.setup()
|
2019-06-10 15:55:28 +02:00
|
|
|
|
|
|
|
initial_projects = []
|
|
|
|
if self.paths:
|
|
|
|
for path in self.paths:
|
|
|
|
if path.startswith('+'):
|
|
|
|
initial_projects.append((True, path[1:]))
|
|
|
|
else:
|
|
|
|
initial_projects.append((False, path))
|
|
|
|
else:
|
|
|
|
for path in self.settings.value('opened_projects', []) or []:
|
|
|
|
initial_projects.append((False, path))
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
"Starting with projects:\n%s",
|
|
|
|
'\n'.join('%s%s' % ('+' if create else '', path)
|
|
|
|
for create, path in initial_projects))
|
|
|
|
|
|
|
|
idx = 0
|
|
|
|
for create, path in initial_projects:
|
|
|
|
if idx == 0:
|
|
|
|
tab = tab_page
|
|
|
|
else:
|
2019-06-10 17:26:21 +02:00
|
|
|
tab = win.addProjectTab()
|
2019-06-10 15:55:28 +02:00
|
|
|
if create:
|
|
|
|
self.process.event_loop.create_task(tab.createProject(path))
|
|
|
|
idx += 1
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
project = self.project_registry.getProject(path)
|
|
|
|
except KeyError:
|
|
|
|
logging.error("There is no known project at %s", path)
|
|
|
|
else:
|
|
|
|
self.process.event_loop.create_task(tab.openProject(project))
|
|
|
|
idx += 1
|
|
|
|
|
|
|
|
if idx == 0:
|
|
|
|
tab_page.showOpenDialog()
|
2019-03-24 05:30:10 +01:00
|
|
|
|
2019-06-02 01:12:45 +02:00
|
|
|
with progress.step("Scanning nodes and plugins..."):
|
|
|
|
await self.createNodeDB()
|
2016-07-02 22:16:46 +02:00
|
|
|
|
2019-06-02 01:12:45 +02:00
|
|
|
with progress.step("Creating URID mapper..."):
|
|
|
|
await self.createURIDMapper()
|
|
|
|
|
|
|
|
with progress.step("Setting up audio engine..."):
|
|
|
|
self.devices = device_list.DeviceList()
|
|
|
|
await self.createAudioProcProcess()
|
|
|
|
|
2019-06-02 18:40:13 +02:00
|
|
|
win.audioprocReady()
|
|
|
|
|
|
|
|
logger.info("Creating AudioThreadProfiler...")
|
|
|
|
self.__audio_thread_profiler = audio_thread_profiler.AudioThreadProfiler(
|
|
|
|
context=self.context)
|
|
|
|
|
|
|
|
logger.info("Creating PipelinePerfMonitor...")
|
|
|
|
self.__pipeline_perf_monitor = pipeline_perf_monitor.PipelinePerfMonitor(
|
|
|
|
context=self.context)
|
|
|
|
self.show_pipeline_perf_monitor_action.setChecked(
|
|
|
|
self.__pipeline_perf_monitor.isVisible())
|
|
|
|
self.show_pipeline_perf_monitor_action.toggled.connect(
|
|
|
|
self.__pipeline_perf_monitor.setVisible)
|
|
|
|
self.__pipeline_perf_monitor.visibilityChanged.connect(
|
|
|
|
self.show_pipeline_perf_monitor_action.setChecked)
|
|
|
|
|
2019-06-02 16:53:49 +02:00
|
|
|
with progress.step("Scanning instruments..."):
|
2019-06-02 20:40:45 +02:00
|
|
|
create_instrument_db_response = editor_main_pb2.CreateProcessResponse()
|
|
|
|
await self.process.manager.call(
|
|
|
|
'CREATE_INSTRUMENT_DB_PROCESS', None, create_instrument_db_response)
|
|
|
|
instrument_db_address = create_instrument_db_response.address
|
|
|
|
|
|
|
|
self.instrument_db = instrument_db.InstrumentDBClient(
|
|
|
|
self.process.event_loop, self.process.server)
|
|
|
|
self.instrument_list = instrument_list.InstrumentList(context=self.context)
|
|
|
|
self.instrument_list.setup()
|
|
|
|
await self.instrument_db.setup()
|
|
|
|
await self.instrument_db.connect(instrument_db_address)
|
|
|
|
|
2019-06-02 16:53:49 +02:00
|
|
|
self.__instrument_library_dialog = instrument_library.InstrumentLibraryDialog(
|
|
|
|
context=self.context)
|
|
|
|
await self.__instrument_library_dialog.setup()
|
|
|
|
|
2019-06-02 01:12:45 +02:00
|
|
|
finally:
|
|
|
|
win.deleteSetupProgress()
|
|
|
|
|
2019-06-02 18:40:13 +02:00
|
|
|
self.new_project_action.setEnabled(True)
|
|
|
|
self.open_project_action.setEnabled(True)
|
2019-06-02 16:05:31 +02:00
|
|
|
self.show_settings_dialog_action.setEnabled(True)
|
2019-06-02 16:53:49 +02:00
|
|
|
self.show_instrument_library_action.setEnabled(True)
|
2019-06-02 18:40:13 +02:00
|
|
|
self.profile_audio_thread_action.setEnabled(True)
|
|
|
|
self.dump_audioproc_action.setEnabled(True)
|
|
|
|
self.show_pipeline_perf_monitor_action.setEnabled(True)
|
|
|
|
self.show_stat_monitor_action.setEnabled(True)
|
2019-06-02 01:12:45 +02:00
|
|
|
self.setup_complete.set()
|
2018-06-03 16:51:41 +02:00
|
|
|
|
2018-05-26 14:47:14 +02:00
|
|
|
async def cleanup(self) -> None:
|
2018-06-03 16:51:41 +02:00
|
|
|
logger.info("Cleanup app...")
|
|
|
|
|
2019-06-02 18:40:13 +02:00
|
|
|
if self.__stat_monitor is not None:
|
|
|
|
self.__stat_monitor.storeState()
|
|
|
|
self.__stat_monitor = None
|
2018-06-03 16:51:41 +02:00
|
|
|
|
2019-06-02 18:40:13 +02:00
|
|
|
if self.__pipeline_perf_monitor is not None:
|
|
|
|
self.__pipeline_perf_monitor.storeState()
|
|
|
|
self.__pipeline_perf_monitor = None
|
2018-06-03 16:51:41 +02:00
|
|
|
|
2019-06-02 18:40:13 +02:00
|
|
|
if self.__audio_thread_profiler is not None:
|
|
|
|
self.__audio_thread_profiler.hide()
|
|
|
|
self.__audio_thread_profiler = None
|
2018-12-27 00:34:14 +01:00
|
|
|
|
2019-06-02 16:53:49 +02:00
|
|
|
if self.__instrument_library_dialog is not None:
|
|
|
|
await self.__instrument_library_dialog.cleanup()
|
|
|
|
self.__instrument_library_dialog = None
|
|
|
|
|
2019-06-02 16:05:31 +02:00
|
|
|
if self.__settings_dialog is not None:
|
|
|
|
self.__settings_dialog.storeState()
|
|
|
|
self.__settings_dialog.close()
|
|
|
|
self.__settings_dialog = None
|
|
|
|
|
2019-06-02 01:12:45 +02:00
|
|
|
while self.__windows:
|
|
|
|
win = self.__windows.pop(0)
|
|
|
|
win.storeState()
|
|
|
|
await win.cleanup()
|
2018-06-03 16:51:41 +02:00
|
|
|
|
2019-06-09 02:29:36 +02:00
|
|
|
if self.project_registry is not None:
|
|
|
|
await self.project_registry.cleanup()
|
|
|
|
self.project_registry = None
|
2017-10-06 13:18:05 +02:00
|
|
|
|
2016-07-02 20:45:40 +02:00
|
|
|
if self.audioproc_client is not None:
|
2019-03-03 16:36:28 +01:00
|
|
|
await self.audioproc_client.disconnect()
|
2016-07-02 20:45:40 +02:00
|
|
|
await self.audioproc_client.cleanup()
|
|
|
|
self.audioproc_client = None
|
|
|
|
|
2019-03-03 16:36:28 +01:00
|
|
|
if self.audioproc_process is not None:
|
|
|
|
await self.process.manager.call(
|
|
|
|
'SHUTDOWN_PROCESS',
|
|
|
|
editor_main_pb2.ShutdownProcessRequest(
|
|
|
|
address=self.audioproc_process))
|
|
|
|
self.audioproc_process = None
|
|
|
|
|
2019-01-04 01:31:33 +01:00
|
|
|
if self.urid_mapper is not None:
|
|
|
|
await self.urid_mapper.cleanup(self.process.event_loop)
|
|
|
|
self.urid_mapper = None
|
|
|
|
|
2019-06-02 20:40:45 +02:00
|
|
|
if self.instrument_list is not None:
|
|
|
|
self.instrument_list.cleanup()
|
|
|
|
self.instrument_list = None
|
|
|
|
|
2016-09-19 19:30:06 +02:00
|
|
|
if self.instrument_db is not None:
|
2019-03-03 16:36:28 +01:00
|
|
|
await self.instrument_db.disconnect()
|
2016-09-19 19:30:06 +02:00
|
|
|
await self.instrument_db.cleanup()
|
|
|
|
self.instrument_db = None
|
|
|
|
|
|
|
|
if self.node_db is not None:
|
2019-03-03 16:36:28 +01:00
|
|
|
await self.node_db.disconnect()
|
2016-09-19 19:30:06 +02:00
|
|
|
await self.node_db.cleanup()
|
|
|
|
self.node_db = None
|
|
|
|
|
2019-06-02 18:40:13 +02:00
|
|
|
self.settings.sync()
|
|
|
|
self.dumpSettings()
|
|
|
|
|
2018-06-03 16:51:41 +02:00
|
|
|
logger.info("Remove custom excepthook.")
|
|
|
|
sys.excepthook = self.__old_excepthook # type: ignore
|
|
|
|
|
2019-06-02 01:12:45 +02:00
|
|
|
async def createWindow(self) -> editor_window.EditorWindow:
|
|
|
|
win = editor_window.EditorWindow(context=self.context)
|
|
|
|
await win.setup()
|
|
|
|
self.__windows.append(win)
|
|
|
|
return win
|
|
|
|
|
2019-06-03 00:45:36 +02:00
|
|
|
async def deleteWindow(self, win: editor_window.EditorWindow) -> None:
|
|
|
|
self.__windows.remove(win)
|
|
|
|
await win.cleanup()
|
|
|
|
|
|
|
|
if not self.__windows:
|
|
|
|
self.quit()
|
|
|
|
|
2018-05-26 14:47:14 +02:00
|
|
|
def quit(self, exit_code: int = 0) -> None:
|
|
|
|
# TODO: quit() is not a method of ProcessBase, only in UIProcess. Find some way to
|
|
|
|
# fix that without a cyclic import.
|
|
|
|
self.process.quit(exit_code) # type: ignore
|
2016-06-23 00:49:45 +02:00
|
|
|
|
2018-05-26 14:47:14 +02:00
|
|
|
async def createAudioProcProcess(self) -> None:
|
2019-03-03 16:36:28 +01:00
|
|
|
create_audioproc_request = editor_main_pb2.CreateAudioProcProcessRequest(
|
|
|
|
name='main',
|
|
|
|
host_parameters=audioproc.HostParameters(
|
|
|
|
block_size=2 ** int(self.settings.value('audio/block_size', 10)),
|
|
|
|
sample_rate=int(self.settings.value('audio/sample_rate', 44100))))
|
|
|
|
create_audioproc_response = editor_main_pb2.CreateProcessResponse()
|
|
|
|
await self.process.manager.call(
|
|
|
|
'CREATE_AUDIOPROC_PROCESS', create_audioproc_request, create_audioproc_response)
|
|
|
|
self.audioproc_process = create_audioproc_response.address
|
|
|
|
|
|
|
|
self.audioproc_client = audioproc.AudioProcClient(
|
2019-04-25 18:27:13 +02:00
|
|
|
self.process.event_loop, self.process.server, self.urid_mapper)
|
2018-12-23 17:23:55 +01:00
|
|
|
self.audioproc_client.engine_notifications.add(self.__handleEngineNotification)
|
2018-06-03 16:51:41 +02:00
|
|
|
await self.audioproc_client.setup()
|
|
|
|
await self.audioproc_client.connect(
|
|
|
|
self.audioproc_process, {'perf_data'})
|
|
|
|
|
|
|
|
await self.audioproc_client.create_realm(name='root')
|
|
|
|
|
|
|
|
await self.audioproc_client.set_backend(
|
|
|
|
self.settings.value('audio/backend', 'portaudio'),
|
|
|
|
)
|
2016-07-03 19:50:13 +02:00
|
|
|
|
2018-05-26 14:47:14 +02:00
|
|
|
async def createNodeDB(self) -> None:
|
2019-03-03 16:36:28 +01:00
|
|
|
create_node_db_response = editor_main_pb2.CreateProcessResponse()
|
|
|
|
await self.process.manager.call(
|
|
|
|
'CREATE_NODE_DB_PROCESS', None, create_node_db_response)
|
|
|
|
node_db_address = create_node_db_response.address
|
2016-08-28 00:25:05 +02:00
|
|
|
|
2018-03-24 16:24:20 +01:00
|
|
|
self.node_db = node_db.NodeDBClient(self.process.event_loop, self.process.server)
|
2016-08-28 00:25:05 +02:00
|
|
|
await self.node_db.setup()
|
|
|
|
await self.node_db.connect(node_db_address)
|
|
|
|
|
2019-01-04 01:31:33 +01:00
|
|
|
async def createURIDMapper(self) -> None:
|
2019-03-03 16:36:28 +01:00
|
|
|
create_urid_mapper_response = editor_main_pb2.CreateProcessResponse()
|
|
|
|
await self.process.manager.call(
|
|
|
|
'CREATE_URID_MAPPER_PROCESS', None, create_urid_mapper_response)
|
|
|
|
urid_mapper_address = create_urid_mapper_response.address
|
2019-01-04 01:31:33 +01:00
|
|
|
|
|
|
|
self.urid_mapper = lv2.ProxyURIDMapper(
|
|
|
|
server_address=urid_mapper_address,
|
|
|
|
tmp_dir=self.process.tmp_dir)
|
|
|
|
await self.urid_mapper.setup(self.process.event_loop)
|
|
|
|
|
2018-05-26 14:47:14 +02:00
|
|
|
def dumpSettings(self) -> None:
|
2015-11-29 13:02:36 +01:00
|
|
|
for key in self.settings.allKeys():
|
|
|
|
value = self.settings.value(key)
|
2016-09-04 22:55:59 +02:00
|
|
|
if isinstance(value, (bytes, QtCore.QByteArray)):
|
2015-11-29 13:02:36 +01:00
|
|
|
value = '[%d bytes]' % len(value)
|
|
|
|
logger.info('%s: %s', key, value)
|
|
|
|
|
2018-12-23 17:23:55 +01:00
|
|
|
def __handleEngineNotification(self, msg: audioproc.EngineNotification) -> None:
|
2019-01-14 04:02:26 +01:00
|
|
|
for device_manager_message in msg.device_manager_messages:
|
|
|
|
action = device_manager_message.WhichOneof('action')
|
|
|
|
if action == 'added':
|
|
|
|
self.devices.addDevice(device_manager_message.added)
|
|
|
|
elif action == 'removed':
|
|
|
|
self.devices.removeDevice(device_manager_message.removed)
|
|
|
|
else:
|
|
|
|
raise ValueError(action)
|
|
|
|
|
2019-06-02 18:40:13 +02:00
|
|
|
def __newProject(self) -> None:
|
2019-06-10 17:26:21 +02:00
|
|
|
win = self.__windows[0]
|
|
|
|
dialog = open_project_dialog.NewProjectDialog(parent=win, context=self.context)
|
|
|
|
dialog.setModal(True)
|
|
|
|
dialog.finished.connect(functools.partial(self.__newProjectDialogDone, dialog, win))
|
|
|
|
dialog.show()
|
|
|
|
|
|
|
|
def __newProjectDialogDone(self, dialog: open_project_dialog.NewProjectDialog, win: editor_window.EditorWindow, result: int) -> None:
|
|
|
|
if result != QtWidgets.QDialog.Accepted:
|
|
|
|
return
|
|
|
|
|
|
|
|
path = dialog.projectPath()
|
|
|
|
tab = win.addProjectTab()
|
|
|
|
self.process.event_loop.create_task(tab.createProject(path))
|
2019-06-02 18:40:13 +02:00
|
|
|
|
|
|
|
def __openProject(self) -> None:
|
2019-06-10 17:26:21 +02:00
|
|
|
win = self.__windows[0]
|
|
|
|
tab = win.addProjectTab()
|
|
|
|
tab.showOpenDialog()
|
2019-06-02 18:40:13 +02:00
|
|
|
|
|
|
|
def __about(self) -> None:
|
|
|
|
QtWidgets.QMessageBox.about(
|
|
|
|
None, "About noisicaä",
|
|
|
|
textwrap.dedent("""\
|
|
|
|
Some text goes here...
|
|
|
|
"""))
|
|
|
|
|
|
|
|
def __restart(self) -> None:
|
|
|
|
raise exceptions.RestartAppException("Restart requested by user.")
|
|
|
|
|
|
|
|
def __restartClean(self) -> None:
|
|
|
|
raise exceptions.RestartAppCleanException("Clean restart requested by user.")
|
|
|
|
|
|
|
|
def __crash(self) -> None:
|
|
|
|
raise RuntimeError("Something bad happened")
|
|
|
|
|
2019-06-02 16:53:49 +02:00
|
|
|
def __showInstrumentLibrary(self) -> None:
|
|
|
|
self.__instrument_library_dialog.show()
|
|
|
|
self.__instrument_library_dialog.activateWindow()
|
|
|
|
|
2019-06-02 16:05:31 +02:00
|
|
|
def __showSettingsDialog(self) -> None:
|
|
|
|
self.__settings_dialog.show()
|
|
|
|
self.__settings_dialog.activateWindow()
|
|
|
|
|
2019-06-02 18:40:13 +02:00
|
|
|
def __profileAudioThread(self) -> None:
|
|
|
|
self.__audio_thread_profiler.show()
|
|
|
|
self.__audio_thread_profiler.raise_()
|
|
|
|
self.__audio_thread_profiler.activateWindow()
|
|
|
|
|
|
|
|
def __dumpAudioProc(self) -> None:
|
|
|
|
self.process.event_loop.create_task(self.audioproc_client.dump())
|
|
|
|
|
2018-12-23 18:38:51 +01:00
|
|
|
# pylint: disable=line-too-long
|
2018-12-23 17:23:55 +01:00
|
|
|
# def onPlayerStatus(self, player_state: audioproc.PlayerState):
|
|
|
|
# if pipeline_disabled:
|
|
|
|
# dialog = QtWidgets.QMessageBox(self)
|
|
|
|
# dialog.setIcon(QtWidgets.QMessageBox.Critical)
|
|
|
|
# dialog.setWindowTitle("noisicaa - Crash")
|
|
|
|
# dialog.setText(
|
|
|
|
# "The audio pipeline has been disabled, because it is repeatedly crashing.")
|
|
|
|
# quit_button = dialog.addButton("Quit", QtWidgets.QMessageBox.DestructiveRole)
|
|
|
|
# undo_and_restart_button = dialog.addButton(
|
|
|
|
# "Undo last command and restart pipeline", QtWidgets.QMessageBox.ActionRole)
|
|
|
|
# restart_button = dialog.addButton("Restart pipeline", QtWidgets.QMessageBox.AcceptRole)
|
|
|
|
# dialog.setDefaultButton(restart_button)
|
|
|
|
# dialog.finished.connect(lambda _: self.call_async(
|
|
|
|
# self.onPipelineDisabledDialogFinished(
|
|
|
|
# dialog, quit_button, undo_and_restart_button, restart_button)))
|
|
|
|
# dialog.show()
|
|
|
|
|
|
|
|
# async def onPipelineDisabledDialogFinished(
|
|
|
|
# self, dialog: QtWidgets.QMessageBox, quit_button: QtWidgets.QAbstractButton,
|
|
|
|
# undo_and_restart_button: QtWidgets.QAbstractButton,
|
|
|
|
# restart_button: QtWidgets.QAbstractButton) -> None:
|
|
|
|
# if dialog.clickedButton() == quit_button:
|
|
|
|
# self.app.quit()
|
|
|
|
|
|
|
|
# elif dialog.clickedButton() == restart_button:
|
|
|
|
# await self.project_client.restart_player_pipeline(self.__player_id)
|
|
|
|
|
|
|
|
# elif dialog.clickedButton() == undo_and_restart_button:
|
|
|
|
# await self.project_client.undo()
|
|
|
|
# await self.project_client.restart_player_pipeline(self.__player_id)
|
2018-12-23 18:38:51 +01:00
|
|
|
# pylint: enable=line-too-long
|
2016-07-17 00:17:25 +02:00
|
|
|
|
2018-05-26 14:47:14 +02:00
|
|
|
def setClipboardContent(self, content: Any) -> None:
|
2016-11-12 18:05:34 +01:00
|
|
|
logger.info(
|
|
|
|
"Setting clipboard contents to: %s", pprint.pformat(content))
|
|
|
|
self.__clipboard = content
|
|
|
|
|
2018-05-26 14:47:14 +02:00
|
|
|
def clipboardContent(self) -> Any:
|
2016-11-12 18:05:34 +01:00
|
|
|
return self.__clipboard
|
|
|
|
|
2018-05-26 14:47:14 +02:00
|
|
|
def crashWithMessage(self, title: str, msg: str) -> None:
|
2016-09-04 01:36:32 +02:00
|
|
|
logger.error('%s: %s', title, msg)
|
|
|
|
|
|
|
|
try:
|
2016-09-04 22:55:59 +02:00
|
|
|
errorbox = QtWidgets.QMessageBox()
|
2016-09-04 01:36:32 +02:00
|
|
|
errorbox.setWindowTitle("noisicaä crashed")
|
|
|
|
errorbox.setText(title)
|
|
|
|
errorbox.setInformativeText(msg)
|
2016-09-04 22:55:59 +02:00
|
|
|
errorbox.setIcon(QtWidgets.QMessageBox.Critical)
|
|
|
|
errorbox.addButton("Exit", QtWidgets.QMessageBox.AcceptRole)
|
2016-09-04 01:36:32 +02:00
|
|
|
errorbox.exec_()
|
2018-04-15 19:29:42 +02:00
|
|
|
except: # pylint: disable=bare-except
|
2016-09-04 01:36:32 +02:00
|
|
|
logger.error(
|
|
|
|
"Failed to show crash dialog: %s", traceback.format_exc())
|
|
|
|
|
2016-10-02 15:13:11 +02:00
|
|
|
sys.stdout.flush()
|
|
|
|
sys.stderr.flush()
|
2016-09-04 01:36:32 +02:00
|
|
|
os._exit(EXIT_EXCEPTION)
|