def __button_press_event(self, event): if self.__browser_id == blaconst.BROWSER_FILESYSTEM: return False # Return on events that don't require any special treatment. if ((event.button == 1 and not (event.type == gtk.gdk._2BUTTON_PRESS or event.type == gtk.gdk._3BUTTON_PRESS)) or event.type == gtk.gdk._3BUTTON_PRESS or (event.button == 2 and event.type == gtk.gdk._2BUTTON_PRESS)): return False if event.button == 1: action = blacfg.getint("library", "doubleclick.action") elif event.button == 2: action = blacfg.getint("library", "middleclick.action") path = self.get_path_at_pos(*map(int, [event.x, event.y]))[0] # TODO: remove me if False and not self.__accept_button_event(column, path, event, check_expander=(event.button == 1)): self.__allow_selection(True) selection.unselect_all() # Handle LMB events if event.button == 1 and action == blaconst.ACTION_EXPAND_COLLAPSE: if self.row_expanded(path): self.collapse_row(path) else: self.expand_row(path, open_all=False) return False # On middle-clicks we must update the selection due to the way DND is # implemented. if event.button == 2: selection = self.get_selection() selection.unselect_all() selection.select_path(path) model = self.get_model() name = model[path][-1] tracks = self.get_tracks() if action == blaconst.ACTION_SEND_TO_CURRENT: playlist_manager.send_to_current_playlist(tracks) elif action == blaconst.ACTION_ADD_TO_CURRENT: playlist_manager.add_to_current_playlist(tracks) elif action == blaconst.ACTION_SEND_TO_NEW: playlist_manager.send_to_new_playlist(tracks, name) return False
def __key_press_event(self, treeview, event): if self.__browser_id == blaconst.BROWSER_FILESYSTEM: return False if blagui.is_accel(event, "Q"): self.__send_to_queue() elif (blagui.is_accel(event, "Return") or blagui.is_accel(event, "KP_Enter")): action = blacfg.getint("library", "return.action") selections = self.get_selection().get_selected_rows()[-1] if not selections: return True name = self.get_model()[selections[0]][-1] tracks = self.get_tracks() if action == blaconst.ACTION_SEND_TO_CURRENT: playlist_manager.send_to_current_playlist(tracks) elif action == blaconst.ACTION_ADD_TO_CURRENT: playlist_manager.add_to_current_playlist(tracks) elif action == blaconst.ACTION_SEND_TO_NEW: playlist_manager.send_to_new_playlist(tracks, name) return False
def __new__(cls, name, bases, dct): # Make sure at least one baseclass inherits from gobject.GObject. if not any([issubclass(base, gobject.GObject) for base in bases]): raise TypeError("%s does not inherit from gobject.GObject" % name) # Add the view_name property. if "view_name" in dct: raise ValueError("View class %s already defines an attribute " "'view_name'" % name) dct["view_name"] = property(lambda self: view_name) # Add the count_changed signal. signals = dct.get("__gsignals__", {}) if "count_changed" in signals or "count-changed" in signals: raise ValueError("Class %s already defines a 'count_changed' " "signal" % name) signals["count_changed"] = blautil.signal(2) dct["__gsignals__"] = signals # Add the init-function stub. if "init" not in dct: dct["init"] = lambda self: None # Add default behavior for `update_statusbar()'. if "update_statusbar" not in dct: dct["update_statusbar"] = lambda s: BlaStatusbar.set_view_info( blacfg.getint("general", "view"), "") return super(_BlaViewMeta, cls).__new__(cls, name, bases, dct)
def set_view(self, view): view_prev = blacfg.getint("general", "view") blacfg.set("general", "view", view) if player.video: # If the previous view was the video view coerce the cover art # display into acting as new video canvas. self.__side_pane.cover_display.use_as_video_canvas( view != blaconst.VIEW_VIDEO) child = self.__container.get_child() if view == view_prev and child is not None: return if child is not None: self.__container.remove(child) child = self.__views[view] if child.get_parent() is not None: child.unparent() self.__container.add(child) child.update_statusbar() # Not all menu items are available for all views so update them # accordingly. ui_manager.update_menu(view) self.__side_pane.set_active_view(view)
def set_view_info(cls, view, string): # TODO: get rid of the view argument if view == blacfg.getint("general", "view"): try: cls.__instance.__view_info.set_text(string) except AttributeError: pass return False
def __shuffle(self, value=None): # Read value if value is None: return (blacfg.getint("general", "play.order") == blaconst.ORDER_SHUFFLE) else: # TODO: see __loop_status pass
def populated(model): self.__expanded_rows = [] self.__treeview.set_model(model) organize_by = blacfg.getint("library", "organize.by") if (organize_by == blaconst.ORGANIZE_BY_DIRECTORY and model.get_iter_first()): self.__treeview.expand_row((0,), open_all=False) try: self.window.set_cursor(None) except AttributeError: pass
def sync_handler(): view = blacfg.getint("general", "view") if view == blaconst.VIEW_VIDEO: element = self.__views[blaconst.VIEW_VIDEO] else: element = self.__side_pane.cover_display # Coerce the cover display into a video canvas. element.use_as_video_canvas(True) canvas = element.get_video_canvas() if canvas.get_realized(): return canvas.window.xid print_w("Drawing area for video playback not yet realized") return 0
def __get_station(self, choice): def get_random(old=None): idx_max = len(model)-1 path = randint(0, idx_max) if old is not None and idx_max > 0: while path == old[0]: path = randint(0, idx_max) return (path,) model = self.__treeview.get_model() for row in model: row[0] = None path = (0,) # FIXME: # The `choice' variable can either be a direction constant as defined # in blaconst if the method is invoked as player callback or a # treemodel path if it's invoked as row_activated callback on the # treeview. if not isinstance(choice, tuple): if len(model) == 0: return player.play_station(None) if self.__current: for row in model: if row[1] == self.__current: path = row.path break order = blacfg.getint("general", "play.order") if choice == blaconst.TRACK_RANDOM: path = get_random() elif order == blaconst.ORDER_SHUFFLE: path = get_random(path) else: if choice == blaconst.TRACK_NEXT: iterator = model.iter_next(model.get_iter(path)) if not iterator: return player.play_station(None) path = model.get_path(iterator) elif choice == blaconst.TRACK_PREVIOUS: if path[0] < 1: return player.play_station(None) path = (path[0]-1,) else: path = choice self.__treeview.set_cursor(path) self.__current = model[path][1] player.play_station(self.__current)
def __loop_status(self, value=None): # Read value if value is None: order = blacfg.getint("general", "play.order") if order == blaconst.ORDER_REPEAT: return "Track" elif order == blaconst.ORDER_SHUFFLE: return "Playlist" return "None" else: # TODO: export a method somewhere which changes the play order. # better yet, let widgets listen for changes of the config # and check if play.order changed. pass
def __query(self, uri): track = library[uri] strings = [track[identifier] for identifier in (ARTIST, TITLE, ALBUM)] if (blacfg.getint("library", "organize.by") == blaconst.ORGANIZE_BY_DIRECTORY): strings.append(track.basename) for r in self.__res: search = r.search for string in strings: if search(string): break else: return False return True
def __init__(self): super(BlaBrowsers, self).__init__() type(self).__library_browser = BlaLibraryBrowser(self) self.__file_browser = BlaFileBrowser(self) self.append_page(self.__library_browser, gtk.Label("Library")) self.append_page(self.__file_browser, gtk.Label("Filesystem")) self.show_all() page_num = blacfg.getint("general", "browser.view") if page_num not in (0, 1): page_num = 0 self.set_current_page(page_num) self.connect("switch_page", lambda *x: blacfg.set("general", "browser.view", x[-1]))
def play(self): if not self.__uri: if blacfg.getint("general", "view") == blaconst.VIEW_RADIO: args = ("get_station", blaconst.TRACK_PLAY) else: args = ("get_track", blaconst.TRACK_PLAY, True) return self.emit(*args) # Check if the resource is available. If it's not it's best to stop # trying and inform the user about the situation. If we'd just ask # for another track we'd potentially end up hitting the interpreter's # recursion limit in case lots of tracks turn out to be invalid. if not os.path.exists(self.__uri) or not os.path.isfile(self.__uri): from blaplay.blagui import blaguiutils uri = self.__uri self.stop() blaguiutils.error_dialog("Playback error", "Resource \"%s\" unavailable." % uri) return # If `update_track' returns None it means the track needed updating, # but failed to be parsed properly so request another song. if library.update_track(self.__uri) is None: self.emit("get_track", blaconst.TRACK_PLAY, True) if (self.__state == blaconst.STATE_STOPPED and not self.__init_pipeline()): return self.__bin.set_state(gst.STATE_NULL) self.__bin.set_property("uri", "file://%s" % self.__uri) self.__bin.set_state(gst.STATE_PLAYING) self.__station = None self.__state = blaconst.STATE_PLAYING self.emit("track_changed") self.emit("state_changed")
def queue_model_update(*args): self.__queue_model_update(blacfg.getint("library", "organize.by"))
def __init__(self, parent): super(BlaLibraryBrowser, self).__init__() self.__treeview = BlaTreeView(parent=parent, multicol=False, browser_id=blaconst.BROWSER_LIBRARY) self.__treeview.set_headers_visible(False) column = gtk.TreeViewColumn() column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) self.__treeview.append_column(column) self.__treeview.connect("row_collapsed", self.__row_collapsed) self.__treeview.connect("row_expanded", self.__row_expanded) self.__treeview.enable_model_drag_source( gtk.gdk.BUTTON1_MASK, [blagui.DND_TARGETS[blagui.DND_LIBRARY]], gtk.gdk.ACTION_COPY) self.__treeview.connect_object( "drag_data_get", BlaLibraryBrowser.__drag_data_get, self) sw = BlaScrolledWindow() sw.add(self.__treeview) hbox = gtk.HBox() cb = gtk.combo_box_new_text() for label in ["directory", "artist", "artist - album", "album", "genre", "year"]: cb.append_text(label) cb.set_active(blacfg.getint("library", "organize.by")) cb.connect("changed", self.__organize_by_changed) alignment = gtk.Alignment() alignment.add(gtk.Label("Organize by:")) table = gtk.Table(rows=2, columns=1, homogeneous=False) table.attach(alignment, 0, 1, 0, 1, xpadding=2, ypadding=2) table.attach(cb, 0, 1, 1, 2) hbox.pack_start(table, expand=False) def queue_model_update(*args): self.__queue_model_update(blacfg.getint("library", "organize.by")) self.__entry = gtk.Entry() self.__entry.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, gtk.STOCK_CLEAR) self.__entry.connect( "icon_release", lambda *x: x[0].delete_text(0, -1)) self.__entry.connect("changed", self.__filter_parameters_changed) self.__entry.connect("activate", queue_model_update) button = gtk.Button() button.add( gtk.image_new_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_SMALL_TOOLBAR)) button.connect("clicked", queue_model_update) alignment = gtk.Alignment() alignment.add(gtk.Label("Filter:")) table = gtk.Table(rows=2, columns=1, homogeneous=False) table.attach(alignment, 0, 1, 0, 1, xpadding=2, ypadding=2) hbox2 = gtk.HBox() hbox2.pack_start(self.__entry, expand=True) hbox2.pack_start(button, expand=False) table.attach(hbox2, 0, 1, 1, 2) hbox.pack_start(table) self.pack_start(sw, expand=True) self.pack_start(hbox, expand=False) self.update_treeview_style() self.update_tree_lines() def config_changed(cfg, section, key): if section == "library": if key == "custom.browser": self.update_treeview_style() elif key == "draw.tree.lines": self.update_tree_lines() blacfg.connect("changed", config_changed) library.connect("library_updated", queue_model_update) queue_model_update()
def __init__(self, views): super(BlaSidePane, self).__init__(spacing=blaconst.WIDGET_SPACING) notebook = gtk.Notebook() notebook.set_scrollable(True) # Set up the lyrics textview. self.__tv = gtk.TextView() self.__tv.set_size_request(self.__MIN_WIDTH, -1) self.__tv.set_editable(False) self.__tv.set_cursor_visible(False) self.__tv.set_wrap_mode(gtk.WRAP_WORD) self.__tv.set_justification(gtk.JUSTIFY_CENTER) sw = BlaScrolledWindow() sw.set_shadow_type(gtk.SHADOW_NONE) sw.add(self.__tv) sw.show_all() self.__style = self.__tv.get_modifier_style().copy() self.__tb = self.__tv.get_buffer() self.__tb.create_tag("bold", weight=pango.WEIGHT_BOLD) self.__tb.create_tag("large", scale=pango.SCALE_LARGE) self.__tb.create_tag("italic", style=pango.STYLE_ITALIC) self.__tag = self.__tb.create_tag("color") # Set up the view selector. viewport = gtk.Viewport() viewport.set_shadow_type(gtk.SHADOW_IN) self.__treeview = blaguiutils.BlaTreeViewBase( allow_empty_selection=False) self.__treeview.get_selection().set_mode(gtk.SELECTION_SINGLE) self.__treeview.set_headers_visible(False) self.__treeview.set_property("rules_hint", True) r = gtk.CellRendererText() r.set_property("ellipsize", pango.ELLIPSIZE_END) c = gtk.TreeViewColumn() c.pack_start(r, expand=True) c.add_attribute(r, "text", 0) r = gtk.CellRendererText() r.set_alignment(1.0, 0.5) c.pack_start(r, expand=False) def cell_data_func(column, renderer, model, iterator): count = model[iterator][1] renderer.set_property( "markup", "<i>(%d)</i>" % count if count > 0 else "") c.set_cell_data_func(r, cell_data_func) self.__treeview.append_column(c) viewport.add(self.__treeview) model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_INT) self.__treeview.set_model(model) [model.append([view.view_name, 0]) for view in views] selection = self.__treeview.get_selection() selection.select_path(blacfg.getint("general", "view")) self.__treeview.get_selection().connect( "changed", self.__selection_changed) self.cover_display = BlaSidePane.BlaCoverDisplay() hbox = gtk.HBox(spacing=blaconst.WIDGET_SPACING) hbox.pack_start(viewport, expand=True) hbox.pack_start(self.cover_display, expand=False, fill=True) notebook.append_page(BlaTagEditor(views[blaconst.VIEW_PLAYLISTS]), gtk.Label("Tags")) notebook.append_page(BlaProperties(views[blaconst.VIEW_PLAYLISTS]), gtk.Label("Properties")) notebook.append_page(sw, gtk.Label("Lyrics")) def switch_page(notebook, page, page_num): action_widget = notebook.get_action_widget(gtk.PACK_END) if action_widget.child is not None: action_widget.remove(action_widget.child) page = notebook.get_nth_page(page_num) blacfg.set("general", "metadata.view", page_num) if page_num == blaconst.METADATA_TAGS: widget = page.get_control_widget() elif page_num == blaconst.METADATA_LYRICS: button = gtk.Button() button.set_tooltip_text("Edit lyrics") button.set_relief(gtk.RELIEF_NONE) button.set_focus_on_click(False) button.add( gtk.image_new_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_MENU)) style = gtk.RcStyle() style.xthickness = style.ythickness = 0 button.modify_style(style) # TODO: Implement a widget to edit metadata. button.connect("clicked", lambda *x: False) button.show_all() widget = button action_widget.set_visible(False) return else: action_widget.set_visible(False) return action_widget.add(widget) action_widget.set_visible(True) action_widget.show_all() viewport = gtk.Viewport() viewport.set_shadow_type(gtk.SHADOW_NONE) notebook.set_action_widget(viewport, gtk.PACK_END) notebook.connect("switch_page", switch_page) page_num = blacfg.getint("general", "metadata.view") notebook.set_current_page(page_num) # Make sure the notebook's action widget gets initialized. switch_page(notebook, None, page_num) self.pack_start(notebook, expand=True) self.pack_start(hbox, expand=False) # Hook up the metadata callbacks. BlaSidePane.fetcher = blametadata.BlaFetcher() self.fetcher.connect_object( "lyrics", BlaSidePane.__update_lyrics, self) self.fetcher.connect_object( "cover", type(self.cover_display).update, self.cover_display) notebook.show() hbox.show_all() self.show()
def __init__(self): super(BlaView, self).__init__() actions = [ ("Clear", None, "_Clear", None, "", self.__clear), ("SelectAll", None, "All", None, "", lambda *x: self.__select(blaconst.SELECT_ALL)), ("SelectComplement", None, "Complement", None, "", lambda *x: self.__select(blaconst.SELECT_COMPLEMENT)), ("SelectByArtist", None, "By artist", None, "", lambda *x: self.__select(blaconst.SELECT_BY_ARTISTS)), ("SelectByAlbum", None, "By album", None, "", lambda *x: self.__select(blaconst.SELECT_BY_ALBUMS)), ("SelectByAlbumArtist", None, "By album artist", None, "", lambda *x: self.__select(blaconst.SELECT_BY_ALBUM_ARTISTS)), ("SelectByGenre", None, "By genre", None, "", lambda *x: self.__select(blaconst.SELECT_BY_GENRES)), ("Cut", None, "Cut", None, "", self.__cut), ("Copy", None, "Copy", None, "", self.__copy), ("Remove", None, "Remove", None, "", self.__remove), ("Paste", None, "Paste", None, "", self.__paste), ("RemoveDuplicates", None, "Remove _duplicates", None, "", self.__remove_duplicates), ("RemoveInvalidTracks", None, "Remove _invalid tracks", None, "", self.__remove_invalid_tracks) ] ui_manager.add_actions(actions) radio_actions = [ ("Playlists", None, "_Playlists", None, "", blaconst.VIEW_PLAYLISTS), ("Queue", None, "_Queue", None, "", blaconst.VIEW_QUEUE), ("Radio", None, "R_adio", None, "", blaconst.VIEW_RADIO), ("Video", None, "_Video", None, "", blaconst.VIEW_VIDEO), ("RecommendedEvents", None, "_Recommended events", None, "", blaconst.VIEW_EVENTS), ("NewReleases", None, "_New releases", None, "", blaconst.VIEW_RELEASES), ] ui_manager.add_radio_actions( radio_actions, value=blacfg.getint("general", "view"), on_change=lambda *x: self.set_view( x[-1].get_current_value())) from blaplaylist import playlist_manager from blaqueue import queue from blavideo import BlaVideo from blaradio import BlaRadio from blaeventbrowser import BlaEventBrowser from blareleasebrowser import BlaReleaseBrowser self.__views = [playlist_manager, queue, BlaRadio(), BlaVideo(), BlaEventBrowser(), BlaReleaseBrowser()] self.__container = gtk.Viewport() self.__container.set_shadow_type(gtk.SHADOW_NONE) self.__side_pane = BlaSidePane(self.__views) player.connect( "state_changed", lambda *x: self.__side_pane.update_track()) # The sync handler gets called every time gstreamer needs an xwindow # for rendering video. def sync_handler(): view = blacfg.getint("general", "view") if view == blaconst.VIEW_VIDEO: element = self.__views[blaconst.VIEW_VIDEO] else: element = self.__side_pane.cover_display # Coerce the cover display into a video canvas. element.use_as_video_canvas(True) canvas = element.get_video_canvas() if canvas.get_realized(): return canvas.window.xid print_w("Drawing area for video playback not yet realized") return 0 player.set_sync_handler(sync_handler) for view in self.__views: view.connect("count_changed", self.__side_pane.update_count) # We have to defer initialization until all count_changed signal # handlers have been hooked up. for view in self.__views: view.init() self.show() self.__container.show_all() self.__side_pane.show() self.pack1(self.__container, resize=True, shrink=False) self.pack2(self.__side_pane, resize=False, shrink=False) def startup_complete(*args): self.set_view(blacfg.getint("general", "view")) blaplay.bla.connect("startup_complete", startup_complete)
def __button_press_event(self, event): def open_cover(*args): blautil.open_with_filehandler( self.__cover, "Failed to open image '%s'" % self.__cover) def fetch_cover(*args): BlaSidePane.fetcher.fetch_cover( BlaSidePane.track, self.__update_timestamp(), force_download=True) def set_cover(*args): diag = gtk.FileChooserDialog( "Select cover", buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) diag.set_local_only(True) response = diag.run() path = diag.get_filename() diag.destroy() if response == gtk.RESPONSE_OK and path: BlaSidePane.fetcher.set_cover( self.__update_timestamp(), path) def delete_cover(*args): BlaSidePane.fetcher.set_cover(self.__update_timestamp()) # If the cover art display is used to display video delegate the # button event to the default handler. if (blacfg.getint("general", "view") != blaconst.VIEW_VIDEO and player.video): return False if (event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS and self.__cover != blaconst.COVER): open_cover() elif (event.button == 3 and event.type not in [gtk.gdk._2BUTTON_PRESS, gtk.gdk._3BUTTON_PRESS]): menu = gtk.Menu() sensitive = self.__cover != blaconst.COVER items = [ ("Open in image viewer", open_cover, sensitive), ("Open directory", lambda *x: blautil.open_directory( os.path.dirname(self.__cover)), sensitive), None, ("Fetch cover", fetch_cover, True), ("Set cover...", lambda *x: set_cover(self.__update_timestamp()), True), ("Delete cover", delete_cover, sensitive) ] track = BlaSidePane.track if (player.get_state() == blaconst.STATE_STOPPED or not track[ARTIST] or not track[ALBUM]): state = False else: state = True for item in items: if not item: m = gtk.SeparatorMenuItem() else: label, callback, sensitive = item m = gtk.MenuItem(label) m.connect("activate", callback) m.set_sensitive(sensitive) menu.append(m) menu.show_all() menu.popup(None, None, None, event.button, event.time) return True
def __update_models(self): def set_sensitive(state): self.__hbox.set_sensitive(state) self.__treeview.set_sensitive(state) return False gobject.idle_add(set_sensitive, False) with self.__lock: images = set() releases = (blafm.get_new_releases(), blafm.get_new_releases(recommended=True)) active = blacfg.getint("general", "releases.filter") if releases[0]: self.__count_library = len(releases[0]) if releases[1]: self.__count_recommended = len(releases[1]) items = [ (blaconst.NEW_RELEASES_FROM_LIBRARY, releases[0] or []), (blaconst.NEW_RELEASES_RECOMMENDED, releases[1] or []) ] current_week = datetime.date.today().isocalendar()[:2] for filt, releases in items: model = gtk.ListStore(gobject.TYPE_PYOBJECT) previous_week = None for release in releases: release = BlaRelease(release) week = release.calender_week if previous_week != week: previous_week = week date = self.__cw_to_start_end_day(*week) date = "%s - %s" % (date[0].strftime("%a %d %b"), date[1].strftime("%a %d %b")) datestring = "\n<span size=\"larger\"><b>%s\n" if week == current_week: datestring %= "This week</b></span> (%s)" % date else: datestring %= "Week of %s</b></span>" % date model.append([datestring]) model.append([release]) path = release.get_cover() if path: images.add(path) self.__models[filt] = model # Get rid of covers for releases that don't show up anymore. for image in set(blautil.discover(blaconst.RELEASES)).difference( images): try: os.unlink(image) except OSError: pass gobject.idle_add(set_sensitive, True) gobject.idle_add(self.__treeview.set_model, self.__models[active]) if active == blaconst.NEW_RELEASES_FROM_LIBRARY: count = self.__count_library else: count = self.__count_recommended gobject.idle_add( self.emit, "count_changed", blaconst.VIEW_RELEASES, count) return True
def __init__(self): super(BlaReleaseBrowser, self).__init__() self.set_shadow_type(gtk.SHADOW_NONE) vbox = gtk.VBox() vbox.set_border_width(10) # Heading hbox = gtk.HBox() items = [ ("<b><span size=\"xx-large\">New releases</span></b>", 0.0, 0.5, 0), ("<b><span size=\"x-small\">powered by</span></b>", 1.0, 1.0, 5) ] for markup, xalign, yalign, padding in items: label = gtk.Label() label.set_markup(markup) alignment = gtk.Alignment(xalign, yalign) alignment.add(label) hbox.pack_start(alignment, expand=True, fill=True, padding=padding) image = gtk.image_new_from_file(blaconst.LASTFM_LOGO) alignment = gtk.Alignment(1.0, 0.5) alignment.add(image) hbox.pack_start(alignment, expand=False) vbox.pack_start(hbox, expand=False) # Type selector self.__hbox = gtk.HBox(spacing=5) self.__hbox.set_border_width(10) items = [ ("From artists in your library", blaconst.NEW_RELEASES_FROM_LIBRARY), ("Recommended by Last.fm", blaconst.NEW_RELEASES_RECOMMENDED) ] active = blacfg.getint("general", "releases.filter") radiobutton = None for label, filt in items: radiobutton = gtk.RadioButton(radiobutton, label) if filt == active: radiobutton.set_active(True) radiobutton.connect("toggled", self.__filter_changed, filt) self.__hbox.pack_start(radiobutton, expand=False) button = gtk.Button("Refresh") button.set_focus_on_click(False) button.connect_object( "clicked", BlaReleaseBrowser.__update_models, self) self.__hbox.pack_start(button, expand=False, padding=5) vbox.pack_start(self.__hbox, expand=False) # Releases list def cell_data_func_pixbuf(column, renderer, model, iterator): release = model[iterator][0] try: renderer.set_property("content", release.cover) except AttributeError: renderer.set_property("content", release) def cell_data_func_text(column, renderer, model, iterator): release = model[iterator][0] try: markup = "<b>%s</b>\n%s\nReleased: %s" % ( release.release_name, release.artist_name, release.release_date) except AttributeError: markup = "" renderer.set_property("markup", markup.replace("&", "&")) self.__treeview = blaguiutils.BlaTreeViewBase( set_button_event_handlers=False) self.__treeview.set_rules_hint(True) self.__treeview.get_selection().set_mode(gtk.SELECTION_SINGLE) self.__treeview.set_headers_visible(False) r = BlaCellRendererPixbuf() column = gtk.TreeViewColumn() column.pack_start(r, expand=False) column.set_cell_data_func(r, cell_data_func_pixbuf) r = gtk.CellRendererText() r.set_alignment(0.0, 0.0) r.set_property("ellipsize", pango.ELLIPSIZE_END) column.pack_start(r) column.set_cell_data_func(r, cell_data_func_text) self.__treeview.append_column(column) self.__treeview.connect("row_activated", self.__row_activated) self.__treeview.connect_object( "key_press_event", BlaReleaseBrowser.__key_press_event, self) self.__treeview.connect_object( "button_press_event", BlaReleaseBrowser.__button_press_event, self) self.__models = map(gtk.ListStore, [gobject.TYPE_PYOBJECT] * 2) self.__treeview.set_model(self.__models[active]) vbox.pack_start(self.__treeview, expand=True, padding=10) # FIXME: The treeview in the vbox, wrapped in a gtk.ScrolledWindow, # does not cause the viewport to scroll according to the # selection in the treeview. self.add_with_viewport(vbox) self.show_all() blaplay.bla.register_for_cleanup(self)
def __update_models(self): def set_sensitive(state): self.__hbox.set_sensitive(state) self.__treeview.set_sensitive(state) return False gobject.idle_add(set_sensitive, False) with self.__lock: images = set() active = blacfg.getint("general", "events.filter") limit = blacfg.getint("general", "events.limit") country = blacfg.getstring("general", "events.country") city = blacfg.getstring("general", "events.city") events = (blafm.get_events(limit=limit, recommended=True), blafm.get_events(limit=limit, recommended=False, country=country, city=city)) if events[0]: self.__count_recommended = len(events[0]) if events[1]: self.__count_all = len(events[1]) items = [ (blaconst.EVENTS_RECOMMENDED, events[0] or []), (blaconst.EVENTS_ALL, events[1] or []) ] for filt, events in items: model = gtk.ListStore(gobject.TYPE_PYOBJECT) previous_date = None for event in events: event = BlaEvent(event) date = event.date if previous_date != date: previous_date = date model.append( ["\n<span size=\"larger\"><b>%s</b></span>\n" % date]) model.append([event]) path = event.get_image() if path: images.add(path) self.__models[filt] = model # Get rid of images for events that don't show up anymore. for image in set(blautil.discover(blaconst.EVENTS)).difference( images): try: os.unlink(image) except OSError: pass gobject.idle_add(set_sensitive, True) # TODO: Only set the model when we verified that we successfully # retrieved event information. This avoids that we delete a # restored model. gobject.idle_add(self.__treeview.set_model, self.__models[active]) if active == blaconst.EVENTS_RECOMMENDED: count = self.__count_recommended else: count = self.__count_all gobject.idle_add( self.emit, "count_changed", blaconst.VIEW_EVENTS, count) return True
def __init__(self): super(BlaEventBrowser, self).__init__() self.set_shadow_type(gtk.SHADOW_NONE) vbox = gtk.VBox() vbox.set_border_width(10) # Heading hbox = gtk.HBox() items = [ ("<b><span size=\"xx-large\">Events</span></b>", 0.0, 0.5, 0), ("<b><span size=\"x-small\">powered by</span></b>", 1.0, 1.0, 5) ] for markup, xalign, yalign, padding in items: label = gtk.Label() label.set_markup(markup) alignment = gtk.Alignment(xalign, yalign) alignment.add(label) hbox.pack_start(alignment, expand=True, fill=True, padding=padding) image = gtk.image_new_from_file(blaconst.LASTFM_LOGO) alignment = gtk.Alignment(1.0, 0.5) alignment.add(image) hbox.pack_start(alignment, expand=False) vbox.pack_start(hbox, expand=False) # Location hbox_location = gtk.HBox(spacing=5) label = gtk.Label() label.set_markup("<b>Location:</b>") location = gtk.Label() country = blacfg.getstring("general", "events.country") city = blacfg.getstring("general", "events.city") if not city: location.set_markup("<i>Unspecified</i>") else: location.set_text( ", ".join([city, country] if country else [city])) button = gtk.Button("Change location") button.set_focus_on_click(False) button.connect( "clicked", self.__change_location, location) for widget, padding in [(label, 0), (location, 0), (button, 5)]: alignment = gtk.Alignment(0.0, 0.5) alignment.add(widget) hbox_location.pack_start(alignment, expand=False, padding=padding) vbox.pack_start(hbox_location, expand=False) # Type selector self.__hbox = gtk.HBox(spacing=5) self.__hbox.set_border_width(10) items = [ ("Recommended events", blaconst.EVENTS_RECOMMENDED), ("All events", blaconst.EVENTS_ALL) ] active = blacfg.getint("general", "events.filter") radiobutton = None for label, filt in items: radiobutton = gtk.RadioButton(radiobutton, label) if filt == active: radiobutton.set_active(True) radiobutton.connect( "toggled", self.__filter_changed, filt, hbox_location) self.__hbox.pack_start(radiobutton, expand=False) button = gtk.Button("Refresh") button.set_focus_on_click(False) button.connect_object("clicked", BlaEventBrowser.__update_models, self) self.__hbox.pack_start(button, expand=False) vbox.pack_start(self.__hbox, expand=False) hbox = gtk.HBox(spacing=5) hbox.pack_start(gtk.Label("Maximum number of results:"), expand=False) limit = blacfg.getint("general", "events.limit") adjustment = gtk.Adjustment(limit, 1.0, 100.0, 1.0, 5.0, 0.0) spinbutton = gtk.SpinButton(adjustment) spinbutton.set_numeric(True) spinbutton.connect( "value_changed", lambda sb: blacfg.set("general", "events.limit", sb.get_value())) hbox.pack_start(spinbutton, expand=False) vbox.pack_start(hbox, expand=False) # Events list def cell_data_func_pixbuf(column, renderer, model, iterator): event = model[iterator][0] try: renderer.set_property("content", event.image) except AttributeError: renderer.set_property("content", event) def cell_data_func_text(column, renderer, model, iterator): event = model[iterator][0] # FIXME: Too much code in the try-block. try: limit = 8 markup = "<b>%s</b>\n%%s" % event.event_name artists = ", ".join(event.artists[:limit]) if len(event.artists) > limit: artists += ", and more" markup %= artists except AttributeError: markup = "" renderer.set_property("markup", markup.replace("&", "&")) def cell_data_func_text2(column, renderer, model, iterator): event = model[iterator][0] try: markup = "<b>%s</b>\n%s\n%s" % ( event.venue, event.city, event.country) except AttributeError: markup = "" renderer.set_property("markup", markup.replace("&", "&")) def cell_data_func_text3(column, renderer, model, iterator): event = model[iterator][0] markup = "" try: if event.cancelled: markup = "<span size=\"x-large\"><b>Cancelled</b></span>" except AttributeError: pass renderer.set_property("markup", markup.replace("&", "&")) self.__treeview = blaguiutils.BlaTreeViewBase( set_button_event_handlers=False) self.__treeview.set_rules_hint(True) self.__treeview.get_selection().set_mode(gtk.SELECTION_SINGLE) self.__treeview.set_headers_visible(False) # Image r = BlaCellRendererPixbuf() column = gtk.TreeViewColumn() column.pack_start(r, expand=False) column.set_cell_data_func(r, cell_data_func_pixbuf) # Title and artists r = gtk.CellRendererText() r.set_alignment(0.0, 0.0) r.set_property("wrap_mode", pango.WRAP_WORD) r.set_property("wrap_width", 350) column.pack_start(r, expand=False) column.set_cell_data_func(r, cell_data_func_text) # Location r = gtk.CellRendererText() r.set_alignment(0.0, 0.0) r.set_property("ellipsize", pango.ELLIPSIZE_END) column.pack_start(r) column.set_cell_data_func(r, cell_data_func_text2) # Event cancelled status r = gtk.CellRendererText() r.set_property("ellipsize", pango.ELLIPSIZE_END) column.pack_start(r) column.set_cell_data_func(r, cell_data_func_text3) self.__treeview.append_column(column) self.__treeview.connect("row_activated", self.__row_activated) self.__treeview.connect_object( "key_press_event", BlaEventBrowser.__key_press_event, self) self.__treeview.connect_object( "button_press_event", BlaEventBrowser.__button_press_event, self) self.__models = map(gtk.ListStore, [gobject.TYPE_PYOBJECT] * 2) self.__treeview.set_model(self.__models[active]) vbox.pack_start(self.__treeview, expand=True, padding=10) self.add_with_viewport(vbox) self.show_all() if active == blaconst.EVENTS_RECOMMENDED: hbox_location.set_visible(False) blaplay.bla.register_for_cleanup(self)
def startup_complete(*args): self.set_view(blacfg.getint("general", "view"))
def __init__(self): super(BlaMainWindow, self).__init__(gtk.WINDOW_TOPLEVEL) self.set_resizable(True) self.connect("delete_event", self.__delete_event) self.enable_tracking(is_main_window=True) # Set up the fullscreen window. self.__fullscreen_window = gtk.Window() def map_(window): pass self.__fullscreen_window.connect("map", map_) self.__fullscreen_window.set_modal(True) self.__fullscreen_window.set_transient_for(self) self.__fullscreen_window.connect_object( "window_state_event", BlaMainWindow.__window_state_event, self) def key_press_event(window, event): if blagui.is_accel(event, "Escape"): window.child.emit("toggle_fullscreen") elif blagui.is_accel(event, "space"): player.play_pause() elif blagui.is_accel(event, "<Ctrl>Q"): blaplay.shutdown() self.__fullscreen_window.connect_object( "key_press_event", key_press_event, self.__fullscreen_window) # Realize the fullscreen window. If we don't do this here and reparent # the drawingarea to it later that in turn will get unrealized again, # causing bad X window errors. self.__fullscreen_window.realize() # Install a global mouse hook. If connected callbacks don't consume the # event by returning True this hook gets called for every widget in the # hierarchy that re-emits the event. We therefore cache the event's # timestamp to detect and ignore signal re-emissions. def button_press_hook(receiver, event): event_time = event.get_time() if event_time != self.__previous_event_time: self.__previous_event_time = event_time if event.button == 8: player.previous() elif event.button == 9: player.next() # This behaves like gobject.{timeout|idle}_add: if the callback # doesn't return True it's only called once. It does NOT prevent # signal callbacks from executing. return True self.__previous_event_time = -1 gobject.add_emission_hook(self, "button_press_event", button_press_hook) # Main menu ui_manager = blaplay.bla.ui_manager self.add_accel_group(ui_manager.get_accel_group()) actions = [ # Menus and submenus ("File", None, "_File"), ("Edit", None, "_Edit"), ("Select", None, "S_elect"), ("Selection", None, "Se_lection"), ("NewPlaylistFrom", None, "_New playlist from"), ("PlayOrder", None, "_Order"), ("View", None, "_View"), ("Help", None, "_Help"), # Menu items ("OpenPlaylist", None, "Open playlist...", None, "", self.__open_playlist), ("AddFiles", None, "Add _files...", None, "", lambda *x: self.__add_tracks()), ("AddDirectories", None, "_Add directories...", None, "", lambda *x: self.__add_tracks(files=False)), ("SavePlaylist", None, "_Save playlist...", None, "", self.__save_playlist), ("Quit", gtk.STOCK_QUIT, "_Quit", "<Ctrl>Q", "", lambda *x: blaplay.shutdown()), ("Preferences", None, "Pre_ferences...", None, "", BlaPreferences), ("About", None, "_About...", None, "", BlaAbout) ] ui_manager.add_actions(actions) toggle_actions = [ ("Statusbar", None, "St_atusbar", None, "", self.__toggle_statusbar, blacfg.getboolean("general", "statusbar")), ("Visualization", None, "Visualization", None, "", self.__toggle_visualization, blacfg.getboolean("general", "show.visualization")) ] ui_manager.add_toggle_actions(toggle_actions) radio_actions = [ ("OrderNormal", None, "_Normal", None, "", blaconst.ORDER_NORMAL), ("OrderRepeat", None, "_Repeat", None, "", blaconst.ORDER_REPEAT), ("OrderShuffle", None, "_Shuffle", None, "", blaconst.ORDER_SHUFFLE) ] # TODO: Emit "order_changed" signal in the on_change handler instead # and let interested widgets handle this instead. ui_manager.add_radio_actions( radio_actions, value=blacfg.getint("general", "play.order"), on_change=BlaStatusbar.set_order) # This is the topmost box that holds all the other components. self.add(gtk.VBox()) # Create instances of the main parts of the GUI. self.__toolbar = BlaToolbar() self.__browsers = BlaBrowsers() self.__visualization = BlaVisualization() self.__view = BlaView() self.__statusbar = BlaStatusbar() # Group browsers and visualization widget. self.__vbox_left = gtk.VBox(spacing=blaconst.WIDGET_SPACING) self.__vbox_left.pack_start(self.__browsers, expand=True) self.__vbox_left.pack_start(self.__visualization, expand=False) self.__vbox_left.show() # Pack the browser + view-widget into a gtk.HPane instance. hpane = gtk.HPaned() hpane.pack1(self.__vbox_left, resize=False, shrink=False) hpane.pack2(self.__view, resize=True, shrink=True) hpane.show() # Restore pane positions. def notify(pane, propspec, key): blacfg.set("general", key, str(pane.get_position())) for pane, side in [(hpane, "left"), (self.__view, "right")]: key = "pane.pos.%s" % side try: pane.set_position(blacfg.getint("general", key)) except TypeError: pass pane.connect("notify", notify, key) # Create a vbox hpane and the statusbar. This allows for setting a # border around those items which excludes the menubar and the toolbar. vbox = gtk.VBox(spacing=blaconst.BORDER_PADDING) vbox.set_border_width(blaconst.BORDER_PADDING) vbox.pack_start(hpane) vbox.pack_start(self.__statusbar, expand=False) vbox.show() self.child.pack_start(ui_manager.get_widget("/Menu"), expand=False) self.child.pack_start(self.__toolbar, expand=False) self.child.pack_start(vbox) self.child.show() self.__tray = BlaTray() def update_title(*args): self.__update_title() player.connect("state_changed", update_title) library.connect("library_updated", update_title) self.__update_title()
def __init__(self): super(BlaStatusbar, self).__init__(rows=1, columns=3, homogeneous=True) type(self).__instance = self self.__pb = gtk.ProgressBar() self.__pb_label = gtk.Label("Scanning library:") self.__track_info = gtk.Label(self.__state_string) hbox = gtk.HBox(spacing=10) hbox.pack_start(self.__pb_label, expand=False) hbox.pack_start(self.__pb, expand=False, fill=True) hbox.pack_start(self.__track_info, expand=True) self.__view_info = gtk.Label("") # Playback order self.__order = gtk.combo_box_new_text() map(self.__order.append_text, blaconst.ORDER_LITERALS) self.__order.set_active(blacfg.getint("general", "play.order")) def order_changed(cb): order = cb.get_active() states = [False] * len(blaconst.ORDER_LITERALS) states[order] = True ui_manager = blaplay.bla.ui_manager for idx, order in enumerate(blaconst.MENU_ORDER): action = ui_manager.get_widget(order) action.set_active(states[idx]) self.__order.connect("changed", order_changed) table = gtk.Table(rows=1, columns=3) table.attach(gtk.Label("Order:"), 0, 1, 0, 1, xpadding=10) table.attach(self.__order, 1, 2, 0, 1) button = gtk.Button() button.set_tooltip_text("Clear queue") from blaqueue import queue button.connect("clicked", lambda *x: queue.clear()) queue.connect("count_changed", lambda *x: button.set_sensitive(x[-1] > 0)) button.add(gtk.image_new_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_SMALL_TOOLBAR)) button.set_relief(gtk.RELIEF_NONE) button.set_sensitive(False) table.attach(button, 2, 3, 0, 1), count = 0 for widget, xalign in [(hbox, 0.0), (self.__view_info, 0.5), (table, 1.0)]: alignment = gtk.Alignment(xalign, 0.5, 0.0, 0.5) alignment.add(widget) self.attach(alignment, count, count+1, 0, 1) count += 1 player.connect("state_changed", self.__changed) library.connect("progress", self.update_progress) self.show_all() # TODO: group these two self.__pb.set_visible(False) self.__pb_label.set_visible(False) self.set_visible(blacfg.getboolean("general", "statusbar"))
def __init__(self): super(BlaPreferences.LibraryBrowsersSettings, self).__init__( "Library/Browsers") restrict_string = blacfg.getstring("library", "restrict.to") exclude_string = blacfg.getstring("library", "exclude") def destroy(*args): if (restrict_string != blacfg.getstring("library", "restrict.to") or exclude_string != blacfg.getstring("library", "exclude")): library.sync() self.connect("destroy", destroy) hbox = gtk.HBox(spacing=10) model = gtk.ListStore(gobject.TYPE_STRING) treeview = gtk.TreeView(model) treeview.set_property("rules_hint", True) r = gtk.CellRendererText() treeview.insert_column_with_attributes( -1, "Directories", r, text=0) sw = BlaScrolledWindow() sw.set_shadow_type(gtk.SHADOW_IN) sw.set_size_request(-1, 140) sw.add(treeview) directories = blacfg.getdotliststr("library", "directories") for f in directories: model.append([f]) table = gtk.Table(rows=2, columns=1) items = [ ("Add...", self.__add_directory), ("Remove", self.__remove_directory), ("Rescan all", self.__rescan_all) ] for idx, (label, callback) in enumerate(items): button = gtk.Button(label) button.connect("clicked", callback, treeview) table.attach(button, 0, 1, idx, idx+1, yoptions=not gtk.EXPAND) hbox.pack_start(sw, expand=True) hbox.pack_start(table, expand=False, fill=False) # Update library checkbutton update_library = gtk.CheckButton("Update library on startup") update_library.set_active( blacfg.getboolean("library", "update.on.startup")) update_library.connect( "toggled", lambda cb: blacfg.setboolean("library", "update.on.startup", cb.get_active())) # The file types restrict_to_entry = gtk.Entry() restrict_to_entry.set_tooltip_text( "Comma-separated list, works on filenames") restrict_to_entry.set_text( blacfg.getstring("library", "restrict.to")) restrict_to_entry.connect( "changed", lambda entry: blacfg.set("library", "restrict.to", entry.get_text())) exclude_entry = gtk.Entry() exclude_entry.set_tooltip_text( "Comma-separated list, works on filenames") exclude_entry.set_text( blacfg.getstring("library", "exclude")) exclude_entry.connect( "changed", lambda entry: blacfg.set("library", "exclude", entry.get_text())) pairs = [ (blaconst.ACTION_SEND_TO_CURRENT, "send to current playlist"), (blaconst.ACTION_ADD_TO_CURRENT, "add to current playlist"), (blaconst.ACTION_SEND_TO_NEW, "send to new playlist"), (blaconst.ACTION_EXPAND_COLLAPSE, "expand/collapse") ] # FIXME: Ugly!! actions = [""] * 4 for idx, label in pairs: actions[idx] = label comboboxes = [] def cb_changed(combobox, key): blacfg.set("library", "%s.action" % key, combobox.get_active()) for key in ["doubleclick", "middleclick", "return"]: cb = gtk.combo_box_new_text() map(cb.append_text, actions) if key == "return": cb.remove_text(3) cb.set_active(blacfg.getint("library", "%s.action" % key)) cb.connect("changed", cb_changed, key) comboboxes.append(cb) widgets = [restrict_to_entry, exclude_entry] + comboboxes labels = ["Restrict to", "Exclude", "Double-click", "Middle-click", "Return"] action_table = gtk.Table(rows=len(labels), columns=2, homogeneous=False) count = 0 for label, widget in zip(labels, widgets): label = gtk.Label("%s:" % label) label.set_alignment(xalign=0.0, yalign=0.5) action_table.attach(label, 0, 1, count, count+1, xoptions=gtk.FILL, xpadding=5) action_table.attach(widget, 1, 2, count, count+1) count += 1 hbox2 = gtk.HBox(spacing=10) draw_tree_lines = gtk.CheckButton("Draw tree lines in browsers") draw_tree_lines.set_active( blacfg.getboolean("library", "draw.tree.lines")) draw_tree_lines.connect("toggled", self.__tree_lines_changed) custom_library_browser = gtk.CheckButton( "Use custom treeview as library browser") custom_library_browser.set_active( blacfg.getboolean("library", "custom.browser")) custom_library_browser.connect( "toggled", self.__custom_library_browser_changed) hbox2.pack_start(draw_tree_lines) hbox2.pack_start(custom_library_browser) self.pack_start(hbox, expand=False) self.pack_start(update_library, expand=False) self.pack_start(action_table, expand=False) self.pack_start(hbox2, expand=False)
def __mediator(self, method_name, *args): view = blacfg.getint("general", "view") if view in (blaconst.VIEW_PLAYLISTS, blaconst.VIEW_QUEUE): getattr(self.__views[view], method_name)(*args)