class RadioPopover(Gtk.Popover): """ Popover with radio logos from the web @Warning: destroy it self on close """ def __init__(self, name, radios_manager): """ Init Popover @param name as string @param radios_manager as RadiosManager """ Gtk.Popover.__init__(self) self.connect("map", self.__on_map) self.connect("unmap", self.__on_unmap) self.__name = name self.__radios_manager = radios_manager self.__start = 0 self.__orig_pixbufs = {} self.__stack = Gtk.Stack() self.__stack.set_transition_duration(1000) self.__stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) 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.__name_entry = builder.get_object("name") self.__uri_entry = builder.get_object("uri") self.__btn_add_modify = builder.get_object("btn_add_modify") self.__spinner = builder.get_object("spinner") self.__stack.add_named(builder.get_object("spinner-grid"), "spinner") self.__stack.add_named(builder.get_object("notfound"), "notfound") self.__stack.add_named(builder.get_object("logo"), "logo") self.__stack.add_named(builder.get_object("widget"), "widget") self.__stack.set_visible_child_name("widget") self.add(self.__stack) track = Track() track.set_radio(name, "") rating = RatingWidget(track) rating.show() builder.get_object("widget").attach(rating, 0, 2, 2, 1) if self.__name == "": # Translators: radio context builder.get_object("btn_add_modify").set_label(_("Add")) else: # Translators: radio context builder.get_object("btn_add_modify").set_label(_("Modify")) builder.get_object("btn_delete").show() self.__name_entry.set_text(self.__name) url = self.__radios_manager.get_url(self.__name) if url: self.__uri_entry.set_text(url) ####################### # PROTECTED # ####################### 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") if self._urls: 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 = Lio.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() def __show_not_found(self): """ Show not found message if view empty """ if len(self.__view.get_children()) == 0: self.__stack.set_visible_child_name("notfound") 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) stream.close() image = Gtk.Image() image.get_style_context().add_class("cover-frame") image.set_property("halign", Gtk.Align.CENTER) image.set_property("valign", Gtk.Align.CENTER) 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, self.get_scale_factor(), None) del scaled_pixbuf image.set_from_surface(surface) del surface image.show() self.__view.add(image) except Exception as e: print(e) pass if self.__stack.get_visible_child_name() == "spinner": self.__spinner.stop() self.__stack.set_visible_child_name("logo") def __on_map(self, widget): """ Grab focus/Disable global shortcuts @param widget as Gtk.Widget """ GLib.idle_add(self.__name_entry.grab_focus) # FIXME Not needed with GTK >= 3.18 Lp().window.enable_global_shortcuts(False) def __on_unmap(self, widget): """ Enable global shortcuts, destroy @param widget as Gtk.Widget """ self._thread = False # FIXME Not needed with GTK >= 3.18 Lp().window.enable_global_shortcuts(True) GLib.idle_add(self.destroy) 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 != "": self.__stack.get_visible_child().hide() if rename: self.__radios_manager.rename(self.__name, new_name) Lp().art.rename_radio(self.__name, new_name) else: self.__radios_manager.add(new_name, uri.lstrip().rstrip()) self.__stack.set_visible_child_name("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 """ self.hide() if self.__name != "": store = Art._RADIOS_PATH self.__radios_manager.delete(self.__name) Lp().art.clean_radio_cache(self.__name) f = Lio.File.new_for_path(store + "/%s.png" % self.__name) if f.query_exists(): f.delete() 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) def _on_button_clicked(self, button): """ Show file chooser @param button as Gtk.button """ dialog = Gtk.FileChooserDialog() dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) dialog.add_buttons(Gtk.STOCK_OPEN, Gtk.ResponseType.OK) dialog.set_transient_for(Lp().window) self.hide() response = dialog.run() if response == Gtk.ResponseType.OK: try: pixbuf = GdkPixbuf.Pixbuf.new_from_file(dialog.get_filename()) Lp().art.save_radio_artwork(pixbuf, self.__name) Lp().art.clean_radio_cache(self.__name) Lp().art.radio_artwork_update(self.__name) self._streams = {} except Exception as e: print("RadioPopover::_on_button_clicked():", e) dialog.destroy() ####################### # PRIVATE # ####################### 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_artwork(pixbuf, self.__name) Lp().art.clean_radio_cache(self.__name) Lp().art.radio_artwork_update(self.__name) self.hide() self._streams = {}
class RadioPopover(Gtk.Popover): """ Popover with radio logos from the web @Warning: destroy it self on close """ def __init__(self, name, radios_manager): """ Init Popover @param name as string @param radios_manager as RadiosManager """ Gtk.Popover.__init__(self) self.connect('map', self._on_map) self.connect('unmap', self._on_unmap) self._name = name self._radios_manager = radios_manager self._start = 0 self._orig_pixbufs = {} self._stack = Gtk.Stack() self._stack.set_transition_duration(1000) self._stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) 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._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_named(builder.get_object('spinner'), 'spinner') self._stack.add_named(builder.get_object('notfound'), 'notfound') self._stack.add_named(builder.get_object('logo'), 'logo') self._stack.add_named(builder.get_object('widget'), 'widget') self._stack.set_visible_child_name('widget') self.add(self._stack) track = Track() track.set_radio(name, '') rating = RatingWidget(track) rating.show() builder.get_object('widget').attach(rating, 0, 2, 2, 1) 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) url = self._radios_manager.get_url(self._name) if url: self._uri_entry.set_text(url) ####################### # 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_duck_arts(self._name+"+logo+radio") if self._urls: 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() def _show_not_found(self): """ Show not found message if view empty """ if len(self._view.get_children()) == 0: self._stack.set_visible_child_name('notfound') 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 if self._stack.get_visible_child_name() == 'spinner': self._stack.set_visible_child_name('logo') def _on_map(self, widget): """ Grab focus/Disable global shortcuts @param widget as Gtk.Widget """ self._name_entry.grab_focus() Lp().window.enable_global_shorcuts(False) def _on_unmap(self, widget): """ Enable global shortcuts, destroy @param widget as Gtk.Widget """ self._thread = False Lp().window.enable_global_shorcuts(True) GLib.idle_add(self.destroy) 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 != '': self._stack.get_visible_child().hide() if rename: self._radios_manager.rename(self._name, new_name) Lp().art.rename_radio(self._name, new_name) else: self._radios_manager.add(new_name, uri.lstrip().rstrip()) self._stack.set_visible_child_name('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 """ self.hide() 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_artwork(pixbuf, self._name) Lp().art.clean_radio_cache(self._name) Lp().art.radio_artwork_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)