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-2018 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 sys
33 import platform
34 import shutil
35 import webbrowser
36 from uuid import uuid4
37 from copy import deepcopy
38 
39 from PyQt5.QtCore import *
40 from PyQt5.QtGui import QIcon, QCursor, QKeySequence
41 from PyQt5.QtWidgets import *
42 import openshot # Python module for libopenshot (required video editing module installed separately)
43 
44 from windows.views.timeline_webview import TimelineWebView
45 from classes import info, ui_util, settings, qt_types, updates
46 from classes.app import get_app
47 from classes.logger import log
48 from classes.timeline import TimelineSync
49 from classes.query import File, Clip, Transition, Marker, Track
50 from classes.metrics import *
51 from classes.version import *
52 from classes.conversion import zoomToSeconds, secondsToZoom
53 from images import openshot_rc
54 from windows.views.files_treeview import FilesTreeView
55 from windows.views.files_listview import FilesListView
56 from windows.views.transitions_treeview import TransitionsTreeView
57 from windows.views.transitions_listview import TransitionsListView
58 from windows.views.effects_treeview import EffectsTreeView
59 from windows.views.effects_listview import EffectsListView
60 from windows.views.properties_tableview import PropertiesTableView, SelectionLabel
61 from windows.views.tutorial import TutorialManager
62 from windows.video_widget import VideoWidget
63 from windows.preview_thread import PreviewParent
64 
65 
66 ##
67 # This class contains the logic for the main window widget
68 class MainWindow(QMainWindow, updates.UpdateWatcher):
69 
70  # Path to ui file
71  ui_path = os.path.join(info.PATH, 'windows', 'ui', 'main-window.ui')
72 
73  previewFrameSignal = pyqtSignal(int)
74  refreshFrameSignal = pyqtSignal()
75  LoadFileSignal = pyqtSignal(str)
76  PlaySignal = pyqtSignal(int)
77  PauseSignal = pyqtSignal()
78  StopSignal = pyqtSignal()
79  SeekSignal = pyqtSignal(int)
80  SpeedSignal = pyqtSignal(float)
81  RecoverBackup = pyqtSignal()
82  FoundVersionSignal = pyqtSignal(str)
83  WaveformReady = pyqtSignal(str, list)
84  TransformSignal = pyqtSignal(str)
85  ExportStarted = pyqtSignal(str, int, int)
86  ExportFrame = pyqtSignal(str, int, int, int)
87  ExportEnded = pyqtSignal(str)
88  MaxSizeChanged = pyqtSignal(object)
89  InsertKeyframe = pyqtSignal(object)
90 
91  # Save window settings on close
92  def closeEvent(self, event):
93 
94  # Close any tutorial dialogs
95  self.tutorial_manager.exit_manager()
96 
97  # Prompt user to save (if needed)
98  if get_app().project.needs_save() and not self.mode == "unittest":
99  log.info('Prompt user to save project')
100  # Translate object
101  _ = get_app()._tr
102 
103  # Handle exception
104  ret = QMessageBox.question(self, _("Unsaved Changes"), _("Save changes to project before closing?"), QMessageBox.Cancel | QMessageBox.No | QMessageBox.Yes)
105  if ret == QMessageBox.Yes:
106  # Save project
107  self.actionSave_trigger(event)
108  event.accept()
109  elif ret == QMessageBox.Cancel:
110  # User canceled prompt - don't quit
111  event.ignore()
112  return
113 
114  # Save settings
115  self.save_settings()
116 
117  # Track end of session
118  track_metric_session(False)
119 
120  # Stop threads
121  self.StopSignal.emit()
122 
123  # Process any queued events
124  QCoreApplication.processEvents()
125 
126  # Stop preview thread (and wait for it to end)
127  self.preview_thread.player.CloseAudioDevice()
128  self.preview_thread.kill()
129  self.preview_parent.background.exit()
130  self.preview_parent.background.wait(5000)
131 
132  # Close & Stop libopenshot logger
133  openshot.ZmqLogger.Instance().Close()
134  get_app().logger_libopenshot.kill()
135 
136  # Destroy lock file
137  self.destroy_lock_file()
138 
139  ##
140  # Recover the backup file (if any)
141  def recover_backup(self):
142  log.info("recover_backup")
143  # Check for backup.osp file
144  recovery_path = os.path.join(info.BACKUP_PATH, "backup.osp")
145 
146  # Load recovery project
147  if os.path.exists(recovery_path):
148  log.info("Recovering backup file: %s" % recovery_path)
149  self.open_project(recovery_path, clear_thumbnails=False)
150 
151  # Clear the file_path (which is set by saving the project)
152  get_app().project.current_filepath = None
153  get_app().project.has_unsaved_changes = True
154 
155  # Set Window title
156  self.SetWindowTitle()
157 
158  # Show message to user
159  msg = QMessageBox()
160  _ = get_app()._tr
161  msg.setWindowTitle(_("Backup Recovered"))
162  msg.setText(_("Your most recent unsaved project has been recovered."))
163  msg.exec_()
164 
165  else:
166  # No backup project found
167  # Load a blank project (to propagate the default settings)
168  get_app().project.load("")
169  self.actionUndo.setEnabled(False)
170  self.actionRedo.setEnabled(False)
171  self.SetWindowTitle()
172 
173  ##
174  # Create a lock file
175  def create_lock_file(self):
176  lock_path = os.path.join(info.USER_PATH, ".lock")
177  lock_value = str(uuid4())
178 
179  # Check if it already exists
180  if os.path.exists(lock_path):
181  # Walk the libopenshot log (if found), and try and find last line before this launch
182  log_path = os.path.join(info.USER_PATH, "libopenshot.log")
183  last_log_line = ""
184  last_stack_trace = ""
185  found_stack = False
186  log_start_counter = 0
187  if os.path.exists(log_path):
188  with open(log_path, "rb") as f:
189  # Read from bottom up
190  for raw_line in reversed(self.tail_file(f, 500)):
191  line = str(raw_line, 'utf-8')
192  # Detect stack trace
193  if "End of Stack Trace" in line:
194  found_stack = True
195  continue
196  elif "Unhandled Exception: Stack Trace" in line:
197  found_stack = False
198  continue
199  elif "libopenshot logging:" in line:
200  log_start_counter += 1
201  if log_start_counter > 1:
202  # Found the previous log start, too old now
203  break
204 
205  if found_stack:
206  # Append line to beginning of stacktrace
207  last_stack_trace = line + last_stack_trace
208 
209  # Ignore certain unuseful lines
210  if line.strip() and "---" not in line and "libopenshot logging:" not in line and not last_log_line:
211  last_log_line = line
212 
213  # Split last stack trace (if any)
214  if last_stack_trace:
215  # Get top line of stack trace (for metrics)
216  last_log_line = last_stack_trace.split("\n")[0].strip()
217 
218  # Send stacktrace for debugging (if send metrics is enabled)
219  track_exception_stacktrace(last_stack_trace, "libopenshot")
220 
221  # Clear / normalize log line (so we can roll them up in the analytics)
222  if last_log_line:
223  # Format last log line based on OS (since each OS can be formatted differently)
224  if platform.system() == "Darwin":
225  last_log_line = "mac-%s" % last_log_line[58:].strip()
226  elif platform.system() == "Windows":
227  last_log_line = "windows-%s" % last_log_line
228  elif platform.system() == "Linux":
229  last_log_line = "linux-%s" % last_log_line.replace("/usr/local/lib/", "")
230 
231  # Remove '()' from line, and split. Trying to grab the beginning of the log line.
232  last_log_line = last_log_line.replace("()", "")
233  log_parts = last_log_line.split("(")
234  if len(log_parts) == 2:
235  last_log_line = "-%s" % log_parts[0].replace("logger_libopenshot:INFO ", "").strip()[:64]
236  elif len(log_parts) >= 3:
237  last_log_line = "-%s (%s" % (log_parts[0].replace("logger_libopenshot:INFO ", "").strip()[:64], log_parts[1])
238  else:
239  last_log_line = ""
240 
241  # Throw exception (with last libopenshot line... if found)
242  log.error("Unhandled crash detected... will attempt to recover backup project: %s" % info.BACKUP_PATH)
243  track_metric_error("unhandled-crash%s" % last_log_line, True)
244 
245  # Remove file
246  self.destroy_lock_file()
247 
248  else:
249  # Normal startup, clear thumbnails
250  self.clear_all_thumbnails()
251 
252  # Create lock file
253  with open(lock_path, 'w') as f:
254  f.write(lock_value)
255 
256  ##
257  # Destroy the lock file
258  def destroy_lock_file(self):
259  lock_path = os.path.join(info.USER_PATH, ".lock")
260 
261  # Check if it already exists
262  if os.path.exists(lock_path):
263  # Remove file
264  os.remove(lock_path)
265 
266  ##
267  # Read the end of a file (n number of lines)
268  def tail_file(self, f, n, offset=None):
269  avg_line_length = 90
270  to_read = n + (offset or 0)
271 
272  while True:
273  try:
274  # Seek to byte position
275  f.seek(-(avg_line_length * to_read), 2)
276  except IOError:
277  # Byte position not found
278  f.seek(0)
279  pos = f.tell()
280  lines = f.read().splitlines()
281  if len(lines) >= to_read or pos == 0:
282  # Return the lines
283  return lines[-to_read:offset and -offset or None]
284  avg_line_length *= 2
285 
286  def actionNew_trigger(self, event):
287 
288  app = get_app()
289  _ = app._tr # Get translation function
290 
291  # Do we have unsaved changes?
292  if get_app().project.needs_save():
293  ret = QMessageBox.question(self, _("Unsaved Changes"), _("Save changes to project first?"), QMessageBox.Cancel | QMessageBox.No | QMessageBox.Yes)
294  if ret == QMessageBox.Yes:
295  # Save project
296  self.actionSave_trigger(event)
297  elif ret == QMessageBox.Cancel:
298  # User canceled prompt
299  return
300 
301  # Clear any previous thumbnails
302  self.clear_all_thumbnails()
303 
304  # clear data and start new project
305  get_app().project.load("")
306  get_app().updates.reset()
307  self.updateStatusChanged(False, False)
308 
309  # Reset selections
310  self.clearSelections()
311 
312  self.filesTreeView.refresh_view()
313  log.info("New Project created.")
314 
315  # Set Window title
316  self.SetWindowTitle()
317 
318  def actionAnimatedTitle_trigger(self, event):
319  # show dialog
320  from windows.animated_title import AnimatedTitle
321  win = AnimatedTitle()
322  # Run the dialog event loop - blocking interaction on this window during that time
323  result = win.exec_()
324  if result == QDialog.Accepted:
325  log.info('animated title add confirmed')
326  else:
327  log.info('animated title add cancelled')
328 
329  def actionAnimation_trigger(self, event):
330  # show dialog
331  from windows.animation import Animation
332  win = Animation()
333  # Run the dialog event loop - blocking interaction on this window during that time
334  result = win.exec_()
335  if result == QDialog.Accepted:
336  log.info('animation confirmed')
337  else:
338  log.info('animation cancelled')
339 
340  def actionTitle_trigger(self, event):
341  # show dialog
342  from windows.title_editor import TitleEditor
343  win = TitleEditor()
344  # Run the dialog event loop - blocking interaction on this window during that time
345  result = win.exec_()
346  if result == QDialog.Accepted:
347  log.info('title editor add confirmed')
348  else:
349  log.info('title editor add cancelled')
350 
351  def actionEditTitle_trigger(self, event):
352 
353  # Get selected svg title file
354  selected_file_id = self.selected_files[0]
355  file = File.get(id=selected_file_id)
356  file_path = file.data.get("path")
357 
358  # Delete thumbnail for this file (it will be recreated soon)
359  thumb_path = os.path.join(info.THUMBNAIL_PATH, "{}.png".format(file.id))
360 
361  # Check if thumb exists (and delete it)
362  if os.path.exists(thumb_path):
363  os.remove(thumb_path)
364 
365  # show dialog for editing title
366  from windows.title_editor import TitleEditor
367  win = TitleEditor(file_path)
368  # Run the dialog event loop - blocking interaction on this window during that time
369  result = win.exec_()
370 
371  # Force update of files model (which will rebuild missing thumbnails)
372  get_app().window.filesTreeView.refresh_view()
373 
375 
376  # Get selected svg title file
377  selected_file_id = self.selected_files[0]
378  file = File.get(id=selected_file_id)
379  file_path = file.data.get("path")
380 
381  # show dialog for editing title
382  from windows.title_editor import TitleEditor
383  win = TitleEditor(file_path, duplicate=True)
384  # Run the dialog event loop - blocking interaction on this window during that time
385  result = win.exec_()
386 
388  # show dialog
389  from windows.Import_image_seq import ImportImageSeq
390  win = ImportImageSeq()
391  # Run the dialog event loop - blocking interaction on this window during that time
392  result = win.exec_()
393  if result == QDialog.Accepted:
394  log.info('Import image sequence add confirmed')
395  else:
396  log.info('Import image sequence add cancelled')
397 
398  ##
399  # Clear history for current project
400  def actionClearHistory_trigger(self, event):
401  app = get_app()
402  app.updates.reset()
403  log.info('History cleared')
404 
405  ##
406  # Save a project to a file path, and refresh the screen
407  def save_project(self, file_path):
408  app = get_app()
409  _ = app._tr # Get translation function
410 
411  try:
412  # Update history in project data
414  app.updates.save_history(app.project, s.get("history-limit"))
415 
416  # Save project to file
417  app.project.save(file_path)
418 
419  # Set Window title
420  self.SetWindowTitle()
421 
422  # Load recent projects again
423  self.load_recent_menu()
424 
425  log.info("Saved project {}".format(file_path))
426 
427  except Exception as ex:
428  log.error("Couldn't save project %s. %s" % (file_path, str(ex)))
429  QMessageBox.warning(self, _("Error Saving Project"), str(ex))
430 
431  ##
432  # Open a project from a file path, and refresh the screen
433  def open_project(self, file_path, clear_thumbnails=True):
434 
435  app = get_app()
436  _ = app._tr # Get translation function
437 
438  # Set cursor to waiting
439  get_app().setOverrideCursor(QCursor(Qt.WaitCursor))
440 
441  try:
442  if os.path.exists(file_path):
443  # Clear any previous thumbnails
444  if clear_thumbnails:
445  self.clear_all_thumbnails()
446 
447  # Load project file
448  app.project.load(file_path)
449 
450  # Set Window title
451  self.SetWindowTitle()
452 
453  # Reset undo/redo history
454  app.updates.reset()
455  app.updates.load_history(app.project)
456 
457  # Reset selections
458  self.clearSelections()
459 
460  # Refresh file tree
461  self.filesTreeView.refresh_view()
462 
463  # Load recent projects again
464  self.load_recent_menu()
465 
466  log.info("Loaded project {}".format(file_path))
467 
468  except Exception as ex:
469  log.error("Couldn't open project {}".format(file_path))
470  QMessageBox.warning(self, _("Error Opening Project"), str(ex))
471 
472  # Restore normal cursor
473  get_app().restoreOverrideCursor()
474 
475  ##
476  # Clear all user thumbnails
478  try:
479  if os.path.exists(info.THUMBNAIL_PATH):
480  log.info("Clear all thumbnails: %s" % info.THUMBNAIL_PATH)
481  # Remove thumbnail folder
482  shutil.rmtree(info.THUMBNAIL_PATH)
483  # Create thumbnail folder
484  os.mkdir(info.THUMBNAIL_PATH)
485 
486  # Clear any blender animations
487  if os.path.exists(info.BLENDER_PATH):
488  log.info("Clear all animations: %s" % info.BLENDER_PATH)
489  # Remove blender folder
490  shutil.rmtree(info.BLENDER_PATH)
491  # Create blender folder
492  os.mkdir(info.BLENDER_PATH)
493 
494  # Clear any assets folder
495  if os.path.exists(info.ASSETS_PATH):
496  log.info("Clear all assets: %s" % info.ASSETS_PATH)
497  # Remove assets folder
498  shutil.rmtree(info.ASSETS_PATH)
499  # Create assets folder
500  os.mkdir(info.ASSETS_PATH)
501 
502  # Clear any backups
503  if os.path.exists(info.BACKUP_PATH):
504  log.info("Clear all backups: %s" % info.BACKUP_PATH)
505  # Remove backup folder
506  shutil.rmtree(info.BACKUP_PATH)
507  # Create backup folder
508  os.mkdir(info.BACKUP_PATH)
509  except:
510  log.info("Failed to clear thumbnails: %s" % info.THUMBNAIL_PATH)
511 
512  def actionOpen_trigger(self, event):
513  app = get_app()
514  _ = app._tr
515  recommended_path = app.project.current_filepath
516  if not recommended_path:
517  recommended_path = info.HOME_PATH
518 
519  # Do we have unsaved changes?
520  if get_app().project.needs_save():
521  ret = QMessageBox.question(self, _("Unsaved Changes"), _("Save changes to project first?"), QMessageBox.Cancel | QMessageBox.No | QMessageBox.Yes)
522  if ret == QMessageBox.Yes:
523  # Save project
524  self.actionSave_trigger(event)
525  elif ret == QMessageBox.Cancel:
526  # User canceled prompt
527  return
528 
529  # Prompt for open project file
530  file_path, file_type = QFileDialog.getOpenFileName(self, _("Open Project..."), recommended_path, _("OpenShot Project (*.osp)"))
531 
532  # Load project file
533  self.open_project(file_path)
534 
535  def actionSave_trigger(self, event):
536  app = get_app()
537  _ = app._tr
538 
539  # Get current filepath if any, otherwise ask user
540  file_path = app.project.current_filepath
541  if not file_path:
542  recommended_path = os.path.join(info.HOME_PATH, "%s.osp" % _("Untitled Project"))
543  file_path, file_type = QFileDialog.getSaveFileName(self, _("Save Project..."), recommended_path, _("OpenShot Project (*.osp)"))
544 
545  if file_path:
546  # Append .osp if needed
547  if ".osp" not in file_path:
548  file_path = "%s.osp" % file_path
549 
550  # Save project
551  self.save_project(file_path)
552 
553  ##
554  # Auto save the project
555  def auto_save_project(self):
556  log.info("auto_save_project")
557 
558  # Get current filepath (if any)
559  file_path = get_app().project.current_filepath
560  if get_app().project.needs_save():
561  if file_path:
562  # A Real project file exists
563  # Append .osp if needed
564  if ".osp" not in file_path:
565  file_path = "%s.osp" % file_path
566 
567  # Save project
568  log.info("Auto save project file: %s" % file_path)
569  self.save_project(file_path)
570 
571  else:
572  # No saved project found
573  recovery_path = os.path.join(info.BACKUP_PATH, "backup.osp")
574  log.info("Creating backup of project file: %s" % recovery_path)
575  get_app().project.save(recovery_path, move_temp_files=False, make_paths_relative=False)
576 
577  # Clear the file_path (which is set by saving the project)
578  get_app().project.current_filepath = None
579  get_app().project.has_unsaved_changes = True
580 
581  def actionSaveAs_trigger(self, event):
582  app = get_app()
583  _ = app._tr
584 
585  recommended_path = app.project.current_filepath
586  if not recommended_path:
587  recommended_path = os.path.join(info.HOME_PATH, "%s.osp" % _("Untitled Project"))
588  file_path, file_type = QFileDialog.getSaveFileName(self, _("Save Project As..."), recommended_path, _("OpenShot Project (*.osp)"))
589  if file_path:
590  # Append .osp if needed
591  if ".osp" not in file_path:
592  file_path = "%s.osp" % file_path
593 
594  # Save new project
595  self.save_project(file_path)
596 
597  def actionImportFiles_trigger(self, event):
598  app = get_app()
599  _ = app._tr
600  recommended_path = app.project.get(["import_path"])
601  if not recommended_path or not os.path.exists(recommended_path):
602  recommended_path = os.path.join(info.HOME_PATH)
603  files = QFileDialog.getOpenFileNames(self, _("Import File..."), recommended_path)[0]
604  for file_path in files:
605  self.filesTreeView.add_file(file_path)
606  self.filesTreeView.refresh_view()
607  app.updates.update(["import_path"], os.path.dirname(file_path))
608  log.info("Imported media file {}".format(file_path))
609 
611  # Loop through selected files
612  f = None
613  files = []
614  for file_id in self.selected_files:
615  # Find matching file
616  files.append(File.get(id=file_id))
617 
618  # Get current position of playhead
619  fps = get_app().project.get(["fps"])
620  fps_float = float(fps["num"]) / float(fps["den"])
621  pos = (self.preview_thread.player.Position() - 1) / fps_float
622 
623  # show window
624  from windows.add_to_timeline import AddToTimeline
625  win = AddToTimeline(files, pos)
626  # Run the dialog event loop - blocking interaction on this window during this time
627  result = win.exec_()
628  if result == QDialog.Accepted:
629  log.info('confirmed')
630  else:
631  log.info('canceled')
632 
633  def actionUploadVideo_trigger(self, event):
634  # show window
635  from windows.upload_video import UploadVideo
636  win = UploadVideo()
637  # Run the dialog event loop - blocking interaction on this window during this time
638  result = win.exec_()
639  if result == QDialog.Accepted:
640  log.info('Upload Video add confirmed')
641  else:
642  log.info('Upload Video add cancelled')
643 
644  def actionExportVideo_trigger(self, event):
645  # show window
646  from windows.export import Export
647  win = Export()
648  # Run the dialog event loop - blocking interaction on this window during this time
649  result = win.exec_()
650  if result == QDialog.Accepted:
651  log.info('Export Video add confirmed')
652  else:
653  log.info('Export Video add cancelled')
654 
655  def actionUndo_trigger(self, event):
656  log.info('actionUndo_trigger')
657  app = get_app()
658  app.updates.undo()
659 
660  # Update the preview
661  self.refreshFrameSignal.emit()
662 
663  def actionRedo_trigger(self, event):
664  log.info('actionRedo_trigger')
665  app = get_app()
666  app.updates.redo()
667 
668  # Update the preview
669  self.refreshFrameSignal.emit()
670 
671  def actionPreferences_trigger(self, event):
672  # Stop preview thread
673  self.SpeedSignal.emit(0)
674  ui_util.setup_icon(self, self.actionPlay, "actionPlay", "media-playback-start")
675  self.actionPlay.setChecked(False)
676 
677  # Show dialog
678  from windows.preferences import Preferences
679  win = Preferences()
680  # Run the dialog event loop - blocking interaction on this window during this time
681  result = win.exec_()
682  if result == QDialog.Accepted:
683  log.info('Preferences add confirmed')
684  else:
685  log.info('Preferences add cancelled')
686 
687  # Save settings
689  s.save()
690 
691  def actionFilesShowAll_trigger(self, event):
692  self.filesTreeView.refresh_view()
693 
695  self.filesTreeView.refresh_view()
696 
698  self.filesTreeView.refresh_view()
699 
701  self.filesTreeView.refresh_view()
702 
704  self.transitionsTreeView.refresh_view()
705 
707  self.transitionsTreeView.refresh_view()
708 
710  self.effectsTreeView.refresh_view()
711 
713  self.effectsTreeView.refresh_view()
714 
716  self.effectsTreeView.refresh_view()
717 
718  def actionHelpContents_trigger(self, event):
719  try:
720  webbrowser.open("http://%s.openshot.org/files/user-guide/?app-menu" % info.website_language())
721  log.info("Help Contents is open")
722  except:
723  QMessageBox.information(self, "Error !", "Unable to open the Help Contents. Please ensure the openshot-doc package is installed.")
724  log.info("Unable to open the Help Contents")
725 
726  ##
727  # Show about dialog
728  def actionAbout_trigger(self, event):
729  from windows.about import About
730  win = About()
731  # Run the dialog event loop - blocking interaction on this window during this time
732  result = win.exec_()
733  if result == QDialog.Accepted:
734  log.info('About Openshot add confirmed')
735  else:
736  log.info('About Openshot add cancelled')
737 
738  def actionReportBug_trigger(self, event):
739  try:
740  webbrowser.open("https://github.com/OpenShot/openshot-qt/issues/?app-menu-bug")
741  log.info("Open the Bug Report GitHub Issues web page with success")
742  except:
743  QMessageBox.information(self, "Error !", "Unable to open the Bug Report GitHub Issues web page")
744  log.info("Unable to open the Bug Report GitHub Issues web page")
745 
746  def actionAskQuestion_trigger(self, event):
747  try:
748  webbrowser.open("https://github.com/OpenShot/openshot-qt/issues/?app-menu-question")
749  log.info("Open the Questions GitHub Issues web page with success")
750  except:
751  QMessageBox.information(self, "Error !", "Unable to open the Questions GitHub Issues web page")
752  log.info("Unable to open the Questions GitHub Issues web page")
753 
754  def actionTranslate_trigger(self, event):
755  try:
756  webbrowser.open("https://translations.launchpad.net/openshot/2.0")
757  log.info("Open the Translate launchpad web page with success")
758  except:
759  QMessageBox.information(self, "Error !", "Unable to open the Translation web page")
760  log.info("Unable to open the Translation web page")
761 
762  def actionDonate_trigger(self, event):
763  try:
764  webbrowser.open("http://%s.openshot.org/donate/?app-menu" % info.website_language())
765  log.info("Open the Donate web page with success")
766  except:
767  QMessageBox.information(self, "Error !", "Unable to open the Donate web page")
768  log.info("Unable to open the Donate web page")
769 
770  def actionUpdate_trigger(self, event):
771  try:
772  webbrowser.open("http://%s.openshot.org/download/?app-toolbar" % info.website_language())
773  log.info("Open the Download web page with success")
774  except:
775  QMessageBox.information(self, "Error !", "Unable to open the Download web page")
776  log.info("Unable to open the Download web page")
777 
778  def actionPlay_trigger(self, event, force=None):
779 
780  # Determine max frame (based on clips)
781  timeline_length = 0.0
782  fps = get_app().window.timeline_sync.timeline.info.fps.ToFloat()
783  clips = get_app().window.timeline_sync.timeline.Clips()
784  for clip in clips:
785  clip_last_frame = clip.Position() + clip.Duration()
786  if clip_last_frame > timeline_length:
787  # Set max length of timeline
788  timeline_length = clip_last_frame
789 
790  # Convert to int and round
791  timeline_length_int = round(timeline_length * fps) + 1
792 
793  if force == "pause":
794  self.actionPlay.setChecked(False)
795  elif force == "play":
796  self.actionPlay.setChecked(True)
797 
798  if self.actionPlay.isChecked():
799  ui_util.setup_icon(self, self.actionPlay, "actionPlay", "media-playback-pause")
800  self.PlaySignal.emit(timeline_length_int)
801 
802  else:
803  ui_util.setup_icon(self, self.actionPlay, "actionPlay") # to default
804  self.PauseSignal.emit()
805 
806  ##
807  # Preview the selected media file
808  def actionPreview_File_trigger(self, event):
809  log.info('actionPreview_File_trigger')
810 
811  # Loop through selected files (set 1 selected file if more than 1)
812  f = None
813  for file_id in self.selected_files:
814  # Find matching file
815  f = File.get(id=file_id)
816 
817  # Bail out if no file selected
818  if not f:
819  log.info(self.selected_files)
820  return
821 
822  # show dialog
823  from windows.cutting import Cutting
824  win = Cutting(f, preview=True)
825  win.show()
826 
827  ##
828  # Preview a specific frame
829  def previewFrame(self, position_seconds, position_frames, time_code):
830  # Notify preview thread
831  self.previewFrameSignal.emit(position_frames)
832 
833  # Notify properties dialog
834  self.propertyTableView.select_frame(position_frames)
835 
836  ##
837  # Handle the pause signal, by refreshing the properties dialog
838  def handlePausedVideo(self):
839  self.propertyTableView.select_frame(self.preview_thread.player.Position())
840 
841  ##
842  # Update playhead position
843  def movePlayhead(self, position_frames):
844  # Notify preview thread
845  self.timeline.movePlayhead(position_frames)
846 
847  def actionFastForward_trigger(self, event):
848 
849  # Get the video player object
850  player = self.preview_thread.player
851 
852  if player.Speed() + 1 != 0:
853  self.SpeedSignal.emit(player.Speed() + 1)
854  else:
855  self.SpeedSignal.emit(player.Speed() + 2)
856 
857  if player.Mode() == openshot.PLAYBACK_PAUSED:
858  self.actionPlay.trigger()
859 
860  def actionRewind_trigger(self, event):
861 
862  # Get the video player object
863  player = self.preview_thread.player
864 
865  if player.Speed() - 1 != 0:
866  self.SpeedSignal.emit(player.Speed() - 1)
867  else:
868  self.SpeedSignal.emit(player.Speed() - 2)
869 
870  if player.Mode() == openshot.PLAYBACK_PAUSED:
871  self.actionPlay.trigger()
872 
873  def actionJumpStart_trigger(self, event):
874  log.info("actionJumpStart_trigger")
875 
876  # Seek to the 1st frame
877  self.SeekSignal.emit(1)
878 
879  def actionJumpEnd_trigger(self, event):
880  log.info("actionJumpEnd_trigger")
881 
882  # Determine max frame (based on clips)
883  timeline_length = 0.0
884  fps = get_app().window.timeline_sync.timeline.info.fps.ToFloat()
885  clips = get_app().window.timeline_sync.timeline.Clips()
886  for clip in clips:
887  clip_last_frame = clip.Position() + clip.Duration()
888  if clip_last_frame > timeline_length:
889  # Set max length of timeline
890  timeline_length = clip_last_frame
891 
892  # Convert to int and round
893  timeline_length_int = round(timeline_length * fps) + 1
894 
895  # Seek to the 1st frame
896  self.SeekSignal.emit(timeline_length_int)
897 
898  def actionAddTrack_trigger(self, event):
899  log.info("actionAddTrack_trigger")
900 
901  # Get # of tracks
902  track_number = len(get_app().project.get(["layers"]))
903 
904  # Create new track above existing layer(s)
905  track = Track()
906  track.data = {"number": track_number, "y": 0, "label": "", "lock": False}
907  track.save()
908 
909  def actionAddTrackAbove_trigger(self, event):
910  log.info("actionAddTrackAbove_trigger")
911 
912  # Get # of tracks
913  max_track_number = len(get_app().project.get(["layers"]))
914  selected_layer_id = self.selected_tracks[0]
915 
916  # Get selected track data
917  existing_track = Track.get(id=selected_layer_id)
918  selected_layer_number = int(existing_track.data["number"])
919 
920  # log.info("Adding track above #{} (id {})".format(selected_layer_number, selected_layer_id))
921 
922  # Loop through tracks above insert point (in descending order), renumbering layers
923  for existing_layer in list(reversed(range(selected_layer_number+1, max_track_number))):
924  existing_track = Track.get(number=existing_layer)
925  # log.info("Renumbering track id {} from {} to {}".format(existing_track.data["id"], existing_layer, existing_layer+1))
926  existing_track.data["number"] = existing_layer + 1
927  existing_track.save()
928 
929  # Loop through clips and transitions for track, moving up to new layer
930  for clip in Clip.filter(layer=existing_layer):
931  # log.info("Moving clip id {} from layer {} to {}".format(clip.data["id"], int(clip.data["layer"]), int(clip.data["layer"])+1))
932  clip.data["layer"] = int(clip.data["layer"]) + 1
933  clip.save()
934 
935  for trans in Transition.filter(layer=existing_layer):
936  # log.info("Moving transition id {} from layer {} to {}".format(trans.data["id"], int(trans.data["layer"]), int(trans.data["layer"])+1))
937  trans.data["layer"] = int(trans.data["layer"]) + 1
938  trans.save()
939 
940  # Create new track at vacated layer
941  track = Track()
942  track.data = {"number": selected_layer_number+1, "y": 0, "label": "", "lock": False}
943  track.save()
944  # log.info("Created new track id {} at layer number {}".format(track.data["id"], track.data["number"]))
945 
946  def actionAddTrackBelow_trigger(self, event):
947  log.info("actionAddTrackBelow_trigger")
948 
949  # Get # of tracks
950  max_track_number = len(get_app().project.get(["layers"]))
951  selected_layer_id = self.selected_tracks[0]
952 
953  # Get selected track data
954  existing_track = Track.get(id=selected_layer_id)
955  selected_layer_number = int(existing_track.data["number"])
956 
957  # log.info("Adding track below #{} (id {})".format(selected_layer_number, selected_layer_id))
958 
959  # Loop through tracks from insert point up (in descending order), renumbering layers
960  for existing_layer in list(reversed(range(selected_layer_number, max_track_number))):
961  existing_track = Track.get(number=existing_layer)
962  # log.info("Renumbering track id {} from {} to {}".format(existing_track.data["id"], existing_layer, existing_layer+1))
963  existing_track.data["number"] = existing_layer + 1
964  existing_track.save()
965 
966  # Loop through clips and transitions for track, moving up to new layer
967  for clip in Clip.filter(layer=existing_layer):
968  # log.info("Moving clip id {} from layer {} to {}".format(clip.data["id"], int(clip.data["layer"]), int(clip.data["layer"])+1))
969  clip.data["layer"] = int(clip.data["layer"]) + 1
970  clip.save()
971 
972  for trans in Transition.filter(layer=existing_layer):
973  # log.info("Moving transition id {} from layer {} to {}".format(trans.data["id"], int(trans.data["layer"]), int(trans.data["layer"])+1))
974  trans.data["layer"] = int(trans.data["layer"]) + 1
975  trans.save()
976 
977  # Create new track at vacated layer
978  track = Track()
979  track.data = {"number": selected_layer_number, "y": 0, "label": "", "lock": False}
980  track.save()
981  # log.info("Created new track id {} at layer number {}".format(track.data["id"], track.data["number"]))
982 
983  def actionArrowTool_trigger(self, event):
984  log.info("actionArrowTool_trigger")
985 
986  def actionSnappingTool_trigger(self, event):
987  log.info("actionSnappingTool_trigger")
988  log.info(self.actionSnappingTool.isChecked())
989 
990  # Enable / Disable snapping mode
991  self.timeline.SetSnappingMode(self.actionSnappingTool.isChecked())
992 
993  ##
994  # Toggle razor tool on and off
995  def actionRazorTool_trigger(self, event):
996  log.info('actionRazorTool_trigger')
997 
998  # Enable / Disable razor mode
999  self.timeline.SetRazorMode(self.actionRazorTool.isChecked())
1000 
1001  def actionAddMarker_trigger(self, event):
1002  log.info("actionAddMarker_trigger")
1003 
1004  # Get player object
1005  player = self.preview_thread.player
1006 
1007  # Calculate frames per second
1008  fps = get_app().project.get(["fps"])
1009  fps_float = float(fps["num"]) / float(fps["den"])
1010 
1011  # Calculate position in seconds
1012  position = (player.Position() - 1) / fps_float
1013 
1014  # Look for existing Marker
1015  marker = Marker()
1016  marker.data = {"position": position, "icon": "blue.png"}
1017  marker.save()
1018 
1020  log.info("actionPreviousMarker_trigger")
1021 
1022  # Calculate current position (in seconds)
1023  fps = get_app().project.get(["fps"])
1024  fps_float = float(fps["num"]) / float(fps["den"])
1025  current_position = (self.preview_thread.current_frame - 1) / fps_float
1026  all_marker_positions = []
1027 
1028  # Get list of marker and important positions (like selected clip bounds)
1029  for marker in Marker.filter():
1030  all_marker_positions.append(marker.data["position"])
1031 
1032  # Loop through selected clips (and add key positions)
1033  for clip_id in self.selected_clips:
1034  # Get selected object
1035  selected_clip = Clip.get(id=clip_id)
1036  if selected_clip:
1037  all_marker_positions.append(selected_clip.data["position"])
1038  all_marker_positions.append(selected_clip.data["position"] + (selected_clip.data["end"] - selected_clip.data["start"]))
1039 
1040  # Loop through selected transitions (and add key positions)
1041  for tran_id in self.selected_transitions:
1042  # Get selected object
1043  selected_tran = Transition.get(id=tran_id)
1044  if selected_tran:
1045  all_marker_positions.append(selected_tran.data["position"])
1046  all_marker_positions.append(selected_tran.data["position"] + (selected_tran.data["end"] - selected_tran.data["start"]))
1047 
1048  # Loop through all markers, and find the closest one to the left
1049  closest_position = None
1050  for marker_position in sorted(all_marker_positions):
1051  # Is marker smaller than position?
1052  if marker_position < current_position and (abs(marker_position - current_position) > 0.1):
1053  # Is marker larger than previous marker
1054  if closest_position and marker_position > closest_position:
1055  # Set a new closest marker
1056  closest_position = marker_position
1057  elif not closest_position:
1058  # First one found
1059  closest_position = marker_position
1060 
1061  # Seek to marker position (if any)
1062  if closest_position != None:
1063  # Seek
1064  frame_to_seek = round(closest_position * fps_float) + 1
1065  self.SeekSignal.emit(frame_to_seek)
1066 
1067  # Update the preview and reselct current frame in properties
1068  get_app().window.refreshFrameSignal.emit()
1069  get_app().window.propertyTableView.select_frame(frame_to_seek)
1070 
1071  def actionNextMarker_trigger(self, event):
1072  log.info("actionNextMarker_trigger")
1073  log.info(self.preview_thread.current_frame)
1074 
1075  # Calculate current position (in seconds)
1076  fps = get_app().project.get(["fps"])
1077  fps_float = float(fps["num"]) / float(fps["den"])
1078  current_position = (self.preview_thread.current_frame - 1) / fps_float
1079  all_marker_positions = []
1080 
1081  # Get list of marker and important positions (like selected clip bounds)
1082  for marker in Marker.filter():
1083  all_marker_positions.append(marker.data["position"])
1084 
1085  # Loop through selected clips (and add key positions)
1086  for clip_id in self.selected_clips:
1087  # Get selected object
1088  selected_clip = Clip.get(id=clip_id)
1089  if selected_clip:
1090  all_marker_positions.append(selected_clip.data["position"])
1091  all_marker_positions.append(selected_clip.data["position"] + (selected_clip.data["end"] - selected_clip.data["start"]))
1092 
1093  # Loop through selected transitions (and add key positions)
1094  for tran_id in self.selected_transitions:
1095  # Get selected object
1096  selected_tran = Transition.get(id=tran_id)
1097  if selected_tran:
1098  all_marker_positions.append(selected_tran.data["position"])
1099  all_marker_positions.append(selected_tran.data["position"] + (selected_tran.data["end"] - selected_tran.data["start"]))
1100 
1101  # Loop through all markers, and find the closest one to the right
1102  closest_position = None
1103  for marker_position in sorted(all_marker_positions):
1104  # Is marker smaller than position?
1105  if marker_position > current_position and (abs(marker_position - current_position) > 0.1):
1106  # Is marker larger than previous marker
1107  if closest_position and marker_position < closest_position:
1108  # Set a new closest marker
1109  closest_position = marker_position
1110  elif not closest_position:
1111  # First one found
1112  closest_position = marker_position
1113 
1114  # Seek to marker position (if any)
1115  if closest_position != None:
1116  # Seek
1117  frame_to_seek = round(closest_position * fps_float) + 1
1118  self.SeekSignal.emit(frame_to_seek)
1119 
1120  # Update the preview and reselct current frame in properties
1121  get_app().window.refreshFrameSignal.emit()
1122  get_app().window.propertyTableView.select_frame(frame_to_seek)
1123 
1124  ##
1125  # Get a key sequence back from the setting name
1126  def getShortcutByName(self, setting_name):
1127  s = settings.get_settings()
1128  shortcut = QKeySequence(s.get(setting_name))
1129  return shortcut
1130 
1131  ##
1132  # Get a key sequence back from the setting name
1134  keyboard_shortcuts = []
1135  all_settings = settings.get_settings()._data
1136  for setting in all_settings:
1137  if setting.get('category') == 'Keyboard':
1138  keyboard_shortcuts.append(setting)
1139  return keyboard_shortcuts
1140 
1141  ##
1142  # Process key press events and match with known shortcuts
1143  def keyPressEvent(self, event):
1144  # Detect the current KeySequence pressed (including modifier keys)
1145  key_value = event.key()
1146  print(key_value)
1147  modifiers = int(event.modifiers())
1148  if (key_value > 0 and key_value != Qt.Key_Shift and key_value != Qt.Key_Alt and
1149  key_value != Qt.Key_Control and key_value != Qt.Key_Meta):
1150  # A valid keysequence was detected
1151  key = QKeySequence(modifiers + key_value)
1152  else:
1153  # No valid keysequence detected
1154  return
1155 
1156  # Debug
1157  log.info("keyPressEvent: %s" % (key.toString()))
1158 
1159  # Get the video player object
1160  player = self.preview_thread.player
1161 
1162  # Get framerate
1163  fps = get_app().project.get(["fps"])
1164  fps_float = float(fps["num"]) / float(fps["den"])
1165  playhead_position = float(self.preview_thread.current_frame - 1) / fps_float
1166 
1167  # Basic shortcuts i.e just a letter
1168  if key.matches(self.getShortcutByName("seekPreviousFrame")) == QKeySequence.ExactMatch:
1169  # Pause video
1170  self.actionPlay_trigger(event, force="pause")
1171  # Set speed to 0
1172  if player.Speed() != 0:
1173  self.SpeedSignal.emit(0)
1174  # Seek to previous frame
1175  self.SeekSignal.emit(player.Position() - 1)
1176 
1177  # Notify properties dialog
1178  self.propertyTableView.select_frame(player.Position())
1179 
1180  elif key.matches(self.getShortcutByName("seekNextFrame")) == QKeySequence.ExactMatch:
1181  # Pause video
1182  self.actionPlay_trigger(event, force="pause")
1183  # Set speed to 0
1184  if player.Speed() != 0:
1185  self.SpeedSignal.emit(0)
1186  # Seek to next frame
1187  self.SeekSignal.emit(player.Position() + 1)
1188 
1189  # Notify properties dialog
1190  self.propertyTableView.select_frame(player.Position())
1191 
1192  elif key.matches(self.getShortcutByName("rewindVideo")) == QKeySequence.ExactMatch:
1193  # Toggle rewind and start playback
1194  self.actionRewind.trigger()
1195  ui_util.setup_icon(self, self.actionPlay, "actionPlay", "media-playback-pause")
1196  self.actionPlay.setChecked(True)
1197 
1198  elif key.matches(self.getShortcutByName("fastforwardVideo")) == QKeySequence.ExactMatch:
1199  # Toggle fastforward button and start playback
1200  self.actionFastForward.trigger()
1201  ui_util.setup_icon(self, self.actionPlay, "actionPlay", "media-playback-pause")
1202  self.actionPlay.setChecked(True)
1203 
1204  elif key.matches(self.getShortcutByName("playToggle")) == QKeySequence.ExactMatch or \
1205  key.matches(self.getShortcutByName("playToggle1")) == QKeySequence.ExactMatch or \
1206  key.matches(self.getShortcutByName("playToggle2")) == QKeySequence.ExactMatch or \
1207  key.matches(self.getShortcutByName("playToggle3")) == QKeySequence.ExactMatch:
1208  # Toggle playbutton and show properties
1209  self.actionPlay.trigger()
1210  self.propertyTableView.select_frame(player.Position())
1211 
1212  elif key.matches(self.getShortcutByName("deleteItem")) == QKeySequence.ExactMatch or \
1213  key.matches(self.getShortcutByName("deleteItem1")) == QKeySequence.ExactMatch:
1214  # Delete selected clip / transition
1215  self.actionRemoveClip.trigger()
1216  self.actionRemoveTransition.trigger()
1217 
1218  # Boiler plate key mappings (mostly for menu support on Ubuntu/Unity)
1219  elif key.matches(self.getShortcutByName("actionNew")) == QKeySequence.ExactMatch:
1220  self.actionNew.trigger()
1221  elif key.matches(self.getShortcutByName("actionOpen")) == QKeySequence.ExactMatch:
1222  self.actionOpen.trigger()
1223  elif key.matches(self.getShortcutByName("actionSave")) == QKeySequence.ExactMatch:
1224  self.actionSave.trigger()
1225  elif key.matches(self.getShortcutByName("actionUndo")) == QKeySequence.ExactMatch:
1226  self.actionUndo.trigger()
1227  elif key.matches(self.getShortcutByName("actionSaveAs")) == QKeySequence.ExactMatch:
1228  self.actionSaveAs.trigger()
1229  elif key.matches(self.getShortcutByName("actionImportFiles")) == QKeySequence.ExactMatch:
1230  self.actionImportFiles.trigger()
1231  elif key.matches(self.getShortcutByName("actionRedo")) == QKeySequence.ExactMatch:
1232  self.actionRedo.trigger()
1233  elif key.matches(self.getShortcutByName("actionExportVideo")) == QKeySequence.ExactMatch:
1234  self.actionExportVideo.trigger()
1235  elif key.matches(self.getShortcutByName("actionQuit")) == QKeySequence.ExactMatch:
1236  self.actionQuit.trigger()
1237  elif key.matches(self.getShortcutByName("actionPreferences")) == QKeySequence.ExactMatch:
1238  self.actionPreferences.trigger()
1239  elif key.matches(self.getShortcutByName("actionAddTrack")) == QKeySequence.ExactMatch:
1240  self.actionAddTrack.trigger()
1241  elif key.matches(self.getShortcutByName("actionAddMarker")) == QKeySequence.ExactMatch:
1242  self.actionAddMarker.trigger()
1243  elif key.matches(self.getShortcutByName("actionPreviousMarker")) == QKeySequence.ExactMatch:
1244  self.actionPreviousMarker.trigger()
1245  elif key.matches(self.getShortcutByName("actionNextMarker")) == QKeySequence.ExactMatch:
1246  self.actionNextMarker.trigger()
1247  elif key.matches(self.getShortcutByName("actionTimelineZoomIn")) == QKeySequence.ExactMatch:
1248  self.actionTimelineZoomIn.trigger()
1249  elif key.matches(self.getShortcutByName("actionTimelineZoomOut")) == QKeySequence.ExactMatch:
1250  self.actionTimelineZoomOut.trigger()
1251  elif key.matches(self.getShortcutByName("actionTitle")) == QKeySequence.ExactMatch:
1252  self.actionTitle.trigger()
1253  elif key.matches(self.getShortcutByName("actionAnimatedTitle")) == QKeySequence.ExactMatch:
1254  self.actionAnimatedTitle.trigger()
1255  elif key.matches(self.getShortcutByName("actionFullscreen")) == QKeySequence.ExactMatch:
1256  self.actionFullscreen.trigger()
1257  elif key.matches(self.getShortcutByName("actionAbout")) == QKeySequence.ExactMatch:
1258  self.actionAbout.trigger()
1259  elif key.matches(self.getShortcutByName("actionThumbnailView")) == QKeySequence.ExactMatch:
1260  self.actionThumbnailView.trigger()
1261  elif key.matches(self.getShortcutByName("actionDetailsView")) == QKeySequence.ExactMatch:
1262  self.actionDetailsView.trigger()
1263  elif key.matches(self.getShortcutByName("actionProfile")) == QKeySequence.ExactMatch:
1264  self.actionProfile.trigger()
1265  elif key.matches(self.getShortcutByName("actionAdd_to_Timeline")) == QKeySequence.ExactMatch:
1266  self.actionAdd_to_Timeline.trigger()
1267  elif key.matches(self.getShortcutByName("actionSplitClip")) == QKeySequence.ExactMatch:
1268  self.actionSplitClip.trigger()
1269  elif key.matches(self.getShortcutByName("actionSnappingTool")) == QKeySequence.ExactMatch:
1270  self.actionSnappingTool.trigger()
1271  elif key.matches(self.getShortcutByName("actionJumpStart")) == QKeySequence.ExactMatch:
1272  self.actionJumpStart.trigger()
1273  elif key.matches(self.getShortcutByName("actionJumpEnd")) == QKeySequence.ExactMatch:
1274  self.actionJumpEnd.trigger()
1275  elif key.matches(self.getShortcutByName("actionProperties")) == QKeySequence.ExactMatch:
1276  self.actionProperties.trigger()
1277  elif key.matches(self.getShortcutByName("actionTransform")) == QKeySequence.ExactMatch:
1278  if not self.is_transforming and self.selected_clips:
1279  self.TransformSignal.emit(self.selected_clips[0])
1280  else:
1281  self.TransformSignal.emit("")
1282 
1283  elif key.matches(self.getShortcutByName("actionInsertKeyframe")) == QKeySequence.ExactMatch:
1284  print("actionInsertKeyframe")
1285  if self.selected_clips or self.selected_transitions:
1286  self.InsertKeyframe.emit(event)
1287 
1288  # Timeline keyboard shortcuts
1289  elif key.matches(self.getShortcutByName("sliceAllKeepBothSides")) == QKeySequence.ExactMatch:
1290  intersecting_clips = Clip.filter(intersect=playhead_position)
1291  intersecting_trans = Transition.filter(intersect=playhead_position)
1292  if intersecting_clips or intersecting_trans:
1293  # Get list of clip ids
1294  clip_ids = [c.id for c in intersecting_clips]
1295  trans_ids = [t.id for t in intersecting_trans]
1296  self.timeline.Slice_Triggered(0, clip_ids, trans_ids, playhead_position)
1297  elif key.matches(self.getShortcutByName("sliceAllKeepLeftSide")) == QKeySequence.ExactMatch:
1298  intersecting_clips = Clip.filter(intersect=playhead_position)
1299  intersecting_trans = Transition.filter(intersect=playhead_position)
1300  if intersecting_clips or intersecting_trans:
1301  # Get list of clip ids
1302  clip_ids = [c.id for c in intersecting_clips]
1303  trans_ids = [t.id for t in intersecting_trans]
1304  self.timeline.Slice_Triggered(1, clip_ids, trans_ids, playhead_position)
1305  elif key.matches(self.getShortcutByName("sliceAllKeepRightSide")) == QKeySequence.ExactMatch:
1306  intersecting_clips = Clip.filter(intersect=playhead_position)
1307  intersecting_trans = Transition.filter(intersect=playhead_position)
1308  if intersecting_clips or intersecting_trans:
1309  # Get list of clip ids
1310  clip_ids = [c.id for c in intersecting_clips]
1311  trans_ids = [t.id for t in intersecting_trans]
1312  self.timeline.Slice_Triggered(2, clip_ids, trans_ids, playhead_position)
1313  elif key.matches(self.getShortcutByName("copyAll")) == QKeySequence.ExactMatch:
1314  self.timeline.Copy_Triggered(-1, self.selected_clips, self.selected_transitions)
1315  elif key.matches(self.getShortcutByName("pasteAll")) == QKeySequence.ExactMatch:
1316  self.timeline.Paste_Triggered(9, float(playhead_position), -1, [], [])
1317 
1318  # Select All / None
1319  elif key.matches(self.getShortcutByName("selectAll")) == QKeySequence.ExactMatch:
1320  self.timeline.SelectAll()
1321 
1322  elif key.matches(self.getShortcutByName("selectNone")) == QKeySequence.ExactMatch:
1323  self.timeline.ClearAllSelections()
1324 
1325  # Bubble event on
1326  event.ignore()
1327 
1328 
1329  def actionProfile_trigger(self, event):
1330  # Show dialog
1331  from windows.profile import Profile
1332  win = Profile()
1333  # Run the dialog event loop - blocking interaction on this window during this time
1334  result = win.exec_()
1335  if result == QDialog.Accepted:
1336  log.info('Profile add confirmed')
1337 
1338 
1339  def actionSplitClip_trigger(self, event):
1340  log.info("actionSplitClip_trigger")
1341 
1342  # Loop through selected files (set 1 selected file if more than 1)
1343  f = None
1344  for file_id in self.selected_files:
1345  # Find matching file
1346  f = File.get(id=file_id)
1347 
1348  # Bail out if no file selected
1349  if not f:
1350  log.info(self.selected_files)
1351  return
1352 
1353  # show dialog
1354  from windows.cutting import Cutting
1355  win = Cutting(f)
1356  # Run the dialog event loop - blocking interaction on this window during that time
1357  result = win.exec_()
1358  if result == QDialog.Accepted:
1359  log.info('Cutting Finished')
1360  else:
1361  log.info('Cutting Cancelled')
1362 
1364  log.info("actionRemove_from_Project_trigger")
1365 
1366  # Loop through selected files
1367  for file_id in self.selected_files:
1368  # Find matching file
1369  f = File.get(id=file_id)
1370  if f:
1371  # Remove file
1372  f.delete()
1373 
1374  # Find matching clips (if any)
1375  clips = Clip.filter(file_id=file_id)
1376  for c in clips:
1377  # Remove clip
1378  c.delete()
1379 
1380  # Clear selected files
1381  self.selected_files = []
1382 
1383  def actionRemoveClip_trigger(self, event):
1384  log.info('actionRemoveClip_trigger')
1385 
1386  # Loop through selected clips
1387  for clip_id in deepcopy(self.selected_clips):
1388  # Find matching file
1389  clips = Clip.filter(id=clip_id)
1390  for c in clips:
1391  # Clear selected clips
1392  self.removeSelection(clip_id, "clip")
1393 
1394  # Remove clip
1395  c.delete()
1396 
1397  def actionProperties_trigger(self, event):
1398  log.info('actionProperties_trigger')
1399 
1400  # Show properties dock
1401  if not self.dockProperties.isVisible():
1402  self.dockProperties.show()
1403 
1404  def actionRemoveEffect_trigger(self, event):
1405  log.info('actionRemoveEffect_trigger')
1406 
1407  # Loop through selected clips
1408  for effect_id in deepcopy(self.selected_effects):
1409  log.info("effect id: %s" % effect_id)
1410 
1411  # Find matching file
1412  clips = Clip.filter()
1413  found_effect = None
1414  for c in clips:
1415  found_effect = False
1416  log.info("c.data[effects]: %s" % c.data["effects"])
1417 
1418  for effect in c.data["effects"]:
1419  if effect["id"] == effect_id:
1420  found_effect = effect
1421  break
1422 
1423  if found_effect:
1424  # Remove found effect from clip data and save clip
1425  c.data["effects"].remove(found_effect)
1426 
1427  # Remove unneeded attributes from JSON
1428  c.data.pop("reader")
1429 
1430  # Save clip
1431  c.save()
1432 
1433  # Clear selected effects
1434  self.removeSelection(effect_id, "effect")
1435 
1437  log.info('actionRemoveTransition_trigger')
1438 
1439  # Loop through selected clips
1440  for tran_id in deepcopy(self.selected_transitions):
1441  # Find matching file
1442  transitions = Transition.filter(id=tran_id)
1443  for t in transitions:
1444  # Clear selected clips
1445  self.removeSelection(tran_id, "transition")
1446 
1447  # Remove transition
1448  t.delete()
1449 
1450  def actionRemoveTrack_trigger(self, event):
1451  log.info('actionRemoveTrack_trigger')
1452 
1453  # Get translation function
1454  _ = get_app()._tr
1455 
1456  track_id = self.selected_tracks[0]
1457  max_track_number = len(get_app().project.get(["layers"]))
1458 
1459  # Get details of selected track
1460  selected_track = Track.get(id=track_id)
1461  selected_track_number = int(selected_track.data["number"])
1462 
1463  # Don't allow user to delete final track
1464  if max_track_number == 1:
1465  # Show error and do nothing
1466  QMessageBox.warning(self, _("Error Removing Track"), _("You must keep at least 1 track"))
1467  return
1468 
1469  # Revove all clips on this track first
1470  for clip in Clip.filter(layer=selected_track_number):
1471  clip.delete()
1472 
1473  # Revove all transitions on this track first
1474  for trans in Transition.filter(layer=selected_track_number):
1475  trans.delete()
1476 
1477  # Remove track
1478  selected_track.delete()
1479 
1480  # Loop through all layers above, and renumber elements (to keep thing in numerical order)
1481  for existing_layer in list(range(selected_track_number + 1, max_track_number)):
1482  # Update existing layer number
1483  track = Track.get(number=existing_layer)
1484  track.data["number"] = existing_layer - 1
1485  track.save()
1486 
1487  for clip in Clip.filter(layer=existing_layer):
1488  clip.data["layer"] = int(clip.data["layer"]) - 1
1489  clip.save()
1490 
1491  for trans in Transition.filter(layer=existing_layer):
1492  trans.data["layer"] = int(trans.data["layer"]) - 1
1493  trans.save()
1494 
1495 
1496  # Clear selected track
1498 
1499  ##
1500  # Callback for locking a track
1501  def actionLockTrack_trigger(self, event):
1502  log.info('actionLockTrack_trigger')
1503 
1504  # Get details of track
1505  track_id = self.selected_tracks[0]
1506  selected_track = Track.get(id=track_id)
1507 
1508  # Lock track and save
1509  selected_track.data['lock'] = True
1510  selected_track.save()
1511 
1512  ##
1513  # Callback for unlocking a track
1514  def actionUnlockTrack_trigger(self, event):
1515  log.info('actionUnlockTrack_trigger')
1516 
1517  # Get details of track
1518  track_id = self.selected_tracks[0]
1519  selected_track = Track.get(id=track_id)
1520 
1521  # Lock track and save
1522  selected_track.data['lock'] = False
1523  selected_track.save()
1524 
1525  ##
1526  # Callback for renaming track
1527  def actionRenameTrack_trigger(self, event):
1528  log.info('actionRenameTrack_trigger')
1529 
1530  # Get translation function
1531  _ = get_app()._tr
1532 
1533  # Get details of track
1534  track_id = self.selected_tracks[0]
1535  selected_track = Track.get(id=track_id)
1536  track_name = selected_track.data["label"] or _("Track %s") % selected_track.data["number"]
1537 
1538  text, ok = QInputDialog.getText(self, _('Rename Track'), _('Track Name:'), text=track_name)
1539  if ok:
1540  # Update track
1541  selected_track.data["label"] = text
1542  selected_track.save()
1543 
1544  def actionRemoveMarker_trigger(self, event):
1545  log.info('actionRemoveMarker_trigger')
1546 
1547  for marker_id in self.selected_markers:
1548  marker = Marker.filter(id=marker_id)
1549  for m in marker:
1550  # Remove track
1551  m.delete()
1552 
1554  self.sliderZoom.setValue(self.sliderZoom.value() - self.sliderZoom.singleStep())
1555 
1557  self.sliderZoom.setValue(self.sliderZoom.value() + self.sliderZoom.singleStep())
1558 
1559  def actionFullscreen_trigger(self, event):
1560  # Toggle fullscreen mode
1561  if not self.isFullScreen():
1562  self.showFullScreen()
1563  else:
1564  self.showNormal()
1565 
1567  log.info("Show file properties")
1568 
1569  # Loop through selected files (set 1 selected file if more than 1)
1570  f = None
1571  for file_id in self.selected_files:
1572  # Find matching file
1573  f = File.get(id=file_id)
1574 
1575  # show dialog
1576  from windows.file_properties import FileProperties
1577  win = FileProperties(f)
1578  # Run the dialog event loop - blocking interaction on this window during that time
1579  result = win.exec_()
1580  if result == QDialog.Accepted:
1581  log.info('File Properties Finished')
1582  else:
1583  log.info('File Properties Cancelled')
1584 
1585  def actionDetailsView_trigger(self, event):
1586  log.info("Switch to Details View")
1587 
1588  # Get settings
1589  app = get_app()
1590  s = settings.get_settings()
1591 
1592  # Prepare treeview for deletion
1593  if self.filesTreeView:
1594  self.filesTreeView.prepare_for_delete()
1595 
1596  # Files
1597  if app.context_menu_object == "files":
1598  s.set("file_view", "details")
1599  self.tabFiles.layout().removeWidget(self.filesTreeView)
1600  self.filesTreeView.deleteLater()
1601  self.filesTreeView = None
1602  self.filesTreeView = FilesTreeView(self)
1603  self.tabFiles.layout().addWidget(self.filesTreeView)
1604 
1605  # Transitions
1606  elif app.context_menu_object == "transitions":
1607  s.set("transitions_view", "details")
1608  self.tabTransitions.layout().removeWidget(self.transitionsTreeView)
1609  self.transitionsTreeView.deleteLater()
1611  self.transitionsTreeView = TransitionsTreeView(self)
1612  self.tabTransitions.layout().addWidget(self.transitionsTreeView)
1613 
1614  # Effects
1615  elif app.context_menu_object == "effects":
1616  s.set("effects_view", "details")
1617  self.tabEffects.layout().removeWidget(self.effectsTreeView)
1618  self.effectsTreeView.deleteLater()
1619  self.effectsTreeView = None
1620  self.effectsTreeView = EffectsTreeView(self)
1621  self.tabEffects.layout().addWidget(self.effectsTreeView)
1622 
1624  log.info("Switch to Thumbnail View")
1625 
1626  # Get settings
1627  app = get_app()
1628  s = settings.get_settings()
1629 
1630  # Prepare treeview for deletion
1631  if self.filesTreeView:
1632  self.filesTreeView.prepare_for_delete()
1633 
1634  # Files
1635  if app.context_menu_object == "files":
1636  s.set("file_view", "thumbnail")
1637  self.tabFiles.layout().removeWidget(self.filesTreeView)
1638  self.filesTreeView.deleteLater()
1639  self.filesTreeView = None
1640  self.filesTreeView = FilesListView(self)
1641  self.tabFiles.layout().addWidget(self.filesTreeView)
1642 
1643  # Transitions
1644  elif app.context_menu_object == "transitions":
1645  s.set("transitions_view", "thumbnail")
1646  self.tabTransitions.layout().removeWidget(self.transitionsTreeView)
1647  self.transitionsTreeView.deleteLater()
1648  self.transitionsTreeView = None
1649  self.transitionsTreeView = TransitionsListView(self)
1650  self.tabTransitions.layout().addWidget(self.transitionsTreeView)
1651 
1652  # Effects
1653  elif app.context_menu_object == "effects":
1654  s.set("effects_view", "thumbnail")
1655  self.tabEffects.layout().removeWidget(self.effectsTreeView)
1656  self.effectsTreeView.deleteLater()
1657  self.effectsTreeView = None
1658  self.effectsTreeView = EffectsListView(self)
1659  self.tabEffects.layout().addWidget(self.effectsTreeView)
1660 
1661  def resize_contents(self):
1662  if self.filesTreeView:
1664 
1665  ##
1666  # Get a list of all dockable widgets
1667  def getDocks(self):
1668  return [self.dockFiles,
1669  self.dockTransitions,
1670  self.dockEffects,
1671  self.dockVideo,
1672  self.dockProperties]
1673 
1674  ##
1675  # Remove all dockable widgets on main screen
1676  def removeDocks(self):
1677  for dock in self.getDocks():
1678  self.removeDockWidget(dock)
1679 
1680  ##
1681  # Add all dockable widgets to the same dock area on the main screen
1682  def addDocks(self, docks, area):
1683  for dock in docks:
1684  self.addDockWidget(area, dock)
1685 
1686  ##
1687  # Float or Un-Float all dockable widgets above main screen
1688  def floatDocks(self, is_floating):
1689  for dock in self.getDocks():
1690  dock.setFloating(is_floating)
1691 
1692  ##
1693  # Show all dockable widgets on the main screen
1694  def showDocks(self, docks):
1695  for dock in docks:
1696  if get_app().window.dockWidgetArea(dock) != Qt.NoDockWidgetArea:
1697  # Only show correctly docked widgets
1698  dock.show()
1699 
1700  ##
1701  # Freeze all dockable widgets on the main screen (no float, moving, or closing)
1702  def freezeDocks(self):
1703  for dock in self.getDocks():
1704  dock.setFeatures(QDockWidget.NoDockWidgetFeatures)
1705 
1706  ##
1707  # Un-freeze all dockable widgets on the main screen (allow them to be moved, closed, and floated)
1708  def unFreezeDocks(self):
1709  for dock in self.getDocks():
1710  dock.setFeatures(QDockWidget.AllDockWidgetFeatures)
1711 
1712  ##
1713  # Hide all dockable widgets on the main screen
1714  def hideDocks(self):
1715  for dock in self.getDocks():
1716  dock.hide()
1717 
1718  ##
1719  # Switch to the default / simple view
1720  def actionSimple_View_trigger(self, event):
1721  self.removeDocks()
1722  self.addDocks([self.dockFiles, self.dockTransitions, self.dockEffects, self.dockVideo], Qt.TopDockWidgetArea)
1723  self.floatDocks(False)
1724  self.tabifyDockWidget(self.dockFiles, self.dockTransitions)
1725  self.tabifyDockWidget(self.dockTransitions, self.dockEffects)
1726  self.showDocks([self.dockFiles, self.dockTransitions, self.dockEffects, self.dockVideo])
1727 
1728  # Set initial size of docks
1729  simple_state = "AAAA/wAAAAD9AAAAAwAAAAAAAAD8AAAA9PwCAAAAAfwAAAILAAAA9AAAAAAA////+v////8CAAAAAvsAAAAcAGQAbwBjAGsAUAByAG8AcABlAHIAdABpAGUAcwAAAAAA/////wAAAKEA////+wAAABgAZABvAGMAawBLAGUAeQBmAHIAYQBtAGUAAAAAAP////8AAAATAP///wAAAAEAAAEcAAABQPwCAAAAAfsAAAAYAGQAbwBjAGsASwBlAHkAZgByAGEAbQBlAQAAAVgAAAAVAAAAAAAAAAAAAAACAAAEqwAAAdz8AQAAAAL8AAAAAAAAAWQAAAB7AP////oAAAAAAgAAAAP7AAAAEgBkAG8AYwBrAEYAaQBsAGUAcwEAAAAA/////wAAAJgA////+wAAAB4AZABvAGMAawBUAHIAYQBuAHMAaQB0AGkAbwBuAHMBAAAAAP////8AAACYAP////sAAAAWAGQAbwBjAGsARQBmAGYAZQBjAHQAcwEAAAAA/////wAAAJgA////+wAAABIAZABvAGMAawBWAGkAZABlAG8BAAABagAAA0EAAAA6AP///wAABKsAAAD2AAAABAAAAAQAAAAIAAAACPwAAAABAAAAAgAAAAEAAAAOAHQAbwBvAGwAQgBhAHIBAAAAAP////8AAAAAAAAAAA=="
1730  self.restoreState(qt_types.str_to_bytes(simple_state))
1731  QCoreApplication.processEvents()
1732 
1733 
1734  ##
1735  # Switch to an alternative view
1737  self.removeDocks()
1738 
1739  # Add Docks
1740  self.addDocks([self.dockFiles, self.dockTransitions, self.dockVideo], Qt.TopDockWidgetArea)
1741  self.addDocks([self.dockEffects], Qt.RightDockWidgetArea)
1742  self.addDocks([self.dockProperties], Qt.LeftDockWidgetArea)
1743 
1744  self.floatDocks(False)
1745  self.showDocks([self.dockFiles, self.dockTransitions, self.dockVideo, self.dockEffects, self.dockProperties])
1746 
1747  # Set initial size of docks
1748  advanced_state = "AAAA/wAAAAD9AAAAAwAAAAAAAAD8AAABQPwCAAAAAfwAAAG/AAABQAAAAKEA////+gAAAAACAAAAAvsAAAAcAGQAbwBjAGsAUAByAG8AcABlAHIAdABpAGUAcwEAAAAA/////wAAAKEA////+wAAABgAZABvAGMAawBLAGUAeQBmAHIAYQBtAGUAAAAAAP////8AAAATAP///wAAAAEAAAEcAAABQPwCAAAAAvsAAAAYAGQAbwBjAGsASwBlAHkAZgByAGEAbQBlAQAAAVgAAAAVAAAAAAAAAAD7AAAAFgBkAG8AYwBrAEUAZgBmAGUAYwB0AHMBAAABvwAAAUAAAACYAP///wAAAAIAAASrAAABkvwBAAAAA/sAAAASAGQAbwBjAGsARgBpAGwAZQBzAQAAAAAAAAFeAAAAcAD////7AAAAHgBkAG8AYwBrAFQAcgBhAG4AcwBpAHQAaQBvAG4AcwEAAAFkAAABAAAAAHAA////+wAAABIAZABvAGMAawBWAGkAZABlAG8BAAACagAAAkEAAAA6AP///wAAAocAAAFAAAAABAAAAAQAAAAIAAAACPwAAAABAAAAAgAAAAEAAAAOAHQAbwBvAGwAQgBhAHIBAAAAAP////8AAAAAAAAAAA=="
1749  self.restoreState(qt_types.str_to_bytes(advanced_state))
1750  QCoreApplication.processEvents()
1751 
1752  ##
1753  # Freeze all dockable widgets on the main screen
1754  def actionFreeze_View_trigger(self, event):
1755  self.freezeDocks()
1756  self.actionFreeze_View.setVisible(False)
1757  self.actionUn_Freeze_View.setVisible(True)
1758 
1759  ##
1760  # Un-Freeze all dockable widgets on the main screen
1762  self.unFreezeDocks()
1763  self.actionFreeze_View.setVisible(True)
1764  self.actionUn_Freeze_View.setVisible(False)
1765 
1766  ##
1767  # Show all dockable widgets
1768  def actionShow_All_trigger(self, event):
1769  self.showDocks(self.getDocks())
1770 
1771  ##
1772  # Show tutorial again
1773  def actionTutorial_trigger(self, event):
1774  s = settings.get_settings()
1775 
1776  # Clear tutorial settings
1777  s.set("tutorial_enabled", True)
1778  s.set("tutorial_ids", "")
1779 
1780  # Show first tutorial dialog again
1781  if self.tutorial_manager:
1782  self.tutorial_manager.exit_manager()
1783  self.tutorial_manager = TutorialManager(self)
1784 
1785  ##
1786  # Set the window title based on a variety of factors
1787  def SetWindowTitle(self, profile=None):
1788 
1789  # Get translation function
1790  _ = get_app()._tr
1791 
1792  if not profile:
1793  profile = get_app().project.get(["profile"])
1794 
1795  # Determine if the project needs saving (has any unsaved changes)
1796  save_indicator = ""
1797  if get_app().project.needs_save():
1798  save_indicator = "*"
1799  self.actionSave.setEnabled(True)
1800  else:
1801  self.actionSave.setEnabled(False)
1802 
1803  # Is this a saved project?
1804  if not get_app().project.current_filepath:
1805  # Not saved yet
1806  self.setWindowTitle("%s %s [%s] - %s" % (save_indicator, _("Untitled Project"), profile, "OpenShot Video Editor"))
1807  else:
1808  # Yes, project is saved
1809  # Get just the filename
1810  parent_path, filename = os.path.split(get_app().project.current_filepath)
1811  filename, ext = os.path.splitext(filename)
1812  filename = filename.replace("_", " ").replace("-", " ").capitalize()
1813  self.setWindowTitle("%s %s [%s] - %s" % (save_indicator, filename, profile, "OpenShot Video Editor"))
1814 
1815  # Update undo and redo buttons enabled/disabled to available changes
1816  def updateStatusChanged(self, undo_status, redo_status):
1817  log.info('updateStatusChanged')
1818  self.actionUndo.setEnabled(undo_status)
1819  self.actionRedo.setEnabled(redo_status)
1820  self.SetWindowTitle()
1821 
1822  # Add to the selected items
1823  def addSelection(self, item_id, item_type, clear_existing=False):
1824  log.info('main::addSelection: item_id: %s, item_type: %s, clear_existing: %s' % (item_id, item_type, clear_existing))
1825 
1826  # Clear existing selection (if needed)
1827  if clear_existing:
1828  if item_type == "clip":
1829  self.selected_clips.clear()
1830  elif item_type == "transition":
1831  self.selected_transitions.clear()
1832  elif item_type == "effect":
1833  self.selected_effects.clear()
1834 
1835  # Clear transform (if any)
1836  self.TransformSignal.emit("")
1837 
1838  if item_id:
1839  # If item_id is not blank, store it
1840  if item_type == "clip" and item_id not in self.selected_clips:
1841  self.selected_clips.append(item_id)
1842  elif item_type == "transition" and item_id not in self.selected_transitions:
1843  self.selected_transitions.append(item_id)
1844  elif item_type == "effect" and item_id not in self.selected_effects:
1845  self.selected_effects.append(item_id)
1846 
1847  # Change selected item in properties view
1848  self.show_property_id = item_id
1849  self.show_property_type = item_type
1850  self.show_property_timer.start()
1851 
1852  # Remove from the selected items
1853  def removeSelection(self, item_id, item_type):
1854  # Remove existing selection (if any)
1855  if item_id:
1856  if item_type == "clip" and item_id in self.selected_clips:
1857  self.selected_clips.remove(item_id)
1858  elif item_type == "transition" and item_id in self.selected_transitions:
1859  self.selected_transitions.remove(item_id)
1860  elif item_type == "effect" and item_id in self.selected_effects:
1861  self.selected_effects.remove(item_id)
1862 
1863  # Clear transform (if any)
1864  get_app().window.TransformSignal.emit("")
1865 
1866  # Move selection to next selected clip (if any)
1867  self.show_property_id = ""
1868  self.show_property_type = ""
1869  if item_type == "clip" and self.selected_clips:
1870  self.show_property_id = self.selected_clips[0]
1871  self.show_property_type = item_type
1872  elif item_type == "transition" and self.selected_transitions:
1873  self.show_property_id = self.selected_transitions[0]
1874  self.show_property_type = item_type
1875  elif item_type == "effect" and self.selected_effects:
1876  self.show_property_id = self.selected_effects[0]
1877  self.show_property_type = item_type
1878 
1879  # Change selected item in properties view
1880  self.show_property_timer.start()
1881 
1882  # Update window settings in setting store
1883  def save_settings(self):
1884  s = settings.get_settings()
1885 
1886  # Save window state and geometry (saves toolbar and dock locations)
1887  s.set('window_state', qt_types.bytes_to_str(self.saveState()))
1888  s.set('window_geometry', qt_types.bytes_to_str(self.saveGeometry()))
1889 
1890  # Get window settings from setting store
1891  def load_settings(self):
1892  s = settings.get_settings()
1893 
1894  # Window state and geometry (also toolbar and dock locations)
1895  if s.get('window_geometry'): self.restoreGeometry(qt_types.str_to_bytes(s.get('window_geometry')))
1896  if s.get('window_state'): self.restoreState(qt_types.str_to_bytes(s.get('window_state')))
1897 
1898  # Load Recent Projects
1899  self.load_recent_menu()
1900 
1901  ##
1902  # Clear and load the list of recent menu items
1903  def load_recent_menu(self):
1904  s = settings.get_settings()
1905  _ = get_app()._tr # Get translation function
1906 
1907  # Get list of recent projects
1908  recent_projects = s.get("recent_projects")
1909 
1910  # Add Recent Projects menu (after Open File)
1911  import functools
1912  if not self.recent_menu:
1913  # Create a new recent menu
1914  self.recent_menu = self.menuFile.addMenu(QIcon.fromTheme("document-open-recent"), _("Recent Projects"))
1915  self.menuFile.insertMenu(self.actionRecent_Placeholder, self.recent_menu)
1916  else:
1917  # Clear the existing children
1918  self.recent_menu.clear()
1919 
1920  # Add recent projects to menu
1921  for file_path in reversed(recent_projects):
1922  new_action = self.recent_menu.addAction(file_path)
1923  new_action.triggered.connect(functools.partial(self.recent_project_clicked, file_path))
1924 
1925  ##
1926  # Load a recent project when clicked
1927  def recent_project_clicked(self, file_path):
1928 
1929  # Load project file
1930  self.open_project(file_path)
1931 
1932  def setup_toolbars(self):
1933  _ = get_app()._tr # Get translation function
1934 
1935  # Start undo and redo actions disabled
1936  self.actionUndo.setEnabled(False)
1937  self.actionRedo.setEnabled(False)
1938 
1939  # Add files toolbar =================================================================================
1940  self.filesToolbar = QToolBar("Files Toolbar")
1941  self.filesActionGroup = QActionGroup(self)
1942  self.filesActionGroup.setExclusive(True)
1943  self.filesActionGroup.addAction(self.actionFilesShowAll)
1944  self.filesActionGroup.addAction(self.actionFilesShowVideo)
1945  self.filesActionGroup.addAction(self.actionFilesShowAudio)
1946  self.filesActionGroup.addAction(self.actionFilesShowImage)
1947  self.actionFilesShowAll.setChecked(True)
1948  self.filesToolbar.addAction(self.actionFilesShowAll)
1949  self.filesToolbar.addAction(self.actionFilesShowVideo)
1950  self.filesToolbar.addAction(self.actionFilesShowAudio)
1951  self.filesToolbar.addAction(self.actionFilesShowImage)
1952  self.filesFilter = QLineEdit()
1953  self.filesFilter.setObjectName("filesFilter")
1954  self.filesFilter.setPlaceholderText(_("Filter"))
1955  self.filesToolbar.addWidget(self.filesFilter)
1956  self.actionFilesClear.setEnabled(False)
1957  self.filesToolbar.addAction(self.actionFilesClear)
1958  self.tabFiles.layout().addWidget(self.filesToolbar)
1959 
1960  # Add transitions toolbar =================================================================================
1961  self.transitionsToolbar = QToolBar("Transitions Toolbar")
1962  self.transitionsActionGroup = QActionGroup(self)
1963  self.transitionsActionGroup.setExclusive(True)
1964  self.transitionsActionGroup.addAction(self.actionTransitionsShowAll)
1965  self.transitionsActionGroup.addAction(self.actionTransitionsShowCommon)
1966  self.actionTransitionsShowAll.setChecked(True)
1967  self.transitionsToolbar.addAction(self.actionTransitionsShowAll)
1968  self.transitionsToolbar.addAction(self.actionTransitionsShowCommon)
1969  self.transitionsFilter = QLineEdit()
1970  self.transitionsFilter.setObjectName("transitionsFilter")
1971  self.transitionsFilter.setPlaceholderText(_("Filter"))
1972  self.transitionsToolbar.addWidget(self.transitionsFilter)
1973  self.actionTransitionsClear.setEnabled(False)
1974  self.transitionsToolbar.addAction(self.actionTransitionsClear)
1975  self.tabTransitions.layout().addWidget(self.transitionsToolbar)
1976 
1977  # Add effects toolbar =================================================================================
1978  self.effectsToolbar = QToolBar("Effects Toolbar")
1979  self.effectsActionGroup = QActionGroup(self)
1980  self.effectsActionGroup.setExclusive(True)
1981  self.effectsActionGroup.addAction(self.actionEffectsShowAll)
1982  self.effectsActionGroup.addAction(self.actionEffectsShowVideo)
1983  self.effectsActionGroup.addAction(self.actionEffectsShowAudio)
1984  self.actionEffectsShowAll.setChecked(True)
1985  self.effectsToolbar.addAction(self.actionEffectsShowAll)
1986  self.effectsToolbar.addAction(self.actionEffectsShowVideo)
1987  self.effectsToolbar.addAction(self.actionEffectsShowAudio)
1988  self.effectsFilter = QLineEdit()
1989  self.effectsFilter.setObjectName("effectsFilter")
1990  self.effectsFilter.setPlaceholderText(_("Filter"))
1991  self.effectsToolbar.addWidget(self.effectsFilter)
1992  self.actionEffectsClear.setEnabled(False)
1993  self.effectsToolbar.addAction(self.actionEffectsClear)
1994  self.tabEffects.layout().addWidget(self.effectsToolbar)
1995 
1996  # Add Video Preview toolbar ==========================================================================
1997  self.videoToolbar = QToolBar("Video Toolbar")
1998 
1999  # Add left spacer
2000  spacer = QWidget(self)
2001  spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
2002  self.videoToolbar.addWidget(spacer)
2003 
2004  # Playback controls
2005  self.videoToolbar.addAction(self.actionJumpStart)
2006  self.videoToolbar.addAction(self.actionRewind)
2007  self.videoToolbar.addAction(self.actionPlay)
2008  self.videoToolbar.addAction(self.actionFastForward)
2009  self.videoToolbar.addAction(self.actionJumpEnd)
2010  self.actionPlay.setCheckable(True)
2011 
2012  # Add right spacer
2013  spacer = QWidget(self)
2014  spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
2015  self.videoToolbar.addWidget(spacer)
2016 
2017  self.tabVideo.layout().addWidget(self.videoToolbar)
2018 
2019  # Add Timeline toolbar ================================================================================
2020  self.timelineToolbar = QToolBar("Timeline Toolbar", self)
2021 
2022  self.timelineToolbar.addAction(self.actionAddTrack)
2023  self.timelineToolbar.addSeparator()
2024 
2025  # rest of options
2026  self.timelineToolbar.addAction(self.actionSnappingTool)
2027  self.timelineToolbar.addAction(self.actionRazorTool)
2028  self.timelineToolbar.addSeparator()
2029  self.timelineToolbar.addAction(self.actionAddMarker)
2030  self.timelineToolbar.addAction(self.actionPreviousMarker)
2031  self.timelineToolbar.addAction(self.actionNextMarker)
2032  self.timelineToolbar.addSeparator()
2033 
2034  # Get project's initial zoom value
2035  initial_scale = get_app().project.get(["scale"]) or 16
2036  # Round non-exponential scale down to next lowest power of 2
2037  initial_zoom = secondsToZoom(initial_scale)
2038 
2039  # Setup Zoom slider
2040  self.sliderZoom = QSlider(Qt.Horizontal, self)
2041  self.sliderZoom.setPageStep(1)
2042  self.sliderZoom.setRange(0, 30)
2043  self.sliderZoom.setValue(initial_zoom)
2044  self.sliderZoom.setInvertedControls(True)
2045  self.sliderZoom.resize(100, 16)
2046 
2047  self.zoomScaleLabel = QLabel( _("{} seconds").format(zoomToSeconds(self.sliderZoom.value())) )
2048 
2049  # add zoom widgets
2050  self.timelineToolbar.addAction(self.actionTimelineZoomIn)
2051  self.timelineToolbar.addWidget(self.sliderZoom)
2052  self.timelineToolbar.addAction(self.actionTimelineZoomOut)
2053  self.timelineToolbar.addWidget(self.zoomScaleLabel)
2054 
2055  # Add timeline toolbar to web frame
2056  self.frameWeb.addWidget(self.timelineToolbar)
2057 
2058  ##
2059  # Clear all selection containers
2060  def clearSelections(self):
2061  self.selected_files = []
2062  self.selected_clips = []
2065  self.selected_tracks = []
2067 
2068  # Clear selection in properties view
2069  if self.propertyTableView:
2070  self.propertyTableView.loadProperties.emit("", "")
2071 
2072  ##
2073  # Handle the callback for detecting the current version on openshot.org
2074  def foundCurrentVersion(self, version):
2075  log.info('foundCurrentVersion: Found the latest version: %s' % version)
2076  _ = get_app()._tr
2077 
2078  # Compare versions (alphabetical compare of version strings should work fine)
2079  if info.VERSION < version:
2080  # Add spacer and 'New Version Available' toolbar button (default hidden)
2081  spacer = QWidget(self)
2082  spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
2083  self.toolBar.addWidget(spacer)
2084 
2085  # Update text for QAction
2086  self.actionUpdate.setVisible(True)
2087  self.actionUpdate.setText(_("Update Available"))
2088  self.actionUpdate.setToolTip(_("Update Available: <b>%s</b>") % version)
2089 
2090  # Add update available button (with icon and text)
2091  updateButton = QToolButton()
2092  updateButton.setDefaultAction(self.actionUpdate)
2093  updateButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
2094  self.toolBar.addWidget(updateButton)
2095 
2096  ##
2097  # Move tutorial dialogs also (if any)
2098  def moveEvent(self, event):
2099  if self.tutorial_manager:
2100  self.tutorial_manager.re_position_dialog()
2101 
2102  ##
2103  # Filter out certain types of window events
2104  def eventFilter(self, object, e):
2105  if e.type() == QEvent.WindowActivate:
2106  self.tutorial_manager.re_show_dialog()
2107  elif e.type() == QEvent.WindowStateChange and self.isMinimized():
2108  self.tutorial_manager.minimize()
2109 
2110  return False
2111 
2112  ##
2113  # Callback for show property timer
2115 
2116  # Stop timer
2117  self.show_property_timer.stop()
2118 
2119  # Emit load properties signal
2120  self.propertyTableView.loadProperties.emit(self.show_property_id, self.show_property_type)
2121 
2122  ##
2123  # Initialize all keyboard shortcuts from the settings file
2125 
2126  # Translate object
2127  _ = get_app()._tr
2128 
2129  # Update all action-based shortcuts (from settings file)
2130  for shortcut in self.getAllKeyboardShortcuts():
2131  for action in self.findChildren(QAction):
2132  if shortcut.get('setting') == action.objectName():
2133  action.setShortcut(QKeySequence(_(shortcut.get('value'))))
2134 
2135  ##
2136  # Set the correct cache settings for the timeline
2138  # Load user settings
2139  s = settings.get_settings()
2140  log.info("InitCacheSettings")
2141  log.info("cache-mode: %s" % s.get("cache-mode"))
2142  log.info("cache-limit-mb: %s" % s.get("cache-limit-mb"))
2143 
2144  # Get MB limit of cache (and convert to bytes)
2145  cache_limit = s.get("cache-limit-mb") * 1024 * 1024 # Convert MB to Bytes
2146 
2147  # Clear old cache
2148  new_cache_object = None
2149  if s.get("cache-mode") == "CacheMemory":
2150  # Create CacheMemory object, and set on timeline
2151  log.info("Creating CacheMemory object with %s byte limit" % cache_limit)
2152  new_cache_object = openshot.CacheMemory(cache_limit)
2153  self.timeline_sync.timeline.SetCache(new_cache_object)
2154 
2155  elif s.get("cache-mode") == "CacheDisk":
2156  # Create CacheDisk object, and set on timeline
2157  log.info("Creating CacheDisk object with %s byte limit at %s" % (cache_limit, info.PREVIEW_CACHE_PATH))
2158  image_format = s.get("cache-image-format")
2159  image_quality = s.get("cache-quality")
2160  image_scale = s.get("cache-scale")
2161  new_cache_object = openshot.CacheDisk(info.PREVIEW_CACHE_PATH, image_format, image_quality, image_scale, cache_limit)
2162  self.timeline_sync.timeline.SetCache(new_cache_object)
2163 
2164  # Clear old cache before it goes out of scope
2165  if self.cache_object:
2166  self.cache_object.Clear()
2167  # Update cache reference, so it doesn't go out of scope
2168  self.cache_object = new_cache_object
2169 
2170  ##
2171  # Connect to Unity launcher (for Linux)
2172  def FrameExported(self, path, start_frame, end_frame, current_frame):
2173  try:
2174  if sys.platform == "linux" and self.has_launcher:
2175  if not self.unity_launchers:
2176  # Get launcher only once
2177  from gi.repository import Unity
2178  self.unity_launchers.append(Unity.LauncherEntry.get_for_desktop_id("openshot-qt.desktop"))
2179  self.unity_launchers.append(Unity.LauncherEntry.get_for_desktop_id("appimagekit-openshot-qt.desktop"))
2180 
2181  # Set progress and show progress bar
2182  for launcher in self.unity_launchers:
2183  launcher.set_property("progress", current_frame / (end_frame - start_frame))
2184  launcher.set_property("progress_visible", True)
2185 
2186  except:
2187  # Just ignore
2188  self.has_launcher = False
2189 
2190  ##
2191  # Export has completed
2192  def ExportFinished(self, path):
2193  try:
2194  if sys.platform == "linux" and self.has_launcher:
2195  for launcher in self.unity_launchers:
2196  # Set progress on Unity launcher and hide progress bar
2197  launcher.set_property("progress", 0.0)
2198  launcher.set_property("progress_visible", False)
2199  except:
2200  pass
2201 
2202  ##
2203  # Handle transform signal (to keep track of whether a transform is happening or not)
2204  def transformTriggered(self, clip_id):
2205  if clip_id and clip_id in self.selected_clips:
2206  self.is_transforming = True
2207  else:
2208  self.is_transforming = False
2209 
2210 
2211  def __init__(self, mode=None):
2212 
2213  # Create main window base class
2214  QMainWindow.__init__(self)
2215  self.mode = mode # None or unittest (None is normal usage)
2216  self.initialized = False
2217 
2218  # set window on app for reference during initialization of children
2219  get_app().window = self
2220  _ = get_app()._tr
2221 
2222  # Load user settings for window
2223  s = settings.get_settings()
2224  self.recent_menu = None
2225 
2226  # Track metrics
2227  track_metric_session() # start session
2228 
2229  # Set unique install id (if blank)
2230  if not s.get("unique_install_id"):
2231  s.set("unique_install_id", str(uuid4()))
2232 
2233  # Track 1st launch metric
2234  track_metric_screen("initial-launch-screen")
2235 
2236  # Track main screen
2237  track_metric_screen("main-screen")
2238 
2239  # Create blank tutorial manager
2240  self.tutorial_manager = None
2241 
2242  # Load UI from designer
2243  ui_util.load_ui(self, self.ui_path)
2244 
2245  # Set all keyboard shortcuts from the settings file
2246  self.InitKeyboardShortcuts()
2247 
2248  # Init UI
2249  ui_util.init_ui(self)
2250 
2251  # Setup toolbars that aren't on main window, set initial state of items, etc
2252  self.setup_toolbars()
2253 
2254  # Add window as watcher to receive undo/redo status updates
2255  get_app().updates.add_watcher(self)
2256 
2257  # Get current version of OpenShot via HTTP
2258  self.FoundVersionSignal.connect(self.foundCurrentVersion)
2260 
2261  # Connect signals
2262  self.is_transforming = False
2263  self.TransformSignal.connect(self.transformTriggered)
2264  if not self.mode == "unittest":
2265  self.RecoverBackup.connect(self.recover_backup)
2266 
2267  # Create the timeline sync object (used for previewing timeline)
2268  self.timeline_sync = TimelineSync(self)
2269 
2270  # Setup timeline
2271  self.timeline = TimelineWebView(self)
2272  self.frameWeb.layout().addWidget(self.timeline)
2273 
2274  # Setup files tree
2275  if s.get("file_view") == "details":
2276  self.filesTreeView = FilesTreeView(self)
2277  else:
2278  self.filesTreeView = FilesListView(self)
2279  self.tabFiles.layout().addWidget(self.filesTreeView)
2280  self.filesTreeView.setFocus()
2281 
2282  # Setup transitions tree
2283  if s.get("transitions_view") == "details":
2284  self.transitionsTreeView = TransitionsTreeView(self)
2285  else:
2286  self.transitionsTreeView = TransitionsListView(self)
2287  self.tabTransitions.layout().addWidget(self.transitionsTreeView)
2288 
2289  # Setup effects tree
2290  if s.get("effects_view") == "details":
2291  self.effectsTreeView = EffectsTreeView(self)
2292  else:
2293  self.effectsTreeView = EffectsListView(self)
2294  self.tabEffects.layout().addWidget(self.effectsTreeView)
2295 
2296  # Process events before continuing
2297  # TODO: Figure out why this is needed for a backup recovery to correctly show up on the timeline
2298  get_app().processEvents()
2299 
2300  # Setup properties table
2301  self.txtPropertyFilter.setPlaceholderText(_("Filter"))
2302  self.propertyTableView = PropertiesTableView(self)
2303  self.selectionLabel = SelectionLabel(self)
2304  self.dockPropertiesContent.layout().addWidget(self.selectionLabel, 0, 1)
2305  self.dockPropertiesContent.layout().addWidget(self.propertyTableView, 2, 1)
2306 
2307  # Init selection containers
2308  self.clearSelections()
2309 
2310  # Show Property timer
2311  # Timer to use a delay before showing properties (to prevent a mass selection from trying
2312  # to update the property model hundreds of times)
2313  self.show_property_id = None
2314  self.show_property_type = None
2315  self.show_property_timer = QTimer()
2316  self.show_property_timer.setInterval(100)
2317  self.show_property_timer.timeout.connect(self.show_property_timeout)
2318  self.show_property_timer.stop()
2319 
2320  # Setup video preview QWidget
2321  self.videoPreview = VideoWidget()
2322  self.tabVideo.layout().insertWidget(0, self.videoPreview)
2323 
2324  # Load window state and geometry
2325  self.load_settings()
2326 
2327  # Setup Cache settings
2328  self.cache_object = None
2329  self.InitCacheSettings()
2330 
2331  # Start the preview thread
2332  self.preview_parent = PreviewParent()
2333  self.preview_parent.Init(self, self.timeline_sync.timeline, self.videoPreview)
2334  self.preview_thread = self.preview_parent.worker
2335 
2336  # Set pause callback
2337  self.PauseSignal.connect(self.handlePausedVideo)
2338 
2339  # QTimer for Autosave
2340  self.auto_save_timer = QTimer(self)
2341  self.auto_save_timer.setInterval(s.get("autosave-interval") * 1000 * 60)
2342  self.auto_save_timer.timeout.connect(self.auto_save_project)
2343  if s.get("enable-auto-save"):
2344  self.auto_save_timer.start()
2345 
2346  # Set hardware decode environment variable
2347  if s.get("hardware_decode"):
2348  os.environ['OS2_DECODE_HW'] = "1"
2349  else:
2350  os.environ['OS2_DECODE_HW'] = "0"
2351 
2352  # Create lock file
2353  self.create_lock_file()
2354 
2355  # Show window
2356  if not self.mode == "unittest":
2357  self.show()
2358 
2359  # Create tutorial manager
2360  self.tutorial_manager = TutorialManager(self)
2361 
2362  # Connect to Unity DBus signal (if linux)
2363  if sys.platform == "linux":
2365  self.has_launcher = True
2366  self.ExportFrame.connect(self.FrameExported)
2367  self.ExportEnded.connect(self.ExportFinished)
2368 
2369  # Install event filter
2370  self.installEventFilter(self)
2371 
2372  # Save settings
2373  s.save()
2374 
2375  # Refresh frame
2376  QTimer.singleShot(100, self.refreshFrameSignal.emit)
2377 
2378  # Main window is initialized
2379  self.initialized = True
def moveEvent(self, event)
Move tutorial dialogs also (if any)
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:149
def website_language()
Get the current website language code for URLs.
Definition: info.py:123
def actionTitle_trigger(self, event)
Definition: main_window.py:340
def actionPreferences_trigger(self, event)
Definition: main_window.py:671
def actionAddTrack_trigger(self, event)
Definition: main_window.py:898
def actionRemove_from_Project_trigger(self, event)
def get_app()
Returns the current QApplication instance of OpenShot.
Definition: app.py:55
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:46
def addDocks(self, docks, area)
Add all dockable widgets to the same dock area on the main screen.
def zoomToSeconds(zoomValue)
Convert zoom factor (slider position) into scale-seconds.
Definition: conversion.py:64
def actionExportVideo_trigger(self, event)
Definition: main_window.py:644
def actionDonate_trigger(self, event)
Definition: main_window.py:762
def actionRedo_trigger(self, event)
Definition: main_window.py:663
def actionRemoveTransition_trigger(self, event)
def tail_file(self, f, n, offset=None)
Read the end of a file (n number of lines)
Definition: main_window.py:268
def secondsToZoom(scaleValue)
Convert a number of seconds to a timeline zoom factor.
Definition: conversion.py:70
def show_property_timeout(self)
Callback for show property timer.
def track_exception_stacktrace(stacktrace, source)
Track an exception/stacktrace has occurred.
Definition: metrics.py:134
def actionSnappingTool_trigger(self, event)
Definition: main_window.py:986
def actionUnlockTrack_trigger(self, event)
Callback for unlocking a track.
def actionTransitionsShowCommon_trigger(self, event)
Definition: main_window.py:706
def actionUndo_trigger(self, event)
Definition: main_window.py:655
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:140
def actionImportImageSequence_trigger(self, event)
Definition: main_window.py:387
def actionDuplicateTitle_trigger(self, event)
Definition: main_window.py:374
def actionFilesShowImage_trigger(self, event)
Definition: main_window.py:700
def actionRemoveTrack_trigger(self, event)
def InitKeyboardShortcuts(self)
Initialize all keyboard shortcuts from the settings file.
def transformTriggered(self, clip_id)
Handle transform signal (to keep track of whether a transform is happening or not) ...
def actionUploadVideo_trigger(self, event)
Definition: main_window.py:633
def actionAdd_to_Timeline_trigger(self, event)
Definition: main_window.py:610
def actionFullscreen_trigger(self, event)
def actionRazorTool_trigger(self, event)
Toggle razor tool on and off.
Definition: main_window.py:995
def get_current_Version()
Get the current version.
Definition: version.py:42
def actionAddTrackAbove_trigger(self, event)
Definition: main_window.py:909
def actionFile_Properties_trigger(self, event)
def actionSave_trigger(self, event)
Definition: main_window.py:535
def actionArrowTool_trigger(self, event)
Definition: main_window.py:983
def getAllKeyboardShortcuts(self)
Get a key sequence back from the setting name.
def actionRemoveMarker_trigger(self, event)
def keyPressEvent(self, event)
Process key press events and match with known shortcuts.
def actionPreview_File_trigger(self, event)
Preview the selected media file.
Definition: main_window.py:808
def handlePausedVideo(self)
Handle the pause signal, by refreshing the properties dialog.
Definition: main_window.py:838
def actionRewind_trigger(self, event)
Definition: main_window.py:860
def actionNew_trigger(self, event)
Definition: main_window.py:286
def load_recent_menu(self)
Clear and load the list of recent menu items.
def actionOpen_trigger(self, event)
Definition: main_window.py:512
def ExportFinished(self, path)
Export has completed.
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:175
def save_project(self, file_path)
Save a project to a file path, and refresh the screen.
Definition: main_window.py:407
def actionTimelineZoomIn_trigger(self, event)
def actionTransitionsShowAll_trigger(self, event)
Definition: main_window.py:703
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:728
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:738
def actionHelpContents_trigger(self, event)
Definition: main_window.py:718
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:715
def actionUpdate_trigger(self, event)
Definition: main_window.py:770
def auto_save_project(self)
Auto save the project.
Definition: main_window.py:555
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:694
def actionEffectsShowVideo_trigger(self, event)
Definition: main_window.py:712
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:691
def actionAnimation_trigger(self, event)
Definition: main_window.py:329
def previewFrame(self, position_seconds, position_frames, time_code)
Preview a specific frame.
Definition: main_window.py:829
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:746
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:44
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:477
def SetWindowTitle(self, profile=None)
Set the window title based on a variety of factors.
def actionClearHistory_trigger(self, event)
Clear history for current project.
Definition: main_window.py:400
Interface for classes that listen for &#39;undo&#39; and &#39;redo&#39; events.
Definition: updates.py:42
def actionNextMarker_trigger(self, event)
def actionEffectsShowAll_trigger(self, event)
Definition: main_window.py:709
def actionAddMarker_trigger(self, event)
def movePlayhead(self, position_frames)
Update playhead position.
Definition: main_window.py:843
def actionRemoveClip_trigger(self, event)
def init_ui(window)
Initialize all child widgets and action of a window or dialog.
Definition: ui_util.py:220
def recover_backup(self)
Recover the backup file (if any)
Definition: main_window.py:141
def destroy_lock_file(self)
Destroy the lock file.
Definition: main_window.py:258
def getShortcutByName(self, setting_name)
Get a key sequence back from the setting name.
def FrameExported(self, path, start_frame, end_frame, current_frame)
Connect to Unity launcher (for Linux)
This class contains the logic for the main window widget.
Definition: main_window.py:68
def track_metric_screen(screen_name)
Track a GUI screen being shown.
Definition: metrics.py:96
def actionPreviousMarker_trigger(self, event)
def updateStatusChanged(self, undo_status, redo_status)
def actionAddTrackBelow_trigger(self, event)
Definition: main_window.py:946
def open_project(self, file_path, clear_thumbnails=True)
Open a project from a file path, and refresh the screen.
Definition: main_window.py:433
def actionSaveAs_trigger(self, event)
Definition: main_window.py:581
def track_metric_error(error_name, is_fatal=False)
Track an error has occurred.
Definition: metrics.py:121
def removeSelection(self, item_id, item_type)
def actionRemoveEffect_trigger(self, event)
def actionTranslate_trigger(self, event)
Definition: main_window.py:754
def actionPlay_trigger(self, event, force=None)
Definition: main_window.py:778
def actionFastForward_trigger(self, event)
Definition: main_window.py:847
def actionFilesShowAudio_trigger(self, event)
Definition: main_window.py:697
def load_ui(window, path)
Load a Qt *.ui file, and also load an XML parsed version.
Definition: ui_util.py:66
def InitCacheSettings(self)
Set the correct cache settings for the timeline.
def actionTimelineZoomOut_trigger(self, event)
def actionShow_All_trigger(self, event)
Show all dockable widgets.
def actionAnimatedTitle_trigger(self, event)
Definition: main_window.py:318
def actionJumpEnd_trigger(self, event)
Definition: main_window.py:879
def actionSplitClip_trigger(self, event)
def __init__(self, mode=None)
def closeEvent(self, event)
Definition: main_window.py:92
def getDocks(self)
Get a list of all dockable widgets.
def actionImportFiles_trigger(self, event)
Definition: main_window.py:597
def actionEditTitle_trigger(self, event)
Definition: main_window.py:351
def actionJumpStart_trigger(self, event)
Definition: main_window.py:873