From 5b88ae681a7cce78f370ef7c00ea29b11ea50f1f Mon Sep 17 00:00:00 2001 From: Andrew Dunai Date: Tue, 2 Jan 2018 11:26:55 +0200 Subject: [PATCH] Cleanups --- .flake8 | 31 ++++++++++++++ clay/__main__.py | 2 - clay/app.py | 99 +++++++++++++++++++++++++++++++------------ clay/gp.py | 2 +- clay/notifications.py | 50 ++++++++++++++++++++++ clay/pages.py | 25 +++-------- setup.py | 2 +- 7 files changed, 160 insertions(+), 51 deletions(-) create mode 100644 .flake8 delete mode 100644 clay/__main__.py create mode 100644 clay/notifications.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..3a863ba --- /dev/null +++ b/.flake8 @@ -0,0 +1,31 @@ +[flake8] +# ignore = D203 +max-line-length=120 + +ignore = + # blank line at end of file + W391 + +exclude = + # Generated stuff + gen, + # Coverage HTML report + htmlcov, + # Logs + log, + # Requirements + requirements, + # Temporary stuff + tmp, + # Virtual envs + .env, + .env2, + .env3, + .env36, + .venv, + .git, + wsgi.py, + utils, + # VLC bindings + vlc.py + diff --git a/clay/__main__.py b/clay/__main__.py deleted file mode 100644 index 4d3032d..0000000 --- a/clay/__main__.py +++ /dev/null @@ -1,2 +0,0 @@ -from clay.app import main - diff --git a/clay/app.py b/clay/app.py index 087792b..292aada 100755 --- a/clay/app.py +++ b/clay/app.py @@ -1,23 +1,26 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +# pylint: disable=wrong-import-position +# pylint: disable=too-many-instance-attributes +""" +Main app entrypoint. +""" import sys -sys.path.append('.') +sys.path.append('.') # noqa import urwid from clay.player import player from clay.widgets import PlayProgress -from clay.pages import StartUp, Error +from clay.pages import StartUp from clay.mylibrary import MyLibrary from clay.myplaylists import MyPlaylists from clay.playerqueue import Queue from clay.settings import Settings +from clay.notifications import NotificationArea # from clay import hotkeys - -loop = None - PALETTE = [ ('logo', '', '', '', '#F54', ''), @@ -45,24 +48,39 @@ PALETTE = [ ('flag', '', '', '', '#AAA', ''), ('flag-active', '', '', '', '#F54', ''), + + ('notification', '', '', '', '#F54', '#222'), ] class AppWidget(urwid.Frame): + """ + Root widget. + + Handles tab switches, global keypresses etc. + """ class Tab(urwid.Text): + """ + Represents a single tab in header tabbar. + """ def __init__(self, page): self.page = page - # self.attrwrap = urwid.AttrWrap(urwid.Text(), 'panel') super(AppWidget.Tab, self).__init__( self.get_title() ) def set_active(self, active): + """ + Mark tab visually as active. + """ self.set_text( [('panel_focus' if active else 'panel', self.get_title())] ) def get_title(self): + """ + Render tab title. + """ return ' {} {} '.format( self.page.key, self.page.name @@ -71,7 +89,6 @@ class AppWidget(urwid.Frame): def __init__(self): self.pages = [ StartUp(self), - Error(self), MyLibrary(self), MyPlaylists(self), Queue(self), @@ -83,6 +100,7 @@ class AppWidget(urwid.Frame): in self.pages if hasattr(page, 'key') ] + NotificationArea.set_app(self) self.header = urwid.Pile([ # urwid.Divider('\u2500'), urwid.AttrWrap(urwid.Columns([ @@ -90,6 +108,7 @@ class AppWidget(urwid.Frame): for tab in self.tabs ], dividechars=1), 'panel'), + NotificationArea.get() # urwid.Divider('\u2500') ]) self.seekbar = PlayProgress( @@ -116,27 +135,48 @@ class AppWidget(urwid.Frame): body=self.current_page ) + self.loop = None + player.media_position_changed += self.media_position_changed player.media_state_changed += self.media_state_changed player.track_changed += self.track_changed player.playback_flags_changed += self.playback_flags_changed + def set_loop(self, loop): + """ + Assign a MainLoop to this app. + """ + self.loop = loop + def media_position_changed(self, progress): + """ + Update slider in seekbar. + Called when current play position changes. + """ if progress < 0: progress = 0 self.seekbar.set_completion(progress * 100) - loop.draw_screen() - # sleep(0.2) + self.loop.draw_screen() - # self.set_page(MyLibrary()) - - def media_state_changed(self, is_playing): + def media_state_changed(self, _): + """ + Update seekbar. + Called when playback is paused/unpaused. + """ self.seekbar.update() def track_changed(self, track): + """ + Update displayed track in seekbar. + Called when current track changes. + """ self.seekbar.set_track(track) def playback_flags_changed(self): + """ + Update seekbar flags. + Called when random/repeat flags change. + """ self.shuffle_el.attr = 'flag-active' \ if player.get_is_random() \ else 'flag' @@ -145,15 +185,10 @@ class AppWidget(urwid.Frame): else 'flag' self.seekbar.update() - def set_page(self, classname, *args): - # if isinstance(page_class, str): - # page_class = [ - # page - # for page - # in self.pages - # if page.__name__ == page_class - # ][0] - # self.current_page = page_class(self, *args) + def set_page(self, classname): + """ + Switch to a different tab. + """ page = [page for page in self.pages if page.__class__.__name__ == classname][0] self.current_page = page self.contents['body'] = (page, None) @@ -169,12 +204,18 @@ class AppWidget(urwid.Frame): page.start() def redraw(self): - if loop: - loop.draw_screen() + """ + Redraw screen. + Needs to be called by other widgets if UI was changed from a different thread. + """ + if self.loop: + self.loop.draw_screen() def keypress(self, size, key): - if isinstance(self.current_page, StartUp): - return + """ + Handle keypress. + Can switch tabs, control playbackm, flags, notifications and app state. + """ for tab in self.tabs: if 'meta {}'.format(tab.page.key) == key: self.set_page(tab.page.__class__.__name__) @@ -196,18 +237,22 @@ class AppWidget(urwid.Frame): player.set_repeat_one(not player.get_is_repeat_one()) elif key == 'ctrl q': sys.exit(0) + elif key == 'esc': + NotificationArea.close_all() else: super(AppWidget, self).keypress(size, key) def main(): - global loop + """ + Application entrypoint. + """ app_widget = AppWidget() loop = urwid.MainLoop(app_widget, PALETTE) + app_widget.set_loop(loop) loop.screen.set_terminal_properties(256) loop.run() if __name__ == '__main__': main() - diff --git a/clay/gp.py b/clay/gp.py index fc87acb..863434a 100644 --- a/clay/gp.py +++ b/clay/gp.py @@ -15,7 +15,6 @@ def async(fn): result = fn(*args, **kwargs) except Exception as e: callback(None, e, **extra) - raise else: callback(result, None, **extra) @@ -147,5 +146,6 @@ class GP(object): def is_authenticated(self): return self.mc.is_authenticated() + gp = GP() diff --git a/clay/notifications.py b/clay/notifications.py new file mode 100644 index 0000000..e01c8d6 --- /dev/null +++ b/clay/notifications.py @@ -0,0 +1,50 @@ +import urwid + + +class NotificationArea(urwid.Pile): + instance = None + app = None + + TEMPLATE = ' \u26A1 {}' + + def __init__(self): + super().__init__([]) + + @classmethod + def get(cls): + if cls.instance is None: + cls.instance = NotificationArea() + + return cls.instance + + @classmethod + def set_app(cls, app): + cls.app = app + + @classmethod + def notify(cls, message): + cls.instance._notify(message) + + @classmethod + def close_all(cls): + cls.instance._close_all() + + def _notify(self, message): + text = urwid.Text(self.__class__.TEMPLATE.format(message)) + self.contents.append( + ( + urwid.AttrWrap( + urwid.Columns([ + text, + ('pack', urwid.Text('[Hit ESC to close] ')) + ]), + 'notification' + ), + ('weight', 1) + ) + ) + self.__class__.app.redraw() + + def _close_all(self): + self.contents[:] = [] + diff --git a/clay/pages.py b/clay/pages.py index 6d8c97d..3f7cd79 100644 --- a/clay/pages.py +++ b/clay/pages.py @@ -2,6 +2,7 @@ import urwid from clay.settings import Settings from clay.gp import gp from clay.meta import VERSION +from clay.notifications import NotificationArea class StartUp(urwid.Filler): @@ -29,14 +30,11 @@ class StartUp(urwid.Filler): def on_login(self, success, error): if error: - self.app.set_page( - 'Error', - 'Failed to log in: {}'.format(str(error)) - ) + NotificationArea.notify('Failed to log in: {}'.format(str(error))) return + if not success: - self.app.set_page( - 'Error', + NotificationArea.notify( 'Google Play Music login failed ' '(API returned false)' ) @@ -54,20 +52,7 @@ class StartUp(urwid.Filler): callback=self.on_login ) else: - self.app.set_page( - 'Error', + NotificationArea.notify( 'Please set your credentials on the settings page.' ) - -class Error(urwid.Filler): - def __init__(self, app): - self.text = urwid.Text('Error'), - super(Error, self).__init__( - self.text, - valign='top' - ) - - def set_error(self, error): - self.text = 'Error:\n\n{}'.format(str(error)) - diff --git a/setup.py b/setup.py index 4a8ef6d..b45d258 100755 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( packages=find_packages(), entry_points={ 'console_scripts': [ - 'clay=clay.__main__:main' + 'clay=clay.app:main' ] } )