Skip to content

Commit 1d3a4b6

Browse files
committed
Models: Persistence enhancements
- Move ModelRefreshed signal to outer class (now a QObject subclass) - Move mimeData() to proxy model, where it needs to be for views - (This allowed dropping the inner Files and Transitions model classes entirely, replaced with a stock `QStandardItemModel`. All of the special logic is in the custom proxy model class. - Add a persistent QItemSelectionModel to all three models, shared by both views. Selection is no longer lost when switching view. - Track the currently visible view for 'foo' (either fooListView or fooTreeView) in main_window, access as `get_app().window.fooView`
1 parent 91ea99a commit 1d3a4b6

13 files changed

+207
-150
lines changed

src/classes/app.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ def __init__(self, *args, mode=None):
243243
else:
244244
# Apply the default settings and Auto import media file
245245
self.project.load("")
246-
self.window.filesTreeView.add_file(path)
246+
self.window.filesView.add_file(path)
247247
else:
248248
# Recover backup file (this can't happen until after the Main Window has completely loaded)
249249
self.window.RecoverBackup.emit()

src/windows/main_window.py

+29-26
Original file line numberDiff line numberDiff line change
@@ -1926,22 +1926,22 @@ def actionDetailsView_trigger(self, event):
19261926
if app.context_menu_object == "files":
19271927
s.set("file_view", "details")
19281928
self.filesListView.hide()
1929-
self.filesTreeView.show()
1930-
self.filesTreeView.clearSelection()
1929+
self.filesView = self.filesTreeView
1930+
self.filesView.show()
19311931

19321932
# Transitions
19331933
elif app.context_menu_object == "transitions":
19341934
s.set("transitions_view", "details")
19351935
self.transitionsListView.hide()
1936-
self.transitionsTreeView.show()
1937-
self.transitionsTreeView.clearSelection()
1936+
self.transitionsView = self.transitionsTreeView
1937+
self.transitionsView.show()
19381938

19391939
# Effects
19401940
elif app.context_menu_object == "effects":
19411941
s.set("effects_view", "details")
19421942
self.effectsListView.hide()
1943-
self.effectsTreeView.show()
1944-
self.effectsTreeView.clearSelection()
1943+
self.effectsView = self.effectsTreeView
1944+
self.effectsView.show()
19451945

19461946
def actionThumbnailView_trigger(self, event):
19471947
log.info("Switch to Thumbnail View")
@@ -1954,25 +1954,25 @@ def actionThumbnailView_trigger(self, event):
19541954
if app.context_menu_object == "files":
19551955
s.set("file_view", "thumbnail")
19561956
self.filesTreeView.hide()
1957-
self.filesListView.show()
1958-
self.filesListView.clearSelection()
1957+
self.filesView = self.filesListView
1958+
self.filesView.show()
19591959

19601960
# Transitions
19611961
elif app.context_menu_object == "transitions":
19621962
s.set("transitions_view", "thumbnail")
19631963
self.transitionsTreeView.hide()
1964-
self.transitionsListView.show()
1965-
self.transitionsListView.clearSelection()
1964+
self.transitionsView = self.transitionsListView
1965+
self.transitionsView.show()
19661966

19671967
# Effects
19681968
elif app.context_menu_object == "effects":
19691969
s.set("effects_view", "thumbnail")
19701970
self.effectsTreeView.hide()
1971-
self.effectsListView.show()
1972-
self.effectsListView.clearSelection()
1971+
self.effectsView = self.effectsListView
1972+
self.effectsView.show()
19731973

19741974
def resize_contents(self):
1975-
if self.filesTreeView:
1975+
if self.filesView == self.filesTreeView:
19761976
self.filesTreeView.resize_contents()
19771977

19781978
def getDocks(self):
@@ -2625,28 +2625,30 @@ def __init__(self, mode=None):
26252625
self.tabFiles.layout().insertWidget(-1, self.filesTreeView)
26262626
self.tabFiles.layout().insertWidget(-1, self.filesListView)
26272627
if s.get("file_view") == "details":
2628+
self.filesView = self.filesTreeView
26282629
self.filesListView.hide()
2629-
self.filesTreeView.show()
2630-
self.filesTreeView.setFocus()
26312630
else:
2631+
self.filesView = self.filesListView
26322632
self.filesTreeView.hide()
2633-
self.filesListView.show()
2634-
self.filesListView.setFocus()
2633+
# Show our currently-enabled project files view
2634+
self.filesView.show()
2635+
self.filesView.setFocus()
26352636

2636-
# Setup transitions tree
2637+
# Setup transitions tree and list views
26372638
self.transition_model = TransitionsModel()
26382639
self.transitionsTreeView = TransitionsTreeView(self.transition_model)
26392640
self.transitionsListView = TransitionsListView(self.transition_model)
26402641
self.tabTransitions.layout().insertWidget(-1, self.transitionsTreeView)
26412642
self.tabTransitions.layout().insertWidget(-1, self.transitionsListView)
26422643
if s.get("transitions_view") == "details":
2644+
self.transitionsView = self.transitionsTreeView
26432645
self.transitionsListView.hide()
2644-
self.transitionsTreeView.show()
2645-
self.transitionsTreeView.setFocus()
26462646
else:
2647+
self.transitionsView = self.transitionsListView
26472648
self.transitionsTreeView.hide()
2648-
self.transitionsListView.show()
2649-
self.transitionsListView.setFocus()
2649+
# Show our currently-enabled transitions view
2650+
self.transitionsView.show()
2651+
self.transitionsView.setFocus()
26502652

26512653
# Setup effects tree
26522654
self.effects_model = EffectsModel()
@@ -2655,13 +2657,14 @@ def __init__(self, mode=None):
26552657
self.tabEffects.layout().insertWidget(-1, self.effectsTreeView)
26562658
self.tabEffects.layout().insertWidget(-1, self.effectsListView)
26572659
if s.get("effects_view") == "details":
2660+
self.effectsView = self.effectsTreeView
26582661
self.effectsListView.hide()
2659-
self.effectsTreeView.show()
2660-
self.effectsTreeView.setFocus()
26612662
else:
2663+
self.effectsView = self.effectsListView
26622664
self.effectsTreeView.hide()
2663-
self.effectsListView.show()
2664-
self.effectsListView.setFocus()
2665+
# Show our currently-enabled effects view
2666+
self.effectsView.show()
2667+
self.effectsView.setFocus()
26652668

26662669
# Setup emojis view
26672670
self.emoji_model = EmojisModel()

src/windows/models/effects_model.py

+21-9
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,14 @@
2727

2828
import os
2929

30-
from PyQt5.QtCore import QMimeData, Qt, QSize, pyqtSignal, QSortFilterProxyModel
31-
from PyQt5.QtGui import *
30+
from PyQt5.QtCore import (
31+
QObject, QMimeData, Qt, QSize, pyqtSignal,
32+
QSortFilterProxyModel, QPersistentModelIndex, QItemSelectionModel,
33+
)
34+
from PyQt5.QtGui import (
35+
QIcon, QPixmap,
36+
QStandardItemModel, QStandardItem,
37+
)
3238
from PyQt5.QtWidgets import QMessageBox
3339
import openshot # Python module for libopenshot (required video editing module installed separately)
3440

@@ -38,11 +44,10 @@
3844

3945
import json
4046

41-
class EffectsStandardItemModel(QStandardItemModel):
42-
ModelRefreshed = pyqtSignal()
4347

48+
class EffectsProxyModel(QSortFilterProxyModel):
4449
def __init__(self, parent=None):
45-
QStandardItemModel.__init__(self)
50+
super().__init__(parent=parent)
4651

4752
def mimeData(self, indexes):
4853
# Create MimeData for drag operation
@@ -60,7 +65,9 @@ def mimeData(self, indexes):
6065
return data
6166

6267

63-
class EffectsModel():
68+
class EffectsModel(QObject):
69+
ModelRefreshed = pyqtSignal()
70+
6471
def update_model(self, clear=True):
6572
log.info("updating effects model.")
6673
app = get_app()
@@ -188,20 +195,25 @@ def update_model(self, clear=True):
188195
self.model_names[effect_name] = effect_name
189196

190197
# Emit signal when model is updated
191-
self.model.ModelRefreshed.emit()
198+
self.ModelRefreshed.emit()
192199

193200
def __init__(self, *args):
201+
# Init QObject superclass
202+
super().__init__(*args)
194203

195204
# Create standard model
196205
self.app = get_app()
197-
self.model = EffectsStandardItemModel()
206+
self.model = QStandardItemModel()
198207
self.model.setColumnCount(5)
199208
self.model_names = {}
200209

201210
# Create proxy model (for sorting and filtering)
202-
self.proxy_model = QSortFilterProxyModel()
211+
self.proxy_model = EffectsProxyModel()
203212
self.proxy_model.setDynamicSortFilter(False)
204213
self.proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive)
205214
self.proxy_model.setSortCaseSensitivity(Qt.CaseSensitive)
206215
self.proxy_model.setSourceModel(self.model)
207216
self.proxy_model.setSortLocaleAware(True)
217+
218+
# Create selection model to share between views
219+
self.selection_model = QItemSelectionModel(self.proxy_model)

