def __init__(self, *args): # Invoke parent init QTableView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.clip_properties_model = PropertiesModel(self) # Keep track of mouse press start position to determine when to start drag self.selected = [] self.selected_item = None # Setup header columns self.setModel(self.clip_properties_model.model) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) # Get table header horizontal_header = self.horizontalHeader() horizontal_header.setSectionResizeMode(QHeaderView.Stretch) vertical_header = self.verticalHeader() vertical_header.setVisible(False) # Refresh view self.clip_properties_model.update_model() # Resize columns self.resizeColumnToContents(0) self.resizeColumnToContents(1) # Connect filter signals get_app().window.txtPropertyFilter.textChanged.connect(self.filter_changed)
def __init__(self, *args): # Invoke parent init QTableView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.clip_properties_model = PropertiesModel(self) self.transition_model = TransitionsModel(self) self.files_model = FilesModel(self) # Keep track of mouse press start position to determine when to start drag self.selected = [] self.selected_label = None self.selected_item = None self.new_value = None self.original_data = None self.lock_selection = False self.prev_row = None # Context menu concurrency lock self.menu_lock = False # Setup header columns self.setModel(self.clip_properties_model.model) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) # Set delegate delegate = PropertyDelegate() self.setItemDelegateForColumn(1, delegate) self.previous_x = -1 # Get table header horizontal_header = self.horizontalHeader() horizontal_header.setSectionResizeMode(QHeaderView.Stretch) vertical_header = self.verticalHeader() vertical_header.setVisible(False) # Refresh view self.clip_properties_model.update_model() self.transition_model.update_model() self.files_model.update_model() # Resize columns self.resizeColumnToContents(0) self.resizeColumnToContents(1) # Connect filter signals get_app().window.txtPropertyFilter.textChanged.connect( self.filter_changed) get_app().window.InsertKeyframe.connect(self.Insert_Action_Triggered) self.doubleClicked.connect(self.doubleClickedCB) self.loadProperties.connect(self.select_item)
def __init__(self, *args): # Invoke parent init QTableView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.clip_properties_model = PropertiesModel(self) self.transition_model = TransitionsModel(self) self.files_model = FilesModel(self) # Keep track of mouse press start position to determine when to start drag self.selected = [] self.selected_label = None self.selected_item = None self.new_value = None self.original_data = None # Setup header columns self.setModel(self.clip_properties_model.model) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) # Set delegate delegate = PropertyDelegate() self.setItemDelegateForColumn(1, delegate) self.previous_x = -1 # Get table header horizontal_header = self.horizontalHeader() horizontal_header.setSectionResizeMode(QHeaderView.Stretch) vertical_header = self.verticalHeader() vertical_header.setVisible(False) # Refresh view self.clip_properties_model.update_model() self.transition_model.update_model() self.files_model.update_model() # Resize columns self.resizeColumnToContents(0) self.resizeColumnToContents(1) # Connect filter signals get_app().window.txtPropertyFilter.textChanged.connect(self.filter_changed) get_app().window.InsertKeyframe.connect(self.Insert_Action_Triggered) self.doubleClicked.connect(self.doubleClickedCB) self.loadProperties.connect(self.select_item)
def __init__(self, *args): # Invoke parent init QTableView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.clip_properties_model = PropertiesModel(self) # Keep track of mouse press start position to determine when to start drag self.selected = [] self.selected_item = None # Setup header columns self.setModel(self.clip_properties_model.model) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) # Get table header horizontal_header = self.horizontalHeader() horizontal_header.setSectionResizeMode(QHeaderView.Stretch) vertical_header = self.verticalHeader() vertical_header.setVisible(False) # Refresh view self.clip_properties_model.update_model() # Resize columns self.resizeColumnToContents(0) self.resizeColumnToContents(1) # Connect filter signals get_app().window.txtPropertyFilter.textChanged.connect(self.filter_changed) self.doubleClicked.connect(self.double_click)
class PropertiesTableView(QTableView): """ A Properties Table QWidget used on the main window """ loadProperties = pyqtSignal(str, str) def mouseMoveEvent(self, event): # Get data model and selection model = self.clip_properties_model.model # Do not change selected row during mouse move if self.lock_selection and self.prev_row: row = self.prev_row else: row = self.indexAt(event.pos()).row() self.prev_row = row self.lock_selection = True if row is None: return if model.item(row, 0): self.selected_label = model.item(row, 0) self.selected_item = model.item(row, 1) # Is the user dragging on the value column if self.selected_label and self.selected_item: # Get the position of the cursor and % value value_column_x = self.columnViewportPosition(1) cursor_value = event.x() - value_column_x cursor_value_percent = cursor_value / self.columnWidth(1) try: cur_property = self.selected_label.data() except Exception: # If item is deleted during this drag... an exception can occur # Just ignore, since this is harmless return property_key = cur_property[0] property_name = cur_property[1]["name"] property_type = cur_property[1]["type"] property_max = cur_property[1]["max"] property_min = cur_property[1]["min"] readonly = cur_property[1]["readonly"] item_id, item_type = self.selected_item.data() # Bail if readonly if readonly: return # Get the original data of this item (prior to any updates, for the undo/redo system) if not self.original_data: # Ignore undo/redo history temporarily (to avoid a huge pile of undo/redo history) get_app().updates.ignore_history = True # Find this clip c = None if item_type == "clip": # Get clip object c = Clip.get(id=item_id) elif item_type == "transition": # Get transition object c = Transition.get(id=item_id) elif item_type == "effect": # Get effect object c = Effect.get(id=item_id) if c: if property_key in c.data: # Grab the original data for this item/property self.original_data = c.data # For numeric values, apply percentage within parameter's allowable range if property_type in ["float", "int"] and property_name != "Track": if self.previous_x == -1: # Start tracking movement (init diff_length and previous_x) self.diff_length = 10 self.previous_x = event.x() # Calculate # of pixels dragged drag_diff = self.previous_x - event.x() # update previous x self.previous_x = event.x() # Ignore small initial movements if abs(drag_diff) < self.diff_length: # Lower threshold to 0 incrementally, to guarantee it'll eventually be exceeded self.diff_length = max(0, self.diff_length - 1) return # Compute size of property's possible values range min_max_range = float(property_max) - float(property_min) if min_max_range < 1000.0: # Small range - use cursor to calculate new value as percentage of total range self.new_value = property_min + (min_max_range * cursor_value_percent) else: # range is unreasonably long (such as position, start, end, etc.... which can be huge #'s) # Get the current value and apply fixed adjustments in response to motion self.new_value = QLocale().system().toDouble( self.selected_item.text())[0] if drag_diff > 0: # Move to the left by a small amount self.new_value -= 0.50 elif drag_diff < 0: # Move to the right by a small amount self.new_value += 0.50 # Clamp value between min and max (just incase user drags too big) self.new_value = max(property_min, self.new_value) self.new_value = min(property_max, self.new_value) # Update value of this property self.clip_properties_model.value_updated( self.selected_item, -1, self.new_value) # Repaint self.viewport().update() def mouseReleaseEvent(self, event): # Inform UpdateManager to accept updates, and only store our final update get_app().updates.ignore_history = False # Add final update to undo/redo history get_app().updates.apply_last_action_to_history(self.original_data) # Clear original data self.original_data = None # Get data model and selection model = self.clip_properties_model.model row = self.indexAt(event.pos()).row() if model.item(row, 0): self.selected_label = model.item(row, 0) self.selected_item = model.item(row, 1) # Allow new selection and prepare to set minimum move threshold self.lock_selection = False self.previous_x = -1 def doubleClickedCB(self, model_index): """Double click handler for the property table""" # Get translation object _ = get_app()._tr # Get data model and selection model = self.clip_properties_model.model row = model_index.row() selected_label = model.item(row, 0) self.selected_item = model.item(row, 1) if selected_label: cur_property = selected_label.data() property_type = cur_property[1]["type"] if property_type == "color": # Get current value of color red = cur_property[1]["red"]["value"] green = cur_property[1]["green"]["value"] blue = cur_property[1]["blue"]["value"] # Show color dialog currentColor = QColor(red, green, blue) newColor = QColorDialog.getColor( currentColor, self, _("Select a Color"), QColorDialog.DontUseNativeDialog) # Set the new color keyframe self.clip_properties_model.color_update( self.selected_item, newColor) def select_item(self, item_id, item_type): """ Update the selected item in the properties window """ # Get translation object _ = get_app()._tr # Update item self.clip_properties_model.update_item(item_id, item_type) def select_frame(self, frame_number): """ Update the values of the selected clip, based on the current frame """ # Update item self.clip_properties_model.update_frame(frame_number) def filter_changed(self, value=None): """ Filter the list of properties """ # Update model self.clip_properties_model.update_model(value) def contextMenuEvent(self, event=None, release=False): """ Display context menu, or release lock when menu displays """ if release: # Just clear the menu lock and exit self.menu_lock = False return if self.menu_lock or not event: # If we're locked, ignore this menu request # But set a 100ms timer to release the lock, just in case QTimer.singleShot(100, partial(self.contextMenuEvent, release=True)) return # Lock against repeated calls until we've displayed the menu self.menu_lock = True # Get data model and selection model = self.clip_properties_model.model row = self.indexAt(event.pos()).row() selected_label = model.item(row, 0) selected_value = model.item(row, 1) self.selected_item = selected_value frame_number = self.clip_properties_model.frame_number # Get translation object _ = get_app()._tr # If item selected if selected_label: # Get data from selected item cur_property = selected_label.data() property_name = cur_property[1]["name"] self.property_type = cur_property[1]["type"] points = cur_property[1]["points"] self.choices = cur_property[1]["choices"] property_key = cur_property[0] clip_id, item_type = selected_value.data() log.info("Context menu shown for %s (%s) for clip %s on frame %s" % (property_name, property_key, clip_id, frame_number)) log.info("Points: %s" % points) log.info("Property: %s" % str(cur_property)) # Handle reader type values if self.property_type == "reader" and not self.choices: # Refresh models self.transition_model.update_model() self.files_model.update_model() # Add all files file_choices = [] for filesIndex in range(self.files_model.model.rowCount()): modelIndex = self.files_model.model.index(filesIndex, 0) fileItem = self.files_model.model.itemFromIndex(modelIndex) fileIcon = self.files_model.model.item(fileItem.row(), 0).icon() fileName = self.files_model.model.item(fileItem.row(), 1).text() fileParentPath = self.files_model.model.item( fileItem.row(), 4).text() # Append file choice file_choices.append({ "name": fileName, "value": os.path.join(fileParentPath, fileName), "selected": False, "icon": fileIcon }) # Add root file choice self.choices.append({ "name": _("Files"), "value": file_choices, "selected": False }) # Add all transitions trans_choices = [] for transIndex in range( self.transition_model.model.rowCount()): modelIndex = self.transition_model.model.index( transIndex, 0) transItem = self.transition_model.model.itemFromIndex( modelIndex) transIcon = self.transition_model.model.item( transItem.row(), 0).icon() transName = self.transition_model.model.item( transItem.row(), 1).text() transPath = self.transition_model.model.item( transItem.row(), 3).text() # Append transition choice trans_choices.append({ "name": transName, "value": transPath, "selected": False, "icon": transIcon }) # Add root transitions choice self.choices.append({ "name": _("Transitions"), "value": trans_choices, "selected": False }) # Handle reader type values if property_name == "Track" and self.property_type == "int" and not self.choices: # Populate all display track names all_tracks = get_app().project.get("layers") display_count = len(all_tracks) for track in reversed( sorted(all_tracks, key=itemgetter('number'))): # Append track choice track_name = track.get( "label") or _("Track %s") % display_count self.choices.append({ "name": track_name, "value": track.get("number"), "selected": False }) display_count -= 1 # Define bezier presets bezier_presets = [ (0.250, 0.100, 0.250, 1.000, _("Ease (Default)")), (0.420, 0.000, 1.000, 1.000, _("Ease In")), (0.000, 0.000, 0.580, 1.000, _("Ease Out")), (0.420, 0.000, 0.580, 1.000, _("Ease In/Out")), (0.550, 0.085, 0.680, 0.530, _("Ease In (Quad)")), (0.550, 0.055, 0.675, 0.190, _("Ease In (Cubic)")), (0.895, 0.030, 0.685, 0.220, _("Ease In (Quart)")), (0.755, 0.050, 0.855, 0.060, _("Ease In (Quint)")), (0.470, 0.000, 0.745, 0.715, _("Ease In (Sine)")), (0.950, 0.050, 0.795, 0.035, _("Ease In (Expo)")), (0.600, 0.040, 0.980, 0.335, _("Ease In (Circ)")), (0.600, -0.280, 0.735, 0.045, _("Ease In (Back)")), (0.250, 0.460, 0.450, 0.940, _("Ease Out (Quad)")), (0.215, 0.610, 0.355, 1.000, _("Ease Out (Cubic)")), (0.165, 0.840, 0.440, 1.000, _("Ease Out (Quart)")), (0.230, 1.000, 0.320, 1.000, _("Ease Out (Quint)")), (0.390, 0.575, 0.565, 1.000, _("Ease Out (Sine)")), (0.190, 1.000, 0.220, 1.000, _("Ease Out (Expo)")), (0.075, 0.820, 0.165, 1.000, _("Ease Out (Circ)")), (0.175, 0.885, 0.320, 1.275, _("Ease Out (Back)")), (0.455, 0.030, 0.515, 0.955, _("Ease In/Out (Quad)")), (0.645, 0.045, 0.355, 1.000, _("Ease In/Out (Cubic)")), (0.770, 0.000, 0.175, 1.000, _("Ease In/Out (Quart)")), (0.860, 0.000, 0.070, 1.000, _("Ease In/Out (Quint)")), (0.445, 0.050, 0.550, 0.950, _("Ease In/Out (Sine)")), (1.000, 0.000, 0.000, 1.000, _("Ease In/Out (Expo)")), (0.785, 0.135, 0.150, 0.860, _("Ease In/Out (Circ)")), (0.680, -0.550, 0.265, 1.550, _("Ease In/Out (Back)")) ] bezier_icon = QIcon( QPixmap( os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.BEZIER))) linear_icon = QIcon( QPixmap( os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.LINEAR))) constant_icon = QIcon( QPixmap( os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.CONSTANT))) # Add menu options for keyframes menu = QMenu(self) if points > 1: # Menu for more than 1 point Bezier_Menu = QMenu(_("Bezier"), self) Bezier_Menu.setIcon(bezier_icon) for bezier_preset in bezier_presets: preset_action = Bezier_Menu.addAction(bezier_preset[4]) preset_action.triggered.connect( partial(self.Bezier_Action_Triggered, bezier_preset)) menu.addMenu(Bezier_Menu) Linear_Action = menu.addAction(_("Linear")) Linear_Action.setIcon(linear_icon) Linear_Action.triggered.connect(self.Linear_Action_Triggered) Constant_Action = menu.addAction(_("Constant")) Constant_Action.setIcon(constant_icon) Constant_Action.triggered.connect( self.Constant_Action_Triggered) menu.addSeparator() Insert_Action = menu.addAction(_("Insert Keyframe")) Insert_Action.triggered.connect(self.Insert_Action_Triggered) Remove_Action = menu.addAction(_("Remove Keyframe")) Remove_Action.triggered.connect(self.Remove_Action_Triggered) menu.popup(QCursor.pos()) elif points == 1: # Menu for a single point Insert_Action = menu.addAction(_("Insert Keyframe")) Insert_Action.triggered.connect(self.Insert_Action_Triggered) Remove_Action = menu.addAction(_("Remove Keyframe")) Remove_Action.triggered.connect(self.Remove_Action_Triggered) menu.popup(QCursor.pos()) if self.choices: # Menu for choices for choice in self.choices: if type(choice["value"]) != list: # Add root choice items Choice_Action = menu.addAction(_(choice["name"])) Choice_Action.setData(choice["value"]) Choice_Action.triggered.connect( self.Choice_Action_Triggered) else: # Add sub-choice items (for nested choice lists) # Divide into smaller QMenus (since large lists cover the entire screen) # For example: Transitions -> 1 -> sub items SubMenu = None SubMenuRoot = QMenu(_(choice["name"]), self) SubMenuSize = 24 SubMenuNumber = 0 for sub_choice in choice["value"]: if len(choice["value"]) > SubMenuSize: if not SubMenu or len(SubMenu.children( )) == SubMenuSize or sub_choice == choice[ "value"][-1]: SubMenuNumber += 1 if SubMenu: SubMenuRoot.addMenu(SubMenu) SubMenu = QMenu(str(SubMenuNumber), self) Choice_Action = SubMenu.addAction( _(sub_choice["name"])) else: Choice_Action = SubMenuRoot.addAction( _(sub_choice["name"])) Choice_Action.setData(sub_choice["value"]) subChoiceIcon = sub_choice.get("icon") if subChoiceIcon: Choice_Action.setIcon(subChoiceIcon) Choice_Action.triggered.connect( self.Choice_Action_Triggered) menu.addMenu(SubMenuRoot) # Show choice menu and release lock menu.popup(QCursor.pos()) self.contextMenuEvent(event, release=True) def Bezier_Action_Triggered(self, preset=[]): log.info("Bezier_Action_Triggered: %s" % str(preset)) if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated( self.selected_item, interpolation=0, interpolation_details=preset) else: # Update colors interpolation mode self.clip_properties_model.color_update( self.selected_item, QColor("#000"), interpolation=0, interpolation_details=preset) def Linear_Action_Triggered(self, event): log.info("Linear_Action_Triggered") if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated(self.selected_item, interpolation=1) else: # Update colors interpolation mode self.clip_properties_model.color_update(self.selected_item, QColor("#000"), interpolation=1, interpolation_details=[]) def Constant_Action_Triggered(self, event): log.info("Constant_Action_Triggered") if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated(self.selected_item, interpolation=2) else: # Update colors interpolation mode self.clip_properties_model.color_update(self.selected_item, QColor("#000"), interpolation=2, interpolation_details=[]) def Insert_Action_Triggered(self, event): log.info("Insert_Action_Triggered") if self.selected_item: current_value = QLocale().system().toDouble( self.selected_item.text())[0] self.clip_properties_model.value_updated(self.selected_item, value=current_value) def Remove_Action_Triggered(self, event): log.info("Remove_Action_Triggered") self.clip_properties_model.remove_keyframe(self.selected_item) def Choice_Action_Triggered(self, event): log.info("Choice_Action_Triggered") choice_value = self.sender().data() # Update value of dropdown item self.clip_properties_model.value_updated(self.selected_item, value=choice_value) def __init__(self, *args): # Invoke parent init QTableView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.clip_properties_model = PropertiesModel(self) self.transition_model = TransitionsModel(self) self.files_model = FilesModel(self) # Keep track of mouse press start position to determine when to start drag self.selected = [] self.selected_label = None self.selected_item = None self.new_value = None self.original_data = None self.lock_selection = False self.prev_row = None # Context menu concurrency lock self.menu_lock = False # Setup header columns self.setModel(self.clip_properties_model.model) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) # Set delegate delegate = PropertyDelegate() self.setItemDelegateForColumn(1, delegate) self.previous_x = -1 # Get table header horizontal_header = self.horizontalHeader() horizontal_header.setSectionResizeMode(QHeaderView.Stretch) vertical_header = self.verticalHeader() vertical_header.setVisible(False) # Refresh view self.clip_properties_model.update_model() self.transition_model.update_model() self.files_model.update_model() # Resize columns self.resizeColumnToContents(0) self.resizeColumnToContents(1) # Connect filter signals get_app().window.txtPropertyFilter.textChanged.connect( self.filter_changed) get_app().window.InsertKeyframe.connect(self.Insert_Action_Triggered) self.doubleClicked.connect(self.doubleClickedCB) self.loadProperties.connect(self.select_item)
class PropertiesTableView(QTableView): """ A Properties Table QWidget used on the main window """ loadProperties = pyqtSignal(str, str) def mouseMoveEvent(self, event): # Get data model and selection model = self.clip_properties_model.model row = self.indexAt(event.pos()).row() column = self.indexAt(event.pos()).column() if model.item(row, 0): self.selected_label = model.item(row, 0) self.selected_item = model.item(row, 1) # Is the user dragging on the value column if self.selected_label and self.selected_item: frame_number = self.clip_properties_model.frame_number # Get the position of the cursor and % value value_column_x = self.columnViewportPosition(1) value_column_y = value_column_x + self.columnWidth(1) cursor_value = event.x() - value_column_x cursor_value_percent = cursor_value / self.columnWidth(1) try: property = self.selected_label.data() except Exception as ex: # If item is deleted during this drag... an exception can occur # Just ignore, since this is harmless return property_key = property[0] property_name = property[1]["name"] property_type = property[1]["type"] property_max = property[1]["max"] property_min = property[1]["min"] property_value = property[1]["value"] readonly = property[1]["readonly"] item_id, item_type = self.selected_item.data() # Bail if readonly if readonly: return # Get the original data of this item (prior to any updates, for the undo/redo system) if not self.original_data: # Ignore undo/redo history temporarily (to avoid a huge pile of undo/redo history) get_app().updates.ignore_history = True # Find this clip c = None if item_type == "clip": # Get clip object c = Clip.get(id=item_id) elif item_type == "transition": # Get transition object c = Transition.get(id=item_id) elif item_type == "effect": # Get effect object c = Effect.get(id=item_id) if c: if property_key in c.data: # Grab the original data for this item/property self.original_data = c.data # Calculate percentage value if property_type in ["float", "int"]: min_max_range = float(property_max) - float(property_min) # Determine if range is unreasonably long (such as position, start, end, etc.... which can be huge #'s) if min_max_range > 1000.0: # Get the current value self.new_value = QLocale().system().toDouble( self.selected_item.text())[0] # Huge range - increment / decrement slowly if self.previous_x == -1: # init previous_x for the first time self.previous_x = event.x() # calculate # of pixels dragged drag_diff = self.previous_x - event.x() if drag_diff > 0: # Move to the left by a small amount self.new_value -= 0.50 elif drag_diff < 0: # Move to the right by a small amount self.new_value += 0.50 # update previous x self.previous_x = event.x() else: # Small range - use cursor % to calculate new value self.new_value = property_min + (min_max_range * cursor_value_percent) # Clamp value between min and max (just incase user drags too big) self.new_value = max(property_min, self.new_value) self.new_value = min(property_max, self.new_value) # Update value of this property self.clip_properties_model.value_updated( self.selected_item, -1, self.new_value) # Repaint self.viewport().update() def mouseReleaseEvent(self, event): # Inform UpdateManager to accept updates, and only store our final update get_app().updates.ignore_history = False # Add final update to undo/redo history get_app().updates.apply_last_action_to_history(self.original_data) # Clear original data self.original_data = None # Get data model and selection model = self.clip_properties_model.model row = self.indexAt(event.pos()).row() column = self.indexAt(event.pos()).column() if model.item(row, 0): self.selected_label = model.item(row, 0) self.selected_item = model.item(row, 1) def doubleClickedCB(self, model_index): """Double click handler for the property table""" # Get data model and selection model = self.clip_properties_model.model row = model_index.row() selected_label = model.item(row, 0) self.selected_item = model.item(row, 1) if selected_label: property = selected_label.data() property_type = property[1]["type"] if property_type == "color": # Get current value of color red = property[1]["red"]["value"] green = property[1]["green"]["value"] blue = property[1]["blue"]["value"] # Show color dialog currentColor = QColor(red, green, blue) newColor = QColorDialog.getColor(currentColor) # Set the new color keyframe self.clip_properties_model.color_update( self.selected_item, newColor) def select_item(self, item_id, item_type): """ Update the selected item in the properties window """ # Get translation object _ = get_app()._tr # Update item self.clip_properties_model.update_item(item_id, item_type) def select_frame(self, frame_number): """ Update the values of the selected clip, based on the current frame """ # Update item self.clip_properties_model.update_frame(frame_number) def filter_changed(self, value=None): """ Filter the list of properties """ # Update model self.clip_properties_model.update_model(value) def contextMenuEvent(self, event): # Get data model and selection model = self.clip_properties_model.model row = self.indexAt(event.pos()).row() selected_label = model.item(row, 0) selected_value = model.item(row, 1) self.selected_item = selected_value frame_number = self.clip_properties_model.frame_number # Get translation object _ = get_app()._tr # If item selected if selected_label: # Get data from selected item property = selected_label.data() property_name = property[1]["name"] self.property_type = property[1]["type"] points = property[1]["points"] self.choices = property[1]["choices"] property_key = property[0] clip_id, item_type = selected_value.data() log.info("Context menu shown for %s (%s) for clip %s on frame %s" % (property_name, property_key, clip_id, frame_number)) log.info("Points: %s" % points) log.info("Property: %s" % str(property)) # Define bezier presets bezier_presets = [ (0.250, 0.100, 0.250, 1.000, _("Ease (Default)")), (0.420, 0.000, 1.000, 1.000, _("Ease In")), (0.000, 0.000, 0.580, 1.000, _("Ease Out")), (0.420, 0.000, 0.580, 1.000, _("Ease In/Out")), (0.550, 0.085, 0.680, 0.530, _("Ease In (Quad)")), (0.550, 0.055, 0.675, 0.190, _("Ease In (Cubic)")), (0.895, 0.030, 0.685, 0.220, _("Ease In (Quart)")), (0.755, 0.050, 0.855, 0.060, _("Ease In (Quint)")), (0.470, 0.000, 0.745, 0.715, _("Ease In (Sine)")), (0.950, 0.050, 0.795, 0.035, _("Ease In (Expo)")), (0.600, 0.040, 0.980, 0.335, _("Ease In (Circ)")), (0.600, -0.280, 0.735, 0.045, _("Ease In (Back)")), (0.250, 0.460, 0.450, 0.940, _("Ease Out (Quad)")), (0.215, 0.610, 0.355, 1.000, _("Ease Out (Cubic)")), (0.165, 0.840, 0.440, 1.000, _("Ease Out (Quart)")), (0.230, 1.000, 0.320, 1.000, _("Ease Out (Quint)")), (0.390, 0.575, 0.565, 1.000, _("Ease Out (Sine)")), (0.190, 1.000, 0.220, 1.000, _("Ease Out (Expo)")), (0.075, 0.820, 0.165, 1.000, _("Ease Out (Circ)")), (0.175, 0.885, 0.320, 1.275, _("Ease Out (Back)")), (0.455, 0.030, 0.515, 0.955, _("Ease In/Out (Quad)")), (0.645, 0.045, 0.355, 1.000, _("Ease In/Out (Cubic)")), (0.770, 0.000, 0.175, 1.000, _("Ease In/Out (Quart)")), (0.860, 0.000, 0.070, 1.000, _("Ease In/Out (Quint)")), (0.445, 0.050, 0.550, 0.950, _("Ease In/Out (Sine)")), (1.000, 0.000, 0.000, 1.000, _("Ease In/Out (Expo)")), (0.785, 0.135, 0.150, 0.860, _("Ease In/Out (Circ)")), (0.680, -0.550, 0.265, 1.550, _("Ease In/Out (Back)")) ] bezier_icon = QIcon( QPixmap( os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.BEZIER))) linear_icon = QIcon( QPixmap( os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.LINEAR))) constant_icon = QIcon( QPixmap( os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.CONSTANT))) # Add menu options for keyframes menu = QMenu(self) if points > 1: # Menu for more than 1 point Bezier_Menu = QMenu(_("Bezier"), self) Bezier_Menu.setIcon(bezier_icon) for bezier_preset in bezier_presets: preset_action = Bezier_Menu.addAction(bezier_preset[4]) preset_action.triggered.connect( partial(self.Bezier_Action_Triggered, bezier_preset)) menu.addMenu(Bezier_Menu) Linear_Action = menu.addAction(_("Linear")) Linear_Action.setIcon(linear_icon) Linear_Action.triggered.connect(self.Linear_Action_Triggered) Constant_Action = menu.addAction(_("Constant")) Constant_Action.setIcon(constant_icon) Constant_Action.triggered.connect( self.Constant_Action_Triggered) menu.addSeparator() Insert_Action = menu.addAction(_("Insert Keyframe")) Insert_Action.triggered.connect(self.Insert_Action_Triggered) Remove_Action = menu.addAction(_("Remove Keyframe")) Remove_Action.triggered.connect(self.Remove_Action_Triggered) menu.popup(QCursor.pos()) elif points == 1: # Menu for a single point Insert_Action = menu.addAction(_("Insert Keyframe")) Insert_Action.triggered.connect(self.Insert_Action_Triggered) Remove_Action = menu.addAction(_("Remove Keyframe")) Remove_Action.triggered.connect(self.Remove_Action_Triggered) menu.popup(QCursor.pos()) if self.choices: # Menu for choices for choice in self.choices: Choice_Action = menu.addAction(_(choice["name"])) Choice_Action.setData(choice["value"]) Choice_Action.triggered.connect( self.Choice_Action_Triggered) # Show choice menu menu.popup(QCursor.pos()) def Bezier_Action_Triggered(self, preset=[]): log.info("Bezier_Action_Triggered: %s" % str(preset)) if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated( self.selected_item, interpolation=0, interpolation_details=preset) else: # Update colors interpolation mode self.clip_properties_model.color_update( self.selected_item, QColor("#000"), interpolation=0, interpolation_details=preset) def Linear_Action_Triggered(self, event): log.info("Linear_Action_Triggered") if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated(self.selected_item, interpolation=1) else: # Update colors interpolation mode self.clip_properties_model.color_update(self.selected_item, QColor("#000"), interpolation=1, interpolation_details=[]) def Constant_Action_Triggered(self, event): log.info("Constant_Action_Triggered") if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated(self.selected_item, interpolation=2) else: # Update colors interpolation mode self.clip_properties_model.color_update(self.selected_item, QColor("#000"), interpolation=2, interpolation_details=[]) def Insert_Action_Triggered(self, event): log.info("Insert_Action_Triggered") if self.selected_item: current_value = QLocale().system().toDouble( self.selected_item.text())[0] self.clip_properties_model.value_updated(self.selected_item, value=current_value) def Remove_Action_Triggered(self, event): log.info("Remove_Action_Triggered") self.clip_properties_model.remove_keyframe(self.selected_item) def Choice_Action_Triggered(self, event): log.info("Choice_Action_Triggered") choice_value = self.sender().data() # Update value of dropdown item self.clip_properties_model.value_updated(self.selected_item, value=choice_value) def __init__(self, *args): # Invoke parent init QTableView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.clip_properties_model = PropertiesModel(self) # Keep track of mouse press start position to determine when to start drag self.selected = [] self.selected_label = None self.selected_item = None self.new_value = None self.original_data = None # Setup header columns self.setModel(self.clip_properties_model.model) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) # Set delegate delegate = PropertyDelegate() self.setItemDelegateForColumn(1, delegate) self.previous_x = -1 # Get table header horizontal_header = self.horizontalHeader() horizontal_header.setSectionResizeMode(QHeaderView.Stretch) vertical_header = self.verticalHeader() vertical_header.setVisible(False) # Refresh view self.clip_properties_model.update_model() # Resize columns self.resizeColumnToContents(0) self.resizeColumnToContents(1) # Connect filter signals get_app().window.txtPropertyFilter.textChanged.connect( self.filter_changed) get_app().window.InsertKeyframe.connect(self.Insert_Action_Triggered) self.doubleClicked.connect(self.doubleClickedCB) self.loadProperties.connect(self.select_item)
class PropertiesTableView(QTableView): """ A Properties Table QWidget used on the main window """ loadProperties = pyqtSignal(str, str) def mouseMoveEvent(self, event): # Get data model and selection model = self.clip_properties_model.model row = self.indexAt(event.pos()).row() column = self.indexAt(event.pos()).column() if model.item(row, 0): self.selected_label = model.item(row, 0) self.selected_item = model.item(row, 1) # Is the user dragging on the value column if self.selected_label and self.selected_item: frame_number = self.clip_properties_model.frame_number # Get the position of the cursor and % value value_column_x = self.columnViewportPosition(1) value_column_y = value_column_x + self.columnWidth(1) cursor_value = event.x() - value_column_x cursor_value_percent = cursor_value / self.columnWidth(1) property = self.selected_label.data() property_key = property[0] property_name = property[1]["name"] property_type = property[1]["type"] property_max = property[1]["max"] property_min = property[1]["min"] property_value = property[1]["value"] readonly = property[1]["readonly"] item_id, item_type = self.selected_item.data() # Bail if readonly if readonly: return # Get the original data of this item (prior to any updates, for the undo/redo system) if not self.original_data: # Ignore undo/redo history temporarily (to avoid a huge pile of undo/redo history) get_app().updates.ignore_history = True # Find this clip c = None if item_type == "clip": # Get clip object c = Clip.get(id=item_id) elif item_type == "transition": # Get transition object c = Transition.get(id=item_id) elif item_type == "effect": # Get effect object c = Effect.get(id=item_id) if c: if property_key in c.data: # Grab the original data for this item/property self.original_data = c.data # Calculate percentage value if property_type in ["float", "int"]: min_max_range = float(property_max) - float(property_min) # Determine if range is unreasonably long (such as position, start, end, etc.... which can be huge #'s) if min_max_range > 1000.0: # Get the current value self.new_value = QLocale().system().toDouble(self.selected_item.text())[0] # Huge range - increment / decrement slowly if self.previous_x == -1: # init previous_x for the first time self.previous_x = event.x() # calculate # of pixels dragged drag_diff = self.previous_x - event.x() if drag_diff > 0: # Move to the left by a small amount self.new_value -= 0.50 elif drag_diff < 0: # Move to the right by a small amount self.new_value += 0.50 # update previous x self.previous_x = event.x() else: # Small range - use cursor % to calculate new value self.new_value = property_min + (min_max_range * cursor_value_percent) # Clamp value between min and max (just incase user drags too big) self.new_value = max(property_min, self.new_value) self.new_value = min(property_max, self.new_value) # Update value of this property self.clip_properties_model.value_updated(self.selected_item, -1, self.new_value) # Repaint self.viewport().update() def mouseReleaseEvent(self, event): # Inform UpdateManager to accept updates, and only store our final update get_app().updates.ignore_history = False # Add final update to undo/redo history get_app().updates.apply_last_action_to_history(self.original_data) # Clear original data self.original_data = None def doubleClicked(self, model_index): """Double click handler for the property table""" # Get data model and selection model = self.clip_properties_model.model row = model_index.row() selected_label = model.item(row, 0) self.selected_item = model.item(row, 1) if selected_label: property = selected_label.data() property_type = property[1]["type"] if property_type == "color": # Get current value of color red = property[1]["red"]["value"] green = property[1]["green"]["value"] blue = property[1]["blue"]["value"] # Show color dialog currentColor = QColor(red, green, blue) newColor = QColorDialog.getColor(currentColor) # Set the new color keyframe self.clip_properties_model.color_update(self.selected_item, newColor) def select_item(self, item_id, item_type): """ Update the selected item in the properties window """ # Get translation object _ = get_app()._tr # Update item self.clip_properties_model.update_item(item_id, item_type) def select_frame(self, frame_number): """ Update the values of the selected clip, based on the current frame """ # Update item self.clip_properties_model.update_frame(frame_number) def filter_changed(self, value=None): """ Filter the list of properties """ # Update model self.clip_properties_model.update_model(value) def contextMenuEvent(self, event): # Get data model and selection model = self.clip_properties_model.model row = self.indexAt(event.pos()).row() selected_label = model.item(row, 0) selected_value = model.item(row, 1) self.selected_item = selected_value frame_number = self.clip_properties_model.frame_number # Get translation object _ = get_app()._tr # If item selected if selected_label: # Get data from selected item property = selected_label.data() property_name = property[1]["name"] self.property_type = property[1]["type"] points = property[1]["points"] self.choices = property[1]["choices"] property_key = property[0] clip_id, item_type = selected_value.data() log.info("Context menu shown for %s (%s) for clip %s on frame %s" % ( property_name, property_key, clip_id, frame_number)) log.info("Points: %s" % points) log.info("Property: %s" % str(property)) bezier_icon = QIcon(QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.BEZIER))) linear_icon = QIcon(QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.LINEAR))) constant_icon = QIcon(QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.CONSTANT))) # Add menu options for keyframes menu = QMenu(self) if points > 1: # Menu for more than 1 point Bezier_Action = menu.addAction(_("Bezier")) Bezier_Action.setIcon(bezier_icon) Bezier_Action.triggered.connect(self.Bezier_Action_Triggered) Linear_Action = menu.addAction(_("Linear")) Linear_Action.setIcon(linear_icon) Linear_Action.triggered.connect(self.Linear_Action_Triggered) Constant_Action = menu.addAction(_("Constant")) Constant_Action.setIcon(constant_icon) Constant_Action.triggered.connect(self.Constant_Action_Triggered) menu.addSeparator() Remove_Action = menu.addAction(_("Remove Keyframe")) Remove_Action.triggered.connect(self.Remove_Action_Triggered) menu.popup(QCursor.pos()) elif points == 1: # Menu for a single point Remove_Action = menu.addAction(_("Remove Keyframe")) Remove_Action.triggered.connect(self.Remove_Action_Triggered) menu.popup(QCursor.pos()) if self.choices: # Menu for choices for choice in self.choices: Choice_Action = menu.addAction(_(choice["name"])) Choice_Action.setData(choice["value"]) Choice_Action.triggered.connect(self.Choice_Action_Triggered) # Show choice menu menu.popup(QCursor.pos()) def Bezier_Action_Triggered(self, event): log.info("Bezier_Action_Triggered") if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated(self.selected_item, 0) else: # Update colors interpolation mode self.clip_properties_model.color_update(self.selected_item, QColor("#000"), 0) def Linear_Action_Triggered(self, event): log.info("Linear_Action_Triggered") if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated(self.selected_item, 1) else: # Update colors interpolation mode self.clip_properties_model.color_update(self.selected_item, QColor("#000"), 1) def Constant_Action_Triggered(self, event): log.info("Constant_Action_Triggered") if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated(self.selected_item, 2) else: # Update colors interpolation mode self.clip_properties_model.color_update(self.selected_item, QColor("#000"), 2) def Remove_Action_Triggered(self, event): log.info("Remove_Action_Triggered") self.clip_properties_model.remove_keyframe(self.selected_item) def Choice_Action_Triggered(self, event): log.info("Choice_Action_Triggered") choice_value = self.sender().data() # Update value of dropdown item self.clip_properties_model.value_updated(self.selected_item, value=choice_value) def __init__(self, *args): # Invoke parent init QTableView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.clip_properties_model = PropertiesModel(self) # Keep track of mouse press start position to determine when to start drag self.selected = [] self.selected_label = None self.selected_item = None self.new_value = None self.original_data = None # Setup header columns self.setModel(self.clip_properties_model.model) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) # Set delegate delegate = PropertyDelegate() self.setItemDelegateForColumn(1, delegate) self.previous_x = -1 # Get table header horizontal_header = self.horizontalHeader() horizontal_header.setSectionResizeMode(QHeaderView.Stretch) vertical_header = self.verticalHeader() vertical_header.setVisible(False) # Refresh view self.clip_properties_model.update_model() # Resize columns self.resizeColumnToContents(0) self.resizeColumnToContents(1) # Connect filter signals get_app().window.txtPropertyFilter.textChanged.connect(self.filter_changed) self.loadProperties.connect(self.select_item)
class PropertiesTableView(QTableView): """ A Properties Table QWidget used on the main window """ loadProperties = pyqtSignal(str, str) def mouseMoveEvent(self, event): # Get data model and selection model = self.clip_properties_model.model row = self.indexAt(event.pos()).row() column = self.indexAt(event.pos()).column() if model.item(row, 0): self.selected_label = model.item(row, 0) self.selected_item = model.item(row, 1) # Is the user dragging on the value column if self.selected_label and self.selected_item: frame_number = self.clip_properties_model.frame_number # Get the position of the cursor and % value value_column_x = self.columnViewportPosition(1) value_column_y = value_column_x + self.columnWidth(1) cursor_value = event.x() - value_column_x cursor_value_percent = cursor_value / self.columnWidth(1) try: property = self.selected_label.data() except Exception as ex: # If item is deleted during this drag... an exception can occur # Just ignore, since this is harmless return property_key = property[0] property_name = property[1]["name"] property_type = property[1]["type"] property_max = property[1]["max"] property_min = property[1]["min"] property_value = property[1]["value"] readonly = property[1]["readonly"] item_id, item_type = self.selected_item.data() # Bail if readonly if readonly: return # Get the original data of this item (prior to any updates, for the undo/redo system) if not self.original_data: # Ignore undo/redo history temporarily (to avoid a huge pile of undo/redo history) get_app().updates.ignore_history = True # Find this clip c = None if item_type == "clip": # Get clip object c = Clip.get(id=item_id) elif item_type == "transition": # Get transition object c = Transition.get(id=item_id) elif item_type == "effect": # Get effect object c = Effect.get(id=item_id) if c: if property_key in c.data: # Grab the original data for this item/property self.original_data = c.data # Calculate percentage value if property_type in ["float", "int"] and property_name != "Track": min_max_range = float(property_max) - float(property_min) # Determine if range is unreasonably long (such as position, start, end, etc.... which can be huge #'s) if min_max_range > 1000.0: # Get the current value self.new_value = QLocale().system().toDouble(self.selected_item.text())[0] # Huge range - increment / decrement slowly if self.previous_x == -1: # init previous_x for the first time self.previous_x = event.x() # calculate # of pixels dragged drag_diff = self.previous_x - event.x() if drag_diff > 0: # Move to the left by a small amount self.new_value -= 0.50 elif drag_diff < 0: # Move to the right by a small amount self.new_value += 0.50 # update previous x self.previous_x = event.x() else: # Small range - use cursor % to calculate new value self.new_value = property_min + (min_max_range * cursor_value_percent) # Clamp value between min and max (just incase user drags too big) self.new_value = max(property_min, self.new_value) self.new_value = min(property_max, self.new_value) # Update value of this property self.clip_properties_model.value_updated(self.selected_item, -1, self.new_value) # Repaint self.viewport().update() def mouseReleaseEvent(self, event): # Inform UpdateManager to accept updates, and only store our final update get_app().updates.ignore_history = False # Add final update to undo/redo history get_app().updates.apply_last_action_to_history(self.original_data) # Clear original data self.original_data = None # Get data model and selection model = self.clip_properties_model.model row = self.indexAt(event.pos()).row() column = self.indexAt(event.pos()).column() if model.item(row, 0): self.selected_label = model.item(row, 0) self.selected_item = model.item(row, 1) def doubleClickedCB(self, model_index): """Double click handler for the property table""" # Get translation object _ = get_app()._tr # Get data model and selection model = self.clip_properties_model.model row = model_index.row() selected_label = model.item(row, 0) self.selected_item = model.item(row, 1) if selected_label: property = selected_label.data() property_type = property[1]["type"] if property_type == "color": # Get current value of color red = property[1]["red"]["value"] green = property[1]["green"]["value"] blue = property[1]["blue"]["value"] # Show color dialog currentColor = QColor(red, green, blue) newColor = QColorDialog.getColor(currentColor, self, _("Select a Color"), QColorDialog.DontUseNativeDialog) # Set the new color keyframe self.clip_properties_model.color_update(self.selected_item, newColor) def select_item(self, item_id, item_type): """ Update the selected item in the properties window """ # Get translation object _ = get_app()._tr # Update item self.clip_properties_model.update_item(item_id, item_type) def select_frame(self, frame_number): """ Update the values of the selected clip, based on the current frame """ # Update item self.clip_properties_model.update_frame(frame_number) def filter_changed(self, value=None): """ Filter the list of properties """ # Update model self.clip_properties_model.update_model(value) def contextMenuEvent(self, event): # Get data model and selection model = self.clip_properties_model.model row = self.indexAt(event.pos()).row() selected_label = model.item(row, 0) selected_value = model.item(row, 1) self.selected_item = selected_value frame_number = self.clip_properties_model.frame_number # Get translation object _ = get_app()._tr # If item selected if selected_label: # Get data from selected item property = selected_label.data() property_name = property[1]["name"] self.property_type = property[1]["type"] memo = json.loads(property[1]["memo"] or "{}") points = property[1]["points"] self.choices = property[1]["choices"] property_key = property[0] clip_id, item_type = selected_value.data() log.info("Context menu shown for %s (%s) for clip %s on frame %s" % (property_name, property_key, clip_id, frame_number)) log.info("Points: %s" % points) log.info("Property: %s" % str(property)) # Handle reader type values if self.property_type == "reader" and not self.choices: # Refresh models self.transition_model.update_model() self.files_model.update_model() # Add all files file_choices = [] for filesIndex in range(self.files_model.model.rowCount()): modelIndex = self.files_model.model.index(filesIndex, 0) fileItem = self.files_model.model.itemFromIndex(modelIndex) fileIcon = self.files_model.model.item(fileItem.row(), 0).icon() fileName = self.files_model.model.item(fileItem.row(), 1).text() fileParentPath = self.files_model.model.item(fileItem.row(), 4).text() # Append file choice file_choices.append({"name": fileName, "value": os.path.join(fileParentPath, fileName), "selected": False, "icon": fileIcon }) # Add root file choice self.choices.append({"name": _("Files"), "value": file_choices, "selected": False}) # Add all transitions trans_choices = [] for transIndex in range(self.transition_model.model.rowCount()): modelIndex = self.transition_model.model.index(transIndex, 0) transItem = self.transition_model.model.itemFromIndex(modelIndex) transIcon = self.transition_model.model.item(transItem.row(), 0).icon() transName = self.transition_model.model.item(transItem.row(), 1).text() transPath = self.transition_model.model.item(transItem.row(), 3).text() # Append transition choice trans_choices.append({"name": transName, "value": transPath, "selected": False, "icon": transIcon }) # Add root transitions choice self.choices.append({"name": _("Transitions"), "value": trans_choices, "selected": False}) # Handle reader type values if property_name =="Track" and self.property_type == "int" and not self.choices: # Populate all display track names track_choices = [] all_tracks = get_app().project.get(["layers"]) display_count = len(all_tracks) for track in reversed(sorted(all_tracks, key=itemgetter('number'))): # Append track choice track_name = track.get("label") or _("Track %s") % display_count self.choices.append({"name": track_name, "value": track.get("number"), "selected": False }) display_count -= 1 # Define bezier presets bezier_presets = [ (0.250, 0.100, 0.250, 1.000, _("Ease (Default)")), (0.420, 0.000, 1.000, 1.000, _("Ease In")), (0.000, 0.000, 0.580, 1.000, _("Ease Out")), (0.420, 0.000, 0.580, 1.000, _("Ease In/Out")), (0.550, 0.085, 0.680, 0.530, _("Ease In (Quad)")), (0.550, 0.055, 0.675, 0.190, _("Ease In (Cubic)")), (0.895, 0.030, 0.685, 0.220, _("Ease In (Quart)")), (0.755, 0.050, 0.855, 0.060, _("Ease In (Quint)")), (0.470, 0.000, 0.745, 0.715, _("Ease In (Sine)")), (0.950, 0.050, 0.795, 0.035, _("Ease In (Expo)")), (0.600, 0.040, 0.980, 0.335, _("Ease In (Circ)")), (0.600, -0.280, 0.735, 0.045, _("Ease In (Back)")), (0.250, 0.460, 0.450, 0.940, _("Ease Out (Quad)")), (0.215, 0.610, 0.355, 1.000, _("Ease Out (Cubic)")), (0.165, 0.840, 0.440, 1.000, _("Ease Out (Quart)")), (0.230, 1.000, 0.320, 1.000, _("Ease Out (Quint)")), (0.390, 0.575, 0.565, 1.000, _("Ease Out (Sine)")), (0.190, 1.000, 0.220, 1.000, _("Ease Out (Expo)")), (0.075, 0.820, 0.165, 1.000, _("Ease Out (Circ)")), (0.175, 0.885, 0.320, 1.275, _("Ease Out (Back)")), (0.455, 0.030, 0.515, 0.955, _("Ease In/Out (Quad)")), (0.645, 0.045, 0.355, 1.000, _("Ease In/Out (Cubic)")), (0.770, 0.000, 0.175, 1.000, _("Ease In/Out (Quart)")), (0.860, 0.000, 0.070, 1.000, _("Ease In/Out (Quint)")), (0.445, 0.050, 0.550, 0.950, _("Ease In/Out (Sine)")), (1.000, 0.000, 0.000, 1.000, _("Ease In/Out (Expo)")), (0.785, 0.135, 0.150, 0.860, _("Ease In/Out (Circ)")), (0.680, -0.550, 0.265, 1.550, _("Ease In/Out (Back)")) ] bezier_icon = QIcon(QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.BEZIER))) linear_icon = QIcon(QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.LINEAR))) constant_icon = QIcon(QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.CONSTANT))) # Add menu options for keyframes menu = QMenu(self) if points > 1: # Menu for more than 1 point Bezier_Menu = QMenu(_("Bezier"), self) Bezier_Menu.setIcon(bezier_icon) for bezier_preset in bezier_presets: preset_action = Bezier_Menu.addAction(bezier_preset[4]) preset_action.triggered.connect(partial(self.Bezier_Action_Triggered, bezier_preset)) menu.addMenu(Bezier_Menu) Linear_Action = menu.addAction(_("Linear")) Linear_Action.setIcon(linear_icon) Linear_Action.triggered.connect(self.Linear_Action_Triggered) Constant_Action = menu.addAction(_("Constant")) Constant_Action.setIcon(constant_icon) Constant_Action.triggered.connect(self.Constant_Action_Triggered) menu.addSeparator() Insert_Action = menu.addAction(_("Insert Keyframe")) Insert_Action.triggered.connect(self.Insert_Action_Triggered) Remove_Action = menu.addAction(_("Remove Keyframe")) Remove_Action.triggered.connect(self.Remove_Action_Triggered) menu.popup(QCursor.pos()) elif points == 1: # Menu for a single point Insert_Action = menu.addAction(_("Insert Keyframe")) Insert_Action.triggered.connect(self.Insert_Action_Triggered) Remove_Action = menu.addAction(_("Remove Keyframe")) Remove_Action.triggered.connect(self.Remove_Action_Triggered) menu.popup(QCursor.pos()) if self.choices: # Menu for choices for choice in self.choices: if type(choice["value"]) != list: # Add root choice items Choice_Action = menu.addAction(_(choice["name"])) Choice_Action.setData(choice["value"]) Choice_Action.triggered.connect(self.Choice_Action_Triggered) else: # Add sub-choice items (for nested choice lists) # Divide into smaller QMenus (since large lists cover the entire screen) # For example: Transitions -> 1 -> sub items SubMenu = None SubMenuRoot = QMenu(_(choice["name"]), self) SubMenuSize = 24 SubMenuNumber = 0 for sub_choice in choice["value"]: if len(choice["value"]) > SubMenuSize: if not SubMenu or len(SubMenu.children()) == SubMenuSize or sub_choice == choice["value"][-1]: SubMenuNumber += 1 if SubMenu: SubMenuRoot.addMenu(SubMenu) SubMenu = QMenu(str(SubMenuNumber), self) Choice_Action = SubMenu.addAction(_(sub_choice["name"])) else: Choice_Action = SubMenuRoot.addAction(_(sub_choice["name"])) Choice_Action.setData(sub_choice["value"]) subChoiceIcon = sub_choice.get("icon") if subChoiceIcon: Choice_Action.setIcon(subChoiceIcon) Choice_Action.triggered.connect(self.Choice_Action_Triggered) menu.addMenu(SubMenuRoot) # Show choice menu menu.popup(QCursor.pos()) def Bezier_Action_Triggered(self, preset=[]): log.info("Bezier_Action_Triggered: %s" % str(preset)) if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated(self.selected_item, interpolation=0, interpolation_details=preset) else: # Update colors interpolation mode self.clip_properties_model.color_update(self.selected_item, QColor("#000"), interpolation=0, interpolation_details=preset) def Linear_Action_Triggered(self, event): log.info("Linear_Action_Triggered") if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated(self.selected_item, interpolation=1) else: # Update colors interpolation mode self.clip_properties_model.color_update(self.selected_item, QColor("#000"), interpolation=1, interpolation_details=[]) def Constant_Action_Triggered(self, event): log.info("Constant_Action_Triggered") if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated(self.selected_item, interpolation=2) else: # Update colors interpolation mode self.clip_properties_model.color_update(self.selected_item, QColor("#000"), interpolation=2, interpolation_details=[]) def Insert_Action_Triggered(self, event): log.info("Insert_Action_Triggered") if self.selected_item: current_value = QLocale().system().toDouble(self.selected_item.text())[0] self.clip_properties_model.value_updated(self.selected_item, value=current_value) def Remove_Action_Triggered(self, event): log.info("Remove_Action_Triggered") self.clip_properties_model.remove_keyframe(self.selected_item) def Choice_Action_Triggered(self, event): log.info("Choice_Action_Triggered") choice_value = self.sender().data() # Update value of dropdown item self.clip_properties_model.value_updated(self.selected_item, value=choice_value) def __init__(self, *args): # Invoke parent init QTableView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.clip_properties_model = PropertiesModel(self) self.transition_model = TransitionsModel(self) self.files_model = FilesModel(self) # Keep track of mouse press start position to determine when to start drag self.selected = [] self.selected_label = None self.selected_item = None self.new_value = None self.original_data = None # Setup header columns self.setModel(self.clip_properties_model.model) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) # Set delegate delegate = PropertyDelegate() self.setItemDelegateForColumn(1, delegate) self.previous_x = -1 # Get table header horizontal_header = self.horizontalHeader() horizontal_header.setSectionResizeMode(QHeaderView.Stretch) vertical_header = self.verticalHeader() vertical_header.setVisible(False) # Refresh view self.clip_properties_model.update_model() self.transition_model.update_model() self.files_model.update_model() # Resize columns self.resizeColumnToContents(0) self.resizeColumnToContents(1) # Connect filter signals get_app().window.txtPropertyFilter.textChanged.connect(self.filter_changed) get_app().window.InsertKeyframe.connect(self.Insert_Action_Triggered) self.doubleClicked.connect(self.doubleClickedCB) self.loadProperties.connect(self.select_item)
def __init__(self, *args): # Invoke parent init QTableView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Create properties model self.clip_properties_model = PropertiesModel(self) # Get base models for files, transitions self.transition_model = self.win.transition_model.model self.files_model = self.win.files_model.model # Connect to update signals, so our menus stay current self.win.files_model.ModelRefreshed.connect(self.refresh_menu) self.win.transition_model.ModelRefreshed.connect(self.refresh_menu) self.menu_reset = False # Keep track of mouse press start position to determine when to start drag self.selected = [] self.selected_label = None self.selected_item = None self.new_value = None self.original_data = None self.lock_selection = False self.prev_row = None # Context menu icons self.bezier_icon = QIcon(QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.BEZIER))) self.linear_icon = QIcon(QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.LINEAR))) self.constant_icon = QIcon(QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.CONSTANT))) # Setup header columns self.setModel(self.clip_properties_model.model) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) # Set delegate delegate = PropertyDelegate() self.setItemDelegateForColumn(1, delegate) self.previous_x = -1 # Get table header horizontal_header = self.horizontalHeader() horizontal_header.setSectionResizeMode(QHeaderView.Stretch) vertical_header = self.verticalHeader() vertical_header.setVisible(False) # Refresh view self.clip_properties_model.update_model() # Resize columns self.resizeColumnToContents(0) self.resizeColumnToContents(1) # Connect filter signals get_app().window.txtPropertyFilter.textChanged.connect(self.filter_changed) get_app().window.InsertKeyframe.connect(self.Insert_Action_Triggered) self.doubleClicked.connect(self.doubleClickedCB) self.loadProperties.connect(self.select_item) get_app().window.CaptionTextUpdated.connect(self.caption_text_updated)
class PropertiesTableView(QTableView): """ A Properties Table QWidget used on the main window """ loadProperties = pyqtSignal(str, str) def mouseMoveEvent(self, event): # Get data model and selection model = self.clip_properties_model.model # Do not change selected row during mouse move if self.lock_selection and self.prev_row: row = self.prev_row else: row = self.indexAt(event.pos()).row() self.prev_row = row self.lock_selection = True if row is None: return event.accept() if model.item(row, 0): self.selected_label = model.item(row, 0) self.selected_item = model.item(row, 1) # Is the user dragging on the value column if self.selected_label and self.selected_item: # Get the position of the cursor and % value value_column_x = self.columnViewportPosition(1) cursor_value = event.x() - value_column_x cursor_value_percent = cursor_value / self.columnWidth(1) try: cur_property = self.selected_label.data() except Exception: # If item is deleted during this drag... an exception can occur # Just ignore, since this is harmless log.debug('Failed to access data on selected label widget') property_key = cur_property[0] property_name = cur_property[1]["name"] property_type = cur_property[1]["type"] property_max = cur_property[1]["max"] property_min = cur_property[1]["min"] readonly = cur_property[1]["readonly"] item_id, item_type = self.selected_item.data() # Bail if readonly if readonly: return # Get the original data of this item (prior to any updates, for the undo/redo system) if not self.original_data: # Ignore undo/redo history temporarily (to avoid a huge pile of undo/redo history) get_app().updates.ignore_history = True # Find this clip c = None if item_type == "clip": # Get clip object c = Clip.get(id=item_id) elif item_type == "transition": # Get transition object c = Transition.get(id=item_id) elif item_type == "effect": # Get effect object c = Effect.get(id=item_id) if c and property_key in c.data: # Grab the original data for this item/property self.original_data = c.data # For numeric values, apply percentage within parameter's allowable range if property_type in ["float", "int"] and property_name != "Track": if self.previous_x == -1: # Start tracking movement (init diff_length and previous_x) self.diff_length = 10 self.previous_x = event.x() # Calculate # of pixels dragged drag_diff = self.previous_x - event.x() # update previous x self.previous_x = event.x() # Ignore small initial movements if abs(drag_diff) < self.diff_length: # Lower threshold to 0 incrementally, to guarantee it'll eventually be exceeded self.diff_length = max(0, self.diff_length - 1) return # Compute size of property's possible values range min_max_range = float(property_max) - float(property_min) if min_max_range < 1000.0: # Small range - use cursor to calculate new value as percentage of total range self.new_value = property_min + (min_max_range * cursor_value_percent) else: # range is unreasonably long (such as position, start, end, etc.... which can be huge #'s) # Get the current value and apply fixed adjustments in response to motion self.new_value = QLocale().system().toDouble(self.selected_item.text())[0] if drag_diff > 0: # Move to the left by a small amount self.new_value -= 0.50 elif drag_diff < 0: # Move to the right by a small amount self.new_value += 0.50 # Clamp value between min and max (just incase user drags too big) self.new_value = max(property_min, self.new_value) self.new_value = min(property_max, self.new_value) if property_type == "int": self.new_value = round(self.new_value, 0) # Update value of this property self.clip_properties_model.value_updated(self.selected_item, -1, self.new_value) # Repaint self.viewport().update() def mouseReleaseEvent(self, event): # Inform UpdateManager to accept updates, and only store our final update event.accept() get_app().updates.ignore_history = False # Add final update to undo/redo history get_app().updates.apply_last_action_to_history(self.original_data) # Clear original data self.original_data = None # Get data model and selection model = self.clip_properties_model.model row = self.indexAt(event.pos()).row() if model.item(row, 0): self.selected_label = model.item(row, 0) self.selected_item = model.item(row, 1) # Allow new selection and prepare to set minimum move threshold self.lock_selection = False self.previous_x = -1 @pyqtSlot(QColor) def color_callback(self, newColor: QColor): # Set the new color keyframe if newColor.isValid(): self.clip_properties_model.color_update( self.selected_item, newColor) def doubleClickedCB(self, model_index): """Double click handler for the property table""" # Get translation object _ = get_app()._tr # Get data model and selection model = self.clip_properties_model.model row = model_index.row() selected_label = model.item(row, 0) self.selected_item = model.item(row, 1) if selected_label: cur_property = selected_label.data() property_type = cur_property[1]["type"] if property_type == "color": # Get current value of color red = cur_property[1]["red"]["value"] green = cur_property[1]["green"]["value"] blue = cur_property[1]["blue"]["value"] # Show color dialog currentColor = QColor(red, green, blue) log.debug("Launching ColorPicker for %s", currentColor.name()) ColorPicker( currentColor, parent=self, title=_("Select a Color"), callback=self.color_callback) return elif property_type == "font": # Get font from user current_font_name = cur_property[1].get("memo", "sans") current_font = QFont(current_font_name) font, ok = QFontDialog.getFont(current_font, caption=("Change Font")) # Update font if ok and font: fontinfo = QFontInfo(font) # TODO: pass font details to value_updated so we can set multiple values font_details = { "font_family": fontinfo.family(), "font_style": fontinfo.styleName(), "font_weight": fontinfo.weight(), "font_size_pixel": fontinfo.pixelSize() } self.clip_properties_model.value_updated(self.selected_item, value=fontinfo.family()) def caption_text_updated(self, new_caption_text, caption_model_row): """Caption text has been updated in the caption editor, and needs saving""" if not caption_model_row: # Ignore blank selections return # Get data model and selection cur_property = caption_model_row[0].data() property_type = cur_property[1]["type"] # Save caption text if property_type == "caption" and cur_property[1].get('memo') != new_caption_text: self.clip_properties_model.value_updated(caption_model_row[1], value=new_caption_text) def select_item(self, item_id, item_type): """ Update the selected item in the properties window """ # Get translation object _ = get_app()._tr # Update item self.clip_properties_model.update_item(item_id, item_type) def select_frame(self, frame_number): """ Update the values of the selected clip, based on the current frame """ # Update item self.clip_properties_model.update_frame(frame_number) def filter_changed(self, value=None): """ Filter the list of properties """ # Update model self.clip_properties_model.update_model(value) def contextMenuEvent(self, event): """ Display context menu """ # Get property being acted on index = self.indexAt(event.pos()) if not index.isValid(): event.ignore() return # Get data model and selection idx = self.indexAt(event.pos()) row = idx.row() selected_label = idx.model().item(row, 0) selected_value = idx.model().item(row, 1) self.selected_item = selected_value frame_number = self.clip_properties_model.frame_number # Get translation object _ = get_app()._tr # If item selected if selected_label: # Get data from selected item cur_property = selected_label.data() property_name = cur_property[1]["name"] self.property_type = cur_property[1]["type"] points = cur_property[1]["points"] self.choices = cur_property[1]["choices"] property_key = cur_property[0] clip_id, item_type = selected_value.data() log.info("Context menu shown for %s (%s) for clip %s on frame %s" % (property_name, property_key, clip_id, frame_number)) log.info("Points: %s" % points) # Clear menu if models updated if self.menu_reset: self.choices = [] self.menu_reset = False # Handle parent effect options if property_key == "parent_effect_id" and not self.choices: clip_choices = [{ "name": "None", "value": "None", "selected": False, "icon": QIcon() }] # Instantiate the timeline timeline_instance = get_app().window.timeline_sync.timeline # Instantiate this effect effect = timeline_instance.GetClipEffect(clip_id) effect_json = json.loads(effect.Json()) # Loop through timeline's clips for clip_instance in timeline_instance.Clips(): clip_instance_id = clip_instance.Id() # Avoid parent a clip effect to it's own effect if (clip_instance_id != effect.ParentClipId()): # Clip's propertyJSON data clip_instance_data = Clip.get(id = clip_instance_id).data # Path to the clip file clip_instance_path = clip_instance_data["reader"]["path"] # Iterate through all clip files on the timeline for clip_number in range(self.files_model.rowCount()): clip_index = self.files_model.index(clip_number, 0) clip_name = clip_index.sibling(clip_number, 1).data() clip_path = os.path.join(clip_index.sibling(clip_number, 4).data(), clip_name) # Check if the timeline's clip file name matches the clip the user selected if (clip_path == clip_instance_path): # Generate the clip icon to show in the selection menu clip_instance_icon = clip_index.data(Qt.DecorationRole) effect_choices = [{"name": "None", "value": "None", "selected": False, "icon": QIcon()}] # Iterate through clip's effects for effect_data in clip_instance_data["effects"]: # Make sure the user can only set a parent effect of the same type as this effect if effect_data['class_name'] == effect_json['class_name']: effect_id = effect_data["id"] effect_name = effect_data['class_name'] effect_icon = QIcon(QPixmap(os.path.join(info.PATH, "effects", "icons", "%s.png" % effect_data['class_name'].lower()))) effect_choices.append({"name": effect_id, "value": effect_id, "selected": False, "icon": effect_icon}) self.choices.append({"name": _(clip_instance_data["title"]), "value": effect_choices, "selected": False, "icon": clip_instance_icon}) # Handle selected object options (ObjectDetection effect) if property_key == "selected_object_index" and not self.choices: # Get all visible object's indexes timeline_instance = get_app().window.timeline_sync.timeline # Instantiate the effect effect = timeline_instance.GetClipEffect(clip_id) # Get the indexes and IDs of the visible objects visible_objects = json.loads(effect.GetVisibleObjects(frame_number)) # Add visible objects as choices object_index_choices = [] for object_index in visible_objects["visible_objects_index"]: object_index_choices.append({ "name": str(object_index), "value": str(object_index), "selected": False, "icon": QIcon() }) self.choices.append({"name": _("Detected Objects"), "value": object_index_choices, "selected": False, "icon": None}) # Handle property to set the Tracked Object's child clip if property_key == "child_clip_id" and not self.choices: clip_choices = [{ "name": "None", "value": "None", "selected": False, "icon": QIcon() }] # Instantiate the timeline timeline_instance = get_app().window.timeline_sync.timeline current_effect = timeline_instance.GetClipEffect(clip_id) # Loop through timeline's clips for clip_instance in timeline_instance.Clips(): clip_instance_id = clip_instance.Id() # Avoid attach a clip to it's own object if (clip_instance_id != clip_id and (current_effect and clip_instance_id != current_effect.ParentClip().Id())): # Clip's propertyJSON data clip_instance_data = Clip.get(id = clip_instance_id).data # Path to the clip file clip_instance_path = clip_instance_data["reader"]["path"] # Iterate through all clip files on the timeline for clip_number in range(self.files_model.rowCount()): clip_index = self.files_model.index(clip_number, 0) clip_name = clip_index.sibling(clip_number, 1).data() clip_path = os.path.join(clip_index.sibling(clip_number, 4).data(), clip_name) # Check if the timeline's clip file name matches the clip the user selected if (clip_path == clip_instance_path): # Generate the clip icon to show in the selection menu clip_instance_icon = clip_index.data(Qt.DecorationRole) self.choices.append({"name": clip_instance_data["title"], "value": clip_instance_id, "selected": False, "icon": clip_instance_icon}) # Handle clip attach options if property_key == "parentObjectId" and not self.choices: # Add all Clips as choices - initialize with None tracked_choices = [{ "name": "None", "value": "None", "selected": False, "icon": QIcon() }] clip_choices = [{ "name": "None", "value": "None", "selected": False, "icon": QIcon() }] # Instantiate the timeline timeline_instance = get_app().window.timeline_sync.timeline # Loop through timeline's clips for clip_instance in timeline_instance.Clips(): clip_instance_id = clip_instance.Id() # Avoid attach a clip to it's own object if (clip_instance_id != clip_id): # Clip's propertyJSON data clip_instance_data = Clip.get(id = clip_instance_id).data # Path to the clip file clip_instance_path = clip_instance_data["reader"]["path"] # Iterate through all clip files on the timeline for clip_number in range(self.files_model.rowCount()): clip_index = self.files_model.index(clip_number, 0) clip_name = clip_index.sibling(clip_number, 1).data() clip_path = os.path.join(clip_index.sibling(clip_number, 4).data(), clip_name) # Check if the timeline's clip file name matches the clip the user selected if (clip_path == clip_instance_path): # Generate the clip icon to show in the selection menu clip_instance_icon = clip_index.data(Qt.DecorationRole) clip_choices.append({"name": clip_instance_data["title"], "value": clip_instance_id, "selected": False, "icon": clip_instance_icon}) # Get the pixmap of the clip icon icon_size = 72 icon_pixmap = clip_instance_icon.pixmap(icon_size, icon_size) # Add tracked objects to the selection menu tracked_objects = [] for effect in clip_instance_data["effects"]: # Check if effect has a tracked object if effect.get("has_tracked_object"): # Instantiate the effect effect_instance = timeline_instance.GetClipEffect(effect["id"]) # Get the visible object's ids visible_objects_id = json.loads(effect_instance.GetVisibleObjects(frame_number))["visible_objects_id"] for object_id in visible_objects_id: # Get the Tracked Object properties object_properties = json.loads(timeline_instance.GetTrackedObjectValues(object_id, 0)) x1 = object_properties['x1'] y1 = object_properties['y1'] x2 = object_properties['x2'] y2 = object_properties['y2'] # Get the tracked object's icon from the clip's icon tracked_object_icon = icon_pixmap.copy(QRect(x1*icon_size, y1*icon_size, (x2-x1)*icon_size, (y2-y1)*icon_size)).scaled(icon_size, icon_size) tracked_objects.append({"name": str(object_id), "value": str(object_id), "selected": False, "icon": QIcon(tracked_object_icon)}) tracked_choices.append({"name": clip_instance_data["title"], "value": tracked_objects, "selected": False, "icon": clip_instance_icon}) self.choices.append({"name": _("Tracked Objects"), "value": tracked_choices, "selected": False, "icon": None}) self.choices.append({"name": _("Clips"), "value": clip_choices, "selected": False, "icon": None}) # Handle reader type values if self.property_type == "reader" and not self.choices: # Add all files file_choices = [] for i in range(self.files_model.rowCount()): idx = self.files_model.index(i, 0) if not idx.isValid(): continue icon = idx.data(Qt.DecorationRole) name = idx.sibling(i, 1).data() path = os.path.join(idx.sibling(i, 4).data(), name) # Append file choice file_choices.append({"name": name, "value": path, "selected": False, "icon": icon }) # Add root file choice self.choices.append({"name": _("Files"), "value": file_choices, "selected": False, icon: None}) # Add all transitions trans_choices = [] for i in range(self.transition_model.rowCount()): idx = self.transition_model.index(i, 0) if not idx.isValid(): continue icon = idx.data(Qt.DecorationRole) name = idx.sibling(i, 1).data() path = idx.sibling(i, 3).data() # Append transition choice trans_choices.append({"name": name, "value": path, "selected": False, "icon": icon }) # Add root transitions choice self.choices.append({"name": _("Transitions"), "value": trans_choices, "selected": False}) # Handle reader type values if property_name == "Track" and self.property_type == "int" and not self.choices: # Populate all display track names all_tracks = get_app().project.get("layers") display_count = len(all_tracks) for track in reversed(sorted(all_tracks, key=itemgetter('number'))): # Append track choice track_name = track.get("label") or _("Track %s") % display_count self.choices.append({"name": track_name, "value": track.get("number"), "selected": False, "icon": None}) display_count -= 1 return elif self.property_type == "font": # Get font from user current_font_name = cur_property[1].get("memo", "sans") current_font = QFont(current_font_name) font, ok = QFontDialog.getFont(current_font, caption=("Change Font")) # Update font if ok and font: fontinfo = QFontInfo(font) self.clip_properties_model.value_updated(self.selected_item, value=fontinfo.family()) # Define bezier presets bezier_presets = [ (0.250, 0.100, 0.250, 1.000, _("Ease (Default)")), (0.420, 0.000, 1.000, 1.000, _("Ease In")), (0.000, 0.000, 0.580, 1.000, _("Ease Out")), (0.420, 0.000, 0.580, 1.000, _("Ease In/Out")), (0.550, 0.085, 0.680, 0.530, _("Ease In (Quad)")), (0.550, 0.055, 0.675, 0.190, _("Ease In (Cubic)")), (0.895, 0.030, 0.685, 0.220, _("Ease In (Quart)")), (0.755, 0.050, 0.855, 0.060, _("Ease In (Quint)")), (0.470, 0.000, 0.745, 0.715, _("Ease In (Sine)")), (0.950, 0.050, 0.795, 0.035, _("Ease In (Expo)")), (0.600, 0.040, 0.980, 0.335, _("Ease In (Circ)")), (0.600, -0.280, 0.735, 0.045, _("Ease In (Back)")), (0.250, 0.460, 0.450, 0.940, _("Ease Out (Quad)")), (0.215, 0.610, 0.355, 1.000, _("Ease Out (Cubic)")), (0.165, 0.840, 0.440, 1.000, _("Ease Out (Quart)")), (0.230, 1.000, 0.320, 1.000, _("Ease Out (Quint)")), (0.390, 0.575, 0.565, 1.000, _("Ease Out (Sine)")), (0.190, 1.000, 0.220, 1.000, _("Ease Out (Expo)")), (0.075, 0.820, 0.165, 1.000, _("Ease Out (Circ)")), (0.175, 0.885, 0.320, 1.275, _("Ease Out (Back)")), (0.455, 0.030, 0.515, 0.955, _("Ease In/Out (Quad)")), (0.645, 0.045, 0.355, 1.000, _("Ease In/Out (Cubic)")), (0.770, 0.000, 0.175, 1.000, _("Ease In/Out (Quart)")), (0.860, 0.000, 0.070, 1.000, _("Ease In/Out (Quint)")), (0.445, 0.050, 0.550, 0.950, _("Ease In/Out (Sine)")), (1.000, 0.000, 0.000, 1.000, _("Ease In/Out (Expo)")), (0.785, 0.135, 0.150, 0.860, _("Ease In/Out (Circ)")), (0.680, -0.550, 0.265, 1.550, _("Ease In/Out (Back)")) ] # Add menu options for keyframes menu = QMenu(self) if self.property_type == "color": Color_Action = menu.addAction(_("Select a Color")) Color_Action.triggered.connect(functools.partial(self.Color_Picker_Triggered, cur_property)) menu.addSeparator() if points > 1: # Menu items only for multiple points Bezier_Menu = menu.addMenu(self.bezier_icon, _("Bezier")) for bezier_preset in bezier_presets: preset_action = Bezier_Menu.addAction(bezier_preset[4]) preset_action.triggered.connect(functools.partial( self.Bezier_Action_Triggered, bezier_preset)) Linear_Action = menu.addAction(self.linear_icon, _("Linear")) Linear_Action.triggered.connect(self.Linear_Action_Triggered) Constant_Action = menu.addAction(self.constant_icon, _("Constant")) Constant_Action.triggered.connect(self.Constant_Action_Triggered) menu.addSeparator() if points >= 1: # Menu items for one or more points Insert_Action = menu.addAction(_("Insert Keyframe")) Insert_Action.triggered.connect(self.Insert_Action_Triggered) Remove_Action = menu.addAction(_("Remove Keyframe")) Remove_Action.triggered.connect(self.Remove_Action_Triggered) menu.popup(event.globalPos()) # Menu for choices if not self.choices: return for choice in self.choices: if type(choice["value"]) != list: # Just add root choice item Choice_Action = menu.addAction(_(choice["name"])) Choice_Action.setData(choice["value"]) Choice_Action.triggered.connect(self.Choice_Action_Triggered) continue # Add sub-choice items (for nested choice lists) # Divide into smaller QMenus (since large lists cover the entire screen) # For example: Transitions -> 1 -> sub items SubMenu = None if choice["icon"] is not None: SubMenuRoot = menu.addMenu(choice["icon"], choice["name"]) else: SubMenuRoot = menu.addMenu(choice["name"]) SubMenuSize = 25 SubMenuNumber = 0 if len(choice["value"]) > SubMenuSize: SubMenu = SubMenuRoot.addMenu(str(SubMenuNumber)) else: SubMenu = SubMenuRoot for i, sub_choice in enumerate(choice["value"], 1): # Divide SubMenu if it's item is a list if type(sub_choice["value"]) == list: SubSubMenu = SubMenu.addMenu(sub_choice["icon"], sub_choice["name"]) for sub_sub_choice in sub_choice["value"]: Choice_Action = SubSubMenu.addAction( sub_sub_choice["icon"], sub_sub_choice["name"]) Choice_Action.setData(sub_sub_choice["value"]) Choice_Action.triggered.connect(self.Choice_Action_Triggered) else: if i % SubMenuSize == 0: SubMenuNumber += 1 SubMenu = SubMenuRoot.addMenu(str(SubMenuNumber)) Choice_Action = SubMenu.addAction( sub_choice["icon"], _(sub_choice["name"])) Choice_Action.setData(sub_choice["value"]) Choice_Action.triggered.connect(self.Choice_Action_Triggered) # Show choice menuk menu.popup(event.globalPos()) def Bezier_Action_Triggered(self, preset=[]): log.info("Bezier_Action_Triggered: %s" % str(preset)) if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated(self.selected_item, interpolation=0, interpolation_details=preset) else: # Update colors interpolation mode self.clip_properties_model.color_update(self.selected_item, QColor("#000"), interpolation=0, interpolation_details=preset) def Linear_Action_Triggered(self): log.info("Linear_Action_Triggered") if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated(self.selected_item, interpolation=1) else: # Update colors interpolation mode self.clip_properties_model.color_update(self.selected_item, QColor("#000"), interpolation=1, interpolation_details=[]) def Constant_Action_Triggered(self): log.info("Constant_Action_Triggered") if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated(self.selected_item, interpolation=2) else: # Update colors interpolation mode self.clip_properties_model.color_update(self.selected_item, QColor("#000"), interpolation=2, interpolation_details=[]) def Color_Picker_Triggered(self, cur_property): log.info("Color_Picker_Triggered") _ = get_app()._tr # Get current value of color red = cur_property[1]["red"]["value"] green = cur_property[1]["green"]["value"] blue = cur_property[1]["blue"]["value"] # Show color dialog currentColor = QColor(red, green, blue) log.debug("Launching ColorPicker for %s", currentColor.name()) ColorPicker( currentColor, parent=self, title=_("Select a Color"), callback=self.color_callback) def Insert_Action_Triggered(self): log.info("Insert_Action_Triggered") if self.selected_item: current_value = QLocale().system().toDouble(self.selected_item.text())[0] self.clip_properties_model.value_updated(self.selected_item, value=current_value) def Remove_Action_Triggered(self): log.info("Remove_Action_Triggered") self.clip_properties_model.remove_keyframe(self.selected_item) def Choice_Action_Triggered(self): log.info("Choice_Action_Triggered") choice_value = self.sender().data() # Update value of dropdown item self.clip_properties_model.value_updated(self.selected_item, value=choice_value) def refresh_menu(self): """ Ensure we update the menu when our source models change """ self.menu_reset = True def __init__(self, *args): # Invoke parent init QTableView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Create properties model self.clip_properties_model = PropertiesModel(self) # Get base models for files, transitions self.transition_model = self.win.transition_model.model self.files_model = self.win.files_model.model # Connect to update signals, so our menus stay current self.win.files_model.ModelRefreshed.connect(self.refresh_menu) self.win.transition_model.ModelRefreshed.connect(self.refresh_menu) self.menu_reset = False # Keep track of mouse press start position to determine when to start drag self.selected = [] self.selected_label = None self.selected_item = None self.new_value = None self.original_data = None self.lock_selection = False self.prev_row = None # Context menu icons self.bezier_icon = QIcon(QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.BEZIER))) self.linear_icon = QIcon(QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.LINEAR))) self.constant_icon = QIcon(QPixmap(os.path.join(info.IMAGES_PATH, "keyframe-%s.png" % openshot.CONSTANT))) # Setup header columns self.setModel(self.clip_properties_model.model) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) # Set delegate delegate = PropertyDelegate() self.setItemDelegateForColumn(1, delegate) self.previous_x = -1 # Get table header horizontal_header = self.horizontalHeader() horizontal_header.setSectionResizeMode(QHeaderView.Stretch) vertical_header = self.verticalHeader() vertical_header.setVisible(False) # Refresh view self.clip_properties_model.update_model() # Resize columns self.resizeColumnToContents(0) self.resizeColumnToContents(1) # Connect filter signals get_app().window.txtPropertyFilter.textChanged.connect(self.filter_changed) get_app().window.InsertKeyframe.connect(self.Insert_Action_Triggered) self.doubleClicked.connect(self.doubleClickedCB) self.loadProperties.connect(self.select_item) get_app().window.CaptionTextUpdated.connect(self.caption_text_updated)
class PropertiesTableView(QTableView): """ A Properties Table QWidget used on the main window """ def select_item(self, item_id, item_type): """ Update the selected item in the properties window """ # Get translation object _ = get_app()._tr # Set label if item_id: get_app().window.lblSelectedItem.setText(_("Selection: %s") % item_id) else: get_app().window.lblSelectedItem.setText(_("No Selection")) # Update item self.clip_properties_model.update_item(item_id, item_type) def select_frame(self, frame_number): """ Update the values of the selected clip, based on the current frame """ # Update item self.clip_properties_model.update_frame(frame_number) def filter_changed(self, value=None): """ Filter the list of properties """ # Update model self.clip_properties_model.update_model(value) def contextMenuEvent(self, pos): # Get data model and selection model = self.clip_properties_model.model selected = self.selectionModel().selectedIndexes() # Get the currently selected item selected_label = None selected_value = None for selection in selected: selected_row = model.itemFromIndex(selection).row() selected_label = model.item(selected_row, 0) selected_value = model.item(selected_row, 1) self.selected_item = selected_value # keep track of selected value column frame_number = self.clip_properties_model.frame_number # If item selected if selected_label and selected_value: # Get data from selected item property = selected_label.data() property_name = property[1]["name"] points = property[1]["points"] property_key = property[0] clip_id, item_type = selected_value.data() log.info("Context menu shown for %s (%s) for clip %s on frame %s" % (property_name, property_key, clip_id, frame_number)) log.info("Points: %s" % points) log.info("Property: %s" % str(property)) # Popup context menu (if keyframeable) if points > 1: menu = QMenu(self) Bezier_Action = menu.addAction("BEZIER") Bezier_Action.triggered.connect(self.Bezier_Action_Triggered) Linear_Action = menu.addAction("LINEAR") Linear_Action.triggered.connect(self.Linear_Action_Triggered) Constant_Action = menu.addAction("CONSTANT") Constant_Action.triggered.connect(self.Constant_Action_Triggered) menu.popup(QCursor.pos()) def Bezier_Action_Triggered(self, event): log.info("Bezier_Action_Triggered") self.clip_properties_model.value_updated(self.selected_item, 0) def Linear_Action_Triggered(self, event): log.info("Linear_Action_Triggered") self.clip_properties_model.value_updated(self.selected_item, 1) def Constant_Action_Triggered(self, event): log.info("Constant_Action_Triggered") self.clip_properties_model.value_updated(self.selected_item, 2) def __init__(self, *args): # Invoke parent init QTableView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.clip_properties_model = PropertiesModel(self) # Keep track of mouse press start position to determine when to start drag self.selected = [] self.selected_item = None # Setup header columns self.setModel(self.clip_properties_model.model) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) # Get table header horizontal_header = self.horizontalHeader() horizontal_header.setSectionResizeMode(QHeaderView.Stretch) vertical_header = self.verticalHeader() vertical_header.setVisible(False) # Refresh view self.clip_properties_model.update_model() # Resize columns self.resizeColumnToContents(0) self.resizeColumnToContents(1) # Connect filter signals get_app().window.txtPropertyFilter.textChanged.connect(self.filter_changed)
class PropertiesTableView(QTableView): """ A Properties Table QWidget used on the main window """ def double_click(self, model_index): """Double click handler for the property table""" # Get data model and selection model = self.clip_properties_model.model row = model_index.row() selected_label = model.item(row, 0) self.selected_item = model.item(row, 1) if selected_label: property = selected_label.data() property_type = property[1]["type"] if property_type == "color": # Get current value of color red = property[1]["red"]["value"] green = property[1]["green"]["value"] blue = property[1]["blue"]["value"] # Show color dialog currentColor = QColor(red, green, blue) newColor = QColorDialog.getColor(currentColor) # Set the new color keyframe self.clip_properties_model.color_update(self.selected_item, newColor) def select_item(self, item_id, item_type): """ Update the selected item in the properties window """ # Get translation object _ = get_app()._tr # Set label if item_id: get_app().window.lblSelectedItem.setText(_("Selection: %s") % item_id) else: get_app().window.lblSelectedItem.setText(_("No Selection")) # Update item self.clip_properties_model.update_item(item_id, item_type) def select_frame(self, frame_number): """ Update the values of the selected clip, based on the current frame """ # Update item self.clip_properties_model.update_frame(frame_number) def filter_changed(self, value=None): """ Filter the list of properties """ # Update model self.clip_properties_model.update_model(value) def contextMenuEvent(self, event): # Get data model and selection model = self.clip_properties_model.model row = self.indexAt(event.pos()).row() selected_label = model.item(row, 0) selected_value = model.item(row, 1) self.selected_item = selected_value frame_number = self.clip_properties_model.frame_number # Get translation object _ = get_app()._tr # If item selected if selected_label: # Get data from selected item property = selected_label.data() property_name = property[1]["name"] self.property_type = property[1]["type"] points = property[1]["points"] self.choices = property[1]["choices"] property_key = property[0] clip_id, item_type = selected_value.data() log.info("Context menu shown for %s (%s) for clip %s on frame %s" % ( property_name, property_key, clip_id, frame_number)) log.info("Points: %s" % points) log.info("Property: %s" % str(property)) # Add menu options for keyframes menu = QMenu(self) if points > 1: # Menu for more than 1 point Bezier_Action = menu.addAction(_("Bezier")) Bezier_Action.triggered.connect(self.Bezier_Action_Triggered) Linear_Action = menu.addAction(_("Linear")) Linear_Action.triggered.connect(self.Linear_Action_Triggered) Constant_Action = menu.addAction(_("Constant")) Constant_Action.triggered.connect(self.Constant_Action_Triggered) menu.addSeparator() Remove_Action = menu.addAction(_("Remove Keyframe")) Remove_Action.triggered.connect(self.Remove_Action_Triggered) menu.popup(QCursor.pos()) elif points == 1: # Menu for a single point Remove_Action = menu.addAction(_("Remove Keyframe")) Remove_Action.triggered.connect(self.Remove_Action_Triggered) menu.popup(QCursor.pos()) if self.choices: # Menu for choices for choice in self.choices: Choice_Action = menu.addAction(_(choice["name"])) Choice_Action.setData(choice["value"]) Choice_Action.triggered.connect(self.Choice_Action_Triggered) # Show choice menu menu.popup(QCursor.pos()) def Bezier_Action_Triggered(self, event): log.info("Bezier_Action_Triggered") if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated(self.selected_item, 0) else: # Update colors interpolation mode self.clip_properties_model.color_update(self.selected_item, QColor("#000"), 0) def Linear_Action_Triggered(self, event): log.info("Linear_Action_Triggered") if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated(self.selected_item, 1) else: # Update colors interpolation mode self.clip_properties_model.color_update(self.selected_item, QColor("#000"), 1) def Constant_Action_Triggered(self, event): log.info("Constant_Action_Triggered") if self.property_type != "color": # Update keyframe interpolation mode self.clip_properties_model.value_updated(self.selected_item, 2) else: # Update colors interpolation mode self.clip_properties_model.color_update(self.selected_item, QColor("#000"), 2) def Remove_Action_Triggered(self, event): log.info("Remove_Action_Triggered") self.clip_properties_model.remove_keyframe(self.selected_item) def Choice_Action_Triggered(self, event): log.info("Choice_Action_Triggered") choice_value = self.sender().data() # Update value of dropdown item self.clip_properties_model.value_updated(self.selected_item, value=choice_value) def __init__(self, *args): # Invoke parent init QTableView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.clip_properties_model = PropertiesModel(self) # Keep track of mouse press start position to determine when to start drag self.selected = [] self.selected_item = None # Setup header columns self.setModel(self.clip_properties_model.model) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) # Get table header horizontal_header = self.horizontalHeader() horizontal_header.setSectionResizeMode(QHeaderView.Stretch) vertical_header = self.verticalHeader() vertical_header.setVisible(False) # Refresh view self.clip_properties_model.update_model() # Resize columns self.resizeColumnToContents(0) self.resizeColumnToContents(1) # Connect filter signals get_app().window.txtPropertyFilter.textChanged.connect(self.filter_changed) self.doubleClicked.connect(self.double_click)