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 QTreeView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.transition_model = TransitionsModel() # Keep track of mouse press start position to determine when to start drag self.setAcceptDrops(True) self.setDragEnabled(True) self.setDropIndicatorShown(True) # Setup header columns self.setModel(self.transition_model.model) self.setIconSize(QSize(75, 62)) self.setIndentation(0) self.setSelectionBehavior(QTreeView.SelectRows) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setWordWrap(True) self.setStyleSheet('QTreeView::item { padding-top: 2px; }') # Refresh view self.refresh_view() # setup filter events app = get_app() app.window.transitionsFilter.textChanged.connect(self.filter_changed) app.window.actionTransitionsClear.triggered.connect(self.clear_filter)
def __init__(self, *args): # Invoke parent init QListView.__init__(self, *args) # Get a reference to the window object self.win = get_app().window # Get Model data self.transition_model = TransitionsModel() # Keep track of mouse press start position to determine when to start drag self.setAcceptDrops(True) self.setDragEnabled(True) self.setDropIndicatorShown(True) # Setup header columns self.setModel(self.transition_model.model) self.setIconSize(QSize(131, 108)) self.setViewMode(QListView.IconMode) self.setResizeMode(QListView.Adjust) self.setUniformItemSizes(True) self.setWordWrap(True) self.setStyleSheet('QListView::item { padding-top: 2px; }') # Refresh view self.refresh_view() # setup filter events app = get_app() app.window.transitionsFilter.textChanged.connect(self.filter_changed) app.window.actionTransitionsClear.triggered.connect(self.clear_filter)
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)
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"] 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)