mirror of
https://github.com/clangen/musikcube.git
synced 2025-02-13 15:41:26 +00:00
Merge pull request #295 from clangen/clangen/upgrades-12-2019
0.80.0 Fixes and Upgrades
This commit is contained in:
commit
c797e00c1a
CMakeLists.txtarchive-macos.shmusikcube.spec
src
3rdparty
core
CMakeLists.txt
audio
core.vcxprojcore.vcxproj.filtersdb
io
library
LocalLibrary.cppLocalLibraryConstants.hLocalMetadataProxy.cpp
query/local
CategoryListQuery.cppCategoryTrackListQuery.cppCategoryTrackListQuery.hLyricsQuery.cppLyricsQuery.hMarkTrackPlayedQuery.cppMarkTrackPlayedQuery.hSearchTrackListQuery.cppSearchTrackListQuery.hSetTrackRatingQuery.cppSetTrackRatingQuery.hTrackMetadataQuery.cpp
util
track
plugin
sdk
DataBuffer.hIBlockingEncoder.hIDataStream.hIDataStreamFactory.hIEncoder.hIEnvironment.hIStreamingEncoder.hconstants.h
support
utfutil.hmusikcube
CMakeLists.txtMain.cpp
app
layout
BrowseLayout.cppBrowseLayout.hCategorySearchLayout.cppCategorySearchLayout.hDirectoryLayout.hLibraryLayout.cppLyricsLayout.cppLyricsLayout.hMainLayout.cppMainLayout.hNowPlayingLayout.cppNowPlayingLayout.hSettingsLayout.cppSettingsLayout.hTrackSearchLayout.cppTrackSearchLayout.h
model
overlay
util
Hotkeys.cppHotkeys.hPreferenceKeys.cppPreferenceKeys.hRating.cppRating.hTrackRowRenderers.cppTrackRowRenderers.h
version.hwindow
cursespp
data/locales
musikcube.rcmusikcube.vcxprojmusikcube.vcxproj.filters@ -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
2
src/3rdparty/bin
vendored
@ -1 +1 @@
|
||||
Subproject commit b2fa669c7ff30009dca3a6d43e040b67cfc73f9e
|
||||
Subproject commit 69c5c8daeda0853fd17b44e677357c9676fa8750
|
9
src/3rdparty/win32_src/pdcurses/pdcscrn.c
vendored
9
src/3rdparty/win32_src/pdcurses/pdcscrn.c
vendored
@ -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;
|
||||
};
|
||||
|
||||
|
73
src/core/library/query/local/LyricsQuery.cpp
Normal file
73
src/core/library/query/local/LyricsQuery.cpp
Normal file
@ -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;
|
||||
}
|
57
src/core/library/query/local/LyricsQuery.h
Normal file
57
src/core/library/query/local/LyricsQuery.h
Normal file
@ -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;
|
||||
};
|
||||
|
||||
} } } }
|
63
src/core/library/query/local/MarkTrackPlayedQuery.cpp
Normal file
63
src/core/library/query/local/MarkTrackPlayedQuery.cpp
Normal file
@ -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;
|
||||
}
|
52
src/core/library/query/local/MarkTrackPlayedQuery.h
Normal file
52
src/core/library/query/local/MarkTrackPlayedQuery.h
Normal file
@ -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;
|
||||
};
|
||||
|
||||
|
58
src/core/library/query/local/SetTrackRatingQuery.cpp
Normal file
58
src/core/library/query/local/SetTrackRatingQuery.cpp
Normal file
@ -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;
|
||||
}
|
54
src/core/library/query/local/SetTrackRatingQuery.h
Normal file
54
src/core/library/query/local/SetTrackRatingQuery.h
Normal file
@ -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
|
||||
|
115
src/core/library/query/local/util/TrackSort.h
Normal file
115
src/core/library/query/local/util/TrackSort.h
Normal file
@ -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) {
|
||||
|
51
src/core/sdk/IBlockingEncoder.h
Normal file
51
src/core/sdk/IBlockingEncoder.h
Normal file
@ -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;
|
||||
|
50
src/core/sdk/IStreamingEncoder.h
Normal file
50
src/core/sdk/IStreamingEncoder.h
Normal file
@ -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) {
|
||||
|
133
src/musikcube/app/overlay/TrackOverlays.cpp
Normal file
133
src/musikcube/app/overlay/TrackOverlays.cpp
Normal file
@ -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);
|
||||
}
|
60
src/musikcube/app/overlay/TrackOverlays.h
Normal file
60
src/musikcube/app/overlay/TrackOverlays.h
Normal file
@ -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;
|
||||
}
|
||||
|
||||
|
87
src/musikcube/app/util/Rating.cpp
Normal file
87
src/musikcube/app/util/Rating.cpp
Normal file
@ -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;
|
||||
}
|
||||
|
||||
} }
|
48
src/musikcube/app/util/Rating.h
Normal file
48
src/musikcube/app/util/Rating.h
Normal file
@ -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();
|
||||
|
||||
} }
|
189
src/musikcube/app/util/TrackRowRenderers.cpp
Normal file
189
src/musikcube/app/util/TrackRowRenderers.cpp
Normal file
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
62
src/musikcube/app/util/TrackRowRenderers.h
Normal file
62
src/musikcube/app/util/TrackRowRenderers.h
Normal file
@ -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) {
|
||||
|
417
src/musikcube/cursespp/SchemaOverlay.cpp
Normal file
417
src/musikcube/cursespp/SchemaOverlay.cpp
Normal file
@ -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;
|
||||
|
85
src/musikcube/cursespp/cursespp/NumberValidator.h
Normal file
85
src/musikcube/cursespp/cursespp/NumberValidator.h
Normal file
@ -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 */
|
||||
}
|
||||
|
88
src/musikcube/cursespp/cursespp/SchemaOverlay.h
Normal file
88
src/musikcube/cursespp/cursespp/SchemaOverlay.h
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
|
217
src/musikcube/data/locales/zh_CN.json
Normal file
217
src/musikcube/data/locales/zh_CN.json
Normal file
@ -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
Loading…
x
Reference in New Issue
Block a user