merge branch 'redesign'

This commit is contained in:
coderkun 2016-08-01 10:49:25 +02:00
commit fb45966d65
5 changed files with 1268 additions and 425 deletions

View file

@ -37,6 +37,16 @@
<summary>Last selected panel</summary> <summary>Last selected panel</summary>
<description>The index of the last selected panel.</description> <description>The index of the last selected panel.</description>
</key> </key>
<key type="s" name="tracklist-size">
<default>'large'</default>
<choices>
<choice value='large' />
<choice value='small' />
<choice value='hidden' />
</choices>
<summary>Size of tracklist on cover panel</summary>
<description>The size of the tracklist on the cover panel.</description>
</key>
<key type="i" name="item-size"> <key type="i" name="item-size">
<range min="100" max="1000" /> <range min="100" max="1000" />
<default>150</default> <default>150</default>

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,13 @@
border:none; border:none;
} }
.cover-labels label {
font-size:larger;
}
.cover-labels label:first-child {
font-weight:bold;
}
iconview.view:selected, iconview.view:selected,
iconview.view:selected:focus, iconview.view:selected:focus,
GtkIconView.cell:selected, GtkIconView.cell:selected,
@ -21,3 +28,7 @@ GtkIconView.cell:selected:focus {
iconview.view:hover { iconview.view:hover {
-gtk-icon-effect:highlight; -gtk-icon-effect:highlight;
} }
actionbar {
background-color:@theme_unfocused_bg_color;
}

View file

@ -38,6 +38,7 @@ class Application(Gtk.Application):
SETTING_WINDOW_SIZE = 'window-size' SETTING_WINDOW_SIZE = 'window-size'
SETTING_WINDOW_MAXIMIZED = 'window-maximized' SETTING_WINDOW_MAXIMIZED = 'window-maximized'
SETTING_PANEL = 'panel' SETTING_PANEL = 'panel'
SETTING_TRACKLIST_SIZE = 'tracklist-size'
SETTING_ITEM_SIZE = 'item-size' SETTING_ITEM_SIZE = 'item-size'
SETTING_SORT_ORDER = 'sort-order' SETTING_SORT_ORDER = 'sort-order'
SETTING_SORT_TYPE = 'sort-type' SETTING_SORT_TYPE = 'sort-type'
@ -46,7 +47,7 @@ class Application(Gtk.Application):
def __init__(self): def __init__(self):
Gtk.Application.__init__(self, application_id="de.coderkun.mcg", flags=Gio.ApplicationFlags.FLAGS_NONE) Gtk.Application.__init__(self, application_id="de.coderkun.mcg-dev", flags=Gio.ApplicationFlags.FLAGS_NONE)
self._window = None self._window = None
@ -77,6 +78,27 @@ class Application(Gtk.Application):
) )
def load_cover(url):
if not url:
return None
if url.startswith('/'):
try:
return GdkPixbuf.Pixbuf.new_from_file(url)
except Exception as e:
print(e)
return None
else:
try:
response = urllib.request.urlopen(url)
loader = GdkPixbuf.PixbufLoader()
loader.write(response.read())
loader.close()
return loader.get_pixbuf()
except Exception as e:
print(e)
return None
def load_thumbnail(cache, album, size): def load_thumbnail(cache, album, size):
cache_url = cache.create_filename(album) cache_url = cache.create_filename(album)
pixbuf = None pixbuf = None
@ -88,26 +110,13 @@ class Application(Gtk.Application):
print(e) print(e)
else: else:
url = album.get_cover() url = album.get_cover()
if url is not None: pixbuf = Application.load_cover(url)
if url.startswith('/'): if pixbuf is not None:
try: pixbuf = pixbuf.scale_simple(size, size, GdkPixbuf.InterpType.HYPER)
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(url, size, size) filetype = os.path.splitext(url)[1][1:]
except Exception as e: if filetype == 'jpg':
print(e) filetype = 'jpeg'
else: pixbuf.savev(cache.create_filename(album), filetype, [], [])
try:
response = urllib.request.urlopen(url)
loader = GdkPixbuf.PixbufLoader()
loader.write(response.read())
loader.close()
pixbuf = loader.get_pixbuf().scale_simple(size, size, GdkPixbuf.InterpType.HYPER)
except Exception as e:
print(e)
if pixbuf is not None:
filetype = os.path.splitext(url)[1][1:]
if filetype == 'jpg':
filetype = 'jpeg'
pixbuf.savev(cache.create_filename(album), filetype, [], [])
return pixbuf return pixbuf
@ -151,6 +160,8 @@ class Window():
self._stack = builder.get_object('panelstack') self._stack = builder.get_object('panelstack')
# Header # Header
self._header_bar = HeaderBar(builder) self._header_bar = HeaderBar(builder)
# Toolbar stack
self._toolbar_stack = builder.get_object('toolbarstack')
# Properties # Properties
self._header_bar.set_sensitive(False, False) self._header_bar.set_sensitive(False, False)
@ -159,6 +170,7 @@ class Window():
if use_keyring: if use_keyring:
self._panels[Window._PANEL_INDEX_CONNECTION].set_password(keyring.get_password(Application.KEYRING_SYSTEM, Application.KEYRING_USERNAME)) self._panels[Window._PANEL_INDEX_CONNECTION].set_password(keyring.get_password(Application.KEYRING_SYSTEM, Application.KEYRING_USERNAME))
self._panels[Window._PANEL_INDEX_CONNECTION].set_image_dir(self._settings.get_string(Application.SETTING_IMAGE_DIR)) self._panels[Window._PANEL_INDEX_CONNECTION].set_image_dir(self._settings.get_string(Application.SETTING_IMAGE_DIR))
self._panels[Window._PANEL_INDEX_COVER].set_tracklist_size(self._settings.get_string(Application.SETTING_TRACKLIST_SIZE))
self._panels[Window._PANEL_INDEX_PLAYLIST].set_item_size(self._settings.get_int(Application.SETTING_ITEM_SIZE)) 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_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_order(self._settings.get_string(Application.SETTING_SORT_ORDER))
@ -171,6 +183,7 @@ class Window():
self._header_bar.connect_signal(HeaderBar.SIGNAL_SET_VOLUME, self.on_header_bar_set_volume) 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_CONNECTION_CHANGED, self.on_connection_panel_connection_changed) self._panels[Window._PANEL_INDEX_CONNECTION].connect_signal(ConnectionPanel.SIGNAL_CONNECTION_CHANGED, self.on_connection_panel_connection_changed)
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_TOGGLE_FULLSCREEN, self.on_cover_panel_toggle_fullscreen)
self._panels[Window._PANEL_INDEX_COVER].connect_signal(CoverPanel.SIGNAL_TRACKLIST_SIZE_CHANGED, self.on_cover_panel_tracklist_size_changed)
self._panels[Window._PANEL_INDEX_COVER].connect_signal(CoverPanel.SIGNAL_SET_SONG, self.on_cover_panel_set_song) self._panels[Window._PANEL_INDEX_COVER].connect_signal(CoverPanel.SIGNAL_SET_SONG, self.on_cover_panel_set_song)
self._panels[Window._PANEL_INDEX_PLAYLIST].connect_signal(PlaylistPanel.SIGNAL_CLEAR_PLAYLIST, self.on_playlist_panel_clear_playlist) self._panels[Window._PANEL_INDEX_PLAYLIST].connect_signal(PlaylistPanel.SIGNAL_CLEAR_PLAYLIST, self.on_playlist_panel_clear_playlist)
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_UPDATE, self.on_library_panel_update)
@ -184,6 +197,7 @@ class Window():
self._mcg.connect_signal(mcg.Client.SIGNAL_LOAD_ALBUMS, self.on_mcg_load_albums) self._mcg.connect_signal(mcg.Client.SIGNAL_LOAD_ALBUMS, self.on_mcg_load_albums)
self._mcg.connect_signal(mcg.Client.SIGNAL_ERROR, self.on_mcg_error) self._mcg.connect_signal(mcg.Client.SIGNAL_ERROR, self.on_mcg_error)
self._settings.connect('changed::'+Application.SETTING_PANEL, self.on_settings_panel_changed) self._settings.connect('changed::'+Application.SETTING_PANEL, self.on_settings_panel_changed)
self._settings.connect('changed::'+Application.SETTING_TRACKLIST_SIZE, self.on_settings_tracklist_size_changed)
self._settings.connect('changed::'+Application.SETTING_ITEM_SIZE, self.on_settings_item_size_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_ORDER, self.on_settings_sort_order_changed)
self._settings.connect('changed::'+Application.SETTING_SORT_TYPE, self.on_settings_sort_type_changed) self._settings.connect('changed::'+Application.SETTING_SORT_TYPE, self.on_settings_sort_type_changed)
@ -233,6 +247,7 @@ class Window():
# HeaderBar callbacks # HeaderBar callbacks
def on_header_bar_stack_switched(self, widget): def on_header_bar_stack_switched(self, widget):
self._set_visible_toolbar()
self._save_visible_panel() self._save_visible_panel()
@ -269,9 +284,13 @@ class Window():
def on_cover_panel_toggle_fullscreen(self): def on_cover_panel_toggle_fullscreen(self):
if not self._fullscreened: if not self._fullscreened:
self.fullscreen() self._appwindow.fullscreen()
else: else:
self.unfullscreen() self._appwindow.unfullscreen()
def on_cover_panel_tracklist_size_changed(self, size):
self._settings.set_string(Application.SETTING_TRACKLIST_SIZE, size)
def on_cover_panel_set_song(self, pos, time): def on_cover_panel_set_song(self, pos, time):
@ -350,6 +369,11 @@ class Window():
self._stack.set_visible_child(self._panels[panel_index].get()) self._stack.set_visible_child(self._panels[panel_index].get())
def on_settings_tracklist_size_changed(self, settings, key):
size = settings.get_string(key)
self._panels[Window._PANEL_INDEX_COVER].set_tracklist_size(size)
def on_settings_item_size_changed(self, settings, key): def on_settings_item_size_changed(self, settings, key):
size = settings.get_int(key) size = settings.get_int(key)
self._panels[Window._PANEL_INDEX_PLAYLIST].set_item_size(size) self._panels[Window._PANEL_INDEX_PLAYLIST].set_item_size(size)
@ -404,10 +428,10 @@ class Window():
if fullscreened_new != self._fullscreened: if fullscreened_new != self._fullscreened:
self._fullscreened = fullscreened_new self._fullscreened = fullscreened_new
if self._fullscreened: if self._fullscreened:
self._header_bar.hide() self._header_bar.get().hide()
self._panels[Window._PANEL_INDEX_COVER].set_fullscreen(True) self._panels[Window._PANEL_INDEX_COVER].set_fullscreen(True)
else: else:
self._header_bar.show() self._header_bar.get().show()
self._panels[Window._PANEL_INDEX_COVER].set_fullscreen(False) self._panels[Window._PANEL_INDEX_COVER].set_fullscreen(False)
@ -418,6 +442,13 @@ class Window():
self._settings.set_int(Application.SETTING_PANEL, panel_index_selected) self._settings.set_int(Application.SETTING_PANEL, panel_index_selected)
def _set_visible_toolbar(self):
panels = [panel.get() for panel in self._panels]
panel_index_selected = panels.index(self._stack.get_visible_child())
toolbar = self._panels[panel_index_selected].get_toolbar()
self._toolbar_stack.set_visible_child(toolbar)
def _show_error(self, message): def _show_error(self, message):
self._infobar.show_error(message) self._infobar.show_error(message)
@ -605,6 +636,7 @@ class ConnectionPanel(mcg.Base):
# Widgets # Widgets
self._panel = builder.get_object('server-panel') self._panel = builder.get_object('server-panel')
self._toolbar = builder.get_object('server-toolbar')
# Zeroconf # Zeroconf
self._zeroconf_list = builder.get_object('server-zeroconf-list') self._zeroconf_list = builder.get_object('server-zeroconf-list')
self._zeroconf_list.set_model(self._services) self._zeroconf_list.set_model(self._services)
@ -629,6 +661,10 @@ class ConnectionPanel(mcg.Base):
return self._panel return self._panel
def get_toolbar(self):
return self._toolbar
def get_signal_handlers(self): def get_signal_handlers(self):
return { return {
'on_server-zeroconf-list-selection_changed': self.on_service_selected, 'on_server-zeroconf-list-selection_changed': self.on_service_selected,
@ -718,7 +754,11 @@ class ConnectionPanel(mcg.Base):
class CoverPanel(mcg.Base): class CoverPanel(mcg.Base):
SIGNAL_TOGGLE_FULLSCREEN = 'toggle-fullscreen' SIGNAL_TOGGLE_FULLSCREEN = 'toggle-fullscreen'
SIGNAL_TRACKLIST_SIZE_CHANGED = 'tracklist-size-changed'
SIGNAL_SET_SONG = 'set-song' SIGNAL_SET_SONG = 'set-song'
TRACKLIST_SIZE_LARGE = 'large'
TRACKLIST_SIZE_SMALL = 'small'
TRACKLIST_SIZE_HIDDEN = 'hidden'
def __init__(self, builder): def __init__(self, builder):
@ -728,27 +768,47 @@ class CoverPanel(mcg.Base):
self._cover_pixbuf = None self._cover_pixbuf = None
self._timer = None self._timer = None
self._properties = {} self._properties = {}
self._tracklist_size = CoverPanel.TRACKLIST_SIZE_LARGE
# Widgets # Widgets
self._appwindow = builder.get_object('appwindow')
self._panel = builder.get_object('cover-panel') self._panel = builder.get_object('cover-panel')
self._toolbar = builder.get_object('cover-toolbar')
# Toolbar menu
self._toolbar_tracklist_buttons = {
CoverPanel.TRACKLIST_SIZE_LARGE: builder.get_object('cover-toolbar-tracklist-large'),
CoverPanel.TRACKLIST_SIZE_SMALL: builder.get_object('cover-toolbar-tracklist-small'),
CoverPanel.TRACKLIST_SIZE_HIDDEN: builder.get_object('cover-toolbar-tracklist-hidden')
}
# Cover # Cover
self._cover_stack = builder.get_object('cover-stack')
self._cover_spinner = builder.get_object('cover-spinner')
self._cover_scroll = builder.get_object('cover-scroll') self._cover_scroll = builder.get_object('cover-scroll')
self._cover_box = builder.get_object('cover-box')
self._cover_image = builder.get_object('cover-image') self._cover_image = builder.get_object('cover-image')
# Songs
self._songs_scale = builder.get_object('cover-songs')
self._songs_scale.override_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0, 0, 0, 1))
# Album Infos # Album Infos
self._info_revealer = builder.get_object('cover-info-revealer')
self._info_box = builder.get_object('cover-info-box')
self._album_title_label = builder.get_object('cover-album') self._album_title_label = builder.get_object('cover-album')
self._album_date_label = builder.get_object('cover-date') self._album_date_label = builder.get_object('cover-date')
self._album_artist_label = builder.get_object('cover-artist') self._album_artist_label = builder.get_object('cover-artist')
# Songs
self._songs_scale = builder.get_object('cover-songs')
self._songs_scale.override_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0, 0, 0, 1))
def get(self): def get(self):
return self._panel return self._panel
def get_toolbar(self):
return self._toolbar
def get_signal_handlers(self): def get_signal_handlers(self):
return { return {
'on_cover-toolbar-fullscreen_clicked': self.on_fullscreen_clicked,
'on_cover-toolbar-tracklist_toggled': self.on_tracklist_togged,
'on_cover-box_button_press_event': self.on_cover_box_pressed, 'on_cover-box_button_press_event': self.on_cover_box_pressed,
'on_cover-scroll_size_allocate': self.on_cover_size_allocate, 'on_cover-scroll_size_allocate': self.on_cover_size_allocate,
'on_cover-songs_button_press_event': self.on_songs_start_change, 'on_cover-songs_button_press_event': self.on_songs_start_change,
@ -756,6 +816,16 @@ class CoverPanel(mcg.Base):
} }
def on_fullscreen_clicked(self, widget):
self._callback(self.SIGNAL_TOGGLE_FULLSCREEN)
def on_tracklist_togged(self, widget):
if widget.get_active():
size = [key for key, value in self._toolbar_tracklist_buttons.items() if value is widget][0]
self._change_tracklist_size(size)
def on_cover_box_pressed(self, widget, event): def on_cover_box_pressed(self, widget, event):
if event.type == Gdk.EventType._2BUTTON_PRESS: if event.type == Gdk.EventType._2BUTTON_PRESS:
self._callback(self.SIGNAL_TOGGLE_FULLSCREEN) self._callback(self.SIGNAL_TOGGLE_FULLSCREEN)
@ -785,31 +855,42 @@ class CoverPanel(mcg.Base):
self._callback(self.SIGNAL_SET_SONG, pos, time) self._callback(self.SIGNAL_SET_SONG, pos, time)
def set_tracklist_size(self, size):
if self._tracklist_size != size:
button = self._toolbar_tracklist_buttons[size]
if button and not button.get_active():
button.set_active(True)
self._change_tracklist_size(size, False)
def get_tracklist_size(self):
return self._tracklist_size
def set_album(self, album): def set_album(self, album):
self._album_title_label.set_markup( # Set labels
"<b><big>{}</big></b>".format( self._album_title_label.set_label(
GObject.markup_escape_text( GObject.markup_escape_text(
album.get_title() album.get_title()
)
) )
) )
self._album_date_label.set_markup( self._album_date_label.set_markup(
"<big>{}</big>".format( GObject.markup_escape_text(
GObject.markup_escape_text( ', '.join(album.get_dates())
', '.join(album.get_dates())
)
) )
) )
self._album_artist_label.set_markup( self._album_artist_label.set_markup(
"<big>{}</big>".format( GObject.markup_escape_text(
GObject.markup_escape_text( ', '.join(album.get_artists())
', '.join(album.get_artists())
)
) )
) )
self._set_cover(album)
# Set tracks
self._set_tracks(album) self._set_tracks(album)
# Load cover
threading.Thread(target=self._set_cover, args=(album,)).start()
def set_play(self, pos, time): def set_play(self, pos, time):
if self._timer is not None: if self._timer is not None:
@ -831,33 +912,39 @@ class CoverPanel(mcg.Base):
def set_fullscreen(self, active): def set_fullscreen(self, active):
if active: if active:
self._songs_scale.hide() self._change_tracklist_size(CoverPanel.TRACKLIST_SIZE_HIDDEN, False, False)
self._info_grid.hide()
self.child_set_property(self._current_box, 'padding', 0)
self._current_box.child_set_property(self._cover_scroll, 'padding', 0)
self._cover_box.override_background_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0, 0, 0, 1)) self._cover_box.override_background_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0, 0, 0, 1))
GObject.idle_add(self._resize_image)
# Hide curser
self._appwindow.get_window().set_cursor(
Gdk.Cursor.new_from_name(Gdk.Display.get_default(), "none")
)
else: else:
self._songs_scale.show() self._change_tracklist_size(self._tracklist_size, False, False)
self._info_grid.show()
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)) self._cover_box.override_background_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0, 0, 0, 0))
GObject.idle_add(self._resize_image) GObject.idle_add(self._resize_image)
# Reset cursor
self._appwindow.get_window().set_cursor(
Gdk.Cursor.new_from_name(Gdk.Display.get_default(), "default")
)
def _set_cover(self, album): def _set_cover(self, album):
if self._current_album is not None and album.get_hash() == self._current_album.get_hash(): self._cover_stack.set_visible_child(self._cover_spinner)
return self._cover_spinner.start()
self._current_album = album if self._current_album is None or album.get_hash() != self._current_album.get_hash():
url = album.get_cover() self._current_album = album
if url is not None and url is not "": url = album.get_cover()
# Load image and draw it if url is not None and url is not "":
self._cover_pixbuf = self._load_cover(url) # Load image and draw it
self._resize_image() self._cover_pixbuf = Application.load_cover(url)
else: self._resize_image()
# Reset image else:
self._cover_pixbuf = None # Reset image
self._cover_image.clear() self._cover_pixbuf = None
self._cover_image.clear()
self._cover_stack.set_visible_child(self._cover_scroll)
self._cover_spinner.stop()
def _set_tracks(self, album): def _set_tracks(self, album):
@ -879,6 +966,27 @@ class CoverPanel(mcg.Base):
self._songs_scale.add_mark(length, Gtk.PositionType.RIGHT, "{0[0]:02d}:{0[1]:02d} minutes".format(divmod(length, 60))) self._songs_scale.add_mark(length, Gtk.PositionType.RIGHT, "{0[0]:02d}:{0[1]:02d} minutes".format(divmod(length, 60)))
def _change_tracklist_size(self, size, notify=True, store=True):
# Set tracklist size
if size == CoverPanel.TRACKLIST_SIZE_LARGE:
self._panel.set_homogeneous(True)
self._info_revealer.set_reveal_child(True)
elif size == CoverPanel.TRACKLIST_SIZE_SMALL:
self._panel.set_homogeneous(False)
self._info_revealer.set_reveal_child(True)
else:
self._panel.set_homogeneous(False)
self._info_revealer.set_reveal_child(False)
# Store size
if store:
self._tracklist_size = size
# Notify signals
if notify:
self._callback(CoverPanel.SIGNAL_TRACKLIST_SIZE_CHANGED, size)
# Resize image
self._resize_image()
def _playing(self): def _playing(self):
value = self._songs_scale.get_value() + 1 value = self._songs_scale.get_value() + 1
self._songs_scale.set_value(value) self._songs_scale.set_value(value)
@ -886,25 +994,6 @@ class CoverPanel(mcg.Base):
return True return True
def _load_cover(self, url):
if url.startswith('/'):
try:
return GdkPixbuf.Pixbuf.new_from_file(url)
except Exception as e:
print(e)
return None
else:
try:
response = urllib.request.urlopen(url)
loader = GdkPixbuf.PixbufLoader()
loader.write(response.read())
loader.close()
return loader.get_pixbuf()
except Exception as e:
print(e)
return None
def _resize_image(self): def _resize_image(self):
"""Diese Methode skaliert das geladene Bild aus dem Pixelpuffer """Diese Methode skaliert das geladene Bild aus dem Pixelpuffer
auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse
@ -943,14 +1032,24 @@ class PlaylistPanel(mcg.Base):
self._host = None self._host = None
self._item_size = 150 self._item_size = 150
self._playlist = None self._playlist = None
self._playlist_albums = None
self._playlist_lock = threading.Lock() self._playlist_lock = threading.Lock()
self._playlist_stop = threading.Event() self._playlist_stop = threading.Event()
self._icon_theme = Gtk.IconTheme.get_default() self._icon_theme = Gtk.IconTheme.get_default()
self._standalone_pixbuf = None
self._selected_albums = []
# Widgets # Widgets
self._appwindow = builder.get_object('appwindow')
self._panel = builder.get_object('playlist-panel') self._panel = builder.get_object('playlist-panel')
self._toolbar = builder.get_object('playlist-toolbar')
self._headerbar = builder.get_object('headerbar')
self._headerbar_standalone = builder.get_object('headerbar-playlist-standalone')
self._panel_normal = builder.get_object('playlist-panel-normal')
self._panel_standalone = builder.get_object('playlist-panel-standalone')
# Clear button # Clear button
self._playlist_clear_button = builder.get_object('playlist-clear') self._playlist_clear_button = builder.get_object('playlist-toolbar-clear')
# Playlist Grid: Model # Playlist Grid: Model
self._playlist_grid_model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str) self._playlist_grid_model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str)
# Playlist Grid # Playlist Grid
@ -960,17 +1059,63 @@ class PlaylistPanel(mcg.Base):
self._playlist_grid.set_text_column(-1) self._playlist_grid.set_text_column(-1)
self._playlist_grid.set_tooltip_column(1) self._playlist_grid.set_tooltip_column(1)
# Standalone labels
self._standalone_title = builder.get_object('headerbar-playlist-standalone-title')
self._standalone_artist = builder.get_object('headerbar-playlist-standalone-artist')
# Standalone Image
self._standalone_stack = builder.get_object('playlist-standalone-stack')
self._standalone_spinner = builder.get_object('playlist-standalone-spinner')
self._standalone_scroll = builder.get_object('playlist-standalone-scroll')
self._standalone_image = builder.get_object('playlist-standalone-image')
# Action bar
action_bar = builder.get_object('playlist-standalone-actionbar')
def get(self): def get(self):
return self._panel return self._panel
def get_toolbar(self):
return self._toolbar
def get_signal_handlers(self): def get_signal_handlers(self):
return { return {
'on_playlist-clear_clicked': self._callback_from_widget 'on_playlist-toolbar-clear_clicked': self._callback_from_widget,
'on_playlist-iconview_item_activated': self.on_playlist_grid_clicked,
'on_playlist-standalone-scroll_size_allocate': self.on_standalone_scroll_size_allocate,
'on_headerbar-playlist-standalone-close_clicked': self.on_standalone_close_clicked
} }
def on_playlist_grid_clicked(self, widget, path):
# Get selected album
iter = self._playlist_grid_model.get_iter(path)
hash = self._playlist_grid_model.get_value(iter, 2)
album = self._playlist_albums[hash]
self._selected_albums = [album]
# Set labels
self._standalone_title.set_text(album.get_title())
self._standalone_artist.set_text(", ".join(album.get_artists()))
# Show panel
self._panel.set_visible_child(self._panel_standalone)
self._appwindow.set_titlebar(self._headerbar_standalone)
# Load cover
threading.Thread(target=self._show_standalone_image, args=(album,)).start()
def on_standalone_scroll_size_allocate(self, widget, allocation):
self._resize_standalone_image()
def on_standalone_close_clicked(self, widget):
self._panel.set_visible_child(self._panel.get_children()[0])
self._appwindow.set_titlebar(self._headerbar)
def set_item_size(self, item_size): def set_item_size(self, item_size):
if self._item_size != item_size: if self._item_size != item_size:
self._item_size = item_size self._item_size = item_size
@ -995,9 +1140,13 @@ class PlaylistPanel(mcg.Base):
self._playlist_lock.acquire() self._playlist_lock.acquire()
self._playlist_stop.clear() self._playlist_stop.clear()
self._playlist = playlist self._playlist = playlist
self._playlist_albums = {}
for album in playlist:
self._playlist_albums[album.get_hash()] = album
self._playlist_grid.set_model(None) self._playlist_grid.set_model(None)
self._playlist_grid.freeze_child_notify() self._playlist_grid.freeze_child_notify()
self._playlist_grid_model.clear() self._playlist_grid_model.clear()
GObject.idle_add(self._playlist_grid.set_item_padding, size / 100)
cache = mcg.MCGCache(host, size) cache = mcg.MCGCache(host, size)
for album in playlist: for album in playlist:
@ -1036,6 +1185,48 @@ class PlaylistPanel(mcg.Base):
self.set_playlist(self._host, self._playlist) self.set_playlist(self._host, self._playlist)
def _show_standalone_image(self, album):
self._standalone_stack.set_visible_child(self._standalone_spinner)
self._standalone_spinner.start()
url = album.get_cover()
if url is not None and url is not "":
# Load image and draw it
self._standalone_pixbuf = Application.load_cover(url)
self._resize_standalone_image()
else:
# Reset image
self._standalone_image.clear()
self._standalone_stack.set_visible_child(self._standalone_scroll)
self._standalone_spinner.stop()
def _resize_standalone_image(self):
"""Diese Methode skaliert das geladene Bild aus dem Pixelpuffer
auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse
"""
pixbuf = self._standalone_pixbuf
size = self._standalone_scroll.get_allocation()
# Check pixelbuffer
if pixbuf is None:
return
# 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(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._standalone_image.set_allocation(self._standalone_scroll.get_allocation())
self._standalone_image.set_from_pixbuf(pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER))
self._standalone_image.show()
def _callback_from_widget(self, widget): def _callback_from_widget(self, widget):
if widget is self._playlist_clear_button: if widget is self._playlist_clear_button:
self._callback(PlaylistPanel.SIGNAL_CLEAR_PLAYLIST) self._callback(PlaylistPanel.SIGNAL_CLEAR_PLAYLIST)
@ -1065,32 +1256,34 @@ class LibraryPanel(mcg.Base):
self._library_lock = threading.Lock() self._library_lock = threading.Lock()
self._library_stop = threading.Event() self._library_stop = threading.Event()
self._icon_theme = Gtk.IconTheme.get_default() self._icon_theme = Gtk.IconTheme.get_default()
self._standalone_pixbuf = None
self._selected_albums = []
# Widgets # Widgets
self._appwindow = builder.get_object('appwindow')
self._panel = builder.get_object('library-panel') self._panel = builder.get_object('library-panel')
self._toolbar = builder.get_object('library-toolbar')
self._headerbar = builder.get_object('headerbar')
self._headerbar_standalone = builder.get_object('headerbar-library-standalone')
self._panel_normal = builder.get_object('library-panel-normal')
self._panel_standalone = builder.get_object('library-panel-standalone')
# Filter/search bar
self._filter_bar = builder.get_object('library-filter-bar')
self._filter_entry = builder.get_object('library-filter')
# Progress Bar # Progress Bar
self._progress_revealer = builder.get_object('library-progress-revealer') self._progress_revealer = builder.get_object('library-progress-revealer')
self._progress_bar = builder.get_object('library-progress') self._progress_bar = builder.get_object('library-progress')
# Toolbar # Toolbar menu
# Filter entry self._toolbar_search_bar = builder.get_object('library-toolbar-search')
self._filter_entry = builder.get_object('library-filter') self._toolbar_sort_buttons = {
# Grid scale mcg.MCGAlbum.SORT_BY_ARTIST: builder.get_object('library-toolbar-sort-artist'),
self._grid_scale = builder.get_object('library-grid-scale') mcg.MCGAlbum.SORT_BY_TITLE: builder.get_object('library-toolbar-sort-title'),
mcg.MCGAlbum.SORT_BY_YEAR: builder.get_object('library-toolbar-sort-year')
}
self._toolbar_sort_order_button = builder.get_object('library-toolbar-sort-order')
self._grid_scale = builder.get_object('library-toolbar-scale')
self._grid_scale.set_value(self._item_size) self._grid_scale.set_value(self._item_size)
# 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"])
library_sort_store.append([mcg.MCGAlbum.SORT_BY_YEAR, "sort by year"])
self._library_sort_combo = builder.get_object('library-sort')
self._library_sort_combo.set_model(library_sort_store)
renderer_text = Gtk.CellRendererText()
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._sort_order)
# Sort type
self._library_sort_type_button = builder.get_object('library-sort-order')
# Library Grid: Model # Library Grid: Model
self._library_grid_model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str) self._library_grid_model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str)
self._library_grid_model.set_sort_func(2, self.compare_albums, self._sort_order) self._library_grid_model.set_sort_func(2, self.compare_albums, self._sort_order)
@ -1103,39 +1296,47 @@ class LibraryPanel(mcg.Base):
self._library_grid.set_pixbuf_column(0) self._library_grid.set_pixbuf_column(0)
self._library_grid.set_text_column(-1) self._library_grid.set_text_column(-1)
self._library_grid.set_tooltip_column(1) self._library_grid.set_tooltip_column(1)
# Standalon labels
self._standalone_title = builder.get_object('headerbar-library-standalone-title')
self._standalone_artist = builder.get_object('headerbar-library-standalone-artist')
# Standalone Image
self._standalone_stack = builder.get_object('library-standalone-stack')
self._standalone_spinner = builder.get_object('library-standalone-spinner')
self._standalone_scroll = builder.get_object('library-standalone-scroll')
self._standalone_image = builder.get_object('library-standalone-image')
# Action bar
action_bar = builder.get_object('library-standalone-actionbar')
play_button = Gtk.Button('play')
play_button.connect('clicked', self.on_standalone_play_clicked)
action_bar.pack_end(play_button)
def get(self): def get(self):
return self._panel return self._panel
def get_toolbar(self):
return self._toolbar
def get_signal_handlers(self): def get_signal_handlers(self):
return { return {
'on_library-update_clicked': self.on_update_clicked, 'on_library-toolbar-search_toggled': self.on_search_toggled,
'on_library-grid-scale_change_value': self.on_grid_scale_change, 'on_library-toolbar-scale_change_value': self.on_grid_scale_change,
'on_library-grid-scale_button_release_event': self.on_grid_scale_changed, 'on_library-toolbar-scale_button_release_event': self.on_grid_scale_changed,
'on_library-sort_changed': self.on_library_sort_combo_changed, 'on_library-toolbar-update_clicked': self.on_update_clicked,
'on_library-sort-order_clicked': self.on_library_sort_type_button_activated, 'on_library-toolbar-sort-toggled': self.on_sort_toggled,
'on_library-toolbar-sort-order_toggled': self.on_sort_order_toggled,
'on_library-filter-bar_notify': self.on_filter_bar_notify,
'on_library-filter_search_changed': self.on_filter_entry_changed, 'on_library-filter_search_changed': self.on_filter_entry_changed,
'on_library-iconview_item_activated': self.on_library_grid_clicked 'on_library-iconview_item_activated': self.on_library_grid_clicked,
'on_library-standalone-scroll_size_allocate': self.on_standalone_scroll_size_allocate,
'on_headerbar-library-standalone-close_clicked': self.on_standalone_close_clicked
} }
def on_update_clicked(self, widget): def on_search_toggled(self, widget):
self._callback(self.SIGNAL_UPDATE) self._filter_bar.set_search_mode(widget.get_active())
def on_filter_visible(self, model, iter, data):
hash = model.get_value(iter, 2)
if not hash in self._albums.keys():
return
album = self._albums[hash]
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): def on_grid_scale_change(self, widget, scroll, value):
@ -1145,6 +1346,7 @@ class LibraryPanel(mcg.Base):
return return
self._item_size = size self._item_size = size
GObject.idle_add(self._set_widget_grid_size, self._library_grid, size, True) GObject.idle_add(self._set_widget_grid_size, self._library_grid, size, True)
GObject.idle_add(self._library_grid.set_item_padding, size / 100)
def on_grid_scale_changed(self, widget, event): def on_grid_scale_changed(self, widget, event):
@ -1156,29 +1358,75 @@ class LibraryPanel(mcg.Base):
self._redraw() self._redraw()
def on_library_sort_combo_changed(self, combo): def on_update_clicked(self, widget):
sort_order = combo.get_active_id() self._callback(self.SIGNAL_UPDATE)
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): def on_sort_toggled(self, widget):
if widget.get_active():
sort = [key for key, value in self._toolbar_sort_buttons.items() if value is widget][0]
self._change_sort(sort)
def on_sort_order_toggled(self, button):
if button.get_active(): if button.get_active():
sort_type = Gtk.SortType.DESCENDING sort_type = Gtk.SortType.DESCENDING
button.set_stock_id(Gtk.STOCK_SORT_DESCENDING)
else: else:
sort_type = Gtk.SortType.ASCENDING sort_type = Gtk.SortType.ASCENDING
button.set_stock_id(Gtk.STOCK_SORT_ASCENDING)
self._sort_type = sort_type self._sort_type = sort_type
self._library_grid_model.set_sort_column_id(2, sort_type) self._library_grid_model.set_sort_column_id(2, sort_type)
self._callback(LibraryPanel.SIGNAL_SORT_TYPE_CHANGED, sort_type) self._callback(LibraryPanel.SIGNAL_SORT_TYPE_CHANGED, sort_type)
def on_filter_bar_notify(self, widget, value):
if self._toolbar_search_bar.get_active() is not self._filter_bar.get_search_mode():
self._toolbar_search_bar.set_active(self._filter_bar.get_search_mode())
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_library_grid_clicked(self, widget, path): def on_library_grid_clicked(self, widget, path):
# Get selected album
path = self._library_grid_filter.convert_path_to_child_path(path) path = self._library_grid_filter.convert_path_to_child_path(path)
iter = self._library_grid_model.get_iter(path) iter = self._library_grid_model.get_iter(path)
self._callback(LibraryPanel.SIGNAL_PLAY, self._library_grid_model.get_value(iter, 2)) hash = self._library_grid_model.get_value(iter, 2)
album = self._albums[hash]
self._selected_albums = [album]
# Set labels
self._standalone_title.set_text(album.get_title())
self._standalone_artist.set_text(", ".join(album.get_artists()))
# Show panel
self._panel.set_visible_child(self._panel_standalone)
self._appwindow.set_titlebar(self._headerbar_standalone)
# Load cover
threading.Thread(target=self._show_standalone_image, args=(album,)).start()
def on_filter_visible(self, model, iter, data):
hash = model.get_value(iter, 2)
if not hash in self._albums.keys():
return
album = self._albums[hash]
return album.filter(self._filter_string)
def on_standalone_scroll_size_allocate(self, widget, allocation):
self._resize_standalone_image()
def on_standalone_play_clicked(self, widget):
self._callback(LibraryPanel.SIGNAL_PLAY, self._selected_albums[0].get_hash())
def on_standalone_close_clicked(self, widget):
self._panel.set_visible_child(self._panel.get_children()[0])
self._appwindow.set_titlebar(self._headerbar)
def set_item_size(self, item_size): def set_item_size(self, item_size):
@ -1192,11 +1440,12 @@ class LibraryPanel(mcg.Base):
return self._item_size return self._item_size
def set_sort_order(self, sort_order): def set_sort_order(self, sort):
if self._sort_order != sort_order: if self._sort_order != sort:
result = self._library_sort_combo.set_active_id(sort_order) button = self._toolbar_sort_buttons[sort]
if self._sort_order != sort_order: if button and not button.get_active():
self._sort_order = sort_order button.set_active(True)
self._sort_order = sort
self._library_grid_model.set_sort_func(2, self.compare_albums, self._sort_order) self._library_grid_model.set_sort_func(2, self.compare_albums, self._sort_order)
@ -1208,15 +1457,12 @@ class LibraryPanel(mcg.Base):
if self._sort_type != sort_type: if self._sort_type != sort_type:
if sort_type: if sort_type:
sort_type_gtk = Gtk.SortType.DESCENDING sort_type_gtk = Gtk.SortType.DESCENDING
stock_id = Gtk.STOCK_SORT_DESCENDING self._toolbar_sort_order_button.set_active(True)
self._library_sort_type_button.set_active(True)
else: else:
sort_type_gtk = Gtk.SortType.ASCENDING sort_type_gtk = Gtk.SortType.ASCENDING
self._library_sort_type_button.set_active(False) self._toolbar_sort_order_button.set_active(False)
stock_id = Gtk.STOCK_SORT_ASCENDING
if self._sort_type != sort_type_gtk: if self._sort_type != sort_type_gtk:
self._sort_type = sort_type_gtk self._sort_type = sort_type_gtk
self._library_sort_type_button.set_stock_id(stock_id)
self._library_grid_model.set_sort_column_id(2, sort_type) self._library_grid_model.set_sort_column_id(2, sort_type)
@ -1243,12 +1489,19 @@ class LibraryPanel(mcg.Base):
self._library_stop.set() self._library_stop.set()
def _change_sort(self, sort):
self._sort_order = sort
self._library_grid_model.set_sort_func(2, self.compare_albums, sort)
self._callback(LibraryPanel.SIGNAL_SORT_ORDER_CHANGED, sort)
def _set_albums(self, host, albums, size): def _set_albums(self, host, albums, size):
self._library_lock.acquire() self._library_lock.acquire()
self._library_stop.clear() self._library_stop.clear()
self._albums = albums self._albums = albums
self._progress_revealer.set_reveal_child(True) GObject.idle_add(self._progress_revealer.set_reveal_child, True)
GObject.idle_add(self._progress_bar.set_fraction, 0.0) GObject.idle_add(self._progress_bar.set_fraction, 0.0)
GObject.idle_add(self._library_grid.set_item_padding, size / 100)
self._library_grid.set_model(None) self._library_grid.set_model(None)
self._library_grid.freeze_child_notify() self._library_grid.freeze_child_notify()
self._library_grid_model.clear() self._library_grid_model.clear()
@ -1351,6 +1604,48 @@ class LibraryPanel(mcg.Base):
self.set_albums(self._host, self._albums) self.set_albums(self._host, self._albums)
def _show_standalone_image(self, album):
self._standalone_stack.set_visible_child(self._standalone_spinner)
self._standalone_spinner.start()
url = album.get_cover()
if url is not None and url is not "":
# Load image and draw it
self._standalone_pixbuf = Application.load_cover(url)
self._resize_standalone_image()
else:
# Reset image
self._standalone_image.clear()
self._standalone_stack.set_visible_child(self._standalone_scroll)
self._standalone_spinner.stop()
def _resize_standalone_image(self):
"""Diese Methode skaliert das geladene Bild aus dem Pixelpuffer
auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse
"""
pixbuf = self._standalone_pixbuf
size = self._standalone_scroll.get_allocation()
# Check pixelbuffer
if pixbuf is None:
return
# 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(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._standalone_image.set_allocation(self._standalone_scroll.get_allocation())
self._standalone_image.set_from_pixbuf(pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER))
self._standalone_image.show()
class StackSwitcher(mcg.Base): class StackSwitcher(mcg.Base):