Guest User

gtkui

a guest
Mar 7th, 2013
78
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 37.14 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. from concurrent import futures
  3.  
  4. from gi.repository import GObject# Possibly remove this, moving GObject calls to GObjectUI
  5. from gi.repository import Gdk, GdkPixbuf, Gtk, Pango
  6. from gi.repository.Gtk import main_quit
  7. from gi._glib import GError
  8.  
  9. # import requests
  10.  
  11. from genericui import GenericUI
  12. from lyrics import Lyrics
  13.  
  14. '''find out under what conditions ending the gui process ends the shell-fm process.
  15. it may depend on when shell-fm launches. User should have easy option to close '''
  16. # class GTKUI(GObjectUI):
  17. class GTKUI(GenericUI):
  18.    
  19.     def __init__(self):
  20.         # super().__init__(bus_name='org.mpris.MediaPlayer2.chipmunk.gtkui')
  21.         super().__init__()
  22.         self.builder = Gtk.Builder()
  23.         self.builder.add_from_file('chipsperimental.glade')
  24.         self.builder.connect_signals(self)
  25.         # Why won't this work when set in glade instead?:
  26.         self.builder.get_object('window_main').connect('delete-event', main_quit)
  27.         self.builder.get_object('progressbar_track').override_font(Pango.FontDescription('mono'))
  28.         self.load_credentials()
  29.         self.load_last_station()
  30.         # sweep some of these into more organized methods to declutter as groupings emerge
  31.         try:
  32.             self.activate('switch_quick_actions', self.config['quick actions'] == 'on')
  33.         except KeyError:
  34.             self.activate('switch_quick_actions')
  35.         try:
  36.             self.builder.get_object('image_album_core').set_pixel_size(int(self.config['small art size']))
  37.         except KeyError:
  38.             pass
  39.         try:
  40.             min_context_height = int(self.config['minimum context height'])
  41.         except KeyError:
  42.             min_context_height = 300
  43.         self.builder.get_object('spinbutton_min_context_height').set_value(min_context_height)
  44.         try:
  45.             max_art_size = int(self.config['maximum art size'])
  46.         except KeyError:
  47.             max_art_size = 1080
  48.         self.builder.get_object('spinbutton_max_art_size').set_value(max_art_size)
  49.         self.info_monitor = GObject.timeout_add(900, self.update_track_info)
  50.         self.progress_monitor = GObject.timeout_add(1100, self.update_track_progress)
  51.         self.status_monitor = GObject.timeout_add(1000, self.update_status)
  52.         self.mode_monitor = GObject.timeout_add(2000, self.update_modes)
  53.         self.update_track_info()
  54.         self.update_status()
  55.         self.update_station_suggestions()
  56.         # for widget in ['scrobble', 'settings', 'album_art']:
  57.         for widget in ['scrobble', 'settings']:
  58.             self.get_label('togglebutton_' + widget).set_visible(False)
  59.  
  60.     def on_spinbutton_min_context_height_value_changed(self, widget):
  61.         value = widget.get_value_as_int()
  62.         for context in ['lyrics', 'bio', 'playlist']:
  63.             sw = self.builder.get_object('scrolledwindow_' + context)
  64.             sw.set_min_content_height(value)
  65.         self.config['minimum context height'] = value
  66.  
  67.     def on_spinbutton_max_art_size_value_changed(self, widget):
  68.         value = widget.get_value_as_int()
  69.         self.config['maximum art size'] = value
  70.  
  71.     def on_comboboxtextentry_station_changed(self, entry):
  72.         if not entry.get_text() or self.service.player.status == 'disconnected':
  73.             switch_possible = False
  74.         else:
  75.             switch_possible = self.user_text_to_lastfm_uri(entry.get_text()) != self.current_station_uri
  76.         for widget in ['button_switch_station', 'togglebutton_switch_station_after_track']:
  77.             self.builder.get_object(widget).set_visible(switch_possible)
  78.  
  79.     def on_comboboxtextentry_station_icon_press(self, entry, position, event):
  80.         if position == Gtk.EntryIconPosition.PRIMARY:
  81.             entry.set_text('')
  82.         else:
  83.             self.toggle_context('infobar_station_syntax')
  84.  
  85.     def on_comboboxtextentry_station_activate(self, widget):
  86.         if self.builder.get_object('button_switch_station').get_visible():
  87.             self.switch()
  88.  
  89.     def on_entry_lastfm_name_activate(self, widget):
  90.         # self.builder.get_object('entry_lastfm_pass').grab_focus()
  91.         self.activate('button_apply_lastfm_credentials')
  92.  
  93.     def on_entry_lastfm_pass_activate(self, widget):
  94.         self.activate('button_apply_lastfm_credentials')
  95.  
  96.     def on_button_close_station_syntax_infobar_clicked(self, widget):
  97.         self.toggle_context('infobar_station_syntax')
  98.  
  99.     # def on_button_hide_track_info_clicked(self, widget):
  100.         # self.toggle_context('infobar_track')
  101.  
  102.     def on_button_copy_title_clicked(self, widget):
  103.         self.on_button_copy_clicked('togglebutton_title')
  104.  
  105.     def on_button_copy_artist_clicked(self, widget):
  106.         self.on_button_copy_clicked('togglebutton_artist')
  107.  
  108.     def on_button_copy_album_clicked(self, widget):
  109.         self.on_button_copy_clicked('togglebutton_album')
  110.  
  111.     def on_button_copy_clicked(self, widget_name):
  112.         widget = self.builder.get_object(widget_name)
  113.         text = widget.get_label()
  114.         widget.get_clipboard(Gdk.SELECTION_CLIPBOARD).set_text(text, -1)
  115.  
  116.     def on_button_switch_station_clicked(self, widget):
  117.         # self.deactivate('togglebutton_switch_station_after_track')
  118.         self.switch()
  119.  
  120.     def deactivate(self, widgets):
  121.         self.activate(widgets, False)
  122.  
  123.     def activate(self, widgets, activate=True):
  124.         if not isinstance(widgets, list):
  125.             widgets = [widgets]
  126.         for widget in widgets:
  127.             try:
  128.                 self.builder.get_object(widget).set_active(activate)
  129.             except AttributeError:
  130.                 self.builder.get_object(widget).activate()
  131.  
  132.     def on_togglebutton_bio_toggled(self, widget):
  133.         self.toggle_context('scrolledwindow_bio', widget, ['togglebutton_lyrics', 'togglebutton_playlist'])
  134.  
  135.     def on_togglebutton_playlist_toggled(self, widget):
  136.         self.toggle_context('scrolledwindow_playlist', widget, ['togglebutton_bio', 'togglebutton_lyrics'])
  137.  
  138.     def on_togglebutton_lyrics_toggled(self, widget):
  139.         self.toggle_context('scrolledwindow_lyrics', widget, ['togglebutton_bio', 'togglebutton_playlist'])
  140.  
  141.     def toggle_context(self, display, control=None, conflicts=[]):
  142.         '''Toggle widgets' visibility.
  143.        display: a widget name or list of names.
  144.        control: a widget object that can be active;
  145.            if provided, it's state determines whether to show or hide display.
  146.        conflicts: a list of control widgets (names) to deactivate when showing display.'''
  147.         if isinstance(display, list):
  148.             for disp in display:
  149.                 self.toggle_context(disp, control, conflicts)
  150.         else:
  151.             try:
  152.                 show = control.get_active()
  153.             except AttributeError:
  154.                 show = not self.builder.get_object(display).get_visible()
  155.             if show:
  156.                 self.deactivate(conflicts)
  157.             self.builder.get_object(display).set_visible(show)
  158.             if not show:
  159.                 self.shrink_window_height()
  160.  
  161.     def on_button_similar_clicked(self, widget):
  162.         entry = self.builder.get_object('comboboxtext-entry_station')
  163.         entry.set_text('similar: ' + self.current_artist_name)
  164.  
  165.     def on_switch_quick_actions_notify(self, widget, nothing_apparently):
  166.         # print(type(nothing_apparently), ':', nothing_apparently)
  167.         self.toggle_context('grid_quick_actions', widget)
  168.         self.config['quick actions'] = {True: 'on', False: 'off'}[widget.get_active()]
  169.  
  170.     def get_lyrics(self):
  171.         # Maybe make the timeout a configuration option. For now, 10.
  172.         return Lyrics(10).fetch(self.current_artist_name, self.current_track_title)
  173.  
  174.     def task(self, func, on_done=None):
  175.         # func should not have gtk code or shell-fm (self.service.player) communications
  176.         executor = futures.ProcessPoolExecutor()
  177.         future = executor.submit(func)
  178.         if on_done:
  179.             future.add_done_callback(on_done)
  180.  
  181.     def update_lyrics(self):
  182.         self.task(self.get_lyrics, self.set_lyrics)
  183.  
  184.     def set_lyrics(self, data):
  185.         data = self.defutured(data)
  186.         lyrics, url = data['lyrics'], data['url']
  187.         artist, title = data['artist'], data['title']
  188.         textview = self.builder.get_object('textview_lyrics')
  189.         source = self.builder.get_object('linkbutton_lyrics_source')
  190.         search = self.builder.get_object('linkbutton_search_lyrics')
  191.         try:
  192.             textview.get_buffer().set_text(lyrics)
  193.             source.set_uri(url)
  194.             source.set_label(url)
  195.             source.set_visible(True)
  196.             search.set_visible(False)
  197.             self.builder.get_object('togglebutton_lyrics').set_label('L_yrics*')
  198.         except TypeError:
  199.             textview.get_buffer().set_text('Sorry, lyrics not found.')
  200.             source.set_visible(False)
  201.             search.set_visible(True)
  202.             search_uri = 'https://duckduckgo.com/?q=lyrics "{}" "{}"'
  203.             search_uri = search_uri.format(artist, title).replace('&', '%26')
  204.             search_uri = search_uri.replace('#', '%23')
  205.             search.set_uri(search_uri)
  206.             self.builder.get_object('togglebutton_lyrics').set_label('L_yrics')
  207.         self.readjust('scrolledwindow_lyrics')
  208.  
  209.     def readjust(self, scrolledwindow):
  210.         self.builder.get_object(scrolledwindow).get_vadjustment().set_value(0)
  211.         self.builder.get_object(scrolledwindow).get_hadjustment().set_value(0)
  212.  
  213.     def get_artist_bio(self):
  214.         return self.lastfm.get_artist_bio(self.current_artist_name)
  215.    
  216.     def set_artist_bio(self, text):
  217.         text = self.defutured(text)
  218.         textview = self.builder.get_object('textview_bio')
  219.         if not text:
  220.             text = 'Sorry, bio not found.'
  221.             self.builder.get_object('togglebutton_bio').set_label('B_io')
  222.         else:
  223.             self.builder.get_object('togglebutton_bio').set_label('B_io*')
  224.         textview.get_buffer().set_text(text)
  225.         self.readjust('scrolledwindow_bio')
  226.  
  227.     def update_bio(self):
  228.         self.task(self.get_artist_bio, self.set_artist_bio)
  229.  
  230.     def on_togglebutton_stop_after_track_toggled(self, widget):
  231.         self.stop_after_track = widget.get_active()
  232.  
  233.     def on_togglebutton_switch_station_after_track_toggled(self, widget):
  234.         self.switch_station_after_track = widget.get_active()
  235.    
  236.     @property
  237.     def stop_after_track(self):
  238.         return self.builder.get_object('togglebutton_stop_after_track').get_active()
  239.     @stop_after_track.setter
  240.     def stop_after_track(self, enable):
  241.         if enable:
  242.             self.stop_artist = self.current_artist_name
  243.             self.stop_title = self.current_track_title
  244.             self.stop_monitor = GObject.timeout_add(100, self.check_for_stop)
  245.         else:
  246.             GObject.source_remove(self.stop_monitor)
  247.  
  248.     @property
  249.     def switch_station_after_track(self):
  250.         return self.builder.get_object('togglebutton_switch_station_after_track').get_active()
  251.     @switch_station_after_track.setter
  252.     def switch_station_after_track(self, enable):
  253.         if enable:
  254.             self.switch_artist = self.current_artist_name
  255.             self.switch_title = self.current_track_title
  256.             self.switch_monitor = GObject.timeout_add(100, self.check_for_switch)
  257.         else:
  258.             GObject.source_remove(self.switch_monitor)
  259.  
  260.     def check_for_stop(self):
  261.         if self.current_artist_name != self.stop_artist or self.current_track_title != self.stop_title:
  262.             self.service.player.stop()
  263.             self.deactivate('togglebutton_stop_after_track')
  264.         return True
  265.  
  266.     def check_for_switch(self):
  267.         if self.current_artist_name != self.switch_artist or self.current_track_title != self.switch_title:
  268.             self.switch()
  269.             self.deactivate('togglebutton_switch_station_after_track')
  270.         return True
  271.  
  272.     def switch(self, attempt=1):
  273.         self.deactivate('togglebutton_switch_station_after_track')
  274.         text = self.builder.get_object('comboboxtext-entry_station').get_text()
  275.         uri = self.user_text_to_lastfm_uri(text)
  276.         status = self.service.player.status
  277.         self.builder.get_object('infobar_check_credentials').set_visible(status == 'disconnected')
  278.         if status == 'disconnected':
  279.             if attempt == 3:
  280.                 self.activate('togglebutton_settings')
  281.                 self.builder.get_object('entry_lastfm_name').grab_focus()
  282.             else:
  283.                 self.switch(attempt + 1)
  284.         elif uri:
  285.             self.service.player.play(uri)
  286.             self.config['last station'] = text
  287.             self.update_station_suggestions()
  288.         else:
  289.             self.builder.get_object('comboboxtext-entry_station').grab_focus()
  290.  
  291.     @property
  292.     def current_station_uri(self):
  293.         return self.service.player.current_station['uri']
  294.     @current_station_uri.setter
  295.     def current_station_uri(self, uri):
  296.         entry = self.builder.get_object('comboboxtext-entry_station')
  297.         entry.set_placeholder_text(self.lastfm_uri_to_user_text(uri))
  298.         self.on_comboboxtextentry_station_changed(entry)
  299.  
  300.     def on_volumebutton_value_changed(self, widget, vol):
  301.         self.service.player.volume = int(vol * 100)
  302.  
  303.     # @property
  304.     # def recent_stations(self):
  305.     #     stations = self.get_recent_stations()
  306.     #     return stations
  307.  
  308.     def update_station_suggestions(self):
  309.         self.update_combobox_station_suggestions()
  310.         # self.update_quick_station_suggestions()
  311.  
  312.     # @property
  313.     # def friends(self):
  314.         # return self.get_friends()
  315.  
  316.     def get_friends(self):
  317.         return self.service.get_friends(self.service.player.config['username'])
  318.  
  319.     def get_hidden_station_texts(self):
  320.         texts = []
  321.         for category in ['recommended: ', 'personal: ', 'mix: ', 'neighbors: ']:
  322.             texts += [category + self.service.player.config['username']]
  323.         for friend in self.get_friends():
  324.             for category in ['personal: ', 'mix: ', '']:
  325.                 texts += [category + friend]
  326.         return texts
  327.  
  328.     def update_combobox_station_suggestions(self):
  329.         combobox = self.builder.get_object('comboboxtext_station')
  330.         combobox.remove_all()
  331.         self.task(self.get_recent_stations, self.append_combobox_station_suggestions)
  332.  
  333.     def append_combobox_station_suggestions(self, station_uris):
  334.         station_uris = self.defutured(station_uris)
  335.         if station_uris != None:
  336.             if isinstance(station_uris, str):
  337.                 station_uris = [station_uris]
  338.             combobox = self.builder.get_object('comboboxtext_station')
  339.             for station in station_uris:
  340.                 text = self.lastfm_uri_to_user_text(station)
  341.                 combobox.append_text(text)
  342.             self.task(self.get_hidden_station_texts, lambda x: self.add_completion(combobox, x))
  343.         else:
  344.             print('station_uris == None')
  345.  
  346.     #     # ensure that user's personal and mix stations are there -- just by bookmarking? unsure...
  347.     #     # add similar to current artist station
  348.     #     # add some (3?) tag stations of current artist
  349.     #     # add fans of current artist station
  350.     #     # add bookmarked stations
  351.        
  352.     def add_completion(self, combobox, hidden=[]):
  353.         hidden = self.defutured(hidden)
  354.         model = Gtk.ListStore(str)
  355.         for row in combobox.get_model():
  356.             model.append([row[0]])
  357.             if row[0].startswith('similar:'):
  358.                 for synonym in ['like:', 'artist:']:
  359.                     hidden += [row[0].replace('similar:', synonym, 1)]
  360.         for extra in hidden:
  361.             if extra.lower() not in [row[0].lower() for row in model]:
  362.                 model.append([extra])
  363.         completion = Gtk.EntryCompletion()
  364.         completion.set_model(model)
  365.         completion.set_text_column(0)
  366.         combobox.get_child().set_completion(completion)
  367.  
  368.     def get_recent_stations(self):
  369.         # try:
  370.         r = self.service.get_recent_stations(self.service.player.config['username'])
  371.         if r == 'invalid session key':
  372.         # except LookupError:
  373.             self.request_lastfm_authorization()
  374.             return None
  375.         return r
  376.        
  377.     def on_togglebutton_play_toggled(self, widget):
  378.         if not getattr(self, 'ignore_play_button', False):
  379.             self.on_play_button_clicked()
  380.             self.set_play_button(widget.get_active())
  381.  
  382.     def on_togglebutton_love_toggled(self, widget):
  383.         if not getattr(self, 'ignore_love_button', False):
  384.             self.service.player.love(widget.get_active())
  385.             self.set_love_button(widget.get_active())
  386.  
  387.     def on_togglebutton_scrobble_toggled(self, widget):
  388.         if not getattr(self, 'ignore_scrobble_button', False):
  389.             self.service.player.toggle_scrobbling()
  390.             self.set_scrobble_button(widget.get_active())
  391.  
  392.     def on_button_skip_clicked(self, widget):
  393.         self.service.player.skip()
  394.  
  395.     def on_togglebutton_settings_toggled(self, widget):
  396.         self.toggle_context('grid_settings', widget)
  397.  
  398.     def request_lastfm_authorization(self):
  399.         self.on_button_authorize_lastfm_clicked(self.builder.get_object('button_authorize_lastfm'))
  400.         self.activate('togglebutton_settings')
  401.  
  402.     def on_button_apply_lastfm_credentials_clicked(self, widget):
  403.         name = self.builder.get_object('entry_lastfm_name').get_text()
  404.         password = self.builder.get_object('entry_lastfm_pass').get_text()
  405.         self.set_credentials(name, password)
  406.         self.builder.get_object('comboboxtext-entry_station').set_text(self.config['default station'])
  407.         self.request_lastfm_authorization()
  408.         self.builder.get_object('infobar_check_credentials').set_visible(False)
  409.  
  410.     def on_button_authorize_lastfm_clicked(self, widget):
  411.         self.service.request_authorization()
  412.         widget.set_visible(False)
  413.         self.builder.get_object('button_authorize_lastfm_completed').show()
  414.        
  415.     def on_button_authorize_lastfm_completed_clicked(self, widget):
  416.         self.service.authenticate()
  417.         widget.set_visible(False)
  418.         self.deactivate('togglebutton_settings')
  419.         # self.recent_stations = self.recent_stations
  420.         self.update_station_suggestions()
  421.         # any other values that require authorization should be set here as well
  422.  
  423.     def load_credentials(self):
  424.         try:
  425.             name = self.service.player.config['username']
  426.             self.builder.get_object('entry_lastfm_name').set_text(name)
  427.         except KeyError:
  428.             pass
  429.  
  430.     def load_last_station(self):
  431.         try:
  432.             self.builder.get_object('comboboxtext-entry_station').set_text(self.config['last station'])
  433.         except KeyError:
  434.             try:
  435.                 self.builder.get_object('comboboxtext-entry_station').set_text(self.config['default station'])
  436.             except KeyError:
  437.                 pass
  438.        
  439.     def on_button_stop_clicked(self, widget):
  440.         self.service.player.stop()
  441.  
  442.     # def on_button_title_clicked(self, widget):
  443.         # pass
  444.  
  445.     # def on_button_artist_clicked(self, widget):
  446.         # pass
  447.  
  448.     # def on_button_album_clicked(self, widget):
  449.         # pass
  450.  
  451.     def on_togglebutton_album_art_toggled(self, widget):
  452.         show_art = widget.get_active()
  453.         art = 'image_album_core'
  454.         self.builder.get_object(art).set_visible(show_art)
  455.         if not show_art:
  456.             self.shrink_window()
  457.  
  458.     # def on_togglebutton_upcoming_toggled(self, widget):
  459.         # show_upcoming = widget.get_active()
  460.         # self.builder.get_object('as-yet-unmade-upcoming-widget').set_visible(show_upcoming)
  461.         # if self.builder.get_object('togglebutton_album_art').get_active():
  462.             # self.builder.get_object('image_album_core').set_visible(not show_upcoming)
  463.             # self.builder.get_object('image_album_upcoming').set_visible(show_upcoming)
  464.         # if not show_upcoming:
  465.             # self.shrink_window()
  466.  
  467.     def shrink_window_height(self):
  468.         widget = self.builder.get_object('window_main')
  469.         width = widget.get_size()[0]
  470.         widget.resize(width, 1)
  471.  
  472.     def shrink_window(self):
  473.         self.builder.get_object('window_main').resize(1, 1)
  474.  
  475.     def update_large_art(self):
  476.         self.task(self.download_large_art, self.set_large_art)
  477.  
  478.     def update_small_art(self):
  479.         self.task(self.download_small_art, self.set_small_art)
  480.         # image = self.builder.get_object('image_album_core')
  481.         # try:
  482.         #     if image.get_pixel_size() != int(self.config['small art size']):
  483.         #         self.config['small art size'] = image.get_pixel_size()
  484.         # except KeyError:
  485.         #     self.config['small art size'] = image.get_pixel_size()
  486.  
  487.     def set_small_art(self, filename):
  488.         filename = self.defutured(filename)
  489.         widget = self.builder.get_object('image_album_core')
  490.         size = widget.get_pixel_size()
  491.         try:
  492.             if size != int(self.config['small art size']):
  493.                 self.config['small art size'] = size
  494.         except KeyError:
  495.             self.config['small art size'] = size
  496.         if filename:
  497.             try:
  498.                 pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(filename, size, size)
  499.             except GError as e:
  500.                 print(e, '[small art]')
  501.                 return
  502.             try:
  503.                 widget.set_from_pixbuf(pixbuf)
  504.             except TypeError as e:
  505.                 print(e, '[small art]')
  506.                 widget.set_from_icon_name('media-optical', size)
  507.         else:
  508.             widget.set_from_icon_name('media-optical', size)
  509.  
  510.     def set_large_art(self, filename):
  511.         filename = self.defutured(filename)
  512.         widget = self.builder.get_object('image_large_art')
  513.         if filename:
  514.             try:
  515.                 pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
  516.             except GError as e:
  517.                 print(e, '[large art]')
  518.                 return
  519.             max_size = int(self.config['maximum art size'])
  520.             min_size = 350# maybe configurable, maybe not.
  521.             if pixbuf.get_height() > max_size:
  522.                 pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(filename, max_size, max_size)
  523.             elif pixbuf.get_height() < min_size:
  524.                 pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(filename, min_size, min_size)
  525.             try:
  526.                 widget.set_from_pixbuf(pixbuf)
  527.             except TypeError as e:
  528.                 print(e, '[large art]')
  529.                 widget.set_from_icon_name('media-optical', 350)
  530.         else:
  531.             widget.set_from_icon_name('media-optical', 350)
  532.  
  533.     def on_image_album_core_scroll_event(self, widget, event):
  534.         image = self.builder.get_object('image_album_core')
  535.         step = 1
  536.         if event.direction == Gdk.ScrollDirection.DOWN:
  537.             step = -step
  538.         size = image.get_pixel_size() + step
  539.         image.set_pixel_size(size)
  540.         pixbuf = image.get_pixbuf()
  541.         if pixbuf:
  542.             self.set_small_art(self.config.sibling('small_art'))
  543.  
  544.     def download_small_art(self):
  545.         url = self.service.get_small_album_art_url(self.current_album_name, self.current_artist_name)
  546.         return self.download(url, 'small_art')
  547.  
  548.     def download_large_art(self):
  549.         url = self.service.get_large_album_art_url(self.current_album_name, self.current_artist_name)
  550.         return self.download(url, 'large_art')
  551.  
  552.     def update_track_progress(self):
  553.         self.current_track_remaining_seconds = self.current_track_remaining_seconds
  554.         return True
  555.  
  556.     def on_enter_show_label(self, widget, event):
  557.         self.get_label(widget).set_visible(True)
  558. # I can probably merge these by reading event attributes to see if it is an entrance or an exit
  559.     def on_leave_hide_label(self, widget, event):
  560.         self.get_label(widget).set_visible(False)
  561.  
  562.     def on_progressbar_track_enter_notify_event(self, *args):
  563.         widget = self.builder.get_object('progressbar_track')
  564.         widget.set_show_text(True)
  565.  
  566.     def on_progressbar_track_leave_notify_event(self, *args):
  567.         if not getattr(self, 'progressbar_text_on', False):
  568.             widget = self.builder.get_object('progressbar_track')
  569.             widget.set_show_text(False)
  570.  
  571.     def set_window_title(self, title='', artist=''):
  572.         prefix = title + ' by ' * {True: 1, False: 0}[len(artist) > 0] + artist
  573.         prefix += ' - ' * {True: 1, False: 0}[len(prefix) > 0]
  574.         self.builder.get_object('window_main').set_title(prefix + 'Chipmunk')
  575.  
  576.     def update_modes(self):
  577.         self.set_love_button(self.service.player.current_track_is_loved)
  578.         self.set_scrobble_button(self.service.player.scrobbling)
  579.         return True
  580.  
  581.     @property
  582.     def status(self):
  583.         return self.service.player.status
  584.     @status.setter
  585.     def status(self, text):
  586.         self.set_play_button(text == 'playing')
  587.         # self.set_scrobble_button(self.service.player.scrobbling)
  588.         # self.set_love_button(self.service.player.current_track_is_loved)
  589.         for widget in ['togglebutton_title', 'togglebutton_artist', 'togglebutton_album',
  590.                         'button_stop', 'button_skip', 'togglebutton_stop_after_track',
  591.                         'grid_title_actions', 'grid_artist_actions', 'grid_album_actions',
  592.                         'button_copy_title', 'button_copy_artist', 'button_copy_album',
  593.                         'togglebutton_love', 'togglebutton_quick_love',
  594.                         'button_share_track', 'button_quick_share_track',
  595.                         'button_ban', 'button_quick_ban',
  596.                         'button_similar', 'button_quick_similar']:
  597.             self.builder.get_object(widget).set_sensitive(text in ['playing', 'paused'])
  598.  
  599.     def set_play_button(self, is_playing=False):
  600.         self.set_togglebutton('togglebutton_play', is_playing, 'ignore_play_button',
  601.                             {False: '_Play', True: '_Playing'},
  602.                             {False: 'icon_play', True: 'icon_pause'})
  603.  
  604.     def set_togglebutton(self, widget_names, set_active, ignore_str=None, texts={}, images={}):
  605.         try:
  606.             image = images[set_active]
  607.         except KeyError:
  608.             image = None
  609.         try:
  610.             text = texts[set_active]
  611.         except KeyError:
  612.             text = None
  613.         if not isinstance(widget_names, list):
  614.             widget_names = [widget_names]
  615.         for widget_name in widget_names:
  616.             widget = self.builder.get_object(widget_name)
  617.             if text:
  618.                 if widget_name == 'togglebutton_play':
  619.                 # method 1:
  620.                     widget.set_label(text)
  621.                 # method 2:
  622.                 else:
  623.                     try:
  624.                         self.get_label(widget).set_markup_with_mnemonic(text)
  625.                     except AttributeError as e:
  626.                         print('get_label returned None', e, widget)
  627.                 # investigate: for some reason, the play button needs method 1 while
  628.                 # the scrobble button needs method 2
  629.             if image:
  630.                 widget.set_image(self.builder.get_object(image))
  631.            
  632.             if ignore_str:
  633.                 setattr(self, ignore_str, True)
  634.                 widget.set_active(set_active)
  635.                 setattr(self, ignore_str, False)
  636.  
  637.     def get_label(self, container):
  638.         if isinstance(container, str):
  639.             widget = self.builder.get_object(container)
  640.         else:
  641.             widget = container
  642.         if isinstance(widget, Gtk.Container):
  643.             for child in widget.get_children():
  644.                 if isinstance(child, Gtk.Label):
  645.                     return child
  646.             for child in widget.get_children():
  647.                 label = self.get_label(child)
  648.                 if label:
  649.                     return label
  650.         return None
  651.  
  652.     def set_love_button(self, loves=False):
  653.         self.set_togglebutton(['togglebutton_love', 'togglebutton_quick_love'], loves,
  654.                                 'ignore_love_button', {False: '_Love', True: '_Loved'})
  655.  
  656.     def set_scrobble_button(self, is_scrobbling=True):
  657.         self.set_togglebutton('togglebutton_scrobble', is_scrobbling,
  658.             # 'ignore_scrobble_button', {False: 'Scrobble', True: 'Scrobbling'})
  659.             'ignore_scrobble_button', {False: 'S_crobble', True: 'S_crobbling'})
  660.  
  661.     def on_button_ban_clicked(self, widget):
  662.         self.on_ban_button_clicked()
  663.  
  664.     def on_grid_context_toggles_scroll_event(self, widget, event):
  665.         toggles = ['lyrics', 'bio', 'playlist']
  666.         if event.direction == Gdk.ScrollDirection.UP:
  667.             toggles.reverse()
  668.         toggle_next = False
  669.         for toggle in toggles:
  670.             if toggle_next:
  671.                 self.activate('togglebutton_' + toggle)
  672.                 break
  673.             if self.builder.get_object('togglebutton_' + toggle).get_active():
  674.                 toggle_next = True
  675.  
  676.     def update_status(self):
  677.         self.status = self.status
  678.         return True
  679.  
  680.     def update_track_info(self):
  681.         title, artist, album = self.current_track_title, self.current_artist_name, self.current_album_name
  682.         self.current_track_title = title
  683.         self.current_artist_name = artist
  684.         self.current_album_name = album
  685.         self.set_window_title(title, artist)
  686.         if artist != getattr(self, 'last_artist', '') or title != getattr(self, 'last_title', ''):
  687.             self.update_lyrics()
  688.             self.current_station_uri = self.current_station_uri
  689.             self.update_bio()
  690.             self.update_small_art()#these are not backgrounded enough!
  691.             self.update_large_art()#these are not backgrounded enough!
  692.             self.update_wiki()
  693.             self.update_tour_info()
  694.             self.last_artist, self.last_title = artist, title
  695.         return True
  696.  
  697.     def get_track_wiki(self):
  698.         return self.service.get_track_wiki(self.current_track_title, self.current_artist_name)
  699.  
  700.     def update_wiki(self):
  701.         self.task(self.get_track_wiki, self.set_infobar_track)
  702.         # pass
  703.  
  704.     def get_tour_info(self):
  705.         return self.service.get_upcoming_events(self.current_artist_name)
  706.  
  707.     def update_tour_info(self):
  708.         self.task(self.get_tour_info, self.set_infobar_tour)
  709.  
  710.     def defutured(self, potential_future):
  711.         if isinstance(potential_future, futures.Future):
  712.             # try:
  713.             return potential_future.result()
  714.             # except TypeError:
  715.                 # print('defutured TypeError:', type(potential_future), potential_future)
  716.                 # return None
  717.         return potential_future
  718.  
  719.     def set_infobar_track(self, text):
  720.         text = self.defutured(text)
  721.         if text:
  722.             self.builder.get_object('label_track_info').set_text(text)
  723.             self.builder.get_object('togglebutton_track_info').set_visible(True)
  724.             self.readjust('scrolledwindow_track_info')
  725.             # self.builder.get_object('scrolledwindow_track_info').get_vadjustment().set_value(0)
  726.             # self.builder.get_object('scrolledwindow_track_info').get_hadjustment().set_value(0)
  727.         else:
  728.             self.deactivate('togglebutton_track_info')
  729.             self.builder.get_object('togglebutton_track_info').set_visible(False)
  730.  
  731.     def set_infobar_tour(self, events):
  732.         events = self.defutured(events)
  733.         if events:
  734.             event_strings = []
  735.             for event in events:
  736.                 event_strings.append(str(TourEvent(event)))
  737.             self.builder.get_object('label_tour_info').set_text('\n\n'.join(event_strings))
  738.             # self.builder.get_object('label_tour_info').set_text('\n\n'.join(event_strings).strip())
  739.             self.builder.get_object('togglebutton_on_tour').set_visible(True)
  740.             self.readjust('scrolledwindow_tour_info')
  741.             # self.builder.get_object('scrolledwindow_tour_info').get_vadjustment().set_value(0)
  742.             # self.builder.get_object('scrolledwindow_tour_info').get_hadjustment().set_value(0)
  743.         else:
  744.             self.deactivate('togglebutton_on_tour')
  745.             self.builder.get_object('togglebutton_on_tour').set_visible(False)
  746.         # self.builder.get_object('scrolledwindow_tour_info').get_vadjustment().set_value(0)
  747.  
  748.     def on_togglebutton_track_info_toggled(self, widget):
  749.         self.toggle_context('scrolledwindow_track_info', widget)
  750.  
  751.     def on_togglebutton_on_tour_toggled(self, widget):
  752.         self.toggle_context('scrolledwindow_tour_info', widget)
  753.  
  754.     def on_progressbar_track_button_press_event(self, widget, event):
  755.         self.progressbar_text_on = not getattr(self, 'progressbar_text_on', False)
  756.  
  757.     def on_image_album_core_button_press_event(self, widget, eventbutton=None):
  758.         window = self.builder.get_object('window_large_art')
  759.         if window.get_visible():
  760.             window.hide()
  761.         else:
  762.             window.show()
  763.  
  764.     def on_image_large_art_button_press_event(self, widget, eventbutton=None):
  765.         self.builder.get_object('window_large_art').hide()
  766.  
  767.     @property
  768.     def current_track_remaining_seconds(self):
  769.         return self.service.player.current_track_remaining_seconds
  770.     @current_track_remaining_seconds.setter
  771.     def current_track_remaining_seconds(self, seconds):
  772.         bar = self.builder.get_object('progressbar_track')
  773.         if seconds < 0:
  774.             bar.pulse()
  775.         else:
  776.             try:
  777.                 bar.set_fraction(seconds/self.current_track_duration)
  778.             except ZeroDivisionError:
  779.                 bar.set_fraction(0)
  780.             bar.set_text(self.time_string(seconds))
  781.  
  782.     def time_string(self, seconds):
  783.         hours, mins = divmod(seconds, 3600)
  784.         hours = str(hours)
  785.         mins, secs = [str(num) for num in divmod(mins, 60)]
  786.         if len(secs) == 1:
  787.             secs = '0' + secs
  788.         if hours != '0':
  789.             if len(mins) == 1:
  790.                 mins = '0' + mins
  791.             text = '{}:{}:{}'.format(hours, mins, secs)
  792.         elif mins != '0':
  793.             text = '{}:{}'.format(mins, secs)
  794.         else:
  795.             text = ':' + secs
  796.         return text
  797.  
  798.     @property
  799.     def current_track_duration(self):
  800.         return self.service.player.current_track_duration
  801.  
  802.     def on_togglebutton_title_toggled(self, widget):
  803.         self.toggle_context(['grid_title_actions', 'button_copy_title'], widget, ['togglebutton_artist', 'togglebutton_album'])
  804.         self.builder.get_object('grid_quick_title_actions').set_visible(not widget.get_active())
  805.  
  806.     def on_togglebutton_artist_toggled(self, widget):
  807.         self.toggle_context(['grid_artist_actions', 'button_copy_artist'], widget, ['togglebutton_title', 'togglebutton_album'])
  808.         self.builder.get_object('button_quick_similar').set_visible(not widget.get_active())
  809.  
  810.     def on_togglebutton_album_toggled(self, widget):
  811.         self.toggle_context(['grid_album_actions', 'button_copy_album'], widget, ['togglebutton_artist', 'togglebutton_title'])
  812.  
  813.     def on_button_share_track_clicked(self, widget):
  814.         pass
  815.  
  816.     @property
  817.     def current_track_title(self):
  818.         return self.service.player.current_track_title
  819.     @current_track_title.setter
  820.     def current_track_title(self, title):
  821.         self.builder.get_object('togglebutton_title').set_label(title)
  822.    
  823.     @property
  824.     def current_album_name(self):
  825.         return self.service.player.current_album_name
  826.     @current_album_name.setter
  827.     def current_album_name(self, album):
  828.         self.builder.get_object('togglebutton_album').set_label(album)
  829.  
  830.     @property
  831.     def current_artist_name(self):
  832.         return self.service.player.current_artist_name
  833.     @current_artist_name.setter
  834.     def current_artist_name(self, artist):
  835.         self.builder.get_object('togglebutton_artist').set_label(artist)
  836.  
  837.     def on_window_main_key_press_event(self, widget, event):
  838.         # this method of shortcut recognition may negatively impact responsiveness
  839.         if event.keyval in [65507, 65508]:# ctrl
  840.             self.ctrl_down = True
  841.         elif event.keyval in [108, 76]:# 'l' or 'L'
  842.             if getattr(self, 'ctrl_down', False):
  843.                 self.builder.get_object('comboboxtext-entry_station').grab_focus()
  844.  
  845.     def on_window_main_key_release_event(self, widget, event):
  846.         if event.keyval == 65507:
  847.             self.ctrl_down = False
  848.  
  849.  
  850. class TourEvent():
  851.  
  852.     def __init__(self, event_data):
  853.         self.data = event_data
  854.  
  855.     def __str__(self):
  856.         output = []
  857.         output += [self.data['startDate']]
  858.         output += [self.data['title']]
  859.         artists = self.data['artists']['artist']
  860.         if isinstance(artists, str):
  861.             output += ['with ' + artists]
  862.         else:
  863.             output += ['with ' + ' and '.join([', '.join(artists[:-1]), artists[-1]])]
  864.         output += [', '.join(['at ' + self.data['venue']['name'], 'in ' + self.data['venue']['location']['city'], self.data['venue']['location']['country']])]
  865.         output += [self.data['venue']['website']]
  866.         ouput = [line.strip() for line in output]
  867.         # for prop in ['tickets', 'url', 'attendance', 'description', 'image']:
  868.             # print(prop, ':', self.data[prop])
  869.         return '\n'.join(output).strip()
  870.  
  871.  
  872.  
  873. if __name__ == '__main__':
  874.     GTKUI()
  875.     Gtk.main()
Add Comment
Please, Sign In to add comment