LosslessCut-style layout: File menu, timeline above transport, centered controls, right-click play/pause
- Load File moved from a button into a new File menu. - Timeline now sits directly under the video; the transport row (frame-step/play/frame-step, centered as a group; mute pinned right) moved below it, matching LosslessCut's layout instead of the previous buttons-then-timeline order. - Right-click on the video toggles play/pause. The right-click implementation took two tries, worth recording: mpv embeds via wid as its own native child window, which receives raw X11 input directly -- a real mouse click never reaches this QWidget's mousePressEvent at all, even though a unit-style test that called the handler directly passed and looked fine. Caught this by testing with an actual xdotool click against a live running window instead of trusting the synthetic test, which is what this project's smoke tests have been doing throughout and what caught the bug here too. Fixed by binding MBTN_RIGHT through mpv's own on_key_press input API instead, which does receive the real clicks -- verified the same way, with a real click on a live window, position/button-text actually changing. Also fixed a genuinely flaky smoke test in the process: QShortcut's active-window check races with real desktop window-manager focus, especially with another instance of this same app already open elsewhere (happened during this session). Switched that test to set Qt's active-window state directly rather than relying on WM-level activation, and independently confirmed the real key bindings with live xdotool keypresses (both directions, plus end-of-file clamping).
This commit is contained in:
@@ -97,6 +97,7 @@ class MainWindow(QMainWindow):
|
||||
self.player = PlayerWidget()
|
||||
self.player.setMinimumHeight(400)
|
||||
self.player.fileDropped.connect(self._load)
|
||||
self.player.rightClicked.connect(self._toggle_play)
|
||||
|
||||
self._build_ui()
|
||||
self._build_menu()
|
||||
@@ -115,30 +116,8 @@ class MainWindow(QMainWindow):
|
||||
|
||||
layout.addWidget(self.player, stretch=1)
|
||||
|
||||
# Transport row: play/pause, frame step, seek slider, position label
|
||||
transport = QHBoxLayout()
|
||||
self.load_button = QPushButton("Load File…")
|
||||
self.load_button.clicked.connect(self._on_load_clicked)
|
||||
transport.addWidget(self.load_button)
|
||||
|
||||
self.play_button = QPushButton("Play")
|
||||
self.play_button.clicked.connect(self._toggle_play)
|
||||
transport.addWidget(self.play_button)
|
||||
|
||||
self.prev_frame_button = QPushButton("⏮ Frame (,)")
|
||||
self.prev_frame_button.clicked.connect(lambda: self._step_frame(-1))
|
||||
transport.addWidget(self.prev_frame_button)
|
||||
|
||||
self.next_frame_button = QPushButton("Frame ⏭ (.)")
|
||||
self.next_frame_button.clicked.connect(lambda: self._step_frame(1))
|
||||
transport.addWidget(self.next_frame_button)
|
||||
|
||||
self.mute_checkbox = QCheckBox("Mute audio")
|
||||
self.mute_checkbox.toggled.connect(self._on_mute_toggled)
|
||||
transport.addWidget(self.mute_checkbox)
|
||||
|
||||
layout.addLayout(transport)
|
||||
|
||||
# Timeline directly under the video (LosslessCut-style), transport
|
||||
# controls below that.
|
||||
seek_row = QHBoxLayout()
|
||||
self.position_label = QLabel("00:00.000")
|
||||
seek_row.addWidget(self.position_label)
|
||||
@@ -161,6 +140,31 @@ class MainWindow(QMainWindow):
|
||||
seek_row.addWidget(self.duration_label)
|
||||
layout.addLayout(seek_row)
|
||||
|
||||
# Transport row: frame-step/play controls centered as a group, mute
|
||||
# pinned to the right. Load File now lives in the File menu.
|
||||
transport = QHBoxLayout()
|
||||
transport.addStretch(1)
|
||||
|
||||
self.prev_frame_button = QPushButton("⏮ Frame (,)")
|
||||
self.prev_frame_button.clicked.connect(lambda: self._step_frame(-1))
|
||||
transport.addWidget(self.prev_frame_button)
|
||||
|
||||
self.play_button = QPushButton("Play")
|
||||
self.play_button.clicked.connect(self._toggle_play)
|
||||
transport.addWidget(self.play_button)
|
||||
|
||||
self.next_frame_button = QPushButton("Frame ⏭ (.)")
|
||||
self.next_frame_button.clicked.connect(lambda: self._step_frame(1))
|
||||
transport.addWidget(self.next_frame_button)
|
||||
|
||||
transport.addStretch(1)
|
||||
|
||||
self.mute_checkbox = QCheckBox("Mute audio")
|
||||
self.mute_checkbox.toggled.connect(self._on_mute_toggled)
|
||||
transport.addWidget(self.mute_checkbox)
|
||||
|
||||
layout.addLayout(transport)
|
||||
|
||||
# Marker row
|
||||
marker_group = QGroupBox("Sync markers")
|
||||
marker_layout = QHBoxLayout(marker_group)
|
||||
@@ -210,6 +214,12 @@ class MainWindow(QMainWindow):
|
||||
self.setCentralWidget(central)
|
||||
|
||||
def _build_menu(self):
|
||||
file_menu = self.menuBar().addMenu("&File")
|
||||
|
||||
load_action = QAction("Load File…", self)
|
||||
load_action.triggered.connect(self._on_load_clicked)
|
||||
file_menu.addAction(load_action)
|
||||
|
||||
view_menu = self.menuBar().addMenu("&View")
|
||||
|
||||
self.toggle_log_action = QAction("Show ffmpeg Log", self, checkable=True, checked=False)
|
||||
|
||||
Reference in New Issue
Block a user