Skip to content

Commit 55b9468

Browse files
committed
Eliminate main_window's self.selected_files list
- Selection is now managed completely in the Files model (outer class), directly from the persistent QItemSelectionModel. - Helper functions in both files_model and main_window ease transition - updateSelection() is also gone, as there's no selection cache. Selection data always comes live from the selection model. - Also greatly simplify drag-and-drop code, using proxy & selection models
1 parent 1d3a4b6 commit 55b9468

File tree

4 files changed

+130
-103
lines changed

4 files changed

+130
-103
lines changed

src/windows/main_window.py

+50-41
Original file line numberDiff line numberDiff line change
@@ -399,9 +399,13 @@ def actionTitle_trigger(self, event):
399399

400400
def actionEditTitle_trigger(self, event):
401401

402-
# Get selected svg title file
403-
selected_file_id = self.selected_files[0]
404-
file = File.get(id=selected_file_id)
402+
# Get requested view item
403+
index = self.filesView.indexAt(event.pos())
404+
405+
# look up svg title file ID from column 5 of that row
406+
selected_id = index.model().sibling(index.row(), 5, index.parent()).data()
407+
408+
file = File.get(id=selected_id)
405409
file_path = file.data.get("path")
406410

407411
# show dialog for editing title
@@ -431,9 +435,7 @@ def actionDuplicateTitle_trigger(self, event):
431435
file_path = None
432436

433437
# Loop through selected files (set 1 selected file if more than 1)
434-
for file_id in self.selected_files:
435-
# Find matching file
436-
f = File.get(id=file_id)
438+
for f in self.selected_files():
437439
if f.data.get("path").endswith(".svg"):
438440
file_path = f.data.get("path")
439441
break
@@ -730,11 +732,11 @@ def actionImportFiles_trigger(self, event):
730732

731733
def actionAdd_to_Timeline_trigger(self, event):
732734
# Loop through selected files
733-
f = None
734-
files = []
735-
for file_id in self.selected_files:
736-
# Find matching file
737-
files.append(File.get(id=file_id))
735+
files = self.selected_files()
736+
737+
# Bail if nothing's selected
738+
if not files:
739+
return
738740

739741
# Get current position of playhead
740742
fps = get_app().project.get("fps")
@@ -918,14 +920,11 @@ def actionPreview_File_trigger(self, event):
918920
log.info('actionPreview_File_trigger')
919921

920922
# Loop through selected files (set 1 selected file if more than 1)
921-
f = None
922-
for file_id in self.selected_files:
923-
# Find matching file
924-
f = File.get(id=file_id)
923+
f = self.files_model.current_file()
925924

926925
# Bail out if no file selected
927926
if not f:
928-
log.info("Preview action failed, selected files: {}".format(self.selected_files))
927+
log.info("Couldn't find current file for preview window")
929928
return
930929

931930
# show dialog
@@ -1660,14 +1659,11 @@ def actionSplitClip_trigger(self, event):
16601659
log.info("actionSplitClip_trigger")
16611660

16621661
# Loop through selected files (set 1 selected file if more than 1)
1663-
f = None
1664-
for file_id in self.selected_files:
1665-
# Find matching file
1666-
f = File.get(id=file_id)
1662+
f = self.files_model.current_file()
16671663

16681664
# Bail out if no file selected
16691665
if not f:
1670-
log.warn("Split clip action failed, selected files: {}".format(self.selected_files))
1666+
log.warn("Split clip action failed, couldn't find current file")
16711667
return
16721668

16731669
# show dialog
@@ -1684,21 +1680,19 @@ def actionRemove_from_Project_trigger(self, event):
16841680
log.info("actionRemove_from_Project_trigger")
16851681

16861682
# Loop through selected files
1687-
for file_id in self.selected_files:
1688-
# Find matching file
1689-
f = File.get(id=file_id)
1690-
if f:
1691-
# Remove file
1692-
f.delete()
1683+
for f in self.selected_files():
1684+
if not f:
1685+
continue
16931686

1694-
# Find matching clips (if any)
1695-
clips = Clip.filter(file_id=file_id)
1696-
for c in clips:
1697-
# Remove clip
1698-
c.delete()
1687+
id = f.data["id"]
1688+
# Remove file
1689+
f.delete()
16991690

1700-
# Clear selected files
1701-
self.selected_files = []
1691+
# Find matching clips (if any)
1692+
clips = Clip.filter(file_id=id)
1693+
for c in clips:
1694+
# Remove clip
1695+
c.delete()
17021696

17031697
# Refresh preview
17041698
get_app().window.refreshFrameSignal.emit()
@@ -1891,11 +1885,11 @@ def actionFullscreen_trigger(self, event):
18911885
def actionFile_Properties_trigger(self, event):
18921886
log.info("Show file properties")
18931887

