diff --git a/gui/gtk.py b/gui/gtk.py index 8e99d33..7c1002f 100755 --- a/gui/gtk.py +++ b/gui/gtk.py @@ -6,14 +6,15 @@ __author__ = "coderkun" __email__ = "" __license__ = "GPL" -__version__ = "0.2" +__version__ = "0.3" __status__ = "Development" +import math import os import time -import urllib import threading +import urllib from gi.repository import Gtk, Gdk, GdkPixbuf, GObject @@ -24,6 +25,12 @@ import mcg class MCGGtk(Gtk.Window): TITLE = "MPDCoverGrid (Gtk)" + VIEW_COVER = 'cover' + VIEW_PLAYLIST = 'playlist' + VIEW_LIBRARY = 'library' + STYLE_CLASS_BG_TEXTURE = 'bg-texture' + STYLE_CLASS_NO_BG = 'no-bg' + def __init__(self): Gtk.Window.__init__(self, title=MCGGtk.TITLE) @@ -31,22 +38,50 @@ class MCGGtk(Gtk.Window): 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._main_box.pack_start(self._bar_box, False, False, 0) + self._view_box = Gtk.EventBox() + self._main_box.pack_end(self._view_box, True, True, 0) self._connection_panel = ConnectionPanel(self._config) - self._main_box.pack_end(self._connection_panel, True, True, 0) - self._cover_panel = CoverPanel(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) # 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) @@ -58,21 +93,16 @@ class MCGGtk(Gtk.Window): 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_UPDATE, self.on_toolbar_update) - self._toolbar.connect_signal(Toolbar.SIGNAL_SET_VOLUME, self.on_toolbar_set_volume) self._toolbar.connect_signal(Toolbar.SIGNAL_PLAYPAUSE, self.on_toolbar_playpause) - self._toolbar.connect_signal(Toolbar.SIGNAL_LIST_MODE, self.on_toolbar_list_mode) - self._toolbar.connect_signal(Toolbar.SIGNAL_CLEAR_PLAYLIST, self.on_toolbar_clear_playlist) - self._toolbar.connect_signal(Toolbar.SIGNAL_FILTER, self.on_toolbar_filter) - self._toolbar.connect_signal(Toolbar.SIGNAL_SORT, self.on_toolbar_sort) - self._toolbar.connect_signal(Toolbar.SIGNAL_SORT_TYPE, self.on_toolbar_sort_type) - self._toolbar.connect_signal(Toolbar.SIGNAL_GRID_SIZE_CHANGE, self.on_toolbar_grid_size_change) - self._toolbar.connect_signal(Toolbar.SIGNAL_GRID_SIZE_CHANGED, self.on_toolbar_grid_size_changed) + 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_ALBUMS_SET, self.on_albums_set) self._cover_panel.connect_signal(CoverPanel.SIGNAL_TOGGLE_FULLSCREEN, self.on_cover_panel_toggle_fullscreen) - self._cover_panel.connect_signal(CoverPanel.SIGNAL_PLAY, self.on_cover_panel_play) + 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) @@ -106,45 +136,24 @@ class MCGGtk(Gtk.Window): self._connect() - def on_toolbar_update(self): - self._mcg.update() - - - def on_toolbar_set_volume(self, volume): - self._mcg.set_volume(volume) - - def on_toolbar_playpause(self): self._mcg.playpause() - def on_toolbar_list_mode(self): - self._config.list_mode = self._toolbar.get_list_mode() - self._cover_panel.set_list_mode(self._toolbar.get_list_mode()) + 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_clear_playlist(self): - self._mcg.clear_playlist() - - - def on_toolbar_filter(self, filter_string): - self._cover_panel.filter(filter_string) - - - def on_toolbar_sort(self, sort_order): - self._cover_panel.set_sort_order(sort_order) - - - def on_toolbar_sort_type(self, sort_type): - self._cover_panel.set_sort_type(sort_type) - - - def on_toolbar_grid_size_change(self, size): - self._cover_panel.set_grid_size(size) - - - def on_toolbar_grid_size_changed(self, size): - self._cover_panel.redraw() + def on_toolbar_set_volume(self, volume): + self._mcg.set_volume(volume) # Infobar callbacks @@ -163,15 +172,23 @@ class MCGGtk(Gtk.Window): # Cover Panel callbacks - def on_albums_set(self): - GObject.idle_add(self._toolbar.set_sensitive, True) - - def on_cover_panel_toggle_fullscreen(self): self._toggle_fullscreen() - def on_cover_panel_play(self, album): + # 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) @@ -180,25 +197,26 @@ class MCGGtk(Gtk.Window): def on_mcg_connect(self, connected, error): if connected: GObject.idle_add(self._connect_connected) - GObject.idle_add(self._load_albums) - GObject.idle_add(self._load_playlist) - GObject.idle_add(self._mcg.get_status) + 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, volume, error): - # State - if state == 'play': - GObject.idle_add(self._toolbar.set_pause) - elif state == 'pause' or state == 'stop': - GObject.idle_add(self._toolbar.set_play) + def on_mcg_status(self, state, album, pos, time, volume, error): # Album if album: - GObject.idle_add(self._toolbar.set_album, album, pos); 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 @@ -209,11 +227,12 @@ class MCGGtk(Gtk.Window): def on_mcg_load_playlist(self, playlist, error): - self._cover_panel.set_playlist(self._connection_panel.get_host(), playlist) + self._playlist_panel.set_playlist(self._connection_panel.get_host(), playlist) def on_mcg_load_albums(self, albums, error): - self._cover_panel.set_albums(self._connection_panel.get_host(), albums) + self._albums = {} + self._library_panel.set_albums(self._connection_panel.get_host(), albums) def on_mcg_error(self, error): @@ -238,29 +257,25 @@ class MCGGtk(Gtk.Window): def _connect_connected(self): self._toolbar.connected() self._toolbar.set_sensitive(True) - self._connection_panel.set_sensitive(True) - self._main_box.remove(self._connection_panel) - self._main_box.pack_start(self._cover_panel, True, True, 0) - self._main_box.show_all() + 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._main_box.remove(self._main_box.get_children()[1]) - self._main_box.pack_end(self._connection_panel, True, True, 0) - self._main_box.show_all() + self._view_box.remove(self._view_box.get_children()[0]) + self._view_box.add(self._connection_panel) + self._view_box.show_all() - def _load_playlist(self): - self._mcg.load_playlist() - - - def _load_albums(self): - self._toolbar.set_sensitive(False) - self._mcg.load_albums() - def _save_size(self): if not self._maximized: self._config.window_width = self.get_allocation().width @@ -284,10 +299,10 @@ class MCGGtk(Gtk.Window): self._fullscreened = fullscreened_new if self._fullscreened: self._toolbar.hide() - self._cover_panel.set_fullscreen_mode(True); + self._cover_panel.set_fullscreen(True) else: self._toolbar.show() - self._cover_panel.set_fullscreen_mode(False); + self._cover_panel.set_fullscreen(False) def _show_error(self, message): @@ -302,20 +317,48 @@ class MCGGtk(Gtk.Window): self._bar_box.remove(self._infobar) + def load_thumbnail(cache, album, size): + cache_url = cache.create_filename(album) + pixbuf = None + + if os.path.isfile(cache_url): + try: + pixbuf = GdkPixbuf.Pixbuf.new_from_file(cache_url) + except Exception as e: + print(e) + else: + url = album.get_cover() + if url is not None: + if url.startswith('/'): + try: + pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(url, size, size) + except Exception as e: + print(e) + else: + 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 + + + class Toolbar(mcg.MCGBase, Gtk.Toolbar): SIGNAL_CONNECT = 'connect' - SIGNAL_UPDATE = 'update' - SIGNAL_SET_VOLUME = 'set-volume' + SIGNAL_VIEW = 'view' SIGNAL_PLAYPAUSE = 'playpause' - SIGNAL_LIST_MODE = 'mode' - SIGNAL_CLEAR_PLAYLIST = 'clear-playlist' - SIGNAL_FILTER = 'filter' - SIGNAL_SORT = 'sort' - SIGNAL_SORT_TYPE = 'sort-type' - SIGNAL_GRID_SIZE_CHANGE = 'grid-size-temp' - SIGNAL_GRID_SIZE_CHANGED = 'grid-size' + SIGNAL_SET_VOLUME = 'set-volume' def __init__(self, config): @@ -327,114 +370,71 @@ class Toolbar(mcg.MCGBase, Gtk.Toolbar): # Widgets # Connection - self._connection_button = Gtk.ToolButton(Gtk.STOCK_DISCONNECT) + self._connection_button = Gtk.ToggleToolButton.new_from_stock(Gtk.STOCK_CONNECT) self.add(self._connection_button) - # Playback + # Separator self.add(Gtk.SeparatorToolItem()) - self._update_button = Gtk.ToolButton(Gtk.STOCK_REFRESH) - self._update_button.set_sensitive(False) - self.add(self._update_button) - self._volume_button = Gtk.VolumeButton()#None, 0, 100, 1) - self._volume_button.set_sensitive(False) - tool_item = Gtk.ToolItem() - tool_item.add(self._volume_button) - self.add(tool_item) - self._playpause_button = Gtk.ToolButton(Gtk.STOCK_MEDIA_PLAY) + # Playback + self._playpause_button = Gtk.ToggleToolButton.new_from_stock(Gtk.STOCK_MEDIA_PAUSE) self._playpause_button.set_sensitive(False) self.add(self._playpause_button) - self._list_mode_button = Gtk.ToggleToolButton(Gtk.STOCK_PAGE_SETUP) - self._list_mode_button.set_sensitive(False) - self.add(self._list_mode_button) - self._clear_playlist_button = Gtk.ToolButton(Gtk.STOCK_CLEAR) - self._clear_playlist_button.set_sensitive(False) - self.add(self._clear_playlist_button) - # Info - self.add(Gtk.SeparatorToolItem()) - tool_item = Gtk.ToolItem() - info_box = Gtk.VBox() - self._info_title_label = Gtk.Label() - self._info_title_label.set_justify(Gtk.Justification.LEFT) - self._info_title_label.set_alignment(0, 0) - info_box.pack_start(self._info_title_label, False, False, 0) - self._info_artist_label = Gtk.Label() - self._info_artist_label.set_justify(Gtk.Justification.LEFT) - self._info_artist_label.set_alignment(0, 0) - info_box.pack_end(self._info_artist_label, True, False, 0) - tool_item.add(info_box) - self.add(tool_item) - # Library Settings + # Separator separator = Gtk.SeparatorToolItem() separator.set_draw(False) separator.set_expand(True) self.add(separator) - tool_item = Gtk.ToolItem() - self._filter_entry = Gtk.SearchEntry() - self._filter_entry.set_sensitive(False) - tool_item.add(self._filter_entry) - self.add(tool_item) - tool_item = Gtk.ToolItem() - self._grid_size_scale = Gtk.HScale() - self._grid_size_scale.set_range(100,600) - self._grid_size_scale.set_round_digits(0) - self._grid_size_scale.set_value(self._config.item_size) - self._grid_size_scale.set_size_request(100, -1) - self._grid_size_scale.set_draw_value(False) - self._grid_size_scale.set_sensitive(False) - tool_item.add(self._grid_size_scale) - self.add(tool_item) - # Library grid menu - self._library_grid_menu = Gtk.Menu() - self._library_grid_menu.show() - menu_item = Gtk.RadioMenuItem(label="sort by artist") - menu_item.set_active(self._config.library_sort_order == mcg.MCGAlbum.SORT_BY_ARTIST) - menu_item.connect('activate', self.on_library_grid_menu_sort, mcg.MCGAlbum.SORT_BY_ARTIST) - menu_item.show() - library_grid_menu_group_sort = menu_item - self._library_grid_menu.add(menu_item) - menu_item = Gtk.RadioMenuItem(label="by title", group=library_grid_menu_group_sort) - menu_item.set_active(self._config.library_sort_order == mcg.MCGAlbum.SORT_BY_TITLE) - menu_item.connect('activate', self.on_library_grid_menu_sort, mcg.MCGAlbum.SORT_BY_TITLE) - menu_item.show() - self._library_grid_menu.add(menu_item) - menu_item = Gtk.RadioMenuItem(label="by year", group=library_grid_menu_group_sort) - menu_item.set_active(self._config.library_sort_order == mcg.MCGAlbum.SORT_BY_YEAR) - menu_item.connect('activate', self.on_library_grid_menu_sort, mcg.MCGAlbum.SORT_BY_YEAR) - menu_item.show() - self._library_grid_menu.add(menu_item) - menu_item_separator = Gtk.SeparatorMenuItem() - menu_item_separator.show() - self._library_grid_menu.add(menu_item_separator) - menu_item = Gtk.CheckMenuItem("Descending") - menu_item.set_active(self._config.library_sort_type == Gtk.SortType.DESCENDING) - menu_item.connect('activate', self.on_library_grid_menu_descending) - menu_item.show() - self._library_grid_menu.add(menu_item) - self._menu_button = Gtk.MenuToolButton() - self._menu_button.set_menu(self._library_grid_menu) - self._menu_button.set_direction(Gtk.ArrowType.DOWN) - self._menu_button.set_halign(Gtk.Align.END) - self._menu_button.set_sensitive(False) - self.add(self._menu_button) - + # View Buttons + 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) # Properties self.get_style_context().add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR) - # Actions - self.set_list_mode(self._config.list_mode) - # Signals - self._connection_button.connect('clicked', self.callback_with_function, self.SIGNAL_CONNECT) - self._update_button.connect('clicked', self.callback_with_function, self.SIGNAL_UPDATE) + 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._playpause_button.connect('clicked', self.callback_with_function, self.SIGNAL_PLAYPAUSE) - self._list_mode_button.connect('clicked', self.callback_with_function, self.SIGNAL_LIST_MODE) - self._clear_playlist_button.connect('clicked', self.callback_with_function, self.SIGNAL_CLEAR_PLAYLIST) - self._filter_entry.connect('changed', self.callback_with_function, self.SIGNAL_FILTER, self._filter_entry.get_text) - self._grid_size_scale.connect('change-value', self.on_grid_size_change) - self._grid_size_scale.connect('button-release-event', self.on_grid_size_changed) + + + def on_set_view(self, button, view): + if button.get_active(): + self._callback(self.SIGNAL_VIEW, view) def on_volume_changed(self, widget, value): @@ -446,55 +446,32 @@ class Toolbar(mcg.MCGBase, Gtk.Toolbar): self._changing_volume = active - def on_grid_size_change(self, widget, scroll, value): - value = round(value) - range = self._grid_size_scale.get_adjustment() - if value < range.get_lower() or value > range.get_upper(): - return - self._callback(self.SIGNAL_GRID_SIZE_CHANGE, value) - - - def on_grid_size_changed(self, widget, event): - value = round(self._grid_size_scale.get_value()) - range = self._grid_size_scale.get_adjustment() - if value < range.get_lower() or value > range.get_upper(): - return - self._callback(self.SIGNAL_GRID_SIZE_CHANGED, value) - - - def on_library_grid_menu_sort(self, widget, sort_order): - self._callback(self.SIGNAL_SORT, sort_order) - - - def on_library_grid_menu_descending(self, widget): - if widget.get_active(): - self._callback(self.SIGNAL_SORT_TYPE, Gtk.SortType.DESCENDING) - else: - self._callback(self.SIGNAL_SORT_TYPE, Gtk.SortType.ASCENDING) - - def connected(self): self._connection_button.set_stock_id(Gtk.STOCK_CONNECT) - self._update_button.set_sensitive(True) - self._volume_button.set_sensitive(True) + with self._connection_button.handler_block(self._connection_button_handler): + self._connection_button.set_active(True) self._playpause_button.set_sensitive(True) - self._list_mode_button.set_sensitive(True) - self._clear_playlist_button.set_sensitive(True) - self._filter_entry.set_sensitive(True) - self._grid_size_scale.set_sensitive(True) - self._menu_button.set_sensitive(True) + self._view_box.set_sensitive(True) + self._volume_button.set_sensitive(True) def disconnected(self): self._connection_button.set_stock_id(Gtk.STOCK_DISCONNECT) - self._update_button.set_sensitive(False) - self._volume_button.set_sensitive(False) + self._connection_button.set_active(False) self._playpause_button.set_sensitive(False) - self._list_mode_button.set_sensitive(False) - self._clear_playlist_button.set_sensitive(False) - self._filter_entry.set_sensitive(False) - self._grid_size_scale.set_sensitive(False) - self._menu_button.set_sensitive(False) + self._view_box.set_sensitive(False) + self._volume_button.set_sensitive(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) + + + def set_pause(self): + self._playpause_button.set_stock_id(Gtk.STOCK_MEDIA_PAUSE) + self._playpause_button.set_active(False) def set_volume(self, volume): @@ -504,31 +481,7 @@ class Toolbar(mcg.MCGBase, Gtk.Toolbar): self._setting_volume = False - def set_play(self): - self._playpause_button.set_stock_id(Gtk.STOCK_MEDIA_PLAY) - - - def set_pause(self): - self._playpause_button.set_stock_id(Gtk.STOCK_MEDIA_PAUSE) - - - def set_list_mode(self, active): - self._list_mode_button.set_active(active) - - - def get_list_mode(self): - return self._list_mode_button.get_active() - - - def set_album(self, album, pos): - self._info_title_label.set_text("{} ({})".format(album.get_title(), album.get_date())) - self._info_artist_label.set_text(', '.join(album.get_artists())) - - - def callback_with_function(self, widget, signal, data_function=None): - data = [] - if data_function is not None: - data = {data_function()} + def _callback_from_widget(self, widget, signal, *data): self._callback(signal, *data) @@ -785,197 +738,106 @@ class ConnectionPanel(mcg.MCGBase, Gtk.Box): -class CoverPanel(mcg.MCGBase, Gtk.HPaned): - SIGNAL_ALBUMS_SET = 'albums-set' +class CoverPanel(mcg.MCGBase, Gtk.VBox): SIGNAL_TOGGLE_FULLSCREEN = 'toggle-fullscreen' - SIGNAL_PLAY = 'play' - MODE_GRID = 'grid' - MODE_LIST = 'list' - MODE_PROGRESS = 'progress' - MODE_FULLSCREEN = 'fullscreen' - def __init__(self, config): + def __init__(self): mcg.MCGBase.__init__(self) - Gtk.HPaned.__init__(self) - self._config = config - self._mode = None - self._cache = None + Gtk.VBox.__init__(self) self._current_album = None self._cover_pixbuf = None - self._host = None - self._albums = [] - self._playlist = [] - self._grid_pixbufs = {} - self._filter_string = "" - self._old_ranges = {} - self._playlist_lock = threading.Lock() - self._playlist_stop = threading.Event() - self._library_lock = threading.Lock() - self._library_stop = threading.Event() + self._timer = None # Widgets - self._current = Gtk.VPaned() + self._current_box = Gtk.Box(Gtk.Orientation.HORIZONTAL) + self._current_box.set_halign(Gtk.Align.FILL) + self._current_box.set_homogeneous(True) + self.pack_start(self._current_box, True, True, 10) # Cover self._cover_image = Gtk.Image() self._cover_box = Gtk.EventBox() - self._cover_box.override_background_color(Gtk.StateFlags.NORMAL, self.get_style_context().get_background_color(Gtk.StateFlags.NORMAL)) self._cover_box.add(self._cover_image) self._cover_scroll = Gtk.ScrolledWindow() - self._cover_scroll.add_with_viewport(self._cover_box) - # Playlist - self._playlist_scroll = Gtk.ScrolledWindow() - # Playlist: GridModel - self._playlist_grid_model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str, str) - self._playlist_grid_filter = self._playlist_grid_model.filter_new() - # Playlist: GridView - self._playlist_grid = Gtk.IconView(self._playlist_grid_filter) - self._playlist_grid.set_pixbuf_column(0) - self._playlist_grid.set_text_column(-1) - self._playlist_grid.set_tooltip_column(2) - self._playlist_grid.set_columns(-1) - self._playlist_grid.set_margin(0) - self._playlist_grid.set_spacing( 0) - self._playlist_grid.set_row_spacing(0) - self._playlist_grid.set_column_spacing(0) - self._playlist_grid.set_item_padding(0) - self._playlist_grid.set_reorderable(False) - self._playlist_grid.set_selection_mode(Gtk.SelectionMode.SINGLE) - self._playlist_grid.override_background_color(Gtk.StateFlags.NORMAL, self.get_style_context().get_background_color(Gtk.StateFlags.NORMAL)) - # Playlist: ListModel - self._playlist_list_model = Gtk.ListStore(str, str, str, str, str, str) - # Playlist: ListView - self._playlist_list = Gtk.TreeView(self._playlist_list_model) - renderer = Gtk.CellRendererText() - column_artist = Gtk.TreeViewColumn("Artist", renderer, text=0) - column_album = Gtk.TreeViewColumn("Album", renderer, text=1) - column_track = Gtk.TreeViewColumn("Track", renderer, text=2) - column_title = Gtk.TreeViewColumn("Title", renderer, text=3) - column_date = Gtk.TreeViewColumn("Year", renderer, text=4) - self._playlist_list.append_column(column_artist) - self._playlist_list.append_column(column_album) - self._playlist_list.append_column(column_track) - self._playlist_list.append_column(column_title) - self._playlist_list.append_column(column_date) - # Library - self._library_scroll = Gtk.ScrolledWindow() - # Library: GridModel - self._library_grid_model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str, str) - self._library_grid_model.set_sort_func(3, self.compare_albums, self._config.library_sort_order) - self._library_grid_model.set_sort_column_id(3, self._config.library_sort_type) - self._library_grid_filter = self._library_grid_model.filter_new() - self._library_grid_filter.set_visible_func(self.on_filter_visible) - # Library: GridView - self._library_grid = Gtk.IconView(self._library_grid_filter) - self._library_grid.set_pixbuf_column(0) - self._library_grid.set_text_column(-1) - self._library_grid.set_tooltip_column(2) - self._library_grid.set_columns(-1) - self._library_grid.set_margin(0) - 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(0) - self._library_grid.set_reorderable(False) - self._library_grid.set_selection_mode(Gtk.SelectionMode.SINGLE) - self._library_grid.override_background_color(Gtk.StateFlags.NORMAL, self.get_style_context().get_background_color(Gtk.StateFlags.NORMAL)) - # Library: ListModel - self._library_list_model = Gtk.ListStore(str, str, str, str, str, str) - self._library_list_filter = self._library_list_model.filter_new() - self._library_list_filter.set_visible_func(self.on_filter_visible) - # Library: ListView - self._library_list = Gtk.TreeView(self._library_list_filter) - renderer = Gtk.CellRendererText() - column_artist = Gtk.TreeViewColumn("Artist", renderer, text=0) - column_album = Gtk.TreeViewColumn("Album", renderer, text=1) - column_track = Gtk.TreeViewColumn("Track", renderer, text=2) - column_title = Gtk.TreeViewColumn("Title", renderer, text=3) - column_date = Gtk.TreeViewColumn("Year", renderer, text=4) - self._library_list.append_column(column_artist) - self._library_list.append_column(column_album) - self._library_list.append_column(column_track) - self._library_list.append_column(column_title) - self._library_list.append_column(column_date) - # Progress Bar - self._progress_bar = Gtk.ProgressBar() - # Layout - self.pack1(self._current, True, True) - self._current.pack1(self._cover_scroll, True, True) - self._current.pack2(self._playlist_scroll, False, False) - self.pack2(self._library_scroll, False, False) - - # Actions - self.set_list_mode(self._config.list_mode) - self.set_position(self._config.library_position) - self._current.set_position(self._config.playlist_position) + self._cover_scroll.add(self._cover_box) + self._current_box.pack_start(self._cover_scroll, True, True, 10) + # Songs + self._songs_scale = Gtk.VScale() + self._songs_scale.set_halign(Gtk.Align.START) + self._songs_scale.set_vexpand(True) + self._songs_scale.set_digits(0) + self._songs_scale.set_draw_value(False) + self._songs_scale.override_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0, 0, 0, 1)) + self._current_box.pack_end(self._songs_scale, True, True, 10) + # Album Infos + self._info_grid = Gtk.Grid() + self._info_grid.set_halign(Gtk.Align.CENTER) + self._info_grid.set_row_spacing(5) + self._album_title_label = Gtk.Label() + self._info_grid.add(self._album_title_label) + self._album_date_label = Gtk.Label() + self._info_grid.attach_next_to(self._album_date_label, self._album_title_label, Gtk.PositionType.BOTTOM, 1, 1) + self._album_artist_label = Gtk.Label() + self._info_grid.attach_next_to(self._album_artist_label, self._album_date_label, Gtk.PositionType.BOTTOM, 1, 1) + self.pack_end(self._info_grid, False, True, 10) # Signals - self.connect('size-allocate', self.on_size_allocate) - self._current.connect('size-allocate', self.on_size_allocate) - self._cover_scroll.connect('size-allocate', self.on_cover_size_allocate) self._cover_box.connect('button-press-event', self.on_cover_box_pressed) - self._library_grid.connect('item-activated', self.on_library_grid_clicked) + self._cover_scroll.connect('size-allocate', self.on_cover_size_allocate) - def on_size_allocate(self, widget, allocation): - if widget is self: - self._config.library_position = self.get_position() - elif widget is self._current: - self._config.playlist_position = self._current.get_position() - - - def on_cover_size_allocate(self, widget, allocation): - self._resize_image() - def on_cover_box_pressed(self, widget, event): if event.type == Gdk.EventType._2BUTTON_PRESS: self._callback(self.SIGNAL_TOGGLE_FULLSCREEN) - - 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, 3)) - - - def on_filter_visible(self, model, iter, data): - if model is self._library_grid_model: - hash = model.get_value(iter, 3) - elif model is self._library_list_model: - hash = model.get_value(iter, 5) - if not hash in self._albums.keys(): - return - album = self._albums[hash] - return album.filter(self._filter_string) - - - def set_list_mode(self, active): - mode = CoverPanel.MODE_GRID - if active: - mode = CoverPanel.MODE_LIST - self.set_mode(mode) - - - def set_fullscreen_mode(self, active): - mode = CoverPanel.MODE_FULLSCREEN - if not active: - mode = self._mode - self._set_mode(mode) - - - def set_mode(self, mode): - if mode != self._mode: - self._mode = mode - GObject.idle_add(self._set_mode, mode) + def on_cover_size_allocate(self, widget, allocation): + self._resize_image() def set_album(self, album): + self._album_title_label.set_markup("{}".format(album.get_title())) + self._album_date_label.set_markup("{}".format(album.get_date())) + self._album_artist_label.set_markup("{}".format(', '.join(album.get_artists()))) + self._set_cover(album) + self._set_tracks(album) + + + def set_play(self, pos, time): + if self._timer is not None: + GObject.source_remove(self._timer) + for index in range(0, pos): + time = time + self._current_album.get_tracks()[index].get_time() + self._songs_scale.set_value(time+1) + self._timer = GObject.timeout_add(1000, self._playing) + + + def set_pause(self): + if self._timer is not None: + GObject.source_remove(self._timer) + self._timer = None + + + def set_fullscreen(self, active): + if active: + self._songs_scale.hide() + 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)) + else: + self._songs_scale.show() + 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)) + + + def _set_cover(self, album): if self._current_album is not None and album.get_hash() == self._current_album.get_hash(): return self._current_album = album url = album.get_cover() - if url is not None and url is not "": # Load image and draw it self._cover_pixbuf = self._load_cover(url) @@ -986,6 +848,114 @@ class CoverPanel(mcg.MCGBase, Gtk.HPaned): self._cover_image.clear() + def _set_tracks(self, album): + self._songs_scale.clear_marks() + 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()) + length = length + track.get_time() + self._songs_scale.add_mark(length, Gtk.PositionType.RIGHT, "{0[0]:02d}:{0[1]:02d} minutes".format(divmod(length, 60))) + + + def _playing(self): + value = self._songs_scale.get_value() + 1 + self._songs_scale.set_value(value) + 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): + """Diese Methode skaliert das geladene Bild aus dem Pixelpuffer + auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse + """ + pixbuf = self._cover_pixbuf + size = self._cover_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(round(pixbuf.get_width()*ratio)) + height = int(round(pixbuf.get_height()*ratio)) + # Pixelpuffer auf Oberfläche zeichnen + self._cover_image.set_from_pixbuf(pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER)) + + + + +class PlaylistPanel(mcg.MCGBase, Gtk.VBox): + SIGNAL_CLEAR_PLAYLIST = 'clear-playlist' + + + def __init__(self, config): + mcg.MCGBase.__init__(self) + Gtk.VBox.__init__(self) + self._config = config + self._host = None + self._playlist = [] + 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._clear_playlist_button = Gtk.ToolButton(Gtk.STOCK_CLEAR) + self._playlist_toolbar.add(self._clear_playlist_button) + # Playlist Grid: Model + self._playlist_grid_model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str) + # Playlist Grid + self._playlist_grid = Gtk.IconView(self._playlist_grid_model) + self._playlist_grid.set_pixbuf_column(0) + self._playlist_grid.set_text_column(-1) + self._playlist_grid.set_tooltip_column(1) + self._playlist_grid.set_margin(0) + self._playlist_grid.set_spacing(0) + self._playlist_grid.set_row_spacing(0) + self._playlist_grid.set_column_spacing(0) + 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_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) + + # Signals + self._clear_playlist_button.connect('clicked' ,self._callback_from_widget, self.SIGNAL_CLEAR_PLAYLIST) + + def set_playlist(self, host, playlist): self._host = host self._playlist = playlist @@ -993,6 +963,227 @@ class CoverPanel(mcg.MCGBase, Gtk.HPaned): threading.Thread(target=self._set_playlist, args=(host, playlist, self._config.item_size,)).start() + def _set_playlist(self, host, playlist, size): + self._playlist_lock.acquire() + self._playlist_stop.clear() + 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) + except Exception as e: + print(e) + if pixbuf is None: + pixbuf = self._playlist_grid.render_icon_pixbuf(Gtk.STOCK_MISSING_IMAGE, Gtk.IconSize.DIALOG) + if pixbuf is not None: + self._playlist_grid_model.append([ + pixbuf, + GObject.markup_escape_text("\n".join([ + album.get_title(), + album.get_date(), + ', '.join(album.get_artists()) + ])), + album.get_hash() + ]) + + if self._playlist_stop.is_set(): + 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 _callback_from_widget(self, widget, signal, *data): + self._callback(signal, *data) + + + + +class LibraryPanel(mcg.MCGBase, Gtk.VBox): + SIGNAL_UPDATE = 'update' + SIGNAL_PLAY = 'play' + + + def __init__(self, config): + mcg.MCGBase.__init__(self) + Gtk.VBox.__init__(self) + self._config = config + self._host = None + self._albums = [] + self._filter_string = "" + self._grid_pixbufs = {} + self._old_ranges = {} + self._library_lock = threading.Lock() + self._library_stop = threading.Event() + + # Widgets + # 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._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._grid_scale = Gtk.HScale() + self._grid_scale.set_range(100,600) + self._grid_scale.set_round_digits(0) + self._grid_scale.set_value(self._config.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 + 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 = Gtk.ComboBox.new_with_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._config.library_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 + # 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_filter = self._library_grid_model.filter_new() + self._library_grid_filter.set_visible_func(self.on_filter_visible) + # Library Grid + self._library_grid = Gtk.IconView(self._library_grid_filter) +# self._library_grid.pack_end(text_renderer, False) +# self._library_grid.add_attribute(text_renderer, "markup", 0) + self._library_grid.set_pixbuf_column(0) + self._library_grid.set_text_column(-1) + self._library_grid.set_tooltip_column(1) + self._library_grid.set_margin(0) + 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_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) + + # 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._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._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 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_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 + GObject.idle_add(self._set_widget_grid_size, self._library_grid, size, True) + + + def on_grid_scale_changed(self, widget, event): + size = round(self._grid_scale.get_value()) + range = self._grid_scale.get_adjustment() + if size < range.get_lower() or size > range.get_upper(): + return + self._redraw() + + + def on_library_sort_combo_changed(self, combo): + sort_order = combo.get_active_id() + self._config.library_sort_order = sort_order + self._library_grid_model.set_sort_func(2, self.compare_albums, sort_order) + + + def on_library_sort_type_button_activated(self, button): + if button.get_active(): + sort_type = Gtk.SortType.DESCENDING + button.set_stock_id(Gtk.STOCK_SORT_DESCENDING) + else: + sort_type = Gtk.SortType.ASCENDING + button.set_stock_id(Gtk.STOCK_SORT_ASCENDING) + self._config.library_sort_type = sort_type + self._library_grid_model.set_sort_column_id(2, 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)) + + def set_albums(self, host, albums): self._host = host self._albums = albums @@ -1000,30 +1191,67 @@ class CoverPanel(mcg.MCGBase, Gtk.HPaned): threading.Thread(target=self._set_albums, args=(host, albums, self._config.item_size,)).start() - def filter(self, filter_string): - self._filter_string = filter_string - GObject.idle_add(self._library_grid_filter.refilter) - GObject.idle_add(self._library_list_filter.refilter) + def compare_albums(self, model, row1, row2, criterion): + hash1 = model.get_value(row1, 2) + hash2 = model.get_value(row2, 2) + + if hash1 == "" or hash2 == "": + return + return mcg.MCGAlbum.compare(self._albums[hash1], self._albums[hash2], criterion) - def set_sort_order(self, sort_order): - self._config.library_sort_order = sort_order - self._library_grid_model.set_sort_func(3, self.compare_albums, sort_order) + def _set_albums(self, host, albums, size): + self._library_lock.acquire() + self._library_stop.clear() + 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() + 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) + cache = mcg.MCGCache(host, size) + self._grid_pixbufs.clear() + for hash in albums.keys(): + album = albums[hash] + pixbuf = None + try: + pixbuf = MCGGtk.load_thumbnail(cache, album, size) + except Exception as e: + print(e) + if pixbuf is None: + pixbuf = self._library_grid.render_icon_pixbuf(Gtk.STOCK_MISSING_IMAGE, Gtk.IconSize.DIALOG) + if pixbuf is not None: + self._grid_pixbufs[album.get_hash()] = pixbuf + self._library_grid_model.append([ + pixbuf, + GObject.markup_escape_text("\n".join([ + album.get_title(), + album.get_date(), + ', '.join(album.get_artists()) + ])), + hash + ]) - def set_sort_type(self, sort_type): - self._config.library_sort_type = sort_type - self._library_grid_model.set_sort_column_id(3, sort_type) + i += 1 + GObject.idle_add(self._progress_bar.set_fraction, i/n) + if self._library_stop.is_set(): + self._library_lock.release() + return - - def set_grid_size(self, size): - self._config.item_width = size - GObject.idle_add(self._set_grid_size, size) - - - def _set_grid_size(self, size): - self._set_widget_grid_size(self._playlist_grid, size, False) - self._set_widget_grid_size(self._library_grid, size, True) + Gdk.threads_enter() + self._library_grid.set_model(self._library_grid_filter) + self._library_grid.thaw_child_notify() + Gdk.threads_leave() + self._library_lock.release() + self.remove(self._progress_bar) + self.pack_start(self._library_toolbar, False, True, 5) + self.show_all() def _set_widget_grid_size(self, grid_widget, size, vertical): @@ -1056,7 +1284,7 @@ class CoverPanel(mcg.MCGBase, Gtk.HPaned): for index in cur_range: iter = grid_filter.convert_iter_to_child_iter(grid_filter[index].iter) if index in vis_range: - hash = grid_model.get_value(iter, 3) + hash = grid_model.get_value(iter, 2) pixbuf = self._grid_pixbufs[hash] pixbuf = pixbuf.scale_simple(size, size, GdkPixbuf.InterpType.NEAREST) else: @@ -1065,265 +1293,15 @@ class CoverPanel(mcg.MCGBase, Gtk.HPaned): self._old_ranges[grid_widget_id] = vis_range grid_widget.set_item_width(size) - self._config.item_size = size + #self._config.item_size = size - def redraw(self): - threading.Thread(target=self._set_playlist_and_albums, args=(self._host, self._playlist, self._albums, self._config.item_size,)).start() + def _redraw(self): + threading.Thread(target=self._set_albums, args=(self._host, self._albums, self._config.item_size,)).start() - def compare_albums(self, model, row1, row2, criterion): - hash1 = model.get_value(row1, 3) - hash2 = model.get_value(row2, 3) - - if hash1 == "" or hash2 == "": - return - return mcg.MCGAlbum.compare(self._albums[hash1], self._albums[hash2], criterion) - - - def _set_mode(self, mode): - # Layout - if len(self.get_children()) > 1: - self.remove(self.get_children()[1]) - if len(self._current.get_children()) > 1: - self._current.remove(self._current.get_children()[1]) - if mode != CoverPanel.MODE_FULLSCREEN: - self._current.pack2(self._playlist_scroll, False, False) - self.pack2(self._library_scroll, False, False) - - # Scroll content - if self._playlist_scroll.get_child() is not None: - self._playlist_scroll.remove(self._playlist_scroll.get_child()) - if self._library_scroll.get_child() is not None: - self._library_scroll.remove(self._library_scroll.get_child()) - if mode == CoverPanel.MODE_GRID: - self._playlist_scroll.add(self._playlist_grid) - self._playlist_scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.NEVER) - self._library_scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) - self._library_scroll.add(self._library_grid) - elif mode == CoverPanel.MODE_LIST: - self._playlist_scroll.add(self._playlist_list) - self._playlist_scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) - self._library_scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) - self._library_scroll.add(self._library_list) - elif mode == CoverPanel.MODE_PROGRESS: - self._playlist_scroll.add(self._playlist_grid) - self._library_scroll.add_with_viewport(self._progress_bar) - elif mode == CoverPanel.MODE_FULLSCREEN: - self._library_scroll.hide() - - # Cover background - if mode == CoverPanel.MODE_FULLSCREEN: - self._cover_box.override_background_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0, 0, 0, 1)) - else: - self._cover_box.override_background_color(Gtk.StateFlags.NORMAL, self.get_style_context().get_background_color(Gtk.StateFlags.NORMAL)) - - self.show_all() - - - def _set_playlist_and_albums(self, host, playlist, albums, size): - self._set_playlist(host, playlist, size) - self._set_albums(host, albums, size) - - - def _set_playlist(self, host, playlist, size): - self._playlist_lock.acquire() - self._playlist_stop.clear() - Gdk.threads_enter() - self._playlist_grid.set_model(None) - self._playlist_list.set_model(None) - self._playlist_grid.freeze_child_notify() - self._playlist_list.freeze_child_notify() - self._playlist_grid_model.clear() - self._playlist_list_model.clear() - Gdk.threads_leave() - - cache = mcg.MCGCache(host, size) - for album in playlist: - for track in album.get_tracks(): - self._playlist_list_model.append([ - ', '.join(track.get_artists()), - album.get_title(), - track.get_track(), - track.get_title(), - album.get_date(), - album.get_hash() - ]) - pixbuf = None - if album.get_cover() is not None: - try: - pixbuf = self._load_thumbnail(cache, album, size) - except Exception as e: - print(e) - if pixbuf is None: - pixbuf = self._playlist_grid.render_icon_pixbuf(Gtk.STOCK_MISSING_IMAGE, Gtk.IconSize.DIALOG) - if pixbuf is not None: - self._playlist_grid_model.append([ - pixbuf, - album.get_title(), - GObject.markup_escape_text("\n".join([ - album.get_title(), - album.get_date(), - ', '.join(album.get_artists()) - ])), - album.get_hash() - ]) - - if self._playlist_stop.is_set(): - self._playlist_lock.release() - return - - Gdk.threads_enter() - self._playlist_grid.set_model(self._playlist_grid_filter) - self._playlist_list.set_model(self._playlist_list_model) - self._playlist_grid.thaw_child_notify() - self._playlist_list.thaw_child_notify() - self._playlist_grid.set_columns(len(playlist)) - Gdk.threads_leave() - self._playlist_lock.release() - - - def _set_albums(self, host, albums, size): - self._library_lock.acquire() - self._library_stop.clear() - Gdk.threads_enter() - self._library_grid.set_model(None) - self._library_list.set_model(None) - self._library_grid.freeze_child_notify() - self._library_list.freeze_child_notify() - self._library_grid_model.clear() - self._library_list_model.clear() - self._progress_bar.set_fraction(0.0) - self._set_mode(CoverPanel.MODE_PROGRESS) - Gdk.threads_leave() - - i = 0 - n = len(albums) - cache = mcg.MCGCache(host, size) - self._grid_pixbufs.clear() - for hash in albums.keys(): - album = albums[hash] - pixbuf = None - for track in album.get_tracks(): - self._library_list_model.append([ - ', '.join(track.get_artists()), - album.get_title(), - track.get_track(), - track.get_title(), - album.get_date(), - hash - ]) - try: - pixbuf = self._load_thumbnail(cache, album, size) - except Exception as e: - print(e) - if pixbuf is None: - pixbuf = self._library_grid.render_icon_pixbuf(Gtk.STOCK_MISSING_IMAGE, Gtk.IconSize.DIALOG) - if pixbuf is not None: - self._grid_pixbufs[album.get_hash()] = pixbuf - self._library_grid_model.append([ - pixbuf, - album.get_title(), - GObject.markup_escape_text("\n".join([ - album.get_title(), - album.get_date(), - ', '.join(album.get_artists()) - ])), - hash - ]) - - i += 1 - GObject.idle_add(self._progress_bar.set_fraction, i/n) - if self._library_stop.is_set(): - self._library_lock.release() - return - - Gdk.threads_enter() - self._library_grid.set_model(self._library_grid_filter) - self._library_list.set_model(self._library_list_filter) - self._library_grid.thaw_child_notify() - self._library_list.thaw_child_notify() - self._set_mode(self._mode) - Gdk.threads_leave() - self._library_lock.release() - self._callback(self.SIGNAL_ALBUMS_SET) - - - 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 _load_thumbnail(self, cache, album, size): - cache_url = cache.create_filename(album) - pixbuf = None - - if os.path.isfile(cache_url): - try: - pixbuf = GdkPixbuf.Pixbuf.new_from_file(cache_url) - except Exception as e: - print(e) - else: - url = album.get_cover() - if url is not None: - if url.startswith('/'): - try: - pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(url, size, size) - except Exception as e: - print(e) - else: - 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 - - - def _resize_image(self): - """Diese Methode skaliert das geladene Bild aus dem Pixelpuffer - auf die Größe des Fensters unter Beibehalt der Seitenverhältnisse - """ - pixbuf = self._cover_pixbuf - size = self._cover_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(round(pixbuf.get_width()*ratio)) - height = int(round(pixbuf.get_height()*ratio)) - # Pixelpuffer auf Oberfläche zeichnen - self._cover_image.set_from_pixbuf(pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.HYPER)) - + def _callback_from_widget(self, widget, signal, *data): + self._callback(signal, *data) @@ -1344,14 +1322,12 @@ class Configuration(mcg.MCGConfig): 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.list_mode = self.getboolean('gui', 'list-mode') - self.library_position = self.getint('gui', 'library-position') + 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 else: self.library_sort_type = Gtk.SortType.DESCENDING - self.playlist_position = self.getint('gui', 'playlist-position') self.save() @@ -1361,14 +1337,12 @@ class Configuration(mcg.MCGConfig): 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', 'list-mode', str(self.list_mode)) - self.set('gui', 'library-position', str(self.library_position)) + 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)) - self.set('gui', 'playlist-position', str(self.playlist_position)) super().save() @@ -1382,9 +1356,7 @@ class Configuration(mcg.MCGConfig): self.set('gui', 'window-height', str(600)) self.set('gui', 'window-maximized', str(False)) self.set('gui', 'item-size', str(100)) - self.set('gui', 'list-mode', str(False)) - self.set('gui', 'library-position', str(450)) + 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)) - self.set('gui', 'playlist-position', str(450)) diff --git a/gui/noise-texture.png b/gui/noise-texture.png new file mode 100644 index 0000000..6b70a2d Binary files /dev/null and b/gui/noise-texture.png differ diff --git a/mcgGtk.py b/mcgGtk.py index c663df4..c92cdff 100755 --- a/mcgGtk.py +++ b/mcgGtk.py @@ -6,7 +6,7 @@ __author__ = "coderkun" __email__ = "" __license__ = "GPL" -__version__ = "0.2" +__version__ = "0.3" __status__ = "Development"