Skip to content

Commit cfc1750

Browse files
committed
Convert files to use new proxy class, and a global shared model. No longer delete the tree/list view widgets either. They both always exist, and share the same data, and toggle visibility back and forth. Added wait cursor when adding/importing files.
1 parent 1a6b8b7 commit cfc1750

7 files changed

+188
-110
lines changed

src/windows/main_window.py

+41-45
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
from classes.conversion import zoomToSeconds, secondsToZoom
5454
from classes.thumbnail import httpThumbnailServerThread
5555
from images import openshot_rc
56+
from windows.models.files_model import FilesModel
5657
from windows.views.files_treeview import FilesTreeView
5758
from windows.views.files_listview import FilesListView
5859
from windows.models.transition_model import TransitionsModel
@@ -81,6 +82,7 @@ class MainWindow(QMainWindow, updates.UpdateWatcher):
8182

8283
previewFrameSignal = pyqtSignal(int)
8384
refreshFrameSignal = pyqtSignal()
85+
refreshFilesSignal = pyqtSignal()
8486
LoadFileSignal = pyqtSignal(str)
8587
PlaySignal = pyqtSignal(int)
8688
PauseSignal = pyqtSignal()
@@ -350,7 +352,8 @@ def actionNew_trigger(self, event):
350352
# Reset selections
351353
self.clearSelections()
352354

353-
self.filesTreeView.refresh_view()
355+
# Refresh files views
356+
self.refreshFilesSignal.emit()
354357
log.info("New Project created.")
355358

356359
# Set Window title
@@ -412,8 +415,8 @@ def actionEditTitle_trigger(self, event):
412415
# Run the dialog event loop - blocking interaction on this window during that time
413416
result = win.exec_()
414417

415-
# Force update of files model (which will rebuild missing thumbnails)
416-
get_app().window.filesTreeView.refresh_view()
418+
# Refresh files views
419+
self.refreshFilesSignal.emit()
417420

418421
# Force update of clips
419422
clips = Clip.filter(file_id=selected_file_id)
@@ -530,8 +533,8 @@ def open_project(self, file_path, clear_thumbnails=True):
530533
# Reset selections
531534
self.clearSelections()
532535

533-
# Refresh file tree
534-
QTimer.singleShot(0, self.filesTreeView.refresh_view)
536+
# Refresh files views
537+
self.refreshFilesSignal.emit()
535538

536539
# Load recent projects again
537540
self.load_recent_menu()
@@ -701,12 +704,21 @@ def actionImportFiles_trigger(self, event):
701704
if not recommended_path or not os.path.exists(recommended_path):
702705
recommended_path = os.path.join(info.HOME_PATH)
703706
files = QFileDialog.getOpenFileNames(self, _("Import File..."), recommended_path)[0]
707+
708+
# Set cursor to waiting
709+
get_app().setOverrideCursor(QCursor(Qt.WaitCursor))
710+
704711
for file_path in files:
705712
self.filesTreeView.add_file(file_path)
706-
self.filesTreeView.refresh_view()
707713
app.updates.update_untracked(["import_path"], os.path.dirname(file_path))
708714
log.info("Imported media file {}".format(file_path))
709715

716+
# Restore cursor
717+
get_app().restoreOverrideCursor()
718+
719+
# Refresh files views
720+
self.refreshFilesSignal.emit()
721+
710722
def actionAdd_to_Timeline_trigger(self, event):
711723
# Loop through selected files
712724
f = None
@@ -730,17 +742,6 @@ def actionAdd_to_Timeline_trigger(self, event):
730742
else:
731743
log.info('canceled')
732744

733-
def actionUploadVideo_trigger(self, event):
734-
# show window
735-
from windows.upload_video import UploadVideo
736-
win = UploadVideo()
737-
# Run the dialog event loop - blocking interaction on this window during this time
738-
result = win.exec_()
739-
if result == QDialog.Accepted:
740-
log.info('Upload Video add confirmed')
741-
else:
742-
log.info('Upload Video add cancelled')
743-
744745
def actionExportVideo_trigger(self, event):
745746
# show window
746747
from windows.export import Export
@@ -809,16 +810,16 @@ def actionPreferences_trigger(self, event):
809810
get_app().restoreOverrideCursor()
810811

