Skip to content

Commit 1e341a5

Browse files
committed
feat(QML): central widget is a now a QQuickWidget
1 parent 76e231b commit 1e341a5

File tree

10 files changed

+161
-154
lines changed

10 files changed

+161
-154
lines changed

friture/CentralWidget.qml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import QtQuick 2.15
2+
import QtQuick.Controls 2.15
3+
import QtQuick.Layouts 1.2
4+
import Friture 1.0
5+
6+
Rectangle {
7+
SystemPalette { id: systemPalette; colorGroup: SystemPalette.Active }
8+
color: systemPalette.window
9+
anchors.fill: parent
10+
11+
property string fixedFont
12+
13+
TileLayout {
14+
id: root
15+
objectName: "main_tile_layout"
16+
anchors.fill: parent
17+
}
18+
}
19+

friture/ControlBar.qml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ RowLayout {
2828
ToolTip.text: "Select the type of audio widget"
2929

3030
// replace with implicitContentWidthPolicy to either ComboBox.WidestText on Qt 6
31-
width: 130
31+
width: 140
3232
Layout.preferredWidth: width
3333
Layout.rightMargin: 5
3434
}

friture/Dock.qml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@ import QtQuick.Layouts 1.2
55
Rectangle {
66
SystemPalette { id: systemPalette; colorGroup: SystemPalette.Active }
77
color: systemPalette.window
8-
anchors.fill: parent
8+
9+
property string fixedFont
910

1011
ColumnLayout {
1112
id: root
1213
anchors.fill: parent
1314

14-
property string fixedFont
15-
1615
Item {
1716
id: control_bar_container
1817
objectName: "control_bar_container"

friture/PitchView.qml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Item {
1010
required property var viewModel
1111
required property string fixedFont
1212

13+
anchors.fill: parent
14+
1315
Plot {
1416
id: plot
1517
scopedata: viewModel

friture/analyzer.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@
2626
import logging
2727
import logging.handlers
2828

29-
from PyQt5 import QtCore
29+
from PyQt5 import QtCore, QtWidgets
3030
# specifically import from PyQt5.QtGui and QWidgets for startup time improvement :
3131
from PyQt5.QtWidgets import QMainWindow, QHBoxLayout, QVBoxLayout, QApplication, QSplashScreen
3232
from PyQt5.QtGui import QPixmap
3333
from PyQt5.QtQml import QQmlEngine, qmlRegisterSingletonType, qmlRegisterType
34+
from PyQt5.QtQuickWidgets import QQuickWidget
35+
from PyQt5.QtCore import QObject
36+
3437
import platformdirs
3538

3639
# importing friture.exceptionhandler also installs a temporary exception hook
@@ -42,7 +45,7 @@
4245
from friture.audiobuffer import AudioBuffer # audio ring buffer class
4346
from friture.audiobackend import AudioBackend # audio backend class
4447
from friture.dockmanager import DockManager
45-
from friture.tilelayout import TileLayout
48+
from friture.tileLayout import TileLayout
4649
from friture.level_view_model import LevelViewModel
4750
from friture.level_data import LevelData
4851
from friture.levels import Levels_Widget
@@ -61,7 +64,7 @@
6164
from friture.spectrum_data import Spectrum_Data
6265
from friture.plotFilledCurve import PlotFilledCurve
6366
from friture.filled_curve import FilledCurve
64-
from friture.qml_tools import qml_url
67+
from friture.qml_tools import qml_url, raise_if_error
6568
from friture.generators.sine import Sine_Generator_Settings_View_Model
6669
from friture.generators.white import White_Generator_Settings_View_Model
6770
from friture.generators.pink import Pink_Generator_Settings_View_Model
@@ -115,6 +118,7 @@ def __init__(self):
115118
qmlRegisterType(SpectrogramImageData, 'Friture', 1, 0, 'SpectrogramImageData')
116119
qmlRegisterType(ColorBar, 'Friture', 1, 0, 'ColorBar')
117120
qmlRegisterType(Tick, 'Friture', 1, 0, 'Tick')
121+
qmlRegisterType(TileLayout, 'Friture', 1, 0, 'TileLayout')
118122
qmlRegisterType(Burst_Generator_Settings_View_Model, 'Friture', 1, 0, 'Burst_Generator_Settings_View_Model')
119123
qmlRegisterType(Pink_Generator_Settings_View_Model, 'Friture', 1, 0, 'Pink_Generator_Settings_View_Model')
120124
qmlRegisterType(White_Generator_Settings_View_Model, 'Friture', 1, 0, 'White_Generator_Settings_View_Model')
@@ -159,16 +163,25 @@ def __init__(self):
159163
self.vboxLayout = QVBoxLayout()
160164
self.hboxLayout.addLayout(self.vboxLayout)
161165

162-
self.centralLayout = TileLayout()
163-
self.centralLayout.setContentsMargins(0, 0, 0, 0)
164-
self.vboxLayout.addLayout(self.centralLayout)
166+
self.centralQuickWidget = QQuickWidget(self.qml_engine, self)
167+
self.centralQuickWidget.setObjectName("centralQuickWidget")
168+
self.centralQuickWidget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
169+
self.centralQuickWidget.setResizeMode(QQuickWidget.SizeRootObjectToView)
170+
self.centralQuickWidget.setSource(qml_url("CentralWidget.qml"))
171+
self.vboxLayout.addWidget(self.centralQuickWidget)
172+
173+
raise_if_error(self.centralQuickWidget)
174+
175+
central_widget_root = self.centralQuickWidget.rootObject()
176+
self.main_grid_layout = central_widget_root.findChild(QObject, "main_tile_layout")
177+
assert self.main_grid_layout is not None, "Main grid layout not found in CentralWidget.qml"
165178

166179
self.playback_widget = PlaybackControlWidget(
167180
self, self.qml_engine, self.player)
168181
self.playback_widget.setVisible(self.settings_dialog.show_playback)
169182
self.vboxLayout.addWidget(self.playback_widget)
170183

171-
self.dockmanager = DockManager(self)
184+
self.dockmanager = DockManager(self, self.main_grid_layout)
172185

173186
# timer ticks
174187
self.display_timer.timeout.connect(self.dockmanager.canvasUpdate)

friture/dock.py

Lines changed: 29 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,10 @@
1919

2020
from inspect import signature
2121

22-
from PyQt5 import QtCore, QtWidgets
23-
from PyQt5.QtQuick import QQuickView, QQuickItem # type: ignore
22+
from PyQt5.QtQuick import QQuickItem # type: ignore
2423
from PyQt5.QtQml import QQmlComponent
25-
from PyQt5.QtQuickWidgets import QQuickWidget
2624
from PyQt5.QtGui import QFontDatabase
27-
from PyQt5.QtCore import QEvent, QObject
28-
from PyQt5.QtWidgets import QWidget
25+
from PyQt5.QtCore import QObject, QSettings
2926

3027
from friture.qml_tools import component_raise_if_error, qml_url
3128
from friture.widgetdict import getWidgetById, widgetIds
@@ -38,7 +35,7 @@
3835
from PyQt5.QtQml import QQmlEngine
3936

4037

41-
class Dock(QtWidgets.QWidget):
38+
class Dock(QObject):
4239

4340
def __init__(
4441
self,
@@ -54,9 +51,6 @@ def __init__(
5451

5552
self.setObjectName(name)
5653

57-
self.vbox = QtWidgets.QVBoxLayout(self)
58-
self.vbox.setContentsMargins(0, 0, 0, 0)
59-
6054
self.qml_engine = qml_engine
6155

6256
self.controlbar_viewmodel = ControlBarViewModel(self)
@@ -67,14 +61,15 @@ def __init__(
6761
self.controlbar_viewmodel.moveNextClicked.connect(self.moveNext)
6862
self.controlbar_viewmodel.closeClicked.connect(self.closeClicked)
6963

70-
self.dockQuickWidget = QQuickWidget(self.qml_engine, self)
71-
self.dockQuickWidget.setObjectName("dockQuickWidget")
72-
self.dockQuickWidget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
73-
self.dockQuickWidget.setResizeMode(QQuickWidget.SizeRootObjectToView)
74-
self.dockQuickWidget.setSource(qml_url("Dock.qml"))
75-
self.vbox.addWidget(self.dockQuickWidget)
64+
dock_component = QQmlComponent(self.qml_engine)
65+
dock_component.loadUrl(qml_url("Dock.qml"))
66+
67+
component_raise_if_error(dock_component)
7668

77-
dock_root = self.dockQuickWidget.rootObject()
69+
context = self.qml_engine.rootContext()
70+
self.dock_qml = dock_component.createWithInitialProperties({}, context)
71+
self.dock_qml.setParent(self.qml_engine)
72+
self.dock_qml.setParentItem(self.parent().main_grid_layout) # type: ignore
7873

7974
initialProperties = {"viewModel": self.controlbar_viewmodel}
8075
component = QQmlComponent(self.qml_engine)
@@ -86,10 +81,12 @@ def __init__(
8681
control_bar_qml = component.createWithInitialProperties(initialProperties, controlbar_context)
8782
control_bar_qml.setParent(self.qml_engine)
8883

89-
control_bar_container = dock_root.findChild(QObject, "control_bar_container")
84+
control_bar_container = self.dock_qml.findChild(QObject, "control_bar_container")
85+
assert control_bar_container is not None, "Control bar container not found in Dock.qml"
9086
control_bar_qml.setParentItem(control_bar_container) # type: ignore
9187

92-
self.audio_widget_container = dock_root.findChild(QObject, "audio_widget_container")
88+
self.audio_widget_container = self.dock_qml.findChild(QObject, "audio_widget_container")
89+
assert self.audio_widget_container is not None, "Audio widget container not found in Dock.qml"
9390

9491
self.widgetId: Optional[int] = None
9592
self.audiowidget: Optional[QObject] = None
@@ -100,10 +97,15 @@ def __init__(
10097

10198
self.widget_select(widgetId)
10299

103-
# note that by default the closeEvent is accepted, no need to do it explicitely
104-
def closeEvent(self, event):
100+
def closeClicked(self):
105101
self.dockmanager.close_dock(self)
106102

103+
def cleanup(self):
104+
if self.dock_qml is not None:
105+
self.dock_qml.setParentItem(None) # type: ignore
106+
self.dock_qml.deleteLater()
107+
self.dock_qml = None
108+
107109
if self.audio_widget_qml is not None:
108110
self.audio_widget_qml.setParentItem(None) # type: ignore
109111
self.audio_widget_qml.deleteLater()
@@ -115,33 +117,11 @@ def closeEvent(self, event):
115117
self.audiowidget.deleteLater()
116118
self.audiowidget = None
117119

118-
def closeClicked(self):
119-
self.close()
120-
121120
def movePrevious(self):
122-
layout = self.parent().parent().centralLayout
123-
itemList = layout.itemList
124-
125-
for i, item in enumerate(itemList):
126-
if item.widget() is self:
127-
if i > 0 and i < len(itemList):
128-
itemList.insert(i-1, itemList.pop(i))
129-
self.dockmanager.docks.insert(i-1, self.dockmanager.docks.pop(i))
130-
layout.update()
131-
132-
break
121+
self.dockmanager.movePrevious(self)
133122

134123
def moveNext(self):
135-
layout = self.parent().parent().centralLayout
136-
itemList = layout.itemList
137-
138-
for i, item in enumerate(itemList):
139-
if item.widget() is self:
140-
if i >= 0 and i < len(itemList)-1:
141-
itemList.insert(i+1, itemList.pop(i))
142-
self.dockmanager.docks.insert(i+1, self.dockmanager.docks.pop(i))
143-
layout.update()
144-
break
124+
self.dockmanager.moveNext(self)
145125

146126
# slot
147127
def indexChanged(self, index):
@@ -161,7 +141,7 @@ def widget_select(self, widgetId: int) -> None:
161141
self.audio_widget_qml = None
162142

163143
if self.audiowidget is not None:
164-
settings = QtCore.QSettings()
144+
settings = QSettings()
165145
self.audiowidget.saveState(settings) # type: ignore
166146
assert self.widgetId is not None
167147
self.dockmanager.last_settings[self.widgetId] = settings
@@ -181,7 +161,7 @@ def widget_select(self, widgetId: int) -> None:
181161
if len(signature(constructor).parameters) == 2:
182162
self.audiowidget = constructor(self, self.qml_engine)
183163
else:
184-
self.audiowidget = constructor(self)
164+
self.audiowidget = constructor(self.parent())
185165
assert self.audiowidget is not None # mypy can't prove this :(
186166

187167
initialProperties = {
@@ -218,7 +198,7 @@ def on_status_changed(self, status):
218198
self.logger.error("QML error: " + error.toString())
219199

220200
def canvasUpdate(self):
221-
if self.audiowidget is not None and self.isVisible():
201+
if self.audiowidget is not None:
222202
self.audiowidget.canvasUpdate()
223203

224204

@@ -237,7 +217,8 @@ def settings_slot(self, checked):
237217
# method
238218
def saveState(self, settings):
239219
settings.setValue("type", self.widgetId)
240-
self.audiowidget.saveState(settings)
220+
if self.audiowidget is not None:
221+
self.audiowidget.saveState(settings)
241222

242223
# method
243224
def restoreState(self, settings):

friture/dockmanager.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from PyQt5.QtWidgets import QMainWindow
2424
from friture.defaults import DEFAULT_DOCKS
2525
from friture.dock import Dock
26+
from friture.tileLayout import TileLayout
2627

2728
from typing import Dict, List, Optional, TYPE_CHECKING
2829
if TYPE_CHECKING:
@@ -31,7 +32,7 @@
3132

3233
class DockManager(QtCore.QObject):
3334

34-
def __init__(self, parent: 'Friture') -> None:
35+
def __init__(self, parent: 'Friture', dock_layout: TileLayout) -> None:
3536
super().__init__(parent)
3637
self._parent = parent
3738

@@ -44,6 +45,8 @@ def __init__(self, parent: 'Friture') -> None:
4445
self.last_settings: Dict[int, QtCore.QSettings] = {}
4546
self.last_widget_stack: List[int] = []
4647

48+
self.dock_layout = dock_layout
49+
4750
# slot
4851
def new_dock(self) -> None:
4952
# the dock objectName is unique
@@ -64,7 +67,6 @@ def new_dock(self) -> None:
6467
new_dock = Dock(self._parent, name, self._parent.qml_engine, widget_id)
6568
if settings is not None:
6669
new_dock.restoreState(settings)
67-
self._parent.centralLayout.addWidget(new_dock)
6870

6971
self.docks += [new_dock]
7072

@@ -76,8 +78,20 @@ def close_dock(self, dock: Dock) -> None:
7678
self.last_settings[dock.widgetId] = settings
7779
self.last_widget_stack.append(dock.widgetId)
7880

79-
self._parent.centralLayout.removeWidget(dock)
8081
self.docks.remove(dock)
82+
dock.cleanup()
83+
84+
def movePrevious(self, dock):
85+
i = self.docks.index(dock)
86+
if i > 0:
87+
self.dock_layout.movePrevious(i)
88+
self.docks.insert(i-1, self.docks.pop(i))
89+
90+
def moveNext(self, dock):
91+
i = self.docks.index(dock)
92+
if i < len(self.docks) - 1:
93+
self.dock_layout.moveNext(i)
94+
self.docks.insert(i+1, self.docks.pop(i))
8195

8296
def saveState(self, settings):
8397
docknames = [dock.objectName() for dock in self.docks]
@@ -107,13 +121,10 @@ def restoreState(self, settings):
107121
dock = Dock(self.parent(), name, self.parent().qml_engine, widgetId)
108122
dock.restoreState(settings)
109123
settings.endGroup()
110-
self.parent().centralLayout.addWidget(dock)
111124
self.docks.append(dock)
112125
else:
113126
self.logger.info("First launch, display a default set of docks")
114127
self.docks = [Dock(self.parent(), "Dock %d" % (i), self.parent().qml_engine, widgetId=widget_type) for i, widget_type in enumerate(DEFAULT_DOCKS)]
115-
for dock in self.docks:
116-
self.parent().centralLayout.addWidget(dock)
117128

118129
# Ugh it seems QSettings encodes an empty stack the same as None, and
119130
# that counts as being set so it doesn't get the default, and hence the
@@ -130,8 +141,9 @@ def restoreState(self, settings):
130141
settings.endGroup()
131142

132143
def canvasUpdate(self):
133-
for dock in self.docks:
134-
dock.canvasUpdate()
144+
if self._parent.isVisible():
145+
for dock in self.docks:
146+
dock.canvasUpdate()
135147

136148
def pause(self):
137149
for dock in self.docks:

friture/spectrogram.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ def handle_new_data(self, floatdata: ndarray) -> None:
162162
norm_spectrogram = self.scale_spectrogram(self.log_spectrogram(spn) + w)
163163

164164
self.screen_resampler.set_height(self.PlotZoneImage.spectrogram_screen_height())
165-
screen_rate_frac = Fraction(self.PlotZoneImage.spectrogram_screen_width(), int(self.timerange_s * 1000))
165+
screen_rate_frac = Fraction(max(self.PlotZoneImage.spectrogram_screen_width(), 1), int(self.timerange_s * 1000))
166166
self.screen_resampler.set_ratio(self.sfft_rate_frac, screen_rate_frac)
167167
self.frequency_resampler.setnsamples(self.PlotZoneImage.spectrogram_screen_height())
168168

0 commit comments

Comments
 (0)