1894-
# Loop through selected files (set 1 selected file if more than 1)
1895-
f = None
1896-
for file_id in self.selected_files:
1897-
# Find matching file
1898-
f = File.get(id=file_id)
1888+
# Get current selected file (corresponding to menu, if possible)
1889+
f = self.files_model.current_file()
1890+
if not f:
1891+
log.warning("Couldn't find current file for properties window")
1892+
return
18991893

19001894
# show dialog
19011895
from windows.file_properties import FileProperties
@@ -1905,7 +1899,7 @@ def actionFile_Properties_trigger(self, event):
19051899
if result == QDialog.Accepted:
19061900

19071901
# BRUTE FORCE approach: go through all clips and update file path
1908-
clips = Clip.filter(file_id=file_id)
1902+
clips = Clip.filter(file_id=f.data["id"])
19091903
for c in clips:
19101904
# update clip
19111905
c.data["reader"]["path"] = f.data["path"]
@@ -2191,6 +2185,22 @@ def removeSelection(self, item_id, item_type):
21912185
# Change selected item in properties view
21922186
self.show_property_timer.start()
21932187

2188+
def selected_files(self):
2189+
""" Return a list of File objects for the Project Files dock's selection """
2190+
return self.files_model.selected_files()
2191+
2192+
def selected_file_ids(self):
2193+
""" Return a list of File IDs for the Project Files dock's selection """
2194+
return self.files_model.selected_file_ids()
2195+
2196+
def current_file(self):
2197+
""" Return the Project Files dock's currently-active item as a File object """
2198+
return self.files_model.current_file()
2199+
2200+
def current_file_id(self):
2201+
""" Return the ID of the Project Files dock's currently-active item """
2202+
return self.files_model.current_file_id()
2203+
21942204
# Update window settings in setting store
21952205
def save_settings(self):
21962206
s = settings.get_settings()
@@ -2399,7 +2409,6 @@ def setup_toolbars(self):
23992409

24002410
def clearSelections(self):
24012411
"""Clear all selection containers"""
2402-
self.selected_files = []
24032412
self.selected_clips = []
24042413
self.selected_transitions = []
24052414
self.selected_markers = []

src/windows/models/files_model.py

+34-5
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,14 @@ def filterAcceptsRow(self, sourceRow, sourceParent):
6060
or get_app().window.filesFilter.text():
6161
# Fetch the file name
6262
index = self.sourceModel().index(sourceRow, 0, sourceParent)
63-
file_name = self.sourceModel().data(index) # file name (i.e. MyVideo.mp4)
63+
file_name = self.sourceModel().data(index) # file name (i.e. MyVideo.mp4)
6464

6565
# Fetch the media_type
6666
index = self.sourceModel().index(sourceRow, 3, sourceParent)
67-
media_type = self.sourceModel().data(index) # media type (i.e. video, image, audio)
67+
media_type = self.sourceModel().data(index) # media type (i.e. video, image, audio)
6868

6969
index = self.sourceModel().index(sourceRow, 2, sourceParent)
70-
tags = self.sourceModel().data(index) # tags (i.e. intro, custom, etc...)
70+
tags = self.sourceModel().data(index) # tags (i.e. intro, custom, etc...)
7171

7272
if get_app().window.actionFilesShowVideo.isChecked():
7373
if not media_type == "video":
@@ -134,8 +134,7 @@ def update_model(self, clear=True, delete_file_id=None):
134134

135135
self.ignore_updates = True
136136

137-
# Get window to check filters
138-
win = app.window
137+
# Translations
139138
_ = app._tr
140139

141140
# Delete a file (if delete_file_id passed in)
@@ -299,6 +298,36 @@ def update_file_thumbnail(self, file_id):
299298

300299
self.ignore_updates = False
301300

301+
def selected_file_ids(self):
302+
""" Get a list of file IDs for all selected files """
303+
# Get the indexes for column 5 of all selected rows
304+
selected = self.selection_model.selectedRows(5)
305+
306+
return [idx.data() for idx in selected]
307+
308+
def selected_files(self):
309+
""" Get a list of File objects representing the current selection """
310+
files = []
311+
for id in self.selected_file_ids():
312+
files.append(File.get(id=id))
313+
return files
314+
315+
def current_file_id(self):
316+
""" Get the file ID of the current files-view item, or the first selection """
317+
cur = self.selection_model.currentIndex()
318+
319+
if not cur or not cur.isValid() and self.selection_model.hasSelection():
320+
cur = self.selection_model.selectedIndexes()[0]
321+
322+
if cur and cur.isValid():
323+
return cur.sibling(cur.row(), 5).data()
324+
325+
def current_file(self):
326+
""" Get the File object for the current files-view item, or the first selection """
327+
cur_id = self.current_file_id()
328+
if cur_id:
329+
return File.get(id=cur_id)
330+
302331
def __init__(self, *args):
303332