811812
def actionFilesShowAll_trigger(self, event):
812-
self.filesTreeView.refresh_view()
813+
self.refreshFilesSignal.emit()
813814

814815
def actionFilesShowVideo_trigger(self, event):
815-
self.filesTreeView.refresh_view()
816+
self.refreshFilesSignal.emit()
816817

817818
def actionFilesShowAudio_trigger(self, event):
818-
self.filesTreeView.refresh_view()
819+
self.refreshFilesSignal.emit()
819820

820821
def actionFilesShowImage_trigger(self, event):
821-
self.filesTreeView.refresh_view()
822+
self.refreshFilesSignal.emit()
822823

823824
def actionTransitionsShowAll_trigger(self, event):
824825
self.transitionsTreeView.refresh_view()
@@ -1823,18 +1824,12 @@ def actionDetailsView_trigger(self, event):
18231824
app = get_app()
18241825
s = settings.get_settings()
18251826

1826-
# Prepare treeview for deletion
1827-
if self.filesTreeView:
1828-
self.filesTreeView.prepare_for_delete()
1829-
18301827
# Files
18311828
if app.context_menu_object == "files":
18321829
s.set("file_view", "details")
1833-
self.tabFiles.layout().removeWidget(self.filesTreeView)
1834-
self.filesTreeView.deleteLater()
1835-
self.filesTreeView = None
1836-
self.filesTreeView = FilesTreeView(self)
1837-
self.tabFiles.layout().addWidget(self.filesTreeView)
1830+
self.filesListView.hide()
1831+
self.filesTreeView.show()
1832+
self.filesTreeView.clearSelection()
18381833

18391834
# Transitions
18401835
elif app.context_menu_object == "transitions":
@@ -1861,18 +1856,12 @@ def actionThumbnailView_trigger(self, event):
18611856
app = get_app()
18621857
s = settings.get_settings()
18631858

1864-
# Prepare treeview for deletion
1865-
if self.filesTreeView:
1866-
self.filesTreeView.prepare_for_delete()
1867-
18681859
# Files
18691860
if app.context_menu_object == "files":
18701861
s.set("file_view", "thumbnail")
1871-
self.tabFiles.layout().removeWidget(self.filesTreeView)
1872-
self.filesTreeView.deleteLater()
1873-
self.filesTreeView = None
1874-
self.filesTreeView = FilesListView(self)
1875-
self.tabFiles.layout().addWidget(self.filesTreeView)
1862+
self.filesListView.show()
1863+
self.filesListView.clearSelection()
1864+
self.filesTreeView.hide()
18761865

18771866
# Transitions
18781867
elif app.context_menu_object == "transitions":
@@ -2205,7 +2194,7 @@ def setup_toolbars(self):
22052194
self.filesFilter.setPlaceholderText(_("Filter"))
22062195
self.filesFilter.setClearButtonEnabled(True)
22072196
self.filesToolbar.addWidget(self.filesFilter)
2208-
self.tabFiles.layout().addWidget(self.filesToolbar)
2197+
self.tabFiles.layout().insertWidget(0, self.filesToolbar)
22092198

22102199
# Add transitions toolbar
22112200
self.transitionsToolbar = QToolBar("Transitions Toolbar")
@@ -2544,13 +2533,20 @@ def __init__(self, mode=None):
25442533
self.setCorner(Qt.TopRightCorner, Qt.RightDockWidgetArea)
25452534
self.setCorner(Qt.BottomRightCorner, Qt.RightDockWidgetArea)
25462535

