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>
The TreeRow is used to display rows from a Gtk.TreeListModel. I adjust
the TreeRow "item" and "child" properties so they still access the
underlying item or child widget instead of the Gtk.TreeRow or
Gtk.TreeExpander.
I also give the TreeRow widget "n-children" and "have-children" properties
which are used to dynamically show or hide the expander arrow when there
aren't any children.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
It only contains a FilterEntry for filtering future playlists. The
application will save its size when resized.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This uses a combination of an Idle Queue, ReaddirThread, and tagger
Thread to scan a directory path and tag the audio files found within. I
do this by adding a scan() function to each Library object to begin
scanning (if not already running).
Libraries also have a stop() function to cancel any pending idle tasks
and stop any running threads. The Library table makes sure to stop each
Library object during shutdown so we don't leave any hanging threads.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This Thread uses the audio.tagger.tag_file() function to find the tags
for a specific file without hanging the UI. There may be cases where we
have an Artist MBID but not the matching Artist name. When this happens,
I do my best to first check the database and then query the musicbrainz
server.
I take some care to only connect to the database once, and to close the
connection when the thread exits.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This tool wraps around a mutagen.File to read tags and translate them
into our database playlists.
Implements: #41 ("Check for new or modified tags during startup")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This table allows us to work with Library playlists that are represented
by a filesystem path. The user can manually enable or disable library
paths to prevent their tracks from showing up in the Collection
playlist. Additionally, library paths have an online property to
determine if the library still exists in the filesystem to prevent us
from removing tracks due to a broken NFS mount or symlink.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Similar to the Artists tree structure. I create a filter on the Year
table for each Decade object and adjust filtering so a Decade remains
visible if one or more years match the query.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This table allows us to work with Decade playlists that can be created
or looked up by an individual year in that decade. I also add a few
custom functions to SQLite to make working with decades easier.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I also adjust how filtering Artists works so an Artist remains visible
if one of its Media matches the query.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
We create a filter on the Media table for each Album object, but
only match Media that have a name set. I also adjust filtering to
display Albums that have a matching Medium.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This table represents an individual medium in an album (such as a single
CD). Each medium has an associated album, number, type, and (optional)
title. This means we can have multiple media for a given album as long
as they each have a different number or type.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I use a sql link table to accomplish this so a single album can be added
to multiple album-artists. Additionally, I set up a view on Artists and
Albums to make filtering easier without needing to use a complicated
join every time.
Additionally, I use the Playlist.add_children() function to set up a
filter on the Album list model for each Artist's albums.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This table allows us to work with Album playlists that have a name,
album artist, release date, (optional) mbid, and (optional) cover.
Note that we can insert multiple albums with the same name as long as
their mbid, artist, or release date is different.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This table allows us to work with Artist playlists that have a name and
(optional) mbid. Note that we can insert multiple artists with the same
name into the database as long as they have different mbids.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This inherits from our base playlist Table class, and implements
functions for creating and renaming playlists. Additionally, the
Playlist object allows for setting a custom image to display as an icon
in the sidebar.
Finally, I add in a custom sqlite3 adapter and converter to support
pathlib.Path types in the database.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is an implementation of an emmental.db.table.Table that adds
support for creating Playlists, updating playlist properties, sorting
based on playlist name, and displaying playlists with their child
playlists in a tree structure.
Additionally, I create a playlist_properties table in the database to store
properties that all playlists have.
Implements: #17 ("Save currently selected playlist")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This object inherits from the table.Row base class. It adds in
properties for name, active state, and propertyid and a rename()
function for updating the Table sort order during a rename.
Additionally, child playlists can be enabled by calling add_children().
This will set up a Gtk.FilterListModel using the provide child Table and
Filter.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Tables use the idle queue to load their data or filter rows in the
background. Tables will create a new queue by default, but can accept a
pre-constructed queue through the queue= parameter.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is intended to be used as a base class for other task-specific
Queues, and runs each task under a single transaction. Queue
implementations should override the do_run_task() function for their
implementation-specific work.
The push_many() function can be used to efficiently add several tasks to
the Queue at the same time.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This class reads the tags in an audio file and parses them into a format
we can use later to build our database playlist objects.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This function creates a ReaddirThread object to read the directory in
a secondary thread. This will let us poll for results without hanging the
UI in the case where a music library is on very slow storage (such as
NFS).
The ReaddirThread will accumulate a list of files that can be polled
using the poll_result() function, which will return the files found since
the last call to poll_result().
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This function casefolds the input string and makes a series of
substitutions before splitting the string into a tuple of strings that
can be compared against.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This button is an ImageToggle configured to show a filled-in heart when
active and an outline when inactive. I added some icons from the Gnome
icon-library to represent the different states.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This button is inspired by the Gtk.ToggleButton button, except it
changes the displayed icon when active.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This displays the current track's album art with a fancy frame drawn
around it. Clicking the image opens a popover showing the artwork at
full size.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
And use it to track the existence of artwork for the current file. This
could either be a cover.jpg file in the same directory as the currently
playing track or embedded artwork found in the Gst.TagList.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
And wire it up to the Player through the Application.
Implements: #45 (Create a new NowPlaying widget)
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
And make sure we are able to watch for changes when tracks are loaded. I
also export it as mpris' mpris:length metadata field.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
And wire up signals between the Now Playing card and the player.
Implements: #45 (Create a new NowPlaying widget)
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Complete with signals so we'll know when the user wants us to do
something. I also clear the autopause property when the user manually
pauses the player. I use large versions of the play and pause icons from
the Gnome Icon Library for the buttons.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is a PopoverButton that has an autopause.Entry set as the child. I
also override the displayed icon to show the current autopause count.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This entry is inspired by the Gtk.SpinButton, but lets us set
placeholder text to display the current autopause value to the user.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is inspired by the Adw.SplitButton, except it allows for
configuring the secondary button so we can show the current autopause
count.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This Button is like a Gtk.Button, except it provides ways to set the
icon-size. I also default to large buttons, since that'll be a good
portion of the users.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
These functions are set up to take an unused argument list so they can
be connected to signals directly. I also add a 'playing' property to
track the current state of the playbin and a 'status' property to
translate 'playing' into something mpris understands.
Implements: #7 (Implement MPRIS2 Support)
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
And bind the Player tag properties to the Now Playing card.
Implements: #45 (Create a new NowPlaying widget)
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This label has properties for both Artist and Album Artist, and chooses
which to display based on the prefer-artist property and which tags have
been set.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This Label supports setting a prefix that is applied to the displayed
string and setting a font size in pixels.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I set these properties when the bus sends us tag messages, and wire them
up do the mpris2.Player object to notify dbus of their values.
These properties are cleared on both EOS and when a new file is started.
This is to account for the user changing the file mid-playback.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Either through the command line, mpris2, or the open button in the
header.
Implements: #7 (Add MPRIS2 Support)
Implements: #47 (Signal that the track has changed when it actually changes)
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
And wire it up to the Header and Mpris.Player so we can apply volume &
replaygain changes as they happen.
Implements: #42 ("Remove global audio.Player instance")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This begins to implement the MediaPlayer2.Player interface. The
properties and signals are there, and I expect to fully implement them
as Emmental development goes on.
Implements: #7 ("Add MPRIS2 Support")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This includes a set of Gtk.CheckButtons to choose what ReplayGain mode
to use, and a Gtk.Switch to enable or disable ReplayGain
Implements: #46 ("Create new Volume controls")
Implements: #48 ("Implement Intelligent ReplayGain")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
They live in a Gtk.MenuButton with a custom popover box that can have
replaygain options added to it. I also modify the Application to save
the volume when it is changed.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I can't use a Gtk.VolumeButton because I want to add ReplayGain controls
under the popover menu as well.
Implements: #46 ("Create new Volume controls")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This dialog is used to manually edit the settings in the database. I
bind the properties in such a way that changes are seen instantly.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The header currently contains just the title & subtitle information, but
will be expanded to add volume controls in the next patch.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The LabelRow is an implementation of the ListRow for the common case of
displaying text to the user. It has some convenience properties for
setting the xalign property and adding the "numeric" class to the Gtk.Label.
The LabelFactory creates LabelRows.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The ListRow class is intended to be used as a base class for displaying
individual Gtk.ListView rows. The implement some helpful functionality
to make it easier to bind list items to child widgets.
The Factory class is designed to create ListRow widgets.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I implement Integer, Float, and String entries that update their "value"
property based on the user-provided text when the Enter key is pressed.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This entry provides some helper functionality around a Gtk.SearchEntry
to make filtering lists easier.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This takes the input string, casefolds it, and then adds some extra glob
operators to it so we can do a case insensitive substring search by
default.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is a MenuButton that already has a popover attached and a property
for setting the popover child directly.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Bind the width and height properties to the settings so they are
restored on startup and bind the fullscreen property to mpris.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This window is set up with specific areas for our header, sidebar, now
playing info, and tracklist. It also implements a post_toast() function
so toast notifications can be displayed to the user.
Implements: #44 ("Create a new 3WayPane widget")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This creates a new class to dynamically create GObject Properties, save
them to the database, and make it easy to bind application properties to
specific settings properties.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is a Gtk.FilterListModel containing a store.SortedList to store
individual rows in sorted order. I also implemented some convenience
functions to make it easier to add, remove, look up, and filter rows.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This filter takes a set of primary keys for rows that should be visible
during filtering. Passing None as a value means that all rows are shown.
It also has functions for adding or removing individual rows from the
filter.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This will be shared between settings, playlists, and tracks so we have a
common interface for working with database rows.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This inherits from the base Connection manager and adds in reading our
sql script to set up the database. It will also eventually hold pointers
to table objects that we can access directly.
Finally, I add a db property to the Application instance. The db is
connected during the ::startup signal handler, and disconnected during
the ::shutdown signal handler.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is a wrapper around the sqlite3.Connection objct that adds some
nice functionality to make working with SQL easier.
I defined the following magic methods:
* __enter__() to manually begin a transaction
* __exit__() to commit or rollback a manual transaction
* __call__() to execute a SQL statement with either positional or
keyword arguments.
Additionally:
* I define a "CASEFOLD" function that can be used in queries
to lowercase unicode text when searching.
* I set foreign_keys = ON so foreign keys checking is always enabled
* I provide an executemany() function for running running the same
statement multiple times with different arguments.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is implemented as an alternative to the Gio.ListStore that uses a
Python list object to hold items.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
We can put all our icons into a single resource bundle that gets loaded
and only exists for our app.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I'm going to put all of the main source code into a single subdirectory
under emmental/ and separate out tests into a separate tests/ directory.
Additionally, I have switched over to pytest for running tests to give
me better output (with color!)
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>