Filtering.

This commit is contained in:
Andrew Dunai 2018-01-14 20:14:56 +02:00
parent 4443667710
commit 3a3891e53f
3 changed files with 109 additions and 14 deletions

View file

@ -32,6 +32,7 @@ Documentation is ![available here](http://clay.readthedocs.io/en/latest/).
- Playlists
- Radio stations
- Song search
- Filtering results
- Queue management
- Notifications
- Global hotkeys

View file

@ -112,7 +112,7 @@ class AppWidget(urwid.Frame):
NotificationArea.set_app(self)
self._login_notification = None
self._popup = None
self._cancel_actions = []
self.header = urwid.Pile([
# urwid.Divider('\u2500'),
@ -304,12 +304,12 @@ class AppWidget(urwid.Frame):
if self.loop:
self.loop.draw_screen()
def register_popup(self, popup):
def append_cancel_action(self, action):
"""
Notify app about a popup that's currently being shown.
This is used to correctly deliver "Escape" key hit.
Notify app about an action that can be cancelled by adding it to the action stack.
It will be called once when "Escape" key is hit.
"""
self._popup = popup
self._cancel_actions.append(action)
def keypress(self, size, key):
"""
@ -339,11 +339,12 @@ class AppWidget(urwid.Frame):
elif key == 'ctrl x':
sys.exit(0)
elif key == 'esc' or key == 'ctrl _':
if self._popup:
self._popup.close()
self._popup = None
else:
try:
action = self._cancel_actions.pop()
except IndexError:
NotificationArea.close_newest()
else:
action()
else:
super(AppWidget, self).keypress(size, key)

View file

@ -2,6 +2,15 @@
Components for song listing.
"""
# pylint: disable=too-many-arguments
# pylint: disable=too-many-instance-attributes
from string import digits
try:
# Python 3.x
from string import ascii_letters
except ImportError:
# Python 2.3
from string import letters as ascii_letters
import urwid
from clay.notifications import NotificationArea
from clay.player import Player
@ -285,20 +294,78 @@ class SongListBox(urwid.Frame):
player.media_state_changed += self.media_state_changed
self.list_box = urwid.ListBox(self.walker)
self.filter_prefix = '> '
self.filter_query = ''
self.filter_box = urwid.Text(self.filter_prefix)
self.filter_info = urwid.Text('')
self.filter_panel = urwid.Columns([
self.filter_box,
('pack', self.filter_info)
])
self.content = urwid.Pile([
self.list_box,
])
self.overlay = urwid.Overlay(
top_w=None,
bottom_w=self.list_box,
bottom_w=self.content,
align='center',
valign='middle',
width=50,
height='pack'
)
self._is_filtering = False
super(SongListBox, self).__init__(
body=self.list_box
body=self.content
)
def perform_filtering(self, char):
"""
Enter filtering mode (if not entered yet) and filter stuff.
"""
if not self._is_filtering:
self.content.contents = [
(self.list_box, ('weight', 1)),
(self.filter_panel, ('pack', None))
]
self.app.append_cancel_action(self.end_filtering)
self.filter_query = ''
self._is_filtering = True
if char == 'backspace':
self.filter_query = self.filter_query[:-1]
else:
self.filter_query += char
self.filter_box.set_text(self.filter_prefix + self.filter_query)
matches = self.get_filtered_items()
self.filter_info.set_text('{} matches'.format(len(matches)))
if matches:
self.walker.set_focus(matches[0].index)
def get_filtered_items(self):
"""
Get song items that match the search query.
"""
matches = []
for songitem in self.walker:
if not isinstance(songitem, SongListItem):
continue
if self.filter_query.lower() in songitem.full_title.lower():
matches.append(songitem)
return matches
def end_filtering(self):
"""
Exit filtering mode.
"""
self.content.contents = [
(self.list_box, ('weight', 1))
]
self._is_filtering = False
def set_placeholder(self, text):
"""
Clear list and add one placeholder item.
@ -377,7 +444,7 @@ class SongListBox(urwid.Frame):
Show context menu.
"""
popup = SongListBoxPopup(songitem)
self.app.register_popup(popup)
self.app.append_cancel_action(popup.close)
self.overlay.top_w = popup
urwid.connect_signal(popup, 'close', self.hide_context_menu)
self.contents['body'] = (self.overlay, None)
@ -393,7 +460,7 @@ class SongListBox(urwid.Frame):
"""
Hide context menu.
"""
self.contents['body'] = (self.list_box, None)
self.contents['body'] = (self.content, None)
def track_changed(self, track):
"""
@ -468,9 +535,35 @@ class SongListBox(urwid.Frame):
songlistitem.set_index(i)
def keypress(self, size, key):
if key == 'meta m' and self.is_context_menu_visible:
if self._is_filtering and key in ('up', 'down', 'home', 'end'):
matches = self.get_filtered_items()
if not matches:
return False
_, index = self.walker.get_focus()
if key == 'home':
self.list_box.set_focus(matches[0].index, 'below')
elif key == 'end':
self.list_box.set_focus(matches[-1].index, 'above')
elif key == 'up':
prev_items = [item for item in matches if item.index < index]
if prev_items:
self.list_box.set_focus(prev_items[-1].index, 'below')
return False
self.list_box.set_focus(matches[-1].index, 'above')
else:
next_items = [item for item in matches if item.index > index]
if next_items:
self.list_box.set_focus(next_items[0].index, 'above')
return False
self.list_box.set_focus(matches[0].index, 'below')
return False
elif key == 'meta m' and self.is_context_menu_visible:
self.hide_context_menu()
return None
elif key in ascii_letters + digits + ' _-.,?!()[]/':
self.perform_filtering(key)
elif key == 'backspace':
self.perform_filtering(key)
return super(SongListBox, self).keypress(size, key)
def mouse_event(self, size, event, button, col, row, focus):