Skip to content

Commit

Permalink
Push viewmodel updates to the main thread (#72)
Browse files Browse the repository at this point in the history
Try to ensure that Qt doesn't update the view whilst the model is being updated by moving everything to the main thread.
  • Loading branch information
machinewrapped authored Oct 8, 2023
1 parent fdb63f6 commit 6221d1a
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 87 deletions.
3 changes: 1 addition & 2 deletions GUI/MainWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,7 @@ def _on_command_complete(self, command : Command, success):
self._show_new_project_Settings(self.datamodel.project)

if command.model_update.HasUpdate():
# Patch the model
command.model_update.UpdateModel(self.datamodel)
self.datamodel.UpdateViewModel(command.model_update)

elif command.datamodel:
# Shouldn't need to do a full model rebuild often?
Expand Down
2 changes: 1 addition & 1 deletion GUI/ProjectCommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ def _on_batch_translated(self, batch : SubtitleBatch):
'translated' : { line.number : { 'text' : line.text } for line in batch.translated if line.number }
})

update.UpdateModel(self.datamodel)
self.datamodel.UpdateViewModel(update)

#############################################################

Expand Down
17 changes: 16 additions & 1 deletion GUI/ProjectDataModel.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from PySide6.QtCore import QMutex, QMutexLocker
from GUI.ProjectViewModel import ProjectViewModel
from GUI.ProjectViewModelUpdate import ModelUpdate
from PySubtitleGPT.Options import Options
from PySubtitleGPT.SubtitleProject import SubtitleProject

Expand Down Expand Up @@ -56,9 +57,23 @@ def PerformModelAction(self, action_name : str, params):
raise ValueError(f"No handler defined for action {action_name}")

def CreateViewModel(self):
"""
Create a viewmodel for the subtitles
"""
with QMutexLocker(self.mutex):
self.viewmodel = ProjectViewModel()
self.viewmodel.CreateModel(self.project.subtitles)
return self.viewmodel


def UpdateViewModel(self, update : ModelUpdate):
"""
Patch the viewmodel
"""
if not isinstance(update, ModelUpdate):
raise Exception("Invalid model update")

if update.rebuild:
# TODO: rebuild on the main thread
self.CreateViewModel()
elif self.viewmodel:
self.viewmodel.AddUpdate(update)
88 changes: 87 additions & 1 deletion GUI/ProjectViewModel.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import logging
import os
from PySide6.QtCore import Qt, QModelIndex
from PySide6.QtCore import Qt, QModelIndex, QMutex, QMutexLocker, Signal

from PySide6.QtGui import QStandardItemModel, QStandardItem
from GUI.GuiHelpers import GetLineHeight
from GUI.ProjectViewModelUpdate import ModelUpdate
from PySubtitleGPT import SubtitleLine

from PySubtitleGPT.Helpers import FormatMessages, Linearise, UpdateFields
Expand All @@ -26,12 +27,29 @@ def __init__(self, message, error = None):
super().__init__(message, error)

class ProjectViewModel(QStandardItemModel):
updatesPending = Signal()

def __init__(self):
super().__init__()
self.model = {}
self.updates = []
self.update_lock = QMutex()

def getRootItem(self):
return self.invisibleRootItem()

def AddUpdate(self, update):
with QMutexLocker(self.update_lock):
self.updates.append(update)
self.updatesPending.emit()

def ProcessUpdates(self):
with QMutexLocker(self.update_lock):
for update in self.updates:
self.ApplyUpdate(update)
self.updates = []

self.layoutChanged.emit()

def CreateModel(self, data : SubtitleFile):
if not isinstance(data, SubtitleFile):
Expand Down Expand Up @@ -130,6 +148,74 @@ def Remap(self):
batch_item.translated = { item.number: item for item in line_items if item.is_translation }
batch_item.originals = { item.number: item for item in line_items if not item.is_translation }

def ApplyUpdate(self, update : ModelUpdate):
"""
Patch the viewmodel
"""
self.beginResetModel()
self.blockSignals(True)

for scene_number, scene_update in update.scenes.updates.items():
self.UpdateScene(scene_number, scene_update)

for key, batch_update in update.batches.updates.items():
scene_number, batch_number = key
self.UpdateBatch(scene_number, batch_number, batch_update)

#TODO: Use UpdateOriginalLines
for key, line_update in update.originals.updates.items():
scene_number, batch_number, line_number = key
self.UpdateOriginalLine(scene_number, batch_number, line_number, line_update)

#TODO: Use UpdateTranslatedLines
for key, line_update in update.translated.updates.items():
scene_number, batch_number, line_number = key
self.UpdateTranslatedLine(scene_number, batch_number, line_number, line_update)

for scene_number, scene in update.scenes.replacements.items():
self.ReplaceScene(scene)

for key, batch in update.batches.replacements.items():
scene_number, batch_number = key
self.ReplaceBatch(batch)

for scene_number in reversed(update.scenes.removals):
self.RemoveScene(scene_number)

for key in reversed(update.batches.removals):
scene_number, batch_number = key
self.RemoveBatch(scene_number, batch_number)

for key in reversed(update.originals.removals):
scene_number, batch_number, line_number = key
self.RemoveOriginalLine(scene_number, batch_number, line_number)

for key in reversed(update.translated.removals):
scene_number, batch_number, line_number = key
self.RemoveTranslatedLine(scene_number, batch_number, line_number)

