Skip to content

timeline

Classes:

Timeline

Timeline(parent: QWidget, **kwargs: Any)

Bases: QWidget

Classes:

Methods:

Attributes:

Source code
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def __init__(self, parent: QWidget, **kwargs: Any) -> None:
    super().__init__(parent, **kwargs)
    self.app = QApplication.instance()
    self.main = main_window()

    self._mode = self.Mode.TIME

    self.rect_f = QRectF()

    self.set_sizes()

    self._cursor_x: int | Frame | Time = 0

    self.notches = dict[NotchProvider, Notches]()

    self.setAttribute(Qt.WidgetAttribute.WA_OpaquePaintEvent)
    self.setMouseTracking(True)

    self.main.reload_before_signal.connect(lambda: self.__setattr__('_after_reload', True))

    self.mousepressed = False
    self.lastpaint = perf_counter_ns()

app instance-attribute

app = instance()

clicked class-attribute instance-attribute

clicked = pyqtSignal(Frame, Time)

cursor_x property writable

cursor_x: int

lastpaint instance-attribute

lastpaint = perf_counter_ns()

main instance-attribute

main = main_window()

mode property writable

mode: str

mousepressed instance-attribute

mousepressed = False

notch_intervals_f class-attribute instance-attribute

notch_intervals_f = list(
    map(
        Frame,
        [
            1,
            5,
            10,
            20,
            25,
            50,
            75,
            100,
            200,
            250,
            500,
            750,
            1000,
            2000,
            2500,
            5000,
            7500,
            10000,
            20000,
            25000,
            50000,
            75000,
        ],
    )
)

notches instance-attribute

notches = dict[NotchProvider, Notches]()

rect_f instance-attribute

rect_f = QRectF()

Mode

Mode(*args: Any, **kwargs: Any)

Bases: AbstractYAMLObject

Methods:

Attributes:

Source code
305
306
307
@abstractmethod
def __init__(self, *args: Any, **kwargs: Any) -> None:
    raise NotImplementedError

FRAME class-attribute instance-attribute

FRAME = 'frame'

TIME class-attribute instance-attribute

TIME = 'time'

storable_attrs class-attribute

storable_attrs: tuple[str, ...] = ()

set_qobject_names

set_qobject_names() -> None
Source code
279
280
281
282
283
284
285
286
287
288
289
290
291
292
def set_qobject_names(self) -> None:
    if not hasattr(self, '__slots__'):
        return

    slots = list(self.__slots__)

    if isinstance(self, AbstractToolbar) and 'main' in slots:
        slots.remove('main')

    for attr_name in slots:
        attr = getattr(self, attr_name)
        if not isinstance(attr, QObject):
            continue
        attr.setObjectName(type(self).__name__ + '.' + attr_name)

c_to_x

c_to_x(cursor: int | Frame | Time) -> int
Source code
341
342
343
344
345
346
347
348
349
350
351
352
353
354
def c_to_x(self, cursor: int | Frame | Time) -> int:
    if isinstance(cursor, int):
        return cursor

    try:
        if isinstance(cursor, Frame):
            return round(int(cursor) / int(self.main.current_output.total_frames) * self.rect_f.width())

        if isinstance(cursor, Time):
            return floor(float(cursor) / float(self.main.current_output.total_time) * self.rect_f.width())
    except ZeroDivisionError:
        ...

    return 0

calculate_notch_interval_f

calculate_notch_interval_f(target_interval_x: int) -> Frame
Source code
309
310
311
312
313
314
315
316
317
318
319
320
321
def calculate_notch_interval_f(self, target_interval_x: int) -> Frame:
    margin = 1 + self.main.settings.timeline_label_notches_margin / 100

    target_interval_f = self.x_to_f(target_interval_x)

    if target_interval_f >= Frame(round(int(self.notch_intervals_f[-1]) * margin)):
        return self.notch_intervals_f[-1]

    for interval in self.notch_intervals_f:
        if target_interval_f < Frame(round(int(interval) * margin)):
            return interval

    raise RuntimeError

calculate_notch_interval_t

calculate_notch_interval_t(target_interval_x: int) -> Time
Source code
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
def calculate_notch_interval_t(self, target_interval_x: int) -> Time:
    notch_intervals_t = list(
        Time(seconds=n) for n in [
            1, 2, 5, 10, 15, 30, 60, 90, 120, 300, 600,
            900, 1200, 1800, 2700, 3600, 5400, 7200
        ]
    )

    margin = 1 + self.main.settings.timeline_label_notches_margin / 100
    target_interval_t = self.x_to_t(target_interval_x)

    if target_interval_t >= notch_intervals_t[-1] * margin:
        return notch_intervals_t[-1]

    for interval in notch_intervals_t:
        if target_interval_t < interval * margin:
            return interval

    raise RuntimeError

cs_to_x

