From 1570b3b121963c6a88549de982ca952d7bc54b62 Mon Sep 17 00:00:00 2001 From: gotik Date: Tue, 19 Jun 2012 02:37:14 +0200 Subject: [PATCH] Komplette ?berarbeitung der Gtk-GUI mit Verbindungspanel, Konfiguration, Threads etc. --- mcgGtk.py | 421 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 322 insertions(+), 99 deletions(-) diff --git a/mcgGtk.py b/mcgGtk.py index 4f9d127..893b3d4 100755 --- a/mcgGtk.py +++ b/mcgGtk.py @@ -9,57 +9,247 @@ import mcg -UI_INFO = """ - - - - - - -""" - - class MCGGtk(Gtk.Window): - _default_cover_size = 128 - def __init__(self): Gtk.Window.__init__(self, title="MPDCoverGridGTK") self._mcg = mcg.MCGClient() - self._cover_pixbuf = None - self.set_default_size(600, 400) - + self._config = Configuration() + self._quit = False + # Box - _main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - self.add(_main_box) - # 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.toolbar_callback) - action_group.add_action_with_accel(self._action_connect, None) - self._action_update = Gtk.Action("Update", "_Update", "Update library", Gtk.STOCK_REFRESH) - self._action_update.connect("activate", self.toolbar_callback) - action_group.add_action_with_accel(self._action_update, None) + self._main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.add(self._main_box) # 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) - # HPaned - hpaned = Gtk.HPaned() - _main_box.pack_start(hpaned, True, True, 0) + self._toolbar = Toolbar() + self._main_box.pack_start(self._toolbar, False, False, 0) + # Connection Panel + self.set_default_size(600, 400) + self._connection_panel = ConnectionPanel(self._config) + self._main_box.pack_end(self._connection_panel, True, True, 0) + # Cover Panel + self._cover_panel = CoverPanel() + + # Signals + self.connect("focus", self.focus) + self.connect("delete-event", self.destroy) + self._toolbar.connect_signal(Toolbar.SIGNAL_CONNECT, self._connect) + self._mcg.connect_signal(mcg.MCGClient.SIGNAL_CONNECT, self.connect_callback) + self._mcg.connect_signal(mcg.MCGClient.SIGNAL_UPDATE, self.update_callback) + self._mcg.connect_signal(mcg.MCGClient.SIGNAL_IDLE_PLAYER, self.idle_player_callback) + + + def focus(self, widget, state): + self._connect() + + + def destroy(self, widget, state): + self._mcg.close() + GObject.idle_add(Gtk.main_quit) + + + def _connect(self): + if self._mcg.is_connected(): + self._cover_panel.stop_update() + self._mcg.disconnect() + else: + self._connection_panel.lock() + host = self._connection_panel.get_host() + port = self._connection_panel.get_port() + password = self._connection_panel.get_password() + self._mcg.connect(host, port, password) + + + def connect_callback(self, connected, message): + if connected: + GObject.idle_add(self._connect_connected) + else: + GObject.idle_add(self._connect_disconnected) + + + + def _connect_connected(self): + self._toolbar.connected() + self._main_box.remove(self._connection_panel) + self._main_box.pack_start(self._cover_panel, True, True, 0) + self._main_box.show_all() + + + def _connect_disconnected(self): + self._main_box.remove(self._cover_panel) + self._main_box.pack_start(self._connection_panel, True, True, 0) + self._main_box.show_all() + self._connection_panel.unlock() + self._toolbar.disconnected() + + + + def update_callback(self, albums): + self._cover_panel.update(albums) + + + def idle_player_callback(self, state, album): + GObject.idle_add(self._cover_panel.set_album, album.get_cover()) + + + + +class Toolbar(Gtk.Toolbar): + SIGNAL_CONNECT = 'connect' + + def __init__(self): + Gtk.Toolbar.__init__(self) + + self._callbacks = {} + + self.get_style_context().add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR) + self._connection_button = Gtk.ToolButton(Gtk.STOCK_DISCONNECT) + self._connection_button.connect("clicked", self._callback) + self.add(self._connection_button) + + + def connect_signal(self, signal, callback): + self._callbacks[signal] = callback + + + def connected(self): + self._connection_button.set_stock_id(Gtk.STOCK_CONNECT) + + + def disconnected(self): + self._connection_button.set_stock_id(Gtk.STOCK_DISCONNECT) + + + def _callback(self, widget): + signal = None + if widget == self._connection_button: + signal = self.SIGNAL_CONNECT + + if signal in self._callbacks: + callback = self._callbacks[signal] + callback() + + + + +class ConnectionPanel(Gtk.Box): + + def __init__(self, config): + Gtk.HBox.__init__(self) + self._callbacks = {} + self._config = config + + vbox = Gtk.VBox() + self.pack_start(vbox, True, False, 0) + self._table = Gtk.Table(3, 2, False) + vbox.pack_start(self._table, True, False, 0) + # Host + host_label = Gtk.Label("Host:") + host_label.set_alignment(0, 0.5) + self._table.attach(host_label, 0, 1, 0, 1) + self._host_entry = Gtk.Entry() + self._host_entry.set_text("localhost") + self._host_entry.connect("focus-out-event", self._lost_focus) + self._table.attach(self._host_entry, 1, 2, 0, 1) + # Port + port_label = Gtk.Label("Port:") + port_label.set_alignment(0, 0.5) + self._table.attach(port_label, 0, 1, 1, 2) + adjustment = Gtk.Adjustment(6600, 1024, 9999, 1, 10, 10) + self._port_spinner = Gtk.SpinButton() + self._port_spinner.set_adjustment(adjustment) + self._port_spinner.connect("focus-out-event", self._lost_focus) + self._table.attach(self._port_spinner, 1, 2, 1, 2) + # Passwort + password_label = Gtk.Label("Password:") + password_label.set_alignment(0, 0.5) + self._table.attach(password_label, 0, 1, 2, 3) + self._password_entry = Gtk.Entry() + self._password_entry.set_visibility(False) + self._password_entry.connect("focus-out-event", self._lost_focus) + self._table.attach(self._password_entry, 1, 2, 2, 3) + + self._load_config() + + + def _lost_focus(self, widget, data): + self._save_config() + + + def _load_config(self): + self.set_host(self._config.host) + self.set_port(self._config.port) + self.set_password(self._config.password) + + + def _save_config(self): + self._config.host = self._host_entry.get_text() + self._config.port = self._port_spinner.get_value_as_int() + self._config.password = self._password_entry.get_text() + self._config.save() + + + def set_host(self, host): + self._host_entry.set_text(host) + + + def get_host(self): + return self._host_entry.get_text() + + + def set_port(self, port): + self._port_spinner.set_value(port) + + + def get_port(self): + return self._port_spinner.get_value_as_int() + + + def set_password(self, password): + if password is None: + password = "" + self._password_entry.set_text(password) + + + def get_password(self): + if self._password_entry.get_text() == "": + return None + else: + return self._password_entry.get_text() + + + def lock(self): + self._lock(False) + + + def unlock(self): + self._lock(True) + + + def _lock(self, sensitive): + self._table.set_sensitive(sensitive) + + + + +from threading import Thread + + +class CoverPanel(Gtk.HPaned): + _default_cover_size = 128 + + + def __init__(self): + Gtk.HPaned.__init__(self) + # Image + self._cover_pixbuf = None self._cover_image = Gtk.Image() self._cover_image.connect('size-allocate', self.on_resize) # EventBox self._cover_box = Gtk.EventBox() self._cover_box.add(self._cover_image) - hpaned.pack1(self._cover_box, resize=True) + self.pack1(self._cover_box, resize=True) # GridModel self._cover_grid_model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str) # GridView @@ -71,65 +261,42 @@ class MCGGtk(Gtk.Window): 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_item_padding(1) 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._cover_grid.override_background_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(color.red, color.green, color.blue, 1)) # Scroll - _cover_grid_scroll = Gtk.ScrolledWindow() - _cover_grid_scroll.add_with_viewport(self._cover_grid) - hpaned.pack2(_cover_grid_scroll, resize=False) - - # Signals - self.connect("focus", self.focus) - self.connect("delete-event", self.destroy) - #self.coverGrid.connect("selection-changed", self.coverGridShow) - self._cover_grid.connect("item-activated", self._cover_grid_play) - self._mcg.connect_signal(mcg.MCGClient.SIGNAL_CONNECT, self.connect_callback) - self._mcg.connect_signal(mcg.MCGClient.SIGNAL_IDLE_PLAYER, self.idle_player_callback) - self._mcg.connect_signal(mcg.MCGClient.SIGNAL_UPDATE, self.update_callback) + self._cover_grid_scroll = Gtk.ScrolledWindow() + self._cover_grid_scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + self._cover_grid_scroll.add(self._cover_grid) + self.pack2(self._cover_grid_scroll, resize=False) + # Progress Bar + self._progress_box = Gtk.VBox() + self._progress_bar = Gtk.ProgressBar() + self._progress_box.pack_start(self._progress_bar, True, False, 0) - def destroy(self, widget, state): - if self._mcg is not None: - self._mcg.disconnect() - Gtk.main_quit() + def update(self, albums): + self._go = True + Thread(target=self._update, args=(albums,)).start() - def focus(self, widget, state): - self._update() + def stop_update(self): + self._go = False - def toolbar_callback(self, widget): - if widget == self._action_connect: - if self._mcg.is_connected(): - self._mcg.disconnect() - else: - self._mcg.connect() - elif widget == self._action_update: - self._update() - - - def on_resize(self, widget, allocation): - self._resize_image() - - - 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 update_callback(self, albums): + def _update(self, albums): + Gdk.threads_enter() + self.remove(self._cover_grid_scroll) + self._progress_bar.set_fraction(0.0) + self.pack2(self._progress_box, False) + self.show_all() self._cover_grid.set_model(None) self._cover_grid.freeze_child_notify() self._cover_grid_model.clear() + Gdk.threads_leave() + + i = 0 + n = len(albums) for hash in albums.keys(): album = albums[hash] file = album.get_cover() @@ -139,35 +306,44 @@ class MCGGtk(Gtk.Window): if pixbuf is None: continue self._cover_grid_model.append([pixbuf, album.get_title(), GObject.markup_escape_text("\n".join([album.get_title(), album.get_artist()]))]) + i += 1 + GObject.idle_add(self._progress_bar.set_fraction, i/n) + + if not self._go: + self._cover_grid_model.clear() + break + + Gdk.threads_enter() self._cover_grid.set_model(self._cover_grid_model) self._cover_grid.thaw_child_notify() + self.remove(self._progress_box) + self.pack2(self._cover_grid_scroll, False) + Gdk.threads_leave() - def _update(self): - if self._mcg is None or not self._mcg.is_connected(): - return - self._mcg.update() - - - def _set_album(self, url): - # Pfad überprüfen + def set_album(self, url): + # Check path if url is not None and url != "": - # Bild laden und zeichnen + # Load image and draw it self._cover_pixbuf = GdkPixbuf.Pixbuf.new_from_file(url) self._resize_image() else: - # Bild zurücksetzen + # Reset image self._cover_pixbuf = None self._cover_image.clear() + def on_resize(self, widget, allocation): + self._resize_image() + + 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 + ## Check pixelbuffer if pixbuf is None: return @@ -184,15 +360,62 @@ class MCGGtk(Gtk.Window): self._cover_image.set_from_pixbuf(pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER)) - def _cover_grid_play(self, widget, item): - # TODO coverGridPlay() - pass + + +import os +import configparser + +class Configuration: + CONFIG_FILE = '~/.config/mcggtk.config' + + + def __init__(self): + self._config = configparser.RawConfigParser() + + self.host = 'localhost' + self.port = 6600 + self.password = "" + self.load() + + + def load(self): + if not os.path.isfile(self._get_filename()): + return + + self._config.read(self._get_filename()) + if self._config.has_section('connection'): + if self._config.has_option('connection', 'host'): + self.host = self._config.get('connection', 'host') + if self._config.has_option('connection', 'port'): + self.port = self._config.getint('connection', 'port') + if self._config.has_option('connection', 'password'): + self.password = self._config.get('connection', 'password') + + + def save(self): + if not self._config.has_section('connection'): + self._config.add_section('connection') + self._config.set('connection', 'host', self.host) + self._config.set('connection', 'port', self.port) + self._config.remove_option('connection', 'password') + if self.password is not "": + self._config.set('connection', 'password', self.password) + + + with open(self._get_filename(), 'w') as configfile: + self._config.write(configfile) + + + def _get_filename(self): + #return os.path.expanduser(self.CONFIG_FILE) + return 'config' if __name__ == "__main__": GObject.threads_init() + Gdk.threads_init() mcgg = MCGGtk() mcgg.show_all() try: