Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development #41

Merged
merged 3 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lyrics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

CONFIG_PATH = Path.home().joinpath('.config', 'lyrics-in-terminal','lyrics.cfg')

__version__ = '1.5.1-dev'
__version__ = '1.6.0-dev'

if not CONFIG_PATH.exists():
from shutil import copy
Expand Down
4 changes: 4 additions & 0 deletions lyrics/lyrics.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mpd_port=6600
mpd_pass=
#colors
#offset=1
statusbar=on

[BINDINGS]
up=arrow_up
Expand All @@ -31,6 +32,9 @@ autoswitchtoggle=a

delete=d
edit=e
find=/
find-next=n
find-prev=p

help=h

Expand Down
158 changes: 153 additions & 5 deletions lyrics/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def input(self, window, key):

elif key == self.binds['delete']:
if window.player.track.delete_lyrics():
window.stdscr.addstr(window.height - 1, window.width - 10,
window.stdscr.addstr(window.height - 1, 1,
' Deleted ', curses.A_REVERSE)
elif key == self.binds['help']:
window.stdscr.erase()
Expand All @@ -64,11 +64,13 @@ def input(self, window, key):
window.current_pos = 0
window.player.refresh(cache=True)
window.update_track()
elif key == self.binds['find']:
window.find()

# autoswitch toggle
elif key == self.binds['autoswitchtoggle']:
window.player.autoswitch = not window.player.autoswitch
window.stdscr.addstr(window.height - 1, window.width - 18,
window.stdscr.addstr(window.height - 1, 1,
f" Autoswitch: {'on' if window.player.autoswitch else 'off'} ", curses.A_REVERSE)

class HelpPage:
Expand Down Expand Up @@ -134,6 +136,7 @@ def main(self):

class Window:
def __init__(self, stdscr, player, timeout):
self.options = Config('OPTIONS')
self.stdscr = stdscr
self.height, self.width = stdscr.getmaxyx()
self.player = player
Expand All @@ -143,9 +146,11 @@ def __init__(self, stdscr, player, timeout):
self.pad_offset = 1
self.text_padding = 5
self.keys = Key()
self.find_position = 0
self.timeout = timeout

curses.use_default_colors()
self.stdscr.timeout(timeout)
self.stdscr.timeout(self.timeout)
self.set_up()

def set_up(self):
Expand All @@ -170,7 +175,15 @@ def set_titlebar(self):
self.stdscr.addstr(1, 1, track_info[1],
curses.A_REVERSE | curses.A_BOLD | curses.A_DIM)
self.stdscr.addstr(2, 1, track_info[2], curses.A_REVERSE)


def set_statusbar(self):
if self.options['statusbar'] == 'on':
lines = self.player.track.length
if self.current_pos < 0:
self.current_pos = 0
pct_progress = f' {round(self.current_pos * 100 / lines) + 1}% '
self.stdscr.insstr(self.height - 1, self.width - len(pct_progress), pct_progress, curses.A_DIM)

def set_offset(self):
if self.player.track.alignment == 0:
# center align
Expand All @@ -181,9 +194,10 @@ def set_offset(self):
self.pad_offset = (self.width - self.player.track.width)

def scroll_down(self, step=1):
if self.current_pos < self.player.track.length - (self.height * 0.5):
if self.current_pos < self.player.track.length - step:
self.current_pos += step
else:
self.current_pos = self.player.track.length - 1
self.stdscr.addstr(self.height - 1, 1, 'END', curses.A_REVERSE)

def scroll_up(self, step=1):
Expand All @@ -194,6 +208,139 @@ def scroll_up(self, step=1):
self.stdscr.clrtoeol()
self.current_pos -= step

def find_check_keys(self, key=None, lines_map=[]):
if key == self.keys.binds['find-next']:
self.stdscr.addstr(self.height - 1, self.width - 3, 'n ')
self.stdscr.clrtoeol()
# reached end of matches, loop back to start
if self.find_position + 1 >= len(lines_map):
self.find_position = 0
else:
self.find_position += 1
return True
elif key == self.keys.binds['find-prev']:
self.stdscr.addstr(self.height - 1, self.width - 3, 'p ')
self.stdscr.clrtoeol()
if self.find_position - 1 < 0:
self.find_position = len(lines_map) - 1
else:
self.find_position -= 1
return True
# other keys for more accessibility
elif key == self.keys.binds['down']:
self.scroll_down()
elif key == self.keys.binds['up']:
self.scroll_up()
elif key == self.keys.binds['step-down']:
self.scroll_down(self.keys.binds['step-size'])
elif key == self.keys.binds['step-up']:
self.scroll_up(self.keys.binds['step-size'])
elif key == self.keys.binds['find']:
self.find()
return False

def find(self):
# wait for input
self.stdscr.timeout(-1)
prompt = ':'
self.stdscr.move(self.height - 1, 0)
self.stdscr.clrtoeol()
self.stdscr.addstr(self.height - 1, self.pad_offset, prompt)
self.set_statusbar()
# show cursor and key presses during find
curses.echo()
curses.curs_set(1)

# (y, x, input max length), case-insensitive
find_string = self.stdscr.getstr(self.height - 1, len(prompt) + self.pad_offset, 100)
find_string = find_string.decode(encoding="utf-8").strip()

# hide cursor and key presses
curses.curs_set(0)
curses.noecho()

if find_string:
# use word wrap which covers both wrap/nowrap and ensures line count is accurate
text = self.player.track.get_text(wrap=True, width=self.width - self.text_padding)
lines = text.split('\n')

# [0,9,10,14] list of lines that contain a match
lines_map = []
for line_num, line in enumerate(lines):
# case-insensitive match
if find_string.lower() in line.lower():
lines_map.append(line_num)

if len(lines_map) > 0:

# continue search from current position
for line in lines_map:
# >= causes us to stay on the current line for a new search
if line >= self.current_pos:
self.find_position = lines_map.index(line)
break
# otherwise loop back to the start
else:
self.find_position = 0

while True:
# update current position based on where we are at in the find
self.current_pos = lines_map[self.find_position]

# duplicated from main() to manually refresh on find
self.stdscr.clear()
self.set_titlebar()
self.stdscr.refresh()
self.scroll_pad.refresh(self.current_pos, 0, 4, self.pad_offset, self.height - 2, self.width - 1)

# find & status bar output
find_string_output = f' {find_string} '
find_count_output = f" {self.find_position + 1}/{len(lines_map)} "
self.stdscr.addstr(self.height - 1, self.pad_offset, find_string_output, curses.A_REVERSE)
self.stdscr.insstr(self.height - 1, self.pad_offset + len(find_string_output) + 1, find_count_output)
# multiple matches, show next/prev
if len(lines_map) > 1:
help_output = f"[{chr(self.keys.binds['find-next'])}]=next, [{chr(self.keys.binds['find-prev'])}]=prev"
self.stdscr.addstr(self.height - 1, self.pad_offset + len(find_string_output) + len(find_count_output) + 2, help_output)
self.set_statusbar()

# highlight found text
line_text = lines[self.current_pos]
# case-insensitive, [4, 5, 21]
found_index_list = [i for i in range(len(line_text)) if line_text.lower().startswith(find_string.lower(), i)]
# loop over each character in the line, highlight found strings
highlight_end = -1
for cpos, char in enumerate(line_text):
attr = curses.A_NORMAL
# start of found string
if cpos in found_index_list:
highlight_end = cpos + len(find_string)
attr = curses.A_REVERSE
# inside string
elif highlight_end > cpos:
attr = curses.A_REVERSE
self.stdscr.addch(4, self.pad_offset + cpos, char, attr)

# after finding a match in a line, stop, wait for input
self.stdscr.timeout(10000)
key = self.stdscr.getch()
result = self.find_check_keys(key, lines_map)
if not result:
break

else:
output = ' not found '
self.stdscr.insstr(self.height - 1, self.pad_offset + len(prompt) + len(find_string) + 2, output, curses.A_REVERSE)
self.set_statusbar()
# timeout or key press
self.stdscr.timeout(5000)
key = self.stdscr.getch()
self.find_check_keys(key, lines_map)

# clear search line
self.stdscr.clear()
self.stdscr.timeout(self.timeout)

def update_track(self):
self.stdscr.clear()
self.scroll_pad.clear()
Expand Down Expand Up @@ -228,6 +375,7 @@ def main(self):
self.keys.input(self, key)

self.set_titlebar()
self.set_statusbar()
self.stdscr.refresh()
self.scroll_pad.refresh(self.current_pos, 0, 4,
self.pad_offset, self.height - 2, self.width - 1)
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ def updateConfigFile(self):
packages=find_packages(),
entry_points={
'console_scripts': [
'lyrics = lyrics.lyrics_in_terminal:main'
'lyrics = lyrics.lyrics_in_terminal:main',
'lyt = lyrics.lyrics_in_terminal:main'
]
},
classifiers=[
Expand Down