Пример #1
0
 def __init__(self,
              device_bank_registry=None,
              banking_info=None,
              device_component=None,
              delete_handler=None,
              chain_selection=None,
              bank_selection=None,
              move_device=None,
              track_list_component=None,
              *a,
              **k):
     assert device_bank_registry is not None
     assert device_component is not None
     assert chain_selection is not None
     assert bank_selection is not None
     assert move_device is not None
     assert track_list_component is not None
     self._flattened_chain = FlattenedDeviceChain()
     super(DeviceNavigationComponent,
           self).__init__(item_provider=self._flattened_chain, *a, **k)
     self._track_decorator = DecoratorFactory()
     self._device_component = device_component
     self.__on_device_changed.subject = device_component
     self._device_bank_registry = device_bank_registry
     self._delete_handler = delete_handler
     self._chain_selection = self.register_component(chain_selection)
     self._bank_selection = self.register_component(bank_selection)
     self._move_device = self.register_component(move_device)
     self._last_pressed_button_index = -1
     self._selected_on_previous_press = None
     self._modes = self.register_component(ModesComponent())
     self._modes.add_mode(u'default', [
         partial(self._chain_selection.set_parent, None),
         partial(self._bank_selection.set_device, None)
     ])
     self._modes.add_mode(u'chain_selection', [self._chain_selection])
     self._modes.add_mode(u'bank_selection', [self._bank_selection])
     self._modes.selected_mode = u'default'
     self.register_disconnectable(self._flattened_chain)
     self.__on_items_changed.subject = self
     self.__on_bank_selection_closed.subject = self._bank_selection
     self._on_selected_track_changed()
     self._on_selected_track_changed.subject = self.song.view
     self._track_list = track_list_component
     watcher = self.register_disconnectable(
         DeviceChainStateWatcher(device_navigation=self))
     self.__on_device_item_state_changed.subject = watcher
     self.__on_device_changed()
     self._update_button_colors()
     return
Пример #2
0
 def __init__(self, device_bank_registry = None, banking_info = None, device_component = None, delete_handler = None, chain_selection = None, bank_selection = None, move_device = None, track_list_component = None, *a, **k):
     raise device_bank_registry is not None or AssertionError
     raise device_component is not None or AssertionError
     raise chain_selection is not None or AssertionError
     raise bank_selection is not None or AssertionError
     raise move_device is not None or AssertionError
     raise track_list_component is not None or AssertionError
     self._flattened_chain = FlattenedDeviceChain()
     super(DeviceNavigationComponent, self).__init__(item_provider=self._flattened_chain, *a, **k)
     self._track_decorator = DecoratorFactory()
     self._device_component = device_component
     self.__on_device_changed.subject = device_component
     self._device_bank_registry = device_bank_registry
     self._delete_handler = delete_handler
     self._chain_selection = self.register_component(chain_selection)
     self._bank_selection = self.register_component(bank_selection)
     self._move_device = self.register_component(move_device)
     self._last_pressed_button_index = -1
     self._selected_on_previous_press = None
     self._modes = self.register_component(ModesComponent())
     self._modes.add_mode('default', [partial(self._chain_selection.set_parent, None), partial(self._bank_selection.set_device, None)])
     self._modes.add_mode('chain_selection', [self._chain_selection])
     self._modes.add_mode('bank_selection', [self._bank_selection])
     self._modes.selected_mode = 'default'
     self.register_disconnectable(self._flattened_chain)
     self.__on_items_changed.subject = self
     self.__on_bank_selection_closed.subject = self._bank_selection
     self._on_selected_track_changed()
     self._on_selected_track_changed.subject = self.song.view
     self._track_list = track_list_component
     watcher = self.register_disconnectable(DeviceChainStateWatcher(device_navigation=self))
     self.__on_device_item_state_changed.subject = watcher
     self.__on_device_changed()
     self._update_button_colors()
