Skip to content

Commit ef9ed7e

Browse files
author
Shallow Copy Bot
committed
feat(QML): main working area is now QML
Original PR #363 by tlecomte Original: tlecomte/friture#363
1 parent 1a685bd commit ef9ed7e

File tree

13 files changed

+370
-240
lines changed

13 files changed

+370
-240
lines changed

friture/FritureHost.qml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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+
id: mainWindow
8+
SystemPalette { id: systemPalette; colorGroup: SystemPalette.Active }
9+
color: systemPalette.window
10+
anchors.fill: parent
11+
}

friture/Levels.qml

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,8 @@ Rectangle {
88
SystemPalette { id: systemPalette; colorGroup: SystemPalette.Active }
99
color: systemPalette.window
1010

11-
property var stateId
12-
property LevelViewModel level_view_model: Store.dock_states[stateId]
13-
14-
property string fixedFont
15-
16-
// parent here will be unset on exit
17-
height: parent ? parent.height : 0
11+
required property LevelViewModel level_view_model
12+
required property string fixedFont
1813

1914
// make width dependent on the text labels
2015
// but do not bind directly to their widths

friture/MainWindow.qml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import QtQuick 2.15
2+
import QtQuick.Controls 2.15
3+
import QtQuick.Layouts 1.2
4+
import Friture 1.0
5+
import "./playback"
6+
7+
Rectangle {
8+
id: main_window
9+
SystemPalette { id: systemPalette; colorGroup: SystemPalette.Active }
10+
color: systemPalette.window
11+
anchors.fill: parent
12+
13+
required property MainWindowViewModel main_window_view_model
14+
required property string fixedFont
15+
16+
GridLayout {
17+
objectName: "main_row_layout"
18+
anchors.fill: parent
19+
rows: main_window.main_window_view_model.playback_control_enabled ? 2 : 1
20+
columns: 2
21+
rowSpacing: 3
22+
columnSpacing: 3
23+
24+
Levels {
25+
level_view_model: main_window.main_window_view_model.level_view_model
26+
Layout.row: 0
27+
Layout.rowSpan: main_window.main_window_view_model.playback_control_enabled ? 2 : 1
28+
Layout.column: 0
29+
Layout.fillHeight: true
30+
Layout.margins: 5
31+
fixedFont: main_window.fixedFont
32+
}
33+
34+
TileLayout {
35+
id: tileLayout
36+
objectName: "main_tile_layout"
37+
Layout.row: 0
38+
Layout.column: 1
39+
Layout.fillWidth: true
40+
Layout.fillHeight: true
41+
Layout.margins: 5
42+
}
43+
44+
PlaybackControl {
45+
id: playbackControl
46+
Layout.row: 1
47+
Layout.column: 1
48+
Layout.fillWidth: true
49+
Layout.margins: 5
50+
51+
viewModel: main_window.main_window_view_model.playback_control_view_model
52+
53+
visible: main_window.main_window_view_model.playback_control_enabled
54+
}
55+
}
56+
}

friture/analyzer.py

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
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
32-
from PyQt5.QtGui import QPixmap
33-
from PyQt5.QtQml import QQmlEngine, qmlRegisterSingletonType, qmlRegisterType
32+
from PyQt5.QtGui import QPixmap, QFontDatabase
33+
from PyQt5.QtQml import QQmlEngine, qmlRegisterSingletonType, qmlRegisterType, QQmlComponent
3434
from PyQt5.QtQuickWidgets import QQuickWidget
3535
from PyQt5.QtCore import QObject
3636

@@ -39,16 +39,18 @@
3939
# importing friture.exceptionhandler also installs a temporary exception hook
4040
from friture.exceptionhandler import errorBox, fileexcepthook
4141
import friture
42+
from friture.playback.playback_control_view_model import PlaybackControlViewModel
4243
from friture.ui_friture import Ui_MainWindow
4344
from friture.about import About_Dialog # About dialog
4445
from friture.settings import Settings_Dialog # Setting dialog
4546
from friture.audiobuffer import AudioBuffer # audio ring buffer class
4647
from friture.audiobackend import AudioBackend # audio backend class
4748
from friture.dockmanager import DockManager
48-
from friture.tileLayout import TileLayout
49+
from friture.tilelayout import TileLayout
4950
from friture.level_view_model import LevelViewModel
5051
from friture.level_data import LevelData
5152
from friture.levels import Levels_Widget
53+
from friture.main_window_view_model import MainWindowViewModel
5254
from friture.store import GetStore, Store
5355
from friture.scope_data import Scope_Data
5456
from friture.axis import Axis
@@ -64,7 +66,7 @@
6466
from friture.spectrum_data import Spectrum_Data
6567
from friture.plotFilledCurve import PlotFilledCurve
6668
from friture.filled_curve import FilledCurve
67-
from friture.qml_tools import qml_url, raise_if_error
69+
from friture.qml_tools import qml_url, raise_if_error, component_raise_if_error
6870
from friture.generators.sine import Sine_Generator_Settings_View_Model
6971
from friture.generators.white import White_Generator_Settings_View_Model
7072
from friture.generators.pink import Pink_Generator_Settings_View_Model
@@ -109,6 +111,8 @@ def __init__(self):
109111
qmlRegisterType(Spectrum_Data, 'Friture', 1, 0, 'SpectrumData')
110112
qmlRegisterType(LevelData, 'Friture', 1, 0, 'LevelData')
111113
qmlRegisterType(LevelViewModel, 'Friture', 1, 0, 'LevelViewModel')
114+
qmlRegisterType(PlaybackControlViewModel, 'Friture', 1, 0, 'PlaybackControlViewModel')
115+
qmlRegisterType(MainWindowViewModel, 'Friture', 1, 0, 'MainWindowViewModel')
112116
qmlRegisterType(Axis, 'Friture', 1, 0, 'Axis')
113117
qmlRegisterType(Curve, 'Friture', 1, 0, 'Curve')
114118
qmlRegisterType(FilledCurve, 'Friture', 1, 0, 'FilledCurve')
@@ -152,36 +156,44 @@ def __init__(self):
152156
self.about_dialog = About_Dialog(self, self.slow_timer)
153157
self.settings_dialog = Settings_Dialog(self)
154158

155-
self.level_widget = Levels_Widget(self, self.qml_engine)
156-
self.level_widget.set_buffer(self.audiobuffer)
157-
self.audiobuffer.new_data_available.connect(self.level_widget.handle_new_data)
158-
159-
self.hboxLayout = QHBoxLayout(self.ui.centralwidget)
160-
self.hboxLayout.setContentsMargins(0, 0, 0, 0)
161-
self.hboxLayout.addWidget(self.level_widget)
162-
163-
self.vboxLayout = QVBoxLayout()
164-
self.hboxLayout.addLayout(self.vboxLayout)
165-
166159
self.centralQuickWidget = QQuickWidget(self.qml_engine, self)
167160
self.centralQuickWidget.setObjectName("centralQuickWidget")
168161
self.centralQuickWidget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
169162
self.centralQuickWidget.setResizeMode(QQuickWidget.SizeRootObjectToView)
170-
self.centralQuickWidget.setSource(qml_url("CentralWidget.qml"))
171-
self.vboxLayout.addWidget(self.centralQuickWidget)
163+
self.centralQuickWidget.setSource(qml_url("FritureHost.qml"))
172164

173165
raise_if_error(self.centralQuickWidget)
174166

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"
167+
self.hboxLayout = QHBoxLayout(self.ui.centralwidget)
168+
self.hboxLayout.setContentsMargins(0, 0, 0, 0)
169+
self.hboxLayout.addWidget(self.centralQuickWidget)
170+
171+
qml_component = QQmlComponent(self.qml_engine)
172+
qml_component.loadUrl(qml_url("MainWindow.qml"))
173+
component_raise_if_error(qml_component)
174+
175+
self._main_window_view_model = MainWindowViewModel(self.qml_engine)
176+
177+
context = self.qml_engine.rootContext()
178+
central_widget_root = qml_component.createWithInitialProperties(
179+
{
180+
"main_window_view_model": self._main_window_view_model,
181+
"fixedFont": QFontDatabase.systemFont(QFontDatabase.FixedFont).family()
182+
},
183+
context) # type: ignore
184+
central_widget_root.setParent(self.qml_engine)
185+
central_widget_root.setParentItem(self.centralQuickWidget.rootObject()) # type: ignore
186+
187+
self.main_tile_layout = central_widget_root.findChild(QObject, "main_tile_layout")
188+
assert self.main_tile_layout is not None, "Main tile layout not found in CentralWidget.qml"
189+
190+
self.level_widget = Levels_Widget(self, self._main_window_view_model.level_view_model)
191+
self.level_widget.set_buffer(self.audiobuffer)
192+
self.audiobuffer.new_data_available.connect(self.level_widget.handle_new_data)
178193

179-
self.playback_widget = PlaybackControlWidget(
180-
self, self.qml_engine, self.player)
181-
self.playback_widget.setVisible(self.settings_dialog.show_playback)
182-
self.vboxLayout.addWidget(self.playback_widget)
194+
self.playback_widget = PlaybackControlWidget(self, self.player, self._main_window_view_model.playback_control_view_model)
183195

184-
self.dockmanager = DockManager(self, self.main_grid_layout)
196+
self.dockmanager = DockManager(self, self.main_tile_layout)
185197

186198
# timer ticks
187199
self.display_timer.timeout.connect(self.dockmanager.canvasUpdate)
@@ -193,7 +205,7 @@ def __init__(self):
193205
self.ui.actionSettings.triggered.connect(self.settings_called)
194206
self.ui.actionAbout.triggered.connect(self.about_called)
195207
self.ui.actionNew_dock.triggered.connect(self.dockmanager.new_dock)
196-
self.playback_widget.recording_toggled.connect(self.timer_toggle)
208+
self.playback_widget.recording_toggled.connect(self.timer_changed)
197209

198210
# settings changes
199211
self.settings_dialog.show_playback_changed.connect(self.show_playback_changed)
@@ -237,7 +249,7 @@ def settings_called(self):
237249
self.settings_dialog.show()
238250

239251
def show_playback_changed(self, show: bool) -> None:
240-
self.playback_widget.setVisible(show)
252+
self._main_window_view_model.playback_control_enabled = show
241253

242254
# slot
243255
def about_called(self):
@@ -341,6 +353,23 @@ def timer_toggle(self):
341353
AudioBackend().restart()
342354
self.dockmanager.restart()
343355

356+
# slot
357+
def timer_changed(self, recording: bool):
358+
if not recording and self.display_timer.isActive():
359+
self.logger.info("Timer stop")
360+
self.display_timer.stop()
361+
self.ui.actionStart.setText("Start")
362+
self.playback_widget.stop_recording()
363+
AudioBackend().pause()
364+
self.dockmanager.pause()
365+
366+
if recording and not self.display_timer.isActive():
367+
self.logger.info("Timer start")
368+
self.display_timer.start()
369+
self.ui.actionStart.setText("Stop")
370+
self.playback_widget.start_recording()
371+
AudioBackend().restart()
372+
self.dockmanager.restart()
344373

345374
def qt_message_handler(mode, context, message):
346375
logger = logging.getLogger(__name__)

friture/dock.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def __init__(
6969
context = self.qml_engine.rootContext()
7070
self.dock_qml = dock_component.createWithInitialProperties({}, context)
7171
self.dock_qml.setParent(self.qml_engine)
72-
self.dock_qml.setParentItem(self.parent().main_grid_layout) # type: ignore
72+
self.dock_qml.setParentItem(self.parent().main_tile_layout) # type: ignore
7373

7474
initialProperties = {"viewModel": self.controlbar_viewmodel}
7575
component = QQmlComponent(self.qml_engine)

friture/dockmanager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +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
26+
from friture.tilelayout import TileLayout
2727

2828
from typing import Dict, List, Optional, TYPE_CHECKING
2929
if TYPE_CHECKING:

friture/levels.py

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,62 +19,32 @@
1919

2020
"""Level widget that displays peak and RMS levels for 1 or 2 ports."""
2121

22-
from PyQt5 import QtWidgets
23-
from PyQt5.QtQml import QQmlComponent
24-
from PyQt5.QtQuick import QQuickWindow # type: ignore
25-
from PyQt5.QtGui import QFontDatabase
22+
from PyQt5.QtCore import QObject
2623
import numpy as np
2724

28-
from friture.store import GetStore
2925
from friture.levels_settings import Levels_Settings_Dialog # settings dialog
3026
from friture.audioproc import audioproc
31-
from friture.level_view_model import LevelViewModel
3227
from friture.iec import dB_to_IEC
3328
from friture_extensions.exp_smoothing_conv import pyx_exp_smoothed_value
3429
from friture.audiobackend import SAMPLING_RATE
35-
from friture.qml_tools import qml_url, raise_if_error
3630

3731
SMOOTH_DISPLAY_TIMER_PERIOD_MS = 25
3832
LEVEL_TEXT_LABEL_PERIOD_MS = 250
3933

4034
LEVEL_TEXT_LABEL_STEPS = LEVEL_TEXT_LABEL_PERIOD_MS / SMOOTH_DISPLAY_TIMER_PERIOD_MS
4135

42-
class Levels_Widget(QtWidgets.QWidget):
36+
class Levels_Widget(QObject):
4337

44-
def __init__(self, parent, engine):
38+
def __init__(self, parent, view_model):
4539
super().__init__(parent)
46-
self.setObjectName("Levels_Widget")
4740

48-
self.gridLayout = QtWidgets.QVBoxLayout(self)
49-
self.gridLayout.setObjectName("gridLayout")
50-
51-
store = GetStore()
52-
self.level_view_model = LevelViewModel(store)
53-
store._dock_states.append(self.level_view_model)
54-
state_id = len(store._dock_states) - 1
55-
56-
self.quickWindow = QQuickWindow()
57-
component = QQmlComponent(engine, qml_url("Levels.qml"), self)
58-
raise_if_error(component)
59-
60-
fixedFont = QFontDatabase.systemFont(QFontDatabase.FixedFont)
61-
62-
engineContext = engine.rootContext()
63-
initialProperties = {"parent": self.quickWindow.contentItem(), "stateId": state_id, "fixedFont": fixedFont }
64-
self.qmlObject = component.createWithInitialProperties(initialProperties, engineContext)
65-
self.qmlObject.setParent(self.quickWindow)
66-
67-
self.quickWidget = QtWidgets.QWidget.createWindowContainer(self.quickWindow, self)
68-
self.quickWidget.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
69-
self.gridLayout.addWidget(self.quickWidget)
70-
71-
self.qmlObject.widthChanged.connect(self.onWidthChanged)
72-
self.onWidthChanged()
41+
self._parent = parent
42+
self.level_view_model = view_model
7343

7444
self.audiobuffer = None
7545

7646
# initialize the settings dialog
77-
self.settings_dialog = Levels_Settings_Dialog(self)
47+
self.settings_dialog = Levels_Settings_Dialog(parent)
7848

7949
# initialize the class instance that will do the fft
8050
self.proc = audioproc()
@@ -107,9 +77,6 @@ def __init__(self, parent, engine):
10777

10878
self.i = 0
10979

110-
def onWidthChanged(self):
111-
self.quickWidget.setFixedWidth(int(self.qmlObject.width()))
112-
11380
# method
11481
def set_buffer(self, buffer):
11582
self.audiobuffer = buffer
@@ -165,7 +132,7 @@ def handle_new_data(self, floatdata):
165132

166133
# method
167134
def canvasUpdate(self):
168-
if not self.isVisible():
135+
if not self._parent.isVisible():
169136
return
170137

171138
self.i += 1

0 commit comments

Comments
 (0)