cs_to_x(*cursors: int | Frame | Time) -> Iterable[int]
Source code
356
357
358
359
360
361
362
363
364
365
366
367
368
def cs_to_x(self, *cursors: int | Frame | Time) -> Iterable[int]:
    r_f = self.rect_f.width()
    t_d = float(self.main.current_output.total_time) * r_f
    f_d = int(self.main.current_output.total_frames) * r_f

    for c in cursors:
        if isinstance(c, int):
            yield c

        try:
            yield round(int(c) / (f_d if isinstance(c, Time) else t_d))
        except ZeroDivisionError:
            yield 0

drawWidget

drawWidget(painter: QPainter) -> None
Source code
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def drawWidget(self, painter: QPainter) -> None:
    setup_key = (self.rect_f, self.main.current_output.index)

    curr_key, (scroll_rect, labels_notches, rects_to_draw) = self.notches_cache[self.mode]

    if setup_key != curr_key:
        lnotch_y, lnotch_x = self.rect_f.top() + self.font_height + self.notch_height + 5, self.rect_f.left()
        lnotch_top = lnotch_y - self.notch_height

        labels_notches = Notches()

        if self.mode == self.Mode.TIME:
            max_value = self.main.current_output.total_time
            notch_interval = self.calculate_notch_interval_t(self.notch_interval_target_x)
            label_format = self.generate_label_format(notch_interval, max_value)
            label_notch = Time()
        elif self.mode == self.Mode.FRAME:
            max_value = self.main.current_output.total_frames - 1  # type: ignore
            notch_interval = self.calculate_notch_interval_f(self.notch_interval_target_x)  # type: ignore
            label_notch = Frame()  # type: ignore

        while (lnotch_x < self.rect_f.right() and label_notch <= max_value):
            labels_notches.add(
                Notch(deepcopy(label_notch), line=QLineF(lnotch_x, lnotch_y, lnotch_x, lnotch_top))
            )
            label_notch += notch_interval
            lnotch_x = self.c_to_x(label_notch)

        labels_notches.add(
            Notch(max_value, line=QLineF(self.rect_f.right() - 1, lnotch_y, self.rect_f.right() - 1, lnotch_top))
        )

        scroll_rect = QRectF(
            self.rect_f.left(), lnotch_y + self.notch_scroll_interval, self.rect_f.width(), self.scroll_height
        )

    cursor_line = QLineF(
        self.cursor_x, scroll_rect.top(), self.cursor_x, scroll_rect.top() + scroll_rect.height() - 1
    )

    for provider, notches in self.notches.items():
        if not provider.is_notches_visible:
            continue

        notches.norm_lines(self, scroll_rect)

    painter.fillRect(self.rect_f, self.palette().color(QPalette.ColorRole.Window))
    painter.setPen(QPen(self.palette().color(QPalette.ColorRole.WindowText)))
    painter.setRenderHint(QPainter.RenderHint.Antialiasing)

    if setup_key != curr_key:
        rects_to_draw = []

        for i, notch in enumerate(labels_notches):
            anchor_rect = QRectF(notch.line.x2(), notch.line.y2(), 0, 0)

            if self.mode == self.Mode.TIME:
                time = cast(Time, notch.data)
                label = strfdelta(time, label_format)
            elif self.mode == self.Mode.FRAME:
                label = str(notch.data)

            if i == 0:
                rect = painter.boundingRect(
                    anchor_rect, Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft, label
                )
                if self.mode == self.Mode.TIME:
                    rect.moveLeft(-2.5)
            elif i == (len(labels_notches) - 1):
                rect = painter.boundingRect(
                    anchor_rect, Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignRight, label
                )
            elif i == (len(labels_notches) - 2):
                rect = painter.boundingRect(
                    anchor_rect, Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter, label
                )

                last_notch = labels_notches[-1]

                if self.mode == self.Mode.TIME:
                    last_label = strfdelta(cast(Time, last_notch.data), label_format)
                elif self.mode == self.Mode.FRAME:
                    last_label = str(last_notch.data)

                anchor_rect = QRectF(last_notch.line.x2(), last_notch.line.y2(), 0, 0)
                last_rect = painter.boundingRect(
                    anchor_rect, Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignRight, last_label
                )

                if last_rect.left() - rect.right() < self.notch_interval_target_x / 10:
                    labels_notches.items.pop(-2)
                    rects_to_draw.append((last_rect, last_label))
                    break
            else:
                rect = painter.boundingRect(
                    anchor_rect, Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter, label
                )

            rects_to_draw.append((rect, label))

            self.notches_cache[self.mode] = (setup_key, (scroll_rect, labels_notches, rects_to_draw))

    for rect, text in rects_to_draw:
        painter.drawText(rect, text)

    painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
    painter.drawLines([notch.line for notch in labels_notches])  # type: ignore
    painter.fillRect(scroll_rect, Qt.GlobalColor.gray)

    for provider, notches in self.notches.items():
        if not provider.is_notches_visible:
            continue

        for notch in notches:
            painter.setPen(notch.color)
            painter.drawLine(notch.line)

    painter.setPen(Qt.GlobalColor.black)
    painter.drawLine(cursor_line)

