Files
ableton-osc/AbletonOSC/view.py
T
valknar 6fc0a84dfe Fix song/application access: property not method call
ControlSurface exposes song and application as properties, not callable
methods. Replace manager.song() and manager.application() with the correct
attribute access throughout handler, application, browser, and view modules.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 13:27:55 +02:00

217 lines
7.8 KiB
Python

"""Handles /live/view/* OSC addresses (UI navigation)."""
import logging
from typing import Optional
from .handler import AbletonOSCHandler
logger = logging.getLogger(__name__)
class ViewHandler(AbletonOSCHandler):
def init_api(self) -> None:
self.clear_listeners()
view = self._song_view()
# selected scene
self._add("/live/view/get/selected_scene", self._get_selected_scene)
self._add("/live/view/set/selected_scene", self._set_selected_scene)
self._add("/live/view/start_listen/selected_scene", self._start_listen_selected_scene)
self._add("/live/view/stop_listen/selected_scene", self._stop_listen_selected_scene)
# selected track
self._add("/live/view/get/selected_track", self._get_selected_track)
self._add("/live/view/set/selected_track", self._set_selected_track)
self._add("/live/view/start_listen/selected_track", self._start_listen_selected_track)
self._add("/live/view/stop_listen/selected_track", self._stop_listen_selected_track)
# selected clip
self._add("/live/view/get/selected_clip", self._get_selected_clip)
self._add("/live/view/set/selected_clip", self._set_selected_clip)
# selected device
self._add("/live/view/get/selected_device", self._get_selected_device)
# focus
self._add("/live/view/show_clip_detail_view", self._show_clip_detail_view)
self._add("/live/view/show_device_detail_view", self._show_device_detail_view)
self._add("/live/view/focus_browser", self._focus_browser)
# arrangement / session toggle
self._add("/live/view/get/focused_document_view", self._get_focused_document_view)
# ------------------------------------------------------------------
def _song_view(self):
try:
return self.song.view
except Exception:
return None
def _get_selected_scene(self, params: tuple) -> Optional[tuple]:
try:
view = self._song_view()
scenes = list(self.song.scenes)
scene = view.selected_scene
idx = scenes.index(scene)
return (idx,)
except Exception as e:
logger.warning("get selected_scene: %s", e)
return None
def _set_selected_scene(self, params: tuple) -> None:
if not params:
return None
try:
idx = int(params[0])
scenes = list(self.song.scenes)
if 0 <= idx < len(scenes):
self._song_view().selected_scene = scenes[idx]
except Exception as e:
logger.warning("set selected_scene: %s", e)
return None
def _start_listen_selected_scene(self, params: tuple) -> None:
view = self._song_view()
if view is None:
return
key = "view.selected_scene"
self._remove_listener(key)
def cb():
result = self._get_selected_scene(())
if result:
self._send("/live/view/get/selected_scene", result)
try:
view.add_selected_scene_listener(cb)
self._listener_store[key] = (view.remove_selected_scene_listener, cb)
cb()
except Exception as e:
logger.warning("selected_scene listener: %s", e)
def _stop_listen_selected_scene(self, params: tuple) -> None:
self._remove_listener("view.selected_scene")
def _get_selected_track(self, params: tuple) -> Optional[tuple]:
try:
view = self._song_view()
all_tracks = list(self.song.tracks) + list(self.song.return_tracks)
track = view.selected_track
idx = all_tracks.index(track)
return (idx,)
except Exception as e:
logger.warning("get selected_track: %s", e)
return None
def _set_selected_track(self, params: tuple) -> None:
if not params:
return None
try:
idx = int(params[0])
all_tracks = list(self.song.tracks) + list(self.song.return_tracks)
if 0 <= idx < len(all_tracks):
self._song_view().selected_track = all_tracks[idx]
except Exception as e:
logger.warning("set selected_track: %s", e)
return None
def _start_listen_selected_track(self, params: tuple) -> None:
view = self._song_view()
if view is None:
return
key = "view.selected_track"
self._remove_listener(key)
def cb():
result = self._get_selected_track(())
if result:
self._send("/live/view/get/selected_track", result)
try:
view.add_selected_track_listener(cb)
self._listener_store[key] = (view.remove_selected_track_listener, cb)
cb()
except Exception as e:
logger.warning("selected_track listener: %s", e)
def _stop_listen_selected_track(self, params: tuple) -> None:
self._remove_listener("view.selected_track")
def _get_selected_clip(self, params: tuple) -> Optional[tuple]:
try:
view = self._song_view()
clip_view = view.detail_clip
if clip_view is None:
return None
tracks = list(self.song.tracks)
for ti, track in enumerate(tracks):
for ci, slot in enumerate(track.clip_slots):
if slot.has_clip and slot.clip == clip_view:
return (ti, ci)
except Exception as e:
logger.warning("get selected_clip: %s", e)
return None
def _set_selected_clip(self, params: tuple) -> None:
if len(params) < 2:
return None
try:
track_idx = int(params[0])
clip_idx = int(params[1])
tracks = list(self.song.tracks)
if 0 <= track_idx < len(tracks):
slots = list(tracks[track_idx].clip_slots)
if 0 <= clip_idx < len(slots) and slots[clip_idx].has_clip:
self._song_view().detail_clip = slots[clip_idx].clip
except Exception as e:
logger.warning("set selected_clip: %s", e)
return None
def _get_selected_device(self, params: tuple) -> Optional[tuple]:
try:
view = self._song_view()
track = view.selected_track
tracks = list(self.song.tracks) + list(self.song.return_tracks)
track_idx = tracks.index(track)
selected = track.view.selected_device
if selected is None:
return None
devices = list(track.devices)
device_idx = devices.index(selected)
return (track_idx, device_idx)
except Exception as e:
logger.warning("get selected_device: %s", e)
return None
def _show_clip_detail_view(self, params: tuple) -> None:
try:
app = self.manager.application
app.view.show_view("Detail/Clip")
except Exception as e:
logger.warning("show_clip_detail_view: %s", e)
return None
def _show_device_detail_view(self, params: tuple) -> None:
try:
app = self.manager.application
app.view.show_view("Detail/DeviceChain")
except Exception as e:
logger.warning("show_device_detail_view: %s", e)
return None
def _focus_browser(self, params: tuple) -> None:
try:
app = self.manager.application
app.view.show_view("Browser")
except Exception as e:
logger.warning("focus_browser: %s", e)
return None
def _get_focused_document_view(self, params: tuple) -> Optional[tuple]:
try:
app = self.manager.application
view = app.view.focused_document_view
return (view,)
except Exception as e:
logger.warning("get focused_document_view: %s", e)
return None