Show a spinner while opening a project.

There are probably issues, when the user tries to close the tab while the project is still loading.
Can't test this now, because opening more than one tab doesn't even work...
looper
Ben Niemann 5 years ago
parent ec32ed4b5c
commit b6feaf9e1e

@ -11,8 +11,25 @@
Sometimes still hangs on shutdown. Subprocess calls _exit(), but manager doesn't seem to get the
SIGCHLD. Non-deterministic and rare, so hard to debug...
* Visual feedback when opening project :FR:
- Create tab first, show some spinning wheel, until project is setup completely.
* Crash when opening second project :CRASH:
ERROR : 8298:7fbd9ef37700:ui.editor_app: Exception in callback: Traceback for <Task finished coro=<BaseEditorApp.openProject() done, defined at /home/pink/noisicaa/build/noisicaa/ui/editor_app.py:253> exception=AssertionError()> (most recent call last):
File "/usr/lib/python3.5/asyncio/tasks.py", line 293, in _step
self = None # Needed to break cycles when an exception occurs.
File "/home/pink/noisicaa/build/noisicaa/ui/editor_app.py", line 256, in openProject
await project_connection.open()
File "/home/pink/noisicaa/build/noisicaa/ui/project_registry.py", line 75, in open
await self.create_process()
File "/home/pink/noisicaa/build/noisicaa/ui/project_registry.py", line 71, in create_process
await self.client.setup()
File "/home/pink/noisicaa/build/noisicaa/music/project_client.py", line 93, in setup
await super().setup()
File "/home/pink/noisicaa/build/noisicaa/music/project_client.py", line 69, in setup
await self.server.setup()
File "/home/pink/noisicaa/build/noisicaa/core/ipc.py", line 177, in setup
stats.Counter, stats.StatName(name='ipc_server_bytes_sent', server=self.name))
File "/home/pink/noisicaa/build/noisicaa/core/stats/registry.py", line 45, in register
return stat
AssertionError
* Move various test helpers to noisidev.unittest :CLEANUP:
- noisicaa.ui.uitest_utils

@ -40,6 +40,7 @@ add_python_package(
project_view.py
project_view_test.py
qled.py
qprogressindicator.py
render_project_dialog.py
render_project_dialog_test.py
selection_set.py

@ -244,28 +244,27 @@ class BaseEditorApp(object):
and self.show_edit_areas_action.isChecked())
async def createProject(self, path):
project = await self.project_registry.create_project(path)
await self.addProject(project)
return project
project_connection = self.project_registry.add_project(path)
idx = self.win.addProjectSetupView(project_connection)
await project_connection.create()
await self.win.activateProjectView(idx, project_connection)
self._updateOpenedProjects()
async def openProject(self, path):
project = await self.project_registry.open_project(path)
await self.addProject(project)
return project
project_connection = self.project_registry.add_project(path)
idx = self.win.addProjectSetupView(project_connection)
await project_connection.open()
await self.win.activateProjectView(idx, project_connection)
self._updateOpenedProjects()
def _updateOpenedProjects(self):
self.settings.setValue(
'opened_projects',
sorted(
project.path
for project
in self.project_registry.projects.values()
for project in self.project_registry.projects.values()
if project.path))
async def addProject(self, project_connection):
await self.win.addProjectView(project_connection)
self._updateOpenedProjects()
async def removeProject(self, project_connection):
await self.win.removeProjectView(project_connection)
await self.project_registry.close_project(project_connection)
@ -333,18 +332,13 @@ class EditorApp(BaseEditorApp, QtWidgets.QApplication):
logger.info("Starting with projects from cmdline.")
for path in self.paths:
if path.startswith('+'):
path = path[1:]
project = await self.project_registry.create_project(
path)
await self.createProject(path[1:])
else:
project = await self.project_registry.open_project(
path)
await self.addProject(project)
await self.openProject(path)
else:
reopen_projects = self.settings.value('opened_projects', [])
for path in reopen_projects or []:
project = await self.project_registry.open_project(path)
await self.addProject(project)
await self.openProject(path)
async def cleanup(self):
logger.info("Cleanup app...")