event

event(event: QEvent) -> bool
Source code
266
267
268
269
270
271
272
def event(self, event: QEvent) -> bool:
    if event.type() in {QEvent.Type.Polish, QEvent.Type.ApplicationPaletteChange}:
        self.setPalette(self.main.palette())
        self.update()
        return True

    return super().event(event)

generate_label_format

generate_label_format(notch_interval_t: Time, end_time: Time | Time) -> str
Source code
323
324
325
326
327
328
329
330
331
332
333
def generate_label_format(self, notch_interval_t: Time, end_time: Time | Time) -> str:
    if end_time >= Time(hours=1):
        return '%h:%M:00'

    if notch_interval_t >= Time(minutes=1):
        return '%m:00'

    if end_time > Time(seconds=10):
        return '%m:%S'

    return '%s.%Z'

mouseMoveEvent

mouseMoveEvent(event: QMouseEvent) -> None
Source code
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
def mouseMoveEvent(self, event: QMouseEvent) -> None:
    super().mouseMoveEvent(event)

    if self.mousepressed and (
        (perf_counter_ns() - self.lastpaint) / 100000 > self.main.settings.dragtimeline_timeout
    ):
        pos = event.pos().toPointF()
        pos.setY(self.notches_cache[self.mode][1][0].top() + 1)

        if self.notches_cache[self.mode][1][0].contains(pos):
            self.cursor_x = int(pos.x())
            self.clicked.emit(self.x_to_f(self.cursor_x), self.x_to_t(self.cursor_x))
            self.lastpaint = perf_counter_ns()

    for provider, notches in self.notches.items():
        if not provider.is_notches_visible:
            continue

        for notch in notches:
            line = notch.line
            if line.x1() - 0.5 <= event.pos().x() <= line.x1() + 0.5:
                QToolTip.showText(event.globalPosition().toPoint(), notch.label)
                return

mousePressEvent

mousePressEvent(event: QMouseEvent) -> None
Source code
232
233
234
235
236
def mousePressEvent(self, event: QMouseEvent) -> None:
    super().mousePressEvent(event)

    self.mousepressed = True
    self.mouseMoveEvent(event)

mouseReleaseEvent

mouseReleaseEvent(event: QMouseEvent) -> None
Source code
229
230
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
    self.mousepressed = False

moveEvent

moveEvent(event: QMoveEvent) -> None
Source code
225
226
227
def moveEvent(self, event: QMoveEvent) -> None:
    super().moveEvent(event)
    self.update()

paintEvent

paintEvent(event: QPaintEvent) -> None
Source code
 99
100
101
102
103
def paintEvent(self, event: QPaintEvent) -> None:
    super().paintEvent(event)
    self.rect_f = QRectF(event.rect())

    self.drawWidget(QPainter(self))

resizeEvent

resizeEvent(event: QResizeEvent) -> None
Source code
262
263
264
def resizeEvent(self, event: QResizeEvent) -> None:
    super().resizeEvent(event)
    self.update()

set_sizes

set_sizes() -> None
Source code
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def set_sizes(self) -> None:
    self.notches_cache = _default_cache

    self.notch_interval_target_x = round(75 * self.main.display_scale)
    self.notch_height = round(6 * self.main.display_scale)
    self.font_height = round(10 * self.main.display_scale)
    self.notch_scroll_interval = round(2 * self.main.display_scale)
    self.scroll_height = round(10 * self.main.display_scale)

    self.setMinimumSize(self.notch_interval_target_x, round(33 * self.main.display_scale))

    font = self.main.font()
    font.setPixelSize(self.font_height)
    self.setFont(font)

    self.update()

update_notches

update_notches(
    provider: NotchProvider | Sequence[NotchProvider] | None = None,
) -> None
Source code
274
275
276
277
278
279
280
281
282
def update_notches(self, provider: NotchProvider | Sequence[NotchProvider] | None = None) -> None:
    if provider is None:
        provider = [*self.main.toolbars, *self.main.plugins]

    for t in cast(list[NotchProvider], to_arr(provider)):
        if t.is_notches_visible:
            self.notches[t] = t.get_notches()

    self.update()

x_to_f

x_to_f(x: int) -> Frame
Source code
338
339
def x_to_f(self, x: int) -> Frame:
    return Frame(round(x / self.rect_f.width() * int(self.main.current_output.total_frames)))

x_to_t

x_to_t(x: int) -> Time
Source code
335
336
def x_to_t(self, x: int) -> Time:
    return Time(seconds=(x * float(self.main.current_output.total_time) / self.rect_f.width()))