summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMateusz Łukasik <mati75@linuxmint.pl>2017-08-21 20:23:34 +0200
committerMateusz Łukasik <mati75@linuxmint.pl>2017-08-21 20:23:34 +0200
commitfe91e6f4733198be72be8dc036fb715b3ffa59b9 (patch)
tree36b115b683b859191fe87a75d852b75b8e1b7d84 /src
parente71d3c04c4dafb4c9c0cb4f8e46180e4f679ed0b (diff)
New upstream version 3.9
Diffstat (limited to 'src')
-rw-r--r--src/audacious/dbus-server.cc129
-rw-r--r--src/audacious/main.cc25
-rw-r--r--src/audtool/audtool.h2
-rw-r--r--src/audtool/handlers_playlist.c10
-rw-r--r--src/audtool/main.c2
-rw-r--r--src/dbus/aud-dbus.xml178
-rw-r--r--src/libaudcore/Makefile5
-rw-r--r--src/libaudcore/adder.cc173
-rw-r--r--src/libaudcore/art.cc172
-rw-r--r--src/libaudcore/audio.cc374
-rw-r--r--src/libaudcore/audio.h.in15
-rw-r--r--src/libaudcore/audstrings.cc46
-rw-r--r--src/libaudcore/audstrings.h6
-rw-r--r--src/libaudcore/charset.cc10
-rw-r--r--src/libaudcore/config.cc158
-rw-r--r--src/libaudcore/drct.cc62
-rw-r--r--src/libaudcore/drct.h8
-rw-r--r--src/libaudcore/equalizer-preset.cc13
-rw-r--r--src/libaudcore/hook.cc12
-rw-r--r--src/libaudcore/hook.h2
-rw-r--r--src/libaudcore/index.h8
-rw-r--r--src/libaudcore/interface.cc4
-rw-r--r--src/libaudcore/internal.h12
-rw-r--r--src/libaudcore/list.h12
-rw-r--r--src/libaudcore/mainloop.cc178
-rw-r--r--src/libaudcore/mainloop.h3
-rw-r--r--src/libaudcore/multihash.h108
-rw-r--r--src/libaudcore/objects.h51
-rw-r--r--src/libaudcore/output.cc33
-rw-r--r--src/libaudcore/playback.cc34
-rw-r--r--src/libaudcore/playlist-cache.cc16
-rw-r--r--src/libaudcore/playlist-data.cc1123
-rw-r--r--src/libaudcore/playlist-data.h172
-rw-r--r--src/libaudcore/playlist-files.cc22
-rw-r--r--src/libaudcore/playlist-internal.h33
-rw-r--r--src/libaudcore/playlist-utils.cc161
-rw-r--r--src/libaudcore/playlist.cc2258
-rw-r--r--src/libaudcore/playlist.h687
-rw-r--r--src/libaudcore/plugin-load.cc1
-rw-r--r--src/libaudcore/plugin-registry.cc22
-rw-r--r--src/libaudcore/plugins-internal.h1
-rw-r--r--src/libaudcore/probe.cc2
-rw-r--r--src/libaudcore/probe.h60
-rw-r--r--src/libaudcore/runtime.cc4
-rw-r--r--src/libaudcore/runtime.h12
-rw-r--r--src/libaudcore/strpool.cc224
-rw-r--r--src/libaudcore/templates.h19
-rw-r--r--src/libaudcore/tests/test.cc72
-rw-r--r--src/libaudgui/Makefile4
-rw-r--r--src/libaudgui/confirm.cc83
-rw-r--r--src/libaudgui/file-opener.cc6
-rw-r--r--src/libaudgui/infopopup.cc25
-rw-r--r--src/libaudgui/infowin.cc48
-rw-r--r--src/libaudgui/init.cc3
-rw-r--r--src/libaudgui/jump-to-track-cache.cc8
-rw-r--r--src/libaudgui/jump-to-track.cc20
-rw-r--r--src/libaudgui/libaudgui-gtk.h25
-rw-r--r--src/libaudgui/libaudgui.h13
-rw-r--r--src/libaudgui/list.cc12
-rw-r--r--src/libaudgui/list.h2
-rw-r--r--src/libaudgui/pixbufs.cc54
-rw-r--r--src/libaudgui/playlists.cc42
-rw-r--r--src/libaudgui/prefs-window.cc47
-rw-r--r--src/libaudgui/preset-browser.cc4
-rw-r--r--src/libaudgui/queue-manager.cc48
-rw-r--r--src/libaudgui/scaled-image.cc25
-rw-r--r--src/libaudgui/urilist.cc16
-rw-r--r--src/libaudgui/util.cc55
-rw-r--r--src/libaudqt/Makefile2
-rw-r--r--src/libaudqt/about.cc10
-rw-r--r--src/libaudqt/art.cc40
-rw-r--r--src/libaudqt/equalizer.cc16
-rw-r--r--src/libaudqt/info-widget.cc2
-rw-r--r--src/libaudqt/info-widget.h7
-rw-r--r--src/libaudqt/infowin.cc44
-rw-r--r--src/libaudqt/libaudqt-internal.h (renamed from src/libaudqt/log-inspector.h)30
-rw-r--r--src/libaudqt/libaudqt.h42
-rw-r--r--src/libaudqt/log-inspector.cc213
-rw-r--r--src/libaudqt/playlist-management.cc31
-rw-r--r--src/libaudqt/plugin-menu.cc37
-rw-r--r--src/libaudqt/prefs-builder.cc20
-rw-r--r--src/libaudqt/prefs-plugin.cc6
-rw-r--r--src/libaudqt/prefs-pluginlist-model.cc152
-rw-r--r--src/libaudqt/prefs-pluginlist-model.h32
-rw-r--r--src/libaudqt/prefs-widget.cc33
-rw-r--r--src/libaudqt/prefs-window.cc427
-rw-r--r--src/libaudqt/queue-manager.cc46
-rw-r--r--src/libaudqt/url-opener.cc8
-rw-r--r--src/libaudqt/util.cc38
-rw-r--r--src/libaudqt/volumebutton.cc14
-rw-r--r--src/libaudtag/id3/id3v24.cc33
91 files changed, 4649 insertions, 3908 deletions
diff --git a/src/audacious/dbus-server.cc b/src/audacious/dbus-server.cc
index f208a4c..ce0f1e8 100644
--- a/src/audacious/dbus-server.cc
+++ b/src/audacious/dbus-server.cc
@@ -38,10 +38,18 @@ typedef GDBusMethodInvocation Invoc;
#define FINISH2(name, ...) \
obj_audacious_complete_##name (obj, invoc, __VA_ARGS__)
-static int current_playlist ()
+static bool prefer_playing = true;
+
+static Playlist current_playlist ()
{
- int list = aud_playlist_get_playing ();
- return (list >= 0) ? list : aud_playlist_get_active ();
+ Playlist list;
+
+ if (prefer_playing)
+ list = Playlist::playing_playlist ();
+ if (list == Playlist ())
+ list = Playlist::active_playlist ();
+
+ return list;
}
#define CURRENT current_playlist ()
@@ -57,28 +65,28 @@ static Index<PlaylistAddItem> strv_to_index (const char * const * strv)
static gboolean do_add (Obj * obj, Invoc * invoc, const char * file)
{
- aud_playlist_entry_insert (CURRENT, -1, file, Tuple (), false);
+ CURRENT.insert_entry (-1, file, Tuple (), false);
FINISH (add);
return true;
}
static gboolean do_add_list (Obj * obj, Invoc * invoc, const char * const * filenames)
{
- aud_playlist_entry_insert_batch (CURRENT, -1, strv_to_index (filenames), false);
+ CURRENT.insert_items (-1, strv_to_index (filenames), false);
FINISH (add_list);
return true;
}
static gboolean do_add_url (Obj * obj, Invoc * invoc, const char * url)
{
- aud_playlist_entry_insert (CURRENT, -1, url, Tuple (), false);
+ CURRENT.insert_entry (-1, url, Tuple (), false);
FINISH (add_url);
return true;
}
static gboolean do_advance (Obj * obj, Invoc * invoc)
{
- aud_drct_pl_next ();
+ CURRENT.next_song (aud_get_bool (nullptr, "repeat"));
FINISH (advance);
return true;
}
@@ -97,22 +105,21 @@ static gboolean do_balance (Obj * obj, Invoc * invoc)
static gboolean do_clear (Obj * obj, Invoc * invoc)
{
- int playlist = CURRENT;
- aud_playlist_entry_delete (playlist, 0, aud_playlist_entry_count (playlist));
+ CURRENT.remove_all_entries ();
FINISH (clear);
return true;
}
static gboolean do_delete (Obj * obj, Invoc * invoc, unsigned pos)
{
- aud_playlist_entry_delete (CURRENT, pos, 1);
+ CURRENT.remove_entry (pos);
FINISH (delete);
return true;
}
static gboolean do_delete_active_playlist (Obj * obj, Invoc * invoc)
{
- aud_playlist_delete (CURRENT);
+ CURRENT.remove_playlist ();
FINISH (delete_active_playlist);
return true;
}
@@ -135,13 +142,13 @@ static gboolean do_equalizer_activate (Obj * obj, Invoc * invoc, gboolean active
static gboolean do_get_active_playlist (Obj * obj, Invoc * invoc)
{
- FINISH2 (get_active_playlist, CURRENT);
+ FINISH2 (get_active_playlist, CURRENT.index ());
return true;
}
static gboolean do_get_active_playlist_name (Obj * obj, Invoc * invoc)
{
- String title = aud_playlist_get_title (CURRENT);
+ String title = CURRENT.get_title ();
FINISH2 (get_active_playlist_name, title ? title : "");
return true;
}
@@ -180,7 +187,7 @@ static gboolean do_get_info (Obj * obj, Invoc * invoc)
static gboolean do_get_playqueue_length (Obj * obj, Invoc * invoc)
{
- FINISH2 (get_playqueue_length, aud_playlist_queue_count (CURRENT));
+ FINISH2 (get_playqueue_length, CURRENT.n_queued ());
return true;
}
@@ -207,14 +214,14 @@ static gboolean do_info (Obj * obj, Invoc * invoc)
static gboolean do_jump (Obj * obj, Invoc * invoc, unsigned pos)
{
- aud_playlist_set_position (CURRENT, pos);
+ CURRENT.set_position (pos);
FINISH (jump);
return true;
}
static gboolean do_length (Obj * obj, Invoc * invoc)
{
- FINISH2 (length, aud_playlist_entry_count (CURRENT));
+ FINISH2 (length, CURRENT.n_entries ());
return true;
}
@@ -226,9 +233,7 @@ static gboolean do_main_win_visible (Obj * obj, Invoc * invoc)
static gboolean do_new_playlist (Obj * obj, Invoc * invoc)
{
- int playlist = CURRENT + 1;
- aud_playlist_insert (playlist);
- aud_playlist_set_active (playlist);
+ Playlist::insert_playlist (CURRENT.index () + 1).activate ();
aud_drct_stop ();
FINISH (new_playlist);
return true;
@@ -236,7 +241,7 @@ static gboolean do_new_playlist (Obj * obj, Invoc * invoc)
static gboolean do_number_of_playlists (Obj * obj, Invoc * invoc)
{
- FINISH2 (number_of_playlists, aud_playlist_count ());
+ FINISH2 (number_of_playlists, Playlist::n_playlists ());
return true;
}
@@ -276,7 +281,7 @@ static gboolean do_play (Obj * obj, Invoc * invoc)
static gboolean do_play_active_playlist (Obj * obj, Invoc * invoc)
{
- aud_playlist_play (CURRENT);
+ CURRENT.start_playback ();
FINISH (play_active_playlist);
return true;
}
@@ -296,7 +301,7 @@ static gboolean do_playing (Obj * obj, Invoc * invoc)
static gboolean do_playlist_add (Obj * obj, Invoc * invoc, const char * list)
{
- aud_playlist_entry_insert (CURRENT, -1, list, Tuple (), false);
+ CURRENT.insert_entry (-1, list, Tuple (), false);
FINISH (playlist_add);
return true;
}
@@ -310,40 +315,39 @@ static gboolean do_playlist_enqueue_to_temp (Obj * obj, Invoc * invoc, const cha
static gboolean do_playlist_ins_url_string (Obj * obj, Invoc * invoc, const char * url, int pos)
{
- aud_playlist_entry_insert (CURRENT, pos, url, Tuple (), false);
+ CURRENT.insert_entry (pos, url, Tuple (), false);
FINISH (playlist_ins_url_string);
return true;
}
static gboolean do_playqueue_add (Obj * obj, Invoc * invoc, int pos)
{
- aud_playlist_queue_insert (CURRENT, -1, pos);
+ CURRENT.queue_insert (-1, pos);
FINISH (playqueue_add);
return true;
}
static gboolean do_playqueue_clear (Obj * obj, Invoc * invoc)
{
- int playlist = CURRENT;
- aud_playlist_queue_delete (playlist, 0, aud_playlist_queue_count (playlist));
+ CURRENT.queue_remove_all ();
FINISH (playqueue_clear);
return true;
}
static gboolean do_playqueue_is_queued (Obj * obj, Invoc * invoc, int pos)
{
- bool queued = (aud_playlist_queue_find_entry (CURRENT, pos) >= 0);
+ bool queued = (CURRENT.queue_find_entry (pos) >= 0);
FINISH2 (playqueue_is_queued, queued);
return true;
}
static gboolean do_playqueue_remove (Obj * obj, Invoc * invoc, int pos)
{
- int playlist = CURRENT;
- int qpos = aud_playlist_queue_find_entry (playlist, pos);
+ auto playlist = CURRENT;
+ int qpos = playlist.queue_find_entry (pos);
if (qpos >= 0)
- aud_playlist_queue_delete (playlist, qpos, 1);
+ playlist.queue_remove (qpos);
FINISH (playqueue_remove);
return true;
@@ -378,19 +382,19 @@ static gboolean do_plugin_is_enabled (Obj * obj, Invoc * invoc, const char * nam
static gboolean do_position (Obj * obj, Invoc * invoc)
{
- FINISH2 (position, aud_playlist_get_position (CURRENT));
+ FINISH2 (position, CURRENT.get_position ());
return true;
}
static gboolean do_queue_get_list_pos (Obj * obj, Invoc * invoc, unsigned qpos)
{
- FINISH2 (queue_get_list_pos, aud_playlist_queue_get_entry (CURRENT, qpos));
+ FINISH2 (queue_get_list_pos, CURRENT.queue_get_entry (qpos));
return true;
}
static gboolean do_queue_get_queue_pos (Obj * obj, Invoc * invoc, unsigned pos)
{
- FINISH2 (queue_get_queue_pos, aud_playlist_queue_find_entry (CURRENT, pos));
+ FINISH2 (queue_get_queue_pos, CURRENT.queue_find_entry (pos));
return true;
}
@@ -403,14 +407,20 @@ static gboolean do_quit (Obj * obj, Invoc * invoc)
static gboolean do_record (Obj * obj, Invoc * invoc)
{
- aud_drct_enable_record (! aud_drct_get_record_enabled ());
+ if (aud_drct_get_record_enabled ())
+ aud_set_bool (nullptr, "record", ! aud_get_bool (nullptr, "record"));
+
FINISH (record);
return true;
}
static gboolean do_recording (Obj * obj, Invoc * invoc)
{
- FINISH2 (recording, aud_drct_get_record_enabled ());
+ bool recording = false;
+ if (aud_drct_get_record_enabled ())
+ recording = aud_get_bool (nullptr, "record");
+
+ FINISH2 (recording, recording);
return true;
}
@@ -422,7 +432,7 @@ static gboolean do_repeat (Obj * obj, Invoc * invoc)
static gboolean do_reverse (Obj * obj, Invoc * invoc)
{
- aud_drct_pl_prev ();
+ CURRENT.prev_song ();
FINISH (reverse);
return true;
}
@@ -434,13 +444,28 @@ static gboolean do_seek (Obj * obj, Invoc * invoc, unsigned pos)
return true;
}
-static gboolean do_set_active_playlist (Obj * obj, Invoc * invoc, int playlist)
+static gboolean do_select_displayed_playlist (Obj * obj, Invoc * invoc)
+{
+ prefer_playing = false;
+ FINISH (select_displayed_playlist);
+ return true;
+}
+
+static gboolean do_select_playing_playlist (Obj * obj, Invoc * invoc)
{
- aud_playlist_set_active (playlist);
+ prefer_playing = true;
+ FINISH (select_playing_playlist);
+ return true;
+}
+
+static gboolean do_set_active_playlist (Obj * obj, Invoc * invoc, int index)
+{
+ auto playlist = Playlist::by_index (index);
+
+ playlist.activate ();
- // check that the requested playlist exists before switching playback
- if (aud_playlist_get_active () == playlist && aud_drct_get_playing ())
- aud_playlist_play (playlist, aud_drct_get_paused ());
+ if (prefer_playing && aud_drct_get_playing ())
+ playlist.start_playback (aud_drct_get_paused ());
FINISH (set_active_playlist);
return true;
@@ -448,7 +473,7 @@ static gboolean do_set_active_playlist (Obj * obj, Invoc * invoc, int playlist)
static gboolean do_set_active_playlist_name (Obj * obj, Invoc * invoc, const char * title)
{
- aud_playlist_set_title (CURRENT, title);
+ CURRENT.set_title (title);
FINISH (set_active_playlist_name);
return true;
}
@@ -564,21 +589,21 @@ static gboolean do_shuffle (Obj * obj, Invoc * invoc)
static gboolean do_song_filename (Obj * obj, Invoc * invoc, unsigned pos)
{
- String filename = aud_playlist_entry_get_filename (CURRENT, pos);
+ String filename = CURRENT.entry_filename (pos);
FINISH2 (song_filename, filename ? filename : "");
return true;
}
static gboolean do_song_frames (Obj * obj, Invoc * invoc, unsigned pos)
{
- Tuple tuple = aud_playlist_entry_get_tuple (CURRENT, pos);
+ Tuple tuple = CURRENT.entry_tuple (pos);
FINISH2 (song_frames, aud::max (0, tuple.get_int (Tuple::Length)));
return true;
}
static gboolean do_song_length (Obj * obj, Invoc * invoc, unsigned pos)
{
- Tuple tuple = aud_playlist_entry_get_tuple (CURRENT, pos);
+ Tuple tuple = CURRENT.entry_tuple (pos);
int length = aud::max (0, tuple.get_int (Tuple::Length));
FINISH2 (song_length, length / 1000);
return true;
@@ -586,7 +611,7 @@ static gboolean do_song_length (Obj * obj, Invoc * invoc, unsigned pos)
static gboolean do_song_title (Obj * obj, Invoc * invoc, unsigned pos)
{
- Tuple tuple = aud_playlist_entry_get_tuple (CURRENT, pos);
+ Tuple tuple = CURRENT.entry_tuple (pos);
String title = tuple.get_str (Tuple::FormattedTitle);
FINISH2 (song_title, title ? title : "");
return true;
@@ -599,7 +624,7 @@ static gboolean do_song_tuple (Obj * obj, Invoc * invoc, unsigned pos, const cha
GVariant * var;
if (field >= 0)
- tuple = aud_playlist_entry_get_tuple (CURRENT, pos);
+ tuple = CURRENT.entry_tuple (pos);
switch (tuple.get_value_type (field))
{
@@ -664,28 +689,28 @@ static gboolean do_time (Obj * obj, Invoc * invoc)
static gboolean do_toggle_auto_advance (Obj * obj, Invoc * invoc)
{
- aud_set_bool (nullptr, "no_playlist_advance", ! aud_get_bool (nullptr, "no_playlist_advance"));
+ aud_toggle_bool (nullptr, "no_playlist_advance");
FINISH (toggle_auto_advance);
return true;
}
static gboolean do_toggle_repeat (Obj * obj, Invoc * invoc)
{
- aud_set_bool (nullptr, "repeat", ! aud_get_bool (nullptr, "repeat"));
+ aud_toggle_bool (nullptr, "repeat");
FINISH (toggle_repeat);
return true;
}
static gboolean do_toggle_shuffle (Obj * obj, Invoc * invoc)
{
- aud_set_bool (nullptr, "shuffle", ! aud_get_bool (nullptr, "shuffle"));
+ aud_toggle_bool (nullptr, "shuffle");
FINISH (toggle_shuffle);
return true;
}
static gboolean do_toggle_stop_after (Obj * obj, Invoc * invoc)
{
- aud_set_bool (nullptr, "stop_after_current_song", ! aud_get_bool (nullptr, "stop_after_current_song"));
+ aud_toggle_bool (nullptr, "stop_after_current_song");
FINISH (toggle_stop_after);
return true;
}
@@ -761,6 +786,8 @@ handlers[] =
{"handle-repeat", (GCallback) do_repeat},
{"handle-reverse", (GCallback) do_reverse},
{"handle-seek", (GCallback) do_seek},
+ {"handle-select-displayed-playlist", (GCallback) do_select_displayed_playlist},
+ {"handle-select-playing-playlist", (GCallback) do_select_playing_playlist},
{"handle-set-active-playlist", (GCallback) do_set_active_playlist},
{"handle-set-active-playlist-name", (GCallback) do_set_active_playlist_name},
{"handle-set-eq", (GCallback) do_set_eq},
diff --git a/src/audacious/main.cc b/src/audacious/main.cc
index f3c4a5f..d8fbbf0 100644
--- a/src/audacious/main.cc
+++ b/src/audacious/main.cc
@@ -21,8 +21,11 @@
#include <stdlib.h>
#include <string.h>
-#include <glib.h> /* for g_get_current_dir, g_path_is_absolute */
+#ifdef _WIN32
+#include <windows.h>
+#endif
+#define AUD_GLIB_INTEGRATION
#include <libaudcore/audstrings.h>
#include <libaudcore/drct.h>
#include <libaudcore/hook.h>
@@ -81,8 +84,7 @@ static const struct {
static bool parse_options (int argc, char * * argv)
{
- char * cur = g_get_current_dir ();
- bool success = true;
+ CharPtr cur (g_get_current_dir ());
#ifdef _WIN32
Index<String> args = get_argv_utf8 ();
@@ -134,8 +136,7 @@ static bool parse_options (int argc, char * * argv)
if (! found)
{
fprintf (stderr, _("Unknown option: %s\n"), arg);
- success = false;
- goto OUT;
+ return false;
}
}
else /* short form */
@@ -157,8 +158,7 @@ static bool parse_options (int argc, char * * argv)
if (! found)
{
fprintf (stderr, _("Unknown option: -%c\n"), arg[c]);
- success = false;
- goto OUT;
+ return false;
}
}
}
@@ -174,16 +174,14 @@ static bool parse_options (int argc, char * * argv)
if (options.qt)
aud_set_mainloop_type (MainloopType::Qt);
-OUT:
- g_free (cur);
- return success;
+ return true;
}
static void print_help ()
{
static const char pad[21] = " ";
- fprintf (stderr, _("Usage: audacious [OPTION] ... [FILE] ...\n\n"));
+ fprintf (stderr, "%s", _("Usage: audacious [OPTION] ... [FILE] ...\n\n"));
fprintf (stderr, " -1, -2, -3, etc. %s\n", _("Select instance to run/control"));
for (auto & arg_info : arg_map)
@@ -330,7 +328,7 @@ static void main_cleanup ()
static bool check_should_quit ()
{
return options.quit_after_play && ! aud_drct_get_playing () &&
- ! aud_playlist_add_in_progress (-1);
+ ! Playlist::add_in_progress_any ();
}
static void maybe_quit ()
@@ -343,6 +341,9 @@ int main (int argc, char * * argv)
{
atexit (main_cleanup);
+#ifdef _WIN32
+ SetErrorMode (SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
+#endif
#ifdef HAVE_SIGWAIT
signals_init_one ();
#endif
diff --git a/src/audtool/audtool.h b/src/audtool/audtool.h
index c0bb88d..51bde99 100644
--- a/src/audtool/audtool.h
+++ b/src/audtool/audtool.h
@@ -59,6 +59,8 @@ void get_current_song_info (int argc, char * * argv);
void get_volume (int, char * *);
void set_volume (int, char * *);
+void select_displayed (int, char * *);
+void select_playing (int, char * *);
void playlist_position (int, char * *);
void playlist_advance (int, char * *);
void playlist_auto_advance_status (int, char * *);
diff --git a/src/audtool/handlers_playlist.c b/src/audtool/handlers_playlist.c
index 21e8f64..e2362c9 100644
--- a/src/audtool/handlers_playlist.c
+++ b/src/audtool/handlers_playlist.c
@@ -24,6 +24,16 @@
#include "audtool.h"
#include "wrappers.h"
+void select_displayed (int argc, char * * argv)
+{
+ obj_audacious_call_select_displayed_playlist_sync (dbus_proxy, NULL, NULL);
+}
+
+void select_playing (int argc, char * * argv)
+{
+ obj_audacious_call_select_playing_playlist_sync (dbus_proxy, NULL, NULL);
+}
+
void playlist_reverse (int argc, char * * argv)
{
obj_audacious_call_reverse_sync (dbus_proxy, NULL, NULL);
diff --git a/src/audtool/main.c b/src/audtool/main.c
index c49d62e..62894e5 100644
--- a/src/audtool/main.c
+++ b/src/audtool/main.c
@@ -59,6 +59,8 @@ const struct commandhandler handlers[] =
{"playback-recording", playback_recording, "exit code = 0 if recording", 0},
{"<sep>", NULL, "Playlist commands", 0},
+ {"select-displayed", select_displayed, "apply commands to displayed playlist", 0},
+ {"select-playing", select_playing, "apply commands to playing playlist", 0},
{"playlist-advance", playlist_advance, "skip to next song", 0},
{"playlist-reverse", playlist_reverse, "skip to previous song", 0},
{"playlist-addurl", playlist_add_url_string, "add URI at end of playlist", 1},
diff --git a/src/dbus/aud-dbus.xml b/src/dbus/aud-dbus.xml
index d028441..0172d98 100644
--- a/src/dbus/aud-dbus.xml
+++ b/src/dbus/aud-dbus.xml
@@ -21,8 +21,11 @@
-->
<node name="/">
- <!-- Audacious General Information -->
<interface name="org.atheme.audacious">
+
+ <!-- General Commands -->
+ <!-- ++++++++++++++++ -->
+
<!-- Audacious version -->
<method name="Version">
<arg type="s" direction="out" name="version"/>
@@ -41,7 +44,13 @@
<!-- Quit Audacious -->
<method name="Quit" />
- <!-- Open files (Eject) -->
+ <!-- Show "Add Files" dialog -->
+ <method name="ShowFilebrowser">
+ <arg type="b" direction="in" name="show"/>
+ </method>
+
+ <!-- Show "Open Files" dialog -->
+ <!-- (same as "eject" button in a Winamp skin) -->
<method name="Eject" />
<!-- Main window visibility -->
@@ -54,6 +63,21 @@
<arg type="b" direction="in" name="show"/>
</method>
+ <!-- Show preferences window -->
+ <method name="ShowPrefsBox">
+ <arg type="b" direction="in" name="show"/>
+ </method>
+
+ <!-- Show about window -->
+ <method name="ShowAboutBox">
+ <arg type="b" direction="in" name="show"/>
+ </method>
+
+ <!-- Show jump to file window -->
+ <method name="ShowJtfBox">
+ <arg type="b" direction="in" name="show"/>
+ </method>
+
<!-- Send startup notification -->
<method name="StartupNotify">
<arg type="s" direction="in" name="id"/>
@@ -66,6 +90,8 @@
</method>
<!-- Playback Information/Manipulation -->
+ <!-- +++++++++++++++++++++++++++++++++ -->
+
<!-- Begin or resume playback -->
<method name="Play" />
@@ -112,12 +138,17 @@
</method>
<!-- What is the bitrate, frequency, and number of channels of the -->
- <!-- current audio format? -->
+ <!-- current audio format? ("Info" and "GetInfo" are synonymous) -->
<method name="Info">
<arg type="i" direction="out" name="rate"/>
<arg type="i" direction="out" name="freq"/>
<arg type="i" direction="out" name="nch"/>
</method>
+ <method name="GetInfo">
+ <arg type="i" direction="out" name="rate"/>
+ <arg type="i" direction="out" name="freq"/>
+ <arg type="i" direction="out" name="nch"/>
+ </method>
<!-- What is the current output position? -->
<method name="Time">
@@ -131,6 +162,9 @@
<arg type="u" direction="in" name="pos"/>
</method>
+ <!-- Volume and Equalizer -->
+ <!-- ++++++++++++++++++++ -->
+
<!-- What is the playback volume? -->
<method name="Volume">
<!-- Volume of the left channel -->
@@ -153,7 +187,47 @@
<arg type="i" direction="out" name="balance"/>
</method>
+ <!-- equalizer -->
+ <method name="GetEq">
+ <arg type="d" direction="out" name="preamp"/>
+ <arg type="ad" direction="out" name="bands"/>
+ </method>
+
+ <method name="GetEqPreamp">
+ <arg type="d" direction="out" name="preamp"/>
+ </method>
+
+ <method name="GetEqBand">
+ <arg type="i" direction="in" name="band"/>
+ <arg type="d" direction="out" name="value"/>
+ </method>
+
+ <method name="SetEq">
+ <arg type="d" direction="in" name="preamp"/>
+ <arg type="ad" direction="in" name="bands"/>
+ </method>
+
+ <method name="SetEqPreamp">
+ <arg type="d" direction="in" name="preamp"/>
+ </method>
+
+ <method name="SetEqBand">
+ <arg type="i" direction="in" name="band"/>
+ <arg type="d" direction="in" name="value"/>
+ </method>
+
+ <!-- Activate/Deactivate Equalizer -->
+ <method name="EqualizerActivate">
+ <arg type="b" direction="in" name="active"/>
+ </method>
+
<!-- Playlist Information/Manipulation -->
+ <!-- +++++++++++++++++++++++++++++++++ -->
+
+ <!-- Select playlist to control -->
+ <method name="SelectDisplayedPlaylist"/>
+ <method name="SelectPlayingPlaylist"/>
+
<!-- Playlist position -->
<method name="Position">
<!-- Return position of current song in current playlist -->
@@ -226,17 +300,24 @@
<arg type="u" direction="in" name="pos"/>
</method>
- <!-- Add some file to the current playlist -->
+ <!-- Add (append) a file to the current playlist -->
+ <!-- ("Add", "AddUrl", and "PlaylistAdd" are synonymous) -->
<method name="Add">
- <!-- File to add -->
+ <!-- URI of file to add -->
<arg type="s" direction="in" name="file"/>
</method>
-
- <!-- Add some URL to the current playlist -->
<method name="AddUrl">
- <!-- URL to add -->
<arg type="s" direction="in" name="url"/>
</method>
+ <method name="PlaylistAdd">
+ <arg type="s" direction="in" name="list"/>
+ </method>
+
+ <!-- Insert a file at the given position in the playlist -->
+ <method name="PlaylistInsUrlString">
+ <arg type="s" direction="in" name="url"/>
+ <arg type="i" direction="in" name="pos"/>
+ </method>
<!-- Add a list of files -->
<method name="AddList">
@@ -250,6 +331,11 @@
<arg type="as" direction="in" name="filenames"/>
</method>
+ <!-- Open a file in a temporary playlist -->
+ <method name="PlaylistEnqueueToTemp">
+ <arg type="s" direction="in" name="url"/>
+ </method>
+
<!-- Open a list of files in a temporary playlist -->
<method name="OpenListToTemp">
<!-- Array of filenames to open -->
@@ -297,25 +383,8 @@
<!-- Toggle stop-after-song -->
<method name="ToggleStopAfter" />
- <!-- Show preferences window -->
- <method name="ShowPrefsBox">
- <arg type="b" direction="in" name="show"/>
- </method>
-
- <!-- Show about window -->
- <method name="ShowAboutBox">
- <arg type="b" direction="in" name="show"/>
- </method>
-
- <!-- Show jump to file window -->
- <method name="ShowJtfBox">
- <arg type="b" direction="in" name="show"/>
- </method>
-
- <!-- Show filebrowser -->
- <method name="ShowFilebrowser">
- <arg type="b" direction="in" name="show"/>
- </method>
+ <!-- Playlist Queue -->
+ <!-- ++++++++++++++ -->
<!-- Playqueue get playlist pos -->
<method name="QueueGetListPos">
@@ -329,26 +398,10 @@
<arg type="u" direction="out" name="qpos"/>
</method>
- <!-- Get Info -->
- <method name="GetInfo">
- <arg type="i" direction="out" name="rate"/>
- <arg type="i" direction="out" name="freq"/>
- <arg type="i" direction="out" name="nch"/>
- </method>
-
<method name="GetPlayqueueLength">
<arg type="i" direction="out" name="length"/>
</method>
- <method name="PlaylistInsUrlString">
- <arg type="s" direction="in" name="url"/>
- <arg type="i" direction="in" name="pos"/>
- </method>
-
- <method name="PlaylistAdd">
- <arg type="s" direction="in" name="list"/>
- </method>
-
<method name="PlayqueueAdd">
<arg type="i" direction="in" name="pos"/>
</method>
@@ -364,43 +417,8 @@
<arg type="b" direction="out" name="is_queued"/>
</method>
- <method name="PlaylistEnqueueToTemp">
- <arg type="s" direction="in" name="url"/>
- </method>
-
- <!-- equalizer -->
- <method name="GetEq">
- <arg type="d" direction="out" name="preamp"/>
- <arg type="ad" direction="out" name="bands"/>
- </method>
-
- <method name="GetEqPreamp">
- <arg type="d" direction="out" name="preamp"/>
- </method>
-
- <method name="GetEqBand">
- <arg type="i" direction="in" name="band"/>
- <arg type="d" direction="out" name="value"/>
- </method>
-
- <method name="SetEq">
- <arg type="d" direction="in" name="preamp"/>
- <arg type="ad" direction="in" name="bands"/>
- </method>
-
- <method name="SetEqPreamp">
- <arg type="d" direction="in" name="preamp"/>
- </method>
-
- <method name="SetEqBand">
- <arg type="i" direction="in" name="band"/>
- <arg type="d" direction="in" name="value"/>
- </method>
-
- <!-- Activate/Deactivate Equalizer -->
- <method name="EqualizerActivate">
- <arg type="b" direction="in" name="active"/>
- </method>
+ <!-- Add/Remove/Switch Playlists -->
+ <!-- +++++++++++++++++++++++++++ -->
<method name="NumberOfPlaylists">
<arg type="i" direction="out" name="count" />
diff --git a/src/libaudcore/Makefile b/src/libaudcore/Makefile
index dde7284..a90d5dc 100644
--- a/src/libaudcore/Makefile
+++ b/src/libaudcore/Makefile
@@ -1,6 +1,6 @@
SHARED_LIB = ${LIB_PREFIX}audcore${LIB_SUFFIX}
-LIB_MAJOR = 4
-LIB_MINOR = 1
+LIB_MAJOR = 5
+LIB_MINOR = 0
SRCS = adder.cc \
art.cc \
@@ -30,6 +30,7 @@ SRCS = adder.cc \
playback.cc \
playlist.cc \
playlist-cache.cc \
+ playlist-data.cc \
playlist-files.cc \
playlist-utils.cc \
plugin-init.cc \
diff --git a/src/libaudcore/adder.cc b/src/libaudcore/adder.cc
index 2866036..f9d9bca 100644
--- a/src/libaudcore/adder.cc
+++ b/src/libaudcore/adder.cc
@@ -36,27 +36,30 @@
#include "interface.h"
#include "vfs.h"
-#ifdef _WIN32
// regrettably, strcmp_nocase can't be used directly as a
-// callback for Index::sort due to taking a third argument
+// callback for Index::sort due to taking a third argument;
+// strcmp also triggers -Wnoexcept-type with GCC 7
static int filename_compare (const char * a, const char * b)
- { return strcmp_nocase (a, b); }
+#ifdef _WIN32
+ { return strcmp_nocase (a, b); }
#else
-#define filename_compare strcmp
+ { return strcmp (a, b); }
#endif
struct AddTask : public ListNode
{
- int playlist_id, at;
+ Playlist playlist;
+ int at;
bool play;
Index<PlaylistAddItem> items;
- PlaylistFilterFunc filter;
+ Playlist::FilterFunc filter;
void * user;
};
struct AddResult : public ListNode
{
- int playlist_id, at;
+ Playlist playlist;
+ int at;
bool play;
String title;
Index<PlaylistAddItem> items;
@@ -67,7 +70,7 @@ static void * add_worker (void * unused);
static List<AddTask> add_tasks;
static List<AddResult> add_results;
-static int current_playlist_id = -1;
+static Playlist current_playlist;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static bool add_thread_started = false;
@@ -75,8 +78,10 @@ static bool add_thread_exited = false;
static pthread_t add_thread;
static QueuedFunc queued_add;
static QueuedFunc status_timer;
+
static char status_path[512];
static int status_count;
+static bool status_shown = false;
static void status_cb (void * unused)
{
@@ -97,6 +102,8 @@ static void status_cb (void * unused)
hook_call ("ui show progress 2", scratch);
}
+ status_shown = true;
+
pthread_mutex_unlock (& mutex);
}
@@ -117,13 +124,18 @@ static void status_done_locked ()
{
status_timer.stop ();
- if (aud_get_headless_mode ())
- printf ("\n");
- else
- hook_call ("ui hide progress", nullptr);
+ if (status_shown)
+ {
+ if (aud_get_headless_mode ())
+ printf ("\n");
+ else
+ hook_call ("ui hide progress", nullptr);
+
+ status_shown = false;
+ }
}
-static void add_file (PlaylistAddItem && item, PlaylistFilterFunc filter,
+static void add_file (PlaylistAddItem && item, Playlist::FilterFunc filter,
void * user, AddResult * result, bool validate)
{
AUDINFO ("Adding file: %s\n", (const char *) item.filename);
@@ -168,8 +180,15 @@ static void add_file (PlaylistAddItem && item, PlaylistFilterFunc filter,
result->items.append (std::move (item));
}
-static void add_playlist (const char * filename, PlaylistFilterFunc filter,
- void * user, AddResult * result, bool is_single)
+/* To prevent infinite recursion, we currently allow adding a folder from within
+ * a playlist, but not a playlist from within a folder, nor a second playlist
+ * from within a playlist (this last rule is enforced by setting
+ * <allow_playlist> to false from within add_playlist()). */
+static void add_generic (PlaylistAddItem && item, Playlist::FilterFunc filter,
+ void * user, AddResult * result, bool save_title, bool allow_playlist);
+
+static void add_playlist (const char * filename, Playlist::FilterFunc filter,
+ void * user, AddResult * result, bool save_title)
{
AUDINFO ("Adding playlist: %s\n", filename);
status_update (filename, result->items.len ());
@@ -180,19 +199,14 @@ static void add_playlist (const char * filename, PlaylistFilterFunc filter,
if (! playlist_load (filename, title, items))
return;
- if (is_single)
+ if (save_title)
result->title = title;
for (auto & item : items)
- {
- if (! filter || filter (item.filename, user))
- add_file (std::move (item), filter, user, result, false);
- else
- result->filtered = true;
- }
+ add_generic (std::move (item), filter, user, result, false, false);
}
-static void add_cuesheets (Index<String> & files, PlaylistFilterFunc filter,
+static void add_cuesheets (Index<String> & files, Playlist::FilterFunc filter,
void * user, AddResult * result)
{
Index<String> cuesheets;
@@ -250,8 +264,8 @@ static void add_cuesheets (Index<String> & files, PlaylistFilterFunc filter,
}
}
-static void add_folder (const char * filename, PlaylistFilterFunc filter,
- void * user, AddResult * result, bool is_single)
+static void add_folder (const char * filename, Playlist::FilterFunc filter,
+ void * user, AddResult * result, bool save_title)
{
AUDINFO ("Adding folder: %s\n", filename);
status_update (filename, result->items.len ());
@@ -265,7 +279,7 @@ static void add_folder (const char * filename, PlaylistFilterFunc filter,
if (! files.len ())
return;
- if (is_single)
+ if (save_title)
{
const char * slash = strrchr (filename, '/');
if (slash)
@@ -302,8 +316,8 @@ static void add_folder (const char * filename, PlaylistFilterFunc filter,
}
}
-static void add_generic (PlaylistAddItem && item, PlaylistFilterFunc filter,
- void * user, AddResult * result, bool is_single)
+static void add_generic (PlaylistAddItem && item, Playlist::FilterFunc filter,
+ void * user, AddResult * result, bool save_title, bool allow_playlist)
{
if (filter && ! filter (item.filename, user))
{
@@ -326,11 +340,11 @@ static void add_generic (PlaylistAddItem && item, PlaylistFilterFunc filter,
(const char *) item.filename, (const char *) error));
else if (mode & VFS_IS_DIR)
{
- add_folder (item.filename, filter, user, result, is_single);
+ add_folder (item.filename, filter, user, result, save_title);
result->saw_folder = true;
}
- else if (aud_filename_is_playlist (item.filename))
- add_playlist (item.filename, filter, user, result, is_single);
+ else if (allow_playlist && Playlist::filename_is_playlist (item.filename))
+ add_playlist (item.filename, filter, user, result, save_title);
else
add_file (std::move (item), filter, user, result, false);
}
@@ -374,7 +388,8 @@ static void add_finish (void * unused)
{
add_results.remove (result);
- int playlist, count;
+ PlaylistEx playlist;
+ int count;
if (! result->items.len ())
{
@@ -383,42 +398,40 @@ static void add_finish (void * unused)
goto FREE;
}
- playlist = aud_playlist_by_unique_id (result->playlist_id);
- if (playlist < 0) /* playlist deleted */
+ playlist = result->playlist;
+ if (! playlist.exists ()) /* playlist deleted */
goto FREE;
if (result->play)
{
if (aud_get_bool (nullptr, "clear_playlist"))
- aud_playlist_entry_delete (playlist, 0, aud_playlist_entry_count (playlist));
+ playlist.remove_all_entries ();
else
- aud_playlist_queue_delete (playlist, 0, aud_playlist_queue_count (playlist));
+ playlist.queue_remove_all ();
}
- count = aud_playlist_entry_count (playlist);
+ count = playlist.n_entries ();
if (result->at < 0 || result->at > count)
result->at = count;
if (result->title && ! count)
{
- String old_title = aud_playlist_get_title (playlist);
-
- if (! strcmp (old_title, N_("New Playlist")))
- aud_playlist_set_title (playlist, result->title);
+ if (! strcmp (playlist.get_title (), _("New Playlist")))
+ playlist.set_title (result->title);
}
/* temporarily disable scanning this playlist; the intent is to avoid
* scanning until the currently playing entry is known, at which time it
* can be scanned more efficiently (album art read in the same pass). */
playlist_enable_scan (false);
- playlist_entry_insert_batch_raw (playlist, result->at, std::move (result->items));
+ playlist.insert_flat_items (result->at, std::move (result->items));
if (result->play)
{
if (! aud_get_bool (0, "shuffle"))
- aud_playlist_set_position (playlist, result->at);
+ playlist.set_position (result->at);
- aud_playlist_play (playlist);
+ playlist.start_playback ();
}
playlist_enable_scan (true);
@@ -447,26 +460,26 @@ static void * add_worker (void * unused)
{
add_tasks.remove (task);
- current_playlist_id = task->playlist_id;
+ current_playlist = task->playlist;
pthread_mutex_unlock (& mutex);
playlist_cache_load (task->items);
AddResult * result = new AddResult ();
- result->playlist_id = task->playlist_id;
+ result->playlist = task->playlist;
result->at = task->at;
result->play = task->play;
- bool is_single = (task->items.len () == 1);
+ bool save_title = (task->items.len () == 1);
for (auto & item : task->items)
- add_generic (std::move (item), task->filter, task->user, result, is_single);
+ add_generic (std::move (item), task->filter, task->user, result, save_title, true);
delete task;
pthread_mutex_lock (& mutex);
- current_playlist_id = -1;
+ current_playlist = Playlist ();
if (! add_results.head ())
queued_add.queue (add_finish, nullptr);
@@ -495,32 +508,30 @@ void adder_cleanup ()
pthread_mutex_unlock (& mutex);
}
-EXPORT void aud_playlist_entry_insert (int playlist, int at,
- const char * filename, Tuple && tuple, bool play)
+EXPORT void Playlist::insert_entry (int at,
+ const char * filename, Tuple && tuple, bool play) const
{
Index<PlaylistAddItem> items;
items.append (String (filename), std::move (tuple));
- aud_playlist_entry_insert_batch (playlist, at, std::move (items), play);
+ insert_items (at, std::move (items), play);
}
-EXPORT void aud_playlist_entry_insert_batch (int playlist, int at,
- Index<PlaylistAddItem> && items, bool play)
+EXPORT void Playlist::insert_items (int at,
+ Index<PlaylistAddItem> && items, bool play) const
{
- aud_playlist_entry_insert_filtered (playlist, at, std::move (items), nullptr, nullptr, play);
+ insert_filtered (at, std::move (items), nullptr, nullptr, play);
}
-EXPORT void aud_playlist_entry_insert_filtered (int playlist, int at,
- Index<PlaylistAddItem> && items, PlaylistFilterFunc filter, void * user,
- bool play)
+EXPORT void Playlist::insert_filtered (int at,
+ Index<PlaylistAddItem> && items, Playlist::FilterFunc filter, void * user,
+ bool play) const
{
- int playlist_id = aud_playlist_get_unique_id (playlist);
-
pthread_mutex_lock (& mutex);
AddTask * task = new AddTask ();
- task->playlist_id = playlist_id;
+ task->playlist = * this;
task->at = at;
task->play = play;
task->items = std::move (items);
@@ -533,32 +544,22 @@ EXPORT void aud_playlist_entry_insert_filtered (int playlist, int at,
pthread_mutex_unlock (& mutex);
}
-EXPORT bool aud_playlist_add_in_progress (int playlist)
+EXPORT bool Playlist::add_in_progress () const
{
pthread_mutex_lock (& mutex);
- if (playlist >= 0)
+ for (AddTask * task = add_tasks.head (); task; task = add_tasks.next (task))
{
- int playlist_id = aud_playlist_get_unique_id (playlist);
-
- for (AddTask * task = add_tasks.head (); task; task = add_tasks.next (task))
- {
- if (task->playlist_id == playlist_id)
- goto YES;
- }
-
- if (current_playlist_id == playlist_id)
+ if (task->playlist == * this)
goto YES;
-
- for (AddResult * result = add_results.head (); result; result = add_results.next (result))
- {
- if (result->playlist_id == playlist_id)
- goto YES;
- }
}
- else
+
+ if (current_playlist == * this)
+ goto YES;
+
+ for (AddResult * result = add_results.head (); result; result = add_results.next (result))
{
- if (add_tasks.head () || current_playlist_id >= 0 || add_results.head ())
+ if (result->playlist == * this)
goto YES;
}
@@ -569,3 +570,15 @@ YES:
pthread_mutex_unlock (& mutex);
return true;
}
+
+EXPORT bool Playlist::add_in_progress_any ()
+{
+ pthread_mutex_lock (& mutex);
+
+ bool in_progress = (add_tasks.head () ||
+ current_playlist != Playlist () ||
+ add_results.head ());
+
+ pthread_mutex_unlock (& mutex);
+ return in_progress;
+}
diff --git a/src/libaudcore/art.cc b/src/libaudcore/art.cc
index 6fdbda7..e406eec 100644
--- a/src/libaudcore/art.cc
+++ b/src/libaudcore/art.cc
@@ -40,7 +40,8 @@
#define FLAG_DONE 1
#define FLAG_SENT 2
-struct ArtItem {
+struct AudArtItem {
+ String filename;
int refcount;
int flag;
@@ -54,25 +55,23 @@ struct ArtItem {
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-static SimpleHash<String, ArtItem> art_items;
-static String current_ref;
+static SimpleHash<String, AudArtItem> art_items;
+static AudArtItem * current_item;
static QueuedFunc queued_requests;
-static void get_queued_cb (const String & key, ArtItem & item, void * list)
+static Index<AudArtItem *> get_queued ()
{
- if (item.flag == FLAG_DONE)
- {
- ((Index<String> *) list)->append (key);
- item.flag = FLAG_SENT;
- }
-}
-
-static Index<String> get_queued ()
-{
- Index<String> queued;
+ Index<AudArtItem *> queued;
pthread_mutex_lock (& mutex);
- art_items.iterate (get_queued_cb, & queued);
+ art_items.iterate ([&] (const String &, AudArtItem & item)
+ {
+ if (item.flag == FLAG_DONE)
+ {
+ queued.append (& item);
+ item.flag = FLAG_SENT;
+ }
+ });
queued_requests.stop ();
@@ -82,16 +81,15 @@ static Index<String> get_queued ()
static void send_requests (void *)
{
- Index<String> queued = get_queued ();
-
- for (const String & file : queued)
+ auto queued = get_queued ();
+ for (AudArtItem * item : queued)
{
- hook_call ("art ready", (void *) (const char *) file);
- aud_art_unref (file); /* release temporary reference */
+ hook_call ("art ready", (void *) (const char *) item->filename);
+ aud_art_unref (item); /* release temporary reference */
}
}
-static void finish_item (ArtItem * item, Index<char> && data, String && art_file)
+static void finish_item_locked (AudArtItem * item, Index<char> && data, String && art_file)
{
/* already finished? */
if (item->flag)
@@ -108,24 +106,24 @@ static void request_callback (ScanRequest * request)
{
pthread_mutex_lock (& mutex);
- ArtItem * item = art_items.lookup (request->filename);
+ AudArtItem * item = art_items.lookup (request->filename);
if (item)
- finish_item (item, std::move (request->image_data), std::move (request->image_file));
+ finish_item_locked (item, std::move (request->image_data), std::move (request->image_file));
pthread_mutex_unlock (& mutex);
}
-static ArtItem * art_item_get (const String & file, bool * queued)
+static AudArtItem * art_item_get_locked (const String & filename, bool * queued)
{
if (queued)
* queued = false;
// blacklist stdin
- if (! strncmp (file, "stdin://", 8))
+ if (! strncmp (filename, "stdin://", 8))
return nullptr;
- ArtItem * item = art_items.lookup (file);
+ AudArtItem * item = art_items.lookup (filename);
if (item && item->flag)
{
@@ -135,10 +133,11 @@ static ArtItem * art_item_get (const String & file, bool * queued)
if (! item)
{
- item = art_items.add (file, ArtItem ());
+ item = art_items.add (filename, AudArtItem ());
+ item->filename = filename;
item->refcount = 1; /* temporary reference */
- scanner_request (new ScanRequest (file, SCAN_IMAGE, request_callback));
+ scanner_request (new ScanRequest (filename, SCAN_IMAGE, request_callback));
}
if (queued)
@@ -147,7 +146,7 @@ static ArtItem * art_item_get (const String & file, bool * queued)
return nullptr;
}
-static void art_item_unref (const String & file, ArtItem * item)
+static void art_item_unref_locked (AudArtItem * item)
{
if (! -- item->refcount)
{
@@ -159,19 +158,16 @@ static void art_item_unref (const String & file, ArtItem * item)
g_unlink (local);
}
- art_items.remove (file);
+ art_items.remove (item->filename);
}
}
static void clear_current_locked ()
{
- if (current_ref)
+ if (current_item)
{
- ArtItem * item = art_items.lookup (current_ref);
- assert (item);
-
- art_item_unref (current_ref, item);
- current_ref = String ();
+ art_item_unref_locked (current_item);
+ current_item = nullptr;
}
}
@@ -181,18 +177,19 @@ void art_cache_current (const String & filename, Index<char> && data, String &&
clear_current_locked ();
- ArtItem * item = art_items.lookup (filename);
+ AudArtItem * item = art_items.lookup (filename);
if (! item)
{
- item = art_items.add (filename, ArtItem ());
+ item = art_items.add (filename, AudArtItem ());
+ item->filename = filename;
item->refcount = 1; /* temporary reference */
}
- finish_item (item, std::move (data), std::move (art_file));
+ finish_item_locked (item, std::move (data), std::move (art_file));
item->refcount ++;
- current_ref = filename;
+ current_item = item;
pthread_mutex_unlock (& mutex);
}
@@ -206,87 +203,78 @@ void art_clear_current ()
void art_cleanup ()
{
- Index<String> queued = get_queued ();
- for (const String & file : queued)
- aud_art_unref (file); /* release temporary reference */
+ auto queued = get_queued ();
+ for (AudArtItem * item : queued)
+ aud_art_unref (item); /* release temporary reference */
/* playback should already be stopped */
- assert (! current_ref);
+ assert (! current_item);
if (art_items.n_items ())
AUDWARN ("Album art reference count not zero at exit!\n");
}
-EXPORT const Index<char> * aud_art_request_data (const char * file, bool * queued)
+EXPORT AudArtPtr aud_art_request (const char * file, int format, bool * queued)
{
- const Index<char> * data = nullptr;
pthread_mutex_lock (& mutex);
String key (file);
- ArtItem * item = art_item_get (key, queued);
+ AudArtItem * item = art_item_get_locked (key, queued);
+ bool good = true;
if (! item)
goto UNLOCK;
- /* load data from external image file */
- if (! item->data.len () && item->art_file)
+ if (format & AUD_ART_DATA)
{
- VFSFile file (item->art_file, "r");
- if (file)
- item->data = file.read_all ();
- }
-
- if (item->data.len ())
- data = & item->data;
- else
- art_item_unref (key, item);
-
-UNLOCK:
- pthread_mutex_unlock (& mutex);
- return data;
-}
-
-EXPORT const char * aud_art_request_file (const char * file, bool * queued)
-{
- const char * art_file = nullptr;
- pthread_mutex_lock (& mutex);
-
- String key (file);
- ArtItem * item = art_item_get (key, queued);
+ /* load data from external image file */
+ if (! item->data.len () && item->art_file)
+ {
+ VFSFile file (item->art_file, "r");
+ if (file)
+ item->data = file.read_all ();
+ }
- if (! item)
- goto UNLOCK;
+ if (! item->data.len ())
+ good = false;
+ }
- /* save data to temporary file */
- if (item->data.len () && ! item->art_file)
+ if (format & AUD_ART_FILE)
{
- String local = write_temp_file (item->data.begin (), item->data.len ());
- if (local)
+ /* save data to temporary file */
+ if (item->data.len () && ! item->art_file)
{
- item->art_file = String (filename_to_uri (local));
- item->is_temp = true;
+ String local = write_temp_file (item->data.begin (), item->data.len ());
+ if (local)
+ {
+ item->art_file = String (filename_to_uri (local));
+ item->is_temp = true;
+ }
}
+
+ if (! item->art_file)
+ good = false;
}
- if (item->art_file)
- art_file = item->art_file;
- else
- art_item_unref (key, item);
+ if (! good)
+ {
+ art_item_unref_locked (item);
+ item = nullptr;
+ }
UNLOCK:
pthread_mutex_unlock (& mutex);
- return art_file;
+ return AudArtPtr (item);
}
-EXPORT void aud_art_unref (const char * file)
+EXPORT const Index<char> * aud_art_data (const AudArtItem * item)
+ { return & item->data; }
+EXPORT const char * aud_art_file (const AudArtItem * item)
+ { return item->art_file; }
+
+EXPORT void aud_art_unref (AudArtItem * item)
{
pthread_mutex_lock (& mutex);
-
- String key (file);
- ArtItem * item = art_items.lookup (key);
- assert (item);
-
- art_item_unref (key, item);
-
+ art_item_unref_locked (item);
pthread_mutex_unlock (& mutex);
}
diff --git a/src/libaudcore/audio.cc b/src/libaudcore/audio.cc
index 6b20f5d..b553864 100644
--- a/src/libaudcore/audio.cc
+++ b/src/libaudcore/audio.cc
@@ -27,17 +27,39 @@
#define SW_VOLUME_RANGE 40 /* decibels */
-#define INTERLACE_LOOP(TYPE) \
-for (int c = 0; c < channels; c ++) \
-{ \
- const TYPE * get = (const TYPE *) in[c]; \
- const TYPE * end = get + frames; \
- TYPE * set = (TYPE *) out + c; \
- while (get < end) \
- { \
- * set = * get ++; \
- set += channels; \
- } \
+struct packed24_t { uint8_t b[3]; };
+static_assert (sizeof (packed24_t) == 3, "invalid packed 24-bit type");
+
+template<class Word>
+void interlace_loop (const void * const * in, int channels, void * out, int frames)
+{
+ for (int c = 0; c < channels; c ++)
+ {
+ auto get = (const Word *) in[c];
+ auto end = get + frames;
+ auto set = (Word *) out + c;
+ while (get < end)
+ {
+ * set = * get ++;
+ set += channels;
+ }
+ }
+}
+
+template<class Word>
+void deinterlace_loop (const void * in, int channels, void * const * out, int frames)
+{
+ for (int c = 0; c < channels; c ++)
+ {
+ auto get = (const Word *) in + c;
+ auto set = (Word *) out[c];
+ auto end = set + frames;
+ while (set < end)
+ {
+ * set ++ = * get;
+ get += channels;
+ }
+ }
}
EXPORT void audio_interlace (const void * const * in, int format, int channels,
@@ -46,45 +68,30 @@ EXPORT void audio_interlace (const void * const * in, int format, int channels,
switch (format)
{
case FMT_FLOAT:
- INTERLACE_LOOP (float);
+ interlace_loop<float> (in, channels, out, frames);
break;
- case FMT_S8:
- case FMT_U8:
- INTERLACE_LOOP (int8_t);
+ case FMT_S8: case FMT_U8:
+ interlace_loop<int8_t> (in, channels, out, frames);
break;
- case FMT_S16_LE:
- case FMT_S16_BE:
- case FMT_U16_LE:
- case FMT_U16_BE:
- INTERLACE_LOOP (int16_t);
+ case FMT_S16_LE: case FMT_S16_BE:
+ case FMT_U16_LE: case FMT_U16_BE:
+ interlace_loop<int16_t> (in, channels, out, frames);
break;
- case FMT_S24_LE:
- case FMT_S24_BE:
- case FMT_U24_LE:
- case FMT_U24_BE:
- case FMT_S32_LE:
- case FMT_S32_BE:
- case FMT_U32_LE:
- case FMT_U32_BE:
- INTERLACE_LOOP (int32_t);
+ case FMT_S24_LE: case FMT_S24_BE:
+ case FMT_U24_LE: case FMT_U24_BE:
+ case FMT_S32_LE: case FMT_S32_BE:
+ case FMT_U32_LE: case FMT_U32_BE:
+ interlace_loop<int32_t> (in, channels, out, frames);
break;
- }
-}
-#define DEINTERLACE_LOOP(TYPE) \
-for (int c = 0; c < channels; c ++) \
-{ \
- const TYPE * get = (const TYPE *) in + c; \
- TYPE * set = (TYPE *) out[c]; \
- TYPE * end = set + frames; \
- while (set < end) \
- { \
- * set ++ = * get; \
- get += channels; \
- } \
+ case FMT_S24_3LE: case FMT_S24_3BE:
+ case FMT_U24_3LE: case FMT_U24_3BE:
+ interlace_loop<packed24_t> (in, channels, out, frames);
+ break;
+ }
}
EXPORT void audio_deinterlace (const void * in, int format, int channels,
@@ -93,131 +100,187 @@ EXPORT void audio_deinterlace (const void * in, int format, int channels,
switch (format)
{
case FMT_FLOAT:
- DEINTERLACE_LOOP (float);
+ deinterlace_loop<float> (in, channels, out, frames);
+ break;
+
+ case FMT_S8: case FMT_U8:
+ deinterlace_loop<int8_t> (in, channels, out, frames);
break;
- case FMT_S8:
- case FMT_U8:
- DEINTERLACE_LOOP (int8_t);
+ case FMT_S16_LE: case FMT_S16_BE:
+ case FMT_U16_LE: case FMT_U16_BE:
+ deinterlace_loop<int16_t> (in, channels, out, frames);
break;
- case FMT_S16_LE:
- case FMT_S16_BE:
- case FMT_U16_LE:
- case FMT_U16_BE:
- DEINTERLACE_LOOP (int16_t);
+ case FMT_S24_LE: case FMT_S24_BE:
+ case FMT_U24_LE: case FMT_U24_BE:
+ case FMT_S32_LE: case FMT_S32_BE:
+ case FMT_U32_LE: case FMT_U32_BE:
+ deinterlace_loop<int32_t> (in, channels, out, frames);
break;
- case FMT_S24_LE:
- case FMT_S24_BE:
- case FMT_U24_LE:
- case FMT_U24_BE:
- case FMT_S32_LE:
- case FMT_S32_BE:
- case FMT_U32_LE:
- case FMT_U32_BE:
- DEINTERLACE_LOOP (int32_t);
+ case FMT_S24_3LE: case FMT_S24_3BE:
+ case FMT_U24_3LE: case FMT_U24_3BE:
+ deinterlace_loop<packed24_t> (in, channels, out, frames);
break;
}
}
-#define FROM_INT_LOOP(NAME, TYPE, SWAP, OFFSET, RANGE) \
-static void NAME (const TYPE * in, float * out, int samples) \
-{ \
- const TYPE * end = in + samples; \
- while (in < end) { \
- TYPE value = SWAP (* in ++) + (RANGE - OFFSET); \
- if (RANGE == 0x800000) value &= 0xffffff; /* ignore high byte */ \
- * out ++ = (TYPE) (value - RANGE) * (1.0f / RANGE); \
- } \
+static constexpr bool is_le (int format)
+{
+ return format == FMT_S16_LE || format == FMT_U16_LE ||
+ format == FMT_S24_LE || format == FMT_U24_LE ||
+ format == FMT_S32_LE || format == FMT_U32_LE ||
+ format == FMT_S24_3LE || format == FMT_U24_3LE;
+}
+
+static constexpr bool is_signed (int format)
+{
+ return (format == FMT_S8 ||
+ format == FMT_S16_LE || format == FMT_S16_BE ||
+ format == FMT_S24_LE || format == FMT_S24_BE ||
+ format == FMT_S32_LE || format == FMT_S32_BE ||
+ format == FMT_S24_3LE || format == FMT_S24_3BE);
}
-#define TO_INT_LOOP(NAME, TYPE, SWAP, OFFSET, RANGE, RANGE_P) \
-static void NAME (const float * in, TYPE * out, int samples) \
-{ \
- const float * end = in + samples; \
- while (in < end) \
- { \
- float f = (* in ++) * RANGE; \
- TYPE value = OFFSET + TYPE (lrintf (aud::clamp (f, -(float) RANGE, (float) RANGE_P))); \
- if (RANGE == 0x800000) value &= 0xffffff; /* zero high byte */ \
- * out ++ = SWAP (value); \
- } \
+static constexpr unsigned neg_range (int format)
+{
+ return (format >= FMT_S32_LE && format < FMT_S24_3LE) ? 0x80000000 :
+ (format >= FMT_S24_LE) ? 0x800000 :
+ (format >= FMT_S16_LE) ? 0x8000 : 0x80;
}
-FROM_INT_LOOP (from_s8, int8_t, , 0, 0x80)
-FROM_INT_LOOP (from_u8, int8_t, , 0x80, 0x80)
-
-TO_INT_LOOP (to_s8, int8_t, , 0, 0x80, 0x7f)
-TO_INT_LOOP (to_u8, int8_t, , 0x80, 0x80, 0x7f)
-
-FROM_INT_LOOP (from_s16le, int16_t, FROM_LE16, 0, 0x8000)
-FROM_INT_LOOP (from_u16le, int16_t, FROM_LE16, 0x8000, 0x8000)
-FROM_INT_LOOP (from_s24le, int32_t, FROM_LE32, 0, 0x800000)
-FROM_INT_LOOP (from_u24le, int32_t, FROM_LE32, 0x800000, 0x800000)
-FROM_INT_LOOP (from_s32le, int32_t, FROM_LE32, 0, 0x80000000)
-FROM_INT_LOOP (from_u32le, int32_t, FROM_LE32, 0x80000000, 0x80000000)
-
-TO_INT_LOOP (to_s16le, int16_t, TO_LE16, 0, 0x8000, 0x7fff)
-TO_INT_LOOP (to_u16le, int16_t, TO_LE16, 0x8000, 0x8000, 0x7fff)
-TO_INT_LOOP (to_s24le, int32_t, TO_LE32, 0, 0x800000, 0x7fffff)
-TO_INT_LOOP (to_u24le, int32_t, TO_LE32, 0x800000, 0x800000, 0x7fffff)
-TO_INT_LOOP (to_s32le, int32_t, TO_LE32, 0, 0x80000000, 0x7fffff80)
-TO_INT_LOOP (to_u32le, int32_t, TO_LE32, 0x80000000, 0x80000000, 0x7fffff80)
// 0x7fffff80 = largest representable floating-point value before 2^31
+static constexpr unsigned pos_range (int format)
+{
+ return (format >= FMT_S32_LE && format < FMT_S24_3LE) ? 0x7fffff80 :
+ (format >= FMT_S24_LE) ? 0x7fffff :
+ (format >= FMT_S16_LE) ? 0x7fff : 0x7f;
+}
-FROM_INT_LOOP (from_s16be, int16_t, FROM_BE16, 0, 0x8000)
-FROM_INT_LOOP (from_u16be, int16_t, FROM_BE16, 0x8000, 0x8000)
-FROM_INT_LOOP (from_s24be, int32_t, FROM_BE32, 0, 0x800000)
-FROM_INT_LOOP (from_u24be, int32_t, FROM_BE32, 0x800000, 0x800000)
-FROM_INT_LOOP (from_s32be, int32_t, FROM_BE32, 0, 0x80000000)
-FROM_INT_LOOP (from_u32be, int32_t, FROM_BE32, 0x80000000, 0x80000000)
+template<class T> T do_swap (T value) { return value; }
+template<> int16_t do_swap (int16_t value) { return bswap16 (value); }
+template<> int32_t do_swap (int32_t value) { return bswap32 (value); }
-TO_INT_LOOP (to_s16be, int16_t, TO_BE16, 0, 0x8000, 0x7fff)
-TO_INT_LOOP (to_u16be, int16_t, TO_BE16, 0x8000, 0x8000, 0x7fff)
-TO_INT_LOOP (to_s24be, int32_t, TO_BE32, 0, 0x800000, 0x7fffff)
-TO_INT_LOOP (to_u24be, int32_t, TO_BE32, 0x800000, 0x800000, 0x7fffff)
-TO_INT_LOOP (to_s32be, int32_t, TO_BE32, 0, 0x80000000, 0x7fffff80)
-TO_INT_LOOP (to_u32be, int32_t, TO_BE32, 0x80000000, 0x80000000, 0x7fffff80)
+template<int format, class Word, class Int>
+struct Convert
+{
+#ifdef WORDS_BIGENDIAN
+ static constexpr bool native_le = false;
+#else
+ static constexpr bool native_le = true;
+#endif
-typedef void (* FromFunc) (const void * in, float * out, int samples);
-typedef void (* ToFunc) (const float * in, void * out, int samples);
+ static Int to_int (Word value)
+ {
+ if (is_le (format) ^ native_le)
+ value = do_swap (value);
+ if (is_signed (format))
+ value += neg_range (format);
+ if (format >= FMT_S24_LE && format <= FMT_U24_BE)
+ value &= 0xffffff; /* ignore high byte */
+
+ return value - neg_range (format);
+ }
-static const struct
+ static Word to_word (Int value)
+ {
+ if (! is_signed (format))
+ value += neg_range (format);
+ if (format >= FMT_S24_LE && format <= FMT_U24_BE)
+ value &= 0xffffff; /* zero high byte */
+ if (is_le (format) ^ native_le)
+ value = do_swap (value);
+
+ return value;
+ }
+};
+
+template<int format>
+struct Convert<format, packed24_t, int32_t>
+{
+ static int32_t to_int (packed24_t value)
+ {
+ uint8_t hi, mid, lo;
+
+ if (is_le (format))
+ hi = value.b[2], mid = value.b[1], lo = value.b[0];
+ else
+ hi = value.b[0], mid = value.b[1], lo = value.b[2];
+
+ if (! is_signed (format))
+ hi -= 0x80;
+
+ return (int8_t (hi) << 16) | (mid << 8) | lo;
+ }
+
+ static packed24_t to_word (int32_t value)
+ {
+ auto hi = uint8_t (value >> 16),
+ mid = uint8_t (value >> 8),
+ lo = uint8_t (value);
+
+ if (! is_signed (format))
+ hi += 0x80;
+
+ if (is_le (format))
+ return {{lo, mid, hi}};
+ else
+ return {{hi, mid, lo}};
+ }
+};
+
+template<int format, class Word, class Int = Word>
+void from_int_loop (const void * in_, float * out, int samples)
{
- int format;
- FromFunc from;
- ToFunc to;
+ auto in = (const Word *) in_;
+ auto end = in + samples;
+ while (in < end)
+ {
+ Int value = Convert<format, Word, Int>::to_int (* in ++);
+ * out ++ = value * (1.0f / neg_range (format));
+ }
}
-convert_table [] =
+
+template<int format, class Word, class Int = Word>
+void to_int_loop (const float * in, void * out_, int samples)
{
- {FMT_S8, (FromFunc) from_s8, (ToFunc) to_s8},
- {FMT_U8, (FromFunc) from_u8, (ToFunc) to_u8},
-
- {FMT_S16_LE, (FromFunc) from_s16le, (ToFunc) to_s16le},
- {FMT_U16_LE, (FromFunc) from_u16le, (ToFunc) to_u16le},
- {FMT_S24_LE, (FromFunc) from_s24le, (ToFunc) to_s24le},
- {FMT_U24_LE, (FromFunc) from_u24le, (ToFunc) to_u24le},
- {FMT_S32_LE, (FromFunc) from_s32le, (ToFunc) to_s32le},
- {FMT_U32_LE, (FromFunc) from_u32le, (ToFunc) to_u32le},
-
- {FMT_S16_BE, (FromFunc) from_s16be, (ToFunc) to_s16be},
- {FMT_U16_BE, (FromFunc) from_u16be, (ToFunc) to_u16be},
- {FMT_S24_BE, (FromFunc) from_s24be, (ToFunc) to_s24be},
- {FMT_U24_BE, (FromFunc) from_u24be, (ToFunc) to_u24be},
- {FMT_S32_BE, (FromFunc) from_s32be, (ToFunc) to_s32be},
- {FMT_U32_BE, (FromFunc) from_u32be, (ToFunc) to_u32be},
-};
+ auto end = in + samples;
+ auto out = (Word *) out_;
+ while (in < end)
+ {
+ float f = (* in ++) * neg_range (format);
+ f = aud::clamp (f, -(float) neg_range (format), (float) pos_range (format));
+ * out ++ = Convert<format, Word, Int>::to_word (lrintf (f));
+ }
+}
EXPORT void audio_from_int (const void * in, int format, float * out, int samples)
{
- for (auto & conv : convert_table)
+ switch (format)
{
- if (conv.format == format)
- {
- conv.from (in, out, samples);
- break;
- }
+ case FMT_S8: from_int_loop<FMT_S8, int8_t> (in, out, samples); break;
+ case FMT_U8: from_int_loop<FMT_U8, int8_t> (in, out, samples); break;
+
+ case FMT_S16_LE: from_int_loop<FMT_S16_LE, int16_t> (in, out, samples); break;
+ case FMT_S16_BE: from_int_loop<FMT_S16_BE, int16_t> (in, out, samples); break;
+ case FMT_U16_LE: from_int_loop<FMT_U16_LE, int16_t> (in, out, samples); break;
+ case FMT_U16_BE: from_int_loop<FMT_U16_BE, int16_t> (in, out, samples); break;
+
+ case FMT_S24_LE: from_int_loop<FMT_S24_LE, int32_t> (in, out, samples); break;
+ case FMT_S24_BE: from_int_loop<FMT_S24_BE, int32_t> (in, out, samples); break;
+ case FMT_U24_LE: from_int_loop<FMT_U24_LE, int32_t> (in, out, samples); break;
+ case FMT_U24_BE: from_int_loop<FMT_U24_BE, int32_t> (in, out, samples); break;
+
+ case FMT_S32_LE: from_int_loop<FMT_S32_LE, int32_t> (in, out, samples); break;
+ case FMT_S32_BE: from_int_loop<FMT_S32_BE, int32_t> (in, out, samples); break;
+ case FMT_U32_LE: from_int_loop<FMT_U32_LE, int32_t> (in, out, samples); break;
+ case FMT_U32_BE: from_int_loop<FMT_U32_BE, int32_t> (in, out, samples); break;
+
+ case FMT_S24_3LE: from_int_loop<FMT_S24_3LE, packed24_t, int32_t> (in, out, samples); break;
+ case FMT_S24_3BE: from_int_loop<FMT_S24_3BE, packed24_t, int32_t> (in, out, samples); break;
+ case FMT_U24_3LE: from_int_loop<FMT_U24_3LE, packed24_t, int32_t> (in, out, samples); break;
+ case FMT_U24_3BE: from_int_loop<FMT_U24_3BE, packed24_t, int32_t> (in, out, samples); break;
}
}
@@ -226,13 +289,30 @@ EXPORT void audio_to_int (const float * in, void * out, int format, int samples)
int save = fegetround ();
fesetround (FE_TONEAREST);
- for (auto & conv : convert_table)
+ switch (format)
{
- if (conv.format == format)
- {
- conv.to (in, out, samples);
- break;
- }
+ case FMT_S8: to_int_loop<FMT_S8, int8_t> (in, out, samples); break;
+ case FMT_U8: to_int_loop<FMT_U8, int8_t> (in, out, samples); break;
+
+ case FMT_S16_LE: to_int_loop<FMT_S16_LE, int16_t> (in, out, samples); break;
+ case FMT_S16_BE: to_int_loop<FMT_S16_BE, int16_t> (in, out, samples); break;
+ case FMT_U16_LE: to_int_loop<FMT_U16_LE, int16_t> (in, out, samples); break;
+ case FMT_U16_BE: to_int_loop<FMT_U16_BE, int16_t> (in, out, samples); break;
+
+ case FMT_S24_LE: to_int_loop<FMT_S24_LE, int32_t> (in, out, samples); break;
+ case FMT_S24_BE: to_int_loop<FMT_S24_BE, int32_t> (in, out, samples); break;
+ case FMT_U24_LE: to_int_loop<FMT_U24_LE, int32_t> (in, out, samples); break;
+ case FMT_U24_BE: to_int_loop<FMT_U24_BE, int32_t> (in, out, samples); break;
+
+ case FMT_S32_LE: to_int_loop<FMT_S32_LE, int32_t> (in, out, samples); break;
+ case FMT_S32_BE: to_int_loop<FMT_S32_BE, int32_t> (in, out, samples); break;
+ case FMT_U32_LE: to_int_loop<FMT_U32_LE, int32_t> (in, out, samples); break;
+ case FMT_U32_BE: to_int_loop<FMT_U32_BE, int32_t> (in, out, samples); break;
+
+ case FMT_S24_3LE: to_int_loop<FMT_S24_3LE, packed24_t, int32_t> (in, out, samples); break;
+ case FMT_S24_3BE: to_int_loop<FMT_S24_3BE, packed24_t, int32_t> (in, out, samples); break;
+ case FMT_U24_3LE: to_int_loop<FMT_U24_3LE, packed24_t, int32_t> (in, out, samples); break;
+ case FMT_U24_3BE: to_int_loop<FMT_U24_3BE, packed24_t, int32_t> (in, out, samples); break;
}
fesetround (save);
diff --git a/src/libaudcore/audio.h.in b/src/libaudcore/audio.h.in
index e49302f..c5b41be 100644
--- a/src/libaudcore/audio.h.in
+++ b/src/libaudcore/audio.h.in
@@ -22,13 +22,13 @@
#define AUD_MAX_CHANNELS 10
-/* 24-bit integer samples are padded to 32-bit; high byte is always 0 */
enum {
FMT_FLOAT,
FMT_S8, FMT_U8,
FMT_S16_LE, FMT_S16_BE, FMT_U16_LE, FMT_U16_BE,
- FMT_S24_LE, FMT_S24_BE, FMT_U24_LE, FMT_U24_BE,
- FMT_S32_LE, FMT_S32_BE, FMT_U32_LE, FMT_U32_BE};
+ FMT_S24_LE, FMT_S24_BE, FMT_U24_LE, FMT_U24_BE, /* padded to 4 bytes */
+ FMT_S32_LE, FMT_S32_BE, FMT_U32_LE, FMT_U32_BE,
+ FMT_S24_3LE, FMT_S24_3BE, FMT_U24_3LE, FMT_U24_3BE }; /* packed in 3 bytes */
struct ReplayGainInfo {
float track_gain; /* dB */
@@ -77,6 +77,8 @@ constexpr uint64_t bswap64 (uint64_t x)
#define FMT_U24_NE FMT_U24_BE
#define FMT_S32_NE FMT_S32_BE
#define FMT_U32_NE FMT_U32_BE
+#define FMT_S24_3NE FMT_S24_3BE
+#define FMT_U24_3NE FMT_U24_3BE
#ifdef WANT_AUD_BSWAP
#define FROM_BE16(x) (x)
@@ -101,6 +103,8 @@ constexpr uint64_t bswap64 (uint64_t x)
#define FMT_U24_NE FMT_U24_LE
#define FMT_S32_NE FMT_S32_LE
#define FMT_U32_NE FMT_U32_LE
+#define FMT_S24_3NE FMT_S24_3LE
+#define FMT_U24_3NE FMT_U24_3LE
#ifdef WANT_AUD_BSWAP
#define FROM_BE16(x) (bswap16 (x))
@@ -119,7 +123,10 @@ constexpr uint64_t bswap64 (uint64_t x)
#endif
-#define FMT_SIZEOF(f) ((f) == FMT_FLOAT ? sizeof (float) : (f) <= FMT_U8 ? 1 : (f) <= FMT_U16_BE ? 2 : 4)
+#define FMT_SIZEOF(f) (((f) >= FMT_S24_3LE) ? 3 : \
+ ((f) >= FMT_S24_LE) ? 4 : \
+ ((f) >= FMT_S16_LE) ? 2 : \
+ ((f) >= FMT_S8) ? 1 : sizeof (float))
void audio_interlace (const void * const * in, int format, int channels, void * out, int frames);
void audio_deinterlace (const void * in, int format, int channels, void * const * out, int frames);
diff --git a/src/libaudcore/audstrings.cc b/src/libaudcore/audstrings.cc
index 6a89121..6d971b9 100644
--- a/src/libaudcore/audstrings.cc
+++ b/src/libaudcore/audstrings.cc
@@ -55,7 +55,13 @@ static const char uri_legal_table[256] =
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0"
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0"
"\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x1\x1\x1" // '-' '.' '/'
+#ifdef _WIN32
+ /* We assume ':' is used with a "reserved purpose" (i.e. drive letter).
+ * This assumption might need to be reconsidered for non-file URIs. */
+ "\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x0\x0\x0\x0\x0" // 0-9 ':'
+#else
"\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x0\x0\x0\x0\x0\x0" // 0-9
+#endif
"\x0\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1" // A-O
"\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x0\x0\x0\x0\x1" // P-Z '_'
"\x0\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1\x1" // a-o
@@ -314,6 +320,37 @@ EXPORT StringBuf str_tolower_utf8 (const char * str)
return buf;
}
+EXPORT StringBuf str_toupper (const char * str)
+{
+ StringBuf buf (strlen (str));
+ char * set = buf;
+
+ while (* str)
+ * set ++ = g_ascii_toupper (* str ++);
+
+ return buf;
+}
+
+EXPORT StringBuf str_toupper_utf8 (const char * str)
+{
+ StringBuf buf (6 * strlen (str));
+ char * set = buf;
+ gunichar c;
+
+ while ((c = g_utf8_get_char (str)))
+ {
+ if (c < 128)
+ * set ++ = g_ascii_toupper (c);
+ else
+ set += g_unichar_to_utf8 (g_unichar_toupper (c), set);
+
+ str = g_utf8_next_char (str);
+ }
+
+ buf.resize (set - buf);
+ return buf;
+}
+
EXPORT void str_replace_char (char * string, char old_c, char new_c)
{
while ((string = strchr (string, old_c)))
@@ -1057,15 +1094,20 @@ EXPORT StringBuf double_array_to_str (const double * array, int count)
EXPORT StringBuf str_format_time (int64_t milliseconds)
{
+ bool neg = milliseconds < 0;
+
+ if (neg)
+ milliseconds *= -1;
+
int hours = milliseconds / 3600000;
int minutes = milliseconds / 60000;
int seconds = (milliseconds / 1000) % 60;
if (hours && aud_get_bool (nullptr, "show_hours"))
- return str_printf ("%d:%02d:%02d", hours, minutes % 60, seconds);
+ return str_printf ("%s%d:%02d:%02d", neg ? "- " : "", hours, minutes % 60, seconds);
else
{
bool zero = aud_get_bool (nullptr, "leading_zero");
- return str_printf (zero ? "%02d:%02d" : "%d:%02d", minutes, seconds);
+ return str_printf (zero ? "%s%02d:%02d" : "%s%d:%02d", neg ? "- " : "", minutes, seconds);
}
}
diff --git a/src/libaudcore/audstrings.h b/src/libaudcore/audstrings.h
index 679fe5f..8c956f0 100644
--- a/src/libaudcore/audstrings.h
+++ b/src/libaudcore/audstrings.h
@@ -34,7 +34,11 @@ int strlen_bounded (const char * s, int len = -1);
StringBuf str_copy (const char * s, int len = -1);
StringBuf str_concat (const std::initializer_list<const char *> & strings);
+#ifdef _WIN32
+StringBuf str_printf (const char * format, ...) __attribute__ ((__format__ (gnu_printf, 1, 2)));
+#else
StringBuf str_printf (const char * format, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
+#endif
StringBuf str_vprintf (const char * format, va_list args);
bool str_has_prefix_nocase (const char * str, const char * prefix);
@@ -52,6 +56,8 @@ static inline char * strstr_nocase_utf8 (char * haystack, const char * needle)
StringBuf str_tolower (const char * str);
StringBuf str_tolower_utf8 (const char * str);
+StringBuf str_toupper (const char * str);
+StringBuf str_toupper_utf8 (const char * str);
void str_replace_char (char * string, char old_c, char new_c);
diff --git a/src/libaudcore/charset.cc b/src/libaudcore/charset.cc
index 8843979..5a4dba6 100644
--- a/src/libaudcore/charset.cc
+++ b/src/libaudcore/charset.cc
@@ -190,7 +190,7 @@ EXPORT StringBuf str_to_utf8 (StringBuf && str)
return std::move (str);
}
-static void chardet_update ()
+static void chardet_update (void * = nullptr, void * = nullptr)
{
String region = aud_get_str (nullptr, "chardet_detector");
String fallbacks = aud_get_str (nullptr, "chardet_fallback");
@@ -202,14 +202,14 @@ void chardet_init ()
{
chardet_update ();
- hook_associate ("set chardet_detector", (HookFunction) chardet_update, nullptr);
- hook_associate ("set chardet_fallback", (HookFunction) chardet_update, nullptr);
+ hook_associate ("set chardet_detector", chardet_update, nullptr);
+ hook_associate ("set chardet_fallback", chardet_update, nullptr);
}
void chardet_cleanup ()
{
- hook_dissociate ("set chardet_detector", (HookFunction) chardet_update);
- hook_dissociate ("set chardet_fallback", (HookFunction) chardet_update);
+ hook_dissociate ("set chardet_detector", chardet_update);
+ hook_dissociate ("set chardet_fallback", chardet_update);
set_charsets (nullptr, nullptr);
}
diff --git a/src/libaudcore/config.cc b/src/libaudcore/config.cc
index 2718a6b..84bd0f4 100644
--- a/src/libaudcore/config.cc
+++ b/src/libaudcore/config.cc
@@ -72,7 +72,7 @@ static const char * const core_defaults[] = {
"output_bit_depth", "-1",
"output_buffer_size", "500",
"record_stream", aud::numeric_string<(int) OutputStream::AfterReplayGain>::str,
- "replay_gain_album", "FALSE",
+ "replay_gain_mode", aud::numeric_string<(int) ReplayGainMode::Track>::str,
"replay_gain_preamp", "0",
"soft_clipping", "FALSE",
"software_volume_control", "FALSE",
@@ -118,66 +118,53 @@ struct ConfigItem {
String value;
};
-struct ConfigNode {
- MultiHash::Node node;
- ConfigItem item;
-};
+struct ConfigNode;
-struct ConfigOp {
+// combined Data and Operation class
+struct ConfigOp
+{
OpType type;
const char * section;
const char * key;
String value;
unsigned hash;
bool result;
-};
-struct SaveState {
- Index<ConfigItem> list;
+ ConfigNode * add (const ConfigOp *);
+ bool found (ConfigNode * node);
};
-static int item_compare (const ConfigItem & a, const ConfigItem & b)
-{
- if (a.section == b.section)
- return strcmp (a.key, b.key);
- else
- return strcmp (a.section, b.section);
-}
-
-static bool config_node_match (const MultiHash::Node * node0, const void * data)
+struct ConfigNode : public MultiHash::Node, public ConfigItem
{
- const ConfigNode * node = (const ConfigNode *) node0;
- const ConfigOp * op = (const ConfigOp *) data;
-
- return ! strcmp (node->item.section, op->section) && ! strcmp (node->item.key, op->key);
-}
+ bool match (const ConfigOp * op) const
+ { return ! strcmp (section, op->section) && ! strcmp (key, op->key); }
+};
-static MultiHash defaults (config_node_match);
-static MultiHash config (config_node_match);
+typedef MultiHash_T<ConfigNode, ConfigOp> ConfigTable;
-static volatile bool modified;
+static ConfigTable s_defaults, s_config;
+static volatile bool s_modified;
-static MultiHash::Node * add_cb (const void * data, void * state)
+ConfigNode * ConfigOp::add (const ConfigOp *)
{
- ConfigOp * op = (ConfigOp *) state;
-
- switch (op->type)
+ switch (type)
{
case OP_IS_DEFAULT:
- op->result = ! op->value[0]; /* empty string is default */
+ result = ! value[0]; /* empty string is default */
return nullptr;
case OP_SET:
- op->result = true;
- modified = true;
+ result = true;
+ s_modified = true;
+ // fall-through
case OP_SET_NO_FLAG:
{
ConfigNode * node = new ConfigNode;
- node->item.section = String (op->section);
- node->item.key = String (op->key);
- node->item.value = op->value;
- return (MultiHash::Node *) node;
+ node->section = String (section);
+ node->key = String (key);
+ node->value = value;
+ return node;
}
default:
@@ -185,33 +172,32 @@ static MultiHash::Node * add_cb (const void * data, void * state)
}
}
-static bool action_cb (MultiHash::Node * node0, void * state)
+bool ConfigOp::found (ConfigNode * node)
{
- ConfigNode * node = (ConfigNode *) node0;
- ConfigOp * op = (ConfigOp *) state;
-
- switch (op->type)
+ switch (type)
{
case OP_IS_DEFAULT:
- op->result = ! strcmp (node->item.value, op->value);
+ result = ! strcmp (node->value, value);
return false;
case OP_GET:
- op->value = node->item.value;
+ value = node->value;
return false;
case OP_SET:
- op->result = !! strcmp (node->item.value, op->value);
- if (op->result)
- modified = true;
+ result = !! strcmp (node->value, value);
+ if (result)
+ s_modified = true;
+ // fall-through
case OP_SET_NO_FLAG:
- node->item.value = op->value;
+ node->value = value;
return false;
case OP_CLEAR:
- op->result = true;
- modified = true;
+ result = true;
+ s_modified = true;
+ // fall-through
case OP_CLEAR_NO_FLAG:
delete node;
@@ -222,14 +208,14 @@ static bool action_cb (MultiHash::Node * node0, void * state)
}
}
-static bool config_op_run (ConfigOp * op, MultiHash * table)
+static bool config_op_run (ConfigOp & op, ConfigTable & table)
{
- if (! op->hash)
- op->hash = str_calc_hash (op->section) + str_calc_hash (op->key);
+ if (! op.hash)
+ op.hash = str_calc_hash (op.section) + str_calc_hash (op.key);
- op->result = false;
- table->lookup (op, op->hash, add_cb, action_cb, op);
- return op->result;
+ op.result = false;
+ table.lookup (& op, op.hash, op);
+ return op.result;
}
class ConfigParser : public IniParser
@@ -246,7 +232,7 @@ private:
return;
ConfigOp op = {OP_SET_NO_FLAG, section, key, String (value)};
- config_op_run (& op, & config);
+ config_op_run (op, s_config);
}
};
@@ -263,29 +249,35 @@ void config_load ()
}
aud_config_set_defaults (nullptr, core_defaults);
-}
-static bool add_to_save_list (MultiHash::Node * node0, void * state0)
-{
- ConfigNode * node = (ConfigNode *) node0;
- SaveState * state = (SaveState *) state0;
-
- state->list.append (node->item);
-
- modified = false;
-
- return false;
+ /* migrate from previous versions */
+ if (aud_get_bool (0, "replay_gain_album"))
+ {
+ aud_set_str (0, "replay_gain_album", "");
+ aud_set_int (0, "replay_gain_mode", (int) ReplayGainMode::Album);
+ }
}
void config_save ()
{
- if (! modified)
+ if (! s_modified)
return;
- SaveState state = SaveState ();
+ Index<ConfigItem> list;
- config.iterate (add_to_save_list, & state);
- state.list.sort (item_compare);
+ s_config.iterate ([&] (ConfigNode * node) {
+ list.append (* node);
+
+ s_modified = false; // must be inside MultiHash lock
+ return false;
+ });
+
+ list.sort ([] (const ConfigItem & a, const ConfigItem & b) {
+ if (a.section == b.section)
+ return strcmp (a.key, b.key);
+ else
+ return strcmp (a.section, b.section);
+ });
StringBuf path = filename_to_uri (aud_get_path (AudPath::UserDir));
path.insert (-1, "/config");
@@ -296,7 +288,7 @@ void config_save ()
if (! file)
goto FAILED;
- for (const ConfigItem & item : state.list)
+ for (const ConfigItem & item : list)
{
if (item.section != current_heading)
{
@@ -332,15 +324,14 @@ EXPORT void aud_config_set_defaults (const char * section, const char * const *
break;
ConfigOp op = {OP_SET_NO_FLAG, section, name, String (value)};
- config_op_run (& op, & defaults);
+ config_op_run (op, s_defaults);
}
}
void config_cleanup ()
{
- ConfigOp op = {OP_CLEAR_NO_FLAG};
- config.iterate (action_cb, & op);
- defaults.iterate (action_cb, & op);
+ s_config.clear ();
+ s_defaults.clear ();
}
EXPORT void aud_set_str (const char * section, const char * name, const char * value)
@@ -348,10 +339,10 @@ EXPORT void aud_set_str (const char * section, const char * name, const char * v
assert (name && value);
ConfigOp op = {OP_IS_DEFAULT, section ? section : DEFAULT_SECTION, name, String (value)};
- bool is_default = config_op_run (& op, & defaults);
+ bool is_default = config_op_run (op, s_defaults);
op.type = is_default ? OP_CLEAR : OP_SET;
- bool changed = config_op_run (& op, & config);
+ bool changed = config_op_run (op, s_config);
if (changed && ! section)
event_queue (str_concat ({"set ", name}), nullptr);
@@ -362,10 +353,10 @@ EXPORT String aud_get_str (const char * section, const char * name)
assert (name);
ConfigOp op = {OP_GET, section ? section : DEFAULT_SECTION, name};
- config_op_run (& op, & config);
+ config_op_run (op, s_config);
if (! op.value)
- config_op_run (& op, & defaults);
+ config_op_run (op, s_defaults);
return op.value ? op.value : String ("");
}
@@ -380,6 +371,11 @@ EXPORT bool aud_get_bool (const char * section, const char * name)
return ! strcmp (aud_get_str (section, name), "TRUE");
}
+EXPORT void aud_toggle_bool (const char * section, const char * name)
+{
+ aud_set_bool (section, name, ! aud_get_bool (section, name));
+}
+
EXPORT void aud_set_int (const char * section, const char * name, int value)
{
aud_set_str (section, name, int_to_str (value));
diff --git a/src/libaudcore/drct.cc b/src/libaudcore/drct.cc
index d8a0589..2ad76eb 100644
--- a/src/libaudcore/drct.cc
+++ b/src/libaudcore/drct.cc
@@ -21,6 +21,7 @@
#include "hook.h"
#include "i18n.h"
+#include "interface.h"
#include "internal.h"
#include "playlist-internal.h"
#include "plugins-internal.h"
@@ -44,9 +45,9 @@ EXPORT void aud_drct_play ()
}
else
{
- int playlist = aud_playlist_get_active ();
- aud_playlist_set_position (playlist, aud_playlist_get_position (playlist));
- aud_playlist_play (playlist);
+ auto playlist = Playlist::active_playlist ();
+ playlist.set_position (playlist.get_position ());
+ playlist.start_playback ();
}
}
@@ -58,22 +59,15 @@ EXPORT void aud_drct_play_pause ()
aud_drct_play ();
}
-EXPORT void aud_drct_stop ()
-{
- aud_playlist_play (-1);
-}
-
EXPORT int aud_drct_get_position ()
{
- int playlist = aud_playlist_get_playing ();
- return aud_playlist_get_position (playlist);
+ return Playlist::playing_playlist ().get_position ();
}
EXPORT String aud_drct_get_filename ()
{
- int playlist = aud_playlist_get_playing ();
- int position = aud_playlist_get_position (playlist);
- return aud_playlist_entry_get_filename (playlist, position);
+ auto playlist = Playlist::playing_playlist ();
+ return playlist.entry_filename (playlist.get_position ());
}
/* --- RECORDING CONTROL --- */
@@ -85,10 +79,25 @@ static PluginHandle * record_plugin;
static bool record_plugin_watcher (PluginHandle *, void *)
{
+ if (! aud_drct_get_record_enabled ())
+ aud_set_bool (nullptr, "record", false);
+
hook_call ("enable record", nullptr);
return true;
}
+static void validate_record_setting (void *, void *)
+{
+ if (aud_get_bool (nullptr, "record") && ! aud_drct_get_record_enabled ())
+ {
+ /* User attempted to start recording without a recording plugin enabled.
+ * This is probably not the best response, but better than nothing. */
+ aud_set_bool (nullptr, "record", false);
+ aud_ui_show_error (_("Stream recording must be configured in Audio "
+ "Settings before it can be used."));
+ }
+}
+
void record_init ()
{
auto plugin = aud_plugin_lookup_basename ("filewriter");
@@ -97,10 +106,17 @@ void record_init ()
record_plugin = plugin;
aud_plugin_add_watch (plugin, record_plugin_watcher, nullptr);
}
+
+ if (! aud_drct_get_record_enabled ())
+ aud_set_bool (nullptr, "record", false);
+
+ hook_associate ("set record", validate_record_setting, nullptr);
}
void record_cleanup ()
{
+ hook_dissociate ("set record", validate_record_setting);
+
if (record_plugin)
{
aud_plugin_remove_watch (record_plugin, record_plugin_watcher, nullptr);
@@ -178,28 +194,28 @@ EXPORT void aud_drct_set_volume_balance (int balance)
EXPORT void aud_drct_pl_next ()
{
- int playlist = aud_playlist_get_playing ();
- if (playlist < 0)
- playlist = aud_playlist_get_active ();
+ PlaylistEx playlist = Playlist::playing_playlist ();
+ if (playlist == Playlist ())
+ playlist = Playlist::active_playlist ();
- playlist_next_song (playlist, aud_get_bool (nullptr, "repeat"));
+ playlist.next_song (aud_get_bool (nullptr, "repeat"));
}
EXPORT void aud_drct_pl_prev ()
{
- int playlist = aud_playlist_get_playing ();
- if (playlist < 0)
- playlist = aud_playlist_get_active ();
+ PlaylistEx playlist = Playlist::playing_playlist ();
+ if (playlist == Playlist ())
+ playlist = Playlist::active_playlist ();
- playlist_prev_song (playlist);
+ playlist.prev_song ();
}
static void add_list (Index<PlaylistAddItem> && items, int at, bool to_temp, bool play)
{
if (to_temp)
- aud_playlist_set_active (aud_playlist_get_temporary ());
+ Playlist::temporary_playlist ().activate ();
- aud_playlist_entry_insert_batch (aud_playlist_get_active (), at, std::move (items), play);
+ Playlist::active_playlist ().insert_items (at, std::move (items), play);
}
EXPORT void aud_drct_pl_add (const char * filename, int at)
diff --git a/src/libaudcore/drct.h b/src/libaudcore/drct.h
index a8ce2dc..a486660 100644
--- a/src/libaudcore/drct.h
+++ b/src/libaudcore/drct.h
@@ -69,6 +69,10 @@ void aud_drct_get_ab_repeat (int & a, int & b);
/* --- RECORDING CONTROL --- */
+/* Note that the behavior of these functions has changed in Audacious 3.9;
+ * "enabled" now means only that a plugin has been selected for recording.
+ * The "record" config option is now used to start/stop recording. */
+
/* Returns the output plugin that will be used for recording, or null if none is
* available. Connect to the "enable record" hook to monitor changes. */
PluginHandle * aud_drct_get_record_plugin ();
@@ -77,8 +81,8 @@ PluginHandle * aud_drct_get_record_plugin ();
* "enable record" hook to monitor changes. */
bool aud_drct_get_record_enabled ();
-/* Enables or disables output recording. If playback is active, recording
- * begins immediately. Returns true on success, otherwise false. */
+/* Enables or disables output recording (but does not actually start recording).
+ * Returns true on success, otherwise false. */
bool aud_drct_enable_record (bool enable);
/* --- VOLUME CONTROL --- */
diff --git a/src/libaudcore/equalizer-preset.cc b/src/libaudcore/equalizer-preset.cc
index 699d0fb..c57d5f0 100644
--- a/src/libaudcore/equalizer-preset.cc
+++ b/src/libaudcore/equalizer-preset.cc
@@ -18,13 +18,12 @@
* the use of this software.
*/
+#define AUD_GLIB_INTEGRATION
#include "equalizer.h"
#include <math.h>
#include <string.h>
-#include <glib.h> /* for GKeyFile */
-
#include "audstrings.h"
#include "runtime.h"
#include "vfs.h"
@@ -49,7 +48,7 @@ EXPORT Index<EqualizerPreset> aud_eq_read_presets (const char * basename)
for (int p = 0;; p ++)
{
- char * name = g_key_file_get_string (rcfile, "Presets", str_printf ("Preset%d", p), nullptr);
+ CharPtr name (g_key_file_get_string (rcfile, "Presets", str_printf ("Preset%d", p), nullptr));
if (! name)
break;
@@ -58,8 +57,6 @@ EXPORT Index<EqualizerPreset> aud_eq_read_presets (const char * basename)
for (int i = 0; i < AUD_EQ_NBANDS; i++)
preset.bands[i] = g_key_file_get_double (rcfile, name, str_printf ("Band%d", i), nullptr);
-
- g_free (name);
}
g_key_file_free (rcfile);
@@ -83,13 +80,12 @@ EXPORT bool aud_eq_write_presets (const Index<EqualizerPreset> & list, const cha
}
size_t len;
- char * data = g_key_file_to_data (rcfile, & len, nullptr);
+ CharPtr data (g_key_file_to_data (rcfile, & len, nullptr));
StringBuf filename = filename_build ({aud_get_path (AudPath::UserDir), basename});
bool success = g_file_set_contents (filename, data, len, nullptr);
g_key_file_free (rcfile);
- g_free (data);
return success;
}
@@ -166,12 +162,11 @@ EXPORT bool aud_save_preset_file (const EqualizerPreset & preset, VFSFile & file
str_printf ("Band%d", i), preset.bands[i]);
size_t len;
- char * data = g_key_file_to_data (rcfile, & len, nullptr);
+ CharPtr data (g_key_file_to_data (rcfile, & len, nullptr));
bool success = (file.fwrite (data, 1, len) == (int64_t) len);
g_key_file_free (rcfile);
- g_free (data);
return success;
}
diff --git a/src/libaudcore/hook.cc b/src/libaudcore/hook.cc
index 3b8b80e..e9dd695 100644
--- a/src/libaudcore/hook.cc
+++ b/src/libaudcore/hook.cc
@@ -127,15 +127,15 @@ DONE:
pthread_mutex_unlock (& mutex);
}
-void leak_cb (const String & name, HookList & list, void *)
-{
- AUDWARN ("Hook not disconnected: %s (%d)\n", (const char *) name, list.items.len ());
-}
-
void hook_cleanup ()
{
pthread_mutex_lock (& mutex);
- hooks.iterate (leak_cb, nullptr);
+
+ hooks.iterate ([] (const String & name, HookList & list) {
+ AUDWARN ("Hook not disconnected: %s (%d)\n", (const char *) name, list.items.len ());
+ });
+
hooks.clear ();
+
pthread_mutex_unlock (& mutex);
}
diff --git a/src/libaudcore/hook.h b/src/libaudcore/hook.h
index 6f8ed4c..147591a 100644
--- a/src/libaudcore/hook.h
+++ b/src/libaudcore/hook.h
@@ -106,7 +106,7 @@ void event_queue (const char * name, void * data, EventDestroyFunc destroy = nul
/* Cancels pending hook calls matching <name> and <data>. If <data> is nullptr,
* all hook calls matching <name> are canceled. */
-void event_queue_cancel (const char * name, void * data);
+void event_queue_cancel (const char * name, void * data = nullptr);
/* Convenience wrapper for C++ classes. Allows non-static member functions to
* be used as hook callbacks. The HookReceiver should be made a member of the
diff --git a/src/libaudcore/index.h b/src/libaudcore/index.h
index 68957c3..92be2d9 100644
--- a/src/libaudcore/index.h
+++ b/src/libaudcore/index.h
@@ -173,19 +173,25 @@ public:
// func(val) returns true to remove val, false to keep it
template<class F>
- void remove_if (F func, bool clear_if_empty = false)
+ bool remove_if (F func, bool clear_if_empty = false)
{
T * iter = begin ();
+ bool changed = false;
while (iter != end ())
{
if (func (* iter))
+ {
remove (iter - begin (), 1);
+ changed = true;
+ }
else
iter ++;
}
if (clear_if_empty && ! len ())
clear ();
+
+ return changed;
}
// compare(a, b) returns <0 if a<b, 0 if a=b, >0 if a>b
diff --git a/src/libaudcore/interface.cc b/src/libaudcore/interface.cc
index 2d2db3d..f83a53e 100644
--- a/src/libaudcore/interface.cc
+++ b/src/libaudcore/interface.cc
@@ -21,6 +21,7 @@
#include "internal.h"
#include <assert.h>
+#include <glib.h>
#include "drct.h"
#include "hook.h"
@@ -126,8 +127,7 @@ EXPORT void aud_ui_show_error (const char * message)
if (aud_get_headless_mode ())
AUDERR ("%s\n", message);
else
- event_queue ("ui show error", String::raw_get (message),
- (EventDestroyFunc) String::raw_unref);
+ event_queue ("ui show error", g_strdup (message), g_free);
}
PluginHandle * iface_plugin_get_current ()
diff --git a/src/libaudcore/internal.h b/src/libaudcore/internal.h
index 534ba69..bf2216c 100644
--- a/src/libaudcore/internal.h
+++ b/src/libaudcore/internal.h
@@ -128,6 +128,18 @@ StringBuf strip_subtune (const char * filename);
unsigned int32_hash (unsigned val);
unsigned ptr_hash (const void * ptr);
+struct IntHashKey
+{
+ int val;
+
+ constexpr IntHashKey (int val) :
+ val (val) {}
+ operator int () const
+ { return val; }
+ unsigned hash () const
+ { return int32_hash (val); }
+};
+
/* vis-runner.cc */
void vis_runner_start_stop (bool playing, bool paused);
void vis_runner_pass_audio (int time, const Index<float> & data, int channels, int rate);
diff --git a/src/libaudcore/list.h b/src/libaudcore/list.h
index 623603d..a3551f1 100644
--- a/src/libaudcore/list.h
+++ b/src/libaudcore/list.h
@@ -74,6 +74,18 @@ public:
void clear ()
{ ListBase::clear (destroy); }
+ template<class MatchFunc>
+ C * find (MatchFunc match)
+ {
+ for (C * node = head (); node; node = next (node))
+ {
+ if (match (* node))
+ return node;
+ }
+
+ return nullptr;
+ }
+
private:
static void destroy (ListNode * node)
{ delete (C *) node; }
diff --git a/src/libaudcore/mainloop.cc b/src/libaudcore/mainloop.cc
index a728660..397106f 100644
--- a/src/libaudcore/mainloop.cc
+++ b/src/libaudcore/mainloop.cc
@@ -46,6 +46,10 @@ struct QueuedFuncParams {
bool repeat;
};
+// Helper class that is created when the QueuedFunc is activated. The base
+// class contains the GLib implementation. The Qt implementation, which is more
+// complex, is contained in the QueuedFuncEvent and QueuedFuncTimer subclasses.
+
struct QueuedFuncHelper
{
QueuedFuncParams params;
@@ -66,47 +70,69 @@ struct QueuedFuncHelper
virtual void stop ();
private:
+ struct RunCheck;
+
int glib_source = 0;
};
-struct QueuedFuncNode : public MultiHash::Node {
+// The following hash table implements a thread-safe "registry" of active
+// QueuedFuncs. This allows a callback executing in the main thread to safely
+// look up the information it needs, without accessing the QueuedFunc directly
+// and risking use-after-free.
+
+struct QueuedFuncNode : public MultiHash::Node
+{
QueuedFunc * queued;
QueuedFuncHelper * helper;
bool can_stop;
+
+ bool match (const QueuedFunc * q) const
+ { return q == queued; }
};
-static bool match_cb (const MultiHash::Node * node_, const void * queued_)
-{
- auto node = (const QueuedFuncNode *) node_;
- return node->queued == (QueuedFunc *) queued_;
-}
+static MultiHash_T<QueuedFuncNode, QueuedFunc> func_table;
-static MultiHash func_table (match_cb);
+// Helper logic common between GLib and Qt
-void QueuedFuncHelper::run ()
+// "run" logic executed within the hash table lock
+struct QueuedFuncHelper::RunCheck
{
- struct State {
- QueuedFuncHelper * helper;
- bool valid;
- };
-
- auto found_cb = [] (MultiHash::Node * node_, void * s_)
- {
- auto node = (const QueuedFuncNode *) node_;
- auto s = (State *) s_;
-
- s->valid = (node->helper == s->helper);
+ QueuedFuncHelper * helper;
+ bool valid;
- if (! s->valid || ! s->helper->params.repeat)
- s->helper->stop ();
+ QueuedFuncNode * add (const QueuedFunc *)
+ { return nullptr; }
- return s->valid && ! s->helper->params.repeat;
- };
+ bool found (const QueuedFuncNode * node)
+ {
+ // Check that the same helper is installed as when the function was
+ // first queued. If a new helper is installed, then we are processing
+ // a "stale" event and should not run the callback.
+ valid = (node->helper == helper);
+
+ // Uninstall the QueuedFunc and helper if this is a one-time (non-
+ // periodic) callback. Do NOT uninstall the QueuedFunc if has already
+ // been reinstalled with a new helper (see previous comment).
+ bool remove = valid && ! helper->params.repeat;
+
+ // Clean up the helper if this is a one-time (non-periodic) callback OR
+ // a new helper has already been installed. No fields or methods of the
+ // helper may be accessed after this point.
+ if (! valid || ! helper->params.repeat)
+ helper->stop ();
+
+ return remove;
+ }
+};
- State s = {this, false};
- func_table.lookup (queued, ptr_hash (queued), nullptr, found_cb, & s);
+void QueuedFuncHelper::run ()
+{
+ // Check whether it's okay to run. The actual check is performed within
+ // the MultiHash lock to eliminate race conditions.
+ RunCheck r = {this, false};
+ func_table.lookup (queued, ptr_hash (queued), r);
- if (s.valid)
+ if (r.valid)
params.func (params.data);
}
@@ -115,22 +141,20 @@ void QueuedFuncHelper::start_for (QueuedFunc * queued_)
queued = queued_;
queued->_running = params.repeat;
- start ();
+ start (); // branch to GLib or Qt implementation
}
+// GLib implementation -- simple wrapper around g_timeout_add_full()
+
void QueuedFuncHelper::start ()
{
auto callback = [] (void * me) -> gboolean {
- ((QueuedFuncHelper *) me)->run ();
+ (static_cast<QueuedFuncHelper *> (me))->run ();
return G_SOURCE_CONTINUE;
};
- auto destroy = [] (void * me) {
- delete (QueuedFuncHelper *) me;
- };
-
glib_source = g_timeout_add_full (G_PRIORITY_HIGH, params.interval_ms,
- callback, this, destroy);
+ callback, this, aud::delete_obj<QueuedFuncHelper>);
}
void QueuedFuncHelper::stop ()
@@ -141,6 +165,16 @@ void QueuedFuncHelper::stop ()
#ifdef USE_QT
+// Qt implementation -- rather more complicated
+
+// One-time callbacks are implemented through custom events posted to a "router"
+// object. In this case, the QueuedFuncHelper class is the QEvent itself. The
+// router object is created in, and associated with, the main thread, thus
+// events are executed in the main thread regardless of their origin. Events
+// can be queued even before the QApplication has been created. Note that
+// QEvents cannot be cancelled once posted; therefore it's necessary to check
+// on our side that the QueuedFunc is still active when the event is received.
+
class QueuedFuncEvent : public QueuedFuncHelper, public QEvent
{
public:
@@ -168,6 +202,15 @@ void QueuedFuncEvent::start ()
QCoreApplication::postEvent (& router, this, Qt::HighEventPriority);
}
+// Periodic callbacks are implemented through QObject's timer capability. In
+// this case, the QueuedFuncHelper is a QObject that is re-associated with the
+// main thread immediately after creation. The timer is started in a roundabout
+// fashion due to the fact that QObject::startTimer() does not work until the
+// QApplication has been created. To work around this, a single QEvent is first
+// posted. When this initial event is executed (in the main thread), we know
+// that the QApplication is up. We then start the timer and run our own
+// callback from the QTimerEvents we receive.
+
class QueuedFuncTimer : public QueuedFuncHelper, public QObject
{
public:
@@ -177,9 +220,6 @@ public:
void start ()
{
moveToThread (router.thread ()); // main thread
-
- // The timer cannot be started until QCoreApplication is instantiated.
- // Send ourselves an event and wait till it comes back, then start the timer.
QCoreApplication::postEvent (this, new QEvent (QEvent::User), Qt::HighEventPriority);
}
@@ -187,14 +227,15 @@ public:
{ deleteLater (); }
protected:
- void customEvent (QEvent * event)
+ void customEvent (QEvent *)
{ startTimer (params.interval_ms); }
- void timerEvent (QTimerEvent * event)
+ void timerEvent (QTimerEvent *)
{ run (); }
};
#endif // USE_QT
+// creates the appropriate helper subclass
static QueuedFuncHelper * create_helper (const QueuedFuncParams & params)
{
#ifdef USE_QT
@@ -210,38 +251,48 @@ static QueuedFuncHelper * create_helper (const QueuedFuncParams & params)
return new QueuedFuncHelper (params);
}
-void QueuedFunc::start (const QueuedFuncParams & params)
+// "start" logic executed within the hash table lock
+struct QueuedFunc::Starter
{
- auto add_cb = [] (const void * me_, void * helper_) {
- auto me = (QueuedFunc *) me_;
- auto helper = (QueuedFuncHelper *) helper_;
+ QueuedFunc * queued;
+ QueuedFuncHelper * helper;
+ // QueuedFunc not yet installed
+ // install, then create a helper and start it
+ QueuedFuncNode * add (const QueuedFunc *)
+ {
auto node = new QueuedFuncNode;
- node->queued = me;
+ node->queued = queued;
node->helper = helper;
node->can_stop = helper->can_stop ();
- helper->start_for (me);
-
- return (MultiHash::Node *) node;
- };
+ helper->start_for (queued);
- auto replace_cb = [] (MultiHash::Node * node_, void * helper_) {
- auto node = (QueuedFuncNode *) node_;
- auto helper = (QueuedFuncHelper *) helper_;
+ return node;
+ }
+ // QueuedFunc already installed
+ // first clean up the existing helper
+ // then create a new helper and start it
+ bool found (QueuedFuncNode * node)
+ {
if (node->can_stop)
node->helper->stop ();
node->helper = helper;
node->can_stop = helper->can_stop ();
- helper->start_for (node->queued);
+ helper->start_for (queued);
return false; // do not remove
- };
+ }
+};
- func_table.lookup (this, ptr_hash (this), add_cb, replace_cb, create_helper (params));
+// common entry point used by all queue() and start() variants
+void QueuedFunc::start (const QueuedFuncParams & params)
+{
+ Starter s = {this, create_helper (params)};
+ func_table.lookup (this, ptr_hash (this), s);
}
EXPORT void QueuedFunc::queue (Func func, void * data)
@@ -261,11 +312,16 @@ EXPORT void QueuedFunc::start (int interval_ms, Func func, void * data)
start ({func, data, interval_ms, true});
}
-EXPORT void QueuedFunc::stop ()
+// "stop" logic executed within the hash table lock
+struct QueuedFunc::Stopper
{
- auto remove_cb = [] (MultiHash::Node * node_, void *) {
- auto node = (QueuedFuncNode *) node_;
+ // not installed, do nothing
+ QueuedFuncNode * add (const QueuedFunc *)
+ { return nullptr; }
+ // installed, clean up the helper and uninstall
+ bool found (QueuedFuncNode * node)
+ {
if (node->can_stop)
node->helper->stop ();
@@ -273,11 +329,17 @@ EXPORT void QueuedFunc::stop ()
delete node;
return true; // remove
- };
+ }
+};
- func_table.lookup (this, ptr_hash (this), nullptr, remove_cb, nullptr);
+EXPORT void QueuedFunc::stop ()
+{
+ Stopper s;
+ func_table.lookup (this, ptr_hash (this), s);
}
+// main loop implementation follows
+
EXPORT void mainloop_run ()
{
pthread_mutex_lock (& mainloop_mutex);
diff --git a/src/libaudcore/mainloop.h b/src/libaudcore/mainloop.h
index dabf9e3..ee78717 100644
--- a/src/libaudcore/mainloop.h
+++ b/src/libaudcore/mainloop.h
@@ -60,6 +60,9 @@ public:
{ stop (); }
private:
+ struct Starter;
+ struct Stopper;
+
bool _running = false;
void start (const QueuedFuncParams & params);
diff --git a/src/libaudcore/multihash.h b/src/libaudcore/multihash.h
index 4ddd37f..663b756 100644
--- a/src/libaudcore/multihash.h
+++ b/src/libaudcore/multihash.h
@@ -1,6 +1,6 @@
/*
* multihash.h
- * Copyright 2013-2014 John Lindgren
+ * Copyright 2013-2017 John Lindgren
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -29,11 +29,13 @@
class HashBase
{
public:
- /* Skeleton structure containing internal members of a hash node. Actual
- * node structures should be defined with Node as the first member. */
+ /* Skeleton structure containing internal members of a hash node (except for
+ * "refs", which is not used internally and included here only to fill an
+ * alignment gap). Actual node structures should subclass Node. */
struct Node {
Node * next;
unsigned hash;
+ unsigned refs;
};
/* Represents the location of a node within the table. */
@@ -143,6 +145,69 @@ private:
HashBase channels[Channels];
};
+/* Type-safe version using templates. */
+
+template<class Node_T, class Data_T>
+class MultiHash_T : private MultiHash
+{
+public:
+ // Required interfaces:
+ //
+ // class Node_T : public Node
+ // {
+ // bool match (const Data_T * data) const;
+ // };
+ //
+ // class Operation
+ // {
+ // Node_T * add (const Data_T * data);
+ // bool found (Node_T * node);
+ // };
+
+ MultiHash_T () : MultiHash (match_cb) {}
+
+ void clear ()
+ { MultiHash::iterate (remove_cb, nullptr); }
+
+ template<class Op>
+ int lookup (const Data_T * data, unsigned hash, Op & op)
+ { return MultiHash::lookup (data, hash, WrapOp<Op>::add, WrapOp<Op>::found, & op); }
+
+ template<class F>
+ void iterate (F func)
+ { MultiHash::iterate (WrapIterate<F>::run, & func); }
+
+private:
+ static bool match_cb (const Node * node, const void * data)
+ { return (static_cast<const Node_T *> (node))->match
+ (static_cast<const Data_T *> (data)); }
+
+ static bool remove_cb (Node * node, void *)
+ {
+ delete static_cast<Node_T *> (node);
+ return true;
+ }
+
+ template<class Op>
+ struct WrapOp {
+ static Node * add (const void * data, void * op)
+ { return (static_cast<Op *> (op))->add
+ (static_cast<const Data_T *> (data)); }
+ static bool found (Node * node, void * op)
+ { return (static_cast<Op *> (op))->found
+ (static_cast<Node_T *> (node)); }
+ };
+
+ template<class F>
+ struct WrapIterate {
+ static bool run (Node * node, void * func)
+ { return (* static_cast<F *> (func))
+ (static_cast<Node_T *> (node)); }
+ };
+};
+
+/* Simpler single-thread hash table. */
+
template<class Key, class Value>
class SimpleHash : private HashBase
{
@@ -156,14 +221,14 @@ public:
Value * lookup (const Key & key)
{
- Node * node = (Node *) HashBase::lookup (match_cb, & key, key.hash ());
+ auto node = static_cast<Node *> (HashBase::lookup (match_cb, & key, key.hash ()));
return node ? & node->value : nullptr;
}
Value * add (const Key & key, Value && value)
{
unsigned hash = key.hash ();
- Node * node = (Node *) HashBase::lookup (match_cb, & key, hash);
+ auto node = static_cast<Node *> (HashBase::lookup (match_cb, & key, hash));
if (node)
node->value = std::move (value);
@@ -179,7 +244,7 @@ public:
void remove (const Key & key)
{
NodeLoc loc;
- Node * node = (Node *) HashBase::lookup (match_cb, & key, key.hash (), & loc);
+ auto node = static_cast<Node *> (HashBase::lookup (match_cb, & key, key.hash (), & loc));
if (node)
{
@@ -188,18 +253,16 @@ public:
}
}
- void iterate (IterFunc func, void * state)
- {
- IterData data = {func, state};
- HashBase::iterate (iterate_cb, & data);
- }
-
void clear ()
{
HashBase::iterate (remove_cb, nullptr);
HashBase::clear ();
}
+ template<class F>
+ void iterate (F func)
+ { HashBase::iterate (WrapIterate<F>::run, & func); }
+
private:
struct Node : public HashBase::Node
{
@@ -217,21 +280,26 @@ private:
};
static bool match_cb (const HashBase::Node * node, const void * data)
- { return ((const Node *) node)->key == * (const Key *) data; }
+ { return (static_cast<const Node *> (node))->key ==
+ * static_cast<const Key *> (data); }
static bool remove_cb (HashBase::Node * node, void *)
{
- delete (Node *) node;
+ delete static_cast<Node *> (node);
return true;
}
- static bool iterate_cb (HashBase::Node * node0, void * data0)
+ // C-style callback wrapping generic iteration functor
+ template<class F>
+ struct WrapIterate
{
- Node * node = (Node *) node0;
- IterData * data = (IterData *) data0;
- data->func (node->key, node->value, data->state);
- return false;
- }
+ static bool run (HashBase::Node * node_, void * func)
+ {
+ auto node = static_cast<Node *> (node_);
+ (* static_cast<F *> (func)) (node->key, node->value);
+ return false;
+ }
+ };
};
#endif /* LIBAUDCORE_MULTIHASH_H */
diff --git a/src/libaudcore/objects.h b/src/libaudcore/objects.h
index 4b98cc6..facc0ba 100644
--- a/src/libaudcore/objects.h
+++ b/src/libaudcore/objects.h
@@ -20,6 +20,10 @@
#ifndef LIBAUDCORE_OBJECTS_H
#define LIBAUDCORE_OBJECTS_H
+#ifdef AUD_GLIB_INTEGRATION
+#include <glib.h>
+#endif
+
#include <libaudcore/templates.h>
// Stores array pointer together with deduced array length.
@@ -51,11 +55,7 @@ struct ArrayRef
// Smart pointer. Deletes object pointed to when the pointer goes out of scope.
-template<class T>
-void SmartPtrDelete (T * ptr)
- { (void) sizeof (T); delete ptr; }
-
-template<class T, void (* deleter) (T *) = SmartPtrDelete>
+template<class T, void (* deleter) (T *) = aud::delete_typed>
class SmartPtr
{
public:
@@ -73,6 +73,13 @@ public:
ptr = ptr2;
}
+ T * release ()
+ {
+ T * ptr2 = ptr;
+ ptr = nullptr;
+ return ptr2;
+ }
+
void clear ()
{ capture (nullptr); }
@@ -119,6 +126,21 @@ SmartPtr<T> SmartNew (Args && ... args)
std::forward<Args> (args) ...));
}
+// Convenience wrapper for a GLib-style string (char *).
+
+#ifdef AUD_GLIB_INTEGRATION
+class CharPtr : public SmartPtr<char, aud::typed_func<char, g_free>>
+{
+public:
+ CharPtr () : SmartPtr () {}
+ explicit CharPtr (char * ptr) : SmartPtr (ptr) {}
+
+ // non-const operator omitted to prevent "CharPtr s; g_free(s);"
+ operator const char * () const
+ { return get (); }
+};
+#endif
+
// Wrapper class for a string stored in the string pool.
class String
@@ -174,30 +196,13 @@ public:
unsigned hash () const
{ return raw_hash (raw); }
- // raw interface
- // avoid using where possible
-
- static String from_raw (char * str)
- {
- String s;
- s.raw = str;
- return s;
- }
-
- char * to_raw ()
- {
- char * str = raw;
- raw = nullptr;
- return str;
- }
-
+private:
static char * raw_get (const char * str);
static char * raw_ref (const char * str);
static void raw_unref (char * str);
static unsigned raw_hash (const char * str);
static bool raw_equal (const char * str1, const char * str2);
-private:
char * raw;
};
diff --git a/src/libaudcore/output.cc b/src/libaudcore/output.cc
index abb3c7f..e1a1015 100644
--- a/src/libaudcore/output.cc
+++ b/src/libaudcore/output.cc
@@ -105,7 +105,7 @@ static inline int get_format (bool & automatic)
switch (aud_get_int (0, "output_bit_depth"))
{
case 16: return FMT_S16_NE;
- case 24: return FMT_S24_NE;
+ case 24: return FMT_S24_3NE;
case 32: return FMT_S32_NE;
// return FMT_FLOAT for "auto" as well
@@ -188,6 +188,8 @@ static void setup_output (bool new_input)
format = FMT_S32_NE;
else if (automatic && format == FMT_S32_NE)
format = FMT_S16_NE;
+ else if (format == FMT_S24_3NE)
+ format = FMT_S24_NE; /* some output plugins support only padded 24-bit */
else
{
aud_ui_show_error (error ? (const char *) error : _("Error opening output stream"));
@@ -243,7 +245,7 @@ static void setup_secondary (bool new_input)
String error;
if (! sop->open_audio (FMT_FLOAT, rate, channels, error))
{
- aud_ui_show_error (error ? (const char *) error : _("Error opening output stream"));
+ aud_ui_show_error (error ? (const char *) error : _("Error recording output stream"));
return;
}
@@ -274,7 +276,10 @@ static void apply_replay_gain (Index<float> & data)
{
float peak;
- if (aud_get_bool (0, "replay_gain_album"))
+ auto mode = (ReplayGainMode) aud_get_int (0, "replay_gain_mode");
+ if ((mode == ReplayGainMode::Album) ||
+ (mode == ReplayGainMode::Automatic &&
+ (! aud_get_bool (0, "shuffle") || aud_get_bool (0, "album_shuffle"))))
{
factor *= powf (10, gain_info.album_gain / 20);
peak = gain_info.album_peak;
@@ -434,7 +439,9 @@ bool output_open_audio (const String & filename, const Tuple & tuple,
setup_effects ();
setup_output (true);
- setup_secondary (true);
+
+ if (aud_get_bool (0, "record"))
+ setup_secondary (true);
UNLOCK_ALL;
return true;
@@ -668,7 +675,9 @@ static void output_reset (OutputReset type, OutputPlugin * op)
setup_effects ();
setup_output (false);
- setup_secondary (false);
+
+ if (aud_get_bool (0, "record"))
+ setup_secondary (false);
}
s_resetting = false;
@@ -745,29 +754,33 @@ bool output_plugin_set_secondary (PluginHandle * plugin)
if (sop && ! sop->init ())
sop = nullptr;
- if (s_input)
+ if (s_input && aud_get_bool (0, "record"))
setup_secondary (false);
UNLOCK_MINOR;
return (! plugin || sop);
}
-static void record_stream_changed (void *, void *)
+static void record_settings_changed (void *, void *)
{
LOCK_MINOR;
- if (s_input)
+ if (s_input && aud_get_bool (0, "record"))
setup_secondary (false);
+ else
+ cleanup_secondary ();
UNLOCK_MINOR;
}
void output_init ()
{
- hook_associate ("set record_stream", record_stream_changed, nullptr);
+ hook_associate ("set record", record_settings_changed, nullptr);
+ hook_associate ("set record_stream", record_settings_changed, nullptr);
}
void output_cleanup ()
{
- hook_dissociate ("set record_stream", record_stream_changed, nullptr);
+ hook_dissociate ("set record", record_settings_changed);
+ hook_dissociate ("set record_stream", record_settings_changed);
}
diff --git a/src/libaudcore/playback.cc b/src/libaudcore/playback.cc
index b7e94a0..aea3b1b 100644
--- a/src/libaudcore/playback.cc
+++ b/src/libaudcore/playback.cc
@@ -184,13 +184,13 @@ static void playback_cleanup_locked ()
end_queue.stop ();
song_finished = false;
- event_queue_cancel ("playback ready", nullptr);
- event_queue_cancel ("playback pause", nullptr);
- event_queue_cancel ("playback unpause", nullptr);
- event_queue_cancel ("playback seek", nullptr);
- event_queue_cancel ("info change", nullptr);
- event_queue_cancel ("title change", nullptr);
- event_queue_cancel ("tuple change", nullptr);
+ event_queue_cancel ("playback ready");
+ event_queue_cancel ("playback pause");
+ event_queue_cancel ("playback unpause");
+ event_queue_cancel ("playback seek");
+ event_queue_cancel ("info change");
+ event_queue_cancel ("title change");
+ event_queue_cancel ("tuple change");
aud_set_bool (nullptr, "stop_after_current_song", false);
}
@@ -233,19 +233,19 @@ static void end_cb (void *)
song_finished = true;
hook_call ("playback end", nullptr);
- int playlist = aud_playlist_get_playing ();
+ PlaylistEx playlist = Playlist::playing_playlist ();
auto do_stop = [playlist] ()
{
- aud_playlist_play (-1);
- aud_playlist_set_position (playlist, aud_playlist_get_position (playlist));
+ aud_drct_stop ();
+ playlist.set_position (playlist.get_position ());
};
auto do_next = [playlist] ()
{
- if (! playlist_next_song (playlist, aud_get_bool (nullptr, "repeat")))
+ if (! playlist.next_song (aud_get_bool (nullptr, "repeat")))
{
- aud_playlist_set_position (playlist, -1);
+ playlist.set_position (-1);
hook_call ("playlist end reached", nullptr);
}
};
@@ -364,10 +364,12 @@ static void finish_playback_locked ()
if (pb_info.error)
{
failed_entries ++;
- aud_ui_show_error (str_printf (_("Error playing %s:\n%s"),
- (const char *) pb_info.filename, pb_info.error_s ?
- (const char *) pb_info.error_s : _("Unknown playback error "
- "(check the console for detailed error information)")));
+
+ if (pb_info.error_s)
+ aud_ui_show_error (str_printf (_("Error playing %s:\n%s"),
+ (const char *) pb_info.filename, (const char *) pb_info.error_s));
+ else
+ AUDERR ("Playback finished with error.\n");
}
else
failed_entries = 0;
diff --git a/src/libaudcore/playlist-cache.cc b/src/libaudcore/playlist-cache.cc
index 9711235..fe371ac 100644
--- a/src/libaudcore/playlist-cache.cc
+++ b/src/libaudcore/playlist-cache.cc
@@ -27,26 +27,26 @@ static SimpleHash<String, PlaylistAddItem> cache;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static QueuedFunc clear_timer;
-EXPORT void aud_playlist_cache_selected (int playlist)
+EXPORT void Playlist::cache_selected () const
{
pthread_mutex_lock (& mutex);
- int entries = aud_playlist_entry_count (playlist);
+ int entries = n_entries ();
for (int i = 0; i < entries; i ++)
{
- if (! aud_playlist_entry_get_selected (playlist, i))
+ if (! entry_selected (i))
continue;
- String filename = aud_playlist_entry_get_filename (playlist, i);
- Tuple tuple = aud_playlist_entry_get_tuple (playlist, i, Playlist::NoWait);
- PluginHandle * decoder = aud_playlist_entry_get_decoder (playlist, i, Playlist::NoWait);
+ String filename = entry_filename (i);
+ Tuple tuple = entry_tuple (i, NoWait);
+ PluginHandle * decoder = entry_decoder (i, NoWait);
if (tuple.valid () || decoder)
cache.add (filename, {filename, std::move (tuple), decoder});
}
- clear_timer.queue (30000, (QueuedFunc::Func) playlist_cache_clear, nullptr);
+ clear_timer.queue (30000, playlist_cache_clear, nullptr);
pthread_mutex_unlock (& mutex);
}
@@ -77,7 +77,7 @@ out:
pthread_mutex_unlock (& mutex);
}
-void playlist_cache_clear ()
+void playlist_cache_clear (void *)
{
pthread_mutex_lock (& mutex);
diff --git a/src/libaudcore/playlist-data.cc b/src/libaudcore/playlist-data.cc
new file mode 100644
index 0000000..274a3e5
--- /dev/null
+++ b/src/libaudcore/playlist-data.cc
@@ -0,0 +1,1123 @@
+/*
+ * playlist-data.cc
+ * Copyright 2017 John Lindgren
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions, and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions, and the following disclaimer in the documentation
+ * provided with the distribution.
+ *
+ * This software is provided "as is" and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising from
+ * the use of this software.
+ */
+
+#include "playlist-data.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "runtime.h"
+#include "scanner.h"
+#include "tuple-compiler.h"
+
+static TupleCompiler s_tuple_formatter;
+static bool s_use_tuple_fallbacks = false;
+
+struct PlaylistEntry
+{
+ PlaylistEntry (PlaylistAddItem && item);
+ ~PlaylistEntry ();
+
+ void format ();
+ void set_tuple (Tuple && new_tuple);
+
+ String filename;
+ PluginHandle * decoder;
+ Tuple tuple;
+ String error;
+ int number;
+ int length;
+ int shuffle_num;
+ bool selected, queued;
+};
+
+void PlaylistEntry::format ()
+{
+ tuple.delete_fallbacks ();
+
+ if (s_use_tuple_fallbacks)
+ tuple.generate_fallbacks ();
+ else
+ tuple.generate_title ();
+
+ s_tuple_formatter.format (tuple);
+}
+
+void PlaylistEntry::set_tuple (Tuple && new_tuple)
+{
+ /* Since 3.8, cuesheet entries are handled differently. The entry filename
+ * points to the .cue file, and the path to the actual audio file is stored
+ * in the Tuple::AudioFile. If Tuple::AudioFile is not set, then assume
+ * that the playlist was created by an older version of Audacious, and
+ * revert to the former behavior (don't refresh this entry). */
+ if (tuple.is_set (Tuple::StartTime) && ! tuple.is_set (Tuple::AudioFile))
+ return;
+
+ error = String ();
+
+ if (! new_tuple.valid ())
+ new_tuple.set_filename (filename);
+
+ length = aud::max (0, new_tuple.get_int (Tuple::Length));
+ tuple = std::move (new_tuple);
+
+ format ();
+}
+
+PlaylistEntry::PlaylistEntry (PlaylistAddItem && item) :
+ filename (item.filename),
+ decoder (item.decoder),
+ number (-1),
+ length (0),
+ shuffle_num (0),
+ selected (false),
+ queued (false)
+{
+ set_tuple (std::move (item.tuple));
+}
+
+PlaylistEntry::~PlaylistEntry ()
+{
+ pl_signal_entry_deleted (this);
+}
+
+void PlaylistData::update_formatter () // static
+{
+ s_tuple_formatter.compile (aud_get_str (nullptr, "generic_title_format"));
+ s_use_tuple_fallbacks = aud_get_bool (nullptr, "metadata_fallbacks");
+}
+
+void PlaylistData::cleanup_formatter () // static
+ { s_tuple_formatter.reset (); }
+
+void PlaylistData::delete_entry (PlaylistEntry * entry) // static
+ { delete entry; }
+
+PlaylistData::PlaylistData (Playlist::ID * id, const char * title) :
+ modified (true),
+ scan_status (NotScanning),
+ title (title),
+ resume_time (0),
+ m_id (id),
+ m_position (nullptr),
+ m_focus (nullptr),
+ m_selected_count (0),
+ m_last_shuffle_num (0),
+ m_total_length (0),
+ m_selected_length (0),
+ m_last_update (),
+ m_next_update (),
+ m_position_changed (false) {}
+
+PlaylistData::~PlaylistData ()
+{
+ pl_signal_playlist_deleted (m_id);
+}
+
+void PlaylistData::number_entries (int at, int length)
+{
+ for (int i = at; i < at + length; i ++)
+ m_entries[i]->number = i;
+}
+
+PlaylistEntry * PlaylistData::entry_at (int i)
+{
+ return (i >= 0 && i < m_entries.len ()) ? m_entries[i].get () : nullptr;
+}
+
+const PlaylistEntry * PlaylistData::entry_at (int i) const
+{
+ return (i >= 0 && i < m_entries.len ()) ? m_entries[i].get () : nullptr;
+}
+
+String PlaylistData::entry_filename (int i) const
+{
+ auto entry = entry_at (i);
+ return entry ? entry->filename : String ();
+}
+
+PluginHandle * PlaylistData::entry_decoder (int i, String * error) const
+{
+ auto entry = entry_at (i);
+ if (error) * error = entry ? entry->error : String ();
+ return entry ? entry->decoder : nullptr;
+}
+
+Tuple PlaylistData::entry_tuple (int i, String * error) const
+{
+ auto entry = entry_at (i);
+ if (error) * error = entry ? entry->error : String ();
+ return entry ? entry->tuple.ref () : Tuple ();
+}
+
+void PlaylistData::set_entry_tuple (PlaylistEntry * entry, Tuple && tuple)
+{
+ m_total_length -= entry->length;
+ if (entry->selected)
+ m_selected_length -= entry->length;
+
+ entry->set_tuple (std::move (tuple));
+
+ m_total_length += entry->length;
+ if (entry->selected)
+ m_selected_length += entry->length;
+}
+
+void PlaylistData::queue_update (Playlist::UpdateLevel level, int at, int count, int flags)
+{
+ if (m_next_update.level)
+ {
+ m_next_update.level = aud::max (m_next_update.level, level);
+ m_next_update.before = aud::min (m_next_update.before, at);
+ m_next_update.after = aud::min (m_next_update.after, m_entries.len () - at - count);
+ }
+ else
+ {
+ m_next_update.level = level;
+ m_next_update.before = at;
+ m_next_update.after = m_entries.len () - at - count;
+ }
+
+ if ((flags & QueueChanged))
+ m_next_update.queue_changed = true;
+
+ pl_signal_update_queued (m_id, level, flags);
+}
+
+void PlaylistData::queue_position_change ()
+{
+ m_position_changed = true;
+ pl_signal_position_changed (m_id);
+}
+
+void PlaylistData::cancel_updates ()
+{
+ m_last_update = Playlist::Update ();
+ m_next_update = Playlist::Update ();
+ m_position_changed = false;
+}
+
+void PlaylistData::swap_updates (bool & position_changed)
+{
+ m_last_update = m_next_update;
+ m_next_update = Playlist::Update ();
+ position_changed = m_position_changed;
+ m_position_changed = false;
+}
+
+void PlaylistData::insert_items (int at, Index<PlaylistAddItem> && items)
+{
+ int n_entries = m_entries.len ();
+ int n_items = items.len ();
+
+ if (at < 0 || at > n_entries)
+ at = n_entries;
+
+ m_entries.insert (at, n_items);
+
+ int i = at;
+ for (auto & item : items)
+ {
+ auto entry = new PlaylistEntry (std::move (item));
+ m_entries[i ++].capture (entry);
+ m_total_length += entry->length;
+ }
+
+ items.clear ();
+
+ number_entries (at, n_entries + n_items - at);
+ queue_update (Playlist::Structure, at, n_items);
+}
+
+void PlaylistData::remove_entries (int at, int number)
+{
+ int n_entries = m_entries.len ();
+ bool position_changed = false;
+ int update_flags = 0;
+
+ if (at < 0 || at > n_entries)
+ at = n_entries;
+ if (number < 0 || number > n_entries - at)
+ number = n_entries - at;
+
+ if (m_position && m_position->number >= at && m_position->number < at + number)
+ {
+ set_position (nullptr, false);
+ position_changed = true;
+ }
+
+ if (m_focus && m_focus->number >= at && m_focus->number < at + number)
+ {
+ if (at + number < n_entries)
+ m_focus = m_entries[at + number].get ();
+ else if (at > 0)
+ m_focus = m_entries[at - 1].get ();
+ else
+ m_focus = nullptr;
+ }
+
+ for (int i = 0; i < number; i ++)
+ {
+ PlaylistEntry * entry = m_entries [at + i].get ();
+
+ if (entry->queued)
+ {
+ m_queued.remove (m_queued.find (entry), 1);
+ update_flags |= QueueChanged;
+ }
+
+ if (entry->selected)
+ {
+ m_selected_count --;
+ m_selected_length -= entry->length;
+ }
+
+ m_total_length -= entry->length;
+ }
+
+ m_entries.remove (at, number);
+
+ number_entries (at, n_entries - at - number);
+ queue_update (Playlist::Structure, at, 0, update_flags);
+
+ if (position_changed)
+ {
+ if (aud_get_bool (nullptr, "advance_on_delete"))
+ next_song_with_hint (aud_get_bool (nullptr, "repeat"), at);
+
+ queue_position_change ();
+ }
+}
+
+int PlaylistData::position () const
+{
+ return m_position ? m_position->number : -1;
+}
+
+int PlaylistData::focus () const
+{
+ return m_focus ? m_focus->number : -1;
+}
+
+bool PlaylistData::entry_selected (int entry_num) const
+{
+ auto entry = entry_at (entry_num);
+ return entry ? entry->selected : false;
+}
+
+int PlaylistData::n_selected (int at, int number) const
+{
+ int n_entries = m_entries.len ();
+
+ if (at < 0 || at > n_entries)
+ at = n_entries;
+ if (number < 0 || number > n_entries - at)
+ number = n_entries - at;
+
+ int n_selected = 0;
+
+ if (at == 0 && number == n_entries)
+ n_selected = m_selected_count;
+ else
+ {
+ for (int i = 0; i < number; i ++)
+ {
+ if (m_entries[at + i]->selected)
+ n_selected ++;
+ }
+ }
+
+ return n_selected;
+}
+
+void PlaylistData::set_focus (int entry_num)
+{
+ auto new_focus = entry_at (entry_num);
+ if (new_focus == m_focus)
+ return;
+
+ int first = m_entries.len ();
+ int last = -1;
+
+ if (m_focus)
+ {
+ first = aud::min (first, m_focus->number);
+ last = aud::max (last, m_focus->number);
+ }
+
+ m_focus = new_focus;
+
+ if (m_focus)
+ {
+ first = aud::min (first, m_focus->number);
+ last = aud::max (last, m_focus->number);
+ }
+
+ if (first <= last)
+ queue_update (Playlist::Selection, first, last + 1 - first);
+}
+
+void PlaylistData::select_entry (int entry_num, bool selected)
+{
+ auto entry = entry_at (entry_num);
+ if (! entry || entry->selected == selected)
+ return;
+
+ entry->selected = selected;
+
+ if (selected)
+ {
+ m_selected_count ++;
+ m_selected_length += entry->length;
+ }
+ else
+ {
+ m_selected_count --;
+ m_selected_length -= entry->length;
+ }
+
+ queue_update (Playlist::Selection, entry_num, 1);
+}
+
+void PlaylistData::select_all (bool selected)
+{
+ int n_entries = m_entries.len ();
+ int first = n_entries, last = 0;
+
+ for (auto & entry : m_entries)
+ {
+ if (entry->selected != selected)
+ {
+ entry->selected = selected;
+ first = aud::min (first, entry->number);
+ last = entry->number;
+ }
+ }
+
+ if (selected)
+ {
+ m_selected_count = n_entries;
+ m_selected_length = m_total_length;
+ }
+ else
+ {
+ m_selected_count = 0;
+ m_selected_length = 0;
+ }
+
+ if (first < n_entries)
+ queue_update (Playlist::Selection, first, last + 1 - first);
+}
+
+int PlaylistData::shift_entries (int entry_num, int distance)
+{
+ PlaylistEntry * entry = entry_at (entry_num);
+ if (! entry || ! entry->selected || ! distance)
+ return 0;
+
+ int n_entries = m_entries.len ();
+ int shift = 0, center, top, bottom;
+
+ if (distance < 0)
+ {
+ for (center = entry_num; center > 0 && shift > distance; )
+ {
+ if (! m_entries[-- center]->selected)
+ shift --;
+ }
+ }
+ else
+ {
+ for (center = entry_num + 1; center < n_entries && shift < distance; )
+ {
+ if (! m_entries[center ++]->selected)
+ shift ++;
+ }
+ }
+
+ top = bottom = center;
+
+ for (int i = 0; i < top; i ++)
+ {
+ if (m_entries[i]->selected)
+ top = i;
+ }
+
+ for (int i = n_entries; i > bottom; i --)
+ {
+ if (m_entries[i - 1]->selected)
+ bottom = i;
+ }
+
+ Index<EntryPtr> temp;
+
+ for (int i = top; i < center; i ++)
+ {
+ if (! m_entries[i]->selected)
+ temp.append (std::move (m_entries[i]));
+ }
+
+ for (int i = top; i < bottom; i ++)
+ {
+ if (m_entries[i] && m_entries[i]->selected)
+ temp.append (std::move (m_entries[i]));
+ }
+
+ for (int i = center; i < bottom; i ++)
+ {
+ if (m_entries[i] && ! m_entries[i]->selected)
+ temp.append (std::move (m_entries[i]));
+ }
+
+ m_entries.move_from (temp, 0, top, bottom - top, false, true);
+
+ number_entries (top, bottom - top);
+ queue_update (Playlist::Structure, top, bottom - top);
+
+ return shift;
+}
+
+void PlaylistData::remove_selected ()
+{
+ if (! m_selected_count)
+ return;
+
+ int n_entries = m_entries.len ();
+ bool position_changed = false;
+ int update_flags = 0;
+
+ if (m_position && m_position->selected)
+ {
+ set_position (nullptr, false);
+ position_changed = true;
+ }
+
+ m_focus = find_unselected_focus ();
+
+ int before = 0; // number of entries before first selected
+ int after = 0; // number of entries after last selected
+
+ while (before < n_entries && ! m_entries[before]->selected)
+ before ++;
+
+ int to = before;
+
+ for (int from = before; from < n_entries; from ++)
+ {
+ PlaylistEntry * entry = m_entries[from].get ();
+
+ if (entry->selected)
+ {
+ if (entry->queued)
+ {
+ m_queued.remove (m_queued.find (entry), 1);
+ update_flags |= QueueChanged;
+ }
+
+ m_total_length -= entry->length;
+ after = 0;
+ }
+ else
+ {
+ m_entries[to ++] = std::move (m_entries[from]);
+ after ++;
+ }
+ }
+
+ n_entries = to;
+ m_entries.remove (n_entries, -1);
+
+ m_selected_count = 0;
+ m_selected_length = 0;
+
+ number_entries (before, n_entries - before);
+ queue_update (Playlist::Structure, before, n_entries - after - before, update_flags);
+
+ if (position_changed)
+ {
+ if (aud_get_bool (nullptr, "advance_on_delete"))
+ next_song_with_hint (aud_get_bool (nullptr, "repeat"), n_entries - after);
+
+ queue_position_change ();
+ }
+}
+
+void PlaylistData::sort_entries (Index<EntryPtr> & entries, const CompareData & data) // static
+{
+ entries.sort ([data] (const EntryPtr & a, const EntryPtr & b) {
+ if (data.filename_compare)
+ return data.filename_compare (a->filename, b->filename);
+ else
+ return data.tuple_compare (a->tuple, b->tuple);
+ });
+}
+
+void PlaylistData::sort (const CompareData & data)
+{
+ sort_entries (m_entries, data);
+
+ number_entries (0, m_entries.len ());
+ queue_update (Playlist::Structure, 0, m_entries.len ());
+}
+
+void PlaylistData::sort_selected (const CompareData & data)
+{
+ int n_entries = m_entries.len ();
+
+ Index<EntryPtr> selected;
+
+ for (auto & entry : m_entries)
+ {
+ if (entry->selected)
+ selected.append (std::move (entry));
+ }
+
+ sort_entries (selected, data);
+
+ int i = 0;
+ for (auto & entry : m_entries)
+ {
+ if (! entry)
+ entry = std::move (selected[i ++]);
+ }
+
+ number_entries (0, n_entries);
+ queue_update (Playlist::Structure, 0, n_entries);
+}
+
+void PlaylistData::reverse_order ()
+{
+ int n_entries = m_entries.len ();
+
+ for (int i = 0; i < n_entries / 2; i ++)
+ std::swap (m_entries[i], m_entries[n_entries - 1 - i]);
+
+ number_entries (0, n_entries);
+ queue_update (Playlist::Structure, 0, n_entries);
+}
+
+void PlaylistData::reverse_selected ()
+{
+ int n_entries = m_entries.len ();
+
+ int top = 0;
+ int bottom = n_entries - 1;
+
+ while (1)
+ {
+ while (top < bottom && ! m_entries[top]->selected)
+ top ++;
+ while (top < bottom && ! m_entries[bottom]->selected)
+ bottom --;
+
+ if (top >= bottom)
+ break;
+
+ std::swap (m_entries[top ++], m_entries[bottom --]);
+ }
+
+ number_entries (0, n_entries);
+ queue_update (Playlist::Structure, 0, n_entries);
+}
+
+void PlaylistData::randomize_order ()
+{
+ int n_entries = m_entries.len ();
+
+ for (int i = 0; i < n_entries; i ++)
+ std::swap (m_entries[i], m_entries[rand () % n_entries]);
+
+ number_entries (0, n_entries);
+ queue_update (Playlist::Structure, 0, n_entries);
+}
+
+void PlaylistData::randomize_selected ()
+{
+ int n_entries = m_entries.len ();
+
+ Index<PlaylistEntry *> selected;
+
+ for (auto & entry : m_entries)
+ {
+ if (entry->selected)
+ selected.append (entry.get ());
+ }
+
+ int n_selected = selected.len ();
+
+ for (int i = 0; i < n_selected; i ++)
+ {
+ int a = selected[i]->number;
+ int b = selected[rand () % n_selected]->number;
+ std::swap (m_entries[a], m_entries[b]);
+ }
+
+ number_entries (0, n_entries);
+ queue_update (Playlist::Structure, 0, n_entries);
+}
+
+int PlaylistData::queue_get_entry (int at) const
+{
+ return (at >= 0 && at < m_queued.len ()) ? m_queued[at]->number : -1;
+}
+
+int PlaylistData::queue_find_entry (int entry_num) const
+{
+ auto entry = entry_at (entry_num);
+ return entry->queued ? m_queued.find ((PlaylistEntry *) entry) : -1;
+}
+
+void PlaylistData::queue_insert (int at, int entry_num)
+{
+ auto entry = entry_at (entry_num);
+ if (entry->queued)
+ return;
+
+ if (at < 0 || at > m_queued.len ())
+ m_queued.append (entry);
+ else
+ {
+ m_queued.insert (at, 1);
+ m_queued[at] = entry;
+ }
+
+ entry->queued = true;
+
+ queue_update (Playlist::Selection, entry_num, 1, QueueChanged);
+}
+
+void PlaylistData::queue_insert_selected (int at)
+{
+ if (at < 0 || at > m_queued.len ())
+ at = m_queued.len ();
+
+ Index<PlaylistEntry *> add;
+ int first = m_entries.len ();
+ int last = 0;
+
+ for (auto & entry : m_entries)
+ {
+ if (! entry->selected || entry->queued)
+ continue;
+
+ add.append (entry.get ());
+ entry->queued = true;
+ first = aud::min (first, entry->number);
+ last = entry->number;
+ }
+
+ m_queued.move_from (add, 0, at, -1, true, true);
+
+ if (first < m_entries.len ())
+ queue_update (Playlist::Selection, first, last + 1 - first, QueueChanged);
+}
+
+void PlaylistData::queue_remove (int at, int number)
+{
+ int queue_len = m_queued.len ();
+
+ if (at < 0 || at > queue_len)
+ at = queue_len;
+ if (number < 0 || number > queue_len - at)
+ number = queue_len - at;
+
+ int n_entries = m_entries.len ();
+ int first = n_entries, last = 0;
+
+ for (int i = at; i < at + number; i ++)
+ {
+ PlaylistEntry * entry = m_queued[i];
+ entry->queued = false;
+ first = aud::min (first, entry->number);
+ last = entry->number;
+ }
+
+ m_queued.remove (at, number);
+
+ if (first < n_entries)
+ queue_update (Playlist::Selection, first, last + 1 - first, QueueChanged);
+}
+
+void PlaylistData::queue_remove_selected ()
+{
+ int n_entries = m_entries.len ();
+ int first = n_entries, last = 0;
+
+ for (int i = 0; i < m_queued.len ();)
+ {
+ PlaylistEntry * entry = m_queued[i];
+
+ if (entry->selected)
+ {
+ m_queued.remove (i, 1);
+ entry->queued = false;
+ first = aud::min (first, entry->number);
+ last = entry->number;
+ }
+ else
+ i ++;
+ }
+
+ if (first < n_entries)
+ queue_update (Playlist::Selection, first, last + 1 - first, QueueChanged);
+}
+
+void PlaylistData::set_position (PlaylistEntry * entry, bool update_shuffle)
+{
+ m_position = entry;
+ resume_time = 0;
+
+ /* move entry to top of shuffle list */
+ if (entry && update_shuffle)
+ entry->shuffle_num = ++ m_last_shuffle_num;
+}
+
+void PlaylistData::set_position (int entry_num)
+{
+ set_position (entry_at (entry_num), true);
+ queue_position_change ();
+}
+
+bool PlaylistData::shuffle_prev ()
+{
+ PlaylistEntry * found = nullptr;
+
+ for (auto & entry : m_entries)
+ {
+ if (entry->shuffle_num &&
+ (! m_position || entry->shuffle_num < m_position->shuffle_num) &&
+ (! found || entry->shuffle_num > found->shuffle_num))
+ {
+ found = entry.get ();
+ }
+ }
+
+ if (! found)
+ return false;
+
+ set_position (found, false);
+ return true;
+}
+
+bool PlaylistData::shuffle_next ()
+{
+ bool by_album = aud_get_bool (nullptr, "album_shuffle");
+
+ // helper #1: determine whether two entries are in the same album
+ auto same_album = [] (const Tuple & a, const Tuple & b)
+ {
+ String album = a.get_str (Tuple::Album);
+ return (album && album == b.get_str (Tuple::Album));
+ };
+
+ // helper #2: determine whether an entry is among the shuffle choices
+ auto is_choice = [&] (PlaylistEntry * prev, PlaylistEntry * entry)
+ {
+ return (! entry->shuffle_num) && (! by_album || ! prev ||
+ prev->shuffle_num || ! same_album (prev->tuple, entry->tuple));
+ };
+
+ if (m_position)
+ {
+ // step #1: check to see if the shuffle order is already established
+ PlaylistEntry * next = nullptr;
+
+ for (auto & entry : m_entries)
+ {
+ if (entry->shuffle_num > m_position->shuffle_num &&
+ (! next || entry->shuffle_num < next->shuffle_num))
+ next = entry.get ();
+ }
+
+ if (next)
+ {
+ set_position (next, false);
+ return true;
+ }
+
+ // step #2: check to see if we should advance to the next entry
+ if (by_album && m_position->number + 1 < m_entries.len ())
+ {
+ next = m_entries[m_position->number + 1].get ();
+
+ if (! next->shuffle_num && same_album (m_position->tuple, next->tuple))
+ {
+ set_position (next, true);
+ return true;
+ }
+ }
+ }
+
+ // step #3: count the number of possible shuffle choices
+ int choices = 0;
+ PlaylistEntry * prev = nullptr;
+
+ for (auto & entry : m_entries)
+ {
+ if (is_choice (prev, entry.get ()))
+ choices ++;
+
+ prev = entry.get ();
+ }
+
+ if (! choices)
+ return false;
+
+ // step #4: pick one of those choices by random and find it again
+ choices = rand () % choices;
+ prev = nullptr;
+
+ for (auto & entry : m_entries)
+ {
+ if (is_choice (prev, entry.get ()))
+ {
+ if (! choices)
+ {
+ set_position (entry.get (), true);
+ return true;
+ }
+
+ choices --;
+ }
+
+ prev = entry.get ();
+ }
+
+ return false; // never reached
+}
+
+void PlaylistData::shuffle_reset ()
+{
+ m_last_shuffle_num = 0;
+
+ for (auto & entry : m_entries)
+ entry->shuffle_num = 0;
+}
+
+bool PlaylistData::prev_song ()
+{
+ if (aud_get_bool (nullptr, "shuffle"))
+ {
+ if (! shuffle_prev ())
+ return false;
+ }
+ else
+ {
+ int pos = position ();
+ if (pos < 1)
+ return false;
+
+ set_position (m_entries[pos - 1].get (), true);
+ }
+
+ queue_position_change ();
+ return true;
+}
+
+bool PlaylistData::next_song_with_hint (bool repeat, int hint)
+{
+ int n_entries = m_entries.len ();
+ if (! n_entries)
+ return false;
+
+ PlaylistEntry * entry;
+ if ((entry = queue_pop ()))
+ {
+ set_position (entry, true);
+ return true;
+ }
+
+ if (aud_get_bool (nullptr, "shuffle"))
+ {
+ if (shuffle_next ())
+ return true;
+
+ if (! repeat)
+ return false;
+
+ shuffle_reset ();
+
+ return shuffle_next ();
+ }
+
+ if (hint < 0 || hint >= n_entries)
+ {
+ if (! repeat)
+ return false;
+
+ hint = 0;
+ }
+
+ set_position (m_entries[hint].get (), true);
+ return true;
+}
+
+bool PlaylistData::next_song (bool repeat)
+{
+ if (! next_song_with_hint (repeat, position () + 1)) // -1 becomes 0
+ return false;
+
+ queue_position_change ();
+ return true;
+}
+
+int PlaylistData::next_unscanned_entry (int entry_num) const
+{
+ if (entry_num < 0)
+ return -1;
+
+ for (; entry_num < m_entries.len (); entry_num ++)
+ {
+ auto & entry = *m_entries[entry_num];
+
+ if (entry.tuple.state () == Tuple::Initial &&
+ strncmp (entry.filename, "stdin://", 8)) // blacklist stdin
+ {
+ return entry_num;
+ }
+ }
+
+ return -1;
+}
+
+ScanRequest * PlaylistData::create_scan_request (PlaylistEntry * entry,
+ ScanRequest::Callback callback, int extra_flags)
+{
+ int flags = extra_flags;
+ if (! entry->tuple.valid ())
+ flags |= SCAN_TUPLE;
+
+ /* scanner uses Tuple::AudioFile from existing tuple, if valid */
+ return new ScanRequest (entry->filename, flags, callback, entry->decoder,
+ (flags & SCAN_TUPLE) ? Tuple () : entry->tuple.ref ());
+}
+
+void PlaylistData::update_entry_from_scan (PlaylistEntry * entry, ScanRequest * request, int update_flags)
+{
+ if (! entry->decoder)
+ entry->decoder = request->decoder;
+
+ if (! entry->tuple.valid () && request->tuple.valid ())
+ {
+ set_entry_tuple (entry, std::move (request->tuple));
+ queue_update (Playlist::Metadata, entry->number, 1, update_flags);
+ }
+
+ if (! entry->decoder || ! entry->tuple.valid ())
+ entry->error = request->error;
+
+ if (entry->tuple.state () == Tuple::Initial)
+ {
+ entry->tuple.set_state (Tuple::Failed);
+ queue_update (Playlist::Metadata, entry->number, 1, update_flags);
+ }
+}
+
+void PlaylistData::update_playback_entry (Tuple && tuple)
+{
+ /* don't update cuesheet entries with stream metadata */
+ if (m_position && ! m_position->tuple.is_set (Tuple::StartTime))
+ {
+ set_entry_tuple (m_position, std::move (tuple));
+ queue_update (Playlist::Metadata, m_position->number, 1);
+ }
+}
+
+bool PlaylistData::entry_needs_rescan (PlaylistEntry * entry, bool need_decoder, bool need_tuple)
+{
+ if (! strncmp (entry->filename, "stdin://", 8)) // blacklist stdin
+ return false;
+
+ // check whether requested data (decoder and/or tuple) has been read
+ return (need_decoder && ! entry->decoder) || (need_tuple && ! entry->tuple.valid ());
+}
+
+void PlaylistData::reformat_titles ()
+{
+ for (auto & entry : m_entries)
+ entry->format ();
+
+ queue_update (Playlist::Metadata, 0, m_entries.len ());
+}
+
+void PlaylistData::reset_tuples (bool selected_only)
+{
+ for (auto & entry : m_entries)
+ {
+ if (! selected_only || entry->selected)
+ set_entry_tuple (entry.get (), Tuple ());
+ }
+
+ queue_update (Playlist::Metadata, 0, m_entries.len ());
+ pl_signal_rescan_needed (m_id);
+}
+
+void PlaylistData::reset_tuple_of_file (const char * filename)
+{
+ bool found = false;
+
+ for (auto & entry : m_entries)
+ {
+ if (! strcmp (entry->filename, filename))
+ {
+ set_entry_tuple (entry.get (), Tuple ());
+ queue_update (Playlist::Metadata, entry->number, 1);
+ found = true;
+ }
+ }
+
+ if (found)
+ pl_signal_rescan_needed (m_id);
+}
+
+PlaylistEntry * PlaylistData::find_unselected_focus ()
+{
+ if (! m_focus || ! m_focus->selected)
+ return m_focus;
+
+ int n_entries = m_entries.len ();
+
+ for (int search = m_focus->number + 1; search < n_entries; search ++)
+ {
+ if (! m_entries[search]->selected)
+ return m_entries[search].get ();
+ }
+
+ for (int search = m_focus->number; search --;)
+ {
+ if (! m_entries[search]->selected)
+ return m_entries[search].get ();
+ }
+
+ return nullptr;
+}
+
+PlaylistEntry * PlaylistData::queue_pop ()
+{
+ if (! m_queued.len ())
+ return nullptr;
+
+ auto entry = m_queued[0];
+ m_queued.remove (0, 1);
+ entry->queued = false;
+
+ queue_update (Playlist::Selection, entry->number, 1, QueueChanged);
+
+ return entry;
+}
diff --git a/src/libaudcore/playlist-data.h b/src/libaudcore/playlist-data.h
new file mode 100644
index 0000000..c94a94c
--- /dev/null
+++ b/src/libaudcore/playlist-data.h
@@ -0,0 +1,172 @@
+/*
+ * playlist-data.h
+ * Copyright 2017 John Lindgren
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions, and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions, and the following disclaimer in the documentation
+ * provided with the distribution.
+ *
+ * This software is provided "as is" and without any warranty, express or
+ * implied. In no event shall the authors be liable for any damages arising from
+ * the use of this software.
+ */
+
+#ifndef PLAYLIST_DATA_H
+#define PLAYLIST_DATA_H
+
+#include "playlist.h"
+#include "scanner.h"
+
+class TupleCompiler;
+struct PlaylistEntry;
+
+class PlaylistData
+{
+public:
+ /* update flags */
+ enum {
+ QueueChanged = (1 << 0),
+ DelayedUpdate = (1 << 1)
+ };
+
+ /* scan status */
+ enum ScanStatus {
+ NotScanning,
+ ScanActive,
+ ScanEnding
+ };
+
+ struct CompareData {
+ Playlist::StringCompareFunc filename_compare;
+ Playlist::TupleCompareFunc tuple_compare;
+ };
+
+ PlaylistData (Playlist::ID * m_id, const char * title);
+ ~PlaylistData ();
+
+ PlaylistEntry * entry_at (int i);
+ const PlaylistEntry * entry_at (int i) const;
+
+ String entry_filename (int i) const;
+ PluginHandle * entry_decoder (int i, String * error = nullptr) const;
+ Tuple entry_tuple (int i, String * error = nullptr) const;
+
+ void cancel_updates ();
+ void swap_updates (bool & position_changed);
+
+ void insert_items (int at, Index<PlaylistAddItem> && items);
+ void remove_entries (int at, int number);
+
+ int position () const;
+ int focus () const;
+
+ bool entry_selected (int entry_num) const;
+ int n_selected (int at, int number) const;
+
+ void set_focus (int entry_num);
+
+ void select_entry (int entry_num, bool selected);
+ void select_all (bool selected);
+ int shift_entries (int entry_num, int distance);
+ void remove_selected ();
+
+ void sort (const CompareData & data);
+ void sort_selected (const CompareData & data);
+
+ void reverse_order ();
+ void randomize_order ();
+ void reverse_selected ();
+ void randomize_selected ();
+
+ int queue_get_entry (int at) const;
+ int queue_find_entry (int entry_num) const;
+
+ void queue_insert (int at, int entry_num);
+ void queue_insert_selected (int pos);
+ void queue_remove (int at, int number);
+ void queue_remove_selected ();
+
+ void set_position (int entry_num);
+
+ bool prev_song ();
+ bool next_song (bool repeat);
+
+ int next_unscanned_entry (int entry_num) const;
+ bool entry_needs_rescan (PlaylistEntry * entry, bool need_decoder, bool need_tuple);
+ ScanRequest * create_scan_request (PlaylistEntry * entry,
+ ScanRequest::Callback callback, int extra_flags);
+ void update_entry_from_scan (PlaylistEntry * entry, ScanRequest * request, int update_flags);
+ void update_playback_entry (Tuple && tuple);
+
+ void reformat_titles ();
+ void reset_tuples (bool selected_only);
+ void reset_tuple_of_file (const char * filename);
+
+ Playlist::ID * id () const { return m_id; }
+
+ int n_entries () const { return m_entries.len (); }
+ int n_queued () const { return m_queued.len (); }
+
+ int64_t total_length () const { return m_total_length; }
+ int64_t selected_length () const { return m_selected_length; }
+
+ const Playlist::Update & last_update () const { return m_last_update; }
+ bool update_pending () const { return m_next_update.level != Playlist::NoUpdate; }
+
+ static void update_formatter ();
+ static void cleanup_formatter ();
+
+private:
+ static void delete_entry (PlaylistEntry * entry);
+ typedef SmartPtr<PlaylistEntry, delete_entry> EntryPtr;
+
+ void number_entries (int at, int length);
+ void set_entry_tuple (PlaylistEntry * entry, Tuple && tuple);
+ void queue_update (Playlist::UpdateLevel level, int at, int count, int flags = 0);
+ void queue_position_change ();
+
+ static void sort_entries (Index<EntryPtr> & entries, const CompareData & data);
+
+ void set_position (PlaylistEntry * entry, bool update_shuffle);
+
+ bool shuffle_prev ();
+ bool shuffle_next ();
+ void shuffle_reset ();
+
+ bool next_song_with_hint (bool repeat, int hint);
+
+ PlaylistEntry * find_unselected_focus ();
+ PlaylistEntry * queue_pop ();
+
+public:
+ bool modified;
+ ScanStatus scan_status;
+ String filename, title;
+ int resume_time;
+
+private:
+ Playlist::ID * m_id;
+ Index<EntryPtr> m_entries;
+ PlaylistEntry * m_position, * m_focus;
+ int m_selected_count;
+ int m_last_shuffle_num;
+ Index<PlaylistEntry *> m_queued;
+ int64_t m_total_length, m_selected_length;
+ Playlist::Update m_last_update, m_next_update;
+ bool m_position_changed;
+};
+
+/* callbacks or "signals" (in the QObject sense) */
+void pl_signal_entry_deleted (PlaylistEntry * entry);
+void pl_signal_position_changed (Playlist::ID * id);
+void pl_signal_update_queued (Playlist::ID * id, Playlist::UpdateLevel level, int flags);
+void pl_signal_rescan_needed (Playlist::ID * id);
+void pl_signal_playlist_deleted (Playlist::ID * id);
+
+#endif // PLAYLIST_DATA_H
diff --git a/src/libaudcore/playlist-files.cc b/src/libaudcore/playlist-files.cc
index 872e9ff..29cbff0 100644
--- a/src/libaudcore/playlist-files.cc
+++ b/src/libaudcore/playlist-files.cc
@@ -26,7 +26,7 @@
#include "plugins-internal.h"
#include "runtime.h"
-EXPORT bool aud_filename_is_playlist (const char * filename)
+EXPORT bool Playlist::filename_is_playlist (const char * filename)
{
StringBuf ext = uri_get_extension (filename);
@@ -93,7 +93,7 @@ bool playlist_load (const char * filename, String & title, Index<PlaylistAddItem
// All support for adding folders, cuesheets, subtunes, etc. is omitted here.
// Additionally, in order to avoid heavy I/O at startup, failed entries are not
// rescanned; they can be rescanned later by refreshing the playlist. */
-bool playlist_insert_playlist_raw (int list, int at, const char * filename)
+bool PlaylistEx::insert_flat_playlist (const char * filename) const
{
String title;
Index<PlaylistAddItem> items;
@@ -101,26 +101,26 @@ bool playlist_insert_playlist_raw (int list, int at, const char * filename)
if (! playlist_load (filename, title, items))
return false;
- if (title && ! aud_playlist_entry_count (list))
- aud_playlist_set_title (list, title);
+ if (title)
+ set_title (title);
- playlist_entry_insert_batch_raw (list, at, std::move (items));
+ insert_flat_items (0, std::move (items));
return true;
}
-EXPORT bool aud_playlist_save (int list, const char * filename, Playlist::GetMode mode)
+EXPORT bool Playlist::save_to_file (const char * filename, GetMode mode) const
{
- String title = aud_playlist_get_title (list);
+ String title = get_title ();
Index<PlaylistAddItem> items;
- items.insert (0, aud_playlist_entry_count (list));
+ items.insert (0, n_entries ());
int i = 0;
for (PlaylistAddItem & item : items)
{
- item.filename = aud_playlist_entry_get_filename (list, i);
- item.tuple = aud_playlist_entry_get_tuple (list, i, mode);
+ item.filename = entry_filename (i);
+ item.tuple = entry_tuple (i, mode);
item.tuple.delete_fallbacks ();
i ++;
}
@@ -153,7 +153,7 @@ EXPORT bool aud_playlist_save (int list, const char * filename, Playlist::GetMod
return false;
}
-EXPORT Index<Playlist::SaveFormat> aud_playlist_save_formats ()
+EXPORT Index<Playlist::SaveFormat> Playlist::save_formats ()
{
Index<Playlist::SaveFormat> formats;
diff --git a/src/libaudcore/playlist-internal.h b/src/libaudcore/playlist-internal.h
index 781cd4c..bbccd6b 100644
--- a/src/libaudcore/playlist-internal.h
+++ b/src/libaudcore/playlist-internal.h
@@ -33,33 +33,44 @@ struct DecodeInfo
String error;
};
+/* extended handle for accessing internal playlist functions */
+class PlaylistEx : public Playlist
+{
+public:
+ PlaylistEx (Playlist::ID * id = nullptr) :
+ Playlist (id) {}
+ PlaylistEx (Playlist playlist) :
+ Playlist (playlist) {}
+
+ int stamp () const;
+
+ static Playlist insert_with_stamp (int at, int stamp);
+
+ bool get_modified () const;
+ void set_modified (bool modified) const;
+
+ bool insert_flat_playlist (const char * filename) const;
+ void insert_flat_items (int at, Index<PlaylistAddItem> && items) const;
+};
+
/* playlist.cc */
void playlist_init ();
void playlist_enable_scan (bool enable);
+void playlist_clear_updates ();
void playlist_end ();
-void playlist_insert_with_id (int at, int id);
-void playlist_set_modified (int playlist, bool modified);
-bool playlist_get_modified (int playlist);
-
void playlist_load_state ();
void playlist_save_state ();
-void playlist_entry_insert_batch_raw (int playlist, int at, Index<PlaylistAddItem> && items);
-
-bool playlist_prev_song (int playlist);
-bool playlist_next_song (int playlist, bool repeat);
-
DecodeInfo playback_entry_read (int serial);
void playback_entry_set_tuple (int serial, Tuple && tuple);
/* playlist-cache.cc */
void playlist_cache_load (Index<PlaylistAddItem> & items);
-void playlist_cache_clear ();
+void playlist_cache_clear (void * = nullptr);
/* playlist-files.cc */
bool playlist_load (const char * filename, String & title, Index<PlaylistAddItem> & items);
-bool playlist_insert_playlist_raw (int list, int at, const char * filename);
/* playlist-utils.cc */
void load_playlists ();
diff --git a/src/libaudcore/playlist-utils.cc b/src/libaudcore/playlist-utils.cc
index d1f8ffd..c017261 100644
--- a/src/libaudcore/playlist-utils.cc
+++ b/src/libaudcore/playlist-utils.cc
@@ -67,51 +67,27 @@ static int tuple_compare_int (const Tuple & a, const Tuple & b, Tuple::Field fie
}
static int tuple_compare_title (const Tuple & a, const Tuple & b)
-{
- return tuple_compare_string (a, b, Tuple::Title);
-}
-
+ { return tuple_compare_string (a, b, Tuple::Title); }
static int tuple_compare_album (const Tuple & a, const Tuple & b)
-{
- return tuple_compare_string (a, b, Tuple::Album);
-}
-
+ { return tuple_compare_string (a, b, Tuple::Album); }
static int tuple_compare_artist (const Tuple & a, const Tuple & b)
-{
- return tuple_compare_string (a, b, Tuple::Artist);
-}
-
+ { return tuple_compare_string (a, b, Tuple::Artist); }
static int tuple_compare_album_artist (const Tuple & a, const Tuple & b)
-{
- return tuple_compare_string (a, b, Tuple::AlbumArtist);
-}
-
+ { return tuple_compare_string (a, b, Tuple::AlbumArtist); }
static int tuple_compare_date (const Tuple & a, const Tuple & b)
-{
- return tuple_compare_int (a, b, Tuple::Year);
-}
-
+ { return tuple_compare_int (a, b, Tuple::Year); }
static int tuple_compare_genre (const Tuple & a, const Tuple & b)
-{
- return tuple_compare_string (a, b, Tuple::Genre);
-}
-
+ { return tuple_compare_string (a, b, Tuple::Genre); }
static int tuple_compare_track (const Tuple & a, const Tuple & b)
-{
- return tuple_compare_int (a, b, Tuple::Track);
-}
-
+ { return tuple_compare_int (a, b, Tuple::Track); }
static int tuple_compare_formatted_title (const Tuple & a, const Tuple & b)
-{
- return tuple_compare_string (a, b, Tuple::FormattedTitle);
-}
-
+ { return tuple_compare_string (a, b, Tuple::FormattedTitle); }
static int tuple_compare_length (const Tuple & a, const Tuple & b)
-{
- return tuple_compare_int (a, b, Tuple::Length);
-}
+ { return tuple_compare_int (a, b, Tuple::Length); }
+static int tuple_compare_comment (const Tuple & a, const Tuple & b)
+ { return tuple_compare_string (a, b, Tuple::Comment); }
-static const PlaylistStringCompareFunc filename_comparisons[] = {
+static const Playlist::StringCompareFunc filename_comparisons[] = {
str_compare_encoded, // path
filename_compare_basename, // filename
nullptr, // title
@@ -122,10 +98,11 @@ static const PlaylistStringCompareFunc filename_comparisons[] = {
nullptr, // genre
nullptr, // track
nullptr, // formatted title
- nullptr // length
+ nullptr, // length
+ nullptr // comment
};
-static const PlaylistTupleCompareFunc tuple_comparisons[] = {
+static const Playlist::TupleCompareFunc tuple_comparisons[] = {
nullptr, // path
nullptr, // filename
tuple_compare_title,
@@ -136,100 +113,101 @@ static const PlaylistTupleCompareFunc tuple_comparisons[] = {
tuple_compare_genre,
tuple_compare_track,
tuple_compare_formatted_title,
- tuple_compare_length
+ tuple_compare_length,
+ tuple_compare_comment
};
static_assert (aud::n_elems (filename_comparisons) == Playlist::n_sort_types &&
aud::n_elems (tuple_comparisons) == Playlist::n_sort_types,
"Update playlist comparison functions");
-EXPORT void aud_playlist_sort_by_scheme (int playlist, Playlist::SortType scheme)
+EXPORT void Playlist::sort_entries (SortType scheme) const
{
if (filename_comparisons[scheme])
- aud_playlist_sort_by_filename (playlist, filename_comparisons[scheme]);
+ sort_by_filename (filename_comparisons[scheme]);
else if (tuple_comparisons[scheme])
- aud_playlist_sort_by_tuple (playlist, tuple_comparisons[scheme]);
+ sort_by_tuple (tuple_comparisons[scheme]);
}
-EXPORT void aud_playlist_sort_selected_by_scheme (int playlist, Playlist::SortType scheme)
+EXPORT void Playlist::sort_selected (SortType scheme) const
{
if (filename_comparisons[scheme])
- aud_playlist_sort_selected_by_filename (playlist, filename_comparisons[scheme]);
+ sort_selected_by_filename (filename_comparisons[scheme]);
else if (tuple_comparisons[scheme])
- aud_playlist_sort_selected_by_tuple (playlist, tuple_comparisons[scheme]);
+ sort_selected_by_tuple (tuple_comparisons[scheme]);
}
/* FIXME: this considers empty fields as duplicates */
-EXPORT void aud_playlist_remove_duplicates_by_scheme (int playlist, Playlist::SortType scheme)
+EXPORT void Playlist::remove_duplicates (SortType scheme) const
{
- int entries = aud_playlist_entry_count (playlist);
+ int entries = n_entries ();
if (entries < 1)
return;
- aud_playlist_select_all (playlist, false);
+ select_all (false);
if (filename_comparisons[scheme])
{
- PlaylistStringCompareFunc compare = filename_comparisons[scheme];
+ StringCompareFunc compare = filename_comparisons[scheme];
- aud_playlist_sort_by_filename (playlist, compare);
- String last = aud_playlist_entry_get_filename (playlist, 0);
+ sort_by_filename (compare);
+ String last = entry_filename (0);
- for (int count = 1; count < entries; count ++)
+ for (int i = 1; i < entries; i ++)
{
- String current = aud_playlist_entry_get_filename (playlist, count);
+ String current = entry_filename (i);
if (compare (last, current) == 0)
- aud_playlist_entry_set_selected (playlist, count, true);
+ select_entry (i, true);
last = current;
}
}
else if (tuple_comparisons[scheme])
{
- PlaylistTupleCompareFunc compare = tuple_comparisons[scheme];
+ TupleCompareFunc compare = tuple_comparisons[scheme];
- aud_playlist_sort_by_tuple (playlist, compare);
- Tuple last = aud_playlist_entry_get_tuple (playlist, 0);
+ sort_by_tuple (compare);
+ Tuple last = entry_tuple (0);
- for (int count = 1; count < entries; count ++)
+ for (int i = 1; i < entries; i ++)
{
- Tuple current = aud_playlist_entry_get_tuple (playlist, count);
+ Tuple current = entry_tuple (i);
if (last.valid () && current.valid () && compare (last, current) == 0)
- aud_playlist_entry_set_selected (playlist, count, true);
+ select_entry (i, true);
last = std::move (current);
}
}
- aud_playlist_delete_selected (playlist);
+ remove_selected ();
}
-EXPORT void aud_playlist_remove_failed (int playlist)
+EXPORT void Playlist::remove_unavailable () const
{
- int entries = aud_playlist_entry_count (playlist);
+ int entries = n_entries ();
- aud_playlist_select_all (playlist, false);
+ select_all (false);
- for (int count = 0; count < entries; count ++)
+ for (int i = 0; i < entries; i ++)
{
- String filename = aud_playlist_entry_get_filename (playlist, count);
+ String filename = entry_filename (i);
/* use VFS_NO_ACCESS since VFS_EXISTS doesn't distinguish between
* inaccessible files and URI schemes that don't support file_test() */
if (VFSFile::test_file (filename, VFS_NO_ACCESS))
- aud_playlist_entry_set_selected (playlist, count, true);
+ select_entry (i, true);
}
- aud_playlist_delete_selected (playlist);
+ remove_selected ();
}
-EXPORT void aud_playlist_select_by_patterns (int playlist, const Tuple & patterns)
+EXPORT void Playlist::select_by_patterns (const Tuple & patterns) const
{
- int entries = aud_playlist_entry_count (playlist);
+ int entries = n_entries ();
- aud_playlist_select_all (playlist, true);
+ select_all (true);
for (Tuple::Field field : {Tuple::Title, Tuple::Album, Tuple::Artist, Tuple::Basename})
{
@@ -240,16 +218,16 @@ EXPORT void aud_playlist_select_by_patterns (int playlist, const Tuple & pattern
G_REGEX_CASELESS, (GRegexMatchFlags) 0, nullptr)))
continue;
- for (int entry = 0; entry < entries; entry ++)
+ for (int i = 0; i < entries; i ++)
{
- if (! aud_playlist_entry_get_selected (playlist, entry))
+ if (! entry_selected (i))
continue;
- Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry);
+ Tuple tuple = entry_tuple (i);
String string = tuple.get_str (field);
if (! string || ! g_regex_match (regex, string, (GRegexMatchFlags) 0, nullptr))
- aud_playlist_entry_set_selected (playlist, entry, false);
+ select_entry (i, false);
}
g_regex_unref (regex);
@@ -279,9 +257,9 @@ static void load_playlists_real ()
if (! g_file_test (path, G_FILE_TEST_EXISTS))
break;
- aud_playlist_insert (count);
- playlist_insert_playlist_raw (count, 0, filename_to_uri (path));
- playlist_set_modified (count, true);
+ PlaylistEx playlist = Playlist::insert_playlist (count);
+ playlist.insert_flat_playlist (filename_to_uri (path));
+ playlist.set_modified (true);
}
/* unique ID-based naming scheme */
@@ -308,24 +286,19 @@ static void load_playlists_real ()
if (! g_file_test (path, G_FILE_TEST_EXISTS))
path.steal (filename_build ({folder, name2}));
- playlist_insert_with_id (count + i, atoi (number));
- playlist_insert_playlist_raw (count + i, 0, filename_to_uri (path));
- playlist_set_modified (count + i, false);
-
- if (g_str_has_suffix (path, ".xspf"))
- playlist_set_modified (count + i, true);
+ PlaylistEx playlist = PlaylistEx::insert_with_stamp (count + i, atoi (number));
+ playlist.insert_flat_playlist (filename_to_uri (path));
+ playlist.set_modified (g_str_has_suffix (path, ".xspf"));
}
DONE:
- if (! aud_playlist_count ())
- aud_playlist_insert (0);
-
- aud_playlist_set_active (0);
+ if (! Playlist::n_playlists ())
+ Playlist::insert_playlist (0);
}
static void save_playlists_real ()
{
- int lists = aud_playlist_count ();
+ int lists = Playlist::n_playlists ();
const char * folder = aud_get_path (AudPath::PlaylistDir);
/* save playlists */
@@ -335,15 +308,15 @@ static void save_playlists_real ()
for (int i = 0; i < lists; i ++)
{
- int id = aud_playlist_get_unique_id (i);
- StringBuf number = int_to_str (id);
+ PlaylistEx playlist = Playlist::by_index (i);
+ StringBuf number = int_to_str (playlist.stamp ());
StringBuf name = str_concat ({number, ".audpl"});
- if (playlist_get_modified (i))
+ if (playlist.get_modified ())
{
StringBuf path = filename_build ({folder, name});
- aud_playlist_save (i, filename_to_uri (path), Playlist::NoWait);
- playlist_set_modified (i, false);
+ playlist.save_to_file (filename_to_uri (path), Playlist::NoWait);
+ playlist.set_modified (false);
}
order.append (String (number));
diff --git a/src/libaudcore/playlist.cc b/src/libaudcore/playlist.cc
index f2bd034..fff37e1 100644
--- a/src/libaudcore/playlist.cc
+++ b/src/libaudcore/playlist.cc
@@ -1,6 +1,6 @@
/*
* playlist.cc
- * Copyright 2009-2014 John Lindgren
+ * Copyright 2009-2017 John Lindgren
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -17,11 +17,7 @@
* the use of this software.
*/
-// uncomment to print a backtrace when scanning blocks the main thread
-// #define WARN_BLOCKED
-
#include "playlist-internal.h"
-#include "runtime.h"
#include <assert.h>
#include <pthread.h>
@@ -35,25 +31,13 @@
#include "drct.h"
#include "hook.h"
#include "i18n.h"
-#include "interface.h"
#include "internal.h"
#include "list.h"
#include "mainloop.h"
#include "multihash.h"
-#include "objects.h"
#include "parse.h"
-#include "plugins.h"
-#include "scanner.h"
-#include "tuple.h"
-#include "tuple-compiler.h"
-
-#ifdef WARN_BLOCKED
-#include <execinfo.h>
-#include <stdio.h>
-#include <stdlib.h>
-#endif
-
-using namespace Playlist;
+#include "playlist-data.h"
+#include "runtime.h"
enum {
ResumeStop,
@@ -61,15 +45,18 @@ enum {
ResumePause
};
-enum PlaybackChange {
- NoChange,
- NextSong,
- PlaybackStopped
+/* update hooks */
+enum {
+ SetActive = (1 << 0),
+ SetPlaying = (1 << 1),
+ PlaybackBegin = (1 << 2),
+ PlaybackStop = (1 << 3)
};
-enum {
- QueueChanged = (1 << 0),
- DelayedUpdate = (1 << 1)
+enum class UpdateState {
+ None,
+ Delayed,
+ Queued
};
#define STATE_FILE "playlist-state"
@@ -82,67 +69,21 @@ enum {
return __VA_ARGS__; \
} while (0)
-#define ENTER_GET_PLAYLIST(...) ENTER; \
- PlaylistData * playlist = lookup_playlist (playlist_num); \
+#define ENTER_GET_PLAYLIST(...) \
+ ENTER; \
+ PlaylistData * playlist = m_id ? m_id->data : nullptr; \
if (! playlist) \
- RETURN (__VA_ARGS__);
+ RETURN (__VA_ARGS__)
-#define ENTER_GET_ENTRY(...) ENTER_GET_PLAYLIST (__VA_ARGS__); \
- Entry * entry = lookup_entry (playlist, entry_num); \
- if (! entry) \
- RETURN (__VA_ARGS__);
+#define SIMPLE_WRAPPER(type, failcode, func, ...) \
+ ENTER_GET_PLAYLIST (failcode); \
+ type retval = playlist->func (__VA_ARGS__); \
+ RETURN (retval)
-struct UniqueID
-{
- constexpr UniqueID (int val) :
- val (val) {}
-
- operator int () const
- { return val; }
-
- unsigned hash () const
- { return int32_hash (val); }
-
-private:
- int val;
-};
-
-struct Entry {
- Entry (PlaylistAddItem && item);
- ~Entry ();
-
- void format ();
- void set_tuple (Tuple && new_tuple);
-
- String filename;
- PluginHandle * decoder;
- Tuple tuple;
- String error;
- int number;
- int length;
- int shuffle_num;
- bool selected, queued;
-};
-
-struct PlaylistData {
- PlaylistData (int id);
- ~PlaylistData ();
-
- void set_entry_tuple (Entry * entry, Tuple && tuple);
-
- int number, unique_id;
- String filename, title;
- bool modified;
- Index<SmartPtr<Entry>> entries;
- Entry * position, * focus;
- int selected_count;
- int last_shuffle_num;
- Index<Entry *> queued;
- int64_t total_length, selected_length;
- bool scanning, scan_ending;
- Update next_update, last_update;
- int resume_time;
-};
+#define SIMPLE_VOID_WRAPPER(func, ...) \
+ ENTER_GET_PLAYLIST (); \
+ playlist->func (__VA_ARGS__); \
+ LEAVE
static const char * const default_title = N_("New Playlist");
static const char * const temp_title = N_("Now Playing");
@@ -150,31 +91,44 @@ static const char * const temp_title = N_("Now Playing");
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
-#ifdef WARN_BLOCKED
-static pthread_t main_thread;
-#endif
+/*
+ * Each playlist is associated with its own ID struct, which contains a unique
+ * integer "stamp" (this is the source of the internal filenames 1000.audpl,
+ * 1001.audpl, etc.) The ID struct also serves as a "weak" pointer to the
+ * actual data, and persists even after the playlist itself is destroyed.
+ * The IDs are stored in a hash table, allowing lookup by stamp.
+ *
+ * In brief: Playlist (public handle)
+ * points to ->
+ * Playlist::ID (unique ID / weak pointer)
+ * points to ->
+ * PlaylistData (actual playlist data)
+ */
+struct Playlist::ID
+{
+ int stamp; // integer stamp, determines filename
+ int index; // display order
+ PlaylistData * data; // pointer to actual playlist data
+};
-/* The unique ID table contains pointers to PlaylistData for ID's in use and nullptr
- * for "dead" (previously used and therefore unavailable) ID's. */
-static SimpleHash<UniqueID, PlaylistData *> unique_id_table;
-static int next_unique_id = 1000;
+static SimpleHash<IntHashKey, Playlist::ID> id_table;
+static int next_stamp = 1000;
static Index<SmartPtr<PlaylistData>> playlists;
-static PlaylistData * active_playlist = nullptr;
-static PlaylistData * playing_playlist = nullptr;
+static Playlist::ID * active_id = nullptr;
+static Playlist::ID * playing_id = nullptr;
static int resume_playlist = -1;
static bool resume_paused = false;
-static bool metadata_fallbacks = false;
-static TupleCompiler title_formatter;
-
static QueuedFunc queued_update;
-static UpdateLevel update_level;
-static bool update_delayed;
+static Playlist::UpdateLevel update_level;
+static int update_hooks;
+static UpdateState update_state;
struct ScanItem : public ListNode
{
- ScanItem (PlaylistData * playlist, Entry * entry, ScanRequest * request, bool for_playback) :
+ ScanItem (PlaylistData * playlist, PlaylistEntry * entry,
+ ScanRequest * request, bool for_playback) :
playlist (playlist),
entry (entry),
request (request),
@@ -182,7 +136,7 @@ struct ScanItem : public ListNode
handled_by_playback (false) {}
PlaylistData * playlist;
- Entry * entry;
+ PlaylistEntry * entry;
ScanRequest * request;
bool for_playback;
bool handled_by_playback;
@@ -193,301 +147,156 @@ static int scan_playlist, scan_row;
static List<ScanItem> scan_list;
static void scan_finish (ScanRequest * request);
-static void scan_cancel (Entry * entry);
-static void scan_queue_playlist (PlaylistData * playlist);
+static void scan_cancel (PlaylistEntry * entry);
static void scan_restart ();
-static bool next_song_locked (PlaylistData * playlist, bool repeat, int hint);
-
-static void playlist_reformat_titles ();
-static void playlist_trigger_scan ();
-
-void Entry::format ()
+/* creates a new playlist with the requested stamp (if not already in use) */
+static Playlist::ID * create_playlist (int stamp)
{
- tuple.delete_fallbacks ();
+ Playlist::ID * id;
- if (metadata_fallbacks)
- tuple.generate_fallbacks ();
+ if (stamp >= 0 && ! id_table.lookup (stamp))
+ id = id_table.add (stamp, {stamp, -1, nullptr});
else
- tuple.generate_title ();
-
- title_formatter.format (tuple);
-}
-
-void Entry::set_tuple (Tuple && new_tuple)
-{
- /* Since 3.8, cuesheet entries are handled differently. The entry filename
- * points to the .cue file, and the path to the actual audio file is stored
- * in the Tuple::AudioFile. If Tuple::AudioFile is not set, then assume
- * that the playlist was created by an older version of Audacious, and
- * revert to the former behavior (don't refresh this entry). */
- if (tuple.is_set (Tuple::StartTime) && ! tuple.is_set (Tuple::AudioFile))
- return;
-
- error = String ();
-
- if (! new_tuple.valid ())
- new_tuple.set_filename (filename);
-
- length = aud::max (0, new_tuple.get_int (Tuple::Length));
- tuple = std::move (new_tuple);
-
- format ();
-}
-
-void PlaylistData::set_entry_tuple (Entry * entry, Tuple && tuple)
-{
- total_length -= entry->length;
- if (entry->selected)
- selected_length -= entry->length;
-
- entry->set_tuple (std::move (tuple));
-
- total_length += entry->length;
- if (entry->selected)
- selected_length += entry->length;
-}
-
-Entry::Entry (PlaylistAddItem && item) :
- filename (item.filename),
- decoder (item.decoder),
- number (-1),
- length (0),
- shuffle_num (0),
- selected (false),
- queued (false)
-{
- set_tuple (std::move (item.tuple));
-}
-
-Entry::~Entry ()
-{
- scan_cancel (this);
-}
-
-static int new_unique_id (int preferred)
-{
- if (preferred >= 0 && ! unique_id_table.lookup (preferred))
- return preferred;
-
- while (unique_id_table.lookup (next_unique_id))
- next_unique_id ++;
+ {
+ while (id_table.lookup (next_stamp))
+ next_stamp ++;
- return next_unique_id ++;
-}
+ id = id_table.add (next_stamp, {next_stamp, -1, nullptr});
+ }
-PlaylistData::PlaylistData (int id) :
- number (-1),
- unique_id (new_unique_id (id)),
- title (_(default_title)),
- modified (true),
- position (nullptr),
- focus (nullptr),
- selected_count (0),
- last_shuffle_num (0),
- total_length (0),
- selected_length (0),
- scanning (false),
- scan_ending (false),
- next_update (),
- last_update (),
- resume_time (0)
-{
- unique_id_table.add (unique_id, (PlaylistData *) this);
-}
+ id->data = new PlaylistData (id, _(default_title));
-PlaylistData::~PlaylistData ()
-{
- unique_id_table.add (unique_id, nullptr);
+ return id;
}
static void number_playlists (int at, int length)
{
for (int i = at; i < at + length; i ++)
- playlists[i]->number = i;
-}
-
-static PlaylistData * lookup_playlist (int i)
-{
- return (i >= 0 && i < playlists.len ()) ? playlists[i].get () : nullptr;
-}
-
-static void number_entries (PlaylistData * p, int at, int length)
-{
- for (int i = at; i < at + length; i ++)
- p->entries[i]->number = i;
-}
-
-static Entry * lookup_entry (PlaylistData * p, int i)
-{
- return (i >= 0 && i < p->entries.len ()) ? p->entries[i].get () : nullptr;
+ playlists[i]->id ()->index = i;
}
static void update (void *)
{
ENTER;
+ int hooks = update_hooks;
+ auto level = update_level;
+
+ Index<PlaylistEx> position_change_list;
+
for (auto & p : playlists)
{
- p->last_update = p->next_update;
- p->next_update = Update ();
+ bool position_changed = false;
+ p->swap_updates (position_changed);
+
+ if (position_changed)
+ position_change_list.append (p->id ());
}
- UpdateLevel level = update_level;
- update_level = NoUpdate;
- update_delayed = false;
+ update_hooks = 0;
+ update_level = Playlist::NoUpdate;
+ update_state = UpdateState::None;
LEAVE;
- hook_call ("playlist update", aud::to_ptr (level));
+ if (level != Playlist::NoUpdate)
+ hook_call ("playlist update", aud::to_ptr (level));
+
+ for (PlaylistEx playlist : position_change_list)
+ hook_call ("playlist position", aud::to_ptr (playlist));
+
+ if ((hooks & SetActive))
+ hook_call ("playlist activate", nullptr);
+ if ((hooks & SetPlaying))
+ hook_call ("playlist set playing", nullptr);
+ if ((hooks & PlaybackBegin))
+ hook_call ("playback begin", nullptr);
+ if ((hooks & PlaybackStop))
+ hook_call ("playback stop", nullptr);
}
-static void queue_update (UpdateLevel level, PlaylistData * p, int at, int count, int flags = 0)
+static void queue_update_hooks (int hooks)
{
- if (p)
- {
- if (level == Structure)
- scan_queue_playlist (p);
+ if ((hooks & PlaybackBegin))
+ update_hooks &= ~PlaybackStop;
+ if ((hooks & PlaybackStop))
+ update_hooks &= ~PlaybackBegin;
- if (level >= Metadata)
- {
- if (p == playing_playlist && p->position)
- playback_set_info (p->position->number, p->position->tuple.ref ());
+ update_hooks |= hooks;
- p->modified = true;
- }
-
- if (p->next_update.level)
- {
- p->next_update.level = aud::max (p->next_update.level, level);
- p->next_update.before = aud::min (p->next_update.before, at);
- p->next_update.after = aud::min (p->next_update.after, p->entries.len () - at - count);
- }
- else
- {
- p->next_update.level = level;
- p->next_update.before = at;
- p->next_update.after = p->entries.len () - at - count;
- }
-
- if ((flags & QueueChanged))
- p->next_update.queue_changed = true;
+ if (update_state < UpdateState::Queued)
+ {
+ queued_update.queue (update, nullptr);
+ update_state = UpdateState::Queued;
}
+}
- if (level == Structure)
+static void queue_global_update (Playlist::UpdateLevel level, int flags = 0)
+{
+ if (level == Playlist::Structure)
scan_restart ();
- // only allow delayed update if a scan is still in progress
- if ((flags & DelayedUpdate) && scan_enabled && p && (p->scanning || p->scan_ending))
+ if ((flags & PlaylistData::DelayedUpdate))
{
- if (! update_level)
+ if (update_state < UpdateState::Delayed)
{
queued_update.queue (250, update, nullptr);
- update_delayed = true;
+ update_state = UpdateState::Delayed;
}
}
else
{
- if (! update_level || update_delayed)
+ if (update_state < UpdateState::Queued)
{
queued_update.queue (update, nullptr);
- update_delayed = false;
+ update_state = UpdateState::Queued;
}
}
update_level = aud::max (update_level, level);
}
-EXPORT bool aud_playlist_update_pending (int playlist_num)
-{
- if (playlist_num >= 0)
- {
- ENTER_GET_PLAYLIST (false);
- bool pending = playlist->next_update.level ? true : false;
- RETURN (pending);
- }
- else
- {
- ENTER;
- bool pending = update_level ? true : false;
- RETURN (pending);
- }
-}
-
-EXPORT Update aud_playlist_update_detail (int playlist_num)
+EXPORT bool Playlist::update_pending_any ()
{
- ENTER_GET_PLAYLIST (Update ());
- Update update = playlist->last_update;
- RETURN (update);
+ ENTER;
+ bool pending = (update_level != Playlist::NoUpdate);
+ RETURN (pending);
}
-EXPORT bool aud_playlist_scan_in_progress (int playlist_num)
+EXPORT bool Playlist::scan_in_progress () const
{
- if (playlist_num >= 0)
- {
- ENTER_GET_PLAYLIST (false);
- bool scanning = playlist->scanning || playlist->scan_ending;
- RETURN (scanning);
- }
- else
- {
- ENTER;
-
- bool scanning = false;
- for (auto & p : playlists)
- {
- if (p->scanning || p->scan_ending)
- scanning = true;
- }
-
- RETURN (scanning);
- }
+ ENTER_GET_PLAYLIST (false);
+ bool scanning = (playlist->scan_status != PlaylistData::NotScanning);
+ RETURN (scanning);
}
-static ScanItem * scan_list_find_playlist (PlaylistData * playlist)
+EXPORT bool Playlist::scan_in_progress_any ()
{
- for (ScanItem * item = scan_list.head (); item; item = scan_list.next (item))
- {
- if (item->playlist == playlist)
- return item;
- }
-
- return nullptr;
-}
+ ENTER;
-static ScanItem * scan_list_find_entry (Entry * entry)
-{
- for (ScanItem * item = scan_list.head (); item; item = scan_list.next (item))
+ bool scanning = false;
+ for (auto & p : playlists)
{
- if (item->entry == entry)
- return item;
+ if (p->scan_status != PlaylistData::NotScanning)
+ scanning = true;
}
- return nullptr;
+ RETURN (scanning);
}
-static ScanItem * scan_list_find_request (ScanRequest * request)
+static ScanItem * scan_list_find_entry (PlaylistEntry * entry)
{
- for (ScanItem * item = scan_list.head (); item; item = scan_list.next (item))
- {
- if (item->request == request)
- return item;
- }
+ auto match = [entry] (const ScanItem & item)
+ { return item.entry == entry; };
- return nullptr;
+ return scan_list.find (match);
}
-static void scan_queue_entry (PlaylistData * playlist, Entry * entry, bool for_playback = false)
+static void scan_queue_entry (PlaylistData * playlist, PlaylistEntry * entry, bool for_playback = false)
{
- int flags = 0;
- if (! entry->tuple.valid ())
- flags |= SCAN_TUPLE;
- if (for_playback)
- flags |= (SCAN_IMAGE | SCAN_FILE);
-
- /* scanner uses Tuple::AudioFile from existing tuple, if valid */
- auto request = new ScanRequest (entry->filename, flags, scan_finish,
- entry->decoder, (flags & SCAN_TUPLE) ? Tuple () : entry->tuple.ref ());
+ int extra_flags = for_playback ? (SCAN_IMAGE | SCAN_FILE) : 0;
+ auto request = playlist->create_scan_request (entry, scan_finish, extra_flags);
scan_list.append (new ScanItem (playlist, entry, request, for_playback));
@@ -498,33 +307,37 @@ static void scan_queue_entry (PlaylistData * playlist, Entry * entry, bool for_p
static void scan_reset_playback ()
{
- for (ScanItem * item = scan_list.head (); item; item = scan_list.next (item))
- {
- if (item->for_playback)
- {
- item->for_playback = false;
+ auto match = [] (const ScanItem & item)
+ { return item.for_playback; };
- /* if playback was canceled before the entry was scanned, requeue it */
- if (! item->handled_by_playback)
- scanner_request (item->request);
- }
- }
+ ScanItem * item = scan_list.find (match);
+ if (! item)
+ return;
+
+ item->for_playback = false;
+
+ /* if playback was canceled before the entry was scanned, requeue it */
+ if (! item->handled_by_playback)
+ scanner_request (item->request);
}
static void scan_check_complete (PlaylistData * playlist)
{
- if (! playlist->scan_ending || scan_list_find_playlist (playlist))
+ auto match = [playlist] (const ScanItem & item)
+ { return item.playlist == playlist; };
+
+ if (playlist->scan_status != PlaylistData::ScanEnding || scan_list.find (match))
return;
- playlist->scan_ending = false;
+ playlist->scan_status = PlaylistData::NotScanning;
- if (update_delayed)
+ if (update_state == UpdateState::Delayed)
{
queued_update.queue (update, nullptr);
- update_delayed = false;
+ update_state = UpdateState::Queued;
}
- event_queue_cancel ("playlist scan complete", nullptr);
+ event_queue_cancel ("playlist scan complete");
event_queue ("playlist scan complete", nullptr);
}
@@ -537,24 +350,25 @@ static bool scan_queue_next_entry ()
{
PlaylistData * playlist = playlists[scan_playlist].get ();
- if (playlist->scanning)
+ if (playlist->scan_status == PlaylistData::ScanActive)
{
- while (scan_row < playlist->entries.len ())
+ while (1)
{
- Entry * entry = playlist->entries[scan_row ++].get ();
+ scan_row = playlist->next_unscanned_entry (scan_row);
+ if (scan_row < 0)
+ break;
- // blacklist stdin
- if (entry->tuple.state () == Tuple::Initial &&
- ! scan_list_find_entry (entry) &&
- strncmp (entry->filename, "stdin://", 8))
+ auto entry = playlist->entry_at (scan_row);
+ if (! scan_list_find_entry (entry))
{
scan_queue_entry (playlist, entry);
return true;
}
+
+ scan_row ++;
}
- playlist->scanning = false;
- playlist->scan_ending = true;
+ playlist->scan_status = PlaylistData::ScanEnding;
scan_check_complete (playlist);
}
@@ -586,32 +400,24 @@ static void scan_finish (ScanRequest * request)
{
ENTER;
- ScanItem * item = scan_list_find_request (request);
+ auto match = [request] (const ScanItem & item)
+ { return item.request == request; };
+
+ ScanItem * item = scan_list.find (match);
if (! item)
RETURN ();
PlaylistData * playlist = item->playlist;
- Entry * entry = item->entry;
+ PlaylistEntry * entry = item->entry;
scan_list.remove (item);
- if (! entry->decoder)
- entry->decoder = request->decoder;
-
- if (! entry->tuple.valid () && request->tuple.valid ())
- {
- playlist->set_entry_tuple (entry, std::move (request->tuple));
- queue_update (Metadata, playlist, entry->number, 1, DelayedUpdate);
- }
+ // only use delayed update if a scan is still in progress
+ int update_flags = 0;
+ if (scan_enabled && playlist->scan_status != PlaylistData::NotScanning)
+ update_flags = PlaylistData::DelayedUpdate;
- if (! entry->decoder || ! entry->tuple.valid ())
- entry->error = request->error;
-
- if (entry->tuple.state () == Tuple::Initial)
- {
- entry->tuple.set_state (Tuple::Failed);
- queue_update (Metadata, playlist, entry->number, 1, DelayedUpdate);
- }
+ playlist->update_entry_from_scan (entry, request, update_flags);
delete item;
@@ -623,7 +429,7 @@ static void scan_finish (ScanRequest * request)
LEAVE;
}
-static void scan_cancel (Entry * entry)
+static void scan_cancel (PlaylistEntry * entry)
{
ScanItem * item = scan_list_find_entry (entry);
if (! item)
@@ -633,12 +439,6 @@ static void scan_cancel (Entry * entry)
delete (item);
}
-static void scan_queue_playlist (PlaylistData * playlist)
-{
- playlist->scanning = true;
- playlist->scan_ending = false;
-}
-
static void scan_restart ()
{
scan_playlist = 0;
@@ -646,52 +446,25 @@ static void scan_restart ()
scan_schedule ();
}
-#ifdef WARN_BLOCKED
-static void warn_main_thread_blocked ()
-{
- printf ("\nMain thread blocked, backtrace:\n");
-
- void * syms[100];
- int n_syms = backtrace (syms, aud::n_elems (syms));
- char * * names = backtrace_symbols (syms, n_syms);
-
- for (int i = 0; i < n_syms; i ++)
- printf ("%d. %s\n", i, names[i]);
-
- free (names);
-}
-#endif
-
/* mutex may be unlocked during the call */
-static Entry * get_entry (int playlist_num, int entry_num,
- bool need_decoder, bool need_tuple)
+static void wait_for_entry (PlaylistData * playlist, int entry_num, bool need_decoder, bool need_tuple)
{
-#ifdef WARN_BLOCKED
- if ((need_decoder || need_tuple) && pthread_self () == main_thread)
- warn_main_thread_blocked ();
-#endif
-
bool scan_started = false;
while (1)
{
- PlaylistData * playlist = lookup_playlist (playlist_num);
- Entry * entry = playlist ? lookup_entry (playlist, entry_num) : nullptr;
+ PlaylistEntry * entry = playlist->entry_at (entry_num);
- // check whether entry was deleted; also blacklist stdin
- if (! entry || ! strncmp (entry->filename, "stdin://", 8))
- return entry;
-
- // check whether requested data (decoder and/or tuple) has been read
- if ((! need_decoder || entry->decoder) && (! need_tuple || entry->tuple.valid ()))
- return entry;
+ // check whether entry is deleted or has already been scanned
+ if (! entry || ! playlist->entry_needs_rescan (entry, need_decoder, need_tuple))
+ return;
// start scan if not already running ...
if (! scan_list_find_entry (entry))
{
// ... but only once
if (scan_started)
- return entry;
+ return;
scan_queue_entry (playlist, entry);
}
@@ -702,20 +475,23 @@ static Entry * get_entry (int playlist_num, int entry_num,
}
}
-static void start_playback (int seek_time, bool pause)
+static void start_playback_locked (int seek_time, bool pause)
{
art_clear_current ();
scan_reset_playback ();
playback_play (seek_time, pause);
+ auto playlist = playing_id->data;
+ auto entry = playlist->entry_at (playlist->position ());
+
// playback always begins with a rescan of the current entry in order to
// open the file, ensure a valid tuple, and read album art
- scan_cancel (playing_playlist->position);
- scan_queue_entry (playing_playlist, playing_playlist->position, true);
+ scan_cancel (entry);
+ scan_queue_entry (playlist, entry, true);
}
-static void stop_playback ()
+static void stop_playback_locked ()
{
art_clear_current ();
scan_reset_playback ();
@@ -723,1478 +499,576 @@ static void stop_playback ()
playback_stop ();
}
-void playlist_init ()
-{
- srand (time (nullptr));
-
-#ifdef WARN_BLOCKED
- main_thread = pthread_self ();
-#endif
-
- ENTER;
-
- update_level = NoUpdate;
- update_delayed = false;
- scan_enabled = false;
- scan_playlist = scan_row = 0;
-
- LEAVE;
-
- /* initialize title formatter */
- playlist_reformat_titles ();
-
- hook_associate ("set metadata_on_play", (HookFunction) playlist_trigger_scan, nullptr);
- hook_associate ("set generic_title_format", (HookFunction) playlist_reformat_titles, nullptr);
- hook_associate ("set leading_zero", (HookFunction) playlist_reformat_titles, nullptr);
- hook_associate ("set show_hours", (HookFunction) playlist_reformat_titles, nullptr);
- hook_associate ("set metadata_fallbacks", (HookFunction) playlist_reformat_titles, nullptr);
- hook_associate ("set show_numbers_in_pl", (HookFunction) playlist_reformat_titles, nullptr);
-}
-
-void playlist_enable_scan (bool enable)
-{
- ENTER;
-
- scan_enabled_nominal = enable;
- scan_enabled = scan_enabled_nominal && ! aud_get_bool (nullptr, "metadata_on_play");
- scan_restart ();
-
- LEAVE;
-}
-
-void playlist_end ()
-{
- hook_dissociate ("set metadata_on_play", (HookFunction) playlist_trigger_scan);
- hook_dissociate ("set generic_title_format", (HookFunction) playlist_reformat_titles);
- hook_dissociate ("set leading_zero", (HookFunction) playlist_reformat_titles);
- hook_dissociate ("set show_hours", (HookFunction) playlist_reformat_titles);
- hook_dissociate ("set metadata_fallbacks", (HookFunction) playlist_reformat_titles);
- hook_dissociate ("set show_numbers_in_pl", (HookFunction) playlist_reformat_titles);
-
- playlist_cache_clear ();
-
- ENTER;
-
- /* playback should already be stopped */
- assert (! playing_playlist);
-
- queued_update.stop ();
-
- active_playlist = nullptr;
- resume_playlist = -1;
- resume_paused = false;
-
- playlists.clear ();
- unique_id_table.clear ();
-
- title_formatter.reset ();
-
- LEAVE;
-}
-
-EXPORT int aud_playlist_count ()
-{
- ENTER;
- int count = playlists.len ();
- RETURN (count);
-}
-
-void playlist_insert_with_id (int at, int id)
-{
- ENTER;
-
- if (at < 0 || at > playlists.len ())
- at = playlists.len ();
-
- auto playlist = new PlaylistData (id);
- playlists.insert (at, 1);
- playlists[at].capture (playlist);
-
- number_playlists (at, playlists.len () - at);
-
- queue_update (Structure, playlist, 0, 0);
- LEAVE;
-}
-
-EXPORT void aud_playlist_insert (int at)
+void pl_signal_entry_deleted (PlaylistEntry * entry)
{
- playlist_insert_with_id (at, -1);
+ scan_cancel (entry);
}
-EXPORT void aud_playlist_reorder (int from, int to, int count)
+void pl_signal_position_changed (Playlist::ID * id)
{
- ENTER;
-
- if (from < 0 || from + count > playlists.len () || to < 0 || to +
- count > playlists.len () || count < 0)
- RETURN ();
-
- Index<SmartPtr<PlaylistData>> displaced;
-
- if (to < from)
- displaced.move_from (playlists, to, -1, from - to, true, false);
- else
- displaced.move_from (playlists, from + count, -1, to - from, true, false);
-
- playlists.shift (from, to, count);
-
- if (to < from)
+ if (update_state < UpdateState::Queued)
{
- playlists.move_from (displaced, 0, to + count, from - to, false, true);
- number_playlists (to, from + count - to);
+ queued_update.queue (update, nullptr);
+ update_state = UpdateState::Queued;
}
- else
+
+ if (id == playing_id)
{
- playlists.move_from (displaced, 0, from, to - from, false, true);
- number_playlists (from, to + count - from);
+ if (id->data->position () >= 0)
+ {
+ start_playback_locked (0, aud_drct_get_paused ());
+ queue_update_hooks (PlaybackBegin);
+ }
+ else
+ {
+ playing_id = nullptr;
+ stop_playback_locked ();
+ queue_update_hooks (SetPlaying | PlaybackStop);
+ }
}
-
- queue_update (Structure, nullptr, 0, 0);
- LEAVE;
}
-EXPORT void aud_playlist_delete (int playlist_num)
+void pl_signal_update_queued (Playlist::ID * id, Playlist::UpdateLevel level, int flags)
{
- ENTER_GET_PLAYLIST ();
+ auto playlist = id->data;
- bool was_active = false;
- bool was_playing = false;
+ if (level == Playlist::Structure)
+ playlist->scan_status = PlaylistData::ScanActive;
- playlists.remove (playlist_num, 1);
-
- if (! playlists.len ())
- playlists.append (SmartNew<PlaylistData> (-1));
-
- number_playlists (playlist_num, playlists.len () - playlist_num);
-
- if (playlist == active_playlist)
+ if (level >= Playlist::Metadata)
{
- int active_num = aud::min (playlist_num, playlists.len () - 1);
- active_playlist = playlists[active_num].get ();
- was_active = true;
- }
+ int pos = playlist->position ();
+ if (id == playing_id && pos >= 0)
+ playback_set_info (pos, playlist->entry_tuple (pos));
- if (playlist == playing_playlist)
- {
- playing_playlist = nullptr;
- stop_playback ();
- was_playing = true;
+ playlist->modified = true;
}
- queue_update (Structure, nullptr, 0, 0);
- LEAVE;
-
- if (was_active)
- hook_call ("playlist activate", nullptr);
-
- if (was_playing)
- {
- hook_call ("playlist set playing", nullptr);
- hook_call ("playback stop", nullptr);
- }
+ queue_global_update (level, flags);
}
-EXPORT int aud_playlist_get_unique_id (int playlist_num)
+void pl_signal_rescan_needed (Playlist::ID * id)
{
- ENTER_GET_PLAYLIST (-1);
- int unique_id = playlist->unique_id;
- RETURN (unique_id);
-}
-
-EXPORT int aud_playlist_by_unique_id (int id)
-{
- ENTER;
-
- PlaylistData * * ptr = unique_id_table.lookup (id);
- int num = (ptr && * ptr) ? (* ptr)->number : -1;
-
- RETURN (num);
+ id->data->scan_status = PlaylistData::ScanActive;
+ scan_restart ();
}
-EXPORT void aud_playlist_set_filename (int playlist_num, const char * filename)
+void pl_signal_playlist_deleted (Playlist::ID * id)
{
- ENTER_GET_PLAYLIST ();
-
- playlist->filename = String (filename);
- playlist->modified = true;
-
- queue_update (Metadata, nullptr, 0, 0);
- LEAVE;
+ /* break weak pointer link */
+ id->data = nullptr;
+ id->index = -1;
}
-EXPORT String aud_playlist_get_filename (int playlist_num)
+static void pl_hook_reformat_titles (void *, void *)
{
- ENTER_GET_PLAYLIST (String ());
- String filename = playlist->filename;
- RETURN (filename);
-}
+ ENTER;
-EXPORT void aud_playlist_set_title (int playlist_num, const char * title)
-{
- ENTER_GET_PLAYLIST ();
+ PlaylistData::update_formatter ();
- playlist->title = String (title);
- playlist->modified = true;
+ for (auto & playlist : playlists)
+ playlist->reformat_titles ();
- queue_update (Metadata, nullptr, 0, 0);
LEAVE;
}
-EXPORT String aud_playlist_get_title (int playlist_num)
+static void pl_hook_trigger_scan (void *, void *)
{
- ENTER_GET_PLAYLIST (String ());
- String title = playlist->title;
- RETURN (title);
-}
-
-void playlist_set_modified (int playlist_num, bool modified)
-{
- ENTER_GET_PLAYLIST ();
- playlist->modified = modified;
+ ENTER;
+ scan_enabled = scan_enabled_nominal && ! aud_get_bool (nullptr, "metadata_on_play");
+ scan_restart ();
LEAVE;
}
-bool playlist_get_modified (int playlist_num)
+void playlist_init ()
{
- ENTER_GET_PLAYLIST (false);
- bool modified = playlist->modified;
- RETURN (modified);
-}
+ srand (time (nullptr));
-EXPORT void aud_playlist_set_active (int playlist_num)
-{
- ENTER_GET_PLAYLIST ();
+ ENTER;
- bool changed = false;
+ PlaylistData::update_formatter ();
- if (playlist != active_playlist)
- {
- changed = true;
- active_playlist = playlist;
- }
+ update_level = Playlist::NoUpdate;
+ update_hooks = 0;
+ update_state = UpdateState::None;
+ scan_enabled = scan_enabled_nominal = false;
+ scan_playlist = scan_row = 0;
LEAVE;
- if (changed)
- hook_call ("playlist activate", nullptr);
+ hook_associate ("set generic_title_format", pl_hook_reformat_titles, nullptr);
+ hook_associate ("set leading_zero", pl_hook_reformat_titles, nullptr);
+ hook_associate ("set metadata_fallbacks", pl_hook_reformat_titles, nullptr);
+ hook_associate ("set show_hours", pl_hook_reformat_titles, nullptr);
+ hook_associate ("set show_numbers_in_pl", pl_hook_reformat_titles, nullptr);
+ hook_associate ("set metadata_on_play", pl_hook_trigger_scan, nullptr);
}
-EXPORT int aud_playlist_get_active ()
-{
- ENTER;
- int list = active_playlist ? active_playlist->number : -1;
- RETURN (list);
-}
-
-EXPORT int aud_playlist_new ()
-{
- int playlist = aud_playlist_get_active () + 1;
- aud_playlist_insert (playlist);
- aud_playlist_set_active (playlist);
- return playlist;
-}
-
-EXPORT void aud_playlist_play (int playlist_num, bool paused)
+void playlist_enable_scan (bool enable)
{
ENTER;
- PlaylistData * playlist = lookup_playlist (playlist_num);
- bool position_changed = false;
-
- if (playlist == playing_playlist)
- {
- /* already playing, just need to pause/unpause */
- if (aud_drct_get_paused () != paused)
- aud_drct_pause ();
-
- RETURN ();
- }
-
- if (playing_playlist)
- playing_playlist->resume_time = aud_drct_get_time ();
-
- /* is there anything to play? */
- if (playlist && ! playlist->position)
- {
- if (next_song_locked (playlist, true, 0))
- position_changed = true;
- else
- playlist = nullptr;
- }
-
- playing_playlist = playlist;
-
- if (playlist)
- start_playback (playlist->resume_time, paused);
- else
- stop_playback ();
+ scan_enabled_nominal = enable;
+ scan_enabled = scan_enabled_nominal && ! aud_get_bool (nullptr, "metadata_on_play");
+ scan_restart ();
LEAVE;
-
- if (position_changed)
- hook_call ("playlist position", aud::to_ptr (playlist_num));
-
- hook_call ("playlist set playing", nullptr);
-
- if (playlist)
- hook_call ("playback begin", nullptr);
- else
- hook_call ("playback stop", nullptr);
}
-EXPORT int aud_playlist_get_playing ()
+void playlist_clear_updates ()
{
ENTER;
- int list = playing_playlist ? playing_playlist->number: -1;
- RETURN (list);
-}
-
-EXPORT int aud_playlist_get_blank ()
-{
- int list = aud_playlist_get_active ();
- String title = aud_playlist_get_title (list);
-
- if (strcmp (title, _(default_title)) || aud_playlist_entry_count (list) > 0)
- aud_playlist_insert (++ list);
- return list;
-}
-
-EXPORT int aud_playlist_get_temporary ()
-{
- int count = aud_playlist_count ();
-
- for (int list = 0; list < count; list ++)
- {
- String title = aud_playlist_get_title (list);
- if (! strcmp (title, _(temp_title)))
- return list;
- }
-
- int list = aud_playlist_get_blank ();
- aud_playlist_set_title (list, _(temp_title));
- return list;
-}
-
-static void set_position (PlaylistData * playlist, Entry * entry, bool update_shuffle)
-{
- playlist->position = entry;
- playlist->resume_time = 0;
-
- /* move entry to top of shuffle list */
- if (entry && update_shuffle)
- entry->shuffle_num = ++ playlist->last_shuffle_num;
-}
-
-// updates playback state (while locked) if playlist position was changed
-static PlaybackChange change_playback (PlaylistData * playlist)
-{
- if (playlist != playing_playlist)
- return NoChange;
-
- if (playlist->position)
- {
- start_playback (0, aud_drct_get_paused ());
- return NextSong;
- }
- else
- {
- playing_playlist = nullptr;
- stop_playback ();
- return PlaybackStopped;
- }
-}
-
-// call hooks (while unlocked) if playback state was changed
-static void call_playback_change_hooks (PlaybackChange change)
-{
- if (change == NextSong)
- hook_call ("playback begin", nullptr);
-
- if (change == PlaybackStopped)
- {
- hook_call ("playlist set playing", nullptr);
- hook_call ("playback stop", nullptr);
- }
-}
-
-EXPORT int aud_playlist_entry_count (int playlist_num)
-{
- ENTER_GET_PLAYLIST (0);
- int count = playlist->entries.len ();
- RETURN (count);
-}
-
-void playlist_entry_insert_batch_raw (int playlist_num, int at, Index<PlaylistAddItem> && items)
-{
- ENTER_GET_PLAYLIST ();
-
- int entries = playlist->entries.len ();
-
- if (at < 0 || at > entries)
- at = entries;
-
- int number = items.len ();
-
- playlist->entries.insert (at, number);
-
- int i = at;
- for (auto & item : items)
- {
- Entry * entry = new Entry (std::move (item));
- playlist->entries[i ++].capture (entry);
- playlist->total_length += entry->length;
- }
-
- items.clear ();
+ /* clear updates queued during init sequence */
+ for (auto & playlist : playlists)
+ playlist->cancel_updates ();
- number_entries (playlist, at, entries + number - at);
+ queued_update.stop ();
+ update_level = Playlist::NoUpdate;
+ update_hooks = 0;
+ update_state = UpdateState::None;
- queue_update (Structure, playlist, at, number);
LEAVE;
}
-EXPORT void aud_playlist_entry_delete (int playlist_num, int at, int number)
+void playlist_end ()
{
- ENTER_GET_PLAYLIST ();
-
- int entries = playlist->entries.len ();
- bool position_changed = false;
- int update_flags = 0;
- PlaybackChange change = NoChange;
-
- if (at < 0 || at > entries)
- at = entries;
- if (number < 0 || number > entries - at)
- number = entries - at;
+ hook_dissociate ("set generic_title_format", pl_hook_reformat_titles);
+ hook_dissociate ("set leading_zero", pl_hook_reformat_titles);
+ hook_dissociate ("set metadata_fallbacks", pl_hook_reformat_titles);
+ hook_dissociate ("set show_hours", pl_hook_reformat_titles);
+ hook_dissociate ("set show_numbers_in_pl", pl_hook_reformat_titles);
+ hook_dissociate ("set metadata_on_play", pl_hook_trigger_scan);
- if (playlist->position && playlist->position->number >= at &&
- playlist->position->number < at + number)
- {
- set_position (playlist, nullptr, false);
- position_changed = true;
- }
-
- if (playlist->focus && playlist->focus->number >= at &&
- playlist->focus->number < at + number)
- {
- if (at + number < entries)
- playlist->focus = playlist->entries[at + number].get ();
- else if (at > 0)
- playlist->focus = playlist->entries[at - 1].get ();
- else
- playlist->focus = nullptr;
- }
-
- for (int count = 0; count < number; count ++)
- {
- Entry * entry = playlist->entries [at + count].get ();
+ playlist_cache_clear ();
- if (entry->queued)
- {
- playlist->queued.remove (playlist->queued.find (entry), 1);
- update_flags |= QueueChanged;
- }
+ ENTER;
- if (entry->selected)
- {
- playlist->selected_count --;
- playlist->selected_length -= entry->length;
- }
+ /* playback should already be stopped */
+ assert (! playing_id);
+ assert (! scan_list.head ());
- playlist->total_length -= entry->length;
- }
+ queued_update.stop ();
- playlist->entries.remove (at, number);
- number_entries (playlist, at, entries - at - number);
+ active_id = nullptr;
+ resume_playlist = -1;
+ resume_paused = false;
- if (position_changed)
- {
- if (aud_get_bool (nullptr, "advance_on_delete"))
- next_song_locked (playlist, aud_get_bool (nullptr, "repeat"), at);
+ playlists.clear ();
+ id_table.clear ();
- change = change_playback (playlist);
- }
+ PlaylistData::cleanup_formatter ();
- queue_update (Structure, playlist, at, 0, update_flags);
LEAVE;
-
- if (position_changed)
- hook_call ("playlist position", aud::to_ptr (playlist_num));
-
- call_playback_change_hooks (change);
}
-EXPORT String aud_playlist_entry_get_filename (int playlist_num, int entry_num)
+EXPORT int Playlist::n_entries () const
+ { SIMPLE_WRAPPER (int, 0, n_entries); }
+EXPORT void Playlist::remove_entries (int at, int number) const
+ { SIMPLE_VOID_WRAPPER (remove_entries, at, number); }
+EXPORT String Playlist::entry_filename (int entry_num) const
+ { SIMPLE_WRAPPER (String, String (), entry_filename, entry_num); }
+
+EXPORT int Playlist::get_position () const
+ { SIMPLE_WRAPPER (int, -1, position); }
+EXPORT void Playlist::set_position (int entry_num) const
+ { SIMPLE_VOID_WRAPPER (set_position, entry_num); }
+EXPORT bool Playlist::prev_song () const
+ { SIMPLE_WRAPPER (bool, false, prev_song); }
+EXPORT bool Playlist::next_song (bool repeat) const
+ { SIMPLE_WRAPPER (bool, false, next_song, repeat); }
+EXPORT int Playlist::get_focus () const
+ { SIMPLE_WRAPPER (int, -1, focus); }
+EXPORT void Playlist::set_focus (int entry_num) const
+ { SIMPLE_VOID_WRAPPER (set_focus, entry_num); }
+EXPORT bool Playlist::entry_selected (int entry_num) const
+ { SIMPLE_WRAPPER (bool, false, entry_selected, entry_num); }
+EXPORT void Playlist::select_entry (int entry_num, bool selected) const
+ { SIMPLE_VOID_WRAPPER (select_entry, entry_num, selected); }
+EXPORT int Playlist::n_selected (int at, int number) const
+ { SIMPLE_WRAPPER (int, 0, n_selected, at, number); }
+EXPORT void Playlist::select_all (bool selected) const
+ { SIMPLE_VOID_WRAPPER (select_all, selected); }
+EXPORT int Playlist::shift_entries (int entry_num, int distance) const
+ { SIMPLE_WRAPPER (int, 0, shift_entries, entry_num, distance); }
+EXPORT void Playlist::remove_selected () const
+ { SIMPLE_VOID_WRAPPER (remove_selected); }
+
+EXPORT void Playlist::sort_by_filename (StringCompareFunc compare) const
+ { SIMPLE_VOID_WRAPPER (sort, {compare, nullptr}); }
+EXPORT void Playlist::sort_by_tuple (TupleCompareFunc compare) const
+ { SIMPLE_VOID_WRAPPER (sort, {nullptr, compare}); }
+EXPORT void Playlist::sort_selected_by_filename (StringCompareFunc compare) const
+ { SIMPLE_VOID_WRAPPER (sort_selected, {compare, nullptr}); }
+EXPORT void Playlist::sort_selected_by_tuple (TupleCompareFunc compare) const
+ { SIMPLE_VOID_WRAPPER (sort_selected, {nullptr, compare}); }
+EXPORT void Playlist::reverse_order () const
+ { SIMPLE_VOID_WRAPPER (reverse_order); }
+EXPORT void Playlist::reverse_selected () const
+ { SIMPLE_VOID_WRAPPER (reverse_selected); }
+EXPORT void Playlist::randomize_order () const
+ { SIMPLE_VOID_WRAPPER (randomize_order); }
+EXPORT void Playlist::randomize_selected () const
+ { SIMPLE_VOID_WRAPPER (randomize_selected); }
+
+EXPORT void Playlist::rescan_all () const
+ { SIMPLE_VOID_WRAPPER (reset_tuples, false); }
+EXPORT void Playlist::rescan_selected () const
+ { SIMPLE_VOID_WRAPPER (reset_tuples, true); }
+
+EXPORT int64_t Playlist::total_length_ms () const
+ { SIMPLE_WRAPPER (int64_t, 0, total_length); }
+EXPORT int64_t Playlist::selected_length_ms () const
+ { SIMPLE_WRAPPER (int64_t, 0, selected_length); }
+
+EXPORT int Playlist::n_queued () const
+ { SIMPLE_WRAPPER (int, 0, n_queued); }
+EXPORT void Playlist::queue_insert (int at, int entry_num) const
+ { SIMPLE_VOID_WRAPPER (queue_insert, at, entry_num); }
+EXPORT void Playlist::queue_insert_selected (int at) const
+ { SIMPLE_VOID_WRAPPER (queue_insert_selected, at); }
+EXPORT int Playlist::queue_get_entry (int at) const
+ { SIMPLE_WRAPPER (int, -1, queue_get_entry, at); }
+EXPORT int Playlist::queue_find_entry (int entry_num) const
+ { SIMPLE_WRAPPER (int, -1, queue_find_entry, entry_num); }
+EXPORT void Playlist::queue_remove (int at, int number) const
+ { SIMPLE_VOID_WRAPPER (queue_remove, at, number); }
+EXPORT void Playlist::queue_remove_selected () const
+ { SIMPLE_VOID_WRAPPER (queue_remove_selected); }
+
+EXPORT bool Playlist::update_pending () const
+ { SIMPLE_WRAPPER (bool, false, update_pending); }
+EXPORT Playlist::Update Playlist::update_detail () const
+ { SIMPLE_WRAPPER (Update, Update (), last_update); }
+
+void PlaylistEx::insert_flat_items (int at, Index<PlaylistAddItem> && items) const
+ { SIMPLE_VOID_WRAPPER (insert_items, at, std::move (items)); }
+
+EXPORT int Playlist::index () const
{
- ENTER_GET_ENTRY (String ());
- String filename = entry->filename;
- RETURN (filename);
+ ENTER_GET_PLAYLIST (-1);
+ int at = m_id->index;
+ RETURN (at);
}
-EXPORT PluginHandle * aud_playlist_entry_get_decoder (int playlist_num,
- int entry_num, GetMode mode, String * error)
+EXPORT int PlaylistEx::stamp () const
{
- ENTER;
-
- Entry * entry = get_entry (playlist_num, entry_num, (mode == Wait), false);
- PluginHandle * decoder = entry ? entry->decoder : nullptr;
-
- if (error)
- * error = entry ? entry->error : String ();
-
- RETURN (decoder);
+ ENTER_GET_PLAYLIST (-1);
+ int stamp = m_id->stamp;
+ RETURN (stamp);
}
-EXPORT Tuple aud_playlist_entry_get_tuple (int playlist_num, int entry_num,
- GetMode mode, String * error)
+EXPORT int Playlist::n_playlists ()
{
ENTER;
-
- Entry * entry = get_entry (playlist_num, entry_num, false, (mode == Wait));
- Tuple tuple = entry ? entry->tuple.ref () : Tuple ();
-
- if (error)
- * error = entry ? entry->error : String ();
-
- RETURN (tuple);
-}
-
-EXPORT void aud_playlist_set_position (int playlist_num, int entry_num)
-{
- ENTER_GET_PLAYLIST ();
-
- Entry * entry = lookup_entry (playlist, entry_num);
- set_position (playlist, entry, true);
-
- PlaybackChange change = change_playback (playlist);
-
- LEAVE;
-
- hook_call ("playlist position", aud::to_ptr (playlist_num));
- call_playback_change_hooks (change);
+ int count = playlists.len ();
+ RETURN (count);
}
-EXPORT int aud_playlist_get_position (int playlist_num)
+EXPORT Playlist Playlist::by_index (int at)
{
- ENTER_GET_PLAYLIST (-1);
- int position = playlist->position ? playlist->position->number : -1;
- RETURN (position);
+ ENTER;
+ Playlist::ID * id = (at >= 0 && at < playlists.len ()) ? playlists[at]->id () : nullptr;
+ RETURN (Playlist (id));
}
-EXPORT void aud_playlist_set_focus (int playlist_num, int entry_num)
+static Playlist::ID * insert_playlist_locked (int at, int stamp = -1)
{
- ENTER_GET_PLAYLIST ();
-
- Entry * new_focus = lookup_entry (playlist, entry_num);
- if (new_focus == playlist->focus)
- RETURN ();
-
- int first = INT_MAX;
- int last = -1;
+ if (at < 0 || at > playlists.len ())
+ at = playlists.len ();
- if (playlist->focus)
- {
- first = aud::min (first, playlist->focus->number);
- last = aud::max (last, playlist->focus->number);
- }
+ auto id = create_playlist (stamp);
- playlist->focus = new_focus;
+ playlists.insert (at, 1);
+ playlists[at].capture (id->data);
- if (playlist->focus)
- {
- first = aud::min (first, playlist->focus->number);
- last = aud::max (last, playlist->focus->number);
- }
+ number_playlists (at, playlists.len () - at);
- if (first <= last)
- queue_update (Selection, playlist, first, last + 1 - first);
+ /* this will only happen at startup */
+ if (! active_id)
+ active_id = id;
- LEAVE;
-}
+ queue_global_update (Playlist::Structure);
-EXPORT int aud_playlist_get_focus (int playlist_num)
-{
- ENTER_GET_PLAYLIST (-1);
- int focus = playlist->focus ? playlist->focus->number : -1;
- RETURN (focus);
+ return id;
}
-EXPORT void aud_playlist_entry_set_selected (int playlist_num, int entry_num,
- bool selected)
+static Playlist::ID * get_blank_locked ()
{
- ENTER_GET_ENTRY ();
-
- if (entry->selected == selected)
- RETURN ();
-
- entry->selected = selected;
+ if (! strcmp (active_id->data->title, _(default_title)) && ! active_id->data->n_entries ())
+ return active_id;
- if (selected)
- {
- playlist->selected_count++;
- playlist->selected_length += entry->length;
- }
- else
- {
- playlist->selected_count--;
- playlist->selected_length -= entry->length;
- }
-
- queue_update (Selection, playlist, entry_num, 1);
- LEAVE;
+ return insert_playlist_locked (active_id->index + 1);
}
-EXPORT bool aud_playlist_entry_get_selected (int playlist_num, int entry_num)
+Playlist PlaylistEx::insert_with_stamp (int at, int stamp)
{
- ENTER_GET_ENTRY (false);
- bool selected = entry->selected;
- RETURN (selected);
+ ENTER;
+ auto id = insert_playlist_locked (at, stamp);
+ RETURN (Playlist (id));
}
-EXPORT int aud_playlist_selected_count (int playlist_num)
+EXPORT Playlist Playlist::insert_playlist (int at)
{
- ENTER_GET_PLAYLIST (0);
- int selected_count = playlist->selected_count;
- RETURN (selected_count);
+ ENTER;
+ auto id = insert_playlist_locked (at);
+ RETURN (Playlist (id));
}
-EXPORT void aud_playlist_select_all (int playlist_num, bool selected)
+EXPORT void Playlist::reorder_playlists (int from, int to, int count)
{
- ENTER_GET_PLAYLIST ();
+ ENTER;
- int entries = playlist->entries.len ();
- int first = entries, last = 0;
+ if (from < 0 || from + count > playlists.len () || to < 0 || to +
+ count > playlists.len () || count < 0)
+ RETURN ();
- for (auto & entry : playlist->entries)
- {
- if ((selected && ! entry->selected) || (entry->selected && ! selected))
- {
- entry->selected = selected;
- first = aud::min (first, entry->number);
- last = entry->number;
- }
- }
+ Index<SmartPtr<PlaylistData>> displaced;
- if (selected)
- {
- playlist->selected_count = entries;
- playlist->selected_length = playlist->total_length;
- }
+ if (to < from)
+ displaced.move_from (playlists, to, -1, from - to, true, false);
else
- {
- playlist->selected_count = 0;
- playlist->selected_length = 0;
- }
-
- if (first < entries)
- queue_update (Selection, playlist, first, last + 1 - first);
-
- LEAVE;
-}
-
-EXPORT int aud_playlist_shift (int playlist_num, int entry_num, int distance)
-{
- ENTER_GET_ENTRY (0);
-
- if (! entry->selected || ! distance)
- RETURN (0);
+ displaced.move_from (playlists, from + count, -1, to - from, true, false);
- int entries = playlist->entries.len ();
- int shift = 0, center, top, bottom;
+ playlists.shift (from, to, count);
- if (distance < 0)
+ if (to < from)
{
- for (center = entry_num; center > 0 && shift > distance; )
- {
- if (! playlist->entries[-- center]->selected)
- shift --;
- }
+ playlists.move_from (displaced, 0, to + count, from - to, false, true);
+ number_playlists (to, from + count - to);
}
else
{
- for (center = entry_num + 1; center < entries && shift < distance; )
- {
- if (! playlist->entries[center ++]->selected)
- shift ++;
- }
- }
-
- top = bottom = center;
-
- for (int i = 0; i < top; i ++)
- {
- if (playlist->entries[i]->selected)
- top = i;
- }
-
- for (int i = entries; i > bottom; i --)
- {
- if (playlist->entries[i - 1]->selected)
- bottom = i;
- }
-
- Index<SmartPtr<Entry>> temp;
-
- for (int i = top; i < center; i ++)
- {
- if (! playlist->entries[i]->selected)
- temp.append (std::move (playlist->entries[i]));
- }
-
- for (int i = top; i < bottom; i ++)
- {
- if (playlist->entries[i] && playlist->entries[i]->selected)
- temp.append (std::move (playlist->entries[i]));
- }
-
- for (int i = center; i < bottom; i ++)
- {
- if (playlist->entries[i] && ! playlist->entries[i]->selected)
- temp.append (std::move (playlist->entries[i]));
- }
-
- playlist->entries.move_from (temp, 0, top, bottom - top, false, true);
-
- number_entries (playlist, top, bottom - top);
- queue_update (Structure, playlist, top, bottom - top);
-
- RETURN (shift);
-}
-
-static Entry * find_unselected_focus (PlaylistData * playlist)
-{
- if (! playlist->focus || ! playlist->focus->selected)
- return playlist->focus;
-
- int entries = playlist->entries.len ();
-
- for (int search = playlist->focus->number + 1; search < entries; search ++)
- {
- Entry * entry = playlist->entries[search].get ();
- if (! entry->selected)
- return entry;
- }
-
- for (int search = playlist->focus->number; search --;)
- {
- Entry * entry = playlist->entries[search].get ();
- if (! entry->selected)
- return entry;
+ playlists.move_from (displaced, 0, from, to - from, false, true);
+ number_playlists (from, to + count - from);
}
- return nullptr;
+ queue_global_update (Structure);
+ LEAVE;
}
-EXPORT void aud_playlist_delete_selected (int playlist_num)
+EXPORT void Playlist::remove_playlist () const
{
ENTER_GET_PLAYLIST ();
- if (! playlist->selected_count)
- RETURN ();
-
- int entries = playlist->entries.len ();
- bool position_changed = false;
- int update_flags = 0;
- PlaybackChange change = NoChange;
-
- if (playlist->position && playlist->position->selected)
- {
- set_position (playlist, nullptr, false);
- position_changed = true;
- }
+ int at = m_id->index;
+ playlists.remove (at, 1);
- playlist->focus = find_unselected_focus (playlist);
-
- int before = 0; // number of entries before first selected
- int after = 0; // number of entries after last selected
-
- while (before < entries && ! playlist->entries[before]->selected)
- before ++;
+ if (! playlists.len ())
+ playlists.append (create_playlist (-1)->data);
- int to = before;
+ number_playlists (at, playlists.len () - at);
- for (int from = before; from < entries; from ++)
+ if (m_id == active_id)
{
- Entry * entry = playlist->entries[from].get ();
-
- if (entry->selected)
- {
- if (entry->queued)
- {
- playlist->queued.remove (playlist->queued.find (entry), 1);
- update_flags |= QueueChanged;
- }
-
- playlist->total_length -= entry->length;
- after = 0;
- }
- else
- {
- playlist->entries[to ++] = std::move (playlist->entries[from]);
- after ++;
- }
+ int active_num = aud::min (at, playlists.len () - 1);
+ active_id = playlists[active_num]->id ();
+ queue_update_hooks (SetActive);
}
- entries = to;
- playlist->entries.remove (entries, -1);
- number_entries (playlist, before, entries - before);
-
- playlist->selected_count = 0;
- playlist->selected_length = 0;
-
- if (position_changed)
+ if (m_id == playing_id)
{
- if (aud_get_bool (nullptr, "advance_on_delete"))
- next_song_locked (playlist, aud_get_bool (nullptr, "repeat"), entries - after);
-
- change = change_playback (playlist);
+ playing_id = nullptr;
+ stop_playback_locked ();
+ queue_update_hooks (SetPlaying | PlaybackStop);
}
- queue_update (Structure, playlist, before, entries - after - before, update_flags);
- LEAVE;
-
- if (position_changed)
- hook_call ("playlist position", aud::to_ptr (playlist_num));
-
- call_playback_change_hooks (change);
-}
-
-EXPORT void aud_playlist_reverse (int playlist_num)
-{
- ENTER_GET_PLAYLIST ();
-
- int entries = playlist->entries.len ();
-
- for (int i = 0; i < entries / 2; i ++)
- std::swap (playlist->entries[i], playlist->entries[entries - 1 - i]);
-
- number_entries (playlist, 0, entries);
- queue_update (Structure, playlist, 0, entries);
+ queue_global_update (Structure);
LEAVE;
}
-EXPORT void aud_playlist_reverse_selected (int playlist_num)
+EXPORT void Playlist::set_filename (const char * filename) const
{
ENTER_GET_PLAYLIST ();
- int entries = playlist->entries.len ();
-
- int top = 0;
- int bottom = entries - 1;
-
- while (1)
- {
- while (top < bottom && ! playlist->entries[top]->selected)
- top ++;
- while (top < bottom && ! playlist->entries[bottom]->selected)
- bottom --;
-
- if (top >= bottom)
- break;
-
- std::swap (playlist->entries[top ++], playlist->entries[bottom --]);
- }
+ playlist->filename = String (filename);
+ playlist->modified = true;
- number_entries (playlist, 0, entries);
- queue_update (Structure, playlist, 0, entries);
+ queue_global_update (Metadata);
LEAVE;
}
-EXPORT void aud_playlist_randomize (int playlist_num)
+EXPORT String Playlist::get_filename () const
{
- ENTER_GET_PLAYLIST ();
-
- int entries = playlist->entries.len ();
-
- for (int i = 0; i < entries; i ++)
- std::swap (playlist->entries[i], playlist->entries[rand () % entries]);
-
- number_entries (playlist, 0, entries);
- queue_update (Structure, playlist, 0, entries);
- LEAVE;
+ ENTER_GET_PLAYLIST (String ());
+ String filename = playlist->filename;
+ RETURN (filename);
}
-EXPORT void aud_playlist_randomize_selected (int playlist_num)
+EXPORT void Playlist::set_title (const char * title) const
{
ENTER_GET_PLAYLIST ();
- int entries = playlist->entries.len ();
-
- Index<Entry *> selected;
-
- for (auto & entry : playlist->entries)
- {
- if (entry->selected)
- selected.append (entry.get ());
- }
-
- int n_selected = selected.len ();
-
- for (int i = 0; i < n_selected; i ++)
- {
- int a = selected[i]->number;
- int b = selected[rand () % n_selected]->number;
- std::swap (playlist->entries[a], playlist->entries[b]);
- }
+ playlist->title = String (title);
+ playlist->modified = true;
- number_entries (playlist, 0, entries);
- queue_update (Structure, playlist, 0, entries);
+ queue_global_update (Metadata);
LEAVE;
}
-enum {COMPARE_TYPE_FILENAME, COMPARE_TYPE_TUPLE, COMPARE_TYPE_TITLE};
-
-struct CompareData {
- PlaylistStringCompareFunc filename_compare;
- PlaylistTupleCompareFunc tuple_compare;
-};
-
-static void sort_entries (Index<SmartPtr<Entry>> & entries, CompareData * data)
-{
- entries.sort ([data] (const SmartPtr<Entry> & a, const SmartPtr<Entry> & b) {
- if (data->filename_compare)
- return data->filename_compare (a->filename, b->filename);
- else
- return data->tuple_compare (a->tuple, b->tuple);
- });
-}
-
-static void sort (PlaylistData * playlist, CompareData * data)
+EXPORT String Playlist::get_title () const
{
- sort_entries (playlist->entries, data);
- number_entries (playlist, 0, playlist->entries.len ());
-
- queue_update (Structure, playlist, 0, playlist->entries.len ());
-}
-
-static void sort_selected (PlaylistData * playlist, CompareData * data)
-{
- int entries = playlist->entries.len ();
-
- Index<SmartPtr<Entry>> selected;
-
- for (auto & entry : playlist->entries)
- {
- if (entry->selected)
- selected.append (std::move (entry));
- }
-
- sort_entries (selected, data);
-
- int i = 0;
- for (auto & entry : playlist->entries)
- {
- if (! entry)
- entry = std::move (selected[i ++]);
- }
-
- number_entries (playlist, 0, entries);
- queue_update (Structure, playlist, 0, entries);
-}
-
-static bool entries_are_scanned (PlaylistData * playlist, bool selected)
-{
- for (auto & entry : playlist->entries)
- {
- if (selected && ! entry->selected)
- continue;
-
- if (entry->tuple.state () == Tuple::Initial)
- {
- aud_ui_show_error (_("The playlist cannot be sorted because "
- "metadata scanning is still in progress (or has been disabled)."));
- return false;
- }
- }
-
- return true;
-}
-
-EXPORT void aud_playlist_sort_by_filename (int playlist_num, PlaylistStringCompareFunc compare)
-{
- ENTER_GET_PLAYLIST ();
-
- CompareData data = {compare};
- sort (playlist, & data);
-
- LEAVE;
+ ENTER_GET_PLAYLIST (String ());
+ String title = playlist->title;
+ RETURN (title);
}
-EXPORT void aud_playlist_sort_by_tuple (int playlist_num, PlaylistTupleCompareFunc compare)
+void PlaylistEx::set_modified (bool modified) const
{
ENTER_GET_PLAYLIST ();
-
- CompareData data = {nullptr, compare};
- if (entries_are_scanned (playlist, false))
- sort (playlist, & data);
-
+ playlist->modified = modified;
LEAVE;
}
-EXPORT void aud_playlist_sort_selected_by_filename (int playlist_num,
- PlaylistStringCompareFunc compare)
+bool PlaylistEx::get_modified () const
{
- ENTER_GET_PLAYLIST ();
-
- CompareData data = {compare};
- sort_selected (playlist, & data);
-
- LEAVE;
+ ENTER_GET_PLAYLIST (false);
+ bool modified = playlist->modified;
+ RETURN (modified);
}
-EXPORT void aud_playlist_sort_selected_by_tuple (int playlist_num,
- PlaylistTupleCompareFunc compare)
+EXPORT void Playlist::activate () const
{
ENTER_GET_PLAYLIST ();
- CompareData data = {nullptr, compare};
- if (entries_are_scanned (playlist, true))
- sort_selected (playlist, & data);
-
- LEAVE;
-}
-
-static void playlist_reformat_titles ()
-{
- ENTER;
-
- metadata_fallbacks = aud_get_bool (nullptr, "metadata_fallbacks");
- title_formatter.compile (aud_get_str (nullptr, "generic_title_format"));
-
- for (auto & playlist : playlists)
+ if (m_id != active_id)
{
- for (auto & entry : playlist->entries)
- entry->format ();
-
- queue_update (Metadata, playlist.get (), 0, playlist->entries.len ());
+ active_id = m_id;
+ queue_update_hooks (SetActive);
}
LEAVE;
}
-static void playlist_trigger_scan ()
+EXPORT Playlist Playlist::active_playlist ()
{
ENTER;
-
- scan_enabled = scan_enabled_nominal && ! aud_get_bool (nullptr, "metadata_on_play");
- scan_restart ();
-
- LEAVE;
+ auto id = active_id;
+ RETURN (Playlist (id));
}
-static void playlist_rescan_real (int playlist_num, bool selected)
+EXPORT Playlist Playlist::new_playlist ()
{
- ENTER_GET_PLAYLIST ();
-
- for (auto & entry : playlist->entries)
- {
- if (! selected || entry->selected)
- playlist->set_entry_tuple (entry.get (), Tuple ());
- }
-
- queue_update (Metadata, playlist, 0, playlist->entries.len ());
- scan_queue_playlist (playlist);
- scan_restart ();
+ ENTER;
- LEAVE;
-}
+ int at = active_id->index + 1;
+ auto id = insert_playlist_locked (at);
-EXPORT void aud_playlist_rescan (int playlist_num)
-{
- playlist_rescan_real (playlist_num, false);
-}
+ active_id = id;
+ queue_update_hooks (SetActive);
-EXPORT void aud_playlist_rescan_selected (int playlist_num)
-{
- playlist_rescan_real (playlist_num, true);
+ RETURN (Playlist (id));
}
-EXPORT void aud_playlist_rescan_file (const char * filename)
+static void set_playing_locked (Playlist::ID * id, bool paused)
{
- ENTER;
-
- bool restart = false;
-
- for (auto & playlist : playlists)
+ if (id == playing_id)
{
- bool queue = false;
-
- for (auto & entry : playlist->entries)
- {
- if (! strcmp (entry->filename, filename))
- {
- playlist->set_entry_tuple (entry.get (), Tuple ());
- queue_update (Metadata, playlist.get (), entry->number, 1);
- queue = true;
- }
- }
+ /* already playing, just need to pause/unpause */
+ if (aud_drct_get_paused () != paused)
+ aud_drct_pause ();
- if (queue)
- {
- scan_queue_playlist (playlist.get ());
- restart = true;
- }
+ return;
}
- if (restart)
- scan_restart ();
+ if (playing_id)
+ playing_id->data->resume_time = aud_drct_get_time ();
- LEAVE;
-}
-
-EXPORT int64_t aud_playlist_get_total_length (int playlist_num)
-{
- ENTER_GET_PLAYLIST (0);
- int64_t length = playlist->total_length;
- RETURN (length);
-}
-
-EXPORT int64_t aud_playlist_get_selected_length (int playlist_num)
-{
- ENTER_GET_PLAYLIST (0);
- int64_t length = playlist->selected_length;
- RETURN (length);
-}
-
-EXPORT int aud_playlist_queue_count (int playlist_num)
-{
- ENTER_GET_PLAYLIST (0);
- int count = playlist->queued.len ();
- RETURN (count);
-}
+ /* is there anything to play? */
+ if (id && id->data->position () < 0 && ! id->data->next_song (true))
+ id = nullptr;
-EXPORT void aud_playlist_queue_insert (int playlist_num, int at, int entry_num)
-{
- ENTER_GET_ENTRY ();
+ playing_id = id;
- if (entry->queued || at > playlist->queued.len ())
- RETURN ();
-
- if (at < 0)
- playlist->queued.append (entry);
- else
+ if (id)
{
- playlist->queued.insert (at, 1);
- playlist->queued[at] = entry;
+ start_playback_locked (id->data->resume_time, paused);
+ queue_update_hooks (SetPlaying | PlaybackBegin);
}
-
- entry->queued = true;
-
- queue_update (Selection, playlist, entry_num, 1, QueueChanged);
- LEAVE;
-}
-
-EXPORT void aud_playlist_queue_insert_selected (int playlist_num, int at)
-{
- ENTER_GET_PLAYLIST ();
-
- if (at > playlist->queued.len ())
- RETURN ();
-
- Index<Entry *> add;
- int first = playlist->entries.len ();
- int last = 0;
-
- for (auto & entry : playlist->entries)
+ else
{
- if (! entry->selected || entry->queued)
- continue;
-
- add.append (entry.get ());
- entry->queued = true;
- first = aud::min (first, entry->number);
- last = entry->number;
+ stop_playback_locked ();
+ queue_update_hooks (SetPlaying | PlaybackStop);
}
-
- playlist->queued.move_from (add, 0, at, -1, true, true);
-
- if (first < playlist->entries.len ())
- queue_update (Selection, playlist, first, last + 1 - first, QueueChanged);
-
- LEAVE;
-}
-
-EXPORT int aud_playlist_queue_get_entry (int playlist_num, int at)
-{
- ENTER_GET_PLAYLIST (-1);
-
- int entry_num = -1;
- if (at >= 0 && at < playlist->queued.len ())
- entry_num = playlist->queued[at]->number;
-
- RETURN (entry_num);
-}
-
-EXPORT int aud_playlist_queue_find_entry (int playlist_num, int entry_num)
-{
- ENTER_GET_ENTRY (-1);
- int pos = entry->queued ? playlist->queued.find (entry) : -1;
- RETURN (pos);
}
-EXPORT void aud_playlist_queue_delete (int playlist_num, int at, int number)
+EXPORT void Playlist::start_playback (bool paused) const
{
ENTER_GET_PLAYLIST ();
-
- if (at < 0 || number < 0 || at + number > playlist->queued.len ())
- RETURN ();
-
- int entries = playlist->entries.len ();
- int first = entries, last = 0;
-
- for (int i = at; i < at + number; i ++)
- {
- Entry * entry = playlist->queued[i];
- entry->queued = false;
- first = aud::min (first, entry->number);
- last = entry->number;
- }
-
- playlist->queued.remove (at, number);
-
- if (first < entries)
- queue_update (Selection, playlist, first, last + 1 - first, QueueChanged);
-
+ set_playing_locked (m_id, paused);
LEAVE;
}
-EXPORT void aud_playlist_queue_delete_selected (int playlist_num)
+EXPORT void aud_drct_stop ()
{
- ENTER_GET_PLAYLIST ();
-
- int entries = playlist->entries.len ();
- int first = entries, last = 0;
-
- for (int i = 0; i < playlist->queued.len ();)
- {
- Entry * entry = playlist->queued[i];
-
- if (entry->selected)
- {
- playlist->queued.remove (i, 1);
- entry->queued = false;
- first = aud::min (first, entry->number);
- last = entry->number;
- }
- else
- i ++;
- }
-
- if (first < entries)
- queue_update (Selection, playlist, first, last + 1 - first, QueueChanged);
-
+ ENTER;
+ set_playing_locked (nullptr, false);
LEAVE;
}
-static bool shuffle_prev (PlaylistData * playlist)
+EXPORT Playlist Playlist::playing_playlist ()
{
- Entry * found = nullptr;
-
- for (auto & entry : playlist->entries)
- {
- if (entry->shuffle_num && (! playlist->position ||
- entry->shuffle_num < playlist->position->shuffle_num) && (! found
- || entry->shuffle_num > found->shuffle_num))
- found = entry.get ();
- }
-
- if (! found)
- return false;
-
- set_position (playlist, found, false);
- return true;
+ ENTER;
+ auto id = playing_id;
+ RETURN (Playlist (id));
}
-bool playlist_prev_song (int playlist_num)
+EXPORT Playlist Playlist::blank_playlist ()
{
- ENTER_GET_PLAYLIST (false);
-
- if (aud_get_bool (nullptr, "shuffle"))
- {
- if (! shuffle_prev (playlist))
- RETURN (false);
- }
- else
- {
- if (! playlist->position || playlist->position->number == 0)
- RETURN (false);
-
- set_position (playlist, playlist->entries[playlist->position->number - 1].get (), true);
- }
-
- PlaybackChange change = change_playback (playlist);
-
- LEAVE;
-
- hook_call ("playlist position", aud::to_ptr (playlist_num));
- call_playback_change_hooks (change);
- return true;
+ ENTER;
+ auto id = get_blank_locked ();
+ RETURN (Playlist (id));
}
-static bool shuffle_next (PlaylistData * playlist)
+EXPORT Playlist Playlist::temporary_playlist ()
{
- bool by_album = aud_get_bool (nullptr, "album_shuffle");
-
- // helper #1: determine whether two entries are in the same album
- auto same_album = [] (const Tuple & a, const Tuple & b)
- {
- String album = a.get_str (Tuple::Album);
- return (album && album == b.get_str (Tuple::Album));
- };
+ ENTER;
- // helper #2: determine whether an entry is among the shuffle choices
- auto is_choice = [&] (Entry * prev, Entry * entry)
- {
- return (! entry->shuffle_num) && (! by_album || ! prev ||
- prev->shuffle_num || ! same_album (prev->tuple, entry->tuple));
- };
+ const char * title = _(temp_title);
+ ID * id = nullptr;
- if (playlist->position)
+ for (auto & playlist : playlists)
{
- // step #1: check to see if the shuffle order is already established
- Entry * next = nullptr;
-
- for (auto & entry : playlist->entries)
- {
- if (entry->shuffle_num > playlist->position->shuffle_num &&
- (! next || entry->shuffle_num < next->shuffle_num))
- next = entry.get ();
- }
-
- if (next)
- {
- set_position (playlist, next, false);
- return true;
- }
-
- // step #2: check to see if we should advance to the next entry
- if (by_album && playlist->position->number + 1 < playlist->entries.len ())
+ if (! strcmp (playlist->title, title))
{
- next = playlist->entries[playlist->position->number + 1].get ();
-
- if (! next->shuffle_num && same_album (playlist->position->tuple, next->tuple))
- {
- set_position (playlist, next, true);
- return true;
- }
+ id = playlist->id ();
+ break;
}
}
- // step #3: count the number of possible shuffle choices
- int choices = 0;
- Entry * prev = nullptr;
-
- for (auto & entry : playlist->entries)
- {
- if (is_choice (prev, entry.get ()))
- choices ++;
-
- prev = entry.get ();
- }
-
- if (! choices)
- return false;
-
- // step #4: pick one of those choices by random and find it again
- choices = rand () % choices;
- prev = nullptr;
-
- for (auto & entry : playlist->entries)
+ if (! id)
{
- if (is_choice (prev, entry.get ()))
- {
- if (! choices)
- {
- set_position (playlist, entry.get (), true);
- return true;
- }
-
- choices --;
- }
-
- prev = entry.get ();
+ id = get_blank_locked ();
+ id->data->title = String (title);
}
- return false; // never reached
+ RETURN (Playlist (id));
}
-static void shuffle_reset (PlaylistData * playlist)
+EXPORT PluginHandle * Playlist::entry_decoder (int entry_num, GetMode mode, String * error) const
{
- playlist->last_shuffle_num = 0;
-
- for (auto & entry : playlist->entries)
- entry->shuffle_num = 0;
+ ENTER_GET_PLAYLIST (nullptr);
+ wait_for_entry (playlist, entry_num, (mode == Wait), false);
+ PluginHandle * decoder = playlist->entry_decoder (entry_num, error);
+ RETURN (decoder);
}
-static bool next_song_locked (PlaylistData * playlist, bool repeat, int hint)
+EXPORT Tuple Playlist::entry_tuple (int entry_num, GetMode mode, String * error) const
{
- int entries = playlist->entries.len ();
- if (! entries)
- return false;
-
- if (playlist->queued.len ())
- {
- set_position (playlist, playlist->queued[0], true);
- playlist->queued.remove (0, 1);
- playlist->position->queued = false;
-
- queue_update (Selection, playlist, playlist->position->number, 1, QueueChanged);
- }
- else if (aud_get_bool (nullptr, "shuffle"))
- {
- if (! shuffle_next (playlist))
- {
- if (! repeat)
- return false;
-
- shuffle_reset (playlist);
-
- if (! shuffle_next (playlist))
- return false;
- }
- }
- else
- {
- if (hint >= entries)
- {
- if (! repeat)
- return false;
-
- hint = 0;
- }
-
- set_position (playlist, playlist->entries[hint].get (), true);
- }
-
- return true;
+ ENTER_GET_PLAYLIST (Tuple ());
+ wait_for_entry (playlist, entry_num, false, (mode == Wait));
+ Tuple tuple = playlist->entry_tuple (entry_num, error);
+ RETURN (tuple);
}
-bool playlist_next_song (int playlist_num, bool repeat)
+EXPORT void Playlist::rescan_file (const char * filename)
{
- ENTER_GET_PLAYLIST (false);
-
- int hint = playlist->position ? playlist->position->number + 1 : 0;
-
- if (! next_song_locked (playlist, repeat, hint))
- RETURN (false);
+ ENTER;
- PlaybackChange change = change_playback (playlist);
+ for (auto & playlist : playlists)
+ playlist->reset_tuple_of_file (filename);
LEAVE;
-
- hook_call ("playlist position", aud::to_ptr (playlist_num));
- call_playback_change_hooks (change);
- return true;
-}
-
-static Entry * get_playback_entry (int serial)
-{
- if (! playback_check_serial (serial))
- return nullptr;
-
- assert (playing_playlist && playing_playlist->position);
- return playing_playlist->position;
}
// called from playback thread
@@ -2202,10 +1076,12 @@ DecodeInfo playback_entry_read (int serial)
{
ENTER;
DecodeInfo dec;
- Entry * entry;
- if ((entry = get_playback_entry (serial)))
+ if (playback_check_serial (serial))
{
+ auto playlist = playing_id->data;
+ auto entry = playlist->entry_at (playlist->position ());
+
ScanItem * item = scan_list_find_entry (entry);
assert (item && item->for_playback);
@@ -2216,13 +1092,17 @@ DecodeInfo playback_entry_read (int serial)
request->run ();
ENTER;
- if ((entry = get_playback_entry (serial)))
+ if (playback_check_serial (serial))
{
- playback_set_info (entry->number, entry->tuple.ref ());
- art_cache_current (entry->filename, std::move (request->image_data),
- std::move (request->image_file));
+ assert (playlist == playing_id->data);
- dec.filename = entry->filename;
+ int pos = playlist->position ();
+ playback_set_info (pos, playlist->entry_tuple (pos));
+
+ art_cache_current (request->filename,
+ std::move (request->image_data), std::move (request->image_file));
+
+ dec.filename = request->filename;
dec.ip = request->ip;
dec.file = std::move (request->file);
dec.error = std::move (request->error);
@@ -2238,14 +1118,9 @@ DecodeInfo playback_entry_read (int serial)
void playback_entry_set_tuple (int serial, Tuple && tuple)
{
ENTER;
- Entry * entry = get_playback_entry (serial);
- /* don't update cuesheet entries with stream metadata */
- if (entry && ! entry->tuple.is_set (Tuple::StartTime))
- {
- playing_playlist->set_entry_tuple (entry, std::move (tuple));
- queue_update (Metadata, playing_playlist, entry->number, 1);
- }
+ if (playback_check_serial (serial))
+ playing_id->data->update_playback_entry (std::move (tuple));
LEAVE;
}
@@ -2265,20 +1140,20 @@ void playlist_save_state ()
if (! handle)
RETURN ();
- fprintf (handle, "active %d\n", active_playlist ? active_playlist->number : -1);
- fprintf (handle, "playing %d\n", playing_playlist ? playing_playlist->number : -1);
+ fprintf (handle, "active %d\n", active_id ? active_id->index : -1);
+ fprintf (handle, "playing %d\n", playing_id ? playing_id->index : -1);
for (auto & playlist : playlists)
{
- fprintf (handle, "playlist %d\n", playlist->number);
+ fprintf (handle, "playlist %d\n", playlist->id ()->index);
if (playlist->filename)
fprintf (handle, "filename %s\n", (const char *) playlist->filename);
- fprintf (handle, "position %d\n", playlist->position ? playlist->position->number : -1);
+ fprintf (handle, "position %d\n", playlist->position ());
/* resume state is stored per-playlist for historical reasons */
- bool is_playing = (playlist.get () == playing_playlist);
+ bool is_playing = (playlist->id () == playing_id);
fprintf (handle, "resume-state %d\n", (is_playing && paused) ? ResumePause : ResumePlay);
fprintf (handle, "resume-time %d\n", is_playing ? time : playlist->resume_time);
}
@@ -2303,19 +1178,19 @@ void playlist_load_state ()
if (parser.get_int ("active", playlist_num))
{
- if (! (active_playlist = lookup_playlist (playlist_num)))
- active_playlist = playlists[0].get ();
+ if (playlist_num >= 0 && playlist_num < playlists.len ())
+ active_id = playlists[playlist_num]->id ();
+
parser.next ();
}
if (parser.get_int ("playing", resume_playlist))
parser.next ();
- while (parser.get_int ("playlist", playlist_num) && playlist_num >= 0 &&
- playlist_num < playlists.len ())
+ while (parser.get_int ("playlist", playlist_num) &&
+ playlist_num >= 0 && playlist_num < playlists.len ())
{
PlaylistData * playlist = playlists[playlist_num].get ();
- int entries = playlist->entries.len ();
parser.next ();
@@ -2325,10 +1200,10 @@ void playlist_load_state ()
int position = -1;
if (parser.get_int ("position", position))
+ {
+ playlist->set_position (position);
parser.next ();
-
- if (position >= 0 && position < entries)
- set_position (playlist, playlist->entries [position].get (), true);
+ }
/* resume state is stored per-playlist for historical reasons */
int resume_state = ResumePlay;
@@ -2350,30 +1225,19 @@ void playlist_load_state ()
fclose (handle);
/* set initial focus and selection */
- /* clear updates queued during init sequence */
-
for (auto & playlist : playlists)
{
- Entry * focus = playlist->position;
- if (! focus && playlist->entries.len ())
- focus = playlist->entries[0].get ();
+ int focus = playlist->position ();
+ if (focus < 0 && playlist->n_entries ())
+ focus = 0;
- if (focus)
+ if (focus >= 0)
{
- focus->selected = true;
- playlist->focus = focus;
- playlist->selected_count = 1;
- playlist->selected_length = focus->length;
+ playlist->set_focus (focus);
+ playlist->select_entry (focus, true);
}
-
- playlist->next_update = Update ();
- playlist->last_update = Update ();
}
- queued_update.stop ();
- update_level = NoUpdate;
- update_delayed = false;
-
LEAVE;
}
@@ -2382,5 +1246,5 @@ EXPORT void aud_resume ()
if (aud_get_bool (nullptr, "always_resume_paused"))
resume_paused = true;
- aud_playlist_play (resume_playlist, resume_paused);
+ Playlist::by_index (resume_playlist).start_playback (resume_paused);
}
diff --git a/src/libaudcore/playlist.h b/src/libaudcore/playlist.h
index ef63a28..18292dc 100644
--- a/src/libaudcore/playlist.h
+++ b/src/libaudcore/playlist.h
@@ -1,6 +1,6 @@
/*
* playlist.h
- * Copyright 2010-2013 John Lindgren
+ * Copyright 2010-2017 John Lindgren
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -25,372 +25,367 @@
#include <libaudcore/index.h>
#include <libaudcore/tuple.h>
-namespace Playlist {
-
-/* The values which can be passed to the "playlist update" hook. Selection
- * means that entries have been selected or unselected, or that entries have
- * been added to or removed from the queue. Metadata means that new metadata
- * has been read for some entries, or that the title or filename of a playlist
- * has changed, and implies Selection. Structure covers any other change, and
- * implies both Selection and Metadata. */
-enum UpdateLevel {
- NoUpdate = 0,
- Selection,
- Metadata,
- Structure
-};
+/*
+ * Persistent handle attached to a playlist.
+ * Follows the same playlist even if playlists are reordered.
+ * Does not prevent the playlist from being deleted (check exists()).
+ */
+class Playlist
+{
+public:
+ /* --- TYPES --- */
+
+ /* Opaque type which uniquely identifies a playlist */
+ struct ID;
+
+ /* The values which can be passed to the "playlist update" hook. Selection
+ * means that entries have been selected or unselected, or that entries have
+ * been added to or removed from the queue. Metadata means that new metadata
+ * has been read for some entries, or that the title or filename of a playlist
+ * has changed, and implies Selection. Structure covers any other change, and
+ * implies both Selection and Metadata. */
+ enum UpdateLevel {
+ NoUpdate = 0,
+ Selection,
+ Metadata,
+ Structure
+ };
+
+ struct Update {
+ UpdateLevel level; // type of update
+ int before; // number of unaffected entries at playlist start
+ int after; // number of unaffected entries at playlist end
+ bool queue_changed; // true if entries have been added to/removed from queue
+ };
+
+ /* Preset sorting "schemes" */
+ enum SortType {
+ Path, // entry's entire URI
+ Filename, // base name (no folder path)
+ Title,
+ Album,
+ Artist,
+ AlbumArtist,
+ Date, // release date (not modification time)
+ Genre,
+ Track,
+ FormattedTitle,
+ Length,
+ Comment,
+ n_sort_types
+ };
+
+ /* Possible behaviors for entry_{decoder, tuple}. */
+ enum GetMode {
+ NoWait, // non-blocking call; returned tuple will be in Initial state if not yet scanned
+ Wait // blocking call; returned tuple will be either Valid or Failed
+ };
+
+ /* Format descriptor returned by save_formats() */
+ struct SaveFormat {
+ String name; // human-readable format name
+ Index<String> exts; // supported filename extensions
+ };
+
+ typedef bool (* FilterFunc) (const char * filename, void * user);
+ typedef int (* StringCompareFunc) (const char * a, const char * b);
+ typedef int (* TupleCompareFunc) (const Tuple & a, const Tuple & b);
+
+ /* --- CONSTRUCTOR ETC. --- */
+
+ /* Default constructor; indicates "no playlist" */
+ constexpr Playlist () : m_id (nullptr) {}
+
+ bool operator== (const Playlist & b) const { return m_id == b.m_id; }
+ bool operator!= (const Playlist & b) const { return m_id != b.m_id; }
+
+ /* The number of the playlist in display order, starting from 0.
+ * Returns -1 if the playlist no longer exists. */
+ int index () const;
+
+ /* True if the playlist exists (i.e. has not been deleted). */
+ bool exists () const { return index () >= 0; }
+
+ /* --- CORE (STATIC) API --- */
+
+ /* Returns the number of playlists currently open (>= 1). */
+ static int n_playlists ();
+
+ /* Looks up a playlist by display order. */
+ static Playlist by_index (int at);
+
+ /* Adds a new playlist before the one numbered <at> (-1 = insert at end). */
+ static Playlist insert_playlist (int at);
+
+ /* Moves a contiguous block of <count> playlists starting with the one
+ * numbered <from> such that that playlist ends up at the position <to>. */
+ static void reorder_playlists (int from, int to, int count);
+
+ /* Returns the active (i.e. displayed) playlist. */
+ static Playlist active_playlist ();
+
+ /* Convenience function which adds a new playlist after the active one and
+ * then sets the new one as active. Returns the new playlist. */
+ static Playlist new_playlist ();
+
+ /* Returns the currently playing playlist. If no playlist is playing,
+ * returns Playlist(). */
+ static Playlist playing_playlist ();
+
+ /* Returns the number of a "blank" playlist. The active playlist is
+ * returned if it has the default title and has no entries; otherwise, a new
+ * playlist is added and returned. */
+ static Playlist blank_playlist ();
+
+ /* Returns the number of the "temporary" playlist (which is no different
+ * from any other playlist except in name). If the playlist does not exist,
+ * a "blank" playlist is renamed to become the temporary playlist. */
+ static Playlist temporary_playlist ();
+
+ /* Discards the metadata stored for all the entries that refer to a
+ * particular song file, in whatever playlist they appear, and starts
+ * reading it afresh from that file in the background. */
+ static void rescan_file (const char * filename);
+
+ /* --- CORE (NON-STATIC) API --- */
+
+ /* Gets/sets the filename associated with this playlist.
+ * (Audacious currently makes no use of the filename.) */
+ String get_filename () const;
+ void set_filename (const char * filename) const;
+
+ /* Gets/sets the title of the playlist. */
+ String get_title () const;
+ void set_title (const char * title) const;
+
+ /* Closes the playlist.
+ * The playlist is not saved, and no confirmation is presented to the user.
+ * When the last playlist is closed, a new one is added in its place.
+ * When the active playlist is closed, another is made active.
+ * When the playing playlist is closed, playback stops. */
+ void remove_playlist () const;
+
+ /* Makes this the active (i.e. displayed) playlist. */
+ void activate () const;
+
+ /* Starts playback of this playlist, unless it is empty.
+ * Resumes from the position last played if possible.
+ * If <paused> is true, starts playback in a paused state. */
+ void start_playback (bool paused = false) const;
+
+ /* Returns the number of entries (numbered from 0). */
+ int n_entries () const;
+
+ /* Adds a single song file, playlist file, or folder before the entry <at>.
+ * If <at> is negative or equal to the number of entries, the item is added
+ * after the last entry. <tuple> may be null, in which case Audacious will
+ * attempt to read metadata from the song file. If <play> is true,
+ * Audacious will begin playback of the items once they have been added.
+ *
+ * This function is asynchronous (the items are added in the background). */
+ void insert_entry (int at, const char * filename, Tuple && tuple, bool play) const;
+
+ /* Adds multiple song files, playlist files, or folders to a playlist. */
+ void insert_items (int at, Index<PlaylistAddItem> && items, bool play) const;
+
+ /* Similar to insert_items() but allows the caller to prevent some items
+ * from being added by returning false from the <filter> callback. Useful
+ * for searching a folder and adding only new files to the playlist. <user>
+ * is an opaque pointer passed to the callback. */
+ void insert_filtered (int at, Index<PlaylistAddItem> && items,
+ FilterFunc filter, void * user, bool play) const;
+
+ /* Removes entries from the playlist. The playback position may be moved,
+ * or playback may be stopped (according to user preference). */
+ void remove_entries (int at, int number) const;
+ void remove_entry (int at) const { remove_entries (at, 1); }
+ void remove_all_entries () const { remove_entries (0, -1); }
+
+ /* Returns an entry's filename. */
+ String entry_filename (int entry) const;
+
+ /* Returns an entry's decoder plugin. On error, or if the entry has not yet
+ * been scanned, returns nullptr according to <mode>. An optional error
+ * message may be returned. */
+ PluginHandle * entry_decoder (int entry, GetMode mode = Wait, String * error = nullptr) const;
+
+ /* Returns an entry's metadata. The state of the returned tuple may
+ * indicate that the entry has not yet been scanned, or an error occurred,
+ * according to <mode>. An optional error message may be returned. */
+ Tuple entry_tuple (int entry, GetMode mode = Wait, String * error = nullptr) const;
+
+ /* Gets/sets the playing or last-played entry (-1 = no entry).
+ * Affects playback only if this playlist is currently playing.
+ * set_position(get_position()) restarts playback from 0:00.
+ * set_position(-1) stops playback. */
+ int get_position () const;
+ void set_position (int position) const;
+
+ /* Advances the playlist position to the next entry in playback order,
+ * taking current shuffle settings into account. At the end of the
+ * playlist, wraps around to the beginning if <repeat> is true. Returns
+ * true on success, false if playlist position was not changed. */
+ bool next_song (bool repeat) const;
+
+ /* Returns the playlist position to the previous entry in playback order.
+ * Does not support wrapping past the beginning of the playlist. Returns
+ * true on success, false if playlist position was not changed. */
+ bool prev_song () const;
+
+ /* Gets/sets the entry which has keyboard focus (-1 = no entry). */
+ int get_focus () const;
+ void set_focus (int entry) const;
+
+ /* Gets/sets whether an entry is selected. */
+ bool entry_selected (int entry) const;
+ void select_entry (int entry, bool selected) const;
+
+ /* Returns the number of selected entries.
+ * An optional range of entries to examine may be specified. */
+ int n_selected (int at = 0, int number = -1) const;
+
+ /* Selects all (or none) of the entries in a playlist. */
+ void select_all (bool selected) const;
+
+ /* Moves a selected entry within a playlist by an offset of <distance>
+ * entries. Other selected entries are gathered around it. Returns the
+ * offset by which the entry was actually moved (which may be less than the
+ * requested offset. */
+ int shift_entries (int position, int distance) const;
+
+ /* Removes all selected entries. */
+ void remove_selected () const;
+
+ /* Sorts the entries in a playlist based on filename. The callback function
+ * should return negative if the first filename comes before the second,
+ * positive if it comes after, or zero if the two are indistinguishable. */
+ void sort_by_filename (StringCompareFunc compare) const;
+
+ /* Sorts the entries in a playlist based on tuple. May fail if metadata
+ * scanning is still in progress (or has been disabled). */
+ void sort_by_tuple (TupleCompareFunc compare) const;
+
+ /* Sorts the entries in a playlist based on formatted title string. May fail if
+ * metadata scanning is still in progress (or has been disabled). */
+ void sort_by_title (StringCompareFunc compare) const;
+
+ /* Sorts only the selected entries in a playlist based on filename. */
+ void sort_selected_by_filename (StringCompareFunc compare) const;
+
+ /* Sorts only the selected entries in a playlist based on tuple. May fail if
+ * metadata scanning is still in progress (or has been disabled). */
+ void sort_selected_by_tuple (TupleCompareFunc compare) const;
+
+ /* Sorts only the selected entries in a playlist based on formatted title
+ * string. May fail if metadata scanning is still in progress (or has been
+ * disabled). */
+ void sort_selected_by_title (StringCompareFunc compare) const;
+
+ /* Reverses the order of the entries in a playlist. */
+ void reverse_order () const;
+
+ /* Reorders the entries in a playlist randomly. */
+ void randomize_order () const;
+
+ /* Reverses the order of the selected entries in a playlist. */
+ void reverse_selected () const;
+
+ /* Reorders the selected entries in a playlist randomly. */
+ void randomize_selected () const;
-struct Update {
- UpdateLevel level; // type of update
- int before; // number of unaffected entries at playlist start
- int after; // number of unaffected entries at playlist end
- bool queue_changed; // true if entries have been added to/removed from queue
-};
+ /* Discards the metadata stored for entries in a playlist and starts reading
+ * it fresh from the song files in the background. */
+ void rescan_all () const;
+ void rescan_selected () const;
-/* The values which can be passed to playlist_sort_by_scheme(),
- * playlist_sort_selected_by_scheme(), and
- * playlist_remove_duplicates_by_scheme(). PlaylistSort::Path means the entire
- * URI of a song file; PlaylistSort::Filename means the portion after the last
- * "/" (forward slash). PlaylistSort::Date means the song's release date (not
- * the file's modification time). */
-enum SortType {
- Path,
- Filename,
- Title,
- Album,
- Artist,
- AlbumArtist,
- Date,
- Genre,
- Track,
- FormattedTitle,
- Length,
- n_sort_types
-};
+ /* Calculates the length in milliseconds of entries in a playlist. Only
+ * takes into account entries for which metadata has already been read. */
+ int64_t total_length_ms () const;
+ int64_t selected_length_ms () const;
-/* Possible behaviors for playlist_entry_get_{decoder, tuple}. */
-enum GetMode {
- NoWait, // non-blocking call; returned tuple will be in Initial state if not yet scanned
- Wait // blocking call; returned tuple will be either Valid or Failed
-};
+ /* Returns the number of entries in a playlist queue. */
+ int n_queued () const;
-/* Format descriptor returned by playlist_save_formats() */
-struct SaveFormat {
- String name; // human-readable format name
- Index<String> exts; // supported filename extensions
-};
+ /* Adds an entry to the queue at <pos> (-1 = at end of queue).
+ * The same entry cannot be added to the queue more than once. */
+ void queue_insert (int pos, int entry) const;
+ void queue_insert_selected (int pos) const;
-} // namespace Playlist
-
-typedef bool (* PlaylistFilterFunc) (const char * filename, void * user);
-typedef int (* PlaylistStringCompareFunc) (const char * a, const char * b);
-typedef int (* PlaylistTupleCompareFunc) (const Tuple & a, const Tuple & b);
-
-/* --- PLAYLIST CORE API --- */
-
-/* Returns the number of playlists currently open. There will always be at
- * least one playlist open. The playlists are numbered starting from zero. */
-int aud_playlist_count ();
-
-/* Adds a new playlist before the one numbered <at>. If <at> is -1 or equal to
- * the number of playlists, adds a new playlist after the last one. */
-void aud_playlist_insert (int at);
-
-/* Moves a contiguous block of <count> playlists starting with the one numbered
- * <from> such that that playlist ends up at the position <to>. */
-void aud_playlist_reorder (int from, int to, int count);
-
-/* Closes a playlist. CAUTION: The playlist is not saved, and no confirmation
- * is presented to the user. If <playlist> is the only playlist, a new playlist
- * is added. If <playlist> is the active playlist, another playlist is marked
- * active. If <playlist> is the currently playing playlist, playback is
- * stopped. */
-void aud_playlist_delete (int playlist);
-
-/* Returns a unique non-negative integer which can be used to identify a given
- * playlist even if its numbering changes (as when playlists are reordered).
- * On error, returns -1. */
-int aud_playlist_get_unique_id (int playlist);
-
-/* Returns the number of the playlist identified by a given integer ID as
- * returned by playlist_get_unique_id(). If the playlist no longer exists,
- * returns -1. */
-int aud_playlist_by_unique_id (int id);
-
-/* Sets the filename associated with a playlist. (Audacious currently makes no
- * use of the filename.) */
-void aud_playlist_set_filename (int playlist, const char * filename);
-
-/* Returns the filename associated with a playlist. */
-String aud_playlist_get_filename (int playlist);
-
-/* Sets the title associated with a playlist. */
-void aud_playlist_set_title (int playlist, const char * title);
-
-/* Returns the title associated with a playlist. */
-String aud_playlist_get_title (int playlist);
-
-/* Sets the active playlist. This is the playlist that user interfaces will
- * show to the user. */
-void aud_playlist_set_active (int playlist);
-
-/* Returns the number of the active playlist. */
-int aud_playlist_get_active ();
-
-/* Convenience function which adds a new playlist after the active one and then
- * sets the new one as active. Returns the number of the new playlist. */
-int aud_playlist_new ();
-
-/* Starts playback of a playlist, resuming from the position last played if
- * possible. If <playlist> is -1 or if the requested playlist is empty, stops
- * playback. If <paused> is true, starts playback in a paused state. */
-void aud_playlist_play (int playlist, bool paused = false);
-
-/* Returns the number of the currently playing playlist. If no playlist is
- * playing, returns -1. */
-int aud_playlist_get_playing ();
-
-/* Returns the number of a "blank" playlist. The active playlist is returned if
- * it has the default title and has no entries; otherwise, a new playlist is
- * added and returned. */
-int aud_playlist_get_blank ();
-
-/* Returns the number of the "temporary" playlist (which is no different from
- * any other playlist except in name). If the playlist does not exist, a
- * "blank" playlist is obtained from playlist_get_blank() and is renamed to
- * become the temporary playlist. */
-int aud_playlist_get_temporary ();
-
-/* Returns the number of entries in a playlist. The entries are numbered
- * starting from zero. */
-int aud_playlist_entry_count (int playlist);
-
-/* Adds a song file, playlist file, or folder to a playlist before the entry
- * numbered <at>. If <at> is negative or equal to the number of entries, the
- * item is added after the last entry. <tuple> may be nullptr, in which case
- * Audacious will attempt to read metadata from the song file. If <play> is
- * true, Audacious will begin playback of the items once they have been
- * added.
- *
- * Because adding items to the playlist can be a slow process, this function may
- * return before the process is complete. Hence, the caller must not assume
- * that there will be new entries in the playlist immediately. */
-void aud_playlist_entry_insert (int playlist, int at, const char * filename,
- Tuple && tuple, bool play);
-
-/* Similar to playlist_entry_insert, adds multiple song files, playlist files,
- * or folders to a playlist. */
-void aud_playlist_entry_insert_batch (int playlist, int at,
- Index<PlaylistAddItem> && items, bool play);
-
-/* Similar to playlist_entry_insert_batch, but allows the caller to prevent some
- * items from being added by returning false from the <filter> callback. Useful
- * for searching a folder and adding only new files to the playlist. <user> is
- * an additional, untyped pointer passed to the callback. */
-void aud_playlist_entry_insert_filtered (int playlist, int at,
- Index<PlaylistAddItem> && items, PlaylistFilterFunc filter, void * user,
- bool play);
-
-/* Removes a contiguous block of <number> entries starting from the one numbered
- * <at> from a playlist. If necessary, the playback position is moved elsewhere
- * in the playlist and playback is restarted (or stopped). */
-void aud_playlist_entry_delete (int playlist, int at, int number);
-
-/* Returns the filename of an entry. */
-String aud_playlist_entry_get_filename (int playlist, int entry);
-
-/* Returns a handle to the decoder plugin associated with an entry. On error,
- * or if the entry has not yet been scanned, returns nullptr according to
- * <mode>. On error, an error message is optionally returned. */
-PluginHandle * aud_playlist_entry_get_decoder (int playlist, int entry,
- Playlist::GetMode mode = Playlist::Wait, String * error = nullptr);
-
-/* Returns the tuple associated with an entry. The state of the returned tuple
- * may indicate that the entry has not yet been scanned, or an error occurred,
- * according to <mode>. On error, an error message is optionally returned. */
-Tuple aud_playlist_entry_get_tuple (int playlist, int entry,
- Playlist::GetMode mode = Playlist::Wait, String * error = nullptr);
-
-/* Moves the playback position to the beginning of the entry at <position>. If
- * <position> is -1, unsets the playback position. If <playlist> is the
- * currently playing playlist, playback is restarted (or stopped). */
-void aud_playlist_set_position (int playlist, int position);
-
-/* Returns the playback position, or -1 if it is not set. Note that the
- * position may be set even if <playlist> is not currently playing. */
-int aud_playlist_get_position (int playlist);
-
-/* Sets the entry which has keyboard focus (-1 means no entry). */
-void aud_playlist_set_focus (int playlist, int entry);
-
-/* Gets the entry which has keyboard focus (-1 means no entry). */
-int aud_playlist_get_focus (int playlist);
-
-/* Sets whether an entry is selected. */
-void aud_playlist_entry_set_selected (int playlist, int entry, bool selected);
-
-/* Returns whether an entry is selected. */
-bool aud_playlist_entry_get_selected (int playlist, int entry);
-
-/* Returns the number of selected entries in a playlist. */
-int aud_playlist_selected_count (int playlist);
-
-/* Selects all (or none) of the entries in a playlist. */
-void aud_playlist_select_all (int playlist, bool selected);
-
-/* Moves a selected entry within a playlist by an offset of <distance> entries.
- * Other selected entries are gathered around it. Returns the offset by which
- * the entry was actually moved, which may be less in absolute value than the
- * requested offset. */
-int aud_playlist_shift (int playlist, int position, int distance);
-
-/* Removes the selected entries from a playlist. If necessary, the playback
- * position is moved elsewhere in the playlist and playback is restarted (or
- * stopped). */
-void aud_playlist_delete_selected (int playlist);
-
-/* Sorts the entries in a playlist based on filename. The callback function
- * should return negative if the first filename comes before the second,
- * positive if it comes after, or zero if the two are indistinguishable. */
-void aud_playlist_sort_by_filename (int playlist, PlaylistStringCompareFunc compare);
-
-/* Sorts the entries in a playlist based on tuple. May fail if metadata
- * scanning is still in progress (or has been disabled). */
-void aud_playlist_sort_by_tuple (int playlist, PlaylistTupleCompareFunc compare);
-
-/* Sorts the entries in a playlist based on formatted title string. May fail if
- * metadata scanning is still in progress (or has been disabled). */
-void aud_playlist_sort_by_title (int playlist, PlaylistStringCompareFunc compare);
-
-/* Sorts only the selected entries in a playlist based on filename. */
-void aud_playlist_sort_selected_by_filename (int playlist, PlaylistStringCompareFunc compare);
-
-/* Sorts only the selected entries in a playlist based on tuple. May fail if
- * metadata scanning is still in progress (or has been disabled). */
-void aud_playlist_sort_selected_by_tuple (int playlist, PlaylistTupleCompareFunc compare);
-
-/* Sorts only the selected entries in a playlist based on formatted title
- * string. May fail if metadata scanning is still in progress (or has been
- * disabled). */
-void aud_playlist_sort_selected_by_title (int playlist, PlaylistStringCompareFunc compare);
-
-/* Reverses the order of the entries in a playlist. */
-void aud_playlist_reverse (int playlist);
-
-/* Reorders the entries in a playlist randomly. */
-void aud_playlist_randomize (int playlist);
-
-/* Reverses the order of the selected entries in a playlist. */
-void aud_playlist_reverse_selected (int playlist);
-
-/* Reorders the selected entries in a playlist randomly. */
-void aud_playlist_randomize_selected (int playlist);
-
-/* Discards the metadata stored for all the entries in a playlist and starts
- * reading it afresh from the song files in the background. */
-void aud_playlist_rescan (int playlist);
+ /* Returns the entry at the given queue position. */
+ int queue_get_entry (int pos) const;
+
+ /* Returns the queue position of the given entry (-1 if not queued). */
+ int queue_find_entry (int entry) const;
-/* Like playlist_rescan, but applies only to the selected entries in a playlist. */
-void aud_playlist_rescan_selected (int playlist);
-
-/* Discards the metadata stored for all the entries that refer to a particular
- * song file, in whatever playlist they appear, and starts reading it afresh
- * from that file in the background. */
-void aud_playlist_rescan_file (const char * filename);
+ /* Removes entries from the queue. */
+ void queue_remove (int pos, int number = 1) const;
+ void queue_remove_all () const { queue_remove (0, -1); }
-/* Calculates the total length in milliseconds of all the entries in a playlist.
- * Only takes into account entries for which metadata has already been read. */
-int64_t aud_playlist_get_total_length (int playlist);
+ /* Removes the selected entries in a playlist from the queue, if they are in it. */
+ void queue_remove_selected () const;
-/* Calculates the total length in milliseconds of only the selected entries in a
- * playlist. Only takes into account entries for which metadata has already
- * been read. */
-int64_t aud_playlist_get_selected_length (int playlist);
+ /* Returns true if a "playlist update" hook call is pending.
+ * A running hook call is not considered pending. */
+ bool update_pending () const;
+ static bool update_pending_any ();
-/* Returns the number of entries in a playlist queue. The entries are numbered
- * starting from zero, lower numbers being played first. */
-int aud_playlist_queue_count (int playlist);
+ /* May be called within the "playlist update" hook to determine the update
+ * level and number of entries changed in a playlist. */
+ Update update_detail () const;
-/* Adds an entry to a playlist's queue before the entry numbered <at> in the
- * queue. If <at> is negative or equal to the number of entries in the queue,
- * adds the entry after the last one in the queue. The same entry cannot be
- * added to the queue more than once. */
-void aud_playlist_queue_insert (int playlist, int at, int entry);
+ /* Returns true if entries are being added in the background. */
+ bool add_in_progress () const;
+ static bool add_in_progress_any ();
-/* Adds the selected entries in a playlist to the queue, if they are not already
- * in it. */
-void aud_playlist_queue_insert_selected (int playlist, int at);
+ /* Returns true if entries are being scanned in the background. */
+ bool scan_in_progress () const;
+ static bool scan_in_progress_any ();
-/* Returns the position in the playlist of the entry at a given position in the
- * queue. */
-int aud_playlist_queue_get_entry (int playlist, int at);
+ /* --- UTILITY API --- */
-/* Returns the position in the queue of the entry at a given position in the
- * playlist. If it is not in the queue, returns -1. */
-int aud_playlist_queue_find_entry (int playlist, int entry);
+ /* Sorts entries according to a preset scheme. */
+ void sort_entries (SortType scheme) const;
+ void sort_selected (SortType scheme) const;
-/* Removes a contiguous block of <number> entries starting with the one numbered
- * <at> from the queue. */
-void aud_playlist_queue_delete (int playlist, int at, int number);
-
-/* Removes the selected entries in a playlist from the queue, if they are in it. */
-void aud_playlist_queue_delete_selected (int playlist);
-
-/* Returns true if a "playlist update" hook call is pending for the given
- * playlist (or for any playlist, if <playlist> is -1). If called from within
- * the hook, the current hook call is not considered pending. */
-bool aud_playlist_update_pending (int playlist = -1);
-
-/* May be called within the "playlist update" hook to determine the update level
- * and number of entries changed in a playlist. */
-Playlist::Update aud_playlist_update_detail (int playlist);
-
-/* Returns true if entries are being added to a playlist in the background.
- * If <playlist> is -1, checks all playlists. */
-bool aud_playlist_add_in_progress (int playlist);
+ /* Removes duplicate entries according to a preset scheme.
+ * The current implementation also sorts the playlist. */
+ void remove_duplicates (SortType scheme) const;
-/* Returns true if entries in a playlist are being scanned for metadata in
- * the background. If <playlist> is -1, checks all playlists. */
-bool aud_playlist_scan_in_progress (int playlist);
+ /* Removes all entries referring to inaccessible files in a playlist. */
+ void remove_unavailable () const;
-/* --- PLAYLIST UTILITY API --- */
+ /* Selects entries by matching regular expressions.
+ * Example: To select all titles starting with the letter "A",
+ * create a blank tuple and set its title field to "^A". */
+ void select_by_patterns (const Tuple & patterns) const;
-/* Sorts the entries in a playlist according to one of the schemes listed in
- * playlist.h. */
-void aud_playlist_sort_by_scheme (int playlist, Playlist::SortType scheme);
+ /* Saves metadata for the selected entries to an internal cache.
+ * This will speed up adding those entries to another playlist. */
+ void cache_selected () const;
-/* Sorts only the selected entries in a playlist according to one of those
- * schemes. */
-void aud_playlist_sort_selected_by_scheme (int playlist, Playlist::SortType scheme);
+ /* Saves the entries in a playlist to a playlist file.
+ * The format of the file is determined from the file extension.
+ * <mode> specifies whether to wait for metadata scanning to complete.
+ * Returns true on success. */
+ bool save_to_file (const char * filename, GetMode mode) const;
-/* Removes duplicate entries in a playlist according to one of those schemes.
- * As currently implemented, first sorts the playlist. */
-void aud_playlist_remove_duplicates_by_scheme (int playlist, Playlist::SortType scheme);
+ /* Checks a filename for an extension matching a known playlist format. */
+ static bool filename_is_playlist (const char * filename);
-/* Removes all entries referring to unavailable files in a playlist. ("Remove
- * failed" is something of a misnomer for the current behavior.) */
-void aud_playlist_remove_failed (int playlist);
+ /* Generates a list of the currently supported formats for saving playlists.
+ * The list should not be cached since it may change as plugins are enabled or
+ * disabled. */
+ static Index<SaveFormat> save_formats ();
-/* Selects all the entries in a playlist that match regular expressions stored
- * in the fields of a tuple. Does not free the memory used by the tuple.
- * Example: To select all the songs whose title starts with the letter "A",
- * create a blank tuple and set its title field to "^A". */
-void aud_playlist_select_by_patterns (int playlist, const Tuple & patterns);
+ /* --- IMPLEMENTATION --- */
-/* Saves metadata for the selected entries in a playlist to an internal cache,
- * which is used to speed up adding these entries to another playlist. */
-void aud_playlist_cache_selected (int playlist);
-
-/* Returns true if <filename> refers to a playlist file. */
-bool aud_filename_is_playlist (const char * filename);
-
-/* Saves the entries in a playlist to a playlist file. The format of the file
- * is determined from the file extension. Returns true on success. */
-bool aud_playlist_save (int playlist, const char * filename, Playlist::GetMode mode);
-
-/* Generates a list of the currently supported formats for saving playlists.
- * The list should not be cached since it may change as plugins are enabled or
- * disabled. */
-Index<Playlist::SaveFormat> aud_playlist_save_formats ();
+private:
+ ID * m_id;
+
+ explicit constexpr Playlist (ID * id) :
+ m_id (id) {}
+
+ friend class PlaylistEx;
+};
#endif
diff --git a/src/libaudcore/plugin-load.cc b/src/libaudcore/plugin-load.cc
index c249fea..5edb062 100644
--- a/src/libaudcore/plugin-load.cc
+++ b/src/libaudcore/plugin-load.cc
@@ -168,6 +168,7 @@ void plugin_system_init ()
void plugin_system_cleanup ()
{
plugin_registry_save ();
+ plugin_registry_cleanup ();
for (LoadedModule & loaded : loaded_modules)
plugin_unload (loaded);
diff --git a/src/libaudcore/plugin-registry.cc b/src/libaudcore/plugin-registry.cc
index cdeed7e..3ca79f3 100644
--- a/src/libaudcore/plugin-registry.cc
+++ b/src/libaudcore/plugin-registry.cc
@@ -118,6 +118,7 @@ static constexpr aud::array<InputKey, const char *> input_key_names = {
static aud::array<PluginType, Index<PluginHandle *>> plugins;
static aud::array<PluginType, Index<PluginHandle *>> compatible;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+static bool modified = false;
static StringBuf get_basename (const char * path)
{
@@ -190,6 +191,9 @@ static void plugin_save (PluginHandle * plugin, FILE * handle)
void plugin_registry_save ()
{
+ if (! modified)
+ return;
+
FILE * handle = open_registry_file ("w");
if (! handle)
return;
@@ -199,18 +203,25 @@ void plugin_registry_save ()
for (auto & list : plugins)
{
for (PluginHandle * plugin : list)
- {
plugin_save (plugin, handle);
+ }
+
+ fclose (handle);
+ modified = false;
+}
+
+void plugin_registry_cleanup ()
+{
+ for (auto & list : plugins)
+ {
+ for (PluginHandle * plugin : list)
delete plugin;
- }
list.clear ();
}
for (auto & list : compatible)
list.clear ();
-
- fclose (handle);
}
static void transport_plugin_parse (PluginHandle * plugin, TextParser & parser)
@@ -556,6 +567,7 @@ void plugin_register (const char * path, int timestamp)
plugin->timestamp = timestamp;
plugin_get_info (plugin, false);
+ modified = true;
}
}
else
@@ -570,6 +582,7 @@ void plugin_register (const char * path, int timestamp)
plugins[plugin->type].append (plugin);
plugin_get_info (plugin, true);
+ modified = true;
}
}
@@ -656,6 +669,7 @@ void plugin_set_enabled (PluginHandle * plugin, PluginEnabled enabled)
{
plugin->enabled = enabled;
plugin_call_watches (plugin);
+ modified = true;
}
void plugin_set_failed (PluginHandle * plugin)
diff --git a/src/libaudcore/plugins-internal.h b/src/libaudcore/plugins-internal.h
index ef293d5..d521c5c 100644
--- a/src/libaudcore/plugins-internal.h
+++ b/src/libaudcore/plugins-internal.h
@@ -50,6 +50,7 @@ Plugin * plugin_load (const char * path);
void plugin_registry_load ();
void plugin_registry_prune ();
void plugin_registry_save ();
+void plugin_registry_cleanup ();
void plugin_register (const char * path, int timestamp);
PluginEnabled plugin_get_enabled (PluginHandle * plugin);
diff --git a/src/libaudcore/probe.cc b/src/libaudcore/probe.cc
index b2fff60..81bac55 100644
--- a/src/libaudcore/probe.cc
+++ b/src/libaudcore/probe.cc
@@ -205,7 +205,7 @@ EXPORT bool aud_file_write_tuple (const char * filename,
success = false;
if (success)
- aud_playlist_rescan_file (filename);
+ Playlist::rescan_file (filename);
return success;
}
diff --git a/src/libaudcore/probe.h b/src/libaudcore/probe.h
index 8944289..c206f6c 100644
--- a/src/libaudcore/probe.h
+++ b/src/libaudcore/probe.h
@@ -27,23 +27,53 @@ class PluginHandle;
class Tuple;
class VFSFile;
-/* Gets album art for <file> (the URI of a song file) as JPEG or PNG data. If
- * the album art is not yet loaded, sets *queued to true, returns nullptr, and
- * begins to load the album art in the background. On completion, the "art
- * ready" hook is called, with <file> as a parameter. If no album art could be
- * loaded, sets *queued to false and returns nullptr.
- *
- * If you only want to display album art for the currently playing song, you can
- * call this function from the "playback ready" hook without any need for a
- * separate "art ready" handler, since the album art is already at that point. */
-const Index<char> * aud_art_request_data (const char * file, bool * queued = nullptr);
+/* ====== ALBUM ART API ====== */
+
+/* request format */
+enum {
+ AUD_ART_DATA = (1 << 0), /* image data in memory */
+ AUD_ART_FILE = (1 << 1) /* filename of image data on disk */
+};
+
+/* opaque type storing art data */
+struct AudArtItem;
+
+/* don't use these directly, use AudArtPtr */
+const Index<char> * aud_art_data (const AudArtItem * item);
+const char * aud_art_file (const AudArtItem * item);
+void aud_art_unref (AudArtItem * item);
+
+/* handle for accessing/tracking album art data */
+class AudArtPtr : public SmartPtr<AudArtItem, aud_art_unref>
+{
+public:
+ AudArtPtr () : SmartPtr () {}
+ explicit AudArtPtr (AudArtItem * ptr) : SmartPtr (ptr) {}
+
+ const Index<char> * data () const
+ { return get () ? aud_art_data (get ()) : nullptr; }
+ const char * file () const
+ { return get () ? aud_art_file (get ()) : nullptr; }
+};
-/* Similar to art_request_data() but returns the URI of an image file.
- * (A temporary file will be created if necessary.) */
-const char * aud_art_request_file (const char * file, bool * queued = nullptr);
+/*
+ * Gets album art for <file> (the URI of a song file). The data will be
+ * returned in the requested <format> (AUD_ART_DATA or AUD_ART_FILE).
+ *
+ * This is a non-blocking call. If the data is not yet loaded, it sets *queued
+ * to true, returns a null pointer, and begins to load the data in the back-
+ * ground. On completion, the "art ready" hook is called, with <file> as a
+ * parameter. The data can then be requested again from within the hook.
+ *
+ * As a special case, album art data for the currently playing song is preloaded
+ * by the time the "playback ready" hook is called, so in that case there is no
+ * need to implement a separate "art ready" handler.
+ *
+ * On error, a null pointer is returned and *queued is set to false.
+ */
+AudArtPtr aud_art_request (const char * file, int format, bool * queued = nullptr);
-/* Releases album art returned by art_request_data() or art_request_file(). */
-void aud_art_unref (const char * file);
+/* ====== GENERAL PROBING API ====== */
/* The following two functions take an additional VFSFile parameter to allow
* opening a file, probing for a decoder, and then reading the song metadata
diff --git a/src/libaudcore/runtime.cc b/src/libaudcore/runtime.cc
index 5c3660f..7eb8e5e 100644
--- a/src/libaudcore/runtime.cc
+++ b/src/libaudcore/runtime.cc
@@ -303,6 +303,7 @@ static void do_autosave (void *)
{
hook_call ("config save", nullptr);
save_playlists (false);
+ plugin_registry_save ();
config_save ();
}
@@ -312,6 +313,7 @@ EXPORT void aud_run ()
* avoid scanning until the currently playing entry is known, at which time
* it can be scanned more efficiently (album art read in the same pass). */
playlist_enable_scan (true);
+ playlist_clear_updates ();
start_plugins_two ();
static QueuedFunc autosave;
@@ -330,7 +332,7 @@ EXPORT void aud_cleanup ()
{
save_playlists (true);
- aud_playlist_play (-1);
+ aud_drct_stop ();
playback_stop (true);
adder_cleanup ();
diff --git a/src/libaudcore/runtime.h b/src/libaudcore/runtime.h
index 570f948..30ad950 100644
--- a/src/libaudcore/runtime.h
+++ b/src/libaudcore/runtime.h
@@ -52,6 +52,12 @@ enum class OutputStream {
AfterEqualizer
};
+enum class ReplayGainMode {
+ Track,
+ Album,
+ Automatic
+};
+
namespace audlog
{
enum Level {
@@ -68,8 +74,13 @@ namespace audlog
void subscribe (Handler handler, Level level);
void unsubscribe (Handler handler);
+#ifdef _WIN32
+ void log (Level level, const char * file, int line, const char * func,
+ const char * format, ...) __attribute__ ((__format__ (gnu_printf, 5, 6)));
+#else
void log (Level level, const char * file, int line, const char * func,
const char * format, ...) __attribute__ ((__format__ (__printf__, 5, 6)));
+#endif
const char * get_level_name (Level level);
}
@@ -101,6 +112,7 @@ void aud_set_str (const char * section, const char * name, const char * value);
String aud_get_str (const char * section, const char * name);
void aud_set_bool (const char * section, const char * name, bool value);
bool aud_get_bool (const char * section, const char * name);
+void aud_toggle_bool (const char * section, const char * name);
void aud_set_int (const char * section, const char * name, int value);
int aud_get_int (const char * section, const char * name);
void aud_set_double (const char * section, const char * name, double value);
diff --git a/src/libaudcore/strpool.cc b/src/libaudcore/strpool.cc
index 8f3679d..c9308d7 100644
--- a/src/libaudcore/strpool.cc
+++ b/src/libaudcore/strpool.cc
@@ -1,6 +1,6 @@
/*
* strpool.c
- * Copyright 2011-2013 John Lindgren
+ * Copyright 2011-2017 John Lindgren
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -17,8 +17,6 @@
* the use of this software.
*/
-#include <assert.h>
-#include <stddef.h>
#include <stdlib.h>
#include <string.h>
@@ -30,126 +28,87 @@
#ifdef VALGRIND_FRIENDLY
-struct StrNode {
- unsigned hash;
- char magic;
- char str[];
-};
-
-#define NODE_SIZE_FOR(s) (offsetof (StrNode, str) + strlen (s) + 1)
-#define NODE_OF(s) ((StrNode *) ((s) - offsetof (StrNode, str)))
+#include <glib.h>
EXPORT char * String::raw_get (const char * str)
-{
- if (! str)
- return nullptr;
-
- StrNode * node = (StrNode *) malloc (NODE_SIZE_FOR (str));
- if (! node)
- throw std::bad_alloc ();
-
- node->magic = '@';
- node->hash = str_calc_hash (str);
-
- strcpy (node->str, str);
- return node->str;
-}
-
+ { return g_strdup (str); }
EXPORT char * String::raw_ref (const char * str)
-{
- if (! str)
- return nullptr;
-
- StrNode * node = NODE_OF (str);
- assert (node->magic == '@');
- assert (str_calc_hash (str) == node->hash);
-
- return raw_get (str);
-}
-
+ { return g_strdup (str); }
EXPORT void String::raw_unref (char * str)
-{
- if (! str)
- return;
+ { g_free (str); }
- StrNode * node = NODE_OF (str);
- assert (node->magic == '@');
- assert (str_calc_hash (str) == node->hash);
-
- node->magic = 0;
- free (node);
-}
+void string_leak_check () {}
EXPORT unsigned String::raw_hash (const char * str)
-{
- if (! str)
- return 0;
-
- StrNode * node = NODE_OF (str);
- assert (node->magic == '@');
-
- return str_calc_hash (str);
-}
-
+ { return str_calc_hash (str); }
EXPORT bool String::raw_equal (const char * str1, const char * str2)
-{
- assert (! str1 || NODE_OF (str1)->magic == '@');
- assert (! str2 || NODE_OF (str2)->magic == '@');
+ { return ! strcmp_safe (str1, str2); }
- return ! strcmp_safe (str1, str2);
-}
+#else // ! VALGRIND_FRIENDLY
-EXPORT void string_leak_check ()
+struct StrNode : public MultiHash::Node
{
-}
+ /* the characters of the string immediately follow the StrNode struct */
+ const char * str () const
+ { return reinterpret_cast<const char *> (this + 1); }
+ char * str ()
+ { return reinterpret_cast<char *> (this + 1); }
-#else /* ! VALGRIND_FRIENDLY */
+ static const StrNode * of (const char * s)
+ { return reinterpret_cast<const StrNode *> (s) - 1; }
+ static StrNode * of (char * s)
+ { return reinterpret_cast<StrNode *> (s) - 1; }
-struct StrNode {
- MultiHash::Node base;
- unsigned refs;
- char magic;
- char str[1]; // variable size
-};
-
-#define NODE_SIZE_FOR(s) (offsetof (StrNode, str) + strlen (s) + 1)
-#define NODE_OF(s) ((StrNode *) ((s) - offsetof (StrNode, str)))
+ static StrNode * create (const char * s)
+ {
+ auto size = sizeof (StrNode) + strlen (s) + 1;
+ auto node = static_cast<StrNode *> (malloc (size));
+ if (! node)
+ throw std::bad_alloc ();
-static bool match_cb (const MultiHash::Node * node_, const void * data_)
-{
- const StrNode * node = (const StrNode *) node_;
- const char * data = (const char *) data_;
+ strcpy (node->str (), s);
+ return node;
+ }
- return data == node->str || ! strcmp (data, node->str);
-}
+ bool match (const char * data) const
+ { return data == str () || ! strcmp (data, str ()); }
+};
-static MultiHash strpool_table (match_cb);
+static MultiHash_T<StrNode, char> strpool_table;
-static MultiHash::Node * add_cb (const void * data_, void * state)
+struct Getter
{
- const char * data = (const char *) data_;
-
- StrNode * node = (StrNode *) malloc (NODE_SIZE_FOR (data));
- if (! node)
- throw std::bad_alloc ();
+ StrNode * node;
- node->refs = 1;
- node->magic = '@';
- strcpy (node->str, data);
+ StrNode * add (const char * data)
+ {
+ node = StrNode::create (data);
+ node->refs = 1;
+ return node;
+ }
- * ((char * *) state) = node->str;
- return (MultiHash::Node *) node;
-}
+ bool found (StrNode * node_)
+ {
+ node = node_;
+ __sync_fetch_and_add (& node->refs, 1);
+ return false;
+ }
+};
-static bool ref_cb (MultiHash::Node * node_, void * state)
+struct Remover
{
- StrNode * node = (StrNode *) node_;
+ StrNode * add (const char *)
+ { return nullptr; }
- __sync_fetch_and_add (& node->refs, 1);
+ bool found (StrNode * node)
+ {
+ if (! __sync_bool_compare_and_swap (& node->refs, 1, 0))
+ return false;
- * ((char * *) state) = node->str;
- return false;
-}
+ free (node);
+ return true;
+ }
+};
/* If the pool contains a copy of <str>, increments its reference count.
* Otherwise, adds a copy of <str> to the pool with a reference count of one.
@@ -161,38 +120,24 @@ EXPORT char * String::raw_get (const char * str)
if (! str)
return nullptr;
- char * ret = nullptr;
- strpool_table.lookup (str, str_calc_hash (str), add_cb, ref_cb, & ret);
- return ret;
+ Getter op;
+ strpool_table.lookup (str, str_calc_hash (str), op);
+ return op.node->str ();
}
/* Increments the reference count of <str>, where <str> is the address of a
* string already in the pool. Faster than calling raw_get() a second time.
* Returns <str> for convenience. If <str> is null, simply returns null with no
* side effects. */
-EXPORT char * String::raw_ref (const char * str)
+EXPORT char * String::raw_ref (const char * str_)
{
+ auto str = const_cast<char *> (str_);
if (! str)
return nullptr;
- StrNode * node = NODE_OF (str);
- assert (node->magic == '@');
-
+ auto node = StrNode::of (str);
__sync_fetch_and_add (& node->refs, 1);
-
- return (char *) str;
-}
-
-static bool remove_cb (MultiHash::Node * node_, void * state)
-{
- StrNode * node = (StrNode *) node_;
-
- if (! __sync_bool_compare_and_swap (& node->refs, 1, 0))
- return false;
-
- node->magic = 0;
- free (node);
- return true;
+ return str;
}
/* Decrements the reference count of <str>, where <str> is the address of a
@@ -204,13 +149,11 @@ EXPORT void String::raw_unref (char * str)
if (! str)
return;
- StrNode * node = NODE_OF (str);
- assert (node->magic == '@');
+ auto node = StrNode::of (str);
while (1)
{
- int refs = __sync_fetch_and_add (& node->refs, 0);
-
+ unsigned refs = __sync_fetch_and_add (& node->refs, 0);
if (refs > 1)
{
if (__sync_bool_compare_and_swap (& node->refs, refs, refs - 1))
@@ -218,49 +161,36 @@ EXPORT void String::raw_unref (char * str)
}
else
{
- int status = strpool_table.lookup (node->str, node->base.hash, nullptr,
- remove_cb, nullptr);
-
- assert (status & MultiHash::Found);
+ Remover op;
+ int status = strpool_table.lookup (str, node->hash, op);
+ if (! (status & MultiHash::Found))
+ throw std::bad_alloc ();
if (status & MultiHash::Removed)
break;
}
}
}
-static bool leak_cb (MultiHash::Node * node, void * state)
-{
- AUDWARN ("String leaked: %s\n", ((StrNode *) node)->str);
- return false;
-}
-
void string_leak_check ()
{
- strpool_table.iterate (leak_cb, nullptr);
+ strpool_table.iterate ([] (const StrNode * node) {
+ AUDWARN ("String leaked: %s\n", node->str ());
+ return false;
+ });
}
/* Returns the cached hash value of a pooled string (or 0 for null). */
EXPORT unsigned String::raw_hash (const char * str)
{
- if (! str)
- return 0;
-
- StrNode * node = NODE_OF (str);
- assert (node->magic == '@');
-
- return node->base.hash;
+ return str ? StrNode::of (str)->hash : 0;
}
-
/* Checks whether two pooled strings are equal. Since the pool never contains
* duplicate strings, this is a simple pointer comparison and thus much faster
* than strcmp(). null is considered equal to null but not equal to any string. */
EXPORT bool String::raw_equal (const char * str1, const char * str2)
{
- assert (! str1 || NODE_OF (str1)->magic == '@');
- assert (! str2 || NODE_OF (str2)->magic == '@');
-
return str1 == str2;
}
-#endif /* ! VALGRIND_FRIENDLY */
+#endif // ! VALGRIND_FRIENDLY
diff --git a/src/libaudcore/templates.h b/src/libaudcore/templates.h
index e78aea0..d14c3d8 100644
--- a/src/libaudcore/templates.h
+++ b/src/libaudcore/templates.h
@@ -98,6 +98,25 @@ inline T from_ptr (void * v)
return u.t;
}
+// Function wrappers (or "casts") for interaction with C-style APIs
+// ================================================================
+
+template<class T>
+void delete_obj (void * obj)
+ { (void) sizeof (T); delete (T *) obj; }
+
+template<class T>
+void delete_typed (T * obj)
+ { (void) sizeof (T); delete obj; }
+
+template<class T, void (* func) (void *)>
+void typed_func (T * obj)
+ { func (obj); }
+
+template<class T, void (T::* func) ()>
+static void obj_member (void * obj)
+ { (((T *) obj)->* func) (); }
+
// Wrapper class allowing enumerations to be used as array indexes;
// the enumeration must begin with zero and have a "count" constant
// ================================================================
diff --git a/src/libaudcore/tests/test.cc b/src/libaudcore/tests/test.cc
index dac2724..a7a6ba8 100644
--- a/src/libaudcore/tests/test.cc
+++ b/src/libaudcore/tests/test.cc
@@ -38,9 +38,19 @@ static void test_audio_conversion ()
{0x800000, 0x800001, 0x800002, -2, -1, 0, 1, 2, 0x7ffffe, 0x7fffff};
float f[10];
+ char packed[30];
int32_t out[10];
audio_from_int (in, FMT_S24_NE, f, 10);
+
+ for (int format = FMT_S24_3LE; format <= FMT_U24_3BE; format ++)
+ {
+ memset (packed, 0, sizeof packed);
+ audio_to_int (f, packed, format, 10);
+ memset (f, 0, sizeof f);
+ audio_from_int (packed, format, f, 10);
+ }
+
audio_to_int (f, out, FMT_S24_NE, 10);
assert (f[0] == -1.0f);
@@ -50,6 +60,67 @@ static void test_audio_conversion ()
assert (out[i] == (in[i] & 0xffffff));
}
+static void test_case_conversion ()
+{
+ const char in[] = "AÄaäEÊeêIÌiìOÕoõUÚuú";
+ const char low_ascii[] = "aÄaäeÊeêiÌiìoÕoõuÚuú";
+ const char low_utf8[] = "aäaäeêeêiìiìoõoõuúuú";
+ const char hi_ascii[] = "AÄAäEÊEêIÌIìOÕOõUÚUú";
+ const char hi_utf8[] = "AÄAÄEÊEÊIÌIÌOÕOÕUÚUÚ";
+
+ assert (! strcmp (low_ascii, str_tolower (in)));
+ assert (! strcmp (low_utf8, str_tolower_utf8 (in)));
+ assert (! strcmp (hi_ascii, str_toupper (in)));
+ assert (! strcmp (hi_utf8, str_toupper_utf8 (in)));
+
+ assert (! strcmp_safe ("abc", "abc"));
+ assert (! strcmp_safe ("abc", "abcdef", 3));
+ assert (strcmp_safe ("abc", "def") < 0);
+ assert (strcmp_safe ("def", "abc") > 0);
+ assert (! strcmp_safe (nullptr, nullptr));
+ assert (strcmp_safe (nullptr, "abc") < 0);
+ assert (strcmp_safe ("abc", nullptr) > 0);
+
+ assert (! strcmp_nocase ("abc", "ABC"));
+ assert (! strcmp_nocase ("ABC", "abcdef", 3));
+ assert (strcmp_nocase ("abc", "DEF") < 0);
+ assert (strcmp_nocase ("ABC", "def") < 0);
+ assert (strcmp_nocase ("def", "ABC") > 0);
+ assert (strcmp_nocase ("DEF", "abc") > 0);
+ assert (! strcmp_nocase (nullptr, nullptr));
+ assert (strcmp_nocase (nullptr, "abc") < 0);
+ assert (strcmp_nocase ("abc", nullptr) > 0);
+
+ assert (! strcmp_nocase (in, low_ascii));
+ assert (strcmp_nocase (in, low_utf8));
+ assert (! strcmp_nocase (in, hi_ascii));
+ assert (strcmp_nocase (in, hi_utf8));
+
+ assert (str_has_prefix_nocase (low_ascii, "AÄaä"));
+ assert (! str_has_prefix_nocase (low_utf8, "AÄaä"));
+ assert (str_has_prefix_nocase (hi_ascii, "AÄaä"));
+ assert (! str_has_prefix_nocase (hi_utf8, "AÄaä"));
+
+ assert (str_has_suffix_nocase (low_ascii, "UÚuú"));
+ assert (! str_has_suffix_nocase (low_utf8, "UÚuú"));
+ assert (str_has_suffix_nocase (hi_ascii, "UÚuú"));
+ assert (! str_has_suffix_nocase (hi_utf8, "UÚuú"));
+
+ assert (! str_has_suffix_nocase ("abc", "abcd"));
+
+ assert (! strcmp (strstr_nocase (low_ascii, "OÕoõ"), "oÕoõuÚuú"));
+ assert (strstr_nocase (low_utf8, "OÕoõ") == nullptr);
+ assert (! strcmp (strstr_nocase (hi_ascii, "OÕoõ"), "OÕOõUÚUú"));
+ assert (strstr_nocase (hi_utf8, "OÕoõ") == nullptr);
+
+ assert (! strcmp (strstr_nocase_utf8 (low_ascii, "OÕoõ"), "oÕoõuÚuú"));
+ assert (! strcmp (strstr_nocase_utf8 (low_utf8, "OÕoõ"), "oõoõuúuú"));
+ assert (strstr_nocase_utf8 (low_utf8, "OOoo") == nullptr);
+ assert (! strcmp (strstr_nocase_utf8 (hi_ascii, "OÕoõ"), "OÕOõUÚUú"));
+ assert (! strcmp (strstr_nocase_utf8 (hi_utf8, "OÕoõ"), "OÕOÕUÚUÚ"));
+ assert (strstr_nocase_utf8 (hi_utf8, "OOoo") == nullptr);
+}
+
static void test_numeric_conversion ()
{
static const char * in[] = {
@@ -386,6 +457,7 @@ static void test_ringbuf ()
int main ()
{
test_audio_conversion ();
+ test_case_conversion ();
test_numeric_conversion ();
test_filename_split ();
test_tuple_formats ();
diff --git a/src/libaudgui/Makefile b/src/libaudgui/Makefile
index 2ef4ed3..b082d02 100644
--- a/src/libaudgui/Makefile
+++ b/src/libaudgui/Makefile
@@ -1,6 +1,6 @@
SHARED_LIB = ${LIB_PREFIX}audgui${LIB_SUFFIX}
-LIB_MAJOR = 4
-LIB_MINOR = 1
+LIB_MAJOR = 5
+LIB_MINOR = 0
SRCS = about.cc \
confirm.cc \
diff --git a/src/libaudgui/confirm.cc b/src/libaudgui/confirm.cc
index bfcc176..1f9b235 100644
--- a/src/libaudgui/confirm.cc
+++ b/src/libaudgui/confirm.cc
@@ -20,78 +20,79 @@
#include <gtk/gtk.h>
#include <libaudcore/audstrings.h>
+#include <libaudcore/drct.h>
+#include <libaudcore/hook.h>
#include <libaudcore/i18n.h>
+#include <libaudcore/interface.h>
#include <libaudcore/playlist.h>
#include <libaudcore/runtime.h>
#include "libaudgui.h"
#include "libaudgui-gtk.h"
-static void no_confirm_cb (GtkToggleButton * toggle)
+static void show_question_dialog (const char * title, const char * text,
+ GtkWidget * widget, GtkWidget * action)
{
- aud_set_bool ("audgui", "no_confirm_playlist_delete", gtk_toggle_button_get_active (toggle));
+ auto cancel = audgui_button_new (_("_Cancel"), "process-stop", nullptr, nullptr);
+ auto dialog = audgui_dialog_new (GTK_MESSAGE_QUESTION, title, text, action, cancel);
+
+ audgui_dialog_add_widget (dialog, widget);
+ gtk_widget_show_all (dialog);
}
-static void confirm_delete_cb (void * data)
+static void no_confirm_cb (GtkToggleButton * toggle, void * config)
{
- int list = aud_playlist_by_unique_id (GPOINTER_TO_INT (data));
+ aud_set_bool ("audgui", (const char *) config, gtk_toggle_button_get_active (toggle));
+}
- if (list >= 0)
- aud_playlist_delete (list);
+static void show_confirm_dialog (const char * title, const char * text,
+ GtkWidget * action, const char * config)
+{
+ auto check = gtk_check_button_new_with_mnemonic (_("_Don’t ask again"));
+ g_signal_connect (check, "toggled", (GCallback) no_confirm_cb, (void *) config);
+
+ show_question_dialog (title, text, check, action);
}
-EXPORT void audgui_confirm_playlist_delete (int playlist)
+static void confirm_delete_cb (void * data)
+{
+ aud::from_ptr<Playlist> (data).remove_playlist ();
+}
+
+EXPORT void audgui_confirm_playlist_delete (Playlist playlist)
{
if (aud_get_bool ("audgui", "no_confirm_playlist_delete"))
{
- aud_playlist_delete (playlist);
+ playlist.remove_playlist ();
return;
}
- StringBuf message = str_printf (_("Do you want to permanently remove “%s”?"),
- (const char *) aud_playlist_get_title (playlist));
-
- int id = aud_playlist_get_unique_id (playlist);
- GtkWidget * button1 = audgui_button_new (_("_Remove"), "edit-delete",
- confirm_delete_cb, GINT_TO_POINTER (id));
- GtkWidget * button2 = audgui_button_new (_("_Cancel"), "process-stop", nullptr, nullptr);
+ StringBuf text = str_printf (_("Do you want to permanently remove “%s”?"),
+ (const char *) playlist.get_title ());
- GtkWidget * dialog = audgui_dialog_new (GTK_MESSAGE_QUESTION,
- _("Remove Playlist"), message, button1, button2);
+ auto button = audgui_button_new (_("_Remove"), "edit-delete",
+ confirm_delete_cb, aud::to_ptr (playlist));
- GtkWidget * check = gtk_check_button_new_with_mnemonic (_("_Don’t ask again"));
- g_signal_connect (check, "toggled", (GCallback) no_confirm_cb, nullptr);
- audgui_dialog_add_widget (dialog, check);
-
- gtk_widget_show_all (dialog);
+ show_confirm_dialog (_("Remove Playlist"), text, button, "no_confirm_playlist_delete");
}
static void rename_cb (void * entry)
{
- void * data = g_object_get_data ((GObject *) entry, "playlist-id");
- int list = aud_playlist_by_unique_id (GPOINTER_TO_INT (data));
+ void * data = g_object_get_data ((GObject *) entry, "playlist");
+ auto playlist = aud::from_ptr<Playlist> (data);
- if (list >= 0)
- aud_playlist_set_title (list, gtk_entry_get_text ((GtkEntry *) entry));
+ playlist.set_title (gtk_entry_get_text ((GtkEntry *) entry));
}
-EXPORT void audgui_show_playlist_rename (int playlist)
+EXPORT void audgui_show_playlist_rename (Playlist playlist)
{
- GtkWidget * entry = gtk_entry_new ();
- gtk_entry_set_text ((GtkEntry *) entry, aud_playlist_get_title (playlist));
+ auto entry = gtk_entry_new ();
+ gtk_entry_set_text ((GtkEntry *) entry, playlist.get_title ());
gtk_entry_set_activates_default ((GtkEntry *) entry, true);
- int id = aud_playlist_get_unique_id (playlist);
- g_object_set_data ((GObject *) entry, "playlist-id", GINT_TO_POINTER (id));
+ g_object_set_data ((GObject *) entry, "playlist", aud::to_ptr (playlist));
- GtkWidget * button1 = audgui_button_new (_("_Rename"), "insert-text", rename_cb, entry);
- GtkWidget * button2 = audgui_button_new (_("_Cancel"), "process-stop", nullptr, nullptr);
-
- GtkWidget * dialog = audgui_dialog_new (GTK_MESSAGE_QUESTION,
- _("Rename Playlist"), _("What would you like to call this playlist?"),
- button1, button2);
-
- audgui_dialog_add_widget (dialog, entry);
-
- gtk_widget_show_all (dialog);
+ auto text = _("What would you like to call this playlist?");
+ auto button = audgui_button_new (_("_Rename"), "insert-text", rename_cb, entry);
+ show_question_dialog (_("Rename Playlist"), text, entry, button);
}
diff --git a/src/libaudgui/file-opener.cc b/src/libaudgui/file-opener.cc
index efe339c..adaab4f 100644
--- a/src/libaudgui/file-opener.cc
+++ b/src/libaudgui/file-opener.cc
@@ -19,6 +19,7 @@
#include <gtk/gtk.h>
+#define AUD_GLIB_INTEGRATION
#include <libaudcore/drct.h>
#include <libaudcore/i18n.h>
#include <libaudcore/runtime.h>
@@ -58,12 +59,9 @@ static void open_cb (void * data)
static void destroy_cb (GtkWidget * chooser)
{
- char * path = gtk_file_chooser_get_current_folder ((GtkFileChooser *) chooser);
+ CharPtr path (gtk_file_chooser_get_current_folder ((GtkFileChooser *) chooser));
if (path)
- {
aud_set_str ("audgui", "filesel_path", path);
- g_free (path);
- }
}
static void toggled_cb (GtkToggleButton * toggle, void * option)
diff --git a/src/libaudgui/infopopup.cc b/src/libaudgui/infopopup.cc
index bc2edfd..680a0b1 100644
--- a/src/libaudgui/infopopup.cc
+++ b/src/libaudgui/infopopup.cc
@@ -21,6 +21,7 @@
#include <gtk/gtk.h>
#include <string.h>
+#define AUD_GLIB_INTEGRATION
#include <libaudcore/audstrings.h>
#include <libaudcore/drct.h>
#include <libaudcore/hook.h>
@@ -57,15 +58,14 @@ static GtkWidget * infopopup_queued;
static bool infopopup_display_image (const char * filename)
{
bool queued;
- GdkPixbuf * pb = audgui_pixbuf_request (filename, & queued);
+ AudguiPixbuf pb = audgui_pixbuf_request (filename, & queued);
if (! pb)
return ! queued;
- audgui_pixbuf_scale_within (& pb, audgui_get_dpi ());
- gtk_image_set_from_pixbuf ((GtkImage *) widgets.image, pb);
+ audgui_pixbuf_scale_within (pb, audgui_get_dpi ());
+ gtk_image_set_from_pixbuf ((GtkImage *) widgets.image, pb.get ());
gtk_widget_show (widgets.image);
- g_object_unref (pb);
return true;
}
@@ -144,9 +144,8 @@ static void infopopup_add_category (GtkWidget * grid, int position,
gtk_widget_modify_fg (* header, GTK_STATE_NORMAL, & gray);
gtk_widget_modify_fg (* label, GTK_STATE_NORMAL, & white);
- char * markup = g_markup_printf_escaped ("<span style=\"italic\">%s</span>", text);
+ CharPtr markup (g_markup_printf_escaped ("<span style=\"italic\">%s</span>", text));
gtk_label_set_markup ((GtkLabel *) * header, markup);
- g_free (markup);
gtk_table_attach ((GtkTable *) grid, * header, 0, 1, position, position + 1,
GTK_FILL, GTK_FILL, 0, 0);
@@ -313,10 +312,10 @@ static void infopopup_show (const char * filename, const Tuple & tuple)
infopopup_queued = infopopup;
}
-EXPORT void audgui_infopopup_show (int playlist, int entry)
+EXPORT void audgui_infopopup_show (Playlist playlist, int entry)
{
- String filename = aud_playlist_entry_get_filename (playlist, entry);
- Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry);
+ String filename = playlist.entry_filename (entry);
+ Tuple tuple = playlist.entry_tuple (entry);
if (filename && tuple.valid ())
infopopup_show (filename, tuple);
@@ -324,11 +323,11 @@ EXPORT void audgui_infopopup_show (int playlist, int entry)
EXPORT void audgui_infopopup_show_current ()
{
- int playlist = aud_playlist_get_playing ();
- if (playlist < 0)
- playlist = aud_playlist_get_active ();
+ auto playlist = Playlist::playing_playlist ();
+ if (playlist == Playlist ())
+ playlist = Playlist::active_playlist ();
- int position = aud_playlist_get_position (playlist);
+ int position = playlist.get_position ();
if (position < 0)
return;
diff --git a/src/libaudgui/infowin.cc b/src/libaudgui/infowin.cc
index 350455d..b36c1a6 100644
--- a/src/libaudgui/infowin.cc
+++ b/src/libaudgui/infowin.cc
@@ -70,7 +70,8 @@ static struct {
} widgets;
static GtkWidget * infowin;
-static int current_playlist_id, current_entry;
+static Playlist current_playlist;
+static int current_entry;
static String current_file;
static Tuple current_tuple;
static PluginHandle * current_decoder = nullptr;
@@ -222,15 +223,14 @@ static void infowin_update_tuple ()
static void infowin_next ()
{
- int list = aud_playlist_by_unique_id (current_playlist_id);
int entry = current_entry + 1;
- if (list >= 0 && entry < aud_playlist_entry_count (list))
+ if (entry < current_playlist.n_entries ())
{
- aud_playlist_select_all (list, false);
- aud_playlist_entry_set_selected (list, entry, true);
- aud_playlist_set_focus (list, entry);
- audgui_infowin_show (list, entry);
+ current_playlist.select_all (false);
+ current_playlist.select_entry (entry, true);
+ current_playlist.set_focus (entry);
+ audgui_infowin_show (current_playlist, entry);
}
else
audgui_infowin_hide ();
@@ -262,15 +262,12 @@ static void infowin_display_image (const char * filename)
if (! current_file || strcmp (filename, current_file))
return;
- GdkPixbuf * pb = audgui_pixbuf_request (filename);
+ AudguiPixbuf pb = audgui_pixbuf_request (filename);
if (! pb)
pb = audgui_pixbuf_fallback ();
if (pb)
- {
- audgui_scaled_image_set (widgets.image, pb);
- g_object_unref (pb);
- }
+ audgui_scaled_image_set (widgets.image, pb.get ());
}
static void infowin_destroyed ()
@@ -413,13 +410,13 @@ static void create_infowin ()
hook_associate ("art ready", (HookFunction) infowin_display_image, nullptr);
}
-static void infowin_show (int list, int entry, const String & filename,
+static void infowin_show (Playlist list, int entry, const String & filename,
const Tuple & tuple, PluginHandle * decoder, bool writable)
{
if (! infowin)
create_infowin ();
- current_playlist_id = aud_playlist_get_unique_id (list);
+ current_playlist = list;
current_entry = entry;
current_file = filename;
current_tuple = tuple.ref ();
@@ -467,16 +464,14 @@ static void infowin_show (int list, int entry, const String & filename,
audgui_show_unique_window (AUDGUI_INFO_WINDOW, infowin);
}
-EXPORT void audgui_infowin_show (int playlist, int entry)
+EXPORT void audgui_infowin_show (Playlist playlist, int entry)
{
- String filename = aud_playlist_entry_get_filename (playlist, entry);
+ String filename = playlist.entry_filename (entry);
g_return_if_fail (filename != nullptr);
String error;
- PluginHandle * decoder = aud_playlist_entry_get_decoder (playlist, entry,
- Playlist::Wait, & error);
- Tuple tuple = decoder ? aud_playlist_entry_get_tuple (playlist, entry,
- Playlist::Wait, & error) : Tuple ();
+ PluginHandle * decoder = playlist.entry_decoder (entry, Playlist::Wait, & error);
+ Tuple tuple = decoder ? playlist.entry_tuple (entry, Playlist::Wait, & error) : Tuple ();
if (decoder && tuple.valid () && ! aud_custom_infowin (filename, decoder))
{
@@ -497,15 +492,12 @@ EXPORT void audgui_infowin_show (int playlist, int entry)
EXPORT void audgui_infowin_show_current ()
{
- int playlist = aud_playlist_get_playing ();
- int position;
-
- if (playlist == -1)
- playlist = aud_playlist_get_active ();
-
- position = aud_playlist_get_position (playlist);
+ auto playlist = Playlist::playing_playlist ();
+ if (playlist == Playlist ())
+ playlist = Playlist::active_playlist ();
- if (position == -1)
+ int position = playlist.get_position ();
+ if (position < 0)
return;
audgui_infowin_show (playlist, position);
diff --git a/src/libaudgui/init.cc b/src/libaudgui/init.cc
index b6192fd..aa5b93c 100644
--- a/src/libaudgui/init.cc
+++ b/src/libaudgui/init.cc
@@ -35,6 +35,7 @@ static const char * const audgui_defaults[] = {
"close_dialog_add", "FALSE",
"close_dialog_open", "TRUE",
"close_jtf_dialog", "TRUE",
+ "record", "FALSE",
"remember_jtf_entry", "TRUE",
nullptr
};
@@ -133,7 +134,7 @@ static void playlist_set_playing_cb (void *, void *)
static void playlist_position_cb (void * list, void *)
{
- if (aud::from_ptr<int> (list) == aud_playlist_get_playing ())
+ if (aud::from_ptr<Playlist> (list) == Playlist::playing_playlist ())
audgui_pixbuf_uncache ();
}
diff --git a/src/libaudgui/jump-to-track-cache.cc b/src/libaudgui/jump-to-track-cache.cc
index 7dcf242..754cce1 100644
--- a/src/libaudgui/jump-to-track-cache.cc
+++ b/src/libaudgui/jump-to-track-cache.cc
@@ -118,8 +118,8 @@ const KeywordMatches * JumpToTrackCache::search_within
*/
void JumpToTrackCache::init ()
{
- int playlist = aud_playlist_get_active ();
- int entries = aud_playlist_entry_count (playlist);
+ auto playlist = Playlist::active_playlist ();
+ int entries = playlist.n_entries ();
// the empty string will match all playlist entries
KeywordMatches & k = * add (String (""), KeywordMatches ());
@@ -130,9 +130,9 @@ void JumpToTrackCache::init ()
{
KeywordMatch & item = k[entry];
item.entry = entry;
- item.path = String (uri_to_display (aud_playlist_entry_get_filename (playlist, entry)));
+ item.path = String (uri_to_display (playlist.entry_filename (entry)));
- Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry, Playlist::NoWait);
+ Tuple tuple = playlist.entry_tuple (entry, Playlist::NoWait);
item.title = tuple.get_str (Tuple::Title);
item.artist = tuple.get_str (Tuple::Artist);
item.album = tuple.get_str (Tuple::Album);
diff --git a/src/libaudgui/jump-to-track.cc b/src/libaudgui/jump-to-track.cc
index a681163..b6db4f4 100644
--- a/src/libaudgui/jump-to-track.cc
+++ b/src/libaudgui/jump-to-track.cc
@@ -78,9 +78,9 @@ static void do_jump (void *)
if (entry < 0)
return;
- int playlist = aud_playlist_get_active ();
- aud_playlist_set_position (playlist, entry);
- aud_playlist_play (playlist);
+ auto playlist = Playlist::active_playlist ();
+ playlist.set_position (entry);
+ playlist.start_playback ();
if (aud_get_bool ("audgui", "close_jtf_dialog"))
audgui_jump_to_track_hide ();
@@ -97,7 +97,7 @@ static void update_queue_button (int entry)
}
else
{
- if (aud_playlist_queue_find_entry (aud_playlist_get_active (), entry) != -1)
+ if (Playlist::active_playlist ().queue_find_entry (entry) >= 0)
gtk_button_set_label ((GtkButton *) queue_button, _("Un_queue"));
else
gtk_button_set_label ((GtkButton *) queue_button, _("_Queue"));
@@ -108,16 +108,16 @@ static void update_queue_button (int entry)
static void do_queue (void *)
{
- int playlist = aud_playlist_get_active ();
+ auto playlist = Playlist::active_playlist ();
int entry = get_selected_entry ();
if (entry < 0)
return;
- int queued = aud_playlist_queue_find_entry (playlist, entry);
+ int queued = playlist.queue_find_entry (entry);
if (queued >= 0)
- aud_playlist_queue_delete (playlist, queued, 1);
+ playlist.queue_remove (queued);
else
- aud_playlist_queue_insert (playlist, -1, entry);
+ playlist.queue_insert (-1, entry);
update_queue_button (entry);
}
@@ -211,7 +211,7 @@ static void list_get_value (void * user, int row, int column, GValue * value)
g_return_if_fail (column >= 0 && column < 2);
g_return_if_fail (row >= 0 && row < search_matches->len ());
- int playlist = aud_playlist_get_active ();
+ auto playlist = Playlist::active_playlist ();
int entry = (* search_matches)[row].entry;
switch (column)
@@ -220,7 +220,7 @@ static void list_get_value (void * user, int row, int column, GValue * value)
g_value_set_int (value, 1 + entry);
break;
case 1:
- Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry, Playlist::NoWait);
+ Tuple tuple = playlist.entry_tuple (entry, Playlist::NoWait);
g_value_set_string (value, tuple.get_str (Tuple::FormattedTitle));
break;
}
diff --git a/src/libaudgui/libaudgui-gtk.h b/src/libaudgui/libaudgui-gtk.h
index a789a6b..f98ba27 100644
--- a/src/libaudgui/libaudgui-gtk.h
+++ b/src/libaudgui/libaudgui-gtk.h
@@ -32,12 +32,27 @@ struct PreferencesWidget;
typedef void (* AudguiCallback) (void * data);
+class AudguiPixbuf : public SmartPtr<GdkPixbuf, aud::typed_func<GdkPixbuf, g_object_unref>>
+{
+public:
+ AudguiPixbuf () : SmartPtr () {}
+ explicit AudguiPixbuf (GdkPixbuf * ptr) : SmartPtr (ptr) {}
+
+ int width ()
+ { return gdk_pixbuf_get_width (get ()); }
+ int height ()
+ { return gdk_pixbuf_get_height (get ()); }
+
+ AudguiPixbuf ref ()
+ { return AudguiPixbuf (get () ? (GdkPixbuf *) g_object_ref (get ()) : nullptr); }
+};
+
/* pixbufs.c */
-GdkPixbuf * audgui_pixbuf_from_data (const void * data, int64_t size);
-GdkPixbuf * audgui_pixbuf_fallback ();
-void audgui_pixbuf_scale_within (GdkPixbuf * * pixbuf, int size);
-GdkPixbuf * audgui_pixbuf_request (const char * filename, bool * queued = nullptr);
-GdkPixbuf * audgui_pixbuf_request_current (bool * queued = nullptr);
+AudguiPixbuf audgui_pixbuf_from_data (const void * data, int64_t size);
+AudguiPixbuf audgui_pixbuf_fallback ();
+void audgui_pixbuf_scale_within (AudguiPixbuf & pixbuf, int size);
+AudguiPixbuf audgui_pixbuf_request (const char * filename, bool * queued = nullptr);
+AudguiPixbuf audgui_pixbuf_request_current (bool * queued = nullptr);
/* plugin-menu.c */
GtkWidget * audgui_get_plugin_menu (AudMenuID id);
diff --git a/src/libaudgui/libaudgui.h b/src/libaudgui/libaudgui.h
index 3cff301..020c611 100644
--- a/src/libaudgui/libaudgui.h
+++ b/src/libaudgui/libaudgui.h
@@ -25,6 +25,7 @@
enum class AudMenuID;
enum class PluginType;
+class Playlist;
class PluginHandle;
struct EqualizerPreset;
@@ -33,8 +34,8 @@ void audgui_show_about_window ();
void audgui_hide_about_window ();
/* confirm.c */
-void audgui_confirm_playlist_delete (int playlist);
-void audgui_show_playlist_rename (int playlist);
+void audgui_confirm_playlist_delete (Playlist playlist);
+void audgui_show_playlist_rename (Playlist playlist);
/* eq-preset.c */
void audgui_show_eq_preset_window ();
@@ -45,7 +46,7 @@ void audgui_show_equalizer_window ();
void audgui_hide_equalizer_window ();
/* infopopup.c */
-void audgui_infopopup_show (int playlist, int entry);
+void audgui_infopopup_show (Playlist playlist, int entry);
void audgui_infopopup_show_current ();
void audgui_infopopup_hide ();
@@ -54,7 +55,7 @@ void audgui_run_filebrowser (bool open);
void audgui_hide_filebrowser ();
/* infowin.c */
-void audgui_infowin_show (int playlist, int entry);
+void audgui_infowin_show (Playlist playlist, int entry);
void audgui_infowin_show_current ();
void audgui_infowin_hide ();
@@ -91,8 +92,8 @@ void audgui_queue_manager_show ();
/* urilist.c */
void audgui_urilist_open (const char * list);
-void audgui_urilist_insert (int playlist, int position, const char * list);
-Index<char> audgui_urilist_create_from_selected (int playlist);
+void audgui_urilist_insert (Playlist playlist, int position, const char * list);
+Index<char> audgui_urilist_create_from_selected (Playlist playlist);
/* url-opener.c */
void audgui_show_add_url_window (bool open);
diff --git a/src/libaudgui/list.cc b/src/libaudgui/list.cc
index 906922b..7f68061 100644
--- a/src/libaudgui/list.cc
+++ b/src/libaudgui/list.cc
@@ -656,7 +656,7 @@ EXPORT void * audgui_list_get_user (GtkWidget * list)
}
EXPORT void audgui_list_add_column (GtkWidget * list, const char * title,
- int column, GType type, int width)
+ int column, GType type, int width, bool use_markup)
{
ListModel * model = (ListModel *) gtk_tree_view_get_model
((GtkTreeView *) list);
@@ -667,9 +667,13 @@ EXPORT void audgui_list_add_column (GtkWidget * list, const char * title,
(type));
GtkCellRenderer * renderer = gtk_cell_renderer_text_new ();
- GtkTreeViewColumn * tree_column = gtk_tree_view_column_new_with_attributes
- (title, renderer, "text", RESERVED_COLUMNS + column, "weight",
- HIGHLIGHT_COLUMN, nullptr);
+
+ GtkTreeViewColumn * tree_column = use_markup ?
+ gtk_tree_view_column_new_with_attributes
+ (title, renderer, "markup", RESERVED_COLUMNS + column, nullptr) :
+ gtk_tree_view_column_new_with_attributes
+ (title, renderer, "text", RESERVED_COLUMNS + column, "weight", HIGHLIGHT_COLUMN, nullptr);
+
gtk_tree_view_column_set_sizing (tree_column, GTK_TREE_VIEW_COLUMN_FIXED);
int pad1, pad2, pad3;
diff --git a/src/libaudgui/list.h b/src/libaudgui/list.h
index df9d52d..7226cd6 100644
--- a/src/libaudgui/list.h
+++ b/src/libaudgui/list.h
@@ -62,7 +62,7 @@ GtkWidget * audgui_list_new_real (const AudguiListCallbacks * cbs, int cbs_size,
void * audgui_list_get_user (GtkWidget * list);
void audgui_list_add_column (GtkWidget * list, const char * title,
- int column, GType type, int width);
+ int column, GType type, int width, bool use_markup = false);
int audgui_list_row_count (GtkWidget * list);
void audgui_list_insert_rows (GtkWidget * list, int at, int rows);
diff --git a/src/libaudgui/pixbufs.cc b/src/libaudgui/pixbufs.cc
index 259469b..bb12c93 100644
--- a/src/libaudgui/pixbufs.cc
+++ b/src/libaudgui/pixbufs.cc
@@ -27,35 +27,28 @@
#include "internal.h"
#include "libaudgui-gtk.h"
-static GdkPixbuf * current_pixbuf;
+static AudguiPixbuf current_pixbuf;
-EXPORT GdkPixbuf * audgui_pixbuf_fallback ()
+EXPORT AudguiPixbuf audgui_pixbuf_fallback ()
{
- static GdkPixbuf * fallback = nullptr;
+ static AudguiPixbuf fallback;
if (! fallback)
- fallback = gdk_pixbuf_new_from_file (filename_build
- ({aud_get_path (AudPath::DataDir), "images", "album.png"}), nullptr);
+ fallback.capture (gdk_pixbuf_new_from_file (filename_build
+ ({aud_get_path (AudPath::DataDir), "images", "album.png"}), nullptr));
- if (fallback)
- g_object_ref ((GObject *) fallback);
-
- return fallback;
+ return fallback.ref ();
}
void audgui_pixbuf_uncache ()
{
- if (current_pixbuf)
- {
- g_object_unref ((GObject *) current_pixbuf);
- current_pixbuf = nullptr;
- }
+ current_pixbuf.clear ();
}
-EXPORT void audgui_pixbuf_scale_within (GdkPixbuf * * pixbuf, int size)
+EXPORT void audgui_pixbuf_scale_within (AudguiPixbuf & pixbuf, int size)
{
- int width = gdk_pixbuf_get_width (* pixbuf);
- int height = gdk_pixbuf_get_height (* pixbuf);
+ int width = pixbuf.width ();
+ int height = pixbuf.height ();
if (width <= size && height <= size)
return;
@@ -76,24 +69,18 @@ EXPORT void audgui_pixbuf_scale_within (GdkPixbuf * * pixbuf, int size)
if (height < 1)
height = 1;
- GdkPixbuf * pixbuf2 = gdk_pixbuf_scale_simple (* pixbuf, width, height, GDK_INTERP_BILINEAR);
- g_object_unref (* pixbuf);
- * pixbuf = pixbuf2;
+ pixbuf.capture (gdk_pixbuf_scale_simple (pixbuf.get (), width, height, GDK_INTERP_BILINEAR));
}
-EXPORT GdkPixbuf * audgui_pixbuf_request (const char * filename, bool * queued)
+EXPORT AudguiPixbuf audgui_pixbuf_request (const char * filename, bool * queued)
{
- const Index<char> * data = aud_art_request_data (filename, queued);
- if (! data)
- return nullptr;
+ AudArtPtr art = aud_art_request (filename, AUD_ART_DATA, queued);
- GdkPixbuf * p = audgui_pixbuf_from_data (data->begin (), data->len ());
-
- aud_art_unref (filename);
- return p;
+ auto data = art.data ();
+ return data ? audgui_pixbuf_from_data (data->begin (), data->len ()) : AudguiPixbuf ();
}
-EXPORT GdkPixbuf * audgui_pixbuf_request_current (bool * queued)
+EXPORT AudguiPixbuf audgui_pixbuf_request_current (bool * queued)
{
if (queued)
* queued = false;
@@ -105,13 +92,10 @@ EXPORT GdkPixbuf * audgui_pixbuf_request_current (bool * queued)
current_pixbuf = audgui_pixbuf_request (filename, queued);
}
- if (current_pixbuf)
- g_object_ref ((GObject *) current_pixbuf);
-
- return current_pixbuf;
+ return current_pixbuf.ref ();
}
-EXPORT GdkPixbuf * audgui_pixbuf_from_data (const void * data, int64_t size)
+EXPORT AudguiPixbuf audgui_pixbuf_from_data (const void * data, int64_t size)
{
GdkPixbuf * pixbuf = nullptr;
GdkPixbufLoader * loader = gdk_pixbuf_loader_new ();
@@ -130,5 +114,5 @@ EXPORT GdkPixbuf * audgui_pixbuf_from_data (const void * data, int64_t size)
}
g_object_unref (loader);
- return pixbuf;
+ return AudguiPixbuf (pixbuf);
}
diff --git a/src/libaudgui/playlists.cc b/src/libaudgui/playlists.cc
index 4922fba..6f00008 100644
--- a/src/libaudgui/playlists.cc
+++ b/src/libaudgui/playlists.cc
@@ -20,6 +20,7 @@
#include <string.h>
#include <gtk/gtk.h>
+#define AUD_GLIB_INTEGRATION
#include <libaudcore/audstrings.h>
#include <libaudcore/i18n.h>
#include <libaudcore/interface.h>
@@ -34,13 +35,13 @@
struct ImportExportJob {
bool save;
- int list_id;
- String filename;
+ Playlist list;
+ CharPtr filename;
GtkWidget * selector = nullptr;
GtkWidget * confirm = nullptr;
- ImportExportJob (bool save, int list_id) :
- save (save), list_id (list_id) {}
+ ImportExportJob (bool save, Playlist list) :
+ save (save), list (list) {}
};
/* "destroy" callback; do not call directly */
@@ -48,13 +49,10 @@ static void cleanup_job (void * data)
{
ImportExportJob * job = (ImportExportJob *) data;
- char * folder = gtk_file_chooser_get_current_folder_uri ((GtkFileChooser *) job->selector);
-
+ CharPtr folder (gtk_file_chooser_get_current_folder_uri ((GtkFileChooser *) job->selector));
if (folder)
aud_set_str ("audgui", "playlist_path", folder);
- g_free (folder);
-
if (job->confirm)
gtk_widget_destroy (job->confirm);
@@ -64,22 +62,21 @@ static void cleanup_job (void * data)
static void finish_job (void * data)
{
ImportExportJob * job = (ImportExportJob *) data;
- int list = aud_playlist_by_unique_id (job->list_id);
Playlist::GetMode mode = Playlist::Wait;
if (aud_get_bool (nullptr, "metadata_on_play"))
mode = Playlist::NoWait;
- if (list >= 0)
+ if (job->list.exists ())
{
- aud_playlist_set_filename (list, job->filename);
+ job->list.set_filename (job->filename);
if (job->save)
- aud_playlist_save (list, job->filename, mode);
+ job->list.save_to_file (job->filename, mode);
else
{
- aud_playlist_entry_delete (list, 0, aud_playlist_entry_count (list));
- aud_playlist_entry_insert (list, 0, job->filename, Tuple (), false);
+ job->list.remove_all_entries ();
+ job->list.insert_entry (0, job->filename, Tuple (), false);
}
}
@@ -107,13 +104,10 @@ static void check_overwrite (void * data)
{
ImportExportJob * job = (ImportExportJob *) data;
- char * filename = gtk_file_chooser_get_uri ((GtkFileChooser *) job->selector);
- if (! filename)
+ job->filename = CharPtr (gtk_file_chooser_get_uri ((GtkFileChooser *) job->selector));
+ if (! job->filename)
return;
- job->filename = String (filename);
- g_free (filename);
-
if (job->save && ! strchr (job->filename, '.'))
{
const char * default_ext = nullptr;
@@ -129,7 +123,7 @@ static void check_overwrite (void * data)
return;
}
- job->filename = String (str_concat ({job->filename, ".", default_ext}));
+ job->filename.capture (g_strconcat (job->filename, ".", default_ext, nullptr));
}
if (job->save && VFSFile::test_file (job->filename, VFS_EXISTS))
@@ -147,7 +141,7 @@ static void set_format_filters (GtkWidget * selector)
gtk_file_filter_add_pattern (filter, "*");
gtk_file_chooser_add_filter ((GtkFileChooser *) selector, filter);
- for (auto & format : aud_playlist_save_formats ())
+ for (auto & format : Playlist::save_formats ())
{
filter = gtk_file_filter_new ();
gtk_file_filter_set_name (filter, format.name);
@@ -211,12 +205,12 @@ static void create_selector (ImportExportJob * job, const char * filename, const
static GtkWidget * start_job (bool save)
{
- int list = aud_playlist_get_active ();
+ auto list = Playlist::active_playlist ();
- String filename = aud_playlist_get_filename (list);
+ String filename = list.get_filename ();
String folder = aud_get_str ("audgui", "playlist_path");
- ImportExportJob * job = new ImportExportJob (save, aud_playlist_get_unique_id (list));
+ ImportExportJob * job = new ImportExportJob (save, list);
create_selector (job, filename, folder[0] ? (const char *) folder : nullptr);
return job->selector;
diff --git a/src/libaudgui/prefs-window.cc b/src/libaudgui/prefs-window.cc
index 987bbc6..e126745 100644
--- a/src/libaudgui/prefs-window.cc
+++ b/src/libaudgui/prefs-window.cc
@@ -1,5 +1,5 @@
/*
- * prefs-window.c
+ * prefs-window.cc
* Copyright 2006-2014 William Pitcock, Tomasz Moń, Michael Färber, and
* John Lindgren
*
@@ -95,18 +95,19 @@ static const PluginCategory plugin_categories[] = {
};
static const TitleFieldTag title_field_tags[] = {
- { N_("Artist") , "${artist}" },
- { N_("Album") , "${album}" },
- { N_("Title") , "${title}" },
+ { N_("Artist") , "${artist}" },
+ { N_("Album") , "${album}" },
+ { N_("Album artist"), "${album-artist}" },
+ { N_("Title") , "${title}" },
{ N_("Track number"), "${track-number}" },
- { N_("Genre") , "${genre}" },
+ { N_("Genre") , "${genre}" },
{ N_("File name") , "${file-name}" },
{ N_("File path") , "${file-path}" },
- { N_("Date") , "${date}" },
- { N_("Year") , "${year}" },
- { N_("Comment") , "${comment}" },
- { N_("Codec") , "${codec}" },
- { N_("Quality") , "${quality}" }
+ { N_("Date") , "${date}" },
+ { N_("Year") , "${year}" },
+ { N_("Comment") , "${comment}" },
+ { N_("Codec") , "${codec}" },
+ { N_("Quality") , "${quality}" }
};
static const ComboItem chardet_detector_presets[] = {
@@ -139,6 +140,12 @@ static const ComboItem record_elements[] = {
ComboItem (N_("After applying equalization"), (int) OutputStream::AfterEqualizer)
};
+static const ComboItem replaygainmode_elements[] = {
+ ComboItem (N_("Track"), (int) ReplayGainMode::Track),
+ ComboItem (N_("Album"), (int) ReplayGainMode::Album),
+ ComboItem (N_("Based on shuffle"), (int) ReplayGainMode::Automatic)
+};
+
static Index<ComboItem> iface_combo_elements;
static int iface_combo_selected;
static GtkWidget * iface_prefs_box;
@@ -148,10 +155,10 @@ static void iface_combo_changed ();
static void * iface_create_prefs_box ();
static const PreferencesWidget appearance_page_widgets[] = {
- WidgetLabel (N_("<b>Interface Settings</b>")),
- WidgetCombo (N_("Interface plugin:"),
+ WidgetCombo (N_("Interface:"),
WidgetInt (iface_combo_selected, iface_combo_changed),
{0, iface_combo_fill}),
+ WidgetSeparator ({true}),
WidgetCustomGTK (iface_create_prefs_box)
};
@@ -219,8 +226,9 @@ static const PreferencesWidget audio_page_widgets[] = {
WidgetLabel (N_("<b>ReplayGain</b>")),
WidgetCheck (N_("Enable ReplayGain"),
WidgetBool (0, "enable_replay_gain")),
- WidgetCheck (N_("Album mode"),
- WidgetBool (0, "replay_gain_album"),
+ WidgetCombo (N_("Mode:"),
+ WidgetInt (0, "replay_gain_mode"),
+ {{replaygainmode_elements}},
WIDGET_CHILD),
WidgetCheck (N_("Prevent clipping (recommended)"),
WidgetBool (0, "enable_clipping_prevention"),
@@ -295,7 +303,7 @@ static const PreferencesWidget playlist_page_widgets[] = {
WidgetLabel (N_("<b>Compatibility</b>")),
WidgetCheck (N_("Interpret \\ (backward slash) as a folder delimiter"),
WidgetBool (0, "convert_backslash")),
- WidgetTable ({{chardet_elements}}),
+ WidgetTable ({{chardet_elements}})
};
static const PreferencesWidget song_info_page_widgets[] = {
@@ -475,13 +483,10 @@ static void fill_category_list (GtkTreeView * treeview, GtkNotebook * notebook)
gettext (category.name), -1);
StringBuf path = filename_build ({data_dir, "images", category.icon_path});
- GdkPixbuf * img = gdk_pixbuf_new_from_file (path, nullptr);
+ AudguiPixbuf img (gdk_pixbuf_new_from_file (path, nullptr));
if (img)
- {
- gtk_list_store_set (store, & iter, CATEGORY_VIEW_COL_ICON, img, -1);
- g_object_unref (img);
- }
+ gtk_list_store_set (store, & iter, CATEGORY_VIEW_COL_ICON, img.get (), -1);
}
g_object_unref (store);
@@ -733,7 +738,7 @@ static void record_update (void * = nullptr, void * = nullptr)
gtk_widget_set_sensitive (record_checkbox, true);
gtk_button_set_label ((GtkButton *) record_checkbox,
- str_printf (_("Record audio stream using %s"), aud_plugin_get_name (p)));
+ str_printf (_("Enable audio stream recording with %s"), aud_plugin_get_name (p)));
gtk_toggle_button_set_active ((GtkToggleButton *) record_checkbox, enabled);
gtk_widget_set_sensitive (record_config_button, enabled && aud_plugin_has_configure (p));
gtk_widget_set_sensitive (record_about_button, enabled && aud_plugin_has_about (p));
diff --git a/src/libaudgui/preset-browser.cc b/src/libaudgui/preset-browser.cc
index 9144086..5b5c78d 100644
--- a/src/libaudgui/preset-browser.cc
+++ b/src/libaudgui/preset-browser.cc
@@ -17,6 +17,7 @@
* the use of this software.
*/
+#define AUD_GLIB_INTEGRATION
#include "internal.h"
#include "libaudgui.h"
#include "preset-browser.h"
@@ -35,9 +36,8 @@ static void browser_response (GtkWidget * dialog, int response, void * data)
{
if (response == GTK_RESPONSE_ACCEPT)
{
- char * filename = gtk_file_chooser_get_uri ((GtkFileChooser *) dialog);
+ CharPtr filename (gtk_file_chooser_get_uri ((GtkFileChooser *) dialog));
((FilebrowserCallback) data) (filename);
- g_free (filename);
}
gtk_widget_destroy (dialog);
diff --git a/src/libaudgui/queue-manager.cc b/src/libaudgui/queue-manager.cc
index 2b5e6d0..67a6ef6 100644
--- a/src/libaudgui/queue-manager.cc
+++ b/src/libaudgui/queue-manager.cc
@@ -36,8 +36,8 @@ enum {
static void get_value (void * user, int row, int column, GValue * value)
{
- int list = aud_playlist_get_active ();
- int entry = aud_playlist_queue_get_entry (list, row);
+ auto list = Playlist::active_playlist ();
+ int entry = list.queue_get_entry (row);
switch (column)
{
@@ -45,7 +45,7 @@ static void get_value (void * user, int row, int column, GValue * value)
g_value_set_int (value, 1 + entry);
break;
case COLUMN_TITLE:
- Tuple tuple = aud_playlist_entry_get_tuple (list, entry, Playlist::NoWait);
+ Tuple tuple = list.entry_tuple (entry, Playlist::NoWait);
g_value_set_string (value, tuple.get_str (Tuple::FormattedTitle));
break;
}
@@ -53,36 +53,36 @@ static void get_value (void * user, int row, int column, GValue * value)
static bool get_selected (void * user, int row)
{
- int list = aud_playlist_get_active ();
- return aud_playlist_entry_get_selected (list, aud_playlist_queue_get_entry (list, row));
+ auto list = Playlist::active_playlist ();
+ return list.entry_selected (list.queue_get_entry (row));
}
static void set_selected (void * user, int row, bool selected)
{
- int list = aud_playlist_get_active ();
- aud_playlist_entry_set_selected (list, aud_playlist_queue_get_entry (list, row), selected);
+ auto list = Playlist::active_playlist ();
+ list.select_entry (list.queue_get_entry (row), selected);
}
static void select_all (void * user, bool selected)
{
- int list = aud_playlist_get_active ();
- int count = aud_playlist_queue_count (list);
+ auto list = Playlist::active_playlist ();
+ int count = list.n_queued ();
for (int i = 0; i < count; i ++)
- aud_playlist_entry_set_selected (list, aud_playlist_queue_get_entry (list, i), selected);
+ list.select_entry (list.queue_get_entry (i), selected);
}
static void shift_rows (void * user, int row, int before)
{
Index<int> shift;
- int list = aud_playlist_get_active ();
- int count = aud_playlist_queue_count (list);
+ auto list = Playlist::active_playlist ();
+ int count = list.n_queued ();
for (int i = 0; i < count; i ++)
{
- int entry = aud_playlist_queue_get_entry (list, i);
+ int entry = list.queue_get_entry (i);
- if (aud_playlist_entry_get_selected (list, entry))
+ if (list.entry_selected (entry))
{
shift.append (entry);
@@ -91,10 +91,10 @@ static void shift_rows (void * user, int row, int before)
}
}
- aud_playlist_queue_delete_selected (list);
+ list.queue_remove_selected ();
for (int i = 0; i < shift.len (); i ++)
- aud_playlist_queue_insert (list, before + i, shift[i]);
+ list.queue_insert (before + i, shift[i]);
}
static const AudguiListCallbacks callbacks = {
@@ -109,17 +109,17 @@ static const AudguiListCallbacks callbacks = {
static void remove_selected (void *)
{
- int list = aud_playlist_get_active ();
- int count = aud_playlist_queue_count (list);
+ auto list = Playlist::active_playlist ();
+ int count = list.n_queued ();
for (int i = 0; i < count; )
{
- int entry = aud_playlist_queue_get_entry (list, i);
+ int entry = list.queue_get_entry (i);
- if (aud_playlist_entry_get_selected (list, entry))
+ if (list.entry_selected (entry))
{
- aud_playlist_queue_delete (list, i, 1);
- aud_playlist_entry_set_selected (list, entry, false);
+ list.queue_remove (i);
+ list.select_entry (entry, false);
count --;
}
else
@@ -132,7 +132,7 @@ static void update_hook (void * data, void * user)
GtkWidget * qm_list = (GtkWidget *) user;
int oldrows = audgui_list_row_count (qm_list);
- int newrows = aud_playlist_queue_count (aud_playlist_get_active ());
+ int newrows = Playlist::active_playlist ().n_queued ();
int focus = audgui_list_get_focus (qm_list);
audgui_list_update_rows (qm_list, 0, aud::min (oldrows, newrows));
@@ -183,7 +183,7 @@ static GtkWidget * create_queue_manager ()
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_box_pack_start ((GtkBox *) vbox, scrolled, true, true, 0);
- int count = aud_playlist_queue_count (aud_playlist_get_active ());
+ int count = Playlist::active_playlist ().n_queued ();
GtkWidget * qm_list = audgui_list_new (& callbacks, nullptr, count);
gtk_tree_view_set_headers_visible ((GtkTreeView *) qm_list, false);
audgui_list_add_column (qm_list, nullptr, 0, G_TYPE_INT, 7);
diff --git a/src/libaudgui/scaled-image.cc b/src/libaudgui/scaled-image.cc
index 41c804a..cc079c9 100644
--- a/src/libaudgui/scaled-image.cc
+++ b/src/libaudgui/scaled-image.cc
@@ -33,12 +33,12 @@ static GdkPixbuf * get_scaled (GtkWidget * widget, int maxwidth, int maxheight)
{
if (width * maxheight > height * maxwidth)
{
- height = height * maxwidth / width;
+ height = aud::rescale (height, width, maxwidth);
width = maxwidth;
}
else
{
- width = width * maxheight / height;
+ width = aud::rescale (width, height, maxheight);
height = maxheight;
}
}
@@ -49,12 +49,10 @@ static GdkPixbuf * get_scaled (GtkWidget * widget, int maxwidth, int maxheight)
{
if (gdk_pixbuf_get_width (scaled) == width && gdk_pixbuf_get_height (scaled) == height)
return scaled;
-
- g_object_unref (scaled);
}
scaled = gdk_pixbuf_scale_simple (unscaled, width, height, GDK_INTERP_BILINEAR);
- g_object_set_data ((GObject *) widget, "pixbuf-scaled", scaled);
+ g_object_set_data_full ((GObject *) widget, "pixbuf-scaled", scaled, g_object_unref);
return scaled;
}
@@ -82,33 +80,20 @@ static int expose_cb (GtkWidget * widget, GdkEventExpose * event)
EXPORT void audgui_scaled_image_set (GtkWidget * widget, GdkPixbuf * pixbuf)
{
- GdkPixbuf * old;
-
- if ((old = (GdkPixbuf *) g_object_get_data ((GObject *) widget, "pixbuf-unscaled")))
- g_object_unref (old);
- if ((old = (GdkPixbuf *) g_object_get_data ((GObject *) widget, "pixbuf-scaled")))
- g_object_unref (old);
-
if (pixbuf)
g_object_ref (pixbuf);
- g_object_set_data ((GObject *) widget, "pixbuf-unscaled", pixbuf);
- g_object_set_data ((GObject *) widget, "pixbuf-scaled", nullptr);
+ g_object_set_data_full ((GObject *) widget, "pixbuf-unscaled", pixbuf, g_object_unref);
+ g_object_set_data_full ((GObject *) widget, "pixbuf-scaled", nullptr, g_object_unref);
gtk_widget_queue_draw (widget);
}
-static void destroy_cb (GtkWidget * widget)
-{
- audgui_scaled_image_set (widget, nullptr);
-}
-
EXPORT GtkWidget * audgui_scaled_image_new (GdkPixbuf * pixbuf)
{
GtkWidget * widget = gtk_drawing_area_new ();
g_signal_connect (widget, "expose-event", (GCallback) expose_cb, nullptr);
- g_signal_connect (widget, "destroy", (GCallback) destroy_cb, nullptr);
audgui_scaled_image_set (widget, pixbuf);
diff --git a/src/libaudgui/urilist.cc b/src/libaudgui/urilist.cc
index a0809f4..6874f4f 100644
--- a/src/libaudgui/urilist.cc
+++ b/src/libaudgui/urilist.cc
@@ -60,26 +60,26 @@ EXPORT void audgui_urilist_open (const char * list)
aud_drct_pl_open_list (urilist_to_index (list));
}
-EXPORT void audgui_urilist_insert (int playlist, int at, const char * list)
+EXPORT void audgui_urilist_insert (Playlist playlist, int at, const char * list)
{
- aud_playlist_entry_insert_batch (playlist, at, urilist_to_index (list), false);
+ playlist.insert_items (at, urilist_to_index (list), false);
}
-EXPORT Index<char> audgui_urilist_create_from_selected (int playlist)
+EXPORT Index<char> audgui_urilist_create_from_selected (Playlist playlist)
{
- aud_playlist_cache_selected (playlist);
+ playlist.cache_selected ();
Index<char> buf;
- int entries = aud_playlist_entry_count (playlist);
+ int entries = playlist.n_entries ();
- for (int count = 0; count < entries; count ++)
+ for (int i = 0; i < entries; i ++)
{
- if (aud_playlist_entry_get_selected (playlist, count))
+ if (playlist.entry_selected (i))
{
if (buf.len ())
buf.append ('\n');
- String filename = aud_playlist_entry_get_filename (playlist, count);
+ String filename = playlist.entry_filename (i);
buf.insert (filename, -1, strlen (filename));
}
}
diff --git a/src/libaudgui/util.cc b/src/libaudgui/util.cc
index 79f8727..2994c89 100644
--- a/src/libaudgui/util.cc
+++ b/src/libaudgui/util.cc
@@ -27,6 +27,7 @@
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
+#define AUD_GLIB_INTEGRATION
#include <libaudcore/audstrings.h>
#include <libaudcore/hook.h>
#include <libaudcore/i18n.h>
@@ -156,12 +157,9 @@ static void entry_response_cb (GtkWidget * dialog, int response, GtkWidget * ent
{
if (response == GTK_RESPONSE_ACCEPT)
{
- char * uri = gtk_file_chooser_get_uri ((GtkFileChooser *) dialog);
+ CharPtr uri (gtk_file_chooser_get_uri ((GtkFileChooser *) dialog));
if (uri)
- {
audgui_file_entry_set_uri (entry, uri);
- g_free (uri);
- }
}
gtk_widget_destroy (dialog);
@@ -227,6 +225,12 @@ EXPORT void audgui_file_entry_set_uri (GtkWidget * entry, const char * uri)
gtk_editable_set_position ((GtkEditable *) entry, -1);
}
+static void set_label_wrap (GtkWidget * label, void *)
+{
+ if (GTK_IS_LABEL (label))
+ gtk_label_set_line_wrap_mode ((GtkLabel *) label, PANGO_WRAP_WORD_CHAR);
+}
+
EXPORT GtkWidget * audgui_dialog_new (GtkMessageType type, const char * title,
const char * text, GtkWidget * button1, GtkWidget * button2)
{
@@ -234,6 +238,9 @@ EXPORT GtkWidget * audgui_dialog_new (GtkMessageType type, const char * title,
GTK_BUTTONS_NONE, "%s", text);
gtk_window_set_title ((GtkWindow *) dialog, title);
+ GtkWidget * box = gtk_message_dialog_get_message_area ((GtkMessageDialog *) dialog);
+ gtk_container_foreach ((GtkContainer *) box, set_label_wrap, nullptr);
+
if (button2)
{
gtk_dialog_add_action_widget ((GtkDialog *) dialog, button2, GTK_RESPONSE_NONE);
@@ -255,39 +262,6 @@ EXPORT void audgui_dialog_add_widget (GtkWidget * dialog, GtkWidget * widget)
gtk_box_pack_start ((GtkBox *) box, widget, false, false, 0);
}
-static StringBuf ellipsize (const char * text)
-{
- constexpr int maxword = 100;
-
- StringBuf buf = str_copy (text);
- int start = 0;
-
- while (1)
- {
- while (buf[start] && g_ascii_isspace (buf[start]))
- start ++;
-
- if (! buf[start])
- break;
-
- int stop = start + 1;
- while (buf[stop] && ! g_ascii_isspace (buf[stop]))
- stop ++;
-
- if (stop - start > maxword)
- {
- buf.remove (start + maxword / 2, stop - start - maxword);
- buf.insert (start + maxword / 2, "…");
-
- stop = start + maxword + strlen ("…");
- }
-
- start = stop;
- }
-
- return buf;
-}
-
EXPORT void audgui_simple_message (GtkWidget * * widget, GtkMessageType type,
const char * title, const char * text)
{
@@ -308,10 +282,9 @@ EXPORT void audgui_simple_message (GtkWidget * * widget, GtkMessageType type,
if (messages > 10)
text = _("\n(Further messages have been hidden.)");
- StringBuf shortened = ellipsize (text);
- if (! strstr (old, shortened))
+ if (! strstr (old, text))
{
- StringBuf both = str_concat ({old, "\n", shortened});
+ StringBuf both = str_concat ({old, "\n", text});
g_object_set ((GObject *) * widget, "text", (const char *) both, nullptr);
g_object_set_data ((GObject *) * widget, "messages", GINT_TO_POINTER (messages + 1));
}
@@ -322,7 +295,7 @@ EXPORT void audgui_simple_message (GtkWidget * * widget, GtkMessageType type,
else
{
GtkWidget * button = audgui_button_new (_("_Close"), "window-close", nullptr, nullptr);
- * widget = audgui_dialog_new (type, title, ellipsize (text), button, nullptr);
+ * widget = audgui_dialog_new (type, title, text, button, nullptr);
g_object_set_data ((GObject *) * widget, "messages", GINT_TO_POINTER (1));
g_signal_connect (* widget, "destroy", (GCallback) gtk_widget_destroyed, widget);
diff --git a/src/libaudqt/Makefile b/src/libaudqt/Makefile
index b3b6f9a..7417d4e 100644
--- a/src/libaudqt/Makefile
+++ b/src/libaudqt/Makefile
@@ -1,5 +1,5 @@
SHARED_LIB = ${LIB_PREFIX}audqt${LIB_SUFFIX}
-LIB_MAJOR = 1
+LIB_MAJOR = 2
LIB_MINOR = 0
SRCS = about.cc \
diff --git a/src/libaudqt/about.cc b/src/libaudqt/about.cc
index e6a64f3..2fdccbd 100644
--- a/src/libaudqt/about.cc
+++ b/src/libaudqt/about.cc
@@ -38,6 +38,8 @@ static QTabWidget * buildCreditsNotebook (QWidget * parent)
const char * filenames[2] = {"AUTHORS", "COPYING"};
auto tabs = new QTabWidget (parent);
+ tabs->setDocumentMode (true);
+ tabs->setMinimumSize (6 * audqt::sizes.OneInch, 2 * audqt::sizes.OneInch);
for (int i = 0; i < 2; i ++)
{
@@ -49,6 +51,7 @@ static QTabWidget * buildCreditsNotebook (QWidget * parent)
auto edit = new QPlainTextEdit (in.readAll ().trimmed (), parent);
edit->setReadOnly (true);
+ edit->setFrameStyle (QFrame::NoFrame);
tabs->addTab (edit, _(titles[i]));
f.close ();
@@ -65,6 +68,7 @@ static QDialog * buildAboutWindow ()
const char * website = "http://audacious-media-player.org";
auto window = new QDialog;
+ window->setWindowTitle (_("About Audacious"));
auto logo = new QLabel (window);
logo->setPixmap (QPixmap (logo_path));
@@ -76,18 +80,14 @@ static QDialog * buildAboutWindow ()
auto anchor = QString ("<a href='%1'>%1</a>").arg (website);
auto link_label = new QLabel (anchor, window);
link_label->setAlignment (Qt::AlignHCenter);
- link_label->setContentsMargins (0, 5, 0, 0);
link_label->setOpenExternalLinks (true);
- auto layout = new QVBoxLayout (window);
+ auto layout = audqt::make_vbox (window);
layout->addWidget (logo);
layout->addWidget (text);
layout->addWidget (link_label);
layout->addWidget (buildCreditsNotebook (window));
- window->setWindowTitle (_("About Audacious"));
- window->setFixedSize (590, 450);
-
return window;
}
diff --git a/src/libaudqt/art.cc b/src/libaudqt/art.cc
index 55d910d..c691992 100644
--- a/src/libaudqt/art.cc
+++ b/src/libaudqt/art.cc
@@ -28,31 +28,37 @@
namespace audqt {
-EXPORT QPixmap art_request (const char * filename, unsigned int w, unsigned int h, bool want_hidpi)
+static QImage load_fallback ()
{
- const Index<char> * data = aud_art_request_data (filename);
- QImage img;
-
- if (data)
- {
- img = QImage::fromData ((const uchar *) data->begin (), data->len ());
+ static QImage fallback;
+ static bool loaded = false;
- aud_art_unref (filename);
- }
- else
- {
- QString fallback = QString (filename_build
+ if (! loaded)
+ fallback.load ((const char *) filename_build
({aud_get_path (AudPath::DataDir), "images", "album.png"}));
- img = QImage (fallback);
- }
+ return fallback; // shallow copy
+}
+
+EXPORT QPixmap art_request (const char * filename, unsigned int w, unsigned int h, bool want_hidpi)
+{
+ AudArtPtr art = aud_art_request (filename, AUD_ART_DATA);
+
+ auto data = art.data ();
+ auto img = data ? QImage::fromData ((const uchar *) data->begin (), data->len ()) : QImage ();
- if (w == 0 && h == 0)
+ if (img.isNull ())
{
- w = img.size ().width ();
- h = img.size ().height ();
+ img = load_fallback ();
+ if (img.isNull ())
+ return QPixmap ();
}
+ // return original image if requested size is zero,
+ // or original size is smaller than requested size
+ if ((w == 0 && h == 0) || ((unsigned) img.width () <= w && (unsigned) img.height () <= h))
+ return QPixmap::fromImage (img);
+
if (! want_hidpi)
return QPixmap::fromImage (img.scaled (w, h, Qt::KeepAspectRatio, Qt::SmoothTransformation));
diff --git a/src/libaudqt/equalizer.cc b/src/libaudqt/equalizer.cc
index 7d2bbb1..01907ce 100644
--- a/src/libaudqt/equalizer.cc
+++ b/src/libaudqt/equalizer.cc
@@ -66,18 +66,20 @@ public:
class EqualizerSlider : public QWidget
{
public:
+ QSlider slider;
+
EqualizerSlider (const char * label, QWidget * parent) :
QWidget (parent),
slider (Qt::Vertical)
{
+ slider.setMinimumHeight (audqt::sizes.OneInch);
slider.setRange (-AUD_EQ_MAX_GAIN, AUD_EQ_MAX_GAIN);
slider.setTickInterval (AUD_EQ_MAX_GAIN >> 1);
slider.setTickPosition (QSlider::TicksBothSides);
- auto layout = new QVBoxLayout (this);
+ auto layout = audqt::make_vbox (this);
auto value_label = new QLabel ("0");
- layout->setContentsMargins (0, 0, 0, 0);
layout->addWidget (new VLabel (label, this), 1, Qt::AlignCenter);
layout->addWidget (& slider, 0, Qt::AlignCenter);
layout->addWidget (value_label, 0, Qt::AlignCenter);
@@ -86,8 +88,6 @@ public:
value_label->setText (QString::number (value));
});
}
-
- QSlider slider;
};
class EqualizerWindow : public QDialog
@@ -118,7 +118,7 @@ EqualizerWindow::EqualizerWindow () :
N_("4 kHz"), N_("8 kHz"), N_("16 kHz")};
auto slider_container = new QWidget (this);
- auto slider_layout = new QHBoxLayout (slider_container);
+ auto slider_layout = audqt::make_hbox (slider_container, audqt::sizes.TwoPt);
m_preamp_slider = new EqualizerSlider (_("Preamp"), this);
slider_layout->addWidget (m_preamp_slider);
@@ -134,11 +134,14 @@ EqualizerWindow::EqualizerWindow () :
slider_layout->addWidget (m_sliders[i]);
}
- auto layout = new QVBoxLayout (this);
+ auto layout = audqt::make_vbox (this);
+ layout->setSizeConstraint (QLayout::SetFixedSize);
layout->addWidget (& m_onoff_checkbox);
layout->addWidget (slider_container);
setWindowTitle (_("Equalizer"));
+ setContentsMargins (audqt::margins.EightPt);
+
m_onoff_checkbox.setFocus ();
updateActive ();
@@ -191,7 +194,6 @@ EXPORT void equalizer_show ()
{
s_equalizer = new EqualizerWindow;
s_equalizer->setAttribute (Qt::WA_DeleteOnClose);
- s_equalizer->layout ()->setSizeConstraint (QLayout::SetFixedSize);
QObject::connect (s_equalizer, & QObject::destroyed, [] () {
s_equalizer = nullptr;
diff --git a/src/libaudqt/info-widget.cc b/src/libaudqt/info-widget.cc
index aa86e14..ef9b218 100644
--- a/src/libaudqt/info-widget.cc
+++ b/src/libaudqt/info-widget.cc
@@ -103,7 +103,7 @@ EXPORT InfoWidget::~InfoWidget ()
{
}
-EXPORT void InfoWidget::fillInfo (int playlist, int entry, const char * filename, const Tuple & tuple,
+EXPORT void InfoWidget::fillInfo (const char * filename, const Tuple & tuple,
PluginHandle * decoder, bool updating_enabled)
{
m_model->setTupleData (tuple, String (filename), decoder);
diff --git a/src/libaudqt/info-widget.h b/src/libaudqt/info-widget.h
index 86e3925..80e0483 100644
--- a/src/libaudqt/info-widget.h
+++ b/src/libaudqt/info-widget.h
@@ -18,6 +18,9 @@
* the use of this software.
*/
+#ifndef LIBAUDQT_INFO_WIDGET_H
+#define LIBAUDQT_INFO_WIDGET_H
+
#include <QTreeView>
#include <libaudqt/export.h>
@@ -34,7 +37,7 @@ public:
InfoWidget (QWidget * parent = nullptr);
~InfoWidget ();
- void fillInfo (int playlist, int entry, const char * filename, const Tuple & tuple,
+ void fillInfo (const char * filename, const Tuple & tuple,
PluginHandle * decoder, bool updating_enabled);
bool updateFile ();
@@ -43,3 +46,5 @@ private:
};
} // namespace audqt
+
+#endif // LIBAUDQT_INFO_WIDGET_H
diff --git a/src/libaudqt/infowin.cc b/src/libaudqt/infowin.cc
index a72e3c3..e51159a 100644
--- a/src/libaudqt/infowin.cc
+++ b/src/libaudqt/infowin.cc
@@ -44,7 +44,7 @@ class InfoWindow : public QDialog
public:
InfoWindow (QWidget * parent = nullptr);
- void fillInfo (int playlist, int entry, const char * filename, const Tuple & tuple,
+ void fillInfo (const char * filename, const Tuple & tuple,
PluginHandle * decoder, bool updating_enabled);
private:
@@ -61,12 +61,13 @@ private:
InfoWindow::InfoWindow (QWidget * parent) : QDialog (parent)
{
setWindowTitle (_("Song Info"));
+ setContentsMargins (margins.TwoPt);
- auto hbox = new QHBoxLayout;
+ auto hbox = make_hbox (nullptr);
hbox->addWidget (& m_image);
hbox->addWidget (& m_infowidget);
- auto vbox = new QVBoxLayout (this);
+ auto vbox = make_vbox (this);
vbox->addLayout (hbox);
auto bbox = new QDialogButtonBox (QDialogButtonBox::Save | QDialogButtonBox::Close, this);
@@ -82,23 +83,23 @@ InfoWindow::InfoWindow (QWidget * parent) : QDialog (parent)
QObject::connect (bbox, & QDialogButtonBox::rejected, this, & QObject::deleteLater);
}
-void InfoWindow::fillInfo (int playlist, int entry, const char * filename, const Tuple & tuple,
+void InfoWindow::fillInfo (const char * filename, const Tuple & tuple,
PluginHandle * decoder, bool updating_enabled)
{
m_filename = String (filename);
displayImage (filename);
- m_infowidget.fillInfo (playlist, entry, filename, tuple, decoder, updating_enabled);
+ m_infowidget.fillInfo (filename, tuple, decoder, updating_enabled);
}
void InfoWindow::displayImage (const char * filename)
{
if (! strcmp_safe (filename, m_filename))
- m_image.setPixmap (art_request (filename));
+ m_image.setPixmap (art_request (filename, 2 * sizes.OneInch, 2 * sizes.OneInch));
}
static InfoWindow * s_infowin = nullptr;
-static void show_infowin (int playlist, int entry, const char * filename,
+static void show_infowin (const char * filename,
const Tuple & tuple, PluginHandle * decoder, bool can_write)
{
if (! s_infowin)
@@ -111,22 +112,20 @@ static void show_infowin (int playlist, int entry, const char * filename,
});
}
- s_infowin->fillInfo (playlist, entry, filename, tuple, decoder, can_write);
- s_infowin->resize (700, 300);
+ s_infowin->fillInfo (filename, tuple, decoder, can_write);
+ s_infowin->resize (6 * sizes.OneInch, 3 * sizes.OneInch);
window_bring_to_front (s_infowin);
}
-EXPORT void infowin_show (int playlist, int entry)
+EXPORT void infowin_show (Playlist playlist, int entry)
{
- String filename = aud_playlist_entry_get_filename (playlist, entry);
+ String filename = playlist.entry_filename (entry);
if (! filename)
return;
String error;
- PluginHandle * decoder = aud_playlist_entry_get_decoder (playlist, entry,
- Playlist::Wait, & error);
- Tuple tuple = decoder ? aud_playlist_entry_get_tuple (playlist, entry,
- Playlist::Wait, & error) : Tuple ();
+ PluginHandle * decoder = playlist.entry_decoder (entry, Playlist::Wait, & error);
+ Tuple tuple = decoder ? playlist.entry_tuple (entry, Playlist::Wait, & error) : Tuple ();
if (decoder && tuple.valid () && ! aud_custom_infowin (filename, decoder))
{
@@ -135,7 +134,7 @@ EXPORT void infowin_show (int playlist, int entry)
! tuple.is_set (Tuple::StartTime);
tuple.delete_fallbacks ();
- show_infowin (playlist, entry, filename, tuple, decoder, can_write);
+ show_infowin (filename, tuple, decoder, can_write);
}
else
infowin_hide ();
@@ -147,15 +146,12 @@ EXPORT void infowin_show (int playlist, int entry)
EXPORT void infowin_show_current ()
{
- int playlist = aud_playlist_get_playing ();
- int position;
+ auto playlist = Playlist::playing_playlist ();
+ if (playlist == Playlist ())
+ playlist = Playlist::active_playlist ();
- if (playlist == -1)
- playlist = aud_playlist_get_active ();
-
- position = aud_playlist_get_position (playlist);
-
- if (position == -1)
+ int position = playlist.get_position ();
+ if (position < 0)
return;
infowin_show (playlist, position);
diff --git a/src/libaudqt/log-inspector.h b/src/libaudqt/libaudqt-internal.h
index 5b88845..ea00ed9 100644
--- a/src/libaudqt/log-inspector.h
+++ b/src/libaudqt/libaudqt-internal.h
@@ -1,6 +1,6 @@
/*
- * log-inspector.h
- * Copyright 2014 William Pitcock
+ * libaudqt-internal.h
+ * Copyright 2016 John Lindgren
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -17,29 +17,15 @@
* the use of this software.
*/
-#include <QAbstractListModel>
-
-#ifndef LIBAUDQT_LOG_INSPECTOR_H
-#define LIBAUDQT_LOG_INSPECTOR_H
+#ifndef LIBAUDQT_INTERNAL_H
+#define LIBAUDQT_INTERNAL_H
namespace audqt {
-struct LogEntry;
-
-class LogEntryModel : public QAbstractListModel
-{
-public:
- LogEntryModel (QObject * parent = nullptr);
- ~LogEntryModel ();
-
- int rowCount (const QModelIndex & parent = QModelIndex ()) const;
- int columnCount (const QModelIndex & parent = QModelIndex ()) const;
- QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const;
- QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
-
- void addEntry (LogEntry * entry);
-};
+/* log-inspector.cc */
+void log_init ();
+void log_cleanup ();
} // namespace audqt
-#endif
+#endif // LIBAUDQT_INTERNAL_H
diff --git a/src/libaudqt/libaudqt.h b/src/libaudqt/libaudqt.h
index ea21708..3f242c2 100644
--- a/src/libaudqt/libaudqt.h
+++ b/src/libaudqt/libaudqt.h
@@ -20,17 +20,22 @@
#ifndef LIBAUDQT_H
#define LIBAUDQT_H
+#include <QMargins>
#include <QMessageBox>
#include <QString>
#include <libaudcore/objects.h>
-class QBoxLayout;
class QLayout;
+class QBoxLayout;
+class QHBoxLayout;
+class QVBoxLayout;
+
class QPixmap;
class QToolButton;
class QWidget;
enum class PluginType;
+class Playlist;
class PluginHandle;
struct PreferencesWidget;
@@ -44,6 +49,19 @@ enum class FileMode {
count
};
+struct PixelSizes {
+ int OneInch;
+ int TwoPt;
+ int FourPt;
+ int EightPt;
+};
+
+struct PixelMargins {
+ QMargins TwoPt;
+ QMargins FourPt;
+ QMargins EightPt;
+};
+
struct MenuItem;
/* about.cc */
@@ -51,8 +69,8 @@ void aboutwindow_show ();
void aboutwindow_hide ();
/* playlist-management.cc */
-void playlist_show_rename (int playlist);
-void playlist_confirm_delete (int playlist);
+void playlist_show_rename (Playlist playlist);
+void playlist_confirm_delete (Playlist playlist);
/* equalizer.cc */
void equalizer_show ();
@@ -65,11 +83,23 @@ void fileopener_show (FileMode mode);
void urlopener_show (bool open);
/* util.cc */
+
+extern const PixelSizes & sizes;
+extern const PixelMargins & margins;
+
+static inline int to_native_dpi (int x)
+ { return aud::rescale (x, 96, sizes.OneInch); }
+static inline int to_portable_dpi (int x)
+ { return aud::rescale (x, sizes.OneInch, 96); }
+
void init ();
void run ();
void quit ();
void cleanup ();
+QHBoxLayout * make_hbox (QWidget * parent, int spacing = sizes.FourPt);
+QVBoxLayout * make_vbox (QWidget * parent, int spacing = sizes.FourPt);
+
void enable_layout (QLayout * layout, bool enabled);
void clear_layout (QLayout * layout);
void window_bring_to_front (QWidget * win);
@@ -100,11 +130,11 @@ void log_inspector_show ();
void log_inspector_hide ();
/* art.cc */
-QPixmap art_request (const char * filename, unsigned int w = 256, unsigned int h = 256, bool want_hidpi = true);
-QPixmap art_request_current (unsigned int w = 256, unsigned int h = 256, bool want_hidpi = true);
+QPixmap art_request (const char * filename, unsigned int w, unsigned int h, bool want_hidpi = true);
+QPixmap art_request_current (unsigned int w, unsigned int h, bool want_hidpi = true);
/* infowin.cc */
-void infowin_show (int playlist, int entry);
+void infowin_show (Playlist playlist, int entry);
void infowin_show_current ();
void infowin_hide ();
diff --git a/src/libaudqt/log-inspector.cc b/src/libaudqt/log-inspector.cc
index 9394ce0..bcaefd5 100644
--- a/src/libaudqt/log-inspector.cc
+++ b/src/libaudqt/log-inspector.cc
@@ -17,29 +17,31 @@
* the use of this software.
*/
-#include "log-inspector.h"
#include "libaudqt.h"
+#include "libaudqt-internal.h"
#include <QComboBox>
#include <QDialog>
+#include <QDialogButtonBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QVBoxLayout>
+#include <QPushButton>
#include <QTreeView>
#include <QWidget>
+#include <libaudcore/audstrings.h>
+#include <libaudcore/hook.h>
#include <libaudcore/i18n.h>
-#include <libaudcore/index.h>
+#include <libaudcore/ringbuf.h>
#include <libaudcore/runtime.h>
-namespace audqt {
+#define LOGENTRY_MAX 1024
-const int LOGENTRY_MAX = 1000;
+namespace audqt {
enum LogEntryColumn {
Level,
- File,
- Line,
Function,
Message,
Count
@@ -47,56 +49,79 @@ enum LogEntryColumn {
struct LogEntry {
audlog::Level level;
- const char * filename;
- unsigned int line;
- const char * function;
- char * message = nullptr;
-
- ~LogEntry ()
- { free (message); }
+ String function;
+ String message;
};
-static Index<SmartPtr<LogEntry>> entries;
+class LogEntryModel : public QAbstractListModel
+{
+public:
+ LogEntryModel (QObject * parent = nullptr) :
+ QAbstractListModel (parent) {}
-static void log_handler (audlog::Level level, const char * file, int line,
- const char * func, const char * message);
+ void cleanup ();
+protected:
+ int rowCount (const QModelIndex & parent = QModelIndex ()) const
+ { return m_entries.len (); }
+ int columnCount (const QModelIndex & parent = QModelIndex ()) const
+ { return LogEntryColumn::Count; }
-/* log entry model */
-LogEntryModel::LogEntryModel (QObject * parent) : QAbstractListModel (parent)
-{
-}
+ QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const;
+ QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
-LogEntryModel::~LogEntryModel ()
-{
-}
+private:
+ RingBuf<LogEntry> m_entries;
-int LogEntryModel::rowCount (const QModelIndex & parent) const
+ void addEntry (const LogEntry * entry);
+ HookReceiver<LogEntryModel, const LogEntry *>
+ log_hook {"audqt log entry", this, & LogEntryModel::addEntry};
+};
+
+void LogEntryModel::cleanup ()
{
- return entries.len ();
+ if (m_entries.len () > 0)
+ {
+ beginRemoveRows (QModelIndex (), 0, m_entries.len () - 1);
+ m_entries.destroy ();
+ endRemoveRows ();
+ }
}
-int LogEntryModel::columnCount (const QModelIndex & parent) const
+/* log entry model */
+void LogEntryModel::addEntry (const LogEntry * entry)
{
- return LogEntryColumn::Count;
+ if (! m_entries.space ())
+ {
+ if (m_entries.len () < LOGENTRY_MAX)
+ m_entries.alloc (aud::max (16, 2 * m_entries.len ()));
+ else
+ {
+ beginRemoveRows (QModelIndex (), 0, 0);
+ m_entries.pop ();
+ endRemoveRows ();
+ }
+ }
+
+ beginInsertRows (QModelIndex (), m_entries.len (), m_entries.len ());
+ m_entries.push (* entry);
+ endInsertRows ();
}
QVariant LogEntryModel::data (const QModelIndex & index, int role) const
{
int row = index.row ();
- if (row < 0 || row >= entries.len ())
+ if (row < 0 || row >= m_entries.len ())
return QVariant ();
- auto & e = entries[row];
+ auto & e = m_entries[row];
if (role == Qt::DisplayRole)
{
switch (index.column ())
{
- case LogEntryColumn::Level: return QString (audlog::get_level_name (e->level));
- case LogEntryColumn::File: return QString (e->filename);
- case LogEntryColumn::Line: return e->line;
- case LogEntryColumn::Function: return QString (e->function);
- case LogEntryColumn::Message: return QString (e->message);
+ case LogEntryColumn::Level: return QString (audlog::get_level_name (e.level));
+ case LogEntryColumn::Function: return QString (e.function);
+ case LogEntryColumn::Message: return QString (e.message);
}
}
@@ -110,8 +135,6 @@ QVariant LogEntryModel::headerData (int section, Qt::Orientation orientation, in
switch (section)
{
case LogEntryColumn::Level: return QString (_("Level"));
- case LogEntryColumn::File: return QString (_("Filename"));
- case LogEntryColumn::Line: return QString (_("Line"));
case LogEntryColumn::Function: return QString (_("Function"));
case LogEntryColumn::Message: return QString (_("Message"));
}
@@ -120,18 +143,38 @@ QVariant LogEntryModel::headerData (int section, Qt::Orientation orientation, in
return QVariant ();
}
-void LogEntryModel::addEntry (LogEntry * entry)
+/* static model */
+static SmartPtr<LogEntryModel> s_model;
+static audlog::Level s_level = audlog::Warning;
+
+static void log_handler (audlog::Level level, const char * file, int line,
+ const char * func, const char * message)
{
- if (entries.len () >= LOGENTRY_MAX)
+ auto messages = str_list_to_index (message, "\n");
+
+ for (auto & message : messages)
{
- beginRemoveRows (QModelIndex (), 0, 0);
- entries.remove (0, 1);
- endRemoveRows ();
+ auto entry = new LogEntry;
+
+ entry->level = level;
+ entry->function = String (str_printf ("%s (%s:%d)", func, file, line));
+ entry->message = std::move (message);
+
+ event_queue ("audqt log entry", entry, aud::delete_obj<LogEntry>);
}
+}
- beginInsertRows (QModelIndex (), entries.len (), entries.len ());
- entries.append (SmartPtr<LogEntry> (entry));
- endInsertRows ();
+void log_init ()
+{
+ s_model.capture (new LogEntryModel);
+ audlog::subscribe (log_handler, s_level);
+}
+
+void log_cleanup ()
+{
+ audlog::unsubscribe (log_handler);
+ event_queue_cancel ("audqt log entry");
+ s_model.clear ();
}
/* log entry inspector */
@@ -139,23 +182,9 @@ class LogEntryInspector : public QDialog
{
public:
LogEntryInspector (QWidget * parent = nullptr);
- ~LogEntryInspector ();
-
- audlog::Level m_level;
-
- void addEntry (LogEntry * entry)
- { m_model->addEntry (entry); }
private:
- QVBoxLayout m_layout;
- LogEntryModel * m_model;
- QTreeView * m_view;
-
- QWidget m_bottom_container;
- QHBoxLayout m_bottom_layout;
-
QComboBox m_level_combobox;
- QLabel m_level_label;
void setLogLevel (audlog::Level level);
};
@@ -164,69 +193,63 @@ LogEntryInspector::LogEntryInspector (QWidget * parent) :
QDialog (parent)
{
setWindowTitle (_("Log Inspector"));
- setLayout (& m_layout);
-
- m_model = new LogEntryModel (this);
- m_view = new QTreeView (this);
- m_view->setModel (m_model);
+ setContentsMargins (margins.TwoPt);
- m_layout.addWidget (m_view);
+ auto view = new QTreeView (this);
+ view->setModel (s_model.get ());
- m_bottom_layout.setContentsMargins (0, 0, 0, 0);
-
- m_level_label.setText (_("Log Level:"));
- m_bottom_layout.addWidget (& m_level_label);
+ view->setAllColumnsShowFocus (true);
+ view->setIndentation (0);
+ view->setUniformRowHeights (true);
+ view->scrollToBottom ();
m_level_combobox.addItem (_("Debug"), audlog::Debug);
m_level_combobox.addItem (_("Info"), audlog::Info);
m_level_combobox.addItem (_("Warning"), audlog::Warning);
m_level_combobox.addItem (_("Error"), audlog::Error);
+ m_level_combobox.setCurrentIndex (s_level);
+
QObject::connect (& m_level_combobox,
static_cast <void (QComboBox::*) (int)> (&QComboBox::currentIndexChanged),
[this] (int idx) { setLogLevel ((audlog::Level) idx); });
- m_bottom_layout.addWidget (& m_level_combobox);
+ auto btnbox = new QDialogButtonBox (this);
- m_bottom_container.setLayout (& m_bottom_layout);
- m_layout.addWidget (& m_bottom_container);
+ auto btn1 = btnbox->addButton (translate_str (N_("Cl_ear")), QDialogButtonBox::ActionRole);
+ btn1->setIcon (QIcon::fromTheme ("edit-clear-all"));
+ btn1->setAutoDefault (false);
+ QObject::connect (btn1, & QPushButton::clicked, [] () {
+ s_model.get ()->cleanup ();
+ });
- resize (800, 350);
+ auto btn2 = btnbox->addButton (QDialogButtonBox::Close);
+ btn2->setText (translate_str (N_("_Close")));
+ btn2->setAutoDefault (false);
+ QObject::connect (btn2, & QPushButton::clicked, this, & QDialog::close);
- setLogLevel (audlog::Info);
-}
+ auto hbox = make_hbox (nullptr);
+ hbox->addWidget (new QLabel (_("Log Level:"), this));
+ hbox->addWidget (& m_level_combobox);
+ hbox->addWidget (btnbox);
-LogEntryInspector::~LogEntryInspector ()
-{
- audlog::unsubscribe (log_handler);
+ auto vbox = make_vbox (this);
+ vbox->addWidget (view);
+ vbox->addLayout (hbox);
+
+ resize (6 * sizes.OneInch, 3 * sizes.OneInch);
}
static LogEntryInspector * s_inspector = nullptr;
-static void log_handler (audlog::Level level, const char * file, int line,
- const char * func, const char * message)
-{
- LogEntry * l = new LogEntry;
-
- l->level = level;
- l->filename = file;
- l->line = line;
- l->function = func;
-
- l->message = strdup (message);
- l->message[strlen (l->message) - 1] = 0;
-
- s_inspector->addEntry (l);
-}
-
void LogEntryInspector::setLogLevel (audlog::Level level)
{
- m_level = level;
+ s_level = level;
audlog::unsubscribe (log_handler);
audlog::subscribe (log_handler, level);
- m_level_combobox.setCurrentIndex (m_level);
+ m_level_combobox.setCurrentIndex (level);
}
EXPORT void log_inspector_show ()
diff --git a/src/libaudqt/playlist-management.cc b/src/libaudqt/playlist-management.cc
index 906acff..60b3120 100644
--- a/src/libaudqt/playlist-management.cc
+++ b/src/libaudqt/playlist-management.cc
@@ -34,11 +34,11 @@
namespace audqt {
-static QDialog * buildRenameDialog (int playlist)
+static QDialog * buildRenameDialog (Playlist playlist)
{
auto dialog = new QDialog;
auto prompt = new QLabel (_("What would you like to call this playlist?"), dialog);
- auto entry = new QLineEdit ((const char *) aud_playlist_get_title (playlist), dialog);
+ auto entry = new QLineEdit ((const char *) playlist.get_title (), dialog);
auto rename = new QPushButton (translate_str (N_("_Rename")), dialog);
auto cancel = new QPushButton (translate_str (N_("_Cancel")), dialog);
@@ -46,28 +46,28 @@ static QDialog * buildRenameDialog (int playlist)
buttonbox->addButton (rename, QDialogButtonBox::AcceptRole);
buttonbox->addButton (cancel, QDialogButtonBox::RejectRole);
- int id = aud_playlist_get_unique_id (playlist);
- QObject::connect (buttonbox, & QDialogButtonBox::accepted, [dialog, entry, id] () {
- int list = aud_playlist_by_unique_id (id);
- if (list >= 0)
- aud_playlist_set_title (list, entry->text ().toUtf8 ());
+ QObject::connect (buttonbox, & QDialogButtonBox::accepted, [dialog, entry, playlist] () {
+ playlist.set_title (entry->text ().toUtf8 ());
dialog->close ();
});
QObject::connect (buttonbox, & QDialogButtonBox::rejected, dialog, & QDialog::close);
- auto layout = new QVBoxLayout (dialog);
+ auto layout = make_vbox (dialog);
layout->addWidget (prompt);
layout->addWidget (entry);
+ layout->addStretch (1);
layout->addWidget (buttonbox);
dialog->setWindowTitle (_("Rename Playlist"));
+ dialog->setContentsMargins (margins.EightPt);
+
entry->selectAll ();
return dialog;
}
-static QDialog * buildDeleteDialog (int playlist)
+static QDialog * buildDeleteDialog (Playlist playlist)
{
auto dialog = new QMessageBox;
auto skip_prompt = new QCheckBox (translate_str (N_("_Don’t ask again")), dialog);
@@ -77,7 +77,7 @@ static QDialog * buildDeleteDialog (int playlist)
dialog->setIcon (QMessageBox::Question);
dialog->setWindowTitle (_("Remove Playlist"));
dialog->setText ((const char *) str_printf (_("Do you want to permanently remove “%s”?"),
- (const char *) aud_playlist_get_title (playlist)));
+ (const char *) playlist.get_title ()));
dialog->setCheckBox (skip_prompt);
dialog->addButton (remove, QMessageBox::AcceptRole);
dialog->addButton (cancel, QMessageBox::RejectRole);
@@ -90,28 +90,25 @@ static QDialog * buildDeleteDialog (int playlist)
});
QObject::connect (remove, & QPushButton::clicked, [dialog, playlist] () {
- int id = aud_playlist_get_unique_id (playlist);
- int list = aud_playlist_by_unique_id (id);
- if (list >= 0)
- aud_playlist_delete (list);
+ playlist.remove_playlist ();
dialog->close ();
});
return dialog;
}
-EXPORT void playlist_show_rename (int playlist)
+EXPORT void playlist_show_rename (Playlist playlist)
{
auto dialog = buildRenameDialog (playlist);
dialog->setAttribute (Qt::WA_DeleteOnClose);
dialog->show ();
}
-EXPORT void playlist_confirm_delete (int playlist)
+EXPORT void playlist_confirm_delete (Playlist playlist)
{
if (aud_get_bool ("audgui", "no_confirm_playlist_delete"))
{
- aud_playlist_delete (playlist);
+ playlist.remove_playlist ();
return;
}
diff --git a/src/libaudqt/plugin-menu.cc b/src/libaudqt/plugin-menu.cc
index e0f16ea..935d83a 100644
--- a/src/libaudqt/plugin-menu.cc
+++ b/src/libaudqt/plugin-menu.cc
@@ -42,19 +42,15 @@ static void show_prefs ()
}
MenuItem default_menu_items[] = {
- MenuCommand ({N_("Plugins ...")}, show_prefs),
- MenuSep ()
+ MenuCommand ({N_("Plugins ..."), "preferences-system"}, show_prefs),
};
-EXPORT QMenu * menu_get_by_id (AudMenuID id)
+void menu_rebuild (AudMenuID id)
{
if (menus[id])
- return menus[id];
-
- menus[id] = new QMenu (_("Services"));
-
- for (auto & item : default_menu_items)
- menus[id]->addAction (menu_action (item, PACKAGE, menus[id]));
+ menus[id]->clear ();
+ else
+ menus[id] = new QMenu (_("Services"));
for (auto & item : items[id])
{
@@ -62,18 +58,26 @@ EXPORT QMenu * menu_get_by_id (AudMenuID id)
menus[id]->addAction (item.action.get ());
}
+ if (! menus[id]->isEmpty ())
+ menus[id]->addAction (menu_action (MenuSep (), PACKAGE, menus[id]));
+
+ for (auto & item : default_menu_items)
+ menus[id]->addAction (menu_action (item, PACKAGE, menus[id]));
+}
+
+EXPORT QMenu * menu_get_by_id (AudMenuID id)
+{
+ if (! menus[id])
+ menu_rebuild (id);
+
return menus[id];
}
EXPORT void menu_add (AudMenuID id, MenuFunc func, const char * name, const char * icon)
{
- auto & item = items[id].append (MenuCommand ({name, icon}, func));
+ items[id].append (MenuCommand ({name, icon}, func));
- if (menus[id])
- {
- item.action.capture (menu_action (item.item, nullptr));
- menus[id]->addAction (item.action.get ());
- }
+ menu_rebuild(id);
}
EXPORT void menu_remove (AudMenuID id, MenuFunc func)
@@ -81,7 +85,8 @@ EXPORT void menu_remove (AudMenuID id, MenuFunc func)
auto is_match = [func] (ItemData & item)
{ return item.item.func == func; };
- items[id].remove_if (is_match, true);
+ if (items[id].remove_if (is_match, true))
+ menu_rebuild (id);
}
} // namespace audqt
diff --git a/src/libaudqt/prefs-builder.cc b/src/libaudqt/prefs-builder.cc
index dece721..2bcdd24 100644
--- a/src/libaudqt/prefs-builder.cc
+++ b/src/libaudqt/prefs-builder.cc
@@ -54,14 +54,13 @@ void prefs_populate (QBoxLayout * layout, ArrayRef<PreferencesWidget> widgets, c
/* create new layout for child widgets */
if (dynamic_cast<QHBoxLayout *> (parent_layout))
- layout = new QHBoxLayout;
+ layout = make_hbox (nullptr, sizes.TwoPt);
else
{
- layout = new QVBoxLayout;
- layout->setContentsMargins (12, 0, 0, 0);
+ layout = make_vbox (nullptr, sizes.TwoPt);
+ layout->setContentsMargins (sizes.EightPt, 0, 0, 0);
}
- layout->setSpacing (parent_layout->spacing ());
parent_layout->addLayout (layout);
orig_layout = layout;
@@ -117,16 +116,15 @@ void prefs_populate (QBoxLayout * layout, ArrayRef<PreferencesWidget> widgets, c
if (strstr (w.label, "<b>"))
{
- /* double spacing above a header */
+ /* extra spacing above a header */
if (orig_layout->itemAt (0))
- orig_layout->addSpacing (orig_layout->spacing ());
+ orig_layout->addSpacing (sizes.EightPt);
orig_layout->addWidget (label);
/* create indented layout below header */
- layout = new QVBoxLayout;
- layout->setContentsMargins (12, 0, 0, 0);
- layout->setSpacing (orig_layout->spacing ());
+ layout = make_vbox (nullptr, sizes.TwoPt);
+ layout->setContentsMargins (sizes.EightPt, 0, 0, 0);
orig_layout->addLayout (layout);
}
else
@@ -203,7 +201,11 @@ void prefs_populate (QBoxLayout * layout, ArrayRef<PreferencesWidget> widgets, c
QFrame * f = new QFrame;
f->setFrameShape (w.data.separator.horizontal ? QFrame::HLine : QFrame::VLine);
f->setFrameShadow (QFrame::Sunken);
+
+ layout->addSpacing (sizes.FourPt);
layout->addWidget (f);
+ layout->addSpacing (sizes.FourPt);
+
break;
}
diff --git a/src/libaudqt/prefs-plugin.cc b/src/libaudqt/prefs-plugin.cc
index 8b38950..03fa53d 100644
--- a/src/libaudqt/prefs-plugin.cc
+++ b/src/libaudqt/prefs-plugin.cc
@@ -100,6 +100,7 @@ EXPORT void plugin_prefs (PluginHandle * ph)
cw->root = new QDialog;
cw->root->setAttribute (Qt::WA_DeleteOnClose);
+ cw->root->setContentsMargins (margins.FourPt);
if (p->init)
p->init ();
@@ -117,10 +118,7 @@ EXPORT void plugin_prefs (PluginHandle * ph)
cw->root->setWindowTitle ((const char *) str_printf(_("%s Settings"), name));
- QVBoxLayout * vbox = new QVBoxLayout (cw->root);
- vbox->setContentsMargins (4, 4, 4, 4);
- vbox->setSpacing (4);
-
+ auto vbox = make_vbox (cw->root, sizes.TwoPt);
prefs_populate (vbox, p->widgets, header->info.domain);
vbox->addStretch (1);
diff --git a/src/libaudqt/prefs-pluginlist-model.cc b/src/libaudqt/prefs-pluginlist-model.cc
index 0778849..969cad7 100644
--- a/src/libaudqt/prefs-pluginlist-model.cc
+++ b/src/libaudqt/prefs-pluginlist-model.cc
@@ -1,6 +1,6 @@
/*
* prefs-pluginlist-model.cc
- * Copyright 2014 William Pitcock
+ * Copyright 2014-2017 John Lindgren and William Pitcock
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -20,43 +20,127 @@
#include "prefs-pluginlist-model.h"
#include <QIcon>
+
+#include <libaudcore/i18n.h>
#include <libaudcore/plugins.h>
+#include <libaudcore/runtime.h>
namespace audqt {
-PluginListModel::PluginListModel (QObject * parent, PluginType category_id) : QAbstractListModel (parent),
- m_list (aud_plugin_list (category_id))
+struct PluginCategory {
+ PluginType type;
+ const char * name;
+};
+
+static const PluginCategory categories[] = {
+ { PluginType::General, N_("General") },
+ { PluginType::Effect, N_("Effect") },
+ { PluginType::Vis, N_("Visualization") },
+ { PluginType::Input, N_("Input") },
+ { PluginType::Playlist, N_("Playlist") },
+ { PluginType::Transport, N_("Transport") }
+};
+
+static constexpr int n_categories = aud::n_elems (categories);
+
+// The model hierarchy is as follows:
+//
+// Root (invalid index)
+// + General category (index with row 0, null internal pointer)
+// + General plugin (index with row 0, internal pointer to PluginHandle)
+// + General plugin (index with row 1, internal pointer to PluginHandle)
+// + General plugin ...
+// + Effect category (index with row 1, null internal pointer)
+// + Effect plugin ...
+// + More categories ...
+
+QModelIndex PluginListModel::index (int row, int column, const QModelIndex & parent) const
{
+ // is parent the root node?
+ if (! parent.isValid ())
+ return createIndex (row, column, nullptr);
+
+ // is parent a plugin node?
+ if (parent.internalPointer () != nullptr)
+ return QModelIndex ();
+
+ // parent must be a category node
+ int cat = parent.row ();
+ if (cat < 0 || cat >= n_categories)
+ return QModelIndex ();
+ auto & list = aud_plugin_list (categories[cat].type);
+ if (row < 0 || row >= list.len ())
+ return QModelIndex ();
+
+ return createIndex (row, column, list[row]);
}
-PluginListModel::~PluginListModel ()
+// for a plugin node, return the category node
+// for all other nodes, return an invalid index
+QModelIndex PluginListModel::parent (const QModelIndex & child) const
{
+ auto p = pluginForIndex (child);
+ return p ? indexForType (aud_plugin_get_type (p)) : QModelIndex ();
+}
+// retrieve the PluginHandle from a plugin node
+PluginHandle * PluginListModel::pluginForIndex (const QModelIndex & index) const
+{
+ return (PluginHandle *) index.internalPointer ();
}
-int PluginListModel::rowCount (const QModelIndex & parent) const
+// look up the category node for a given plugin type
+QModelIndex PluginListModel::indexForType (PluginType type) const
{
- return m_list.len ();
+ for (int cat = 0; cat < n_categories; cat ++)
+ {
+ if (categories[cat].type == type)
+ return createIndex (cat, 0, nullptr);
+ }
+
+ return QModelIndex ();
}
-int PluginListModel::columnCount (const QModelIndex & parent) const
+int PluginListModel::rowCount (const QModelIndex & parent) const
{
- return NumColumns;
+ // for the root node, return the # of categories
+ if (! parent.isValid ())
+ return n_categories;
+
+ // for a plugin node, return 0 (no children)
+ if (parent.internalPointer () != nullptr)
+ return 0;
+
+ // for a category node, return the # of plugins
+ int cat = parent.row ();
+ if (cat < 0 || cat >= n_categories)
+ return 0;
+
+ return aud_plugin_list (categories[cat].type).len ();
}
-QVariant PluginListModel::headerData (int section, Qt::Orientation orientation, int role) const
+int PluginListModel::columnCount (const QModelIndex & parent) const
{
- return QVariant ();
+ return NumColumns;
}
QVariant PluginListModel::data (const QModelIndex & index, int role) const
{
- int row = index.row ();
- if (row < 0 || row >= m_list.len ())
- return QVariant ();
+ auto p = pluginForIndex (index);
+
+ if (! p) // category node?
+ {
+ if (role != Qt::DisplayRole || index.column () != 0)
+ return QVariant ();
+
+ int cat = index.row ();
+ if (cat < 0 || cat >= n_categories)
+ return QVariant ();
+
+ return QString (_(categories[cat].name));
+ }
- PluginHandle * p = m_list[row];
bool enabled = aud_plugin_get_enabled (p);
switch (index.column ())
@@ -87,15 +171,14 @@ QVariant PluginListModel::data (const QModelIndex & index, int role) const
bool PluginListModel::setData (const QModelIndex &index, const QVariant &value, int role)
{
- int row = index.row ();
- if (row < 0 || row >= m_list.len ())
+ if (role != Qt::CheckStateRole)
return false;
- if (role == Qt::CheckStateRole)
- {
- aud_plugin_enable (m_list[row], value.toUInt () != Qt::Unchecked);
- }
+ auto p = pluginForIndex (index);
+ if (! p)
+ return false;
+ aud_plugin_enable (p, value.toUInt () != Qt::Unchecked);
emit dataChanged (index, index.sibling (index.row (), NumColumns - 1));
return true;
}
@@ -105,33 +188,4 @@ Qt::ItemFlags PluginListModel::flags (const QModelIndex & index) const
return (Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
}
-bool PluginListModel::insertRows (int row, int count, const QModelIndex & parent)
-{
- int last = row + count - 1;
- beginInsertRows (parent, row, last);
- endInsertRows ();
- return true;
-}
-
-bool PluginListModel::removeRows (int row, int count, const QModelIndex & parent)
-{
- int last = row + count - 1;
- beginRemoveRows (parent, row, last);
- endRemoveRows ();
- return true;
-}
-
-void PluginListModel::updateRows (int row, int count)
-{
- int bottom = row + count - 1;
- auto topLeft = createIndex (row, 0);
- auto bottomRight = createIndex (bottom, columnCount () - 1);
- emit dataChanged (topLeft, bottomRight);
-}
-
-void PluginListModel::updateRow (int row)
-{
- updateRows (row, 1);
-}
-
} // namespace audqt
diff --git a/src/libaudqt/prefs-pluginlist-model.h b/src/libaudqt/prefs-pluginlist-model.h
index 1b74b6f..aaf1874 100644
--- a/src/libaudqt/prefs-pluginlist-model.h
+++ b/src/libaudqt/prefs-pluginlist-model.h
@@ -1,6 +1,6 @@
/*
* prefs-pluginlist-model.h
- * Copyright 2014 William Pitcock
+ * Copyright 2014-2017 John Lindgren and William Pitcock
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
@@ -20,15 +20,14 @@
#ifndef PREFS_PLUGINLIST_MODEL_H
#define PREFS_PLUGINLIST_MODEL_H
-#include <QAbstractListModel>
-#include <libaudcore/index.h>
+#include <QAbstractItemModel>
enum class PluginType;
class PluginHandle;
namespace audqt {
-class PluginListModel : public QAbstractListModel
+class PluginListModel : public QAbstractItemModel
{
public:
enum {
@@ -38,23 +37,20 @@ public:
NumColumns
};
- PluginListModel (QObject * parent, PluginType category_id);
- ~PluginListModel ();
+ PluginListModel (QObject * parent) : QAbstractItemModel (parent) {}
- int rowCount (const QModelIndex & parent = QModelIndex ()) const;
- int columnCount (const QModelIndex & parent = QModelIndex ()) const;
- QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const;
- QVariant headerData (int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
- bool setData (const QModelIndex & index, const QVariant & value, int role = Qt::DisplayRole);
- Qt::ItemFlags flags (const QModelIndex & parent = QModelIndex ()) const;
+ QModelIndex index (int row, int column, const QModelIndex & parent) const;
+ QModelIndex parent (const QModelIndex & child) const;
+
+ PluginHandle * pluginForIndex (const QModelIndex & index) const;
+ QModelIndex indexForType (PluginType type) const;
- bool insertRows (int row, int count, const QModelIndex & parent = QModelIndex ());
- bool removeRows (int row, int count, const QModelIndex & parent = QModelIndex ());
- void updateRows (int row, int count);
- void updateRow (int row);
+ int rowCount (const QModelIndex & parent) const;
+ int columnCount (const QModelIndex & parent) const;
-private:
- const Index<PluginHandle *> & m_list;
+ QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const;
+ bool setData (const QModelIndex & index, const QVariant & value, int role);
+ Qt::ItemFlags flags (const QModelIndex & parent) const;
};
} // namespace audqt
diff --git a/src/libaudqt/prefs-widget.cc b/src/libaudqt/prefs-widget.cc
index 096809b..c67280f 100644
--- a/src/libaudqt/prefs-widget.cc
+++ b/src/libaudqt/prefs-widget.cc
@@ -116,9 +116,7 @@ IntegerWidget::IntegerWidget (const PreferencesWidget * parent, const char * dom
HookableWidget (parent, domain),
m_spinner (new QSpinBox)
{
- auto layout = new QHBoxLayout (this);
- layout->setContentsMargins (0, 0, 0, 0);
- layout->setSpacing (4);
+ auto layout = make_hbox (this);
if (parent->label)
layout->addWidget (new QLabel (translate_str (parent->label, domain)));
@@ -155,9 +153,7 @@ DoubleWidget::DoubleWidget (const PreferencesWidget * parent, const char * domai
HookableWidget (parent, domain),
m_spinner (new QDoubleSpinBox)
{
- auto layout = new QHBoxLayout (this);
- layout->setContentsMargins (0, 0, 0, 0);
- layout->setSpacing (4);
+ auto layout = make_hbox (this);
if (parent->label)
layout->addWidget (new QLabel (translate_str (parent->label, domain)));
@@ -194,9 +190,7 @@ StringWidget::StringWidget (const PreferencesWidget * parent, const char * domai
HookableWidget (parent, domain),
m_lineedit (new QLineEdit)
{
- auto layout = new QHBoxLayout (this);
- layout->setContentsMargins (0, 0, 0, 0);
- layout->setSpacing (4);
+ auto layout = make_hbox (this);
if (parent->label)
layout->addWidget (new QLabel (translate_str (parent->label, domain)));
@@ -224,9 +218,7 @@ ComboBoxWidget::ComboBoxWidget (const PreferencesWidget * parent, const char * d
HookableWidget (parent, domain),
m_combobox (new QComboBox)
{
- auto layout = new QHBoxLayout (this);
- layout->setContentsMargins (0, 0, 0, 0);
- layout->setSpacing (4);
+ auto layout = make_hbox (this);
if (parent->label)
layout->addWidget (new QLabel (translate_str (parent->label, domain)));
@@ -324,12 +316,9 @@ BoxWidget::BoxWidget (const PreferencesWidget * parent, const char * domain, boo
{
QBoxLayout * layout;
if (parent->data.box.horizontal)
- layout = new QHBoxLayout (this);
+ layout = make_hbox (this, sizes.TwoPt);
else
- layout = new QVBoxLayout (this);
-
- layout->setContentsMargins (0, 0, 0, 0);
- layout->setSpacing (4);
+ layout = make_vbox (this, sizes.TwoPt);
prefs_populate (layout, parent->data.box.widgets, domain);
@@ -341,10 +330,7 @@ BoxWidget::BoxWidget (const PreferencesWidget * parent, const char * domain, boo
TableWidget::TableWidget (const PreferencesWidget * parent, const char * domain)
{
// TODO: proper table layout
- auto layout = new QVBoxLayout (this);
- layout->setContentsMargins (0, 0, 0, 0);
- layout->setSpacing (4);
-
+ auto layout = make_vbox (this, sizes.TwoPt);
prefs_populate (layout, parent->data.table.widgets, domain);
}
@@ -353,10 +339,9 @@ NotebookWidget::NotebookWidget (const PreferencesWidget * parent, const char * d
for (const NotebookTab & tab : parent->data.notebook.tabs)
{
auto widget = new QWidget (this);
- auto layout = new QVBoxLayout (widget);
- layout->setContentsMargins (4, 4, 4, 4);
- layout->setSpacing (4);
+ widget->setContentsMargins (margins.FourPt);
+ auto layout = make_vbox (widget, sizes.TwoPt);
prefs_populate (layout, tab.widgets, domain);
layout->addStretch (1);
diff --git a/src/libaudqt/prefs-window.cc b/src/libaudqt/prefs-window.cc
index c11e133..19f5dc1 100644
--- a/src/libaudqt/prefs-window.cc
+++ b/src/libaudqt/prefs-window.cc
@@ -19,6 +19,7 @@
*/
#include <QAction>
+#include <QCheckBox>
#include <QComboBox>
#include <QDialog>
#include <QDialogButtonBox>
@@ -55,13 +56,63 @@
namespace audqt {
-struct Category {
- const char * icon_path;
- const char * name;
+class PrefsWindow : public QDialog
+{
+public:
+ static PrefsWindow * get_instance () {
+ if (! instance)
+ (void) new PrefsWindow;
+ return instance;
+ }
+
+ static void destroy_instance () {
+ if (instance)
+ delete instance;
+ }
+
+ static ArrayRef<ComboItem> get_output_combo () {
+ return {instance->output_combo_elements.begin (),
+ instance->output_combo_elements.len ()};
+ }
+
+ static int output_combo_selected;
+ static void output_combo_changed () { instance->output_change (); }
+
+ static void * get_output_config_button () { return instance->output_config_button; }
+ static void * get_output_about_button () { return instance->output_about_button; }
+
+ static void * get_record_checkbox () { return instance->record_checkbox; }
+ static void * get_record_config_button () { return instance->record_config_button; }
+ static void * get_record_about_button () { return instance->record_about_button; }
+
+private:
+ static PrefsWindow * instance;
+
+ PrefsWindow ();
+ ~PrefsWindow () { instance = nullptr; }
+
+ Index<ComboItem> output_combo_elements;
+ QPushButton * output_config_button, * output_about_button;
+
+ QCheckBox * record_checkbox;
+ QPushButton * record_config_button, * record_about_button;
+
+ void output_setup ();
+ void output_change ();
+
+ void record_setup ();
+ void record_update ();
+
+ const HookReceiver<PrefsWindow>
+ record_hook {"enable record", this, & PrefsWindow::record_update};
};
-struct PluginCategory {
- PluginType type;
+/* static data */
+PrefsWindow * PrefsWindow::instance = nullptr;
+int PrefsWindow::output_combo_selected;
+
+struct Category {
+ const char * icon_path;
const char * name;
};
@@ -89,28 +140,20 @@ static const Category categories[] = {
{ "plugins.png", N_("Plugins") }
};
-static const PluginCategory plugin_categories[] = {
- { PluginType::General, N_("General") },
- { PluginType::Effect, N_("Effect") },
- { PluginType::Vis, N_("Visualization") },
- { PluginType::Input, N_("Input") },
- { PluginType::Playlist, N_("Playlist") },
- { PluginType::Transport, N_("Transport") }
-};
-
static const TitleFieldTag title_field_tags[] = {
- { N_("Artist") , "${artist}" },
- { N_("Album") , "${album}" },
- { N_("Title") , "${title}" },
+ { N_("Artist") , "${artist}" },
+ { N_("Album") , "${album}" },
+ { N_("Album Artist"), "${album-artist}" },
+ { N_("Title") , "${title}" },
{ N_("Track number"), "${track-number}" },
- { N_("Genre") , "${genre}" },
- { N_("File name") , "${file-name}" },
- { N_("File path") , "${file-path}" },
- { N_("Date") , "${date}" },
- { N_("Year") , "${year}" },
- { N_("Comment") , "${comment}" },
- { N_("Codec") , "${codec}" },
- { N_("Quality") , "${quality}" }
+ { N_("Genre") , "${genre}" },
+ { N_("File name") , "${file-name}" },
+ { N_("File path") , "${file-path}" },
+ { N_("Date") , "${date}" },
+ { N_("Year") , "${year}" },
+ { N_("Comment") , "${comment}" },
+ { N_("Codec") , "${codec}" },
+ { N_("Quality") , "${quality}" }
};
static const ComboItem chardet_detector_presets[] = {
@@ -136,6 +179,19 @@ static const ComboItem bitdepth_elements[] = {
ComboItem (N_("Floating point"), 0)
};
+static const ComboItem record_elements[] = {
+ ComboItem (N_("As decoded"), (int) OutputStream::AsDecoded),
+ ComboItem (N_("After applying ReplayGain"), (int) OutputStream::AfterReplayGain),
+ ComboItem (N_("After applying effects"), (int) OutputStream::AfterEffects),
+ ComboItem (N_("After applying equalization"), (int) OutputStream::AfterEqualizer)
+};
+
+static const ComboItem replaygainmode_elements[] = {
+ ComboItem (N_("Track"), (int) ReplayGainMode::Track),
+ ComboItem (N_("Album"), (int) ReplayGainMode::Album),
+ ComboItem (N_("Based on shuffle"), (int) ReplayGainMode::Automatic)
+};
+
static Index<ComboItem> iface_combo_elements;
static int iface_combo_selected;
static QWidget * iface_prefs_box;
@@ -145,30 +201,28 @@ static void iface_combo_changed ();
static void * iface_create_prefs_box ();
static const PreferencesWidget appearance_page_widgets[] = {
- WidgetLabel (N_("<b>Interface Settings</b>")),
- WidgetCombo (N_("Interface plugin:"),
+ WidgetCombo (N_("Interface:"),
WidgetInt (iface_combo_selected, iface_combo_changed),
{0, iface_combo_fill}),
+ WidgetSeparator ({true}),
WidgetCustomQt (iface_create_prefs_box)
};
-static Index<ComboItem> output_combo_elements;
-static int output_combo_selected;
-static QPushButton * output_config_button;
-static QPushButton * output_about_button;
-
-static ArrayRef<ComboItem> output_combo_fill ();
-static void output_combo_changed ();
-static void * output_create_config_button ();
-static void * output_create_about_button ();
static void output_bit_depth_changed ();
static const PreferencesWidget output_combo_widgets[] = {
WidgetCombo (N_("Output plugin:"),
- WidgetInt (output_combo_selected, output_combo_changed, "audqt update output combo"),
- {0, output_combo_fill}),
- WidgetCustomQt (output_create_config_button),
- WidgetCustomQt (output_create_about_button)
+ WidgetInt (PrefsWindow::output_combo_selected,
+ PrefsWindow::output_combo_changed,
+ "audqt update output combo"),
+ {0, PrefsWindow::get_output_combo}),
+ WidgetCustomQt (PrefsWindow::get_output_config_button),
+ WidgetCustomQt (PrefsWindow::get_output_about_button)
+};
+
+static const PreferencesWidget record_buttons[] = {
+ WidgetCustomQt (PrefsWindow::get_record_config_button),
+ WidgetCustomQt (PrefsWindow::get_record_about_button)
};
static const PreferencesWidget gain_table[] = {
@@ -193,11 +247,19 @@ static const PreferencesWidget audio_page_widgets[] = {
WidgetBool (0, "soft_clipping")),
WidgetCheck (N_("Use software volume control (not recommended)"),
WidgetBool (0, "software_volume_control")),
+ WidgetLabel (N_("<b>Recording Settings</b>")),
+ WidgetCustomQt (PrefsWindow::get_record_checkbox),
+ WidgetBox ({{record_buttons}, true},
+ WIDGET_CHILD),
+ WidgetCombo (N_("Record stream:"),
+ WidgetInt (0, "record_stream"),
+ {{record_elements}}),
WidgetLabel (N_("<b>ReplayGain</b>")),
WidgetCheck (N_("Enable ReplayGain"),
WidgetBool (0, "enable_replay_gain")),
- WidgetCheck (N_("Album mode"),
- WidgetBool (0, "replay_gain_album"),
+ WidgetCombo (N_("Mode:"),
+ WidgetInt (0, "replay_gain_mode"),
+ {{replaygainmode_elements}},
WIDGET_CHILD),
WidgetCheck (N_("Prevent clipping (recommended)"),
WidgetBool (0, "enable_clipping_prevention"),
@@ -336,6 +398,8 @@ static void * create_titlestring_table ()
{
QWidget * w = new QWidget;
QGridLayout * l = new QGridLayout (w);
+ l->setContentsMargins (0, 0, 0, 0);
+ l->setSpacing (sizes.TwoPt);
QLabel * lbl = new QLabel (_("Title format:"), w);
l->addWidget (lbl, 0, 0);
@@ -362,13 +426,12 @@ static void * create_titlestring_table ()
cbox->setCurrentIndex (i);
}
- QObject::connect (le, &QLineEdit::textChanged, [=] (const QString & text) {
+ QObject::connect (le, & QLineEdit::textChanged, [] (const QString & text) {
aud_set_str (nullptr, "generic_title_format", text.toUtf8 ().data ());
});
- QObject::connect (cbox,
- static_cast <void (QComboBox::*) (int)> (&QComboBox::currentIndexChanged),
- [=] (int idx) {
+ void (QComboBox::* signal) (int) = & QComboBox::currentIndexChanged;
+ QObject::connect (cbox, signal, [le] (int idx) {
if (idx < TITLESTRING_NPRESETS)
le->setText (titlestring_presets [idx]);
});
@@ -377,7 +440,6 @@ static void * create_titlestring_table ()
QPushButton * btn_mnu = new QPushButton (w);
btn_mnu->setFixedWidth (btn_mnu->sizeHint ().height ());
btn_mnu->setIcon (QIcon::fromTheme ("list-add"));
- btn_mnu->setIconSize (QSize (16, 16));
l->addWidget (btn_mnu, 1, 2);
QMenu * mnu_fields = new QMenu (w);
@@ -419,10 +481,7 @@ static void iface_fill_prefs_box ()
Plugin * header = (Plugin *) aud_plugin_get_header (aud_plugin_get_current (PluginType::Iface));
if (header && header->info.prefs)
{
- QVBoxLayout * vbox = new QVBoxLayout (iface_prefs_box);
-
- vbox->setContentsMargins (0, 0, 0, 0);
- vbox->setSpacing (4);
+ auto vbox = make_vbox (iface_prefs_box, sizes.TwoPt);
prefs_populate (vbox, header->info.prefs->widgets, header->info.domain);
}
}
@@ -449,8 +508,8 @@ static ArrayRef<ComboItem> iface_combo_fill ()
if (! iface_combo_elements.len ())
{
iface_combo_elements = fill_plugin_combo (PluginType::Iface);
- iface_combo_selected = aud_plugin_list (PluginType::Iface)
- .find (aud_plugin_get_current (PluginType::Iface));
+ iface_combo_selected = aud_plugin_list (PluginType::Iface).
+ find (aud_plugin_get_current (PluginType::Iface));
}
return {iface_combo_elements.begin (), iface_combo_elements.len ()};
@@ -463,64 +522,6 @@ static void * iface_create_prefs_box ()
return iface_prefs_box;
}
-static void output_combo_changed ()
-{
- auto & list = aud_plugin_list (PluginType::Output);
- PluginHandle * plugin = list[output_combo_selected];
-
- if (aud_plugin_enable (plugin, true))
- {
- output_config_button->setEnabled (aud_plugin_has_configure (plugin));
- output_about_button->setEnabled (aud_plugin_has_about (plugin));
- }
- else
- {
- /* set combo box back to current output */
- output_combo_selected = list.find (aud_plugin_get_current (PluginType::Output));
- hook_call ("audqt update output combo", nullptr);
- }
-}
-
-static void * output_create_config_button ()
-{
- bool enabled = aud_plugin_has_configure (aud_plugin_get_current (PluginType::Output));
-
- output_config_button = new QPushButton (translate_str (N_("_Settings")));
- output_config_button->setEnabled (enabled);
-
- QObject::connect (output_config_button, & QAbstractButton::clicked, [=] (bool) {
- plugin_prefs (aud_plugin_get_current (PluginType::Output));
- });
-
- return output_config_button;
-}
-
-static void * output_create_about_button ()
-{
- bool enabled = aud_plugin_has_about (aud_plugin_get_current (PluginType::Output));
-
- output_about_button = new QPushButton (translate_str (N_("_About")));
- output_about_button->setEnabled (enabled);
-
- QObject::connect (output_about_button, &QAbstractButton::clicked, [=] (bool) {
- plugin_about (aud_plugin_get_current (PluginType::Output));
- });
-
- return output_about_button;
-}
-
-static ArrayRef<ComboItem> output_combo_fill ()
-{
- if (! output_combo_elements.len ())
- {
- output_combo_elements = fill_plugin_combo (PluginType::Output);
- output_combo_selected = aud_plugin_list (PluginType::Output)
- .find (aud_plugin_get_current (PluginType::Output));
- }
-
- return {output_combo_elements.begin (), output_combo_elements.len ()};
-}
-
static void output_bit_depth_changed ()
{
aud_output_reset (OutputReset::ReopenStream);
@@ -529,91 +530,81 @@ static void output_bit_depth_changed ()
static void create_category (QStackedWidget * notebook, ArrayRef<PreferencesWidget> widgets)
{
QWidget * w = new QWidget;
- QVBoxLayout * vbox = new QVBoxLayout (w);
-
- vbox->setContentsMargins (0, 0, 0, 0);
- vbox->setSpacing (4);
+ auto vbox = make_vbox (w, sizes.TwoPt);
prefs_populate (vbox, widgets, nullptr);
vbox->addStretch (1);
notebook->addWidget (w);
}
-static void create_plugin_category_page (PluginType category_id, const char * category_name, QTabWidget * parent)
+static QTreeView * s_plugin_view;
+static PluginListModel * s_plugin_model;
+
+static void create_plugin_category (QStackedWidget * parent)
{
- QTreeView * view = new QTreeView;
- QHeaderView * header = view->header ();
+ s_plugin_view = new QTreeView (parent);
+ s_plugin_model = new PluginListModel (s_plugin_view);
+
+ s_plugin_view->setModel (s_plugin_model);
+ s_plugin_view->setSelectionMode (QTreeView::NoSelection);
- view->setIndentation (0);
- view->setModel (new PluginListModel (view, category_id));
- view->setSelectionMode (view->NoSelection);
+ auto header = s_plugin_view->header ();
header->hide ();
header->setSectionResizeMode (header->ResizeToContents);
header->setStretchLastSection (false);
- parent->addTab (view, category_name);
+ parent->addWidget (s_plugin_view);
- QObject::connect (view, & QAbstractItemView::clicked,
- [category_id] (const QModelIndex & index)
+ QObject::connect (s_plugin_view, & QAbstractItemView::clicked, [] (const QModelIndex & index)
{
- int row = index.row ();
- auto & list = aud_plugin_list (category_id);
-
- if (row < 0 || row >= list.len () || ! aud_plugin_get_enabled (list[row]))
+ auto p = s_plugin_model->pluginForIndex (index);
+ if (! p)
return;
switch (index.column ())
{
case PluginListModel::AboutColumn:
- plugin_about (list[row]);
+ plugin_about (p);
break;
case PluginListModel::SettingsColumn:
- plugin_prefs (list[row]);
+ plugin_prefs (p);
break;
}
});
}
-static QTabWidget * plugin_tabs = nullptr;
-
-static void create_plugin_category (QStackedWidget * parent)
-{
- plugin_tabs = new QTabWidget;
-
- for (const PluginCategory & w : plugin_categories)
- {
- create_plugin_category_page (w.type, _(w.name), plugin_tabs);
- }
-
- parent->addWidget (plugin_tabs);
-}
-
-static QDialog * s_prefswin = nullptr;
static QStackedWidget * s_category_notebook = nullptr;
-static void create_prefs_window ()
+PrefsWindow::PrefsWindow () :
+ output_combo_elements (fill_plugin_combo (PluginType::Output)),
+ output_config_button (new QPushButton (translate_str (N_("_Settings")))),
+ output_about_button (new QPushButton (translate_str (N_("_About")))),
+ record_checkbox (new QCheckBox),
+ record_config_button (new QPushButton (translate_str (N_("_Settings")))),
+ record_about_button (new QPushButton (translate_str (N_("_About"))))
{
- s_prefswin = new QDialog;
- s_prefswin->setWindowTitle (_("Audacious Settings"));
- s_prefswin->setAttribute (Qt::WA_DeleteOnClose);
-
- QObject::connect (s_prefswin, & QObject::destroyed, [] () {
- s_prefswin = nullptr;
- });
+ /* initialize static data */
+ instance = this;
+ output_combo_selected = aud_plugin_list (PluginType::Output)
+ .find (aud_plugin_get_current (PluginType::Output));
- QVBoxLayout * vbox_parent = new QVBoxLayout (s_prefswin);
- vbox_parent->setSpacing (0);
- vbox_parent->setContentsMargins (0, 0, 0, 0);
+ setAttribute (Qt::WA_DeleteOnClose);
+ setWindowTitle (_("Audacious Settings"));
+ setContentsMargins (0, 0, 0, 0);
QToolBar * toolbar = new QToolBar;
toolbar->setToolButtonStyle (Qt::ToolButtonTextUnderIcon);
- vbox_parent->addWidget (toolbar);
QWidget * child = new QWidget;
- QVBoxLayout * child_vbox = new QVBoxLayout (child);
+ child->setContentsMargins (margins.FourPt);
+
+ auto vbox_parent = make_vbox (this);
+ vbox_parent->addWidget (toolbar);
vbox_parent->addWidget (child);
+ auto child_vbox = make_vbox (child);
+
s_category_notebook = new QStackedWidget;
child_vbox->addWidget (s_category_notebook);
@@ -628,9 +619,9 @@ static void create_prefs_window ()
bbox->button (QDialogButtonBox::Close)->setText (translate_str (N_("_Close")));
child_vbox->addWidget (bbox);
- QObject::connect (bbox, & QDialogButtonBox::rejected, s_prefswin, & QObject::deleteLater);
+ QObject::connect (bbox, & QDialogButtonBox::rejected, this, & QObject::deleteLater);
- QSignalMapper * mapper = new QSignalMapper (s_prefswin);
+ QSignalMapper * mapper = new QSignalMapper (this);
const char * data_dir = aud_get_path (AudPath::DataDir);
QObject::connect (mapper, static_cast <void (QSignalMapper::*)(int)>(&QSignalMapper::mapped),
@@ -642,24 +633,101 @@ static void create_prefs_window ()
QAction * a = new QAction (ico, translate_str (categories[i].name), toolbar);
toolbar->addAction (a);
-
mapper->setMapping (a, i);
- QObject::connect (a, & QAction::triggered, mapper, static_cast <void (QSignalMapper::*)()>(& QSignalMapper::map));
+ void (QSignalMapper::* slot) () = & QSignalMapper::map;
+ QObject::connect (a, & QAction::triggered, mapper, slot);
}
+
+ output_setup ();
+ record_setup ();
+ record_update ();
}
-EXPORT void prefswin_show ()
+void PrefsWindow::output_setup ()
{
- if (! s_prefswin)
- create_prefs_window ();
+ auto p = aud_plugin_get_current (PluginType::Output);
- window_bring_to_front (s_prefswin);
+ output_config_button->setEnabled (aud_plugin_has_configure (p));
+ output_about_button->setEnabled (aud_plugin_has_about (p));
+
+ QObject::connect (output_config_button, & QPushButton::clicked, [] (bool) {
+ plugin_prefs (aud_plugin_get_current (PluginType::Output));
+ });
+
+ QObject::connect (output_about_button, & QPushButton::clicked, [] (bool) {
+ plugin_about (aud_plugin_get_current (PluginType::Output));
+ });
+}
+
+void PrefsWindow::output_change ()
+{
+ auto & list = aud_plugin_list (PluginType::Output);
+ auto p = list[output_combo_selected];
+
+ if (aud_plugin_enable (p, true))
+ {
+ output_config_button->setEnabled (aud_plugin_has_configure (p));
+ output_about_button->setEnabled (aud_plugin_has_about (p));
+ }
+ else
+ {
+ /* set combo box back to current output */
+ output_combo_selected = list.find (aud_plugin_get_current (PluginType::Output));
+ hook_call ("audqt update output combo", nullptr);
+ }
+}
+
+void PrefsWindow::record_setup ()
+{
+ QObject::connect (record_checkbox, & QCheckBox::clicked, [] (bool checked) {
+ aud_drct_enable_record (checked);
+ });
+
+ QObject::connect (record_config_button, & QPushButton::clicked, [] (bool) {
+ if (aud_drct_get_record_enabled ())
+ plugin_prefs (aud_drct_get_record_plugin ());
+ });
+
+ QObject::connect (record_about_button, & QPushButton::clicked, [] (bool) {
+ if (aud_drct_get_record_enabled ())
+ plugin_about (aud_drct_get_record_plugin ());
+ });
+}
+
+void PrefsWindow::record_update ()
+{
+ auto p = aud_drct_get_record_plugin ();
+
+ if (p)
+ {
+ bool enabled = aud_drct_get_record_enabled ();
+ auto text = str_printf (_("Enable audio stream recording with %s"), aud_plugin_get_name (p));
+
+ record_checkbox->setEnabled (true);
+ record_checkbox->setText ((const char *) text);
+ record_checkbox->setChecked (enabled);
+ record_config_button->setEnabled (enabled && aud_plugin_has_configure (p));
+ record_about_button->setEnabled (enabled && aud_plugin_has_about (p));
+ }
+ else
+ {
+ record_checkbox->setEnabled (false);
+ record_checkbox->setText (_("No audio recording plugin available"));
+ record_checkbox->setChecked (false);
+ record_config_button->setEnabled (false);
+ record_about_button->setEnabled (false);
+ }
+}
+
+EXPORT void prefswin_show ()
+{
+ window_bring_to_front (PrefsWindow::get_instance ());
}
EXPORT void prefswin_hide ()
{
- delete s_prefswin;
+ PrefsWindow::destroy_instance ();
}
EXPORT void prefswin_show_page (int id, bool show)
@@ -667,35 +735,34 @@ EXPORT void prefswin_show_page (int id, bool show)
if (id < 0 || id > CATEGORY_COUNT)
return;
- if (! s_prefswin)
- create_prefs_window ();
-
+ auto win = PrefsWindow::get_instance ();
s_category_notebook->setCurrentIndex (id);
if (show)
- window_bring_to_front (s_prefswin);
+ window_bring_to_front (win);
}
EXPORT void prefswin_show_plugin_page (PluginType type)
{
- if (! s_prefswin)
- create_prefs_window ();
-
if (type == PluginType::Iface)
- return prefswin_show_page (CATEGORY_APPEARANCE);
+ prefswin_show_page (CATEGORY_APPEARANCE);
else if (type == PluginType::Output)
- return prefswin_show_page (CATEGORY_AUDIO);
+ prefswin_show_page (CATEGORY_AUDIO);
else
{
prefswin_show_page (CATEGORY_PLUGINS, false);
- for (const PluginCategory & category : plugin_categories)
+ s_plugin_view->collapseAll ();
+
+ auto index = s_plugin_model->indexForType (type);
+ if (index.isValid ())
{
- if (category.type == type)
- plugin_tabs->setCurrentIndex (& category - plugin_categories);
+ s_plugin_view->expand (index);
+ s_plugin_view->scrollTo (index, QTreeView::PositionAtTop);
+ s_plugin_view->setCurrentIndex (index);
}
- window_bring_to_front (s_prefswin);
+ window_bring_to_front (PrefsWindow::get_instance ());
}
}
diff --git a/src/libaudqt/queue-manager.cc b/src/libaudqt/queue-manager.cc
index 59dbc08..11301b3 100644
--- a/src/libaudqt/queue-manager.cc
+++ b/src/libaudqt/queue-manager.cc
@@ -60,14 +60,14 @@ QVariant QueueManagerModel::data (const QModelIndex & index, int role) const
{
if (role == Qt::DisplayRole)
{
- int list = aud_playlist_get_active ();
- int entry = aud_playlist_queue_get_entry (list, index.row ());
+ auto list = Playlist::active_playlist ();
+ int entry = list.queue_get_entry (index.row ());
if (index.column () == 0)
return entry + 1;
else
{
- Tuple tuple = aud_playlist_entry_get_tuple (list, entry, Playlist::NoWait);
+ Tuple tuple = list.entry_tuple (entry, Playlist::NoWait);
return QString ((const char *) tuple.get_str (Tuple::FormattedTitle));
}
}
@@ -79,8 +79,8 @@ QVariant QueueManagerModel::data (const QModelIndex & index, int role) const
void QueueManagerModel::update (QItemSelectionModel * sel)
{
- int list = aud_playlist_get_active ();
- int rows = aud_playlist_queue_count (list);
+ auto list = Playlist::active_playlist ();
+ int rows = list.n_queued ();
int keep = aud::min (rows, m_rows);
m_in_update = true;
@@ -107,7 +107,7 @@ void QueueManagerModel::update (QItemSelectionModel * sel)
for (int i = 0; i < rows; i ++)
{
- if (aud_playlist_entry_get_selected (list, aud_playlist_queue_get_entry (list, i)))
+ if (list.entry_selected (list.queue_get_entry (i)))
sel->select (createIndex (i, 0), sel->Select | sel->Rows);
else
sel->select (createIndex (i, 0), sel->Deselect | sel->Rows);
@@ -122,15 +122,13 @@ void QueueManagerModel::selectionChanged (const QItemSelection & selected,
if (m_in_update)
return;
- int list = aud_playlist_get_active ();
+ auto list = Playlist::active_playlist ();
for (auto & index : selected.indexes ())
- aud_playlist_entry_set_selected (list,
- aud_playlist_queue_get_entry (list, index.row ()), true);
+ list.select_entry (list.queue_get_entry (index.row ()), true);
for (auto & index : deselected.indexes ())
- aud_playlist_entry_set_selected (list,
- aud_playlist_queue_get_entry (list, index.row ()), false);
+ list.select_entry (list.queue_get_entry (index.row ()), false);
}
class QueueManagerDialog : public QDialog
@@ -139,7 +137,6 @@ public:
QueueManagerDialog (QWidget * parent = nullptr);
private:
- QVBoxLayout m_layout;
QTreeView m_treeview;
QDialogButtonBox m_buttonbox;
QPushButton m_btn_unqueue;
@@ -157,6 +154,9 @@ private:
QueueManagerDialog::QueueManagerDialog (QWidget * parent) :
QDialog (parent)
{
+ setWindowTitle (_("Queue Manager"));
+ setContentsMargins (margins.TwoPt);
+
m_btn_unqueue.setText (translate_str (N_("_Unqueue")));
m_btn_close.setText (translate_str (N_("_Close")));
@@ -166,39 +166,37 @@ QueueManagerDialog::QueueManagerDialog (QWidget * parent) :
m_buttonbox.addButton (& m_btn_close, QDialogButtonBox::AcceptRole);
m_buttonbox.addButton (& m_btn_unqueue, QDialogButtonBox::AcceptRole);
- m_layout.addWidget (& m_treeview);
- m_layout.addWidget (& m_buttonbox);
+ auto layout = make_vbox (this);
+ layout->addWidget (& m_treeview);
+ layout->addWidget (& m_buttonbox);
m_treeview.setIndentation (0);
m_treeview.setModel (& m_model);
m_treeview.setSelectionMode (QAbstractItemView::ExtendedSelection);
m_treeview.setHeaderHidden (true);
- setLayout (& m_layout);
- setWindowTitle (_("Queue Manager"));
-
update ();
connect (m_treeview.selectionModel (),
& QItemSelectionModel::selectionChanged, & m_model,
& QueueManagerModel::selectionChanged);
- resize (500, 250);
+ resize (4 * sizes.OneInch, 3 * sizes.OneInch);
}
void QueueManagerDialog::removeSelected ()
{
- int list = aud_playlist_get_active ();
- int count = aud_playlist_queue_count (list);
+ auto list = Playlist::active_playlist ();
+ int count = list.n_queued ();
for (int i = 0; i < count; )
{
- int entry = aud_playlist_queue_get_entry (list, i);
+ int entry = list.queue_get_entry (i);
- if (aud_playlist_entry_get_selected (list, entry))
+ if (list.entry_selected (entry))
{
- aud_playlist_queue_delete (list, i, 1);
- aud_playlist_entry_set_selected (list, entry, false);
+ list.queue_remove (i);
+ list.select_entry (entry, false);
count --;
}
else
diff --git a/src/libaudqt/url-opener.cc b/src/libaudqt/url-opener.cc
index 37b6e14..49645f0 100644
--- a/src/libaudqt/url-opener.cc
+++ b/src/libaudqt/url-opener.cc
@@ -51,12 +51,13 @@ static QDialog * buildUrlDialog (bool open)
auto dialog = new QDialog;
dialog->setWindowTitle (title);
- dialog->setMinimumWidth (325);
+ dialog->setContentsMargins (margins.EightPt);
auto label = new QLabel (_("Enter URL:"), dialog);
auto combobox = new QComboBox (dialog);
combobox->setEditable (true);
+ combobox->setMinimumContentsLength (50);
auto button1 = new QPushButton (translate_str (verb), dialog);
button1->setIcon (QIcon::fromTheme (icon));
@@ -67,12 +68,11 @@ static QDialog * buildUrlDialog (bool open)
auto buttonbox = new QDialogButtonBox (dialog);
buttonbox->addButton (button1, QDialogButtonBox::AcceptRole);
buttonbox->addButton (button2, QDialogButtonBox::RejectRole);
- buttonbox->setContentsMargins (0, 10, 0, 0);
- auto layout = new QVBoxLayout (dialog);
- layout->setSizeConstraint (QLayout::SetFixedSize);
+ auto layout = make_vbox (dialog);
layout->addWidget (label);
layout->addWidget (combobox);
+ layout->addStretch (1);
layout->addWidget (buttonbox);
for (int i = 0;; i ++)
diff --git a/src/libaudqt/util.cc b/src/libaudqt/util.cc
index 9ca03b0..14ed33c 100644
--- a/src/libaudqt/util.cc
+++ b/src/libaudqt/util.cc
@@ -20,6 +20,7 @@
#include <stdlib.h>
#include <QApplication>
+#include <QDesktopWidget>
#include <QPushButton>
#include <QVBoxLayout>
@@ -27,6 +28,7 @@
#include <libaudcore/i18n.h>
#include <libaudcore/runtime.h>
+#include "libaudqt-internal.h"
#include "libaudqt.h"
namespace audqt {
@@ -34,6 +36,12 @@ namespace audqt {
static int init_count;
static QApplication * qapp;
+static PixelSizes sizes_local;
+static PixelMargins margins_local;
+
+EXPORT const PixelSizes & sizes = sizes_local;
+EXPORT const PixelMargins & margins = margins_local;
+
EXPORT void init ()
{
if (init_count ++ || qapp)
@@ -53,6 +61,18 @@ EXPORT void init ()
qapp->setApplicationName (_("Audacious"));
qapp->setWindowIcon (QIcon::fromTheme (app_name));
+
+ auto desktop = qapp->desktop ();
+ sizes_local.OneInch = aud::max (96, (desktop->logicalDpiX () + desktop->logicalDpiY ()) / 2);
+ sizes_local.TwoPt = aud::rescale (2, 72, sizes_local.OneInch);
+ sizes_local.FourPt = aud::rescale (4, 72, sizes_local.OneInch);
+ sizes_local.EightPt = aud::rescale (8, 72, sizes_local.OneInch);
+
+ margins_local.TwoPt = QMargins (sizes.TwoPt, sizes.TwoPt, sizes.TwoPt, sizes.TwoPt);
+ margins_local.FourPt = QMargins (sizes.FourPt, sizes.FourPt, sizes.FourPt, sizes.FourPt);
+ margins_local.EightPt = QMargins (sizes.EightPt, sizes.EightPt, sizes.EightPt, sizes.EightPt);
+
+ log_init ();
}
EXPORT void run ()
@@ -76,6 +96,24 @@ EXPORT void cleanup ()
log_inspector_hide ();
prefswin_hide ();
queue_manager_hide ();
+
+ log_cleanup ();
+}
+
+EXPORT QHBoxLayout * make_hbox (QWidget * parent, int spacing)
+{
+ auto layout = new QHBoxLayout (parent);
+ layout->setContentsMargins (0, 0, 0, 0);
+ layout->setSpacing (spacing);
+ return layout;
+}
+
+EXPORT QVBoxLayout * make_vbox (QWidget * parent, int spacing)
+{
+ auto layout = new QVBoxLayout (parent);
+ layout->setContentsMargins (0, 0, 0, 0);
+ layout->setSpacing (spacing);
+ return layout;
}
EXPORT void enable_layout (QLayout * layout, bool enabled)
diff --git a/src/libaudqt/volumebutton.cc b/src/libaudqt/volumebutton.cc
index ca25310..2668263 100644
--- a/src/libaudqt/volumebutton.cc
+++ b/src/libaudqt/volumebutton.cc
@@ -57,15 +57,15 @@ VolumeButton::VolumeButton (QWidget * parent) :
m_container = new QFrame (this, Qt::Popup);
m_container->setFrameShape (QFrame::StyledPanel);
- auto layout = new QVBoxLayout (m_container);
- layout->setSpacing (0);
- layout->setMargin (2);
-
m_slider = new QSlider (Qt::Vertical, this);
+ m_slider->setMinimumHeight (audqt::sizes.OneInch);
m_slider->setRange (0, 100);
m_slider->setSingleStep (2);
m_slider->setPageStep (20);
+ auto layout = make_vbox (m_container, sizes.TwoPt);
+ layout->setContentsMargins (margins.TwoPt);
+
layout->addWidget (newSliderButton (5));
layout->addWidget (m_slider);
layout->addWidget (newSliderButton (-5));
@@ -87,11 +87,11 @@ void VolumeButton::updateIcon (int val)
{
if (val == 0)
setIcon (QIcon::fromTheme ("audio-volume-muted"));
- else if (val > 0 && val < 35)
+ else if (val < 34)
setIcon (QIcon::fromTheme ("audio-volume-low"));
- else if (val >= 35 && val < 70)
+ else if (val < 67)
setIcon (QIcon::fromTheme ("audio-volume-medium"));
- else if (val >= 70)
+ else
setIcon (QIcon::fromTheme ("audio-volume-high"));
setToolTip (QString ("%1 %").arg (val));
diff --git a/src/libaudtag/id3/id3v24.cc b/src/libaudtag/id3/id3v24.cc
index c38f227..f8ab6e9 100644
--- a/src/libaudtag/id3/id3v24.cc
+++ b/src/libaudtag/id3/id3v24.cc
@@ -407,31 +407,22 @@ static bool write_frame (VFSFile & file, const GenericFrame & frame, int version
return true;
}
-struct WriteState {
- VFSFile & file;
- int version;
- int written_size;
-};
-
-static void write_frame_list (const String & key, FrameList & list, void * user)
+static int write_all_frames (VFSFile & file, FrameDict & dict, int version)
{
- WriteState * state = (WriteState *) user;
+ int written_size = 0;
- for (const GenericFrame & frame : list)
+ dict.iterate ([&] (const String & key, FrameList & list)
{
- int size;
- if (write_frame (state->file, frame, state->version, & size))
- state->written_size += size;
- }
-}
-
-static int write_all_frames (VFSFile & file, FrameDict & dict, int version)
-{
- WriteState state = {file, version, 0};
- dict.iterate (write_frame_list, & state);
+ for (const GenericFrame & frame : list)
+ {
+ int size;
+ if (write_frame (file, frame, version, & size))
+ written_size += size;
+ }
+ });
- AUDDBG ("Total frame bytes written = %d.\n", state.written_size);
- return state.written_size;
+ AUDDBG ("Total frame bytes written = %d.\n", written_size);
+ return written_size;
}
static bool write_header (VFSFile & file, int version, int size)