2015-11-29 13:02:36 +01:00
|
|
|
#!/usr/bin/python3
|
|
|
|
|
2017-10-04 15:03:38 +02:00
|
|
|
# @begin:license
|
|
|
|
#
|
|
|
|
# Copyright (c) 2015-2017, Benjamin Niemann <pink@odahoda.de>
|
|
|
|
#
|
|
|
|
# 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
|
|
|
|
|
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
|
|
|
|
import traceback
|
|
|
|
|
2016-09-04 22:55:59 +02:00
|
|
|
from PyQt5 import QtCore
|
|
|
|
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
|
2016-01-01 17:58:08 +01:00
|
|
|
from noisicaa import devices
|
2015-11-29 13:02:36 +01:00
|
|
|
from ..exceptions import RestartAppException, RestartAppCleanException
|
|
|
|
from ..constants import EXIT_EXCEPTION, EXIT_RESTART, EXIT_RESTART_CLEAN
|
|
|
|
from .editor_window import EditorWindow
|
|
|
|
|
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-08-21 16:06:59 +02:00
|
|
|
from . import pipeline_graph_monitor
|
2016-11-27 21:31:15 +01:00
|
|
|
from . import stat_monitor
|
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):
|
|
|
|
def __init__(self, app):
|
|
|
|
self.app = app
|
|
|
|
|
|
|
|
def __call__(self, exc_type, exc_value, tb):
|
|
|
|
if issubclass(exc_type, RestartAppException):
|
2016-06-24 01:11:25 +02:00
|
|
|
self.app.quit(EXIT_RESTART)
|
2015-11-29 13:02:36 +01:00
|
|
|
return
|
|
|
|
if issubclass(exc_type, 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
|
|
|
|
|
|
|
|
2016-07-02 20:45:40 +02:00
|
|
|
class AudioProcClientImpl(object):
|
|
|
|
def __init__(self, event_loop, server):
|
|
|
|
super().__init__()
|
|
|
|
self.event_loop = event_loop
|
|
|
|
self.server = server
|
|
|
|
|
|
|
|
async def setup(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
async def cleanup(self):
|
|
|
|
pass
|
|
|
|
|
2016-07-17 00:17:25 +02:00
|
|
|
class AudioProcClient(
|
|
|
|
audioproc.AudioProcClientMixin, AudioProcClientImpl):
|
|
|
|
def __init__(self, app, *args, **kwargs):
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.__app = app
|
|
|
|
|
|
|
|
def handle_pipeline_status(self, status):
|
|
|
|
self.__app.onPipelineStatus(status)
|
2016-07-02 20:45:40 +02:00
|
|
|
|
|
|
|
|
2016-08-28 00:25:05 +02:00
|
|
|
class NodeDBClientImpl(object):
|
|
|
|
def __init__(self, event_loop, server):
|
|
|
|
super().__init__()
|
|
|
|
self.event_loop = event_loop
|
|
|
|
self.server = server
|
|
|
|
|
|
|
|
async def setup(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
async def cleanup(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class NodeDBClient(node_db.NodeDBClientMixin, NodeDBClientImpl):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2016-09-19 19:30:06 +02:00
|
|
|
class InstrumentDBClientImpl(object):
|
|
|
|
def __init__(self, event_loop, server):
|
|
|
|
super().__init__()
|
|
|
|
self.event_loop = event_loop
|
|
|
|
self.server = server
|
|
|
|
|
|
|
|
async def setup(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
async def cleanup(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class InstrumentDBClient(instrument_db.InstrumentDBClientMixin, InstrumentDBClientImpl):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2016-09-04 22:55:59 +02:00
|
|
|
class BaseEditorApp(QtWidgets.QApplication):
|
2016-06-23 00:49:45 +02:00
|
|
|
def __init__(self, process, runtime_settings, settings=None):
|
2015-11-29 13:02:36 +01:00
|
|
|
super().__init__(['noisicaä'])
|
|
|
|
|
2016-06-23 00:49:45 +02:00
|
|
|
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()
|
|
|
|
|
2016-06-23 00:49:45 +02:00
|
|
|
self.setQuitOnLastWindowClosed(False)
|
|
|
|
|
2015-11-29 13:02:36 +01:00
|
|
|
self.default_style = None
|
|
|
|
|
2016-06-25 20:48:52 +02:00
|
|
|
self.project_registry = None
|
2016-01-01 17:58:08 +01:00
|
|
|
self.sequencer = None
|
|
|
|
self.midi_hub = None
|
2016-07-02 20:45:40 +02:00
|
|
|
self.audioproc_client = None
|
|
|
|
self.audioproc_process = None
|
2016-08-28 00:25:05 +02:00
|
|
|
self.node_db = None
|
2016-09-19 19:30:06 +02:00
|
|
|
self.instrument_db = None
|
2015-11-29 13:02:36 +01:00
|
|
|
|
2016-11-12 18:05:34 +01:00
|
|
|
self.__clipboard = None
|
|
|
|
|
2016-06-25 20:48:52 +02:00
|
|
|
async def setup(self):
|
2015-11-29 13:02:36 +01:00
|
|
|
self.default_style = self.style().objectName()
|
|
|
|
|
|
|
|
style_name = self.settings.value('appearance/qtStyle', '')
|
|
|
|
if style_name:
|
2016-09-04 22:55:59 +02:00
|
|
|
style = QtWidgets.QStyleFactory.create(style_name)
|
2015-11-29 13:02:36 +01:00
|
|
|
self.setStyle(style)
|
|
|
|
|
2016-08-28 00:25:05 +02:00
|
|
|
await self.createNodeDB()
|
2016-09-19 19:30:06 +02:00
|
|
|
await self.createInstrumentDB()
|
2016-08-28 00:25:05 +02:00
|
|
|
|
2016-06-25 20:48:52 +02:00
|
|
|
self.project_registry = project_registry.ProjectRegistry(
|
2016-08-28 00:25:05 +02:00
|
|
|
self.process.event_loop, self.process.manager, self.node_db)
|
2016-06-25 20:48:52 +02:00
|
|
|
|
2016-01-01 17:58:08 +01:00
|
|
|
self.sequencer = self.createSequencer()
|
|
|
|
|
|
|
|
self.midi_hub = self.createMidiHub()
|
|
|
|
self.midi_hub.start()
|
|
|
|
|
2016-09-04 22:55:59 +02:00
|
|
|
self.show_edit_areas_action = QtWidgets.QAction(
|
2015-11-29 13:02:36 +01:00
|
|
|
"Show Edit Areas", self,
|
|
|
|
checkable=True,
|
|
|
|
triggered=self.onShowEditAreasChanged)
|
|
|
|
self.show_edit_areas_action.setChecked(
|
|
|
|
int(self.settings.value('dev/show_edit_areas', '0')))
|
|
|
|
|
2016-07-03 19:50:13 +02:00
|
|
|
await self.createAudioProcProcess()
|
2016-07-02 22:16:46 +02:00
|
|
|
|
2016-06-25 20:48:52 +02:00
|
|
|
async def cleanup(self):
|
2015-11-29 13:02:36 +01:00
|
|
|
logger.info("Cleaning up.")
|
2016-07-02 20:45:40 +02:00
|
|
|
|
|
|
|
if self.audioproc_client is not None:
|
|
|
|
await self.audioproc_client.disconnect(shutdown=True)
|
|
|
|
await self.audioproc_client.cleanup()
|
|
|
|
self.audioproc_client = None
|
|
|
|
|
2016-09-19 19:30:06 +02:00
|
|
|
if self.instrument_db is not None:
|
|
|
|
await self.instrument_db.disconnect(shutdown=True)
|
|
|
|
await self.instrument_db.cleanup()
|
|
|
|
self.instrument_db = None
|
|
|
|
|
|
|
|
if self.node_db is not None:
|
|
|
|
await self.node_db.disconnect(shutdown=True)
|
|
|
|
await self.node_db.cleanup()
|
|
|
|
self.node_db = None
|
|
|
|
|
2016-01-01 17:58:08 +01:00
|
|
|
if self.midi_hub is not None:
|
|
|
|
self.midi_hub.stop()
|
|
|
|
self.midi_hub = None
|
|
|
|
|
|
|
|
if self.sequencer is not None:
|
|
|
|
self.sequencer.close()
|
|
|
|
self.sequencer = None
|
|
|
|
|
2016-07-02 20:45:40 +02:00
|
|
|
if self.project_registry is not None:
|
|
|
|
await self.project_registry.close_all()
|
|
|
|
self.project_registry = None
|
|
|
|
|
2016-06-24 01:11:25 +02:00
|
|
|
def quit(self, exit_code=0):
|
|
|
|
self.process.quit(exit_code)
|
2016-06-23 00:49:45 +02:00
|
|
|
|
2016-01-01 17:58:08 +01:00
|
|
|
def createSequencer(self):
|
|
|
|
return None
|
|
|
|
|
|
|
|
def createMidiHub(self):
|
|
|
|
return devices.MidiHub(self.sequencer)
|
|
|
|
|
2016-07-03 19:50:13 +02:00
|
|
|
async def createAudioProcProcess(self):
|
|
|
|
pass
|
|
|
|
|
2016-08-28 00:25:05 +02:00
|
|
|
async def createNodeDB(self):
|
|
|
|
node_db_address = await self.process.manager.call(
|
|
|
|
'CREATE_NODE_DB_PROCESS')
|
|
|
|
|
|
|
|
self.node_db = NodeDBClient(
|
|
|
|
self.process.event_loop, self.process.server)
|
|
|
|
await self.node_db.setup()
|
|
|
|
await self.node_db.connect(node_db_address)
|
|
|
|
|
2016-09-19 19:30:06 +02:00
|
|
|
async def createInstrumentDB(self):
|
|
|
|
instrument_db_address = await self.process.manager.call(
|
|
|
|
'CREATE_INSTRUMENT_DB_PROCESS')
|
|
|
|
|
|
|
|
self.instrument_db = InstrumentDBClient(
|
|
|
|
self.process.event_loop, self.process.server)
|
|
|
|
await self.instrument_db.setup()
|
|
|
|
await self.instrument_db.connect(instrument_db_address)
|
|
|
|
|
2015-11-29 13:02:36 +01:00
|
|
|
def dumpSettings(self):
|
|
|
|
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)
|
|
|
|
|
|
|
|
def onShowEditAreasChanged(self):
|
|
|
|
self.settings.setValue(
|
|
|
|
'dev/show_edit_areas', int(self.show_edit_areas_action.isChecked()))
|
|
|
|
self.win.updateView()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def showEditAreas(self):
|
|
|
|
return (self.runtime_settings.dev_mode
|
|
|
|
and self.show_edit_areas_action.isChecked())
|
|
|
|
|
2016-06-25 21:30:39 +02:00
|
|
|
async def createProject(self, path):
|
|
|
|
project = await self.project_registry.create_project(path)
|
2016-07-03 04:15:01 +02:00
|
|
|
await self.addProject(project)
|
2016-06-25 21:30:39 +02:00
|
|
|
return project
|
|
|
|
|
|
|
|
async def openProject(self, path):
|
|
|
|
project = await self.project_registry.open_project(path)
|
2016-07-03 04:15:01 +02:00
|
|
|
await self.addProject(project)
|
2016-06-25 21:30:39 +02:00
|
|
|
return project
|
|
|
|
|
2016-06-25 23:07:19 +02:00
|
|
|
def _updateOpenedProjects(self):
|
2015-11-29 13:02:36 +01:00
|
|
|
self.settings.setValue(
|
|
|
|
'opened_projects',
|
2016-06-25 23:07:19 +02:00
|
|
|
sorted(
|
|
|
|
project.path
|
|
|
|
for project
|
|
|
|
in self.project_registry.projects.values()
|
|
|
|
if project.path))
|
|
|
|
|
2016-07-03 04:15:01 +02:00
|
|
|
async def addProject(self, project_connection):
|
2016-07-11 21:30:23 +02:00
|
|
|
await self.win.addProjectView(project_connection)
|
2017-01-16 20:01:23 +01:00
|
|
|
self._updateOpenedProjects()
|
2015-11-29 13:02:36 +01:00
|
|
|
|
2016-07-02 15:05:47 +02:00
|
|
|
async def removeProject(self, project_connection):
|
2016-07-11 21:30:23 +02:00
|
|
|
await self.win.removeProjectView(project_connection)
|
2016-07-02 15:05:47 +02:00
|
|
|
await self.project_registry.close_project(project_connection)
|
2017-01-16 20:01:23 +01:00
|
|
|
self._updateOpenedProjects()
|
2015-11-29 13:02:36 +01:00
|
|
|
|
2016-07-17 00:17:25 +02:00
|
|
|
def onPipelineStatus(self, status):
|
|
|
|
pass
|
|
|
|
|
2016-11-12 18:05:34 +01:00
|
|
|
def setClipboardContent(self, content):
|
|
|
|
logger.info(
|
|
|
|
"Setting clipboard contents to: %s", pprint.pformat(content))
|
|
|
|
self.__clipboard = content
|
|
|
|
|
|
|
|
def clipboardContent(self):
|
|
|
|
return self.__clipboard
|
|
|
|
|
2015-11-29 13:02:36 +01:00
|
|
|
|
|
|
|
class EditorApp(BaseEditorApp):
|
2016-06-23 00:49:45 +02:00
|
|
|
def __init__(self, process, runtime_settings, paths, settings=None):
|
|
|
|
super().__init__(process, runtime_settings, settings)
|
2015-11-29 13:02:36 +01:00
|
|
|
|
|
|
|
self.paths = paths
|
|
|
|
|
|
|
|
self._old_excepthook = None
|
|
|
|
self.win = None
|
2016-08-07 00:18:02 +02:00
|
|
|
self.pipeline_perf_monitor = None
|
2016-08-21 16:06:59 +02:00
|
|
|
self.pipeline_graph_monitor = None
|
2016-11-27 21:31:15 +01:00
|
|
|
self.stat_monitor = None
|
2015-11-29 13:02:36 +01:00
|
|
|
|
2016-06-25 20:48:52 +02:00
|
|
|
async def setup(self):
|
2015-11-29 13:02:36 +01:00
|
|
|
logger.info("Installing custom excepthook.")
|
|
|
|
self._old_excepthook = sys.excepthook
|
|
|
|
sys.excepthook = ExceptHook(self)
|
|
|
|
|
2016-06-25 20:48:52 +02:00
|
|
|
await super().setup()
|
2015-11-29 13:02:36 +01:00
|
|
|
|
2016-08-07 00:18:02 +02:00
|
|
|
logger.info("Creating PipelinePerfMonitor.")
|
|
|
|
self.pipeline_perf_monitor = pipeline_perf_monitor.PipelinePerfMonitor(self)
|
|
|
|
|
2016-09-18 12:22:19 +02:00
|
|
|
# logger.info("Creating PipelineGraphMonitor.")
|
|
|
|
# self.pipeline_graph_monitor = pipeline_graph_monitor.PipelineGraphMonitor(self)
|
2016-08-21 16:06:59 +02:00
|
|
|
|
2016-11-27 21:31:15 +01:00
|
|
|
logger.info("Creating StatMonitor.")
|
|
|
|
self.stat_monitor = stat_monitor.StatMonitor(self)
|
|
|
|
|
2015-11-29 13:02:36 +01:00
|
|
|
logger.info("Creating EditorWindow.")
|
|
|
|
self.win = EditorWindow(self)
|
2016-07-09 22:45:41 +02:00
|
|
|
await self.win.setup()
|
2015-11-29 13:02:36 +01:00
|
|
|
self.win.show()
|
|
|
|
|
2016-09-18 12:22:19 +02:00
|
|
|
# self.pipeline_graph_monitor.addWindow(self.win)
|
2016-08-21 16:06:59 +02:00
|
|
|
|
2015-11-29 13:02:36 +01:00
|
|
|
if self.paths:
|
|
|
|
logger.info("Starting with projects from cmdline.")
|
|
|
|
for path in self.paths:
|
2016-06-25 16:29:29 +02:00
|
|
|
if path.startswith('+'):
|
|
|
|
path = path[1:]
|
2016-06-25 21:30:39 +02:00
|
|
|
project = await self.project_registry.create_project(
|
|
|
|
path)
|
2016-06-25 16:29:29 +02:00
|
|
|
else:
|
2016-06-25 21:30:39 +02:00
|
|
|
project = await self.project_registry.open_project(
|
|
|
|
path)
|
2016-07-03 04:15:01 +02:00
|
|
|
await self.addProject(project)
|
2015-11-29 13:02:36 +01:00
|
|
|
else:
|
|
|
|
reopen_projects = self.settings.value('opened_projects', [])
|
2016-06-25 20:48:52 +02:00
|
|
|
for path in reopen_projects or []:
|
2016-06-25 21:30:39 +02:00
|
|
|
project = await self.project_registry.open_project(path)
|
2016-07-03 04:15:01 +02:00
|
|
|
await self.addProject(project)
|
2015-11-29 13:02:36 +01:00
|
|
|
|
|
|
|
self.aboutToQuit.connect(self.shutDown)
|
|
|
|
|
|
|
|
def shutDown(self):
|
|
|
|
logger.info("Shutting down.")
|
|
|
|
|
2016-06-23 00:49:45 +02:00
|
|
|
if self.win is not None:
|
|
|
|
self.win.storeState()
|
|
|
|
self.settings.sync()
|
|
|
|
self.dumpSettings()
|
2015-11-29 13:02:36 +01:00
|
|
|
|
2016-06-25 20:48:52 +02:00
|
|
|
async def cleanup(self):
|
2016-11-27 21:31:15 +01:00
|
|
|
if self.stat_monitor is not None:
|
|
|
|
self.stat_monitor.storeState()
|
|
|
|
self.stat_monitor = None
|
|
|
|
|
2016-08-07 00:18:02 +02:00
|
|
|
if self.pipeline_perf_monitor is not None:
|
|
|
|
self.pipeline_perf_monitor.storeState()
|
|
|
|
self.pipeline_perf_monitor = None
|
2016-07-17 00:17:25 +02:00
|
|
|
|
2016-08-21 16:06:59 +02:00
|
|
|
if self.pipeline_graph_monitor is not None:
|
|
|
|
self.pipeline_graph_monitor.storeState()
|
|
|
|
self.pipeline_graph_monitor = None
|
|
|
|
|
2016-06-23 00:49:45 +02:00
|
|
|
if self.win is not None:
|
2016-07-09 22:45:41 +02:00
|
|
|
await self.win.cleanup()
|
2016-06-23 00:49:45 +02:00
|
|
|
self.win = None
|
2015-11-29 13:02:36 +01:00
|
|
|
|
2016-06-25 20:48:52 +02:00
|
|
|
await super().cleanup()
|
2015-11-29 13:02:36 +01:00
|
|
|
|
|
|
|
logger.info("Remove custom excepthook.")
|
|
|
|
sys.excepthook = self._old_excepthook
|
|
|
|
|
2016-01-01 17:58:08 +01:00
|
|
|
def createSequencer(self):
|
|
|
|
# Do other clients handle non-ASCII names?
|
|
|
|
# 'aconnect' seems to work (or just spits out whatever bytes it gets
|
|
|
|
# and the console interprets it as UTF-8), 'aconnectgui' shows the
|
|
|
|
# encoded bytes.
|
|
|
|
return devices.AlsaSequencer('noisicaä')
|
2016-07-03 19:50:13 +02:00
|
|
|
|
|
|
|
async def createAudioProcProcess(self):
|
|
|
|
self.audioproc_process = await self.process.manager.call(
|
|
|
|
'CREATE_AUDIOPROC_PROCESS', 'main')
|
|
|
|
|
|
|
|
self.audioproc_client = AudioProcClient(
|
2016-07-17 00:17:25 +02:00
|
|
|
self, self.process.event_loop, self.process.server)
|
2016-07-03 19:50:13 +02:00
|
|
|
await self.audioproc_client.setup()
|
2016-07-17 00:17:25 +02:00
|
|
|
await self.audioproc_client.connect(
|
|
|
|
self.audioproc_process, {'perf_data'})
|
2016-07-03 19:50:13 +02:00
|
|
|
|
|
|
|
await self.audioproc_client.set_backend(
|
2017-09-23 11:14:55 +02:00
|
|
|
self.settings.value('audio/backend', 'portaudio'),
|
|
|
|
block_size=2 ** int(self.settings.value('audio/block_size', 10)))
|
2016-07-17 00:17:25 +02:00
|
|
|
|
|
|
|
def onPipelineStatus(self, status):
|
|
|
|
if 'perf_data' in status:
|
2016-08-07 00:18:02 +02:00
|
|
|
if self.pipeline_perf_monitor is not None:
|
|
|
|
self.pipeline_perf_monitor.addPerfData(
|
2016-07-17 00:17:25 +02:00
|
|
|
status['perf_data'])
|
2016-09-04 01:36:32 +02:00
|
|
|
|
|
|
|
def crashWithMessage(self, title, msg):
|
|
|
|
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_()
|
|
|
|
except:
|
|
|
|
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)
|
|
|
|
|