for scene_number, scene in update.scenes.additions.items():
self.AddScene(scene)

for key, batch in update.batches.additions.items():
scene_number, batch_number = key
self.AddBatch(batch)

for key, line in update.originals.additions.items():
scene_number, batch_number, line_number = key
self.AddOriginalLine(scene_number, batch_number, line_number, line)

for key, line in update.translated.additions.items():
scene_number, batch_number, line_number = key
self.AddTranslatedLine(scene_number, batch_number, line_number, line)

# Rebuild the model dictionaries
self.Remap()
self.blockSignals(False)
self.endResetModel()



#############################################################################

def AddScene(self, scene : SubtitleScene):
Expand Down
80 changes: 0 additions & 80 deletions GUI/ProjectViewModelUpdate.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
from GUI.ProjectDataModel import ProjectDataModel
from GUI.ProjectViewModel import ProjectViewModel

class ModelUpdateSection:
def __init__(self):
self.updates = {}
Expand Down Expand Up @@ -34,80 +31,3 @@ def __init__(self):
def HasUpdate(self) -> bool:
return self.scenes.HasUpdate() or self.batches.HasUpdate() or self.originals.HasUpdate() or self.translated.HasUpdate()

def UpdateModel(self, datamodel: ProjectDataModel):
"""
Patch the viewmodel
"""
if not datamodel or not isinstance(datamodel, ProjectDataModel):
raise Exception("Invalid datamodel")

if self.rebuild:
datamodel.CreateViewModel()
return

with datamodel.GetLock():
viewmodel : ProjectViewModel = datamodel.viewmodel
viewmodel.beginResetModel()
viewmodel.blockSignals(True)

for scene_number, scene_update in self.scenes.updates.items():
viewmodel.UpdateScene(scene_number, scene_update)

for key, batch_update in self.batches.updates.items():
scene_number, batch_number = key
viewmodel.UpdateBatch(scene_number, batch_number, batch_update)

#TODO: Use UpdateOriginalLines
for key, line_update in self.originals.updates.items():
scene_number, batch_number, line_number = key
viewmodel.UpdateOriginalLine(scene_number, batch_number, line_number, line_update)

#TODO: Use UpdateTranslatedLines
for key, line_update in self.translated.updates.items():
scene_number, batch_number, line_number = key
viewmodel.UpdateTranslatedLine(scene_number, batch_number, line_number, line_update)

for scene_number, scene in self.scenes.replacements.items():
viewmodel.ReplaceScene(scene)

for key, batch in self.batches.replacements.items():
scene_number, batch_number = key
viewmodel.ReplaceBatch(batch)

for scene_number in reversed(self.scenes.removals):
viewmodel.RemoveScene(scene_number)

for key in reversed(self.batches.removals):
scene_number, batch_number = key
viewmodel.RemoveBatch(scene_number, batch_number)

for key in reversed(self.originals.removals):
scene_number, batch_number, line_number = key
viewmodel.RemoveOriginalLine(scene_number, batch_number, line_number)

for key in reversed(self.translated.removals):
scene_number, batch_number, line_number = key
viewmodel.RemoveTranslatedLine(scene_number, batch_number, line_number)

for scene_number, scene in self.scenes.additions.items():
viewmodel.AddScene(scene)

for key, batch in self.batches.additions.items():
scene_number, batch_number = key
viewmodel.AddBatch(batch)

for key, line in self.originals.additions.items():
scene_number, batch_number, line_number = key
viewmodel.AddOriginalLine(scene_number, batch_number, line_number, line)

for key, line in self.translated.additions.items():
scene_number, batch_number, line_number = key
viewmodel.AddTranslatedLine(scene_number, batch_number, line_number, line)

# Rebuild the model dictionaries
viewmodel.Remap()
viewmodel.blockSignals(False)
viewmodel.endResetModel()

viewmodel.layoutChanged.emit()

9 changes: 7 additions & 2 deletions GUI/Widgets/ContentView.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
from PySide6.QtWidgets import QSplitter, QVBoxLayout, QWidget, QSizePolicy, QDialog
from PySide6.QtCore import Qt, Signal
from PySide6.QtCore import Qt, QMutexLocker, Signal, Slot
from GUI.ProjectSelection import ProjectSelection
from GUI.ProjectViewModel import LineItem, ProjectViewModel
from GUI.Widgets.Editors import EditSubtitleDialog
Expand Down Expand Up @@ -50,8 +50,9 @@ def ShowSelection(self, selection : ProjectSelection):
self.translation_view.ShowSelection(selection)
self.selection_view.ShowSelection(selection)

def Populate(self, viewmodel):
def Populate(self, viewmodel : ProjectViewModel):
self.viewmodel = viewmodel
self.viewmodel.updatesPending.connect(self._update_view_model)
self.subtitle_view.SetViewModel(viewmodel)
self.translation_view.SetViewModel(viewmodel)
self.selection_view.ShowSelection(ProjectSelection())
Expand Down Expand Up @@ -107,3 +108,7 @@ def _edit_line(self, item : LineItem):

self.actionRequested.emit('Update Line', (item.number, original_text, translated_text,))

@Slot()
def _update_view_model(self):
if self.viewmodel:
self.viewmodel.ProcessUpdates()

0 comments on commit 6221d1a

Please sign in to comment.