And wire up the properties so we can save the user's current setting.
Implements: #50 ("Background Music Mode")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This widget will be added to the Volume popover and allow configuring a
lower volume for background listening while focusing on tasks.
Implements: #50 ("Background Music Mode")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This will be added to a ListBox with the volume controls. Expanding the
row will enable ReplayGain and give the user a menu to select ReplayGain
mode.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The plan is to convert the volume control panel into a Gtk.ListBox for a
more modern appearance.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is nice to have so users know what to expect from each button. I do
take some liberties, such as putting version numbers of our dependencies
as the tooltip for the Title widget. I also display the current volume
level in the volume button tooltip.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I split the function into two. Calling env_string() will return the
string we generate with the versions of the various modules we depend
on. Calling print_env() will print this string.
I need env_string() so we can set the environment as a tooltip in the
header.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Future proof. If we update the database schema, then we'll bump the
user_version field. If the user then tries to open the new database with
an old Emmental version then there could be a lot of issues. Let's
detect this and raise an error with a description of the problem.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
These are used to manually rearrange the Tracks in the Playlist. The
buttons are only marked sensitive if one item is selected.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The Add Tracks button is a popover button configured to display a list
of playlists that tracks could be added to. I take some extra care to
make sure we only display playlists that have their user-tracks property
set to True, and to hide the currently visible playlist from the list.
Additionally, I create a horizontal size group so the Add and Remove
buttons are the same size.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The button is placed inside a Gtk.Overlay, and is hidden by default. The
button will be shown when tracks are selected if the current Playlist
has its "user-tracks" property set to True.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This widget displays the number of visible tracks, number of selected
tracks, and runtime of the current playlist. I wire it up to these
properties from the trackview.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is a Popover Button containing a Gtk.ListView to display the Sort
Order Model for the currently visible Playlist.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This Widget is intended to be used as the child widget in a Gtk.ListView
to display and change the sort order of the current playlist. I use
arrow buttons from the icon library to represent sort order
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is a Gio.ListStore with some extra functions for enabling,
disabling, rearranging, and reversing sort fields. It also has a
sort-string property for getting the current sort order to save
to the database.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The SortField object is used to represent a single column in the
Tracklist that the user could sort by.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is an ImageToggle button that has been configured to cycle between
3 states corresponding to no looping, playlist looping, and track
looping.
I also update the Tracklist to look for changes in the visible Playlist
to update the Loop button.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The OSD will eventually contain buttons for modifying playlists, but for
now it just has functions and properties for mananging the current track
selection.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
And wire this up to not only the Now Playing "jump" signal, but also the
next track pickers so we scroll when tracks are changed.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The MediumName TrackRow is used to combine the album and medium names
into a single string. This means we won't need to have a separate medium
name column that is empty for most tracks.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The AlbumCover shows a cover.jpg image for a specific Album in a column.
I also need to do some special handling so generate a tooltip to show a
larger version of the image.
I try to cache the AlbumCover Texture to cut down on disk accesses,
since we'll usually end up loading the same image several times for each
track in an album.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is an InscriptionRow that binds a Track's Album's property to the
Gtk.Inscription. I use it to display album artist and release information
in columns.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
These are specially configured TrackRows that take a non-string Track
property and convert it into a string displayed in the Inscription. I
use them to add Length, Play Count, Last Started, Last Played, and
Filepath columns to the TrackView.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The TrackRow widget is used to bind Tracks to a generic Widget. The
InscriptionRow builds on this to create a Gtk.Inscription that can be
used in derived classes. Finally, the TrackString widget implements
binding a string Track property directly to the Inscription.
I use these widgets to create a Title and Artist column in the
TrackView.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The TrackView sets up a scrollable Gtk.ColumnView inside a nice looking
frame. It also creates a FilterListModel for filtering tracks in a
separate layer from the Playlist so we don't affect choosing the next
track.
Finally, I add the TrackView to the TrackList Card.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
For now it only has the Gtk.CenterBox child with an entry.Filter widget,
but this will be expanded on in future patches. I also take the chance
to bind the factory:visible-playlist property to the playlist displayed
in the tracklist.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
And connect to the Player EOS and about-to-finish signals so we can
select the next track when the current one finishes (or slightly before
for gapless playback).
Implements: #7 (Add MPRIS2 Support)
Implements: #48 (Implement Intelligent ReplayGain)
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
It takes an additional user= parameter to indicate if the user is asking
for the next track or if it is coming from the audio player (such as an
EOS / about-to-finish signal). This lets us handle the next_track() call
slightly differently for the user case.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
And also a can-go-previous property to set the correct sensitivity on
the "Previous" button and for notifying MPRIS.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
And add some special handling so a previous.Previous() playlist type is
created for the db_previous playlist.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
And add some special handling for setting the active playlist and
visible playlist to the same db playlist. I also add active-loop and
active-shuffle properties that are wired up to the MPRIS2 "Shuffle" and
"LoopStatus" player properties.
Implements: #7 (Add MPRIS2 Support)
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I either create or reuse an existing Playlist object when the
db-visible property changes to a new value.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This Playlist implements some special handling for the Previous
Playlist. This includes adding a previous_tracks() function and
properties to indicate if the Playlist has a previous or next track.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
We need to do some bookkeeping inside the Playlist before notifying the
Factory that a track has been requested.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This property is updated whenever the items in the Playlist change or if
the current-track property is changed. It can be used to know in advance
if calling next_track() can be expected to return a valid Track
instance.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
And add a "shuffle" property to the Playlist class. I use a TrackidSet
instance to keep track of which trackids have already been picked, and
to select a random trackid from the remaining options.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I add a "loop" property to the Playlist class that can be set to "None",
"Playlist", or "Track" (to match the MPRIS2 loop property). I can then
tune the behavior of next_track() based on how loop is configured.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This sets the current_trackid field on the database playlist object to
the trackid of the provided track.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The move_track_up() and move_track_down() functions are used to manually
arrange the tracks in a playlist.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This model builds on the TrackidModel to make it more Playlist and Track
focused instead of trackid centric.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The TrackidModel takes a TrackidSet and presents it as a Gio.ListModel
that maps trackids into Track objects. Tracks can be found by value
using the bisect() function, which sorts the trackids by number by
default (this can be changed by overriding the do_get_sort_key()
function).
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The playlist factory has properties for the currently selected,
currently active, and previous playlists. It will eventually create
Gio.ListModel instances representing the tracks in each of these playlists,
in sorted order.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Setting this to True will cause the Player to change state to PAUSED
during the next emission of the "file-loaded" signal.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I attach these to the Now Playing card so the "add to favorites" button
can be correctly marked as sensitive and enabled when a Track is
selected for playback.
Additionally, I look for a notification from the Track table to say it
has been loaded. This lets me set the Player to load up the current_track
if one is set.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is much faster than removing tracks from their playlists one at a
time. I also clear and reload the Tracks table to clear out pointers to
old Tracks.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This includes both creating new Tracks and updating existing Tracks when
their tags have changed. New tracks are added to playlists using
idle=True so Gtk can spread out UI updates for each playlist so we don't
slow things down too much for the user.
This patch also adds a library argument to the Tagger thread
get_result() function which we pass to the Tagger class to be used by
Tracks.creat(). I also add an mtime argument to the Tagger thread
tag_file() function to pass down to the audio.tagger layer so we can
skip updating tracks that have not changed since the last scan.
Implements: #41 (Check for new or modified tags during startup)
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I expand on the libraries_view to include additional playlist
properties, and configure the default sort order to sort by filepath.
I then set up the library_tracks_view to make it easy to select tracks
that belong to a specific Library.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I expand on the years_view to include additional playlist properties,
and configure the default sort order to stort by release date first.
I then set up the year_tracks_view to make it easy to select tracks that
belong to a specific year.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I expand on the decades_view to include additional playlist properties,
and configure the default sort order to sort by year first.
I then set up the decade_tracks_view to make it easy to select tracks
that belong to a specific decade.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I expand on the genres_view to include additional playlist properties,
and configure the default sort order to sort by artist, album, and track
number.
I then configure the Genres table to use the system_tracks table to
manage each genre's associated tracks and set up the genre_tracks_view
to make it easy for Tracks to find their Genres.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I expand on the media_view to include additional playlist properties,
and configure the default sort order to sort by track number.
I then set up the medium_tracks_view to make it easy to select tracks
that belong to a specific medium.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I expand on the albums_view to include additional playlist properties,
and configure the default sort order to sort by track numbers in an
intuitive way.
I then set up the album_tracks_view to make it easy to select tracks
that belong to a specific album.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I expand on the artists_view to include additional playlist properties,
and configure the default sort_order to sort albums in an intuitive way.
I then configure the Artists table to us the system_tracks table to
manage each artist's associated tracks and set up the artist_tracks_view
to make it easy for Tracks to find their Artists.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I need to do something slightly different for each Playlist.
* Collection: I load tracks from the collection_view, which filters
tracks to those where the library is enabled but not deleting.
* Favorite Tracks: I load tracks from the favorites_view, which filters
tracks based on the tracks.favorite and library.deleting column.
* Most Played Tracks: I load tracks with a playcount greater than the
average playcount of all tracks (rounded up to the nearest integer).
* New Tracks: I load tracks that have been added within the last week.
* Previous Tracks: I load tracks that have been played since startup
using the system_tracks table. I take care to clear these entries
in the table during startup.
* Queued Tracks: Load tracks from the user_tracks table.
* Unplayed Tracks: I load tracks with a playcount equal to 0 and remove
when they are played.
* User-Defined Playlists: Load tracks from the track_playlist_link
table.
Additionally, I implement move_track_up() and move_track_down() support
for user playlists and queued tracks.
Finally, I update the have-next-track property to take into account if
the Collection has tracks too.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I need to do something slightly different for each system Playlist:
* Collection: I disallow setting loop to "None".
* Favorite Tracks: I set the user-tracks property to "True"
* Most Played Tracks: I add playcount as the first sort field.
* Previous Tracks: I disallow changing loop, shuffle, and sort-order.
* Queued Tracks: I set the user-tracks and tracks-movable properties to "True"
User created playlists also set the user-tracks and tracks-movable
properties to "True". I also disable autodelete on the Table so
playlists aren't deleted unexpectedly.
New Tracks and Unplayed Tracks have no special properties set.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
* loop is based on the mpris loop property, and can be set to "None",
"Track", or "Playlist".
* shuffle is a boolean True / False value.
* sort-order saves a user-configured sort order for each playlist.
* current-trackid is an integer referring to the trackid of the currently
playing track.
* user-tracks: is a boolean representing if the user should be allowed
to manually add and remove tracks.
I also use the sort-order property to implement a get_track_order()
function to get the sort keys for tracks in a Playlist.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I implement add_track(), get_trackids(), and remove_track() functions that
either modify the 'system_playlist_tracks' table or call a virtual function
depending on the value of the 'system-tracks' property.
I also add the "autodelete" property. When set to True, Playlists will
be deleted when they have 0 tracks remaining.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Playlists use a tracks.TrackidSet to manage a set of trackids
representing the Tracks in this Playlist.
I have two functions for loading tracks: load_tracks() and
reload_tracks(). Calling load_tracks() checks if the tracks have been
loaded first before doing any work, but calling reload_tracks() will
force the Playlist to go to the database to load the latest tracks.
Finally, I add a have-next-track property to the main database
connection. This is set to True whenever the active playlist has one or
more tracks.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The TrackidSet is intened to be used by Playlists to keep track of which
Tracks have been added without much overhead.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This includes the favorite status, playcount, last played timestamp, and
last started timestamp. These values will be restored if a Track with
the same mbid is created at a later time.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This patch adds extra handling for changing the value of the
Track:favorite property, if the Track is the current track. This lets us
have the Now Playing card bind to the current-favorite property to
update the UI (and have the UI update the Track).
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Tracks now have start(), stop(), and restart() functions that can be
used by the application to update the laststarted, lastplayed,
playcount, active, and restarted properties.
The track Table implements their half of these functions in addition to
a mark_path_active() function so opening Emmental with a filepath can
update the current track before the database is loaded. The Table also
adjusts the necessary system playlists when tracks are marked as played.
Finally, the Table now has have-current-track and current-track
properties that can be wired up to the Now Playing card.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The Track Table does all the work for saving, loading, and managing
Track objects. I also create a SQLite View to link tracks to their
associated artists, albums, and mediums.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The Track object represents a single track in the Library along with all
their corresponding metadata.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I extract the artist, length, mbid, mtime, tracknumber, and title from
the tags to use when constructing Tracks.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I am going to use this to determine if a track has been played or not.
Gstreamer resets the clock when seeking, so I do some extra work to save
the play time just before seeking and add it back later.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This section shows a list of library path playlists. I also add an extra
widget that opens a Gtk.FileDialogso users can add music to their
collections.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This section shows a tree of Decade and Year playlists. I use the
year-alt icon from the gnome icon-library as the section header.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This section uses the default Row for displaying genre playlists. I use
the theater icon from the gnome icon-library as the section header.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I make sure to save the "show-all" property to the settings so it can be
preserved across sessions.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This section shows a tree of Artist and Album playlists. I use the
library-artists icon from the gnome icon-library as the section header,
and the music-artist / music-artist2 icons for the "show all" button to
indicate state..
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I created a section Group with this section as the only member for now, and
bind the "selected-playlist" property to the sidebar.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This section creates PlaylistRows for displaying user and system
playlists. We also hook into the database playlist table to provide a
way to create new playlists. I add several new icons from the
icon-library to use for the section header and playlists.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This group manages a list of sections so only one is active at a time.
Additionally, it adjusts the animation property of each section to match
the direction the header moves when activated.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This class combines a header with an initially hidden ListView that can
be configured to list our playlists. It also implements
"playlist-activated" and "playlist-selected" signals to signal user
interaction.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The TreeRow is a factory.TreeRow configured for binding playlists to a
child widget. Individual sections are expected to inherit from this to
set up their section-specific widgets and bind any extra properties.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is a preconfigured row for displaying library paths. It includes a
switch to enable & disable the path, buttons for removing and updating a
path, and a progress bar for displaying scan progress.
I use the "update-symbolic" and "stop-sign-large-symbolic" icons from
the gnome icon library for this widget.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This row is configured for showing user and system playlists. This means
we use a SettableIcon, and EditableTitle, and an extra button for
deleting playlists. I use the big-x-symbolic icon from the gnome icon
library for deleting playlists.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is a basic Row widget with an Icon as a prefix widget, and no
postfix widget. You can set the "year" property to set a year value to
the icon text.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is intended to be used as a base class for our playlist Row
widgets, and sets up some common variables needed by both.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This will be used to display different types of playlists in the
sidebar, such as artist or genre. It also has a revealer that shows its
child when the header is active.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This widget allows users to change the displayed image by selecting a
new one from the filesystem.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This widget sets the Title::subtitle property to a nicely formatted
string based on the number set to the 'count' property.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>