304333
# Add self as listener to project data updates

src/windows/views/files_listview.py

+25-29
Original file line numberDiff line numberDiff line change
@@ -47,38 +47,33 @@ class FilesListView(QListView):
4747
""" A ListView QWidget used on the main window """
4848
drag_item_size = 48
4949

50-
def updateSelection(self):
51-
# Track selected items
52-
self.selected = self.selectionModel().selectedIndexes()
53-
54-
# Track selected file ids on main window
55-
rows = []
56-
self.win.selected_files = []
57-
for selection in self.selected:
58-
selected_row = self.files_model.model.itemFromIndex(self.files_model.proxy_model.mapToSource(selection)).row()
59-
if selected_row not in rows:
60-
self.win.selected_files.append(self.files_model.model.item(selected_row, 5).text())
61-
rows.append(selected_row)
62-
6350
def contextMenuEvent(self, event):
64-
# Update selection
65-
self.updateSelection()
6651

6752
# Set context menu mode
6853
app = get_app()
6954
app.context_menu_object = "files"
7055

56+
index = self.indexAt(event.pos())
57+
58+
# Build menu
7159
menu = QMenu(self)
7260

7361
menu.addAction(self.win.actionImportFiles)
7462
menu.addAction(self.win.actionDetailsView)
75-
if self.selected:
76-
# If file selected, show file related options
63+
64+
if index.isValid():
65+
# Look up the model item and our unique ID
66+
model = self.model()
67+
68+
# Look up file_id from 5th column of row
69+
id_index = index.sibling(index.row(), 5)
70+
file_id = model.data(id_index, Qt.DisplayRole)
71+
72+
# If a valid file selected, show file related options
7773
menu.addSeparator()
7874

7975
# Add edit title option (if svg file)
80-
selected_file_id = self.win.selected_files[0]
81-
file = File.get(id=selected_file_id)
76+
file = File.get(id=file_id)
8277
if file and file.data.get("path").endswith(".svg"):
8378
menu.addAction(self.win.actionEditTitle)
8479
menu.addAction(self.win.actionDuplicateTitle)
@@ -101,16 +96,19 @@ def dragEnterEvent(self, event):
10196
event.setDropAction(Qt.CopyAction)
10297
event.accept()
10398

104-
def startDrag(self, event):
99+
def startDrag(self, supportedActions):
105100
""" Override startDrag method to display custom icon """
106101

102+
# The relevant model for index values
103+
model = self.model()
104+
107105
# Get image of selected item
108-
selected_row = self.files_model.model.itemFromIndex(self.files_model.proxy_model.mapToSource(self.selectionModel().selectedIndexes()[0])).row()
109-
icon = self.files_model.model.item(selected_row, 0).icon()
106+
selected_rows = self.selectionModel().selectedRows(0)
107+
icon = selected_rows[0].data(Qt.DecorationRole)
110108

111109
# Start drag operation
112110
drag = QDrag(self)
113-
drag.setMimeData(self.files_model.proxy_model.mimeData(self.selectionModel().selectedIndexes()))
111+
drag.setMimeData(model.mimeData(self.selectionModel().selectedIndexes()))
114112
drag.setPixmap(icon.pixmap(QSize(self.drag_item_size, self.drag_item_size)))
115113
drag.setHotSpot(QPoint(self.drag_item_size / 2, self.drag_item_size / 2))
116114
drag.exec_()
@@ -301,13 +299,12 @@ def filter_changed(self):
301299

302300
def refresh_view(self):
303301
"""Filter files with proxy class"""
302+
model = self.model()
304303
filter_text = self.win.filesFilter.text()
305-
self.files_model.proxy_model.setFilterRegExp(QRegExp(filter_text.replace(' ', '.*')))
306-
self.files_model.proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive)
307-
self.files_model.proxy_model.sort(Qt.AscendingOrder)
304+
model.setFilterRegExp(QRegExp(filter_text.replace(' ', '.*'), Qt.CaseInsensitive))
308305

309-
def currentChanged(self, selected, deselected):
310-
self.updateSelection()
306+
col = model.sortColumn()
307+
model.sort(col)
311308

312309
def resize_contents(self):
313310
pass
@@ -333,7 +330,6 @@ def __init__(self, model, *args):
333330
self.setAcceptDrops(True)
334331
self.setDragEnabled(True)
335332
self.setDropIndicatorShown(True)
336-
self.selected = []
337333
self.ignore_image_sequence_paths = []
338334

339335
# Setup header columns and layout

0 commit comments

Comments
 (0)