1
0
mirror of https://github.com/clangen/musikcube.git synced 2025-02-13 15:41:26 +00:00

Merge pull request from clangen/clangen/upgrades-12-2019

0.80.0 Fixes and Upgrades
This commit is contained in:
casey langen 2020-01-10 17:28:26 -08:00 committed by GitHub
commit c797e00c1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
184 changed files with 4835 additions and 1755 deletions
CMakeLists.txtarchive-macos.shmusikcube.spec
src
3rdparty
bin
win32_src/pdcurses
core
musikcube

@ -7,7 +7,7 @@ cmake_minimum_required(VERSION 3.0)
project(musikcube)
set (musikcube_VERSION_MAJOR 0)
set (musikcube_VERSION_MINOR 70)
set (musikcube_VERSION_MINOR 80)
set (musikcube_VERSION_PATCH 0)
set (musikcube_VERSION "${musikcube_VERSION_MAJOR}.${musikcube_VERSION_MINOR}.${musikcube_VERSION_PATCH}")
@ -103,15 +103,15 @@ add_subdirectory(src/plugins/stockencoders)
add_subdirectory(src/plugins/supereqdsp)
add_subdirectory(src/plugins/gmedecoder)
if (${FFMPEG_DECODER} MATCHES "false")
message(STATUS "[ffmpeg] decoder enabled = false")
if (${FFMPEG_ENABLED} MATCHES "false")
message(STATUS "[ffmpeg] enabled = false")
add_subdirectory(src/plugins/m4adecoder)
add_subdirectory(src/plugins/oggdecoder)
add_subdirectory(src/plugins/nomaddecoder)
add_subdirectory(src/plugins/flacdecoder)
add_dependencies(musikcube m4adecoder oggdecoder nomaddecoder flacdecoder)
else()
message(STATUS "[ffmpeg] decoder enabled = true")
message(STATUS "[ffmpeg] enabled = true")
add_subdirectory(src/plugins/ffmpegdecoder)
add_dependencies(musikcube ffmpegdecoder)
endif()
@ -309,7 +309,7 @@ if (GENERATE_DEB MATCHES "1")
set(DEPENDENCIES "libboost-thread${DEB_BOOST_VERSION},
libboost-system${DEB_BOOST_VERSION}, libboost-chrono${DEB_BOOST_VERSION}, libboost-filesystem${DEB_BOOST_VERSION}, libboost-date-time${DEB_BOOST_VERSION}, libmicrohttpd${DEB_MICROHTTPD_VERSION}, libcurl${DEB_LIBCURL_VERSION}, libogg0, libvorbis0a, libvorbisfile3, libncursesw5, libasound2, libpulse0, pulseaudio, libmp3lame0, libev4")
if (${FFMPEG_DECODER} MATCHES "false")
if (${FFMPEG_ENABLED} MATCHES "false")
set(DEPENDENCIES "${DEPENDENCIES}, libflac8, libfaad2")
else()
set(DEPENDENCIES "${DEPENDENCIES}, libavcodec-extra, libavutil${DEB_AVUTIL_VERSION}, libavformat${DEB_AVFORMAT_VERSION}, libswresample${DEB_SWRESAMPLE_VERSION}")

@ -10,7 +10,7 @@ fi
rm -rf bin/
./clean-nix.sh
cmake -DCMAKE_BUILD_TYPE=Release -DLINK_STATICALLY=true -DFFMPEG_DECODER=false .
cmake -DCMAKE_BUILD_TYPE=Release -DLINK_STATICALLY=true -DFFMPEG_ENABLED=false .
make -j4
DIRNAME="musikcube_macos_$VERSION"
@ -33,7 +33,7 @@ strip bin/musikcubed
strip bin/libmusikcore.dylib
strip bin/plugins/*.dylib
pushd bin/dist
pushd bin/dist
tar cvf musikcube_macos_$VERSION.tar $DIRNAME
bzip2 musikcube_macos_$VERSION.tar
popd

@ -1,6 +1,6 @@
%define name musikcube
%define build_timestamp %{lua: print(os.date("%Y%m%d"))}
%define version 0.70.0
%define version 0.80.0
Name: %{name}
Version: %{version}
Release: %{dist}

2
src/3rdparty/bin vendored

@ -1 +1 @@
Subproject commit b2fa669c7ff30009dca3a6d43e040b67cfc73f9e
Subproject commit 69c5c8daeda0853fd17b44e677357c9676fa8750

@ -2457,13 +2457,14 @@ INLINE int set_up_window( void)
debug_printf( "WindowTitle = '%ls'\n", WindowTitle);
#endif
get_default_sizes_from_registry( &n_default_columns, &n_default_rows, &xloc, &yloc,
&menu_shown);
if( PDC_n_rows > 2 && PDC_n_cols > 2)
if (PDC_n_rows > 2 && PDC_n_cols > 2)
{
n_default_columns = PDC_n_cols;
n_default_rows = PDC_n_rows;
n_default_rows = PDC_n_rows;
}
get_default_sizes_from_registry( &n_default_columns, &n_default_rows, &xloc, &yloc,
&menu_shown);
if( ttytype[1])
PDC_set_resize_limits( (unsigned char)ttytype[0],
(unsigned char)ttytype[1],

@ -29,11 +29,14 @@ set(CORE_SOURCES
./library/query/local/CategoryTrackListQuery.cpp
./library/query/local/DeletePlaylistQuery.cpp
./library/query/local/DirectoryTrackListQuery.cpp
./library/query/local/LyricsQuery.cpp
./library/query/local/MarkTrackPlayedQuery.cpp
./library/query/local/NowPlayingTrackListQuery.cpp
./library/query/local/PersistedPlayQueueQuery.cpp
./library/query/local/ReplayGainQuery.cpp
./library/query/local/SavePlaylistQuery.cpp
./library/query/local/SearchTrackListQuery.cpp
./library/query/local/SetTrackRatingQuery.cpp
./library/query/local/TrackMetadataQuery.cpp
./library/query/local/util/CategoryQueryUtil.cpp
./library/metadata/MetadataMap.cpp

@ -338,7 +338,10 @@ void GaplessTransport::OnPlayerFinished(Player* player) {
}
if (stopped) {
this->Stop();
/* note we call through to StopInternal() because we don't
want to stop the output immediately, it may still have some
trailing samples queued up */
this->StopInternal(false, false);
}
}

