diff --git a/.github/labeler.yml b/.github/labeler.yml index 4325617961..1a6e74371f 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -21,8 +21,5 @@ opentime: 'time calculations': - any: ['src/opentime/**'] -otioview: -- any: ['src/opentimelineview/**'] - python-bindings: - any: ['src/py-opentimelineio/**'] diff --git a/README.md b/README.md index 814f36bfe0..22932b1f6d 100644 --- a/README.md +++ b/README.md @@ -135,10 +135,6 @@ There are more code examples here: https://github.com/AcademySoftwareFoundation/ Also, looking through the unit tests is a great way to see what OTIO can do: https://github.com/AcademySoftwareFoundation/OpenTimelineIO/tree/main/tests -OTIO includes a viewer program as well (see the quickstart section for instructions on installing it): - -![OTIO View Screenshot](docs/_static/otioview.png) - Developing ---------- @@ -197,4 +193,3 @@ Contact For more information, please visit http://opentimeline.io/ or https://github.com/AcademySoftwareFoundation/OpenTimelineIO or join our discussion forum: https://lists.aswf.io/g/otio-discussion - diff --git a/docs/_static/otioview.png b/docs/_static/otioview.png deleted file mode 100644 index c8a3af792c..0000000000 Binary files a/docs/_static/otioview.png and /dev/null differ diff --git a/docs/tutorials/otio-filebundles.md b/docs/tutorials/otio-filebundles.md index 3e5a97d79f..21714dc8b2 100644 --- a/docs/tutorials/otio-filebundles.md +++ b/docs/tutorials/otio-filebundles.md @@ -60,9 +60,9 @@ The `media` directory contains all the media files that the `ExternalReference`s When a bundle is read from disk using the OpenTimelineIO Python API (using the adapters.read_from_* functions), only the `content.otio` file is read and parsed. -For example, to view the timeline (not the media) of an otioz file in `otioview`, you can run: +For example, to get some stats of the timeline (not the media) of an otioz file in `otiostat`, you can run: -`otioview something.otioz` +`otiostat something.otioz` Because this will _only_ read the `content.otio` from the bundle, it is usually a fast operation to run. None of the media is decoded or unzipped during this process. diff --git a/docs/tutorials/quickstart.md b/docs/tutorials/quickstart.md index 3d01011ab2..2183350a78 100644 --- a/docs/tutorials/quickstart.md +++ b/docs/tutorials/quickstart.md @@ -1,16 +1,7 @@ # Quickstart -This is for users who wish to get started using the "OTIOView" application to inspect the contents of editorial timelines. - **Note** This guide assumes that you are working inside a [virtualenv](https://virtualenv.pypa.io/en/latest/). -## Install Prerequisites - -OTIOView has an additional prerequisite to OTIO: - -- Try `python -m pip install PySide2` or `python -m pip install PySide6` -- If difficulties are encountered, please file an issue on OpenTimelineIO's github for assistance. - ## Install OTIO - `python -m pip install opentimelineio` @@ -24,11 +15,16 @@ A curated list of adapters for popular file formats like EDL, AAF, ALE, and FCP For more information, see the [Adapters](./adapters) section. -## Run OTIOView +## Timeline Viewers +OpenTimelineIO provides applications to view timelines in a graphical interface. -Once you have pip installed OpenTimelineIO, you should be able to run: +### Raven +[Raven](https://github.com/OpenTimelineIO/raven) is the preferred application and replaces OTIOView as the main +application for viewing timelines. -+ `otioview path/to/your/file.edl` +### OTIOView +[OTIOView](https://github.com/OpenTimelineIO/otioview) has been moved to its own repository so those who rely on it +still have access to it. # Developer Quickstart diff --git a/setup.py b/setup.py index eb73f5fde8..48b90690c1 100644 --- a/setup.py +++ b/setup.py @@ -237,7 +237,6 @@ def _append_version_info_to_init_scripts(build_lib): for module, parentdir in [ ("opentimelineio", "src/py-opentimelineio"), - ("opentimelineview", "src") ]: target_file = os.path.join(build_lib, module, "__init__.py") source_file = os.path.join( @@ -336,8 +335,7 @@ def run(self): }, packages=( - find_packages(where="src/py-opentimelineio") + # opentimelineio - find_packages(where="src") # opentimelineview + find_packages(where="src/py-opentimelineio") # opentimelineio ), ext_modules=[ @@ -352,7 +350,6 @@ def run(self): package_dir={ 'opentimelineio': 'src/py-opentimelineio/opentimelineio', - 'opentimelineview': 'src/opentimelineview', }, # Disallow 3.9.0 because of https://github.com/python/cpython/pull/22670 @@ -367,7 +364,6 @@ def run(self): 'otiopluginfo = opentimelineio.console.otiopluginfo:main', 'otiostat = opentimelineio.console.otiostat:main', 'otiotool = opentimelineio.console.otiotool:main', - 'otioview = opentimelineview.console:main', ( 'otioautogen_serialized_schema_docs = ' 'opentimelineio.console.autogen_serialized_datamodel:main' @@ -381,10 +377,6 @@ def run(self): 'coverage>=4.5', 'urllib3>=1.24.3' ], - 'view': [ - 'PySide2~=5.11; platform.machine=="x86_64"', - 'PySide6~=6.2; platform.machine=="aarch64"' - ] }, # because we need to open() the adapters manifest, we aren't zip-safe diff --git a/src/opentimelineview/__init__.py b/src/opentimelineview/__init__.py deleted file mode 100644 index 34e0506818..0000000000 --- a/src/opentimelineview/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright Contributors to the OpenTimelineIO project - -# flake8: noqa - -from . import ( - details_widget, - timeline_widget, -) diff --git a/src/opentimelineview/console.py b/src/opentimelineview/console.py deleted file mode 100755 index 2c4b2ee84a..0000000000 --- a/src/opentimelineview/console.py +++ /dev/null @@ -1,309 +0,0 @@ -#!/usr/bin/env python -# -# SPDX-License-Identifier: Apache-2.0 -# Copyright Contributors to the OpenTimelineIO project - -"""Simple otio viewer""" - -import os -import sys -import argparse -try: - from PySide6 import QtWidgets, QtGui - from PySide6.QtGui import QAction -except ImportError: - from PySide2 import QtWidgets, QtGui - from PySide2.QtWidgets import QAction - -import opentimelineio as otio -import opentimelineio.console as otio_console -import opentimelineview as otioViewWidget -from opentimelineview import settings - - -def _parsed_args(): - parser = argparse.ArgumentParser( - description=__doc__, - formatter_class=argparse.ArgumentDefaultsHelpFormatter - ) - - parser.add_argument( - 'input', - type=str, - help='path to input file', - ) - parser.add_argument( - '-a', - '--adapter-arg', - type=str, - default=[], - action='append', - help='Extra arguments to be passed to adapter in the form of ' - 'key=value. Values are strings, numbers or Python literals: True, ' - 'False, etc. Can be used multiple times: -a burrito="bar" -a taco=12.' - ) - parser.add_argument( - '-H', - '--hook-function-arg', - type=str, - default=[], - action='append', - help='Extra arguments to be passed to the hook functions in the form of ' - 'key=value. Values are strings, numbers or Python literals: True, ' - 'False, etc. Can be used multiple times: -H burrito="bar" -H taco=12.' - ) - parser.add_argument( - '-m', - '--media-linker', - type=str, - default="Default", - help=( - "Specify a media linker. 'Default' means use the " - "$OTIO_DEFAULT_MEDIA_LINKER if set, 'None' or '' means explicitly " - "disable the linker, and anything else is interpreted as the name" - " of the media linker to use." - ) - ) - parser.add_argument( - '-M', - '--media-linker-arg', - type=str, - default=[], - action='append', - help='Extra arguments to be passed to the media linker in the form of ' - 'key=value. Values are strings, numbers or Python literals: True, ' - 'False, etc. Can be used multiple times: -M burrito="bar" -M taco=12.' - ) - - return parser.parse_args() - - -class TimelineWidgetItem(QtWidgets.QListWidgetItem): - def __init__(self, timeline, *args, **kwargs): - super().__init__(*args, **kwargs) - self.timeline = timeline - - -class Main(QtWidgets.QMainWindow): - def __init__( - self, - adapter_argument_map, - hook_function_argument_map, - media_linker, - media_linker_argument_map, - *args, - **kwargs - ): - super().__init__(*args, **kwargs) - self.adapter_argument_map = adapter_argument_map or {} - self.media_linker = media_linker - self.media_linker_argument_map = media_linker_argument_map - self.hook_function_argument_map = hook_function_argument_map - - self._current_file = None - - # window options - self.setWindowTitle('OpenTimelineIO Viewer') - self.resize(1900, 1200) - - # widgets - self.tracks_widget = QtWidgets.QListWidget( - parent=self - ) - self.timeline_widget = otioViewWidget.timeline_widget.Timeline( - parent=self - ) - self.details_widget = otioViewWidget.details_widget.Details( - parent=self - ) - - root = QtWidgets.QWidget(parent=self) - layout = QtWidgets.QVBoxLayout(root) - - splitter = QtWidgets.QSplitter(parent=root) - splitter.addWidget(self.tracks_widget) - splitter.addWidget(self.timeline_widget) - splitter.addWidget(self.details_widget) - - splitter.setSizes([100, 700, 300]) - - layout.addWidget(splitter) - self.setCentralWidget(root) - - # menu - menubar = self.menuBar() - - file_load = QAction('Open...', menubar) - file_load.setShortcut(QtGui.QKeySequence.Open) - file_load.triggered.connect(self._file_load) - - exit_action = QAction('Exit', menubar) - exit_action.setShortcut(QtGui.QKeySequence.Quit) - exit_action.triggered.connect(self.close) - - file_menu = menubar.addMenu('File') - file_menu.addAction(file_load) - file_menu.addSeparator() - file_menu.addAction(exit_action) - - # navigation menu - navigation_menu = QtWidgets.QMenu() - navigation_menu.setTitle("Navigation") - menubar.addMenu(navigation_menu) - self._create_navigation_menu(navigation_menu) - - # signals - self.tracks_widget.itemSelectionChanged.connect( - self._change_track - ) - self.timeline_widget.selection_changed.connect( - self.details_widget.set_item - ) - - self.setStyleSheet(settings.VIEW_STYLESHEET) - - def _file_load(self): - start_folder = None - if self._current_file is not None: - start_folder = os.path.dirname(self._current_file) - - extensions = otio.adapters.suffixes_with_defined_adapters(read=True) - - extensions_string = ' '.join(f'*.{x}' for x in extensions) - - path = str( - QtWidgets.QFileDialog.getOpenFileName( - self, - 'Open OpenTimelineIO', - start_folder, - f'OTIO ({extensions_string})' - )[0] - ) - - if path: - self.load(path) - - def load(self, path): - self._current_file = path - self.setWindowTitle(f'OpenTimelineIO View: "{path}"') - self.details_widget.set_item(None) - self.tracks_widget.clear() - file_contents = otio.adapters.read_from_file( - path, - hook_function_argument_map=self.hook_function_argument_map, - media_linker_name=self.media_linker, - media_linker_argument_map=self.media_linker_argument_map, - **self.adapter_argument_map - ) - - if isinstance(file_contents, otio.schema.Timeline): - self.timeline_widget.set_timeline(file_contents) - self.tracks_widget.setVisible(False) - elif isinstance( - file_contents, - otio.schema.SerializableCollection - ): - for s in file_contents: - TimelineWidgetItem(s, s.name, self.tracks_widget) - self.tracks_widget.setVisible(True) - self.timeline_widget.set_timeline(None) - - def _change_track(self): - selection = self.tracks_widget.selectedItems() - if selection: - self.timeline_widget.set_timeline(selection[0].timeline) - - def _create_navigation_menu(self, navigation_menu): - - actions = otioViewWidget.timeline_widget.build_menu(navigation_menu) - - def __callback(): - self._navigation_filter_callback(actions) - navigation_menu.triggered[[QAction]].connect(__callback) - - def _navigation_filter_callback(self, filters): - nav_filter = 0 - filter_dict = otioViewWidget.timeline_widget.get_nav_menu_data() - for filter in filters: - if filter.isChecked(): - nav_filter += filter_dict[filter.text()].bitmask - - self.timeline_widget.navigationfilter_changed.emit(nav_filter) - - def center(self): - screens = QtWidgets.QApplication.screens() - if screens: - style = QtWidgets.QApplication.style() - title_bar_height = style.pixelMetric(QtWidgets.QStyle.PM_TitleBarHeight) - - screen = screens[0] - screen_geo = screen.availableGeometry() - screen_w = screen_geo.width() - screen_h = screen_geo.height() - title_bar_height - - frame_geo = self.frameGeometry() - frame_w = frame_geo.width() - frame_h = frame_geo.height() - - new_frame_w = screen_w if frame_w > screen_w else frame_w - new_frame_h = screen_h if frame_h > screen_h else frame_h - if new_frame_w != frame_w or new_frame_h != frame_h: - self.resize(new_frame_w, new_frame_h) - frame_geo = self.frameGeometry() - frame_w = frame_geo.width() - frame_h = frame_geo.height() - - center_point = screen_geo.center() - center_point.setY(center_point.y() - title_bar_height // 2) - frame_geo.moveCenter(center_point) - self.move(frame_geo.topLeft()) - - def show(self): - super().show() - self.timeline_widget.frame_all() - - -def main(): - args = _parsed_args() - - media_linker_name = otio_console.console_utils.media_linker_name( - args.media_linker - ) - - try: - read_adapter_arg_map = otio_console.console_utils.arg_list_to_map( - args.adapter_arg, - "adapter" - ) - media_linker_argument_map = otio_console.console_utils.arg_list_to_map( - args.media_linker_arg, - "media linker" - ) - hook_function_argument_map = otio_console.console_utils.arg_list_to_map( - args.hook_function_arg, - "hook function" - ) - except ValueError as exc: - sys.stderr.write("\n" + str(exc) + "\n") - sys.exit(1) - - application = QtWidgets.QApplication(sys.argv) - - window = Main( - read_adapter_arg_map, - hook_function_argument_map, - media_linker_name, - media_linker_argument_map - ) - - if args.input is not None: - window.load(args.input) - - window.center() - window.show() - window.raise_() - application.exec_() - - -if __name__ == '__main__': - main() diff --git a/src/opentimelineview/details_widget.py b/src/opentimelineview/details_widget.py deleted file mode 100644 index 62d5768bc5..0000000000 --- a/src/opentimelineview/details_widget.py +++ /dev/null @@ -1,120 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright Contributors to the OpenTimelineIO project - -try: - from PySide6 import QtWidgets, QtGui, QtCore -except ImportError: - from PySide2 import QtWidgets, QtGui, QtCore - -import opentimelineio as otio - - -class Details(QtWidgets.QTextEdit): - """Text widget with the JSON string of the specified OTIO object.""" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.setReadOnly(True) - self.font = QtGui.QFontDatabase.systemFont( - QtGui.QFontDatabase.FixedFont) - self.font.setPointSize(12) - self.setFont(self.font) - - self.backgroundColor = QtGui.QColor(33, 33, 33) - self.textColor = QtGui.QColor(180, 180, 180) - self.highlightColor = QtGui.QColor(255, 198, 109) - self.keywordColor = QtGui.QColor(204, 120, 50) - - self.palette = QtGui.QPalette() - self.palette.setColor(QtGui.QPalette.Base, self.backgroundColor) - self.palette.setColor(QtGui.QPalette.Text, self.textColor) - self.palette.setColor(QtGui.QPalette.BrightText, self.highlightColor) - self.palette.setColor(QtGui.QPalette.Link, self.keywordColor) - self.setPalette(self.palette) - - self.highlighter = OTIOSyntaxHighlighter(self.palette, self.document()) - - def set_item(self, item): - if item is None: - self.setPlainText('') - else: - s = otio.adapters.write_to_string(item, 'otio_json') - self.setPlainText(s) - - -class OTIOSyntaxHighlighter(QtGui.QSyntaxHighlighter): - def __init__(self, palette, parent=None): - super().__init__(parent) - - self.punctuation_format = QtGui.QTextCharFormat() - self.punctuation_format.setForeground(palette.link()) - self.punctuation_format.setFontWeight(QtGui.QFont.Bold) - - self.key_format = QtGui.QTextCharFormat() - # self.key_format.setFontItalic(True) - - self.literal_format = QtGui.QTextCharFormat() - self.literal_format.setForeground(palette.brightText()) - self.literal_format.setFontWeight(QtGui.QFont.Bold) - - self.value_format = QtGui.QTextCharFormat() - self.value_format.setForeground(palette.brightText()) - self.value_format.setFontWeight(QtGui.QFont.Bold) - - self.schema_format = QtGui.QTextCharFormat() - self.schema_format.setForeground(QtGui.QColor(161, 194, 97)) - self.schema_format.setFontWeight(QtGui.QFont.Bold) - - def highlightBlock(self, text): - expression = QtCore.QRegularExpression("(\\{|\\}|\\[|\\]|\\:|\\,)") - match = expression.match(text) - index = match.capturedStart() - while index >= 0: - length = match.capturedLength() - self.setFormat(index, length, self.punctuation_format) - match = expression.match(text, index + length) - index = match.capturedStart() - - text.replace("\\\"", " ") - - expression = QtCore.QRegularExpression( - "\".*\" *\\:", - QtCore.QRegularExpression.InvertedGreedinessOption) - match = expression.match(text) - index = match.capturedStart() - while index >= 0: - length = match.capturedLength() - self.setFormat(index, length - 1, self.key_format) - match = expression.match(text, index + length) - index = match.capturedStart() - - expression = QtCore.QRegularExpression( - "\\: *\".*\"", - QtCore.QRegularExpression.InvertedGreedinessOption) - match = expression.match(text) - index = match.capturedStart() - while index >= 0: - length = match.capturedLength() - firstQuoteIndex = text.index('"', index) - valueLength = length - (firstQuoteIndex - index) - 2 - self.setFormat(firstQuoteIndex + 1, valueLength, self.value_format) - match = expression.match(text, index + length) - index = match.capturedStart() - - expression = QtCore.QRegularExpression(r"\\: (null|true|false|[0-9\.]+)") - match = expression.match(text) - index = match.capturedStart() - while index >= 0: - length = match.capturedLength() - self.setFormat(index, length, self.literal_format) - match = expression.match(text, index + length) - index = match.capturedStart() - - expression = QtCore.QRegularExpression(r"\"OTIO_SCHEMA\"\s*:\s*\".*\"") - match = expression.match(text) - index = match.capturedStart() - while index >= 0: - length = match.capturedLength() - self.setFormat(index, length, self.schema_format) - match = expression.match(text, index + length) - index = match.capturedStart() diff --git a/src/opentimelineview/ruler_widget.py b/src/opentimelineview/ruler_widget.py deleted file mode 100644 index 18c8ece54d..0000000000 --- a/src/opentimelineview/ruler_widget.py +++ /dev/null @@ -1,277 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright Contributors to the OpenTimelineIO project - -try: - from PySide6 import QtGui, QtCore, QtWidgets -except ImportError: - from PySide2 import QtGui, QtCore, QtWidgets -import collections -import math -from . import track_widgets - -RULER_SIZE = 10 - - -class FrameNumber(QtWidgets.QGraphicsRectItem): - - def __init__(self, text, position, *args, **kwargs): - super().__init__(*args, **kwargs) - self.frameNumber = QtWidgets.QGraphicsSimpleTextItem(self) - self.frameNumber.setText("%s" % text) - self.setBrush( - QtGui.QBrush(QtGui.QColor(5, 55, 0, 255)) - ) - self.setPen( - QtCore.Qt.NoPen - ) - self.frameNumber.setBrush( - QtGui.QBrush(QtGui.QColor(25, 255, 10, 255)) - ) - # if position < 0 then the frameNumber will appear on the left side - # of the ruler - self.position = position - - def setText(self, txt, highlight=False): - if txt: - self.show() - self.frameNumber.setText("%s" % txt) - rect = self.frameNumber.boundingRect() - self.setRect(self.frameNumber.boundingRect()) - if highlight: - # paint with a different color when on - # the first frame of a clip - self.setBrush( - QtGui.QBrush(QtGui.QColor(55, 5, 0, 120)) - ) - self.frameNumber.setBrush( - QtGui.QBrush(QtGui.QColor(255, 250, 250, 255)) - ) - else: - self.frameNumber.setBrush( - QtGui.QBrush(QtGui.QColor(25, 255, 10, 255)) - ) - self.setBrush( - QtGui.QBrush(QtGui.QColor(5, 55, 0, 120)) - ) - if self.position < 0: - self.setX(-rect.width() - 2) - else: - self.setX(2) - else: - self.hide() - - -class Ruler(QtWidgets.QGraphicsPolygonItem): - # @TODO pending on Global Space implementation - # ("external_space", "External Space"), - time_space = collections.OrderedDict([("media_space", "Media Space"), - ("trimmed_space", "Trimmed Space")]) - - time_space_default = "media_space" - - def __init__(self, height, composition, *args, **kwargs): - - poly = QtGui.QPolygonF() - poly.append(QtCore.QPointF(0.5 * RULER_SIZE, -0.5 * RULER_SIZE)) - poly.append(QtCore.QPointF(0.5 * RULER_SIZE, 0.5 * RULER_SIZE)) - poly.append(QtCore.QPointF(0, RULER_SIZE)) - poly.append(QtCore.QPointF(0, height)) - poly.append(QtCore.QPointF(0, RULER_SIZE)) - poly.append(QtCore.QPointF(-0.5 * RULER_SIZE, 0.5 * RULER_SIZE)) - poly.append(QtCore.QPointF(-0.5 * RULER_SIZE, -0.5 * RULER_SIZE)) - super().__init__(poly, *args, **kwargs) - - # to retrieve tracks and its children - self.composition = composition - self.setBrush( - QtGui.QBrush(QtGui.QColor(50, 255, 20, 255)) - ) - - self.setAcceptHoverEvents(True) - self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True) - self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable, True) - - self.labels = list() - self._time_space = self.time_space_default - self._bounded_data = collections.namedtuple("bounded_data", - ["f", - "is_bounded", - "is_tail", - "is_head"]) - self.init() - - def contextMenuEvent(self, event): - menu = QtWidgets.QMenu() - for name, label in self.time_space.items(): - def __callback(): - self.set_time_space_callback(name) - menu.addAction(label, __callback) - menu.exec_(event.screenPos()) - - super().contextMenuEvent(event) - - def set_time_space_callback(self, time_space): - self._time_space = time_space - self.update_frame() - - def mouseMoveEvent(self, mouse_event): - pos = self.mapToScene(mouse_event.pos()) - self.setPos(QtCore.QPointF(max(pos.x() - track_widgets.CURRENT_ZOOM_LEVEL * - track_widgets.TRACK_NAME_WIDGET_WIDTH, 0), - track_widgets.TIME_SLIDER_HEIGHT - - track_widgets.MARKER_SIZE)) - self.update_frame() - - super().mouseMoveEvent(mouse_event) - - def mouseReleaseEvent(self, mouse_event): - self.setSelected(False) - super().mouseReleaseEvent(mouse_event) - - def hoverEnterEvent(self, event): - self.setSelected(True) - super().hoverEnterEvent(event) - - def hoverLeaveEvent(self, event): - self.setSelected(False) - super().hoverLeaveEvent(event) - - def init(self): - for track_item in self.composition.items(): - if isinstance(track_item, track_widgets.Track): - frameNumber_tail = FrameNumber("", position=-1) - frameNumber_tail.setParentItem(self) - frameNumber_head = FrameNumber("", position=1) - frameNumber_head.setParentItem(self) - frameNumber_tail.setY(track_item.pos().y()) - frameNumber_head.setY(track_item.pos().y()) - items = list() - for item in track_item.childItems(): - if not (isinstance(item, track_widgets.ClipItem) or - isinstance(item, track_widgets.NestedItem)): - continue - items.append(item) - items.sort(key=lambda x: x.x()) - self.labels.append([items, frameNumber_tail, frameNumber_head]) - - self.update_frame() - - def setParentItem(self, timeSlider): - ''' - subclass in order to add the rule to the timeSlider item. - ''' - timeSlider.add_ruler(self) - super().setParentItem(timeSlider) - - def map_to_time_space(self, item): - ''' - Temporary implementation. - @TODO: modify this function once Time Coordinates Spaces - feature is implemented. - ''' - - is_bounded = False - is_head = False - is_tail = False - f = "-?-" - ratio = -1.0 - width = float(item.rect().width()) - - if width > 0.0: - ratio = (self.x() - item.x() + - track_widgets.CURRENT_ZOOM_LEVEL * - track_widgets.TRACK_NAME_WIDGET_WIDTH) / width - else: - print(f"Warning: zero width item: {item}.") - - # The 'if' condition should be : ratio < 0 or ration >= 1 - # However, we are cheating in order to display the last frame of - # a clip (tail) and the first frame of the following clip (head) - # when we know that we cannot be on 2 frames at the same time - if ratio < 0 or ratio > 1: - return self._bounded_data(f, is_bounded, is_tail, is_head) - - is_bounded = True - trimmed_range = item.item.trimmed_range() - duration = trimmed_range.duration.value - start_time = trimmed_range.start_time.value - - f_nb = ratio * duration + start_time - if self._time_space == "trimmed_space": - f = ratio * duration - elif self._time_space == "media_space": - f = duration * ratio + start_time - - f = math.floor(f) - f_nb = math.floor(f_nb) - if f_nb == start_time: - is_head = True - - last_item_frame = start_time + duration - 1 - if f_nb >= (last_item_frame): - is_tail = True - # As we cheated in the first place by saying that the ruler - # was within the boundary of this item when it is not... - if ratio == 1.0: - f -= 1 - - return self._bounded_data(f, is_bounded, is_tail, is_head) - - def update_frame(self): - - for tw, frameNumber_tail, frameNumber_head in self.labels: - f_tail = "" - f_head = "" - highlight_head = False - for item_widget in tw: - bounded_data = self.map_to_time_space(item_widget) - # check if ruler is within an item boundary - # in other word, start_frame <= ruler < end_frame - if not bounded_data.is_bounded: - continue - f = int(round(bounded_data.f)) - if bounded_data.is_head: - highlight_head = True - if bounded_data.is_tail: - f_tail = f - else: - f_head = f - break - frameNumber_head.setText("%s" % f_head, highlight_head) - frameNumber_tail.setText("%s" % f_tail) - - def snap(self, direction, scene_width): - ruler_pos = self.x() + direction - closest_left = scene_width - ruler_pos - closest_right = 0 - ruler_pos - move_to_item = None - - for tw, frameNumber_tail, frameNumber_head in self.labels: - for item in tw: - d = item.x() - ruler_pos - if direction > 0 and d > 0 and d < closest_left: - closest_left = d - move_to_item = item - elif direction < 0 and d < 0 and d > closest_right: - closest_right = d - move_to_item = item - - if move_to_item: - self.setX(move_to_item.x()) - self.update_frame() - - def paint(self, *args, **kwargs): - new_args = [args[0], - QtWidgets.QStyleOptionGraphicsItem()] + list(args[2:]) - super().paint(*new_args, **kwargs) - - def itemChange(self, change, value): - if change == QtWidgets.QGraphicsItem.ItemSelectedHasChanged: - self.setPen( - QtGui.QColor(0, 255, 0, 255) if self.isSelected() - else QtGui.QColor(0, 0, 0, 255) - ) - return super().itemChange(change, value) - - def counteract_zoom(self, zoom_level=1.0): - self.setTransform(QtGui.QTransform.fromScale(zoom_level, 1.0)) diff --git a/src/opentimelineview/settings.py b/src/opentimelineview/settings.py deleted file mode 100644 index 3df8986011..0000000000 --- a/src/opentimelineview/settings.py +++ /dev/null @@ -1,78 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright Contributors to the OpenTimelineIO project - -VIEW_STYLESHEET = """ -QMainWindow { - background-color: rgb(27, 27, 27); -} - -QScrollBar:horizontal { - background: rgb(21, 21, 21); - height: 15px; - margin: 0px 20px 0 20px; -} - -QScrollBar::handle:horizontal { - background: rgb(255, 83, 112); - min-width: 20px; -} - -QScrollBar::add-line:horizontal { - background: rgb(33, 33, 33); - width: 20px; - subcontrol-position: right; - subcontrol-origin: margin; -} - -QScrollBar::sub-line:horizontal { - background: rgb(33, 33, 33); - width: 20px; - subcontrol-position: left; - subcontrol-origin: margin; -} - -QScrollBar:left-arrow:horizontal, QScrollBar::right-arrow:horizontal { - width: 3px; - height: 3px; - background: transparent; -} - -QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { - background: none; -} - -QScrollBar:vertical { - background: rgb(21, 21, 21); - width: 15px; - margin: 22px 0 22px 0; -} - -QScrollBar::handle:vertical { - background: rgb(255, 83, 112); - min-height: 20px; -} - -QScrollBar::add-line:vertical { - background: rgb(33, 33, 33); - height: 20px; - subcontrol-position: bottom; - subcontrol-origin: margin; -} - -QScrollBar::sub-line:vertical { - background: rgb(33, 33, 33); - height: 20px; - subcontrol-position: top; - subcontrol-origin: margin; -} - -QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { - width: 3px; - height: 3px; - background: transparent; -} - -QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { - background: none; -} -""" diff --git a/src/opentimelineview/timeline_widget.py b/src/opentimelineview/timeline_widget.py deleted file mode 100644 index cfe2daff37..0000000000 --- a/src/opentimelineview/timeline_widget.py +++ /dev/null @@ -1,822 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright Contributors to the OpenTimelineIO project - -try: - from PySide6 import QtGui, QtCore, QtWidgets -except ImportError: - from PySide2 import QtGui, QtCore, QtWidgets -from collections import OrderedDict, namedtuple - -import opentimelineio as otio -from . import ( - ruler_widget, - track_widgets, -) - - -KEY_SYM = { - QtCore.Qt.Key_Left: QtCore.Qt.Key_Right, - QtCore.Qt.Key_Right: QtCore.Qt.Key_Left, - QtCore.Qt.Key_Up: QtCore.Qt.Key_Down, - QtCore.Qt.Key_Down: QtCore.Qt.Key_Up -} - - -def get_nav_menu_data(): - _nav_menu = namedtuple( - "nav_menu", - ["bitmask", "otioItem", "default", "exclusive"] - ) - - filter_dict = OrderedDict( - [ - ( - "Clip", - _nav_menu(0b00000001, track_widgets.ClipItem, True, False) - ), - ( - "Nested Clip", - _nav_menu(0b00000010, track_widgets.NestedItem, True, False) - ), - ( - "Gap", - _nav_menu(0b00000100, track_widgets.GapItem, True, False) - ), - ( - "Transition", - _nav_menu(0b00001000, track_widgets.TransitionItem, True, False) - ), - ( - "Only with Marker", - _nav_menu(0b00010000, track_widgets.Marker, False, True) - ), - ( - "Only with Effect", - _nav_menu(0b00100000, track_widgets.EffectItem, False, True) - ), - # ("All", nav_menu(0b01000000, "None", False)) @TODO - ] - ) - return filter_dict - - -def get_filters(filter_dict, bitmask): - filters = list() - for item in filter_dict.values(): - if bitmask & item.bitmask: - filters.append(item) - return filters - - -def build_menu(navigation_menu): - filter_dict = get_nav_menu_data() - actions = list() - for label, nav in filter_dict.items(): - if label == "Only with Marker": - navigation_menu.addSeparator() - - action = navigation_menu.addAction(label) - action.setCheckable(True) - action.setChecked(nav.default) - actions.append(action) - - return actions - - -def group_filters(bitmask): - inclusive_filters = list() - exclusive_filters = list() - filter_dict = get_nav_menu_data() - for item in get_filters(filter_dict, bitmask): - if item.exclusive: - exclusive_filters.append(item) - else: - inclusive_filters.append(item) - return inclusive_filters, exclusive_filters - - -class CompositionWidget(QtWidgets.QGraphicsScene): - - def __init__(self, composition, *args, **kwargs): - super().__init__(*args, **kwargs) - self.composition = composition - self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(33, 33, 33))) - - self._adjust_scene_size() - self._add_tracks() - self._add_time_slider() - self._add_markers() - self._ruler = self._add_ruler() - - self._data_cache = self._cache_tracks() - - def track_name_width(self): - # Choose track name item from the longest track, since that track will - # define the composition's scene width. - width = track_widgets.TRACK_NAME_WIDGET_WIDTH - longest_track = None - max_end_time = 0 - - for item in self.items(): - if isinstance(item, track_widgets.Track): - track_range = item.track.trimmed_range_in_parent() - end_time = track_range.end_time_inclusive().to_seconds() - if end_time > max_end_time: - max_end_time = end_time - longest_track = item - - if longest_track is not None: - if longest_track.track_name_item is not None: - width = longest_track.track_name_item.rect().width() - - return width - - def _adjust_scene_size(self, zoom_level=1.0): - scene_range = self.composition.trimmed_range() - - start_time = otio.opentime.to_seconds(scene_range.start_time) - duration = otio.opentime.to_seconds(scene_range.end_time_exclusive()) - - if isinstance(self.composition, otio.schema.Stack): - # non audio tracks are sorted into one area - has_video_tracks = any( - t.kind != otio.schema.TrackKind.Audio - for t in self.composition - ) - has_audio_tracks = any( - t.kind == otio.schema.TrackKind.Audio - for t in self.composition - ) - elif isinstance(self.composition, otio.schema.Track): - has_video_tracks = ( - self.composition.kind != otio.schema.TrackKind.Audio - ) - has_audio_tracks = ( - self.composition.kind == otio.schema.TrackKind.Audio - ) - else: - raise otio.exceptions.NotSupportedError( - "Error: file includes composition '{}', of type '{}'," - " not supported by opentimeview. Only supports children of" - " otio.schema.Stack and otio.schema.Track".format( - self.composition, - type(self.composition) - ) - ) - - height = ( - track_widgets.TIME_SLIDER_HEIGHT + - ( - int(has_video_tracks and has_audio_tracks) * - track_widgets.MEDIA_TYPE_SEPARATOR_HEIGHT - ) + - len(self.composition) * track_widgets.TRACK_HEIGHT - ) - - self.setSceneRect( - start_time * track_widgets.TIME_MULTIPLIER, - 0, - duration * track_widgets.TIME_MULTIPLIER + - self.track_name_width() * zoom_level, - height - ) - - def counteract_zoom(self, zoom_level=1.0): - # some items we do want to keep the same visual size. So we need to - # inverse the effect of the zoom - items_to_scale = [ - i for i in self.items() - if (isinstance(i, (track_widgets.BaseItem, track_widgets.Marker, - ruler_widget.Ruler, track_widgets.TimeSlider))) - ] - - for item in items_to_scale: - item.counteract_zoom(zoom_level) - - self._adjust_scene_size(zoom_level) - - def _add_time_slider(self): - scene_rect = self.sceneRect() - scene_rect.setWidth(scene_rect.width() * 10) - scene_rect.setHeight(track_widgets.TIME_SLIDER_HEIGHT) - self._time_slider = track_widgets.TimeSlider(scene_rect) - self.addItem(self._time_slider) - # Make sure that the ruler is on top of the selected Track items - self._time_slider.setZValue(float('inf')) - - def _add_track(self, track, y_pos): - scene_rect = self.sceneRect() - rect = QtCore.QRectF(0, 0, scene_rect.width() * 10, - track_widgets.TRACK_HEIGHT) - new_track = track_widgets.Track(track, rect) - self.addItem(new_track) - new_track.setPos(scene_rect.x(), y_pos) - - def _add_tracks(self): - video_tracks_top = track_widgets.TIME_SLIDER_HEIGHT - audio_tracks_top = track_widgets.TIME_SLIDER_HEIGHT - - video_tracks = [] - audio_tracks = [] - other_tracks = [] - - if isinstance(self.composition, otio.schema.Stack): - video_tracks = [ - t for t in self.composition - if t.kind == otio.schema.TrackKind.Video - ] - audio_tracks = [ - t for t in self.composition - if t.kind == otio.schema.TrackKind.Audio - ] - video_tracks.reverse() - - other_tracks = [ - t for t in self.composition - if ( - t.kind not in ( - otio.schema.TrackKind.Video, - otio.schema.TrackKind.Audio - ) - ) - ] - else: - if self.composition.kind == otio.schema.TrackKind.Video: - video_tracks = [self.composition] - elif self.composition.kind == otio.schema.TrackKind.Audio: - audio_tracks = [self.composition] - else: - other_tracks = [self.composition] - - if other_tracks: - for t in other_tracks: - print( - "Warning: track named '{}' has nonstandard track type:" - " '{}'".format(t.name, t.kind) - ) - - video_tracks.extend(other_tracks) - - video_tracks_top = track_widgets.TIME_SLIDER_HEIGHT - audio_tracks_top = ( - track_widgets.TIME_SLIDER_HEIGHT + - len(video_tracks) * track_widgets.TRACK_HEIGHT + - int( - bool(video_tracks) and bool(audio_tracks) - ) * track_widgets.MEDIA_TYPE_SEPARATOR_HEIGHT - ) - - for i, track in enumerate(audio_tracks): - self._add_track(track, audio_tracks_top + i * - track_widgets.TRACK_HEIGHT) - - for i, track in enumerate(video_tracks): - self._add_track(track, video_tracks_top + i * - track_widgets.TRACK_HEIGHT) - - def _add_markers(self): - for m in self.composition.markers: - marker = track_widgets.Marker(m, None) - marker.setX( - otio.opentime.to_seconds(m.marked_range.start_time) * - track_widgets.TIME_MULTIPLIER - ) - marker.setY(track_widgets.TIME_SLIDER_HEIGHT - - track_widgets.MARKER_SIZE) - marker.setParentItem(self._time_slider) - marker.counteract_zoom() - - def _add_ruler(self): - scene_rect = self.sceneRect() - ruler = ruler_widget.Ruler(scene_rect.height(), composition=self) - ruler.setParentItem(self._time_slider) - ruler.setX(scene_rect.width() / 2) - ruler.setY(track_widgets.TIME_SLIDER_HEIGHT - - track_widgets.MARKER_SIZE) - - ruler.counteract_zoom() - return ruler - - def get_ruler(self): - return self._ruler - - def get_next_item(self, item, key): - otio_item = item.item - next_item = None - if key in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left]: - head, tail = otio_item.parent().neighbors_of(otio_item) - next_item = head if key == QtCore.Qt.Key_Left else tail - elif key in [QtCore.Qt.Key_Up, QtCore.Qt.Key_Down]: - track = item.parentItem() - if self._data_cache[track][key]: - next_track = self._data_cache[track][key] - else: - # No more track in that direction. - return item - at_time = otio_item.trimmed_range_in_parent().start_time - - # If at_time is out of track_range, then get the latest item - # in the track. - at_time = min(at_time, - self._data_cache[next_track]["end_time_inclusive"]) - - next_item = next_track.track.child_at_time(at_time) - - # need the widget, not the otio instance. - if next_item: - next_item = self._data_cache["map_to_widget"][next_item] - - return next_item - - def _get_closest_item(self, item, start_time, filters): - next_item = item - tail = self.get_next_item_filters(item, QtCore.Qt.Key_Right, filters) - head = self.get_next_item_filters(item, QtCore.Qt.Key_Left, filters) - - # 3 cases - # 1. tail and head are both valid => get the closest one - if not tail == item and not head == item: - tail_start_time = tail.item.trimmed_range_in_parent().start_time - head_end_time = head.item.trimmed_range_in_parent().\ - end_time_inclusive() - - time_to_tail = tail_start_time - start_time - time_to_head = start_time - head_end_time - - next_item = tail if time_to_tail < time_to_head else head - - # 2. only tail is valid - elif not tail == item and head == item: - next_item = tail - - # 3. only head is valid - elif tail == item and not head == item: - next_item = head - - return next_item - - def get_next_item_filters(self, item, key, filters): - original_item = item - next_item = None - while not match_filters(next_item, filters): - next_item = self.get_next_item(item, key) - - if key in [QtCore.Qt.Key_Up, QtCore.Qt.Key_Down] and \ - not match_filters(next_item, filters): - start_time = item.item.trimmed_range_in_parent().start_time - next_item = self._get_closest_item(next_item, start_time, - filters) - - if next_item is None or next_item == item: - next_item = original_item - break - - item = next_item - - return next_item - - def _cache_tracks(self): - ''' - Create a doubly linked list to navigate from track to track: - track->get_next_up & track->get_next_up - "map_to_widget" : Create a map to retrieve the pyside widget from - the otio item - ''' - data_cache = dict() - tracks = list() - data_cache["map_to_widget"] = dict() - for track_item in self.items(): - if not isinstance(track_item, track_widgets.Track): - continue - tracks.append(track_item) - track_range = track_item.track.available_range() - data_cache[track_item] = {QtCore.Qt.Key_Up: None, - QtCore.Qt.Key_Down: None, - "end_time_inclusive": - track_range.end_time_inclusive() - } - - for item in track_item.childItems(): - data_cache["map_to_widget"][item.item] = item - - tracks.sort(key=lambda y: y.pos().y()) - index_last_track = len(tracks) - 1 - for i, track_item in enumerate(tracks): - data_cache[track_item][QtCore.Qt.Key_Up] = \ - tracks[i - 1] if i > 0 else None - data_cache[track_item][QtCore.Qt.Key_Down] = \ - tracks[i + 1] if i < index_last_track else None - - return data_cache - - -def match_filters(item, filters=None): - if not item: - return None - if filters is None: - return item - - otio_children = list() - if isinstance(item, track_widgets.BaseItem): - otio_children = item.get_otio_sub_items() - - for filter in filters.exclusive: - excl_item = [i for i in otio_children - if isinstance(i, filter.otioItem)] - if not excl_item: - return None - - for filter in filters.inclusive: - if isinstance(item, filter.otioItem): - return item - - return None - - -class CompositionView(QtWidgets.QGraphicsView): - - open_stack = QtCore.Signal(otio.schema.Stack) - selection_changed = QtCore.Signal(otio.core.SerializableObject) - - def __init__(self, stack, *args, **kwargs): - super().__init__(*args, **kwargs) - self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse) - self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse) - self.setScene(CompositionWidget(stack, parent=self)) - self.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) - self.setStyleSheet('border: 0px;') - self.scene().selectionChanged.connect(self.parse_selection_change) - self._navigation_filter = None - self._last_item_cache = {"key": None, "item": None, - "previous_item": None} - - def parse_selection_change(self): - selection = self.scene().selectedItems() - if not selection: - return - # Exclude ruler from selection - for item in selection: - if isinstance(item, ruler_widget.Ruler): - continue - self.selection_changed.emit(item.item) - break - - def mousePressEvent(self, mouse_event): - modifiers = QtWidgets.QApplication.keyboardModifiers() - self.setDragMode( - QtWidgets.QGraphicsView.ScrollHandDrag - if modifiers == QtCore.Qt.AltModifier - else QtWidgets.QGraphicsView.NoDrag - ) - self.setInteractive(not modifiers == QtCore.Qt.AltModifier) - - super().mousePressEvent(mouse_event) - - def mouseReleaseEvent(self, mouse_event): - super().mouseReleaseEvent(mouse_event) - self.setDragMode(QtWidgets.QGraphicsView.NoDrag) - - def wheelEvent(self, event): - try: - # PySide6: - # https://doc.qt.io/qtforpython/PySide6/QtGui/QWheelEvent.html - delta_point = event.angleDelta() - delta = delta_point.y() - - except AttributeError: - # PySide2: - # https://doc.qt.io/qtforpython-5/PySide2/QtGui/QWheelEvent.html - delta = event.delta() - - scale_by = 1.0 + float(delta) / 1000 - self.scale(scale_by, 1) - zoom_level = 1.0 / self.transform().m11() - track_widgets.CURRENT_ZOOM_LEVEL = zoom_level - self.scene().counteract_zoom(zoom_level) - - def _get_first_item(self): - newXpos = 0 - newYpos = track_widgets.TIME_SLIDER_HEIGHT - - newPosition = QtCore.QPointF(newXpos, newYpos) - - return self.scene().itemAt(newPosition, QtGui.QTransform()) - - def _get_left_item(self, curSelectedItem): - curItemXpos = curSelectedItem.pos().x() - - if curSelectedItem.parentItem(): - curTrackYpos = curSelectedItem.parentItem().pos().y() - - newXpos = curItemXpos - 1 - newYpos = curTrackYpos - - if newXpos < 0: - newXpos = 0 - else: - newXpos = curItemXpos - newYpos = curSelectedItem.y() - - newPosition = QtCore.QPointF(newXpos, newYpos) - - return self.scene().itemAt(newPosition, QtGui.QTransform()) - - def _get_right_item(self, curSelectedItem): - curItemXpos = curSelectedItem.pos().x() - - if curSelectedItem.parentItem(): - curTrackYpos = curSelectedItem.parentItem().pos().y() - - newXpos = curItemXpos + curSelectedItem.rect().width() - newYpos = curTrackYpos - else: - newXpos = curItemXpos - newYpos = curSelectedItem.y() - - newPosition = QtCore.QPointF(newXpos, newYpos) - - return self.scene().itemAt(newPosition, QtGui.QTransform()) - - def _get_up_item(self, curSelectedItem): - curItemXpos = curSelectedItem.pos().x() - - if curSelectedItem.parentItem(): - curTrackYpos = curSelectedItem.parentItem().pos().y() - - newXpos = curItemXpos - newYpos = curTrackYpos - track_widgets.TRACK_HEIGHT - - newSelectedItem = self.scene().itemAt( - QtCore.QPointF( - newXpos, - newYpos - ), - QtGui.QTransform() - ) - - if ( - not newSelectedItem - or isinstance(newSelectedItem, otio.schema.Track) - ): - newYpos = newYpos - track_widgets.TRANSITION_HEIGHT - else: - newXpos = curItemXpos - newYpos = curSelectedItem.y() - - newPosition = QtCore.QPointF(newXpos, newYpos) - - return self.scene().itemAt(newPosition, QtGui.QTransform()) - - def _get_down_item(self, curSelectedItem): - curItemXpos = curSelectedItem.pos().x() - - if curSelectedItem.parentItem(): - curTrackYpos = curSelectedItem.parentItem().pos().y() - newXpos = curItemXpos - newYpos = curTrackYpos + track_widgets.TRACK_HEIGHT - - newSelectedItem = self.scene().itemAt( - QtCore.QPointF( - newXpos, - newYpos - ), - QtGui.QTransform() - ) - - if ( - not newSelectedItem - or isinstance(newSelectedItem, otio.schema.Track) - ): - newYpos = newYpos + track_widgets.TRANSITION_HEIGHT - - if newYpos < track_widgets.TRACK_HEIGHT: - newYpos = track_widgets.TRACK_HEIGHT - else: - newXpos = curItemXpos - newYpos = ( - track_widgets.MARKER_SIZE + track_widgets.TIME_SLIDER_HEIGHT + 1 - ) - newYpos = track_widgets.TIME_SLIDER_HEIGHT - newPosition = QtCore.QPointF(newXpos, newYpos) - - return self.scene().itemAt(newPosition, QtGui.QTransform()) - - def _deselect_all_items(self): - if self.scene().selectedItems: - for selectedItem in self.scene().selectedItems(): - selectedItem.setSelected(False) - - def _select_new_item(self, newSelectedItem): - # Check for text item - # Text item shouldn't be selected, - # maybe a bug in the population of timeline. - if isinstance(newSelectedItem, QtWidgets.QGraphicsSimpleTextItem): - newSelectedItem = newSelectedItem.parentItem() - - # Validate new item for edge cases - # If valid, set selected - if ( - not isinstance(newSelectedItem, track_widgets.Track) and - newSelectedItem - ): - self._deselect_all_items() - newSelectedItem.setSelected(True) - self.centerOn(newSelectedItem) - - def _get_new_item(self, key_event, curSelectedItem): - key = key_event.key() - modifier = key_event.modifiers() - if not (key in ( - QtCore.Qt.Key_Left, - QtCore.Qt.Key_Right, - QtCore.Qt.Key_Up, - QtCore.Qt.Key_Down, - QtCore.Qt.Key_Return, - QtCore.Qt.Key_Enter - ) and not (modifier & QtCore.Qt.ControlModifier)): - return None - - if key in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Right, - QtCore.Qt.Key_Up, QtCore.Qt.Key_Down]: - - # check if last key was the opposite direction of current one - if KEY_SYM[key] == self._last_item_cache["key"] and \ - curSelectedItem == self._last_item_cache["item"] and \ - not curSelectedItem == self._last_item_cache["previous_item"]: - newSelectedItem = self._last_item_cache["previous_item"] - else: - filters = self.get_filters() - # make sure that the selected item is a BaseItem - while (not - isinstance(curSelectedItem, track_widgets.BaseItem) and - curSelectedItem): - curSelectedItem = curSelectedItem.parentItem() - if not curSelectedItem: - return None - - newSelectedItem = self.scene().get_next_item_filters( - curSelectedItem, - key, - filters - ) - - # self._last_item_cache["item"] = curSelectedItem - self._last_item_cache["item"] = newSelectedItem - self._last_item_cache["previous_item"] = curSelectedItem - self._last_item_cache["key"] = key - elif key in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Return]: - if isinstance(curSelectedItem, track_widgets.NestedItem): - curSelectedItem.keyPressEvent(key_event) - newSelectedItem = None - - return newSelectedItem - - def keyPressEvent(self, key_event): - super().keyPressEvent(key_event) - self.setInteractive(True) - - # Remove ruler_widget.Ruler instance from selection - selections = [ - x for x in self.scene().selectedItems() - if not isinstance(x, ruler_widget.Ruler) - ] - - # Based on direction key, select new selected item - if not selections: - newSelectedItem = self._get_first_item() - # No item selected, so select the first item - else: - curSelectedItem = selections[0] - - # Check to see if the current selected item is a rect item - # If current selected item is not a rect, then extra tests - # are needed. - if not isinstance(curSelectedItem, QtWidgets.QGraphicsRectItem): - if curSelectedItem.parentItem(): - curSelectedItem = curSelectedItem.parentItem() - - newSelectedItem = self._get_new_item(key_event, curSelectedItem) - self._keyPress_frame_all(key_event) - self._snap(key_event, curSelectedItem) - if newSelectedItem: - self._select_new_item(newSelectedItem) - - def _snap(self, key_event, curSelectedItem): - key = key_event.key() - modifier = key_event.modifiers() - if key in ( - QtCore.Qt.Key_Left, - QtCore.Qt.Key_Right, - ) and (modifier & QtCore.Qt.ControlModifier): - direction = 0 - if key == QtCore.Qt.Key_Left: - direction = -1.0 - elif key == QtCore.Qt.Key_Right: - direction = 1.0 - if direction: - ruler = self.scene().get_ruler() - ruler.snap(direction=direction, - scene_width=self.sceneRect().width()) - self.ensureVisible(ruler) - - def _keyPress_frame_all(self, key_event): - key = key_event.key() - modifier = key_event.modifiers() - if key == QtCore.Qt.Key_F and (modifier & QtCore.Qt.ControlModifier): - self.frame_all() - - def frame_all(self): - self.resetTransform() - track_widgets.CURRENT_ZOOM_LEVEL = 1.0 - self.scene().counteract_zoom() - - track_name_width = self.scene().track_name_width() - view_width = self.size().width() - track_name_width - scene_width = self.sceneRect().width() - track_name_width - if not view_width or not scene_width: - # Prevent zero division errors - return - - scale_by = view_width / scene_width - self.scale(scale_by, 1) - zoom_level = 1.0 / self.transform().m11() - track_widgets.CURRENT_ZOOM_LEVEL = zoom_level - self.scene().counteract_zoom(zoom_level) - - def navigationfilter_changed(self, bitmask): - ''' - Update the navigation filter according to the filters checked in the - navigation menu. Reset _last_item_cache - ''' - nav_d = namedtuple("navigation_filter", ["inclusive", "exclusive"]) - incl_filter, excl_filter = group_filters(bitmask) - self._navigation_filter = nav_d(incl_filter, excl_filter) - self._last_item_cache = {"key": None, "item": None, - "previous_item": None} - - def get_filters(self): - return self._navigation_filter - - -class Timeline(QtWidgets.QTabWidget): - - selection_changed = QtCore.Signal(otio.core.SerializableObject) - navigationfilter_changed = QtCore.Signal(int) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.timeline = None - - self.setTabsClosable(True) - self.tabCloseRequested.connect(self._close_tab) - - def _close_tab(self, index): - self.widget(index).deleteLater() - self.removeTab(index) - - def set_timeline(self, timeline): - # close all the tabs - for i in reversed(range(self.count())): - self._close_tab(i) - - # load new timeline - self.timeline = timeline - if timeline is not None: - self.add_stack(timeline.tracks) - - def add_stack(self, stack): - """Open a tab for the stack or go to it if already present""" - - # find the tab for the stack if the tab has already been opened - tab_index = next( - ( - i for i in range(self.count()) - if stack is self.widget(i).scene().composition - ), - None - ) - - if tab_index is not None: - self.setCurrentIndex(tab_index) - return - - new_stack = CompositionView(stack, parent=self) - self.addTab(new_stack, stack.name) - - # cannot close the first tab - if self.count() == 1: - button = self.tabBar().tabButton(0, QtWidgets.QTabBar.RightSide) - if button: - button.resize(0, 0) - - new_stack.open_stack.connect(self.add_stack) - new_stack.selection_changed.connect(self.selection_changed) - self.navigationfilter_changed.connect( - new_stack.navigationfilter_changed - ) - self.setCurrentIndex(self.count() - 1) - self.frame_all() - - def frame_all(self): - if self.currentWidget(): - self.currentWidget().frame_all() diff --git a/src/opentimelineview/track_widgets.py b/src/opentimelineview/track_widgets.py deleted file mode 100644 index 6648b24846..0000000000 --- a/src/opentimelineview/track_widgets.py +++ /dev/null @@ -1,572 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright Contributors to the OpenTimelineIO project - -try: - from PySide6 import QtGui, QtCore, QtWidgets - from PySide6.QtGui import QFontMetrics -except ImportError: - from PySide2 import QtGui, QtCore, QtWidgets - from PySide2.QtGui import QFontMetrics - -import opentimelineio as otio - -TIME_SLIDER_HEIGHT = 20 -MEDIA_TYPE_SEPARATOR_HEIGHT = 5 -TRACK_HEIGHT = 45 -TRANSITION_HEIGHT = 10 -TIME_MULTIPLIER = 25 -LABEL_MARGIN = 5 -MARKER_SIZE = 10 -EFFECT_HEIGHT = (1.0 / 3.0) * TRACK_HEIGHT -HIGHLIGHT_WIDTH = 5 -TRACK_NAME_WIDGET_WIDTH = 100.0 -SHORT_NAME_LENGTH = 7 -CURRENT_ZOOM_LEVEL = 1.0 -MARKER_COLORS = { - otio.schema.MarkerColor.RED: (0xff, 0x00, 0x00, 0xff), - otio.schema.MarkerColor.PINK: (0xff, 0x70, 0x70, 0xff), - otio.schema.MarkerColor.ORANGE: (0xff, 0xa0, 0x00, 0xff), - otio.schema.MarkerColor.YELLOW: (0xff, 0xff, 0x00, 0xff), - otio.schema.MarkerColor.GREEN: (0x00, 0xff, 0x00, 0xff), - otio.schema.MarkerColor.CYAN: (0x00, 0xff, 0xff, 0xff), - otio.schema.MarkerColor.BLUE: (0x00, 0x00, 0xff, 0xff), - otio.schema.MarkerColor.PURPLE: (0xa0, 0x00, 0xd0, 0xff), - otio.schema.MarkerColor.MAGENTA: (0xff, 0x00, 0xff, 0xff), - otio.schema.MarkerColor.WHITE: (0xff, 0xff, 0xff, 0xff), - otio.schema.MarkerColor.BLACK: (0x00, 0x00, 0x00, 0xff) -} - - -class BaseItem(QtWidgets.QGraphicsRectItem): - - def __init__(self, item, timeline_range, *args, **kwargs): - super().__init__(*args, **kwargs) - self.item = item - self.timeline_range = timeline_range - - # List of otio.core.SerializableObject - # it excludes decorator widgets as QLabel ... - self._otio_sub_items = list() - - self.setFlags(QtWidgets.QGraphicsItem.ItemIsSelectable) - self.setBrush( - QtGui.QBrush(QtGui.QColor(180, 180, 180, 255)) - ) - - pen = QtGui.QPen() - pen.setWidth(0) - pen.setCosmetic(True) - self.setPen(pen) - - self.source_in_label = QtWidgets.QGraphicsSimpleTextItem(self) - self.source_out_label = QtWidgets.QGraphicsSimpleTextItem(self) - self.source_name_label = QtWidgets.QGraphicsSimpleTextItem(self) - - self._add_markers() - self._add_effects() - self._set_labels() - self._set_tooltip() - - self.x_value = 0.0 - self.current_x_offset = TRACK_NAME_WIDGET_WIDTH - - def paint(self, *args, **kwargs): - new_args = [args[0], - QtWidgets.QStyleOptionGraphicsItem()] + list(args[2:]) - super().paint(*new_args, **kwargs) - - def itemChange(self, change, value): - if change == QtWidgets.QGraphicsItem.ItemSelectedHasChanged: - pen = self.pen() - pen.setColor( - QtGui.QColor(0, 255, 0, 255) if self.isSelected() - else QtGui.QColor(0, 0, 0, 255) - ) - pen.setWidth(HIGHLIGHT_WIDTH if self.isSelected() else 0) - self.setPen(pen) - self.setZValue( - self.zValue() + 1 if self.isSelected() else self.zValue() - 1 - ) - self.parentItem().setZValue( - self.parentItem().zValue() + 1 if self.isSelected() else - self.parentItem().zValue() - 1 - ) - - return super().itemChange(change, value) - - def _add_markers(self): - trimmed_range = self.item.trimmed_range() - - for m in self.item.markers: - marked_time = m.marked_range.start_time - if not trimmed_range.overlaps(marked_time): - continue - - marker = Marker(m) - marker.setY(0.5 * MARKER_SIZE) - marker.setX( - ( - otio.opentime.to_seconds(m.marked_range.start_time) - - otio.opentime.to_seconds(trimmed_range.start_time) - ) * TIME_MULTIPLIER - ) - marker.setParentItem(self) - self._add_otio_sub_item(marker) - - def _add_effects(self): - if not hasattr(self.item, "effects"): - return - if not self.item.effects: - return - effect = EffectItem(self.item.effects, self.rect()) - effect.setParentItem(self) - self._add_otio_sub_item(effect) - - def _add_otio_sub_item(self, item): - self._otio_sub_items.append(item) - - def get_otio_sub_items(self): - return self._otio_sub_items - - def _position_labels(self): - self.source_in_label.setY(LABEL_MARGIN) - self.source_out_label.setY(LABEL_MARGIN) - self.source_name_label.setY( - (TRACK_HEIGHT - - self.source_name_label.boundingRect().height()) / 2.0 - ) - - def _set_labels_rational_time(self): - trimmed_range = self.item.trimmed_range() - self.source_in_label.setText( - '{value}\n@{rate}'.format( - value=trimmed_range.start_time.value, - rate=trimmed_range.start_time.rate - ) - ) - self.source_out_label.setText( - '{value}\n@{rate}'.format( - value=trimmed_range.end_time_inclusive().value, - rate=trimmed_range.end_time_inclusive().rate - ) - ) - - def _set_labels_timecode(self): - self.source_in_label.setText( - '{timeline}\n{source}'.format( - timeline=otio.opentime.to_timecode( - self.timeline_range.start_time, - self.timeline_range.start_time.rate - ), - source=otio.opentime.to_timecode( - self.item.trimmed_range.start_time, - self.item.trimmed_range.start_time.rate - ) - ) - ) - - self.source_out_label.setText( - '{timeline}\n{source}'.format( - timeline=otio.opentime.to_timecode( - self.timeline_range.end_time_exclusive(), - self.timeline_range.end_time_exclusive().rate - ), - source=otio.opentime.to_timecode( - self.item.trimmed_range.end_time_exclusive(), - self.item.trimmed_range.end_time_exclusive().rate - ) - ) - ) - - def _set_labels(self): - self._set_labels_rational_time() - self.source_name_label.setText('PLACEHOLDER') - self._position_labels() - - def _set_tooltip(self): - self.setToolTip(self.item.name) - - def counteract_zoom(self, zoom_level=1.0): - self.setX(self.x_value + self.current_x_offset * zoom_level) - for label in ( - self.source_name_label, - self.source_in_label, - self.source_out_label - ): - label.setTransform(QtGui.QTransform.fromScale(zoom_level, 1.0)) - - self_rect = self.boundingRect() - name_width = self.source_name_label.boundingRect().width() * zoom_level - in_width = self.source_in_label.boundingRect().width() * zoom_level - out_width = self.source_out_label.boundingRect().width() * zoom_level - - frames_space = in_width + out_width + 3 * LABEL_MARGIN * zoom_level - - if frames_space > self_rect.width(): - self.source_in_label.setVisible(False) - self.source_out_label.setVisible(False) - else: - self.source_in_label.setVisible(True) - self.source_out_label.setVisible(True) - - self.source_in_label.setX(LABEL_MARGIN * zoom_level) - - self.source_out_label.setX( - self_rect.width() - LABEL_MARGIN * zoom_level - out_width - ) - - total_width = (name_width + frames_space + LABEL_MARGIN * zoom_level) - if total_width > self_rect.width(): - self.source_name_label.setVisible(False) - else: - self.source_name_label.setVisible(True) - self.source_name_label.setX(0.5 * (self_rect.width() - name_width)) - - -class GapItem(BaseItem): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.setBrush( - QtGui.QBrush(QtGui.QColor(100, 100, 100, 255)) - ) - self.source_name_label.setText('GAP') - - -class TrackNameItem(BaseItem): - - def __init__(self, track, rect, *args, **kwargs): - super().__init__(None, None, rect, *args, **kwargs) - self.track = track - self.track_name = 'Track' if not track.name else track.name - self.full_track_name = self.track_name - if len(self.track_name) > SHORT_NAME_LENGTH: - self.track_name = self.track_name[:SHORT_NAME_LENGTH] + '...' - self.source_name_label.setText(self.track_name + f'\n({track.kind})') - self.source_name_label.setY( - (TRACK_HEIGHT - - self.source_name_label.boundingRect().height()) / 2.0 - ) - self.setToolTip(f'{len(track)} items') - self.track_widget = None - self.name_toggle = False - self.font = self.source_name_label.font() - self.short_width = TRACK_NAME_WIDGET_WIDTH - font_metrics = QFontMetrics(self.font) - self.full_width = font_metrics.horizontalAdvance(self.full_track_name) + 40 - - if not self.track.enabled: - self.setBrush( - QtGui.QBrush(QtGui.QColor(100, 100, 100, 255)) - ) - - def mouseDoubleClickEvent(self, event): - super().mouseDoubleClickEvent(event) - if self.name_toggle: - track_name_rect = QtCore.QRectF( - 0, - 0, - TRACK_NAME_WIDGET_WIDTH, - TRACK_HEIGHT - ) - self.setRect(track_name_rect) - self.source_name_label.setText( - self.track_name + f'\n({self.track.kind})') - for widget in self.track_widget.widget_items: - widget.current_x_offset = self.short_width - self.name_toggle = False - else: - track_name_rect = QtCore.QRectF( - 0, - 0, - self.full_width, - TRACK_HEIGHT - ) - self.setRect(track_name_rect) - self.source_name_label.setText( - self.full_track_name + f'\n({self.track.kind})') - for widget in self.track_widget.widget_items: - widget.current_x_offset = self.full_width - self.name_toggle = True - - scene = self.scene() - if scene and hasattr(scene, "counteract_zoom"): - # If scene is CompositionWidget, trigger counteract zoom through - # it to also update the scene rect. - scene.counteract_zoom(CURRENT_ZOOM_LEVEL) - else: - for widget in self.track_widget.widget_items: - widget.counteract_zoom(CURRENT_ZOOM_LEVEL) - - def itemChange(self, change, value): - return super(BaseItem, self).itemChange(change, value) - - def _add_markers(self): - return - - def _set_labels(self): - return - - def _set_tooltip(self): - return - - def counteract_zoom(self, zoom_level=1.0): - name_width = self.source_name_label.boundingRect().width() - self.source_name_label.setX(0.5 * (self.boundingRect().width() - name_width)) - self.setTransform(QtGui.QTransform.fromScale(zoom_level, 1.0)) - - -class EffectItem(QtWidgets.QGraphicsRectItem): - - def __init__(self, item, rect, *args, **kwargs): - super().__init__(rect, *args, **kwargs) - self.item = item - self.setFlags(QtWidgets.QGraphicsItem.ItemIsSelectable) - self.init() - self._set_tooltip() - - def init(self): - rect = self.rect() - rect.setY(TRACK_HEIGHT - EFFECT_HEIGHT) - rect.setHeight(EFFECT_HEIGHT) - self.setRect(rect) - - dark = QtGui.QColor(0, 0, 0, 150) - colour = QtGui.QColor(255, 255, 255, 200) - gradient = QtGui.QLinearGradient( - QtCore.QPointF(0, self.boundingRect().top()), - QtCore.QPointF(0, self.boundingRect().bottom()) - ) - gradient.setColorAt(0.2, QtCore.Qt.transparent) - gradient.setColorAt(0.45, colour) - gradient.setColorAt(0.7, QtCore.Qt.transparent) - gradient.setColorAt(1.0, dark) - self.setBrush(QtGui.QBrush(gradient)) - - pen = self.pen() - pen.setColor(QtGui.QColor(0, 0, 0, 80)) - pen.setWidth(0) - self.setPen(pen) - - def _set_tooltip(self): - tool_tips = list() - for effect in self.item: - name = effect.name if effect.name else "" - effect_name = effect.effect_name if effect.effect_name else "" - tool_tips.append(f"{name} {effect_name}") - self.setToolTip("\n".join(tool_tips)) - - def paint(self, *args, **kwargs): - new_args = [args[0], - QtWidgets.QStyleOptionGraphicsItem()] + list(args[2:]) - super().paint(*new_args, **kwargs) - - def itemChange(self, change, value): - if change == QtWidgets.QGraphicsItem.ItemSelectedHasChanged: - pen = self.pen() - pen.setColor( - QtGui.QColor(0, 255, 0, 255) if self.isSelected() - else QtGui.QColor(0, 0, 0, 80) - ) - self.setPen(pen) - self.setZValue( - self.zValue() + 1 if self.isSelected() else self.zValue() - 1 - ) - - return super().itemChange(change, value) - - -class TransitionItem(BaseItem): - - def __init__(self, item, timeline_range, rect, *args, **kwargs): - rect.setHeight(TRANSITION_HEIGHT) - super().__init__( - item, - timeline_range, - rect, - *args, - **kwargs - ) - self.setBrush( - QtGui.QBrush(QtGui.QColor(237, 228, 148, 255)) - ) - self.setY(TRACK_HEIGHT - TRANSITION_HEIGHT) - self.setZValue(2) - - # add extra bit of shading - shading_poly_f = QtGui.QPolygonF() - shading_poly_f.append(QtCore.QPointF(0, 0)) - shading_poly_f.append(QtCore.QPointF(rect.width(), 0)) - shading_poly_f.append(QtCore.QPointF(0, rect.height())) - - shading_poly = QtWidgets.QGraphicsPolygonItem( - shading_poly_f, parent=self) - shading_poly.setBrush(QtGui.QBrush(QtGui.QColor(0, 0, 0, 30))) - - try: - shading_poly.setPen(QtCore.Qt.NoPen) - except TypeError: - shading_poly.setPen(QtCore.Qt.transparent) - - def _add_markers(self): - return - - def _set_labels(self): - return - - -class ClipItem(BaseItem): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.setBrush(QtGui.QBrush(QtGui.QColor(168, 197, 255, 255) if self.item.enabled - else QtGui.QColor(100, 100, 100, 255))) - self.source_name_label.setText(self.item.name) - - -class NestedItem(BaseItem): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.setBrush( - QtGui.QBrush(QtGui.QColor(255, 113, 91, 255)) - ) - - self.source_name_label.setText(self.item.name) - - def mouseDoubleClickEvent(self, event): - super().mouseDoubleClickEvent(event) - self.scene().views()[0].open_stack.emit(self.item) - - def keyPressEvent(self, key_event): - super().keyPressEvent(key_event) - key = key_event.key() - - if key == QtCore.Qt.Key_Return: - self.scene().views()[0].open_stack.emit(self.item) - - -class Track(QtWidgets.QGraphicsRectItem): - - def __init__(self, track, *args, **kwargs): - super().__init__(*args, **kwargs) - self.track = track - self.widget_items = [] - self.track_name_item = None - self.setBrush(QtGui.QBrush(QtGui.QColor(43, 52, 59, 255))) - self._populate() - - def _populate(self): - track_map = self.track.range_of_all_children() - track_name_rect = QtCore.QRectF( - 0, - 0, - TRACK_NAME_WIDGET_WIDTH, - TRACK_HEIGHT - ) - self.track_name_item = TrackNameItem(self.track, track_name_rect) - self.track_name_item.setParentItem(self) - self.track_name_item.setX(0) - self.track_name_item.counteract_zoom() - self.track_name_item.track_widget = self - for n, item in enumerate(self.track): - timeline_range = track_map[item] - - rect = QtCore.QRectF( - 0, - 0, - otio.opentime.to_seconds(timeline_range.duration) * - TIME_MULTIPLIER, - TRACK_HEIGHT - ) - - if not self.track.enabled: - item.enabled = False - - if isinstance(item, otio.schema.Clip): - new_item = ClipItem(item, timeline_range, rect) - elif isinstance(item, otio.schema.Stack): - new_item = NestedItem(item, timeline_range, rect) - elif isinstance(item, otio.schema.Track): - new_item = NestedItem(item, timeline_range, rect) - elif isinstance(item, otio.schema.Gap): - new_item = GapItem(item, timeline_range, rect) - elif isinstance(item, otio.schema.Transition): - new_item = TransitionItem(item, timeline_range, rect) - else: - print(f"Warning: could not add item {item} to UI.") - continue - - new_item.setParentItem(self) - new_item.x_value = otio.opentime.to_seconds( - timeline_range.start_time) * TIME_MULTIPLIER - new_item.setX( - otio.opentime.to_seconds(timeline_range.start_time) * - TIME_MULTIPLIER - ) - new_item.counteract_zoom() - self.widget_items.append(new_item) - - -class Marker(QtWidgets.QGraphicsPolygonItem): - - def __init__(self, marker, *args, **kwargs): - self.item = marker - - poly = QtGui.QPolygonF() - poly.append(QtCore.QPointF(0.5 * MARKER_SIZE, -0.5 * MARKER_SIZE)) - poly.append(QtCore.QPointF(0.5 * MARKER_SIZE, 0.5 * MARKER_SIZE)) - poly.append(QtCore.QPointF(0, MARKER_SIZE)) - poly.append(QtCore.QPointF(-0.5 * MARKER_SIZE, 0.5 * MARKER_SIZE)) - poly.append(QtCore.QPointF(-0.5 * MARKER_SIZE, -0.5 * MARKER_SIZE)) - super().__init__(poly, *args, **kwargs) - - self.setFlags(QtWidgets.QGraphicsItem.ItemIsSelectable) - color = MARKER_COLORS.get(marker.color, (121, 212, 177, 255)) - - self.setBrush( - QtGui.QBrush(QtGui.QColor(*color)) - ) - - def paint(self, *args, **kwargs): - new_args = [args[0], - QtWidgets.QStyleOptionGraphicsItem()] + list(args[2:]) - super().paint(*new_args, **kwargs) - - def itemChange(self, change, value): - if change == QtWidgets.QGraphicsItem.ItemSelectedHasChanged: - self.setPen( - QtGui.QColor(0, 255, 0, 255) if self.isSelected() - else QtGui.QColor(0, 0, 0, 255) - ) - return super().itemChange(change, value) - - def counteract_zoom(self, zoom_level=1.0): - self.setTransform(QtGui.QTransform.fromScale(zoom_level, 1.0)) - - -class TimeSlider(QtWidgets.QGraphicsRectItem): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.setBrush(QtGui.QBrush(QtGui.QColor(64, 78, 87, 255))) - pen = QtGui.QPen() - pen.setWidth(0) - self.setPen(pen) - self._ruler = None - - def mousePressEvent(self, mouse_event): - pos = self.mapToScene(mouse_event.pos()) - self._ruler.setPos(QtCore.QPointF( - max(pos.x() - TRACK_NAME_WIDGET_WIDTH * CURRENT_ZOOM_LEVEL, 0), - TIME_SLIDER_HEIGHT - - MARKER_SIZE)) - self._ruler.update_frame() - - super().mousePressEvent(mouse_event) - - def add_ruler(self, ruler): - self._ruler = ruler - - def counteract_zoom(self, zoom_level=1.0): - self.setX(zoom_level * TRACK_NAME_WIDGET_WIDTH) diff --git a/src/py-opentimelineio/CMakeLists.txt b/src/py-opentimelineio/CMakeLists.txt index 8364173e93..b2a9e8ab2f 100644 --- a/src/py-opentimelineio/CMakeLists.txt +++ b/src/py-opentimelineio/CMakeLists.txt @@ -9,9 +9,4 @@ if(OTIO_INSTALL_PYTHON_MODULES) install(DIRECTORY "${PROJECT_SOURCE_DIR}/src/py-opentimelineio/opentimelineio/" DESTINATION "${OTIO_RESOLVED_PYTHON_INSTALL_DIR}/opentimelineio") - if(OTIO_INSTALL_COMMANDLINE_TOOLS) - install(DIRECTORY "${PROJECT_SOURCE_DIR}/src/opentimelineview" - DESTINATION "${OTIO_RESOLVED_PYTHON_INSTALL_DIR}") - endif() - endif()