Added buttons places
This commit is contained in:
@@ -293,11 +293,14 @@ class LibremsonicApp(Gtk.Application):
|
||||
def on_stack_change(self, stack, child):
|
||||
self.update_window()
|
||||
|
||||
def on_song_clicked(self, win, song_id, song_queue):
|
||||
def on_song_clicked(self, win, song_id, song_queue, metadata):
|
||||
# Reset the play queue so that we don't ever revert back to the
|
||||
# previous one.
|
||||
old_play_queue = song_queue.copy()
|
||||
|
||||
if metadata.get('force_shuffle_state') is not None:
|
||||
self.state.shuffle_on = metadata['force_shuffle_state']
|
||||
|
||||
# If shuffle is enabled, then shuffle the playlist.
|
||||
if self.state.shuffle_on:
|
||||
song_queue.remove(song_id)
|
||||
@@ -449,7 +452,10 @@ class LibremsonicApp(Gtk.Application):
|
||||
self.player.reset()
|
||||
self.state.song_progress = 0
|
||||
|
||||
def on_song_download_complete(_):
|
||||
def on_song_download_complete(song_id):
|
||||
if self.state.current_song != song.id:
|
||||
return
|
||||
|
||||
# Switch to the local media if the player can hotswap (MPV can,
|
||||
# Chromecast cannot hotswap without lag).
|
||||
if self.player.can_hotswap_source:
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import gi
|
||||
import threading
|
||||
from typing import Optional, Union
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
@@ -8,7 +7,7 @@ from gi.repository import Gtk, GObject, GLib
|
||||
from libremsonic.state_manager import ApplicationState
|
||||
from libremsonic.cache_manager import CacheManager
|
||||
from libremsonic.ui import util
|
||||
from libremsonic.ui.common import AlbumWithSongs, CoverArtGrid
|
||||
from libremsonic.ui.common import AlbumWithSongs, IconButton, CoverArtGrid
|
||||
|
||||
from libremsonic.server.api_objects import Child, AlbumWithSongsID3
|
||||
|
||||
@@ -20,7 +19,7 @@ class AlbumsPanel(Gtk.Box):
|
||||
'song-clicked': (
|
||||
GObject.SignalFlags.RUN_FIRST,
|
||||
GObject.TYPE_NONE,
|
||||
(str, object),
|
||||
(str, object, object),
|
||||
),
|
||||
}
|
||||
|
||||
@@ -84,7 +83,7 @@ class AlbumsPanel(Gtk.Box):
|
||||
self.to_year_entry.connect('changed', self.on_year_changed)
|
||||
actionbar.pack_start(self.to_year_entry)
|
||||
|
||||
refresh = util.button_with_icon('view-refresh')
|
||||
refresh = IconButton('view-refresh')
|
||||
refresh.connect('clicked', lambda *a: self.update(force=True))
|
||||
actionbar.pack_end(refresh)
|
||||
|
||||
@@ -94,7 +93,8 @@ class AlbumsPanel(Gtk.Box):
|
||||
self.grid = AlbumsGrid()
|
||||
self.grid.connect(
|
||||
'song-clicked',
|
||||
lambda _, song, queue: self.emit('song-clicked', song, queue),
|
||||
lambda _, song, queue, metadata: self.emit('song-clicked', song,
|
||||
queue, metadata),
|
||||
)
|
||||
scrolled_window.add(self.grid)
|
||||
self.add(scrolled_window)
|
||||
|
@@ -4,6 +4,16 @@
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#icon-button-box image {
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
#icon-button-box label {
|
||||
margin-left: 5px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
/* ********** Playlist ********** */
|
||||
#playlist-list-listbox row {
|
||||
margin: 0;
|
||||
@@ -58,6 +68,10 @@
|
||||
margin: -10px 0 0 10px;
|
||||
}
|
||||
|
||||
#playlist-play-shuffle-buttons {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* ********** Playback Controls ********** */
|
||||
#player-controls-album-artwork {
|
||||
min-height: 70px;
|
||||
@@ -73,7 +87,8 @@
|
||||
|
||||
/* Make the play icon look centered. */
|
||||
#player-controls-bar #play-button image {
|
||||
margin-left: 1px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
|
@@ -8,7 +8,7 @@ from gi.repository import Gtk, GObject, Pango
|
||||
from libremsonic.state_manager import ApplicationState
|
||||
from libremsonic.cache_manager import CacheManager
|
||||
from libremsonic.ui import util
|
||||
from libremsonic.ui.common import AlbumWithSongs, SpinnerImage
|
||||
from libremsonic.ui.common import AlbumWithSongs, IconButton, SpinnerImage
|
||||
|
||||
from libremsonic.server.api_objects import (
|
||||
AlbumID3,
|
||||
@@ -25,7 +25,7 @@ class ArtistsPanel(Gtk.Paned):
|
||||
'song-clicked': (
|
||||
GObject.SignalFlags.RUN_FIRST,
|
||||
GObject.TYPE_NONE,
|
||||
(str, object),
|
||||
(str, object, object),
|
||||
),
|
||||
}
|
||||
artist_id: Optional[str] = None
|
||||
@@ -45,7 +45,8 @@ class ArtistsPanel(Gtk.Paned):
|
||||
self.artist_detail_panel = ArtistDetailPanel()
|
||||
self.artist_detail_panel.connect(
|
||||
'song-clicked',
|
||||
lambda _, song, queue: self.emit('song-clicked', song, queue),
|
||||
lambda _, song, queue, metadata: self.emit('song-clicked', song,
|
||||
queue, metadata),
|
||||
)
|
||||
self.pack2(self.artist_detail_panel, True, False)
|
||||
|
||||
@@ -75,7 +76,7 @@ class ArtistList(Gtk.Box):
|
||||
|
||||
list_actions = Gtk.ActionBar()
|
||||
|
||||
refresh = util.button_with_icon('view-refresh')
|
||||
refresh = IconButton('view-refresh')
|
||||
refresh.connect('clicked', lambda *a: self.update(force=True))
|
||||
list_actions.pack_end(refresh)
|
||||
|
||||
@@ -156,7 +157,7 @@ class ArtistDetailPanel(Gtk.Box):
|
||||
'song-clicked': (
|
||||
GObject.SignalFlags.RUN_FIRST,
|
||||
GObject.TYPE_NONE,
|
||||
(str, object),
|
||||
(str, object, object),
|
||||
),
|
||||
}
|
||||
|
||||
@@ -190,12 +191,12 @@ class ArtistDetailPanel(Gtk.Box):
|
||||
self.artist_action_buttons = Gtk.Box(
|
||||
orientation=Gtk.Orientation.HORIZONTAL)
|
||||
|
||||
view_refresh_button = util.button_with_icon('view-refresh-symbolic')
|
||||
view_refresh_button = IconButton('view-refresh-symbolic')
|
||||
view_refresh_button.connect('clicked', self.on_view_refresh_click)
|
||||
self.artist_action_buttons.pack_end(view_refresh_button, False, False,
|
||||
5)
|
||||
|
||||
download_all_btn = util.button_with_icon('folder-download-symbolic')
|
||||
download_all_btn = IconButton('folder-download-symbolic')
|
||||
download_all_btn.connect('clicked', self.on_download_all_click)
|
||||
self.artist_action_buttons.pack_end(download_all_btn, False, False, 5)
|
||||
|
||||
@@ -237,7 +238,8 @@ class ArtistDetailPanel(Gtk.Box):
|
||||
self.albums_list = AlbumsListWithSongs()
|
||||
self.albums_list.connect(
|
||||
'song-clicked',
|
||||
lambda _, song, queue: self.emit('song-clicked', song, queue),
|
||||
lambda _, song, queue, metadata: self.emit(
|
||||
'song-clicked', song, queue, metadata),
|
||||
)
|
||||
artist_info_box.pack_start(self.albums_list, True, True, 0)
|
||||
|
||||
@@ -361,7 +363,7 @@ class AlbumsListWithSongs(Gtk.Overlay):
|
||||
'song-clicked': (
|
||||
GObject.SignalFlags.RUN_FIRST,
|
||||
GObject.TYPE_NONE,
|
||||
(str, object),
|
||||
(str, object, object),
|
||||
),
|
||||
}
|
||||
|
||||
@@ -391,7 +393,8 @@ class AlbumsListWithSongs(Gtk.Overlay):
|
||||
album_with_songs = AlbumWithSongs(album, show_artist_name=False)
|
||||
album_with_songs.connect(
|
||||
'song-clicked',
|
||||
lambda _, song, queue: self.emit('song-clicked', song, queue),
|
||||
lambda _, song, queue, metadata: self.emit(
|
||||
'song-clicked', song, queue, metadata),
|
||||
)
|
||||
album_with_songs.connect('song-selected', self.on_song_selected)
|
||||
album_with_songs.show_all()
|
||||
|
@@ -1,6 +1,13 @@
|
||||
from .album_with_songs import AlbumWithSongs
|
||||
from .cover_art_grid import CoverArtGrid
|
||||
from .edit_form_dialog import EditFormDialog
|
||||
from .icon_button import IconButton
|
||||
from .spinner_image import SpinnerImage
|
||||
|
||||
__all__ = ('AlbumWithSongs', 'CoverArtGrid', 'EditFormDialog', 'SpinnerImage')
|
||||
__all__ = (
|
||||
'AlbumWithSongs',
|
||||
'CoverArtGrid',
|
||||
'EditFormDialog',
|
||||
'IconButton',
|
||||
'SpinnerImage',
|
||||
)
|
||||
|
@@ -1,4 +1,5 @@
|
||||
from typing import Union
|
||||
from random import randint
|
||||
|
||||
import gi
|
||||
|
||||
@@ -7,6 +8,7 @@ from gi.repository import Gtk, GObject, Pango, GLib
|
||||
|
||||
from libremsonic.cache_manager import CacheManager
|
||||
from libremsonic.ui import util
|
||||
from .icon_button import IconButton
|
||||
from .spinner_image import SpinnerImage
|
||||
|
||||
from libremsonic.server.api_objects import (
|
||||
@@ -26,7 +28,7 @@ class AlbumWithSongs(Gtk.Box):
|
||||
'song-clicked': (
|
||||
GObject.SignalFlags.RUN_FIRST,
|
||||
GObject.TYPE_NONE,
|
||||
(str, object),
|
||||
(str, object, object),
|
||||
),
|
||||
}
|
||||
|
||||
@@ -60,13 +62,39 @@ class AlbumWithSongs(Gtk.Box):
|
||||
lambda f: GLib.idle_add(cover_art_future_done, f))
|
||||
|
||||
album_details = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
album_details.add(
|
||||
album_title_and_buttons = Gtk.Box(
|
||||
orientation=Gtk.Orientation.HORIZONTAL)
|
||||
|
||||
album_title_and_buttons.add(
|
||||
Gtk.Label(
|
||||
label=album.get('name', album.get('title')),
|
||||
name='artist-album-list-album-name',
|
||||
halign=Gtk.Align.START,
|
||||
))
|
||||
|
||||
play_btn = IconButton('media-playback-start-symbolic')
|
||||
play_btn.connect('clicked', self.play_btn_clicked)
|
||||
album_title_and_buttons.pack_start(play_btn, False, False, 5)
|
||||
|
||||
shuffle_btn_clicked = IconButton('media-playlist-shuffle-symbolic')
|
||||
shuffle_btn_clicked.connect('clicked', self.shuffle_btn_clicked)
|
||||
album_title_and_buttons.pack_start(shuffle_btn_clicked, False, False,
|
||||
5)
|
||||
|
||||
play_next = IconButton('go-top-symbolic')
|
||||
play_next.connect('clicked', self.play_next_clicked)
|
||||
album_title_and_buttons.pack_start(play_next, False, False, 5)
|
||||
|
||||
add_to_queue = IconButton('go-jump-symbolic')
|
||||
add_to_queue.connect('clicked', self.add_to_queue_clicked)
|
||||
album_title_and_buttons.pack_start(add_to_queue, False, False, 5)
|
||||
|
||||
download_all_btn = IconButton('folder-download-symbolic')
|
||||
download_all_btn.connect('clicked', self.on_download_all_click)
|
||||
album_title_and_buttons.pack_end(download_all_btn, False, False, 5)
|
||||
|
||||
album_details.add(album_title_and_buttons)
|
||||
|
||||
stats = [
|
||||
album.artist if show_artist_name else None,
|
||||
album.year,
|
||||
@@ -144,6 +172,8 @@ class AlbumWithSongs(Gtk.Box):
|
||||
|
||||
self.update_album_songs(album.id)
|
||||
|
||||
# Event Handlers
|
||||
# =========================================================================
|
||||
def on_song_selection_change(self, event):
|
||||
if not self.album_songs.has_focus():
|
||||
self.emit('song-selected')
|
||||
@@ -152,7 +182,7 @@ class AlbumWithSongs(Gtk.Box):
|
||||
# The song ID is in the last column of the model.
|
||||
song_id = self.album_song_store[idx][-1]
|
||||
self.emit('song-clicked', song_id,
|
||||
[m[-1] for m in self.album_song_store])
|
||||
[m[-1] for m in self.album_song_store], {})
|
||||
|
||||
def on_song_button_press(self, tree, event):
|
||||
if event.button == 3: # Right click
|
||||
@@ -192,6 +222,42 @@ class AlbumWithSongs(Gtk.Box):
|
||||
if not allow_deselect:
|
||||
return True
|
||||
|
||||
def on_download_all_click(self, btn):
|
||||
CacheManager.batch_download_songs(
|
||||
[x[-1] for x in self.album_song_store],
|
||||
before_download=self.update,
|
||||
on_song_download_complete=lambda x: self.update(),
|
||||
)
|
||||
|
||||
def play_btn_clicked(self, btn):
|
||||
song_ids = [x[-1] for x in self.album_song_store]
|
||||
self.emit(
|
||||
'song-clicked',
|
||||
song_ids[0],
|
||||
song_ids,
|
||||
{'force_shuffle_state': False},
|
||||
)
|
||||
|
||||
def shuffle_btn_clicked(self, btn):
|
||||
rand_idx = randint(0, len(self.album_song_store))
|
||||
song_ids = [x[-1] for x in self.album_song_store]
|
||||
self.emit(
|
||||
'song-clicked',
|
||||
song_ids[rand_idx],
|
||||
song_ids,
|
||||
{'force_shuffle_state': True},
|
||||
)
|
||||
|
||||
def play_next_clicked(self, btn):
|
||||
# TODO
|
||||
print('play next')
|
||||
|
||||
def add_to_queue_clicked(self, btn):
|
||||
# TODO
|
||||
print('add to queue')
|
||||
|
||||
# Helper Methods
|
||||
# =========================================================================
|
||||
def deselect_all(self):
|
||||
self.album_songs.get_selection().unselect_all()
|
||||
|
||||
|
@@ -15,7 +15,7 @@ class CoverArtGrid(Gtk.ScrolledWindow):
|
||||
'song-clicked': (
|
||||
GObject.SignalFlags.RUN_FIRST,
|
||||
GObject.TYPE_NONE,
|
||||
(str, object),
|
||||
(str, object, object),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -281,7 +281,8 @@ class CoverArtGrid(Gtk.ScrolledWindow):
|
||||
detail_element = self.create_detail_element_from_model(model)
|
||||
detail_element.connect(
|
||||
'song-clicked',
|
||||
lambda _, song, queue: self.emit('song-clicked', song, queue),
|
||||
lambda _, song, queue, metadata: self.emit(
|
||||
'song-clicked', song, queue, metadata),
|
||||
)
|
||||
detail_element.connect('song-selected', lambda *a: None)
|
||||
|
||||
|
32
libremsonic/ui/common/icon_button.py
Normal file
32
libremsonic/ui/common/icon_button.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gio, Gtk
|
||||
|
||||
|
||||
class IconButton(Gtk.Button):
|
||||
def __init__(
|
||||
self,
|
||||
icon_name,
|
||||
relief=False,
|
||||
icon_size=Gtk.IconSize.BUTTON,
|
||||
label=None,
|
||||
):
|
||||
Gtk.Button.__init__(self)
|
||||
self.icon_size = icon_size
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
|
||||
name='icon-button-box')
|
||||
|
||||
self.image = Gtk.Image()
|
||||
self.image.set_from_icon_name(icon_name, self.icon_size)
|
||||
box.add(self.image)
|
||||
|
||||
if label is not None:
|
||||
box.add(Gtk.Label(label=label))
|
||||
|
||||
if not relief:
|
||||
self.props.relief = Gtk.ReliefStyle.NONE
|
||||
|
||||
self.add(box)
|
||||
|
||||
def set_icon(self, icon_name):
|
||||
self.image.set_from_icon_name(icon_name, self.icon_size)
|
@@ -12,8 +12,8 @@ from libremsonic.state_manager import ApplicationState
|
||||
class MainWindow(Gtk.ApplicationWindow):
|
||||
"""Defines the main window for LibremSonic."""
|
||||
__gsignals__ = {
|
||||
'song-clicked':
|
||||
(GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (str, object)),
|
||||
'song-clicked': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE,
|
||||
(str, object, object)),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -56,8 +56,8 @@ class MainWindow(Gtk.ApplicationWindow):
|
||||
|
||||
self.player_controls.update(state)
|
||||
|
||||
def on_song_clicked(self, panel, song, queue):
|
||||
self.emit('song-clicked', song, queue)
|
||||
def on_song_clicked(self, panel, song, queue, metadata):
|
||||
self.emit('song-clicked', song, queue, metadata)
|
||||
|
||||
def create_stack(self, **kwargs):
|
||||
stack = Gtk.Stack()
|
||||
|
@@ -7,7 +7,7 @@ from gi.repository import Gtk, Pango, GObject, Gio, GLib
|
||||
from libremsonic.cache_manager import CacheManager
|
||||
from libremsonic.state_manager import ApplicationState, RepeatType
|
||||
from libremsonic.ui import util
|
||||
from libremsonic.ui.common import SpinnerImage
|
||||
from libremsonic.ui.common import IconButton, SpinnerImage
|
||||
from libremsonic.ui.common.players import ChromecastPlayer
|
||||
|
||||
|
||||
@@ -41,8 +41,7 @@ class PlayerControls(Gtk.ActionBar):
|
||||
state.current_song.duration)
|
||||
|
||||
icon = 'pause' if state.playing else 'start'
|
||||
self.play_button.get_child().set_from_icon_name(
|
||||
f"media-playback-{icon}-symbolic", Gtk.IconSize.LARGE_TOOLBAR)
|
||||
self.play_button.set_icon(f"media-playback-{icon}-symbolic")
|
||||
|
||||
has_current_song = (hasattr(state, 'current_song')
|
||||
and state.current_song is not None)
|
||||
@@ -118,7 +117,8 @@ class PlayerControls(Gtk.ActionBar):
|
||||
else:
|
||||
song_label = str(play_queue_len) + ' ' + util.pluralize(
|
||||
'song', play_queue_len)
|
||||
self.popover_label.set_markup(f'<b>Play Queue:</b> {song_label}')
|
||||
self.popover_label.set_markup(
|
||||
f'<b>Play Queue:</b> {song_label}')
|
||||
|
||||
# Remove everything from the play queue.
|
||||
for c in self.play_queue_list.get_children():
|
||||
@@ -285,35 +285,32 @@ class PlayerControls(Gtk.ActionBar):
|
||||
buttons.pack_start(Gtk.Box(), True, True, 0)
|
||||
|
||||
# Repeat button
|
||||
self.repeat_button = util.button_with_icon('media-playlist-repeat')
|
||||
self.repeat_button = IconButton('media-playlist-repeat')
|
||||
self.repeat_button.set_action_name('app.repeat-press')
|
||||
buttons.pack_start(self.repeat_button, False, False, 5)
|
||||
|
||||
# Previous button
|
||||
self.prev_button = util.button_with_icon(
|
||||
'media-skip-backward-symbolic',
|
||||
icon_size=Gtk.IconSize.LARGE_TOOLBAR)
|
||||
self.prev_button = IconButton('media-skip-backward-symbolic',
|
||||
icon_size=Gtk.IconSize.LARGE_TOOLBAR)
|
||||
self.prev_button.set_action_name('app.prev-track')
|
||||
buttons.pack_start(self.prev_button, False, False, 5)
|
||||
|
||||
# Play button
|
||||
self.play_button = util.button_with_icon(
|
||||
'media-playback-start-symbolic',
|
||||
relief=True,
|
||||
icon_size=Gtk.IconSize.LARGE_TOOLBAR)
|
||||
self.play_button = IconButton('media-playback-start-symbolic',
|
||||
relief=True,
|
||||
icon_size=Gtk.IconSize.LARGE_TOOLBAR)
|
||||
self.play_button.set_name('play-button')
|
||||
self.play_button.set_action_name('app.play-pause')
|
||||
buttons.pack_start(self.play_button, False, False, 0)
|
||||
|
||||
# Next button
|
||||
self.next_button = util.button_with_icon(
|
||||
'media-skip-forward-symbolic',
|
||||
icon_size=Gtk.IconSize.LARGE_TOOLBAR)
|
||||
self.next_button = IconButton('media-skip-forward-symbolic',
|
||||
icon_size=Gtk.IconSize.LARGE_TOOLBAR)
|
||||
self.next_button.set_action_name('app.next-track')
|
||||
buttons.pack_start(self.next_button, False, False, 5)
|
||||
|
||||
# Shuffle button
|
||||
self.shuffle_button = util.button_with_icon('media-playlist-shuffle')
|
||||
self.shuffle_button = IconButton('media-playlist-shuffle')
|
||||
self.shuffle_button.set_action_name('app.shuffle-press')
|
||||
buttons.pack_start(self.shuffle_button, False, False, 5)
|
||||
|
||||
@@ -329,8 +326,8 @@ class PlayerControls(Gtk.ActionBar):
|
||||
|
||||
# Device button (for chromecast)
|
||||
# TODO need icon
|
||||
device_button = util.button_with_icon(
|
||||
'view-list-symbolic', icon_size=Gtk.IconSize.LARGE_TOOLBAR)
|
||||
device_button = IconButton('view-list-symbolic',
|
||||
icon_size=Gtk.IconSize.LARGE_TOOLBAR)
|
||||
device_button.connect('clicked', self.on_device_click)
|
||||
box.pack_start(device_button, False, True, 5)
|
||||
|
||||
@@ -347,7 +344,7 @@ class PlayerControls(Gtk.ActionBar):
|
||||
)
|
||||
device_popover_header.add(self.popover_label)
|
||||
|
||||
refresh_devices = util.button_with_icon('view-refresh')
|
||||
refresh_devices = IconButton('view-refresh')
|
||||
refresh_devices.connect('clicked', self.on_device_refresh_click)
|
||||
device_popover_header.pack_end(refresh_devices, False, False, 0)
|
||||
|
||||
@@ -376,8 +373,8 @@ class PlayerControls(Gtk.ActionBar):
|
||||
self.device_popover.add(device_popover_box)
|
||||
|
||||
# Play Queue button
|
||||
play_queue_button = util.button_with_icon(
|
||||
'view-list-symbolic', icon_size=Gtk.IconSize.LARGE_TOOLBAR)
|
||||
play_queue_button = IconButton('view-list-symbolic',
|
||||
icon_size=Gtk.IconSize.LARGE_TOOLBAR)
|
||||
play_queue_button.connect('clicked', self.on_play_queue_click)
|
||||
box.pack_start(play_queue_button, False, True, 5)
|
||||
|
||||
@@ -412,7 +409,7 @@ class PlayerControls(Gtk.ActionBar):
|
||||
self.play_queue_popover.add(play_queue_popover_box)
|
||||
|
||||
# Volume mute toggle
|
||||
self.volume_mute_toggle = util.button_with_icon('audio-volume-high')
|
||||
self.volume_mute_toggle = IconButton('audio-volume-high')
|
||||
self.volume_mute_toggle.set_action_name('app.mute-toggle')
|
||||
box.pack_start(self.volume_mute_toggle, False, True, 0)
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
from functools import lru_cache
|
||||
from random import randint
|
||||
from typing import List, OrderedDict
|
||||
|
||||
from fuzzywuzzy import process
|
||||
@@ -11,7 +12,7 @@ from libremsonic.server.api_objects import PlaylistWithSongs
|
||||
from libremsonic.state_manager import ApplicationState
|
||||
from libremsonic.cache_manager import CacheManager
|
||||
from libremsonic.ui import util
|
||||
from libremsonic.ui.common import EditFormDialog, SpinnerImage
|
||||
from libremsonic.ui.common import EditFormDialog, IconButton, SpinnerImage
|
||||
|
||||
|
||||
class EditPlaylistDialog(EditFormDialog):
|
||||
@@ -38,8 +39,8 @@ class EditPlaylistDialog(EditFormDialog):
|
||||
class PlaylistsPanel(Gtk.Paned):
|
||||
"""Defines the playlists panel."""
|
||||
__gsignals__ = {
|
||||
'song-clicked':
|
||||
(GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (str, object)),
|
||||
'song-clicked': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE,
|
||||
(str, object, object)),
|
||||
}
|
||||
|
||||
playlist_map: OrderedDict[int, PlaylistWithSongs] = {}
|
||||
@@ -57,17 +58,15 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
|
||||
playlist_list_actions = Gtk.ActionBar()
|
||||
|
||||
self.new_playlist = Gtk.Button(relief=Gtk.ReliefStyle.NONE)
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
add_icon = Gio.ThemedIcon(name='list-add')
|
||||
image = Gtk.Image.new_from_gicon(add_icon, Gtk.IconSize.LARGE_TOOLBAR)
|
||||
box.add(image)
|
||||
box.add(Gtk.Label(label='New Playlist', margin=5))
|
||||
self.new_playlist.add(box)
|
||||
self.new_playlist = IconButton(
|
||||
relief=Gtk.ReliefStyle.NONE,
|
||||
icon_name='list-add',
|
||||
label='New Playlist',
|
||||
)
|
||||
self.new_playlist.connect('clicked', self.on_new_playlist_clicked)
|
||||
playlist_list_actions.pack_start(self.new_playlist)
|
||||
|
||||
list_refresh_button = util.button_with_icon('view-refresh')
|
||||
list_refresh_button = IconButton('view-refresh')
|
||||
list_refresh_button.connect('clicked', self.on_list_refresh_click)
|
||||
playlist_list_actions.pack_end(list_refresh_button)
|
||||
|
||||
@@ -150,18 +149,18 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
self.playlist_action_buttons = Gtk.Box(
|
||||
orientation=Gtk.Orientation.HORIZONTAL)
|
||||
|
||||
view_refresh_button = util.button_with_icon('view-refresh-symbolic')
|
||||
view_refresh_button = IconButton('view-refresh-symbolic')
|
||||
view_refresh_button.connect('clicked', self.on_view_refresh_click)
|
||||
self.playlist_action_buttons.pack_end(view_refresh_button, False,
|
||||
False, 5)
|
||||
|
||||
playlist_edit_button = util.button_with_icon('document-edit-symbolic')
|
||||
playlist_edit_button = IconButton('document-edit-symbolic')
|
||||
playlist_edit_button.connect('clicked',
|
||||
self.on_playlist_edit_button_click)
|
||||
self.playlist_action_buttons.pack_end(playlist_edit_button, False,
|
||||
False, 5)
|
||||
|
||||
download_all_button = util.button_with_icon('folder-download-symbolic')
|
||||
download_all_button = IconButton('folder-download-symbolic')
|
||||
download_all_button.connect(
|
||||
'clicked', self.on_playlist_list_download_all_button_click)
|
||||
self.playlist_action_buttons.pack_end(download_all_button, False,
|
||||
@@ -184,6 +183,29 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
self.playlist_stats = self.make_label(name='playlist-stats')
|
||||
playlist_details_box.add(self.playlist_stats)
|
||||
|
||||
self.play_shuffle_buttons = Gtk.Box(
|
||||
orientation=Gtk.Orientation.HORIZONTAL,
|
||||
name='playlist-play-shuffle-buttons',
|
||||
)
|
||||
|
||||
play_button = IconButton(
|
||||
'media-playback-start-symbolic',
|
||||
label='Play All',
|
||||
relief=True,
|
||||
)
|
||||
play_button.connect('clicked', self.on_play_all_clicked)
|
||||
self.play_shuffle_buttons.pack_start(play_button, False, False, 0)
|
||||
|
||||
shuffle_button = IconButton(
|
||||
'media-playlist-shuffle-symbolic',
|
||||
label='Shuffle All',
|
||||
relief=True,
|
||||
)
|
||||
shuffle_button.connect('clicked', self.on_shuffle_all_button)
|
||||
self.play_shuffle_buttons.pack_start(shuffle_button, False, False, 5)
|
||||
|
||||
playlist_details_box.add(self.play_shuffle_buttons)
|
||||
|
||||
self.big_info_panel.pack_start(playlist_details_box, True, True, 10)
|
||||
|
||||
playlist_box.pack_start(self.big_info_panel, False, True, 0)
|
||||
@@ -351,11 +373,29 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
self.playlist_list.get_selected_row().get_index()]
|
||||
self.update_playlist_view(playlist.id, force=True)
|
||||
|
||||
def on_play_all_clicked(self, btn):
|
||||
song_id = self.playlist_song_store[0][-1]
|
||||
self.emit('song-clicked', song_id,
|
||||
[m[-1] for m in self.playlist_song_store],
|
||||
{'force_shuffle_state': False})
|
||||
|
||||
def on_shuffle_all_button(self, btn):
|
||||
rand_idx = randint(0, len(self.playlist_song_store))
|
||||
self.emit(
|
||||
'song-clicked',
|
||||
self.playlist_song_store[rand_idx][-1],
|
||||
[m[-1] for m in self.playlist_song_store],
|
||||
{'force_shuffle_state': True},
|
||||
)
|
||||
|
||||
def on_song_activated(self, treeview, idx, column):
|
||||
# The song ID is in the last column of the model.
|
||||
song_id = self.playlist_song_store[idx][-1]
|
||||
self.emit('song-clicked', song_id,
|
||||
[m[-1] for m in self.playlist_song_store])
|
||||
self.emit(
|
||||
'song-clicked',
|
||||
self.playlist_song_store[idx][-1],
|
||||
[m[-1] for m in self.playlist_song_store],
|
||||
{},
|
||||
)
|
||||
|
||||
def on_song_button_press(self, tree, event):
|
||||
if event.button == 3: # Right click
|
||||
@@ -456,8 +496,10 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
playlist_id = self.playlist_map[selected.get_index()].id
|
||||
self.update_playlist_view(playlist_id)
|
||||
self.playlist_action_buttons.show()
|
||||
self.play_shuffle_buttons.show()
|
||||
else:
|
||||
self.playlist_action_buttons.hide()
|
||||
self.play_shuffle_buttons.hide()
|
||||
|
||||
self.playlist_songs.set_headers_visible(state.config.show_headers)
|
||||
|
||||
@@ -536,6 +578,7 @@ class PlaylistsPanel(Gtk.Paned):
|
||||
|
||||
self.update_playlist_song_list(playlist.id)
|
||||
self.playlist_action_buttons.show()
|
||||
self.play_shuffle_buttons.show()
|
||||
|
||||
@util.async_callback(
|
||||
lambda *a, **k: CacheManager.get_playlist(*a, **k),
|
||||
|
@@ -8,27 +8,11 @@ from deepdiff import DeepDiff
|
||||
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gio, Gtk, GLib, Gdk
|
||||
from gi.repository import Gtk, GLib, Gdk
|
||||
|
||||
from libremsonic.cache_manager import CacheManager, SongCacheStatus
|
||||
|
||||
|
||||
def button_with_icon(
|
||||
icon_name,
|
||||
relief=False,
|
||||
icon_size=Gtk.IconSize.BUTTON,
|
||||
) -> Gtk.Button:
|
||||
button = Gtk.Button()
|
||||
icon = Gio.ThemedIcon(name=icon_name)
|
||||
image = Gtk.Image.new_from_gicon(icon, icon_size)
|
||||
button.add(image)
|
||||
|
||||
if not relief:
|
||||
button.props.relief = Gtk.ReliefStyle.NONE
|
||||
|
||||
return button
|
||||
|
||||
|
||||
def format_song_duration(duration_secs) -> str:
|
||||
return f'{duration_secs // 60}:{duration_secs % 60:02}'
|
||||
|
||||
@@ -120,15 +104,19 @@ def show_song_popover(
|
||||
extra_menu_items: List[Tuple[Gtk.ModelButton, Any]] = [],
|
||||
):
|
||||
def on_play_next_click(button):
|
||||
# TODO
|
||||
print('play next click')
|
||||
|
||||
def on_add_to_queue_click(button):
|
||||
# TODO
|
||||
print('add to queue click')
|
||||
|
||||
def on_go_to_album_click(button):
|
||||
# TODO
|
||||
print('go to album click')
|
||||
|
||||
def on_go_to_artist_click(button):
|
||||
# TODO
|
||||
print('go to artist click')
|
||||
|
||||
def on_download_songs_click(button):
|
||||
|
Reference in New Issue
Block a user