From 8a4bb3e41e29d892111f10b8bf32c141a27ad30a Mon Sep 17 00:00:00 2001 From: gotik Date: Thu, 19 Apr 2012 18:28:17 +0200 Subject: [PATCH] Grob umstrukturiert und an Python-Styleguide angepasst --- MPDCoverGrid.py | 221 +++++++++++++++++++++++++++++++-------------- MPDCoverGridGTK.py | 210 +++++++++++++++++++++++++++--------------- 2 files changed, 291 insertions(+), 140 deletions(-) diff --git a/MPDCoverGrid.py b/MPDCoverGrid.py index 13e8360..496705f 100644 --- a/MPDCoverGrid.py +++ b/MPDCoverGrid.py @@ -3,7 +3,7 @@ -from mpd import MPDClient +import mpd import os from threading import Thread @@ -11,120 +11,205 @@ from threading import Thread class MPDCoverGrid: - client = MPDClient() - albums = {} + SIGNAL_CONNECT = 'connect' + SIGNAL_IDLE = 'idle' + SIGNAL_IDLE_PLAYER = 'idlePlayer' + SIGNAL_UPDATE = 'update' - def __init__(self, host='localhost', port='6600', password=None): + def __init__(self, host="localhost", port=6600, password=None): self._host = host self._port = port self._password = password - self.updateCallback = None + self._connected = False + + self._callbacks = {} + self._threads = {} + + self._client = mpd.MPDClient() + self._albums = {} def connect(self): - try: - self.client.connect(self._host, self._port) - if self._password: - self.client.password(self._password) - except IOError as e: - # TODO Error - print(e) + self._start_thread(self.SIGNAL_CONNECT, self._connect) def disconnect(self): + self._disconnect() + + + def is_connected(self): + return self._connected + + + def _connect(self): try: - self.client.disconnect() + self._client.connect(self._host, self._port) + if self._password: + self._client.password(self._password) + # TODO Verbindung testen + self._connected = True + self._callback(self.SIGNAL_CONNECT, self._connected, None) + #self._start_idle() + self.update() except IOError as e: - # TODO Error - print(e) - self.client = MPDClient() + self._connected = False + self._callback(self.SIGNAL_CONNECT, self._connected, e) - def getAlbums(self): - self.update() - return self.albums + def _disconnect(self): + if not self.is_connected(): + return + self._stop_idle() + try: + #self._client.close() + self._client.disconnect() + except: + self._client = mpd.MPDClient() + self._connected = False + self._callback(self.SIGNAL_CONNECT, self._connected, None) - def connectUpdate(self, updateCallback): - self.updateCallback = updateCallback + def _start_idle(self): + self._start_thread(self.SIGNAL_IDLE, self._idle) + + + def _stop_idle(self): + if not self._is_doing(self.SIGNAL_IDLE): + return + + try: + del self._threads[self.SIGNAL_IDLE] + self._client.noidle() + except TypeError as e: + pass + + def _idle(self): + while not self._is_doing(self.SIGNAL_IDLE): + pass + while self._client is not None and self._connected and self._is_doing(self.SIGNAL_IDLE): + self._client.send_idle() + if self._is_doing(self.SIGNAL_IDLE): + modules = self._client.fetch_idle() + if 'player' in modules: + self._idlePlayer() + if 'database' in modules: + # TODO update DB + # self.update()? + pass + if 'update' in modules: + # TODO update + #self._idleUpdate() + pass + if 'mixer' in modules: + pass + + + def _idlePlayer(self): + if not self._has_callback(self.SIGNAL_IDLE_PLAYER): + return + status = self._client.status() + state = status['state'] + song = self._client.currentsong() + album = MCGAlbum(song['artist'], song['album'], os.path.dirname(song['file'])) + self._callback(self.SIGNAL_IDLE_PLAYER, state, album) def update(self): - Thread(target=self._update, args=()).start() + self._start_thread(self.SIGNAL_UPDATE, self._update) def _update(self): - for song in self.client.listallinfo(): + self._stop_idle() + for song in self._client.listallinfo(): try: - new = False - if song['album'] not in self.albums: - self.albums[song['album']] = MCGAlbum(song['artist'], song['album'], os.path.dirname(song['file'])) - new = True - - album = self.albums[song['album']] - album.addTrack(song['title']) - if new and self.updateCallback is not None: - self.updateCallback(album) + if song['album'] not in self._albums: + album = MCGAlbum(song['artist'], song['album'], os.path.dirname(song['file'])) + self._albums[song['album']] = album + self._callback(self.SIGNAL_UPDATE, album) except KeyError: pass + self._start_idle() -class MCGAlbum(): - fileNames = ['folder', 'cover'] - fileExts = ['jpg', 'jpeg', 'png'] + + def connect_signal(self, signal, callback): + self._callbacks[signal] = callback + + + def _has_callback(self, signal): + return signal in self._callbacks + + + def _callback(self, signal, *args): + if self._has_callback(signal): + callback = self._callbacks[signal] + callback(*args) + + + def _start_thread(self, signal, method): + self._threads[signal] = Thread(target=method, args=()).start() + + + def _is_doing(self, signal): + return signal in self._threads + + + + + + def play(self): + # TODO play() + pass + + + + +class MCGAlbum: + _file_names = ['folder', 'cover'] + _file_exts = ['jpg', 'jpeg', 'png'] def __init__(self, artist, title, path): - self.artist = artist - if type(self.artist) is list: - self.artist = self.artist[0] - self.title = title - self.path = path - self.tracks = [] - self.cover = None - self._findCover() + self._artist = artist + if type(self._artist) is list: + self._artist = self._artist[0] + self._title = title + self._path = path + self._cover = None + self._find_cover() - def getArtist(self): - return self.artist + def get_artist(self): + return self._artist - def getTitle(self): - return self.title + def get_title(self): + return self._title - def getPath(self): - return self.path + def get_path(self): + return self._path - def addTrack(self, track): - self.tracks.append(track) + def get_cover(self): + return self._cover - def getTracks(self): - return self.tracks - - - def getCover(self): - return self.cover - - - def _findCover(self): - names = list(self.fileNames) - names.append(self.title) - names.append(' - '.join((self.artist, self.title))) + def _find_cover(self): + names = list(self._file_names) + names.append(self._title) + names.append(' - '.join((self._artist, self._title))) for name in names: - for ext in self.fileExts: - filename = os.path.join('/home/oliver/Musik/', self.path, '.'.join([name, ext])) + for ext in self._file_exts: + filename = os.path.join('/home/oliver/Musik/', self._path, '.'.join([name, ext])) if os.path.isfile(filename): - self.cover = filename + self._cover = filename break - if self.cover is not None: + if self._cover is not None: break - diff --git a/MPDCoverGridGTK.py b/MPDCoverGridGTK.py index 490a3d9..e296f58 100755 --- a/MPDCoverGridGTK.py +++ b/MPDCoverGridGTK.py @@ -5,121 +5,186 @@ from gi.repository import Gtk, Gdk, GdkPixbuf, GObject from MPDCoverGrid import MPDCoverGrid -import inspect +UI_INFO = """ + + + + + +""" + + class MPDCoverGridGTK(Gtk.Window): - size = 128 + _default_cover_size = 128 def __init__(self): Gtk.Window.__init__(self, title="MPDCoverGridGTK") self.set_default_size(600, 400) - self.connect("focus", self.updateSignal) + self.connect("focus", self._focus) self.connect("delete-event", self._destroy) - GObject.threads_init() + self._cover_pixbuf = None - # VPaned - VPaned = Gtk.VPaned() - self.add(VPaned) + # Box + _main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) # HPaned - HPaned = Gtk.HPaned() - VPaned.pack1(HPaned, resize=True) + hpaned = Gtk.HPaned() + + # UIManager + action_group = Gtk.ActionGroup("toolbar") + ui_manager = Gtk.UIManager() + ui_manager.add_ui_from_string(UI_INFO) + accel_group = ui_manager.get_accel_group() + self.add_accel_group(accel_group) + ui_manager.insert_action_group(action_group) + + self._action_connect = Gtk.Action("Connect", "_Connect", "Connect to server", Gtk.STOCK_DISCONNECT) + self._action_connect.connect("activate", self._on_toolbar_connect) + action_group.add_action_with_accel(self._action_connect, None) + + # Toolbar + toolbar = ui_manager.get_widget("/ToolBar") + toolbar_context = toolbar.get_style_context() + toolbar_context.add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR) + _main_box.pack_start(toolbar, False, False, 0) # Image - self.coverImage = Gtk.Image() + self._cover_image = Gtk.Image() + self._cover_image.connect('size-allocate', self._on_resize) # EventBox - self.coverBox = Gtk.EventBox() - self.coverBox.add(self.coverImage) - # Viewport - self.coverView = Gtk.Viewport() - self.coverView.add(self.coverBox) - HPaned.pack1(self.coverView, resize=True) + self._cover_box = Gtk.EventBox() + self._cover_box.add(self._cover_image) + hpaned.pack1(self._cover_box, resize=True) # GridModel - self.coverGridModel = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str) + self._cover_grid_model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str) # GridView - self.coverGrid = Gtk.IconView.new_with_model(self.coverGridModel) - self.coverGrid.set_pixbuf_column(0) - self.coverGrid.set_text_column(-1) - self.coverGrid.set_tooltip_column(2) - self.coverGrid.set_columns(-1) - self.coverGrid.set_margin(0) - self.coverGrid.set_row_spacing(0) - self.coverGrid.set_column_spacing(0) - self.coverGrid.set_item_padding(0) - self.coverGrid.set_reorderable(False) - self.coverGrid.set_selection_mode(Gtk.SelectionMode.SINGLE) + self._cover_grid = Gtk.IconView.new_with_model(self._cover_grid_model) + self._cover_grid.set_pixbuf_column(0) + self._cover_grid.set_text_column(-1) + self._cover_grid.set_tooltip_column(2) + self._cover_grid.set_columns(-1) + self._cover_grid.set_margin(0) + self._cover_grid.set_row_spacing(0) + self._cover_grid.set_column_spacing(0) + self._cover_grid.set_item_padding(0) + self._cover_grid.set_reorderable(False) + self._cover_grid.set_selection_mode(Gtk.SelectionMode.SINGLE) #color = self.get_style_context().lookup_color('bg_color')[1] - #self.coverGrid.override_background_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(color.red, color.green, color.blue, 1)) + #self._cover_grid.override_background_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(color.red, color.green, color.blue, 1)) # Scroll - coverGridScroll = Gtk.ScrolledWindow() - coverGridScroll.add_with_viewport(self.coverGrid) - HPaned.pack2(coverGridScroll, resize=False) + _cover_grid_scroll = Gtk.ScrolledWindow() + _cover_grid_scroll.add_with_viewport(self._cover_grid) + hpaned.pack2(_cover_grid_scroll, resize=False) - # ListModel - self.songListModel = Gtk.ListStore(str, str) - # ListView - self.songList = Gtk.TreeView(self.songListModel) - renderer = Gtk.CellRendererText() - column1 = Gtk.TreeViewColumn("Artist", renderer, text=0) - column2 = Gtk.TreeViewColumn("Album", renderer, text=0) - self.songList.append_column(column1) - self.songList.append_column(column2) - self.songList.set_headers_visible(True) - VPaned.pack2(self.songList, resize=False) + _main_box.pack_start(hpaned, True, True, 0) + self.add(_main_box) + + self._mcg = MPDCoverGrid() # Signals - self.coverGrid.connect("selection-changed", self.coverGridShow) - self.coverGrid.connect("item-activated", self.coverGridPlay) - - self._initClient() - self.mcg.connectUpdate(self.updateCallback) + #self.coverGrid.connect("selection-changed", self.coverGridShow) + self._cover_grid.connect("item-activated", self._cover_grid_play) + self._mcg.connect_signal(MPDCoverGrid.SIGNAL_CONNECT, self._connect_callback) + self._mcg.connect_signal(MPDCoverGrid.SIGNAL_IDLE_PLAYER, self._idle_player_callback) + self._mcg.connect_signal(MPDCoverGrid.SIGNAL_UPDATE, self._update_callback) - def _initClient(self): - self.mcg = MPDCoverGrid() - self.mcg.connect() + def _on_toolbar_connect(self, widget): + if self._mcg.is_connected(): + self._mcg.disconnect() + else: + self._mcg.connect() + + + def _connect_callback(self, connected, message): + if connected: + self._action_connect.set_stock_id(Gtk.STOCK_CONNECT) + else: + self._action_connect.set_stock_id(Gtk.STOCK_DISCONNECT) + + + def _idle_player_callback(self, state, album): + self._set_album(album.get_cover()) def _destroy(self, widget, state): - if self.mcg is not None: - self.mcg.disconnect() + if self._mcg is not None: + self._mcg.disconnect() Gtk.main_quit() - def updateSignal(self, widget, state): - self.update() + def _on_resize(self, widget, allocation): + self._resize_image() - def update(self): - if self.mcg is None: + def _set_album(self, url): + # Pfad überprüfen + if url is not None and url != "": + # Bild laden und zeichnen + self._cover_pixbuf = GdkPixbuf.Pixbuf.new_from_file(url) + self._resize_image() + else: + # Bild zurücksetzen + self._cover_pixbuf = None + self._cover_image.clear() + + + def _resize_image(self): + """Diese Methode skaliert das geladene Bild aus dem Pixelpuffer + auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse + """ + pixbuf = self._cover_pixbuf + size = self._cover_image.get_allocation() + ## Pixelpuffer überprüfen + if pixbuf is None: return - self.mcg.update() + + # Skalierungswert für Breite und Höhe ermitteln + ratioW = float(size.width) / float(pixbuf.get_width()) + ratioH = float(size.height) / float(pixbuf.get_height()) + # Kleineren beider Skalierungswerte nehmen, nicht Hochskalieren + ratio = min(ratioW, ratioH) + ratio = min(ratio, 1) + # Neue Breite und Höhe berechnen + width = int(round(pixbuf.get_width()*ratio)) + height = int(round(pixbuf.get_height()*ratio)) + # Pixelpuffer auf Oberfläche zeichnen + self._cover_image.set_from_pixbuf(pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER)) + + + + - def updateCallback(self, album): - if album.getCover() is not None: - pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(album.getCover(), self.size, self.size) + def _focus(self, widget, state): + self._update() + + + def _update(self): + if self._mcg is None: + return + self._mcg.update() + + + def _update_callback(self, album): + if album.get_cover() is not None: + pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(album.get_cover(), self._default_cover_size, self._default_cover_size) if pixbuf is not None: - self.coverGridModel.append([pixbuf, album.getTitle(), ' von '.join([album.getTitle(), album.getArtist()])]) + self._cover_grid_model.append([pixbuf, album.get_title(), "\n".join([album.get_title(), album.get_artist()])]) else: - print("pixbuf none: "+album.getTitle()) + print("pixbuf none: "+album.get_title()) - def coverGridShow(self, widget): - # TODO coverGridShow() - pass + def _player_callback(self, state): + print(state) - def coverGridSelected(self, widget, index, data): - # TODO coverGridSelected() - pass - - - def coverGridPlay(self, widget, item): + def _cover_grid_play(self, widget, item): # TODO coverGridPlay() pass @@ -127,6 +192,7 @@ class MPDCoverGridGTK(Gtk.Window): if __name__ == "__main__": + GObject.threads_init() mcgg = MPDCoverGridGTK() mcgg.show_all() Gtk.main()