提交 ce2c009a 编写于 作者: J Jonathan Thomas

Refactored file tree code to use the model/view appraoch, so multiple widgets...

Refactored file tree code to use the model/view appraoch, so multiple widgets can be bound to a single model (i.e. iconview and details view)
上级 129a33b3
......@@ -37,7 +37,8 @@ from classes import info, ui_util, settings, qt_types, updates
from classes.app import get_app
from classes.logger import log
from images import openshot_rc
from windows.media_treeview import MediaTreeView
from windows.views.files_treeview import FilesTreeView
from windows.views.files_listview import FilesListView
import xml.etree.ElementTree as ElementTree
class MainWindow(QMainWindow, updates.UpdateWatcher, updates.UpdateInterface):
......@@ -125,7 +126,7 @@ class MainWindow(QMainWindow, updates.UpdateWatcher, updates.UpdateInterface):
if file_path:
app.project.load(file_path)
app.updates.reset()
self.filesTreeView.update_model()
self.filesTreeView.filter_changed()
log.info("Loaded project {}".format(file_path))
def actionSave_trigger(self, event):
......@@ -162,7 +163,7 @@ class MainWindow(QMainWindow, updates.UpdateWatcher, updates.UpdateInterface):
files = QFileDialog.getOpenFileNames(self, _("Import File..."))[0]
for file_path in files:
self.filesTreeView.add_file(file_path)
self.filesTreeView.update_model()
self.filesTreeView.filter_changed()
log.info("Loaded project {}".format(file_path))
......@@ -177,13 +178,13 @@ class MainWindow(QMainWindow, updates.UpdateWatcher, updates.UpdateInterface):
#log.info(app.project._data)
def actionFilesShowAll_trigger(self, event):
self.filesTreeView.update_model()
self.filesTreeView.filter_changed()
def actionFilesShowVideo_trigger(self, event):
self.filesTreeView.update_model()
self.filesTreeView.filter_changed()
def actionFilesShowAudio_trigger(self, event):
self.filesTreeView.update_model()
self.filesTreeView.filter_changed()
def actionFilesShowImage_trigger(self, event):
self.filesTreeView.update_model()
self.filesTreeView.filter_changed()
def actionAbout_trigger(self, event):
#Show dialog
......@@ -196,31 +197,6 @@ class MainWindow(QMainWindow, updates.UpdateWatcher, updates.UpdateInterface):
else:
log.info('About Openshot add cancelled')
#Project settings test code
# app = get_app()
# curr_val = app.project.get("settings/nfigg-setting")
# if curr_val == None:
# log.info ("Addvalue")
# app.updates.insert("settings/nfigg-setting", {"a": 1, "b": 5})
# else:
# log.info ("Increment value")
# update = dict()
# update["a"] = curr_val["a"] + 1
# app.updates.update("settings/nfigg-setting", update, partial_update=True)
#log.info(app.project._data)
#Other project settings test code
# app = get_app()
# curr_val = app.project.get("settings/nfigg-setting")
# if not curr_val == None and curr_val["a"] > 1:
# log.info ("Decrement value")
# update = dict()
# update["a"] = curr_val["a"] - 1
# app.updates.update("settings/nfigg-setting", update, partial_update=True)
# elif not curr_val == None:
# log.info ("Remove value")
# app.updates.delete("settings/nfigg-setting")
#log.info(app.project._data)
def actionPlay_trigger(self, event):
if self.actionPlay.isChecked():
ui_util.setup_icon(self, self.actionPlay, "actionPlay", "media-playback-pause")
......@@ -417,10 +393,23 @@ class MainWindow(QMainWindow, updates.UpdateWatcher, updates.UpdateInterface):
#Add timeline toolbar to web frame
self.frameWeb.layout().addWidget(self.timelineToolbar)
def actionDetailsView_trigger(self, event):
log.info("Switch to Details View")
self.tabFiles.layout().removeWidget(self.filesTreeView)
self.filesTreeView = FilesTreeView(self)
self.tabFiles.layout().addWidget(self.filesTreeView) #gridLayout_2 , 1, 0
def actionThumbnailView_trigger(self, event):
log.info("Switch to Thumbnail View")
self.tabFiles.layout().removeWidget(self.filesTreeView)
self.filesTreeView = FilesListView(self)
self.tabFiles.layout().addWidget(self.filesTreeView) #gridLayout_2 , 1, 0
def __init__(self):
#Create main window base class
QMainWindow.__init__(self)
#set window on app for reference during initialization of children
get_app().window = self
......@@ -442,29 +431,19 @@ class MainWindow(QMainWindow, updates.UpdateWatcher, updates.UpdateInterface):
# Init fullscreen menu visibility
self.init_fullscreen_menu()
#sys.path.insert(0,"/usr/local/share/pyshared/libopenshot")
#import openshot
#import sip
# Draw test image to QGraphicsView
#scene = QGraphicsScene()
#self.videoDisplay.setScene(scene)
#reader = openshot.DummyReader(openshot.Fraction(24,1), 640, 480, 48000, 2, 10.0)
#reader.DrawFrameOnScene("/home/jonathan/Pictures/tux.jpg", int(sip.unwrapinstance(scene)))
#setup timeline
self.timeline = TimelineWebView(self)
self.frameWeb.layout().addWidget(self.timeline)
#setup files tree
self.filesTreeView = MediaTreeView(self)
self.filesTreeView = FilesListView(self)
self.tabFiles.layout().addWidget(self.filesTreeView) #gridLayout_2 , 1, 0
#setup transitions tree
self.transitionsTreeView = MediaTreeView(self)
self.tabTransitions.layout().addWidget(self.transitionsTreeView) #gridLayout_2 , 1, 0
#self.transitionsTreeView = MediaTreeView(self)
#self.tabTransitions.layout().addWidget(self.transitionsTreeView) #gridLayout_2 , 1, 0
#setup effects tree
self.effectsTreeView = MediaTreeView(self)
self.tabEffects.layout().addWidget(self.effectsTreeView) #gridLayout_2 , 1, 0
#self.effectsTreeView = MediaTreeView(self)
#self.tabEffects.layout().addWidget(self.effectsTreeView) #gridLayout_2 , 1, 0
"""
@file
@brief This file contains the project file tree, used by the main window
@author Noah Figg <eggmunkee@hotmail.com>
@author Jonathan Thomas <jonathan@openshot.org>
@section LICENSE
Copyright (c) 2008-2014 OpenShot Studios, LLC
(http://www.openshotstudios.com). This file is part of
OpenShot Video Editor (http://www.openshot.org), an open-source project
dedicated to delivering high quality video editing and animation solutions
to the world.
OpenShot Video Editor is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenShot Video Editor is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
"""
import os
from urllib.parse import urlparse
from classes import updates
from classes import info
from classes.logger import log
from classes.settings import SettingStore
from classes.app import get_app
from PyQt5.QtCore import QMimeData, QSize, Qt, QCoreApplication, QPoint, QFileInfo
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QTreeWidget, QApplication, QMessageBox, QTreeWidgetItem, QAbstractItemView
import openshot # Python module for libopenshot (required video editing module installed separately)
class FilesModel(updates.UpdateInterface):
# This method is invoked by the UpdateManager each time a change happens (i.e UpdateInterface)
def changed(self, action):
# Something was changed in the 'files' list
if len(action.key) >= 1 and action.key[0].lower() == "files":
# Refresh project files model
self.update_model()
def update_model(self):
log.info("updating files model.")
app = get_app()
proj = app.project
files = proj.get(["files"])
# Get window to check filters
win = app.window
# Clear all items
self.model.clear()
# Add Headers
self.model.setHorizontalHeaderLabels(["Thumb", "Name", "Type" ])
# add item for each file
for file in files:
path, filename = os.path.split(file["path"])
if not win.actionFilesShowAll.isChecked():
if win.actionFilesShowVideo.isChecked():
if not file["media_type"] == "video":
continue #to next file, didn't match filter
elif win.actionFilesShowAudio.isChecked():
if not file["media_type"] == "audio":
continue #to next file, didn't match filter
elif win.actionFilesShowImage.isChecked():
if not file["media_type"] == "image":
continue #to next file, didn't match filter
if win.filesFilter.text() != "":
if not win.filesFilter.text() in filename:
continue
# Generate thumbnail for file (if needed)
if (file["media_type"] == "video" or file["media_type"] == "image"):
# Determine thumb path
thumb_path = os.path.join(proj.current_filepath, "%s.png" % file["id"])
# Check if thumb exists
if not os.path.exists(thumb_path):
try:
# Reload this reader
clip = openshot.Clip(file["path"])
reader = clip.Reader()
# Open reader
reader.Open()
# Determine scale of thumbnail
scale = 95.0 / file["width"]
# Save thumbnail
reader.GetFrame(0).Save(thumb_path, scale)
reader.Close()
except:
# Handle exception
msg = QMessageBox()
msg.setText(app._tr("%s is not a valid video, audio, or image file." % filename))
msg.exec_()
return False
else:
# Audio file
thumb_path = os.path.join(info.PATH, "images", "AudioThumbnail.png")
row = []
# Append thumbnail
col = QStandardItem()
col.setIcon(QIcon(thumb_path))
#col.setData("Thumb", Qt.DisplayRole)
col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
row.append(col)
# Append Filename
col = QStandardItem("Name")
col.setData(filename, Qt.DisplayRole)
col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
row.append(col)
# Append Media Type
col = QStandardItem("Type")
col.setData(file["media_type"], Qt.DisplayRole)
col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
row.append(col)
# Append Path
col = QStandardItem("Path")
col.setData(path, Qt.DisplayRole)
col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
row.append(col)
# Append ID
col = QStandardItem("ID")
col.setData(file["id"], Qt.DisplayRole)
col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
row.append(col)
# Append ROW to MODEL
self.model.appendRow(row)
# Process events in QT (to keep the interface responsive)
app.processEvents()
def __init__(self, *args):
# Add self as listener to project data updates (undo/redo, as well as normal actions handled within this class all update the tree model)
app = get_app()
app.updates.add_listener(self)
# Create standard model
self.model = QStandardItemModel()
self.model.setColumnCount(5)
# Update model based on loaded project
self.update_model()
\ No newline at end of file
......@@ -43,16 +43,7 @@
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<property name="margin">
<number>5</number>
</property>
<property name="spacing">
......@@ -88,16 +79,7 @@
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<property name="margin">
<number>5</number>
</property>
</layout>
......@@ -110,16 +92,7 @@
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<property name="margin">
<number>5</number>
</property>
</layout>
......@@ -132,16 +105,7 @@
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<property name="margin">
<number>5</number>
</property>
</layout>
......@@ -162,16 +126,7 @@
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<property name="margin">
<number>5</number>
</property>
<item>
......@@ -190,16 +145,7 @@
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<property name="margin">
<number>5</number>
</property>
</layout>
......@@ -962,6 +908,30 @@
<string>Ctrl+A</string>
</property>
</action>
<action name="actionThumbnailView">
<property name="icon">
<iconset resource="../../images/openshot.qrc">
<normaloff>:/icons/Compass/actions/misc/view-preview.svg</normaloff>:/icons/Compass/actions/misc/view-preview.svg</iconset>
</property>
<property name="text">
<string>Thumbnail View</string>
</property>
<property name="toolTip">
<string>Thumbnail View</string>
</property>
</action>
<action name="actionDetailsView">
<property name="icon">
<iconset resource="../../images/openshot.qrc">
<normaloff>:/icons/Compass/actions/misc/view-list-details.svg</normaloff>:/icons/Compass/actions/misc/view-list-details.svg</iconset>
</property>
<property name="text">
<string>Details View</string>
</property>
<property name="toolTip">
<string>Details View</string>
</property>
</action>
</widget>
<resources>
<include location="../../images/openshot.qrc"/>
......
"""
@file
@brief This file contains the project file listview, used by the main window
@author Noah Figg <eggmunkee@hotmail.com>
@author Jonathan Thomas <jonathan@openshot.org>
@section LICENSE
Copyright (c) 2008-2014 OpenShot Studios, LLC
(http://www.openshotstudios.com). This file is part of
OpenShot Video Editor (http://www.openshot.org), an open-source project
dedicated to delivering high quality video editing and animation solutions
to the world.
OpenShot Video Editor is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenShot Video Editor is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
"""
import os
from urllib.parse import urlparse
from classes import updates
from classes import info
from classes.logger import log
from classes.settings import SettingStore
from classes.app import get_app
from PyQt5.QtCore import QMimeData, QSize, Qt, QCoreApplication, QPoint, QFileInfo
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QListView, QApplication, QMessageBox, QAbstractItemView, QMenu
from windows.models.files_model import FilesModel
import openshot # Python module for libopenshot (required video editing module installed separately)
try:
import json
except ImportError:
import simplejson as json
class FilesListView(QListView):
""" A ListView QWidget used on the main window """
drag_item_size = 32
def currentChanged(self, selected, deselected):
# get selected item
self.selected = selected
self.deselected = deselected
def contextMenuEvent(self, event):
menu = QMenu(self)
menu.addAction(self.win.actionDetailsView)
menu.addAction(self.win.actionThumbnailView)
menu.exec_(QCursor.pos())
def mouseMoveEvent(self, event):
#If mouse drag detected, set the proper data and icon and start dragging
if event.buttons() & Qt.LeftButton == Qt.LeftButton and (event.pos() - self.startDragPos).manhattanLength() >= QApplication.startDragDistance():
# Get selected item
dragItemRow = self.files_model.model.itemFromIndex(self.selected).row()
# Get all selected rows items
dragItem = []
for col in range(5):
dragItem.append(self.files_model.model.item(dragItemRow, col))
# Setup data based on item being dragged
data = QMimeData()
data.setText(dragItem[4].text()) # Add file ID to mimedata
# Start drag operation
drag = QDrag(self)
drag.setMimeData(data)
drag.setPixmap(QIcon.fromTheme('document-new').pixmap(QSize(self.drag_item_size,self.drag_item_size)))
drag.setHotSpot(QPoint(self.drag_item_size/2,self.drag_item_size/2))
drag.exec_()
# Accept event
event.accept()
return
# Ignore event, propagate to parent
event.ignore()
super().mouseMoveEvent(event)
def dragEnterEvent(self, event):
# If dragging urls onto widget, accept
if event.mimeData().hasUrls():
event.setDropAction(Qt.CopyAction)
event.accept()
# Without defining this method, the 'copy' action doesn't show with cursor
def dragMoveEvent(self, event):
pass
def mousePressEvent(self, event):
# Save position of mouse press to check for drag
self.startDragPos = event.pos()
# Ignore event, propagate to parent
event.ignore()
super().mousePressEvent(event)
def is_image(self, file):
path = file["path"].lower()
if path.endswith((".jpg", ".jpeg", ".png", ".bmp", ".svg")):
return True
else:
return False
def add_file(self, filepath):
path, filename = os.path.split(filepath)
# Add file into project
app = get_app()
proj = app.project
files = proj.get(["files"]) #get files list
found = False
new_id = proj.generate_id()
# Check for new_id and filepath in current file list to prevent duplicates
index = 0
sanity_check, sanity_limit = 0, 10
while index < len(files):
if sanity_check > sanity_limit: #Stop checking if we can't get a unique id in 10 tries
break;
f = files[index]
# Stop checking if we find this filepath already in the list, don't allow duplicate.
if f["path"] == filepath:
found = True
break
# Generate new id and start loop over again if duplicate id is found
if f["id"] == new_id: #If this ID is found, generate a new one
new_id = proj.generate_id()
index = 0
sanity_check += 1
continue
# Move to next item
index += 1
if sanity_check > sanity_limit:
msg = QMessageBox()
msg.setText(app._tr("Error generating unique file ID for %s." % filename))
msg.exec_()
return False
if found:
msg = QMessageBox()
msg.setText(app._tr("%s already added to project." % filename))
msg.exec_()
return False
# Inspect the file with libopenshot
clip = None
# Load filepath in libopenshot clip object (which will try multiple readers to open it)
clip = openshot.Clip(filepath)
# Get the JSON for the clip's internal reader
try:
reader = clip.Reader()
file_json = json.loads(reader.Json())
# Add unique ID to the JSON
file_json["id"] = new_id
# Determine media type
if file_json["has_video"] and not self.is_image(file_json):
file_json["media_type"] = "video"
elif file_json["has_video"] and self.is_image(file_json):
file_json["media_type"] = "image"
elif file_json["has_audio"] and not file_json["has_video"]:
file_json["media_type"] = "audio"
# Add file to files list via update manager
app.updates.insert(["files"], file_json)
return True
except:
# Handle exception
msg = QMessageBox()
msg.setText(app._tr("%s is not a valid video, audio, or image file." % filename))
msg.exec_()
return False
# Handle a drag and drop being dropped on widget
def dropEvent(self, event):
#log.info('Dropping file(s) on files tree.')
for uri in event.mimeData().urls():
file_url = urlparse(uri.toString())
if file_url.scheme == "file":
filepath = file_url.path
if filepath[0] == "/" and ":" in filepath:
filepath = filepath[1:]
if os.path.exists(filepath) and os.path.isfile(filepath):
log.info('Adding file: {}'.format(filepath))
if self.add_file(filepath):
event.accept()
def clear_filter(self):
get_app().window.filesFilter.setText("")
def filter_changed(self):
self.files_model.update_model()
if self.win.filesFilter.text() == "":
self.win.actionFilesClear.setEnabled(False)
else:
self.win.actionFilesClear.setEnabled(True)
def __init__(self, *args):
# Invoke parent init
QListView.__init__(self, *args)
# Get a reference to the window object
self.win = get_app().window
# Get Model data
self.files_model = FilesModel()
# Keep track of mouse press start position to determine when to start drag
self.startDragPos = None
self.setAcceptDrops(True)
self.selected = None
self.deselected = None
# Setup header columns
self.setModel(self.files_model.model)
self.setIconSize(QSize(131, 108))
self.setViewMode(QListView.IconMode)
self.setResizeMode(QListView.Adjust)
# setup filter events
app = get_app()
app.window.filesFilter.textChanged.connect(self.filter_changed)
app.window.actionFilesClear.triggered.connect(self.clear_filter)
"""
@file
@brief This file contains the project file tree, used by the main window
@author Noah Figg <eggmunkee@hotmail.com>
@author Jonathan Thomas <jonathan@openshot.org>
@author Olivier Girard <eolinwen@gmail.com>
@section LICENSE
Copyright (c) 2008-2014 OpenShot Studios, LLC
(http://www.openshotstudios.com). This file is part of
OpenShot Video Editor (http://www.openshot.org), an open-source project
dedicated to delivering high quality video editing and animation solutions
to the world.
OpenShot Video Editor is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenShot Video Editor is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
"""
import os
from urllib.parse import urlparse
from classes import updates
from classes import info
from classes.logger import log
from classes.settings import SettingStore
from classes.app import get_app
from PyQt5.QtCore import QMimeData, QSize, Qt, QCoreApplication, QPoint, QFileInfo
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QTreeWidget, QApplication, QMessageBox, QTreeWidgetItem, QAbstractItemView
import openshot # Python module for libopenshot (required video editing module installed separately)
try:
import json
except ImportError:
import simplejson as json
class MediaTreeView(QTreeWidget, updates.UpdateInterface):
""" A TreeView QWidget used on the main window """
drag_item_size = 32
# This method is invoked by the UpdateManager each time a change happens (i.e UpdateInterface)
def changed(self, action):
# Something was changed in the 'files' list
if len(action.key) >= 1 and action.key[0].lower() == "files":
# Refresh project files model
self.update_model()
def mouseMoveEvent(self, event):
#If mouse drag detected, set the proper data and icon and start dragging
if event.buttons() & Qt.LeftButton == Qt.LeftButton and (event.pos() - self.startDragPos).manhattanLength() >= QApplication.startDragDistance():
#Setup data based on item being dragged
data = QMimeData()
data.setText(self.dragItem.text(4)) # Add file ID to mimedata
#Start drag operation
drag = QDrag(self)
drag.setMimeData(data)
drag.setPixmap(QIcon.fromTheme('document-new').pixmap(QSize(self.drag_item_size,self.drag_item_size)))
drag.setHotSpot(QPoint(self.drag_item_size/2,self.drag_item_size/2))
drag.exec_()
# Accept event
event.accept()
return
# Ignore event, propagate to parent
event.ignore()
super().mouseMoveEvent(event)
def dragEnterEvent(self, event):
#If dragging urls onto widget, accept
if event.mimeData().hasUrls():
event.setDropAction(Qt.CopyAction)
event.accept()
#Without defining this method, the 'copy' action doesn't show with cursor
def dragMoveEvent(self, event):
pass
def mousePressEvent(self, event):
# Select the current item
self.setCurrentItem(self.itemAt(event.pos()))
#Save position of mouse press to check for drag
self.startDragPos = event.pos()
#Save item clicked on to use if drag starts
self.dragItem = self.itemAt(event.pos())
def is_image(self, file):
path = file["path"].lower()
if path.endswith((".jpg", ".jpeg", ".png", ".bmp", ".svg")):
return True
else:
return False
def update_model(self):
log.info("updating files model.")
app = get_app()
proj = app.project
files = proj.get(["files"])
#Get window to check filters
win = app.window
self.clear() #Clear items in tree
#add item for each file
for file in files:
path, filename = os.path.split(file["path"])
if not win.actionFilesShowAll.isChecked():
if win.actionFilesShowVideo.isChecked():
if not file["media_type"] == "video":
continue #to next file, didn't match filter
elif win.actionFilesShowAudio.isChecked():
if not file["media_type"] == "audio":
continue #to next file, didn't match filter
elif win.actionFilesShowImage.isChecked():
if not file["media_type"] == "image":
continue #to next file, didn't match filter
if win.filesFilter.text() != "":
if not win.filesFilter.text() in filename:
continue
# Generate thumbnail for file (if needed)
if (file["media_type"] == "video" or file["media_type"] == "image"):
# Determine thumb path
thumb_path = os.path.join(proj.current_filepath, "%s.png" % file["id"])
# Check if thumb exists
if not os.path.exists(thumb_path):
try:
# Reload this reader
clip = openshot.Clip(file["path"])
reader = clip.Reader()
# Open reader
reader.Open()
# Determine scale of thumbnail
scale = 86.0 / file["width"]
# Save thumbnail
reader.GetFrame(0).Save(thumb_path, scale)
reader.Close()
except:
# Handle exception
msg = QMessageBox()
msg.setText(app._tr("%s is not a valid video, audio, or image file." % filename))
msg.exec_()
return False
else:
# Audio file
thumb_path = os.path.join(info.PATH, "images", "AudioThumbnail.png")
item = QTreeWidgetItem(self)
item.setIcon(0, QIcon(thumb_path));
item.setText(1, filename)
item.setText(2, file["media_type"])
item.setText(3, path)
item.setText(4, file["id"])
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
self.addTopLevelItem(item)
# Process events in QT (to keep the interface responsive)
app.processEvents()
# Resize columns to match contents
for column in range(4):
self.resizeColumnToContents(column)
def add_file(self, filepath):
path, filename = os.path.split(filepath)
#Add file into project
app = get_app()
proj = app.project
files = proj.get(["files"]) #get files list
found = False
new_id = proj.generate_id()
#Check for new_id and filepath in current file list to prevent duplicates
index = 0
sanity_check, sanity_limit = 0, 10
while index < len(files):
if sanity_check > sanity_limit: #Stop checking if we can't get a unique id in 10 tries
break;
f = files[index]
#Stop checking if we find this filepath already in the list, don't allow duplicate.
if f["path"] == filepath:
found = True
break
#Generate new id and start loop over again if duplicate id is found
if f["id"] == new_id: #If this ID is found, generate a new one
new_id = proj.generate_id()
index = 0
sanity_check += 1
continue
#Move to next item
index += 1
if sanity_check > sanity_limit:
msg = QMessageBox()
msg.setText(app._tr("Error generating unique file ID for %s." % filename))
msg.exec_()
return False
if found:
msg = QMessageBox()
msg.setText(app._tr("%s already added to project." % filename))
msg.exec_()
return False
# Inspect the file with libopenshot
clip = None
# Load filepath in libopenshot clip object (which will try multiple readers to open it)
clip = openshot.Clip(filepath)
# Get the JSON for the clip's internal reader
try:
reader = clip.Reader()
file_json = json.loads(reader.Json())
# Add unique ID to the JSON
file_json["id"] = new_id
# Determine media type
if file_json["has_video"] and not self.is_image(file_json):
file_json["media_type"] = "video"
elif file_json["has_video"] and self.is_image(file_json):
file_json["media_type"] = "image"
elif file_json["has_audio"] and not file_json["has_video"]:
file_json["media_type"] = "audio"
# Add file to files list via update manager
app.updates.insert(["files"], file_json)
return True
except:
# Handle exception
msg = QMessageBox()
msg.setText(app._tr("%s is not a valid video, audio, or image file." % filename))
msg.exec_()
return False
# Handle a drag and drop being dropped on widget
def dropEvent(self, event):
#log.info('Dropping file(s) on files tree.')
for uri in event.mimeData().urls():
file_url = urlparse(uri.toString())
if file_url.scheme == "file":
filepath = file_url.path
if filepath[0] == "/" and ":" in filepath:
filepath = filepath[1:]
if os.path.exists(filepath) and os.path.isfile(filepath):
log.info('Adding file: {}'.format(filepath))
if self.add_file(filepath):
event.accept()
def clear_filter(self):
get_app().window.filesFilter.setText("")
def filter_changed(self):
self.update_model()
win = get_app().window
if win.filesFilter.text() == "":
win.actionFilesClear.setEnabled(False)
else:
win.actionFilesClear.setEnabled(True)
def __init__(self, *args):
QTreeWidget.__init__(self, *args)
#Keep track of mouse press start position to determine when to start drag
self.startDragPos = None
self.setAcceptDrops(True)
#Add self as listener to project data updates (undo/redo, as well as normal actions handled within this class all update the tree model)
app = get_app()
app.updates.add_listener(self)
#Setup header columns
self.setColumnCount(5)
self.setHeaderLabels(["Thumb","Name","Type","Path","ID"])
self.setIconSize(QSize(75, 62))
self.setIndentation(0)
self.setSelectionBehavior(QTreeWidget.SelectRows)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
#Update model based on loaded project
self.update_model()
#setup filter events
app.window.filesFilter.textChanged.connect(self.filter_changed)
app.window.actionFilesClear.triggered.connect(self.clear_filter)
"""
@file
@brief This file contains the project file treeview, used by the main window
@author Noah Figg <eggmunkee@hotmail.com>
@author Jonathan Thomas <jonathan@openshot.org>
@author Olivier Girard <eolinwen@gmail.com>
@section LICENSE
Copyright (c) 2008-2014 OpenShot Studios, LLC
(http://www.openshotstudios.com). This file is part of
OpenShot Video Editor (http://www.openshot.org), an open-source project
dedicated to delivering high quality video editing and animation solutions
to the world.
OpenShot Video Editor is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenShot Video Editor is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
"""
import os
from urllib.parse import urlparse
from classes import updates
from classes import info
from classes.logger import log
from classes.settings import SettingStore
from classes.app import get_app
from PyQt5.QtCore import QMimeData, QSize, Qt, QCoreApplication, QPoint, QFileInfo
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QTreeView, QApplication, QMessageBox, QAbstractItemView, QMenu
from windows.models.files_model import FilesModel
import openshot # Python module for libopenshot (required video editing module installed separately)
try:
import json
except ImportError:
import simplejson as json
class FilesTreeView(QTreeView):
""" A TreeView QWidget used on the main window """
drag_item_size = 32
def currentChanged(self, selected, deselected):
# get selected item
self.selected = selected
self.deselected = deselected
def contextMenuEvent(self, event):
menu = QMenu(self)
menu.addAction(self.win.actionDetailsView)
menu.addAction(self.win.actionThumbnailView)
menu.exec_(QCursor.pos())
def mouseMoveEvent(self, event):
#If mouse drag detected, set the proper data and icon and start dragging
if event.buttons() & Qt.LeftButton == Qt.LeftButton and (event.pos() - self.startDragPos).manhattanLength() >= QApplication.startDragDistance():
# Get selected item
dragItemRow = self.files_model.model.itemFromIndex(self.selected).row()
# Get all selected rows items
dragItem = []
for col in range(5):
dragItem.append(self.files_model.model.item(dragItemRow, col))
# Setup data based on item being dragged
data = QMimeData()
data.setText(dragItem[4].text()) # Add file ID to mimedata
# Start drag operation
drag = QDrag(self)
drag.setMimeData(data)
drag.setPixmap(QIcon.fromTheme('document-new').pixmap(QSize(self.drag_item_size,self.drag_item_size)))
drag.setHotSpot(QPoint(self.drag_item_size/2,self.drag_item_size/2))
drag.exec_()
# Accept event
event.accept()
return
# Ignore event, propagate to parent
event.ignore()
super().mouseMoveEvent(event)
def dragEnterEvent(self, event):
# If dragging urls onto widget, accept
if event.mimeData().hasUrls():
event.setDropAction(Qt.CopyAction)
event.accept()
# Without defining this method, the 'copy' action doesn't show with cursor
def dragMoveEvent(self, event):
pass
def mousePressEvent(self, event):
# Save position of mouse press to check for drag
self.startDragPos = event.pos()
# Ignore event, propagate to parent
event.ignore()
super().mousePressEvent(event)
def is_image(self, file):
path = file["path"].lower()
if path.endswith((".jpg", ".jpeg", ".png", ".bmp", ".svg")):
return True
else:
return False
def add_file(self, filepath):
path, filename = os.path.split(filepath)
# Add file into project
app = get_app()
proj = app.project
files = proj.get(["files"]) #get files list
found = False
new_id = proj.generate_id()
# Check for new_id and filepath in current file list to prevent duplicates
index = 0
sanity_check, sanity_limit = 0, 10
while index < len(files):
if sanity_check > sanity_limit: #Stop checking if we can't get a unique id in 10 tries
break;
f = files[index]
# Stop checking if we find this filepath already in the list, don't allow duplicate.
if f["path"] == filepath:
found = True
break
# Generate new id and start loop over again if duplicate id is found
if f["id"] == new_id: #If this ID is found, generate a new one
new_id = proj.generate_id()
index = 0
sanity_check += 1
continue
# Move to next item
index += 1
if sanity_check > sanity_limit:
msg = QMessageBox()
msg.setText(app._tr("Error generating unique file ID for %s." % filename))
msg.exec_()
return False
if found:
msg = QMessageBox()
msg.setText(app._tr("%s already added to project." % filename))
msg.exec_()
return False
# Inspect the file with libopenshot
clip = None
# Load filepath in libopenshot clip object (which will try multiple readers to open it)
clip = openshot.Clip(filepath)
# Get the JSON for the clip's internal reader
try:
reader = clip.Reader()
file_json = json.loads(reader.Json())
# Add unique ID to the JSON
file_json["id"] = new_id
# Determine media type
if file_json["has_video"] and not self.is_image(file_json):
file_json["media_type"] = "video"
elif file_json["has_video"] and self.is_image(file_json):
file_json["media_type"] = "image"
elif file_json["has_audio"] and not file_json["has_video"]:
file_json["media_type"] = "audio"
# Add file to files list via update manager
app.updates.insert(["files"], file_json)
return True
except:
# Handle exception
msg = QMessageBox()
msg.setText(app._tr("%s is not a valid video, audio, or image file." % filename))
msg.exec_()
return False
# Handle a drag and drop being dropped on widget
def dropEvent(self, event):
#log.info('Dropping file(s) on files tree.')
for uri in event.mimeData().urls():
file_url = urlparse(uri.toString())
if file_url.scheme == "file":
filepath = file_url.path
if filepath[0] == "/" and ":" in filepath:
filepath = filepath[1:]
if os.path.exists(filepath) and os.path.isfile(filepath):
log.info('Adding file: {}'.format(filepath))
if self.add_file(filepath):
event.accept()
def clear_filter(self):
get_app().window.filesFilter.setText("")
def filter_changed(self):
self.files_model.update_model()
if self.win.filesFilter.text() == "":
self.win.actionFilesClear.setEnabled(False)
else:
self.win.actionFilesClear.setEnabled(True)
def __init__(self, *args):
# Invoke parent init
QTreeView.__init__(self, *args)
# Get a reference to the window object
self.win = get_app().window
# Get Model data
self.files_model = FilesModel()
# Keep track of mouse press start position to determine when to start drag
self.startDragPos = None
self.setAcceptDrops(True)
self.selected = None
self.deselected = None
# Setup header columns
self.setModel(self.files_model.model)
self.setIconSize(QSize(75, 62))
self.setIndentation(0)
self.setSelectionBehavior(QTreeView.SelectRows)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.hideColumn(3)
self.hideColumn(4)
# setup filter events
app = get_app()
app.window.filesFilter.textChanged.connect(self.filter_changed)
app.window.actionFilesClear.triggered.connect(self.clear_filter)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册