class RadioPopover(Gtk.Popover): """ Popover with radio logos from the web """ def __init__(self, name, radios_manager): """ Init Popover @param name as string @param radios_manager as RadiosManager """ Gtk.Popover.__init__(self) self._name = name self._radios_manager = radios_manager self._start = 0 self._orig_pixbufs = {} self._stack = ViewContainer(1000) self._stack.show() builder = Gtk.Builder() builder.add_from_resource('/org/gnome/Lollypop/RadioPopover.ui') builder.connect_signals(self) self._view = Gtk.FlowBox() self._view.set_selection_mode(Gtk.SelectionMode.NONE) self._view.connect('child-activated', self._on_activate) self._view.set_max_children_per_line(100) self._view.set_property('row-spacing', 10) self._view.show() builder.get_object('viewport').add(self._view) self._widget = builder.get_object('widget') self._logo = builder.get_object('logo') self._spinner = builder.get_object('spinner') self._not_found = builder.get_object('notfound') self._name_entry = builder.get_object('name') self._uri_entry = builder.get_object('uri') self._btn_add_modify = builder.get_object('btn_add_modify') self._stack.add(self._spinner) self._stack.add(self._not_found) self._stack.add(self._logo) self._stack.add(self._widget) self._stack.set_visible_child(self._widget) self.add(self._stack) if self._name == '': builder.get_object('btn_add_modify').set_label(_("Add")) else: builder.get_object('btn_add_modify').set_label(_("Modify")) builder.get_object('btn_delete').show() self._name_entry.set_text(self._name) uris = self._radios_manager.get_tracks(self._name) if len(uris) > 0: self._uri_entry.set_text(uris[0]) def do_show(self): """ Resize popover and set signals callback """ Gtk.Popover.do_show(self) self._name_entry.grab_focus() Lp.window.enable_global_shorcuts(False) def do_hide(self): """ Kill thread """ self._thread = False Gtk.Popover.do_hide(self) Lp.window.enable_global_shorcuts(True) ####################### # PRIVATE # ####################### def _populate_threaded(self): """ Populate view """ self._thread = True start_new_thread(self._populate, ()) def _populate(self): """ Same as _populate_threaded() @thread safe """ self._urls = Lp.art.get_google_arts(self._name + "+logo+radio", self._start) if self._urls: self._start += GOOGLE_INC self._add_pixbufs() else: GLib.idle_add(self._show_not_found) def _add_pixbufs(self): """ Add urls to the view """ if self._urls: url = self._urls.pop() stream = None try: f = Gio.File.new_for_uri(url) (status, data, tag) = f.load_contents() if status: stream = Gio.MemoryInputStream.new_from_data(data, None) except: if self._thread: self._add_pixbufs() if stream: GLib.idle_add(self._add_pixbuf, stream) if self._thread: self._add_pixbufs() elif self._start < GOOGLE_MAX: self._populate_threaded() def _show_not_found(self): """ Show not found message if view empty """ if len(self._view.get_children()) == 0: self._stack.set_visible_child(self._not_found) self._stack.clean_old_views(self._not_found) def _add_pixbuf(self, stream): """ Add stream to the view """ try: pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale( stream, ArtSize.MONSTER, ArtSize.MONSTER, True, None) image = Gtk.Image() self._orig_pixbufs[image] = pixbuf # Scale preserving aspect ratio width = pixbuf.get_width() height = pixbuf.get_height() if width > height: height = height * ArtSize.BIG * self.get_scale_factor() / width width = ArtSize.BIG * self.get_scale_factor() else: width = width * ArtSize.BIG * self.get_scale_factor() / height height = ArtSize.BIG * self.get_scale_factor() scaled_pixbuf = pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR) del pixbuf surface = Gdk.cairo_surface_create_from_pixbuf( scaled_pixbuf, 0, None) del scaled_pixbuf image.set_from_surface(surface) del surface image.show() self._view.add(image) except Exception as e: print(e) pass # Remove spinner if exist if self._spinner is not None: self._stack.set_visible_child(self._logo) self._stack.clean_old_views(self._logo) self._spinner = None def _on_btn_add_modify_clicked(self, widget): """ Add/Modify a radio @param widget as Gtk.Widget """ uri = self._uri_entry.get_text() new_name = self._name_entry.get_text() rename = self._name != '' and self._name != new_name if uri != '' and new_name != '': if rename: self._radios_manager.rename(new_name, self._name) else: self._radios_manager.add(new_name) self._radios_manager.add_track(new_name, uri.lstrip().rstrip()) self._stack.remove(self._widget) self._stack.set_visible_child(self._spinner) self._name = new_name self._populate_threaded() self.set_size_request(700, 400) def _on_btn_delete_clicked(self, widget): """ Delete a radio @param widget as Gtk.Widget """ if self._name != '': cache = Art._RADIOS_PATH self._radios_manager.delete(self._name) Lp.art.clean_radio_cache(self._name) if os.path.exists(cache + "/%s.png" % self._name): os.remove(cache + "/%s.png" % self._name) def _on_activate(self, flowbox, child): """ Use pixbuf as cover Reset cache and use player object to announce cover change """ pixbuf = self._orig_pixbufs[child.get_child()] Lp.art.save_radio_logo(pixbuf, self._name) Lp.art.clean_radio_cache(self._name) Lp.art.announce_logo_update(self._name) self.hide() self._streams = {} def _on_entry_changed(self, entry): """ Update modify/add button @param entry as Gtk.Entry """ uri = self._uri_entry.get_text() name = self._name_entry.get_text() if name != '' and uri.find('://') != -1: self._btn_add_modify.set_sensitive(True) else: self._btn_add_modify.set_sensitive(False)
class RadioPopover(Gtk.Popover): """ Popover with radio logos from the web """ def __init__(self, name, radios_manager): """ Init Popover @param name as string @param radios_manager as RadiosManager """ Gtk.Popover.__init__(self) self._name = name self._radios_manager = radios_manager self._start = 0 self._orig_pixbufs = {} self._stack = ViewContainer(1000) self._stack.show() builder = Gtk.Builder() builder.add_from_resource('/org/gnome/Lollypop/RadioPopover.ui') builder.connect_signals(self) self._view = Gtk.FlowBox() self._view.set_selection_mode(Gtk.SelectionMode.NONE) self._view.connect('child-activated', self._on_activate) self._view.set_max_children_per_line(100) self._view.set_property('row-spacing', 10) self._view.show() builder.get_object('viewport').add(self._view) self._widget = builder.get_object('widget') self._logo = builder.get_object('logo') self._spinner = builder.get_object('spinner') self._not_found = builder.get_object('notfound') self._name_entry = builder.get_object('name') self._uri_entry = builder.get_object('uri') self._btn_add_modify = builder.get_object('btn_add_modify') self._stack.add(self._spinner) self._stack.add(self._not_found) self._stack.add(self._logo) self._stack.add(self._widget) self._stack.set_visible_child(self._widget) self.add(self._stack) if self._name == '': builder.get_object('btn_add_modify').set_label(_("Add")) else: builder.get_object('btn_add_modify').set_label(_("Modify")) builder.get_object('btn_delete').show() self._name_entry.set_text(self._name) uris = self._radios_manager.get_tracks(self._name) if len(uris) > 0: self._uri_entry.set_text(uris[0]) def do_show(self): """ Resize popover and set signals callback """ Gtk.Popover.do_show(self) self._name_entry.grab_focus() Lp.window.enable_global_shorcuts(False) def do_hide(self): """ Kill thread """ self._thread = False Gtk.Popover.do_hide(self) Lp.window.enable_global_shorcuts(True) ####################### # PRIVATE # ####################### def _populate_threaded(self): """ Populate view """ self._thread = True t = Thread(target=self._populate) t.daemon = True t.start() def _populate(self): """ Same as _populate_threaded() @thread safe """ self._urls = Lp.art.get_google_arts(self._name+"+logo+radio", self._start) if self._urls: self._start += GOOGLE_INC self._add_pixbufs() else: GLib.idle_add(self._show_not_found) def _add_pixbufs(self): """ Add urls to the view """ if self._urls: url = self._urls.pop() stream = None try: f = Gio.File.new_for_uri(url) (status, data, tag) = f.load_contents() if status: stream = Gio.MemoryInputStream.new_from_data(data, None) except: if self._thread: self._add_pixbufs() if stream: GLib.idle_add(self._add_pixbuf, stream) if self._thread: self._add_pixbufs() elif self._start < GOOGLE_MAX: self._populate_threaded() def _show_not_found(self): """ Show not found message if view empty """ if len(self._view.get_children()) == 0: self._stack.set_visible_child(self._not_found) self._stack.clean_old_views(self._not_found) def _add_pixbuf(self, stream): """ Add stream to the view """ try: pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale( stream, ArtSize.MONSTER, ArtSize.MONSTER, True, None) image = Gtk.Image() self._orig_pixbufs[image] = pixbuf # Scale preserving aspect ratio width = pixbuf.get_width() height = pixbuf.get_height() if width > height: height = height*ArtSize.BIG*self.get_scale_factor()/width width = ArtSize.BIG*self.get_scale_factor() else: width = width*ArtSize.BIG*self.get_scale_factor()/height height = ArtSize.BIG*self.get_scale_factor() scaled_pixbuf = pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR) del pixbuf surface = Gdk.cairo_surface_create_from_pixbuf(scaled_pixbuf, 0, None) del scaled_pixbuf image.set_from_surface(surface) del surface image.show() self._view.add(image) except Exception as e: print(e) pass # Remove spinner if exist if self._spinner is not None: self._stack.set_visible_child(self._logo) self._stack.clean_old_views(self._logo) self._spinner = None def _on_btn_add_modify_clicked(self, widget): """ Add/Modify a radio @param widget as Gtk.Widget """ uri = self._uri_entry.get_text() new_name = self._name_entry.get_text() rename = self._name != '' and self._name != new_name if uri != '' and new_name != '': if rename: self._radios_manager.rename(new_name, self._name) else: self._radios_manager.add(new_name) self._radios_manager.add_track(new_name, uri.lstrip().rstrip()) self._stack.remove(self._widget) self._stack.set_visible_child(self._spinner) self._name = new_name self._populate_threaded() self.set_size_request(700, 400) def _on_btn_delete_clicked(self, widget): """ Delete a radio @param widget as Gtk.Widget """ if self._name != '': cache = Art._RADIOS_PATH self._radios_manager.delete(self._name) Lp.art.clean_radio_cache(self._name) if os.path.exists(cache+"/%s.png" % self._name): os.remove(cache+"/%s.png" % self._name) def _on_activate(self, flowbox, child): """ Use pixbuf as cover Reset cache and use player object to announce cover change """ pixbuf = self._orig_pixbufs[child.get_child()] Lp.art.save_radio_logo(pixbuf, self._name) Lp.art.clean_radio_cache(self._name) Lp.art.announce_logo_update(self._name) self.hide() self._streams = {} def _on_entry_changed(self, entry): """ Update modify/add button @param entry as Gtk.Entry """ uri = self._uri_entry.get_text() name = self._name_entry.get_text() if name != '' and uri.find('://') != -1: self._btn_add_modify.set_sensitive(True) else: self._btn_add_modify.set_sensitive(False)