src/windows/models/files_model.py

+52-37
Original file line numberDiff line numberDiff line change
@@ -27,41 +27,26 @@
2727
"""
2828

2929
import os
30-
31-
from PyQt5.QtCore import QMimeData, Qt, pyqtSignal, QSortFilterProxyModel, QEventLoop
32-
from PyQt5.QtGui import *
33-
from PyQt5.QtWidgets import QMessageBox
34-
import openshot # Python module for libopenshot (required video editing module installed separately)
35-
30+
import json
31+
import re
32+
import glob
33+
34+
from PyQt5.QtCore import (
35+
QMimeData, Qt, pyqtSignal, QEventLoop, QObject,
36+
QSortFilterProxyModel, QItemSelectionModel, QPersistentModelIndex,
37+
)
38+
from PyQt5.QtGui import (
39+
QIcon, QStandardItem, QStandardItemModel
40+
)
3641
from classes import updates
3742
from classes import info
43+
from classes.image_types import is_image
3844
from classes.query import File
3945
from classes.logger import log
4046
from classes.app import get_app
4147
from requests import get
4248

43-
import json
44-
45-
class FileStandardItemModel(QStandardItemModel):
46-
ModelRefreshed = pyqtSignal()
47-
48-
def __init__(self, parent=None):
49-
QStandardItemModel.__init__(self)
50-
51-
def mimeData(self, indexes):
52-
# Create MimeData for drag operation
53-
data = QMimeData()
54-
55-
# Get list of all selected file ids
56-
files = []
57-
for item in indexes:
58-
selected_row = self.itemFromIndex(item).row()
59-
files.append(self.item(selected_row, 5).text())
60-
data.setText(json.dumps(files))
61-
data.setHtml("clip")
62-
63-
# Return Mimedata
64-
return data
49+
import openshot
6550

6651

6752
class FileFilterProxyModel(QSortFilterProxyModel):
@@ -98,10 +83,32 @@ def filterAcceptsRow(self, sourceRow, sourceParent):
9883
return self.filterRegExp().indexIn(file_name) >= 0 or self.filterRegExp().indexIn(tags) >= 0
9984

10085
# Continue running built-in parent filter logic
101-
return super(FileFilterProxyModel, self).filterAcceptsRow(sourceRow, sourceParent)
86+
return super().filterAcceptsRow(sourceRow, sourceParent)
10287

88+
def mimeData(self, indexes):
89+
# Create MimeData for drag operation
90+
data = QMimeData()
91+
92+
# Get list of all selected file ids
93+
ids = self.parent.selected_file_ids()
94+
data.setText(json.dumps(ids))
95+
data.setHtml("clip")
96+
97+
# Return Mimedata
98+
return data
99+
100+
def __init__(self, **kwargs):
101+
if "parent" in kwargs:
102+
self.parent = kwargs["parent"]
103+
kwargs.pop("parent")
104+
105+
# Call base class implementation
106+
super().__init__(**kwargs)
107+
108+
109+
class FilesModel(QObject, updates.UpdateInterface):
110+
ModelRefreshed = pyqtSignal()
103111

104-
class FilesModel(updates.UpdateInterface):
105112
# This method is invoked by the UpdateManager each time a change happens (i.e UpdateInterface)
106113
def changed(self, action):
107114

@@ -246,7 +253,7 @@ def update_model(self, clear=True, delete_file_id=None):
246253
self.ignore_updates = False
247254

248255
# Emit signal when model is updated
249-
self.model.ModelRefreshed.emit()
256+
self.ModelRefreshed.emit()
250257

251258
def get_thumb_path(self, file_id, thumbnail_frame, clear_cache=False):
252259
"""Get thumbnail path by invoking HTTP thumbnail request"""
@@ -287,31 +294,39 @@ def update_file_thumbnail(self, file_id):
287294
item = self.model.itemFromIndex(thumb_index)
288295
item.setIcon(QIcon(thumb_path))
289296
item.setText(name)
297+
# Emit signal when model is updated
298+
self.ModelRefreshed.emit()
290299

291-
# Emit signal when model is updated
292-
self.model.ModelRefreshed.emit()
293-
break
294300
self.ignore_updates = False
295301

296302
def __init__(self, *args):
297303

298-
# Add self as listener to project data updates (undo/redo, as well as normal actions handled within this class all update the tree model)
304+
# Add self as listener to project data updates
305+
# (undo/redo, as well as normal actions handled within this class all update the model)
299306
app = get_app()
300307
app.updates.add_listener(self)
301308

302309
# Create standard model
303-
self.model = FileStandardItemModel()
310+
self.model = QStandardItemModel()
304311
self.model.setColumnCount(6)
305312
self.model_ids = {}
306313
self.ignore_updates = False
307314

315+
self.ignore_image_sequence_paths = []
316+
308317
# Create proxy model (for sorting and filtering)
309-
self.proxy_model = FileFilterProxyModel()
318+
self.proxy_model = FileFilterProxyModel(parent=self)
310319
self.proxy_model.setDynamicSortFilter(True)
311320
self.proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive)
312321
self.proxy_model.setSortCaseSensitivity(Qt.CaseSensitive)
313322
self.proxy_model.setSourceModel(self.model)
314323
self.proxy_model.setSortLocaleAware(True)
315324

325+
# Create selection model to share between views
326+
self.selection_model = QItemSelectionModel(self.proxy_model)
327+
316328
# Connect signal
317329
app.window.FileUpdated.connect(self.update_file_thumbnail)
330+
331+
# Call init for superclass QObject
332+
super(QObject, FilesModel).__init__(self, *args)

0 commit comments

Comments
 (0)