2547-
# Setup files tree
2536+
# Setup files tree and list view (both share a model)
2537+
self.files_model = FilesModel()
2538+
self.filesTreeView = FilesTreeView(self.files_model)
2539+
self.filesListView = FilesListView(self.files_model)
2540+
self.tabFiles.layout().insertWidget(-1, self.filesTreeView)
2541+
self.tabFiles.layout().insertWidget(-1, self.filesListView)
25482542
if s.get("file_view") == "details":
2549-
self.filesTreeView = FilesTreeView(self)
2543+
self.filesTreeView.show()
2544+
self.filesListView.hide()
2545+
self.filesTreeView.setFocus()
25502546
else:
2551-
self.filesTreeView = FilesListView(self)
2552-
self.tabFiles.layout().addWidget(self.filesTreeView)
2553-
self.filesTreeView.setFocus()
2547+
self.filesTreeView.hide()
2548+
self.filesListView.show()
2549+
self.filesListView.setFocus()
25542550

25552551
# Setup transitions tree
25562552
self.transition_model = TransitionsModel()

src/windows/models/files_model.py

+62-25
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
import os
3030

31-
from PyQt5.QtCore import QMimeData, Qt, pyqtSignal
31+
from PyQt5.QtCore import QMimeData, Qt, pyqtSignal, QSortFilterProxyModel, QEventLoop
3232
from PyQt5.QtGui import *
3333
from PyQt5.QtWidgets import QMessageBox
3434
import openshot # Python module for libopenshot (required video editing module installed separately)
@@ -64,28 +64,82 @@ def mimeData(self, indexes):
6464
return data
6565

6666

67+
class FileFilterProxyModel(QSortFilterProxyModel):
68+
"""Proxy class used for sorting and filtering model data"""
69+
70+
def filterAcceptsRow(self, sourceRow, sourceParent):
71+
"""Filter for text"""
72+
73+
if get_app().window.actionFilesShowVideo.isChecked() \
74+
or get_app().window.actionFilesShowAudio.isChecked() \
75+
or get_app().window.actionFilesShowImage.isChecked() \
76+
or get_app().window.filesFilter.text():
77+
# Fetch the file name
78+
index = self.sourceModel().index(sourceRow, 0, sourceParent)
79+
file_name = self.sourceModel().data(index) # file name (i.e. MyVideo.mp4)
80+
81+
# Fetch the media_type
82+
index = self.sourceModel().index(sourceRow, 3, sourceParent)
83+
media_type = self.sourceModel().data(index) # media type (i.e. video, image, audio)
84+
85+
index = self.sourceModel().index(sourceRow, 2, sourceParent)
86+
tags = self.sourceModel().data(index) # tags (i.e. intro, custom, etc...)
87+
88+
if get_app().window.actionFilesShowVideo.isChecked():
89+
if not media_type == "video":
90+
return False
91+
elif get_app().window.actionFilesShowAudio.isChecked():
92+
if not media_type == "audio":
93+
return False
94+
elif get_app().window.actionFilesShowImage.isChecked():
95+
if not media_type == "image":
96+
return False
97+
98+
# Match against regex pattern
99+
return self.filterRegExp().indexIn(file_name) >= 0 or self.filterRegExp().indexIn(tags) >= 0
100+
101+
# Continue running built-in parent filter logic
102+
return super(FileFilterProxyModel, self).filterAcceptsRow(sourceRow, sourceParent)
103+
104+
67105
class FilesModel(updates.UpdateInterface):
68106
# This method is invoked by the UpdateManager each time a change happens (i.e UpdateInterface)
69107
def changed(self, action):
70108

71109
# Something was changed in the 'files' list
72-
if len(action.key) >= 1 and action.key[0].lower() == "files":
110+
if (len(action.key) >= 1 and action.key[0].lower() == "files") or action.type == "load":
73111
# Refresh project files model
74112
if action.type == "insert":
75113
# Don't clear the existing items if only inserting new things
76114
self.update_model(clear=False)
115+
elif action.type == "delete" and action.key[0].lower() == "files":
116+
# Don't clear the existing items if only deleting things
117+
self.update_model(clear=False, delete_file_id=action.key[1].get('id', ''))
77118
else:
78119
# Clear existing items
79120
self.update_model(clear=True)
80121

