diff --git a/__init__.py b/__init__.py
index 396cef0..e56acfb 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# -*- coding: utf-8 -*-
diff --git a/data/de.coderkun.MCG.gschema.xml b/data/de.coderkun.MCG.gschema.xml
new file mode 100644
index 0000000..6ed2bc8
--- /dev/null
+++ b/data/de.coderkun.MCG.gschema.xml
@@ -0,0 +1,47 @@
+
+
+
+
+ [800, 600]
+ Window size
+ Window size (width and height).
+
+
+ false
+ Window maximized
+ Window maximized state.
+
+
+ 0
+ Last selected profile
+ The index of the last selected profile used to connect to the MPD server.
+
+
+
+ 1
+ Last selected panel
+ The index of the last selected panel.
+
+
+
+ 150
+ Size of library items
+ The size of items displayed in the library.
+
+
+ 'year'
+
+
+
+
+
+ Sort criterium for library items
+ The sort criterium of items displayed in the library.
+
+
+ true
+ Sort type for library items
+ The sort type of items displayed in the library.
+
+
+
diff --git a/data/gschemas.compiled b/data/gschemas.compiled
new file mode 100644
index 0000000..799f95f
Binary files /dev/null and b/data/gschemas.compiled differ
diff --git a/gui/noise-texture.png b/data/noise-texture.png
similarity index 100%
rename from gui/noise-texture.png
rename to data/noise-texture.png
diff --git a/gui/__init__.py b/gui/__init__.py
new file mode 100644
index 0000000..e56acfb
--- /dev/null
+++ b/gui/__init__.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+
+
diff --git a/gui/gtk.py b/gui/gtk.py
index cac5e83..07738e3 100755
--- a/gui/gtk.py
+++ b/gui/gtk.py
@@ -6,315 +6,53 @@
__author__ = "coderkun"
__email__ = ""
__license__ = "GPL"
-__version__ = "0.3"
+__version__ = "0.4"
__status__ = "Development"
import math
import os
-import time
import threading
import urllib
-from gi.repository import Gtk, Gdk, GdkPixbuf, GObject
+from gi.repository import Gio, Gtk, Gdk, GObject, GdkPixbuf, GLib
import mcg
-class MCGGtk(Gtk.Window):
+class Application(Gtk.Application):
TITLE = "MPDCoverGrid (Gtk)"
- VIEW_COVER = 'cover'
- VIEW_PLAYLIST = 'playlist'
- VIEW_LIBRARY = 'library'
- STYLE_CLASS_BG_TEXTURE = 'bg-texture'
- STYLE_CLASS_NO_BG = 'no-bg'
+ SETTINGS_BASE_KEY = 'de.coderkun.mcg'
+ SETTING_WINDOW_SIZE = 'window-size'
+ SETTING_WINDOW_MAXIMIZED = 'window-maximized'
+ SETTING_PROFILE = 'profile'
+ SETTING_PANEL = 'panel'
+ SETTING_ITEM_SIZE = 'item-size'
+ SETTING_SORT_ORDER = 'sort-order'
+ SETTING_SORT_TYPE = 'sort-type'
def __init__(self):
- Gtk.Window.__init__(self, title=MCGGtk.TITLE)
- self._mcg = mcg.MCGClient()
- self._config = Configuration()
- self._maximized = False
- self._fullscreened = False
- self._albums = {}
-
- # Widgets
- self._main_box = Gtk.VBox()
- self.add(self._main_box)
- self._bar_box = Gtk.VBox()
- self._main_box.pack_start(self._bar_box, False, False, 0)
- self._toolbar = Toolbar(self._config)
- self._bar_box.pack_start(self._toolbar, True, True, 0)
- self._infobar = InfoBar()
- self._infobar.show()
- self._view_box = Gtk.EventBox()
- self._main_box.pack_end(self._view_box, True, True, 0)
- self._connection_panel = ConnectionPanel(self._config)
- self._view_box.add(self._connection_panel)
-
- # Views
- self._cover_panel = CoverPanel()
- self._playlist_panel = PlaylistPanel(self._config)
- self._library_panel = LibraryPanel(self._config)
+ Gtk.Application.__init__(self, application_id="de.coderkun.mcg", flags=Gio.ApplicationFlags.FLAGS_NONE)
+ self._window = None
+ self._settings = Gio.Settings.new(Application.SETTINGS_BASE_KEY)
- # Properties
- self.set_hide_titlebar_when_maximized(True)
- self._view_box.get_style_context().add_class(MCGGtk.STYLE_CLASS_BG_TEXTURE)
- provider = Gtk.CssProvider()
- provider.load_from_data(b"""
- GtkWidget.bg-texture {
- box-shadow:inset 4px 4px 10px rgba(0,0,0,0.3);
- background-image:url('gui/noise-texture.png');
- }
- GtkWidget.no-bg {
- background:none;
- }
- GtkIconView.cell:selected,
- GtkIconView.cell:selected:focus {
- background-color:@theme_selected_bg_color;
- }
- GtkToolbar.primary-toolbar {
- background:none;
- border:none;
- box-shadow:none;
- }
- """)
- self.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
- # Actions
- self.resize(self._config.window_width, self._config.window_height)
- if self._config.window_maximized:
- self.maximize()
+ def do_startup(self):
+ Gtk.Application.do_startup(self)
- # Signals
- self.connect('size-allocate', self.on_resize)
- self.connect('window-state-event', self.on_state)
- self.connect('delete-event', self.on_destroy)
- self._toolbar.connect_signal(Toolbar.SIGNAL_CONNECT, self.on_toolbar_connect)
- self._toolbar.connect_signal(Toolbar.SIGNAL_PLAYPAUSE, self.on_toolbar_playpause)
- self._toolbar.connect_signal(Toolbar.SIGNAL_VIEW, self.on_toolbar_view)
- self._toolbar.connect_signal(Toolbar.SIGNAL_SET_VOLUME, self.on_toolbar_set_volume)
- self._infobar.connect_signal(InfoBar.SIGNAL_CLOSE, self.on_infobar_close)
- self._connection_panel.connect_signal(ConnectionPanel.SIGNAL_PROFILE_CHANGED, self.on_connection_profile_changed)
- self._cover_panel.connect_signal(CoverPanel.SIGNAL_TOGGLE_FULLSCREEN, self.on_cover_panel_toggle_fullscreen)
- self._playlist_panel.connect_signal(PlaylistPanel.SIGNAL_CLEAR_PLAYLIST, self.on_playlist_panel_clear_playlist)
- self._library_panel.connect_signal(LibraryPanel.SIGNAL_PLAY, self.on_library_panel_play)
- self._library_panel.connect_signal(LibraryPanel.SIGNAL_UPDATE, self.on_library_panel_update)
- # View panels
- self._mcg.connect_signal(mcg.MCGClient.SIGNAL_CONNECT, self.on_mcg_connect)
- self._mcg.connect_signal(mcg.MCGClient.SIGNAL_STATUS, self.on_mcg_status)
- self._mcg.connect_signal(mcg.MCGClient.SIGNAL_LOAD_PLAYLIST, self.on_mcg_load_playlist)
- self._mcg.connect_signal(mcg.MCGClient.SIGNAL_LOAD_ALBUMS, self.on_mcg_load_albums)
- self._mcg.connect_signal(mcg.MCGClient.SIGNAL_ERROR, self.on_mcg_error)
+ def do_activate(self):
+ if not self._window:
+ self._window = Window(self, Application.TITLE, self._settings)
+ self._window.present()
- def on_resize(self, widget, event):
- self._save_size()
-
- def on_state(self, widget, state):
- self._fullscreen((state.new_window_state & Gdk.WindowState.FULLSCREEN > 0))
- self._save_state(state)
-
-
- def on_destroy(self, widget, state):
- self._mcg.disconnect_signal(mcg.MCGClient.SIGNAL_CONNECT)
- self._mcg.disconnect_signal(mcg.MCGClient.SIGNAL_STATUS)
- if self._mcg.is_connected():
- self._mcg.disconnect()
- self._mcg.join()
- self._config.save()
- self._connection_panel.save_profiles()
- GObject.idle_add(Gtk.main_quit)
-
-
- # Toolbar callbacks
-
- def on_toolbar_connect(self):
- self._connect()
-
-
- def on_toolbar_playpause(self):
- self._mcg.playpause()
-
-
- def on_toolbar_view(self, view):
- self._config.view = view
- self._view_box.remove(self._view_box.get_children()[0])
- if view == MCGGtk.VIEW_COVER:
- self._view_box.add(self._cover_panel)
- elif view == MCGGtk.VIEW_PLAYLIST:
- self._view_box.add(self._playlist_panel)
- elif view == MCGGtk.VIEW_LIBRARY:
- self._view_box.add(self._library_panel)
- self._view_box.show_all()
-
-
- def on_toolbar_set_volume(self, volume):
- self._mcg.set_volume(volume)
-
-
- # Infobar callbacks
-
- def on_infobar_close(self):
- self._hide_message()
-
-
- # Connection Panel callbacks
-
- def on_connection_profile_changed(self, index, profile):
- self._config.last_profile = index
- if ConnectionPanel.TAG_AUTOCONNECT in profile.get_tags():
- self._connect()
-
-
- # Cover Panel callbacks
-
- def on_cover_panel_toggle_fullscreen(self):
- self._toggle_fullscreen()
-
-
- # Playlist Panel callbacks
-
- def on_playlist_panel_clear_playlist(self):
- self._mcg.clear_playlist()
-
-
- # Library Panel callbacks
-
- def on_library_panel_update(self):
- self._mcg.update()
-
-
- def on_library_panel_play(self, album):
- self._mcg.play_album(album)
-
-
- # MCG callbacks
-
- def on_mcg_connect(self, connected, error):
- if connected:
- GObject.idle_add(self._connect_connected)
- self._mcg.load_playlist()
- self._mcg.load_albums()
- self._mcg.get_status()
- else:
- if error:
- self._show_error(str(error))
- GObject.idle_add(self._connect_disconnected)
-
-
- def on_mcg_status(self, state, album, pos, time, volume, error):
- # Album
- if album:
- GObject.idle_add(self._cover_panel.set_album, album)
- # State
- if state == 'play':
- GObject.idle_add(self._toolbar.set_play)
- GObject.idle_add(self._cover_panel.set_play, pos, time)
- elif state == 'pause' or state == 'stop':
- GObject.idle_add(self._toolbar.set_pause)
- GObject.idle_add(self._cover_panel.set_pause)
- # Volume
- GObject.idle_add(self._toolbar.set_volume, volume)
- # Error
- if error is None:
- self._hide_message()
- else:
- self._show_error(error)
-
-
- def on_mcg_load_playlist(self, playlist, error):
- self._playlist_panel.set_playlist(self._connection_panel.get_host(), playlist)
-
-
- def on_mcg_load_albums(self, albums, error):
- self._albums = {}
- self._library_panel.set_albums(self._connection_panel.get_host(), albums)
-
-
- def on_mcg_error(self, error):
- self._show_error(str(error))
-
-
- # Private methods
-
- def _connect(self):
- self._connection_panel.set_sensitive(False)
- self._toolbar.set_sensitive(False)
- if self._mcg.is_connected():
- self._mcg.disconnect()
- else:
- host = self._connection_panel.get_host()
- port = self._connection_panel.get_port()
- password = self._connection_panel.get_password()
- image_dir = self._connection_panel.get_image_dir()
- self._mcg.connect(host, port, password, image_dir)
-
-
- def _connect_connected(self):
- self._toolbar.connected()
- self._toolbar.set_sensitive(True)
- self._view_box.remove(self._view_box.get_children()[0])
- if self._config.view == MCGGtk.VIEW_COVER:
- self._view_box.add(self._cover_panel)
- elif self._config.view == MCGGtk.VIEW_PLAYLIST:
- self._view_box.add(self._playlist_panel)
- elif self._config.view == MCGGtk.VIEW_LIBRARY:
- self._view_box.add(self._library_panel)
- self._view_box.show_all()
-
-
- def _connect_disconnected(self):
- self._toolbar.disconnected()
- self._toolbar.set_sensitive(True)
- self._connection_panel.set_sensitive(True)
- self._view_box.remove(self._view_box.get_children()[0])
- self._view_box.add(self._connection_panel)
- self._view_box.show_all()
-
-
- def _save_size(self):
- if not self._maximized:
- self._config.window_width = self.get_allocation().width
- self._config.window_height = self.get_allocation().height
-
-
- def _save_state(self, state):
- self._config.window_maximized = (state.new_window_state & Gdk.WindowState.MAXIMIZED > 0)
- self._maximized = (state.new_window_state & Gdk.WindowState.MAXIMIZED > 0)
-
-
- def _toggle_fullscreen(self):
- if not self._fullscreened:
- self.fullscreen()
- else:
- self.unfullscreen()
-
-
- def _fullscreen(self, fullscreened_new):
- if fullscreened_new != self._fullscreened:
- self._fullscreened = fullscreened_new
- if self._fullscreened:
- self._toolbar.hide()
- self._cover_panel.set_fullscreen(True)
- else:
- self._toolbar.show()
- self._cover_panel.set_fullscreen(False)
-
-
- def _show_error(self, message):
- self._infobar.show_error(message)
- if len(self._bar_box.get_children()) > 1:
- self._bar_box.remove(self._infobar)
- self._bar_box.pack_end(self._infobar, False, True, 0)
-
-
- def _hide_message(self):
- if len(self._bar_box.get_children()) > 1:
- self._bar_box.remove(self._infobar)
+ def action_quit_cb(self, action, parameter):
+ self._window.destroy()
+ self.quit()
def load_thumbnail(cache, album, size):
@@ -353,88 +91,381 @@ class MCGGtk(Gtk.Window):
+class Window(Gtk.ApplicationWindow):
+ STYLE_CLASS_BG_TEXTURE = 'bg-texture'
+ STYLE_CLASS_NO_BG = 'no-bg'
+ STYLE_CLASS_NO_BORDER = 'no-border'
+ _PANEL_INDEX_CONNECTION = 0
+ _PANEL_INDEX_COVER = 1
+ _PANEL_INDEX_PLAYLIST = 2
+ _PANEL_INDEX_LIBRARY = 3
-class Toolbar(mcg.MCGBase, Gtk.Toolbar):
+
+ def __init__(self, app, title, settings):
+ Gtk.Window.__init__(self, title=title, application=app)
+ self._settings = settings
+ self._panels = []
+ self._mcg = mcg.MCGClient()
+ self._size = self._settings.get_value(Application.SETTING_WINDOW_SIZE)
+ self._maximized = self._settings.get_boolean(Application.SETTING_WINDOW_MAXIMIZED)
+ self._fullscreened = False
+
+ # Panels
+ self._panels.append(ConnectionPanel())
+ self._panels.append(CoverPanel())
+ self._panels.append(PlaylistPanel())
+ self._panels.append(LibraryPanel())
+
+ # Widgets
+ self._main_box = Gtk.VBox()
+ self._main_box.get_style_context().add_class(Window.STYLE_CLASS_NO_BG)
+ self.add(self._main_box)
+ # InfoBar
+ self._infobar = InfoBar()
+ self._main_box.pack_start(self._infobar, False, True, 0)
+ # Stack
+ self._stack = Gtk.Stack()
+ for panel in self._panels:
+ self._stack.add_titled(panel, panel.get_name(), panel.get_title())
+ self._stack.set_homogeneous(True)
+ self._main_box.pack_end(self._stack, True, True, 0)
+ # Header
+ self._header_bar = HeaderBar(self._stack)
+ self.set_titlebar(self._header_bar)
+
+ # Properties
+ self._header_bar.set_sensitive(False, False)
+ styleProvider = Gtk.CssProvider()
+ styleProvider.load_from_data(b"""
+ GtkWidget.bg-texture {
+ box-shadow:inset 4px 4px 10px rgba(0,0,0,0.3);
+ background-image:url('data/noise-texture.png');
+ }
+ GtkWidget.no-bg {
+ background:none;
+ }
+ GtkWidget.no-border {
+ border:none;
+ }
+ GtkIconView.cell:selected,
+ GtkIconView.cell:selected:focus {
+ background-color:@theme_selected_bg_color;
+ }
+ """)
+ self.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), styleProvider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+ self.get_style_context().add_class(Window.STYLE_CLASS_BG_TEXTURE)
+ self._panels[Window._PANEL_INDEX_PLAYLIST].set_item_size(self._settings.get_int(Application.SETTING_ITEM_SIZE))
+ self._panels[Window._PANEL_INDEX_LIBRARY].set_item_size(self._settings.get_int(Application.SETTING_ITEM_SIZE))
+ self._panels[Window._PANEL_INDEX_LIBRARY].set_sort_order(self._settings.get_string(Application.SETTING_SORT_ORDER))
+ self._panels[Window._PANEL_INDEX_LIBRARY].set_sort_type(self._settings.get_boolean(Application.SETTING_SORT_TYPE))
+
+ # Signals
+ self.connect('size-allocate', self.on_resize)
+ self.connect('window-state-event', self.on_state)
+ self.connect('destroy', self.on_destroy)
+ self._header_bar.connect_signal(HeaderBar.SIGNAL_STACK_SWITCHED, self.on_header_bar_stack_switched)
+ self._header_bar.connect_signal(HeaderBar.SIGNAL_CONNECT, self.on_header_bar_connect)
+ self._header_bar.connect_signal(HeaderBar.SIGNAL_PLAYPAUSE, self.on_header_bar_playpause)
+ self._header_bar.connect_signal(HeaderBar.SIGNAL_SET_VOLUME, self.on_header_bar_set_volume)
+ self._panels[Window._PANEL_INDEX_CONNECTION].connect_signal(ConnectionPanel.SIGNAL_PROFILE_CHANGED, self.on_connection_panel_profile_changed)
+ self._panels[Window._PANEL_INDEX_PLAYLIST].connect_signal(PlaylistPanel.SIGNAL_CLEAR_PLAYLIST, self.on_playlist_panel_clear_playlist)
+ self._panels[Window._PANEL_INDEX_COVER].connect_signal(CoverPanel.SIGNAL_TOGGLE_FULLSCREEN, self.on_cover_panel_toggle_fullscreen)
+ self._panels[Window._PANEL_INDEX_COVER].connect_signal(CoverPanel.SIGNAL_SET_SONG, self.on_cover_panel_set_song)
+ self._panels[Window._PANEL_INDEX_LIBRARY].connect_signal(LibraryPanel.SIGNAL_UPDATE, self.on_library_panel_update)
+ self._panels[Window._PANEL_INDEX_LIBRARY].connect_signal(LibraryPanel.SIGNAL_PLAY, self.on_library_panel_play)
+ self._panels[Window._PANEL_INDEX_LIBRARY].connect_signal(LibraryPanel.SIGNAL_ITEM_SIZE_CHANGED, self.on_library_panel_item_size_changed)
+ self._panels[Window._PANEL_INDEX_LIBRARY].connect_signal(LibraryPanel.SIGNAL_SORT_ORDER_CHANGED, self.on_library_panel_sort_order_changed)
+ self._panels[Window._PANEL_INDEX_LIBRARY].connect_signal(LibraryPanel.SIGNAL_SORT_TYPE_CHANGED, self.on_library_panel_sort_type_changed)
+ self._mcg.connect_signal(mcg.MCGClient.SIGNAL_CONNECT, self.on_mcg_connect)
+ self._mcg.connect_signal(mcg.MCGClient.SIGNAL_STATUS, self.on_mcg_status)
+ self._mcg.connect_signal(mcg.MCGClient.SIGNAL_LOAD_PLAYLIST, self.on_mcg_load_playlist)
+ self._mcg.connect_signal(mcg.MCGClient.SIGNAL_LOAD_ALBUMS, self.on_mcg_load_albums)
+ self._mcg.connect_signal(mcg.MCGClient.SIGNAL_ERROR, self.on_mcg_error)
+ self._settings.connect('changed::'+Application.SETTING_PANEL, self.on_settings_panel_changed)
+ self._settings.connect('changed::'+Application.SETTING_ITEM_SIZE, self.on_settings_item_size_changed)
+ self._settings.connect('changed::'+Application.SETTING_SORT_ORDER, self.on_settings_sort_order_changed)
+ self._settings.connect('changed::'+Application.SETTING_SORT_TYPE, self.on_settings_sort_type_changed)
+
+ # Actions
+ self.resize(int(self._size[0]), int(self._size[1]))
+ if self._maximized:
+ self.maximize()
+ self.show_all()
+ self._infobar.hide()
+ self._stack.set_visible_child(self._panels[Window._PANEL_INDEX_CONNECTION])
+ self._panels[Window._PANEL_INDEX_CONNECTION].select_profile(self._settings.get_int(Application.SETTING_PROFILE))
+
+
+ def on_resize(self, widget, event):
+ if not self._maximized:
+ self._size = (self.get_allocation().width, self.get_allocation().height)
+
+
+ def on_state(self, widget, state):
+ self._maximized = (state.new_window_state & Gdk.WindowState.MAXIMIZED > 0)
+ self._fullscreen((state.new_window_state & Gdk.WindowState.FULLSCREEN > 0))
+ self._settings.set_boolean(Application.SETTING_WINDOW_MAXIMIZED, self._maximized)
+
+
+ def on_destroy(self, window):
+ self._settings.set_value(Application.SETTING_WINDOW_SIZE, GLib.Variant('ai', self._size))
+
+
+ # HeaderBar callbacks
+
+ def on_header_bar_stack_switched(self, widget):
+ self._save_visible_panel()
+
+
+ def on_header_bar_connect(self):
+ self._connect()
+
+
+ def on_header_bar_playpause(self):
+ self._mcg.playpause()
+
+
+ def on_header_bar_set_volume(self, volume):
+ self._mcg.set_volume(volume)
+
+
+ # Panel callbacks
+
+ def on_connection_panel_profile_changed(self, index, profile):
+ self._settings.set_int(Application.SETTING_PROFILE, index)
+ if ConnectionPanel.TAG_AUTOCONNECT in profile.get_tags():
+ self._connect()
+
+
+ def on_playlist_panel_clear_playlist(self):
+ self._mcg.clear_playlist()
+
+
+ def on_cover_panel_toggle_fullscreen(self):
+ if not self._fullscreened:
+ self.fullscreen()
+ else:
+ self.unfullscreen()
+
+
+ def on_cover_panel_set_song(self, pos, time):
+ self._mcg.seek(pos, time)
+
+
+ def on_library_panel_update(self):
+ self._mcg.update()
+
+
+ def on_library_panel_play(self, album):
+ self._mcg.play_album(album)
+
+
+ def on_library_panel_item_size_changed(self, size):
+ self._panels[Window._PANEL_INDEX_PLAYLIST].set_item_size(size)
+ self._settings.set_int(Application.SETTING_ITEM_SIZE, self._panels[Window._PANEL_INDEX_LIBRARY].get_item_size())
+
+
+ def on_library_panel_sort_order_changed(self, sort_order):
+ self._settings.set_string(Application.SETTING_SORT_ORDER, self._panels[Window._PANEL_INDEX_LIBRARY].get_sort_order())
+
+
+ def on_library_panel_sort_type_changed(self, sort_type):
+ self._settings.set_boolean(Application.SETTING_SORT_TYPE, self._panels[Window._PANEL_INDEX_LIBRARY].get_sort_type())
+
+
+ # MCG callbacks
+
+ def on_mcg_connect(self, connected, error):
+ if connected:
+ GObject.idle_add(self._connect_connected)
+ self._mcg.load_playlist()
+ self._mcg.load_albums()
+ self._mcg.get_status()
+ else:
+ if error:
+ GObject.idle_add(self._show_error, str(error))
+ GObject.idle_add(self._connect_disconnected)
+
+
+ def on_mcg_status(self, state, album, pos, time, volume, error):
+ # Album
+ if album:
+ GObject.idle_add(self._panels[Window._PANEL_INDEX_COVER].set_album, album)
+ # State
+ if state == 'play':
+ GObject.idle_add(self._header_bar.set_play)
+ GObject.idle_add(self._panels[Window._PANEL_INDEX_COVER].set_play, pos, time)
+ elif state == 'pause' or state == 'stop':
+ GObject.idle_add(self._header_bar.set_pause)
+ GObject.idle_add(self._panels[Window._PANEL_INDEX_COVER].set_pause)
+ # Volume
+ GObject.idle_add(self._header_bar.set_volume, volume)
+ # Error
+ if error is None:
+ self._infobar.hide()
+ else:
+ self._show_error(error)
+
+
+ def on_mcg_load_playlist(self, playlist, error):
+ self._panels[self._PANEL_INDEX_PLAYLIST].set_playlist(self._panels[self._PANEL_INDEX_CONNECTION].get_host(), playlist)
+
+
+ def on_mcg_load_albums(self, albums, error):
+ self._panels[self._PANEL_INDEX_LIBRARY].set_albums(self._panels[self._PANEL_INDEX_CONNECTION].get_host(), albums)
+
+
+ def on_mcg_error(self, error):
+ GObject.idle_add(self._show_error, str(error))
+
+
+ # Settings callbacks
+
+ def on_settings_panel_changed(self, settings, key):
+ panel_index = settings.get_int(key)
+ self._stack.set_visible_child(self._panels[panel_index])
+
+
+ def on_settings_item_size_changed(self, settings, key):
+ size = settings.get_int(key)
+ self._panels[Window._PANEL_INDEX_PLAYLIST].set_item_size(size)
+ self._panels[Window._PANEL_INDEX_LIBRARY].set_item_size(size)
+
+
+ def on_settings_sort_order_changed(self, settings, key):
+ sort_order = settings.get_string(key)
+ self._panels[Window._PANEL_INDEX_LIBRARY].set_sort_order(sort_order)
+
+
+ def on_settings_sort_type_changed(self, settings, key):
+ sort_type = settings.get_boolean(key)
+ self._panels[Window._PANEL_INDEX_LIBRARY].set_sort_type(sort_type)
+
+
+ # Private methods
+
+ def _connect(self):
+ connection_panel = self._panels[Window._PANEL_INDEX_CONNECTION]
+ connection_panel.set_sensitive(False)
+ self._header_bar.set_sensitive(False, True)
+ if self._mcg.is_connected():
+ self._mcg.disconnect()
+ else:
+ host = connection_panel.get_host()
+ port = connection_panel.get_port()
+ password = connection_panel.get_password()
+ image_dir = connection_panel.get_image_dir()
+ self._mcg.connect(host, port, password, image_dir)
+
+
+ def _connect_connected(self):
+ self._header_bar.connected()
+ self._header_bar.set_sensitive(True, False)
+ self._stack.set_visible_child(self._panels[self._settings.get_int(Application.SETTING_PANEL)])
+
+
+ def _connect_disconnected(self):
+ self._panels[Window._PANEL_INDEX_PLAYLIST].stop_threads();
+ self._panels[Window._PANEL_INDEX_LIBRARY].stop_threads();
+ self._header_bar.disconnected()
+ self._header_bar.set_sensitive(False, False)
+ self._save_visible_panel()
+ self._stack.set_visible_child(self._panels[Window._PANEL_INDEX_CONNECTION])
+ self._panels[Window._PANEL_INDEX_CONNECTION].set_sensitive(True)
+
+
+ def _fullscreen(self, fullscreened_new):
+ if fullscreened_new != self._fullscreened:
+ self._fullscreened = fullscreened_new
+ if self._fullscreened:
+ self._header_bar.hide()
+ self._panels[Window._PANEL_INDEX_COVER].set_fullscreen(True)
+ else:
+ self._header_bar.show()
+ self._panels[Window._PANEL_INDEX_COVER].set_fullscreen(False)
+
+
+ def _save_visible_panel(self):
+ panel_index_selected = self._panels.index(self._stack.get_visible_child())
+ if(panel_index_selected > 0):
+ self._settings.set_int(Application.SETTING_PANEL, panel_index_selected)
+
+
+ def _show_error(self, message):
+ self._infobar.show_error(message)
+ self._infobar.show()
+
+
+
+
+class HeaderBar(mcg.MCGBase, Gtk.HeaderBar):
+ SIGNAL_STACK_SWITCHED = 'stack-switched'
SIGNAL_CONNECT = 'connect'
- SIGNAL_VIEW = 'view'
SIGNAL_PLAYPAUSE = 'playpause'
SIGNAL_SET_VOLUME = 'set-volume'
- def __init__(self, config):
+ def __init__(self, stack):
mcg.MCGBase.__init__(self)
- Gtk.Toolbar.__init__(self)
- self._config = config
+ Gtk.HeaderBar.__init__(self)
+ self._stack = stack
+ self._buttons = {}
+ self._button_handlers = {}
self._changing_volume = False
self._setting_volume = False
# Widgets
- # Connection
- self._connection_button = Gtk.ToggleToolButton.new_from_stock(Gtk.STOCK_CONNECT)
- self.add(self._connection_button)
- # Separator
- self.add(Gtk.SeparatorToolItem())
- # Playback
- self._playpause_button = Gtk.ToggleToolButton.new_from_stock(Gtk.STOCK_MEDIA_PAUSE)
- self._playpause_button.set_sensitive(False)
- self.add(self._playpause_button)
- # Separator
- separator = Gtk.SeparatorToolItem()
- separator.set_draw(False)
- separator.set_expand(True)
- self.add(separator)
- # View Buttons
+ # StackSwitcher
+ self._stack_switcher = StackSwitcher()
+ self._stack_switcher.set_stack(self._stack)
+ self.set_custom_title(self._stack_switcher)
+ # Buttons left
+ self._left_toolbar = Gtk.Toolbar()
+ self._left_toolbar.set_show_arrow(False)
+ self._left_toolbar.get_style_context().add_class(Window.STYLE_CLASS_NO_BG)
+ self.pack_start(self._left_toolbar)
+ # Buttons left: Connection
+ self._buttons[HeaderBar.SIGNAL_CONNECT] = Gtk.ToggleToolButton.new_from_stock(Gtk.STOCK_DISCONNECT)
+ self._left_toolbar.add(self._buttons[HeaderBar.SIGNAL_CONNECT])
+ # Buttons left: Separator
+ self._left_toolbar.add(Gtk.SeparatorToolItem())
+ # Buttons left: Playback
+ self._buttons[HeaderBar.SIGNAL_PLAYPAUSE] = Gtk.ToggleToolButton.new_from_stock(Gtk.STOCK_MEDIA_PAUSE)
+ self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].set_sensitive(False)
+ self._left_toolbar.add(self._buttons[HeaderBar.SIGNAL_PLAYPAUSE])
+ # Buttons right
+ self._right_toolbar = Gtk.Toolbar()
+ self._right_toolbar.set_show_arrow(False)
+ self._right_toolbar.get_style_context().add_class(Window.STYLE_CLASS_NO_BG)
+ self.pack_end(self._right_toolbar)
+ # Buttons right: Volume
item = Gtk.ToolItem()
- item.get_style_context().add_class(Gtk.STYLE_CLASS_RAISED);
- self._view_box = Gtk.ButtonBox()
- self._view_box.set_layout(Gtk.ButtonBoxStyle.CENTER)
- self._view_box.get_style_context().add_class(Gtk.STYLE_CLASS_RAISED);
- self._view_box.get_style_context().add_class(Gtk.STYLE_CLASS_LINKED);
- self._view_box.set_sensitive(False)
- self._view_cover_button = Gtk.RadioButton(label="Cover")
- self._view_cover_button.set_mode(False)
- self._view_cover_button.set_active(self._config.view == MCGGtk.VIEW_COVER)
- self._view_box.add(self._view_cover_button)
- self._view_playlist_button = Gtk.RadioButton.new_with_label_from_widget(self._view_cover_button, "Playlist")
- self._view_playlist_button.set_mode(False)
- self._view_playlist_button.set_active(self._config.view == MCGGtk.VIEW_PLAYLIST)
- self._view_box.add(self._view_playlist_button)
- self._view_library_button = Gtk.RadioButton.new_with_label_from_widget(self._view_playlist_button, "Library")
- self._view_library_button.set_mode(False)
- self._view_library_button.set_active(self._config.view == MCGGtk.VIEW_LIBRARY)
- self._view_box.add(self._view_library_button)
- item.add(self._view_box)
- item.show_all()
- self.add(item)
- # Separator
- separator = Gtk.SeparatorToolItem()
- separator.set_draw(False)
- separator.set_expand(True)
- self.add(separator)
- # Volume
- item = Gtk.ToolItem()
- self._volume_button = Gtk.VolumeButton()
- self._volume_button.set_sensitive(False)
- item.add(self._volume_button)
- self.add(item)
+ self._buttons[HeaderBar.SIGNAL_SET_VOLUME] = Gtk.VolumeButton()
+ self._buttons[HeaderBar.SIGNAL_SET_VOLUME].set_sensitive(False)
+ item.add(self._buttons[HeaderBar.SIGNAL_SET_VOLUME])
+ self._right_toolbar.add(item)
# Properties
- self.get_style_context().add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR)
+ self.set_show_close_button(True)
# Signals
- self._connection_button_handler = self._connection_button.connect('toggled', self._callback_from_widget, self.SIGNAL_CONNECT)
- self._playpause_button_handler = self._playpause_button.connect('toggled', self._callback_from_widget, self.SIGNAL_PLAYPAUSE)
- self._view_cover_button.connect('toggled', self.on_set_view, MCGGtk.VIEW_COVER)
- self._view_playlist_button.connect('toggled', self.on_set_view, MCGGtk.VIEW_PLAYLIST)
- self._view_library_button.connect('toggled', self.on_set_view, MCGGtk.VIEW_LIBRARY)
- self._volume_button.connect('value-changed', self.on_volume_changed)
- self._volume_button.connect('button-press-event', self.on_volume_set_active, True)
- self._volume_button.connect('button-release-event', self.on_volume_set_active, False)
+ self._stack_switcher.connect_signal(StackSwitcher.SIGNAL_STACK_SWITCHED, self.on_stack_switched)
+ self._button_handlers[HeaderBar.SIGNAL_CONNECT] = self._buttons[HeaderBar.SIGNAL_CONNECT].connect('toggled', self._callback_from_widget, self.SIGNAL_CONNECT)
+ self._button_handlers[HeaderBar.SIGNAL_PLAYPAUSE] = self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].connect('toggled', self._callback_from_widget, self.SIGNAL_PLAYPAUSE)
+ self._buttons[HeaderBar.SIGNAL_SET_VOLUME].connect('value-changed', self.on_volume_changed)
+ self._buttons[HeaderBar.SIGNAL_SET_VOLUME].connect('button-press-event', self.on_volume_set_active, True)
+ self._buttons[HeaderBar.SIGNAL_SET_VOLUME].connect('button-release-event', self.on_volume_set_active, False)
- def on_set_view(self, button, view):
- if button.get_active():
- self._callback(self.SIGNAL_VIEW, view)
+ def set_sensitive(self, sensitive, connecting):
+ for button_signal in self._buttons:
+ self._buttons[button_signal].set_sensitive(sensitive)
+ self._stack_switcher.set_sensitive(sensitive)
+ self._buttons[HeaderBar.SIGNAL_CONNECT].set_sensitive(not connecting)
+
+
+ def on_stack_switched(self, widget):
+ self._callback(HeaderBar.SIGNAL_STACK_SWITCHED, widget)
def on_volume_changed(self, widget, value):
@@ -447,37 +478,33 @@ class Toolbar(mcg.MCGBase, Gtk.Toolbar):
def connected(self):
- self._connection_button.set_stock_id(Gtk.STOCK_CONNECT)
- with self._connection_button.handler_block(self._connection_button_handler):
- self._connection_button.set_active(True)
- self._playpause_button.set_sensitive(True)
- self._view_box.set_sensitive(True)
- self._volume_button.set_sensitive(True)
+ self._buttons[HeaderBar.SIGNAL_CONNECT].set_stock_id(Gtk.STOCK_CONNECT)
+ with self._buttons[HeaderBar.SIGNAL_CONNECT].handler_block(self._button_handlers[HeaderBar.SIGNAL_CONNECT]):
+ self._buttons[HeaderBar.SIGNAL_CONNECT].set_active(True)
def disconnected(self):
- self._connection_button.set_stock_id(Gtk.STOCK_DISCONNECT)
- self._connection_button.set_active(False)
- self._playpause_button.set_sensitive(False)
- self._view_box.set_sensitive(False)
- self._volume_button.set_sensitive(False)
+ self._buttons[HeaderBar.SIGNAL_CONNECT].set_stock_id(Gtk.STOCK_DISCONNECT)
+ with self._buttons[HeaderBar.SIGNAL_CONNECT].handler_block(self._button_handlers[HeaderBar.SIGNAL_CONNECT]):
+ self._buttons[HeaderBar.SIGNAL_CONNECT].set_active(False)
def set_play(self):
- self._playpause_button.set_stock_id(Gtk.STOCK_MEDIA_PLAY)
- with self._playpause_button.handler_block(self._playpause_button_handler):
- self._playpause_button.set_active(True)
+ self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].set_stock_id(Gtk.STOCK_MEDIA_PLAY)
+ with self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].handler_block(self._button_handlers[HeaderBar.SIGNAL_PLAYPAUSE]):
+ self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].set_active(True)
def set_pause(self):
- self._playpause_button.set_stock_id(Gtk.STOCK_MEDIA_PAUSE)
- self._playpause_button.set_active(False)
+ self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].set_stock_id(Gtk.STOCK_MEDIA_PAUSE)
+ with self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].handler_block(self._button_handlers[HeaderBar.SIGNAL_PLAYPAUSE]):
+ self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].set_active(False)
def set_volume(self, volume):
if not self._changing_volume:
self._setting_volume = True
- self._volume_button.set_value(volume / 100)
+ self._buttons[HeaderBar.SIGNAL_SET_VOLUME].set_value(volume / 100)
self._setting_volume = False
@@ -487,53 +514,62 @@ class Toolbar(mcg.MCGBase, Gtk.Toolbar):
-class InfoBar(mcg.MCGBase, Gtk.InfoBar):
- SIGNAL_CLOSE = 'close'
- RESPONSE_CLOSE = 1
+class InfoBar(Gtk.InfoBar):
+ _RESPONSE_CLOSE = 1
def __init__(self):
- mcg.MCGBase.__init__(self)
Gtk.InfoBar.__init__(self)
# Widgets
- self.add_button(Gtk.STOCK_CLOSE, InfoBar.RESPONSE_CLOSE)
+ self.add_button(Gtk.STOCK_CLOSE, InfoBar._RESPONSE_CLOSE)
self._message_label = Gtk.Label()
self._message_label.show()
self.get_content_area().add(self._message_label)
# Signals
- self.connect('close', self.on_response, InfoBar.RESPONSE_CLOSE)
+ self.connect('close', self.on_response, InfoBar._RESPONSE_CLOSE)
self.connect('response', self.on_response)
def on_response(self, widget, response):
- if response == InfoBar.RESPONSE_CLOSE:
- self._callback(InfoBar.SIGNAL_CLOSE)
+ if response == InfoBar._RESPONSE_CLOSE:
+ self.hide()
def show_error(self, message):
self.set_message_type(Gtk.MessageType.ERROR)
self._message_label.set_text(message)
- #Thread(target=self._wait_and_close).start()
-
-
- def _wait_and_close(self):
- time.sleep(5)
- self._callback(InfoBar.SIGNAL_CLOSE)
-class ConnectionPanel(mcg.MCGBase, Gtk.Box):
- SIGNAL_PROFILE_CHANGED = 'change-profile'
+class Panel(mcg.MCGBase):
+
+
+ def __init__(self):
+ mcg.MCGBase.__init__(self)
+
+
+ def get_name(self):
+ raise NotImplementedError()
+
+
+ def get_title(self):
+ raise NotImplementedError()
+
+
+
+
+class ConnectionPanel(Panel, Gtk.HBox):
+ SIGNAL_PROFILE_CHANGED = 'profile-changed'
TAG_AUTOCONNECT = 'autoconnect'
- def __init__(self, config):
- mcg.MCGBase.__init__(self)
+ def __init__(self):
+ Panel.__init__(self)
Gtk.HBox.__init__(self)
- self._config = mcg.MCGProfileConfig()
+ self._profile_config = mcg.MCGProfileConfig()
self._profiles = Gtk.ListStore(str)
self._profile = None
@@ -548,7 +584,6 @@ class ConnectionPanel(mcg.MCGBase, Gtk.Box):
# Profile Selection
self._profile_combo = Gtk.ComboBox.new_with_model(self._profiles)
self._profile_combo.set_entry_text_column(0)
- self._profile_combo.connect("changed", self.on_profile_combo_changed)
renderer = Gtk.CellRendererText()
self._profile_combo.pack_start(renderer, True)
self._profile_combo.add_attribute(renderer, "text", 0)
@@ -559,12 +594,10 @@ class ConnectionPanel(mcg.MCGBase, Gtk.Box):
# New Profile
self._profile_new_button = Gtk.Button()
self._profile_new_button.set_image(Gtk.Image.new_from_stock(Gtk.STOCK_ADD, Gtk.IconSize.BUTTON))
- self._profile_new_button.connect('clicked', self.on_profile_new_clicked)
profile_button_box.add(self._profile_new_button)
# Delete Profile
self._profile_delete_button = Gtk.Button()
self._profile_delete_button.set_image(Gtk.Image.new_from_stock(Gtk.STOCK_DELETE, Gtk.IconSize.BUTTON))
- self._profile_delete_button.connect('clicked', self.on_profile_delete_clicked)
profile_button_box.add(self._profile_delete_button)
# Host
host_label = Gtk.Label("Host:")
@@ -598,6 +631,12 @@ class ConnectionPanel(mcg.MCGBase, Gtk.Box):
self._table.attach(self._autoconnect_button, 1, 2, 5, 6)
# Signals
+ self._profiles.connect('row-changed', self.on_profiles_changed)
+ self._profiles.connect('row-inserted', self.on_profiles_changed)
+ self._profiles.connect('row-deleted', self.on_profiles_changed)
+ self._profile_combo.connect("changed", self.on_profile_combo_changed)
+ self._profile_new_button.connect('clicked', self.on_profile_new_clicked)
+ self._profile_delete_button.connect('clicked', self.on_profile_delete_clicked)
self._host_entry.connect('focus-out-event', self.on_host_entry_outfocused)
self._port_spinner.connect('value-changed', self.on_port_spinner_value_changed)
self._password_entry.connect('focus-out-event', self.on_password_entry_outfocused)
@@ -605,8 +644,19 @@ class ConnectionPanel(mcg.MCGBase, Gtk.Box):
self._autoconnect_button.connect('toggled', self.on_autoconnect_button_toggled)
# Actions
- self._load_config()
- GObject.idle_add(self._select_last_profile, config.last_profile)
+ self._load_profiles()
+
+
+ def get_name(self):
+ return 'connection'
+
+
+ def get_title(self):
+ return "Server"
+
+
+ def on_profiles_changed(self, *data):
+ self._profile_config.save()
def on_profile_combo_changed(self, combo):
@@ -618,21 +668,21 @@ class ConnectionPanel(mcg.MCGBase, Gtk.Box):
self.set_password(self._profile.get('password'))
self.set_image_dir(self._profile.get('image_dir'))
self._autoconnect_button.set_active(ConnectionPanel.TAG_AUTOCONNECT in self._profile.get_tags())
- self._callback(ConnectionPanel.SIGNAL_PROFILE_CHANGED, index, self._profile)
+ self._callback(ConnectionPanel.SIGNAL_PROFILE_CHANGED, index, profile)
def on_profile_new_clicked(self, widget):
profile = mcg.MCGProfile()
- self._config.add_profile(profile)
- self._reload_config()
+ self._profile_config.add_profile(profile)
+ self._reload_profiles()
self._profile_combo.set_active(len(self._profiles)-1)
def on_profile_delete_clicked(self, widget):
(index, profile) = self._get_selected_profile()
if profile is not None:
- self._config.delete_profile(profile)
- self._reload_config()
+ self._profile_config.delete_profile(profile)
+ self._reload_profiles()
self._profile_combo.set_active(0)
@@ -664,10 +714,6 @@ class ConnectionPanel(mcg.MCGBase, Gtk.Box):
self._profile.set_tags(tags)
- def save_profiles(self):
- self._config.save()
-
-
def set_host(self, host):
self._host_entry.set_text(host)
@@ -705,49 +751,47 @@ class ConnectionPanel(mcg.MCGBase, Gtk.Box):
return self._image_dir_entry.get_text()
- def _load_config(self):
- self._config.load()
- for profile in self._config.get_profiles():
- self._profiles.append([str(profile)])
-
-
- def _reload_config(self):
- self._profiles.clear()
- for profile in self._config.get_profiles():
- self._profiles.append([str(profile)])
-
-
- def set_sensitive(self, sensitive):
- self._table.set_sensitive(sensitive)
-
-
- def _get_selected_profile(self):
- index = self._profile_combo.get_active()
- if index >= 0:
- profiles = self._config.get_profiles()
- if index < len(profiles):
- return (index, profiles[index])
- return (-1, None)
-
-
- def _select_last_profile(self, index):
+ def select_profile(self, index):
if len(self._profiles) <= index:
index = 0
self._profile_combo.set_active(index)
+ def _load_profiles(self):
+ self._profile_config.load()
+ for profile in self._profile_config.get_profiles():
+ self._profiles.append([str(profile)])
-class CoverPanel(mcg.MCGBase, Gtk.VBox):
+ def _reload_profiles(self):
+ self._profiles.clear()
+ for profile in self._profile_config.get_profiles():
+ self._profiles.append([str(profile)])
+
+
+ def _get_selected_profile(self):
+ index = self._profile_combo.get_active()
+ if index >= 0:
+ profiles = self._profile_config.get_profiles()
+ if index < len(profiles):
+ return (index, profiles[index])
+ return (-1, None)
+
+
+
+
+class CoverPanel(Panel, Gtk.VBox):
SIGNAL_TOGGLE_FULLSCREEN = 'toggle-fullscreen'
+ SIGNAL_SET_SONG = 'set-song'
def __init__(self):
- mcg.MCGBase.__init__(self)
+ Panel.__init__(self)
Gtk.VBox.__init__(self)
self._current_album = None
self._cover_pixbuf = None
self._timer = None
+ self._properties = {}
# Widgets
self._current_box = Gtk.Box(Gtk.Orientation.HORIZONTAL)
@@ -784,17 +828,46 @@ class CoverPanel(mcg.MCGBase, Gtk.VBox):
# Signals
self._cover_box.connect('button-press-event', self.on_cover_box_pressed)
self._cover_scroll.connect('size-allocate', self.on_cover_size_allocate)
+ self._songs_scale.connect('button-press-event', self.on_songs_start_change)
+ self._songs_scale.connect('button-release-event', self.on_songs_change)
+ def get_name(self):
+ return 'cover'
+
+
+ def get_title(self):
+ return "Cover"
+
def on_cover_box_pressed(self, widget, event):
if event.type == Gdk.EventType._2BUTTON_PRESS:
self._callback(self.SIGNAL_TOGGLE_FULLSCREEN)
+
def on_cover_size_allocate(self, widget, allocation):
self._resize_image()
+ def on_songs_start_change(self, widget, event):
+ if self._timer:
+ GObject.source_remove(self._timer)
+
+
+ def on_songs_change(self, widget, event):
+ value = int(self._songs_scale.get_value())
+ time = self._current_album.get_length()
+ tracks = self._current_album.get_tracks()
+ pos = len(tracks)
+ for index in range(pos-1, -1, -1):
+ time = time - tracks[index].get_length()
+ pos = pos - 1
+ if time < value:
+ break
+ time = max(value - time - 1, 0)
+ self._callback(self.SIGNAL_SET_SONG, pos, time)
+
+
def set_album(self, album):
self._album_title_label.set_markup("{}".format(album.get_title()))
self._album_date_label.set_markup("{}".format(', '.join(album.get_dates())))
@@ -806,8 +879,10 @@ class CoverPanel(mcg.MCGBase, Gtk.VBox):
def set_play(self, pos, time):
if self._timer is not None:
GObject.source_remove(self._timer)
+ tracks = self._current_album.get_tracks()
for index in range(0, pos):
- time = time + self._current_album.get_tracks()[index].get_length()
+ time = time + tracks[index].get_length()
+
self._songs_scale.set_value(time+1)
self._timer = GObject.timeout_add(1000, self._playing)
@@ -831,6 +906,7 @@ class CoverPanel(mcg.MCGBase, Gtk.VBox):
self.child_set_property(self._current_box, 'padding', 10)
self._current_box.child_set_property(self._cover_scroll, 'padding', 10)
self._cover_box.override_background_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0, 0, 0, 0))
+ GObject.idle_add(self._resize_image)
def _set_cover(self, album):
@@ -853,7 +929,10 @@ class CoverPanel(mcg.MCGBase, Gtk.VBox):
self._songs_scale.set_range(0, album.get_length())
length = 0
for track in album.get_tracks():
- self._songs_scale.add_mark(length, Gtk.PositionType.RIGHT, track.get_title())
+ cur_length = length
+ if length > 0 and length < album.get_length():
+ cur_length = cur_length + 1
+ self._songs_scale.add_mark(cur_length, Gtk.PositionType.RIGHT, track.get_title())
length = length + track.get_length()
self._songs_scale.add_mark(length, Gtk.PositionType.RIGHT, "{0[0]:02d}:{0[1]:02d} minutes".format(divmod(length, 60)))
@@ -861,6 +940,7 @@ class CoverPanel(mcg.MCGBase, Gtk.VBox):
def _playing(self):
value = self._songs_scale.get_value() + 1
self._songs_scale.set_value(value)
+
return True
@@ -900,32 +980,35 @@ class CoverPanel(mcg.MCGBase, Gtk.VBox):
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))
+ width = int(math.floor(pixbuf.get_width()*ratio))
+ height = int(math.floor(pixbuf.get_height()*ratio))
+ if width <= 0 or height <= 0:
+ return
# Pixelpuffer auf Oberfläche zeichnen
+ self._cover_image.set_allocation(self._cover_scroll.get_allocation())
self._cover_image.set_from_pixbuf(pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER))
+ self._cover_image.show()
-class PlaylistPanel(mcg.MCGBase, Gtk.VBox):
+class PlaylistPanel(Panel, Gtk.VBox):
SIGNAL_CLEAR_PLAYLIST = 'clear-playlist'
- def __init__(self, config):
- mcg.MCGBase.__init__(self)
+ def __init__(self):
+ Panel.__init__(self)
Gtk.VBox.__init__(self)
- self._config = config
self._host = None
- self._playlist = []
+ self._item_size = 150
+ self._playlist = None
self._playlist_lock = threading.Lock()
self._playlist_stop = threading.Event()
- # Widgets
# Toolbar
self._playlist_toolbar = Gtk.Toolbar()
- self.pack_start(self._playlist_toolbar, False, True, 5)
- # Clear button
+ self.pack_start(self._playlist_toolbar, False, True, 0)
+ # Toolbar: Clear Button
self._clear_playlist_button = Gtk.ToolButton(Gtk.STOCK_CLEAR)
self._playlist_toolbar.add(self._clear_playlist_button)
# Playlist Grid: Model
@@ -942,42 +1025,62 @@ class PlaylistPanel(mcg.MCGBase, Gtk.VBox):
self._playlist_grid.set_item_padding(10)
self._playlist_grid.set_reorderable(False)
self._playlist_grid.set_item_width(-1)
- #self._playlist_grid.set_selection_mode(Gtk.SelectionMode.SINGLE)
+ self._playlist_grid.set_selection_mode(Gtk.SelectionMode.SINGLE)
self._playlist_scroll = Gtk.ScrolledWindow()
self._playlist_scroll.add(self._playlist_grid)
self.pack_end(self._playlist_scroll, True, True, 0)
self.show_all();
# Properties
- self._playlist_toolbar.get_style_context().add_class(MCGGtk.STYLE_CLASS_NO_BG)
- self._playlist_grid.get_style_context().add_class(MCGGtk.STYLE_CLASS_NO_BG)
+ self._playlist_toolbar.get_style_context().add_class(Window.STYLE_CLASS_NO_BG)
+ self._playlist_grid.get_style_context().add_class(Window.STYLE_CLASS_NO_BG)
# Signals
- self._clear_playlist_button.connect('clicked' ,self._callback_from_widget, self.SIGNAL_CLEAR_PLAYLIST)
+ self._clear_playlist_button.connect('clicked' ,self._callback_from_widget, PlaylistPanel.SIGNAL_CLEAR_PLAYLIST)
+
+
+ def get_name(self):
+ return "playlist"
+
+
+ def get_title(self):
+ return "Playlist"
+
+
+ def set_item_size(self, item_size):
+ if self._item_size != item_size:
+ self._item_size = item_size
+ self._redraw()
+
+
+ def get_item_size(self):
+ return self._item_size
def set_playlist(self, host, playlist):
self._host = host
self._playlist_stop.set()
- threading.Thread(target=self._set_playlist, args=(host, playlist, self._config.item_size,)).start()
+ threading.Thread(target=self._set_playlist, args=(host, playlist, self._item_size,)).start()
+
+
+ def stop_threads(self):
+ self._playlist_stop.set()
def _set_playlist(self, host, playlist, size):
self._playlist_lock.acquire()
self._playlist_stop.clear()
self._playlist = playlist
- Gdk.threads_enter()
self._playlist_grid.set_model(None)
self._playlist_grid.freeze_child_notify()
self._playlist_grid_model.clear()
- Gdk.threads_leave()
cache = mcg.MCGCache(host, size)
for album in playlist:
pixbuf = None
if album.get_cover() is not None:
try:
- pixbuf = MCGGtk.load_thumbnail(cache, album, size)
+ pixbuf = Application.load_thumbnail(cache, album, size)
except Exception as e:
print(e)
if pixbuf is None:
@@ -997,71 +1100,76 @@ class PlaylistPanel(mcg.MCGBase, Gtk.VBox):
self._playlist_lock.release()
return
- Gdk.threads_enter()
self._playlist_grid.set_model(self._playlist_grid_model)
self._playlist_grid.thaw_child_notify()
self._playlist_grid.set_columns(len(playlist))
- Gdk.threads_leave()
self._playlist_lock.release()
+ def _redraw(self):
+ if self._playlist is not None:
+ self.set_playlist(self._host, self._playlist)
+
+
def _callback_from_widget(self, widget, signal, *data):
self._callback(signal, *data)
-class LibraryPanel(mcg.MCGBase, Gtk.VBox):
+class LibraryPanel(Panel, Gtk.VBox):
SIGNAL_UPDATE = 'update'
SIGNAL_PLAY = 'play'
+ SIGNAL_ITEM_SIZE_CHANGED = 'item-size-changed'
+ SIGNAL_SORT_ORDER_CHANGED = 'sort-order-changed'
+ SIGNAL_SORT_TYPE_CHANGED = 'sort-type-changed'
- def __init__(self, config):
- mcg.MCGBase.__init__(self)
+ def __init__(self):
+ Panel.__init__(self)
Gtk.VBox.__init__(self)
- self._config = config
- self._host = None
- self._albums = {}
+ self._buttons = {}
+ self._albums = None
+ self._host = "localhost"
self._filter_string = ""
+ self._item_size = 150
+ self._sort_order = mcg.MCGAlbum.SORT_BY_YEAR
+ self._sort_type = Gtk.SortType.DESCENDING
self._grid_pixbufs = {}
self._old_ranges = {}
self._library_lock = threading.Lock()
self._library_stop = threading.Event()
# Widgets
+ # Progress Bar
+ self._progress_bar = Gtk.ProgressBar()
# Toolbar
- self._library_toolbar = Gtk.Toolbar()
- self.pack_start(self._library_toolbar, False, True, 5)
- # Update Button
- self._update_library_button = Gtk.ToolButton(Gtk.STOCK_REFRESH)
- self._library_toolbar.add(self._update_library_button)
- # Separator
- separator = Gtk.SeparatorToolItem()
- separator.set_draw(False)
- separator.set_expand(True)
- self._library_toolbar.add(separator)
- # Filter Entry
+ self._library_toolbar = Gtk.HeaderBar()
+ self.pack_start(self._library_toolbar, False, True, 0)
+ # Toolbar: buttons left
+ # Toolbar: buttons left: Update Button
+ self._buttons[LibraryPanel.SIGNAL_UPDATE] = Gtk.ToolButton(Gtk.STOCK_REFRESH)
+ self._library_toolbar.pack_start(self._buttons[LibraryPanel.SIGNAL_UPDATE])
+ # Toolbar: Filter Entry
self._filter_entry = Gtk.SearchEntry()
self._filter_entry.set_placeholder_text("Bibliothek durchsuchen")
- item = Gtk.ToolItem()
- item.add(self._filter_entry)
- self._library_toolbar.add(item)
- # Separator
- separator = Gtk.SeparatorToolItem()
- separator.set_draw(False)
- separator.set_expand(True)
- self._library_toolbar.add(separator)
- # Grid Scale
+ self._library_toolbar.set_custom_title(self._filter_entry)
+ # Toolbar: buttons right
+ self._right_toolbar = Gtk.Toolbar()
+ self._right_toolbar.set_show_arrow(False)
+ self._right_toolbar.get_style_context().add_class(Window.STYLE_CLASS_NO_BG)
+ self._library_toolbar.pack_end(self._right_toolbar)
+ # Toolbar: buttons right: Grid Scale
self._grid_scale = Gtk.HScale()
- self._grid_scale.set_range(100,600)
+ self._grid_scale.set_range(100, 1000)
self._grid_scale.set_round_digits(0)
- self._grid_scale.set_value(self._config.item_size)
+ self._grid_scale.set_value(self._item_size)
self._grid_scale.set_size_request(100, -1)
self._grid_scale.set_draw_value(False)
item = Gtk.ToolItem()
item.add(self._grid_scale)
- self._library_toolbar.add(item)
- # Library Sort Menu
+ self._right_toolbar.add(item)
+ # Toolbar: buttons right: Library Sort Menu
library_sort_store = Gtk.ListStore(str, str)
library_sort_store.append([mcg.MCGAlbum.SORT_BY_ARTIST, "sort by artist"])
library_sort_store.append([mcg.MCGAlbum.SORT_BY_TITLE, "sort by title"])
@@ -1071,30 +1179,19 @@ class LibraryPanel(mcg.MCGBase, Gtk.VBox):
self._library_sort_combo.pack_start(renderer_text, True)
self._library_sort_combo.add_attribute(renderer_text, "text", 1)
self._library_sort_combo.set_id_column(0)
- self._library_sort_combo.set_active_id(self._config.library_sort_order)
+ self._library_sort_combo.set_active_id(self._sort_order)
item = Gtk.ToolItem()
item.add(self._library_sort_combo)
- self._library_toolbar.add(item)
- # Library Sort Type
- self._library_sort_type_button = Gtk.ToggleToolButton.new_from_stock(Gtk.STOCK_SORT_ASCENDING)
- if self._config.library_sort_type == Gtk.SortType.DESCENDING:
- self._library_sort_type_button.set_active(True)
- self._library_sort_type_button.set_stock_id(Gtk.STOCK_SORT_DESCENDING)
- self._library_toolbar.add(self._library_sort_type_button)
- # Progress Bar
- self._progress_bar = Gtk.ProgressBar()
- # Library Grid: TextRenderer
-# text_renderer = Gtk.CellRendererText()
-# text_renderer.props.alignment = Pango.Alignment.CENTER
-# text_renderer.props.wrap_mode = Pango.WrapMode.WORD
-# text_renderer.props.xalign = 0.5
-# text_renderer.props.yalign = 0
-# text_renderer.props.width = 150
-# text_renderer.props.wrap_width = 150
+ self._right_toolbar.add(item)
+ # Toolbar: buttons right: Library Sort Type
+ self._library_sort_type_button = Gtk.ToggleToolButton.new_from_stock(Gtk.STOCK_SORT_ASCENDING)
+ self._library_sort_type_button.set_active(True)
+ self._library_sort_type_button.set_stock_id(Gtk.STOCK_SORT_DESCENDING)
+ self._right_toolbar.add(self._library_sort_type_button)
# Library Grid: Model
self._library_grid_model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str)
- self._library_grid_model.set_sort_func(2, self.compare_albums, self._config.library_sort_order)
- self._library_grid_model.set_sort_column_id(2, self._config.library_sort_type)
+ self._library_grid_model.set_sort_func(2, self.compare_albums, self._sort_order)
+ self._library_grid_model.set_sort_column_id(2, self._sort_type)
self._library_grid_filter = self._library_grid_model.filter_new()
self._library_grid_filter.set_visible_func(self.on_filter_visible)
# Library Grid
@@ -1108,32 +1205,36 @@ class LibraryPanel(mcg.MCGBase, Gtk.VBox):
self._library_grid.set_spacing(0)
self._library_grid.set_row_spacing(0)
self._library_grid.set_column_spacing(0)
- self._library_grid.set_item_padding(10)
+ self._library_grid.set_item_padding(5)
self._library_grid.set_reorderable(False)
self._library_grid.set_item_width(-1)
self._library_grid.set_selection_mode(Gtk.SelectionMode.SINGLE)
self._library_scroll = Gtk.ScrolledWindow()
self._library_scroll.add(self._library_grid)
self.pack_end(self._library_scroll, True, True, 0)
- #self.show_all();
# Properties
- self._library_grid.get_style_context().add_class(MCGGtk.STYLE_CLASS_NO_BG)
- self._library_toolbar.get_style_context().add_class(MCGGtk.STYLE_CLASS_NO_BG)
+ self._library_toolbar.get_style_context().add_class(Window.STYLE_CLASS_NO_BG)
+ self._library_toolbar.get_style_context().add_class(Window.STYLE_CLASS_NO_BORDER)
+ self._library_grid.get_style_context().add_class(Window.STYLE_CLASS_NO_BG)
+ self._library_grid.get_style_context().add_class(Window.STYLE_CLASS_NO_BORDER)
# Signals
- self._update_library_button.connect('clicked', self._callback_from_widget, self.SIGNAL_UPDATE)
- self._filter_entry.connect('changed', self.on_filter_entry_changed)
+ self._buttons[LibraryPanel.SIGNAL_UPDATE].connect('clicked', self._callback_from_widget, self.SIGNAL_UPDATE)
self._grid_scale.connect('change-value', self.on_grid_scale_change)
self._grid_scale.connect('button-release-event', self.on_grid_scale_changed)
self._library_sort_combo.connect("changed", self.on_library_sort_combo_changed)
self._library_sort_type_button.connect('clicked', self.on_library_sort_type_button_activated)
+ self._filter_entry.connect('changed', self.on_filter_entry_changed)
self._library_grid.connect('item-activated', self.on_library_grid_clicked)
- def on_filter_entry_changed(self, widget):
- self._filter_string = self._filter_entry.get_text()
- GObject.idle_add(self._library_grid_filter.refilter)
+ def get_name(self):
+ return "library"
+
+
+ def get_title(self):
+ return "Library"
def on_filter_visible(self, model, iter, data):
@@ -1144,12 +1245,17 @@ class LibraryPanel(mcg.MCGBase, Gtk.VBox):
return album.filter(self._filter_string)
+ def on_filter_entry_changed(self, widget):
+ self._filter_string = self._filter_entry.get_text()
+ GObject.idle_add(self._library_grid_filter.refilter)
+
+
def on_grid_scale_change(self, widget, scroll, value):
size = round(value)
range = self._grid_scale.get_adjustment()
if size < range.get_lower() or size > range.get_upper():
return
- self._config.item_width = size
+ self._item_size = size
GObject.idle_add(self._set_widget_grid_size, self._library_grid, size, True)
@@ -1158,13 +1264,15 @@ class LibraryPanel(mcg.MCGBase, Gtk.VBox):
range = self._grid_scale.get_adjustment()
if size < range.get_lower() or size > range.get_upper():
return
+ self._callback(LibraryPanel.SIGNAL_ITEM_SIZE_CHANGED, size)
self._redraw()
def on_library_sort_combo_changed(self, combo):
sort_order = combo.get_active_id()
- self._config.library_sort_order = sort_order
+ self._sort_order = sort_order
self._library_grid_model.set_sort_func(2, self.compare_albums, sort_order)
+ self._callback(LibraryPanel.SIGNAL_SORT_ORDER_CHANGED, sort_order)
def on_library_sort_type_button_activated(self, button):
@@ -1174,20 +1282,56 @@ class LibraryPanel(mcg.MCGBase, Gtk.VBox):
else:
sort_type = Gtk.SortType.ASCENDING
button.set_stock_id(Gtk.STOCK_SORT_ASCENDING)
- self._config.library_sort_type = sort_type
+ self._sort_type = sort_type
self._library_grid_model.set_sort_column_id(2, sort_type)
+ self._callback(LibraryPanel.SIGNAL_SORT_TYPE_CHANGED, sort_type)
def on_library_grid_clicked(self, widget, path):
path = self._library_grid_filter.convert_path_to_child_path(path)
iter = self._library_grid_model.get_iter(path)
- self._callback(self.SIGNAL_PLAY, self._library_grid_model.get_value(iter, 2))
+ self._callback(LibraryPanel.SIGNAL_PLAY, self._library_grid_model.get_value(iter, 2))
+
+
+ def set_item_size(self, item_size):
+ if self._item_size != item_size:
+ self._item_size = item_size
+ self._grid_scale.set_value(item_size)
+ self._redraw()
+
+
+ def get_item_size(self):
+ return self._item_size
+
+
+ def set_sort_order(self, sort_order):
+ if self._sort_order != sort_order:
+ self._sort_order = sort_order
+ self._library_sort_combo.set_active_id(sort_order)
+
+
+ def get_sort_order(self):
+ return self._sort_order
+
+
+ def set_sort_type(self, sort_type):
+ if self._sort_type != sort_type:
+ if sort_type:
+ self._sort_type = Gtk.SortType.DESCENDING
+ self._library_sort_type_button.set_active(True)
+ else:
+ self._sort_type = Gtk.SortType.ASCENDING
+ self._library_sort_type_button.set_active(False)
+
+
+ def get_sort_type(self):
+ return (self._sort_type != Gtk.SortType.ASCENDING)
def set_albums(self, host, albums):
self._host = host
self._library_stop.set()
- threading.Thread(target=self._set_albums, args=(host, albums, self._config.item_size,)).start()
+ threading.Thread(target=self._set_albums, args=(host, albums, self._item_size,)).start()
def compare_albums(self, model, row1, row2, criterion):
@@ -1199,19 +1343,22 @@ class LibraryPanel(mcg.MCGBase, Gtk.VBox):
return mcg.MCGAlbum.compare(self._albums[hash1], self._albums[hash2], criterion)
+ def stop_threads(self):
+ self._library_stop.set()
+
+
def _set_albums(self, host, albums, size):
self._library_lock.acquire()
self._library_stop.clear()
self._albums = albums
- self.remove(self._library_toolbar)
- self._progress_bar.set_fraction(0.0)
- self.pack_start(self._progress_bar, False, True, 5)
- self.show_all()
- Gdk.threads_enter()
+ if len(self.get_children()) > 1:
+ GObject.idle_add(self.remove, self.get_children()[0])
+ GObject.idle_add(self._progress_bar.set_fraction, 0.0)
+ GObject.idle_add(self.pack_start, self._progress_bar, False, True, 0)
+ GObject.idle_add(self.show_all)
self._library_grid.set_model(None)
self._library_grid.freeze_child_notify()
self._library_grid_model.clear()
- Gdk.threads_leave()
i = 0
n = len(albums)
@@ -1221,7 +1368,7 @@ class LibraryPanel(mcg.MCGBase, Gtk.VBox):
album = albums[hash]
pixbuf = None
try:
- pixbuf = MCGGtk.load_thumbnail(cache, album, size)
+ pixbuf = Application.load_thumbnail(cache, album, size)
except Exception as e:
print(e)
if pixbuf is None:
@@ -1244,17 +1391,24 @@ class LibraryPanel(mcg.MCGBase, Gtk.VBox):
self._library_lock.release()
return
- Gdk.threads_enter()
self._library_grid.set_model(self._library_grid_filter)
self._library_grid.thaw_child_notify()
- Gdk.threads_leave()
+ self._library_grid.set_item_width(-1)
self._library_lock.release()
- self.remove(self._progress_bar)
- self.pack_start(self._library_toolbar, False, True, 5)
- self.show_all()
+ if len(self.get_children()) > 1:
+ GObject.idle_add(self.remove, self.get_children()[0])
+ GObject.idle_add(self.pack_start, self._library_toolbar, False, True, 0)
+ GObject.idle_add(self.show_all)
def _set_widget_grid_size(self, grid_widget, size, vertical):
+ self._library_stop.set()
+ threading.Thread(target=self._set_widget_grid_size_thread, args=(grid_widget, size, vertical,)).start()
+
+
+ def _set_widget_grid_size_thread(self, grid_widget, size, vertical):
+ self._library_lock.acquire()
+ self._library_stop.clear()
grid_filter = grid_widget.get_model()
grid_model = grid_filter.get_model()
@@ -1272,6 +1426,7 @@ class LibraryPanel(mcg.MCGBase, Gtk.VBox):
c = w * h
vis_range = grid_widget.get_visible_range()
if vis_range is None:
+ self._library_lock.release()
return
(vis_start,), (vis_end,) = vis_range
vis_end = min(vis_start + c, len(grid_filter))
@@ -1291,13 +1446,19 @@ class LibraryPanel(mcg.MCGBase, Gtk.VBox):
pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, 1, 1)
grid_model.set_value(iter, 0, pixbuf)
+ if self._library_stop.is_set():
+ self._library_lock.release()
+ return
+
self._old_ranges[grid_widget_id] = vis_range
grid_widget.set_item_width(size)
- #self._config.item_size = size
+
+ self._library_lock.release()
def _redraw(self):
- threading.Thread(target=self._set_albums, args=(self._host, self._albums, self._config.item_size,)).start()
+ if self._albums is not None:
+ self.set_albums(self._host, self._albums)
def _callback_from_widget(self, widget, signal, *data):
@@ -1305,58 +1466,31 @@ class LibraryPanel(mcg.MCGBase, Gtk.VBox):
-class Configuration(mcg.MCGConfig):
- CONFIG_FILE = 'mcggtk.conf'
+
+class StackSwitcher(mcg.MCGBase, Gtk.StackSwitcher):
+ SIGNAL_STACK_SWITCHED = 'stack-switched'
def __init__(self):
- mcg.MCGConfig.__init__(self, Configuration.CONFIG_FILE)
- self._setup()
- self.load()
+ mcg.MCGBase.__init__(self)
+ Gtk.StackSwitcher.__init__(self)
+ self._temp_button = None
- def load(self):
- super().load()
- self.last_profile = self.getint('default', 'last-profile')
- self.window_width = self.getint('gui', 'window-width')
- self.window_height = self.getint('gui', 'window-height')
- self.window_maximized = self.getboolean('gui', 'window-maximized')
- self.item_size = self.getint('gui', 'item-size')
- self.view = self.get('gui', 'view')
- self.library_sort_order = self.get('gui', 'library-sort-order')
- if self.getint('gui', 'library-sort-type') == 0:
- self.library_sort_type = Gtk.SortType.ASCENDING
+ def set_stack(self, stack):
+ super().set_stack(stack)
+ for child in self.get_children():
+ if type(child) is Gtk.RadioButton:
+ child.connect('clicked', self.on_clicked)
+
+
+ def on_clicked(self, widget):
+ if not self._temp_button:
+ self._temp_button = widget
else:
- self.library_sort_type = Gtk.SortType.DESCENDING
- self.save()
+ self._temp_button = None
+ self._callback(StackSwitcher.SIGNAL_STACK_SWITCHED, self)
- def save(self):
- self.set('default', 'last-profile', str(self.last_profile))
- self.set('gui', 'window-width', str(self.window_width))
- self.set('gui', 'window-height', str(self.window_height))
- self.set('gui', 'window-maximized', str(self.window_maximized))
- self.set('gui', 'item-size', str(self.item_size))
- self.set('gui', 'view', str(self.view))
- self.set('gui', 'library-sort-order', str(self.library_sort_order))
- if self.library_sort_type == Gtk.SortType.ASCENDING:
- self.set('gui', 'library-sort-type', str(0))
- else:
- self.set('gui', 'library-sort-type', str(1))
- super().save()
- def _setup(self):
- if not self.has_section('default'):
- self.add_section('default')
- self.set('default', 'last-profile', str(0))
- if not self.has_section('gui'):
- self.add_section('gui')
- self.set('gui', 'window-width', str(800))
- self.set('gui', 'window-height', str(600))
- self.set('gui', 'window-maximized', str(False))
- self.set('gui', 'item-size', str(100))
- self.set('gui', 'view', MCGGtk.VIEW_COVER)
- self.set('gui', 'library-sort-order', mcg.MCGAlbum.SORT_BY_YEAR)
- self.set('gui', 'library-sort-type', str(1))
-
diff --git a/mcg.py b/mcg.py
index bb274ed..e8f95bc 100644
--- a/mcg.py
+++ b/mcg.py
@@ -144,6 +144,12 @@ class MCGClient(MCGBase, mpd.MPDClient):
self._add_action(self._play_album, album)
+ def seek(self, pos, time):
+ """Seeks to a song at a position
+ """
+ self._add_action(self._seek, pos, time)
+
+
def stop(self):
self._add_action(self._stop)
@@ -340,6 +346,10 @@ class MCGClient(MCGBase, mpd.MPDClient):
self._call('playid', track_ids[0])
+ def _seek(self, pos, time):
+ self._call('seek', pos, time)
+
+
def _stop(self):
self._call('stop')
@@ -766,6 +776,8 @@ class MCGProfileConfig(MCGConfig):
self.add_section(section)
for attribute in profile.get_attributes():
self.set(section, attribute, str(profile.get(attribute)))
+ for section in self.sections()[len(self._profiles)+1:]:
+ self.remove_section(section)
super().save()
@@ -825,13 +837,15 @@ class MCGProfile(MCGConfigurable):
class MCGCache():
DIRNAME = '~/.cache/mcg/'
SIZE_FILENAME = 'size'
+ _lock = threading.Lock()
def __init__(self, host, size):
+ self._host = host
+ self._size = size
self._dirname = os.path.expanduser(os.path.join(MCGCache.DIRNAME, host))
if not os.path.exists(self._dirname):
os.makedirs(self._dirname)
- self._size = size
self._read_size()
@@ -841,23 +855,28 @@ class MCGCache():
def _read_size(self):
size = 100
+ MCGCache._lock.acquire()
+ # Read old size
filename = os.path.join(self._dirname, MCGCache.SIZE_FILENAME)
if os.path.exists(filename):
with open(filename, 'r') as f:
size = int(f.readline())
+ # Clear cache if size has changed
if size != self._size:
self._clear()
- with open(filename, 'w') as f:
- f.write(str(self._size))
+ # Write new size
+ with open(filename, 'w') as f:
+ f.write(str(self._size))
+ MCGCache._lock.release()
def _clear(self):
for filename in os.listdir(self._dirname):
path = os.path.join(self._dirname, filename)
- try:
- if os.path.isfile(path):
+ if os.path.isfile(path):
+ try:
os.unlink(path)
- except Exception as e:
- print("clear:", e)
+ except Exception as e:
+ print("clear:", e)
diff --git a/mcgGtk.py b/mcgGtk.py
index c92cdff..25e6e8f 100755
--- a/mcgGtk.py
+++ b/mcgGtk.py
@@ -6,24 +6,33 @@
__author__ = "coderkun"
__email__ = ""
__license__ = "GPL"
-__version__ = "0.3"
+__version__ = "0.4"
__status__ = "Development"
+import os
+import sys
+
from gi.repository import Gtk, Gdk, GObject
-import gui.gtk
+from gui import gtk
+
+
+
+
+# Set environment
+srcdir = os.path.abspath(os.path.join(os.path.dirname(gtk.__file__), '..'))
+if not os.environ.get('GSETTINGS_SCHEMA_DIR'):
+ os.environ['GSETTINGS_SCHEMA_DIR'] = os.path.join(srcdir, 'data')
+
+
if __name__ == "__main__":
- GObject.threads_init()
- Gdk.threads_init()
- mcgg = gui.gtk.MCGGtk()
- mcgg.show_all()
- try:
- Gtk.main()
- except (KeyboardInterrupt, SystemExit):
- pass
+ # Start application
+ app = gtk.Application()
+ exit_status = app.run(sys.argv)
+ sys.exit(exit_status)