Пример #3
0
class DeviceNavigationComponent(ItemListerComponent):
    __events__ = (u'drum_pad_selection',
                  u'mute_solo_stop_cancel_action_performed')

    def __init__(self,
                 device_bank_registry=None,
                 banking_info=None,
                 device_component=None,
                 delete_handler=None,
                 chain_selection=None,
                 bank_selection=None,
                 move_device=None,
                 track_list_component=None,
                 *a,
                 **k):
        assert device_bank_registry is not None
        assert device_component is not None
        assert chain_selection is not None
        assert bank_selection is not None
        assert move_device is not None
        assert track_list_component is not None
        self._flattened_chain = FlattenedDeviceChain()
        super(DeviceNavigationComponent,
              self).__init__(item_provider=self._flattened_chain, *a, **k)
        self._track_decorator = DecoratorFactory()
        self._device_component = device_component
        self.__on_device_changed.subject = device_component
        self._device_bank_registry = device_bank_registry
        self._delete_handler = delete_handler
        self._chain_selection = self.register_component(chain_selection)
        self._bank_selection = self.register_component(bank_selection)
        self._move_device = self.register_component(move_device)
        self._last_pressed_button_index = -1
        self._selected_on_previous_press = None
        self._modes = self.register_component(ModesComponent())
        self._modes.add_mode(u'default', [
            partial(self._chain_selection.set_parent, None),
            partial(self._bank_selection.set_device, None)
        ])
        self._modes.add_mode(u'chain_selection', [self._chain_selection])
        self._modes.add_mode(u'bank_selection', [self._bank_selection])
        self._modes.selected_mode = u'default'
        self.register_disconnectable(self._flattened_chain)
        self.__on_items_changed.subject = self
        self.__on_bank_selection_closed.subject = self._bank_selection
        self._on_selected_track_changed()
        self._on_selected_track_changed.subject = self.song.view
        self._track_list = track_list_component
        watcher = self.register_disconnectable(
            DeviceChainStateWatcher(device_navigation=self))
        self.__on_device_item_state_changed.subject = watcher
        self.__on_device_changed()
        self._update_button_colors()
        return

    @property
    def modes(self):
        return self._modes

    def _in_device_enabling_mode(self):
        return self._track_list.selected_mode == u'mute'

    def _on_select_button_pressed(self, button):
        device_or_pad = self.items[button.index].item
        if self._in_device_enabling_mode():
            self._toggle_device(device_or_pad)
            self.notify_mute_solo_stop_cancel_action_performed()
        else:
            self._last_pressed_button_index = button.index
            if not self._delete_handler or not self._delete_handler.is_deleting:
                self._selected_on_previous_press = device_or_pad if self.selected_object != device_or_pad else None
                self._select_item(device_or_pad)
        return

    def _on_select_button_released_immediately(self, button):
        if not self._in_device_enabling_mode():
            self._last_pressed_button_index = -1
            device_or_pad = self.items[button.index].item
            if self._delete_handler and self._delete_handler.is_deleting:
                self._delete_item(device_or_pad)
            elif self.selected_object == device_or_pad and device_or_pad != self._selected_on_previous_press:
                self._on_reselecting_object(device_or_pad)
            self._selected_on_previous_press = None
        return

    def _on_select_button_pressed_delayed(self, button):
        if not self._in_device_enabling_mode():
            self._on_pressed_delayed(self.items[button.index].item)

    def _on_select_button_released(self, button):
        if button.index == self._last_pressed_button_index:
            self._modes.selected_mode = u'default'
            self._last_pressed_button_index = -1
            self._end_move_device()

    @dispatch(Live.DrumPad.DrumPad)
    def _toggle_device(self, drum_pad):
        if liveobj_valid(drum_pad):
            drum_pad.mute = not drum_pad.mute

    @dispatch(object)
    def _toggle_device(self, device):
        if liveobj_valid(device) and device.parameters[0].is_enabled:
            set_enabled(device, not is_on(device))

    @listens(u'state')
    def __on_device_item_state_changed(self):
        self._update_button_colors()

    @listens(u'items')
    def __on_items_changed(self):
        new_items = map(lambda x: x.item, self.items)
        lost_selection_on_empty_pad = new_items and is_drum_pad(
            new_items[-1]
        ) and self._flattened_chain.selected_item not in new_items
        if self._should_select_drum_pad() or lost_selection_on_empty_pad:
            self._select_item(self._current_drum_pad())
        if self.moving:
            self._show_selected_item()
        self.notify_drum_pad_selection()

    @listenable_property
    def moving(self):
        return self._move_device.is_enabled()

    @property
    def device_selection_update_allowed(self):
        return not self._should_select_drum_pad()

    def _color_for_button(self, button_index, is_selected):
        item = self.items[button_index]
        device_or_pad = item.item
        is_active = liveobj_valid(device_or_pad) and is_active_element(
            device_or_pad)
        chain = find_chain_or_track(device_or_pad)
        if not is_active:
            return u'DefaultButton.Off'
        elif is_selected:
            return u'ItemNavigation.ItemSelected'
        elif liveobj_valid(chain):
            return IndexedColor.from_live_index(chain.color_index,
                                                DISPLAY_BUTTON_SHADE_LEVEL)
        else:
            return u'ItemNavigation.ItemNotSelected'

    def _begin_move_device(self, device):
        if not self._move_device.is_enabled(
        ) and device.type != Live.Device.DeviceType.instrument:
            self._move_device.set_device(device)
            self._move_device.set_enabled(True)
            self._scroll_overlay.set_enabled(False)
            self.notify_moving()

    def _end_move_device(self):
        if self._move_device.is_enabled():
            self._move_device.set_device(None)
            self._move_device.set_enabled(False)
            self._scroll_overlay.set_enabled(True)
            self.notify_moving()
        return

    def _show_selected_item(self):
        selected_item = self.item_provider.selected_item
        if selected_item is not None:
            items = self.item_provider.items
            if len(items) > self._num_visible_items:
                selected_index = index_if(lambda i: i[0] == selected_item,
                                          items)
                if selected_index >= self._num_visible_items + self.item_offset - 1 and selected_index < len(
                        items) - 1:
                    self.item_offset = selected_index - self._num_visible_items + 2
                elif selected_index > 0 and selected_index <= self.item_offset:
                    self.item_offset = selected_index - 1
        return

    def request_drum_pad_selection(self):
        self._current_track().drum_pad_selected = True

    def unfold_current_drum_pad(self):
        self._current_track().drum_pad_selected = False
        self._current_drum_pad(
        ).canonical_parent.view.is_showing_chain_devices = True

    def sync_selection_to_selected_device(self):
        self._update_item_provider(
            self.song.view.selected_track.view.selected_device)

    @property
    def is_drum_pad_selected(self):
        return is_drum_pad(self._flattened_chain.selected_item)

    @property
    def is_drum_pad_unfolded(self):
        selection = self._flattened_chain.selected_item
        assert is_drum_pad(selection)
        return drum_rack_for_pad(selection).view.is_showing_chain_devices

    def _current_track(self):
        return self._track_decorator.decorate(
            self.song.view.selected_track,
            additional_properties={u'drum_pad_selected': False})

    def _should_select_drum_pad(self):
        return self._current_track().drum_pad_selected

    def _current_drum_pad(self):
        return find_drum_pad(self.items)

    @listens(u'selected_track')
    def _on_selected_track_changed(self):
        self._selected_track = self.song.view.selected_track
        selected_track = self._current_track()
        self.reset_offset()
        self._flattened_chain.set_device_parent(selected_track)
        self._device_selection_in_track_changed.subject = selected_track.view
        self._modes.selected_mode = u'default'
        self._end_move_device()
        self._restore_selection(selected_track)

    def _restore_selection(self, selected_track):
        to_select = None
        if self._should_select_drum_pad():
            to_select = self._current_drum_pad()
        if to_select == None:
            to_select = selected_track.view.selected_device
        self._select_item(to_select)
        return

    def back_to_top(self):
        pass

    @property
    def selected_object(self):
        selected_item = self.item_provider.selected_item
        return getattr(selected_item, u'proxied_object', selected_item)

    def _select_item(self, device_or_pad):
        if device_or_pad:
            self._do_select_item(device_or_pad)
        self._update_item_provider(device_or_pad)

    @dispatch(Live.DrumPad.DrumPad)
    def _do_select_item(self, pad):
        self._current_track().drum_pad_selected = True
        device = self._first_device_on_pad(pad)
        self._appoint_device(device)

    def _first_device_on_pad(self, drum_pad):
        chain = drum_rack_for_pad(drum_pad).view.selected_chain
        if chain and chain.devices:
            return first(chain.devices)
        else:
            return None

    def _appoint_device(self, device):
        if self._device_component.device_changed(device):
            self._device_component.set_device(device)

    @dispatch(object)
    def _do_select_item(self, device):
        self._current_track().drum_pad_selected = False
        appointed_device = device_to_appoint(device)
        self._appoint_device(appointed_device)
        self.song.view.select_device(device, False)
        self.song.appointed_device = appointed_device

    @dispatch(Live.DrumPad.DrumPad)
    def _on_reselecting_object(self, drum_pad):
        rack = drum_rack_for_pad(drum_pad)
        self._toggle(rack)
        if rack.view.is_showing_chain_devices:
            first_device = self._first_device_on_pad(drum_pad)
            if first_device:
                self._select_item(first_device)
        self.notify_drum_pad_selection()

    @dispatch(object)
    def _on_reselecting_object(self, device):
        if liveobj_valid(device) and device.can_have_chains:
            if not device.can_have_drum_pads:
                self._toggle(device)
        else:
            self._bank_selection.set_device(device)
            self._modes.selected_mode = u'bank_selection'

    @dispatch(Live.DrumPad.DrumPad)
    def _on_pressed_delayed(self, _):
        pass

    @dispatch(object)
    def _on_pressed_delayed(self, device):
        self._show_chains(device)
        self._begin_move_device(device)

    @dispatch(Live.DrumPad.DrumPad)
    def _delete_item(self, pad):
        pass

    @dispatch(object)
    def _delete_item(self, device):
        delete_device(device)

    def _show_chains(self, device):
        if device.can_have_chains:
            self._chain_selection.set_parent(device)
            self._modes.selected_mode = u'chain_selection'

    @listens(u'back')
    def __on_bank_selection_closed(self):
        self._modes.selected_mode = u'default'

    @listens(u'device')
    def __on_device_changed(self):
        if not self._should_select_drum_pad(
        ) and not self._is_drum_rack_selected():
            self._modes.selected_mode = u'default'
            self._update_item_provider(self._device_component.device())

    def _is_drum_rack_selected(self):
        selected_item = self._flattened_chain.selected_item
        instrument = self._find_top_level_instrument()
        return liveobj_valid(selected_item) and isinstance(
            selected_item, Live.RackDevice.RackDevice
        ) and selected_item.can_have_drum_pads and not liveobj_changed(
            selected_item, instrument)

    def _find_top_level_instrument(self):
        return find_if(
            lambda device: device.type == Live.Device.DeviceType.instrument,
            self._current_track().devices)

    @listens(u'selected_device')
    def _device_selection_in_track_changed(self):
        new_selection = self.song.view.selected_track.view.selected_device
        if self._can_update_device_selection(new_selection):
            self._modes.selected_mode = u'default'
            self._update_item_provider(new_selection)

    def _toggle(self, item):
        view = item.view
        if view.is_collapsed:
            view.is_collapsed = False
            view.is_showing_chain_devices = True
        else:
            view.is_showing_chain_devices = not view.is_showing_chain_devices

    def _can_update_device_selection(self, new_selection):
        can_update = liveobj_valid(new_selection)
        drum_pad_selected_or_requested = self.is_drum_pad_selected or self._should_select_drum_pad(
        )
        if can_update and drum_pad_selected_or_requested:
            if is_empty_rack(new_selection):
                can_update = False
            if can_update and self.is_drum_pad_selected:
                can_update = not is_first_device_on_pad(
                    new_selection, self._flattened_chain.selected_item)
        elif not can_update and not drum_pad_selected_or_requested:
            can_update = True
        return can_update

    def _update_item_provider(self, selection):
        self._flattened_chain.selected_item = selection
        if not is_drum_pad(selection):
            self._current_track().drum_pad_selected = False
        self.notify_drum_pad_selection()