@ -43,6 +43,7 @@ from ..importers.abc import ABCImporter, ImporterError
from . import ui_base
from . import instrument_library
from . import selection_set
from . import qprogressindicator
logger = logging.getLogger(__name__)
@ -479,7 +480,32 @@ class EditorWindow(ui_base.CommonMixin, QtWidgets.QMainWindow):
else:
self.currentProjectChanged.emit(None)
async def addProjectView(self, project_connection):
def addProjectSetupView(self, project_connection):
widget = QtWidgets.QWidget()
label = QtWidgets.QLabel(widget)
label.setText("Opening project '%s'..." % project_connection.path)
wheel = qprogressindicator.QProgressIndicator(widget)
wheel.setMinimumSize(48, 48)
wheel.setMaximumSize(48, 48)
wheel.setAnimationDelay(100)
wheel.startAnimation()
layout = QtWidgets.QVBoxLayout()
layout.addStretch(2)
layout.addWidget(label, 0, Qt.AlignHCenter)
layout.addSpacing(10)
layout.addWidget(wheel, 0, Qt.AlignHCenter)
layout.addStretch(3)
widget.setLayout(layout)
idx = self._project_tabs.addTab(widget, project_connection.name)
self._project_tabs.setCurrentIndex(idx)
self._main_area.setCurrentIndex(0)
return idx
async def activateProjectView(self, idx, project_connection):
context = ui_base.ProjectContext(
selection_set=selection_set.SelectionSet(),
project_connection=project_connection,
@ -487,18 +513,18 @@ class EditorWindow(ui_base.CommonMixin, QtWidgets.QMainWindow):
view = ProjectView(context=context)
await view.setup()
idx = self._project_tabs.addTab(view, project_connection.name)
self._project_tabs.insertTab(idx, view, project_connection.name)
self._project_tabs.removeTab(idx + 1)
self._project_tabs.setCurrentIndex(idx)
self._close_current_project_action.setEnabled(True)
self._main_area.setCurrentIndex(0)
self.projectListChanged.emit()
async def removeProjectView(self, project_connection):
for idx in range(self._project_tabs.count()):
view = self._project_tabs.widget(idx)
if view.project_connection is project_connection:
if isinstance(view, ProjectView) and view.project_connection is project_connection:
self._project_tabs.removeTab(idx)
if self._project_tabs.count() == 0:
self._main_area.setCurrentIndex(1)
@ -515,19 +541,20 @@ class EditorWindow(ui_base.CommonMixin, QtWidgets.QMainWindow):
view = self._project_tabs.currentWidget()
closed = view.close()
if closed:
self.call_async(
self.app.removeProject(view.project_connection))
self.call_async(self.app.removeProject(view.project_connection))
def onCurrentProjectTabChanged(self, idx):
project_view = self._project_tabs.widget(idx)
self.setCurrentProjectView(project_view)
widget = self._project_tabs.widget(idx)
if isinstance(widget, ProjectView):
self.setCurrentProjectView(widget)
else:
self.setCurrentProjectView(None)
def onCloseProjectTab(self, idx):
view = self._project_tabs.widget(idx)
closed = view.close()
if closed:
self.call_async(
self.app.removeProject(view.project_connection))
self.call_async(self.app.removeProject(view.project_connection))
def getCurrentProjectView(self):
return self._project_tabs.currentWidget()

@ -98,18 +98,9 @@ class ProjectRegistry(QtCore.QObject):
self.node_db = node_db
self.projects = {}
async def open_project(self, path):
def add_project(self, path):
project = Project(
path, self.event_loop, self.tmp_dir, self.process_manager, self.node_db)
await project.open()
self.projects[path] = project
self.projectListChanged.emit()
return project
async def create_project(self, path):
project = Project(
path, self.event_loop, self.tmp_dir, self.process_manager, self.node_db)
await project.create()
self.projects[path] = project
self.projectListChanged.emit()
return project

@ -0,0 +1,149 @@
#!/usr/bin/env python
# @custom_license
#
# Taken from https://github.com/mojocorp/QProgressIndicator with minor modifications
# (use PyQt5 instead of PySide).
#
# Original license:
# Author: Jared P. Sutton <jpsutton@gmail.com>
# License: LGPL
# Note: I've licensed this code as LGPL because it was a complete translation of the
# code found here...
# https://github.com/mojocorp/QProgressIndicator
# Ignore formatting of 3rd party code for now.
# pylint: skip-file
import sys
from PyQt5.QtCore import Qt
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5 import QtWidgets
class QProgressIndicator(QtWidgets.QWidget):
m_angle = None
m_timerId = None
m_delay = None
m_displayedWhenStopped = None
m_color = None
def __init__ (self, parent):
# Call parent class constructor first
super(QProgressIndicator, self).__init__(parent)
# Initialize Qt Properties
self.setProperties()
# Intialize instance variables
self.m_angle = 0
self.m_timerId = -1
self.m_delay = 40
self.m_displayedWhenStopped = False
self.m_color = Qt.black
# Set size and focus policy
self.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
self.setFocusPolicy(Qt.NoFocus)
# Show the widget
self.show()
def animationDelay (self):
return self.delay
def isAnimated (self):
return (self.m_timerId != -1)
def isDisplayedWhenStopped (self):
return self.displayedWhenStopped
def getColor (self):
return self.color
def sizeHint (self):
return QtCore.QSize(20, 20)
def startAnimation (self):
self.m_angle = 0
if self.m_timerId == -1:
self.m_timerId = self.startTimer(self.m_delay)
def stopAnimation (self):
if self.m_timerId != -1:
self.killTimer(self.m_timerId)
self.m_timerId = -1
self.update()
def setAnimationDelay (self, delay):
if self.m_timerId != -1:
self.killTimer(self.m_timerId)
self.m_delay = delay
if self.m_timerId != -1:
self.m_timerId = self.startTimer(self.m_delay)
def setDisplayedWhenStopped (self, state):
self.displayedWhenStopped = state
self.update()
def setColor (self, color):
self.m_color = color
self.update()
def timerEvent (self, event):
self.m_angle = (self.m_angle + 30) % 360
self.update()
def paintEvent (self, event):
if (not self.m_displayedWhenStopped) and (not self.isAnimated()):
return
width = min(self.width(), self.height())
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
outerRadius = (width - 1) * 0.5
innerRadius = (width - 1) * 0.5 * 0.38
capsuleHeight = outerRadius - innerRadius
capsuleWidth = capsuleHeight *.23 if (width > 32) else capsuleHeight *.35
capsuleRadius = capsuleWidth / 2
for i in range(0, 12):
color = QtGui.QColor(self.m_color)
if self.isAnimated():
color.setAlphaF(1.0 - (i / 12.0))
else:
color.setAlphaF(0.2)
painter.setPen(Qt.NoPen)
painter.setBrush(color)
painter.save()
painter.translate(self.rect().center())
painter.rotate(self.m_angle - (i * 30.0))
painter.drawRoundedRect(capsuleWidth * -0.5, (innerRadius + capsuleHeight) * -1, capsuleWidth, capsuleHeight, capsuleRadius, capsuleRadius)
painter.restore()
def setProperties (self):
self.delay = QtCore.pyqtProperty(int, self.animationDelay, self.setAnimationDelay)
self.displayedWhenStopped = QtCore.pyqtProperty(bool, self.isDisplayedWhenStopped, self.setDisplayedWhenStopped)
self.color = QtCore.pyqtProperty(QtGui.QColor, self.getColor, self.setColor)
def TestProgressIndicator ():
app = QtWidgets.QApplication(sys.argv)
progress = QProgressIndicator(None)
progress.setAnimationDelay(70)
progress.startAnimation()
# Execute the application
sys.exit(app.exec_())
if __name__ == "__main__":
TestProgressIndicator()
Loading…
Cancel
Save