The following shortcuts are implemented:
- Escape to unselect any selected tracks
- Delete to remove selected tracks from the current playlist
- <Control>/ to focus the "filter tracks" entry
- <Control>l to cycle the loop state of the current playlist
- <Control>s to toggle the shuffle state of the current playlist
- <Control>Up to move the selected track up one position
- <Control>Down to move the selected track down one position
I also change the volume up and down shortcuts to use the <Shift>
modifier. This matches how other Header shortcuts are triggered, and
frees up the non-shifted versions to use here.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This simplifies the code a lot by letting the TrackList directly call
OSD functions without going through the TrackView. I can also simplify
the TrackView to just contain our columnview.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I also convert my PlaylistRowWidget into a Gtk.ListBoxRow that has the
same functionality. This looks a little nicer, and lets us keep the same
style as the rest of the app.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I convert my SortRow widget into a Gtk.ListBoxRow that has the same
functionality. The main benefit is that it looks nicer in the
Gtk.Popover compared to the Gtk.ListView that I had been using.
I also connect to the listbox "row-activated" signal so I can handle
clicking a specific sort row in the list. Clicking a disabled sort row
will enable it, and clicking an enabled one will reverse the sort order.
I think this is what feels the most natural to the user.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I create a custom Gtk.ListBoxRow for displaying each individual column
name and visibility status. I then bind it to a listbox placed as the
popover button's popover child. This lets me set the 'boxed-list' style
class on the listbox to give it a nicer appearance, and clicking the
label will also toggle column visibility.
Implements: #57 ("Rework visible columns button")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
If the application changes the active or inactive tooltip text, then we
want to apply that to the button depending on what state it currently
has.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I was using this to set some custom styling for the active playlist and
track inside a ListView. I can accomplish the same thing by adding and
removing a style class from the ListRowWidget, and this doesn't break
Gtk internal stuff that changed in the 4.12 release.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The following shortcuts are implemented:
- <Control>? (<Shift><Control>/) to focus the "filter playlists" entry
- <Control><Alt>g to go to the current playlist
- <Shift><Control>p to open the Playlists section
- <Shift><Control>a to open the Artists section
- <Shift><Control>g to open the Genres section
- <Shift><Control>d to open the Decades section
- <Shift><Control>l to open the Libraries section
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I'm going to add a button to jump to the current playlist, and the first
step is to add an area to put it in the sidebar.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I change it to inherit from Gtk.Box, and append Sections as they are
added. I also add some stand-alone styling to set it apart as its own
widget.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This applies to both the Table and child Playlists models. I'm doing my
own idle handling already for searching, so we can rely on that instead
of needing Gtk to do it. The benefit to this is that we can select
playlists programmatically during startup, since we don't need to worry
about the Table not being fully loaded yet.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is used to notify the model that the rows have changed when it's
not automatically being detected. I first noticed this when attempting
to disable incremental filtering, due to the Artist list not getting
refiltered when new child Albums were added to the Artist.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I scheduled the filter query with first=True so it can run without a
long delay during scanning. Additionally, I cancel any pending filter
calls to discard stale arguments that otherwise would be processed.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
If we have a high priority task, then we want to push it to the front of
the idle queue so it runs as soon as possible.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
It's sometimes desireable to cancel a pending task and re-add it with
new parameters to cut out some unnecessary work.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I combine this with the table's Filter object to show playlists matching
the current search query.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I implement add_year(), remove_year(), and has_year() functions and make
sure we load the set of yearids during startup. Additionally, I have
Years add and remove themselves from Decades as they are created and
deleted.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I implement add_medium(), remove_medium(), and has_medium() functions
and make sure we load the set of mediumids during startup. Additionally,
I have Mediums add and remove themselves from Albums as they are created
and deleted.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I switch over to adding and removing Albums using the generic
add_child() and remove_child() functions. I also switch from using a
KeySet filter holding albumids to a Gtk.CustomFilter that calls
Artist.has_album() to check if an Album is in the set.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
We want to filter out Medium playlists with empty names in the sidebar,
and the easiest way to do that is through a custom filter attached to
the Media table.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
It defaults to a None pointer, but calling add_children() will set one
up with an empty set. I also implement functions for adding, removing,
and verifying child playlists.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is similar to a Gtk.FilterListModel, except we already know exactly
which rows are part of the model or not. So we can skip the entire
filtering step and show rows directly instead.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This includes implementing the __contains__() magic method, and adding
signals that are emitted when rows are added, removed, or directly set.
This will allow us to build a model around the rows represented by the
set.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I'm going to expand on this and use it for more than just filtering
Gtk.FilterListModels. Renaming it to something more generic is the first
step.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
It is much easier to pass a single boolean value instead of a Gtk
constant for specifying the icon size.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is used to set the tooltip text to fixed strings based on the
active status of the button. I also update the tooltip text on the
Favorite button in the Now Playing widget at the same time.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I add accelerators for play, pause, next track, previous track, setting
autopause, adding the current track to the favorites playlist, and
scrolling to the current track in the tracklist.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I create can-activate-* properties to indicate if a specific accelerator
can be activated. At the same time, I introduce functions intended to be
called by accelerators to activate each of our widgets.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
All I needed the frame for was to add rounded corners to the
Gtk.Picture, but this had some problems with the Frame expanding beyond
the edges of the picture in some cases. I can get the same effect by
adding the "card" css class to the Picture so hopefully this will work
better.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is a keybinding function that calls into the primary button
activate() function. At the same time, I add an "activate-primary"
signal that is emitted when the primary button is activated.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I add accelerators for opening files, changing the volume, toggling
background mode, and running the settings editor.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I made these functions part of the public interface with option
arguments so they can be used in accelerator callbacks for increasing or
decreasing the volume.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I use the current focus widget to set the "user-editing" property, which
can be used to enable or disable accelerator actions.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is inspried by the Gio.ActionEntry struct, which I can't figure out
how to get working in Python. I add on a few extra helpful features,
such as:
- Automatically creating a Gio.SimpleAction
- Tracking the desired accelerator keys
- Binding the "enabled" state to a specificed property at construction
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Otherwise we could lose the changes if the app crashes.
Fixes: #63 ("The database isn't being committed enough")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Otherwise we could have data loss if the application crashes.
Fixes: #63 ("The database isn't being committed enough")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Leaving the database in a dirty state could cause unintentional data
loss if the app crashes.
Fixes: #63 ("The database isn't being committed enough")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This lets us skip a handful of database calls if we detect that we're
setting the active playlist to the same value.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I do this unconditionally, so even if the track is already on the
playlist we set it as active. I think this is what the user would
expect, but I'm open to revising this approach later.
Fixes: #62 ("Queueing tracks doesn't activate the Queued Tracks playlist")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
And wire up the bg-enabled and bg-volume properties from the header to
the playbin properties with the same name.
Implements: #50 ("Background Music Mode")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This links together our ReplayGain filter with a volume element that is
set to the user configured background volume when background listening
mode is enabled, and 100% when background listening mode is disabled.
Implements: #50 ("Background Music Mode")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I also update the button tooltip to display the background listening
status along with the current volume level.
Implements: #50 ("Background Music Mode")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
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>