@ -39,6 +39,7 @@
#include <core/audio/MasterTransport.h>
#include <core/library/LocalLibraryConstants.h>
#include <core/library/track/Track.h>
#include <core/library/query/local/MarkTrackPlayedQuery.h>
#include <core/library/query/local/ReplayGainQuery.h>
#include <core/plugin/PluginFactory.h>
#include <core/runtime/MessageQueue.h>
@ -79,8 +80,9 @@ using Editor = PlaybackService::Editor;
#define MESSAGE_SEEK 1009
#define MESSAGE_RELOAD_OUTPUT 1010
#define MESSAGE_LOAD_PLAYBACK_CONTEXT 1011
#define MESSAGE_MARK_TRACK_PLAYED 1012
class StreamMessage : public Message {
class StreamMessage: public Message {
public:
StreamMessage(IMessageTarget* target, int eventType, const std::string& uri)
: Message(target, MESSAGE_STREAM_EVENT, eventType, 0) {
@ -101,6 +103,10 @@ class StreamMessage : public Message {
this->messageQueue.Post( \
musik::core::runtime::Message::Create(instance, type, user1, user2));
#define POST_DELAYED(instance, type, user1, user2, afterMs) \
this->messageQueue.Post( \
musik::core::runtime::Message::Create(instance, type, user1, user2), afterMs);
#define POST_STREAM_MESSAGE(instance, eventType, uri) \
this->messageQueue.Post( \
musik::core::runtime::IMessagePtr(new StreamMessage(instance, eventType, uri)));
@ -295,6 +301,9 @@ void PlaybackService::ProcessMessage(IMessage &message) {
playback::LoadPlaybackContext(appPrefs, library, *this);
this->InitRemotes();
}
else if (type == MESSAGE_MARK_TRACK_PLAYED) {
this->MarkTrackAsPlayed(message.UserData1()); /* UserData1 is a trackId */
}
else if (type == MESSAGE_STREAM_EVENT) {
StreamMessage* streamMessage = static_cast<StreamMessage*>(&message);
@ -461,9 +470,23 @@ void PlaybackService::NotifyRemotesModeChanged() {
void PlaybackService::OnTrackChanged(size_t pos, TrackPtr track) {
this->playingTrack = track;
this->TrackChanged(this->index, track);
this->messageQueue.Remove(this, MESSAGE_MARK_TRACK_PLAYED);
if (track && this->GetPlaybackState() == PlaybackPlaying) {
/* TODO: maybe consider folding Scrobble() the `MarkTrackAsPlayed` logic?
needs a bit more thought */
lastfm::Scrobble(track);
/* we consider a track to be played if (1) it enters the playing state and
it's less than 10 seconds long, or (2) it enters the playing state, and
remains playing for > 10 seconds */
double duration = this->transport->GetDuration();
if (duration > 0 && duration < 10.0) {
this->MarkTrackAsPlayed(track->GetId());
}
else {
POST_DELAYED(this, MESSAGE_MARK_TRACK_PLAYED, track->GetId(), 0, 10000LL);
}
}
for (auto it = remotes.begin(); it != remotes.end(); it++) {
@ -471,6 +494,10 @@ void PlaybackService::OnTrackChanged(size_t pos, TrackPtr track) {
}
}
void PlaybackService::MarkTrackAsPlayed(int64_t trackId) {
this->library->Enqueue(std::make_shared<MarkTrackPlayedQuery>(trackId));
}
bool PlaybackService::Next() {
if (transport->GetPlaybackState() == PlaybackStopped) {
return false;

@ -185,6 +185,7 @@ namespace musik { namespace core { namespace audio {
void PrepareNextTrack();
void InitRemotes();
void ResetRemotes();
void MarkTrackAsPlayed(int64_t trackId);
void PlayAt(size_t index, ITransport::StartMode mode);

@ -120,7 +120,7 @@ bool Stream::OpenStream(std::string uri) {
/* use our file stream abstraction to open the data at the
specified URI */
this->dataStream = DataStreamFactory::OpenSharedDataStream(uri.c_str());
this->dataStream = DataStreamFactory::OpenSharedDataStream(uri.c_str(), OpenFlags::Read);
if (!this->dataStream) {
musik::debug::error(TAG, "failed to open " + uri);

@ -191,11 +191,14 @@
<ClCompile Include="library\query\local\DeletePlaylistQuery.cpp" />
<ClCompile Include="library\query\local\DirectoryTrackListQuery.cpp" />
<ClCompile Include="library\query\local\GetPlaylistQuery.cpp" />
<ClCompile Include="library\query\local\LyricsQuery.cpp" />
<ClCompile Include="library\query\local\MarkTrackPlayedQuery.cpp" />
<ClCompile Include="library\query\local\NowPlayingTrackListQuery.cpp" />
<ClCompile Include="library\query\local\PersistedPlayQueueQuery.cpp" />
<ClCompile Include="library\query\local\ReplayGainQuery.cpp" />
<ClCompile Include="library\query\local\SavePlaylistQuery.cpp" />
<ClCompile Include="library\query\local\SearchTrackListQuery.cpp" />
<ClCompile Include="library\query\local\SetTrackRatingQuery.cpp" />
<ClCompile Include="library\query\local\TrackMetadataQuery.cpp" />
<ClCompile Include="library\query\local\util\CategoryQueryUtil.cpp" />
<ClCompile Include="library\track\IndexerTrack.cpp" />
@ -260,16 +263,20 @@
<ClInclude Include="library\query\local\DeletePlaylistQuery.h" />
<ClInclude Include="library\query\local\DirectoryTrackListQuery.h" />
<ClInclude Include="library\query\local\GetPlaylistQuery.h" />
<ClInclude Include="library\query\local\LyricsQuery.h" />
<ClInclude Include="library\query\local\MarkTrackPlayedQuery.h" />
<ClInclude Include="library\query\local\NowPlayingTrackListQuery.h" />
<ClInclude Include="library\query\local\PersistedPlayQueueQuery.h" />
<ClInclude Include="library\query\local\ReplayGainQuery.h" />
<ClInclude Include="library\query\local\SavePlaylistQuery.h" />
<ClInclude Include="library\query\local\SearchTrackListQuery.h" />
<ClInclude Include="library\query\local\SetTrackRatingQuery.h" />
<ClInclude Include="library\query\local\TrackListQueryBase.h" />
<ClInclude Include="Library\query\local\LocalQueryBase.h" />
<ClInclude Include="library\query\local\TrackMetadataQuery.h" />
<ClInclude Include="library\query\local\util\CategoryQueryUtil.h" />
<ClInclude Include="library\query\local\util\SdkWrappers.h" />
<ClInclude Include="library\query\local\util\TrackSort.h" />
<ClInclude Include="library\track\IndexerTrack.h" />
<ClInclude Include="library\track\LibraryTrack.h" />
<ClInclude Include="library\track\Track.h" />
@ -286,7 +293,9 @@
<ClInclude Include="sdk\constants.h" />
<ClInclude Include="sdk\HttpClient.h" />
<ClInclude Include="sdk\IAnalyzer.h" />
<ClInclude Include="sdk\IStreamingEncoder.h" />
<ClInclude Include="sdk\IBuffer.h" />
<ClInclude Include="sdk\IBlockingEncoder.h" />
<ClInclude Include="sdk\IDecoder.h" />
<ClInclude Include="sdk\IDecoderFactory.h" />
<ClInclude Include="sdk\IDevice.h" />

@ -229,6 +229,15 @@
<ClCompile Include="library\LocalMetadataProxy.cpp">
<Filter>src\library</Filter>
</ClCompile>
<ClCompile Include="library\query\local\LyricsQuery.cpp">
<Filter>src\library\query\local</Filter>
</ClCompile>
<ClCompile Include="library\query\local\MarkTrackPlayedQuery.cpp">
<Filter>src\library\query\local</Filter>
</ClCompile>
<ClCompile Include="library\query\local\SetTrackRatingQuery.cpp">
<Filter>src\library\query\local</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.hpp">
@ -555,5 +564,23 @@
<ClInclude Include="library\LocalMetadataProxy.h">
<Filter>src\library</Filter>
</ClInclude>
<ClInclude Include="sdk\IBlockingEncoder.h">
<Filter>src\sdk\audio</Filter>
</ClInclude>
<ClInclude Include="sdk\IStreamingEncoder.h">
<Filter>src\sdk\audio</Filter>
</ClInclude>
<ClInclude Include="library\query\local\LyricsQuery.h">
<Filter>src\library\query\local</Filter>
</ClInclude>
<ClInclude Include="library\query\local\MarkTrackPlayedQuery.h">
<Filter>src\library\query\local</Filter>
</ClInclude>
<ClInclude Include="library\query\local\util\TrackSort.h">
<Filter>src\library\query\local\util</Filter>
</ClInclude>
<ClInclude Include="library\query\local\SetTrackRatingQuery.h">
<Filter>src\library\query\local</Filter>
</ClInclude>
</ItemGroup>
</Project>

@ -45,7 +45,7 @@ namespace musik { namespace core { namespace db {
class Statement {
public:
Statement(const char* sql,Connection &connection);
Statement(const char* sql, Connection &connection);
Statement(const Statement&) = delete;
virtual ~Statement();

@ -63,7 +63,7 @@ DataStreamFactory* DataStreamFactory::Instance() {
return instance;
}
IDataStream* DataStreamFactory::OpenDataStream(const char* uri) {
IDataStream* DataStreamFactory::OpenDataStream(const char* uri, OpenFlags flags) {
typedef musik::core::PluginFactory::ReleaseDeleter<IDataStream> StreamDeleter;
if (uri) {
@ -73,7 +73,7 @@ IDataStream* DataStreamFactory::OpenDataStream(const char* uri) {
/* plugins get the first crack at the uri */
for (; it != DataStreamFactory::Instance()->dataStreamFactories.end(); it++) {
if ((*it)->CanRead(uri)) {
IDataStream* dataStream = (*it)->Open(uri);
IDataStream* dataStream = (*it)->Open(uri, flags);
if (dataStream) {
return dataStream;
@ -83,7 +83,7 @@ IDataStream* DataStreamFactory::OpenDataStream(const char* uri) {
/* no plugins accepted it? try to open as a local file */
IDataStream* regularFile = new LocalFileStream();
if (regularFile->Open(uri)) {
if (regularFile->Open(uri, flags)) {
return regularFile;
}
else {
@ -94,7 +94,7 @@ IDataStream* DataStreamFactory::OpenDataStream(const char* uri) {
return nullptr;
}
DataStreamPtr DataStreamFactory::OpenSharedDataStream(const char *uri) {
auto stream = OpenDataStream(uri);
DataStreamPtr DataStreamFactory::OpenSharedDataStream(const char *uri, OpenFlags flags) {
auto stream = OpenDataStream(uri, flags);
return stream ? DataStreamPtr(stream, StreamDeleter()) : DataStreamPtr();
}

@ -42,10 +42,11 @@ namespace musik { namespace core { namespace io {
class DataStreamFactory {
public:
typedef std::shared_ptr<musik::core::sdk::IDataStream> DataStreamPtr;
using DataStreamPtr = std::shared_ptr<musik::core::sdk::IDataStream>;
using OpenFlags = musik::core::sdk::OpenFlags;
static DataStreamPtr OpenSharedDataStream(const char *uri);
static musik::core::sdk::IDataStream* OpenDataStream(const char* uri);
static DataStreamPtr OpenSharedDataStream(const char *uri, OpenFlags flags);
static musik::core::sdk::IDataStream* OpenDataStream(const char* uri, OpenFlags flags);
private:
typedef std::vector<std::shared_ptr<musik::core::sdk::IDataStreamFactory> > DataStreamFactoryVector;

@ -65,33 +65,58 @@ bool LocalFileStream::Seekable() {
return true;
}
bool LocalFileStream::Open(const char *filename, unsigned int options) {
bool LocalFileStream::Open(const char *filename, OpenFlags flags) {
try {
this->uri = filename;
debug::info(TAG, "opening file: " + std::string(filename));
boost::filesystem::path file(filename);
bool exists = boost::filesystem::exists(file);
if (!boost::filesystem::exists(file)) {
debug::error(TAG, "open failed " + this->uri);
if (flags & OpenFlags::Read && !exists) {
debug::error(TAG, "open with OpenFlags::Read failed because file doesn't exist. " + this->uri);
return false;
}
if (!boost::filesystem::is_regular(file)) {
if (exists && !boost::filesystem::is_regular(file)) {
debug::error(TAG, "not a regular file" + this->uri);
return false;
}
this->filesize = (long)boost::filesystem::file_size(file);
boost::system::error_code ec;
this->filesize = (long) boost::filesystem::file_size(file, ec);
if (ec && flags & OpenFlags::Write) {
this->filesize = 0;
}
/* convert the OpenFlags bitmask to an fopen compatible string */
std::string openFlags = "";
if (flags & OpenFlags::Read) {
openFlags += "rb";
}
if (flags & OpenFlags::Write) {
if (openFlags.size() == 2) {
openFlags += "+";
}
else {
this->filesize = 0;
openFlags = "wb";
}
}
this->extension = file.extension().string();
#ifdef WIN32
std::wstring u16fn = u8to16(this->uri);
this->file = _wfopen(u16fn.c_str(), L"rb");
std::wstring u16flags = u8to16(openFlags);
this->file = _wfopen(u16fn.c_str(), u16flags.c_str());
#else
this->file = fopen(filename, "rb");
this->file = fopen(filename, openFlags.c_str());
#endif
if (this->file.load()) {
this->flags = flags;
return true;
}
}
@ -129,6 +154,21 @@ PositionType LocalFileStream::Read(void* buffer, PositionType readBytes) {
return (PositionType) fread(buffer, 1, readBytes, this->file);
}
PositionType LocalFileStream::Write(void* buffer, PositionType writeBytes) {
if (!this->file.load()) {
return 0;
}
long position = ftell(this->file);
size_t written = fwrite(buffer, 1, writeBytes, this->file);
if (written + position > this->filesize) {
this->filesize = written + position;
}
return (PositionType) written;
}
bool LocalFileStream::SetPosition(PositionType position) {
if (!this->file.load()) {
return false;

@ -43,15 +43,19 @@ namespace musik { namespace core { namespace io {
class LocalFileStream : public musik::core::sdk::IDataStream {
public:
using PositionType = musik::core::sdk::PositionType;
using OpenFlags = musik::core::sdk::OpenFlags;
LocalFileStream();
virtual ~LocalFileStream();
virtual bool Open(const char *filename, unsigned int options = 0);
virtual bool Open(const char *filename, OpenFlags flags);
virtual bool Close();
virtual void Interrupt();
virtual void Release();
virtual bool Readable() { return (flags & OpenFlags::Read) != 0; }
virtual bool Writable() { return (flags & OpenFlags::Write) != 0; }
virtual PositionType Read(void* buffer, PositionType readBytes);
virtual PositionType Write(void* buffer, PositionType writeBytes);
virtual bool SetPosition(PositionType position);
virtual PositionType Position();
virtual bool Eof();
@ -62,6 +66,7 @@ namespace musik { namespace core { namespace io {
virtual bool CanPrefetch() { return true; }
private:
OpenFlags flags { OpenFlags::None };
std::string extension;
std::string uri;
std::atomic<FILE*> file;

@ -50,7 +50,7 @@ using namespace musik::core;
using namespace musik::core::library;
using namespace musik::core::runtime;
#define DATABASE_VERSION 8
#define DATABASE_VERSION 9
#define VERBOSE_LOGGING 0
#define MESSAGE_QUERY_COMPLETED 5000
@ -351,9 +351,16 @@ static void upgradeV7ToV8(db::Connection& db) {
scheduleSyncDueToDbUpgrade = true;
}
static void upgradeV8ToV9(db::Connection& db) {
db.Execute("ALTER TABLE tracks ADD COLUMN rating INTEGER DEFAULT 0");
db.Execute("ALTER TABLE tracks ADD COLUMN last_played REAL DEFAULT null");
db.Execute("ALTER TABLE tracks ADD COLUMN play_count INTEGER DEFAULT 0");
db.Execute("ALTER TABLE tracks ADD COLUMN date_added REAL DEFAULT null");
db.Execute("ALTER TABLE tracks ADD COLUMN date_updated REAL DEFAULT null");
}
static void setVersion(db::Connection& db, int version) {
db.Execute("DELETE FROM version");
db::Statement stmt("INSERT INTO version VALUES(?)", db);
stmt.BindInt32(0, version);
stmt.Step();
@ -381,8 +388,13 @@ void LocalLibrary::CreateDatabase(db::Connection &db){
"thumbnail_id INTEGER DEFAULT 0,"
"source_id INTEGER DEFAULT 0,"
"visible INTEGER DEFAULT 1,"
"external_id TEXT DEFAULT null"
")");
"external_id TEXT DEFAULT null,"
"rating INTEGER DEFAULT 0,"
"last_played REAL DEFAULT null,"
"play_count INTEGER DEFAULT 0,"
"date_added REAL DEFAULT null,"
"date_updated REAL DEFAULT null,"
")");
/* genres tables */
db.Execute(
@ -527,8 +539,10 @@ void LocalLibrary::CreateDatabase(db::Connection &db){
"CREATE VIEW tracks_view AS "
"SELECT DISTINCT "
" t.id, t.track, t.disc, t.bpm, t.duration, t.filesize, t.title, t.filename, "
" t.thumbnail_id, t.external_id, al.name AS album, alar.name AS album_artist, gn.name AS genre, "
" ar.name AS artist, t.filetime, t.visual_genre_id, t.visual_artist_id, t.album_artist_id, t.album_id "
" t.thumbnail_id, t.external_id, t.rating, t.last_played, t.play_count, t.date_added, "
" t.date_updated, al.name AS album, alar.name AS album_artist, gn.name AS genre, "
" ar.name AS artist, t.filetime, t.visual_genre_id, t.visual_artist_id, t.album_artist_id, "
" t.album_id "
"FROM "
" tracks t, albums al, artists alar, artists ar, genres gn "
"WHERE "
@ -587,6 +601,10 @@ void LocalLibrary::CreateDatabase(db::Connection &db){
upgradeV7ToV8(db);
}
if (lastVersion >= 1 && lastVersion < 9) {
upgradeV8ToV9(db);
}
/* ensure our version is set correctly */
setVersion(db, DATABASE_VERSION);

@ -57,6 +57,11 @@ namespace musik { namespace core { namespace library { namespace constants {
static const char* PATH_ID = "path_id";
static const char* SOURCE_ID = "source_id";
static const char* EXTERNAL_ID = "external_id";
static const char* RATING = "rating";
static const char* LAST_PLAYED = "last_played";
static const char* PLAY_COUNT = "play_count";
static const char* DATE_ADDED = "date_added";
static const char* DATE_UPDATED = "date_updated";
/* used in Track instances where foreign key IDs have been
replaced with actual values... */

@ -296,7 +296,10 @@ LocalMetadataProxy::~LocalMetadataProxy() {
ITrackList* LocalMetadataProxy::QueryTracks(const char* query, int limit, int offset) {
try {
std::shared_ptr<SearchTrackListQuery> search(
new SearchTrackListQuery(this->library, std::string(query ? query : "")));
new SearchTrackListQuery(
this->library,
std::string(query ? query : ""),
TrackSortType::Album));
if (limit >= 0) {
search->SetLimitAndOffset(limit, offset);

@ -36,6 +36,8 @@
#include "CategoryListQuery.h"
#include <core/library/LocalLibraryConstants.h>
#include <core/db/Statement.h>
#include <core/i18n/Locale.h>
#include <core/utfutil.h>
using musik::core::db::Statement;
using musik::core::db::Row;
@ -189,13 +191,27 @@ void CategoryListQuery::QueryExtended(musik::core::db::Connection &db) {
}
void CategoryListQuery::ProcessResult(musik::core::db::Statement &stmt) {
SdkValueList unknowns;
while (stmt.Step() == Row) {
auto row = std::make_shared<SdkValue>(
stmt.ColumnText(1),
stmt.ColumnInt64(0),
this->trackField);
int64_t id = stmt.ColumnInt64(0);
std::string displayValue = musik::core::Trim(stmt.ColumnText(1));
result->Add(row);
/* we track empty / blank values separately, then sort them to the bottom
of the returned list so they don't pollute the first results */
if (!displayValue.size()) {
unknowns.Add(std::make_shared<SdkValue>(
u8fmt(_TSTR("unknown_category_value"), unknowns.Count() + 1),
id,
this->trackField
));
}
else {
result->Add(std::make_shared<SdkValue>(displayValue, id, this->trackField));
}
}
for (size_t i = 0; i < unknowns.Count(); i++) {
result->Add(unknowns.At(i));
}
}

@ -56,8 +56,9 @@ using namespace boost::algorithm;
CategoryTrackListQuery::CategoryTrackListQuery(
musik::core::ILibraryPtr library,
const std::string& filter)
: CategoryTrackListQuery(library, category::PredicateList(), filter)
const std::string& filter,
TrackSortType sortType)
: CategoryTrackListQuery(library, category::PredicateList(), filter, sortType)
{
}
@ -65,23 +66,26 @@ CategoryTrackListQuery::CategoryTrackListQuery(
musik::core::ILibraryPtr library,
const std::string& column,
int64_t id,
const std::string& filter)
: CategoryTrackListQuery(library, { column, id }, filter)
const std::string& filter,
TrackSortType sortType)
: CategoryTrackListQuery(library, { column, id }, filter, sortType)
{
}
CategoryTrackListQuery::CategoryTrackListQuery(
ILibraryPtr library,
const category::Predicate predicate,
const std::string& filter)
: CategoryTrackListQuery(library, category::PredicateList { predicate }, filter)
const std::string& filter,
TrackSortType sortType)
: CategoryTrackListQuery(library, category::PredicateList { predicate }, filter, sortType)
{
}
CategoryTrackListQuery::CategoryTrackListQuery(
ILibraryPtr library,
const category::PredicateList predicates,
const std::string& filter)
const std::string& filter,
TrackSortType sortType)
{
this->library = library;
this->result.reset(new musik::core::TrackList(library));
@ -100,6 +104,9 @@ CategoryTrackListQuery::CategoryTrackListQuery(
else {
this->type = Regular;
}
this->orderBy = "ORDER BY " + kTrackListSortOrderBy.find(sortType)->second;
this->parseHeaders = kTrackSortTypeWithAlbumGrouping.find(sortType) != kTrackSortTypeWithAlbumGrouping.end();
}
CategoryTrackListQuery::~CategoryTrackListQuery() {
@ -148,6 +155,7 @@ void CategoryTrackListQuery::RegularQuery(musik::core::db::Connection &db) {
category::ReplaceAll(query, "{{extended_predicates}}", extended);
category::ReplaceAll(query, "{{regular_predicates}}", regular);
category::ReplaceAll(query, "{{tracklist_filter}}", trackFilter);
category::ReplaceAll(query, "{{order_by}}", this->orderBy);
category::ReplaceAll(query, "{{limit_and_offset}}", limitAndOffset);
Statement stmt(query.c_str(), db);
@ -163,7 +171,7 @@ void CategoryTrackListQuery::ProcessResult(musik::core::db::Statement& trackQuer
int64_t id = trackQuery.ColumnInt64(0);
std::string album = trackQuery.ColumnText(1);
if (album != lastAlbum) {
if (this->parseHeaders && album != lastAlbum) {
headers->insert(index);
lastAlbum = album;
}

@ -38,6 +38,7 @@
#include <core/library/track/Track.h>
#include <core/library/query/local/LocalQueryBase.h>
#include <core/library/query/local/util/CategoryQueryUtil.h>
#include <core/library/query/local/util/TrackSort.h>
#include <core/db/Statement.h>
#include "TrackListQueryBase.h"
@ -48,23 +49,27 @@ namespace musik { namespace core { namespace db { namespace local {
public:
CategoryTrackListQuery(
musik::core::ILibraryPtr library,
const std::string& filter = "");
const std::string& filter = "",
TrackSortType sortType = TrackSortType::Album);
CategoryTrackListQuery(
musik::core::ILibraryPtr library,
const std::string& column,
int64_t id,
const std::string& filter = "");
const std::string& filter = "",
TrackSortType sortType = TrackSortType::Album);
CategoryTrackListQuery(
musik::core::ILibraryPtr library,
const category::Predicate predicate,
const std::string& filter = "");
const std::string& filter = "",
TrackSortType sortType = TrackSortType::Album);
CategoryTrackListQuery(
musik::core::ILibraryPtr library,
const category::PredicateList predicates,
const std::string& filter = "");
const std::string& filter = "",
TrackSortType sortType = TrackSortType::Album);
virtual ~CategoryTrackListQuery();
@ -85,11 +90,13 @@ namespace musik { namespace core { namespace db { namespace local {
void ProcessResult(musik::core::db::Statement& stmt);
musik::core::ILibraryPtr library;
bool parseHeaders;
Result result;
Headers headers;
Type type;
category::PredicateList regular, extended;
size_t hash;
std::string orderBy;
std::string filter;
};

@ -0,0 +1,73 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#include "pch.hpp"
#include "LyricsQuery.h"
using namespace musik::core::db;
using namespace musik::core::db::local;
using namespace musik::core::sdk;
LyricsQuery::LyricsQuery(const std::string& trackExternalId) {
this->trackExternalId = trackExternalId;
}
LyricsQuery::~LyricsQuery() {
}
std::string LyricsQuery::GetResult() {
return this->result;
}
bool LyricsQuery::OnRun(musik::core::db::Connection &db) {
Statement stmt(
"SELECT value "
"FROM extended_metadata "
"WHERE external_id=? AND meta_key_id=("
" SELECT id "
" FROM meta_keys "
" WHERE name=?"
");",
db);
stmt.BindText(0, this->trackExternalId);
stmt.BindText(1, "lyrics");
if (stmt.Step() == db::Row) {
this->result = stmt.ColumnText(0);
}
return true;
}

@ -0,0 +1,57 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#pragma once
#include <core/library/query/local/LocalQueryBase.h>
namespace musik { namespace core { namespace db { namespace local {
class LyricsQuery : public musik::core::db::LocalQueryBase {
public:
LyricsQuery(const std::string& trackExternalId);
virtual ~LyricsQuery();
std::string Name() { return "LyricsQuery"; }
virtual std::string GetResult();
protected:
virtual bool OnRun(musik::core::db::Connection &db);
std::string trackExternalId;
std::string result;
};
} } } }

@ -0,0 +1,63 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#include "pch.hpp"
#include "MarkTrackPlayedQuery.h"
using namespace musik::core::db;
using namespace musik::core::db::local;
using namespace musik::core::sdk;
MarkTrackPlayedQuery::MarkTrackPlayedQuery(const int64_t trackId) {
this->trackId = trackId;
}
MarkTrackPlayedQuery::~MarkTrackPlayedQuery() {
}
bool MarkTrackPlayedQuery::OnRun(musik::core::db::Connection &db) {
Statement stmt(
"UPDATE tracks "
"SET play_count=(play_count+1), last_played=julianday('now') "
"WHERE id=?",
db);
stmt.BindInt64(0, this->trackId);
if (stmt.Step() == db::Done) {
return true;
}
return false;
}

@ -0,0 +1,52 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#pragma once
#include <core/library/query/local/LocalQueryBase.h>
namespace musik { namespace core { namespace db { namespace local {
class MarkTrackPlayedQuery: public musik::core::db::LocalQueryBase {
public:
MarkTrackPlayedQuery(const int64_t trackId);
virtual ~MarkTrackPlayedQuery();
std::string Name() { return "MarkTrackPlayedQuery"; }
protected:
virtual bool OnRun(musik::core::db::Connection &db);
int64_t trackId;
};
} } } }

@ -43,6 +43,8 @@
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string.hpp>
#include <iostream>
using musik::core::db::Statement;
using musik::core::db::Row;
using musik::core::TrackPtr;
@ -54,13 +56,22 @@ using namespace musik::core::library::constants;
using namespace musik::core::db::local;
using namespace boost::algorithm;
SearchTrackListQuery::SearchTrackListQuery(ILibraryPtr library, const std::string& filter) {
SearchTrackListQuery::SearchTrackListQuery(
ILibraryPtr library, const std::string& filter, TrackSortType sort)
{
this->library = library;
if (filter.size()) {
this->filter = "%" + trim_copy(to_lower_copy(filter)) + "%";
}
if (kTrackSearchSortOrderByPredicate.find(sort) != kTrackSearchSortOrderByPredicate.end()) {
this->orderByPredicate = kTrackSearchSortOrderByPredicate.find(sort)->second + " AND ";
}
this->parseHeaders = kTrackSortTypeWithAlbumGrouping.find(sort) != kTrackSortTypeWithAlbumGrouping.end();
this->displayString = _TSTR(kTrackListOrderByToDisplayKey.find(sort)->second);
this->orderBy = kTrackListSortOrderBy.find(sort)->second;
this->result.reset(new musik::core::TrackList(library));
this->headers.reset(new std::set<size_t>());
this->hash = 0;
@ -82,6 +93,10 @@ size_t SearchTrackListQuery::GetQueryHash() {
return this->hash;
}
std::string SearchTrackListQuery::GetSortDisplayString() {
return this->displayString;
}
bool SearchTrackListQuery::OnRun(Connection& db) {
if (result) {
result.reset(new musik::core::TrackList(this->library));
@ -97,24 +112,30 @@ bool SearchTrackListQuery::OnRun(Connection& db) {
if (hasFilter) {
query =
"SELECT DISTINCT t.id, al.name "
"FROM tracks t, albums al, artists ar, genres gn "
"SELECT DISTINCT tracks.id, al.name "
"FROM tracks, albums al, artists ar, genres gn "
"WHERE "
" t.visible=1 AND "
"(t.title LIKE ? OR al.name LIKE ? OR ar.name LIKE ? OR gn.name LIKE ?) "
" AND t.album_id=al.id AND t.visual_genre_id=gn.id AND t.visual_artist_id=ar.id "
"ORDER BY al.name, disc, track, ar.name ";
" tracks.visible=1 AND "
+ this->orderByPredicate +
"(tracks.title LIKE ? OR al.name LIKE ? OR ar.name LIKE ? OR gn.name LIKE ?) "
" AND tracks.album_id=al.id AND tracks.visual_genre_id=gn.id AND tracks.visual_artist_id=ar.id "
"ORDER BY " + this->orderBy + " ";
}
else {
query =
"SELECT DISTINCT t.id, al.name "
"FROM tracks t, albums al, artists ar, genres gn "
"WHERE t.visible=1 AND t.album_id=al.id AND t.visual_genre_id=gn.id AND t.visual_artist_id=ar.id "
"ORDER BY al.name, disc, track, ar.name ";
"SELECT DISTINCT tracks.id, al.name "
"FROM tracks, albums al, artists ar, genres gn "
"WHERE "
" tracks.visible=1 AND "
+ this->orderByPredicate +
" tracks.album_id=al.id AND tracks.visual_genre_id=gn.id AND tracks.visual_artist_id=ar.id "
"ORDER BY " + this->orderBy + " ";
}
query += this->GetLimitAndOffset();
std::cerr << query << "\n";
Statement trackQuery(query.c_str(), db);
if (hasFilter) {
@ -132,7 +153,7 @@ bool SearchTrackListQuery::OnRun(Connection& db) {
album = _TSTR("tracklist_unknown_album");
}
if (album != lastAlbum) {
if (this->parseHeaders && album != lastAlbum) {
headers->insert(index);
lastAlbum = album;
}

@ -35,6 +35,7 @@
#pragma once
#include "TrackListQueryBase.h"
#include <core/library/query/local/util/TrackSort.h>
namespace musik { namespace core { namespace db { namespace local {
@ -42,12 +43,15 @@ namespace musik { namespace core { namespace db { namespace local {
public:
SearchTrackListQuery(
musik::core::ILibraryPtr library,
const std::string& filter);
const std::string& filter,
TrackSortType sort);
virtual ~SearchTrackListQuery();
virtual std::string Name() { return "SearchTrackListQuery"; }
std::string GetSortDisplayString();
virtual Result GetResult();
virtual Headers GetHeaders();
virtual size_t GetQueryHash();
@ -59,7 +63,11 @@ namespace musik { namespace core { namespace db { namespace local {
musik::core::ILibraryPtr library;
Result result;
Headers headers;
bool parseHeaders;
std::string filter;
std::string orderBy;
std::string orderByPredicate;
std::string displayString;
size_t hash;
};

@ -0,0 +1,58 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#include "pch.hpp"
#include "SetTrackRatingQuery.h"
using namespace musik::core::db;
using namespace musik::core::db::local;
using namespace musik::core::sdk;
SetTrackRatingQuery::SetTrackRatingQuery(int64_t trackId, int rating) {
this->trackId = trackId;
this->rating = std::max(0, std::min(5, rating));
}
SetTrackRatingQuery::~SetTrackRatingQuery() {
}
bool SetTrackRatingQuery::OnRun(musik::core::db::Connection &db) {
Statement stmt("UPDATE tracks SET rating=? WHERE id=?", db);
stmt.BindInt32(0, this->rating);
stmt.BindInt64(1, this->trackId);
if (stmt.Step() == db::Done) {
return true;
}
return false;
}

@ -0,0 +1,54 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#pragma once
#include <core/library/query/local/LocalQueryBase.h>
namespace musik { namespace core { namespace db { namespace local {
class SetTrackRatingQuery: public musik::core::db::LocalQueryBase {
public:
SetTrackRatingQuery(int64_t trackId, int rating);
virtual ~SetTrackRatingQuery();
std::string Name() { return "SetTrackRatingQuery"; }
protected:
virtual bool OnRun(musik::core::db::Connection &db);
int64_t trackId;
int rating;
};
} } } }

@ -41,7 +41,7 @@ using namespace musik::core::db::local;
using namespace musik::core;
using namespace musik::core::library;
static const std::string COLUMNS = "t.track, t.disc, t.bpm, t.duration, t.filesize, t.title, t.filename, t.thumbnail_id, al.name AS album, alar.name AS album_artist, gn.name AS genre, ar.name AS artist, t.filetime, t.visual_genre_id, t.visual_artist_id, t.album_artist_id, t.album_id, t.source_id, t.external_id";
static const std::string COLUMNS = "t.track, t.disc, t.bpm, t.duration, t.filesize, t.title, t.filename, t.thumbnail_id, al.name AS album, alar.name AS album_artist, gn.name AS genre, ar.name AS artist, t.filetime, t.visual_genre_id, t.visual_artist_id, t.album_artist_id, t.album_id, t.source_id, t.external_id, t.rating ";
static const std::string TABLES = "tracks t, albums al, artists alar, artists ar, genres gn";
static const std::string PREDICATE = "t.album_id=al.id AND t.album_artist_id=alar.id AND t.visual_genre_id=gn.id AND t.visual_artist_id=ar.id";
@ -118,6 +118,7 @@ bool TrackMetadataQuery::OnRun(Connection& db) {
result->SetValue(constants::Track::ALBUM_ID, trackQuery.ColumnText(16));
result->SetValue(constants::Track::SOURCE_ID, trackQuery.ColumnText(17));
result->SetValue(constants::Track::EXTERNAL_ID, trackQuery.ColumnText(18));
result->SetValue(constants::Track::RATING, trackQuery.ColumnText(19));
}
else {
result->SetValue(constants::Track::EXTERNAL_ID, trackQuery.ColumnText(0));

@ -172,8 +172,10 @@ namespace musik { namespace core { namespace db { namespace local {
static const std::string CATEGORY_TRACKLIST_FILTER =
" AND (tracks.title LIKE ? OR al.name LIKE ? OR ar.name LIKE ? OR gn.name LIKE ?) ";
/* note: al.name needs to be the second column selected to ensure proper grouping by
album in the UI layer! */
static const std::string CATEGORY_TRACKLIST_QUERY =
"SELECT DISTINCT tracks.id, al.name "
"SELECT DISTINCT tracks.id, al.name, tracks.date_added, tracks.date_updated, tracks.last_played, tracks.play_count, tracks.rating "
"FROM tracks, albums al, artists ar, genres gn "
"{{extended_predicates}} "
"WHERE "
@ -183,7 +185,7 @@ namespace musik { namespace core { namespace db { namespace local {
" tracks.visual_artist_id=ar.id "
" {{regular_predicates}} "
" {{tracklist_filter}} "
"ORDER BY al.name, disc, track, ar.name "
"{{order_by}} "
"{{limit_and_offset}} ";
/* ALBUM_LIST_QUERY is like a specialized REGULAR_PROPERTY_QUERY used by

@ -0,0 +1,115 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#pragma once
#include <map>
namespace musik {
namespace core {
namespace db {
namespace local {
enum class TrackSortType : int {
Album = 0,
Title = 1,
Artist = 2,
DateAddedAsc = 3,
DateAddedDesc = 4,
DateUpdatedAsc = 5,
DateUpdatedDesc = 6,
LastPlayedAsc = 7,
LastPlayedDesc = 8,
RatingAsc = 9,
RatingDesc = 10,
PlayCountAsc = 11,
PlayCountDesc = 12,
Genre = 13,
};
static const std::map<TrackSortType, std::string> kTrackListOrderByToDisplayKey = {
{ TrackSortType::Title, "track_list_sort_title" },
{ TrackSortType::Album, "track_list_sort_album" },
{ TrackSortType::Artist, "track_list_sort_artist" },
{ TrackSortType::DateAddedAsc, "track_list_sort_date_added_asc" },
{ TrackSortType::DateAddedDesc, "track_list_sort_date_added_desc" },
{ TrackSortType::DateUpdatedAsc, "track_list_sort_date_updated_asc" },
{ TrackSortType::DateUpdatedDesc, "track_list_sort_date_updated_desc" },
{ TrackSortType::LastPlayedAsc, "track_list_sort_last_played_asc" },
{ TrackSortType::LastPlayedDesc, "track_list_sort_last_played_desc" },
{ TrackSortType::RatingAsc, "track_list_sort_rating_asc" },
{ TrackSortType::RatingDesc, "track_list_sort_rating_desc" },
{ TrackSortType::PlayCountAsc, "track_list_sort_play_count_asc" },
{ TrackSortType::PlayCountDesc, "track_list_sort_play_count_desc" },
{ TrackSortType::Genre, "track_list_sort_genre" },
};
static const std::map<TrackSortType, std::string> kTrackListSortOrderBy = {
{ TrackSortType::Title, "tracks.title, ar.name, al.name" },
{ TrackSortType::Album, "al.name, disc, track, ar.name" },
{ TrackSortType::Artist, "ar.name, al.name, disc, track" },
{ TrackSortType::DateAddedAsc, "date(tracks.date_added) ASC, al.name, disc, track, ar.name" },
{ TrackSortType::DateAddedDesc, "date(tracks.date_added) DESC, al.name, disc, track, ar.name" },
{ TrackSortType::DateUpdatedAsc, "date(tracks.date_updated) ASC, al.name, disc, track, ar.name" },
{ TrackSortType::DateUpdatedDesc, "date(tracks.date_updated) DESC, al.name, disc, track, ar.name" },
{ TrackSortType::LastPlayedAsc, "datetime(tracks.last_played) ASC" },
{ TrackSortType::LastPlayedDesc, "datetime(tracks.last_played) DESC" },
{ TrackSortType::RatingAsc, "tracks.rating ASC" },
{ TrackSortType::RatingDesc, "tracks.rating DESC" },
{ TrackSortType::PlayCountAsc, "tracks.play_count ASC" },
{ TrackSortType::PlayCountDesc, "tracks.play_count DESC" },
{ TrackSortType::Genre, "gn.name, al.name, disc, track, ar.name" },
};
static const std::map<TrackSortType, std::string> kTrackSearchSortOrderByPredicate {
{ TrackSortType::LastPlayedAsc, "tracks.last_played IS NOT NULL" },
{ TrackSortType::LastPlayedDesc, "tracks.last_played IS NOT NULL" },
{ TrackSortType::RatingAsc, "tracks.rating IS NOT NULL AND tracks.rating > 0" },
{ TrackSortType::RatingDesc, "tracks.rating IS NOT NULL AND tracks.rating > 0" },
{ TrackSortType::PlayCountAsc, "tracks.play_count IS NOT NULL AND tracks.play_count > 0" },
{ TrackSortType::PlayCountDesc, "tracks.play_count IS NOT NULL AND tracks.play_count > 0" },
};
static const std::set<TrackSortType> kTrackSortTypeWithAlbumGrouping = {
TrackSortType::Album,
TrackSortType::Artist,
TrackSortType::DateAddedAsc,
TrackSortType::DateAddedDesc,
TrackSortType::DateUpdatedAsc,
TrackSortType::DateUpdatedDesc,
TrackSortType::Genre,
};
}
}
}
}

@ -317,14 +317,16 @@ static int64_t writeToTracksTable(
query =
"UPDATE tracks "
"SET track=?, disc=?, bpm=?, duration=?, filesize=?, "
" title=?, filename=?, filetime=?, path_id=?, external_id=? "
" title=?, filename=?, filetime=?, path_id=?, "
" date_updated=julianday('now'), external_id=? "
"WHERE id=?";
}
else {
query =
"INSERT INTO tracks "
"(track, disc, bpm, duration, filesize, title, filename, filetime, path_id, external_id) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
"(track, disc, bpm, duration, filesize, title, filename, "
" filetime, path_id, external_id, date_added, date_updated) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, julianday('now'), julianday('now'))";
}
db::Statement stmt(query.c_str(), dbConnection);

@ -223,7 +223,7 @@ bool LibraryTrack::Load(Track *target, db::Connection &db) {
"ORDER BY tm.id", db);
db::Statement trackQuery(
"SELECT t.track, t.disc, t.bpm, t.duration, t.filesize, t.title, t.filename, t.thumbnail_id, al.name, t.filetime, t.visual_genre_id, t.visual_artist_id, t.album_artist_id, t.album_id " \
"SELECT t.track, t.disc, t.bpm, t.duration, t.filesize, t.title, t.filename, t.thumbnail_id, al.name, t.filetime, t.visual_genre_id, t.visual_artist_id, t.album_artist_id, t.album_id, t.rating " \
"FROM tracks t, paths p, albums al " \
"WHERE t.id=? AND t.album_id=al.id", db);
@ -243,6 +243,7 @@ bool LibraryTrack::Load(Track *target, db::Connection &db) {
target->SetValue("visual_artist_id", trackQuery.ColumnText(11));
target->SetValue("album_artist_id", trackQuery.ColumnText(12));
target->SetValue("album_id", trackQuery.ColumnText(13));
target->SetValue("rating", trackQuery.ColumnText(14));
genresQuery.BindInt64(0, (int64_t) target->GetId());
while (genresQuery.Step() == db::Row) {

@ -140,8 +140,8 @@ static class Environment: public IEnvironment {
return CopyString(path, dst, size);
}
virtual IDataStream* GetDataStream(const char* uri) override {
return DataStreamFactory::OpenDataStream(uri);
virtual IDataStream* GetDataStream(const char* uri, OpenFlags flags) override {
return DataStreamFactory::OpenDataStream(uri, flags);
}
virtual IDecoder* GetDecoder(IDataStream* stream) override {

@ -53,7 +53,8 @@ struct DataBuffer {
delete[] data;
data = new T[newLength];
}
rawLength = length = newLength;
rawLength = (newLength > rawLength) ? newLength : rawLength;
length = newLength;
offset = 0;
}
@ -91,7 +92,7 @@ struct DataBuffer {
realloc(size);
memcpy(data + length, source, size * sizeof(T));
length += size;
return size;
return (int) size;
}
int pad(char value, size_t size) {

@ -0,0 +1,51 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#pragma once
#include "IEncoder.h"
#include "IBuffer.h"
#include "IDataStream.h"
#include <stddef.h>
namespace musik { namespace core { namespace sdk {
class IBlockingEncoder: public IEncoder {
public:
virtual bool Initialize(IDataStream* out, size_t rate, size_t channels, size_t bitrate) = 0;
virtual bool Encode(const IBuffer* pcm) = 0;
virtual void Finalize() = 0;
};
} } }

@ -34,17 +34,22 @@
#pragma once
#include "constants.h"
namespace musik { namespace core { namespace sdk {
typedef long PositionType;
class IDataStream {
public:
virtual bool Open(const char *uri, unsigned int options = 0) = 0;
virtual bool Open(const char *uri, OpenFlags flags) = 0;
virtual bool Close() = 0;
virtual void Interrupt() = 0;
virtual void Release() = 0;
virtual bool Readable() = 0;
virtual bool Writable() = 0;
virtual PositionType Read(void *buffer, PositionType readBytes) = 0;
virtual PositionType Write(void *buffer, PositionType writeBytes) = 0;
virtual bool SetPosition(PositionType position) = 0;
virtual PositionType Position() = 0;
virtual bool Seekable() = 0;

@ -39,8 +39,10 @@
namespace musik { namespace core { namespace sdk {
class IDataStreamFactory{
public:
using OpenFlags = musik::core::sdk::OpenFlags;
virtual bool CanRead(const char *uri) = 0;
virtual IDataStream* Open(const char *uri, unsigned int options = 0) = 0;
virtual IDataStream* Open(const char *uri, OpenFlags flags) = 0;
virtual void Release() = 0;
};

@ -35,7 +35,6 @@
#pragma once
#include "IBuffer.h"
#include "IPreferences.h"
#include <stddef.h>
namespace musik { namespace core { namespace sdk {
@ -43,11 +42,6 @@ namespace musik { namespace core { namespace sdk {
class IEncoder {
public:
virtual void Release() = 0;
virtual void Initialize(size_t rate, size_t channels, size_t bitrate) = 0;
virtual int Encode(const IBuffer* pcm, char** data) = 0;
virtual int Flush(char** data) = 0;
virtual void Finalize(const char* uri) = 0;
virtual IPreferences* GetPreferences() = 0;
};
} } }

@ -48,7 +48,7 @@ namespace musik { namespace core { namespace sdk {
class IEnvironment {
public:
virtual size_t GetPath(PathType type, char* dst, int size) = 0;
virtual IDataStream* GetDataStream(const char* uri) = 0;
virtual IDataStream* GetDataStream(const char* uri, OpenFlags flags) = 0;
virtual IDecoder* GetDecoder(IDataStream* stream) = 0;
virtual IEncoder* GetEncoder(const char* type) = 0;
virtual IBuffer* GetBuffer(size_t samples, size_t rate = 44100, size_t channels = 2) = 0;

@ -0,0 +1,50 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#pragma once
#include "IEncoder.h"
#include "IBuffer.h"
namespace musik { namespace core { namespace sdk {
class IStreamingEncoder: public IEncoder {
public:
virtual bool Initialize(size_t rate, size_t channels, size_t bitrate) = 0;
virtual int Encode(const IBuffer* pcm, char** data) = 0;
virtual int Flush(char** data) = 0;
virtual void Finalize(const char* uri) = 0;
};
} } }

@ -116,6 +116,12 @@ namespace musik {
Crossfade = 1
};
enum OpenFlags {
None = 0,
Read = 1,
Write = 2
};
static const size_t EqualizerBandCount = 18;
static const size_t EqualizerBands[] = {
@ -155,5 +161,5 @@ namespace musik {
static const char* ExternalId = "external_id";
}
static const int SdkVersion = 16;
static const int SdkVersion = 17;
} } }

@ -215,7 +215,22 @@ double Preferences::GetDouble(const std::string& key, double defaultValue) {
std::string Preferences::GetString(const std::string& key, const std::string& defaultValue) {
std::unique_lock<std::mutex> lock(this->mutex);
RETURN_VALUE(defaultValue);
try {
auto it = json.find(key);
if (it == json.end()) {
json[key] = defaultValue;
return defaultValue;
}
std::string value = it.value();
if (!value.size() && defaultValue.size()) {
json[key] = defaultValue;
return defaultValue;
}
return value;
}
catch (...) {
return defaultValue;
}
}
void Preferences::SetBool(const std::string& key, bool value) {

@ -78,4 +78,14 @@ static std::string u8fmt(const std::string& format, Args ... args) {
std::unique_ptr<char[]> buf(new char[size]);
std::snprintf(buf.get(), size, format.c_str(), args ...);
return std::string(buf.get(), buf.get() + size - 1); /* omit the '\0' */
}
}
static inline void u8replace(
std::string& input, const std::string& find, const std::string& replace)
{
size_t pos = input.find(find);
while (pos != std::string::npos) {
input.replace(pos, find.size(), replace);
pos = input.find(find, pos + replace.size());
}
}

@ -25,12 +25,15 @@ set (CUBE_SRCS
./app/overlay/PreampOverlay.cpp
./app/overlay/ReassignHotkeyOverlay.cpp
./app/overlay/ServerOverlay.cpp
./app/overlay/TrackOverlays.cpp
./app/overlay/VisualizerOverlay.cpp
./app/util/ConsoleLogger.cpp
./app/util/GlobalHotkeys.cpp
./app/util/Hotkeys.cpp
./app/util/PreferenceKeys.cpp
./app/util/Playback.cpp
./app/util/Rating.cpp
./app/util/TrackRowRenderers.cpp
./app/util/UpdateCheck.cpp
./app/window/CategoryListView.cpp
./app/window/TrackListView.cpp
@ -47,11 +50,12 @@ set (CUBE_SRCS
./cursespp/ListWindow.cpp
./cursespp/MultiLineEntry.cpp
./cursespp/OverlayStack.cpp
./cursespp/ShortcutsWindow.cpp
./cursespp/SchemaOverlay.cpp
./cursespp/Screen.cpp
./cursespp/ScrollableWindow.cpp
./cursespp/ScrollAdapterBase.cpp
./cursespp/Scrollbar.cpp
./cursespp/ShortcutsWindow.cpp
./cursespp/SimpleScrollAdapter.cpp
./cursespp/SingleLineEntry.cpp
./cursespp/Text.cpp

@ -108,10 +108,10 @@ int main(int argc, char* argv[]) {
#ifdef WIN32
musik::core::RemoveOldDlls();
AddDllDirectory(u8to16(musik::core::GetPluginDirectory()).c_str());
#ifdef __PDCURSES__
PDC_set_resize_limits(MIN_HEIGHT, 1000, MIN_WIDTH, 1000);
resize_term(26, 100); /* must be before app init */
#endif
#ifdef __PDCURSES__
PDC_set_resize_limits(MIN_HEIGHT, 1000, MIN_WIDTH, 1000);
resize_term(26, 100); /* must be before app init */
#endif
#endif
std::string errorFn = musik::core::GetDataDirectory() + "stderr.txt";

@ -40,6 +40,8 @@
#include <core/library/LocalLibraryConstants.h>
#include <core/library/query/local/CategoryTrackListQuery.h>
#include <core/library/query/local/SavePlaylistQuery.h>
#include <core/library/query/local/util/TrackSort.h>
#include <core/support/PreferenceKeys.h>
#include <core/support/Messages.h>
#include <core/i18n/Locale.h>
@ -48,6 +50,7 @@
#include <app/util/PreferenceKeys.h>
#include <app/util/Messages.h>
#include <app/overlay/PlayQueueOverlays.h>
#include <app/overlay/TrackOverlays.h>
#include "BrowseLayout.h"
@ -71,7 +74,7 @@ static int MIN_LIST_TITLE_HEIGHT = 26;
static std::set<std::string> EDIT_KEYS;
static std::map <std::string, std::string> FIELD_TO_TITLE{
static std::map <std::string, std::string> FIELD_TO_TITLE {
std::make_pair(constants::Track::ARTIST, "browse_title_artists"),
std::make_pair(constants::Track::ALBUM, "browse_title_albums"),
std::make_pair(constants::Track::GENRE, "browse_title_genres"),
@ -95,6 +98,11 @@ static inline std::string getTitleForCategory(const std::string& fieldName) {
? _TSTR(fieldName) : _TSTR(FIELD_TO_TITLE[fieldName]);
}
static TrackSortType getDefaultTrackSort(std::shared_ptr<musik::core::Preferences> prefs) {
return (TrackSortType)prefs->GetInt(
keys::CategoryTrackListSortOrder, (int) TrackSortType::Album);
}
BrowseLayout::BrowseLayout(
musik::core::audio::PlaybackService& playback,
ILibraryPtr library)
@ -147,7 +155,7 @@ void BrowseLayout::InitializeWindows() {
this->categoryList->SetFrameTitle(_TSTR(DEFAULT_CATEGORY_NAME));
this->trackList.reset(new TrackListView(this->playback, this->library));
this->trackList->SetFrameTitle(_TSTR("browse_title_tracks"));
this->trackList->Requeried.connect(this, &BrowseLayout::OnRequeried);
this->modifiedLabel.reset(new TextLabel());
this->modifiedLabel->SetText(getModifiedText(), text::AlignCenter);
@ -231,13 +239,28 @@ void BrowseLayout::OnIndexerProgress(int count) {
this->Post(message::IndexerProgress);
}
void BrowseLayout::OnRequeried(TrackListQueryBase* query) {
std::string sortByDisplayString = kTrackListOrderByToDisplayKey
.find(getDefaultTrackSort(this->prefs))->second;
this->trackList->SetFrameTitle(u8fmt(
_TSTR("browse_title_tracks"),
_TSTR(sortByDisplayString).c_str()));
}
void BrowseLayout::RequeryTrackList(ListWindow *view) {
if (view == this->categoryList.get()) {
int64_t selectedId = this->categoryList->GetSelectedId();
if (selectedId != -1) {
TrackSortType sortOrder = getDefaultTrackSort(this->prefs);
auto column = this->categoryList->GetFieldName();
this->trackList->Requery(std::shared_ptr<TrackListQueryBase>(
new CategoryTrackListQuery(this->library, column, selectedId)));
new CategoryTrackListQuery(
this->library,
column,
selectedId,
"",
sortOrder)));
}
else {
this->trackList->Clear();
@ -270,7 +293,8 @@ void BrowseLayout::SwitchCategory(const std::string& fieldName) {
this->categoryList->SetFrameTitle(getTitleForCategory(fieldName));
this->trackList->SetTrackNumType(fieldName == constants::Playlists::TABLE_NAME
? TrackListView::TrackNumType::Sequential : TrackListView::TrackNumType::Metadata);
? TrackRowRenderers::TrackNumType::Sequential
: TrackRowRenderers::TrackNumType::Metadata);
}
bool BrowseLayout::KeyPress(const std::string& key) {
@ -285,6 +309,16 @@ bool BrowseLayout::KeyPress(const std::string& key) {
}
}
}
else if (Hotkeys::Is(Hotkeys::TrackListChangeSortOrder, key)) {
TrackOverlays::ShowTrackSearchSortOverlay(
getDefaultTrackSort(this->prefs),
kTrackListOrderByToDisplayKey,
[this](TrackSortType type) {
this->prefs->SetInt(keys::CategoryTrackListSortOrder, (int)type);
this->RequeryTrackList(this->categoryList.get());
});
return true;
}
else if (Hotkeys::Is(Hotkeys::ViewRefresh, key)) {
this->categoryList->Requery();
return true;

@ -39,7 +39,6 @@
#include <app/window/CategoryListView.h>
#include <app/window/TrackListView.h>
#include <app/window/TransportWindow.h>
#include <core/audio/PlaybackService.h>
#include <core/support/Preferences.h>
#include <core/library/ILibrary.h>
@ -84,6 +83,8 @@ namespace musik {
void OnCategoryViewInvalidated(
cursespp::ListWindow *view, size_t selectedIndex);
void OnRequeried(musik::core::db::local::TrackListQueryBase* query);
bool IsPlaylist();
bool ProcessEditOperation(const std::string& key);
bool ProcessPlaylistOperation(const std::string& key);

@ -38,6 +38,7 @@
#include <cursespp/Screen.h>
#include <cursespp/Text.h>
#include <core/library/LocalLibraryConstants.h>
#include <core/support/PreferenceKeys.h>
#include <app/util/Hotkeys.h>
#include <app/util/PreferenceKeys.h>
#include "CategorySearchLayout.h"

@ -40,7 +40,6 @@
#include <app/window/CategoryListView.h>
#include <app/window/TrackListView.h>
#include <app/window/TransportWindow.h>
#include <core/audio/PlaybackService.h>
#include <core/library/ILibrary.h>

@ -40,7 +40,6 @@
#include <app/window/CategoryListView.h>
#include <app/window/TrackListView.h>
#include <app/window/TransportWindow.h>
#include <app/model/DirectoryAdapter.h>
#include <core/audio/PlaybackService.h>

@ -55,8 +55,6 @@
using namespace musik::core::library::constants;
using namespace musik::core::runtime;
#define TRANSPORT_HEIGHT 3
#define SHOULD_REFOCUS(target) \
(this->visibleLayout == target) && \
(this->shortcuts && !this->shortcuts->IsFocused())
@ -96,17 +94,16 @@ LibraryLayout::~LibraryLayout() {
}
void LibraryLayout::OnLayout() {
bool autoHideCommandBar = this->prefs->GetBool(keys::AutoHideCommandBar, false);
int x = 0, y = 0;
int cx = this->GetWidth(), cy = this->GetHeight();
int mainHeight = cy - TRANSPORT_HEIGHT;
this->transportView->MoveAndResize(
1,
mainHeight,
cx - 2,
TRANSPORT_HEIGHT);
#ifdef WIN32
int transportCy = 3;
#else
int transportCy = (autoHideCommandBar ? 2 : 3);
#endif
int mainHeight = cy - transportCy;
this->transportView->MoveAndResize(1, mainHeight, cx - 2, transportCy);
if (this->visibleLayout) {
this->visibleLayout->MoveAndResize(x, y, cx, mainHeight);
this->visibleLayout->Show();

@ -37,6 +37,7 @@
#include <core/i18n/Locale.h>
#include <core/support/Auddio.h>
#include <core/support/Common.h>
#include <core/library/query/local/LyricsQuery.h>
#include <cursespp/App.h>
#include <cursespp/Screen.h>
#include <cursespp/ToastOverlay.h>
@ -46,13 +47,16 @@
using namespace musik::cube;
using namespace musik::core;
using namespace musik::core::audio;
using namespace musik::core::db::local;
using namespace musik::core::runtime;
using namespace musik::core::sdk;
using namespace cursespp;
LyricsLayout::LyricsLayout(musik::core::audio::PlaybackService& playback)
LyricsLayout::LyricsLayout(PlaybackService& playback, ILibraryPtr library)
: LayoutBase()
, currentTrackId(-1LL)
, library(library)
, playback(playback) {
this->playback.TrackChanged.connect(this, &LyricsLayout::OnTrackChanged);
@ -83,6 +87,20 @@ void LyricsLayout::OnTrackChanged(size_t index, TrackPtr track) {
}
}
void LyricsLayout::OnLyricsLoaded(TrackPtr track, const std::string& lyrics) {
if (!track || !lyrics.size()) {
return;
}
this->UpdateAdapter(lyrics);
this->listView->ScrollTo(0);
this->listView->SetSelectedIndex(0);
this->listView->SetFrameTitle(u8fmt(
_TSTR("lyrics_list_title"),
track->GetString("title").c_str(),
track->GetString("artist").c_str()));
this->SetState(State::Loaded);
}
bool LyricsLayout::KeyPress(const std::string& kn) {
if (Hotkeys::Is(Hotkeys::LyricsRetry, kn)) {
this->LoadLyricsForCurrentTrack();
@ -117,22 +135,26 @@ void LyricsLayout::LoadLyricsForCurrentTrack() {
if (track && track->GetId() != this->currentTrackId) {
this->currentTrackId = track->GetId();
this->SetState(State::Loading);
auddio::FindLyrics(track, [this](TrackPtr track, std::string lyrics) {
if (this->currentTrackId == track->GetId()) {
if (lyrics.size()) {
this->UpdateAdapter(lyrics);
this->listView->ScrollTo(0);
this->listView->SetSelectedIndex(0);
this->listView->SetFrameTitle(u8fmt(
_TSTR("lyrics_list_title"),
track->GetString("title").c_str(),
track->GetString("artist").c_str()));
auto trackExternalId = track->GetString("external_id");
auto lyricsDbQuery = std::make_shared<LyricsQuery>(trackExternalId);
this->library->Enqueue(lyricsDbQuery, ILibrary::QuerySynchronous);
auto localLyrics = lyricsDbQuery->GetResult();
if (localLyrics.size()) {
this->OnLyricsLoaded(track, localLyrics);
}
else {
auddio::FindLyrics(track, [this](TrackPtr track, std::string remoteLyrics) {
if (this->currentTrackId == track->GetId()) {
if (remoteLyrics.size()) {
this->OnLyricsLoaded(track, remoteLyrics);
}
else {
this->SetState(State::Failed);
}
}
else {
this->SetState(State::Failed);
}
}
});
});
}
}
else if (!track) {
this->currentTrackId = -1LL;
@ -149,7 +171,6 @@ void LyricsLayout::UpdateAdapter(const std::string& lyrics) {
for (auto& text : items) {
this->adapter->AddEntry(std::make_shared<SingleLineEntry>(text));
}
this->SetState(State::Loaded);
}
void LyricsLayout::SetState(State state) {

@ -6,6 +6,7 @@
#include <cursespp/ITopLevelLayout.h>
#include <cursespp/SimpleScrollAdapter.h>
#include <core/audio/PlaybackService.h>
#include <core/library/ILibrary.h>
namespace musik { namespace cube {
@ -15,7 +16,9 @@ namespace musik { namespace cube {
public sigslot::has_slots<>
{
public:
LyricsLayout(musik::core::audio::PlaybackService& playback);
LyricsLayout(
musik::core::audio::PlaybackService& playback,
musik::core::ILibraryPtr library);
virtual void OnLayout() override;
virtual void SetShortcutsWindow(cursespp::ShortcutsWindow* w) override;
@ -29,12 +32,14 @@ namespace musik { namespace cube {
void OnSelectionChanged(cursespp::ListWindow* window, size_t index, size_t prev);
void OnPlaybackEvent(int playbackEvent);
void OnTrackChanged(size_t index, musik::core::TrackPtr track);
void OnLyricsLoaded(musik::core::TrackPtr track, const std::string& lyrics);
void SetState(State state);
void LoadLyricsForCurrentTrack();
void UpdateAdapter(const std::string& lyrics);
State state { State::NotPlaying };
musik::core::ILibraryPtr library;
musik::core::audio::PlaybackService& playback;
std::shared_ptr<cursespp::SimpleScrollAdapter> adapter;
std::shared_ptr<cursespp::ListWindow> listView;

@ -97,9 +97,10 @@ MainLayout::MainLayout(
library->Indexer()->Started.connect(this, &MainLayout::OnIndexerStarted);
library->Indexer()->Finished.connect(this, &MainLayout::OnIndexerFinished);
library->Indexer()->Progress.connect(this, &MainLayout::OnIndexerProgress);
playback.TrackChanged.connect(this, &MainLayout::OnTrackChanged);
this->libraryLayout = std::make_shared<LibraryLayout>(playback, library);
this->lyricsLayout = std::make_shared<LyricsLayout>(playback);
this->lyricsLayout = std::make_shared<LyricsLayout>(playback, library);
this->consoleLayout = std::make_shared<ConsoleLayout>(logger);
this->settingsLayout = std::make_shared<SettingsLayout>(app, library, playback);
this->hotkeysLayout = std::make_shared<HotkeysLayout>();
@ -114,6 +115,7 @@ MainLayout::MainLayout(
std::vector<std::string> paths;
library->Indexer()->GetPaths(paths);
this->SetLayout(paths.size() > 0 ? libraryLayout : settingsLayout);
this->SetAutoHideCommandBar(this->prefs->GetBool(prefs::keys::AutoHideCommandBar, false));
this->RunUpdateCheck();
}
@ -231,6 +233,18 @@ void MainLayout::OnIndexerFinished(int count) {
this->Post(message::IndexerFinished);
}
void MainLayout::OnTrackChanged(size_t index, musik::core::TrackPtr track) {
if (!track) {
App::Instance().SetTitle("musikcube");
}
else {
App::Instance().SetTitle(u8fmt(
"musikcube [%s - %s]",
track->GetString("artist").c_str(),
track->GetString("title").c_str()));
}
}
void MainLayout::RunUpdateCheck() {
if (!prefs->GetBool(cube::prefs::keys::AutoUpdateCheck, true)) {
return;

@ -73,6 +73,7 @@ namespace musik {
void OnIndexerStarted();
void OnIndexerProgress(int count);
void OnIndexerFinished(int count);
void OnTrackChanged(size_t index, musik::core::TrackPtr track);
void Initialize();
void RunUpdateCheck();

@ -45,6 +45,7 @@
#include <core/library/query/local/GetPlaylistQuery.h>
#include <core/library/query/local/PersistedPlayQueueQuery.h>
#include <core/support/Duration.h>
#include <core/support/PreferenceKeys.h>
#include "NowPlayingLayout.h"
#include <set>
@ -78,6 +79,9 @@ NowPlayingLayout::NowPlayingLayout(
this->playback.QueueEdited.connect(this, &NowPlayingLayout::RequeryTrackList);
this->trackListView->SetRowRenderer(
TrackRowRenderers::Get(TrackRowRenderers::Type::NowPlaying));
EDIT_KEYS = {
Hotkeys::Get(Hotkeys::PlayQueueMoveUp),
Hotkeys::Get(Hotkeys::PlayQueueMoveDown),
@ -127,7 +131,6 @@ void NowPlayingLayout::InitializeWindows() {
this->trackListView.reset(new TrackListView(
this->playback,
this->library,
std::bind(formatWithAlbum, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
std::bind(&NowPlayingLayout::RowDecorator, this, std::placeholders::_1, std::placeholders::_2)));
this->trackListView->SetFrameTitle(_TSTR("playqueue_title"));
@ -279,52 +282,3 @@ bool NowPlayingLayout::ProcessEditOperation(const std::string& key) {
return false;
}
#define TRACK_COL_WIDTH 3
#define ARTIST_COL_WIDTH 14
#define ALBUM_COL_WIDTH 14
#define DURATION_COL_WIDTH 5 /* 00:00 */
#define DIGITS(x) (x > 9 ? (int) log10((double) x) + 1 : 1)
static std::string formatWithAlbum(TrackPtr track, size_t index, size_t width) {
size_t trackColWidth = std::max(TRACK_COL_WIDTH, DIGITS(index + 1));
std::string trackNum = text::Align(std::to_string(index + 1), text::AlignRight, trackColWidth);
std::string duration = text::Align(
duration::Duration(track->GetString(constants::Track::DURATION)),
text::AlignRight,
DURATION_COL_WIDTH);
std::string album = text::Align(
track->GetString(constants::Track::ALBUM),
text::AlignLeft,
ALBUM_COL_WIDTH);
std::string artist = text::Align(
track->GetString(constants::Track::ARTIST),
text::AlignLeft,
ARTIST_COL_WIDTH);
int titleWidth =
width -
trackColWidth -
DURATION_COL_WIDTH -
ALBUM_COL_WIDTH -
ARTIST_COL_WIDTH -
(4 * 3); /* 3 = spacing */
titleWidth = std::max(0, titleWidth);
std::string title = text::Align(
track->GetString(constants::Track::TITLE),
text::AlignLeft,
(int) titleWidth);
return u8fmt(
"%s %s %s %s %s",
trackNum.c_str(),
title.c_str(),
duration.c_str(),
album.c_str(),
artist.c_str());
}

@ -38,7 +38,6 @@
#include <cursespp/LayoutBase.h>
#include <app/window/CategoryListView.h>
#include <app/window/TrackListView.h>
#include <app/window/TransportWindow.h>
#include <core/audio/PlaybackService.h>
#include <core/support/Preferences.h>

@ -40,12 +40,15 @@
#include <cursespp/InputOverlay.h>
#include <cursespp/Screen.h>
#include <cursespp/SingleLineEntry.h>
#include <cursespp/SchemaOverlay.h>
#include <cursespp/AppLayout.h>
#include <core/library/Indexer.h>
#include <core/library/LocalLibraryConstants.h>
#include <core/support/PreferenceKeys.h>
#include <core/audio/Outputs.h>
#include <core/support/Messages.h>
#include <core/sdk/ISchema.h>
#include <app/util/Hotkeys.h>
#include <app/util/Messages.h>
@ -58,6 +61,7 @@
#include <app/overlay/PluginOverlay.h>
#include <app/overlay/ServerOverlay.h>
#include <app/overlay/PreampOverlay.h>
#include <app/util/Rating.h>
#include "SettingsLayout.h"
@ -99,6 +103,19 @@ static bool showDotfiles = false;
static UpdateCheck updateCheck;
static inline std::shared_ptr<ISchema> AdvancedSettingsSchema() {
std::shared_ptr<TSchema<>> schema(new musik::core::sdk::TSchema<>());
schema->AddBool(cube::prefs::keys::AutoUpdateCheck, false);
#ifdef ENABLE_MINIMIZE_TO_TRAY
schema->AddBool(cube::prefs::keys::MinimizeToTray, false);
schema->AddBool(cube::prefs::keys::StartMinimized, false);
#endif
schema->AddBool(cube::prefs::keys::AutoHideCommandBar, false);
schema->AddString(cube::prefs::keys::RatingPositiveChar, kFilledStar.c_str());
schema->AddString(cube::prefs::keys::RatingNegativeChar, kEmptyStar.c_str());
return schema;
}
static std::string getOutputDeviceName() {
std::string deviceName = _TSTR("settings_output_device_default");
@ -163,7 +180,7 @@ void SettingsLayout::OnCheckboxChanged(cursespp::Checkbox* cb, bool checked) {
else if (cb == seekScrubCheckbox.get()) {
TimeChangeMode mode = cb->IsChecked() ? TimeChangeSeek : TimeChangeScrub;
this->prefs->SetInt(core::prefs::keys::TimeChangeMode, (int)mode);
this->seekScrubCheckbox->SetChecked(this->prefs->GetInt(core::prefs::keys::TimeChangeMode) == (int)TimeChangeSeek);
this->seekScrubCheckbox->SetChecked(this->prefs->GetInt(core::prefs::keys::TimeChangeMode) == (int) TimeChangeSeek);
this->playback.SetTimeChangeMode(mode);
}
#ifdef ENABLE_UNIX_TERMINAL_OPTIONS
@ -171,30 +188,18 @@ void SettingsLayout::OnCheckboxChanged(cursespp::Checkbox* cb, bool checked) {
ColorThemeOverlay::Show256ColorsInfo(
checked,
[this]() {
this->LoadPreferences();
});
this->LoadPreferences();
});
}
else if (cb == enableTransparencyCheckbox.get()) {
auto bgType = checked ? Colors::Inherit : Colors::Theme;
prefs->SetBool(cube::prefs::keys::InheritBackgroundColor, checked);
app.SetColorBackgroundType(bgType);
}
#endif
#ifdef ENABLE_MINIMIZE_TO_TRAY
else if (cb == minimizeToTrayCheckbox.get()) {
app.SetMinimizeToTray(checked);
this->prefs->SetBool(cube::prefs::keys::MinimizeToTray, checked);
}
else if (cb == startMinimizedCheckbox.get()) {
this->prefs->SetBool(cube::prefs::keys::StartMinimized, checked);
}
#endif
else if (cb == saveSessionCheckbox.get()) {
this->prefs->SetBool(core::prefs::keys::SaveSessionOnExit, checked);
}
else if (cb == autoUpdateCheckbox.get()) {
this->prefs->SetBool(cube::prefs::keys::AutoUpdateCheck, checked);
}
}
void SettingsLayout::OnLocaleDropdownActivate(cursespp::TextLabel* label) {
@ -265,6 +270,21 @@ void SettingsLayout::OnServerDropdownActivate(cursespp::TextLabel* label) {
ServerOverlay::Show([this]() { /* nothing, for now */ });
}
void SettingsLayout::OnAdvancedSettingsActivate(cursespp::TextLabel* label) {
SchemaOverlay::Show(
_TSTR("settings_advanced_settings"),
this->prefs,
AdvancedSettingsSchema(),
[this](bool) {
auto prefs = this->prefs;
namespace keys = cube::prefs::keys;
this->app.SetMinimizeToTray(prefs->GetBool(keys::MinimizeToTray, false));
bool autoHideCommandBar = prefs->GetBool(keys::AutoHideCommandBar, false);
((AppLayout*) this->app.GetLayout().get())->SetAutoHideCommandBar(autoHideCommandBar);
updateDefaultRatingSymbols();
});
}
void SettingsLayout::OnUpdateDropdownActivate(cursespp::TextLabel* label) {
updateCheck.Run([this](bool updateRequired, std::string version, std::string url) {
if (updateRequired) {
@ -326,14 +346,12 @@ void SettingsLayout::OnLayout() {
this->syncOnStartupCheckbox->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT);
this->removeCheckbox->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT);
this->seekScrubCheckbox->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT);
#ifdef ENABLE_MINIMIZE_TO_TRAY
this->minimizeToTrayCheckbox->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT);
this->startMinimizedCheckbox->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT);
#endif
this->saveSessionCheckbox->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT);
this->autoUpdateCheckbox->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT);
y++;
++y;
this->advancedDropdown->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT);
++y;
this->updateDropdown->MoveAndResize(column2, y++, columnCx, LABEL_HEIGHT);
}
@ -431,6 +449,10 @@ void SettingsLayout::InitializeWindows() {
this->updateDropdown->SetText(arrow + _TSTR("settings_check_for_updates"));
this->updateDropdown->Activated.connect(this, &SettingsLayout::OnUpdateDropdownActivate);
this->advancedDropdown.reset(new TextLabel());
this->advancedDropdown->SetText(arrow + _TSTR("settings_advanced_settings"));
this->advancedDropdown->Activated.connect(this, &SettingsLayout::OnAdvancedSettingsActivate);
CREATE_CHECKBOX(this->dotfileCheckbox, _TSTR("settings_show_dotfiles"));
CREATE_CHECKBOX(this->syncOnStartupCheckbox, _TSTR("settings_sync_on_startup"));
CREATE_CHECKBOX(this->removeCheckbox, _TSTR("settings_remove_missing"));
@ -439,13 +461,8 @@ void SettingsLayout::InitializeWindows() {
#ifdef ENABLE_UNIX_TERMINAL_OPTIONS
CREATE_CHECKBOX(this->paletteCheckbox, _TSTR("settings_degrade_256"));
CREATE_CHECKBOX(this->enableTransparencyCheckbox, _TSTR("settings_enable_transparency"));
#endif
#ifdef ENABLE_MINIMIZE_TO_TRAY
CREATE_CHECKBOX(this->minimizeToTrayCheckbox, _TSTR("settings_minimize_to_tray"));
CREATE_CHECKBOX(this->startMinimizedCheckbox, _TSTR("settings_start_minimized"));
#endif
CREATE_CHECKBOX(this->saveSessionCheckbox, _TSTR("settings_save_session_on_exit"));
CREATE_CHECKBOX(this->autoUpdateCheckbox, _TSTR("settings_auto_update_check"));
int order = 0;
this->browseList->SetFocusOrder(order++);
@ -472,12 +489,8 @@ void SettingsLayout::InitializeWindows() {
this->syncOnStartupCheckbox->SetFocusOrder(order++);
this->removeCheckbox->SetFocusOrder(order++);
this->seekScrubCheckbox->SetFocusOrder(order++);
#ifdef ENABLE_MINIMIZE_TO_TRAY
this->minimizeToTrayCheckbox->SetFocusOrder(order++);
this->startMinimizedCheckbox->SetFocusOrder(order++);
#endif
this->saveSessionCheckbox->SetFocusOrder(order++);
this->autoUpdateCheckbox->SetFocusOrder(order++);
this->advancedDropdown->SetFocusOrder(order++);
this->updateDropdown->SetFocusOrder(order++);
this->AddWindow(this->browseList);
@ -505,12 +518,8 @@ void SettingsLayout::InitializeWindows() {
this->AddWindow(this->syncOnStartupCheckbox);
this->AddWindow(this->removeCheckbox);
this->AddWindow(this->seekScrubCheckbox);
#ifdef ENABLE_MINIMIZE_TO_TRAY
this->AddWindow(this->minimizeToTrayCheckbox);
this->AddWindow(this->startMinimizedCheckbox);
#endif
this->AddWindow(this->saveSessionCheckbox);
this->AddWindow(this->autoUpdateCheckbox);
this->AddWindow(this->advancedDropdown);
this->AddWindow(updateDropdown);
}
@ -619,12 +628,7 @@ void SettingsLayout::LoadPreferences() {
this->enableTransparencyCheckbox->CheckChanged.connect(this, &SettingsLayout::OnCheckboxChanged);
#endif
#ifdef ENABLE_MINIMIZE_TO_TRAY
this->minimizeToTrayCheckbox->SetChecked(this->prefs->GetBool(cube::prefs::keys::MinimizeToTray, false));
this->startMinimizedCheckbox->SetChecked(this->prefs->GetBool(cube::prefs::keys::StartMinimized, false));
#endif
this->saveSessionCheckbox->SetChecked(this->prefs->GetBool(core::prefs::keys::SaveSessionOnExit, true));
this->autoUpdateCheckbox->SetChecked(this->prefs->GetBool(cube::prefs::keys::AutoUpdateCheck, true));
/* output driver */
std::shared_ptr<IOutput> output = outputs::SelectedOutput();

@ -107,6 +107,7 @@ namespace musik {
void OnServerDropdownActivate(cursespp::TextLabel* label);
void OnUpdateDropdownActivate(cursespp::TextLabel* label);
void OnLastFmDropdownActivate(cursespp::TextLabel* label);
void OnAdvancedSettingsActivate(cursespp::TextLabel* label);
cursespp::Color ListItemDecorator(
cursespp::ScrollableWindow* w,
@ -133,6 +134,7 @@ namespace musik {
Text serverDropdown;
Text updateDropdown;
Text themeDropdown;
Text advancedDropdown;
using Check = std::shared_ptr<cursespp::Checkbox>;
Check paletteCheckbox;
@ -141,10 +143,7 @@ namespace musik {
Check syncOnStartupCheckbox;
Check removeCheckbox;
Check seekScrubCheckbox;
Check minimizeToTrayCheckbox;
Check startMinimizedCheckbox;
Check saveSessionCheckbox;
Check autoUpdateCheckbox;
std::shared_ptr<cursespp::ListWindow> browseList;
std::shared_ptr<cursespp::ListWindow> addedPathsList;

@ -46,6 +46,7 @@
#include <app/util/Playback.h>
#include <app/util/PreferenceKeys.h>
#include <app/overlay/PlayQueueOverlays.h>
#include <app/overlay/TrackOverlays.h>
#include "TrackSearchLayout.h"
@ -64,6 +65,11 @@ namespace components = musik::core::prefs::components;
#define SEARCH_HEIGHT 3
#define REQUERY_INTERVAL_MS 300
static TrackSortType getDefaultTrackSort(std::shared_ptr<musik::core::Preferences> prefs) {
return (TrackSortType) prefs->GetInt(
keys::TrackSearchSortOrder, (int)TrackSortType::Album);
}
TrackSearchLayout::TrackSearchLayout(
musik::core::audio::PlaybackService& playback,
musik::core::ILibraryPtr library)
@ -102,8 +108,6 @@ void TrackSearchLayout::OnLayout() {
y + SEARCH_HEIGHT,
this->GetWidth(),
this->GetHeight() - SEARCH_HEIGHT);
this->trackList->SetFrameTitle(_TSTR("track_filter_title"));
}
void TrackSearchLayout::InitializeWindows() {
@ -116,6 +120,7 @@ void TrackSearchLayout::InitializeWindows() {
this->trackList.reset(new TrackListView(this->playback, this->library));
this->trackList->SetFocusOrder(1);
this->trackList->SetAllowArrowKeyPropagation();
this->trackList->Requeried.connect(this, &TrackSearchLayout::OnRequeried);
this->AddWindow(this->trackList);
}
@ -139,8 +144,9 @@ void TrackSearchLayout::FocusInput() {
void TrackSearchLayout::Requery() {
const std::string& filter = this->input->GetText();
const TrackSortType sortOrder = getDefaultTrackSort(this->prefs);
this->trackList->Requery(std::shared_ptr<TrackListQueryBase>(
new SearchTrackListQuery(this->library, filter)));
new SearchTrackListQuery(this->library, filter, sortOrder)));
}
void TrackSearchLayout::ProcessMessage(IMessage &message) {
@ -151,6 +157,15 @@ void TrackSearchLayout::ProcessMessage(IMessage &message) {
}
}
void TrackSearchLayout::OnRequeried(TrackListQueryBase* query) {
auto searchQuery = dynamic_cast<SearchTrackListQuery*>(query);
if (searchQuery) {
this->trackList->SetFrameTitle(u8fmt(
_TSTR("track_filter_title"),
searchQuery->GetSortDisplayString().c_str()));
}
}
void TrackSearchLayout::OnInputChanged(cursespp::TextInput* sender, std::string value) {
if (this->IsVisible()) {
Debounce(message::RequeryTrackList, 0, 0, REQUERY_INTERVAL_MS);
@ -177,6 +192,15 @@ bool TrackSearchLayout::KeyPress(const std::string& key) {
return true;
}
}
else if (Hotkeys::Is(Hotkeys::TrackListChangeSortOrder, key)) {
TrackOverlays::ShowTrackSearchSortOverlay(
getDefaultTrackSort(this->prefs),
kTrackListOrderByToDisplayKey,
[this](TrackSortType type) {
this->prefs->SetInt(keys::TrackSearchSortOrder, (int)type);
this->Requery();
});
return true;
}
return LayoutBase::KeyPress(key);
}

@ -73,6 +73,8 @@ namespace musik {
void InitializeWindows();
void Requery();
void OnRequeried(musik::core::db::local::TrackListQueryBase* query);
void OnInputChanged(
cursespp::TextInput* sender,
std::string value);

@ -58,6 +58,14 @@ static void buildDriveList(std::vector<std::string>& target) {
}
}
}
static bool shouldBuildDriveList(const boost::filesystem::path& dir) {
std::string pathstr = dir.string();
return
(pathstr.size() == 2 && pathstr[1] == ':') ||
(pathstr.size() == 3 && pathstr[2] == ':') ||
(pathstr.size() == 0);
}
#endif
static bool hasSubdirectories(
@ -82,7 +90,6 @@ static bool hasSubdirectories(
return false;
}
static void buildDirectoryList(
const path& p,
std::vector<std::string>& target,
@ -155,10 +162,7 @@ size_t DirectoryAdapter::Select(cursespp::ListWindow* window) {
}
#ifdef WIN32
std::string pathstr = this->dir.string();
if ((pathstr.size() == 2 && pathstr[1] == ':') ||
(pathstr.size() == 3 && pathstr[2] == ':'))
{
if (shouldBuildDriveList(this->dir)) {
dir = path();
buildDriveList(subdirs);
return selectedIndex;
@ -219,6 +223,12 @@ size_t DirectoryAdapter::GetEntryCount() {
void DirectoryAdapter::SetDotfilesVisible(bool visible) {
if (showDotfiles != visible) {
showDotfiles = visible;
#ifdef WIN32
if (shouldBuildDriveList(this->dir)) {
buildDriveList(subdirs);
return;
}
#endif
buildDirectoryList(dir, subdirs, showDotfiles);
}
}

@ -97,7 +97,7 @@ static std::string formatBandRow(size_t width, const std::string& band, double d
/* TODO: i'm dumb and it's late; we shouldn't need the `+ 1` here, there's
another calculation error that i'm currently blind to. */
int remain = width - (u8cols(leftText) + u8cols(rightText)) + 1;
int remain = (int) width - (u8cols(leftText) + u8cols(rightText)) + 1;
/* pad the area between the left and right text, if necessary */
return text::Align(leftText, text::AlignLeft, u8cols(leftText) + remain) + rightText;

@ -43,19 +43,11 @@
#include <core/sdk/ISchema.h>
#include <cursespp/App.h>
#include <cursespp/Colors.h>
#include <cursespp/DialogOverlay.h>
#include <cursespp/InputOverlay.h>
#include <cursespp/ListOverlay.h>
#include <cursespp/ScrollAdapterBase.h>
#include <cursespp/SingleLineEntry.h>
#include <cursespp/Text.h>
#include <algorithm>
#include <ostream>
#include <iomanip>
#include <limits>
#include <sstream>
#include <cursespp/SchemaOverlay.h>
using namespace musik::core;
using namespace musik::core::sdk;
@ -73,347 +65,14 @@ struct PluginInfo {
using PluginInfoPtr = std::shared_ptr<PluginInfo>;
using PluginList = std::vector<PluginInfoPtr>;
using PrefsPtr = std::shared_ptr<Preferences>;
using SinglePtr = std::shared_ptr<SingleLineEntry>;
using SchemaPtr = std::shared_ptr<ISchema>;
#define DEFAULT(type) reinterpret_cast<const ISchema::type*>(entry)->defaultValue
static size_t DEFAULT_INPUT_WIDTH = 26;
static std::string stringValueForDouble(const double value, const int precision = 2) {
std::ostringstream out;
out << std::fixed << std::setprecision(precision) << value;
return out.str();
}
static std::function<std::string(int)> INT_FORMATTER =
[](int value) -> std::string {
return std::to_string(value);
};
static std::function<std::string(double)> doubleFormatter(int precision) {
return [precision](double value) -> std::string {
return stringValueForDouble(value, precision);
};
}
template <typename T>
bool bounded(T minimum, T maximum) {
return
minimum != std::numeric_limits<T>::min() &&
maximum != std::numeric_limits<T>::max();
}
template <typename T>
std::string numberInputTitle(
std::string keyName,
T minimum,
T maximum,
std::function<std::string(T)> formatter)
{
if (bounded(minimum, maximum)) {
return keyName + " (" + formatter(minimum)
+ " - " + formatter(maximum) + ")";
}
return keyName;
}
template <typename T>
static std::string stringValueFor(
PrefsPtr prefs,
const T* entry,
ISchema::Type type,
const std::string& name)
{
switch (type) {
case ISchema::Type::Bool:
return prefs->GetBool(name, DEFAULT(BoolEntry)) ? "true" : "false";
case ISchema::Type::Int:
return std::to_string(prefs->GetInt(name, DEFAULT(IntEntry)));
case ISchema::Type::Double: {
auto doubleEntry = reinterpret_cast<const ISchema::DoubleEntry*>(entry);
auto defaultValue = doubleEntry->defaultValue;
auto precision = doubleEntry->precision;
return stringValueForDouble(prefs->GetDouble(name, defaultValue), precision);
}
case ISchema::Type::String:
return prefs->GetString(name, DEFAULT(StringEntry));
case ISchema::Type::Enum:
return prefs->GetString(name, DEFAULT(EnumEntry));
}
throw std::runtime_error("invalid type passed to stringValueFor!");
}
template <typename T>
static std::string stringValueFor(PrefsPtr prefs, const T* entry) {
return stringValueFor(prefs, entry, entry->entry.type, entry->entry.name);
}
static std::string stringValueFor(PrefsPtr prefs, const ISchema::Entry* entry) {
return stringValueFor(prefs, entry, entry->type, entry->name);
}
class StringListAdapter : public ScrollAdapterBase {
public:
StringListAdapter(std::vector<std::string>& items) : items(items) { }
std::string At(const size_t index) { return items[index]; }
virtual ~StringListAdapter() { }
virtual size_t GetEntryCount() override { return items.size(); }
virtual EntryPtr GetEntry(cursespp::ScrollableWindow* window, size_t index) override {
auto entry = std::make_shared<SingleLineEntry>(
text::Ellipsize(items[index], window->GetWidth()));
entry->SetAttrs(Color::Default);
if (index == window->GetScrollPosition().logicalIndex) {
entry->SetAttrs(Color::ListItemHighlighted);
}
return entry;
}
private:
std::vector<std::string> items;
};
class SchemaAdapter: public ScrollAdapterBase {
public:
SchemaAdapter(PrefsPtr prefs, SchemaPtr schema): prefs(prefs), schema(schema) {
}
virtual ~SchemaAdapter() {
}
bool Changed() const {
return this->changed;
}
virtual size_t GetEntryCount() override {
return schema->Count();
}
virtual EntryPtr GetEntry(cursespp::ScrollableWindow* window, size_t index) override {
auto entry = schema->At(index);
std::string name = entry->name;
std::string value = stringValueFor(prefs, entry);
int width = window->GetContentWidth();
int avail = std::max(0, width - int(u8cols(name)) - 1 - 1);
auto display = " " + name + " " + text::Align(value + " ", text::AlignRight, avail);
SinglePtr result = SinglePtr(new SingleLineEntry(text::Ellipsize(display, width)));
result->SetAttrs(Color::Default);
if (index == window->GetScrollPosition().logicalIndex) {
result->SetAttrs(Color::ListItemHighlighted);
}
return result;
}
void ShowOverlay(size_t index) {
auto entry = schema->At(index);
switch (entry->type) {
case ISchema::Type::Bool:
return ShowBoolOverlay(reinterpret_cast<const ISchema::BoolEntry*>(entry));
case ISchema::Type::Int:
return ShowIntOverlay(reinterpret_cast<const ISchema::IntEntry*>(entry));
case ISchema::Type::Double:
return ShowDoubleOverlay(reinterpret_cast<const ISchema::DoubleEntry*>(entry));
case ISchema::Type::String:
return ShowStringOverlay(reinterpret_cast<const ISchema::StringEntry*>(entry));
case ISchema::Type::Enum:
return ShowEnumOverlay(reinterpret_cast<const ISchema::EnumEntry*>(entry));
}
}
private:
template <typename T>
struct NumberValidator : public InputOverlay::IValidator {
using Formatter = std::function<std::string(T)>;
NumberValidator(T minimum, T maximum, Formatter formatter)
: minimum(minimum), maximum(maximum), formatter(formatter) {
}
virtual bool IsValid(const std::string& input) const override {
try {
double result = std::stod(input);
if (bounded(minimum, maximum) && (result < minimum || result > maximum)) {
return false;
}
}
catch (std::invalid_argument) {
return false;
}
return true;
}
virtual const std::string ErrorMessage() const override {
if (bounded(minimum, maximum)) {
std::string result = _TSTR("validator_dialog_number_parse_bounded_error");
ReplaceAll(result, "{{minimum}}", formatter(minimum));
ReplaceAll(result, "{{maximum}}", formatter(maximum));
return result;
}
return _TSTR("validator_dialog_number_parse_error");
}
Formatter formatter;
T minimum, maximum;
};
void ShowListOverlay(
const std::string& title,
std::vector<std::string>& items,
const std::string defaultValue,
std::function<void(std::string)> cb)
{
auto stringAdapter = std::make_shared<StringListAdapter>(items);
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
size_t width = u8cols(title) + 4; /* extra padding for border and spacing */
size_t index = 0;
for (size_t i = 0; i < items.size(); i++) {
auto current = items[i];
if (current == defaultValue) {
index = i;
}
width = std::max(width, u8cols(current));
}
dialog->SetAdapter(stringAdapter)
.SetTitle(title)
.SetWidth(width)
.SetSelectedIndex(index)
.SetAutoDismiss(true)
.SetItemSelectedCallback(
[cb, stringAdapter](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
if (cb) {
cb(stringAdapter->At(index));
}
});
cursespp::App::Overlays().Push(dialog);
}
void ShowBoolOverlay(const ISchema::BoolEntry* entry) {
auto name = entry->entry.name;
std::vector<std::string> items = { "true", "false" };
ShowListOverlay(name, items, stringValueFor(prefs, entry),
[this, name](std::string value) {
this->prefs->SetBool(name, value == "true");
this->changed = true;
});
}
void ShowIntOverlay(const ISchema::IntEntry* entry) {
auto name = entry->entry.name;
auto title = numberInputTitle(
name, entry->minValue, entry->maxValue, INT_FORMATTER);
auto validator = std::make_shared<NumberValidator<int>>(
entry->minValue, entry->maxValue, INT_FORMATTER);
auto width = std::max(u8cols(title), DEFAULT_INPUT_WIDTH);
std::shared_ptr<InputOverlay> dialog(new InputOverlay());
dialog->SetTitle(title)
.SetText(stringValueFor(prefs, entry))
.SetValidator(validator)
.SetWidth(width)
.SetInputAcceptedCallback([this, name](const std::string& value) {
this->prefs->SetInt(name, std::stoi(value));
this->changed = true;
});
App::Overlays().Push(dialog);
}
void ShowDoubleOverlay(const ISchema::DoubleEntry* entry) {
auto name = entry->entry.name;
auto formatter = doubleFormatter(entry->precision);
auto title = numberInputTitle(
name, entry->minValue, entry->maxValue, formatter);
auto validator = std::make_shared<NumberValidator<double>>(
entry->minValue, entry->maxValue, formatter);
auto width = std::max(u8cols(title) + 4, DEFAULT_INPUT_WIDTH);
std::shared_ptr<InputOverlay> dialog(new InputOverlay());
dialog->SetTitle(title)
.SetText(stringValueFor(prefs, entry))
.SetValidator(validator)
.SetWidth(width)
.SetInputAcceptedCallback([this, name](const std::string& value) {
this->prefs->SetDouble(name, std::stod(value));
this->changed = true;
});
App::Overlays().Push(dialog);
}
void ShowStringOverlay(const ISchema::StringEntry* entry) {
auto name = entry->entry.name;
std::shared_ptr<InputOverlay> dialog(new InputOverlay());
dialog->SetTitle(name)
.SetText(stringValueFor(prefs, entry))
.SetInputAcceptedCallback([this, name](const std::string& value) {
this->prefs->SetString(name, value.c_str());
this->changed = true;
});
App::Overlays().Push(dialog);
}
void ShowEnumOverlay(const ISchema::EnumEntry* entry) {
auto name = entry->entry.name;
std::vector<std::string> items;
for (size_t i = 0; i < entry->count; i++) {
items.push_back(entry->values[i]);
}
ShowListOverlay(name, items, stringValueFor(prefs, entry),
[this, name](std::string value) {
this->prefs->SetString(name, value.c_str());
this->changed = true;
});
}
PrefsPtr prefs;
SchemaPtr schema;
bool changed{false};
};
static void showConfigureOverlay(IPlugin* plugin, SchemaPtr schema) {
auto prefs = Preferences::ForPlugin(plugin->Name());
std::shared_ptr<SchemaAdapter> schemaAdapter(new SchemaAdapter(prefs, schema));
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
std::string title = _TSTR("settings_configure_plugin_title");
ReplaceAll(title, "{{name}}", plugin->Name());
dialog->SetAdapter(schemaAdapter)
.SetTitle(title)
.SetWidthPercent(80)
.SetAutoDismiss(false)
.SetItemSelectedCallback(
[schemaAdapter](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
schemaAdapter->ShowOverlay(index);
})
.SetDismissedCallback([plugin, schemaAdapter](ListOverlay* overlay) {
if (schemaAdapter->Changed()) {
plugin->Reload();
}
});
cursespp::App::Overlays().Push(dialog);
auto prefs = Preferences::ForPlugin(plugin->Name());
SchemaOverlay::Show(title, prefs, schema, [](bool) {});
}
static void showNoSchemaDialog(const std::string& name) {

@ -0,0 +1,133 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#include <stdafx.h>
#include "TrackOverlays.h"
#include <core/library/query/local/util/TrackSort.h>
#include <core/library/query/local/SetTrackRatingQuery.h>
#include <cursespp/SimpleScrollAdapter.h>
#include <cursespp/ListOverlay.h>
#include <cursespp/DialogOverlay.h>
#include <cursespp/App.h>
#include <app/util/Rating.h>
#include <set>
using namespace cursespp;
using namespace musik::cube;
using namespace musik::core;
using namespace musik::core::db;
using namespace musik::core::db::local;
static const int kDefaultSortOverlayWidth = 32;
static const int kDefaultRatingOverlayWidth = 24;
void TrackOverlays::ShowTrackSearchSortOverlay(
TrackSortType sortType,
const std::map<TrackSortType, std::string>& availableSortTypes,
std::function<void(TrackSortType)> callback)
{
size_t i = 0;
size_t selectedIndex = 0;
std::vector<TrackSortType> allKeys;
auto adapter = std::make_shared<SimpleScrollAdapter>();
adapter->SetSelectable(true);
for (auto it : availableSortTypes) {
allKeys.push_back(it.first);
adapter->AddEntry(_TSTR(it.second));
if (it.first == sortType) {
selectedIndex = i;
}
++i;
}
auto dialog = std::make_shared<ListOverlay>();
dialog->SetAdapter(adapter)
.SetTitle(_TSTR("track_list_sort_overlay_title"))
.SetWidth(_DIMEN("track_search_sort_order_width", kDefaultSortOverlayWidth))
.SetSelectedIndex(selectedIndex)
.SetItemSelectedCallback(
[callback, allKeys](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
callback(allKeys[index]);
});
cursespp::App::Overlays().Push(dialog);
}
void TrackOverlays::ShowRateTrackOverlay(
musik::core::TrackPtr track,
musik::core::ILibraryPtr library,
std::function<void(bool)> callback)
{
int64_t trackId = track->GetId();
int currentRating = track->GetInt32("rating", 0);
currentRating = std::max(0, std::min(5, currentRating));
auto adapter = std::make_shared<SimpleScrollAdapter>();
adapter->SetSelectable(true);
for (int i = 0; i <= 5; i++) {
adapter->AddEntry(getRatingString(i));
}
auto dialog = std::make_shared<ListOverlay>();
auto updateRatingInLibrary = [track, library, callback, dialog](int index) {
auto query = std::make_shared<SetTrackRatingQuery>(track->GetId(), (int) index);
library->Enqueue(query, ILibrary::QuerySynchronous);
dialog->Dismiss();
callback(true);
};
dialog->SetAdapter(adapter)
.SetTitle(_TSTR("track_list_rate_track_overlay_title"))
.SetWidth(_DIMEN("track_list_rate_track_width", kDefaultRatingOverlayWidth))
.SetSelectedIndex((int) currentRating)
.SetKeyInterceptorCallback(
[updateRatingInLibrary](ListOverlay* overlay, std::string key) -> bool {
if (key == "0") { updateRatingInLibrary(0); return true; }
if (key == "1") { updateRatingInLibrary(1); return true; }
if (key == "2") { updateRatingInLibrary(2); return true; }
if (key == "3") { updateRatingInLibrary(3); return true; }
if (key == "4") { updateRatingInLibrary(4); return true; }
if (key == "5") { updateRatingInLibrary(5); return true; }
return false;
})
.SetItemSelectedCallback(
[updateRatingInLibrary]
(ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
updateRatingInLibrary((int) index);
});
cursespp::App::Overlays().Push(dialog);
}

@ -0,0 +1,60 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#pragma once
#include <core/library/query/local/util/TrackSort.h>
#include <core/library/ILibrary.h>
#include <core/library/track/Track.h>
#include <functional>
namespace musik {
namespace cube {
class TrackOverlays {
public:
using TrackSortType = musik::core::db::local::TrackSortType;
static void ShowTrackSearchSortOverlay(
TrackSortType currentSortType,
const std::map<TrackSortType, std::string>& availableSortTypes,
std::function<void(TrackSortType)> callback);
static void ShowRateTrackOverlay(
musik::core::TrackPtr track,
musik::core::ILibraryPtr library,
std::function<void(bool)> callback);
};
}
}

@ -98,6 +98,10 @@ static std::unordered_map<std::string, Id> NAME_TO_ID = {
{ "browse_playlists_rename", Id::BrowsePlaylistsRename },
{ "browse_playlists_delete", Id::BrowsePlaylistsDelete },
{ "track_list_change_sort_order", Id::TrackListChangeSortOrder },
{ "track_list_rate_track", Id::TrackListRateTrack },
{ "lyrics_retry", Id::LyricsRetry },
{ "playback_toggle_mute", Id::ToggleMute },
@ -184,6 +188,10 @@ static std::unordered_map<Id, std::string, EnumHasher> ID_TO_DEFAULT = {
{ Id::BrowsePlaylistsDelete, "KEY_DC" },
#endif
{ Id::TrackListChangeSortOrder, "^S" },
{ Id::TrackListRateTrack, "r" },
{ Id::LyricsRetry, "r" },
{ Id::ToggleMute, "m" },

@ -102,12 +102,16 @@ namespace musik {
PlayQueuePlaylistDelete,
PlayQueueHotSwap,
/* browse ->playlists */
/* browse -> playlists */
BrowsePlaylistsNew,
BrowsePlaylistsSave,
BrowsePlaylistsRename,
BrowsePlaylistsDelete,
/* tracklist items */
TrackListRateTrack,
TrackListChangeSortOrder,
/* lyrics */
LyricsRetry,

@ -52,6 +52,11 @@ namespace musik { namespace cube { namespace prefs {
const std::string keys::LastBrowseDirectoryRoot = "LastBrowseDirectoryRoot";
const std::string keys::LastCategoryFilter = "LastCategoryFilter";
const std::string keys::LastTrackFilter = "LastTrackFilter";
const std::string keys::TrackSearchSortOrder = "TrackSearchSortOrder";
const std::string keys::CategoryTrackListSortOrder = "CategoryTrackListSortOrder";
const std::string keys::RatingPositiveChar = "RatingPositiveChar";
const std::string keys::RatingNegativeChar = "RatingNegativeChar";
const std::string keys::AutoHideCommandBar = "AutoHideCommandBar";
const std::string keys::AppQuitKey = "AppQuitKey";
} } }

@ -54,6 +54,11 @@ namespace musik { namespace cube { namespace prefs {
extern const std::string LastBrowseDirectoryRoot;
extern const std::string LastCategoryFilter;
extern const std::string LastTrackFilter;
extern const std::string TrackSearchSortOrder;
extern const std::string CategoryTrackListSortOrder;
extern const std::string RatingPositiveChar;
extern const std::string RatingNegativeChar;
extern const std::string AutoHideCommandBar;
extern const std::string AppQuitKey;
}

@ -0,0 +1,87 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#include <stdafx.h>
#include "Rating.h"
#include <core/support/Preferences.h>
#include <core/support/PreferenceKeys.h>
#include <core/utfutil.h>
#include <app/util/PreferenceKeys.h>
#include <string>
#include <map>
using namespace musik::core;
using namespace musik::core::prefs;
static bool initialized = false;
namespace musik { namespace cube {
static std::map<int, std::string> kRatingToSymbols = {
{ 0, kEmptyStar + kEmptyStar + kEmptyStar + kEmptyStar + kEmptyStar },
{ 1, kFilledStar + kEmptyStar + kEmptyStar + kEmptyStar + kEmptyStar },
{ 2, kFilledStar + kFilledStar + kEmptyStar + kEmptyStar + kEmptyStar },
{ 3, kFilledStar + kFilledStar + kFilledStar + kEmptyStar + kEmptyStar },
{ 4, kFilledStar + kFilledStar + kFilledStar + kFilledStar + kEmptyStar },
{ 5, kFilledStar + kFilledStar + kFilledStar + kFilledStar + kFilledStar },
};
const std::string getRatingString(int value) {
if (!initialized) {
updateDefaultRatingSymbols();
}
return kRatingToSymbols.find(value)->second;
}
extern void updateDefaultRatingSymbols() {
initialized = true;
auto prefs = Preferences::ForComponent(components::Settings);
std::string filled = prefs->GetString(musik::cube::prefs::keys::RatingPositiveChar, kFilledStar);
if (!filled.size()) filled = kFilledStar;
std::string empty = prefs->GetString(musik::cube::prefs::keys::RatingNegativeChar, kEmptyStar);
if (!empty.size()) filled = kEmptyStar;
filled = u8substr(filled, 0, 1);
empty = u8substr(empty, 0, 1);
kRatingToSymbols[0] = empty + empty + empty + empty + empty;
kRatingToSymbols[1] = filled + empty + empty + empty + empty;
kRatingToSymbols[2] = filled + filled + empty + empty + empty;
kRatingToSymbols[3] = filled + filled + filled + empty + empty;
kRatingToSymbols[4] = filled + filled + filled + filled + empty;
kRatingToSymbols[5] = filled + filled + filled + filled + filled;
}
} }

@ -0,0 +1,48 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#pragma once
#include <string>
#include <map>
namespace musik { namespace cube {
const std::string kFilledStar = "\xE2\x98\x85";
const std::string kEmptyStar = "\xC2\xB7";
extern const std::string getRatingString(int value);
extern void updateDefaultRatingSymbols();
} }

@ -0,0 +1,189 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#include <stdafx.h>
#include <math.h>
#include "TrackRowRenderers.h"
#include <cursespp/Colors.h>
#include <cursespp/SingleLineEntry.h>
#include <cursespp/Text.h>
#include <core/library/LocalLibraryConstants.h>
#include <app/util/Rating.h>
#include <core/support/Duration.h>
using namespace musik::core;
using namespace musik::core::library;
using namespace musik::cube;
using namespace musik::cube::TrackRowRenderers;
using namespace cursespp;
#define DIGITS(x) (x > 9 ? (int) log10((double) x) + 1 : 1)
static const int kDurationColWidth = 5; /* 00:00 */
static const int kRatingBreakpointWidth = 90;
static inline std::string getRatingForTrack(TrackPtr track, size_t width) {
int rating = std::max(0, std::min(5, track->GetInt32("rating", 0)));
return (width > kRatingBreakpointWidth) ? " " + getRatingString(rating) : "";
}
namespace AlbumSort {
static const int kTrackColWidth = 3;
static const int kArtistColWidth = 17;
static const int kRatingColumnWidth = 5;
static Renderer renderer = [](TrackPtr track, size_t index, size_t width, TrackNumType type) -> std::string {
std::string trackNum;
std::string rating = getRatingForTrack(track, width);
int trackColWidth = kTrackColWidth;
if (type == TrackNumType::Metadata) {
trackNum = text::Align(
track->GetString(constants::Track::TRACK_NUM),
text::AlignRight,
kTrackColWidth);
}
else {
trackColWidth = std::max(kTrackColWidth, DIGITS(index + 1));
trackNum = text::Align(std::to_string(index + 1), text::AlignRight, trackColWidth);
}
std::string duration = text::Align(
musik::core::duration::Duration(track->GetString(constants::Track::DURATION)),
text::AlignRight,
kDurationColWidth);
std::string artist = text::Align(
track->GetString(constants::Track::ARTIST),
text::AlignLeft,
kArtistColWidth);
int titleWidth = 0;
titleWidth =
(int) width -
(int) trackColWidth -
kDurationColWidth -
kArtistColWidth -
u8len(rating) -
(3 * 3); /* 3 = spacing */
titleWidth = std::max(0, titleWidth);
std::string title = text::Align(
track->GetString(constants::Track::TITLE),
text::AlignLeft,
(int) titleWidth);
return u8fmt(
"%s %s%s %s %s",
trackNum.c_str(),
title.c_str(),
rating.c_str(),
duration.c_str(),
artist.c_str());
};
}
namespace NowPlaying {
static const int kTrackColWidth = 3;
static const int kArtistColWidth = 14;
static const int kAlbumColWidth = 14;
static Renderer renderer = [](TrackPtr track, size_t index, size_t width, TrackNumType type) -> std::string {
size_t trackColWidth = std::max(kTrackColWidth, DIGITS(index + 1));
std::string trackNum = text::Align(std::to_string(index + 1), text::AlignRight, trackColWidth);
std::string rating = getRatingForTrack(track, width);
std::string duration = text::Align(
duration::Duration(track->GetString(constants::Track::DURATION)),
text::AlignRight,
kDurationColWidth);
std::string album = text::Align(
track->GetString(constants::Track::ALBUM),
text::AlignLeft,
kAlbumColWidth);
std::string artist = text::Align(
track->GetString(constants::Track::ARTIST),
text::AlignLeft,
kArtistColWidth);
int titleWidth =
(int) width -
(int) trackColWidth -
kDurationColWidth -
kAlbumColWidth -
kArtistColWidth -
u8cols(rating) -
(4 * 3); /* 3 = spacing */
titleWidth = std::max(0, titleWidth);
std::string title = text::Align(
track->GetString(constants::Track::TITLE),
text::AlignLeft,
(int) titleWidth);
return u8fmt(
"%s %s%s %s %s %s",
trackNum.c_str(),
title.c_str(),
rating.c_str(),
duration.c_str(),
album.c_str(),
artist.c_str());
};
}
namespace musik {
namespace cube {
namespace TrackRowRenderers {
const Renderer Get(Type type) {
switch (type) {
case Type::AlbumSort: return AlbumSort::renderer;
case Type::NowPlaying: return NowPlaying::renderer;
}
throw std::runtime_error("invalid type specified to TrackRowRenderers::Get");
}
}
}
}

@ -0,0 +1,62 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#pragma once
#include <core/library/track/Track.h>
namespace musik {
namespace cube {
namespace TrackRowRenderers {
enum class Type {
AlbumSort,
NowPlaying,
};
enum class TrackNumType {
Metadata,
Sequential,
};
using Renderer = std::function<std::string(
musik::core::TrackPtr /*metadata*/,
size_t /*index*/,
size_t /*width*/,
TrackNumType /*type*/
)>;
extern const Renderer Get(Type type);
}
}
}

@ -3,9 +3,9 @@
#include <string>
#define VERSION_MAJOR 0
#define VERSION_MINOR 70
#define VERSION_MINOR 80
#define VERSION_PATCH 0
#define VERSION "0.70.0"
#define VERSION "0.80.0"
namespace musik {
namespace cube {

@ -48,6 +48,7 @@
#include <app/util/Hotkeys.h>
#include <app/util/Playback.h>
#include <app/overlay/PlayQueueOverlays.h>
#include <app/overlay/TrackOverlays.h>
#define WINDOW_MESSAGE_SCROLL_TO_PLAYING 1003
@ -74,7 +75,6 @@ static inline milliseconds now() {
TrackListView::TrackListView(
PlaybackService& playback,
ILibraryPtr library,
RowFormatter formatter,
RowDecorator decorator)
: ListWindow(nullptr)
, playback(playback) {
@ -84,9 +84,9 @@ TrackListView::TrackListView(
this->adapter = new Adapter(*this);
this->lastQueryHash = 0;
this->lastChanged = now();
this->formatter = formatter;
this->decorator = decorator;
this->trackNumType = TrackNumType::Metadata;
this->trackNumType = TrackRowRenderers::TrackNumType::Metadata;
this->renderer = TrackRowRenderers::Get(TrackRowRenderers::Type::AlbumSort);
}
TrackListView::~TrackListView() {
@ -103,13 +103,20 @@ void TrackListView::SelectFirstTrack() {
this->SetSelectedIndex(this->headers.HeaderAt(0) ? 1 : 0);
}
void TrackListView::SetTrackNumType(TrackNumType type) {
void TrackListView::SetTrackNumType(TrackRowRenderers::TrackNumType type) {
if (this->trackNumType != type) {
this->trackNumType = type;
this->OnAdapterChanged();
}
}
void TrackListView::SetRowRenderer(TrackRowRenderers::Renderer renderer) {
if (!renderer) {
throw std::runtime_error("invalid Renderer supplied to TrackListView::SetRowRenderer");
}
this->renderer = renderer;
}
void TrackListView::OnQueryCompleted(IQuery* query) {
if (this->query && query == this->query.get()) {
if (this->query->GetStatus() == IQuery::Finished) {
@ -160,6 +167,11 @@ void TrackListView::Clear() {
this->OnAdapterChanged();
}
void TrackListView::InvalidateData() {
this->tracks->ClearCache();
this->OnAdapterChanged();
}
TrackPtr TrackListView::GetSelectedTrack() {
auto i = this->GetSelectedTrackIndex();
return (i == ListWindow::NO_SELECTION) ? TrackPtr() : this->tracks->Get(i);
@ -261,6 +273,20 @@ bool TrackListView::KeyPress(const std::string& key) {
}
handled = true;
}
else if (Hotkeys::Is(Hotkeys::TrackListRateTrack, key)) {
TrackPtr track = this->GetSelectedTrack();
if (track && !headers.HeaderAt(this->GetSelectedIndex())) {
TrackOverlays::ShowRateTrackOverlay(
track,
this->library,
[this](bool success) {
if (success && this->tracks) {
this->InvalidateData();
}
});
}
handled = true;
}
if (!handled) {
handled = ListWindow::KeyPress(key);
@ -352,60 +378,6 @@ size_t TrackListView::Adapter::GetEntryCount() {
return parent.tracks ? parent.tracks->Count() + parent.headers.Count() : 0;
}
#define TRACK_COL_WIDTH 3
#define ARTIST_COL_WIDTH 17
#define DURATION_COL_WIDTH 5 /* 00:00 */
#define DIGITS(x) (x > 9 ? (int) log10((double) x) + 1 : 1)
using TrackNumType = TrackListView::TrackNumType;
static std::string formatWithoutAlbum(TrackPtr track, size_t index, size_t width, TrackNumType type) {
std::string trackNum;
int trackColWidth = TRACK_COL_WIDTH;
if (type == TrackNumType::Metadata) {
trackNum = text::Align(
track->GetString(constants::Track::TRACK_NUM),
text::AlignRight,
TRACK_COL_WIDTH);
}
else {
trackColWidth = std::max(TRACK_COL_WIDTH, DIGITS(index + 1));
trackNum = text::Align(std::to_string(index + 1), text::AlignRight, trackColWidth);
}
std::string duration = text::Align(
musik::core::duration::Duration(track->GetString(constants::Track::DURATION)),
text::AlignRight,
DURATION_COL_WIDTH);
std::string artist = text::Align(
track->GetString(constants::Track::ARTIST),
text::AlignLeft,
ARTIST_COL_WIDTH);
int titleWidth =
width -
trackColWidth -
DURATION_COL_WIDTH -
ARTIST_COL_WIDTH -
(3 * 3); /* 3 = spacing */
titleWidth = std::max(0, titleWidth);
std::string title = text::Align(
track->GetString(constants::Track::TITLE),
text::AlignLeft,
(int) titleWidth);
return u8fmt(
"%s %s %s %s",
trackNum.c_str(),
title.c_str(),
duration.c_str(),
artist.c_str());
}
IScrollAdapter::EntryPtr TrackListView::Adapter::GetEntry(cursespp::ScrollableWindow* window, size_t rawIndex) {
bool selected = (rawIndex == parent.GetSelectedIndex());
@ -466,9 +438,8 @@ IScrollAdapter::EntryPtr TrackListView::Adapter::GetEntry(cursespp::ScrollableWi
}
}
std::string text = parent.formatter
? parent.formatter(track, rawIndex, this->GetWidth())
: formatWithoutAlbum(track, rawIndex, this->GetWidth(), parent.trackNumType);
std::string text = parent.renderer(
track, rawIndex, this->GetWidth(), parent.trackNumType);
std::shared_ptr<TrackListEntry> entry(
new TrackListEntry(text, trackIndex, RowType::Track));

@ -47,6 +47,8 @@
#include <core/runtime/IMessage.h>
#include <core/library/ILibrary.h>
#include <app/util/TrackRowRenderers.h>
namespace musik {
namespace cube {
class TrackListView:
@ -54,8 +56,6 @@ namespace musik {
public sigslot::has_slots<>
{
public:
enum class TrackNumType: int { Metadata = 0, Sequential = 1 };
typedef musik::core::TrackPtr TrackPtr;
typedef musik::core::db::local::TrackListQueryBase TrackListQueryBase;
@ -63,7 +63,6 @@ namespace musik {
sigslot::signal1<musik::core::db::local::TrackListQueryBase*> Requeried;
/* types */
typedef std::function<std::string(TrackPtr, size_t, size_t)> RowFormatter;
typedef std::function<cursespp::Color(TrackPtr, size_t)> RowDecorator;
typedef std::shared_ptr<std::set<size_t> > Headers;
@ -71,7 +70,6 @@ namespace musik {
TrackListView(
musik::core::audio::PlaybackService& playback,
musik::core::ILibraryPtr library,
RowFormatter formatter = RowFormatter(),
RowDecorator decorator = RowDecorator());
virtual ~TrackListView();
@ -89,7 +87,10 @@ namespace musik {
void Clear();
size_t TrackCount();
size_t EntryCount();
void SetTrackNumType(TrackNumType type);
void InvalidateData();
void SetTrackNumType(TrackRowRenderers::TrackNumType type);
void SetRowRenderer(TrackRowRenderers::Renderer renderer);
void Requery(std::shared_ptr<TrackListQueryBase> query);
@ -166,10 +167,10 @@ namespace musik {
musik::core::TrackPtr playing;
musik::core::ILibraryPtr library;
size_t lastQueryHash;
RowFormatter formatter;
RowDecorator decorator;
TrackRowRenderers::Renderer renderer;
std::chrono::milliseconds lastChanged;
TrackNumType trackNumType;
TrackRowRenderers::TrackNumType trackNumType;
};
}
}

@ -583,7 +583,7 @@ void TransportWindow::Update(TimeMode timeMode) {
}
/* draw the "shuffle" label */
const int shuffleOffset = cx - shuffleWidth;
const int shuffleOffset = (int) (cx - shuffleWidth);
wmove(c, 0, shuffleOffset);
Color shuffleAttrs = this->playback.IsShuffled() ? gb : disabled;
ON(c, shuffleAttrs);
@ -593,7 +593,7 @@ void TransportWindow::Update(TimeMode timeMode) {
/* volume slider */
int volumePercent = (size_t) round(this->transport.Volume() * 100.0f);
int volumePercent = (int) round(this->transport.Volume() * 100.0f);
int thumbOffset = std::min(10, (volumePercent * 10) / 100);
std::string volume;

@ -110,9 +110,10 @@ App::App(const std::string& title) {
this->minWidth = this->minHeight = 0;
this->mouseEnabled = true;
this->SetTitle(title);
#ifdef WIN32
this->iconId = 0;
this->appTitle = title;
this->colorMode = Colors::RGB;
win32::ConfigureDpiAwareness();
#else
@ -233,6 +234,29 @@ void App::SetDefaultMenuVisibility(bool visible) {
}
#endif
void App::SetTitle(const std::string& title) {
this->appTitle = title;
#ifdef WIN32
PDC_set_title(this->appTitle.c_str());
win32::SetAppTitle(this->appTitle);
#else
/* stolen from https://github.com/cmus/cmus/blob/fead80b207b79ae6d10ab2b1601b11595d719908/ui_curses.c#L2349 */
const char* term = getenv("TERM");
if (term) {
if (!strcmp(term, "screen")) {
std::cout << "\033_" << this->appTitle.c_str() << "\033\\";
}
else if (!strncmp(term, "xterm", 5) ||
!strncmp(term, "rxvt", 4) ||
!strcmp(term, "Eterm"))
{
std::cout << "\033]0;" << this->appTitle.c_str() << "\007";
}
}
Window::InvalidateScreen();
#endif
}
void App::SetMinimizeToTray(bool minimizeToTray) {
#ifdef WIN32
win32::SetMinimizeToTray(minimizeToTray);
@ -506,6 +530,10 @@ void App::CheckShowOverlay() {
}
}
ILayoutPtr App::GetLayout() {
return this->state.layout;
}
void App::ChangeLayout(ILayoutPtr newLayout) {
if (this->state.layout == newLayout) {
return;

@ -79,19 +79,30 @@ void AppLayout::OnLayout() {
--cy;
#endif
bool sf = this->shortcutsFocused;
int mainCyOffset = this->autoHideCommandBar ? 0 : 1;
if (this->layout) {
this->layout->MoveAndResize(paddingL, paddingT, cx, cy - 1);
this->layout->MoveAndResize(paddingL, paddingT, cx, cy - mainCyOffset);
this->layout->Show();
this->layout->BringToTop();
if (this->shortcutsFocused) {
this->layout->SetFocus(IWindowPtr());
}
}
this->shortcuts->MoveAndResize(
0, Screen::GetHeight() - 1,
Screen::GetWidth(), 1);
0, Screen::GetHeight() - 1, Screen::GetWidth(), 1);
if (this->autoHideCommandBar) {
if (sf) {
this->shortcuts->Show();
this->shortcuts->BringToTop();
}
else {
this->shortcuts->Hide();
}
}
else {
this->shortcuts->Show();
}
}
void AppLayout::Initialize() {
@ -179,8 +190,9 @@ void AppLayout::SetLayout(std::shared_ptr<cursespp::LayoutBase> layout) {
}
cursespp::IWindowPtr AppLayout::BlurShortcuts() {
this->shortcuts->Blur();
this->shortcutsFocused = false;
this->shortcuts->Hide();
this->shortcuts->Blur();
if (this->layout) {
bool refocused = false;
@ -194,10 +206,13 @@ cursespp::IWindowPtr AppLayout::BlurShortcuts() {
}
}
this->Layout();
return this->layout ? this->layout->GetFocus() : IWindowPtr();
}
void AppLayout::FocusShortcuts() {
this->shortcuts->Show();
this->Layout();
this->shortcuts->Focus();
if (this->layout) {
@ -239,6 +254,17 @@ bool AppLayout::KeyPress(const std::string& key) {
return this->layout ? this->layout->KeyPress(key) : false;
}
void AppLayout::SetAutoHideCommandBar(bool autoHide) {
if (autoHide != this->autoHideCommandBar) {
this->autoHideCommandBar = autoHide;
this->Layout();
}
}
bool AppLayout::GetAutoHideCommandBar() {
return this->autoHideCommandBar;
}
void AppLayout::EnableDemoModeIfNecessary() {
#if ENABLE_DEMO_MODE
App::Instance().SetKeyHook([this](const std::string& key) -> bool {

@ -58,6 +58,7 @@ InputOverlay::InputOverlay() {
this->SetFrameColor(Color::OverlayFrame);
this->SetContentColor(Color::OverlayContent);
this->allowEmptyValue = false;
this->width = this->height = this->setWidth = 0;
this->textInput.reset(new TextInput());
@ -104,6 +105,11 @@ InputOverlay& InputOverlay::SetText(const std::string& text) {
return *this;
}
InputOverlay& InputOverlay::SetAllowEmptyValue(bool allowEmptyValue) {
this->allowEmptyValue = allowEmptyValue;
return *this;
}
InputOverlay& InputOverlay::SetWidth(int width) {
this->setWidth = width;
@ -133,7 +139,7 @@ bool InputOverlay::KeyPress(const std::string& key) {
}
void InputOverlay::OnInputEnterPressed(TextInput* input) {
if (input->GetText().size()) {
if (input->GetText().size() || this->allowEmptyValue) {
if (validator && !validator->IsValid(input->GetText())) {
std::shared_ptr<DialogOverlay> dialog(new DialogOverlay());

@ -361,7 +361,7 @@ void LayoutBase::SetFocusIndex(int index) {
}
int LayoutBase::GetFocusableCount() {
return this->focusable.size();
return (int) this->focusable.size();
}
IWindowPtr LayoutBase::GetFocusableAt(int index) {

@ -104,10 +104,10 @@ void ListWindow::ScrollUp(int delta) {
size_t first = spos.firstVisibleEntryIndex;
size_t last = first + spos.visibleEntryCount;
int drawIndex = first;
int drawIndex = (int) first;
int minIndex = 0;
int newIndex = this->selectedIndex - delta;
int newIndex = (int) this->selectedIndex - delta;
newIndex = std::max(newIndex, minIndex);
if (newIndex < (int)first + 1) {

@ -0,0 +1,417 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#include <stdafx.h>
#include <cursespp/SchemaOverlay.h>
#include <core/i18n/Locale.h>
#include <core/utfutil.h>
#include <cursespp/App.h>
#include <cursespp/Colors.h>
#include <cursespp/DialogOverlay.h>
#include <cursespp/InputOverlay.h>
#include <cursespp/ListOverlay.h>
#include <cursespp/NumberValidator.h>
#include <cursespp/Screen.h>
#include <cursespp/ScrollAdapterBase.h>
#include <cursespp/SingleLineEntry.h>
#include <cursespp/Text.h>
#include <sstream>
#include <iomanip>
using namespace musik::core::sdk;
using namespace musik::core::i18n;
using namespace cursespp;
using SchemaPtr = SchemaOverlay::SchemaPtr;
using PrefsPtr = SchemaOverlay::PrefsPtr;
using SinglePtr = std::shared_ptr<SingleLineEntry>;
#define DEFAULT(type) reinterpret_cast<const ISchema::type*>(entry)->defaultValue
static size_t DEFAULT_INPUT_WIDTH = 26;
static size_t MINIMUM_OVERLAY_WIDTH = 16;
static int overlayWidth() {
return (int)(0.8f * (float) Screen::GetWidth());
}
static std::string stringValueForDouble(const double value, const int precision = 2) {
std::ostringstream out;
out << std::fixed << std::setprecision(precision) << value;
return out.str();
}
static std::function<std::string(int)> intFormatter =
[](int value) -> std::string {
return std::to_string(value);
};
static std::function<std::string(double)> doubleFormatter(int precision) {
return [precision](double value) -> std::string {
return stringValueForDouble(value, precision);
};
}
template <typename T>
std::string numberInputTitle(
std::string keyName,
T minimum,
T maximum,
std::function<std::string(T)> formatter)
{
if (NumberValidator<T>::bounded(minimum, maximum)) {
return keyName + " (" + formatter(minimum)
+ " - " + formatter(maximum) + ")";
}
return keyName;
}
template <typename T>
static std::string stringValueFor(
PrefsPtr prefs,
const T* entry,
ISchema::Type type,
const std::string& name)
{
switch (type) {
case ISchema::Type::Bool:
return prefs->GetBool(name, DEFAULT(BoolEntry)) ? "true" : "false";
case ISchema::Type::Int:
return std::to_string(prefs->GetInt(name, DEFAULT(IntEntry)));
case ISchema::Type::Double: {
auto doubleEntry = reinterpret_cast<const ISchema::DoubleEntry*>(entry);
auto defaultValue = doubleEntry->defaultValue;
auto precision = doubleEntry->precision;
return stringValueForDouble(prefs->GetDouble(name, defaultValue), precision);
}
case ISchema::Type::String: {
return prefs->GetString(name, DEFAULT(StringEntry));
}
case ISchema::Type::Enum:
return prefs->GetString(name, DEFAULT(EnumEntry));
}
throw std::runtime_error("invalid type passed to stringValueFor!");
}
template <typename T>
static std::string stringValueFor(PrefsPtr prefs, const T* entry) {
return stringValueFor(prefs, entry, entry->entry.type, entry->entry.name);
}
static std::string stringValueFor(PrefsPtr prefs, const ISchema::Entry* entry) {
return stringValueFor(prefs, entry, entry->type, entry->name);
}
class StringListAdapter : public ScrollAdapterBase {
public:
StringListAdapter(std::vector<std::string>& items) : items(items) { }
std::string At(const size_t index) { return items[index]; }
virtual ~StringListAdapter() { }
virtual size_t GetEntryCount() override { return items.size(); }
virtual EntryPtr GetEntry(cursespp::ScrollableWindow* window, size_t index) override {
auto entry = std::make_shared<SingleLineEntry>(
text::Ellipsize(items[index], window->GetWidth()));
entry->SetAttrs(Color(Color::Default));
if (index == window->GetScrollPosition().logicalIndex) {
entry->SetAttrs(Color(Color::ListItemHighlighted));
}
return entry;
}
private:
std::vector<std::string> items;
};
class SchemaAdapter: public ScrollAdapterBase {
public:
SchemaAdapter(PrefsPtr prefs, SchemaPtr schema): prefs(prefs), schema(schema) {
onChanged = [this](std::string value) {
this->changed = true;
};
}
virtual ~SchemaAdapter() {
}
bool Changed() const {
return this->changed;
}
virtual size_t GetEntryCount() override {
return schema->Count();
}
virtual EntryPtr GetEntry(cursespp::ScrollableWindow* window, size_t index) override {
auto entry = schema->At(index);
std::string name = entry->name;
std::string value = stringValueFor(prefs, entry);
int width = window->GetContentWidth();
int avail = std::max(0, width - int(u8cols(name)) - 1 - 1);
auto display = " " + name + " " + text::Align(value + " ", text::AlignRight, avail);
auto result = std::make_shared<SingleLineEntry>(text::Ellipsize(display, width));
result->SetAttrs(Color(Color::Default));
if (index == window->GetScrollPosition().logicalIndex) {
result->SetAttrs(Color(Color::ListItemHighlighted));
}
return result;
}
void ShowOverlay(size_t index) {
auto entry = schema->At(index);
switch (entry->type) {
case ISchema::Type::Bool:
return ShowBoolOverlay(reinterpret_cast<const ISchema::BoolEntry*>(entry));
case ISchema::Type::Int:
return ShowIntOverlay(reinterpret_cast<const ISchema::IntEntry*>(entry));
case ISchema::Type::Double:
return ShowDoubleOverlay(reinterpret_cast<const ISchema::DoubleEntry*>(entry));
case ISchema::Type::String:
return ShowStringOverlay(reinterpret_cast<const ISchema::StringEntry*>(entry));
case ISchema::Type::Enum:
return ShowEnumOverlay(reinterpret_cast<const ISchema::EnumEntry*>(entry));
}
}
private:
void ShowBoolOverlay(const ISchema::BoolEntry* entry) {
SchemaOverlay::ShowBoolOverlay(entry, prefs, onChanged);
}
void ShowIntOverlay(const ISchema::IntEntry* entry) {
SchemaOverlay::ShowIntOverlay(entry, prefs, onChanged);
}
void ShowDoubleOverlay(const ISchema::DoubleEntry* entry) {
SchemaOverlay::ShowDoubleOverlay(entry, prefs, onChanged);
}
void ShowStringOverlay(const ISchema::StringEntry* entry) {
SchemaOverlay::ShowStringOverlay(entry, prefs, onChanged);
}
void ShowEnumOverlay(const ISchema::EnumEntry* entry) {
SchemaOverlay::ShowEnumOverlay(entry, prefs, onChanged);
}
std::function<void(std::string)> onChanged;
PrefsPtr prefs;
SchemaPtr schema;
bool changed{false};
};
void SchemaOverlay::ShowListOverlay(
const std::string& title,
std::vector<std::string>& items,
const std::string defaultValue,
std::function<void(std::string)> cb)
{
auto stringAdapter = std::make_shared<StringListAdapter>(items);
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
size_t index = 0;
for (size_t i = 0; i < items.size(); i++) {
auto current = items[i];
if (current == defaultValue) {
index = i;
}
}
dialog->SetAdapter(stringAdapter)
.SetTitle(title)
.SetWidth(overlayWidth())
.SetSelectedIndex(index)
.SetAutoDismiss(true)
.SetItemSelectedCallback(
[cb, stringAdapter](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
if (cb) {
cb(stringAdapter->At(index));
}
});
cursespp::App::Overlays().Push(dialog);
}
void SchemaOverlay::ShowBoolOverlay(
const ISchema::BoolEntry* entry,
PrefsPtr prefs,
std::function<void(std::string)> callback)
{
std::string name(entry->entry.name);
std::vector<std::string> items = { "true", "false" };
auto handler = [prefs, name, callback](std::string value) {
prefs->SetBool(name, value == "true");
if (callback) { callback(value); }
};
ShowListOverlay(name, items, stringValueFor(prefs, entry), handler);
}
void SchemaOverlay::ShowIntOverlay(
const ISchema::IntEntry* entry,
PrefsPtr prefs,
std::function<void(std::string)> callback)
{
std::string name(entry->entry.name);
auto title = numberInputTitle(
name, entry->minValue, entry->maxValue, intFormatter);
auto validator = std::make_shared<NumberValidator<int>>(
entry->minValue, entry->maxValue, intFormatter);
auto handler = [prefs, name, callback](std::string value) {
prefs->SetInt(name, (int) std::stod(value));
if (callback) { callback(value); }
};
std::shared_ptr<InputOverlay> dialog(new InputOverlay());
dialog->SetTitle(title)
.SetText(stringValueFor(prefs, entry))
.SetValidator(validator)
.SetWidth(overlayWidth())
.SetInputAcceptedCallback(callback);
App::Overlays().Push(dialog);
}
void SchemaOverlay::ShowDoubleOverlay(
const ISchema::DoubleEntry* entry,
PrefsPtr prefs,
std::function<void(std::string)> callback)
{
std::string name(entry->entry.name);
auto formatter = doubleFormatter(entry->precision);
auto title = numberInputTitle(
name, entry->minValue, entry->maxValue, formatter);
auto validator = std::make_shared<NumberValidator<double>>(
entry->minValue, entry->maxValue, formatter);
auto handler = [prefs, name, callback](std::string value) {
prefs->SetDouble(name, std::stod(value));
if (callback) { callback(value); }
};
std::shared_ptr<InputOverlay> dialog(new InputOverlay());
dialog->SetTitle(title)
.SetText(stringValueFor(prefs, entry))
.SetValidator(validator)
.SetWidth(overlayWidth())
.SetInputAcceptedCallback(handler);
App::Overlays().Push(dialog);
}
void SchemaOverlay::ShowStringOverlay(
const ISchema::StringEntry* entry,
PrefsPtr prefs,
std::function<void(std::string)> callback)
{
std::string name(entry->entry.name);
auto handler = [prefs, name, callback](std::string value) {
prefs->SetString(name, value.c_str());
if (callback) { callback(value); }
};
std::shared_ptr<InputOverlay> dialog(new InputOverlay());
dialog->SetTitle(name)
.SetText(stringValueFor(prefs, entry))
.SetWidth(overlayWidth())
.SetAllowEmptyValue(entry->defaultValue && strlen(entry->defaultValue))
.SetInputAcceptedCallback(handler);
App::Overlays().Push(dialog);
}
void SchemaOverlay::ShowEnumOverlay(
const ISchema::EnumEntry* entry,
PrefsPtr prefs,
std::function<void(std::string)> callback)
{
std::string name(entry->entry.name);
std::vector<std::string> items;
for (size_t i = 0; i < entry->count; i++) {
items.push_back(entry->values[i]);
}
auto handler = [prefs, name, callback](std::string value) {
prefs->SetString(name, value.c_str());
if (callback) { callback(value); }
};
ShowListOverlay(name, items, stringValueFor(prefs, entry), handler);
}
void SchemaOverlay::Show(
const std::string& title,
PrefsPtr prefs,
SchemaPtr schema,
std::function<void(bool)> callback)
{
std::shared_ptr<SchemaAdapter> schemaAdapter(new SchemaAdapter(prefs, schema));
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
dialog->SetAdapter(schemaAdapter)
.SetTitle(title)
.SetWidthPercent(80)
.SetAutoDismiss(false)
.SetItemSelectedCallback(
[schemaAdapter](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
schemaAdapter->ShowOverlay(index);
})
.SetDismissedCallback([callback, schemaAdapter](ListOverlay* overlay) {
if (callback) {
callback(schemaAdapter->Changed());
}
});
cursespp::App::Overlays().Push(dialog);
}

@ -172,7 +172,7 @@ void Window::BringToTop() {
if (this->framePanel) {
top_panel(this->framePanel);
if (this->contentPanel != this->framePanel) {
if (this->contentPanel && this->contentPanel != this->framePanel) {
top_panel(this->contentPanel);
}
@ -500,7 +500,7 @@ void Window::Show() {
if (!this->isVisibleInParent) {
show_panel(this->framePanel);
if (this->framePanel != this->contentPanel) {
if (this->contentPanel && this->framePanel != this->contentPanel) {
show_panel(this->contentPanel);
}
@ -716,17 +716,17 @@ void Window::Hide() {
this->Destroy();
this->isVisibleInParent = false;
this->OnVisibilityChanged(false);
notifyParent = (this->parent != nullptr);
notifyParent = true;
}
}
else {
if (this->isVisibleInParent) {
notifyParent = (this->parent != nullptr);
notifyParent = true;
this->isVisibleInParent = false;
}
}
if (notifyParent) {
if (notifyParent && this->parent) {
this->parent->OnChildVisibilityChanged(false, this);
}
}

@ -64,6 +64,7 @@ namespace cursespp {
void SetMouseEnabled(bool enabled);
bool IsOverlayVisible() { return this->state.overlay != nullptr; }
void SetMinimizeToTray(bool minimizeToTray);
void SetTitle(const std::string& title);
std::string GetQuitKey();
void SetQuitKey(const std::string& kn);
void Minimize();
@ -81,6 +82,7 @@ namespace cursespp {
void Run(ILayoutPtr layout);
void ChangeLayout(ILayoutPtr layout);
ILayoutPtr GetLayout();
void InjectKeyPress(const std::string& key);
void Quit();
@ -125,10 +127,11 @@ namespace cursespp {
int minWidth, minHeight;
bool mouseEnabled{true};
bool quit{false}, initialized{false};
std::string appTitle;
#ifdef WIN32
int iconId;
std::string uniqueId, appTitle;
std::string uniqueId;
#endif
};
}

@ -61,6 +61,9 @@ namespace cursespp {
void SetLayout(std::shared_ptr<cursespp::LayoutBase> layout);
void SetAutoHideCommandBar(bool autoHide);
bool GetAutoHideCommandBar();
protected:
virtual void SetPadding(size_t t, size_t l, size_t b, size_t r);
@ -79,5 +82,6 @@ namespace cursespp {
ITopLevelLayout* topLevelLayout;
size_t paddingT{0}, paddingL{0}, paddingB{0}, paddingR{0};
bool shortcutsFocused;
bool autoHideCommandBar{ false };
};
}

@ -59,6 +59,7 @@ namespace cursespp {
InputOverlay& SetValidator(std::shared_ptr<IValidator> validator);
InputOverlay& SetWidth(int width);
InputOverlay& SetInputMode(IInput::InputMode mode);
InputOverlay& SetAllowEmptyValue(bool allowEmptyValue);
virtual void Layout();
virtual bool KeyPress(const std::string& key);
@ -76,6 +77,7 @@ namespace cursespp {
int x, y;
int width, height;
int setWidth;
bool allowEmptyValue;
std::shared_ptr<TextInput> textInput;
std::shared_ptr<IValidator> validator;
InputAcceptedCallback inputAcceptedCallback;

@ -0,0 +1,85 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2007-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#pragma once
#include <cursespp/InputOverlay.h>
#include <core/utfutil.h>
namespace cursespp {
template <typename T>
struct NumberValidator : public InputOverlay::IValidator {
using Formatter = std::function<std::string(T)>;
NumberValidator(T minimum, T maximum, Formatter formatter)
: minimum(minimum), maximum(maximum), formatter(formatter) {
}
virtual bool IsValid(const std::string& input) const override {
try {
double result = std::stod(input);
if (bounded(minimum, maximum) && (result < minimum || result > maximum)) {
return false;
}
}
catch (std::invalid_argument) {
return false;
}
return true;
}
virtual const std::string ErrorMessage() const override {
if (bounded(minimum, maximum)) {
std::string result = _TSTR("validator_dialog_number_parse_bounded_error");
u8replace(result, "{{minimum}}", formatter(minimum));
u8replace(result, "{{maximum}}", formatter(maximum));
return result;
}
return _TSTR("validator_dialog_number_parse_error");
}
static bool bounded(T minimum, T maximum) {
return
minimum != std::numeric_limits<T>::min() &&
maximum != std::numeric_limits<T>::max();
}
Formatter formatter;
T minimum, maximum;
};
}

@ -83,6 +83,13 @@ namespace cursespp {
return focus;
}
void Dismiss() {
if (this->stack) {
stack->Remove(this);
this->OnDismissed();
}
}
protected:
static void style(TextLabel& label) {
label.SetContentColor(Color::OverlayContent);
@ -119,13 +126,6 @@ namespace cursespp {
return this->stack;
}
void Dismiss() {
if (this->stack) {
stack->Remove(this);
this->OnDismissed();
}
}
virtual void OnDismissed() {
/* for subclass use */
}

@ -0,0 +1,88 @@
//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2007-2019 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#pragma once
#include <core/support/Preferences.h>
#include <core/sdk/ISchema.h>
namespace cursespp {
class SchemaOverlay {
public:
using Prefs = musik::core::Preferences;
using ISchema = musik::core::sdk::ISchema;
using SchemaPtr = std::shared_ptr<ISchema>;
using PrefsPtr = std::shared_ptr<Prefs>;
static void Show(
const std::string& title,
PrefsPtr prefs,
SchemaPtr schema,
std::function<void(bool)> callback);
static void ShowListOverlay(
const std::string& title,
std::vector<std::string>& items,
const std::string defaultValue,
std::function<void(std::string)> cb);
static void ShowBoolOverlay(
const ISchema::BoolEntry* entry,
PrefsPtr prefs,
std::function<void(std::string)> callback);
static void ShowIntOverlay(
const ISchema::IntEntry* entry,
PrefsPtr prefs,
std::function<void(std::string)> callback);
static void ShowDoubleOverlay(
const ISchema::DoubleEntry* entry,
PrefsPtr prefs,
std::function<void(std::string)> callback);
static void ShowStringOverlay(
const ISchema::StringEntry* entry,
PrefsPtr prefs,
std::function<void(std::string)> callback);
static void ShowEnumOverlay(
const ISchema::EnumEntry* entry,
PrefsPtr prefs,
std::function<void(std::string)> callback);
private:
SchemaOverlay();
};
}

@ -22,7 +22,7 @@
"browse_title_genres": "genres",
"browse_title_album_artists": "album artists",
"browse_title_category": "category",
"browse_title_tracks": "tracks",
"browse_title_tracks": "tracks (sorted by %s)",
"browse_title_playlists": "playlists",
"browse_title_directory": "directory",
"browse_playlist_modified": "playlist modified. press '%s' to save.",
@ -83,6 +83,7 @@
"settings_auto_update_check": "check for updates on startup",
"settings_save_session_on_exit": "save session on exit",
"settings_check_for_updates": "check for updates now",
"settings_advanced_settings": "advanced settings",
"settings_last_fm": "last.fm",
"settings_last_fm_dialog_title": "last.fm registration",
"settings_last_fm_dialog_message_unregistered": "no last.fm account registered.\n\npress ENTER to begin the linking process.",
@ -123,7 +124,7 @@
"validator_dialog_number_parse_bounded_error": "please enter a number between {{minimum}} and {{maximum}} to continue.",
"validator_dialog_number_parse_error": "please enter a number to continue.",
"track_filter_title": "track filter results",
"track_filter_title": "tracks (sorted by %s)",
"locale_overlay_select_title": "select locale",
@ -212,7 +213,27 @@
"update_check_dialog_title": "new version available!",
"update_check_dialog_message": "musikcube version %s is now available for download. a changelog and binaries are available at:\n\n%s",
"update_check_no_updates_title": "update check",
"update_check_no_updates_message": "no updates found at this time."
"update_check_no_updates_message": "no updates found at this time.",
"track_list_sort_overlay_title": "select sort order",
"track_list_rate_track_overlay_title": "rate track",
"track_list_sort_title": "track title",
"track_list_sort_album": "album",
"track_list_sort_artist": "artist",
"track_list_sort_date_added_asc": "date added [asc]",
"track_list_sort_date_added_desc": "date added [desc]",
"track_list_sort_date_updated_asc": "date updated [asc]",
"track_list_sort_date_updated_desc": "date updated [desc]",
"track_list_sort_last_played_asc": "last played [asc]",
"track_list_sort_last_played_desc": "last played [desc]",
"track_list_sort_rating_asc": "rating [asc]",
"track_list_sort_rating_desc": "rating [desc]",
"track_list_sort_play_count_asc": "play count [asc]",
"track_list_sort_play_count_desc": "play count [desc]",
"track_list_sort_genre": "genre",
"unknown_category_value": "[unknown #%d]"
},
"dimensions": {
@ -226,6 +247,8 @@
"reassign_hotkey_overlay_width": 35,
"server_overlay_width": 45,
"preamp_overlay_width": 40,
"equalizer_overlay_width": 46
"equalizer_overlay_width": 46,
"track_search_sort_order_width": 32,
"track_list_rate_track_width": 24
}
}

@ -0,0 +1,217 @@
{
"schemaVersion": 1,
"strings": {
"default_overlay_title": "musikcube",
"button_ok": "OK",
"button_yes": "是",
"button_no": "否",
"button_save": "保存",
"button_cancel": "取消",
"button_close": "关闭",
"button_start": "开始",
"button_retry": "重试",
"button_continue": "继续",
"button_unregister": "解除关联",
"button_open_url": "打开 URL",
"button_dont_remind_me": "不要提醒我",
"button_remind_me_later": "稍后提醒我",
"browse_title_artists": "艺术家",
"browse_title_albums": "专辑",
"browse_title_genres": "流派",
"browse_title_album_artists": "专辑艺术家",
"browse_title_category": "分类",
"browse_title_tracks": "曲目",
"browse_title_playlists": "播放列表",
"browse_title_directory": "目录",
"browse_playlist_modified": "播放列表已更改,按 %s 保存。",
"browse_categories_title": "选择分类",
"browse_title_directory_tracks": "%s 中的曲目",
"browse_pick_path_overlay_title": "选择根目录",
"browse_pick_path_last_directory": "上一个目录",
"browse_no_paths_overlay_error_title": "没有配置路径",
"browse_no_paths_overlay_error_message": "您没有配置元数据路径。\n\n要按目录浏览您必须在设置界面添加至少一个路径。",
"browse_no_subdirectories_toast": "没有更多子目录。\n\n按 %s 播放所选项目。",
"search_filter_hint": "搜索",
"console_list_title": "调试日志",
"console_version": "musikcube 版本号 %s",
"hotkeys_title": "快捷键",
"hotkeys_reset_defaults": "全部重设",
"hotkeys_backup": "备份",
"hotkeys_reassign_overlay_title": "重新指派快捷键",
"hotkeys_reset_all_title": "重设快捷键",
"hotkeys_reset_all_message": "确定将所有快捷键重设为默认值吗?\n\n现有的自定义快捷键会被丢弃",
"hotkeys_conflict_title": "快捷键冲突",
"hotkeys_conflict_message": "快捷键 {{hotkey}} 在某些场合 *可能* 和 {{existing}} 冲突。\n\n确定指派吗",
"hotkeys_backup_success_title": "备份成功",
"hotkeys_backup_success_message": "快捷键已备份到该位置:\n\n{{path}}",
"hotkeys_backup_failure_title": "备份失败",
"hotkeys_backup_failure_message": "备份失败!请确认您有该目录的写入权限:\n\n{{path}}",
"hotkeys_delete_binding_title": "重设快捷键",
"hotkeys_delete_binding_message": "确定将 {{key}} 的快捷键重设为默认值 {{default}}",
"settings_space_to_add": "浏览(按空格键添加)",
"settings_backspace_to_remove": "已索引目录(按退格键移除)",
"settings_enable_disable_plugins": "配置插件",
"settings_color_theme": "颜色主题:",
"settings_hotkey_tester": "快捷键",
"settings_output_driver": "输出驱动:",
"settings_output_device": "输出设备:",
"settings_output_device_default": "默认值",
"settings_transport_type": "回放模式:",
"settings_degrade_256": "降级至 256 色",
"settings_show_dotfiles": "在文件浏览器显示名称以“.”开头的文件",
"settings_sync_on_startup": "启动时同步元数据",
"settings_remove_missing": "从音乐库移除缺失的文件",
"settings_default_theme_name": "默认",
"settings_8color_theme_name": "8 位色(兼容模式)",
"settings_transport_type_gapless": "无缝播放",
"settings_transport_type_crossfade": "淡入淡出",
"settings_first_run_dialog_title": "欢迎使用 musikcube",
"settings_first_run_dialog_body": "添加一些包含音乐文件的目录,然后按 %s 打开音乐库并开始聆听!\n\n如需调试错误按 %s 打开控制台。\n\n其它快捷键已在屏幕底部的命令栏列出。按 ESC 开关命令模式。\n\n选择“OK”开始使用。",
"settings_needs_restart": "您需要重启 musikcube 以使更改生效。",
"settings_selected_locale": "语言:",
"settings_seek_not_scrub": "快进时立即跳转到新位置",
"settings_minimize_to_tray": "最小化至托盘",
"settings_start_minimized": "以最小化模式启动",
"settings_server_setup": "服务器设置",
"settings_auto_update_check": "启动时检查更新",
"settings_save_session_on_exit": "退出时保存会话",
"settings_check_for_updates": "检查更新",
"settings_last_fm": "Last.fm",
"settings_last_fm_dialog_title": "关联至 Last.fm",
"settings_last_fm_dialog_message_unregistered": "没有关联 Last.fm 账户。\n\n按回车键开始关联。",
"settings_last_fm_dialog_message_obtaining_token": "正在向 Last.fm 请求账户关联令牌……",
"settings_last_fm_dialog_message_waiting_for_user": "按 O 在默认浏览器打开账户关联页面,或者手动打开该 URL\n\n{{link}}\n\n授权后按回车键继续。\n\n注意您可能会被要求登录 Last.fm 账户。)",
"settings_last_fm_dialog_message_registering_session": "正在获取会话令牌,请稍候……",
"settings_last_fm_dialog_message_registered": "已关联账户:“{{username}}”\n\n开始聆听吧",
"settings_last_fm_dialog_message_link_error": "未能从 Last.fm 获取账户关联令牌。\n\n请稍候再试。",
"settings_last_fm_dialog_message_register_error": "账户关联失败。\n\n请确认您已经打开该 URL 并授权:\n\n{{link}}\n\n按回车键重试。",
"settings_server_enable_websockets": "启用元数据服务器",
"settings_server_enable_http": "启用流媒体播放",
"settings_server_port": "端口:",
"settings_server_transcoder_synchronous": "同步转码",
"settings_server_transcoder_cache_count": "转码文件缓存计数:",
"settings_server_password": "密码:",
"settings_server_invalid_settings_title": "无效设置",
"settings_server_invalid_settings_message": "设置无效或不完整,请检查并重试。",
"settings_server_use_ipv6": "启用 IPv6",
"settings_enable_transparency": "启用透明支持",
"settings_preamp": "前置放大和回放增益",
"settings_preamp_label": "前置放大增益dB:",
"settings_preamp_invalid_gain_title": "无效增益",
"settings_preamp_invalid_gain_message": "前置放大增益值必须在 -20.0 到 20.0 dB 之间",
"settings_replay_gain": "回放增益:",
"settings_replay_gain_title": "回放增益模式",
"settings_replay_gain_mode_disabled": "禁用",
"settings_replay_gain_mode_track": "单曲",
"settings_replay_gain_mode_album": "专辑",
"settings_configure_plugin_title": "配置 {{name}}",
"settings_no_plugin_config_title": "无配置",
"settings_no_plugin_config_message": "插件 {{name}} 没有可用配置。",
"validator_dialog_title": "验证错误",
"validator_dialog_number_parse_bounded_error": "请输入 {{minimum}} 到 {{maximum}} 之间的数字以继续。",
"validator_dialog_number_parse_error": "请输入数字以继续。",
"track_filter_title": "曲目过滤结果",
"locale_overlay_select_title": "选择语言",
"color_theme_list_overlay_title": "颜色主题",
"color_theme_256_overlay_message": "停止降级至 256 色会启用 RGB 色彩模式并替换现有颜色。禁用该选项能更准确地显示颜色主题,但在终端重置前可能导致其它程序显示不正常。\n\n确定停止降级至 256 色吗?",
"playback_overlay_transport_title": "回放模式",
"playback_overlay_output_plugins_title": "输出驱动插件",
"playback_overlay_output_device_title": "输出设备",
"playback_overlay_invalid_transport": "所选输出驱动(%s不支持淡入淡出。",
"playback_overlay_no_output_plugins_mesage": "未发现输出插件。",
"playqueue_title": "播放队列",
"playqueue_overlay_new": "新建……",
"playqueue_overlay_confirm_overwrite_message": "确定覆盖播放列表 %s",
"playqueue_overlay_add_category_title": "分类操作",
"playqueue_overlay_load_playlist_title": "加载播放列表",
"playqueue_overlay_save_playlist_title": "保存播放列表",
"playqueue_overlay_rename_playlist_title": "重命名播放列表",
"playqueue_overlay_delete_playlist_title": "删除播放列表",
"playqueue_overlay_playlist_name_title": "播放列表名称",
"playqueue_overlay_new_playlist_name_title": "新播放列表名称",
"playqueue_overlay_confirm_delete_message": "确定删除 %s",
"playqueue_overlay_load_playlists_none_message": "您没有保存过播放列表。",
"playqueue_overlay_track_actions_title": "曲目操作",
"playqueue_overlay_album_header_actions_title": "专辑操作",
"playqueue_overlay_album_jump_to": "在音乐库显示该专辑",
"playqueue_overlay_artist_jump_to": "在音乐库显示该艺术家",
"playqueue_overlay_album_play": "播放专辑",
"playqueue_overlay_genre_jump_to": "在音乐库显示该流派",
"playqueue_overlay_add_to_start_of_queue": "添加至队列列首",
"playqueue_overlay_add_to_end_in_queue": "添加至队列列尾",
"playqueue_overlay_add_as_next_in_queue": "在队列下一首播放",
"playqueue_overlay_add_to_playlist": "添加至播放列表",
"playqueue_overlay_select_playlist_title": "选择播放列表",
"visualizer_overlay_title": "可视化效果",
"visualizer_overlay_no_visualizers_message": "未发现可视化效果。",
"equalizer_overlay_enabled": "启用均衡器",
"equalizer_overlay_frequencies": "频段",
"equalizer_button_load": "加载",
"equalizer_button_save": "保存",
"equalizer_button_zero": "归零",
"plugin_overlay_title": "配置插件",
"shortcuts_settings": "设置",
"shortcuts_library": "音乐库",
"shortcuts_lyrics": "歌词",
"shortcuts_console": "日志",
"shortcuts_quit": "退出",
"shortcuts_browse": "浏览",
"shortcuts_filter": "过滤",
"shortcuts_tracks": "曲目",
"shortcuts_play_queue": "播放队列",
"transport_playing_format": "正在播放: $title 艺术家: $artist 专辑: $album",
"transport_stopped": "回放停止",
"transport_empty_song": "[未知歌曲]",
"transport_empty_album": "[未知专辑]",
"transport_empty_artist": "[未知艺术家]",
"transport_shuffle": "随机播放",
"transport_muted": "静音",
"transport_volume": "音量",
"transport_repeat_list": "列表循环",
"transport_repeat_track": "单曲循环",
"transport_repeat_off": "循环关闭",
"indexer_overlay_title": "元数据索引器",
"indexer_overlay_reindex": "扫描变化",
"indexer_overlay_rebuild": "重建音乐库",
"tracklist_unknown_album": "[未知专辑]",
"tracklist_hot_swap_success_toast": "播放队列已和当前列表交换",
"main_syncing_banner_start": "正在同步元数据……",
"main_syncing_banner": "正在同步元数据(已处理 %d 首歌曲)",
"lyrics_list_title": "%s 的歌词,作者:%s",
"lyrics_not_playing": "没有播放歌曲,无法显示歌词。",
"lyrics_loading": "正在检索歌词……",
"lyrics_lookup_failed": "检索歌词失败,按 %s 重试。",
"update_check_dialog_title": "发现新版本!",
"update_check_dialog_message": "musikcube %s 版已经发布。在这里获取更新日志和程序:\n\n%s",
"update_check_no_updates_title": "更新检查",
"update_check_no_updates_message": "未发现更新。"
}
}

Binary file not shown.

@ -136,7 +136,6 @@ xcopy "$(ProjectDir)data\locales\*" "$(TargetDir)locales\" /Y /e
xcopy "$(SolutionDir)src\3rdparty\bin\win32\dll\ffmpeg\*" "$(TargetDir)" /Y /e
xcopy "$(SolutionDir)src\3rdparty\bin\win32\dll\lame\*" "$(TargetDir)" /Y /e
xcopy "$(SolutionDir)src\3rdparty\bin\win32\dll\libcurl\*" "$(TargetDir)" /Y /e
xcopy "$(SolutionDir)src\3rdparty\bin\win32\dll\vorbis\*" "$(TargetDir)" /Y /e
if not exist "$(TargetDir)fonts" mkdir "$(TargetDir)fonts"
xcopy "$(SolutionDir)src\3rdparty\bin\win\font\*.ttf" "$(TargetDir)fonts\" /Y /e</Command>
</PostBuildEvent>
@ -174,7 +173,6 @@ xcopy "$(ProjectDir)data\locales\*" "$(TargetDir)locales\" /Y /e
xcopy "$(SolutionDir)src\3rdparty\bin\win64\dll\ffmpeg\*" "$(TargetDir)" /Y /e
xcopy "$(SolutionDir)src\3rdparty\bin\win64\dll\lame\*" "$(TargetDir)" /Y /e
xcopy "$(SolutionDir)src\3rdparty\bin\win64\dll\libcurl\*" "$(TargetDir)" /Y /e
xcopy "$(SolutionDir)src\3rdparty\bin\win64\dll\vorbis\*" "$(TargetDir)" /Y /e
if not exist "$(TargetDir)fonts" mkdir "$(TargetDir)fonts"
xcopy "$(SolutionDir)src\3rdparty\bin\win\font\*.ttf" "$(TargetDir)fonts\" /Y /e</Command>
</PostBuildEvent>
@ -220,7 +218,6 @@ xcopy "$(ProjectDir)data\locales\*" "$(TargetDir)locales\" /Y /e
xcopy "$(SolutionDir)src\3rdparty\bin\win32\dll\ffmpeg\*" "$(TargetDir)" /Y /e
xcopy "$(SolutionDir)src\3rdparty\bin\win32\dll\lame\*" "$(TargetDir)" /Y /e
xcopy "$(SolutionDir)src\3rdparty\bin\win32\dll\libcurl\*" "$(TargetDir)" /Y /e
xcopy "$(SolutionDir)src\3rdparty\bin\win32\dll\vorbis\*" "$(TargetDir)" /Y /e
if not exist "$(TargetDir)fonts" mkdir "$(TargetDir)fonts"
xcopy "$(SolutionDir)src\3rdparty\bin\win\font\*.ttf" "$(TargetDir)fonts\" /Y /e</Command>
</PostBuildEvent>
@ -265,7 +262,6 @@ xcopy "$(ProjectDir)data\locales\*" "$(TargetDir)locales\" /Y /e
xcopy "$(SolutionDir)src\3rdparty\bin\win64\dll\ffmpeg\*" "$(TargetDir)" /Y /e
xcopy "$(SolutionDir)src\3rdparty\bin\win64\dll\lame\*" "$(TargetDir)" /Y /e
xcopy "$(SolutionDir)src\3rdparty\bin\win64\dll\libcurl\*" "$(TargetDir)" /Y /e
xcopy "$(SolutionDir)src\3rdparty\bin\win64\dll\vorbis\*" "$(TargetDir)" /Y /e
if not exist "$(TargetDir)fonts" mkdir "$(TargetDir)fonts"
xcopy "$(SolutionDir)src\3rdparty\bin\win\font\*.ttf" "$(TargetDir)fonts\" /Y /e</Command>
</PostBuildEvent>
@ -295,12 +291,15 @@ xcopy "$(SolutionDir)src\3rdparty\bin\win\font\*.ttf" "$(TargetDir)fonts\" /Y /e
<ClCompile Include="app\overlay\PreampOverlay.cpp" />
<ClCompile Include="app\overlay\ReassignHotkeyOverlay.cpp" />
<ClCompile Include="app\overlay\ServerOverlay.cpp" />
<ClCompile Include="app\overlay\TrackOverlays.cpp" />
<ClCompile Include="app\overlay\VisualizerOverlay.cpp" />
<ClCompile Include="app\util\ConsoleLogger.cpp" />
<ClCompile Include="app\util\GlobalHotkeys.cpp" />
<ClCompile Include="app\util\Hotkeys.cpp" />
<ClCompile Include="app\util\Playback.cpp" />
<ClCompile Include="app\util\PreferenceKeys.cpp" />
<ClCompile Include="app\util\Rating.cpp" />
<ClCompile Include="app\util\TrackRowRenderers.cpp" />
<ClCompile Include="app\util\UpdateCheck.cpp" />
<ClCompile Include="app\window\CategoryListView.cpp" />
<ClCompile Include="app\window\TrackListView.cpp" />
@ -317,6 +316,7 @@ xcopy "$(SolutionDir)src\3rdparty\bin\win\font\*.ttf" "$(TargetDir)fonts\" /Y /e
<ClCompile Include="cursespp\ListWindow.cpp" />
<ClCompile Include="cursespp\MultiLineEntry.cpp" />
<ClCompile Include="cursespp\OverlayStack.cpp" />
<ClCompile Include="cursespp\SchemaOverlay.cpp" />
<ClCompile Include="cursespp\Screen.cpp" />
<ClCompile Include="cursespp\ScrollableWindow.cpp" />
<ClCompile Include="cursespp\ScrollAdapterBase.cpp" />
@ -364,6 +364,7 @@ xcopy "$(SolutionDir)src\3rdparty\bin\win\font\*.ttf" "$(TargetDir)fonts\" /Y /e
<ClInclude Include="app\overlay\PreampOverlay.h" />
<ClInclude Include="app\overlay\ReassignHotkeyOverlay.h" />
<ClInclude Include="app\overlay\ServerOverlay.h" />
<ClInclude Include="app\overlay\TrackOverlays.h" />
<ClInclude Include="app\overlay\VisualizerOverlay.h" />
<ClInclude Include="app\util\ConsoleLogger.h" />
<ClInclude Include="app\util\GlobalHotkeys.h" />
@ -371,6 +372,8 @@ xcopy "$(SolutionDir)src\3rdparty\bin\win\font\*.ttf" "$(TargetDir)fonts\" /Y /e
<ClInclude Include="app\util\Messages.h" />
<ClInclude Include="app\util\Playback.h" />
<ClInclude Include="app\util\PreferenceKeys.h" />
<ClInclude Include="app\util\Rating.h" />
<ClInclude Include="app\util\TrackRowRenderers.h" />
<ClInclude Include="app\util\UpdateCheck.h" />
<ClInclude Include="app\window\CategoryListView.h" />
<ClInclude Include="app\window\TrackListView.h" />
@ -399,8 +402,10 @@ xcopy "$(SolutionDir)src\3rdparty\bin\win\font\*.ttf" "$(TargetDir)fonts\" /Y /e
<ClInclude Include="cursespp\cursespp\ListOverlay.h" />
<ClInclude Include="cursespp\cursespp\ListWindow.h" />
<ClInclude Include="cursespp\cursespp\MultiLineEntry.h" />
<ClInclude Include="cursespp\cursespp\NumberValidator.h" />
<ClInclude Include="cursespp\cursespp\OverlayBase.h" />
<ClInclude Include="cursespp\cursespp\OverlayStack.h" />
<ClInclude Include="cursespp\cursespp\SchemaOverlay.h" />
<ClInclude Include="cursespp\cursespp\Screen.h" />
<ClInclude Include="cursespp\cursespp\ScrollableWindow.h" />
<ClInclude Include="cursespp\cursespp\ScrollAdapterBase.h" />

@ -180,6 +180,18 @@
<ClCompile Include="app\layout\LyricsLayout.cpp">
<Filter>app\layout</Filter>
</ClCompile>
<ClCompile Include="app\util\TrackRowRenderers.cpp">
<Filter>app\util</Filter>
</ClCompile>
<ClCompile Include="app\overlay\TrackOverlays.cpp">
<Filter>app\overlay</Filter>
</ClCompile>
<ClCompile Include="cursespp\SchemaOverlay.cpp">
<Filter>cursespp</Filter>
</ClCompile>
<ClCompile Include="app\util\Rating.cpp">
<Filter>app\util</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="stdafx.h" />
@ -412,6 +424,21 @@
<ClInclude Include="app\layout\LyricsLayout.h">
<Filter>app\layout</Filter>
</ClInclude>
<ClInclude Include="app\util\TrackRowRenderers.h">
<Filter>app\util</Filter>
</ClInclude>
<ClInclude Include="app\overlay\TrackOverlays.h">
<Filter>app\overlay</Filter>
</ClInclude>
<ClInclude Include="cursespp\cursespp\NumberValidator.h">
<Filter>cursespp\include</Filter>
</ClInclude>
<ClInclude Include="cursespp\cursespp\SchemaOverlay.h">
<Filter>cursespp\include</Filter>
</ClInclude>
<ClInclude Include="app\util\Rating.h">
<Filter>app\util</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="cursespp">

Some files were not shown because too many files have changed in this diff Show More