OpenShot Video Editor  2.0.0
main_window.py
Go to the documentation of this file.
1 ##
2 #
3 # @file
4 # @brief This file loads the main window (i.e. the primary user-interface)
5 # @author Noah Figg <eggmunkee@hotmail.com>
6 # @author Jonathan Thomas <jonathan@openshot.org>
7 # @author Olivier Girard <olivier@openshot.org>
8 #
9 # @section LICENSE
10 #
11 # Copyright (c) 2008-2016 OpenShot Studios, LLC
12 # (http://www.openshotstudios.com). This file is part of
13 # OpenShot Video Editor (http://www.openshot.org), an open-source project
14 # dedicated to delivering high quality video editing and animation solutions
15 # to the world.
16 #
17 # OpenShot Video Editor is free software: you can redistribute it and/or modify
18 # it under the terms of the GNU General Public License as published by
19 # the Free Software Foundation, either version 3 of the License, or
20 # (at your option) any later version.
21 #
22 # OpenShot Video Editor is distributed in the hope that it will be useful,
23 # but WITHOUT ANY WARRANTY; without even the implied warranty of
24 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 # GNU General Public License for more details.
26 #
27 # You should have received a copy of the GNU General Public License
28 # along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
29 #
30 
31 import os
32 import shutil
33 import webbrowser
34 from uuid import uuid4
35 from copy import deepcopy
36 
37 from PyQt5.QtCore import *
38 from PyQt5.QtGui import QIcon, QCursor, QKeySequence
39 from PyQt5.QtWidgets import *
40 import openshot # Python module for libopenshot (required video editing module installed separately)
41 
42 from windows.views.timeline_webview import TimelineWebView
43 from classes import info, ui_util, settings, qt_types, updates
44 from classes.app import get_app
45 from classes.logger import log
46 from classes.timeline import TimelineSync
47 from classes.query import File, Clip, Transition, Marker, Track
48 from classes.metrics import *
49 from classes.version import *
50 from images import openshot_rc
51 from windows.views.files_treeview import FilesTreeView
52 from windows.views.files_listview import FilesListView
53 from windows.views.transitions_treeview import TransitionsTreeView
54 from windows.views.transitions_listview import TransitionsListView
55 from windows.views.effects_treeview import EffectsTreeView
56 from windows.views.effects_listview import EffectsListView
57 from windows.views.properties_tableview import PropertiesTableView, SelectionLabel
58 from windows.views.tutorial import TutorialManager
59 from windows.video_widget import VideoWidget
60 from windows.preview_thread import PreviewParent
61 
62 
63 ##
64 # This class contains the logic for the main window widget
66 
67  # Path to ui file
68  ui_path = os.path.join(info.PATH, 'windows', 'ui', 'main-window.ui')
69 
70  previewFrameSignal = pyqtSignal(int)
71  refreshFrameSignal = pyqtSignal()
72  LoadFileSignal = pyqtSignal(str)
73  PlaySignal = pyqtSignal(int)
74  PauseSignal = pyqtSignal()
75  StopSignal = pyqtSignal()
76  SeekSignal = pyqtSignal(int)
77  SpeedSignal = pyqtSignal(float)
78  RecoverBackup = pyqtSignal()
79  FoundVersionSignal = pyqtSignal(str)
80  WaveformReady = pyqtSignal(str, list)
81 
82  # Save window settings on close
83  def closeEvent(self, event):
84 
85  # Close any tutorial dialogs
86  self.tutorial_manager.exit_manager()
87 
88  # Prompt user to save (if needed)
89  if get_app().project.needs_save():
90  log.info('Prompt user to save project')
91  # Translate object
92  _ = get_app()._tr
93 
94  # Handle exception
95  ret = QMessageBox.question(self, _("Unsaved Changes"), _("Save changes to project before closing?"), QMessageBox.Cancel | QMessageBox.No | QMessageBox.Yes)
96  if ret == QMessageBox.Yes:
97  # Save project
98  self.actionSave_trigger(event)
99  event.accept()
100  elif ret == QMessageBox.Cancel:
101  # User canceled prompt - don't quit
102  event.ignore()
103  return
104 
105  # Save settings
106  self.save_settings()
107 
108  # Track end of session
109  track_metric_session(False)
110 
111  # Stop threads
112  self.StopSignal.emit()
113  self.preview_thread.kill()
114 
115  # Close & Stop libopenshot logger
116  openshot.ZmqLogger.Instance().Close()
117  get_app().logger_libopenshot.kill()
118 
119  # Wait for thread
120  self.preview_parent.background.wait(500)
121 
122  # Destroy lock file
123  self.destroy_lock_file()
124 
125  ##
126  # Recover the backup file (if any)
127  def recover_backup(self):
128  log.info("recover_backup")
129  # Check for backup.osp file
130  recovery_path = os.path.join(info.BACKUP_PATH, "backup.osp")
131 
132  # Load recovery project
133  if os.path.exists(recovery_path):
134  log.info("Recovering backup file: %s" % recovery_path)
135  self.open_project(recovery_path, clear_thumbnails=False)
136 
137  # Clear the file_path (which is set by saving the project)
138  get_app().project.current_filepath = None
139  get_app().project.has_unsaved_changes = True
140 
141  # Set Window title
142  self.SetWindowTitle()
143 
144  # Show message to user
145  msg = QMessageBox()
146  _ = get_app()._tr
147  msg.setWindowTitle(_("Backup Recovered"))
148  msg.setText(_("Your most recent unsaved project has been recovered."))
149  msg.exec_()
150  else:
151  # Load a blank project (to propagate the default settings)
152  get_app().project.load("")
153 
154  ##
155  # Create a lock file
156  def create_lock_file(self):
157  lock_path = os.path.join(info.USER_PATH, ".lock")
158  lock_value = str(uuid4())
159 
160  # Check if it already exists
161  if os.path.exists(lock_path):
162  # Walk the libopenshot log (if found), and try and find last line before this launch
163  log_path = os.path.join(info.USER_PATH, "libopenshot.log")
164  last_log_line = None
165  if os.path.exists(log_path):
166  with open(log_path, "r") as f:
167  # Read from bottom up
168  for line in reversed(f.readlines()):
169  # Ignore certain unuseful lines
170  if "---" not in line and "libopenshot logging:" not in line:
171  last_log_line = line
172  break
173 
174  # Clear / normalize log line (so we can roll them up in the analytics)
175  if last_log_line:
176  # Remove '()' from line, and split. Trying to grab the beginning of the log line.
177  last_log_line = last_log_line.replace("()", "")
178  log_parts = last_log_line.split("(")
179  if len(log_parts) == 2:
180  last_log_line = "-%s" % log_parts[0].replace("logger_libopenshot:INFO ", "").strip()[:64]
181  elif len(log_parts) >= 3:
182  last_log_line = "-%s (%s" % (log_parts[0].replace("logger_libopenshot:INFO ", "").strip()[:64], log_parts[1])
183  else:
184  last_log_line = ""
185 
186  # Throw exception (with last libopenshot line... if found)
187  log.error("Unhandled crash detected... will attempt to recover backup project: %s" % info.BACKUP_PATH)
188  track_metric_error("unhandled-crash%s" % last_log_line, True)
189 
190  # Remove file
191  os.remove(lock_path)
192 
193  # Recover backup file (this can't happen until after the Main Window has completely loaded)
194  QTimer.singleShot(0, self.RecoverBackup.emit)
195 
196  else:
197  # Normal startup, clear thumbnails
198  self.clear_all_thumbnails()
199 
200  # Load a blank project (to propagate the default settings)
201  get_app().project.load("")
202 
203  # Create lock file
204  with open(lock_path, 'w') as f:
205  f.write(lock_value)
206 
207  ##
208  # Destroy the lock file
209  def destroy_lock_file(self):
210  lock_path = os.path.join(info.USER_PATH, ".lock")
211 
212  # Check if it already exists
213  if os.path.exists(lock_path):
214  # Remove file
215  os.remove(lock_path)
216 
217  def actionNew_trigger(self, event):
218  # Clear any previous thumbnails
219  self.clear_all_thumbnails()
220 
221  # clear data and start new project
222  get_app().project.load("")
223  get_app().updates.reset()
224  self.updateStatusChanged(False, False)
225 
226  # Reset selections
227  self.clearSelections()
228 
229  self.filesTreeView.refresh_view()
230  log.info("New Project created.")
231 
232  # Set Window title
233  self.SetWindowTitle()
234 
235  def actionAnimatedTitle_trigger(self, event):
236  # show dialog
237  from windows.animated_title import AnimatedTitle
238  win = AnimatedTitle()
239  # Run the dialog event loop - blocking interaction on this window during that time
240  result = win.exec_()
241  if result == QDialog.Accepted:
242  log.info('animated title add confirmed')
243  else:
244  log.info('animated title add cancelled')
245 
246  def actionAnimation_trigger(self, event):
247  # show dialog
248  from windows.animation import Animation
249  win = Animation()
250  # Run the dialog event loop - blocking interaction on this window during that time
251  result = win.exec_()
252  if result == QDialog.Accepted:
253  log.info('animation confirmed')
254  else:
255  log.info('animation cancelled')
256 
257  def actionTitle_trigger(self, event):
258  # show dialog
259  from windows.title_editor import TitleEditor
260  win = TitleEditor()
261  # Run the dialog event loop - blocking interaction on this window during that time
262  result = win.exec_()
263  if result == QDialog.Accepted:
264  log.info('title editor add confirmed')
265  else:
266  log.info('title editor add cancelled')
267 
269  # show dialog
270  from windows.Import_image_seq import ImportImageSeq
271  win = ImportImageSeq()
272  # Run the dialog event loop - blocking interaction on this window during that time
273  result = win.exec_()
274  if result == QDialog.Accepted:
275  log.info('Import image sequence add confirmed')
276  else:
277  log.info('Import image sequence add cancelled')
278 
279  ##
280  # Save a project to a file path, and refresh the screen
281  def save_project(self, file_path):
282  app = get_app()
283  _ = app._tr # Get translation function
284 
285  try:
286  # Save project to file
287  app.project.save(file_path)
288 
289  # Set Window title
290  self.SetWindowTitle()
291 
292  # Load recent projects again
293  self.load_recent_menu()
294 
295  log.info("Saved project {}".format(file_path))
296 
297  except Exception as ex:
298  log.error("Couldn't save project %s. %s" % (file_path, str(ex)))
299  QMessageBox.warning(self, _("Error Saving Project"), str(ex))
300 
301  ##
302  # Open a project from a file path, and refresh the screen
303  def open_project(self, file_path, clear_thumbnails=True):
304 
305  app = get_app()
306  _ = app._tr # Get translation function
307 
308  # Set cursor to waiting
309  get_app().setOverrideCursor(QCursor(Qt.WaitCursor))
310 
311  try:
312  if os.path.exists(file_path.encode('UTF-8')):
313  # Clear any previous thumbnails
314  if clear_thumbnails:
315  self.clear_all_thumbnails()
316 
317  # Load project file
318  app.project.load(file_path)
319 
320  # Set Window title
321  self.SetWindowTitle()
322 
323  # Reset undo/redo history
324  app.updates.reset()
325  self.updateStatusChanged(False, False)
326 
327  # Reset selections
328  self.clearSelections()
329 
330  # Refresh file tree
331  self.filesTreeView.refresh_view()
332 
333  # Load recent projects again
334  self.load_recent_menu()
335 
336  log.info("Loaded project {}".format(file_path))
337 
338  except Exception as ex:
339  log.error("Couldn't open project {}".format(file_path))
340  QMessageBox.warning(self, _("Error Opening Project"), str(ex))
341 
342  # Restore normal cursor
343  get_app().restoreOverrideCursor()
344 
345  ##
346  # Clear all user thumbnails
348  try:
349  if os.path.exists(info.THUMBNAIL_PATH):
350  log.info("Clear all thumbnails: %s" % info.THUMBNAIL_PATH)
351  # Remove thumbnail folder
352  shutil.rmtree(info.THUMBNAIL_PATH)
353  # Create thumbnail folder
354  os.mkdir(info.THUMBNAIL_PATH)
355 
356  # Clear any blender animations
357  if os.path.exists(info.BLENDER_PATH):
358  log.info("Clear all animations: %s" % info.BLENDER_PATH)
359  # Remove blender folder
360  shutil.rmtree(info.BLENDER_PATH)
361  # Create blender folder
362  os.mkdir(info.BLENDER_PATH)
363 
364  # Clear any blender animations
365  if os.path.exists(info.BACKUP_PATH):
366  log.info("Clear all backups: %s" % info.BACKUP_PATH)
367  # Remove backup folder
368  shutil.rmtree(info.BACKUP_PATH)
369  # Create backup folder
370  os.mkdir(info.BACKUP_PATH)
371  except:
372  log.info("Failed to clear thumbnails: %s" % info.THUMBNAIL_PATH)
373 
374  def actionOpen_trigger(self, event):
375  app = get_app()
376  _ = app._tr
377  recommended_path = app.project.current_filepath
378  if not recommended_path:
379  recommended_path = info.HOME_PATH
380  file_path, file_type = QFileDialog.getOpenFileName(self, _("Open Project..."), recommended_path, _("OpenShot Project (*.osp)"))
381 
382  # Load project file
383  self.open_project(file_path)
384 
385  def actionSave_trigger(self, event):
386  app = get_app()
387  _ = app._tr
388 
389  # Get current filepath if any, otherwise ask user
390  file_path = app.project.current_filepath
391  if not file_path:
392  recommended_path = os.path.join(info.HOME_PATH, "%s.osp" % _("Untitled Project"))
393  file_path, file_type = QFileDialog.getSaveFileName(self, _("Save Project..."), recommended_path, _("OpenShot Project (*.osp)"))
394 
395  if file_path:
396  # Append .osp if needed
397  if ".osp" not in file_path:
398  file_path = "%s.osp" % file_path
399 
400  # Save project
401  self.save_project(file_path)
402 
403  ##
404  # Auto save the project
405  def auto_save_project(self):
406  log.info("auto_save_project")
407 
408  # Get current filepath (if any)
409  file_path = get_app().project.current_filepath
410  if get_app().project.needs_save():
411  if file_path:
412  # A Real project file exists
413  # Append .osp if needed
414  if ".osp" not in file_path:
415  file_path = "%s.osp" % file_path
416 
417  # Save project
418  log.info("Auto save project file: %s" % file_path)
419  self.save_project(file_path)
420 
421  else:
422  # No saved project found
423  recovery_path = os.path.join(info.BACKUP_PATH, "backup.osp")
424  log.info("Creating backup of project file: %s" % recovery_path)
425  get_app().project.save(recovery_path, move_temp_files=False, make_paths_relative=False)
426 
427  # Clear the file_path (which is set by saving the project)
428  get_app().project.current_filepath = None
429  get_app().project.has_unsaved_changes = True
430 
431  def actionSaveAs_trigger(self, event):
432  app = get_app()
433  _ = app._tr
434 
435  recommended_path = app.project.current_filepath
436  if not recommended_path:
437  recommended_path = os.path.join(info.HOME_PATH, "%s.osp" % _("Untitled Project"))
438  file_path, file_type = QFileDialog.getSaveFileName(self, _("Save Project As..."), recommended_path, _("OpenShot Project (*.osp)"))
439  if file_path:
440  # Append .osp if needed
441  if ".osp" not in file_path:
442  file_path = "%s.osp" % file_path
443 
444  # Save new project
445  self.save_project(file_path)
446 
447  def actionImportFiles_trigger(self, event):
448  app = get_app()
449  _ = app._tr
450  recommended_path = app.project.current_filepath
451  if not recommended_path:
452  recommended_path = os.path.join(info.HOME_PATH)
453  files = QFileDialog.getOpenFileNames(self, _("Import File..."), recommended_path)[0]
454  for file_path in files:
455  self.filesTreeView.add_file(file_path)
456  self.filesTreeView.refresh_view()
457  log.info("Imported media file {}".format(file_path))
458 
460  # Loop through selected files
461  f = None
462  files = []
463  for file_id in self.selected_files:
464  # Find matching file
465  files.append(File.get(id=file_id))
466 
467  # Get current position of playhead
468  fps = get_app().project.get(["fps"])
469  fps_float = float(fps["num"]) / float(fps["den"])
470  pos = (self.preview_thread.player.Position() - 1) / fps_float
471 
472  # show window
473  from windows.add_to_timeline import AddToTimeline
474  win = AddToTimeline(files, pos)
475  # Run the dialog event loop - blocking interaction on this window during this time
476  result = win.exec_()
477  if result == QDialog.Accepted:
478  log.info('confirmed')
479  else:
480  log.info('canceled')
481 
482  def actionUploadVideo_trigger(self, event):
483  # show window
484  from windows.upload_video import UploadVideo
485  win = UploadVideo()
486  # Run the dialog event loop - blocking interaction on this window during this time
487  result = win.exec_()
488  if result == QDialog.Accepted:
489  log.info('Upload Video add confirmed')
490  else:
491  log.info('Upload Video add cancelled')
492 
493  def actionExportVideo_trigger(self, event):
494  # show window
495  from windows.export import Export
496  win = Export()
497  # Run the dialog event loop - blocking interaction on this window during this time
498  result = win.exec_()
499  if result == QDialog.Accepted:
500  log.info('Export Video add confirmed')
501  else:
502  log.info('Export Video add cancelled')
503 
504  def actionUndo_trigger(self, event):
505  log.info('actionUndo_trigger')
506  app = get_app()
507  app.updates.undo()
508 
509  def actionRedo_trigger(self, event):
510  log.info('actionRedo_trigger')
511  app = get_app()
512  app.updates.redo()
513 
514  def actionPreferences_trigger(self, event):
515  # Show dialog
516  from windows.preferences import Preferences
517  win = Preferences()
518  # Run the dialog event loop - blocking interaction on this window during this time
519  result = win.exec_()
520  if result == QDialog.Accepted:
521  log.info('Preferences add confirmed')
522  else:
523  log.info('Preferences add cancelled')
524 
525  def actionFilesShowAll_trigger(self, event):
526  self.filesTreeView.refresh_view()
527 
529  self.filesTreeView.refresh_view()
530 
532  self.filesTreeView.refresh_view()
533 
535  self.filesTreeView.refresh_view()
536 
538  self.transitionsTreeView.refresh_view()
539 
541  self.transitionsTreeView.refresh_view()
542 
544  self.effectsTreeView.refresh_view()
545 
547  self.effectsTreeView.refresh_view()
548 
550  self.effectsTreeView.refresh_view()
551 
552  def actionHelpContents_trigger(self, event):
553  try:
554  webbrowser.open("http://openshotusers.com/?app-menu")
555  log.info("Help Contents is open")
556  except:
557  QMessageBox.information(self, "Error !",
558  "Unable to open the Help Contents. Please ensure the openshot-doc package is installed.")
559  log.info("Unable to open the Help Contents")
560 
561  ##
562  # Show about dialog
563  def actionAbout_trigger(self, event):
564  from windows.about import About
565  win = About()
566  # Run the dialog event loop - blocking interaction on this window during this time
567  result = win.exec_()
568  if result == QDialog.Accepted:
569  log.info('About Openshot add confirmed')
570  else:
571  log.info('About Openshot add cancelled')
572 
573  def actionReportBug_trigger(self, event):
574  try:
575  webbrowser.open("https://github.com/OpenShot/openshot-qt/issues/?app-menu-bug")
576  log.info("Open the Bug Report GitHub Issues web page with success")
577  except:
578  QMessageBox.information(self, "Error !", "Unable to open the Bug Report GitHub Issues web page")
579  log.info("Unable to open the Bug Report GitHub Issues web page")
580 
581  def actionAskQuestion_trigger(self, event):
582  try:
583  webbrowser.open("https://github.com/OpenShot/openshot-qt/issues/?app-menu-question")
584  log.info("Open the Questions GitHub Issues web page with success")
585  except:
586  QMessageBox.information(self, "Error !", "Unable to open the Questions GitHub Issues web page")
587  log.info("Unable to open the Questions GitHub Issues web page")
588 
589  def actionTranslate_trigger(self, event):
590  try:
591  webbrowser.open("https://translations.launchpad.net/openshot/2.0")
592  log.info("Open the Translate launchpad web page with success")
593  except:
594  QMessageBox.information(self, "Error !", "Unable to open the Translation web page")
595  log.info("Unable to open the Translation web page")
596 
597  def actionDonate_trigger(self, event):
598  try:
599  webbrowser.open("http://openshot.org/donate/?app-menu")
600  log.info("Open the Donate web page with success")
601  except:
602  QMessageBox.information(self, "Error !", "Unable to open the Donate web page")
603  log.info("Unable to open the Donate web page")
604 
605  def actionUpdate_trigger(self, event):
606  try:
607  webbrowser.open("http://openshot.org/download/?app-toolbar")
608  log.info("Open the Download web page with success")
609  except:
610  QMessageBox.information(self, "Error !", "Unable to open the Download web page")
611  log.info("Unable to open the Download web page")
612 
613  def actionPlay_trigger(self, event, force=None):
614 
615  # Determine max frame (based on clips)
616  timeline_length = 0.0
617  fps = get_app().window.timeline_sync.timeline.info.fps.ToFloat()
618  clips = get_app().window.timeline_sync.timeline.Clips()
619  for clip in clips:
620  clip_last_frame = clip.Position() + clip.Duration()
621  if clip_last_frame > timeline_length:
622  # Set max length of timeline
623  timeline_length = clip_last_frame
624 
625  # Convert to int and round
626  timeline_length_int = round(timeline_length * fps) + 1
627 
628  if force == "pause":
629  self.actionPlay.setChecked(False)
630  elif force == "play":
631  self.actionPlay.setChecked(True)
632 
633  if self.actionPlay.isChecked():
634  ui_util.setup_icon(self, self.actionPlay, "actionPlay", "media-playback-pause")
635  self.PlaySignal.emit(timeline_length_int)
636 
637  else:
638  ui_util.setup_icon(self, self.actionPlay, "actionPlay") # to default
639  self.PauseSignal.emit()
640 
641  ##
642  # Preview the selected media file
643  def actionPreview_File_trigger(self, event):
644  log.info('actionPreview_File_trigger')
645 
646  if self.selected_files:
647  # Find matching file
648  f = File.get(id=self.selected_files[0])
649  if f:
650  # Get file path
651  previewPath = f.data["path"]
652 
653  # Load file into player
654  self.LoadFileSignal.emit(previewPath)
655 
656  # Trigger play button
657  self.actionPlay.setChecked(False)
658  self.actionPlay.trigger()
659 
660  ##
661  # Preview a specific frame
662  def previewFrame(self, position_seconds, position_frames, time_code):
663  # Notify preview thread
664  self.previewFrameSignal.emit(position_frames)
665 
666  # Notify properties dialog
667  self.propertyTableView.select_frame(position_frames)
668 
669  ##
670  # Update playhead position
671  def movePlayhead(self, position_frames):
672  # Notify preview thread
673  self.timeline.movePlayhead(position_frames)
674 
675  def actionFastForward_trigger(self, event):
676 
677  # Get the video player object
678  player = self.preview_thread.player
679 
680  if player.Speed() + 1 != 0:
681  self.SpeedSignal.emit(player.Speed() + 1)
682  else:
683  self.SpeedSignal.emit(player.Speed() + 2)
684 
685  if player.Mode() == openshot.PLAYBACK_PAUSED:
686  self.actionPlay.trigger()
687 
688  def actionRewind_trigger(self, event):
689 
690  # Get the video player object
691  player = self.preview_thread.player
692 
693  if player.Speed() - 1 != 0:
694  self.SpeedSignal.emit(player.Speed() - 1)
695  else:
696  self.SpeedSignal.emit(player.Speed() - 2)
697 
698  if player.Mode() == openshot.PLAYBACK_PAUSED:
699  self.actionPlay.trigger()
700 
701  def actionJumpStart_trigger(self, event):
702  log.info("actionJumpStart_trigger")
703 
704  # Seek to the 1st frame
705  self.SeekSignal.emit(1)
706 
707  def actionJumpEnd_trigger(self, event):
708  log.info("actionJumpEnd_trigger")
709 
710  # Determine max frame (based on clips)
711  timeline_length = 0.0
712  fps = get_app().window.timeline_sync.timeline.info.fps.ToFloat()
713  clips = get_app().window.timeline_sync.timeline.Clips()
714  for clip in clips:
715  clip_last_frame = clip.Position() + clip.Duration()
716  if clip_last_frame > timeline_length:
717  # Set max length of timeline
718  timeline_length = clip_last_frame
719 
720  # Convert to int and round
721  timeline_length_int = round(timeline_length * fps) + 1
722 
723  # Seek to the 1st frame
724  self.SeekSignal.emit(timeline_length_int)
725 
726  def actionAddTrack_trigger(self, event):
727  log.info("actionAddTrack_trigger")
728 
729  # Get # of tracks
730  track_number = len(get_app().project.get(["layers"]))
731 
732  # Look for existing Marker
733  track = Track()
734  track.data = {"number": track_number, "y": 0, "label": "", "lock": False}
735  track.save()
736 
737  def actionAddTrackAbove_trigger(self, event):
738  log.info("actionAddTrackAbove_trigger")
739 
740  # Get # of tracks
741  max_track_number = len(get_app().project.get(["layers"]))
742  selected_layer_id = self.selected_tracks[0]
743 
744  # Get selected track data
745  existing_track = Track.get(id=selected_layer_id)
746  selected_layer_number = int(existing_track.data["number"])
747 
748  # Create new track
749  track = Track()
750  track.data = {"number": max_track_number, "y": 0, "label": "", "lock": False}
751  track.save()
752 
753  # Loop through all clips on higher layers, and move to new layer (in reverse order)
754  for existing_layer in list(reversed(range(selected_layer_number + 1, max_track_number))):
755  existing_track.data["label"] = ""
756  existing_track.save()
757 
758  for clip in Clip.filter(layer=existing_layer):
759  clip.data["layer"] = int(clip.data["layer"]) + 1
760  clip.save()
761 
762  def actionAddTrackBelow_trigger(self, event):
763  log.info("actionAddTrackAbove_trigger")
764 
765  # Get # of tracks
766  max_track_number = len(get_app().project.get(["layers"]))
767  selected_layer_id = self.selected_tracks[0]
768 
769  # Get selected track data
770  existing_track = Track.get(id=selected_layer_id)
771  selected_layer_number = int(existing_track.data["number"])
772 
773  # Create new track
774  track = Track()
775  track.data = {"number": max_track_number, "y": 0, "label": "", "lock": False}
776  track.save()
777 
778  # Loop through all clips on higher layers, and move to new layer (in reverse order)
779  for existing_layer in list(reversed(range(selected_layer_number, max_track_number))):
780  existing_track.data["label"] = ""
781  existing_track.save()
782 
783  for clip in Clip.filter(layer=existing_layer):
784  clip.data["layer"] = int(clip.data["layer"]) + 1
785  clip.save()
786 
787  def actionArrowTool_trigger(self, event):
788  log.info("actionArrowTool_trigger")
789 
790  def actionSnappingTool_trigger(self, event):
791  log.info("actionSnappingTool_trigger")
792  log.info(self.actionSnappingTool.isChecked())
793 
794  # Enable / Disable snapping mode
795  self.timeline.SetSnappingMode(self.actionSnappingTool.isChecked())
796 
797  def actionAddMarker_trigger(self, event):
798  log.info("actionAddMarker_trigger")
799 
800  # Get player object
801  player = self.preview_thread.player
802 
803  # Calculate frames per second
804  fps = get_app().project.get(["fps"])
805  fps_float = float(fps["num"]) / float(fps["den"])
806 
807  # Calculate position in seconds
808  position = player.Position() / fps_float
809 
810  # Look for existing Marker
811  marker = Marker()
812  marker.data = {"position": position, "icon": "blue.png"}
813  marker.save()
814 
816  log.info("actionPreviousMarker_trigger")
817 
818  # Calculate current position (in seconds)
819  fps = get_app().project.get(["fps"])
820  fps_float = float(fps["num"]) / float(fps["den"])
821  current_position = self.preview_thread.current_frame / fps_float
822 
823  # Loop through all markers, and find the closest one to the left
824  closest_position = None
825  for marker in Marker.filter():
826  marker_position = marker.data["position"]
827 
828  # Is marker smaller than position?
829  if marker_position < current_position:
830  # Is marker larger than previous marker
831  if closest_position and marker_position > closest_position:
832  # Set a new closest marker
833  closest_position = marker_position
834  elif not closest_position:
835  # First one found
836  closest_position = marker_position
837 
838  # Seek to marker position (if any)
839  if closest_position:
840  # Seek
841  frame_to_seek = int(closest_position * fps_float)
842  self.SeekSignal.emit(frame_to_seek)
843 
844  def actionNextMarker_trigger(self, event):
845  log.info("actionNextMarker_trigger")
846  log.info(self.preview_thread.current_frame)
847 
848  # Calculate current position (in seconds)
849  fps = get_app().project.get(["fps"])
850  fps_float = float(fps["num"]) / float(fps["den"])
851  current_position = self.preview_thread.current_frame / fps_float
852 
853  # Loop through all markers, and find the closest one to the right
854  closest_position = None
855  for marker in Marker.filter():
856  marker_position = marker.data["position"]
857 
858  # Is marker smaller than position?
859  if marker_position > current_position:
860  # Is marker larger than previous marker
861  if closest_position and marker_position < closest_position:
862  # Set a new closest marker
863  closest_position = marker_position
864  elif not closest_position:
865  # First one found
866  closest_position = marker_position
867 
868  # Seek to marker position (if any)
869  if closest_position:
870  # Seek
871  frame_to_seek = int(closest_position * fps_float)
872  self.SeekSignal.emit(frame_to_seek)
873 
874  ##
875  # Get a key sequence back from the setting name
876  def getShortcutByName(self, setting_name):
878  shortcut = QKeySequence(s.get(setting_name))
879  return shortcut
880 
881  ##
882  # Get a key sequence back from the setting name
884  keyboard_shortcuts = []
885  all_settings = settings.get_settings()._data
886  for setting in all_settings:
887  if setting.get('category') == 'Keyboard':
888  keyboard_shortcuts.append(setting)
889  return keyboard_shortcuts
890 
891  ##
892  # Process key press events and match with known shortcuts
893  def keyPressEvent(self, event):
894  MOD_MASK = (Qt.CTRL | Qt.ALT | Qt.SHIFT | Qt.META)
895 
896  # Detect the current KeySequence pressed (including modifier keys)
897  key_value = event.key()
898  print(key_value)
899  modifiers = int(event.modifiers())
900  if (key_value > 0 and key_value != Qt.Key_Shift and key_value != Qt.Key_Alt and
901  key_value != Qt.Key_Control and key_value != Qt.Key_Meta):
902  # A valid keysequence was detected
903  key = QKeySequence(modifiers + key_value)
904  else:
905  # No valid keysequence detected
906  return
907 
908  # Debug
909  log.info("keyPressEvent: %s" % (key.toString()))
910 
911  # Get the video player object
912  player = self.preview_thread.player
913 
914  # Get framerate
915  fps = get_app().project.get(["fps"])
916  fps_float = float(fps["num"]) / float(fps["den"])
917  playhead_position = float(self.preview_thread.current_frame) / fps_float
918 
919  # Basic shortcuts i.e just a letter
920  if key.matches(self.getShortcutByName("seekPreviousFrame")) == QKeySequence.ExactMatch:
921  # Pause video
922  self.actionPlay_trigger(event, force="pause")
923  # Set speed to 0
924  if player.Speed() != 0:
925  self.SpeedSignal.emit(0)
926  # Seek to previous frame
927  self.SeekSignal.emit(player.Position() - 1)
928 
929  # Notify properties dialog
930  self.propertyTableView.select_frame(player.Position())
931 
932  elif key.matches(self.getShortcutByName("seekNextFrame")) == QKeySequence.ExactMatch:
933  # Pause video
934  self.actionPlay_trigger(event, force="pause")
935  # Set speed to 0
936  if player.Speed() != 0:
937  self.SpeedSignal.emit(0)
938  # Seek to next frame
939  self.SeekSignal.emit(player.Position() + 1)
940 
941  # Notify properties dialog
942  self.propertyTableView.select_frame(player.Position())
943 
944  elif key.matches(self.getShortcutByName("rewindVideo")) == QKeySequence.ExactMatch:
945  # Toggle rewind and start playback
946  self.actionRewind.trigger()
947  ui_util.setup_icon(self, self.actionPlay, "actionPlay", "media-playback-pause")
948  self.actionPlay.setChecked(True)
949 
950  elif key.matches(self.getShortcutByName("fastforwardVideo")) == QKeySequence.ExactMatch:
951  # Toggle fastforward button and start playback
952  self.actionFastForward.trigger()
953  ui_util.setup_icon(self, self.actionPlay, "actionPlay", "media-playback-pause")
954  self.actionPlay.setChecked(True)
955 
956  elif key.matches(self.getShortcutByName("playToggle")) == QKeySequence.ExactMatch or \
957  key.matches(self.getShortcutByName("playToggle1")) == QKeySequence.ExactMatch or \
958  key.matches(self.getShortcutByName("playToggle2")) == QKeySequence.ExactMatch or \
959  key.matches(self.getShortcutByName("playToggle3")) == QKeySequence.ExactMatch:
960  # Toggle playbutton and show properties
961  self.actionPlay.trigger()
962  self.propertyTableView.select_frame(player.Position())
963 
964  elif key.matches(self.getShortcutByName("deleteItem")) == QKeySequence.ExactMatch or \
965  key.matches(self.getShortcutByName("deleteItem1")) == QKeySequence.ExactMatch:
966  # Delete selected clip / transition
967  self.actionRemoveClip.trigger()
968  self.actionRemoveTransition.trigger()
969 
970  # Boiler plate key mappings (mostly for menu support on Ubuntu/Unity)
971  elif key.matches(self.getShortcutByName("actionNew")) == QKeySequence.ExactMatch:
972  self.actionNew.trigger()
973  elif key.matches(self.getShortcutByName("actionOpen")) == QKeySequence.ExactMatch:
974  self.actionOpen.trigger()
975  elif key.matches(self.getShortcutByName("actionSave")) == QKeySequence.ExactMatch:
976  self.actionSave.trigger()
977  elif key.matches(self.getShortcutByName("actionUndo")) == QKeySequence.ExactMatch:
978  self.actionUndo.trigger()
979  elif key.matches(self.getShortcutByName("actionSaveAs")) == QKeySequence.ExactMatch:
980  self.actionSaveAs.trigger()
981  elif key.matches(self.getShortcutByName("actionImportFiles")) == QKeySequence.ExactMatch:
982  self.actionImportFiles.trigger()
983  elif key.matches(self.getShortcutByName("actionRedo")) == QKeySequence.ExactMatch:
984  self.actionRedo.trigger()
985  elif key.matches(self.getShortcutByName("actionExportVideo")) == QKeySequence.ExactMatch:
986  self.actionExportVideo.trigger()
987  elif key.matches(self.getShortcutByName("actionQuit")) == QKeySequence.ExactMatch:
988  self.actionQuit.trigger()
989  elif key.matches(self.getShortcutByName("actionPreferences")) == QKeySequence.ExactMatch:
990  self.actionPreferences.trigger()
991  elif key.matches(self.getShortcutByName("actionAddTrack")) == QKeySequence.ExactMatch:
992  self.actionAddTrack.trigger()
993  elif key.matches(self.getShortcutByName("actionAddMarker")) == QKeySequence.ExactMatch:
994  self.actionAddMarker.trigger()
995  elif key.matches(self.getShortcutByName("actionPreviousMarker")) == QKeySequence.ExactMatch:
996  self.actionPreviousMarker.trigger()
997  elif key.matches(self.getShortcutByName("actionNextMarker")) == QKeySequence.ExactMatch:
998  self.actionNextMarker.trigger()
999  elif key.matches(self.getShortcutByName("actionTimelineZoomIn")) == QKeySequence.ExactMatch:
1000  self.actionTimelineZoomIn.trigger()
1001  elif key.matches(self.getShortcutByName("actionTimelineZoomOut")) == QKeySequence.ExactMatch:
1002  self.actionTimelineZoomOut.trigger()
1003  elif key.matches(self.getShortcutByName("actionTitle")) == QKeySequence.ExactMatch:
1004  self.actionTitle.trigger()
1005  elif key.matches(self.getShortcutByName("actionAnimatedTitle")) == QKeySequence.ExactMatch:
1006  self.actionAnimatedTitle.trigger()
1007  elif key.matches(self.getShortcutByName("actionFullscreen")) == QKeySequence.ExactMatch:
1008  self.actionFullscreen.trigger()
1009  elif key.matches(self.getShortcutByName("actionAbout")) == QKeySequence.ExactMatch:
1010  self.actionAbout.trigger()
1011  elif key.matches(self.getShortcutByName("actionThumbnailView")) == QKeySequence.ExactMatch:
1012  self.actionThumbnailView.trigger()
1013  elif key.matches(self.getShortcutByName("actionDetailsView")) == QKeySequence.ExactMatch:
1014  self.actionDetailsView.trigger()
1015  elif key.matches(self.getShortcutByName("actionProfile")) == QKeySequence.ExactMatch:
1016  self.actionProfile.trigger()
1017  elif key.matches(self.getShortcutByName("actionAdd_to_Timeline")) == QKeySequence.ExactMatch:
1018  self.actionAdd_to_Timeline.trigger()
1019  elif key.matches(self.getShortcutByName("actionSplitClip")) == QKeySequence.ExactMatch:
1020  self.actionSplitClip.trigger()
1021  elif key.matches(self.getShortcutByName("actionSnappingTool")) == QKeySequence.ExactMatch:
1022  self.actionSnappingTool.trigger()
1023  elif key.matches(self.getShortcutByName("actionJumpStart")) == QKeySequence.ExactMatch:
1024  self.actionJumpStart.trigger()
1025  elif key.matches(self.getShortcutByName("actionJumpEnd")) == QKeySequence.ExactMatch:
1026  self.actionJumpEnd.trigger()
1027  elif key.matches(self.getShortcutByName("actionProperties")) == QKeySequence.ExactMatch:
1028  self.actionProperties.trigger()
1029 
1030  # Timeline keyboard shortcuts
1031  elif key.matches(self.getShortcutByName("sliceAllKeepBothSides")) == QKeySequence.ExactMatch:
1032  intersecting_clips = Clip.filter(intersect=playhead_position)
1033  intersecting_trans = Transition.filter(intersect=playhead_position)
1034  if intersecting_clips or intersecting_trans:
1035  # Get list of clip ids
1036  clip_ids = [c.id for c in intersecting_clips]
1037  trans_ids = [t.id for t in intersecting_trans]
1038  self.timeline.Slice_Triggered(0, clip_ids, trans_ids, playhead_position)
1039  elif key.matches(self.getShortcutByName("sliceAllKeepLeftSide")) == QKeySequence.ExactMatch:
1040  intersecting_clips = Clip.filter(intersect=playhead_position)
1041  intersecting_trans = Transition.filter(intersect=playhead_position)
1042  if intersecting_clips or intersecting_trans:
1043  # Get list of clip ids
1044  clip_ids = [c.id for c in intersecting_clips]
1045  trans_ids = [t.id for t in intersecting_trans]
1046  self.timeline.Slice_Triggered(1, clip_ids, trans_ids, playhead_position)
1047  elif key.matches(self.getShortcutByName("sliceAllKeepRightSide")) == QKeySequence.ExactMatch:
1048  intersecting_clips = Clip.filter(intersect=playhead_position)
1049  intersecting_trans = Transition.filter(intersect=playhead_position)
1050  if intersecting_clips or intersecting_trans:
1051  # Get list of clip ids
1052  clip_ids = [c.id for c in intersecting_clips]
1053  trans_ids = [t.id for t in intersecting_trans]
1054  self.timeline.Slice_Triggered(2, clip_ids, trans_ids, playhead_position)
1055  elif key.matches(self.getShortcutByName("copyAll")) == QKeySequence.ExactMatch:
1056  self.timeline.Copy_Triggered(-1, self.selected_clips, self.selected_transitions)
1057  elif key.matches(self.getShortcutByName("pasteAll")) == QKeySequence.ExactMatch:
1058  self.timeline.Paste_Triggered(9, float(playhead_position), -1, [], [])
1059 
1060  # Select All / None
1061  elif key.matches(self.getShortcutByName("selectAll")) == QKeySequence.ExactMatch:
1062  self.timeline.SelectAll()
1063 
1064  elif key.matches(self.getShortcutByName("selectNone")) == QKeySequence.ExactMatch:
1065  self.timeline.ClearAllSelections()
1066 
1067  # Bubble event on
1068  event.ignore()
1069 
1070 
1071  def actionProfile_trigger(self, event):
1072  # Show dialog
1073  from windows.profile import Profile
1074  win = Profile()
1075  # Run the dialog event loop - blocking interaction on this window during this time
1076  result = win.exec_()
1077  if result == QDialog.Accepted:
1078  log.info('Profile add confirmed')
1079 
1080 
1081  def actionSplitClip_trigger(self, event):
1082  log.info("actionSplitClip_trigger")
1083 
1084  # Loop through selected files (set 1 selected file if more than 1)
1085  f = None
1086  for file_id in self.selected_files:
1087  # Find matching file
1088  f = File.get(id=file_id)
1089 
1090  # Bail out if no file selected
1091  if not f:
1092  log.info(self.selected_files)
1093  return
1094 
1095  # show dialog
1096  from windows.cutting import Cutting
1097  win = Cutting(f)
1098  # Run the dialog event loop - blocking interaction on this window during that time
1099  result = win.exec_()
1100  if result == QDialog.Accepted:
1101  log.info('Cutting Finished')
1102  else:
1103  log.info('Cutting Cancelled')
1104 
1106  log.info("actionRemove_from_Project_trigger")
1107 
1108  # Loop through selected files
1109  for file_id in self.selected_files:
1110  # Find matching file
1111  f = File.get(id=file_id)
1112  if f:
1113  # Remove file
1114  f.delete()
1115 
1116  # Find matching clips (if any)
1117  clips = Clip.filter(file_id=file_id)
1118  for c in clips:
1119  # Remove clip
1120  c.delete()
1121 
1122  # Clear selected files
1123  self.selected_files = []
1124 
1125  def actionRemoveClip_trigger(self, event):
1126  log.info('actionRemoveClip_trigger')
1127 
1128  # Loop through selected clips
1129  for clip_id in deepcopy(self.selected_clips):
1130  # Find matching file
1131  clips = Clip.filter(id=clip_id)
1132  for c in clips:
1133  # Clear selected clips
1134  self.removeSelection(clip_id, "clip")
1135 
1136  # Remove clip
1137  c.delete()
1138 
1139  def actionProperties_trigger(self, event):
1140  log.info('actionProperties_trigger')
1141 
1142  # Show properties dock
1143  if not self.dockProperties.isVisible():
1144  self.dockProperties.show()
1145 
1146  def actionRemoveEffect_trigger(self, event):
1147  log.info('actionRemoveEffect_trigger')
1148 
1149  # Loop through selected clips
1150  for effect_id in deepcopy(self.selected_effects):
1151  log.info("effect id: %s" % effect_id)
1152 
1153  # Find matching file
1154  clips = Clip.filter()
1155  found_effect = None
1156  for c in clips:
1157  found_effect = False
1158  log.info("c.data[effects]: %s" % c.data["effects"])
1159 
1160  for effect in c.data["effects"]:
1161  if effect["id"] == effect_id:
1162  found_effect = effect
1163  break
1164 
1165  if found_effect:
1166  # Remove found effect from clip data and save clip
1167  c.data["effects"].remove(found_effect)
1168 
1169  # Remove unneeded attributes from JSON
1170  c.data.pop("reader")
1171 
1172  # Save clip
1173  c.save()
1174 
1175  # Clear selected effects
1176  self.removeSelection(effect_id, "effect")
1177 
1179  log.info('actionRemoveTransition_trigger')
1180 
1181  # Loop through selected clips
1182  for tran_id in deepcopy(self.selected_transitions):
1183  # Find matching file
1184  transitions = Transition.filter(id=tran_id)
1185  for t in transitions:
1186  # Clear selected clips
1187  self.removeSelection(tran_id, "transition")
1188 
1189  # Remove transition
1190  t.delete()
1191 
1192  def actionRemoveTrack_trigger(self, event):
1193  log.info('actionRemoveTrack_trigger')
1194 
1195  # Get translation function
1196  _ = get_app()._tr
1197 
1198  track_id = self.selected_tracks[0]
1199  max_track_number = len(get_app().project.get(["layers"]))
1200 
1201  # Get details of selected track
1202  selected_track = Track.get(id=track_id)
1203  selected_track_number = int(selected_track.data["number"])
1204 
1205  # Don't allow user to delete final track
1206  if max_track_number == 1:
1207  # Show error and do nothing
1208  QMessageBox.warning(self, _("Error Removing Track"), _("You must keep at least 1 track"))
1209  return
1210 
1211  # Revove all clips on this track first
1212  for clip in Clip.filter(layer=selected_track_number):
1213  clip.delete()
1214 
1215  # Revove all transitions on this track first
1216  for trans in Transition.filter(layer=selected_track_number):
1217  trans.delete()
1218 
1219  # Remove track
1220  selected_track.delete()
1221 
1222  # Loop through all tracks, and renumber (to keep thing in numerical order)
1223  for existing_layer in list(range(selected_track_number + 1, max_track_number)):
1224  # Update existing layer #
1225  track = Track.get(number=existing_layer)
1226  track.data["number"] = existing_layer - 1
1227  track.data["label"] = ""
1228  track.save()
1229 
1230  for clip in Clip.filter(layer=existing_layer):
1231  clip.data["layer"] = int(clip.data["layer"]) - 1
1232  clip.save()
1233 
1234  # Clear selected track
1236 
1237  ##
1238  # Callback for locking a track
1239  def actionLockTrack_trigger(self, event):
1240  log.info('actionLockTrack_trigger')
1241 
1242  # Get details of track
1243  track_id = self.selected_tracks[0]
1244  selected_track = Track.get(id=track_id)
1245 
1246  # Lock track and save
1247  selected_track.data['lock'] = True
1248  selected_track.save()
1249 
1250  ##
1251  # Callback for unlocking a track
1252  def actionUnlockTrack_trigger(self, event):
1253  log.info('actionUnlockTrack_trigger')
1254 
1255  # Get details of track
1256  track_id = self.selected_tracks[0]
1257  selected_track = Track.get(id=track_id)
1258 
1259  # Lock track and save
1260  selected_track.data['lock'] = False
1261  selected_track.save()
1262 
1263  ##
1264  # Callback for renaming track
1265  def actionRenameTrack_trigger(self, event):
1266  log.info('actionRenameTrack_trigger')
1267 
1268  # Get translation function
1269  _ = get_app()._tr
1270 
1271  # Get details of track
1272  track_id = self.selected_tracks[0]
1273  selected_track = Track.get(id=track_id)
1274  track_name = selected_track.data["label"] or _("Track %s") % selected_track.data["number"]
1275 
1276  text, ok = QInputDialog.getText(self, _('Rename Track'), _('Track Name:'), text=track_name)
1277  if ok:
1278  # Update track
1279  selected_track.data["label"] = text
1280  selected_track.save()
1281 
1282  def actionRemoveMarker_trigger(self, event):
1283  log.info('actionRemoveMarker_trigger')
1284 
1285  for marker_id in self.selected_markers:
1286  marker = Marker.filter(id=marker_id)
1287  for m in marker:
1288  # Remove track
1289  m.delete()
1290 
1292  self.sliderZoom.setValue(self.sliderZoom.value() - self.sliderZoom.singleStep())
1293 
1295  self.sliderZoom.setValue(self.sliderZoom.value() + self.sliderZoom.singleStep())
1296 
1297  def actionFullscreen_trigger(self, event):
1298  # Toggle fullscreen mode
1299  if not self.isFullScreen():
1300  self.showFullScreen()
1301  else:
1302  self.showNormal()
1303 
1305  log.info("Show file properties")
1306 
1307  # Loop through selected files (set 1 selected file if more than 1)
1308  f = None
1309  for file_id in self.selected_files:
1310  # Find matching file
1311  f = File.get(id=file_id)
1312 
1313  # show dialog
1314  from windows.file_properties import FileProperties
1315  win = FileProperties(f)
1316  # Run the dialog event loop - blocking interaction on this window during that time
1317  result = win.exec_()
1318  if result == QDialog.Accepted:
1319  log.info('File Properties Finished')
1320  else:
1321  log.info('File Properties Cancelled')
1322 
1323  def actionDetailsView_trigger(self, event):
1324  log.info("Switch to Details View")
1325 
1326  # Get settings
1327  app = get_app()
1328  s = settings.get_settings()
1329 
1330  # Files
1331  if app.context_menu_object == "files":
1332  s.set("file_view", "details")
1333  self.tabFiles.layout().removeWidget(self.filesTreeView)
1334  self.filesTreeView.deleteLater()
1335  self.filesTreeView = None
1336  self.filesTreeView = FilesTreeView(self)
1337  self.tabFiles.layout().addWidget(self.filesTreeView)
1338 
1339  # Transitions
1340  elif app.context_menu_object == "transitions":
1341  s.set("transitions_view", "details")
1342  self.tabTransitions.layout().removeWidget(self.transitionsTreeView)
1343  self.transitionsTreeView.deleteLater()
1345  self.transitionsTreeView = TransitionsTreeView(self)
1346  self.tabTransitions.layout().addWidget(self.transitionsTreeView)
1347 
1348  # Effects
1349  elif app.context_menu_object == "effects":
1350  s.set("effects_view", "details")
1351  self.tabEffects.layout().removeWidget(self.effectsTreeView)
1352  self.effectsTreeView.deleteLater()
1353  self.effectsTreeView = None
1354  self.effectsTreeView = EffectsTreeView(self)
1355  self.tabEffects.layout().addWidget(self.effectsTreeView)
1356 
1358  log.info("Switch to Thumbnail View")
1359 
1360  # Get settings
1361  app = get_app()
1362  s = settings.get_settings()
1363 
1364  # Files
1365  if app.context_menu_object == "files":
1366  s.set("file_view", "thumbnail")
1367  self.tabFiles.layout().removeWidget(self.filesTreeView)
1368  self.filesTreeView.deleteLater()
1369  self.filesTreeView = None
1370  self.filesTreeView = FilesListView(self)
1371  self.tabFiles.layout().addWidget(self.filesTreeView)
1372 
1373  # Transitions
1374  elif app.context_menu_object == "transitions":
1375  s.set("transitions_view", "thumbnail")
1376  self.tabTransitions.layout().removeWidget(self.transitionsTreeView)
1377  self.transitionsTreeView.deleteLater()
1378  self.transitionsTreeView = None
1379  self.transitionsTreeView = TransitionsListView(self)
1380  self.tabTransitions.layout().addWidget(self.transitionsTreeView)
1381 
1382  # Effects
1383  elif app.context_menu_object == "effects":
1384  s.set("effects_view", "thumbnail")
1385  self.tabEffects.layout().removeWidget(self.effectsTreeView)
1386  self.effectsTreeView.deleteLater()
1387  self.effectsTreeView = None
1388  self.effectsTreeView = EffectsListView(self)
1389  self.tabEffects.layout().addWidget(self.effectsTreeView)
1390 
1391  def resize_contents(self):
1392  if self.filesTreeView:
1393  self.filesTreeView.resize_contents()
1394 
1395  ##
1396  # Get a list of all dockable widgets
1397  def getDocks(self):
1398  return [self.dockFiles,
1399  self.dockTransitions,
1400  self.dockEffects,
1401  self.dockVideo,
1402  self.dockProperties]
1403 
1404  ##
1405  # Remove all dockable widgets on main screen
1406  def removeDocks(self):
1407  for dock in self.getDocks():
1408  self.removeDockWidget(dock)
1409 
1410  ##
1411  # Add all dockable widgets to the same dock area on the main screen
1412  def addDocks(self, docks, area):
1413  for dock in docks:
1414  self.addDockWidget(area, dock)
1415 
1416  ##
1417  # Float or Un-Float all dockable widgets above main screen
1418  def floatDocks(self, is_floating):
1419  for dock in self.getDocks():
1420  dock.setFloating(is_floating)
1421 
1422  ##
1423  # Show all dockable widgets on the main screen
1424  def showDocks(self, docks):
1425  for dock in docks:
1426  if get_app().window.dockWidgetArea(dock) != Qt.NoDockWidgetArea:
1427  # Only show correctly docked widgets
1428  dock.show()
1429 
1430  ##
1431  # Freeze all dockable widgets on the main screen (no float, moving, or closing)
1432  def freezeDocks(self):
1433  for dock in self.getDocks():
1434  dock.setFeatures(QDockWidget.NoDockWidgetFeatures)
1435 
1436  ##
1437  # Un-freeze all dockable widgets on the main screen (allow them to be moved, closed, and floated)
1438  def unFreezeDocks(self):
1439  for dock in self.getDocks():
1440  dock.setFeatures(QDockWidget.AllDockWidgetFeatures)
1441 
1442  ##
1443  # Hide all dockable widgets on the main screen
1444  def hideDocks(self):
1445  for dock in self.getDocks():
1446  dock.hide()
1447 
1448  ##
1449  # Switch to the default / simple view
1450  def actionSimple_View_trigger(self, event):
1451  self.removeDocks()
1452  self.addDocks([self.dockFiles, self.dockTransitions, self.dockEffects, self.dockVideo], Qt.TopDockWidgetArea)
1453  self.floatDocks(False)
1454  self.tabifyDockWidget(self.dockFiles, self.dockTransitions)
1455  self.tabifyDockWidget(self.dockTransitions, self.dockEffects)
1456  self.showDocks([self.dockFiles, self.dockTransitions, self.dockEffects, self.dockVideo])
1457 
1458  # Set initial size of docks
1459  simple_state = "AAAA/wAAAAD9AAAAAwAAAAAAAAD8AAAA9PwCAAAAAfwAAAILAAAA9AAAAAAA////+v////8CAAAAAvsAAAAcAGQAbwBjAGsAUAByAG8AcABlAHIAdABpAGUAcwAAAAAA/////wAAAKEA////+wAAABgAZABvAGMAawBLAGUAeQBmAHIAYQBtAGUAAAAAAP////8AAAATAP///wAAAAEAAAEcAAABQPwCAAAAAfsAAAAYAGQAbwBjAGsASwBlAHkAZgByAGEAbQBlAQAAAVgAAAAVAAAAAAAAAAAAAAACAAAEqwAAAdz8AQAAAAL8AAAAAAAAAWQAAAB7AP////oAAAAAAgAAAAP7AAAAEgBkAG8AYwBrAEYAaQBsAGUAcwEAAAAA/////wAAAJgA////+wAAAB4AZABvAGMAawBUAHIAYQBuAHMAaQB0AGkAbwBuAHMBAAAAAP////8AAACYAP////sAAAAWAGQAbwBjAGsARQBmAGYAZQBjAHQAcwEAAAAA/////wAAAJgA////+wAAABIAZABvAGMAawBWAGkAZABlAG8BAAABagAAA0EAAAA6AP///wAABKsAAAD2AAAABAAAAAQAAAAIAAAACPwAAAABAAAAAgAAAAEAAAAOAHQAbwBvAGwAQgBhAHIBAAAAAP////8AAAAAAAAAAA=="
1460  self.restoreState(qt_types.str_to_bytes(simple_state))
1461  QCoreApplication.processEvents()
1462 
1463 
1464  ##
1465  # Switch to an alternative view
1467  self.removeDocks()
1468 
1469  # Add Docks
1470  self.addDocks([self.dockFiles, self.dockTransitions, self.dockVideo], Qt.TopDockWidgetArea)
1471  self.addDocks([self.dockEffects], Qt.RightDockWidgetArea)
1472  self.addDocks([self.dockProperties], Qt.LeftDockWidgetArea)
1473 
1474  self.floatDocks(False)
1475  self.showDocks([self.dockFiles, self.dockTransitions, self.dockVideo, self.dockEffects, self.dockProperties])
1476 
1477  # Set initial size of docks
1478  advanced_state = "AAAA/wAAAAD9AAAAAwAAAAAAAAD8AAABQPwCAAAAAfwAAAG/AAABQAAAAKEA////+gAAAAACAAAAAvsAAAAcAGQAbwBjAGsAUAByAG8AcABlAHIAdABpAGUAcwEAAAAA/////wAAAKEA////+wAAABgAZABvAGMAawBLAGUAeQBmAHIAYQBtAGUAAAAAAP////8AAAATAP///wAAAAEAAAEcAAABQPwCAAAAAvsAAAAYAGQAbwBjAGsASwBlAHkAZgByAGEAbQBlAQAAAVgAAAAVAAAAAAAAAAD7AAAAFgBkAG8AYwBrAEUAZgBmAGUAYwB0AHMBAAABvwAAAUAAAACYAP///wAAAAIAAASrAAABkvwBAAAAA/sAAAASAGQAbwBjAGsARgBpAGwAZQBzAQAAAAAAAAFeAAAAcAD////7AAAAHgBkAG8AYwBrAFQAcgBhAG4AcwBpAHQAaQBvAG4AcwEAAAFkAAABAAAAAHAA////+wAAABIAZABvAGMAawBWAGkAZABlAG8BAAACagAAAkEAAAA6AP///wAAAocAAAFAAAAABAAAAAQAAAAIAAAACPwAAAABAAAAAgAAAAEAAAAOAHQAbwBvAGwAQgBhAHIBAAAAAP////8AAAAAAAAAAA=="
1479  self.restoreState(qt_types.str_to_bytes(advanced_state))
1480  QCoreApplication.processEvents()
1481 
1482  ##
1483  # Freeze all dockable widgets on the main screen
1484  def actionFreeze_View_trigger(self, event):
1485  self.freezeDocks()
1486  self.actionFreeze_View.setVisible(False)
1487  self.actionUn_Freeze_View.setVisible(True)
1488 
1489  ##
1490  # Un-Freeze all dockable widgets on the main screen
1492  self.unFreezeDocks()
1493  self.actionFreeze_View.setVisible(True)
1494  self.actionUn_Freeze_View.setVisible(False)
1495 
1496  ##
1497  # Show all dockable widgets
1498  def actionShow_All_trigger(self, event):
1499  self.showDocks(self.getDocks())
1500 
1501  ##
1502  # Show tutorial again
1503  def actionTutorial_trigger(self, event):
1504  s = settings.get_settings()
1505 
1506  # Clear tutorial settings
1507  s.set("tutorial_enabled", True)
1508  s.set("tutorial_ids", "")
1509 
1510  # Show first tutorial dialog again
1511  if self.tutorial_manager:
1512  self.tutorial_manager.exit_manager()
1513  self.tutorial_manager = TutorialManager(self)
1514 
1515  ##
1516  # Set the window title based on a variety of factors
1517  def SetWindowTitle(self, profile=None):
1518 
1519  # Get translation function
1520  _ = get_app()._tr
1521 
1522  if not profile:
1523  profile = get_app().project.get(["profile"])
1524 
1525  # Is this a saved project?
1526  if not get_app().project.current_filepath:
1527  # Not saved yet
1528  self.setWindowTitle("%s [%s] - %s" % (_("Untitled Project"), profile, "OpenShot Video Editor"))
1529  else:
1530  # Yes, project is saved
1531  # Get just the filename
1532  parent_path, filename = os.path.split(get_app().project.current_filepath)
1533  filename, ext = os.path.splitext(filename)
1534  filename = filename.replace("_", " ").replace("-", " ").capitalize()
1535  self.setWindowTitle("%s [%s] - %s" % (filename, profile, "OpenShot Video Editor"))
1536 
1537  # Update undo and redo buttons enabled/disabled to available changes
1538  def updateStatusChanged(self, undo_status, redo_status):
1539  log.info('updateStatusChanged')
1540  self.actionUndo.setEnabled(undo_status)
1541  self.actionRedo.setEnabled(redo_status)
1542 
1543  # Add to the selected items
1544  def addSelection(self, item_id, item_type, clear_existing=False):
1545  log.info('main::addSelection: item_id: %s, item_type: %s, clear_existing: %s' % (item_id, item_type, clear_existing))
1546 
1547  # Clear existing selection (if needed)
1548  if clear_existing:
1549  if item_type == "clip":
1550  self.selected_clips.clear()
1551  elif item_type == "transition":
1552  self.selected_transitions.clear()
1553  elif item_type == "effect":
1554  self.selected_effects.clear()
1555 
1556  if item_id:
1557  # If item_id is not blank, store it
1558  if item_type == "clip" and item_id not in self.selected_clips:
1559  self.selected_clips.append(item_id)
1560  elif item_type == "transition" and item_id not in self.selected_transitions:
1561  self.selected_transitions.append(item_id)
1562  elif item_type == "effect" and item_id not in self.selected_effects:
1563  self.selected_effects.append(item_id)
1564 
1565  # Change selected item in properties view
1566  self.propertyTableView.loadProperties.emit(item_id, item_type)
1567 
1568  # Remove from the selected items
1569  def removeSelection(self, item_id, item_type):
1570  if item_type == "clip" and item_id in self.selected_clips:
1571  self.selected_clips.remove(item_id)
1572  elif item_type == "transition" and item_id in self.selected_transitions:
1573  self.selected_transitions.remove(item_id)
1574  elif item_type == "effect" and item_id in self.selected_effects:
1575  self.selected_effects.remove(item_id)
1576 
1577  # Move selection to next selected clip (if any)
1578  if item_type == "clip" and self.selected_clips:
1579  self.propertyTableView.loadProperties.emit(self.selected_clips[0], item_type)
1580  elif item_type == "transition" and self.selected_transitions:
1581  self.propertyTableView.loadProperties.emit(self.selected_transitions[0], item_type)
1582  elif item_type == "effect" and self.selected_effects:
1583  self.propertyTableView.loadProperties.emit(self.selected_effects[0], item_type)
1584  else:
1585  # Clear selection in properties view
1586  self.propertyTableView.loadProperties.emit("", "")
1587 
1588  # Update window settings in setting store
1589  def save_settings(self):
1590  s = settings.get_settings()
1591 
1592  # Save window state and geometry (saves toolbar and dock locations)
1593  s.set('window_state', qt_types.bytes_to_str(self.saveState()))
1594  s.set('window_geometry', qt_types.bytes_to_str(self.saveGeometry()))
1595 
1596  # Get window settings from setting store
1597  def load_settings(self):
1598  s = settings.get_settings()
1599 
1600  # Window state and geometry (also toolbar and dock locations)
1601  if s.get('window_geometry'): self.restoreGeometry(qt_types.str_to_bytes(s.get('window_geometry')))
1602  if s.get('window_state'): self.restoreState(qt_types.str_to_bytes(s.get('window_state')))
1603 
1604  # Load Recent Projects
1605  self.load_recent_menu()
1606 
1607  ##
1608  # Clear and load the list of recent menu items
1609  def load_recent_menu(self):
1610  s = settings.get_settings()
1611  _ = get_app()._tr # Get translation function
1612 
1613  # Get list of recent projects
1614  recent_projects = s.get("recent_projects")
1615 
1616  # Add Recent Projects menu (after Open File)
1617  import functools
1618  if not self.recent_menu:
1619  # Create a new recent menu
1620  self.recent_menu = self.menuFile.addMenu(QIcon.fromTheme("document-open-recent"), _("Recent Projects"))
1621  self.menuFile.insertMenu(self.actionRecent_Placeholder, self.recent_menu)
1622  else:
1623  # Clear the existing children
1624  self.recent_menu.clear()
1625 
1626  # Add recent projects to menu
1627  for file_path in reversed(recent_projects):
1628  new_action = self.recent_menu.addAction(file_path)
1629  new_action.triggered.connect(functools.partial(self.recent_project_clicked, file_path))
1630 
1631  ##
1632  # Load a recent project when clicked
1633  def recent_project_clicked(self, file_path):
1634 
1635  # Load project file
1636  self.open_project(file_path)
1637 
1638  def setup_toolbars(self):
1639  _ = get_app()._tr # Get translation function
1640 
1641  # Start undo and redo actions disabled
1642  self.actionUndo.setEnabled(False)
1643  self.actionRedo.setEnabled(False)
1644 
1645  # Add files toolbar =================================================================================
1646  self.filesToolbar = QToolBar("Files Toolbar")
1647  self.filesActionGroup = QActionGroup(self)
1648  self.filesActionGroup.setExclusive(True)
1649  self.filesActionGroup.addAction(self.actionFilesShowAll)
1650  self.filesActionGroup.addAction(self.actionFilesShowVideo)
1651  self.filesActionGroup.addAction(self.actionFilesShowAudio)
1652  self.filesActionGroup.addAction(self.actionFilesShowImage)
1653  self.actionFilesShowAll.setChecked(True)
1654  self.filesToolbar.addAction(self.actionFilesShowAll)
1655  self.filesToolbar.addAction(self.actionFilesShowVideo)
1656  self.filesToolbar.addAction(self.actionFilesShowAudio)
1657  self.filesToolbar.addAction(self.actionFilesShowImage)
1658  self.filesFilter = QLineEdit()
1659  self.filesFilter.setObjectName("filesFilter")
1660  self.filesFilter.setPlaceholderText(_("Filter"))
1661  self.filesToolbar.addWidget(self.filesFilter)
1662  self.actionFilesClear.setEnabled(False)
1663  self.filesToolbar.addAction(self.actionFilesClear)
1664  self.tabFiles.layout().addWidget(self.filesToolbar)
1665 
1666  # Add transitions toolbar =================================================================================
1667  self.transitionsToolbar = QToolBar("Transitions Toolbar")
1668  self.transitionsActionGroup = QActionGroup(self)
1669  self.transitionsActionGroup.setExclusive(True)
1670  self.transitionsActionGroup.addAction(self.actionTransitionsShowAll)
1671  self.transitionsActionGroup.addAction(self.actionTransitionsShowCommon)
1672  self.actionTransitionsShowAll.setChecked(True)
1673  self.transitionsToolbar.addAction(self.actionTransitionsShowAll)
1674  self.transitionsToolbar.addAction(self.actionTransitionsShowCommon)
1675  self.transitionsFilter = QLineEdit()
1676  self.transitionsFilter.setObjectName("transitionsFilter")
1677  self.transitionsFilter.setPlaceholderText(_("Filter"))
1678  self.transitionsToolbar.addWidget(self.transitionsFilter)
1679  self.actionTransitionsClear.setEnabled(False)
1680  self.transitionsToolbar.addAction(self.actionTransitionsClear)
1681  self.tabTransitions.layout().addWidget(self.transitionsToolbar)
1682 
1683  # Add effects toolbar =================================================================================
1684  self.effectsToolbar = QToolBar("Effects Toolbar")
1685  self.effectsActionGroup = QActionGroup(self)
1686  self.effectsActionGroup.setExclusive(True)
1687  self.effectsActionGroup.addAction(self.actionEffectsShowAll)
1688  self.effectsActionGroup.addAction(self.actionEffectsShowVideo)
1689  self.effectsActionGroup.addAction(self.actionEffectsShowAudio)
1690  self.actionEffectsShowAll.setChecked(True)
1691  self.effectsToolbar.addAction(self.actionEffectsShowAll)
1692  self.effectsToolbar.addAction(self.actionEffectsShowVideo)
1693  self.effectsToolbar.addAction(self.actionEffectsShowAudio)
1694  self.effectsFilter = QLineEdit()
1695  self.effectsFilter.setObjectName("effectsFilter")
1696  self.effectsFilter.setPlaceholderText(_("Filter"))
1697  self.effectsToolbar.addWidget(self.effectsFilter)
1698  self.actionEffectsClear.setEnabled(False)
1699  self.effectsToolbar.addAction(self.actionEffectsClear)
1700  self.tabEffects.layout().addWidget(self.effectsToolbar)
1701 
1702  # Add Video Preview toolbar ==========================================================================
1703  self.videoToolbar = QToolBar("Video Toolbar")
1704 
1705  # Add left spacer
1706  spacer = QWidget(self)
1707  spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
1708  self.videoToolbar.addWidget(spacer)
1709 
1710  # Playback controls
1711  self.videoToolbar.addAction(self.actionJumpStart)
1712  self.videoToolbar.addAction(self.actionRewind)
1713  self.videoToolbar.addAction(self.actionPlay)
1714  self.videoToolbar.addAction(self.actionFastForward)
1715  self.videoToolbar.addAction(self.actionJumpEnd)
1716  self.actionPlay.setCheckable(True)
1717 
1718  # Add right spacer
1719  spacer = QWidget(self)
1720  spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
1721  self.videoToolbar.addWidget(spacer)
1722 
1723  self.tabVideo.layout().addWidget(self.videoToolbar)
1724 
1725  # Add Timeline toolbar ================================================================================
1726  self.timelineToolbar = QToolBar("Timeline Toolbar", self)
1727 
1728  self.timelineToolbar.addAction(self.actionAddTrack)
1729  self.timelineToolbar.addSeparator()
1730 
1731  # rest of options
1732  self.timelineToolbar.addAction(self.actionSnappingTool)
1733  self.timelineToolbar.addSeparator()
1734  self.timelineToolbar.addAction(self.actionAddMarker)
1735  self.timelineToolbar.addAction(self.actionPreviousMarker)
1736  self.timelineToolbar.addAction(self.actionNextMarker)
1737  self.timelineToolbar.addSeparator()
1738 
1739  # Setup Zoom slider
1740  self.sliderZoom = QSlider(Qt.Horizontal, self)
1741  self.sliderZoom.setPageStep(6)
1742  self.sliderZoom.setRange(8, 800)
1743  self.sliderZoom.setValue(20)
1744  self.sliderZoom.setInvertedControls(True)
1745  self.sliderZoom.resize(100, 16)
1746 
1747  self.zoomScaleLabel = QLabel(_("{} seconds").format(self.sliderZoom.value()))
1748 
1749  # add zoom widgets
1750  self.timelineToolbar.addAction(self.actionTimelineZoomIn)
1751  self.timelineToolbar.addWidget(self.sliderZoom)
1752  self.timelineToolbar.addAction(self.actionTimelineZoomOut)
1753  self.timelineToolbar.addWidget(self.zoomScaleLabel)
1754 
1755  # Add timeline toolbar to web frame
1756  self.frameWeb.addWidget(self.timelineToolbar)
1757 
1758  # Add spacer and 'New Version Available' toolbar button (default hidden)
1759  spacer = QWidget(self)
1760  spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
1761  self.toolBar.addWidget(spacer)
1762  self.toolBar.addAction(self.actionUpdate)
1763 
1764  ##
1765  # Clear all selection containers
1766  def clearSelections(self):
1767  self.selected_files = []
1768  self.selected_clips = []
1771  self.selected_tracks = []
1773 
1774  # Clear selection in properties view
1775  if self.propertyTableView:
1776  self.propertyTableView.loadProperties.emit("", "")
1777 
1778  ##
1779  # Handle the callback for detecting the current version on openshot.org
1780  def foundCurrentVersion(self, version):
1781  log.info('foundCurrentVersion: Found the latest version: %s' % version)
1782  _ = get_app()._tr
1783 
1784  # Compare versions (alphabetical compare of version strings should work fine)
1785  if info.VERSION < version:
1786  # Display upgrade toolbar button
1787  self.actionUpdate.setVisible(True)
1788  self.actionUpdate.setText(_("New Version Available: %s") % version)
1789  self.actionUpdate.setToolTip(_("New Version Available: %s") % version)
1790 
1791  ##
1792  # Move tutorial dialogs also (if any)
1793  def moveEvent(self, event):
1794  if self.tutorial_manager:
1795  self.tutorial_manager.re_position_dialog()
1796 
1797  ##
1798  # Filter out certain types of window events
1799  def eventFilter(self, object, e):
1800  if e.type() == QEvent.WindowActivate:
1801  self.tutorial_manager.re_show_dialog()
1802  elif e.type() == QEvent.WindowStateChange and self.isMinimized():
1803  self.tutorial_manager.minimize()
1804 
1805  return False
1806 
1807  ##
1808  # Initialize all keyboard shortcuts from the settings file
1810 
1811  # Translate object
1812  _ = get_app()._tr
1813 
1814  # Update all action-based shortcuts (from settings file)
1815  for shortcut in self.getAllKeyboardShortcuts():
1816  for action in self.findChildren(QAction):
1817  if shortcut.get('setting') == action.objectName():
1818  action.setShortcut(QKeySequence(_(shortcut.get('value'))))
1819 
1820  def __init__(self):
1821 
1822  # Create main window base class
1823  QMainWindow.__init__(self)
1824 
1825  # set window on app for reference during initialization of children
1826  get_app().window = self
1827  _ = get_app()._tr
1828 
1829  # Track metrics
1830  track_metric_session() # start session
1831  track_metric_screen("main-screen")
1832 
1833  # Create blank tutorial manager
1834  self.tutorial_manager = None
1835 
1836  # Load UI from designer
1837  ui_util.load_ui(self, self.ui_path)
1838 
1839  # Set all keyboard shortcuts from the settings file
1840  self.InitKeyboardShortcuts()
1841 
1842  # Load user settings for window
1843  s = settings.get_settings()
1844  self.recent_menu = None
1845 
1846  # Init UI
1847  ui_util.init_ui(self)
1848 
1849  # Setup toolbars that aren't on main window, set initial state of items, etc
1850  self.setup_toolbars()
1851 
1852  # Add window as watcher to receive undo/redo status updates
1853  get_app().updates.add_watcher(self)
1854 
1855  # Get current version of OpenShot via HTTP
1856  self.FoundVersionSignal.connect(self.foundCurrentVersion)
1858 
1859  # Connect signals
1860  self.RecoverBackup.connect(self.recover_backup)
1861 
1862  # Setup timeline
1863  self.timeline = TimelineWebView(self)
1864  self.frameWeb.layout().addWidget(self.timeline)
1865 
1866  # Set Window title
1867  self.SetWindowTitle()
1868 
1869  # Setup files tree
1870  if s.get("file_view") == "details":
1871  self.filesTreeView = FilesTreeView(self)
1872  else:
1873  self.filesTreeView = FilesListView(self)
1874  self.tabFiles.layout().addWidget(self.filesTreeView)
1875 
1876  # Setup transitions tree
1877  if s.get("transitions_view") == "details":
1878  self.transitionsTreeView = TransitionsTreeView(self)
1879  else:
1880  self.transitionsTreeView = TransitionsListView(self)
1881  self.tabTransitions.layout().addWidget(self.transitionsTreeView)
1882 
1883  # Setup effects tree
1884  if s.get("effects_view") == "details":
1885  self.effectsTreeView = EffectsTreeView(self)
1886  else:
1887  self.effectsTreeView = EffectsListView(self)
1888  self.tabEffects.layout().addWidget(self.effectsTreeView)
1889 
1890  # Setup properties table
1891  self.txtPropertyFilter.setPlaceholderText(_("Filter"))
1892  self.propertyTableView = PropertiesTableView(self)
1893  self.selectionLabel = SelectionLabel(self)
1894  self.dockPropertiesContent.layout().addWidget(self.selectionLabel, 0, 1)
1895  self.dockPropertiesContent.layout().addWidget(self.propertyTableView, 2, 1)
1896 
1897  # Init selection containers
1898  self.clearSelections()
1899 
1900  # Setup video preview QWidget
1901  self.videoPreview = VideoWidget()
1902  self.tabVideo.layout().insertWidget(0, self.videoPreview)
1903 
1904  # Load window state and geometry
1905  self.load_settings()
1906 
1907  # Create the timeline sync object (used for previewing timeline)
1908  self.timeline_sync = TimelineSync(self)
1909 
1910  # Start the preview thread
1911  self.preview_parent = PreviewParent()
1912  self.preview_parent.Init(self, self.timeline_sync.timeline, self.videoPreview)
1913  self.preview_thread = self.preview_parent.worker
1914 
1915  # QTimer for Autosave
1916  self.auto_save_timer = QTimer(self)
1917  self.auto_save_timer.setInterval(s.get("autosave-interval") * 1000 * 60)
1918  self.auto_save_timer.timeout.connect(self.auto_save_project)
1919  if s.get("enable-auto-save"):
1920  self.auto_save_timer.start()
1921 
1922  # Create lock file
1923  self.create_lock_file()
1924 
1925  # Show window
1926  self.show()
1927 
1928  # Create tutorial manager
1929  self.tutorial_manager = TutorialManager(self)
1930 
1931  # Install event filter
1932  self.installEventFilter(self)
def moveEvent(self, event)
Move tutorial dialogs also (if any)
jQuery Animation
Definition: jquery.js:9033
def actionProperties_trigger(self, event)
def addSelection(self, item_id, item_type, clear_existing=False)
def actionThumbnailView_trigger(self, event)
def setup_icon(window, elem, name, theme_name=None)
Using the window xml, set the icon on the given element, or if theme_name passed load that icon...
Definition: ui_util.py:129
def actionTitle_trigger(self, event)
Definition: main_window.py:257
def actionPreferences_trigger(self, event)
Definition: main_window.py:514
def actionAddTrack_trigger(self, event)
Definition: main_window.py:726
def actionRemove_from_Project_trigger(self, event)
def get_app()
Returns the current QApplication instance of OpenShot.
Definition: app.py:54
def updateStatusChanged(self, undo_status, redo_status)
Easily be notified each time there are &#39;undo&#39; or &#39;redo&#39; actions available in the UpdateManager.
Definition: updates.py:45
def addDocks(self, docks, area)
Add all dockable widgets to the same dock area on the main screen.
def actionExportVideo_trigger(self, event)
Definition: main_window.py:493
def actionDonate_trigger(self, event)
Definition: main_window.py:597
def actionRedo_trigger(self, event)
Definition: main_window.py:509
def actionRemoveTransition_trigger(self, event)
def actionSnappingTool_trigger(self, event)
Definition: main_window.py:790
def actionUnlockTrack_trigger(self, event)
Callback for unlocking a track.
def actionTransitionsShowCommon_trigger(self, event)
Definition: main_window.py:540
def actionUndo_trigger(self, event)
Definition: main_window.py:504
def freezeDocks(self)
Freeze all dockable widgets on the main screen (no float, moving, or closing)
def track_metric_session(is_start=True)
Track a GUI screen being shown.
Definition: metrics.py:130
def actionImportImageSequence_trigger(self, event)
Definition: main_window.py:268
def actionFilesShowImage_trigger(self, event)
Definition: main_window.py:534
def actionRemoveTrack_trigger(self, event)
def InitKeyboardShortcuts(self)
Initialize all keyboard shortcuts from the settings file.
def actionUploadVideo_trigger(self, event)
Definition: main_window.py:482
def actionAdd_to_Timeline_trigger(self, event)
Definition: main_window.py:459
def actionFullscreen_trigger(self, event)
def get_current_Version()
Get the current version.
Definition: version.py:42
def actionAddTrackAbove_trigger(self, event)
Definition: main_window.py:737
def actionFile_Properties_trigger(self, event)
def actionSave_trigger(self, event)
Definition: main_window.py:385
def actionArrowTool_trigger(self, event)
Definition: main_window.py:787
def getAllKeyboardShortcuts(self)
Get a key sequence back from the setting name.
Definition: main_window.py:883
def actionRemoveMarker_trigger(self, event)
def keyPressEvent(self, event)
Process key press events and match with known shortcuts.
Definition: main_window.py:893
def actionPreview_File_trigger(self, event)
Preview the selected media file.
Definition: main_window.py:643
def actionRewind_trigger(self, event)
Definition: main_window.py:688
def actionNew_trigger(self, event)
Definition: main_window.py:217
def load_recent_menu(self)
Clear and load the list of recent menu items.
def actionOpen_trigger(self, event)
Definition: main_window.py:374
def actionLockTrack_trigger(self, event)
Callback for locking a track.
def str_to_bytes(string)
This is required to save Qt byte arrays into a base64 string (to save screen preferences) ...
Definition: qt_types.py:39
def actionUn_Freeze_View_trigger(self, event)
Un-Freeze all dockable widgets on the main screen.
def create_lock_file(self)
Create a lock file.
Definition: main_window.py:156
def save_project(self, file_path)
Save a project to a file path, and refresh the screen.
Definition: main_window.py:281
def actionTimelineZoomIn_trigger(self, event)
def actionTransitionsShowAll_trigger(self, event)
Definition: main_window.py:537
def actionFreeze_View_trigger(self, event)
Freeze all dockable widgets on the main screen.
def eventFilter(self, object, e)
Filter out certain types of window events.
def unFreezeDocks(self)
Un-freeze all dockable widgets on the main screen (allow them to be moved, closed, and floated)
def actionAbout_trigger(self, event)
Show about dialog.
Definition: main_window.py:563
def floatDocks(self, is_floating)
Float or Un-Float all dockable widgets above main screen.
def clearSelections(self)
Clear all selection containers.
def actionReportBug_trigger(self, event)
Definition: main_window.py:573
def actionHelpContents_trigger(self, event)
Definition: main_window.py:552
def actionTutorial_trigger(self, event)
Show tutorial again.
def actionRenameTrack_trigger(self, event)
Callback for renaming track.
def removeDocks(self)
Remove all dockable widgets on main screen.
def showDocks(self, docks)
Show all dockable widgets on the main screen.
def actionEffectsShowAudio_trigger(self, event)
Definition: main_window.py:549
def actionUpdate_trigger(self, event)
Definition: main_window.py:605
def auto_save_project(self)
Auto save the project.
Definition: main_window.py:405
def bytes_to_str(bytes)
This is required to load base64 Qt byte array strings into a Qt byte array (to load screen preference...
Definition: qt_types.py:45
def actionFilesShowVideo_trigger(self, event)
Definition: main_window.py:528
def actionEffectsShowVideo_trigger(self, event)
Definition: main_window.py:546
def foundCurrentVersion(self, version)
Handle the callback for detecting the current version on openshot.org.
def actionDetailsView_trigger(self, event)
def actionFilesShowAll_trigger(self, event)
Definition: main_window.py:525
def actionAnimation_trigger(self, event)
Definition: main_window.py:246
def previewFrame(self, position_seconds, position_frames, time_code)
Preview a specific frame.
Definition: main_window.py:662
def actionProfile_trigger(self, event)
def hideDocks(self)
Hide all dockable widgets on the main screen.
def actionAskQuestion_trigger(self, event)
Definition: main_window.py:581
def recent_project_clicked(self, file_path)
Load a recent project when clicked.
def get_settings()
Get the current QApplication&#39;s settings instance.
Definition: settings.py:43
def actionSimple_View_trigger(self, event)
Switch to the default / simple view.
def actionAdvanced_View_trigger(self, event)
Switch to an alternative view.
def clear_all_thumbnails(self)
Clear all user thumbnails.
Definition: main_window.py:347
def SetWindowTitle(self, profile=None)
Set the window title based on a variety of factors.
Interface for classes that listen for &#39;undo&#39; and &#39;redo&#39; events.
Definition: updates.py:41
def actionNextMarker_trigger(self, event)
Definition: main_window.py:844
def actionEffectsShowAll_trigger(self, event)
Definition: main_window.py:543
def actionAddMarker_trigger(self, event)
Definition: main_window.py:797
def movePlayhead(self, position_frames)
Update playhead position.
Definition: main_window.py:671
def actionRemoveClip_trigger(self, event)
def init_ui(window)
Initialize all child widgets and action of a window or dialog.
Definition: ui_util.py:200
def recover_backup(self)
Recover the backup file (if any)
Definition: main_window.py:127
def destroy_lock_file(self)
Destroy the lock file.
Definition: main_window.py:209
def getShortcutByName(self, setting_name)
Get a key sequence back from the setting name.
Definition: main_window.py:876
This class contains the logic for the main window widget.
Definition: main_window.py:65
def track_metric_screen(screen_name)
Track a GUI screen being shown.
Definition: metrics.py:94
def actionPreviousMarker_trigger(self, event)
Definition: main_window.py:815
def updateStatusChanged(self, undo_status, redo_status)
def actionAddTrackBelow_trigger(self, event)
Definition: main_window.py:762
def open_project(self, file_path, clear_thumbnails=True)
Open a project from a file path, and refresh the screen.
Definition: main_window.py:303
def actionSaveAs_trigger(self, event)
Definition: main_window.py:431
def track_metric_error(error_name, is_fatal=False)
Track an error has occurred.
Definition: metrics.py:117
def removeSelection(self, item_id, item_type)
def actionRemoveEffect_trigger(self, event)
def actionTranslate_trigger(self, event)
Definition: main_window.py:589
def actionPlay_trigger(self, event, force=None)
Definition: main_window.py:613
def actionFastForward_trigger(self, event)
Definition: main_window.py:675
def actionFilesShowAudio_trigger(self, event)
Definition: main_window.py:531
def load_ui(window, path)
Load a Qt *.ui file, and also load an XML parsed version.
Definition: ui_util.py:65
def actionTimelineZoomOut_trigger(self, event)
def actionShow_All_trigger(self, event)
Show all dockable widgets.
def actionAnimatedTitle_trigger(self, event)
Definition: main_window.py:235
Interface for classes that listen for changes (insert, update, and delete).
Definition: updates.py:51
def actionJumpEnd_trigger(self, event)
Definition: main_window.py:707
def actionSplitClip_trigger(self, event)
def closeEvent(self, event)
Definition: main_window.py:83
def getDocks(self)
Get a list of all dockable widgets.
def actionImportFiles_trigger(self, event)
Definition: main_window.py:447
def actionJumpStart_trigger(self, event)
Definition: main_window.py:701