Improve typing of ui_base.

Break cyclic import by defining some abstract base classes.
looper
Ben Niemann 5 years ago
parent c0cf27e370
commit 4c237f8ea6

@ -1,15 +1,9 @@
# -*- org-tags-column: -98 -*-
NEXT: make qtapp attribute of EditorApp
* runtests: warn if selector did not match any tests :TESTING:
* what is the proper type for target in noisicaa.ui.tools? :CLEANUP:
* solve cyclic import dependencies from ui_base :CLEANUP:
- ui_base needs EditorApp, EditorWindow, ProjectView - which need ui_base
- ui_base defines interfaces for those classes (and possibly more)
- use those for type annotations everywhere else
- modules defining the implementations of those classes shouldn't be imported elsewhere
- might also make testing easier
- tests can implement mocks for those interfaces
* refactor noisicaa.ui.editor_app :CLEANUP:
- some better way to make it testable than this BaseEditorApp/EditorApp split.
- perhaps don't subclass QApplication, just be a QObject with a reference to the QApplication?

@ -85,7 +85,7 @@ class AudioProcClient(audioproc.AudioProcClientMixin, AudioProcClientImpl):
self.__app.onPipelineStatus(status)
class BaseEditorApp(object):
class BaseEditorApp(ui_base.AbstractEditorApp): # pylint: disable=abstract-method
def __init__(
self, *,
process: core.ProcessBase,

@ -43,7 +43,7 @@ from . import project_registry
logger = logging.getLogger(__name__)
class EditorWindow(ui_base.CommonMixin, QtWidgets.QMainWindow):
class EditorWindow(ui_base.AbstractEditorWindow):
# Could not figure out how to define a signal that takes either an instance
# of a specific class or None.
currentProjectChanged = QtCore.pyqtSignal(object)
@ -214,7 +214,7 @@ class EditorWindow(ui_base.CommonMixin, QtWidgets.QMainWindow):
self._aboutqt_action = QtWidgets.QAction("About Qt", self)
self._aboutqt_action.setStatusTip("Show the Qt library's About box")
self._aboutqt_action.triggered.connect(self.app.aboutQt)
self._aboutqt_action.triggered.connect(self.qt_app.aboutQt)
self._open_settings_action = QtWidgets.QAction("Settings", self)
self._open_settings_action.setStatusTip("Open the settings dialog.")

@ -231,7 +231,7 @@ class PluginUI(ui_base.ProjectMixin, QtWidgets.QScrollArea):
async def __cleanupAsync(self) -> None:
async with self.__lock:
if self.__wid is not None:
await self.project_view.deletePluginUI(self.__node.id)
await self.project_view.deletePluginUI('%016x' % self.__node.id)
self.__wid = None
def showEvent(self, evt: QtGui.QShowEvent) -> None:
@ -750,7 +750,7 @@ class NodeItem(ui_base.ProjectMixin, QtWidgets.QGraphicsRectItem):
self._properties_dialog = NodePropertyDialog(
node_item=self,
parent=self.window,
parent=self.editor_window,
context=self.context)
@property
@ -1290,7 +1290,7 @@ class PipelineGraphView(ui_base.ProjectMixin, QtWidgets.QWidget):
self._graph_view = PipelineGraphGraphicsView(context=self.context)
self._node_list_dock = NodeListDock(parent=self.window, context=self.context)
self._node_list_dock = NodeListDock(parent=self.editor_window, context=self.context)
self._node_list_dock.hide()
layout = QtWidgets.QHBoxLayout()

@ -34,7 +34,7 @@ from noisicaa.core import perf_stats_capnp # type: ignore # pylint: disable=no
from . import ui_base
class PipelinePerfMonitor(ui_base.CommonMixin, QtWidgets.QMainWindow):
class PipelinePerfMonitor(ui_base.AbstractPipelinePerfMonitor):
visibilityChanged = QtCore.pyqtSignal(bool)
def __init__(self, **kwargs: Any) -> None:

@ -1128,7 +1128,7 @@ class Frame(QtWidgets.QFrame):
self.__layout.addWidget(widget, 1)
class ProjectView(ui_base.ProjectMixin, QtWidgets.QMainWindow):
class ProjectView(ui_base.AbstractProjectView, QtWidgets.QMainWindow):
currentToolBoxChanged = QtCore.pyqtSignal(tools.ToolBox)
playingChanged = QtCore.pyqtSignal(bool)
loopEnabledChanged = QtCore.pyqtSignal(bool)
@ -1360,7 +1360,7 @@ class ProjectView(ui_base.ProjectMixin, QtWidgets.QMainWindow):
self.__player_state.updateFromProto(player_state)
if pipeline_state is not None:
self.window.pipeline_status.setText(pipeline_state)
self.editor_window.pipeline_status.setText(pipeline_state)
logger.info("pipeline state: %s", pipeline_state)
if pipeline_disabled:

@ -117,7 +117,8 @@ class AppearancePage(Page):
def qtStyleChanged(self, index: int) -> None:
style_name = self._qt_styles[index]
style = QtWidgets.QStyleFactory.create(style_name)
self.app.setStyle(style)
# TODO: something's weird here...
self.qt_app.setStyle(style) # type: ignore
self.app.settings.setValue('appearance/qtStyle', style_name)

@ -148,7 +148,7 @@ class QTextEdit(QtWidgets.QTextEdit):
self.__initial_text = None
class StatMonitor(ui_base.CommonMixin, QtWidgets.QMainWindow):
class StatMonitor(ui_base.AbstractStatMonitor):
visibilityChanged = QtCore.pyqtSignal(bool)
def __init__(self, **kwargs):

@ -448,7 +448,7 @@ class SampleTrackEditorItem(base_track_item.BaseTrackEditorItem):
def onAddSample(self, time: audioproc.MusicalTime) -> None:
path, _ = QtWidgets.QFileDialog.getOpenFileName(
parent=self.window,
parent=self.editor_window,
caption="Add Sample to track \"%s\"" % self.track.name,
#directory=self.ui_state.get(
#'instruments_add_dialog_path', ''),

@ -77,11 +77,11 @@ class ScoreToolBase(base_track_item.MeasuredToolBase):
idx, _, _ = target.getEditArea(evt.pos().x())
if idx < 0:
self.window.setInfoMessage('')
self.editor_window.setInfoMessage('')
else:
pitch = model.Pitch.name_from_stave_line(
stave_line, target.measure.key_signature)
self.window.setInfoMessage(pitch)
self.editor_window.setInfoMessage(pitch)
super().mouseMoveEvent(target, evt)

@ -23,31 +23,37 @@
import asyncio
import functools
import io
from typing import Any, Optional, Dict, Callable, Awaitable
from typing import Any, Optional, Dict, Tuple, Callable, Awaitable
from PyQt5 import QtCore # pylint: disable=unused-import
from PyQt5 import QtWidgets
from noisicaa import audioproc
from noisicaa import music
from noisicaa import core
from noisicaa import instrument_db as instrument_db_lib # pylint: disable=unused-import
from noisicaa import node_db as node_db_lib # pylint: disable=unused-import
from noisicaa import devices # pylint: disable=unused-import
from noisicaa import runtime_settings as runtime_settings_lib # pylint: disable=unused-import
from . import selection_set as selection_set_lib
from . import project_registry
# TODO: these would create cyclic import dependencies.
EditorApp = Any
EditorWindow = Any
ProjectView = Any
class CommonContext(object):
def __init__(self, *, app: EditorApp) -> None:
def __init__(self, *, app: 'AbstractEditorApp') -> None:
self.__app = app
@property
def app(self) -> EditorApp:
def qt_app(self) -> QtWidgets.QApplication:
# TODO: this should be an attribute of EditorApp, not itself...
return self.__app # type: ignore
@property
def app(self) -> 'AbstractEditorApp':
return self.__app
@property
def window(self) -> EditorWindow:
def editor_window(self) -> 'AbstractEditorWindow':
return self.__app.win
@property
@ -92,12 +98,16 @@ class CommonMixin(object):
return self._context
@property
def app(self) -> EditorApp:
def qt_app(self) -> QtWidgets.QApplication:
return self._context.qt_app
@property
def app(self) -> 'AbstractEditorApp':
return self._context.app
@property
def window(self) -> EditorWindow:
return self._context.window
def editor_window(self) -> 'AbstractEditorWindow':
return self._context.editor_window
@property
def audioproc_client(self) -> audioproc.AudioProcClientMixin:
@ -117,7 +127,7 @@ class ProjectContext(CommonContext):
self, *,
project_connection: project_registry.Project,
selection_set: selection_set_lib.SelectionSet,
project_view: ProjectView,
project_view: 'AbstractProjectView',
**kwargs: Any) -> None:
super().__init__(**kwargs)
self.__project_connection = project_connection
@ -129,7 +139,7 @@ class ProjectContext(CommonContext):
return self.__selection_set
@property
def project_view(self) -> ProjectView:
def project_view(self) -> 'AbstractProjectView':
return self.__project_view
@property
@ -177,7 +187,7 @@ class ProjectMixin(CommonMixin):
return self._context.project
@property
def project_view(self) -> ProjectView:
def project_view(self) -> 'AbstractProjectView':
return self._context.project_view
@property
@ -203,3 +213,59 @@ class ProjectMixin(CommonMixin):
def add_session_listener(self, key: str, listener: Callable[[Any], None]) -> core.Listener:
return self._context.add_session_listener(key, listener)
class AbstractProjectView(ProjectMixin):
async def createPluginUI(self, node_id: str) -> Tuple[int, Tuple[int, int]]:
raise NotImplementedError
async def deletePluginUI(self, node_id: str) -> None:
raise NotImplementedError
class AbstractEditorWindow(CommonMixin, QtWidgets.QMainWindow):
pipeline_status = None # type: QtWidgets.QLabel
class AbstractPipelinePerfMonitor(CommonMixin, QtWidgets.QMainWindow):
visibilityChanged = None # type: QtCore.pyqtSignal
class AbstractStatMonitor(CommonMixin, QtWidgets.QMainWindow):
visibilityChanged = None # type: QtCore.pyqtSignal
class AbstractEditorApp(object):
win = None # type: AbstractEditorWindow
audioproc_client = None # type: audioproc.AudioProcClientMixin
process = None # type: core.ProcessBase
settings = None # type: QtCore.QSettings
pipeline_perf_monitor = None # type: AbstractPipelinePerfMonitor
stat_monitor = None # type: AbstractStatMonitor
runtime_settings = None # type: runtime_settings_lib.RuntimeSettings
show_edit_areas_action = None # type: QtWidgets.QAction
midi_hub = None # type: devices.MidiHub
node_db = None # type: node_db_lib.NodeDBClient
instrument_db = None # type: instrument_db_lib.InstrumentDBClient
default_style = None # type: str
def quit(self, exit_code: int = 0) -> None:
raise NotImplementedError
async def createProject(self, path: str) -> None:
raise NotImplementedError
async def openProject(self, path: str) -> None:
raise NotImplementedError
async def removeProject(self, project_connection: project_registry.Project) -> None:
raise NotImplementedError
def crashWithMessage(self, title: str, msg: str) -> None:
raise NotImplementedError
def setClipboardContent(self, content: Any) -> None:
raise NotImplementedError
def clipboardContent(self) -> Any:
raise NotImplementedError

Loading…
Cancel
Save