Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions friture/CentralWidget.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2
import Friture 1.0

Rectangle {
SystemPalette { id: systemPalette; colorGroup: SystemPalette.Active }
color: systemPalette.window
anchors.fill: parent

property string fixedFont

TileLayout {
id: root
objectName: "main_tile_layout"
anchors.fill: parent
}
}

2 changes: 1 addition & 1 deletion friture/ControlBar.qml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ RowLayout {
ToolTip.text: "Select the type of audio widget"

// replace with implicitContentWidthPolicy to either ComboBox.WidestText on Qt 6
width: 130
width: 140
Layout.preferredWidth: width
Layout.rightMargin: 5
}
Expand Down
5 changes: 2 additions & 3 deletions friture/Dock.qml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ import QtQuick.Layouts 1.2
Rectangle {
SystemPalette { id: systemPalette; colorGroup: SystemPalette.Active }
color: systemPalette.window
anchors.fill: parent

property string fixedFont

ColumnLayout {
id: root
anchors.fill: parent

property string fixedFont

Item {
id: control_bar_container
objectName: "control_bar_container"
Expand Down
2 changes: 2 additions & 0 deletions friture/PitchView.qml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Item {
required property var viewModel
required property string fixedFont

anchors.fill: parent

Plot {
id: plot
scopedata: viewModel
Expand Down
27 changes: 20 additions & 7 deletions friture/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@
import logging
import logging.handlers

from PyQt5 import QtCore
from PyQt5 import QtCore, QtWidgets
# specifically import from PyQt5.QtGui and QWidgets for startup time improvement :
from PyQt5.QtWidgets import QMainWindow, QHBoxLayout, QVBoxLayout, QApplication, QSplashScreen
from PyQt5.QtGui import QPixmap
from PyQt5.QtQml import QQmlEngine, qmlRegisterSingletonType, qmlRegisterType
from PyQt5.QtQuickWidgets import QQuickWidget
from PyQt5.QtCore import QObject

import platformdirs

# importing friture.exceptionhandler also installs a temporary exception hook
Expand All @@ -42,7 +45,7 @@
from friture.audiobuffer import AudioBuffer # audio ring buffer class
from friture.audiobackend import AudioBackend # audio backend class
from friture.dockmanager import DockManager
from friture.tilelayout import TileLayout
from friture.tileLayout import TileLayout
from friture.level_view_model import LevelViewModel
from friture.level_data import LevelData
from friture.levels import Levels_Widget
Expand All @@ -61,7 +64,7 @@
from friture.spectrum_data import Spectrum_Data
from friture.plotFilledCurve import PlotFilledCurve
from friture.filled_curve import FilledCurve
from friture.qml_tools import qml_url
from friture.qml_tools import qml_url, raise_if_error
from friture.generators.sine import Sine_Generator_Settings_View_Model
from friture.generators.white import White_Generator_Settings_View_Model
from friture.generators.pink import Pink_Generator_Settings_View_Model
Expand Down Expand Up @@ -115,6 +118,7 @@ def __init__(self):
qmlRegisterType(SpectrogramImageData, 'Friture', 1, 0, 'SpectrogramImageData')
qmlRegisterType(ColorBar, 'Friture', 1, 0, 'ColorBar')
qmlRegisterType(Tick, 'Friture', 1, 0, 'Tick')
qmlRegisterType(TileLayout, 'Friture', 1, 0, 'TileLayout')
qmlRegisterType(Burst_Generator_Settings_View_Model, 'Friture', 1, 0, 'Burst_Generator_Settings_View_Model')
qmlRegisterType(Pink_Generator_Settings_View_Model, 'Friture', 1, 0, 'Pink_Generator_Settings_View_Model')
qmlRegisterType(White_Generator_Settings_View_Model, 'Friture', 1, 0, 'White_Generator_Settings_View_Model')
Expand Down Expand Up @@ -159,16 +163,25 @@ def __init__(self):
self.vboxLayout = QVBoxLayout()
self.hboxLayout.addLayout(self.vboxLayout)

self.centralLayout = TileLayout()
self.centralLayout.setContentsMargins(0, 0, 0, 0)
self.vboxLayout.addLayout(self.centralLayout)
self.centralQuickWidget = QQuickWidget(self.qml_engine, self)
self.centralQuickWidget.setObjectName("centralQuickWidget")
self.centralQuickWidget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.centralQuickWidget.setResizeMode(QQuickWidget.SizeRootObjectToView)
self.centralQuickWidget.setSource(qml_url("CentralWidget.qml"))
self.vboxLayout.addWidget(self.centralQuickWidget)

raise_if_error(self.centralQuickWidget)

central_widget_root = self.centralQuickWidget.rootObject()
self.main_grid_layout = central_widget_root.findChild(QObject, "main_tile_layout")
assert self.main_grid_layout is not None, "Main grid layout not found in CentralWidget.qml"

self.playback_widget = PlaybackControlWidget(
self, self.qml_engine, self.player)
self.playback_widget.setVisible(self.settings_dialog.show_playback)
self.vboxLayout.addWidget(self.playback_widget)

self.dockmanager = DockManager(self)
self.dockmanager = DockManager(self, self.main_grid_layout)

# timer ticks
self.display_timer.timeout.connect(self.dockmanager.canvasUpdate)
Expand Down
77 changes: 29 additions & 48 deletions friture/dock.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,10 @@

from inspect import signature

from PyQt5 import QtCore, QtWidgets
from PyQt5.QtQuick import QQuickView, QQuickItem # type: ignore
from PyQt5.QtQuick import QQuickItem # type: ignore
from PyQt5.QtQml import QQmlComponent
from PyQt5.QtQuickWidgets import QQuickWidget
from PyQt5.QtGui import QFontDatabase
from PyQt5.QtCore import QEvent, QObject
from PyQt5.QtWidgets import QWidget
from PyQt5.QtCore import QObject, QSettings

from friture.qml_tools import component_raise_if_error, qml_url
from friture.widgetdict import getWidgetById, widgetIds
Expand All @@ -38,7 +35,7 @@
from PyQt5.QtQml import QQmlEngine


class Dock(QtWidgets.QWidget):
class Dock(QObject):

def __init__(
self,
Expand All @@ -54,9 +51,6 @@ def __init__(

self.setObjectName(name)

self.vbox = QtWidgets.QVBoxLayout(self)
self.vbox.setContentsMargins(0, 0, 0, 0)

self.qml_engine = qml_engine

self.controlbar_viewmodel = ControlBarViewModel(self)
Expand All @@ -67,14 +61,15 @@ def __init__(
self.controlbar_viewmodel.moveNextClicked.connect(self.moveNext)
self.controlbar_viewmodel.closeClicked.connect(self.closeClicked)

self.dockQuickWidget = QQuickWidget(self.qml_engine, self)
self.dockQuickWidget.setObjectName("dockQuickWidget")
self.dockQuickWidget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
self.dockQuickWidget.setResizeMode(QQuickWidget.SizeRootObjectToView)
self.dockQuickWidget.setSource(qml_url("Dock.qml"))
self.vbox.addWidget(self.dockQuickWidget)
dock_component = QQmlComponent(self.qml_engine)
dock_component.loadUrl(qml_url("Dock.qml"))

component_raise_if_error(dock_component)

dock_root = self.dockQuickWidget.rootObject()
context = self.qml_engine.rootContext()
self.dock_qml = dock_component.createWithInitialProperties({}, context)
self.dock_qml.setParent(self.qml_engine)
self.dock_qml.setParentItem(self.parent().main_grid_layout) # type: ignore

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

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

self.audio_widget_container = dock_root.findChild(QObject, "audio_widget_container")
self.audio_widget_container = self.dock_qml.findChild(QObject, "audio_widget_container")
assert self.audio_widget_container is not None, "Audio widget container not found in Dock.qml"

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

self.widget_select(widgetId)

# note that by default the closeEvent is accepted, no need to do it explicitely
def closeEvent(self, event):
def closeClicked(self):
self.dockmanager.close_dock(self)

def cleanup(self):
if self.dock_qml is not None:
self.dock_qml.setParentItem(None) # type: ignore
self.dock_qml.deleteLater()
self.dock_qml = None

if self.audio_widget_qml is not None:
self.audio_widget_qml.setParentItem(None) # type: ignore
self.audio_widget_qml.deleteLater()
Expand All @@ -115,33 +117,11 @@ def closeEvent(self, event):
self.audiowidget.deleteLater()
self.audiowidget = None

def closeClicked(self):
self.close()

def movePrevious(self):
layout = self.parent().parent().centralLayout
itemList = layout.itemList

for i, item in enumerate(itemList):
if item.widget() is self:
if i > 0 and i < len(itemList):
itemList.insert(i-1, itemList.pop(i))
self.dockmanager.docks.insert(i-1, self.dockmanager.docks.pop(i))
layout.update()

break
self.dockmanager.movePrevious(self)

def moveNext(self):
layout = self.parent().parent().centralLayout
itemList = layout.itemList

for i, item in enumerate(itemList):
if item.widget() is self:
if i >= 0 and i < len(itemList)-1:
itemList.insert(i+1, itemList.pop(i))
self.dockmanager.docks.insert(i+1, self.dockmanager.docks.pop(i))
layout.update()
break
self.dockmanager.moveNext(self)

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

if self.audiowidget is not None:
settings = QtCore.QSettings()
settings = QSettings()
self.audiowidget.saveState(settings) # type: ignore
assert self.widgetId is not None
self.dockmanager.last_settings[self.widgetId] = settings
Expand All @@ -181,7 +161,7 @@ def widget_select(self, widgetId: int) -> None:
if len(signature(constructor).parameters) == 2:
self.audiowidget = constructor(self, self.qml_engine)
else:
self.audiowidget = constructor(self)
self.audiowidget = constructor(self.parent())
assert self.audiowidget is not None # mypy can't prove this :(

initialProperties = {
Expand Down Expand Up @@ -218,7 +198,7 @@ def on_status_changed(self, status):
self.logger.error("QML error: " + error.toString())

def canvasUpdate(self):
if self.audiowidget is not None and self.isVisible():
if self.audiowidget is not None:
self.audiowidget.canvasUpdate()


Expand All @@ -237,7 +217,8 @@ def settings_slot(self, checked):
# method
def saveState(self, settings):
settings.setValue("type", self.widgetId)
self.audiowidget.saveState(settings)
if self.audiowidget is not None:
self.audiowidget.saveState(settings)

# method
def restoreState(self, settings):
Expand Down
28 changes: 20 additions & 8 deletions friture/dockmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from PyQt5.QtWidgets import QMainWindow
from friture.defaults import DEFAULT_DOCKS
from friture.dock import Dock
from friture.tileLayout import TileLayout

from typing import Dict, List, Optional, TYPE_CHECKING
if TYPE_CHECKING:
Expand All @@ -31,7 +32,7 @@

class DockManager(QtCore.QObject):

def __init__(self, parent: 'Friture') -> None:
def __init__(self, parent: 'Friture', dock_layout: TileLayout) -> None:
super().__init__(parent)
self._parent = parent

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

self.dock_layout = dock_layout

# slot
def new_dock(self) -> None:
# the dock objectName is unique
Expand All @@ -64,7 +67,6 @@ def new_dock(self) -> None:
new_dock = Dock(self._parent, name, self._parent.qml_engine, widget_id)
if settings is not None:
new_dock.restoreState(settings)
self._parent.centralLayout.addWidget(new_dock)

self.docks += [new_dock]

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

self._parent.centralLayout.removeWidget(dock)
self.docks.remove(dock)
dock.cleanup()

def movePrevious(self, dock):
i = self.docks.index(dock)
if i > 0:
self.dock_layout.movePrevious(i)
self.docks.insert(i-1, self.docks.pop(i))

def moveNext(self, dock):
i = self.docks.index(dock)
if i < len(self.docks) - 1:
self.dock_layout.moveNext(i)
self.docks.insert(i+1, self.docks.pop(i))

def saveState(self, settings):
docknames = [dock.objectName() for dock in self.docks]
Expand Down Expand Up @@ -107,13 +121,10 @@ def restoreState(self, settings):
dock = Dock(self.parent(), name, self.parent().qml_engine, widgetId)
dock.restoreState(settings)
settings.endGroup()
self.parent().centralLayout.addWidget(dock)
self.docks.append(dock)
else:
self.logger.info("First launch, display a default set of docks")
self.docks = [Dock(self.parent(), "Dock %d" % (i), self.parent().qml_engine, widgetId=widget_type) for i, widget_type in enumerate(DEFAULT_DOCKS)]
for dock in self.docks:
self.parent().centralLayout.addWidget(dock)

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

def canvasUpdate(self):
for dock in self.docks:
dock.canvasUpdate()
if self._parent.isVisible():
for dock in self.docks:
dock.canvasUpdate()

def pause(self):
for dock in self.docks:
Expand Down
2 changes: 1 addition & 1 deletion friture/spectrogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def handle_new_data(self, floatdata: ndarray) -> None:
norm_spectrogram = self.scale_spectrogram(self.log_spectrogram(spn) + w)

self.screen_resampler.set_height(self.PlotZoneImage.spectrogram_screen_height())
screen_rate_frac = Fraction(self.PlotZoneImage.spectrogram_screen_width(), int(self.timerange_s * 1000))
screen_rate_frac = Fraction(max(self.PlotZoneImage.spectrogram_screen_width(), 1), int(self.timerange_s * 1000))
self.screen_resampler.set_ratio(self.sfft_rate_frac, screen_rate_frac)
self.frequency_resampler.setnsamples(self.PlotZoneImage.spectrogram_screen_height())

Expand Down
Loading