81-
def update_model(self, clear=True):
122+
def update_model(self, clear=True, delete_file_id=None):
82123
log.info("updating files model.")
83124
app = get_app()
84125

85126
# Get window to check filters
86127
win = app.window
87128
_ = app._tr
88129

130+
# Delete a file (if delete_file_id passed in)
131+
if delete_file_id in self.model_ids:
132+
for row_num in range(self.model.rowCount()):
133+
id_index = self.model.index(row_num, 5)
134+
if delete_file_id == self.model.data(id_index):
135+
# Delete row from model
136+
self.model.beginRemoveRows(id_index.parent(), row_num, row_num)
137+
self.model.removeRow(row_num, id_index.parent())
138+
self.model.endRemoveRows()
139+
self.model.submit()
140+
self.model_ids.pop(delete_file_id)
141+
break
142+
89143
# Skip updates (if needed)
90144
if self.ignore_update_signal:
91145
return
@@ -103,6 +157,10 @@ def update_model(self, clear=True):
103157

104158
# add item for each file
105159
for file in files:
160+
if file.data["id"] in self.model_ids:
161+
# Ignore files that already exist in model
162+
continue
163+
106164
path, filename = os.path.split(file.data["path"])
107165
tags = ""
108166
if "tags" in file.data.keys():
@@ -111,24 +169,6 @@ def update_model(self, clear=True):
111169
if "name" in file.data.keys():
112170
name = file.data["name"]
113171

114-
if not win.actionFilesShowAll.isChecked():
115-
if win.actionFilesShowVideo.isChecked():
116-
if not file.data["media_type"] == "video":
117-
continue # to next file, didn't match filter
118-
elif win.actionFilesShowAudio.isChecked():
119-
if not file.data["media_type"] == "audio":
120-
continue # to next file, didn't match filter
121-
elif win.actionFilesShowImage.isChecked():
122-
if not file.data["media_type"] == "image":
123-
continue # to next file, didn't match filter
124-
125-
126-
if win.filesFilter.text() != "":
127-
if not win.filesFilter.text().lower() in filename.lower() \
128-
and not win.filesFilter.text().lower() in tags.lower() \
129-
and not win.filesFilter.text().lower() in name.lower():
130-
continue
131-
132172
# Generate thumbnail for file (if needed)
133173
if (file.data["media_type"] == "video" or file.data["media_type"] == "image"):
134174
# Check for start and end attributes (optional)
@@ -201,10 +241,7 @@ def update_model(self, clear=True):
201241
if not file.data["id"] in self.model_ids:
202242
self.model.appendRow(row)
203243
self.model_ids[file.data["id"]] = file.data["id"]
204-
205-
# Process events in QT (to keep the interface responsive)
206-
# TODO: FIX THIS
207-
#app.processEvents()
244+
get_app().processEvents(QEventLoop.ExcludeUserInputEvents)
208245

209246
# Refresh view and filters (to hide or show this new item)
210247
get_app().window.resize_contents()

src/windows/views/effects_listview.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def __init__(self, model):
104104
self.setTextElideMode(Qt.ElideRight)
105105
self.setStyleSheet('QListView::item { padding-top: 2px; }')
106106

107-
# Load initial emoji model data
107+
# Load initial effects model data
108108
self.effects_model.update_model()
109109

110110
# setup filter events

src/windows/views/effects_treeview.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def __init__(self, model):
103103
self.setWordWrap(True)
104104
self.setStyleSheet('QTreeView::item { padding-top: 2px; }')
105105

106-
# Load initial emoji model data
106+
# Load initial effects model data
107107
self.effects_model.update_model()
108108
self.hideColumn(3)
109109
self.hideColumn(4)

0 commit comments

Comments
 (0)