Пример #4
0
class DeviceNavigationComponent(ItemListerComponent):
    __events__ = ('drum_pad_selection', 'mute_solo_stop_cancel_action_performed')

    def __init__(self, device_bank_registry = None, banking_info = None, device_component = None, delete_handler = None, chain_selection = None, bank_selection = None, move_device = None, track_list_component = None, *a, **k):
        raise device_bank_registry is not None or AssertionError
        raise device_component is not None or AssertionError
        raise chain_selection is not None or AssertionError
        raise bank_selection is not None or AssertionError
        raise move_device is not None or AssertionError
        raise track_list_component is not None or AssertionError
        self._flattened_chain = FlattenedDeviceChain()
        super(DeviceNavigationComponent, self).__init__(item_provider=self._flattened_chain, *a, **k)
        self._track_decorator = DecoratorFactory()
        self._device_component = device_component
        self.__on_device_changed.subject = device_component
        self._device_bank_registry = device_bank_registry
        self._delete_handler = delete_handler
        self._chain_selection = self.register_component(chain_selection)
        self._bank_selection = self.register_component(bank_selection)
        self._move_device = self.register_component(move_device)
        self._last_pressed_button_index = -1
        self._selected_on_previous_press = None
        self._modes = self.register_component(ModesComponent())
        self._modes.add_mode('default', [partial(self._chain_selection.set_parent, None), partial(self._bank_selection.set_device, None)])
        self._modes.add_mode('chain_selection', [self._chain_selection])
        self._modes.add_mode('bank_selection', [self._bank_selection])
        self._modes.selected_mode = 'default'
        self.register_disconnectable(self._flattened_chain)
        self.__on_items_changed.subject = self
        self.__on_bank_selection_closed.subject = self._bank_selection
        self._on_selected_track_changed()
        self._on_selected_track_changed.subject = self.song.view
        self._track_list = track_list_component
        watcher = self.register_disconnectable(DeviceChainStateWatcher(device_navigation=self))
        self.__on_device_item_state_changed.subject = watcher
        self.__on_device_changed()
        self._update_button_colors()

    @property
    def modes(self):
        return self._modes

    def _in_device_enabling_mode(self):
        return self._track_list.selected_mode == 'mute'

    def _on_select_button_pressed(self, button):
        device_or_pad = self.items[button.index].item
        if self._in_device_enabling_mode():
            self._toggle_device(device_or_pad)
            self.notify_mute_solo_stop_cancel_action_performed()
        else:
            self._last_pressed_button_index = button.index
            if not self._delete_handler or not self._delete_handler.is_deleting:
                self._selected_on_previous_press = device_or_pad if self.selected_object != device_or_pad else None
                self._select_item(device_or_pad)

    def _on_select_button_released_immediately(self, button):
        if not self._in_device_enabling_mode():
            self._last_pressed_button_index = -1
            device_or_pad = self.items[button.index].item
            if self._delete_handler and self._delete_handler.is_deleting:
                self._delete_item(device_or_pad)
            elif self.selected_object == device_or_pad and device_or_pad != self._selected_on_previous_press:
                self._on_reselecting_object(device_or_pad)
            self._selected_on_previous_press = None

    def _on_select_button_pressed_delayed(self, button):
        if not self._in_device_enabling_mode():
            self._on_pressed_delayed(self.items[button.index].item)

    def _on_select_button_released(self, button):
        if button.index == self._last_pressed_button_index:
            self._modes.selected_mode = 'default'
            self._last_pressed_button_index = -1
            self._end_move_device()

    @dispatch(Live.DrumPad.DrumPad)
    def _toggle_device(self, drum_pad):
        if liveobj_valid(drum_pad):
            drum_pad.mute = not drum_pad.mute

    @dispatch(object)
    def _toggle_device(self, device):
        if liveobj_valid(device) and device.parameters[0].is_enabled:
            set_enabled(device, not is_on(device))

    @listens('state')
    def __on_device_item_state_changed(self):
        self._update_button_colors()

    @listens('items')
    def __on_items_changed(self):
        new_items = map(lambda x: x.item, self.items)
        lost_selection_on_empty_pad = new_items and is_drum_pad(new_items[-1]) and self._flattened_chain.selected_item not in new_items
        if self._should_select_drum_pad() or lost_selection_on_empty_pad:
            self._select_item(self._current_drum_pad())
        if self.moving:
            self._show_selected_item()
        self.notify_drum_pad_selection()

    @listenable_property
    def moving(self):
        return self._move_device.is_enabled()

    @property
    def device_selection_update_allowed(self):
        return not self._should_select_drum_pad()

    def _color_for_button(self, button_index, is_selected):
        item = self.items[button_index]
        device_or_pad = item.item
        is_active = liveobj_valid(device_or_pad) and is_active_element(device_or_pad)
        chain = find_chain_or_track(device_or_pad)
        if not is_active:
            return 'DefaultButton.Off'
        elif is_selected:
            return 'ItemNavigation.ItemSelected'
        elif liveobj_valid(chain):
            return IndexedColor.from_live_index(chain.color_index, DISPLAY_BUTTON_SHADE_LEVEL)
        else:
            return 'ItemNavigation.ItemNotSelected'

    def _begin_move_device(self, device):
        if not self._move_device.is_enabled() and device.type != Live.Device.DeviceType.instrument:
            self._move_device.set_device(device)
            self._move_device.set_enabled(True)
            self._scroll_overlay.set_enabled(False)
            self.notify_moving()

    def _end_move_device(self):
        if self._move_device.is_enabled():
            self._move_device.set_device(None)
            self._move_device.set_enabled(False)
            self._scroll_overlay.set_enabled(True)
            self.notify_moving()

    def _show_selected_item(self):
        selected_item = self.item_provider.selected_item
        if selected_item is not None:
            items = self.item_provider.items
            if len(items) > self._num_visible_items:
                selected_index = index_if(lambda i: i[0] == selected_item, items)
                if selected_index >= self._num_visible_items + self.item_offset - 1 and selected_index < len(items) - 1:
                    self.item_offset = selected_index - self._num_visible_items + 2
                elif selected_index > 0 and selected_index <= self.item_offset:
                    self.item_offset = selected_index - 1

    def request_drum_pad_selection(self):
        self._current_track().drum_pad_selected = True

    def unfold_current_drum_pad(self):
        self._current_track().drum_pad_selected = False
        self._current_drum_pad().canonical_parent.view.is_showing_chain_devices = True

    def sync_selection_to_selected_device(self):
        self._update_item_provider(self.song.view.selected_track.view.selected_device)

    @property
    def is_drum_pad_selected(self):
        return is_drum_pad(self._flattened_chain.selected_item)

    @property
    def is_drum_pad_unfolded(self):
        selection = self._flattened_chain.selected_item
        raise is_drum_pad(selection) or AssertionError
        return drum_rack_for_pad(selection).view.is_showing_chain_devices

    def _current_track(self):
        return self._track_decorator.decorate(self.song.view.selected_track, additional_properties={'drum_pad_selected': False})

    def _should_select_drum_pad(self):
        return self._current_track().drum_pad_selected

    def _current_drum_pad(self):
        return find_drum_pad(self.items)

    @listens('selected_track')
    def _on_selected_track_changed(self):
        self._selected_track = self.song.view.selected_track
        selected_track = self._current_track()
        self.reset_offset()
        self._flattened_chain.set_device_parent(selected_track)
        self._device_selection_in_track_changed.subject = selected_track.view
        self._modes.selected_mode = 'default'
        self._end_move_device()
        self._restore_selection(selected_track)

    def _restore_selection(self, selected_track):
        to_select = None
        if self._should_select_drum_pad():
            to_select = self._current_drum_pad()
        if to_select == None:
            to_select = selected_track.view.selected_device
        self._select_item(to_select)

    def back_to_top(self):
        pass

    @property
    def selected_object(self):
        selected_item = self.item_provider.selected_item
        return getattr(selected_item, 'proxied_object', selected_item)

    def _select_item(self, device_or_pad):
        if device_or_pad:
            self._do_select_item(device_or_pad)
        self._update_item_provider(device_or_pad)

    @dispatch(Live.DrumPad.DrumPad)
    def _do_select_item(self, pad):
        self._current_track().drum_pad_selected = True
        device = self._first_device_on_pad(pad)
        self._appoint_device(device)

    def _first_device_on_pad(self, drum_pad):
        chain = drum_rack_for_pad(drum_pad).view.selected_chain
        if chain and chain.devices:
            return first(chain.devices)

    def _appoint_device(self, device):
        if self._device_component._device_changed(device):
            self._device_component.set_device(device)

    @dispatch(object)
    def _do_select_item(self, device):
        self._current_track().drum_pad_selected = False
        appointed_device = device_to_appoint(device)
        self._appoint_device(appointed_device)
        self.song.view.select_device(device, False)
        self.song.appointed_device = appointed_device

    @dispatch(Live.DrumPad.DrumPad)
    def _on_reselecting_object(self, drum_pad):
        rack = drum_rack_for_pad(drum_pad)
        self._toggle(rack)
        if rack.view.is_showing_chain_devices:
            first_device = self._first_device_on_pad(drum_pad)
            if first_device:
                self._select_item(first_device)
        self.notify_drum_pad_selection()

    @dispatch(object)
    def _on_reselecting_object(self, device):
        if liveobj_valid(device) and device.can_have_chains:
            if not device.can_have_drum_pads:
                self._toggle(device)
        else:
            self._bank_selection.set_device(device)
            self._modes.selected_mode = 'bank_selection'

    @dispatch(Live.DrumPad.DrumPad)
    def _on_pressed_delayed(self, _):
        pass

    @dispatch(object)
    def _on_pressed_delayed(self, device):
        self._show_chains(device)
        self._begin_move_device(device)

    @dispatch(Live.DrumPad.DrumPad)
    def _delete_item(self, pad):
        pass

    @dispatch(object)
    def _delete_item(self, device):
        delete_device(device)

    def _show_chains(self, device):
        if device.can_have_chains:
            self._chain_selection.set_parent(device)
            self._modes.selected_mode = 'chain_selection'

    @listens('back')
    def __on_bank_selection_closed(self):
        self._modes.selected_mode = 'default'

    @listens('device')
    def __on_device_changed(self):
        if not self._should_select_drum_pad() and not self._is_drum_rack_selected():
            self._modes.selected_mode = 'default'
            self._update_item_provider(self._device_component.device())

    def _is_drum_rack_selected(self):
        selected_item = self._flattened_chain.selected_item
        instrument = self._find_top_level_instrument()
        return liveobj_valid(selected_item) and isinstance(selected_item, Live.RackDevice.RackDevice) and selected_item.can_have_drum_pads and not liveobj_changed(selected_item, instrument)

    def _find_top_level_instrument(self):
        return find_if(lambda device: device.type == Live.Device.DeviceType.instrument, self._current_track().devices)

    @listens('selected_device')
    def _device_selection_in_track_changed(self):
        new_selection = self.song.view.selected_track.view.selected_device
        if self._can_update_device_selection(new_selection):
            self._modes.selected_mode = 'default'
            self._update_item_provider(new_selection)

    def _toggle(self, item):
        view = item.view
        if view.is_collapsed:
            view.is_collapsed = False
            view.is_showing_chain_devices = True
        else:
            view.is_showing_chain_devices = not view.is_showing_chain_devices

    def _can_update_device_selection(self, new_selection):
        can_update = liveobj_valid(new_selection)
        drum_pad_selected_or_requested = self.is_drum_pad_selected or self._should_select_drum_pad()
        if can_update and drum_pad_selected_or_requested:
            if is_empty_rack(new_selection):
                can_update = False
            if can_update and self.is_drum_pad_selected:
                can_update = not is_first_device_on_pad(new_selection, self._flattened_chain.selected_item)
        elif not can_update and not drum_pad_selected_or_requested:
            can_update = True
        return can_update

    def _update_item_provider(self, selection):
        self._flattened_chain.selected_item = selection
        if not is_drum_pad(selection):
            self._current_track().drum_pad_selected = False
        self.notify_drum_pad_selection()