OpenShot Video Editor  2.0.0
properties_tableview.py
Go to the documentation of this file.
1 ##
2 #
3 # @file
4 # @brief This file contains the properties tableview, used by the main window
5 # @author Jonathan Thomas <jonathan@openshot.org>
6 #
7 # @section LICENSE
8 #
9 # Copyright (c) 2008-2018 OpenShot Studios, LLC
10 # (http://www.openshotstudios.com). This file is part of
11 # OpenShot Video Editor (http://www.openshot.org), an open-source project
12 # dedicated to delivering high quality video editing and animation solutions
13 # to the world.
14 #
15 # OpenShot Video Editor is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 3 of the License, or
18 # (at your option) any later version.
19 #
20 # OpenShot Video Editor is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
24 #
25 # You should have received a copy of the GNU General Public License
26 # along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
27 #
28 
29 import os
30 from functools import partial
31 from PyQt5.QtCore import Qt, QRectF, QLocale, pyqtSignal, Qt, QObject, QTimer
32 from PyQt5.QtGui import *
33 from PyQt5.QtWidgets import QTableView, QAbstractItemView, QMenu, QSizePolicy, QHeaderView, QColorDialog, QItemDelegate, QStyle, QLabel, QPushButton, QHBoxLayout, QFrame
34 
35 from classes.logger import log
36 from classes.app import get_app
37 from classes import info
38 from classes.query import Clip, Effect, Transition, File
39 from windows.models.properties_model import PropertiesModel
40 from windows.models.transition_model import TransitionsModel
41 from windows.models.files_model import FilesModel
42 
43 import openshot
44 
45 try:
46  import json
47 except ImportError:
48  import simplejson as json
49 
50 
51 class PropertyDelegate(QItemDelegate):
52  def __init__(self, parent=None, *args):
53  QItemDelegate.__init__(self, parent, *args)
54 
55  # pixmaps for curve icons
56  self.curve_pixmaps = { openshot.BEZIER: QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.BEZIER)),
57  openshot.LINEAR: QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.LINEAR)),
58  openshot.CONSTANT: QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.CONSTANT))
59  }
60 
61  def paint(self, painter, option, index):
62  painter.save()
63  painter.setRenderHint(QPainter.Antialiasing)
64 
65  # Get data model and selection
66  model = get_app().window.propertyTableView.clip_properties_model.model
67  row = model.itemFromIndex(index).row()
68  selected_label = model.item(row, 0)
69  selected_value = model.item(row, 1)
70  property = selected_label.data()
71 
72  # Get min/max values for this property
73  property_name = property[1]["name"]
74  property_type = property[1]["type"]
75  property_max = property[1]["max"]
76  property_min = property[1]["min"]
77  readonly = property[1]["readonly"]
78  keyframe = property[1]["keyframe"]
79  points = property[1]["points"]
80  interpolation = property[1]["interpolation"]
81 
82  # Calculate percentage value
83  if property_type in ["float", "int"]:
84  # Get the current value
85  current_value = QLocale().system().toDouble(selected_value.text())[0]
86 
87  # Shift my range to be positive
88  if property_min < 0.0:
89  property_shift = 0.0 - property_min
90  property_min += property_shift
91  property_max += property_shift
92  current_value += property_shift
93 
94  # Calculate current value as % of min/max range
95  min_max_range = float(property_max) - float(property_min)
96  value_percent = current_value / min_max_range
97  else:
98  value_percent = 0.0
99 
100  # set background color
101  painter.setPen(QPen(Qt.NoPen))
102  if property_type == "color":
103  # Color keyframe
104  red = property[1]["red"]["value"]
105  green = property[1]["green"]["value"]
106  blue = property[1]["blue"]["value"]
107  painter.setBrush(QBrush(QColor(QColor(red, green, blue))))
108  else:
109  # Normal Keyframe
110  if option.state & QStyle.State_Selected:
111  painter.setBrush(QBrush(QColor("#575757")))
112  else:
113  painter.setBrush(QBrush(QColor("#3e3e3e")))
114 
115  if not readonly:
116  path = QPainterPath()
117  path.addRoundedRect(QRectF(option.rect), 15, 15)
118  painter.fillPath(path, QColor("#3e3e3e"))
119  painter.drawPath(path)
120 
121  # Render mask rectangle
122  painter.setBrush(QBrush(QColor("#000000")))
123  mask_rect = QRectF(option.rect)
124  mask_rect.setWidth(option.rect.width() * value_percent)
125  painter.setClipRect(mask_rect, Qt.IntersectClip)
126 
127  # gradient for value box
128  gradient = QLinearGradient(option.rect.topLeft(), option.rect.topRight())
129  gradient.setColorAt(0, QColor("#828282"))
130  gradient.setColorAt(1, QColor("#828282"))
131 
132  # Render progress
133  painter.setBrush(gradient)
134  path = QPainterPath()
135  value_rect = QRectF(option.rect)
136  path.addRoundedRect(value_rect, 15, 15);
137  painter.fillPath(path, gradient)
138  painter.drawPath(path);
139  painter.setClipping(False)
140 
141  if points > 1:
142  # Draw interpolation icon on top
143  painter.drawPixmap(option.rect.x() + option.rect.width() - 30.0, option.rect.y() + 4, self.curve_pixmaps[interpolation])
144 
145  # set text color
146  painter.setPen(QPen(Qt.white))
147  value = index.data(Qt.DisplayRole)
148  if value:
149  painter.drawText(option.rect, Qt.AlignCenter, value)
150 
151  painter.restore()
152 
153 
154 ##
155 # A Properties Table QWidget used on the main window
156 class PropertiesTableView(QTableView):
157  loadProperties = pyqtSignal(str, str)
158 
159  def mouseMoveEvent(self, event):
160  # Get data model and selection
161  model = self.clip_properties_model.model
162  row = self.indexAt(event.pos()).row()
163  column = self.indexAt(event.pos()).column()
164  if model.item(row, 0):
165  self.selected_label = model.item(row, 0)
166  self.selected_item = model.item(row, 1)
167 
168  # Is the user dragging on the value column
169  if self.selected_label and self.selected_item:
170  frame_number = self.clip_properties_model.frame_number
171 
172  # Get the position of the cursor and % value
173  value_column_x = self.columnViewportPosition(1)
174  value_column_y = value_column_x + self.columnWidth(1)
175  cursor_value = event.x() - value_column_x
176  cursor_value_percent = cursor_value / self.columnWidth(1)
177 
178  try:
179  property = self.selected_label.data()
180  except Exception as ex:
181  # If item is deleted during this drag... an exception can occur
182  # Just ignore, since this is harmless
183  return
184 
185  property_key = property[0]
186  property_name = property[1]["name"]
187  property_type = property[1]["type"]
188  property_max = property[1]["max"]
189  property_min = property[1]["min"]
190  property_value = property[1]["value"]
191  readonly = property[1]["readonly"]
192  item_id, item_type = self.selected_item.data()
193 
194  # Bail if readonly
195  if readonly:
196  return
197 
198  # Get the original data of this item (prior to any updates, for the undo/redo system)
199  if not self.original_data:
200  # Ignore undo/redo history temporarily (to avoid a huge pile of undo/redo history)
201  get_app().updates.ignore_history = True
202 
203  # Find this clip
204  c = None
205  if item_type == "clip":
206  # Get clip object
207  c = Clip.get(id=item_id)
208  elif item_type == "transition":
209  # Get transition object
210  c = Transition.get(id=item_id)
211  elif item_type == "effect":
212  # Get effect object
213  c = Effect.get(id=item_id)
214 
215  if c:
216  if property_key in c.data:
217  # Grab the original data for this item/property
218  self.original_data = c.data
219 
220  # Calculate percentage value
221  if property_type in ["float", "int"]:
222  min_max_range = float(property_max) - float(property_min)
223 
224  # Determine if range is unreasonably long (such as position, start, end, etc.... which can be huge #'s)
225  if min_max_range > 1000.0:
226  # Get the current value
227  self.new_value = QLocale().system().toDouble(self.selected_item.text())[0]
228 
229  # Huge range - increment / decrement slowly
230  if self.previous_x == -1:
231  # init previous_x for the first time
232  self.previous_x = event.x()
233  # calculate # of pixels dragged
234  drag_diff = self.previous_x - event.x()
235  if drag_diff > 0:
236  # Move to the left by a small amount
237  self.new_value -= 0.50
238  elif drag_diff < 0:
239  # Move to the right by a small amount
240  self.new_value += 0.50
241  # update previous x
242  self.previous_x = event.x()
243  else:
244  # Small range - use cursor % to calculate new value
245  self.new_value = property_min + (min_max_range * cursor_value_percent)
246 
247  # Clamp value between min and max (just incase user drags too big)
248  self.new_value = max(property_min, self.new_value)
249  self.new_value = min(property_max, self.new_value)
250 
251  # Update value of this property
252  self.clip_properties_model.value_updated(self.selected_item, -1, self.new_value)
253 
254  # Repaint
255  self.viewport().update()
256 
257  def mouseReleaseEvent(self, event):
258  # Inform UpdateManager to accept updates, and only store our final update
259  get_app().updates.ignore_history = False
260 
261  # Add final update to undo/redo history
262  get_app().updates.apply_last_action_to_history(self.original_data)
263 
264  # Clear original data
265  self.original_data = None
266 
267  # Get data model and selection
268  model = self.clip_properties_model.model
269  row = self.indexAt(event.pos()).row()
270  column = self.indexAt(event.pos()).column()
271  if model.item(row, 0):
272  self.selected_label = model.item(row, 0)
273  self.selected_item = model.item(row, 1)
274 
275  ##
276  # Double click handler for the property table
277  def doubleClickedCB(self, model_index):
278  # Get data model and selection
279  model = self.clip_properties_model.model
280 
281  row = model_index.row()
282  selected_label = model.item(row, 0)
283  self.selected_item = model.item(row, 1)
284 
285  if selected_label:
286  property = selected_label.data()
287  property_type = property[1]["type"]
288 
289  if property_type == "color":
290  # Get current value of color
291  red = property[1]["red"]["value"]
292  green = property[1]["green"]["value"]
293  blue = property[1]["blue"]["value"]
294 
295  # Show color dialog
296  currentColor = QColor(red, green, blue)
297  newColor = QColorDialog.getColor(currentColor)
298 
299  # Set the new color keyframe
300  self.clip_properties_model.color_update(self.selected_item, newColor)
301 
302  ##
303  # Update the selected item in the properties window
304  def select_item(self, item_id, item_type):
305 
306  # Get translation object
307  _ = get_app()._tr
308 
309  # Update item
310  self.clip_properties_model.update_item(item_id, item_type)
311 
312  ##
313  # Update the values of the selected clip, based on the current frame
314  def select_frame(self, frame_number):
315 
316  # Update item
317  self.clip_properties_model.update_frame(frame_number)
318 
319  ##
320  # Filter the list of properties
321  def filter_changed(self, value=None):
322 
323  # Update model
324  self.clip_properties_model.update_model(value)
325 
326  def contextMenuEvent(self, event):
327  # Get data model and selection
328  model = self.clip_properties_model.model
329  row = self.indexAt(event.pos()).row()
330  selected_label = model.item(row, 0)
331  selected_value = model.item(row, 1)
332  self.selected_item = selected_value
333  frame_number = self.clip_properties_model.frame_number
334 
335  # Get translation object
336  _ = get_app()._tr
337 
338  # If item selected
339  if selected_label:
340  # Get data from selected item
341  property = selected_label.data()
342  property_name = property[1]["name"]
343  self.property_type = property[1]["type"]
344  memo = json.loads(property[1]["memo"] or "{}")
345  points = property[1]["points"]
346  self.choices = property[1]["choices"]
347  property_key = property[0]
348  clip_id, item_type = selected_value.data()
349  log.info("Context menu shown for %s (%s) for clip %s on frame %s" % (property_name, property_key, clip_id, frame_number))
350  log.info("Points: %s" % points)
351  log.info("Property: %s" % str(property))
352 
353  # Handle reader type values
354  if self.property_type == "reader" and not self.choices:
355 
356  # Refresh models
357  self.transition_model.update_model()
358  self.files_model.update_model()
359 
360  # Add all files
361  file_choices = []
362  for filesIndex in range(self.files_model.model.rowCount()):
363  modelIndex = self.files_model.model.index(filesIndex, 0)
364  fileItem = self.files_model.model.itemFromIndex(modelIndex)
365  fileIcon = self.files_model.model.item(fileItem.row(), 0).icon()
366  fileName = self.files_model.model.item(fileItem.row(), 1).text()
367  fileParentPath = self.files_model.model.item(fileItem.row(), 4).text()
368 
369  # Append file choice
370  file_choices.append({"name": fileName, "value": os.path.join(fileParentPath, fileName), "selected": False, "icon": fileIcon })
371 
372  # Add root file choice
373  self.choices.append({"name": _("Files"), "value": file_choices, "selected": False})
374 
375  # Add all transitions
376  trans_choices = []
377  for transIndex in range(self.transition_model.model.rowCount()):
378  modelIndex = self.transition_model.model.index(transIndex, 0)
379  transItem = self.transition_model.model.itemFromIndex(modelIndex)
380  transIcon = self.transition_model.model.item(transItem.row(), 0).icon()
381  transName = self.transition_model.model.item(transItem.row(), 1).text()
382  transPath = self.transition_model.model.item(transItem.row(), 3).text()
383 
384  # Append transition choice
385  trans_choices.append({"name": transName, "value": transPath, "selected": False, "icon": transIcon })
386 
387  # Add root transitions choice
388  self.choices.append({"name": _("Transitions"), "value": trans_choices, "selected": False})
389 
390  # Define bezier presets
391  bezier_presets = [
392  (0.250, 0.100, 0.250, 1.000, _("Ease (Default)")),
393  (0.420, 0.000, 1.000, 1.000, _("Ease In")),
394  (0.000, 0.000, 0.580, 1.000, _("Ease Out")),
395  (0.420, 0.000, 0.580, 1.000, _("Ease In/Out")),
396 
397  (0.550, 0.085, 0.680, 0.530, _("Ease In (Quad)")),
398  (0.550, 0.055, 0.675, 0.190, _("Ease In (Cubic)")),
399  (0.895, 0.030, 0.685, 0.220, _("Ease In (Quart)")),
400  (0.755, 0.050, 0.855, 0.060, _("Ease In (Quint)")),
401  (0.470, 0.000, 0.745, 0.715, _("Ease In (Sine)")),
402  (0.950, 0.050, 0.795, 0.035, _("Ease In (Expo)")),
403  (0.600, 0.040, 0.980, 0.335, _("Ease In (Circ)")),
404  (0.600, -0.280, 0.735, 0.045, _("Ease In (Back)")),
405 
406  (0.250, 0.460, 0.450, 0.940, _("Ease Out (Quad)")),
407  (0.215, 0.610, 0.355, 1.000, _("Ease Out (Cubic)")),
408  (0.165, 0.840, 0.440, 1.000, _("Ease Out (Quart)")),
409  (0.230, 1.000, 0.320, 1.000, _("Ease Out (Quint)")),
410  (0.390, 0.575, 0.565, 1.000, _("Ease Out (Sine)")),
411  (0.190, 1.000, 0.220, 1.000, _("Ease Out (Expo)")),
412  (0.075, 0.820, 0.165, 1.000, _("Ease Out (Circ)")),
413  (0.175, 0.885, 0.320, 1.275, _("Ease Out (Back)")),
414 
415  (0.455, 0.030, 0.515, 0.955, _("Ease In/Out (Quad)")),
416  (0.645, 0.045, 0.355, 1.000, _("Ease In/Out (Cubic)")),
417  (0.770, 0.000, 0.175, 1.000, _("Ease In/Out (Quart)")),
418  (0.860, 0.000, 0.070, 1.000, _("Ease In/Out (Quint)")),
419  (0.445, 0.050, 0.550, 0.950, _("Ease In/Out (Sine)")),
420  (1.000, 0.000, 0.000, 1.000, _("Ease In/Out (Expo)")),
421  (0.785, 0.135, 0.150, 0.860, _("Ease In/Out (Circ)")),
422  (0.680, -0.550, 0.265, 1.550, _("Ease In/Out (Back)"))
423  ]
424 
425  bezier_icon = QIcon(QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.BEZIER)))
426  linear_icon = QIcon(QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.LINEAR)))
427  constant_icon = QIcon(QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.CONSTANT)))
428 
429  # Add menu options for keyframes
430  menu = QMenu(self)
431  if points > 1:
432  # Menu for more than 1 point
433  Bezier_Menu = QMenu(_("Bezier"), self)
434  Bezier_Menu.setIcon(bezier_icon)
435  for bezier_preset in bezier_presets:
436  preset_action = Bezier_Menu.addAction(bezier_preset[4])
437  preset_action.triggered.connect(partial(self.Bezier_Action_Triggered, bezier_preset))
438  menu.addMenu(Bezier_Menu)
439  Linear_Action = menu.addAction(_("Linear"))
440  Linear_Action.setIcon(linear_icon)
441  Linear_Action.triggered.connect(self.Linear_Action_Triggered)
442  Constant_Action = menu.addAction(_("Constant"))
443  Constant_Action.setIcon(constant_icon)
444  Constant_Action.triggered.connect(self.Constant_Action_Triggered)
445  menu.addSeparator()
446  Insert_Action = menu.addAction(_("Insert Keyframe"))
447  Insert_Action.triggered.connect(self.Insert_Action_Triggered)
448  Remove_Action = menu.addAction(_("Remove Keyframe"))
449  Remove_Action.triggered.connect(self.Remove_Action_Triggered)
450  menu.popup(QCursor.pos())
451  elif points == 1:
452  # Menu for a single point
453  Insert_Action = menu.addAction(_("Insert Keyframe"))
454  Insert_Action.triggered.connect(self.Insert_Action_Triggered)
455  Remove_Action = menu.addAction(_("Remove Keyframe"))
456  Remove_Action.triggered.connect(self.Remove_Action_Triggered)
457  menu.popup(QCursor.pos())
458 
459  if self.choices:
460  # Menu for choices
461  for choice in self.choices:
462  if type(choice["value"]) != list:
463  # Add root choice items
464  Choice_Action = menu.addAction(_(choice["name"]))
465  Choice_Action.setData(choice["value"])
466  Choice_Action.triggered.connect(self.Choice_Action_Triggered)
467  else:
468  # Add sub-choice items (for nested choice lists)
469  # Divide into smaller QMenus (since large lists cover the entire screen)
470  # For example: Transitions -> 1 -> sub items
471  SubMenu = None
472  SubMenuRoot = QMenu(_(choice["name"]), self)
473  SubMenuSize = 24
474  SubMenuNumber = 0
475  for sub_choice in choice["value"]:
476  if len(choice["value"]) > SubMenuSize:
477  if not SubMenu or len(SubMenu.children()) == SubMenuSize or sub_choice == choice["value"][-1]:
478  SubMenuNumber += 1
479  if SubMenu:
480  SubMenuRoot.addMenu(SubMenu)
481  SubMenu = QMenu(str(SubMenuNumber), self)
482  Choice_Action = SubMenu.addAction(_(sub_choice["name"]))
483  else:
484  Choice_Action = SubMenuRoot.addAction(_(sub_choice["name"]))
485  Choice_Action.setData(sub_choice["value"])
486  subChoiceIcon = sub_choice.get("icon")
487  if subChoiceIcon:
488  Choice_Action.setIcon(subChoiceIcon)
489  Choice_Action.triggered.connect(self.Choice_Action_Triggered)
490  menu.addMenu(SubMenuRoot)
491 
492  # Show choice menu
493  menu.popup(QCursor.pos())
494 
495  def Bezier_Action_Triggered(self, preset=[]):
496  log.info("Bezier_Action_Triggered: %s" % str(preset))
497  if self.property_type != "color":
498  # Update keyframe interpolation mode
499  self.clip_properties_model.value_updated(self.selected_item, interpolation=0, interpolation_details=preset)
500  else:
501  # Update colors interpolation mode
502  self.clip_properties_model.color_update(self.selected_item, QColor("#000"), interpolation=0, interpolation_details=preset)
503 
504  def Linear_Action_Triggered(self, event):
505  log.info("Linear_Action_Triggered")
506  if self.property_type != "color":
507  # Update keyframe interpolation mode
508  self.clip_properties_model.value_updated(self.selected_item, interpolation=1)
509  else:
510  # Update colors interpolation mode
511  self.clip_properties_model.color_update(self.selected_item, QColor("#000"), interpolation=1, interpolation_details=[])
512 
513  def Constant_Action_Triggered(self, event):
514  log.info("Constant_Action_Triggered")
515  if self.property_type != "color":
516  # Update keyframe interpolation mode
517  self.clip_properties_model.value_updated(self.selected_item, interpolation=2)
518  else:
519  # Update colors interpolation mode
520  self.clip_properties_model.color_update(self.selected_item, QColor("#000"), interpolation=2, interpolation_details=[])
521 
522  def Insert_Action_Triggered(self, event):
523  log.info("Insert_Action_Triggered")
524  if self.selected_item:
525  current_value = QLocale().system().toDouble(self.selected_item.text())[0]
526  self.clip_properties_model.value_updated(self.selected_item, value=current_value)
527 
528  def Remove_Action_Triggered(self, event):
529  log.info("Remove_Action_Triggered")
530  self.clip_properties_model.remove_keyframe(self.selected_item)
531 
532  def Choice_Action_Triggered(self, event):
533  log.info("Choice_Action_Triggered")
534  choice_value = self.sender().data()
535 
536  # Update value of dropdown item
537  self.clip_properties_model.value_updated(self.selected_item, value=choice_value)
538 
539  def __init__(self, *args):
540  # Invoke parent init
541  QTableView.__init__(self, *args)
542 
543  # Get a reference to the window object
544  self.win = get_app().window
545 
546  # Get Model data
547  self.clip_properties_model = PropertiesModel(self)
548  self.transition_model = TransitionsModel(self)
549  self.files_model = FilesModel(self)
550 
551  # Keep track of mouse press start position to determine when to start drag
552  self.selected = []
553  self.selected_label = None
554  self.selected_item = None
555  self.new_value = None
556  self.original_data = None
557 
558  # Setup header columns
559  self.setModel(self.clip_properties_model.model)
560  self.setSelectionBehavior(QAbstractItemView.SelectRows)
561  self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
562  self.setWordWrap(True)
563 
564  # Set delegate
565  delegate = PropertyDelegate()
566  self.setItemDelegateForColumn(1, delegate)
567  self.previous_x = -1
568 
569  # Get table header
570  horizontal_header = self.horizontalHeader()
571  horizontal_header.setSectionResizeMode(QHeaderView.Stretch)
572  vertical_header = self.verticalHeader()
573  vertical_header.setVisible(False)
574 
575  # Refresh view
576  self.clip_properties_model.update_model()
577  self.transition_model.update_model()
578  self.files_model.update_model()
579 
580  # Resize columns
581  self.resizeColumnToContents(0)
582  self.resizeColumnToContents(1)
583 
584  # Connect filter signals
585  get_app().window.txtPropertyFilter.textChanged.connect(self.filter_changed)
586  get_app().window.InsertKeyframe.connect(self.Insert_Action_Triggered)
587  self.doubleClicked.connect(self.doubleClickedCB)
588  self.loadProperties.connect(self.select_item)
589 
590 
591 ##
592 # The label to display selections
593 class SelectionLabel(QFrame):
594 
595  def getMenu(self):
596  # Build menu for selection button
597  menu = QMenu(self)
598 
599  # Get translation object
600  _ = get_app()._tr
601 
602  # Look up item for more info
603  if self.item_type == "clip":
604  item = Clip.get(id=self.item_id)
605  if item:
606  self.item_name = item.title()
607  elif self.item_type == "transition":
608  item = Transition.get(id=self.item_id)
609  if item:
610  self.item_name = item.title()
611  elif self.item_type == "effect":
612  item = Effect.get(id=self.item_id)
613  if item:
614  self.item_name = item.title()
615 
616  # Bail if no item name was found
617  if not self.item_name:
618  return
619 
620  # Add selected clips
621  for item_id in get_app().window.selected_clips:
622  clip = Clip.get(id=item_id)
623  if clip:
624  item_name = clip.title()
625  item_icon = QIcon(QPixmap(clip.data.get('image')))
626  action = menu.addAction(item_name)
627  action.setIcon(item_icon)
628  action.setData({'item_id':item_id, 'item_type':'clip'})
629  action.triggered.connect(self.Action_Triggered)
630 
631  # Add effects for these clips (if any)
632  for effect in clip.data.get('effects'):
633  effect = Effect.get(id=effect.get('id'))
634  if effect:
635  item_name = effect.title()
636  item_icon = QIcon(QPixmap(os.path.join(info.PATH, "effects", "icons", "%s.png" % effect.data.get('class_name').lower())))
637  action = menu.addAction(' > %s' % _(item_name))
638  action.setIcon(item_icon)
639  action.setData({'item_id': effect.id, 'item_type': 'effect'})
640  action.triggered.connect(self.Action_Triggered)
641 
642  # Add selected transitions
643  for item_id in get_app().window.selected_transitions:
644  trans = Transition.get(id=item_id)
645  if trans:
646  item_name = _(trans.title())
647  item_icon = QIcon(QPixmap(trans.data.get('reader',{}).get('path')))
648  action = menu.addAction(_(item_name))
649  action.setIcon(item_icon)
650  action.setData({'item_id': item_id, 'item_type': 'transition'})
651  action.triggered.connect(self.Action_Triggered)
652 
653  # Add selected effects
654  for item_id in get_app().window.selected_effects:
655  effect = Effect.get(id=item_id)
656  if effect:
657  item_name = _(effect.title())
658  item_icon = QIcon(QPixmap(os.path.join(info.PATH, "effects", "icons", "%s.png" % effect.data.get('class_name').lower())))
659  action = menu.addAction(_(item_name))
660  action.setIcon(item_icon)
661  action.setData({'item_id': item_id, 'item_type': 'effect'})
662  action.triggered.connect(self.Action_Triggered)
663 
664  # Return the menu object
665  return menu
666 
667  def Action_Triggered(self, event):
668  # Switch selection
669  item_id = self.sender().data()['item_id']
670  item_type = self.sender().data()['item_type']
671  log.info('switch selection to %s:%s' % (item_id, item_type))
672 
673  # Set the property tableview to the new item
674  get_app().window.propertyTableView.loadProperties.emit(item_id, item_type)
675 
676  # Update selected item label
677  def select_item(self, item_id, item_type):
678  self.item_name = None
679  self.item_icon = None
680  self.item_type = item_type
681  self.item_id = item_id
682 
683  # Get translation object
684  _ = get_app()._tr
685 
686  # Look up item for more info
687  if self.item_type == "clip":
688  clip = Clip.get(id=self.item_id)
689  if clip:
690  self.item_name = clip.title()
691  self.item_icon = QIcon(QPixmap(clip.data.get('image')))
692  elif self.item_type == "transition":
693  trans = Transition.get(id=self.item_id)
694  if trans:
695  self.item_name = _(trans.title())
696  self.item_icon = QIcon(QPixmap(trans.data.get('reader', {}).get('path')))
697  elif self.item_type == "effect":
698  effect = Effect.get(id=self.item_id)
699  if effect:
700  self.item_name = _(effect.title())
701  self.item_icon = QIcon(QPixmap(os.path.join(info.PATH, "effects", "icons", "%s.png" % effect.data.get('class_name').lower())))
702 
703  # Truncate long text
704  if self.item_name and len(self.item_name) > 25:
705  self.item_name = "%s..." % self.item_name[:22]
706 
707  # Set label
708  if self.item_id:
709  self.lblSelection.setText("<strong>%s</strong>" % _("Selection:"))
710  self.btnSelectionName.setText(self.item_name)
711  self.btnSelectionName.setVisible(True)
712  if self.item_icon:
713  self.btnSelectionName.setIcon(self.item_icon)
714  else:
715  self.lblSelection.setText("<strong>%s</strong>" % _("No Selection"))
716  self.btnSelectionName.setVisible(False)
717 
718  # Set the menu on the button
719  self.btnSelectionName.setMenu(self.getMenu())
720 
721  def __init__(self, *args):
722  # Invoke parent init
723  QFrame.__init__(self, *args)
724  self.item_id = None
725  self.item_type = None
726 
727  # Get translation object
728  _ = get_app()._tr
729 
730  # Widgets
731  self.lblSelection = QLabel()
732  self.lblSelection.setText("<strong>%s</strong>" % _("No Selection"))
733  self.btnSelectionName = QPushButton()
734  self.btnSelectionName.setVisible(False)
735  self.btnSelectionName.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
736 
737  # Support rich text
738  self.lblSelection.setTextFormat(Qt.RichText)
739 
740  hbox = QHBoxLayout()
741  hbox.setContentsMargins(0,0,0,0)
742  hbox.addWidget(self.lblSelection)
743  hbox.addWidget(self.btnSelectionName)
744  self.setLayout(hbox)
745 
746  # Connect signals
747  get_app().window.propertyTableView.loadProperties.connect(self.select_item)
def get_app
Returns the current QApplication instance of OpenShot.
Definition: app.py:55
def filter_changed
Filter the list of properties.
def select_item
Update the selected item in the properties window.
The label to display selections.
A Properties Table QWidget used on the main window.
def doubleClickedCB
Double click handler for the property table.
def select_frame
Update the values of the selected clip, based on the current frame.