feat: move audio editor selection with ctrl drag
This commit is contained in:
@@ -4101,6 +4101,8 @@ class AudioWaveform(QWidget):
|
|||||||
self._playhead: float | None = None
|
self._playhead: float | None = None
|
||||||
self._markers: list[float] = []
|
self._markers: list[float] = []
|
||||||
self._drag_anchor: float | None = None
|
self._drag_anchor: float | None = None
|
||||||
|
self._drag_mode: str | None = None
|
||||||
|
self._drag_orig_sel: tuple[float, float] | None = None
|
||||||
self._press_x: float | None = None
|
self._press_x: float | None = None
|
||||||
self.setFixedHeight(96)
|
self.setFixedHeight(96)
|
||||||
self.setToolTip("Waveform of the current audio area (↻ to refresh)")
|
self.setToolTip("Waveform of the current audio area (↻ to refresh)")
|
||||||
@@ -4153,6 +4155,8 @@ class AudioWaveform(QWidget):
|
|||||||
self._playhead = None
|
self._playhead = None
|
||||||
self._markers = []
|
self._markers = []
|
||||||
self._drag_anchor = None
|
self._drag_anchor = None
|
||||||
|
self._drag_mode = None
|
||||||
|
self._drag_orig_sel = None
|
||||||
self._press_x = None
|
self._press_x = None
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
@@ -4208,18 +4212,50 @@ class AudioWaveform(QWidget):
|
|||||||
return self._sel[0] # drag end; pivot on start
|
return self._sel[0] # drag end; pivot on start
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _selection_contains_x(self, x: float) -> bool:
|
||||||
|
if self._sel is None or self._view_dur <= 0:
|
||||||
|
return False
|
||||||
|
xs = self._t_to_px(self._sel[0])
|
||||||
|
xe = self._t_to_px(self._sel[1])
|
||||||
|
return min(xs, xe) <= x <= max(xs, xe)
|
||||||
|
|
||||||
|
def _begin_move_at_x(self, x: float) -> None:
|
||||||
|
if self._sel is None:
|
||||||
|
return
|
||||||
|
self._drag_mode = "move"
|
||||||
|
self._drag_anchor = self._px_to_t(x)
|
||||||
|
self._drag_orig_sel = self._sel
|
||||||
|
|
||||||
def _begin_drag_at_x(self, x: float) -> None:
|
def _begin_drag_at_x(self, x: float) -> None:
|
||||||
t = self._px_to_t(x)
|
t = self._px_to_t(x)
|
||||||
anchor = self._selection_handle_anchor(x)
|
anchor = self._selection_handle_anchor(x)
|
||||||
if anchor is None:
|
if anchor is None:
|
||||||
anchor = t
|
anchor = t
|
||||||
|
self._drag_mode = "select"
|
||||||
self._drag_anchor = anchor
|
self._drag_anchor = anchor
|
||||||
|
self._drag_orig_sel = self._sel
|
||||||
self._drag_to_x(x)
|
self._drag_to_x(x)
|
||||||
|
|
||||||
def _drag_to_x(self, x: float) -> None:
|
def _drag_to_x(self, x: float) -> None:
|
||||||
if self._drag_anchor is None:
|
if self._drag_anchor is None:
|
||||||
return
|
return
|
||||||
t = self._px_to_t(x)
|
t = self._px_to_t(x)
|
||||||
|
if self._drag_mode == "move" and self._drag_orig_sel is not None:
|
||||||
|
s0, e0 = self._drag_orig_sel
|
||||||
|
width = e0 - s0
|
||||||
|
delta = t - self._drag_anchor
|
||||||
|
lo = s0 + delta
|
||||||
|
hi = e0 + delta
|
||||||
|
vs, ve = self._view_start, self._view_start + self._view_dur
|
||||||
|
if self._view_dur > 0:
|
||||||
|
if lo < vs:
|
||||||
|
lo = vs
|
||||||
|
hi = lo + width
|
||||||
|
if hi > ve:
|
||||||
|
hi = ve
|
||||||
|
lo = hi - width
|
||||||
|
self.set_selection(lo, hi)
|
||||||
|
return
|
||||||
lo, hi = min(self._drag_anchor, t), max(self._drag_anchor, t)
|
lo, hi = min(self._drag_anchor, t), max(self._drag_anchor, t)
|
||||||
vs, ve = self._view_start, self._view_start + self._view_dur
|
vs, ve = self._view_start, self._view_start + self._view_dur
|
||||||
if self._view_dur > 0:
|
if self._view_dur > 0:
|
||||||
@@ -4236,6 +4272,8 @@ class AudioWaveform(QWidget):
|
|||||||
if self._drag_anchor is None:
|
if self._drag_anchor is None:
|
||||||
return
|
return
|
||||||
self._drag_anchor = None
|
self._drag_anchor = None
|
||||||
|
self._drag_mode = None
|
||||||
|
self._drag_orig_sel = None
|
||||||
if self._sel is not None:
|
if self._sel is not None:
|
||||||
self.selection_changed.emit(self._sel[0], self._sel[1])
|
self.selection_changed.emit(self._sel[0], self._sel[1])
|
||||||
|
|
||||||
@@ -4261,7 +4299,10 @@ class AudioWaveform(QWidget):
|
|||||||
if e.button() == Qt.MouseButton.LeftButton:
|
if e.button() == Qt.MouseButton.LeftButton:
|
||||||
x = e.position().x()
|
x = e.position().x()
|
||||||
self._press_x = x
|
self._press_x = x
|
||||||
if self._selection_handle_anchor(x) is not None:
|
if (e.modifiers() & Qt.KeyboardModifier.ControlModifier
|
||||||
|
and self._selection_contains_x(x)):
|
||||||
|
self._begin_move_at_x(x)
|
||||||
|
elif self._selection_handle_anchor(x) is not None:
|
||||||
self._begin_drag_at_x(x)
|
self._begin_drag_at_x(x)
|
||||||
|
|
||||||
def mouseMoveEvent(self, e):
|
def mouseMoveEvent(self, e):
|
||||||
@@ -4271,6 +4312,10 @@ class AudioWaveform(QWidget):
|
|||||||
elif (e.buttons() & Qt.MouseButton.LeftButton
|
elif (e.buttons() & Qt.MouseButton.LeftButton
|
||||||
and self._press_x is not None
|
and self._press_x is not None
|
||||||
and abs(x - self._press_x) >= self.CLICK_DRAG_PX):
|
and abs(x - self._press_x) >= self.CLICK_DRAG_PX):
|
||||||
|
if (e.modifiers() & Qt.KeyboardModifier.ControlModifier
|
||||||
|
and self._selection_contains_x(self._press_x)):
|
||||||
|
self._begin_move_at_x(self._press_x)
|
||||||
|
else:
|
||||||
self._begin_drag_at_x(self._press_x)
|
self._begin_drag_at_x(self._press_x)
|
||||||
self._drag_to_x(x)
|
self._drag_to_x(x)
|
||||||
|
|
||||||
|
|||||||
@@ -671,6 +671,43 @@ def test_waveform_click_sets_playhead_without_changing_selection(win):
|
|||||||
assert abs((w.playhead() or 0.0) - 7.0) < 0.05
|
assert abs((w.playhead() or 0.0) - 7.0) < 0.05
|
||||||
|
|
||||||
|
|
||||||
|
def test_waveform_ctrl_drag_moves_selection_without_moving_playhead(win):
|
||||||
|
from PyQt6.QtCore import QEvent, QPointF, Qt
|
||||||
|
from PyQt6.QtGui import QMouseEvent
|
||||||
|
w = win._wave
|
||||||
|
w.resize(400, 96)
|
||||||
|
w.set_view(0.0, 10.0)
|
||||||
|
w.set_selection(2.0, 4.0)
|
||||||
|
w.set_playhead(7.0)
|
||||||
|
got_sel = []
|
||||||
|
got_play = []
|
||||||
|
w.selection_changed.connect(lambda s, e: got_sel.append((s, e)))
|
||||||
|
w.playhead_changed.connect(lambda t: got_play.append(t))
|
||||||
|
|
||||||
|
press = QMouseEvent(
|
||||||
|
QEvent.Type.MouseButtonPress, QPointF(w._t_to_px(3.0), 20),
|
||||||
|
Qt.MouseButton.LeftButton, Qt.MouseButton.LeftButton,
|
||||||
|
Qt.KeyboardModifier.ControlModifier)
|
||||||
|
move = QMouseEvent(
|
||||||
|
QEvent.Type.MouseMove, QPointF(w._t_to_px(5.0), 20),
|
||||||
|
Qt.MouseButton.NoButton, Qt.MouseButton.LeftButton,
|
||||||
|
Qt.KeyboardModifier.ControlModifier)
|
||||||
|
release = QMouseEvent(
|
||||||
|
QEvent.Type.MouseButtonRelease, QPointF(w._t_to_px(5.0), 20),
|
||||||
|
Qt.MouseButton.LeftButton, Qt.MouseButton.NoButton,
|
||||||
|
Qt.KeyboardModifier.ControlModifier)
|
||||||
|
w.mousePressEvent(press)
|
||||||
|
w.mouseMoveEvent(move)
|
||||||
|
w.mouseReleaseEvent(release)
|
||||||
|
|
||||||
|
s, e = w.selection()
|
||||||
|
assert abs(s - 4.0) < 0.05
|
||||||
|
assert abs(e - 6.0) < 0.05
|
||||||
|
assert got_sel and abs(got_sel[-1][0] - 4.0) < 0.05
|
||||||
|
assert got_play == []
|
||||||
|
assert abs((w.playhead() or 0.0) - 7.0) < 0.05
|
||||||
|
|
||||||
|
|
||||||
def test_waveform_drag_emits_selection(win):
|
def test_waveform_drag_emits_selection(win):
|
||||||
w = win._wave
|
w = win._wave
|
||||||
w.resize(400, 96)
|
w.resize(400, 96)
|
||||||
|
|||||||
Reference in New Issue
Block a user